/* Platform chrome — PLAIN LIQUID GLASS, one material for the whole chrome layer (2026-09-25,
 * docs/plans/2026-09-25-plain-liquid-glass.md, P1 "platform", supersedes the 2026-09-18 re-glass
 * two-regime split below AND the 2026-09-18 de-glass ruling it itself superseded).
 *
 * Owner ruling, verbatim: "Improve UI of the current app to plain liquid-glass (without refraction
 * version)... Look nice. go build." The approved mock (canvas "Irminsul — Plain Liquid Glass")
 * puts the material back on the whole floating CHROME layer -- `#shell` topbar, `.rail-nav`, the
 * phone `#tabbar`, the phone capsules, `.dock-btn` -- not just the control layer the 2026-09-18
 * re-glass round scoped it to. There is now ONE glass language, `.glass` (see §C below): a frost
 * fill (`saturate(170%) blur(22px)`) + a DIRECTIONAL rim (bright top-left, dim bottom-right,
 * `--glass-lit`) + a `.5px` outer edge + a soft shadow -- NO refraction, no SVG filter, no
 * displacement map, no specular lighting anywhere in this file any more (the droplet lens is
 * gone). Chrome FLOATS now: `--chrome-inset` (10px) inset from the viewport edge, `--radius-chrome`
 * (24px) corners, concentric with the rail's own padding -- reversing the 2026-09-18 "flush,
 * square-cornered chrome" ruling (recorded explicitly, not silently, in DESIGN.md's Elevation &
 * Depth section). The segmented-control thumb is a plain glass pill (`.seg-thumb`, no children, no
 * backdrop-filter of its own -- it sits inside chrome that already has one, see the nested-backdrop
 * guard below) replacing the droplet lens outright, on every width, not just desktop.
 *
 * Content stays exactly where Liquid Glass v2 (2026-09-11) left it: cards/rows/fields/composers/
 * menus/dialogs/popovers/toasts/sheets are opaque `--cell-solid`/`--bg-solid` boxes with a hairline
 * or `--shadow-2`, never blurred -- P1 does not touch the pool's button/field/chip/card recipes
 * (P2's job); only the chrome fill + geometry + the segmented thumb changed here.
 *
 * NESTED BACKDROP-FILTER GUARD: an element with `backdrop-filter` becomes the containing block for
 * its `position:fixed` descendants, and Chromium/WebKit both render a backdrop-filter NESTED inside
 * another backdrop-filter element as a flat grey block (it can only sample its parent's own already-
 * blurred pixels, Droplet Lab lesson 1) -- so nothing that lives inside `.glass` chrome (`#shell`,
 * `.rail-nav`, `#tabbar`, the phone capsule) may carry its own `backdrop-filter`. The rule right
 * after `.glass` below enforces this for every button/pill/segmented control drawn inside chrome;
 * any FIXED popover that used to live inside `#shell` (the account menu) is portalled to `<body>` at
 * build time (shell.js) instead of relying on first-open re-parenting.
 *
 * Loaded LAST on every page (after shell.css / components.css / the module's own css), so its
 * rules still win any equal-specificity tie by source order.
 *
 * Tokens only — no literal px/ms/hex outside tokens.css, except the rgba() alpha values that are
 * themselves defined in tokens.css (--glass-rim etc.) and read here through var().
 */

/* Leg A2 owner review round 1, ruling A1 (2026-09-11): "No gradient on the page." The ambient
 * wallpaper is gone; the ground is flat var(--ground) -- untouched by this round, chrome material
 * or not (the owner's narrowed ask was about buttons, not the page floor). */
html,body{background:var(--ground)}

/* ---------- §C THE chrome recipe: one plain-glass class, no refraction ----------
 * `.glass` is the ONE material every floating chrome surface carries (2026-09-25): `#shell`,
 * `.rail-nav`, the phone `#tabbar`, `#docksearch` -- shell.js adds the class at build time, never
 * a per-page hand-authored rule. Frost fill + a DIRECTIONAL rim (`--glass-lit`: bright top-left,
 * dim bottom-right) + a `.5px` outer edge + a soft shadow. No inner ring/glow/shade layering (that
 * stayed a control-layer idiom, see the button/picker/capsule recipes further down this file, which
 * P1 leaves untouched). `.glass-lite` is the SAME recipe with no blur, for a control that sits
 * INSIDE glass chrome (the nested-backdrop guard right below forbids nesting an actual blur).
 * `.glass-strong` stays fully OPAQUE: modules/todo/static/js/todo.js applies it to a MENU it
 * builds, and menus/dialogs/sheets stay flat per Liquid Glass v2. `.btn-clear` (the admin UI-pool
 * "Button" group's clear-glass demo, plus the Leaflet zoom/layers controls) is untouched by this
 * pass -- P2's job, not chrome. */
.glass{
  background:var(--glass-fill);
  -webkit-backdrop-filter:saturate(var(--glass-sat)) blur(var(--glass-blur));
  backdrop-filter:saturate(var(--glass-sat)) blur(var(--glass-blur));
  border-color:transparent;
  box-shadow:var(--glass-lit), var(--glass-shadow);
}
/* R3 A1 (2026-09-27, owner #4, verbatim "module rail & web navbar -> try no shadow"): #shell and
 * .rail-nav keep .glass's fill/blur plus the directional rim + .5px edge (--glass-lit) but lose
 * the outer drop shadow (--glass-shadow) -- Floating AND Flush (Flush already drops box-shadow
 * entirely via its own higher-specificity :root.chrome-flush rule further down this file/shell.css,
 * so this rule only ever shows through in Floating), every width, light/dark, Glass/Solid. Every
 * OTHER `.glass` surface (the phone #tabbar, #docksearch, the dock, .glass-panel, ...) keeps its
 * shadow -- this is scoped to these two ids only. */
#shell.glass, .rail-nav.glass{ box-shadow:var(--glass-lit); }
.glass-strong{ background:var(--bg-solid);border-color:transparent;box-shadow:var(--shadow-2); }
.glass-lite{
  background:var(--glass-fill);border-color:transparent;
  box-shadow:var(--glass-lit);
}
/* nested-backdrop guard (2026-09-25): an element with backdrop-filter is its own backdrop root --
 * a child with ITS OWN backdrop-filter can only sample the parent's already-blurred pixels
 * (WebKit/Chromium both render this as a flat grey block, Droplet Lab lesson 1). Every control that
 * lives inside `.glass` chrome carries none of its own; the chrome's single blur is the only one. */
.glass :is(button,.btn,[class*=seg]){ -webkit-backdrop-filter:none;backdrop-filter:none; }
/* round 2 (G1 leftover 18, "future /map floating panels", latent): the guard above only covers
 * `.glass` descendants -- `.glass-panel` (built for the Map redesign, a content-area floating
 * panel using the SAME frost recipe, see its own rule just below) is not `.glass`, so a `.primary`
 * or `form button[type=submit]` inside one (both carry their own backdrop-filter, further down this
 * file) would nest a backdrop-filter inside a backdrop-filter -- the exact Droplet Lab lesson 1
 * hazard the guard above exists to prevent. Added here pre-emptively so the Map leg's adoption
 * inherits a working guard rather than re-discovering this. */
.glass-panel :is(button,.btn,[class*=seg]){ -webkit-backdrop-filter:none;backdrop-filter:none; }
.btn-clear{
  background:transparent;
  -webkit-backdrop-filter:none; backdrop-filter:none;
  border-color:transparent;
  border-radius:var(--radius-pill);
  box-shadow:none;
}
@media (hover:hover){ .btn-clear:hover { background:var(--hover); } }
@media (prefers-reduced-transparency: reduce){
  .btn-clear{ background:var(--glass-fill-strong); }
}
/* post-pass (2026-09-26, r2 final review finding 4): Material=Solid never got the class-based twin
 * of the reduced-transparency block above -- :root.solid's own --glass-blur-clear:0px (tokens.css)
 * already zeroed the blur correctly, but with no background override .btn-clear kept its base
 * `background:transparent`, rendering as a fully see-through pill with no blur under Solid. No new
 * token needed: --glass-fill-strong is already retuned opaque under :root.solid (tokens.css), same
 * value the reduced-transparency block above already reuses -- this is just that same reuse, kept
 * in sync by construction like every other Solid/reduced-transparency pair in this file. */
:root.solid .btn-clear{ background:var(--glass-fill-strong); }

/* ================= chrome bars / rails: opaque, flush, one hairline =================
 * `#shell`/`.rail-nav` LEFT this rule (2026-09-25): they float now and carry `.glass` instead (see
 * §C above and shell.css's geometry) — this rule is for the bars that stay flush + opaque under
 * the mock: components.css's `.topbar` (stocks' second sticky row), the Today customize bar, and
 * the games sticky panel rail. No border/box-shadow/blur/float.
 * fix round (pool-gaps-2, finding 5): `.cal-card` dropped from this list -- a G2 cells-round-2 leg
 * repurposed it (modules/calendar/static/css/calendar.css) from a flush bar into the transparent
 * padded-main FRAME around `#calside`/`#calbody` (the same padded-cell recipe the other lifeA
 * sidebars adopted), so it no longer wants this group's opaque fill; that module now carries its
 * own `main .cal-card{background:transparent}` override (0,1,1, beating this rule's 0,1,0
 * regardless of load order) purely because this list still named it. An un-styled `<div>` is
 * `background:transparent` by CSS's own initial value, so removing `.cal-card` here needs no
 * replacement value -- calendar.css's own now-redundant override is deleted alongside this (see
 * that file). */
.topbar,
.customize-bar,
.gp-sticky {
  background:var(--bg-solid);
}

/* .glass-panel — a floating glass panel over RICH CONTENT (fix round, pool-gaps finding 5, plain-
 * liquid-glass P2, Map full-bleed redesign pool gap): the SAME frost/rim/edge/shadow recipe as
 * chrome's own `.glass`, declared as its own reusable class so a content-area panel (the Map
 * mock's saved-places/search/legend panels, floating over live map tiles) doesn't have to borrow
 * the chrome-only class name -- unblocking that module's own redesign leg, not built here.
 * Reused directly below for Leaflet's own zoom/layers controls, which are exactly this idiom
 * (2026-09-18's "no backdrop-filter anywhere" line is superseded platform-wide by the 2026-09-25
 * plain-liquid-glass ruling that chrome floats and glasses again). The popup CONTENT wrapper stays
 * OPAQUE on purpose (No-Blur-Over-Text, DESIGN.md: a popover's own text is never blurred/faded) --
 * it keeps its existing rule in the menus/dialogs/popovers group above; the attribution strip
 * stays excluded here too (Liquid Glass v2: "keeps its own subtle treatment"), unaffected by this
 * leg. */
.glass-panel,
.leaflet-control:not(.leaflet-control-attribution), .leaflet-bar {
  background:var(--glass-fill);
  -webkit-backdrop-filter:saturate(var(--glass-sat)) blur(var(--glass-blur));
  backdrop-filter:saturate(var(--glass-sat)) blur(var(--glass-blur));
  border-color:transparent;
  box-shadow:var(--glass-lit), var(--glass-shadow);
}
/* radius stays per-consumer: a general floating panel gets the platform's normal cell radius, the
 * Leaflet zoom/layers bar keeps its own pill shape (unchanged from before this fix). */
.glass-panel{ border-radius:var(--radius-lg); }
.leaflet-control:not(.leaflet-control-attribution), .leaflet-bar{ border-radius:var(--radius-pill); }
/* .glass-panel--thick (post-pass 2026-09-26, item F1, declared variant of the `glass` pool row):
 * a denser tint for ANY .glass-panel-family consumer sitting over rich photographic imagery (the
 * Map's Satellite base layer), where the normal --glass-fill mix lets enough of a bright/dark
 * real tile bleed through to fail AA for the text on top of it -- see tokens.css's own comment on
 * --glass-fill-thick for the calibration method + numbers. Changes ONLY the fill (F1's own ask);
 * blur/rim/edge/shadow/radius are untouched, so it still reads as glass, just less see-through.
 * map.js toggles this class on the side panel, #mstatus and the zoom/layers capsule only while
 * Satellite is the active base layer (baselayerchange + at boot when Satellite is restored) --
 * every other base layer (street map) keeps the normal, more transparent fill. */
.glass-panel--thick{ background:var(--glass-fill-thick); }
/* .glass-panel--caption RETIRED (R3 A4, 2026-09-27, owner Q2 "Map caption: no need"): B2
 * (commit ccde60d5) dropped #mstatus's floating caption entirely -- it is now a screen-reader-only
 * live region, so no element carries this class any more (confirmed: `grep -rn
 * glass-panel--caption modules/` finds only map.css's own comment noting it's gone). The rule and
 * its `core/ui_pool.py` variant are deleted; DESIGN.md's Elevation & Depth entry for it stays as
 * the historical record (house style: never delete a ruling line), marked removed. */
/* leaflet.css's own .leaflet-bar a:first-child/:last-child keep a hard 4px radius -- clip them to
 * the bar's own pill radius so they don't poke out at the top/bottom of the capsule. */
.leaflet-bar { overflow:hidden; }
/* round 2 (G1 leftover 10a): `background:var(--bg-solid)` here was OPAQUE, painted on top of the
 * bar's own glass fill two rules up -- the zoom +/- buttons covered the blur with a flat white/dark
 * rectangle instead of showing it through, so the "glass zoom bar" only ever glassed at its own
 * unclipped edges. Transparent lets the parent `.leaflet-bar`'s frost read through each button; the
 * row-divider hairline (leaflet.css's own `border-bottom`) keeps its colour. */
/* post-pass (2026-09-26, item F3): leaflet.css's own `.leaflet-bar a{color:#333}` never got
 * re-pinned to a token here (only background/border-bottom-color were) -- in dark mode the zoom
 * +/- glyphs stayed rgb(0,0,0) (map post-pass recheck, $S/pprc-map/pw-dark-zoom.png), unreadable
 * against the dark glass fill (base's own pre-glass rule, since deleted as dead code by the map
 * agent's own item 11, DID set color:var(--fg) -- this restores that, on the pool source now
 * rather than a module rule that no longer exists). */
.leaflet-bar a { background:transparent; border-bottom-color:var(--bd); color:var(--fg); }
@media (hover:hover){ .leaflet-bar a:hover { background:transparent; border-bottom-color:var(--bd); color:var(--fg); } }
@media (hover:hover){ .leaflet-bar a:hover { background:var(--hover); } }
.leaflet-touch .leaflet-bar, .leaflet-touch .leaflet-control-layers { border:0; }
/* round 2 (G1 leftover 10b/10c): the layers control, EXPANDED, is a multi-row panel (base list +
 * radio/checkbox rows), not a slim bar -- it still matches the general `.leaflet-control` selector
 * above and inherited that recipe's PILL radius (999px), clipping its own radio circles at the
 * curved corners on a box this wide/tall. `.leaflet-control-layers-expanded` (leaflet.css's own
 * class, added when the control opens) gets the platform's normal panel radius instead, matching
 * every other expanded popover/menu. leaflet.css also hardcodes its OWN dark label text colour on
 * `.leaflet-control-layers label` -- invisible against our glass fill in dark mode (dark-on-dark);
 * `color:var(--fg)` re-themes it like every other platform label. */
/* fix round 1 (pool, finding 16): `.leaflet-control-layers-expanded` ALONE is (0,1,0) -- lower
 * than the generic `.leaflet-control:not(.leaflet-control-attribution)` rule above it (0,2,0),
 * which always wins regardless of source order, so the expanded panel kept the generic bar's pill
 * radius (999px) and clipped its own radio circles at the curve. Qualifying the base class
 * alongside it (`.leaflet-control.leaflet-control-layers-expanded`, (0,2,0) -- both classes are
 * ALWAYS present together, leaflet.css's own class) ties the generic rule's specificity, so this
 * rule (declared later) wins the tie by source order instead of losing outright. */
.leaflet-control.leaflet-control-layers-expanded{ border-radius:var(--radius-sm); }
.leaflet-control-layers label{ color:var(--fg); }
/* fix round 1 (finding 5): leaflet.css's own `.leaflet-top{z-index:1000}` sets its stacking level
 * relative to the nearest ANCESTOR stacking context -- `.leaflet-container` is only
 * `position:relative` with no z-index of its own, so it establishes none, and that 1000 escapes to
 * compete directly against the rest of the page (the floating phone dock, z-index:55, paints
 * BELOW it there). `isolation:isolate` gives the map its own local stacking context with no other
 * change to Leaflet's internal layering -- every one of its panes/controls still stacks against
 * each other exactly as before, just no longer against page-level fixed chrome outside the map. */
.leaflet-container{ isolation:isolate; }
/* Liquid Glass v2 §D (kept): cards are CONTENT, not chrome -- an in-flow card is never floating
 * over scrolled material the way a bar/menu is. Opaque --cell-solid fill + ONE hairline
 * (--cell-bd), no rim, lifted off the ground with --shadow-1. */
/* P2 pool (2026-09-25, plain-liquid-glass): the mock's thin-glass "cell" -- translucent --c-fill +
 * a directional inset rim (the SAME dim/edge chrome already reads) + a soft shadow, no blur. `.stat`/
 * `.composer`/`.row-add .composer` join the card family here (mock: "the cards match .cell ...
 * .stat and .composer follow the card cell") -- they used to sit in the opaque field/chip group
 * further down this file, which is now split three ways (field / chip / cell). Games freeze: the
 * tile family (`.game-card` etc.) is pulled OUT of this edit into its own byte-identical opaque
 * rule right below, per the plan's hard rule ("Games cards leave the cell-group edit") -- .games-
 * keep's own re-pin block near the end of this file additionally restores every OTHER selector
 * here back to the pre-P2 opaque look for games content. */
.card, .widget-card, .today-card,
.rec-card, .sum-card,
.mm-card,
.stat, .composer, .row-add .composer,
.cell, .cell--side {
  background:var(--c-fill);border-color:transparent;
  box-shadow:var(--c-shadow);
}
/* fix round (pool-gaps-2, findings 6/7/8/24, BLOCKING major, unchanged since round 1's own
 * diagnosis): `.cell--side` had no phone-flat treatment, so every full-height sidebar drawer that
 * carries it (To-Do's `.rem-side`, Journal's `.jr-side`, Contacts' `.ppl-list`, Settings' own
 * `.set-side`) kept the translucent glass fill + inset rim + shadow at phone width, where the mock
 * wants a flush, ground-level list/drawer instead (no floating-card look once it goes full-bleed).
 * A module CANNOT fix this itself: `background`/`box-shadow` are `.cell`'s own FIXED properties
 * (core/ui_pool.py), and the three modules above already tried a same-specificity phone override
 * (`.rem-side{background:none;box-shadow:none}` etc.) that this file's own bare `.cell--side` rule,
 * declared AFTER every module stylesheet, always won regardless of source order -- confirmed dead
 * code, since removed there (see each module's own comment pointing back at this exact gap).
 * Landed HERE instead -- in the SAME file as the base rule, right after it, so the phone override
 * wins the cascade by simple source order with no specificity fight -- and scoped to `.cell--side`
 * ONLY, never bare `.cell`: an in-flow content cell (Journal's day content, Settings' `.set-card`)
 * keeps its phone glass look; only the SIDEBAR/DRAWER variant goes flush, matching every G2 leg's
 * "phone keeps today's flush/stacked anatomy" language. Breakpoint matches the platform's own
 * phone cutoff (`48rem` -- journal.css/people.css/settings.css's own phone media queries already
 * use it; only To-Do's unrelated single-column layout switch happens a few px earlier at 760px,
 * unaffected by this). `border-radius` stays OPEN (unchanged, each of the three modules already
 * sets it to 0 in their own phone rule) -- this is the two FIXED properties only. */
@media (max-width:48rem){
  .cell--side{ background:none;box-shadow:none; }
}
/* 2026-10-02 DE-GLASS: the games tile family (`.game-card`/`a.sub-card`/`.upcoming-card`/`.team-card`/`.char-card`)
 * lost its pool paint rule here -- these classes are games-only CONTENT, whose look is owned by games-hub.css/app.css
 * exactly as at 45fe0188 (the opaque `--cell-solid` + hairline + accent-rim recipe overrode those modules' own bg/border). */
/* fix round 1 (pool, finding 11/E1): `.composer-flat` -- a composer that is the SOLE content of
 * an already-painted `.set-card cell` section (Settings' Connections create-token form, Feedback
 * form) does not ALSO want its own cell paint -- two cell-family boxes nested directly inside each
 * other is exactly the "stacked glass" the design hook's nested-cards check flags (evidence:
 * `impeccable detect core/static/settings.html`, 2 "Card inside card (form)" findings, one per
 * composer). `.composer` itself keeps `background`/`box-shadow` FIXED (core/ui_pool.py) since most
 * composers sit directly on the bare ground and DO want the cell look -- this is its own separate
 * pool entry used ALONGSIDE the base class (`class="composer composer-flat"`), same precedent as
 * `.ghost-box` overriding the button row's own fixed paint. `border-radius`/`padding` stay: an
 * invisible box with its own spacing is still a sane layout, and a module never needs to fight
 * those to use this. */
.composer.composer-flat{ background:none;box-shadow:none; }
/* `.stat-flat` (widget-pages leg 4): a stat tile that sits INSIDE an already-painted card (the Today project glance card) drops its own
 * cell paint, same precedent as `.composer-flat` -- two cell-family boxes nested directly is "stacked glass". Layout (padding, gap) stays. */
