The last of the anti-slop interaction work (guidelines G6, G7, G14, G15,
G17), plus booth-dev's note from S5b's gate. Every S5b promise holds: no
re-POST, serialized saves, a batch never reloads, focus survives a swap.
- Keys (G6): one rule in base.html's <head>, BoothKeys.theirs(e), called
first by the grid, the review and compare. A field or a player owns every
key but Escape (Esc still goes back from a focused player); a control
owns Space; a focused 1:1 stage that can pan owns the
arrows and Space (Chromium puts it in the Tab order); Ctrl/Meta/Alt are
the browser's. The field check lives on as BoothKeys.isEditable. Before:
an arrow on a focused video left the review, and Enter on any control
also opened the grid cursor's tile.
- The grid cursor is real focus: the tile it moves to gets tabindex=-1
(script-set, one tile at a time) and focus, without a scroll; the cursor
is an item (its data-item), and a doc closed with its ✕ is skipped; focus that
lands on a tile (S5b's fallback) makes it the cursor; Enter opens the
review only from the body, the grid or the tile, by its view?f= link;
n opens a closed doc's fold; Escape clears the cursor
and releases the tile's focus. The reticle is its focus mark (no second
ring).
- The doc bar (G7): the controls leave the <summary>. div.doc-bar holds
details.doc-fold (its summary is the label only) and div.doc-tools beside
it; the body and notes follow in div.doc-inline, hidden with a closed
fold by :has(), scripts on or off. A closed doc keeps its tools. Renders
pixel-identical to today at 1280 and 390, light and dark.
- Tile sizes (G14): a gallery tile's <img> carries width/height, the
picture as the browser draws it (EXIF 5-8 swap), read from the header
only (no decode; PNG getexif is skipped unless the header carried it),
opened O_NOFOLLOW|O_NONBLOCK, cached by the file's identity (ctime
included, so cp -p over a file is seen), in a separate
step (items.image_dims over thumbs.drawn_size) so the Desk never pays it.
Measured before: a link to tile 30 of 40 landed 44px low (3/3); after, on
its mark. content-visibility:auto, which the report proposed too, is NOT
added: a swapped-in tile has no remembered size, and a flag far down moved
the page 2929px (3/3; 0px without it).
- The rail (G15): html:has(.rail){scroll-padding-top} replaces .item's
scroll-margin-top (the two add), so a control reached by Tab stops below
the sticky rail too. Measured before: a Tab-focused flag button at 19.6px,
under the rail's bottom at 47.6px. The scripts-off fallbacks are the old
rules' numbers (132px, 217px at <=480), now pinned by a test. The height
script follows the live rail after every in-place save (it watched the
replaced node, and read 0px after one flag), and the rail's own controls
cancel the padding (a Tab between stuck group links scrolled 357px).
- Reveal names (G17): no aria-label on any reveal control; the name is the
words on it, the glyph in an aria-hidden span, the item's name as
.sr-only text ("reveal a.png" / "hide a.png"). Reveal all drops
aria-pressed (its words already say the state; r2b rules them) and its
"on" look reads the .reveal-all class on <html>. No pixel changes.
- booth-dev's note: a refused batch's forms enter `unsent` with the
refusal's words, and a later save says every standing failure's words
(each once, in order) instead of "Saved.", and every warning says the
other standing failures first, so no failure buries another. Test first:
test_a_batch_refusal_outlives_an_unrelated_save.
- Rows re-anchored to the same failure: r2b "Space on a focused review
button", r3 "C3 a held modifier" and both "C3 Space on a focused ..."
(now in BoothKeys), r2c "the stage reveal shows with scripts off", and
this contract's S3 doc-bar row and five S5b status-line rows.
Folded from the heid contract review (BEINKA, panel 4/4, thread
01M3NZJNX8D3BEYD48M9K3MV3Q): 24 flags, all prose the tests left open; the
contract states the tile/focus/cursor seam with S5b, the helper's union and
scope, the size's source and every path to none, Reveal all's name, the
refusal sentence's lifetime, and the fallback arithmetic (one test added).
Folded from the heid bug-hunt (HRÖSKVA, panel 4/4, thread
01M3P0ZPRSASFSE5K3PR4NTQP6): R1 closed docs and the cursor as an item, R2
the rail's height after a save, R3 no warning buries another, R5 the view?f=
link, R7 ctime in the size cache, R8 Escape from a player, R9 the rail's own
controls, R10 n on a closed doc. Refuted with reasons: R4 (unreachable: refused
picks re-send together), R6 (Chrome takes the same header's size with or
without the attributes; measured), R11 (by design).
From this slice's own falsifier runs: a "one row wide" row that mutated a
flex basis a non-wrapping bar just shrinks (re-aimed at the bar's flex), and
a Reveal-all "on look" read under the clicking pointer, where :hover draws
the same border (the pointer now leaves first; 3/3 proved).
Contract: as_antislop S5c.
Falsifiers: antislop.toml S5c section.
80 lines
4.5 KiB
HTML
80 lines
4.5 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}{{ file }} · {{ name }} · The Booth{% endblock %}
|
|
{% block html_attrs %} data-booth="{{ name }}"{% endblock %}
|
|
{% block content %}
|
|
<div class="docview">
|
|
<div class="vbar">
|
|
<a class="vbtn vx" href="/b/{{ name_url }}/" title="back to gallery (Esc)" aria-label="back to the gallery">✕</a>
|
|
<span class="vname">{{ file }}</span>
|
|
<span class="vspacer"></span>
|
|
{# Reveal all can lift this page's blur, so this page must be able to put it
|
|
back (r2b, heid bug-hunt). #}
|
|
{% if blurred %}<button type="button" class="reveal-all-btn" data-reveal-all hidden title="blur is cosmetic — the files are still served"><span class="ra-label"><span class="ra-glyph" aria-hidden="true">👁</span> <span class="ra-word">reveal all</span></span><span class="ra-note"> — blur is cosmetic</span></button>{% endif %}
|
|
<a class="vbtn" href="{{ file_url }}?dl=1" title="download {{ file }}" aria-label="download {{ file }}">⬇</a>
|
|
</div>
|
|
{# Same record, same reason as the image viewer: the sidecar that says what
|
|
this doc IS travels with it to full-page view. #}
|
|
{% if caption %}<div class="doccap">{{ caption }}</div>{% endif %}
|
|
{% if marks %}
|
|
<div class="docmarks">
|
|
{% for m in marks if m.shape == 'note' %}<pre class="vnote">{{ m.text }}</pre>{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{# Blur honesty reaches the full page too (r2b, heid code-review): a blurred
|
|
doc's own page rendered clear. Its reveal is per-page and JS-only, like the
|
|
review stage's; Reveal all lifts it by the same <html> class. #}
|
|
<div class="docbody{% if blurred %} is-blurred{% endif %}" id="docbody">
|
|
{# as S5c (G17): the name is the words on it, the glyph hidden #}
|
|
{% if blurred %}<button type="button" class="reveal" id="docreveal" hidden><span class="rv-glyph" aria-hidden="true">👁</span> <span class="rv-word">reveal</span><span class="sr-only"> {{ file }}</span><span class="rv-note"> — blur is cosmetic</span></button>{% endif %}
|
|
{% if is_html %}
|
|
<article class="markdown-body">{{ body|safe }}</article>
|
|
{% else %}
|
|
<pre class="textview">{{ body }}</pre>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<style>
|
|
/* .markdown-body and .textview live in base.html (shared with the inline
|
|
gallery view). Only the full-page layout wrapper is page-specific. */
|
|
.docview{max-width:52rem;margin:0 auto;padding:0 clamp(12px,3vw,20px) 64px}
|
|
.docview .vbar{margin:0 calc(-1 * clamp(12px,3vw,20px)) 20px;border-radius:0}
|
|
.docview .textview{overflow-x:auto}
|
|
.doccap{margin:0 0 20px;padding:8px 14px;font-size:var(--size-body);line-height:var(--leading-body);
|
|
color:var(--text-body);border-left:3px solid var(--border-strong);white-space:pre-wrap}
|
|
.docmarks{display:flex;flex-direction:column;gap:8px;margin:0 0 20px}
|
|
.docbody{position:relative}
|
|
.docbody.is-blurred .markdown-body,.docbody.is-blurred .textview{filter:blur(22px);transition:filter var(--dur-2)}
|
|
.docbody.is-blurred.revealed .markdown-body,.docbody.is-blurred.revealed .textview,
|
|
.reveal-all .docbody.is-blurred .markdown-body,.reveal-all .docbody.is-blurred .textview{filter:none}
|
|
.reveal-all #docreveal{display:none}
|
|
#docreveal{position:absolute;top:10px;left:10px;z-index:2;cursor:pointer;font-family:var(--font-mono);
|
|
font-size:var(--size-micro);line-height:1;padding:6px 9px;border-radius:var(--radius-md);
|
|
border:1px solid rgb(255 255 255 / .16);background:oklch(0.17 0.01 250 / .86);color:oklch(0.91 0.008 216)}
|
|
</style>
|
|
<script>
|
|
(function () {
|
|
/* Escape leaves the page, so it must not fire from inside a field someone
|
|
is typing in — the same guard the image viewer carries, stated in both
|
|
places because the handler is on `document` in both. */
|
|
function isEditable(el) {
|
|
return !!(el && (el.isContentEditable ||
|
|
/^(input|textarea|select)$/i.test(el.tagName || '')));
|
|
}
|
|
var rv = document.getElementById('docreveal');
|
|
if (rv) {
|
|
rv.hidden = false;
|
|
rv.addEventListener('click', function () {
|
|
var on = document.getElementById('docbody').classList.toggle('revealed');
|
|
rv.querySelector('.rv-glyph').textContent = on ? '🙈' : '👁';
|
|
rv.querySelector('.rv-word').textContent = on ? 'hide' : 'reveal';
|
|
rv.querySelector('.rv-note').hidden = on;
|
|
});
|
|
}
|
|
document.addEventListener('keydown', function (e) {
|
|
if (isEditable(e.target)) return;
|
|
if (e.key === 'Escape') window.location.href = {{ ('/b/' ~ name_url ~ '/')|tojson }};
|
|
});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|