Spaces:
Running
Running
One page scroll bar and American English
Browse files- app.js +30 -10
- app.py +2 -2
- index.html +68 -3
- onescroll.html +62 -0
- style.css +30 -7
app.js
CHANGED
|
@@ -175,21 +175,23 @@ function kitCommand(row) {
|
|
| 175 |
function setCard(r) {
|
| 176 |
$('c-kicker').textContent = `${r.scroll}${r.volume ? ' · VOLUME ' + r.volume : ''}${r.voxel_um ? ' · ' + r.voxel_um + ' µm' : ''}`;
|
| 177 |
$('c-title').textContent = r.segment + (r.kind !== 'mesh' ? ' (' + r.mesh + ')' : '');
|
| 178 |
-
|
|
|
|
|
|
|
|
|
|
| 179 |
['area', r.area_cm2 == null ? '-' : fmt(r.area_cm2) + ' cm²'],
|
| 180 |
['grid', `${r.grid_w} x ${r.grid_h}`],
|
| 181 |
['filled', pct(r.filled_share)],
|
| 182 |
-
['edges within 10%
|
| 183 |
['median edge / step', fmt(r.edge_p50, 3)],
|
| 184 |
-
['grid angle
|
| 185 |
-
['holes', `${fmt(r.holes_mm2)} mm² (${(r.holes_nodes || 0).toLocaleString()} nodes)`],
|
| 186 |
-
['folds', `${fmt(r.folds_mm2)} mm² (${(r.folds_nodes || 0).toLocaleString()} nodes)`],
|
| 187 |
-
['stretched', `${fmt(r.stretched_mm2)} mm² (${(r.stretched_nodes || 0).toLocaleString()} nodes)`],
|
| 188 |
['area vs meta.json', r.meta_area_ratio == null ? '-' : fmt(r.meta_area_ratio, 4)],
|
| 189 |
-
]
|
| 190 |
-
|
|
|
|
|
|
|
| 191 |
$('a-bucket').href = r.url + '/meta.json';
|
| 192 |
-
$('a-bucket').textContent = 'meta.json
|
| 193 |
}
|
| 194 |
|
| 195 |
function esc(s) {
|
|
@@ -256,7 +258,24 @@ $('b-copy').addEventListener('click', async () => {
|
|
| 256 |
try { await navigator.clipboard.writeText(kitCommand(current)); toast('kit command copied'); }
|
| 257 |
catch { toast(kitCommand(current)); }
|
| 258 |
});
|
| 259 |
-
$('b-about').addEventListener('click', () => $('about').showModal());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
|
| 261 |
// ---------------------------------------------------------------- table
|
| 262 |
|
|
@@ -296,6 +315,7 @@ $('tbl').addEventListener('click', (e) => {
|
|
| 296 |
function setMode(m) {
|
| 297 |
document.querySelectorAll('.chip[data-mode]').forEach((b) => b.classList.toggle('on', b.dataset.mode === m));
|
| 298 |
$('table-view').hidden = m !== 'table';
|
|
|
|
| 299 |
if (m === 'table') fillTable();
|
| 300 |
}
|
| 301 |
document.querySelectorAll('.chip[data-mode]').forEach((b) => b.addEventListener('click', () => setMode(b.dataset.mode)));
|
|
|
|
| 175 |
function setCard(r) {
|
| 176 |
$('c-kicker').textContent = `${r.scroll}${r.volume ? ' · VOLUME ' + r.volume : ''}${r.voxel_um ? ' · ' + r.voxel_um + ' µm' : ''}`;
|
| 177 |
$('c-title').textContent = r.segment + (r.kind !== 'mesh' ? ' (' + r.mesh + ')' : '');
|
| 178 |
+
// one line per number, so the card fits beside the list without a scroll bar of its own
|
| 179 |
+
const dd = (v, cls = '') => `<dd${cls ? ` class="${cls}"` : ''}>${esc(String(v))}</dd>`;
|
| 180 |
+
const flag = (k, mm2, nodes) => `<dt>${k}</dt>${dd(fmt(mm2), 'v')}${dd((nodes || 0).toLocaleString(), 'u')}`;
|
| 181 |
+
$('c-nums').innerHTML = r.error ? `<dt>not measured</dt>${dd(r.error, 'wrap')}` : [
|
| 182 |
['area', r.area_cm2 == null ? '-' : fmt(r.area_cm2) + ' cm²'],
|
| 183 |
['grid', `${r.grid_w} x ${r.grid_h}`],
|
| 184 |
['filled', pct(r.filled_share)],
|
| 185 |
+
['edges within 10%', pct(r.within_10pct)],
|
| 186 |
['median edge / step', fmt(r.edge_p50, 3)],
|
| 187 |
+
['median grid angle', fmt(r.angle_p50, 1) + '°'],
|
|
|
|
|
|
|
|
|
|
| 188 |
['area vs meta.json', r.meta_area_ratio == null ? '-' : fmt(r.meta_area_ratio, 4)],
|
| 189 |
+
].map(([k, v]) => `<dt>${k}</dt>${dd(v)}`).join('') +
|
| 190 |
+
'<dt class="hd">flagged</dt><dd class="v hd">mm²</dd><dd class="u hd">nodes</dd>' +
|
| 191 |
+
flag('holes', r.holes_mm2, r.holes_nodes) + flag('folds', r.folds_mm2, r.folds_nodes) +
|
| 192 |
+
flag('stretched', r.stretched_mm2, r.stretched_nodes);
|
| 193 |
$('a-bucket').href = r.url + '/meta.json';
|
| 194 |
+
$('a-bucket').textContent = 'open meta.json';
|
| 195 |
}
|
| 196 |
|
| 197 |
function esc(s) {
|
|
|
|
| 258 |
try { await navigator.clipboard.writeText(kitCommand(current)); toast('kit command copied'); }
|
| 259 |
catch { toast(kitCommand(current)); }
|
| 260 |
});
|
| 261 |
+
$('b-about').addEventListener('click', () => { $('about').showModal(); cover(); });
|
| 262 |
+
$('about').addEventListener('close', cover);
|
| 263 |
+
|
| 264 |
+
// One scroll bar at a time (one-screen viewer): the segment list scrolls, unless the table or the guide is open,
|
| 265 |
+
// which then scrolls instead. The panels under the table step aside; under the guide the list stops scrolling, and
|
| 266 |
+
// so does the table when the guide is opened over it.
|
| 267 |
+
function cover() {
|
| 268 |
+
const table = !$('table-view').hidden, guide = $('about').open;
|
| 269 |
+
$('picker').hidden = $('card').hidden = table;
|
| 270 |
+
document.documentElement.classList.toggle('covered', table || guide);
|
| 271 |
+
document.documentElement.classList.toggle('guide', guide);
|
| 272 |
+
}
|
| 273 |
+
// On a phone the picker sits above the card and takes the height the card leaves (style.css, --card-h).
|
| 274 |
+
if (window.ResizeObserver) {
|
| 275 |
+
new ResizeObserver(() => document.documentElement.style.setProperty('--card-h', $('card').offsetHeight + 'px'))
|
| 276 |
+
.observe($('card'));
|
| 277 |
+
}
|
| 278 |
+
if (window.matchMedia && matchMedia('(max-width: 760px), (max-height: 560px)').matches) $('c-more').open = false;
|
| 279 |
|
| 280 |
// ---------------------------------------------------------------- table
|
| 281 |
|
|
|
|
| 315 |
function setMode(m) {
|
| 316 |
document.querySelectorAll('.chip[data-mode]').forEach((b) => b.classList.toggle('on', b.dataset.mode === m));
|
| 317 |
$('table-view').hidden = m !== 'table';
|
| 318 |
+
cover();
|
| 319 |
if (m === 'table') fillTable();
|
| 320 |
}
|
| 321 |
document.querySelectorAll('.chip[data-mode]').forEach((b) => b.addEventListener('click', () => setMode(b.dataset.mode)));
|
app.py
CHANGED
|
@@ -33,7 +33,7 @@ LENS_CACHE = 32
|
|
| 33 |
SORTS = ("area_cm2", "filled_share", "within_10pct", "holes_mm2", "folds_mm2", "stretched_mm2", "grid_h", "segment")
|
| 34 |
# the page's colors (app.js COLORS), as 0-255 RGB: plain, hole, fold, stretched
|
| 35 |
COLORS = {"base": (204, 214, 219), "hole": (89, 166, 255), "fold": (255, 92, 92), "str": (255, 166, 51)}
|
| 36 |
-
WEB_FILES = ("index.html", "app.js", "style.css", "connect.html", "connect.json", "CONNECT.md")
|
| 37 |
SUMMARY_KEYS = ("id", "scroll", "segment", "mesh", "kind", "volume", "voxel_um", "area_cm2", "filled_share",
|
| 38 |
"within_10pct", "holes_mm2", "folds_mm2", "stretched_mm2", "clean", "prize_lists")
|
| 39 |
|
|
@@ -288,7 +288,7 @@ def get_segment(segment_id: str):
|
|
| 288 |
|
| 289 |
def segment_picture(segment_id: str, view: str = "3d", azimuth: float = 30.0, elevation: float = 20.0):
|
| 290 |
"""A picture of one mesh's preview with its flags painted on, as on the page: holes blue, folds red,
|
| 291 |
-
stretched cells orange, plain
|
| 292 |
stays on one sheet. Only meshes with a preview (scrolls on no prize list) can be drawn.
|
| 293 |
|
| 294 |
Args:
|
|
|
|
| 33 |
SORTS = ("area_cm2", "filled_share", "within_10pct", "holes_mm2", "folds_mm2", "stretched_mm2", "grid_h", "segment")
|
| 34 |
# the page's colors (app.js COLORS), as 0-255 RGB: plain, hole, fold, stretched
|
| 35 |
COLORS = {"base": (204, 214, 219), "hole": (89, 166, 255), "fold": (255, 92, 92), "str": (255, 166, 51)}
|
| 36 |
+
WEB_FILES = ("index.html", "app.js", "style.css", "connect.html", "connect.json", "CONNECT.md", "onescroll.html")
|
| 37 |
SUMMARY_KEYS = ("id", "scroll", "segment", "mesh", "kind", "volume", "voxel_um", "area_cm2", "filled_share",
|
| 38 |
"within_10pct", "holes_mm2", "folds_mm2", "stretched_mm2", "clean", "prize_lists")
|
| 39 |
|
|
|
|
| 288 |
|
| 289 |
def segment_picture(segment_id: str, view: str = "3d", azimuth: float = 30.0, elevation: float = 20.0):
|
| 290 |
"""A picture of one mesh's preview with its flags painted on, as on the page: holes blue, folds red,
|
| 291 |
+
stretched cells orange, plain gray. Geometry only: no CT, no ink, and a clean surface is not proof the trace
|
| 292 |
stays on one sheet. Only meshes with a preview (scrolls on no prize list) can be drawn.
|
| 293 |
|
| 294 |
Args:
|
index.html
CHANGED
|
@@ -11,7 +11,69 @@
|
|
| 11 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 12 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 13 |
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 14 |
-
<link rel="stylesheet" href="style.css?v=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
<script type="importmap">
|
| 16 |
{ "imports": {
|
| 17 |
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
|
|
@@ -47,7 +109,10 @@
|
|
| 47 |
<aside class="hud br panel" id="card" aria-live="polite">
|
| 48 |
<div class="mono small dim" id="c-kicker">SELECT A SEGMENT</div>
|
| 49 |
<h2 id="c-title">…</h2>
|
| 50 |
-
<
|
|
|
|
|
|
|
|
|
|
| 51 |
<div class="legend mono small">
|
| 52 |
<label><input type="checkbox" id="t-hole" checked> <span class="sw hole"></span>holes</label>
|
| 53 |
<label><input type="checkbox" id="t-fold" checked> <span class="sw fold"></span>folds</label>
|
|
@@ -174,6 +239,6 @@ python -m kit tifxyz check MESH_URL --png check.png</pre>
|
|
| 174 |
</dialog>
|
| 175 |
|
| 176 |
<div id="toast" class="toast mono small" role="status" hidden></div>
|
| 177 |
-
<script type="module" src="app.js?v=
|
| 178 |
</body>
|
| 179 |
</html>
|
|
|
|
| 11 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 12 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 13 |
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 14 |
+
<link rel="stylesheet" href="style.css?v=2">
|
| 15 |
+
<!-- space-onescroll:start -->
|
| 16 |
+
<!-- one page scroll bar: written by python -m kit spaceconnect render; do not edit -->
|
| 17 |
+
<style>iframe[srcdoc]{display:block;max-height:none!important}</style>
|
| 18 |
+
<script>
|
| 19 |
+
(function () {
|
| 20 |
+
if (window.__oneScroll) return;
|
| 21 |
+
window.__oneScroll = true;
|
| 22 |
+
var CSS = 'html,body{height:auto!important;min-height:0!important}html{overflow:hidden!important}';
|
| 23 |
+
function size(f) {
|
| 24 |
+
var d, st = f.__os;
|
| 25 |
+
try { d = f.contentDocument; } catch (e) { return; }
|
| 26 |
+
if (!d || !d.body || st.off) return;
|
| 27 |
+
var de = d.documentElement, r = Math.ceil(de.getBoundingClientRect().height), over = de.scrollHeight;
|
| 28 |
+
var ch = f.clientHeight, t = r;
|
| 29 |
+
if (over > ch + 1) t = Math.max(r, over);
|
| 30 |
+
else if (r >= st.r) t = Math.max(r, ch);
|
| 31 |
+
st.r = r;
|
| 32 |
+
if (Math.abs(t - ch) < 2) return;
|
| 33 |
+
var now = Date.now();
|
| 34 |
+
st.n = now - st.t0 < 4000 ? st.n + 1 : 1;
|
| 35 |
+
if (st.n === 1) st.t0 = now;
|
| 36 |
+
if (st.n > 40) { st.off = true; f.removeAttribute('scrolling'); var g = d.getElementById('space-onescroll'); if (g) g.remove(); return; }
|
| 37 |
+
f.style.setProperty('height', t + 'px', 'important');
|
| 38 |
+
}
|
| 39 |
+
function attach(f) {
|
| 40 |
+
var d, st = f.__os;
|
| 41 |
+
try { d = f.contentDocument; } catch (e) { return; }
|
| 42 |
+
if (!d || !d.documentElement || st.doc === d) return;
|
| 43 |
+
st.doc = d; st.r = Infinity; st.n = 0; st.off = false;
|
| 44 |
+
if (!d.getElementById('space-onescroll')) {
|
| 45 |
+
var s = d.createElement('style'); s.id = 'space-onescroll'; s.textContent = CSS;
|
| 46 |
+
(d.head || d.documentElement).appendChild(s);
|
| 47 |
+
}
|
| 48 |
+
f.setAttribute('scrolling', 'no');
|
| 49 |
+
f.style.setProperty('min-height', '0', 'important');
|
| 50 |
+
f.style.setProperty('max-height', 'none', 'important');
|
| 51 |
+
var w = d.defaultView, go = function () { size(f); };
|
| 52 |
+
if (w && w.ResizeObserver) { var ro = new w.ResizeObserver(go); ro.observe(d.documentElement); if (d.body) ro.observe(d.body); }
|
| 53 |
+
d.addEventListener('load', go, true);
|
| 54 |
+
go();
|
| 55 |
+
}
|
| 56 |
+
function bind(f) {
|
| 57 |
+
if (f.__os || f.getAttribute('data-scroll') === 'inner') return;
|
| 58 |
+
try { if (!f.contentDocument) return; } catch (e) { return; }
|
| 59 |
+
f.__os = {t0: 0, n: 0, r: 0};
|
| 60 |
+
f.addEventListener('load', function () { attach(f); });
|
| 61 |
+
if (f.contentDocument.readyState !== 'loading') attach(f);
|
| 62 |
+
else f.contentDocument.addEventListener('DOMContentLoaded', function () { attach(f); });
|
| 63 |
+
}
|
| 64 |
+
function scan() { var fs = document.getElementsByTagName('iframe'); for (var i = 0; i < fs.length; i++) bind(fs[i]); }
|
| 65 |
+
function start() {
|
| 66 |
+
scan();
|
| 67 |
+
new MutationObserver(scan).observe(document.documentElement, {childList: true, subtree: true});
|
| 68 |
+
window.addEventListener('resize', function () {
|
| 69 |
+
var fs = document.getElementsByTagName('iframe');
|
| 70 |
+
for (var i = 0; i < fs.length; i++) if (fs[i].__os) size(fs[i]);
|
| 71 |
+
});
|
| 72 |
+
}
|
| 73 |
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start();
|
| 74 |
+
})();
|
| 75 |
+
</script>
|
| 76 |
+
<!-- space-onescroll:end -->
|
| 77 |
<script type="importmap">
|
| 78 |
{ "imports": {
|
| 79 |
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
|
|
|
|
| 109 |
<aside class="hud br panel" id="card" aria-live="polite">
|
| 110 |
<div class="mono small dim" id="c-kicker">SELECT A SEGMENT</div>
|
| 111 |
<h2 id="c-title">…</h2>
|
| 112 |
+
<details id="c-more" class="more" open>
|
| 113 |
+
<summary class="mono dim">NUMBERS</summary>
|
| 114 |
+
<dl id="c-nums" class="nums mono small"></dl>
|
| 115 |
+
</details>
|
| 116 |
<div class="legend mono small">
|
| 117 |
<label><input type="checkbox" id="t-hole" checked> <span class="sw hole"></span>holes</label>
|
| 118 |
<label><input type="checkbox" id="t-fold" checked> <span class="sw fold"></span>folds</label>
|
|
|
|
| 239 |
</dialog>
|
| 240 |
|
| 241 |
<div id="toast" class="toast mono small" role="status" hidden></div>
|
| 242 |
+
<script type="module" src="app.js?v=2"></script>
|
| 243 |
</body>
|
| 244 |
</html>
|
onescroll.html
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- space-onescroll:start -->
|
| 2 |
+
<!-- one page scroll bar: written by python -m kit spaceconnect render; do not edit -->
|
| 3 |
+
<style>iframe[srcdoc]{display:block;max-height:none!important}</style>
|
| 4 |
+
<script>
|
| 5 |
+
(function () {
|
| 6 |
+
if (window.__oneScroll) return;
|
| 7 |
+
window.__oneScroll = true;
|
| 8 |
+
var CSS = 'html,body{height:auto!important;min-height:0!important}html{overflow:hidden!important}';
|
| 9 |
+
function size(f) {
|
| 10 |
+
var d, st = f.__os;
|
| 11 |
+
try { d = f.contentDocument; } catch (e) { return; }
|
| 12 |
+
if (!d || !d.body || st.off) return;
|
| 13 |
+
var de = d.documentElement, r = Math.ceil(de.getBoundingClientRect().height), over = de.scrollHeight;
|
| 14 |
+
var ch = f.clientHeight, t = r;
|
| 15 |
+
if (over > ch + 1) t = Math.max(r, over);
|
| 16 |
+
else if (r >= st.r) t = Math.max(r, ch);
|
| 17 |
+
st.r = r;
|
| 18 |
+
if (Math.abs(t - ch) < 2) return;
|
| 19 |
+
var now = Date.now();
|
| 20 |
+
st.n = now - st.t0 < 4000 ? st.n + 1 : 1;
|
| 21 |
+
if (st.n === 1) st.t0 = now;
|
| 22 |
+
if (st.n > 40) { st.off = true; f.removeAttribute('scrolling'); var g = d.getElementById('space-onescroll'); if (g) g.remove(); return; }
|
| 23 |
+
f.style.setProperty('height', t + 'px', 'important');
|
| 24 |
+
}
|
| 25 |
+
function attach(f) {
|
| 26 |
+
var d, st = f.__os;
|
| 27 |
+
try { d = f.contentDocument; } catch (e) { return; }
|
| 28 |
+
if (!d || !d.documentElement || st.doc === d) return;
|
| 29 |
+
st.doc = d; st.r = Infinity; st.n = 0; st.off = false;
|
| 30 |
+
if (!d.getElementById('space-onescroll')) {
|
| 31 |
+
var s = d.createElement('style'); s.id = 'space-onescroll'; s.textContent = CSS;
|
| 32 |
+
(d.head || d.documentElement).appendChild(s);
|
| 33 |
+
}
|
| 34 |
+
f.setAttribute('scrolling', 'no');
|
| 35 |
+
f.style.setProperty('min-height', '0', 'important');
|
| 36 |
+
f.style.setProperty('max-height', 'none', 'important');
|
| 37 |
+
var w = d.defaultView, go = function () { size(f); };
|
| 38 |
+
if (w && w.ResizeObserver) { var ro = new w.ResizeObserver(go); ro.observe(d.documentElement); if (d.body) ro.observe(d.body); }
|
| 39 |
+
d.addEventListener('load', go, true);
|
| 40 |
+
go();
|
| 41 |
+
}
|
| 42 |
+
function bind(f) {
|
| 43 |
+
if (f.__os || f.getAttribute('data-scroll') === 'inner') return;
|
| 44 |
+
try { if (!f.contentDocument) return; } catch (e) { return; }
|
| 45 |
+
f.__os = {t0: 0, n: 0, r: 0};
|
| 46 |
+
f.addEventListener('load', function () { attach(f); });
|
| 47 |
+
if (f.contentDocument.readyState !== 'loading') attach(f);
|
| 48 |
+
else f.contentDocument.addEventListener('DOMContentLoaded', function () { attach(f); });
|
| 49 |
+
}
|
| 50 |
+
function scan() { var fs = document.getElementsByTagName('iframe'); for (var i = 0; i < fs.length; i++) bind(fs[i]); }
|
| 51 |
+
function start() {
|
| 52 |
+
scan();
|
| 53 |
+
new MutationObserver(scan).observe(document.documentElement, {childList: true, subtree: true});
|
| 54 |
+
window.addEventListener('resize', function () {
|
| 55 |
+
var fs = document.getElementsByTagName('iframe');
|
| 56 |
+
for (var i = 0; i < fs.length; i++) if (fs[i].__os) size(fs[i]);
|
| 57 |
+
});
|
| 58 |
+
}
|
| 59 |
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start();
|
| 60 |
+
})();
|
| 61 |
+
</script>
|
| 62 |
+
<!-- space-onescroll:end -->
|
style.css
CHANGED
|
@@ -30,7 +30,9 @@ a { color: var(--accent); }
|
|
| 30 |
.tl a { pointer-events: auto; }
|
| 31 |
.tr { z-index: 5; top: var(--gut); right: var(--gut); display: flex; gap: 8px; align-items: center; }
|
| 32 |
.bl { left: var(--gut); bottom: var(--gut); width: 300px; max-height: 52vh; }
|
| 33 |
-
|
|
|
|
|
|
|
| 34 |
.kicker { font-size: 10.5px; letter-spacing: .14em; }
|
| 35 |
h1 { font-family: var(--serif); font-weight: 400; font-size: clamp(34px, 5vw, 54px); margin: .15em 0 .1em; line-height: 1; }
|
| 36 |
h1 em, h2 em { color: var(--accent); }
|
|
@@ -52,10 +54,15 @@ select, input[type=search] { background: #0a1217; color: var(--fg); border: 1px
|
|
| 52 |
.chip.on { background: var(--fg); color: var(--bg); }
|
| 53 |
.seg { display: flex; gap: 4px; }
|
| 54 |
h2 { font-family: var(--serif); font-weight: 400; font-size: 24px; margin: .2em 0 .4em; word-break: break-word; }
|
| 55 |
-
.
|
|
|
|
| 56 |
.nums dt { color: var(--dim); }
|
| 57 |
-
.nums dd { margin: 0; text-align: right; }
|
| 58 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
.legend label { display: flex; align-items: center; gap: 5px; cursor: pointer; }
|
| 60 |
.sw { display: inline-block; width: 10px; height: 10px; border-radius: 2px; }
|
| 61 |
.sw.hole { background: var(--hole); } .sw.fold { background: var(--fold); } .sw.str { background: var(--str); }
|
|
@@ -74,11 +81,27 @@ tbody tr.go { cursor: pointer; }
|
|
| 74 |
.sheet-dialog { background: #0a1217; color: var(--fg); border: 1px solid var(--line-strong); border-radius: 12px; max-width: min(680px, calc(100vw - 32px)); padding: 18px 22px; line-height: 1.55; font-size: 13px; }
|
| 75 |
.sheet-dialog::backdrop { background: rgba(0, 0, 0, .6); }
|
| 76 |
.sheet-dialog .x { float: right; background: none; color: var(--fg); border: 0; font-size: 22px; cursor: pointer; }
|
| 77 |
-
.sheet-dialog pre { background: #05080b; padding: 10px; border-radius: 6px; overflow: auto; }
|
| 78 |
.toast { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%); z-index: 6; background: var(--fg); color: var(--bg); padding: 8px 14px; border-radius: 999px; }
|
| 79 |
.loading { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 2; color: var(--dim); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
@media (max-width: 760px) {
|
| 81 |
.tl .lede { display: none; }
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
}
|
|
|
|
| 30 |
.tl a { pointer-events: auto; }
|
| 31 |
.tr { z-index: 5; top: var(--gut); right: var(--gut); display: flex; gap: 8px; align-items: center; }
|
| 32 |
.bl { left: var(--gut); bottom: var(--gut); width: 300px; max-height: 52vh; }
|
| 33 |
+
/* One-screen viewer: the segment list is the one box that scrolls. The card grows to fit and never scrolls; on a
|
| 34 |
+
window too short for it, its bottom is cut (NUMBERS folds it back), so its top stays on screen below the buttons. */
|
| 35 |
+
.br { right: var(--gut); bottom: var(--gut); width: 330px; max-height: calc(100vh - 2 * var(--gut) - 44px); overflow: hidden; overflow: clip; }
|
| 36 |
.kicker { font-size: 10.5px; letter-spacing: .14em; }
|
| 37 |
h1 { font-family: var(--serif); font-weight: 400; font-size: clamp(34px, 5vw, 54px); margin: .15em 0 .1em; line-height: 1; }
|
| 38 |
h1 em, h2 em { color: var(--accent); }
|
|
|
|
| 54 |
.chip.on { background: var(--fg); color: var(--bg); }
|
| 55 |
.seg { display: flex; gap: 4px; }
|
| 56 |
h2 { font-family: var(--serif); font-weight: 400; font-size: 24px; margin: .2em 0 .4em; word-break: break-word; }
|
| 57 |
+
.more > summary { cursor: pointer; font-size: 10.5px; letter-spacing: .14em; margin: 0 0 6px; }
|
| 58 |
+
.nums { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 3px 10px; margin: 0 0 8px; }
|
| 59 |
.nums dt { color: var(--dim); }
|
| 60 |
+
.nums dd { margin: 0; text-align: right; white-space: nowrap; grid-column: span 2; }
|
| 61 |
+
.nums dd.v, .nums dd.u { grid-column: auto; }
|
| 62 |
+
.nums dd.u { color: var(--dim); }
|
| 63 |
+
.nums dd.wrap { white-space: normal; }
|
| 64 |
+
.nums .hd { color: var(--dim); font-size: 10.5px; padding-top: 4px; }
|
| 65 |
+
.legend { display: flex; flex-wrap: wrap; gap: 6px 10px; margin: 8px 0; }
|
| 66 |
.legend label { display: flex; align-items: center; gap: 5px; cursor: pointer; }
|
| 67 |
.sw { display: inline-block; width: 10px; height: 10px; border-radius: 2px; }
|
| 68 |
.sw.hole { background: var(--hole); } .sw.fold { background: var(--fold); } .sw.str { background: var(--str); }
|
|
|
|
| 81 |
.sheet-dialog { background: #0a1217; color: var(--fg); border: 1px solid var(--line-strong); border-radius: 12px; max-width: min(680px, calc(100vw - 32px)); padding: 18px 22px; line-height: 1.55; font-size: 13px; }
|
| 82 |
.sheet-dialog::backdrop { background: rgba(0, 0, 0, .6); }
|
| 83 |
.sheet-dialog .x { float: right; background: none; color: var(--fg); border: 0; font-size: 22px; cursor: pointer; }
|
| 84 |
+
.sheet-dialog pre { background: #05080b; padding: 10px; border-radius: 6px; overflow-x: auto; }
|
| 85 |
.toast { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%); z-index: 6; background: var(--fg); color: var(--bg); padding: 8px 14px; border-radius: 999px; }
|
| 86 |
.loading { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 2; color: var(--dim); }
|
| 87 |
+
/* While the table or the guide is open it is the one box that scrolls: the panels under it step aside. */
|
| 88 |
+
html.covered .list { overflow: hidden; }
|
| 89 |
+
html.guide .tablewrap { overflow: hidden; }
|
| 90 |
+
@media (max-height: 640px) {
|
| 91 |
+
.br h2 { font-size: 19px; }
|
| 92 |
+
.nums { gap: 2px 10px; }
|
| 93 |
+
}
|
| 94 |
@media (max-width: 760px) {
|
| 95 |
.tl .lede { display: none; }
|
| 96 |
+
/* Stacked under the title: the picker, then the card. The card keeps its own height (app.js writes it to
|
| 97 |
+
--card-h) up to what leaves the picker 225px, room for one row of the list; the picker takes what is left. */
|
| 98 |
+
.bl { width: calc(100vw - 2 * var(--gut)); bottom: auto; top: calc(var(--gut) + 78px);
|
| 99 |
+
max-height: max(225px, min(40vh, calc(100vh - 78px - 3 * var(--gut) - var(--card-h, 34vh)))); }
|
| 100 |
+
.br { width: calc(100vw - 2 * var(--gut)); max-height: calc(100vh - 78px - 3 * var(--gut) - 225px); }
|
| 101 |
+
.list { min-height: 56px; }
|
| 102 |
+
}
|
| 103 |
+
@media (max-width: 760px) and (max-height: 500px) {
|
| 104 |
+
/* a phone on its side: the picker and the card side by side, as on a wide screen */
|
| 105 |
+
.bl { top: auto; bottom: var(--gut); width: calc(50vw - 1.5 * var(--gut)); max-height: calc(100vh - 78px - 3 * var(--gut)); }
|
| 106 |
+
.br { width: calc(50vw - 1.5 * var(--gut)); max-height: calc(100vh - 2 * var(--gut) - 44px); }
|
| 107 |
}
|