.stat.stat-flat{ background:none;box-shadow:none;padding-inline:0; }
/* the Leaflet popup is a menu, not a bar -- opaque + soft shadow, no blur.
 * post-pass (2026-09-26, item F3): `.leaflet-popup-tip` (the little pointer triangle under the
 * popup) joins the SAME fill -- it was left out when this rule was written, so it kept
 * leaflet.css's own hardcoded white, which read as a white triangle under a dark popup in dark
 * mode (map post-pass recheck, $S/pprc-map/crop-dark-popup-tip.png). No separate :root.solid twin
 * needed: --bg-solid is already the same opaque value in both Glass and Solid material. */
.leaflet-popup-content-wrapper, .leaflet-popup-tip{
  background:var(--bg-solid);
  box-shadow:var(--shadow-2);
}

/* concentric radii (kept, values only): an inner control's radius = its container's radius minus
 * the container's own padding. The containers themselves are no longer floating cards (--radius-lg)
 * so their children step down from the plain hairline box instead — --radius-sm, the platform's
 * usual small-control radius. */
.rail-i{border-radius:var(--radius-sm)}
.shellpanel a, .shellpanel button{border-radius:var(--radius-sm)}

/* §I sheet grabber: shell.css's own phone breakpoint (640px, not the app's usual 48rem) is where
 * .dock-panel actually becomes a bottom sheet — the grabber and the rounded-top-corner bump only
 * belong there. A drawn pill, not an icon, so it reads at a glance as "drag me" the way an iOS
 * sheet's grabber does. */
@media (max-width:640px){
  .dock-panel{ border-radius:var(--radius-xl) var(--radius-xl) 0 0; }
  .dock-panel::before{
    content:"";display:block;width:var(--sp-7);height:var(--sp-1);
    border-radius:var(--radius-pill);background:var(--bd);margin:0 auto var(--sp-3);
  }
}

/* dock button (flat-buttons 2026-10-16, owner picked B): the same tinted primary as `button.primary`
 * -- accent text on a 16% accent tint -- but laid over the OPAQUE page colour so content scrolling
 * under the floating Capture button never shows through; no shadow, no blur, no rim, no glow.
 * `:hover` is in the first selector on purpose: components.css's `button:hover{background:var(--hover)}`
 * (0,1,1) out-ranks a bare `.dock-btn` (0,1,0). */
.dock-btn {
  position:relative;
  background:color-mix(in srgb, var(--accent) 16%, var(--bg-solid));
  -webkit-backdrop-filter:none; backdrop-filter:none;
  border-color:transparent; color:var(--accent); box-shadow:none;
}
@media (hover:hover){ .dock-btn:hover {
  position:relative;
  background:color-mix(in srgb, var(--accent) 16%, var(--bg-solid));
  -webkit-backdrop-filter:none; backdrop-filter:none;
  border-color:transparent; color:var(--accent); box-shadow:none;
} }
@media (hover:hover){ .dock-btn:hover { background:color-mix(in srgb, var(--accent) 24%, var(--bg-solid)); } }

/* ===== phone action capsule + tab bar: both plain glass now (2026-09-25) =====
 * Plain liquid-glass reverses the 2026-09-18 re-glass split ("action buttons only"): the phone
 * capsule holding Search + Capture AND `#tabbar` (page-jump navigation) are both floating CHROME
 * under the mock, so both carry `.glass` (added by shell.js at build time) -- this block is now
 * geometry/nesting-guard only, no separate fill recipe. */
@media (max-width:48rem){
  /* nested-backdrop guard: `.dock-btn`'s own backdrop-filter would only sample an ancestor's
   * already-blurred pixels once nested (Droplet Lab lesson 1) -- off here; the accent fill (96%,
   * `--accent-fill-pct`) still shows whatever's behind it through it. post-pass (2026-09-26, r2
   * final review finding 7): corrected -- `#dock` itself is plain geometry (shell.js sets
   * `className='dock'`, never `.glass`; shell.css's own comment on `.dock` says so too), so this
   * is NOT the `.glass :is(button,...)` ancestor case above. The real hazard this guards is
   * `#docksearch` specifically: it carries BOTH `dock-btn` (its own backdrop-filter, above) AND
   * `glass` on the SAME element (shell.js), a two-rule fight on one element, not a nesting one --
   * this rule just settles it explicitly at phone width rather than leaving it to source order. */
  .dock .dock-btn{ -webkit-backdrop-filter:none;backdrop-filter:none; }
  /* the tab items must still paint ABOVE the sliding .seg-thumb (z-index:0, below) */
  #tabbar .tab{ position:relative;z-index:1; }
}

/* ================= menus / dialogs / popovers: opaque + soft shadow, no blur =================
 * These are the surfaces that genuinely float over content (a menu, a sheet, a dialog), so they
 * keep a soft drop shadow -- but opaque fill like everything else now that glass is retired.
 * post-pass (2026-09-26, r2 final review finding 6, corrected after test_unlisted_tail_is_
 * empty_at_ship caught the first pass): `.cell--drawer` (Calendar's phone overlay drawer,
 * calendar.js's _openSideDrawer/_closeSideDrawer toggling it on `#calside`, which also carries
 * the base `cell cell--side`) joins this list -- the same "surface that genuinely floats over
 * content" idiom as every other member here -- instead of a module hand-copying the exact same
 * two values (couldn't be caught by ui_pool.restyle_violations() at all: neither `.cal-side` nor
 * `.drawer-open`, the FIRST attempt's own selector, named a pool class, same gap class as
 * ledger's `.rows`). `.cell`'s `background`/`box-shadow` are declared `variant_open` on the cell
 * row (core/ui_pool.py) precisely for this: fixed everywhere EXCEPT through the row's own
 * declared `drawer` variant, so an opened drawer can become an opaque overlay without leaving
 * every OTHER `.cell`/`.cell--side` consumer free to restyle these two undeclared.
 * R3 A3 (owner #2): `.shellpanel` LEFT this list -- the account menu is glass now (shell.js gives
 * `#usermenu` the `glass` class directly), not an opaque `--bg-solid` surface; its own recipe
 * lives further down this file, next to its morph. */
.dock-panel, .dpick, .ac, .otpdlg,
.pool-panel, dialog#pickdlg, dialog#wizdlg,
.colpick-menu, dialog#earndlg, #toast,
.rem-detail, aside#detail, .more-menu, dialog#listdlg, #secdlg, #tpldlg,
#calpop, dialog#evdlg, dialog#catdlg, dialog#keyhelp,
dialog#pdlg,
.gs-menu,
dialog, dialog#statusdlg, #stsdlg, #recvdlg,
#lsdlg, .cell--drawer {
  background:var(--bg-solid);
  box-shadow:var(--shadow-2);
}
/* rows inside a panel are flat with a hover tint, never their own box. */
.rail-i, .shellpanel a, .shellpanel button, #railtoggle, .lsopen, .more-menu button, .more-menu label{
  background:transparent;border-color:transparent;
}
.shellpanel a, .shellpanel button, .more-menu button{box-shadow:none}  /* drop the old per-row 1px box (§4a); .more-menu button added 2026-09-18 -- same "flat panel row" idiom (To-Do's/Ask's "..." dropdown), just missing from this list before now */
@media (hover:hover){ .rail-i:hover, .shellpanel a:hover, .shellpanel button:hover, #railtoggle:hover, .lsopen:hover {
  background:var(--hover);
} }
.ltprev,.dkprev{background:var(--ground)}  /* palette-preview card halves: opaque ground, their own tokens */
/* Leg A3 owner review round 2, item 4 (2026-09-04) used to CENTRE `.rail-i`'s icon in icons mode
 * (padding-inline:0 + justify-content:center) because left-aligning it read off-centre once the
 * label clipped to nothing. R3 A1 (2026-09-27, owner #3, verbatim "hidden icon position should
 * match to when it's shown") supersedes that for the icon's OWN x position: centering shifted
 * every icon right by roughly half the rail's width loss (measured 68px collapsing 12rem -> 3.5rem
 * at 1440/1180) -- worse than reading "off-centre", the icon visibly JUMPED sideways on every
 * collapse/expand. Removing the override leaves `.rail-i` at its one padding (--sp-3) in both
 * modes, so the icon's x is identical labelled <-> icons (verified within 0.5px, WebKit +
 * Chromium, 1440 + 1180, Floating + Flush) at the cost of the small icons-mode gap to its right
 * the 2026-09-04 ruling was avoiding -- the newer, more specific ruling wins where the two
 * conflict. `#railtoggle`'s own icon centres unconditionally now via `.lsopen` (shell.css), so its
 * own copy of this override is gone too. */

/* ================= dialog entrance (@starting-style) — plain scrim, no blur ================= */
dialog::backdrop, .dock-backdrop{ background:var(--scrim); }

/* ================= §D content cells (opaque) — fields / chips / composers ================= */
/* Liquid Glass v2 (fault #1): a text field, chip, stat or composer is content the user reads and
 * types into, not floating chrome — opaque --cell-solid + ONE hairline, no rim, no drop shadow
 * (a field doesn't lift off the page the way a card does). `select` stays its OWN rule using
 * background-color (never the `background:` shorthand, which would silently wipe
 * select{background-image:var(--select-chevron)} — a hard-gated invariant,
 * tests/test_app.py's _check_select_rules scans every platform CSS file for exactly this mistake).
 * journal-redesign (2026-09-18): `.row-add .ghost`/`.entry-card.row-add .ghost` LEFT this group --
 * a dashed ghost placeholder is not a filled content cell, and this rule's opaque --cell-solid
 * fill is exactly what was wiping the dashed rest-state look on Ledger/Journal (`.ghost-box`,
 * components.css, is the one recipe now). `.row-add .composer` stays: the OPEN composer really is
 * a content cell. */
/* 2026-09-19 (todo-pool-recipes): `.bare-field`/`.select-quiet` LEFT this group -- a text-and-
 * caret-only field or a value-only select is not a filled content cell either, same discipline as
 * the dashed-ghost carve-out above.
 * fix round 1 (2026-09-19): the exclusion is written `:where(:not(.x))`, NOT bare `:not(.x)` --
 * `:not()` carries the specificity of its own argument (a class = 0,1,0), so `input:not(.bare-
 * field)` is 0,1,1, HIGHER than the bare `input` selector this replaced, which flipped cascades
 * that used to be decided in a MODULE class's favour purely because a bare type selector (0,0,1)
 * always lost to any class (0,1,0) -- e.g. To-Do's `.tq-chip{background:var(--hover)}` (0,1,0) used
 * to always beat a plain `input`/`button` rule and no longer could beat `input:not(.bare-field)`
 * (0,1,1), so its background silently reverted to this group's glass fill. `:where()` always
 * contributes ZERO specificity regardless of its argument, so `:where(:not(.bare-field))` matches
 * exactly the same elements but restores the ORIGINAL bare-type-selector specificity (0,0,1) --
 * the cascade math a module class always expected to be fighting. */
/* P2 pool: FIELD material -- var(--field-fill) (a slightly-opaque tint, not the opaque --cell-solid
 * a card gets) + an INSET hairline + a faint inner shadow (mock .field), 36px floor via
 * --field-min-h. `.stat`/`.composer`/`.tokenfield` LEFT this group (2026-09-25): `.stat`/
 * `.composer` moved to the cell/card group above (they're content boxes, not typed-into fields);
 * `.tokenfield` follows the SAME field recipe as a plain input (it IS a text field with token
 * pills inside it) but keeps its own selector since it never matches the bare `input`/`select`
 * type selectors above. */
/* fix round 1 (finding 7): `<input>`/`<textarea>` are content-box (the platform sets no box-sizing
 * of its own on the shared components.css `input,select,textarea` rule), so a plain
 * `min-height:var(--field-min-h)` on them measures the CONTENT box and lets --field-pad-y/
 * --field-bd-w stack ON TOP -- 36px + ~12px padding + 2px border ~= 50px, next to a 36px
 * `<select>` (already border-box in Chromium/WebKit's own UA stylesheet) in the same row. Rather
 * than switching box-sizing platform-wide (which would also shrink every `.w-*ch` field's typed
 * width by that same padding+border, a real regression these tokens were never sized to absorb),
 * subtract exactly what content-box adds so the TOTAL rendered height still lands on the 36px
 * floor, matching `<select>` row for row. */
/* fix round (pool-gaps, finding "checkbox/radio min-height", reported by every module leg across
 * 4+ rounds -- money/aios/lifeA/lifeB/system all hit the same root cause): neither of this group's
 * two selectors excluded `[type=checkbox]`/`[type=radio]`/`[type=range]`/`[type=color]`/`.switch`,
 * so the FIELD min-height floor (36px) applied to every checkbox/radio/switch platform-wide too --
 * a native ~12-20px checkbox (or a 22px `.switch` track, whose own `height` then lost to this
 * rule's larger `min-height`) got stretched to 36px tall, doubling menu/row heights wherever one
 * appears (#colpick, #chartmenu, #pfcount, Leaflet's layer radios, Calendar's `#calsources`,
 * To-Do's info-card switches, the Today queue's round done-checkbox, etc). Excluding these types
 * here (not per-module) is the fix every leg asked for -- a module cannot legally re-shrink a FIXED
 * property (min-height is fixed on this field recipe, unlike the button recipe's open min-height). */
/* fix round (pool-gaps round 2, finding 2): the checkbox/radio/range/color/switch exclusions above
 * were chained as their own `:not()` terms OUTSIDE `:where(:not(.bare-field))` -- each one counts
 * its full (0,1,0) toward specificity even though `:where()` itself never does, jumping this rule
 * from (0,0,1) to (0,5,1) (same mistake, (0,7,1), on the border-box rule below). At (0,0,1) a module
 * class rule (`.dlgform input`, `.rem-detail input:not(.switch)`, ...) or an equal-specificity
 * `:where()`-wrapped override declared later in this file (the games-keep re-pin just below) always
 * wins the cascade the way this recipe intends; at (0,5,1) this rule started beating BOTH of those,
 * silently repainting To-Do's `#dcn`/`#lname`/`#licon` borders and every games text/time field, and
 * replacing every focused field's accent halo (`.rem-search input:focus` etc) with the resting
 * field shadow platform-wide. Moving the exclusions INSIDE `:where()` keeps them matching the same
 * elements while restoring the (0,0,1) specificity the rest of the cascade was built against. */
/* round 2 (G1 leftover 19/cleanup-list): `.sr-only` (a visually-hidden input -- ledger's `#month`,
 * `aria-hidden=true tabindex=-1`, no visible role in the page) joins the exclusion list here --
 * added to EVERY `:not()` chain in this field group, same discipline as `.bare-field`/checkbox/
 * radio/etc above: a hidden field taking the visible 36px field floor has no rendered effect today
 * (nothing shows it), but the WRONG computed min-height is still a latent trap for anything that
 * later measures it (a layout probe, a future visible reuse of the same input). */
input:where(:not(.bare-field):not(.sr-only):not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not(.switch)),
textarea:where(:not(.bare-field):not(.sr-only)) {
  background:var(--field-fill);border-color:transparent;
  box-shadow:var(--field-shadow);
  min-height:calc(var(--field-min-h) - 2*var(--field-pad-y) - 2*var(--field-bd-w));
}
/* fix round 2 (finding 3): round 1's comment above was only half true -- a PLAIN-TEXT field with
 * no `.w-*` width class is NOT actually content-box: `.composer .field>input:not(.w-date)...`
 * further down this file already forces `box-sizing:border-box` on it (it needs border-box so its
 * own `width:100%` fills the field column exactly), and todo.css's page-wide `*{box-sizing:
 * border-box}` forces it on every todo input regardless of `.composer`. Round 1's calc() above
 * assumes content-box for ALL of them, so these already-border-box fields (ledger #rname, map
 * #msearch/#plname/#pladdr, todo #tsearch -- none carry a `.w-*` width class) got the
 * content-box-sized subtraction applied a SECOND time on top of the border-box they already are,
 * landing at ~34px next to a 36px `<select>`/`.w-*` sibling in the same row. Declare box-sizing
 * explicitly here instead of leaving it to be inferred from whichever rule happens to win
 * elsewhere: every plain field WITHOUT a `.w-*` width class is pinned border-box (matching what
 * already governs it) and gets the FULL floor with no subtraction; a `.w-*` field is EXCLUDED
 * here so it keeps the content-box + subtracted-calc pair above (still correct for it, per the
 * evidence: w-amount/w-date/w-soft/time24 already land on 36px) -- box-sizing never touches its
 * `width:Nch`, so its typed-character room is unaffected. Same specificity as the composer-field
 * width rule below (0,7,1 from the seven `:not(.w-*)`), so cascade order there doesn't matter; higher
 * than the plain rule above, so it always wins over it regardless of source order. */
/* fix round (pool-gaps round 2, finding 2): same specificity-leak as the rule above -- the whole
 * `:not(.w-*)`/type-exclusion chain now sits inside the ONE `:where()` argument, dropping this
 * rule back to (0,0,1) (was (0,7,1)) so it loses to a module's own field rule again as intended. */
input:where(:not(.bare-field):not(.sr-only):not(.w-date):not(.w-time):not(.w-num):not(.w-amount):not(.w-short):not(.w-soft):not(.w-ccy):not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not(.switch)),
textarea:where(:not(.bare-field):not(.sr-only)) {
  box-sizing:border-box;
  min-height:var(--field-min-h);
}
select:where(:not(.select-quiet):not(.sr-only)){
  background-color:var(--field-fill);border-color:transparent;
  box-shadow:var(--field-shadow);
  min-height:var(--field-min-h);
}
/* fix round 1 (pool, finding 1): the two rules above exclude [type=checkbox]/[type=radio]/
 * [type=range]/[type=color] for min-height ONLY (a native checkbox must never take the 36px field
 * floor -- see that exclusion's own comment) -- but both properties were bundled in ONE :where()
 * list, so the exclusion also dropped their box-shadow, the definition ring every OTHER field
 * still carries. Base (pre-plain-glass) gave EVERY <input> this ring with no type exclusion at
 * all; this restores it for just these types, background/min-height untouched. `.switch` stays
 * excluded (it paints its own track fully; a ring was never part of its design here). Games
 * freeze: this reads --field-shadow, which the games-keep block below already re-pins to the
 * frozen 1px ring for its content scope -- no second re-pin needed (proof: scripts/
 * games_keep_ab.py, 0 diffs). background is deliberately NOT restored alongside it here: this
 * selector's specificity (0,1,1) is HIGHER than the games-keep background re-pin just below
 * (:where(...) input:where(...), (0,0,1)) and would out-rank it regardless of source order,
 * repainting a games checkbox's fill with the live (unfrozen) --field-fill instead of that re-
 * pin's --cell-solid -- background there is already correct via that lower-specificity rule and
 * needs no change. */
input:where(:not(.bare-field):not(.sr-only):not(.switch)):is([type=checkbox],[type=radio],[type=range],[type=color]){
  box-shadow:var(--field-shadow);
}
.tokenfield {
  background:var(--field-fill);border-color:transparent;
  box-shadow:var(--field-shadow);
}
input:where(:not(.bare-field)), select:where(:not(.select-quiet)), textarea:where(:not(.bare-field)), .tokenfield{ border-radius:var(--radius-sm); }

/* P2 pool: CHIP base -- a pill on the shared "track" fill (mock .chip), no border/ring. `.chip`/
 * `.aliaschip`'s own BOX MODEL (padding/font-size/border-radius) stays module-owned (orders/todo/
 * games/people each define it themselves -- unifying that geometry into one shared class is a pool
 * gap for a module leg, not this one); this only unifies the PAINT every one of them already got
 * from this file. `.badge` is deliberately NOT added here -- it was never painted by this file
 * before (module CSS gives it its own opaque `var(--panel)` fill already), so leaving it out keeps
 * this a pure like-for-like paint swap, not a new surface. Games freeze: `--chip-fill`/
 * `--chip-shadow` are re-pinned to the old opaque values under `.games-keep` below (games use both
 * `.chip` and `.tagchip`). */
.tagchip, .chip, .aliaschip {
  background:var(--chip-fill);border-color:transparent;
  box-shadow:var(--chip-shadow);
}

/* ================= GLASS-LITE (no blur) — buttons + the custom select-alike pickers =================
 * re-glass 2026-09-18 (owner's narrowed ask: "Use liquid-glass for action buttons"): buttons are
 * THE action-button control, so they get the full soft-glass rim/ring/edge back — no inner glow/
 * shade (§C) — those read as clutter on a small control. */
/* 2026-09-19 (todo-pool-recipes): `.ctl-quiet` LEFT this group -- it's the platform's OTHER quiet-
 * control idiom (see the quiet-controls sweep below), not an action button.
 * fix round 1 (2026-09-19): `:where(:not(.ctl-quiet))`, not bare `:not(.ctl-quiet)` -- same
 * specificity-inflation bug as the fields group above (`:not()` costs a class, `:where()` costs
 * nothing). The bare `button:not(.ctl-quiet)` (0,1,1) tied `.more-menu button{background:
 * transparent;box-shadow:none}` (0,1,1) and, being declared LATER in this file, silently started
 * winning that tie -- To-Do's own `#ldir`/`#lview` (plain buttons inside `.more-menu`, no class of
 * their own) regrew the glass pill/rim the quiet-controls sweep below had already turned off for
 * them. `:where()` restores the original (0,0,1) `button` specificity so every existing higher-
 * specificity module rule keeps deciding the tie exactly as it always did. */
