diff --git a/RELEASING.md b/RELEASING.md index e749d2d..8dcb7f5 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -116,9 +116,16 @@ python3 tools/screenshots.py That rewrites all five in `fastlane/metadata/android/en-US/images/phoneScreenshots/` at 1170×2532. They -are rendered from `public_html/index.html` with headless Chromium, not taken on -a phone, so they reproduce exactly — anything that changes the look of the app -is a reason to re-run it. +are rendered from `public_html/index.html` with headless Chromium rather than +taken on a phone, so anything that changes the look of the app is a reason to +re-run it. + +**Near-exact, not exact.** Two runs of identical code differ by a few bytes in +shots 1 and 4: a 4×13 pixel sliver at the delay bar's fill edge, where the +`scaleX` boundary lands mid-pixel and the rasteriser rounds it differently from +one run to the next. Max channel difference 11, invisible. So a `git diff` after +re-running is not evidence that anything changed — compare the pixels before +believing it, and `git checkout` the files back if that sliver is all it is. The script sets each scene by seeding `localStorage` before the app boots, so the app renders its own saved state rather than having the DOM poked from diff --git a/public_html/index.html b/public_html/index.html index 362cd1e..e71940e 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -209,26 +209,73 @@ .btn--vacant{visibility:hidden} .btn:focus-visible{outline:2px solid var(--icon-hot);outline-offset:-6px;border-radius:8px} - /* reset armed for a second tap — the ring unwinds over the same 2s as the timeout */ + /* Reset confirms two different ways, because the two input methods can't share + one. A finger holds the button and the ring fills under it; lifting early + springs the ring back, which is the only way to say "keep going" without + words — and the bar carries no words by design, since it is shared by two + players sitting opposite each other. Keyboard and assistive activation + can't hold, so they keep the older two-presses-within-2s, where the ring + drains to show the window closing. */ #reset{position:relative} - #reset.armed{color:var(--icon-hot)} + #reset.armed,#reset.holding{color:var(--icon-hot)} #reset .ring{ position:absolute; top:50%;left:50%; - width:clamp(36px,9.6vw,48px);height:clamp(36px,9.6vw,48px); + /* As big as the bar will take, so a thumb can't cover it: the button is the + bar's full height, leaving 4px of dark above and below. width:auto is + load-bearing — .btn svg sets a width for the icons, and inheriting it here + would give the ring that width against this height and draw an ellipse. + It ends up a few px wider than the button's own slot, which is only ever + empty space; the neighbouring glyph stays a good 12px clear. */ + height:calc(100% - 8px); + width:auto; + aspect-ratio:1; transform:translate(-50%,-50%) rotate(-90deg); /* start the arc at 12 o'clock */ - display:none; + opacity:0; + /* Hiding waits out the spring-back, then fades. display:none here would cut + the retreat off at the instant the finger lifts, and that retreat is the + whole reason the gesture teaches itself. The 120ms is a real duration on + purpose: a 0s transition with a delay is allowed to be treated as no + transition at all, which would take the delay with it. */ + transition:opacity 120ms linear 180ms; pointer-events:none; } - #reset.armed .ring{display:block} + /* appearing is instant — no duration, no delay */ + #reset.armed .ring,#reset.holding .ring{opacity:1;transition:none} + #reset .ring circle{ fill:none; - stroke:currentColor; /* the same flash the buttons use */ - stroke-width:2.6; + /* user units, so it scales with the ring: 2 of 40 lands between 2.5 and + 3.4 real px across phone sizes, a shade heavier than the icon strokes + because this one has to read past a thumb */ + stroke-width:2; + } + /* the whole circle, dim, there from the moment the finger lands: it shows how + far there is to go, which an arc growing out of nothing cannot */ + #reset .ring-track{stroke:var(--icon)} + /* and the bright arc that covers it, from 12 o'clock clockwise */ + #reset .ring-fill{ + stroke:var(--icon-hot); stroke-linecap:round; stroke-dasharray:113.1; /* 2πr, r=18 */ - animation:unwind 2s linear forwards; + stroke-dashoffset:113.1; /* empty */ + /* The spring back. 180ms is the ceiling, not the figure: reversing a + transition part-way shortens it in proportion, so letting go at 41% takes + 74ms, and the arc never spends longer retreating than it spent filling. + The dim track outlasts it either way — the ring holds for 180ms and then + fades, so even a stray tap shows a whole circle appear and go, which is + what says there was something to finish. */ + transition:stroke-dashoffset 180ms ease-out; } + #reset.holding .ring-fill{ + stroke-dashoffset:0; + transition-duration:975ms; /* keep in step with HOLD_MS */ + transition-timing-function:linear; + } + /* the keyboard path: a full ring draining over the 2s window. An animation + beats a transition on the same property, which is fine here only because + .armed and .holding are never set at once. */ + #reset.armed .ring-fill{animation:unwind 2s linear forwards} @keyframes unwind{ from{stroke-dashoffset:0} to{stroke-dashoffset:113.1} @@ -362,6 +409,13 @@ @media (prefers-reduced-motion:reduce){ *{transition-duration:.01ms!important} + /* ...except the reset ring, which reports how far through the hold you are. + Collapsing it would fill the ring the instant you touched the button and + tell you it was done 650ms before it was. This one is state, not + decoration. Higher specificity than the * above, so it wins despite both + being !important. */ + #reset .ring-fill{transition-duration:180ms!important} + #reset.holding .ring-fill{transition-duration:975ms!important} } @@ -387,8 +441,12 @@
-