game room title: GAME ROOM ⇄ GAME SCROLL reel on the scroll aperture — TDD
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed

The h2's second slot becomes a two-word vertical reel: GAME stays put, ROOM rests in view, SCROLL is parked one notch below in the slot's bottom fade. room-scroll.js toggles `.is-scroll` on the h2 from the SAME IntersectionObserver that already watches the table-hex aperture's scroll pane — ROOM slides up & out under the navbar line while SCROLL rises out of the page-aperture gradient (reverses on scroll-up). Table-phase only; the gate phase stays a plain GAME ROOM.

One translateY drives both orientations. Portrait: the word is a short horizontal row in a short slot. Landscape: writing-mode: vertical-rl (inherited from the rotated gutter wordmark) makes the word a tall letter-column, so the same translateY slides it ALONG the wordmark — the user-chosen landscape behaviour for free. Landscape uses a shallower --gr-fade + a letter inset so the space-between end-letters parked at the slot edges aren't dimmed by the dissolve.

Motion is deliberately old & rusty: a single cubic-bezier can overshoot at most once and can't oscillate, so the easing is a CSS linear() curve — stall against the grime, jerk free, clunk PAST the mark, then a damped end-wobble into place. Exposed as --gr-ease / --gr-dur / --gr-fade knobs on .gr-swap.

base.html's letter-splitter now also splits the two .gr-word words; the .gr-swap window ships data-letters-split="1" so the splitter skips it (no 'roomscroll' run). Reel SCSS is scoped to .gr-swap/.gr-word; `> span.gr-swap` ties `> span:last-child` at (0,4,3) and wins on later source order [[feedback-scss-import-order-specificity]].

TRAP: libsass does NOT strip `//` comments INSIDE a CSS custom-property value — they leak into the compiled output, making the linear() (hence the whole `transition` shorthand) invalid-at-computed-value-time, which silently resets to 0s/ease (no animation). Keep every annotation OUTSIDE the linear(). [[feedback-libsass-comment-in-custom-property]]

Reusable .gr-swap seam: my_sea gets GAME SEA → GAME SCROLL via a one-line header swap once its sea-scroll pane is built (deferred — the sea scroll doesn't exist yet).

Tests: 2 ITs (RoomScrollOfEventsTest) — reel markup renders in the table phase, stays plain in the gate phase; 1 FT (test_scroll_swaps_room_title_to_scroll) — scrolling the aperture toggles GAME ROOM ⇄ GAME SCROLL both ways. collectstatic'd room-scroll.js for the FT [[feedback-collectstatic-before-ft]].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-06-02 01:05:00 -04:00
parent 114f0fd0db
commit cf1965c439
6 changed files with 187 additions and 6 deletions

View File

@@ -128,6 +128,39 @@ class RoomScrollOfEventsTest(FunctionalTest):
By.CSS_SELECTOR, "#id_room_menu .room-menu-default a.btn-cancel"
).is_displayed())
def test_scroll_swaps_room_title_to_scroll(self):
"""Scrolling the aperture to the feed advances the page-title reel
GAME ROOM → GAME SCROLL (the <h2> gains `.is-scroll`, which the reel
CSS uses to slide the words); scrolling back to the hex reverses it."""
self._open()
# Both reel words ship in the header; the window starts at the hex.
self.assertTrue(
self.browser.find_elements(By.CSS_SELECTOR, ".gr-word--base"))
self.assertTrue(
self.browser.find_elements(By.CSS_SELECTOR, ".gr-word--scroll"))
h2 = self.browser.find_element(By.CSS_SELECTOR, ".row .col-lg-6 h2")
self.assertNotIn("is-scroll", h2.get_attribute("class") or "")
# Scroll down to the feed → the reel advances to GAME SCROLL.
self.browser.execute_script(
"document.querySelector('.room-aperture').scrollTop = 99999;")
self.wait_for(lambda: self.assertIn(
"is-scroll",
self.browser.find_element(
By.CSS_SELECTOR, ".row .col-lg-6 h2"
).get_attribute("class") or "",
))
# Scroll back up to the hex → the reel reverses to GAME ROOM.
self.browser.execute_script(
"document.querySelector('.room-aperture').scrollTop = 0;")
self.wait_for(lambda: self.assertNotIn(
"is-scroll",
self.browser.find_element(
By.CSS_SELECTOR, ".row .col-lg-6 h2"
).get_attribute("class") or "",
))
def test_redact_filter_hides_struck_rows(self):
"""Unchecking Redact + OK in the scroll-view gear hides the struck
(retracted) rows while the framed ones stay."""