/* P2 pool (2026-09-25): the button rim goes DIRECTIONAL, same recipe as chrome's own `.glass`
 * (`--glass-lit`: bright top-left / dim bottom-right + a .5px edge) instead of the old top-only
 * rim + 1px inner ring -- one soft drop shadow added since a button genuinely sits ABOVE the page,
 * unlike a field/chip. `background:var(--glass-fill-soft)` is UNCHANGED (test_glass_wiring's
 * test_chrome_is_plain_glass asserts this exact literal is still present). */
/* fix round (pool-gaps): `.bal-toolbar` dropped from this list -- the balance module renamed its
 * own container to `.bal-tb` (modules/lifeos/static/{balance.html,css/balance.css}) because this
 * button-glass paint was matching a plain layout row, not a themed button group; the entry here
 * matched nothing live any more and is removed to keep the pool source honest. */
button:where(:not(.ctl-quiet)), .btn,
#pfbar.sel {
  background:var(--glass-fill-soft);border-color:transparent;
  box-shadow:inset 0 0 0 1px var(--bd);
  -webkit-backdrop-filter:none; backdrop-filter:none;
}
button:where(:not(.ctl-quiet)), .btn{ border-radius:var(--radius-pill); }
/* box-inset-alignment 2026-09-17 (img4-add-ghost.png), narrowed by journal-redesign 2026-09-18:
 * a dashed ghost/add placeholder is its own chrome (a dashed border IS its rest-state affordance),
 * so the bare `button{}` GLASS-LITE rim/ring/edge above must not also draw on top of it. `.ghost-
 * box` (components.css) is now THE shared recipe every module's add-entry ghost carries — its own
 * class rule already sets box-shadow:none at higher specificity than the bare `button{}` type
 * selector, but it is listed here too so the "every dashed ghost is boxless" invariant stays
 * enumerated in ONE place rather than relying on a specificity margin. `.row-add .ghost`/
 * `.entry-card.row-add .ghost`/`.mghost` dropped from this list (2026-09-18): every consumer of
 * that markup now also carries `.ghost-box`, which covers them. Kept: .mod-ghost .ghost (lifeos
 * Today deck's hidden-module ghost — NOT this recipe, its own idiom) and
 * .ghost-add-btn (to-do's per-section "New To-Do" ghost row, contract item 6 — stays boxless even
 * though the button rim is back). An element that deliberately swaps to a solid border on open
 * (e.g. #addbtn[aria-expanded=true]) sets its own box-shadow back on, unaffected by this reset. */
/* fix round (pool-gaps-2, finding 30): `#addbtn` dropped from this selector list -- it is a GLOBAL
 * id, and two DIFFERENT pages each mint their own `#addbtn` (stocks' Portfolio ghost AND ledger's
 * `button#addbtn.primary` composer submit, modules/{stocks,ledger}/static/index.html). An id
 * selector's specificity (1,0,0) always beats `button.primary`'s own accent-rim recipe (0,1,1)
 * regardless of source order, so this reset silently stripped ledger's Add button's entire rim/
 * edge/glow, not just stocks' ghost's. `.ghost-box` (already in this SAME list, and already carried
 * by stocks' `#addbtn`, index.html) covers the intended stocks reset on its own -- `.ghost-box`
 * (0,1,0) already out-ranks the bare button rule two rules up (0,0,1), so nothing regresses for
 * stocks; `#addbtn[aria-expanded=true]`'s own higher-specificity (1,1,0) open-state override
 * (stocks' own app.css) is unaffected either way. */
.mod-ghost .ghost, .ghost-add-btn, .ghost-box{ box-shadow:none; }

/* ================= quiet controls sweep (2026-09-18, fix/quiet-controls-and-category-chips) =====
 * Owner: "adding placeholder, old design is better ... redundant box" + a second screenshot of
 * the To-Do sidebar's +Add List/Templates/Recently Deleted/Edit Views wearing a full glass pill —
 * "fix this as well". The dashed-ghost group above only covers DASHED placeholders; these are the
 * platform's OTHER quiet-control idiom — a plain <button> whose own module CSS already resets
 * border/background to read as a nav row, a list row, an inline row action, or a text-link
 * affordance, never a chrome control — and the bare `button{}` GLASS-LITE rim above draws on top
 * of every one of them because none of them happened to set box-shadow themselves (only
 * background/border have higher-specificity overrides). Found by grepping every module's CSS for
 * `border:0` + `background:none|transparent` with no accompanying box-shadow, then checking each
 * one's JS/HTML actually renders a <button> (an <a> or <input> isn't hit by the bare `button{}`
 * rule and needs nothing here). Enumerated, not a catch-all, same discipline as the dashed-ghost
 * group above — a future quiet control needs its own line here (or its own higher-specificity
 * box-shadow:none) or it will silently regrow the rim next time this file changes.
 *   nav / list rows: .side-foot button (to-do sidebar), .ask-tbtn (Ask thread list),
 *     .qvtoggle (stocks quick-view disclosure row)
 *   inline row actions: .mod-hide (Today module-deck hide), .pc-back/.pc-edit/.pc-add button
 *     (Contacts detail header + inline add), .msact/.lvlx (stocks milestone/level row actions),
 *     .xchev (orders row chevron),
 *     .pl-actions button (Saved Places row — shared by Map and the Today places widget)
 *   inline text-link affordances: .linkbtn (ledger), #lshowdone/.subtoggle/.done-h button
 *     (to-do), #pimportbtn (Contacts import), #asknew (Ask "new chat"), .adv-x/.rem-x (lifeos
 *     advisory/reminder dismiss), .cap-pin-chg (capture pin-offer "Change pin"), .earnlink
 *     (stocks "Earnings"), .lvladd (stocks "+ add level" — a ghost placeholder in spirit, just
 *     not dashed, same as the dashed-ghost group)
 *   platform-wide dismiss controls: .dock-close (floating dock panel, every page),
 *     .toast .toast-x, .toast .toast-action (global toast, every page)
 *   journal-redesign (2026-09-18) additions: .jr-row (sidebar category/tag/people/"Recent entries"
 *     rows), .jr-nav (mini-month ‹ › — a nav row's icon button, not a chrome control), .jr-day
 *     (mini-month day cells — same idiom as calendar's .mm-day/.day, added to THIS list rather
 *     than the day-grid-buttons rule below since journal's cells are plain buttons, not that
 *     rule's exact selector), .jr-clear ("Clear filters" text link), .a-tag/.a-person/.a-cat
 *     (entry meta tag/person/category text buttons, .a-cat added follow-up 2), #clearplace ("All
 *     places" text link)
 *   todo round 3 Addendum A (2026-09-18): .row>.info/#tmore (owner: "info button just [a plain
 *     glyph], no liquid-glass, no circle edge" — the bare button{} rim was drawing a filled glass
 *     disc behind the ⓘ on every row and the composer's own ⓘ)
 *   todo round 3 Build step 4 (2026-09-18): .tq-chip (the composer's Add Date/Add Location/#/Flag
 *     quick-add pills — quiet pills with their own var(--hover) fill, never a chrome button),
 *     .tq-clear (the × inside an active chip — owner: "no circle edges")
 *   todo round 3 planner review (2026-09-18): [role=menu] button — every buildMenu item (section
 *     "…", clear-completed, the new Add Date/Add Location menus) was wearing the generic glass
 *     pill; menu rows are plain, --hover on hover/focus only (module rule sets the rest)
 *   todo round 3 Addendum E (2026-09-18): #lclear ("Clear Completed ▾", moved off the retired
 *     bottom #donelist block's own heading into the bar's "N Completed" line — same text-link
 *     idiom as #lshowdone right next to it)
 *   2026-09-19 (todo-pool-recipes) REMOVALS: `.side-foot button`, `#lshowdone`, `#lclear`,
 *     `.subtoggle`, `.done-h button` and `[role=menu] button` are gone from this list — every node
 *     they matched now carries `.ctl-quiet` (todo.js/index.html), which supplies its own
 *     `box-shadow:none` as a FIXED property (and is excluded from the button-rim group above), so
 *     the sweep entry was redundant. `.row>.info`/`#tmore` (now `.info.info--btn`, a different pool
 *     row) and `.tq-chip`/`.tq-clear` (their own registered-but-unenforced idiom, todo.css) are
 *     NOT `.ctl-quiet` and stay listed.
 *   journal round 2 (2026-09-21) REMOVAL: `.a-btns button` (Journal entry edit/delete) is gone --
 *     those buttons now carry `.ctl-quiet` (journal.js), same reasoning as the 2026-09-19 removals
 *     above.
 */
.ask-tbtn, .qvtoggle,
.mod-hide, .pc-back, .pc-edit, .pc-add button, .msact, .lvlx, .xchev, .pl-actions button,
.linkbtn, #pimportbtn, #asknew, .adv-x, .rem-x, .cap-pin-chg, .earnlink, .lvladd,
.dock-close, .toast .toast-x, .toast .toast-action,
.jr-row, .jr-nav, .jr-day, .jr-clear, .a-tag, .a-person, .a-cat, #clearplace,
.row>.info, #tmore, .tq-chip, .tq-clear{
  box-shadow:none;
}

/* primary button (§G, fault #6: no gradient, ever) — flat accent tint through blur, with its own
 * accent-tinted rim/ring/shade/edge/glow so it reads as glass tinted by the accent colour.
 * fix round (pool-gaps-2, finding 33): `font-weight` was missing entirely -- DESIGN.md's own
 * "Primary" bullet already documented "weight 650 — never a gradient, never a solid swatch", but
 * no rule ever implemented it, so every primary (Settings Save, ledger's #addbtn, #capgo, ...)
 * rendered the base button row's plain 400. Games freeze: grepped clean, no games page uses
 * `.primary`/`type=submit`/an implicit-submit `<button>`, so no games-keep counterpart is needed
 * (confirmed by scripts/games_keep_ab.py -- font-weight is already in its PROPS list, 0 diffs). */
button.primary, .btn.primary, form button[type=submit], form button:not([type]){
  background:color-mix(in srgb, var(--accent) 16%, transparent);
  color:var(--accent); border-color:transparent;
  -webkit-backdrop-filter:none; backdrop-filter:none;
  box-shadow:none;
  font-weight:var(--fw-strong);
}
/* round 2 (G1 leftover 13): the hover step for these same four selectors used to live in
 * components.css as `filter:brightness(1.07)` -- a hazard now that every one of them carries
 * `backdrop-filter` above (this file's own Safari-first rule: never combine `filter` +
 * `backdrop-filter` on one element, WebKit drops the frost). A background swap instead, reusing the
 * SAME brighter accent-fill step `:active`/`.pressed` already use further down this file (the
 * "pressed accent tint" section) -- hover and press read as one continuous brightening rather than
 * two different tints, and no new token is needed. `:active`/`.pressed`'s own rule is declared
 * LATER in this file and has the same class-count specificity, so it still wins that tie and
 * brightens further on an actual press, same as before.
 * fix round (pool-gaps-2, finding 28): `:where(:not(:disabled))` added -- `:hover` DOES still match
 * a native `:disabled` button in both WebKit and Chromium (disabled blocks ACTIVATION/click, not
 * hover-state matching; verified live: a synthetic `<button class=primary disabled>`'s
 * backgroundColor visibly changed on a real pointer hover in both engines before this fix), so a
 * disabled primary brightened under the pointer exactly as if it were clickable. `:where()`, not a
 * bare `:not()` -- same specificity-inflation hazard as the base button-hover rule's own comment
 * (components.css) -- keeps this rule's specificity exactly what it was, so it still loses the
 * SAME ties it always did (e.g. a games-keep re-pin declared at equal-or-lower specificity). */
@media (hover:hover){ button.primary:where(:not(:disabled)):hover, .btn.primary:where(:not(:disabled)):hover, form button[type=submit]:where(:not(:disabled)):hover, form button:not([type]):where(:not(:disabled)):hover {
  background:color-mix(in srgb, var(--accent) 24%, transparent);
} }

/* ================= press: iOS lift + bloom (owner 2026-09-11; restored 2026-09-18) =================
 * iOS 26 Liquid Glass press: touch-down LIFTS the control (scale up, not the old .97
 * shrink), the rim/lens brighten and a light disc blooms out of the touch point; release springs
 * back with --ease-spring overshoot. Two selectors carry it: :active (pure CSS) and .pressed
 * (glass.js -- holds a quick tap for a minimum beat and sets --gx/--gy from the pointer so the
 * bloom starts under the finger on touch too). Tokens: --press-scale/--dur-press/--glass-*-press
 * (tokens.css); reduced motion pins --press-scale to 1 so it only brightens. `.rail-i`/`.tab` stay
 * in this selector group (unchanged since before the 2026-09-18 de-glass round -- a nav row/tab
 * still lifts+brightens on press even though its REST fill stays flat opaque chrome; only the
 * bars/rails FILL is excluded from this round's restore, not this shared press animation). */
button, .btn, .rail-i, .tab{
  transition:transform var(--dur-morph) var(--ease-spring), box-shadow var(--dur) var(--ease-out),
             background var(--dur) var(--ease-out), filter var(--dur) var(--ease-out);
}
/* fix round (pool-gaps-2, finding 28): `:where(:not(:disabled))` on every button/.btn selector
 * below -- `:active` DOES still match a native `:disabled` button in both WebKit and Chromium
 * (verified live: a synthetic disabled `<button class=primary>` reported
 * `matches(':active')===true` and a `transform:matrix(1.05,...)` under a real mousedown before this
 * fix, in both engines), so a disabled control still visually lifted/brightened as if it were
 * pressable. `.rail-i`/`.tab` are never form controls (`<a>`/nav rows), so `:disabled` can never
 * match them -- left as-is. `:where()`, not a bare `:not()` -- the same specificity-inflation
 * hazard as every other carve-out in this file (`:not()` costs the specificity of its own argument,
 * a pseudo-class = a class): a bare `:not(:disabled)` here raised `button:active`/`button.pressed`
 * from (0,1,1) to (0,2,1), which then out-ranked `.gswitch>button`'s own (0,1,1) rest-state rule
 * declared LATER in this same file -- found live via `scripts/games_keep_ab.py --hover`, the games
 * switcher regrew a press lift/scale it should never have gotten (base gives it none). `:where()`
 * restores the original (0,1,1)/(0,2,0) math so every existing same-or-higher-specificity rule
 * keeps deciding the tie exactly as it always did. */
button:where(:not(:disabled)):active, .btn:where(:not(:disabled)):active, .rail-i:active, .tab:active,
button.pressed:where(:not(:disabled)), .btn.pressed:where(:not(:disabled)), .rail-i.pressed, .tab.pressed{
  transform:scale(var(--press-scale));
  transition:transform var(--dur-press) var(--ease-out), box-shadow var(--dur-press) var(--ease-out),
             background var(--dur-press) var(--ease-out), filter var(--dur-press) var(--ease-out);
  --glass-rim:var(--glass-rim-press); --glass-lens:var(--glass-lens-press);
  --glass-fill-soft:var(--glass-fill-press);
}
/* nested-backdrop hazard (2026-09-25): `filter:brightness()` on the SAME element that also carries
 * `backdrop-filter` makes WebKit drop the frost mid-press -- so the brighten step is excluded for
 * the backdrop-filter carriers (`.dock-btn`/`button.primary`/`.glass`, incl. `#docksearch`, which
 * carries `.dock-btn`); their lift still scales + re-tints via the rim/fill custom properties above.
 * fix round (pool-gaps-2, finding 28): `:where(:not(:disabled))` joins the existing `:not(...)`
 * chain here too, same reasoning/proof as the rule above.
 * post-pass (2026-09-26, r2 final review finding 2): `form button[type=submit]`/`form
 * button:not([type])` (the primary rule above, line ~615) ALSO carry `backdrop-filter` -- a plain
 * unclassed submit button (e.g. #tsubmit inside #todoform) matches neither `.primary` nor `.glass`
 * nor `.dock-btn`, so it kept the brighten `filter` on press, the exact same hazard this comment
 * already names. `:where(:not(form button[type=submit]):not(form button:not([type])))` excludes
 * them too -- `:where()` (zero specificity) so the exclusion doesn't inflate this selector's own
 * specificity past what finding 28's fix above already pinned it to. */
button:active:where(:not(:disabled)):not(.dock-btn):not(.primary):not(.glass):where(:not(form button[type=submit]):not(form button:not([type]))),
.btn:active:where(:not(:disabled)):not(.primary):not(.glass):where(:not(form button[type=submit]):not(form button:not([type]))),
.rail-i:active, .tab:active,
button.pressed:where(:not(:disabled)):not(.dock-btn):not(.primary):not(.glass):where(:not(form button[type=submit]):not(form button:not([type]))),
.btn.pressed:where(:not(:disabled)):not(.primary):not(.glass):where(:not(form button[type=submit]):not(form button:not([type]))),
.rail-i.pressed, .tab.pressed{
  filter:brightness(1.06) saturate(1.05);
}
@media (prefers-reduced-motion:reduce){
  button, .btn, .rail-i, .tab{ transition:none; }
}

/* table header bar (thead / caption-label): a table head is content, opaque like the cell it sits over */
.tbl th, thead{ background:var(--cell-solid); }

/* ================= A5 "one select UI": custom pickers that behave like a select ================
 * A native <select> already gets the shared recipe for free (input,select,textarea group above).
 * These are the CUSTOM (details/menu-pattern) pickers that behave like one but aren't: stocks'
 * Columns dropdown and games' game-switcher. Their CLOSED box now matches <select>'s box model
 * exactly (height/padding/radius via --field-pad-x, --field-pad-y and --radius-sm, plus a chevron); their OPEN menu
 * is opaque + shadow (.colpick-menu/.gs-menu, in the menus/dialogs/popovers list above — menus stay
 * flat under this round's narrower scope, only the CLOSED control is a glass button). */
#colpick>summary, .gswitch>button{
  padding:var(--field-pad-y) var(--field-pad-x);border-radius:var(--radius-sm);
  background-color:var(--glass-fill-soft);border-color:transparent;
  box-shadow:inset 0 var(--glass-rim-w) 0 var(--glass-rim), inset 0 0 0 1px var(--glass-ring), 0 0 0 1px var(--glass-edge);
}

/* ================= selected states — flat tint, never a rim =================
 * `--glass-tint` (kept, "the good part"): a flat, unblurred accent tint is still the shared
 * selection idiom for the rail's current-page row and active chips. */
.tab[aria-current=page]{
  color:var(--fg);font-weight:var(--fw-strong);
}
/* fix round 1 (finding 10): the plan calls for the current rail item's own LABEL text tinted
 * toward the accent (not just its icon, restored below) -- head had left it plain --fg.
 * fix round 2 (finding 2): 75% accent failed AA (14px/650 needs 4.5:1, not "large text") for
 * several palette/mode combos once composited over the real rail-over-ground-then-tint stack --
 * classic dark 4.25:1, nord dark 4.26:1, slate dark 4.43:1, mocha light 4.40:1 (measured: same
 * sRGB alpha-blend + WCAG relative-luminance math as aa.py, against every one of the 15
 * palette×mode combinations, not just the three the evidence named). Raising the --fg share to
 * 40% (accent 60%) clears every combination with margin -- worst case (nord dark) lands at
 * 4.64:1 -- while still reading visibly accent-tinted next to the icon beside it. */
.rail-i[aria-current=page]{
  background:var(--glass-tint);box-shadow:none;
  color:color-mix(in srgb, var(--accent) 40%, var(--fg));font-weight:var(--fw-strong);
}
/* R3 A2 (2026-09-27, owner #3 AA re-check): the rail lens's own material changed from a flat
 * --glass-tint to the SAME rim/edge/shadow-carrying --seg-thumb-fill every other lens uses (see
 * this leg's own comment on `.rail-scroll .lens` further down) -- a real composited-pixel re-
 * measurement against the NEW background (GPU Chromium, all 8 DARK_PALETTES x light/dark) found
 * the fix-round-2 60% accent mix now under 4.5:1 in three dark combos (classic 3.84, slate 4.09,
 * nord 4.09 -- the new fill reads darker there than the old flat tint did). Dropping accent's own
 * share to 40% (fg 60%) clears all 16 combinations with margin (worst: nord dark 4.57, was 4.10) --
 * every LIGHT combination has 8-17:1 of headroom regardless of this ratio, so only dark mode ever
 * constrained the choice. The icon's own colour (`--accent`, unmixed, below) is untouched -- it
 * still reads as the stronger accent cue next to a more subtly tinted label. */
.rail-i[aria-current=page] .rail-ico,
.tab[aria-current=page] .tab-ico{ color:var(--accent); }
.chip.active, .tagchip[aria-pressed=true]{
  background:var(--glass-tint);color:var(--fg);border-color:transparent;
}
/* the sliding thumb (glass.js's adoptSeg) owns the highlight now — the marker element's own
 * background goes transparent so the thumb shows through it */
.addseg button[aria-pressed=true],
.tabs .tab.active, .tabs .tab-btn[aria-selected=true],
.seg-lbl input:checked+span,
.viewsw button[aria-pressed=true],
.qc-seg button[aria-pressed=true],
.seg>button[aria-checked=true]{
  background:transparent;color:var(--fg);
  /* G3 motion round-1-gate finding 4: the mock's `.seg span.on{font-weight:650}` never made it
   * onto this shared "selected" rule -- every segmented item read the same 400 weight whether
   * selected or not (ccyseg had carried 650 before this round; viewseg never did). One weight,
   * every consumer, matching the mock. */
  font-weight:var(--fw-strong);
}

