$gate-node: 64px; $gate-gap: 36px; $gate-line: 2px; .room-page { position: relative; display: flex; align-items: center; justify-content: center; flex: 1; min-height: 0; overflow: hidden; } // Scroll-lock when gate is open. Uses html (not body) to avoid CSS overflow // propagation quirk on Linux headless Firefox where body overflow:hidden can // disrupt pointer events on position:fixed descendants. // NOTE: may be superfluous — root cause of CI kit-btn failures turned out to be // game-kit.js missing from git (was in gitignored STATIC_ROOT only). html:has(.gate-backdrop) { overflow: hidden; } // Aperture fill — solid --duoUser layer that covers the game table (.room-page). // Uses position:absolute so it's clipped to .room-page bounds (overflow:hidden), // naturally staying below the h2 title + navbar/footer in both orientations. // Sits at z-90: below blur backdrops (z-100) which render on top via backdrop-filter. // Fades in/out via opacity transition when a backdrop class is present. #id_aperture_fill { position: absolute; inset: 0; background: rgba(var(--duoUser), 1); z-index: 90; pointer-events: none; opacity: 0; transition: opacity 0.15s ease; } html:has(.gate-backdrop) #id_aperture_fill, html:has(.sig-backdrop) #id_aperture_fill, html:has(.role-select-backdrop) #id_aperture_fill { opacity: 1; } // NB: `html.sea-open #id_aperture_fill { opacity: 1 }` was DROPPED in the felt // rebuild (2026-06-07). The DRAW SEA felt now sits at z-5 INSIDE the hex pane // (.sea-page--room), BELOW the z-90 fill — lighting the fill painted an opaque // green sheet OVER the felt + spread (the 0.15s opacity transition is why the // spread "flashed then vanished"). Same trap + fix as the CAST SKY felt. // [[feedback-felt-aperture-fill-covers-felt]] // ─── Table-hex aperture: binary scroll-snap toggle ───────────────────────── // Mirrors my_sky's wheel<->form swap (`_sky.scss` body.sky-saved block). The // aperture fills .room-page; from Role Select onwards it holds TWO panes — // the hex (default) and the room's provenance scroll — and scroll-snaps // between them. CRITICAL: the aperture and panes set NO z-index / transform / // opacity / filter, so they create NO stacking context — the position strip's // z-130 (a hex-pane descendant) still resolves in the root context, above the // gate/sig overlays (z-100/120), exactly as when it lived at room-page root. .room-aperture { position: absolute; inset: 0; display: flex; flex-direction: column; // Gate phase (single pane): static + clipped, like the old .room-page. overflow: hidden; } .room-pane { position: relative; // containing block for the position strip (z-auto) flex: 0 0 auto; width: 100%; // EXACTLY one aperture tall (not min-height) so the snap stops land at // integer multiples of the viewport — the binary toggle, no mid-scroll. height: 100%; } .room-hex-pane { display: flex; align-items: center; justify-content: center; } // Two panes present (table phase) → engage the binary snap. `is-scrollable` // is added by the template iff `room.table_status` is set (Role Select on). .room-aperture.is-scrollable { overflow-y: auto; overflow-x: hidden; scroll-snap-type: y mandatory; overscroll-behavior-y: contain; -webkit-overflow-scrolling: touch; .room-pane { scroll-snap-align: start; scroll-snap-stop: always; // hard stop each section — no coasting } } // While the CAST SKY felt is summoned (html.sky-open) the outer aperture must // NOT scroll down to the reelhouse carousel (ATLAS/SCROLL/YARN/POST/PULSE) — // the felt's OWN form↔wheel scroll-snap (.sky-page--room) is the only scroll // in play. Pin the aperture to the hex pane (which the felt fills); restored // the instant the felt closes (sky-open removed). overflow:hidden alone leaves // the aperture parked on whichever pane it was showing — and CAST SKY is only // reachable from the hex pane, so it pins to the hex every time. html.sky-open .room-aperture.is-scrollable { overflow-y: hidden; scroll-snap-type: none; } // Same pin while the DRAW SEA felt is summoned (html.sea-open) — the felt fills // the hex pane + the reelhouse below must stay out of reach. html.sea-open .room-aperture.is-scrollable { overflow-y: hidden; scroll-snap-type: none; } .room-scroll-pane { display: flex; flex-direction: column; // ── Horizontal game-views carousel ──────────────────────────────────── // The pane holds a row of 5 full-width views (ATLAS/SCROLL/POST/CHAT/ // PULSE) on native x scroll-snap, nested inside the aperture's binary y // snap. room-views.js parks the initial scrollLeft on SCROLL (2nd view). // The horizontal scrollbar is hidden — the root icon strip is the // affordance. CRITICAL: no z-index/transform here (root stacking context). .room-views { flex: 1; min-height: 0; display: flex; flex-direction: row; overflow-x: auto; overflow-y: hidden; scroll-snap-type: x mandatory; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch; scrollbar-width: none; &::-webkit-scrollbar { width: 0; height: 0; } } .room-view { flex: 0 0 100%; width: 100%; height: 100%; min-width: 0; box-sizing: border-box; scroll-snap-align: start; scroll-snap-stop: always; // hard stop each view — no coasting display: flex; flex-direction: column; // Deeper bottom padding reserves a lane for the icon strip (absolute @ // bottom 0.85rem, ~2.1rem tall incl. the active scale) so the // .applet-scroll card ENDS ABOVE the strip — the strip stands on its // own beneath the card instead of overlapping its bottom edge. padding: 0.75rem 0.75rem 3.5rem; } // Same %applet-box card chrome + rotated room-name title (`> h2`) as the // Billscroll page's .applet-scroll. No --duoUser pane bg — the dark card // sits straight on the room-page background, matching scroll.html. .applet-scroll { @extend %applet-box; flex: 1; min-height: 0; display: flex; flex-direction: column; #id_drama_scroll { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; // Pin the "What happens next…?" buffer to the bottom when the feed // is short (pure-CSS equivalent of billscroll's rAF marginTop nudge). .scroll-buffer { margin-top: auto; display: flex; justify-content: center; align-items: baseline; padding: 2rem 0 1rem; opacity: 0.4; font-size: 0.8rem; text-transform: uppercase; .scroll-buffer-text { letter-spacing: 0.33em; } .scroll-buffer-dots { display: inline-flex; letter-spacing: 0; span { display: inline-block; width: 0.7em; text-align: center; } } } } } // ── Per-view title recolor + view-specific card styling ─────────────── // Each reelhouse view's rotated room-name `> h2` carries a distinct // font/bg pair (user-spec 2026-06-08) so the five views read as // colour-coded sections. ATLAS's per-source row accents (further down) // echo the SCROLL + POST h2 bgs so a merged row's origin is legible. // SCROLL — provenance feed. --priUser font on a --sixUser strip. .room-view--scroll .applet-scroll > h2 { color: rgba(var(--priUser), 1); background-color: rgba(var(--sixUser), 1); } // YARN view — the room-scoped chat thread (most of it forthcoming). Inherits // the full green-felt input-pill treatment POST used to carry: a --duoUser // card, the green-tinted h2 strip, the input-style line pills + composer. // SALVAGED here verbatim (the .post-* selectors renamed .yarn-*) because POST // reverts to the plain applet wash below — so the styling survives for when // YARN's backing model + markup land. Reuses post.html's composer markup // language; the line/buffer/form/table selectors are the future .yarn-* ones. .room-view--yarn { .applet-scroll { background-color: rgba(var(--duoUser), 1); > h2 { // Three translucent 0,0,0/0.125 layers and NO opaque base, so the // --duoUser felt shows THROUGH for a green-tinted "partial mask". background-color: rgba(0, 0, 0, 0.125); background-image: linear-gradient(rgba(0, 0, 0, 0.125), rgba(0, 0, 0, 0.125)), linear-gradient(rgba(0, 0, 0, 0.125), rgba(0, 0, 0, 0.125)); } } #id_yarn_table { list-style: none; margin: 0; padding: 0; // pills span the full card content width (= composer row) flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; justify-content: flex-end; // bottom-anchor short threads // Each line is styled to LOOK LIKE the composer input below it // (mirrors .form-control, _base.scss): a --priUser fill + a 0.1rem // --secUser border at the same border-radius, full width, an up/down // margin and content-driven (dynamic) height — so the thread reads as // a stack of input-style pills on the --duoUser felt. .yarn-line { display: grid; grid-template-columns: minmax(4rem, auto) 1fr minmax(3rem, auto); align-items: baseline; gap: 0.5rem; width: 100%; background-color: rgba(var(--priUser), 0.8); border: 0.1rem solid rgba(var(--secUser), 0.5); border-radius: calc((1rem + 1em) / 3); // == .form-control radius margin: 0.35rem 0; padding: 0.5rem 0.75rem; box-shadow: 1px 1px 0.125rem 1px rgba(0, 0, 0, 0.6); .yarn-line-author { font-weight: bold; color: rgba(var(--quaUser), 1); white-space: nowrap; font-size: 0.85rem; } .yarn-line-text { min-width: 0; overflow-wrap: anywhere; } .yarn-line-time { font-size: 0.75rem; opacity: 0.5; text-align: right; white-space: nowrap; } } .yarn-line-buffer { flex-shrink: 0; height: 0.25rem; } } .yarn-line-form { flex-shrink: 0; margin: 0; padding-top: 0.25rem; .composer-row { display: flex; gap: 0.5rem; align-items: center; } .composer-row input.form-control { flex: 1; min-width: 0; width: auto; } } } // POST view — reverts to the plain dark %applet-box wash (its green-felt // input-pill styling moved to YARN above). Only its h2 is recoloured: // --priUser font on a --sepUser strip. .room-view--post { .applet-scroll > h2 { color: rgba(var(--priUser), 1); background-color: rgba(var(--sepUser), 1); } // Bare-