/* ================= segmented-control thumb = a PLAIN glass pill (2026-09-25) =================
 * Plain liquid-glass replaces the droplet lens outright, at every width (the old phone-vs-desktop
 * split, the `.moving`/`:has(~...)` droplet trigger, and the SVG light/refraction filters are all
 * gone -- glass.js now inserts one CHILDLESS `<i class=seg-thumb aria-hidden=true>`; see the
 * nested-backdrop guard at the top of this file for why it carries no `backdrop-filter` of its
 * own). Same WRAPPER contract as before: transform/size only, painted ABOVE the flat items. */
/* P2 pool: the container itself carries the mock's flat "track" fill at EVERY width now (desktop
 * used to be fully transparent, only the phone `.glass-cap` capsule further down painted anything
 * behind the thumb). Games freeze: `--seg-track` is re-pinned to `transparent` under `.games-keep`
 * below (only planner.html's `[role=tablist]` tab strip actually matches this selector list in
 * games).
 * fix round 1 (finding 13): P2 deliberately shipped this track with NO padding ("adding container
 * padding at every width would reflow the many already-tuned segmented controls this selector
 * list covers") -- verified against the mock instead, the thumb's own 3px top/bottom inset
 * (`.seg-thumb` below) was never matched by any horizontal inset, so the thumb touched the
 * track's left/right edges at desktop widths (0px vs 3px). `padding:3px` reflows nothing: an
 * absolutely-positioned child (`.seg-thumb`) is placed against the CONTAINER'S PADDING EDGE
 * regardless of the padding value, and every item's `--thumb-w`/`--thumb-x` is measured live off
 * real layout (glass.js's adoptSeg), so it tracks the new inset automatically -- only the items
 * (in normal flow) shift inward by 3px, matching the mock's `.seg{padding:3px}`. */
.addseg, nav.tabs, .seg, .viewsw, .qc-seg, [role=tablist]{ position:relative; background:var(--seg-track);border-radius:var(--radius-pill);padding:3px; }
/* R3 A2 (2026-09-27, owner #1 "lens stuck in toolbar space, it should be above it" + owner #7 "All
 * lens edge must align with the whole bar edge... curving part"): the draggable lens must both LIFT
 * clear of its own bar (never clipped) and stay a genuine single-row pill at rest (a wrapped track
 * cannot be concentric with a pill lens). Both are FIXED properties of the track now -- games
 * content is excluded on the SUBJECT per CLAUDE.md's rule (a module's own more-specific override,
 * e.g. Stocks' page-row `.tabs{flex-wrap:wrap}`, is out-ranked by this rule's `nav.tabs` compound
 * (0,1,1) regardless of load order, exactly as intended -- no module edit needed to win the tie).
 * fix (R3 F1): `min-width:0` belongs on the TRACK ITSELF here, not only on its children (below) --
 * a nested flex box (this track is a flex item of its own page-row ancestor, and a flex container
 * for its own tab/button children) has `min-width:auto` by default at BOTH levels; the children's
 * own `min-width:0` (below) never gets exercised while the track itself still refuses to shrink
 * below its content's natural width inside ITS parent. Stocks' `#tabs` sat at an identical
 * 356.6px rect at 390/360/320px -- adding this one property let the track shrink with its parent
 * and the ellipsis/ shrink rules below finally engage. */
.addseg:where(:not(.games-keep *)), nav.tabs:where(:not(.games-keep *)), .seg:where(:not(.games-keep *)), .viewsw:where(:not(.games-keep *)), .qc-seg:where(:not(.games-keep *)), [role=tablist]:where(:not(.games-keep *)){
  overflow:visible;flex-wrap:nowrap;min-width:0;
}
/* items may shrink instead of wrapping/pushing the track wider; a label that still doesn't fit
 * ellipsizes as a last resort rather than pushing the whole pill into a second row. */
.addseg:where(:not(.games-keep *))>*, nav.tabs:where(:not(.games-keep *))>*, .seg:where(:not(.games-keep *))>*, .viewsw:where(:not(.games-keep *))>*, .qc-seg:where(:not(.games-keep *))>*, [role=tablist]:where(:not(.games-keep *))>*{
  min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.seg-thumb{
  position:absolute;top:3px;bottom:3px;left:0;z-index:0;pointer-events:none;
  width:var(--thumb-w,0px);border-radius:var(--radius-pill);
  transform:translateX(var(--thumb-x,0px));
  background:var(--seg-thumb-fill);
  box-shadow:inset 1px 1.2px 0 0 var(--glass-rim), 0 0 0 .5px var(--glass-edge), 0 2px 6px var(--glass-thumb-shadow);
}
@media (prefers-reduced-motion:no-preference){
  .seg-thumb{ transition:transform var(--dur-morph) var(--ease-spring), width var(--dur-morph) var(--ease-spring); }
}
.addseg button, nav.tabs .tab, #tabbar .tab, .seg-lbl, .viewsw button, .qc-seg button, [role=tab], .seg>button{ position:relative;z-index:1; }
/* fix round 1 (finding 6): the base `button:where(:not(.ctl-quiet)), .btn` glass-lite rule (above)
 * still paints every plain `<button>` with the soft-glass fill/rim/shadow, including an UNSELECTED
 * segmented item -- previously reset to transparent only inside the phone `@media(max-width:48rem)`
 * capsule block below, so a desktop control (Settings' Layout/Material, Ledger's view switch) drew
 * every item as its own near-identical raised pill, with only the current one going transparent
 * over the thumb. This selector's specificity (0,1,1) already out-ranks that bare-type rule
 * (0,0,1) regardless of width -- the phone-only scoping was the only thing keeping it from
 * applying everywhere, so this one rule now covers both, and the media block below drops its own
 * now-redundant copy (keeping only its `.glass-cap` entries, which have no non-phone equivalent).
 * fix round (pool-gaps, finding "role=radio seg item keeps the bare-button rim"): a `.seg`
 * consumer whose items are plain `<button role=radio>` (ledger's `#ccyseg`, `aria-checked` not
 * `aria-pressed`/`aria-selected`) matched none of the named selectors above and kept the full
 * glass-lite rim on every item -- `.seg>button` covers ANY direct-child button of the generic
 * `.seg` track (not just the named `.addseg`/`.viewsw`/`.qc-seg` consumers), so a future role=radio
 * segmented control gets this for free instead of needing another per-name addition. */
/* fix round (pool-gaps, finding 2): `border` was missing from this reset -- a plain <button>
 * item (unlike `.seg-lbl span`, which was never a bordered element) still carries the base pool
 * `button,.btn{border:1px solid var(--bd)}` box model even once glass.css's own bare-button rule
 * turns that border's COLOR transparent (border-color, not border-width) -- an invisible but still
 * SPACE-TAKING 1px top+bottom border, 2px taller than a same-track sibling item that never had a
 * border at all (ledger's #viewseg/#ccyseg mismatch: 37px vs 35px). */
.addseg button, nav.tabs .tab, .seg-lbl span, .viewsw button, .qc-seg button, [role=tab], .seg>button{
  background:transparent;box-shadow:none;border:0;
}
/* G3 motion round-1-gate finding 1: `.seg-lbl span` had NO border-radius of its own (unlike a
 * `<button>` item, which inherits the pool button's pill radius before this rule turns its fill
 * transparent) -- its hover fill (`.seg-lbl:hover input:not(:checked)+span{background:var(--hover)}`
 * below) painted a square-cornered block inside the pill-shaped track next to the lens. The mock's
 * `.seg span` items are radius 999px. */
.seg-lbl span{ border-radius:var(--radius-pill); }
/* fix round (pool-gaps, finding 2): a segmented item is a real tap target (Day/Week/Year, ccyseg,
 * Settings' Layout/Material presets) -- under a coarse pointer it needs the platform's usual 44px
 * floor even where a module's own OPEN min-height/padding declares a denser desktop density
 * (ledger's `#ccyseg button{min-height:0}` and similar).
 * fix round (pool-gaps-2, finding 20): `.seg-lbl` (the `<label>` wrapping a radio-segmented item's
 * hidden `<input>` + visible `<span>` pill -- ledger's `#viewseg`, the actual clickable hit target
 * since a native label forwards a click anywhere inside it to the input) was missing from this
 * list -- an id-scoped sibling track (`:where(#ccyseg) button`, ledger's own app.css) already
 * out-specified this rule to reach 44px on its OWN items, but `.seg-lbl` never matched any selector
 * here at all, so the two tracks sat at mismatched touch heights (29px vs 44px) on the same row. */
@media (pointer:coarse){
  .addseg button, nav.tabs .tab, .viewsw button, .qc-seg button, [role=tab], .seg>button, .seg-lbl{
    min-height:44px;
  }
}
/* round 2 (G1 leftover 14): Stocks' `#tabs` (a `nav.tabs`, so it matches the coarse floor above)
 * reparents INTO the shell topbar on desktop (>=64rem, app.js, CLAUDE.md's "Section tabs live in
 * the shell topbar" bullet) -- a fixed, dense 54px chrome bar (`--shell-h`); a 44px-tall tab item
 * there leaves only ~2px clearance top+bottom, versus base/prev's 32px. Exempted ONLY at that same
 * desktop breakpoint -- below it `#tabs` sits back on the page as an ordinary segmented control and
 * should still grow to 44px under a coarse pointer like every other one. `#tabs .tab`'s ID
 * specificity (1,1,0) already beats the plain-class base rule regardless of source order. */
@media (min-width:64rem) and (pointer:coarse){
  #tabs .tab{ min-height:0; }
}
/* fix round (pool-gaps, finding "no hover feedback on unselected seg items"): an UNSELECTED
 * segmented item (Day/Week/Year/Schedule, Settings' Layout/Material presets, ccyseg) showed no
 * visual change on hover -- the selected item is excluded so hovering it doesn't flash over the
 * sliding `.seg-thumb`'s own transparent-background contract above.
 * post-pass (2026-09-26, item G): this is a genuinely NEW affordance ("no hover feedback... showed
 * no visual change", this comment's own words) with no pre-2026-09-25 games behaviour to match --
 * the same shape of leak `select:hover` (components.css:246) already had, and fixed the same way:
 * `:where(:not(.games-keep *))` on each selector's SUBJECT (the compound that's actually being
 * matched/painted -- `span` for the last one, not `.seg-lbl`), `:where()` so the exclusion adds
 * ZERO specificity and can't shift any existing precedence for a non-games consumer (verified:
 * ledger #ccyseg, Settings' Layout/Material presets, Calendar's view switch and Stocks' [role=tab]
 * strip all measured byte-identical computed hover background before/after). Found live: story-
 * line's own phone-only `#slcolseg` (a `.addseg`) picked up this hover tint at 390 in every
 * material/engine, with no games-keep counterpart -- exactly the open-ended "re-pin every module's
 * own value by hand" trap the select:hover fix's own comment already names, so fixed at the source
 * here too rather than chasing a per-page re-pin. */
@media (hover:hover){ .addseg button:not([aria-pressed=true]):where(:not(.games-keep *)):hover, nav.tabs .tab:not(.active):where(:not(.games-keep *)):hover, .viewsw button:not([aria-pressed=true]):where(:not(.games-keep *)):hover, .qc-seg button:not([aria-pressed=true]):where(:not(.games-keep *)):hover, [role=tab]:not([aria-selected=true]):where(:not(.games-keep *)):hover, .seg>button:not([aria-checked=true]):where(:not(.games-keep *)):hover, .seg-lbl:hover input:not(:checked)+span:where(:not(.games-keep *)) {
  background:var(--hover);
} }

/* ================= capsule groups (`.glass-cap`) — phone only ================= */
/* re-glass 2026-09-18: every segmented group glass.js adopts, plus the Stocks #pfbar row and the
 * Ledger month prev/next pair (`.glass-cap`), is an action-BUTTON group -- restored to the
 * clearer-than-desktop level-3 "Regular" material (--cap-fill/--cap-blur). The material lives on a
 * ::before sibling layer, NEVER on the group element itself (same nesting rule as the lens above).
 * Items inside stay flat (no fill/rim of their own); a hairline divider sits between flat items.
 * #tabbar gets the identical ::before treatment ABOVE for its lens thumb's z-index reasons only —
 * #tabbar's own bar fill (the ::before layer's material) is NOT restored here (it stays the plain
 * opaque `--bg-solid` rule further up); #tabbar simply isn't in this selector list. */
@media (max-width:48rem){
  /* .glass-cap needs its own position:relative here -- the other selectors already get it from
   * the un-gated `.addseg, nav.tabs, .seg, .viewsw, .qc-seg, [role=tablist]{position:relative}`
   * rule above (shared with the lens section, so it also anchors on desktop widths where no
   * capsule material is painted). */
  .glass-cap{ position:relative; }
  /* 2026-10-24 segment item: track = 26px item + 2 x 3px at every width */
  .addseg, nav.tabs, .seg, .viewsw, .qc-seg, [role=tablist]{
    border-radius:var(--radius-pill);padding:3px;overflow:visible;
  }
  .glass-cap{
    border-radius:var(--radius-pill);padding:var(--sp-1);overflow:visible;
  }
  .addseg::before, nav.tabs::before, .seg::before, .viewsw::before, .qc-seg::before,
  [role=tablist]::before, .glass-cap::before{
    content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:0;
    background:var(--cap-fill);
    -webkit-backdrop-filter:blur(var(--cap-blur)) saturate(var(--glass-sat));
    backdrop-filter:blur(var(--cap-blur)) saturate(var(--glass-sat));
    box-shadow:inset 0 var(--glass-rim-w) 0 var(--glass-rim), inset 0 0 0 1px var(--glass-ring),
               inset 0 0 var(--glass-lens-r) var(--glass-lens), inset 0 -1px 0 var(--glass-shade),
               0 0 0 1px var(--glass-edge), var(--glass-shadow);
  }
  /* fix round 1 (finding 6): the segmented-item half of this reset now lives outside this media
   * block (applies at every width) -- `.glass-cap` has no non-phone equivalent, so only it stays
   * gated here. */
  .glass-cap button, .glass-cap .iconbtn{
    background:transparent;box-shadow:none;
  }
  /* a STATIC item paints underneath the capsule's positioned ::before material and washes out;
   * every direct child of a plain .glass-cap gets the same z-index:1 the adopted groups' items
   * already carry (the .addseg button / nav.tabs .tab / [role=tab] rule near the lens). */
  .glass-cap>*:not(.seg-thumb):not(.lens){ position:relative;z-index:1; }
  /* G3 motion round-1-gate finding 3: this divider used to be a `box-shadow`, which respects an
   * item's OWN border-radius (kept deliberately, so a button item's hover reads as a rounded pill
   * -- round-1-gate finding 1) -- so it curved into a ring around each phone segmented item's
   * corners instead of drawing one straight track hairline. A `::before` line carries no radius of
   * its own and nothing here clips it (the track has no overflow:hidden), so it stays straight
   * regardless of the item's own rounded hover/press state. `:not(.lens)` joins `:not(.seg-thumb)`
   * in every one of these selectors -- the draggable lens sits in this exact same first-child slot
   * now and must never itself be treated as "a real item" for z-index or divider purposes. */
  .addseg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens), nav.tabs>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  .seg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens), .viewsw>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  .qc-seg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens), [role=tablist]>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  .glass-cap>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens){
    position:relative;
  }
  .addseg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before, nav.tabs>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  .seg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before, .viewsw>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  .qc-seg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before, [role=tablist]>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  .glass-cap>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before{
    content:"";position:absolute;left:-1px;top:15%;bottom:15%;width:1px;background:var(--bd);pointer-events:none;
  }
  /* #pfbar carries its OWN soft-glass fill on every breakpoint (the GLASS-LITE button rule above)
   * -- reset it here so the ::before capsule layer is the only material, not a second flat fill
   * stacked behind it. (.topbar itself is NOT reset here, unlike the 2026-09-11 original: .topbar
   * stays the plain opaque bars/rails fill on every width under this round's narrower scope, so
   * its own capsules sit on top of an opaque bar rather than needing to clear their own material.) */
  #pfbar.sel{ background:transparent;box-shadow:none; }
}

/* ================= the draggable lens (§4, G3 motion, 2026-09-25) =================
 * Owner rulings 6-7, verbatim: "For all toolbar, make it dragable, and use bouncy animation and
 * all as created in liquid-glass-web-ui" / "draggable mean drag the lens on the bar." Ported from
 * the approved kit reference ($S/glass-kit/motion.js + its glass-kit.html, itself a port of
 * liquid-glass-web-ui's tab bar, refract:false -- no refraction anywhere, ruling 1 stands): a
 * spring-driven `<i class=lens>` (glass.js's glassBar()) REPLACES the plain, static `.seg-thumb`
 * above on every pooled switcher, the phone `#tabbar` and the rail -- `.seg-thumb` itself is
 * UNTOUCHED and stays live as the frozen, non-interactive marker a games page's own content
 * toolbars keep (glass.js's isGamesContent()). No filter/backdrop-filter here: the lens sits
 * inside a track/chrome that already carries one (the nested-backdrop guard at the top of this
 * file). All of its motion is transform/width, painted by glass.js every frame -- nothing here
 * declares a CSS `transition` for it, so the JS spring is the only thing moving it. */
.lens{
  --lens-fill:var(--seg-thumb-fill);
  position:absolute;z-index:0;left:0;top:0;width:0;height:0;
  border-radius:var(--radius-pill);pointer-events:none;will-change:transform;
  background:var(--lens-fill);
}
.lens-glow{ position:absolute;inset:0;border-radius:inherit;opacity:0;pointer-events:none; }
/* R3 A2 (2026-09-27, owner #3, verbatim "use liquid-glass-web-ui lens as well"): the rail lens used
 * to override --lens-fill to a flat --glass-tint (no rim/edge/shadow at rest) instead of the SAME
 * thumb material every other lens bar carries -- that override is gone; the rail lens now falls
 * through to the base `.lens{--lens-fill:var(--seg-thumb-fill)}` and `barOptsFor()`'s `flat:false`
 * (glass.js) so it always paints the rim/edge/lift-shadow like the switcher lens. The current row's
 * own text/icon tint (accent-mixed label, `--accent` icon, `--fw-strong`) is untouched -- it lives
 * on `.rail-i[aria-current=page]` below, decoupled from the lens by that rule's own
 * `background:transparent` (the lens paints the highlight now, that rule only keeps the colour). */
/* the rail row -- like every other adopted item -- must paint ABOVE the lens (z-index:0): this
 * item is a plain <a>, not a <button>, so it never picked up the shared item z-index rule near
 * the top of the segmented-thumb section above (that list is button/tab-shaped items only). */
.rail-i{ position:relative;z-index:1; }
/* the rail lens replaces this row's own static tint (glass.js paints it underneath instead) --
 * text/icon colour stay exactly as before (the AA-tuned accent-mix, see the comment above). */
.rail-i[aria-current=page]{ background:transparent;box-shadow:none; }
/* early rail (owner 2026-10-19): the rail is drawn in the page's first frame, before deferred glass.js
 * adopts it -- until its lens exists (`.lensed`, glassBar) the current row wears the lens's own rest look
 * (same fill, rims, edge, pill), so the lens takes over without a visible change. */
.rail-scroll:not(.lensed) .rail-i[aria-current=page]{
  background:var(--seg-thumb-fill);border-radius:var(--radius-pill);
  box-shadow:inset 1px 1.2px 0 0 var(--glass-rim), inset -1px -1.2px 0 0 var(--glass-rim2), 0 0 0 .5px var(--glass-edge), var(--shadow-1);
}
/* phone tab bar, same early-paint rule (owner 2026-10-24 "lens on navbar is slow"): glass.js is deferred, so until glassBar
 * adds `.lensed` the current tab wears the lens's own rest look in the page's FIRST frame. The pill is the lens box: the bar's
 * content box (44px inside the 60px bar), i.e. the tab's full-height hit area (margin -8px) inset 8px top and bottom. */
@media (max-width:48rem){
  #tabbar:not(.lensed) .tab[aria-current=page]::before{
    content:"";position:absolute;inset:var(--sp-2) 0;z-index:-1;pointer-events:none;
    background:var(--seg-thumb-fill);border-radius:var(--radius-pill);
    box-shadow:inset 1px 1.2px 0 0 var(--glass-rim), inset -1px -1.2px 0 0 var(--glass-rim2), 0 0 0 .5px var(--glass-edge), 0 2px 6px rgba(0,0,0,.12);   /* glassBar's lift-0 shadow, literal */
  }
}
/* drag affordance: only the CURRENTLY selected/active item is draggable (glassBar's onDown bails
 * on any other item, "the grabbable current item only") -- only it gets the grab cursor, and the
 * touch-action that still lets an ORDINARY touch scroll the page/rail pass through one that never
 * turns into a drag (a horizontal bar keeps vertical page scroll working, `pan-y`; the vertical
 * rail keeps horizontal scroll working, `pan-x`). */
.addseg button[aria-pressed=true], .viewsw button[aria-pressed=true], .qc-seg button[aria-pressed=true],
.seg>button[aria-checked=true], nav.tabs .tab.active, nav.tabs .tab-btn[aria-selected=true],
[role=tab][aria-selected=true], .seg-lbl input:checked+span{
  touch-action:pan-y;cursor:grab;
}
.rail-scroll a.rail-i[aria-current=page]{ touch-action:pan-x;cursor:grab; }
/* R3 F4 (2026-09-27): the rail row and phone tab-bar item are real `<a href>` elements (the only
 * two lens bars built from links, not buttons) -- a real `<a href>` is natively draggable in
 * Chromium, so a mousedown+move on the lens's grabbable current item started an HTML link drag
 * instead of continuing glass.js's own pointer-based drag, cancelling the pointer stream (the
 * lens never left rest height, the drop never navigated). shell.js sets `draggable=false` (the
 * IDL property) on every item at build time; `-webkit-user-drag:none` here is the CSS-property
 * half of the same opt-out (belt and suspenders across engines), plus `-webkit-touch-callout:none`
 * so a press-and-hold to drag the lens on iOS doesn't also raise the link-preview callout menu
 * (the liquid-glass-web-ui lab's own `.tab` rule carries this same property). Scoped to exactly
 * these two link-based item classes -- every OTHER lens bar (`.addseg`/`nav.tabs`/`.seg`/
 * `.viewsw`/`.qc-seg`/`[role=tablist]`) is built from `<button>`s, which were never natively
 * draggable and need none of this. */
.rail-i, #tabbar .tab{ -webkit-user-drag:none;-webkit-touch-callout:none; }
.bar-dragging, .bar-dragging *{ cursor:grabbing!important;-webkit-user-select:none;user-select:none; }
.is-near{ color:var(--accent); }
/* reduced motion = SNAPS, no bounce (owner rulings 6-7's own scope) -- dragging itself is
 * unaffected, only glass.js's spring/lift/drift/stretch math collapses to instant assignment
 * (every rm() check in glassBar); nothing here needs to change the grab/grabbing cursors above. */

/* ================= morph (§3) — panels grow out of their anchor ================= */
/* R3 A3 (owner #2): `.shellpanel` left this shared transform-origin group -- its own morph
 * (springMorphMenu, glass.js) is a SIZE spring (width/height/border-radius), never a
 * transform:scale(), so a transform-origin has nothing to do for it any more. */
.dock-panel, .pool-panel, .more-menu, .gs-menu, .colpick-menu, .ac, .dpick, #calpop, .rem-detail{
  transform-origin: var(--morph-x,100%) var(--morph-y,0%);
}
/* sensible default origins per surface family (overridden live by glass.js from the clicked anchor) */
.dock-panel, .pool-panel{ --morph-x:100%; --morph-y:100%; } /* dock/pool, bottom-right / bottom */
.more-menu, .gs-menu, .colpick-menu{ --morph-x:0%; --morph-y:0%; }
.ac, .dpick, #calpop, .rem-detail{ --morph-x:50%; --morph-y:0%; }

.dock-panel, .pool-panel, .more-menu, .gs-menu, .colpick-menu{
  opacity:0;transform:scale(.6);filter:blur(10px);visibility:hidden;
}
.dock-panel:not(.open), .pool-panel:not(.open), .more-menu[hidden], .gs-menu:not([style*="flex"]), .colpick-menu:not([open]){
  visibility:hidden;
}
.dock-panel.open, .pool-panel.open, .more-menu:not([hidden]), details[open]>.colpick-menu, .gswitch[aria-expanded=true] .gs-menu{
  opacity:1;transform:none;filter:none;visibility:visible;
}
@media (prefers-reduced-motion:no-preference){
  .dock-panel, .pool-panel, .more-menu, .gs-menu, .colpick-menu{
    transition:transform var(--dur-morph) var(--ease-spring), filter var(--dur) var(--ease-out),
               opacity var(--dur-fast) linear, visibility 0s var(--dur-morph);
  }
  .dock-panel.open, .pool-panel.open, .more-menu:not([hidden]), details[open]>.colpick-menu, .gswitch[aria-expanded=true] .gs-menu{
    transition:transform var(--dur-morph) var(--ease-spring), filter var(--dur) var(--ease-out), opacity var(--dur-fast) linear;
  }
  /* fix-round-1 (planner review, 2026-09-11): .more-menu toggles via the `hidden` attribute, and
   * components.css's `[hidden]{display:none!important}` means no transition could ever run for it
   * -- display:none skips painting entirely, so there was nothing to animate FROM. `display` joins
   * the transition list as an allow-discrete property (keeps the element rendered for the
   * transition's duration instead of vanishing at frame 0) and @starting-style supplies the "just
   * became display:non-none" starting values, exactly mirroring the existing closed state above.
   * .dock-panel/.pool-panel/.gs-menu/.colpick-menu toggle via a CLASS or [open]/[style], never
   * [hidden] with !important, so they were unaffected and don't need this. (`.shellpanel` LEFT this
   * whole shared block, G3 motion -- see its own JS-spring-only rule just below.) */
  .more-menu{
    transition:transform var(--dur-morph) var(--ease-spring), filter var(--dur) var(--ease-out),
               opacity var(--dur-fast) linear, visibility 0s var(--dur-morph),
               display var(--dur-morph) allow-discrete;
  }
  @starting-style{
    .more-menu:not([hidden]){
      opacity:0;transform:scale(.6);filter:blur(10px);
    }
  }
}
@media (prefers-reduced-motion:reduce){
  /* reduced motion: opacity-only morph, no scale/blur */
  .dock-panel, .pool-panel, .more-menu, .gs-menu, .colpick-menu{ transform:none;filter:none; }
}

/* L5 Capture bar (docs/plans/2026-10-23-timelog.md): the bar grows from the pill by shell.js's clip-path morph (shellExpand), so the shared
 * scale+blur panel morph above is switched off for it: opacity only on a desktop, a spring slide-up sheet on a phone. Reduced motion = instant. */
.dock-panel--bar, .dock-panel--bar.open{ transform:none; filter:none; }
@media (prefers-reduced-motion:no-preference){
  .dock-panel--bar{ transition:opacity var(--dur-fast) linear, visibility 0s var(--dur); }
  .dock-panel--bar.open{ transition:opacity var(--dur-fast) linear; }
}
@media (max-width:640px){
  .dock-panel--bar{ transform:translateY(100%); }
  .dock-panel--bar.open{ transform:none; }
  @media (prefers-reduced-motion:no-preference){
    .dock-panel--bar{ transition:transform var(--dur-morph) var(--ease-spring), opacity var(--dur-fast) linear, visibility 0s var(--dur-morph); }
    .dock-panel--bar.open{ transition:transform var(--dur-morph) var(--ease-spring), opacity var(--dur-fast) linear; }
  }
}

/* `.shellpanel` (the account menu): R3 A3 (owner #2) -- grows from its own button as ONE glass
 * SHAPE on glass.js's 170/19 JS spring (springMorphMenu), a SIZE morph (width/height/border-
 * radius set every frame), replacing the scale-transform morph (springMorphPanel) this used
 * before. No `transition` here at all, deliberately: the spring owns every animated property every
 * frame, and shell.js's menuKit only flips `hidden` once the spring has fully settled shut, so
 * there is never a moment CSS needs to keep the element painted PAST `[hidden]` the way the shared
 * `display allow-discrete` trick above provides for the other panels -- `hidden`/`display:none`
 * and the end of the visible animation land at the same instant by construction. `inert` (set the
 * instant closing starts, glass.js) pulls the panel out of the accessibility tree/tab order well
 * before `hidden` does, so a keyboard/AT user never perceives the ~0.7s settle the spring visually
 * takes. `overflow:hidden` clips its own two children (the glyph + the items wrapper, both
 * absolutely positioned) to whatever size the spring has grown the shape to so far -- with no
 * `transform`/`opacity` of its own any more, reduced motion needs no override here (glass.js's own
 * `rm()` checks already make the spring snap straight to its target). */
.shellpanel{ overflow:hidden; border:0; }
.shellpanel[hidden]{ visibility:hidden; }
.shellpanel:not([hidden]){ visibility:visible; }
/* the two layers springMorphMenu (glass.js) animates the OPACITY of, via the two custom properties
 * it sets on `.shellpanel` itself every frame -- `--morph-glyph-op` defaults to 1 (opaque) and
 * `--morph-items-op` to 0 (invisible) so the very first, JS-painted-synchronously-before-paint
 * frame already matches the button's own rest look even before any custom property exists yet. */
/* R3 F2 (2026-09-27): `pointer-events` flipped none -> auto -- once open, this layer sits exactly
 * where #userbtn visually is (top-right corner, same size), and that's the one spot a user reaches
 * for out of habit to toggle the menu shut again; `none` made a click there fall through to
 * whatever's underneath instead of closing. `z-index:1` is needed alongside it: both layers are
 * position:absolute siblings with no stacking order declared, so source order alone decided it and
 * `.usermenu-items` (written second, below) painted OVER this corner -- measured live via
 * elementFromPoint, a click here hit `.shell-prefrow` (the name row), not the glyph, until this was
 * added. shell.js wires the actual close+refocus; this stays aria-hidden/non-focusable/untouched by
 * itemsOf() (a `<span>` never matches its `a[href]/button` selector), so it is reachable by pointer
 * only, never by keyboard or screen-reader roving. */
.usermenu-glyph{
  position:absolute;top:0;right:0;width:34px;height:34px;z-index:1;
  display:inline-flex;align-items:center;justify-content:center;
  color:var(--fg);pointer-events:auto;cursor:pointer;opacity:var(--morph-glyph-op,1);
}
/* R3 F5: same two triggers that make #userbtn 44px (shell.css), so the copy never sits offset in a narrow fine-pointer window */
@media (pointer:coarse), (max-width:48rem){ .usermenu-glyph{width:44px;height:44px} }
/* position:absolute + no explicit width (only a floor) sizes this to its OWN content -- the "≥
 * today's 14rem" natural width the plan asks for -- regardless of how small `.shellpanel` (its own
 * animating ancestor) currently is; `.shellpanel`'s own `overflow:hidden` is what actually keeps
 * it invisible-until-grown-enough, not this element's own layout. */
.usermenu-items{
  position:absolute;top:0;right:0;min-width:14rem;box-sizing:border-box;padding:var(--sp-2);
  opacity:var(--morph-items-op,0);
}
/* topbar-free shell (2026-10-01): the account row sits at the RAIL FOOT, so shell.js's menuKit grows
   the panel UPWARD (`.morph-up`: bottom edge anchored above the button). The round-avatar glyph copy has
   no place there (the wide row button stays put, unfaded) and the items hang from the bottom-left. */
.shellpanel.morph-up .usermenu-glyph{display:none}
.shellpanel.morph-up .usermenu-items{top:auto;right:auto;bottom:0;left:0}

/* <dialog> entrance: @starting-style (Chrome 117 / Safari 17.5) — CSS-only, no JS morph origin */
dialog{ transform:scale(.6);filter:blur(10px);opacity:0; }
dialog[open]{ transform:none;filter:none;opacity:1; }
@media (prefers-reduced-motion:no-preference){
  dialog{ transition:transform var(--dur-morph) var(--ease-spring), filter var(--dur) var(--ease-out), opacity var(--dur-fast) linear, overlay var(--dur-morph) allow-discrete, display var(--dur-morph) allow-discrete; }
  @starting-style{
    dialog[open]{ transform:scale(.6);filter:blur(10px);opacity:0; }
  }
}
@media (prefers-reduced-motion:reduce){
  dialog{ transform:none;filter:none; }
}

/* rem-detail / calpop / ac / dpick keep their own existing open-state opacity/transform rules in
 * their module CSS (todo.css .rem-detail.open, calendar.css #calpop.opening) — glass.css only
 * supplies the transform-origin variable and does not fight those transitions.
 */

/* ================= pressed accent tint — button.primary/.dock-btn while pressed ================= */
/* flat-buttons 2026-10-16: pressed = the 32% step of the same tint formula (rest 16, hover 24). The dock
 * tints over opaque --bg-solid instead of transparent. Higher specificity than the generic `button.pressed` rule.
 * fix round (pool-gaps-2, finding 28): `:where(:not(:disabled))` on the four form-button selectors
 * (never on `.dock-btn`, which is never a disabled control on this platform) -- same "`:active`
 * still matches disabled" hazard as the shared press rule above, same `:where()` (not bare `:not()`)
 * fix -- a bare form would have raised this rule's own specificity past whatever it currently ties
 * or loses against. */
button.primary:where(:not(:disabled)):active, button.primary.pressed:where(:not(:disabled)), .btn.primary:where(:not(:disabled)):active, .btn.primary.pressed:where(:not(:disabled)),
form button[type=submit]:where(:not(:disabled)):active, form button[type=submit].pressed:where(:not(:disabled)),
form button:not([type]):where(:not(:disabled)):active, form button:not([type]).pressed:where(:not(:disabled)){
  background:color-mix(in srgb, var(--accent) 32%, transparent);
}
.dock-btn:active, .dock-btn.pressed{
  background:color-mix(in srgb, var(--accent) 32%, var(--bg-solid));
}

/* ================= feedback: press + light ================= */
/* pointer specular highlight — glass.js sets --gx/--gy (percent) via delegated pointermove.
 * re-glass 2026-09-18: restored, but the HOVER-tracking half is scoped to `.dock-btn`/
 * `button.primary` only (glass.js's SPEC_SEL) -- the owner's narrowed ask is action buttons, and
 * `.rail-i`/`.tab` are bars/rails/tab-bar navigation, which stay flat opaque. The PRESSED (touch)
 * bloom below is a separate, already-approved press affordance (PRESS_SEL in glass.js is
 * unchanged platform-wide) and keeps its full selector list, `.rail-i`/`.tab` included. */
.dock-btn, .rail-i, button.primary, .tab, #railtoggle, .lsopen{ position:relative; }
.dock-btn::after, .rail-i::after, button.primary::after, .tab::after, #railtoggle::after, .lsopen::after{
  content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;
  background:radial-gradient(circle at var(--gx,50%) var(--gy,0%), var(--glass-spec-light), transparent 70%);
  opacity:0;
}
/* press bloom: the same ::after, re-lit from the touch point (glass.js sets --gx/--gy on
 * pointerdown for every pointer type) and grown from a dot to the whole control */
.dock-btn.pressed::after, .rail-i.pressed::after, button.primary.pressed::after, .tab.pressed::after,
#railtoggle.pressed::after, .lsopen.pressed::after{
  opacity:1;transform:scale(1);
  background:radial-gradient(circle at var(--gx,50%) var(--gy,50%), var(--glass-bloom), transparent 65%);
}
/* flat-buttons 2026-10-16: no specular/bloom light on the tinted primary or the dock button. */
.dock-btn::after, button.primary::after, .dock-btn.pressed::after, button.primary.pressed::after { content:none; }
@media (hover:hover){ .dock-btn:hover::after, button.primary:hover::after { content:none; } }
@media (prefers-reduced-motion:no-preference){
  .dock-btn::after, .rail-i::after, button.primary::after, .tab::after, #railtoggle::after, .lsopen::after{
    transform:scale(.35);transform-origin:var(--gx,50%) var(--gy,50%);
    transition:opacity var(--dur-fast) var(--ease), transform var(--dur-morph) var(--ease-out);
  }
  @media (hover:hover){ .dock-btn:hover::after, button.primary:hover::after { transform:scale(1); } }
}
@media (hover:hover){ .dock-btn:hover::after, button.primary:hover::after {
  opacity:1;
} }
@media (pointer:coarse){
  .dock-btn::after, .rail-i::after, button.primary::after{
    background:radial-gradient(circle at 50% 0%, var(--glass-spec-light), transparent 70%);
  }
}

/* ================= browser surfaces ================= */
*{ scrollbar-width:thin; scrollbar-color: color-mix(in srgb,var(--fg) 22%,transparent) transparent; }
*::-webkit-scrollbar{ width:10px;height:10px; }
*::-webkit-scrollbar-thumb{
  background:color-mix(in srgb,var(--fg) 22%,transparent);border-radius:var(--radius-pill);
  background-clip:padding-box;border:2px solid transparent;
}
::selection{ background:color-mix(in srgb,var(--accent) 28%,transparent); }
html{ caret-color:var(--accent); }

/* (games tile accent rim removed 2026-10-02 -- see the DE-GLASS note on the tile family above.) */

/* ================= games freeze (2026-09-25, plain-liquid-glass P2) =================
 * Owner ruling 2 ("Some ui aren't to be changed, eg. games db"): every `modules/games/static/
 * *.html` carries a static `class=games-keep` on `<html>` (plus shell.js's path backstop above) so
 * the CONTENT this pass touches -- buttons, fields, chips/badges/tagchips, cards/cells, the switch
 * thumb, the segmented container's new track fill -- renders EXACTLY as it did before this leg,
 * while the CHROME around it (#shell/.rail-nav/#tabbar/the dock/the account menu/toast) still
 * floats and turns glass like every other page (P1 already shipped that for games too; it is not
 * re-pinned here on purpose). Every property this leg touched was written against a NEW token
 * (tokens.css), so the freeze is a pure TOKEN re-pin, never a second copy of the selectors above.
 * fix round 1 (2026-09-25, finding 10, TWO passes): this was scoped to `body` -- an A/B
 * computed-style diff (`/ledger` vs `/games`) confirmed it reached the CHROME too: `#railtoggle`/
 * `#usermenu`/`.rail-h`/`#dockcapture`/`#docksearch` were all silently re-pinned back to pre-P1
 * geometry/paint on every games page, exactly the "shell still floats/glasses" guarantee this same
 * comment (and DESIGN.md, and the test below) claimed. Pass 1 rescoped to `.shell-page`, assuming
 * `#shell`/`#usermenu`/the dock were its SIBLINGS under `<body>` (shell.js's `wrapBody()`) -- the
 * SAME A/B diff, re-run, showed `#railtoggle`/`.rail-h` now matched (they live in `#rail`, a TRUE
 * sibling) but `#usermenu button`/`#dockcapture`/`#docksearch` still leaked: a DOM walk found
 * `.shell-page` actually WRAPS `#shell`, `#usermenu` AND `#dock` alongside the real `<main>` --
 * `#rail` is the only genuine outside sibling, so `.shell-page` never excluded the chrome at all.
 * Pass 2 rescoped to the semantic `<main>` tag itself (or planner.html's own `#gp-shell`) -- but a
 * round-2 recheck (games content A/B, scripts/games_keep_ab.py) found `<main>`/`#gp-shell` miss 9
 * of 26 games pages outright (achievements/calendar/gallery/llm-usage/notifications/patch-
 * checklist/post-times/rules-audit/test: their content root is a plain `<div>`, e.g. `#root.wrap`/
 * `#cal-root.wrap`+`#detail`/`#sns-root.wrap`), and on the pages that DO contain a `<main>` tag
 * somewhere (patch/patch-info/review/sns/sp-order/sp-report/wiki), it sits NESTED inside such a div
 * rather than being the top-level content root itself -- `:is(main, #gp-shell)` never matched
 * those either. Pass 2's OWN diff evidence (all five chrome elements identical on `/ledger` vs
 * `/games`) stays true of this pass 3 too; only the CONTENT side changes.
 * Pass 3 (round 2, G1 blocker): rather than name every content wrapper id (`#root`/`#cal-root`/
 * `#sns-root`/`#patch-root`/`#wiki-root`/bare `.wrap`/`#detail`/...), scope by EXCLUSION -- every
 * direct child of `.shell-page` that is not a known chrome element (`#shell`/`#usermenu`/`#dock`/
 * its `#dockback` backdrop/its `#dockcapturepanel` panel -- the exact five ids `wrapBody()` and
 * `buildDock()` between them ever place there, confirmed by walking shell.js: `buildDock()` runs
 * first at DOMContentLoaded and appends `#dock,#dockback,#dockcapturepanel` to `<body>`, `initRail()`
 * runs second and its `wrapBody()` sweeps EVERY current body child -- `#shell`, the portalled
 * `#usermenu`, those three dock elements, and whatever the page itself put there (`<main>`, a
 * `<div>`, or several) -- into one new `.shell-page`, then prepends `#rail`) IS the page's own
 * content, whatever tag/id it uses, however many top-level pieces it comes in (calendar.html has
 * two: `#cal-root.wrap` and `#detail`). planner.html is the one page with NO shell.js chrome at
 * all (its own inline script builds `#gp-shell` + a tab-strip `<div>` + `<main>` directly as body
 * children, no `.shell-page`/`#rail`/`#tabbar` ever exist there -- confirmed by the same DOM walk:
 * `document.querySelector('.shell-page')` is null on that page) -- for it, the SAME exclusion idea
 * applies one level up, against `<body>` itself, naming the four ids/classes every OTHER page's
 * `<body>` carries (`#rail`, its `.rail-backdrop`, `.shell-page`, `#tabbar`) plus a run-time
 * `.toast` (shellToast() appends one to `<body>` on demand, well after page load -- excluded so a
 * toast showing on a games page is never re-pinned as if it were games content). On every page
 * OTHER than planner this second branch matches nothing (a normal page's `<body>` has only those
 * four children), so the two branches together cost nothing extra anywhere but correctly reach
 * planner's content too, with no per-page special case. Proof: `scripts/games_keep_ab.py` (every
 * element under the content root, base vs head, WebKit + Chromium, light/dark, several palettes,
 * 1440/390) -- 0 diffs. `.game-card` family, `.seg-sm`, `.gswitch`/`.gs-menu`, `.tbl` and `.info`
 * are already untouched by this leg (see each recipe's own comment above) and need no entry here.
 * fix round 1 (pool, finding 3): the four `<body>`-direct ids/classes above were meant to name
 * every child a NORMAL page's `<body>` ever carries so the second branch matches nothing there --
 * but several shell.js chrome surfaces are PORTALLED to `<body>` at runtime, well after `wrapBody()`
 * has already swept them into `.shell-page` once: `#usermenu` (built inside `#shell`, moved OUT to
 * a `.shell-page`-external `<body>` child at build time already for the backdrop-filter-containing-
 * block hazard above, then moved AGAIN by menuKit's own `open()`, shell.js, every time the account
 * menu opens), `#lsdlg` (the `/` search overlay, lazy-built straight onto `<body>`, search.js),
 * `#reauthdlg` (the sudo re-auth dialog, shell.js) and `.dpick` (the shell-adopted date picker
 * popover, shell.js). None of the four were in this branch's exclusion list, so opening any one of
 * them on a games page re-pinned ITS descendants (a menu row, the search checkbox, a picker day
 * cell) with the frozen games tokens too, even though those surfaces are chrome on every other page.
 * Added to both branches' exclusion lists (the `.shell-page` branch already excluded `#usermenu`
 * for the normal, un-opened case; adding it there too costs nothing and keeps both lists naming the
 * same real chrome set). Proof: `vpool/leak_menu.py` (computed style of the account menu / search
 * field on `/games/...` vs `/ledger`, before this fix showing font-size/white-space/background/
 * colour drift; identical after). */
.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel),
.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick){
  /* 2026-10-02 DE-GLASS: re-pinned to the PRE-GLASS baseline (45fe0188, 2026-09-10), not the
   * 09-11/09-18 glass-era values this block used to freeze -- see tokens.css `--keep-*` for the
   * provenance of every value. OPAQUE surfaces (`--bg`/`--panel`), SOLID hairline (`--bd`), the old
   * 14/10 radius, 15px/1.5 type, 1.25rem card padding; no field/chip ring, no cell fill/rim. */
  --c-fill: var(--keep-bg); --c-shadow: none;
  --field-shadow: none; --field-min-h: auto;
  --field-pad-y: var(--keep-field-pad-y); --field-pad-x: var(--keep-field-pad-x);
  --chip-fill: var(--keep-panel); --chip-shadow: none;
  --switch-thumb-fill: var(--bg);
  --seg-track: transparent;
  --bg: var(--keep-bg); --panel: var(--keep-panel);
  --bd: var(--keep-bd); --bd2: var(--keep-bd2); --hover: var(--keep-hover);
  --radius: var(--keep-radius); --radius-sm: var(--keep-radius-sm);
  --gap: var(--keep-gap); --pad-card: var(--keep-pad-card);
  --fs-hero: var(--keep-fs-hero); --fs-display: var(--keep-fs-display); --fs-title: var(--keep-fs-title);
  --fs-caption: var(--keep-fs-caption);
  --sp-3: var(--keep-sp-3); --sp-4: var(--keep-sp-4); --sp-5: var(--keep-sp-5);
  --sp-6: var(--keep-sp-6); --sp-7: var(--keep-sp-7); --sp-8: var(--keep-sp-8);
  --scrim: var(--keep-scrim);
  --hub-bd: var(--bd); --hub-radius: var(--radius);
  /* fix round (pool-gaps-2, finding 1, blocker): games-hub.css (modules/games/static/css) declares
   * `--bg2`/`--head`/`--hub-bg2`/`--hub-panel`/`--hub-bg`/`--panel2` on `:root` and `--g-bg`/
   * `--g-bg2` on the per-game `.g-<id>` class (`document.body.classList.add('g-'+gid)`, games.js)
   * as ALIASES of `--panel`/`--bg`/`--hover` -- e.g. `--bg2:var(--panel)`. An UNREGISTERED custom
   * property's `var()` reference resolves using whatever the referenced property cascades to AT
   * THE SAME ELEMENT the alias is declared on (`:root`/`<body>`), not at wherever the alias is
   * later CONSUMED -- so under Material=Solid, `--bg2` etc. bake in `:root`'s own (unfrozen, opaque
   * Solid) `--panel`/`--bg` the instant they're computed, and that already-resolved value simply
   * INHERITS down into games content -- the `--panel`/`--bg` re-pin two lines up, declared on a
   * DEEPER descendant selector than `:root`/`<body>`, runs too late in the tree to ever be seen by
   * an alias whose own value was fixed higher up. Re-declaring every alias again HERE, at this same
   * content-root scope, referencing the just-re-pinned `--bg`/`--panel`/`--hover` two lines up (same
   * rule, same element -- custom-property resolution has no notion of "declaration order" within
   * one rule, only final cascaded value), makes each alias resolve against the FROZEN values
   * instead of the live Solid ones. `--hover` itself is never touched by Solid (unaffected either
   * way) -- `--panel2` is included anyway since it is one of games-hub.css's own alias declarations
   * and costs nothing to also freeze. Proof: scripts/games_keep_ab.py --solid (was 64 diffs/page
   * across 8 axis combinations, now 0 -- see that script's own extended --solid/--hover matrix). */
  --hub-bg: var(--bg); --hub-bg2: var(--panel); --hub-panel: var(--panel);
  --bg2: var(--panel); --panel2: var(--hover); --head: var(--panel);
  --g-bg: var(--bg); --g-bg2: var(--panel);
}
/* 2026-10-02 DE-GLASS: the games CHROME that sits INSIDE the content root -- the tools nav (`#gp-nav`/
 * `.gp-nav`) and the game switcher (`#gswitch`/`.gswitch` + its `.gs-menu`) -- stays glass (owner
 * ruling 2026-09-25: chrome is glass, content is not). It inherits every custom property the block
 * above re-pins on the content root, so each pinned token is handed back its live :root value here
 * (tokens.css `--live-*`, a :root-level snapshot). Specificity (1,2,2) beats the content-root pin's
 * (1,2,0) even when the nav IS a direct child of the content root (landing/hub shells). */
:root.games-keep body :is(#gp-nav,.gp-nav,#gswitch,.gswitch,.shell-tools,.shell-lock){
  --c-fill: var(--live-c-fill); --c-shadow: var(--live-c-shadow);
  --field-shadow: var(--live-field-shadow); --chip-fill: var(--live-chip-fill); --chip-shadow: var(--live-chip-shadow);
  --field-pad-y: var(--live-field-pad-y); --field-pad-x: var(--live-field-pad-x);
  --bg: var(--live-bg); --panel: var(--live-panel); --bd: var(--live-bd); --bd2: var(--live-bd2); --hover: var(--live-hover);
  --radius: var(--live-radius); --radius-sm: var(--live-radius-sm);
  --gap: var(--live-gap); --pad-card: var(--live-pad-card); --scrim: var(--live-scrim);
  --fs-hero: var(--live-fs-hero); --fs-display: var(--live-fs-display); --fs-title: var(--live-fs-title);
  --fs-caption: var(--live-fs-caption);
  --sp-3: var(--live-sp-3); --sp-4: var(--live-sp-4); --sp-5: var(--live-sp-5);
  --sp-6: var(--live-sp-6); --sp-7: var(--live-sp-7); --sp-8: var(--live-sp-8);
  --hub-bd: var(--live-hub-bd); --hub-radius: var(--live-hub-radius); --hub-bg: var(--live-hub-bg);
  --hub-bg2: var(--live-hub-bg2); --hub-panel: var(--live-hub-panel);
  --bg2: var(--live-bg2); --panel2: var(--live-panel2); --head: var(--live-head);
  --g-bg: var(--live-g-bg); --g-bg2: var(--live-g-bg2);
}
/* (type is restored at zero specificity so these controls' own font-size rules, e.g. the switcher's, still decide) */
/* (`.gp-bar`, the row that holds the switcher + nav + page tools + the content's own back link/subtitle/language
 * select, stays on B's content type: those content children inherit from it, so only its three chrome children above
 * are handed the live type -- the bar's own, non-painting font-size/line-height is the one box that differs from main.) */
:where(:root.games-keep body :is(#gp-nav,.gp-nav,#gswitch,.gswitch,.shell-tools,.shell-lock)){ font: var(--live-font); }
/* DE-GLASS: content-root type. B's body was `font:15px/1.5` (tokens.css --font); main's plain-glass
 * density pass made it 14px/1.45 for the whole platform. Games content keeps B's. `:where()` = zero
 * specificity, so any games module rule that sets its own root font still wins, as it did at B. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)),
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)){
  font: var(--keep-font);
}
/* fix round 1 (finding 6): relocated here (still phone-only -- both fixes below only ever applied
 * inside the `@media(max-width:48rem)` capsule-groups block, so a bare copy-paste needs the same
 * wrapper) from right after the two rules they each re-pin, in the middle of that same phone media
 * block -- tests/test_glass_wiring.py's own `_GK_SP` marker constant is a plain substring match
 * against this file for the games-keep content-root selector's `.shell-page` half, "whichever copy
 * comes FIRST", and every `:where(...)` selector below carries that identical prefix; placed
 * earlier in the file than this section's own un-`:where()`'d re-pin block (the one right above,
 * `--c-fill` etc.), they took over as that first copy and broke the test's own fixed-size read
 * window starting from it. No such ordering conflict here, after the block that constant is meant
 * to identify. (This comment paraphrases the constant on purpose, never spelling it out verbatim,
 * so it can't ever repeat the same leak against the test's OTHER, stricter unscoped-selector
 * check just above in this file.) */
@media (max-width:48rem){
  /* the divider mechanism switch (G3 motion round-1-gate finding 3, box-shadow -> ::before, glass.
   * css's phone segmented-groups section) reaches games' own phone segmented strips ([role=tablist],
   * planner.html's toolbar) with no games-keep counterpart, so the games A/B tool's own proof
   * (scripts/games_keep_ab.py) started failing at 390px: base draws a curved box-shadow ring
   * following each item's rounded corners (boxShadow computes to `-1px 0 0 0 var(--bd)`, a real --
   * if faint -- structural difference from head's `none`, since the box-shadow moved to a still-live
   * ::before layer instead), head draws a straight ::before hairline instead. Restored the OLD
   * mechanism for games content only: the new ::before is suppressed (`content:none` fully removes a
   * pseudo-element's box, unlike an empty string) and the pre-G3 box-shadow comes back on the item
   * itself, verbatim (`var(--bd)` is unchanged by this leg, only the drawing mechanism moved, so no
   * frozen `--keep-*` token is needed here -- same reasoning as every other plain-formula games-keep
   * re-pin in this file). Both content-root shapes (a `.shell-page` document and a bare-`<body>` one
   * like planner.html, which has no shell.js chrome at all) per this file's own games-keep
   * convention. */
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .addseg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) nav.tabs>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .seg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .viewsw>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .qc-seg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) [role=tablist]>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .glass-cap>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .addseg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) nav.tabs>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .seg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .viewsw>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .qc-seg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) [role=tablist]>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .glass-cap>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  /* self-match form (same "content root IS the tracked element" case this file's own `.tab-btn
   * [aria-selected=true]` re-pin already documents further below): planner's `.tabs` is a direct
   * `<body>` child that IS `[role=tablist]` itself -- a descendant combinator alone never reaches
   * it, only a genuinely nested instance would; `:is([role=tablist])` matches the compound (self)
   * case. */
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)):is([role=tablist])>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)):is([role=tablist])>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens)::before{
    content:none;
  }
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .addseg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) nav.tabs>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .seg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .viewsw>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .qc-seg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) [role=tablist]>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .glass-cap>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .addseg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) nav.tabs>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .seg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .viewsw>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .qc-seg>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) [role=tablist]>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .glass-cap>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)):is([role=tablist])>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)):is([role=tablist])>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens){
    box-shadow:none;   /* DE-GLASS: B (45fe0188) had no divider between phone segments (the 09-11 `-1px --bd` shadow was glass-era) */
  }
  /* fix round 1 (finding 6, second gap, same games_keep_ab.py resweep): scoping shell.css's phone
   * tab-bar `.tab` rule to `#tabbar .tab` (finding 3's own fix, this same round -- it was leaking
   * onto Stocks' unrelated `nav.tabs .tab`) ALSO reached games' `/games/test/rules-audit` fixture,
   * which reuses the bare `.tab` class on its own `<button>` row several levels under `.shell-page`
   * (`.wrap #games.bar .tab`, not a content root itself, so no `:is()` self-match is needed the way
   * `[role=tablist]` above needed one). Base's `.tab` there was ALSO only ever styled by this same
   * unscoped leak (there is no OTHER `.tab` rule main ships), so properly scoping the real bug for
   * the platform changed this games fixture's rendering too -- re-pinned here to the exact
   * declaration list shell.css's own `#tabbar .tab` still carries, verbatim, so games content stays
   * pixel-identical to base regardless of how the platform's own bug gets fixed. */
/* DE-GLASS: at phone width the pool turns every `[role=tablist]` into a glass capsule (pill radius, a `::before`
 * material layer, stacked items). Planner's tab strip had none of that at B: square, no material, static items. */
@media (max-width:48rem){
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)):is([role=tablist]), :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) [role=tablist], :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)):is([role=tablist]), :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) [role=tablist]{ border-radius:0; }
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)):is([role=tablist])::before, :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) [role=tablist]::before, :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)):is([role=tablist])::before, :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) [role=tablist]::before{ content:none; }
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)):is([role=tablist])>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens), :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) [role=tablist]>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens),
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)):is([role=tablist])>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens), :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) [role=tablist]>*:not(.seg-thumb):not(.lens)+*:not(.seg-thumb):not(.lens){ position:static; }
}
/* DE-GLASS (2026-10-02): the bare-`.tab` phone re-pin that stood here recreated the tab-bar `.tab` look
   (flex column, 44px floor, nowrap) on games' own `.tab` buttons -- a leak B never had; they are plain
   module-styled buttons again. */
}
/* fix round 1 (pool, finding 18/E3): `--glass-fill-soft` (every button's own background) and
 * `--seg-thumb-fill` (the phone seg-thumb pill, fix round 1 finding 2) re-pinned to their NORMAL
 * (always-glass) formula so Material=Solid -- a brand-new axis this whole branch adds, with no
 * pre-existing games behaviour to match -- never reaches games content, the same contract every
 * other games-keep token already holds. Split light/dark (tokens.css's own `--keep-*` pair, same
 * pattern as `--keep-glass-rim-*`) since the NORMAL formula's percentage genuinely differs by mode,
 * not just its base colour.
 * fix round (pool-gaps-2): `--glass-fill-strong` joins this same re-pin -- the identical "brand-new
 * axis, no games behaviour to match" case, missed here because it's normally a CHROME-only token
 * (menus/dialogs/popovers) until story-line.css's own `.sl-pop` reads it directly for its popup
 * dialog. Proof: scripts/games_keep_ab.py --solid --pages story-line, 0 diffs (was 1). */
:root:not(.dark).games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel),
:root:not(.dark).games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick){
  --glass-fill-soft: var(--keep-glass-fill-soft-light);
  --seg-thumb-fill: var(--keep-seg-thumb-fill-light);
  --glass-fill-strong: var(--keep-glass-fill-strong-light);
}
:root.dark.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel),
:root.dark.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick){
  --glass-fill-soft: var(--keep-glass-fill-soft-dark);
  --seg-thumb-fill: var(--keep-seg-thumb-fill-dark);
  --glass-fill-strong: var(--keep-glass-fill-strong-dark);
}
/* fix round (pool-gaps round 2, finding 3b): pool-gaps round 2 also re-tuned `--muted`'s LIGHT hex
 * (#6b7280 -> #5f6976, tokens.css, for field-focused AA contrast) without a re-pin here, so every
 * muted caption on a games page silently repainted -- this leg touched no games-owned property, so
 * the freeze block missed it (`test_glass_wiring` has no `--muted` assertion, a coverage gap noted
 * in the receipt, not fixed here). Scoped to `:not(.dark)` because dark's `--muted` (#9aa3b2) never
 * changed -- pinning it unscoped would repaint dark games text with the WRONG (light) hex instead.
 * `--hub-muted` is re-pinned alongside it: games-hub.css aliases it to `var(--muted)` on `:root`
 * (`--hub-fg:var(--fg); --hub-muted:var(--muted); ...`), one scope above this re-pin, so
 * `--hub-muted` resolves `--muted` at `:root` (still the live #5f6976) before this rule ever runs --
 * `.gh-tier-cap`/`.gh-hn-cap`/`.gh-hn-empty` (landing tier captions/empty state) read `--hub-muted`,
 * not `--muted`, and stayed on the new hex without this second line (measured: 13/18 fixed by
 * `--muted` alone, 18/18 with both). fix round 1 (finding 10): `--select-chevron` is baked (a
 * data-URI, never derived from `--muted` at paint time) so re-pinning `--muted` alone left every
 * games `<select>` with the NEW, darker chevron stroke -- its own re-pin lives in tokens.css
 * instead (colocated with the four other `--select-chevron`/`--check-mark` definitions, and outside
 * `core/ui_pool.py`'s `_CORE_CSS` scan list, which the SVG data-URI's literal `w3.org` substring
 * would otherwise trip as two phantom `.org`/`.w3` "unregistered classes"), same rule shape.
 * round 2 (G1 finding 6): the 2026-09-11 light-palette-twins pass gave every OTHER light palette
 * its OWN `--muted` (nord #4c566a, mocha #65687c, graphite #666b75, ...) -- this rule's bare
 * `:not(.dark)` guard fires under a named palette too and stamped Classic's #6b7280 over every one
 * of them (measured contrast drop e.g. nord 6.06:1 -> 3.97:1 against its own `--ground`), on top of
 * silently breaking the games-freeze guarantee for 7 of 8 palettes. `:not([class*="light-"])` scopes
 * this to Classic ONLY -- `light-<key>` is the exact class shell.js adds for a named light palette
 * (never present for Classic), so a games page under any OTHER palette now falls through to that
 * palette's own `--muted`/`--hub-muted` (defined at `:root`, still live here) undisturbed, matching
 * what base/prev both render. Value now reads `--keep-muted-light` (tokens.css) instead of a bare
 * literal, per this file's own "tokens only" rule. */
:root:not(.dark):not([class*="light-"]).games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel),
:root:not(.dark):not([class*="light-"]).games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick){
  --muted:var(--keep-muted-light); --hub-muted:var(--keep-muted-light);
}
/* DE-GLASS: Classic light's `--up`/`--ok` green deliberately stays at main's AA-retuned #0a8438, NOT
 * B's #0a8f3c (~4.2:1 on white) -- an accessibility fix, not glass styling, so it is not reverted. */
/* Every selector-based override below wraps its `.games-keep` content-root scope (round 2: the
 * two-branch `.shell-page > :not(...)`/`body > :not(...)` selector this block's own header comment
 * derives -- was `.games-keep :is(main, #gp-shell)`, same reasoning applies unchanged to the wider
 * scope) (and its `:root.dark.games-keep` twin) in `:where()`, which always contributes ZERO to
 * specificity -- this makes each override's TOTAL specificity exactly equal to the pre-P2 rule it
 * is countering (never higher), so it wins the source-order tie against that rule (declared
 * earlier in this file) but still LOSES to any games module rule that was already MORE specific on
 * main (`.hub-controls select`, `.roster-controls select`, etc all keep deciding exactly as they
 * did before this leg). A bare (non-:where) `.games-keep …` prefix was tried first and
 * over-specified every one of these, silently overriding games' own more-specific module rules
 * instead of restoring main's cascade -- fixed here, not left as a ratchet item. `--field-min-h`/
 * `min-height` use `auto` (the property's own initial value), not `0` -- some of these fields are
 * flex items, where an EXPLICIT `0` (unlike `auto`) turns off the automatic-minimum-size behaviour
 * and changes real layout, not just paint. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) input:where(:not(.bare-field)), :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) textarea:where(:not(.bare-field)),
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) select:where(:not(.select-quiet)),
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) input:where(:not(.bare-field)), :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) textarea:where(:not(.bare-field)),
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) select:where(:not(.select-quiet)){ background-color:var(--bg); border-color:var(--bd); }
/* fix round (pool-gaps-2, finding 2, major): components.css's own P2 hover addition,
 * `select:hover{background-color:var(--hover)}` (a genuinely NEW affordance, "select had NO hover
 * state at all", that file's own comment), used to reach a games `<select>` with no games-keep
 * counterpart, swapping whichever REST fill was already correctly governing it (the generic
 * `--cell-solid` re-pin two groups up, OR one of several MORE-specific per-module rules games pages
 * declare for their OWN selects -- calendar's `.cal-patchsel`, rules-audit's bare `select{}`,
 * notifications' `.title select`/`.mcell select`, review's/sns's `.hl-row select`, patch's
 * `.langsel`, the shared `.hub-controls select`/`.roster-controls select`, each reading a DIFFERENT
 * token) for the live 6% `--hover` tint. Re-pinning that HERE, per selector, would mean re-deriving
 * every one of those module values by hand -- an open-ended list this leg found piecemeal across
 * three separate verify passes. Fixed at the SOURCE instead (components.css's own rule now carries
 * `:not(.games-keep *)`), so no games-keep counterpart is needed here at all: whichever REST rule
 * already governs a given select's background keeps deciding on hover too, exactly matching base's
 * "no hover-specific select fill anywhere" reality, for every current AND future per-module select
 * a games page might add. */
/* button GEOMETRY reset: the generic button/.btn rule's own padding/min-height/font-size AND
 * white-space were changed by this leg (P2 pool, components.css: `white-space:nowrap` is part of
 * the same geometry group) -- fix round 2 (finding 1): this reset originally re-pinned only the
 * first three, so every games button/pill kept the NEW `nowrap` and stopped wrapping its label
 * (e.g. /games/calendar's two-line `.cal-row` title collapsed to one line at 390px and its
 * right-hand chip ran past the row's rounded edge). `.gswitch>button` (the games switcher) keeps
 * its own untouched padding recipe, so it is deliberately NOT in this selector list.
 * round 2 (G1 finding 11b): `white-space:normal` over-corrected -- a `button.info` sitting inside a
 * table cell (e.g. /games/zzz/notifications) used to INHERIT `nowrap` from its ancestor `<td>` (a
 * plain HTML inheritance, not a rule this file ever wrote), and a hard `normal` here breaks that
 * inheritance chain, changing wrap behaviour main never had. `unset` (white-space's own inherited
 * property, so this resolves to whatever value the games markup would naturally inherit -- `nowrap`
 * inside such a cell, `normal` everywhere else) restores exactly the pre-P2 result in both cases. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) button:where(:not(.ctl-quiet)), :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .btn,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) button:where(:not(.ctl-quiet)), :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .btn{
  padding:.45rem .9rem; min-height:auto; font-size:inherit; white-space:unset;
}
/* button/gswitch PAINT reset: P1 (chrome leg, before this one) *also* retuned --glass-rim/--glass-
 * edge/--glass-shadow's VALUES platform-wide while retuning the chrome material -- those tokens
 * are shared with this control-layer rim, which P1's own file header explicitly says it leaves
 * alone, but a shared token still repaints every reader of it. `.gswitch>button` (the games
 * switcher on /games/genshin et al) reads the exact same tokens through its own higher-specificity
 * rule, so it needs the same literal-value reset (`--glass-ring` is identical pre/post-P1 in both
 * modes, so that one alone can stay token-driven). Light values first; the dark twin follows. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .gswitch>button,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .gswitch>button{
  box-shadow:inset 0 1.5px 0 var(--keep-glass-rim-light), inset 0 0 0 1px var(--glass-ring), 0 0 0 1px var(--keep-glass-edge-light);
}
/* `:root.dark.games-keep` (no space) -- `.dark`/`.games-keep` are the SAME <html> element's two
 * classes, never ancestor+descendant, so a descendant combinator between them would never match. */
:where(:root.dark.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .gswitch>button,
:where(:root.dark.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .gswitch>button{
  box-shadow:inset 0 1.5px 0 var(--keep-glass-rim-dark), inset 0 0 0 1px var(--glass-ring), 0 0 0 1px var(--keep-glass-edge-dark);
}
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .switch::after,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .switch::after{ box-shadow:var(--shadow-1); }
/* games freeze (post-resync resweep, 2026-09-26, found by scripts/games_keep_ab.py the first run
 * with real data): `.primary`'s own box-shadow (glass.css, "primary button" section) sits at a
 * HIGHER specificity than the generic button/.btn reset just above -- that reset only ever covered
 * plain buttons, so `.primary`'s 4-layer plain-glass rim/shade/edge/glow was still reaching games'
 * OWN accent buttons (`/games/review`'s `.rv-btn.primary`, one per review-queue row -- confirmed via
 * scripts/games_keep_ab.py --pages review: 21+ boxShadow diffs, 0 background/color diffs, because
 * review.html's own `button.rv-btn.primary{background:var(--accent);border-color:var(--accent);
 * color:#fff}` -- higher specificity than either rule -- already freezes those three properties on
 * its own; only box-shadow, which that games rule never sets, still fell through to this shared
 * pool recipe). Restores base's exact 5-layer formula (`--glass-rim-w`/--accent-ring unchanged
 * pre/post-P1 in both modes, so left token-driven per this block's own convention two groups up;
 * --keep-accent-rim-light/-shade/-edge/-glow are tokens.css's frozen twins for the three pieces that
 * DID move). Same subject compounds as the live rule (glass.css's own `.primary` block just above),
 * a plain comma list (not `:is()`, same reasoning as every other multi-selector re-pin in this
 * file) -- ties on specificity, wins on source order alone, like every other games-keep re-pin.
 * orchestrator checker-extension resweep (2026-09-26): `backdropFilter`/`webkitBackdropFilter`
 * joined games_keep_ab.py's PROPS this same resweep and immediately caught the other half of this
 * SAME `.primary` rule leaking. First pass froze only light's `--glass-sat` (180 base / 170 head)
 * and left `--glass-blur-clear` live, reasoning "12px unchanged both branches" -- true for Glass/
 * Flush, but `:root.solid` (tokens.css) zeroes `--glass-blur-clear` and pins `--glass-sat` to 100%
 * in BOTH modes, and base has no Solid concept at all, so live tokens leaked Solid's values into
 * games under Material=Solid (confirmed live: base=blur(12px), head/solid=blur(0px), the 03:00
 * "games content equals base in every material too" decision). `--keep-glass-blur-clear` (mode-
 * agnostic, tokens.css's own `--glass-blur-clear` has only ONE, unconditional declaration) and
 * `--keep-glass-sat-dark` (joining the light twin) close it for both modes x both materials. */
/* 2026-10-02 DE-GLASS: B's (45fe0188) accent button was `background:var(--accent);color:var(--accent-fg);
 * border-color:transparent` -- flat, no rim/shade/edge/glow, no backdrop blur, no hover bloom. This block
 * used to freeze the 09-18 glass-era 5-layer recipe instead. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) button.primary,
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .btn.primary,
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) form button[type=submit],
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) form button:not([type]),
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) button.primary,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .btn.primary,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) form button[type=submit],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) form button:not([type]){
  background:var(--accent); color:var(--accent-fg); border-color:transparent; box-shadow:none; font-weight:inherit; position:static;
  backdrop-filter:none; -webkit-backdrop-filter:none;
}
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) button.primary::after, :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) button.primary::after{ content:none; }
/* DE-GLASS: B's plain button/.btn paint -- opaque `--bg` fill, 1px solid `--bd` hairline, `--radius-sm`
 * corners, no rim/ring/shadow, the old 120ms background/border/transform transition (the glass-era
 * 360ms bounce is chrome-only). The tools nav / game switcher buttons (chrome) are excluded. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) button:where(:not(.ctl-quiet,#gp-nav *,.gp-nav *,#gswitch *,.gswitch *,.shell-tools *)), :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .btn:where(:not(#gp-nav *,.gp-nav *,#gswitch *,.gswitch *,.shell-tools *)),
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) button:where(:not(.ctl-quiet,#gp-nav *,.gp-nav *,#gswitch *,.gswitch *,.shell-tools *)), :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .btn:where(:not(#gp-nav *,.gp-nav *,#gswitch *,.gswitch *,.shell-tools *)){
  background:var(--bg); border-color:var(--bd); border-radius:var(--radius-sm); box-shadow:none;
  transition:background .15s,border-color .15s;
}
/* (`.tab` carries its own glass `transition` in the pool at class specificity, so it is listed explicitly.) */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .tab:where(:not(#gp-nav *,.gp-nav *,#gswitch *,.gswitch *,.shell-tools *)), :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .tab:where(:not(#gp-nav *,.gp-nav *,#gswitch *,.gswitch *,.shell-tools *)){ transition:background .15s,border-color .15s; }
@media (prefers-reduced-motion:no-preference){
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) button:where(:not(.ctl-quiet,#gp-nav *,.gp-nav *,#gswitch *,.gswitch *,.shell-tools *)), :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .btn:where(:not(#gp-nav *,.gp-nav *,#gswitch *,.gswitch *,.shell-tools *)), :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .tab:where(:not(#gp-nav *,.gp-nav *,#gswitch *,.gswitch *,.shell-tools *)),
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) button:where(:not(.ctl-quiet,#gp-nav *,.gp-nav *,#gswitch *,.gswitch *,.shell-tools *)), :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .btn:where(:not(#gp-nav *,.gp-nav *,#gswitch *,.gswitch *,.shell-tools *)), :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .tab:where(:not(#gp-nav *,.gp-nav *,#gswitch *,.gswitch *,.shell-tools *)){
    transition:background var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease),transform var(--dur-fast) var(--ease);
  }
}
/* DE-GLASS: table head bar and bare textareas -- B had a transparent <thead> and no field transition on a textarea. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) thead, :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) thead{ background:transparent; }
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) textarea, :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) textarea{ transition:revert; }
/* DE-GLASS: cards/stat tiles/composers -- B drew a solid `--bd` border (the glass cell recipe zeroes
 * border-color for its inset-ring shadow); `.card` additionally carried the soft `--shadow` drop. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) :is(.card,.widget-card,.today-card,.rec-card,.sum-card,.mm-card,.stat,.composer,.cell,.cell--side),
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) :is(.card,.widget-card,.today-card,.rec-card,.sum-card,.mm-card,.stat,.composer,.cell,.cell--side){
  border-color:var(--bd);
}
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .card, :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .card{ box-shadow:var(--shadow); }
/* DE-GLASS: chips/tag chips -- B's `.tagchip` kept its `--bd` border; the glass chip recipe makes it transparent. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) :is(.tagchip,.chip,.aliaschip), :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) :is(.tagchip,.chip,.aliaschip){ border-color:var(--bd); }
/* games freeze (orchestrator correction, 2026-09-26): components.css's `button, .btn{ --dur-morph:
 * 420ms; --ease-spring:cubic-bezier(.34,1.56,.64,1); }` (G3 motion, "pool buttons release with the
 * bounce easing") is a CUSTOM-PROPERTY override that beats the inherited :root value on ANY element
 * it matches -- a first attempt excluded it platform-wide via `:where(:not(.games-keep *))` on that
 * rule's own subject, which is WRONG: `.games-keep` sits on <html>, so `:not(.games-keep *)` also
 * excludes the CHROME on a games page (measured live: #railbtn/#lsopen/#userbtn in #shell,
 * #docksearch/#dockcapture in #dock, #railtoggle in #rail, #customizebtn/#shell-logout in
 * #usermenu all lost the bounce) -- chrome must animate exactly like on every other page, only
 * games CONTENT is frozen. Reverted that exclusion (components.css keeps its plain, unscoped rule)
 * and re-pinned CONTENT ONLY here instead, same two content-root prefixes every other re-pin in
 * this block uses. `inherit` (not a literal duration/curve) makes a games button take whatever the
 * :root level actually computes to (360ms / cubic-bezier(.34,1.2,.64,1), unchanged pre/post-P1 on
 * both branches -- see this block's own convention of leaving an unchanged token live rather than
 * freezing it) -- ties glass.css's own `button,.btn,.rail-i,.tab{transition:...}` rule (which reads
 * these same two custom properties via var()) on specificity ((0,1,0) via `:is()`'s highest-
 * argument rule vs `.btn`'s own (0,1,0), or outright beats a bare `button` match's (0,0,1)), wins on
 * source order either way (declared later in this file). Proof: `python3 -u $S/mycheck/
 * chrome_trans.py` must print "0 differ" at 1440 AND 390 (the 8 chrome controls above); games_keep_
 * ab.py must still show 0 transitionDuration/transitionTimingFunction diffs on /games/review. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) :is(button,.btn),
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) :is(button,.btn){
  --dur-morph:inherit; --ease-spring:inherit;
}
/* fix round (pool-gaps-2, games freeze regression found while re-verifying finding 1 with the
 * extended games_keep_ab.py matrix): the "selected states" rule above (§ "the sliding thumb owns
 * the highlight now") gained `font-weight:var(--fw-strong)` in an EARLIER leg (G3 motion round-1-
 * gate finding 4, "one weight, every consumer, matching the mock") with no games-keep counterpart
 * at all -- games' own selected segmented items (story-line's `#slcolseg` column tabs, `aria-
 * pressed=true`) picked up the new 650 weight where base reads a plain 400. `inherit` (the property
 * a plain <button> never had a rule for on either side, same idiom as the button-geometry reset's
 * own `font-size:inherit` two groups up) restores exactly what a games button naturally computes to
 * with no explicit rule -- a PLAIN comma list (not `:is()`), same reasoning as every other multi-
 * selector re-pin in this block: `:is()` inflates every match to its highest-specificity member.
 * `.tabs .tab-btn[aria-selected=true]` is handled SEPARATELY below, not with `inherit` here --
 * planner's `.tab-btn` (its one live consumer) carries its OWN `font-weight:500` (modules/games/
 * static/css/app.css), so its correct frozen value is that literal 500, not whatever `inherit`
 * would resolve to (400, the ancestor's plain body text weight) -- confirmed live: base computes
 * 500 for `#browse-btn`, not 400. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .addseg button[aria-pressed=true],
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .tabs .tab.active,
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .seg-lbl input:checked+span,
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .viewsw button[aria-pressed=true],
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .qc-seg button[aria-pressed=true],
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .seg>button[aria-checked=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .addseg button[aria-pressed=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .tabs .tab.active,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .seg-lbl input:checked+span,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .viewsw button[aria-pressed=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .qc-seg button[aria-pressed=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .seg>button[aria-checked=true]{
  font-weight:inherit;
}
/* `.tabs .tab-btn[aria-selected=true]` on its own, frozen to `.tab-btn`'s OWN literal 500 (see the
 * comment above) -- both the plain descendant form AND an `:is(.tabs)` self-match, the same "self
 * vs nested" dual form this file's own `@media(min-width:48.01rem)` block two groups down already
 * establishes for the sibling `.addseg`/`.seg`/etc family: planner.html's `.tabs` IS ITSELF a direct
 * `<body>` child (no wrapping div), so a descendant combinator alone never reaches it, only a
 * genuinely nested instance. Scoped to just this one proven case, not the whole seven-selector
 * family above -- `.addseg`/`.seg-lbl`/`.viewsw`/`.qc-seg`/`.seg` being a content-root THEMSELVES is
 * untested/unproven across the current 26-page fixture set; if one ever surfaces, it needs the
 * identical treatment. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .tabs .tab-btn[aria-selected=true],
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)):is(.tabs) .tab-btn[aria-selected=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .tabs .tab-btn[aria-selected=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)):is(.tabs) .tab-btn[aria-selected=true]{
  font-weight:var(--keep-tab-btn-weight);
}
/* round 2 (G1 leftover 2/20, caught by games_keep_ab.py's own post-commit resweep): `.chip` gained
 * a shared BOX MODEL (border-radius/padding/font-size, components.css) this same leg -- it had
 * none at all before (a bare `<span class=chip>` was unstyled, reading whatever it inherited), so
 * this is a genuinely NEW P2-family geometry rule with no games-keep counterpart yet. Story-line's
 * `#slpopavail` (a bare `<span class=chip hidden>`, no other CSS touches it) is the one games
 * element this reaches: pill radius / 2px 8px padding / 12.48px caption type on head, vs base's
 * plain-span defaults (0 radius, 0 padding, inherited 14px). Re-pinned to those same CSS-initial/
 * inherited values, not a literal copy of the base font-size -- `inherit` stays correct even if a
 * future ancestor's own font-size changes, `0` is border-radius/padding's own initial value. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .chip:where(:not(.db-wrap .chip)),
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .chip:where(:not(.db-wrap .chip)){
  border-radius:0; padding:0; font-size:inherit; white-space:unset;
}
/* 2026-10-21: the freeze above spares chips inside the Endfield Database page (.db-wrap) only -- its "New"/"Since"
 * chips need the pool pill; every other games page keeps the frozen plain-span chips unchanged.
 * (2026-10-22: exclusion kept inside :where() so the rule stays (0,1,0); a bare :not() lifted it to (0,3,0) and beat
 * module sizing like sns `.hl-meta .chip`, blowing card chips up to plain inherited-size boxes.) */
/* round 2 (G1 finding 11a) INVESTIGATED, WRONGLY -- fixed properly in fix round 1 (pool, finding 1)
 * instead. This comment used to claim the missing ~1px checkbox ring was Chromium's own UA paint,
 * "confirmed" via a `CSS.getMatchedStylesForNode` read of `#hideDone` that in fact turned up NO
 * matched author rule at all on either engine for the simple reason that box-shadow WAS an author
 * rule main always shipped (the old combined field selector, `input:where(:not(.bare-field))`,
 * applied to every `<input>` with no type exclusion) which P2's min-height fix (this file's own
 * comment two groups above) accidentally excluded checkboxes/radios/etc from ALONGSIDE min-height
 * -- there was never any browser-native ring to begin with, on Chromium OR WebKit, on `main` or
 * here; WebKit's 0-diff result in the original evidence was never explained by this theory either,
 * it simply doesn't apply box-shadow to unstyled checkboxes at all the way Chromium's UA sheet
 * happens to draw its OWN (different, unrelated) focus/validity decorations. Proof this was the
 * real cause and the fix above the real fix: `scripts/games_keep_ab.py --engine chromium --pages
 * achievements planner calendar` went from 'FAIL: 15 total diffs' (every one a checkbox/radio
 * boxShadow, base='...0 0 0 1px' head='none') to 0. */
/* round 2 (G1 blocker, games content A/B, scripts/games_keep_ab.py): the comment this replaces
 * claimed "only planner.html's [role=tablist]... matches" -- true when it was written, but
 * story-line.html (shipped the same day, before this branch forked) ALSO uses this container
 * family via `.addseg` (`#slcolseg`), a real gap the old grep-once comment couldn't catch and the
 * A/B tool did. Re-pinned as the FULL selector list the base rule itself uses
 * (`.addseg, nav.tabs, .seg, .viewsw, .qc-seg, [role=tablist]`), not just the one member observed
 * in use today -- cheap to cover the whole family now rather than re-discovering each member one
 * probe run at a time. Each content-root branch needs BOTH a plain and a `:is()`-self form: when
 * the matching element (e.g. planner's `.tabs`, itself `role=tablist`) IS the content root -- a
 * DIRECT child of `.shell-page`/`<body>`, per this block's own header derivation -- a descendant
 * combinator alone never matches it (a selector can't reach "this same node" through a descendant
 * step), only a genuinely NESTED instance; both forms are needed to cover either shape.
 * fix round 1 (finding 15, still true): the unconditional `border-radius:0` re-pin over-corrects at
 * PHONE width -- base's OWN phone capsule rule already gives this family the identical pill radius
 * + `--sp-1` padding head carries there (unchanged by this leg) -- so this stays desktop-scoped
 * (min-width:48.01rem, the same breakpoint base's droplet-lens file used for `.seg-thumb`'s own
 * flat-fill fallback): re-pins `.seg-thumb` to base's desktop selection idiom (flat `--glass-tint`
 * fill, no shadow, since the new plain glass pill is itself a P2 repaint), and clears the border-
 * radius the NEW always-on track rule above adds at every width -- base's desktop `.tabs`/`[role=
 * tablist]`/`.addseg`/etc had none. `:is()` here is safe (only ONE property, no competing rule of
 * equal-or-lower specificity found for it, unlike the padding/background story below) -- self AND
 * descendant forms both needed: when the matching element (planner's `.tabs`, itself `role=
 * tablist`) IS the content root -- a direct child of `.shell-page`/`<body>` -- a descendant
 * combinator alone never reaches it, only a genuinely nested instance.
 * round 2 (G1, same A/B, two passes): this used to ALSO zero `padding` on EVERY match (self and
 * descendant) -- disproven by CDP `CSS.getMatchedStylesForNode` on `.tabs`: it carries its OWN
 * page-level `.tabs{padding:.5rem 0}` rule (planner.html, unrelated to this leg), tied in
 * specificity with the SELF-matching form, so glass.css's own load-order tiebreak silently
 * clobbered it. `padding:0` is restored below but ONLY on the DESCENDANT form -- a NESTED instance
 * (story-line's `#slcolseg`, matched via `.addseg`) has no such page-level rule of its own and does
 * need it (base's `.addseg` padding is 0; the always-on track rule above adds `3px` platform-wide).
 * A plain comma list, not `:is()`, for the SAME reason the background/border/box-shadow re-pin
 * below needs one: `nav.tabs`'s own compound specificity (0,1,1) is higher than `.addseg`/`.seg`/
 * etc alone (0,1,0), and `:is()` would inflate every match to that ceiling regardless of which
 * alternative actually matched -- no proven collision for padding today, but consistent with the
 * one proven case rather than reintroducing the same class of bug unobserved. */
@media (min-width:48.01rem){
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)):is(.addseg,nav.tabs,.seg,.viewsw,.qc-seg,[role=tablist]),
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) :is(.addseg,nav.tabs,.seg,.viewsw,.qc-seg,[role=tablist]),
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)):is(.addseg,nav.tabs,.seg,.viewsw,.qc-seg,[role=tablist]),
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) :is(.addseg,nav.tabs,.seg,.viewsw,.qc-seg,[role=tablist])
  { border-radius:0; }
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .addseg,
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) nav.tabs,
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .seg,
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .viewsw,
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .qc-seg,
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) [role=tablist],
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .addseg,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) nav.tabs,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .seg,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .viewsw,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .qc-seg,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) [role=tablist]
  { padding:0; }
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .seg-thumb,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .seg-thumb{ background:var(--glass-tint);box-shadow:none; }
}
/* round 2 (G1 blocker, same A/B): the "unselected segmented item" family (glass.css's segmented-
 * control section: `.addseg button, nav.tabs .tab, .seg-lbl span, .viewsw button, .qc-seg button,
 * [role=tab], .seg>button` -- flattened to transparent/no-shadow/no-border, a NEW P2 pool addition)
 * reaches games' own `.addseg`/`[role=tab]` items with no games-keep counterpart, so a games
 * segment button lost its OLD button-paint rim/edge entirely (story-line's `#slcolseg` children,
 * planner's `.tab-btn` items) instead of keeping it -- proven by the same A/B tool. Re-pinned to the
 * same frozen paint the plain button-geometry re-pin above restores, PLUS the border this specific
 * reset zeroes outright (`border:0`): base's own (pre-this-leg) `button,.btn{border:1px solid
 * var(--bd)}` box model was already colour-transparent by an EARLIER fix (glass.css's bare-button
 * rule), so its WIDTH (1px, invisible) still occupied space main's layout accounted for -- `border:1px
 * solid transparent` restores that same invisible-but-space-taking box, not `border:0`.
 * `[role=tab]` is DELIBERATELY EXCLUDED from this background+border pair (kept in its own box-
 * shadow-only rule further below): planner's `.tab-btn` (the one games `[role=tab]` consumer) carries
 * its own page-level `.tab-btn{background:none;border:none}` rule, tied in specificity with this one
 * (both (0,1,0)) -- glass.css's own file header says it wins equal-specificity ties by load order
 * (loaded LAST), so adding background/border here would silently OVERRIDE `.tab-btn`'s already-
 * correct (and unrelated to this leg) styling instead of restoring anything main ever had.
 * round 2 (G1, same A/B, one more pass): a PLAIN comma list here, never `:is(...)` -- `:is()`'s
 * specificity is the MAX of every one of its arguments, not the specificity of whichever argument
 * actually matched a given element, so bundling `.addseg button` (0,1,1) together with `nav.tabs
 * .tab` (0,2,1) inflated EVERY match, including a plain `.addseg button` one, to the higher figure.
 * That collided with the platform's own SELECTED-item rule (`.addseg button[aria-pressed=true],
 * ...`, also (0,2,1)) at story-line's `#slcolseg` -- an EQUAL-specificity tie this (later-declared)
 * rule then won by source order, wrongly painting the SELECTED segment's fill too (base leaves a
 * pressed segment transparent so the sliding thumb shows through). A plain list keeps each
 * alternative's OWN true specificity ((0,1,1) for `.addseg button`), well under the selected-item
 * rule's (0,2,1), so that rule keeps deciding the pressed segment exactly as it always did.
 * round 2 (G1, same A/B, 390px pass, TWO attempts): background+box-shadow for this family (and the
 * standalone `[role=tab]` box-shadow rule right after it) are DESKTOP-ONLY (the SAME
 * `min-width:48.01rem` gate the container reset above uses) -- at phone width base shows NEITHER
 * on any of these items (the segmented-track section's own comment: "only the phone `.glass-cap`
 * capsule... paints anything behind the thumb" -- games' segmented controls never carry `.glass-
 * cap`, so their phone look was always plain); un-gated, this pair would WIN the cascade over the
 * base "unselected item" reset it counters even though that reset's OWN transparent/none result is
 * already what phone needs. `border`, however, does NOT follow this split -- first attempt bundled
 * it into the SAME desktop-only rule and broke story-line's `#slcolseg` items at 390px: base's 1px-
 * wide, colour-TRANSPARENT border (the older, width-INDEPENDENT bare-button box model this file's
 * own comment above already explains) holds at every width, so it needs its own UNGATED rule,
 * immediately below, kept OUTSIDE the media block that background/box-shadow live in. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .addseg button,
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) nav.tabs .tab,
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .seg-lbl span,
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .viewsw button,
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .qc-seg button,
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .seg>button,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .addseg button,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) nav.tabs .tab,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .seg-lbl span,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .viewsw button,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .qc-seg button,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .seg>button{
  border:1px solid transparent;
}
/* 2026-10-02 DE-GLASS: the 09-18 glass-rim re-pin that used to sit here for the segmented-item family and
 * `[role=tab]` is gone. At B (45fe0188) a games segment/tab was a plain module-styled button -- no rim, no
 * ring, no edge. `[role=tab]` (planner's `.tab-btn`) keeps no shadow; the `.addseg`/`nav.tabs`/`.seg` items
 * (story-line, a page B never had) fall back to the pool's own flat unselected-item recipe. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) [role=tab],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) [role=tab]{ box-shadow:none; position:static; z-index:auto; }
/* DE-GLASS: the draggable-lens affordances (grab cursor on the selected item, the bar's stacking context, the
 * sliding thumb on a plain `[role=tablist]`) are not games content -- a games tab/segment is a plain pointer
 * button as at B. (story-line's `.addseg` keeps its thumb: that page postdates B and needs it for the
 * selected state; glass.js already runs it on the frozen, non-draggable path.) */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .addseg button[aria-pressed=true],
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) [role=tab][aria-selected=true],
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) nav.tabs .tab-btn[aria-selected=true],
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .seg>button[aria-checked=true],
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .viewsw button[aria-pressed=true],
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .qc-seg button[aria-pressed=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .addseg button[aria-pressed=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) [role=tab][aria-selected=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) nav.tabs .tab-btn[aria-selected=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .seg>button[aria-checked=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .viewsw button[aria-pressed=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .qc-seg button[aria-pressed=true]{ cursor:pointer; touch-action:auto; }
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .tab, :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .tab{ position:static; }
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) [role=tablist]>.seg-thumb, :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)):is([role=tablist])>.seg-thumb,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) [role=tablist]>.seg-thumb, :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)):is([role=tablist])>.seg-thumb{ display:none; }
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)):is([role=tablist]), :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) [role=tablist], :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)):is([role=tablist]), :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) [role=tablist]{ position:static; }
/* planner's selected `.tab-btn` kept the module's own `background:var(--surface,#fff)` at B; the pool's
 * "selected segment item is transparent, the sliding thumb owns the highlight" rule (glass.css segmented
 * section) outranks the module's (0,2,0), so the module value is restated here. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .tabs .tab-btn[aria-selected=true],
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)):is(.tabs) .tab-btn[aria-selected=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .tabs .tab-btn[aria-selected=true],
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)):is(.tabs) .tab-btn[aria-selected=true]{ background:var(--surface,#fff); }
/* round 2 (G1, same A/B, 390px pass): `.seg-thumb` itself (not its items) needs the OPPOSITE
 * treatment at phone -- its desktop re-pin (top of this games-keep block) gives it base's desktop
 * idiom, a flat `--glass-tint` fill; at phone base shows NEITHER that tint NOR the new plain-glass
 * pill paint the platform's own unconditional (all-widths) `.seg-thumb` rule now adds -- fully
 * transparent, no shadow, matching every OTHER phone segmented-control surface in games (nothing
 * painted behind the thumb, since games never adopted the phone `.glass-cap` capsule material that
 * idiom depends on). `max-width:48rem` (not `47.99rem`) matches shell.css's own phone breakpoint
 * for this exact family, mutually exclusive with the `min-width:48.01rem` gate above so the two
 * never compete for the same element. */
@media (max-width:48rem){
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .seg-thumb,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .seg-thumb{
    background:transparent; box-shadow:none;
  }
  /* fix round 1 (pool, finding 2): base's phone `.seg-thumb` showed a raised white pill behind the
   * selected segment (story-line's `#slcolseg`: Main/Character/World Series/World) -- but it came
   * from the OLD droplet-lens's own CHILD elements (z-index 2), deleted platform-wide by P1 ("no
   * refraction, ever"), never from the container's own background/box-shadow (confirmed: base's
   * `.seg-thumb` container itself is transparent too -- only its children painted). Zeroing the
   * container above therefore correctly matches base's container-level paint (games_keep_ab.py
   * stays 0 diffs there), but leaves NOTHING visible behind a selected phone segment any more, since
   * P1 also deleted the children that used to do the painting -- there is no lens left to restore,
   * refracting or not, so a byte-identical fix does not exist; a `::before` (never a real DOM
   * child, so games_keep_ab.py's walk -- which does not recurse into `.seg-thumb` -- never sees it,
   * matching the finding's own note that this script "cannot see it") fills the exact box the old
   * lens occupied with the SAME plain, non-refracting pill the platform's own new segmented thumb
   * already uses everywhere else (the unscoped `.seg-thumb` rule above this games-keep block) --
   * verified in real screenshots (WebProbe), not this A/B script. */
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .seg-thumb::before,
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .seg-thumb::before{
    content:"";position:absolute;inset:0;border-radius:inherit;
    background:var(--seg-thumb-fill);
    box-shadow:inset 1px 1.2px 0 0 var(--glass-rim), 0 0 0 .5px var(--glass-edge), 0 2px 6px var(--glass-thumb-shadow);
  }
}
/* round 2 (G1, same A/B, 390px pass, TWO attempts): `.tab` (bare class) collides with an UNRELATED
 * platform rule -- the phone tab bar's OWN `#tabbar .tab` sizing (shell.css: `min-height:calc(var(
 * --phonebar-h) - 2*var(--sp-1))`) is written against the bare `.tab` class with no `#tabbar`
 * scoping, so it also reaches any OTHER page's unrelated `.tab` class -- here, `/games/test/rules-
 * audit`'s own inline filter tabs (`.tab{padding:4px 12px;...}`, no `min-height` of their own).
 * First attempt re-pinned `min-height:auto` directly -- WRONG: base (main) has this exact SAME
 * bare-`.tab` collision already (confirmed via CDP on port 8141) and computes 44px from it, not 0 --
 * `--phonebar-h` was RETUNED as part of this leg's approved chrome work (shell.css: `calc(var(
 * --sp-8) + var(--sp-2))` on base, 52px, -> `calc(var(--sp-8) + 2*var(--sp-2))` on head, 60px, a
 * real, intended phone-tab-bar-height change), so the SAME collision now computes 52px (`60-8`)
 * instead of base's 44px (`52-8`) -- `auto` overshot the fix to 0. Re-pinning the TOKEN itself back
 * to its old formula, scoped to games content, is the correct shape here (matching every other
 * entry in this block) -- `--phonebar-h` stays var()-driven outside games content (the REAL
 * `#tabbar`, a true sibling never reached by this selector, keeps the new 60px), so this has no
 * effect on the actual phone tab bar anywhere on the platform. */
/* fix round 1 (pool, finding 17): re-pinning the SHARED `--phonebar-h` token (as the comment above
 * still explains the collision it was chasing) also reaches shell.css's OWN `.scroll-y{padding-
 * bottom:calc(var(--phonebar-h)+...)}` -- the bottom clearance a page-fixed page's inner scroller
 * reserves for the REAL floating tab bar -- since that rule reads the exact same token. Games'
 * OWN page-fixed pages (story-line's `#slgraph`) sit inside `.shell-page`, so they inherit this
 * re-pin too: 84px of clearance instead of every other page's 92px, an 8px content:read collision
 * with the bar this token was never meant to affect. Fixed at the true point of collision instead
 * -- the bare `.tab` class directly, to its OLD computed min-height (52-8=44px, the same formula
 * the comment above already derives) -- `--phonebar-h` itself stays live everywhere in games
 * content now, so `.scroll-y`'s bottom padding is correct again. Scoped to the SAME `max-width:
 * 48rem` the real `#tabbar .tab` collision rule (shell.css) only ever applies under -- at desktop
 * width base computes `auto` for a bare `.tab` (no floor at all, nothing collides there), so an
 * unconditional re-pin here regressed rules-audit's own desktop tabs to 44px where base has none
 * (caught by scripts/games_keep_ab.py's own full-page sweep, 1440 width). */
/* (DE-GLASS: the phone bare-`.tab` min-height re-pin is gone with the block above.) */
/* round 2 (G1 finding 22/23): a `[role=tab]` element anywhere on the platform (including planner's
 * own `[role=tablist]` strip above) now gets a `min-height:44px` coarse-pointer floor (glass.css's
 * segmented-control section, a NEW pool-gaps addition this same leg -- games predates it and never
 * had any such floor). `[role=tab]`'s bare specificity (0,1,0) beats the games button-geometry
 * reset's `button:where(...)` (0,0,1), so it would win the cascade and grow a games tab past its
 * frozen size under a coarse pointer regardless of the geometry reset above -- re-pinned back to
 * `auto` (button-geometry's own initial value) the same way that reset already does unconditionally. */
@media (pointer:coarse){
  :where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) [role=tab],
  :where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) [role=tab]{ min-height:auto; }
}
/* round 2 (G1 blocker, same A/B, story-line's `#slpopclose.ctl-quiet`): `.ctl-quiet` is deliberately
 * EXCLUDED from the button-geometry re-pin above (it is the platform's OTHER, newer quiet-control
 * idiom, not an "old games button" needing its paint restored) -- but `.ctl-quiet` does not reset
 * `white-space` itself, so it still inherited the P2-introduced base `button,.btn{white-space:
 * nowrap}` (components.css) that main never had. This one property alone needs the same `unset`
 * fix as the main geometry re-pin, without touching any of `.ctl-quiet`'s own (correct, shared)
 * border/background/box-shadow. */
:where(.games-keep .shell-page > :not(#shell,#usermenu,#dock,#dockback,#dockcapturepanel)) .ctl-quiet,
:where(.games-keep body > :not(#rail,.rail-backdrop,.shell-page,#tabbar,.toast,#usermenu,#lsdlg,#reauthdlg,.dpick)) .ctl-quiet{ white-space:unset; }

/* ================= day-grid buttons (round 3e) =================
 * Owner screenshot: the calendar sidebar mini-month's day cells rendered as bordered circles ("no
 * edge, it's too dense"). Same button-property-cascade leak as every other fix this pass: .mm-day
 * (calendar.css's sidebar mini-month AND the twelve Year-view grids, same _miniGrid() builder) and
 * Today's 7-day-strip .day (dashboard.css) each reset border/background/radius for their own
 * design but never touched box-shadow, so the platform's bare `button{}` recipe went unopposed on
 * every dense day cell. Fixed ONCE here for every current (and future) day-grid button rather than
 * sprinkling box-shadow:none across each module's own file — each grid still owns its actual
 * at-rest look (.day's own bordered-tile design is untouched; only the leaked shadow is reset). */
.mm-day, .day{ box-shadow:none; }
/* fix round (pool-gaps, finding 3): the base pool `button,.btn{min-height:2rem}` (32px) still
 * reached every dense grid/round-cell control that sets its OWN small width+height -- a day cell
 * kept its intended width but was stretched to 32px tall (an oval, not a circle/square). Enumerated
 * here (same discipline as the quiet-controls sweep above) rather than per-module: `.mm-day`/`.day`
 * (calendar/today day-grid cells, joined here by `.jr-day`, journal's own mini-month day cells),
 * `.chk` (every done/checkbox circle -- To-Do's list-row circle AND its info-card subtask circle,
 * `.det-sub .chk.chk--sm`), `.iconcell` (the list-dialog icon-swatch picker cell).
 * fix round 1 (2026-09-25, finding 2/14): widened from `.row .chk` to bare `.chk` -- the info
 * card's own subtask checkbox (`.det-sub .chk.chk--sm`, todo.css) is a REAL <button> too and hit
 * the exact same 32px-floor-beats-16px-height leak (a 16x32 oval instead of a 16x16 circle,
 * caught only under `@media(pointer:coarse)`'s own separate reset, never at rest/desktop). Per
 * the finding: this belongs once in the shared `.chk` recipe, not as a second per-context
 * exemption -- every OTHER `.chk` in the app (calendar's `.ev-deadline .chk`, the subtask add-row
 * circle) is a plain `<span>`, never reached by the button pool rule in the first place, so
 * widening the selector costs nothing there. */
/* round 2 (G1 leftover 8): three more dense/quiet controls widened this exemption -- each already
 * had `box-shadow:none` from the quiet-controls-sweep group above (so no rim/glass paint leaked),
 * but not `min-height:0`, so the base pool 32px floor still stretched them into an oval/tall capsule
 * the same way every OTHER entry on this line was already fixed for: journal's mini-month `.jr-nav`
 * (‹/› nav icons, base 28x28 -> was 28x32), ledger's `.linkbtn` (Budgets "Edit" and every other
 * inline text-link affordance it renders, base 25x20 -> was 25x32 -- text-link affordances are
 * text-sized everywhere they appear, matching the box-shadow:none treatment they already got
 * unconditionally), calendar's `#calprev`/`#calnext` (base 25x31 -> was 25x32). */
/* fix round 1 (pool, finding 10): `#calprev`/`#calnext` as bare ID selectors are (1,0,0) --
 * higher than ANY class/attribute combination regardless of source order, so they always beat
 * shell.css's coarse-pointer 44px floor (`main button:not(...)`, (0,1,2)) the same way `.jr-nav`
 * right next to them here never could (a class always loses that fight, which is exactly why
 * `.jr-nav` correctly still grows to 44x44 under a coarse pointer while these two shrank to
 * 44x30). `[id=calprev]`/`[id=calnext]` target the exact same two elements as an ATTRIBUTE
 * selector, (0,1,0) -- the standard way to write an id-scoped rule that still loses ties the way a
 * class does, restoring the same behaviour `.jr-nav` already has rather than adding a second,
 * order-dependent override. */
.mm-day, .day, .jr-day, .chk, .iconcell, .jr-nav, .linkbtn, [id=calprev], [id=calnext]{ min-height:0; }

/* --- .more: the "…" disclosure trigger of the more-menu pool row (2026-10-02; moved VERBATIM from
   modules/todo/static/css/todo.css so a page that does not load todo.css -- Projects, the routine
   page -- gets the same trigger; one source, never two). The menu panel itself is `.more-menu`. */
details.more{position:relative}   /* details only: games.js toggles a bare 'more' class on the .gp-nav strip (scroll-edge flag) */
details.more>summary{list-style:none;cursor:pointer;box-sizing:border-box;width:28px;height:28px;padding:0;border-radius:var(--radius-pill);   /* flat-buttons: 28px in-row icon box */
 
  font-size:var(--fs-title);color:var(--muted);display:inline-flex;align-items:center;justify-content:center}
details.more>summary::-webkit-details-marker{display:none}
@media (hover:hover){ details.more>summary:hover {background:var(--hover);color:var(--fg)} }
details.more[open]>summary{background:var(--hover);color:var(--fg)}
/* near the bottom of the screen the menu opens upward instead (glass.js flipMenu sets the attribute; the
   specificity beats a module's own `top:100%` placement, which is only ever the resting default) */
details.more>.more-menu[data-flip]{top:auto;bottom:100%}

/* --- glass-cap--step: declared variant of the `segmented` row (phone flaws D3, 2026-10-23). A prev/next stepper capsule
   (‹ title ›: Ledger's month, Calendar's range) whose 44px buttons ARE its height: no 4px ring, so the capsule is 44px
   and sits level with the 44px view menu / + beside it instead of 52px. Phone only (the capsule paints nowhere else). */
@media (max-width:48rem){
  .glass-cap--step{padding:0}
}
/* --- addseg--scroll: declared variant of the `segmented` row (lifted from story-line.css 2026-10-02;
   Projects is its second module -- the project view bar at phone width). */
.addseg--scroll{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;
  white-space:nowrap}   /* + white-space (projects): at phone width .addseg computes display:block, so its
                           inline buttons wrapped like text and flex-wrap alone never applied */
.addseg--scroll::-webkit-scrollbar{display:none}
/* edge fade (critique A4: "Links" was cut off at 390px with no sign it existed): the right edge
   fades over --sp-6 and the same --sp-6 is end padding, so the last segment clears the fade
   once scrolled fully right. Phone-only: at desktop width the segment fits and is never masked. */
@media (max-width:48rem){
  /* the tab strip (Stocks sections, phone toolbar) carries the variant too: `nav.tabs` sets overflow:visible at (0,1,1), above the variant's (0,1,0), so it is restated here (phone only: the desktop lens overflows the strip) */
  nav.tabs.addseg--scroll{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;white-space:nowrap}
  nav.tabs.addseg--scroll::-webkit-scrollbar{display:none}
  .addseg--scroll, nav.tabs.addseg--scroll{max-width:100%;padding-inline-end:var(--sp-6);
    -webkit-mask-image:linear-gradient(to left,transparent,black var(--sp-6));
            mask-image:linear-gradient(to left,transparent,black var(--sp-6))}
}
/* --- addseg--dense: declared variant of the `segmented` row (G3, 2026-10-05). Six options that fit
   one phone row WITHOUT scrolling: the items are auto-width grid columns, which stretch to share the row and keep only --sp-1 of
   inner spacing as their minimum, so the six Projects view tabs fit 358px and 328px (a 360px phone)
   and the labels stay whole. Below 48rem only; at desktop width the base spacing is untouched. The
   tap height stays the base 44px on coarse pointers (min-height is the base's). If a label set is
   ever wider than the row (a long translation) the row scrolls instead of clipping. */
@media (max-width:48rem){
  .addseg--dense{display:grid;grid-auto-flow:column;grid-auto-columns:auto;overflow-x:auto;scrollbar-width:none}
  .addseg--dense::-webkit-scrollbar{display:none}
  .addseg--dense>button{padding-inline:var(--sp-1)}
}
