/* css/store.css — the storefront skin. Self-contained: the store pages load
   THIS file and nothing else — no deck-ui.css, no scry-ui.css. Dark chrome,
   Steam-shaped: two-tier mast, a featured module, shelf rows of cards, a game
   page with media left and an action rail right. Fonts are the town's own
   vendored OFL faces (fonts/), declared here so no request ever leaves
   the origin.

   THE PALETTE, and why (operator, 2026-08-03: "xbox black and green vibes,
   robinhood green", over ungstein/OG-Steam — the VGUI skin that reproduces
   the 2004 Steam client). Three sources, one ramp:

     · black       Xbox's near-black chrome, not a navy. #0A0C0A up to #1F2620.
     · #107C10     Xbox green — STRUCTURE. Tab bars, rules, active states.
     · #00C805     Robinhood green — MONEY AND ACTS. Filled buttons, links,
                   any number that settles.
     · #A0AA95     OG-Steam's own `Text` colour, lifted from
                   resource/styles/steam.styles ("160 170 149") — the sage the
                   2004 client set every label in. It is the secondary ink
                   here for the same reason it worked there: it recedes on
                   black without going grey-dead.

   COLOUR CARRIES MEANING, and green is now the chrome, so colour ALONE can no
   longer say "settled" — the fill does. A green OUTLINE or green INK is
   decoration and means nothing. A SOLID green fill (.btn, .pill.live) is
   reserved for an act that moves money or a state that is armed on chain.
   Gold is the honest opposite: --store-gold marks in-build, not-armed, and
   caution. --breach-red is breach only. */

@font-face { font-family: "Space Grotesk";
  src: url("../fonts/spacegrotesk.woff2") format("woff2");
  font-weight: 400 700; font-display: swap; }
@font-face { font-family: "JetBrains Mono";
  src: url("../fonts/jbmono.woff2") format("woff2");
  font-weight: 100 800; font-display: swap; }

:root {
  /* the black ramp */
  --store-bg: #0a0c0a;
  --store-hull: #0e1210;      /* mast tier one, footer */
  --store-pit: #121613;       /* inputs, wells, capsule backdrop */
  --store-panel: #161b17;
  --store-panel-2: #1f2620;
  --store-line: #2b332c;
  --store-line-2: #3a453c;

  /* the ink ramp */
  --store-ink: #e8f0e9;
  --store-muted: #a0aa95;     /* OG-Steam steam.styles Text="160 170 149" */
  --store-dim: #8a9689;      /* the floor: 4.5:1 on --store-panel, measured */

  /* the greens */
  --g-live: #00c805;          /* robinhood — acts, links, settled money */
  --g-core: #107c10;          /* xbox — structure */
  --g-deep: #06340b;
  --g-wash: rgba(0, 200, 5, .09);

  --store-gold: #c9a227;      /* in build · not armed · caution */
  --store-link: var(--g-live);
  --settle-green: var(--g-live);
  --breach-red: #ff3b30;

  /* geometry — the store is square. Steam's chrome has no pill in it. */
  --r: 3px;
  --r-sm: 2px;
  --wrap: 1180px;
  --bevel: inset 0 1px 0 rgba(255, 255, 255, .045);

  /* ── the faces, named twice on purpose ──────────────────────────────────
     --sg / --mono / --body are the names every rule in this sheet paints
     with. --face-* below are what they POINT AT, and they exist because of a
     cycle that took the type off ten money pages without anything rendering
     as broken.

     css/scry-ui.css carries a legacy alias block that re-points the store's
     names at the deck's — `--mono: var(--face-mono)` — which was right when
     --face-mono came from deck/deck-tokens.css and was a real font stack.
     css/deck-bridge.css then points the deck's names back at the store's:
     `--face-mono: var(--mono)`. Each is reasonable alone; together they are a
     custom-property CYCLE, and a cycle resolves to the empty string. Every
     declaration painting with var(--mono) or var(--body) was then invalid at
     computed-value time and was DROPPED — including `body { font: 15px/1.5
     var(--body) }`, so all seventeen pages that load both sheets rendered
     their body copy in the browser's initial font at the browser's initial
     size: Times New Roman at 16px, on the money floor of a store built out of
     Space Grotesk. Every `.num`, every balance, every tabular figure lost
     JetBrains Mono the same way.

     Measured with getPropertyValue on documentElement, which returns "" for a
     cycle and the stack for a healthy token — the front page (no scry-ui.css)
     read the stack, wallet/pools/bank/claim read "". Aliasing through --face-*
     breaks the loop at its source: nothing here references a deck name, so
     nothing downstream can close a ring back onto it. */
  --face-sg: "Space Grotesk", system-ui, sans-serif;
  --face-jb: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --face-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --sg: var(--face-sg);
  --mono: var(--face-jb);
  --body: var(--face-ui);
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }

/* ⚠ `hidden` MUST MEAN HIDDEN, and this sheet is why it did not. The attribute
   is implemented as a UA rule (`[hidden] { display: none }`) with the weakest
   origin in the cascade, so ANY author `display` on the same element beats it
   silently: the element stays on screen and the JS that set `.hidden = true`
   looks like it ran and did nothing.

   This file already carried FIVE separate one-off patches for it — `.btn`,
   `.crumbs`, `.inv-connect`, `.hub-id`, `.doors-drawer` — each written after
   the trap bit again, each with a comment rediscovering it. Two more were
   found by rendering the pages on 2026-08-11:

     · `.askrow` (seat.html) — a "show more" button sat under an EMPTY gallery
       on a run with nothing minted, offering to load more of nothing
     · `.instr .seal` (verify.html) — a green MOUNTED badge on the Herald,
       shown whether or not the probe in js/verify.js ever answered. That one
       is a page CLAIMING A SERVICE IS LIVE without having looked, which is the
       failure this repo cares about most

   One rule instead of an eighth patch, and it lives here rather than in
   deck-bridge.css because every page loads this sheet and only eleven load
   that one. `!important` is right rather than lazy: the whole point of the
   attribute is that it outranks layout, and there is no such thing as an
   element that is both hidden and displayed. The five patches below are left
   where they are — each is a true statement at its point of use, and each
   records an incident. `test_site.py` gates this line so it cannot be dropped.

   Anything that wants to be present-but-invisible (for animation, for a
   screen reader) must use `visibility`, `opacity` or `.visually-hidden`, never
   the attribute. */
[hidden] { display: none !important; }
body {
  background: var(--store-bg); color: var(--store-ink);
  font: 15px/1.5 var(--body);
  /* the faintest green cast over the black, top-down — the thing that keeps a
     near-black page from reading as a bug report */
  background-image: radial-gradient(120% 60% at 50% -10%, rgba(16, 124, 16, .13), transparent 60%);
  background-attachment: fixed;
}
img { max-width: 100%; display: block; }
a { color: var(--store-link); text-decoration: none; }
a:hover { text-decoration: underline; }
:focus-visible { outline: 2px solid var(--g-live); outline-offset: 2px; border-radius: var(--r-sm); }
.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 16px 48px; }
.skip { position: absolute; left: -999px; top: 0; background: var(--store-panel-2); padding: 12px 14px; min-height: 44px; z-index: 9; }
.skip:focus { left: 8px; }
.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.dim { color: var(--store-dim); }

/* ── the mast: two tiers, the way a client's chrome is built ─────────────
   Tier one is the wordmark row (wordmark · wishlist · search); tier two is
   the nav band, full-bleed and one shade lighter, with the active tab lit
   from below in Xbox green. The markup is a single .mast-in on every store
   page, so both tiers are laid out here with grid areas rather than by
   adding a wrapper to fourteen files. The band behind tier two is painted by
   .paper-mast::before, whose height MUST equal the nav row's (44px, the tap
   floor) — the one coupling in this file, and it is why .mast-nav sets an
   explicit height instead of letting its content size it. */
.paper-mast {
  /* z-index 30, and it is load-bearing rather than defensive. `.mast-in` sets
     z-index:1, which makes it a STACKING CONTEXT — so the browse menu's
     z-index:20 is scoped inside it and competes with the page as a 1, not as
     a 20. `.mq-body` is a 2. The menu opened over the marquee and the
     marquee won, with the panel's own background painting under the hero
     copy. Raising the mast itself is the fix: one context for the whole
     chrome, above the page, and the numbers inside it stay local. */
  position: relative; z-index: 30;
  background-color: #131813; background-image: linear-gradient(180deg, #131813 0%, var(--store-hull) 100%);
  border-bottom: 1px solid #000;
  box-shadow: var(--bevel), 0 6px 20px rgba(0, 0, 0, .55);
}
.paper-mast::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 45px;
  background-color: var(--store-panel-2); background-image: linear-gradient(180deg, var(--store-panel-2) 0%, #141a15 100%);
  border-top: 1px solid var(--store-line);
  pointer-events: none;
}
.mast-in {
  position: relative; z-index: 1;
  max-width: var(--wrap); margin: 0 auto; padding: 0 16px;
  display: grid; align-items: center; gap: 0 14px;
  grid-template-columns: auto 1fr auto auto;
  grid-template-areas: "brand props get purse" "nav nav search wish";
}
.wordmark {
  grid-area: brand;
  font-family: var(--sg); font-weight: 700; font-size: 1.34rem; letter-spacing: .02em;
  color: var(--store-ink); min-height: 52px; display: inline-flex; align-items: center; gap: 9px;
}
/* the mark: a green glass orb — the store's only logo, drawn in CSS so it
   costs no request. It is the SAME mark as /favicon.svg and the social card;
   if you change the light here, change it there (watchtower/build_meta.py
   holds the SVG, and its --check fails when the two drift apart).

   What makes it read as glass rather than a green circle is four lights, and
   the last one is the one that is usually missing: a hard specular glint up
   and left, a body that deepens toward the rim, a dark terminator along the
   bottom, and then a thin BOUNCE along the bottom rim — light that entered
   the sphere, hit the surface under it and came back up. A ball with only a
   highlight looks like plastic; the bounce is what says the thing is clear. */
.wordmark::before {
  content: ""; width: 16px; height: 16px; border-radius: 50%;
  background-color: var(--g-core);
  background-image:
    radial-gradient(circle at 33% 27%, rgba(255, 255, 255, .95) 0 7%, rgba(214, 255, 219, .38) 17%, transparent 33%),
    radial-gradient(circle at 38% 32%, #7cff88 0 10%, var(--g-live) 44%, var(--g-core) 79%, #04300a 100%);
  box-shadow:
    0 0 12px rgba(0, 200, 5, .45),              /* the glow it always had */
    inset 0 -3px 5px rgba(0, 0, 0, .45),        /* the terminator */
    inset 0 2px 3px rgba(255, 255, 255, .16),   /* the crown */
    inset 0 -1px 0 rgba(124, 255, 136, .38);    /* the bounce — this is the glass */
}
.wordmark:hover { text-decoration: none; color: #fff; }

.mast-props {
  grid-area: props; display: flex; gap: 2px; align-items: center;
  overflow-x: auto; scrollbar-width: none;
}
.mast-props::-webkit-scrollbar { display: none; }
.mast-props a {
  font-family: var(--sg); font-weight: 600; font-size: .78rem;
  letter-spacing: .1em; text-transform: uppercase; white-space: nowrap;
  color: var(--store-muted); padding: 0 11px; min-height: 44px;
  display: inline-flex; align-items: center;
}
.mast-props a:hover { color: var(--store-ink); text-decoration: none; }
.mast-props a[aria-current="page"] { color: var(--store-ink); }
.mast-props a[aria-current="page"]::after {
  content: ""; position: absolute; margin-top: 30px; width: 1.4em; height: 2px;
  background: var(--store-ink); opacity: .8;
}
/* the one green thing in the chrome, and it is the client — Steam's "Install
   Steam" button, in the same corner and for the same reason. It is a BUTTON
   and not a nav word on purpose: the store just grew a downloadable client,
   and a link in a row of four uppercase words is not how anyone finds one.
   The solid-green rule (§the meaning colours) permits it — this is an act,
   and the act is leaving with software. */
.mast-get {
  grid-area: get; justify-self: end; white-space: nowrap;
  font-family: var(--sg); font-weight: 700; font-size: .68rem;
  letter-spacing: .12em; text-transform: uppercase;
  color: #04240a; padding: 0 12px; height: 28px; border-radius: var(--r);
  display: inline-flex; align-items: center;
  background-color: var(--g-live); background-image: linear-gradient(180deg, var(--g-live) 0%, var(--g-core) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .45), 0 0 10px rgba(0, 200, 5, .3);
}
.mast-get:hover { text-decoration: none; filter: brightness(1.1); color: #04240a; }
/* THE ACCOUNT CHIP — Steam's, in Steam's corner, for Steam's reason.
   It replaced a bare "the purse" text link on 2026-08-06 (operator: "where is
   my user profile and stuff… the purse navigation make me go to the old
   website"). Two failures were the same failure: the only door to your own
   profile was a footer link, and the chip beside it advertised a balance it
   never showed.

   The shape is two stacked lines against a face, and each line is its own
   anchor rather than one wrapping link — nesting an <a> in an <a> is invalid
   and the balance and the profile are genuinely two places. Top line is who
   you are (→ profile), bottom is what you hold (→ the purse). The face is the
   third target and the largest, because it is what a cursor actually goes for.

   AWAY IS THE HONEST DEFAULT, and the states are named for what is true:
   `away` — no wallet has been read yet, or none is connected. The chip says
     "your profile" and "the purse" — invitations, never a fake identity.
   `here` — a silent eth_accounts read found an address. The name becomes the
     handle if the register knows one and the short address if it does not.
   It never renders a zero balance it did not read; see store.js §the chip. */
.mast-acct {
  grid-area: purse; justify-self: end; white-space: nowrap;
  display: flex; align-items: center; gap: 8px; min-height: 44px;
  /* the account menu hangs off THIS, not off .mast-in like the nav panels —
     it belongs to the chip and follows it when the grid reflows on a phone */
  position: relative;
}
.acct-lines { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; line-height: 1.15; }
.acct-name {
  font-family: var(--sg); font-weight: 600; font-size: .72rem;
  letter-spacing: .06em; color: var(--store-muted); max-width: 15ch;
  overflow: hidden; text-overflow: ellipsis;
}
.acct-name:hover { color: var(--store-ink); text-decoration: none; }
/* AWAY, THE NAME IS A BUTTON (js/store.js §the account chip). It reads as the
   same line of type either way — the corner must not jump between a link and a
   control — so this is a button reset plus the gold that says it does
   something, and nothing else. */
button.acct-name {
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  text-align: right; color: var(--g-live); line-height: inherit;
}
button.acct-name:hover:not(:disabled) { color: var(--store-ink); }
button.acct-name:disabled { color: var(--store-dim); cursor: progress; }
.acct-bal { font-size: .7rem; color: var(--store-dim); letter-spacing: 0; }
.acct-bal:hover { color: var(--store-ink); text-decoration: none; }
.mast-acct[data-state="here"] .acct-bal { color: var(--g-live); }
/* the face: a lettered tile until a picture is both set and permitted, which
   is the same rule the profile's own id-face keeps (store.js §faceSrc) */
.acct-face {
  width: 30px; height: 30px; border-radius: 2px; overflow: hidden; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--store-line); border: 1px solid rgba(255, 255, 255, .07);
  position: relative;
}
.acct-face:hover { border-color: var(--store-muted); text-decoration: none; }
.acct-tile {
  font-family: var(--sg); font-weight: 700; font-size: .82rem;
  color: var(--store-muted); text-transform: uppercase;
}
.acct-face img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

.purse-v { font-size: .74rem; color: var(--g-live); letter-spacing: 0; }
.purse-v.dim { color: var(--store-dim); }

.mast-nav {
  grid-area: nav; height: 44px;
  display: flex; gap: 0; overflow-x: auto; scrollbar-width: none;
}
.mast-nav::-webkit-scrollbar { display: none; }
.mast-nav a {
  position: relative;
  font-family: var(--sg); font-weight: 600; font-size: .715rem;
  letter-spacing: .15em; text-transform: uppercase; white-space: nowrap;
  color: var(--store-muted); padding: 0 15px; height: 44px;
  display: inline-flex; align-items: center;
  border-right: 1px solid rgba(0, 0, 0, .35);
  box-shadow: 1px 0 0 rgba(255, 255, 255, .022);
}
.mast-nav a:hover { color: var(--store-ink); text-decoration: none; background: rgba(255, 255, 255, .035); }
.mast-nav a[aria-current="page"] {
  color: #fff; background: linear-gradient(180deg, rgba(16, 124, 16, .30) 0%, rgba(16, 124, 16, .06) 100%);
}
/* the lit edge under the current tab. Decoration, not a settled-money fill. */
.mast-nav a[aria-current="page"]::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--g-live); box-shadow: 0 0 9px rgba(0, 200, 5, .7);
}
/* ── the money bar: a third tier, on the money pages only ────────────────
   Built by js/store.js §MONEY_ROOMS into <nav id="money-nav">, directly under
   the mast. It reads as chrome because it IS chrome — same band, same type,
   same lit edge under the room you are in — and it replaced ten description
   panels above the footer, which were wayfinding placed where nobody looking
   for their way would be.

   It scrolls sideways rather than wrapping. Ten rooms fit one line on a
   desktop and never will on a 390px phone, and the alternatives are both
   worse: wrapped, it is three rows of chrome above every money page's first
   sentence; shortened, some rooms are unreachable from the others. */
.moneybar { background: var(--store-pit); border-bottom: 1px solid var(--store-line); }
.mb-in {
  max-width: var(--wrap); margin: 0 auto; padding: 0 16px;
  /* 44px, the tap floor, and the same height as the mast's tier two — the
     capture's mobile oracle flags every target whose short side is under it,
     and ten rooms at 40px is ten findings on every money page. */
  display: flex; gap: 0; height: 44px;
  overflow-x: auto; scrollbar-width: none;
}
.mb-in::-webkit-scrollbar { display: none; }
.mb-in a {
  position: relative; display: inline-flex; align-items: center; height: 44px;
  padding: 0 13px; white-space: nowrap;
  font-family: var(--sg); font-weight: 600; font-size: .7rem;
  letter-spacing: .14em; text-transform: uppercase; color: var(--store-dim);
}
.mb-in a:hover { color: var(--store-ink); text-decoration: none; background: rgba(255, 255, 255, .03); }
.mb-in a.on { color: var(--store-ink); }
.mb-in a.on::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--g-live); box-shadow: 0 0 9px rgba(0, 200, 5, .7);
}
/* the first and last rooms sit flush with the page's own gutter, so the strip
   lines up with the copy under it instead of floating 13px in */
.mb-in a:first-child { padding-left: 0; }
.mb-in a:last-child { padding-right: 0; }
@media (max-width: 560px) {
  /* the capture's tinyType oracle measures these at 11.2px — under the 13px
     floor every page-local block already enforces. The strip scrolls and its
     edge fades, so larger type costs scroll distance and nothing else. */
  .mb-in a { font-size: max(13px, .7rem); }
}
@media (max-width: 900px) {
  /* ten rooms never fit a phone, so the strip always scrolls here and its right
     edge FADES rather than guillotining a room mid-word — the difference
     between "there is more this way" and "this is broken" */
  .mb-in {
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
    mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
  }
}

.mast-search { grid-area: search; width: 230px; }
.mast-search input {
  width: 100%; min-height: 34px; padding: 0 11px;
  background: var(--store-bg); color: var(--store-ink);
  border: 1px solid var(--store-line); border-radius: var(--r);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .55);
  font: .84rem var(--body);
}
.mast-search input::placeholder { color: var(--store-dim); }
.mast-search input:focus { border-color: var(--g-live); outline: none; box-shadow: inset 0 1px 3px rgba(0, 0, 0, .55), 0 0 0 1px rgba(0, 200, 5, .35); }
/* ── the burger + the menu (2026-08-04) ─────────────────────────────────
   Both are injected by js/store.js, and both are OPT-IN by a class that same
   script adds (.mast-collapsible). With no script the burger does not exist,
   the class is absent, and every rule below that hides a tier is inert — the
   mast falls back to the wrapped rows it has always been. A chrome whose only
   way to the navigation is a button that failed to render is worse than a
   tall chrome. */
.mast-burger {
  grid-area: burger; display: none; justify-self: end;
  /* 44, not 42: this is the control that holds the entire navigation on a
     phone, and it is the only one that does. Two pixels short of the floor is
     still short of the floor. */
  width: 44px; height: 44px; padding: 0; cursor: pointer;
  background: transparent; border: 1px solid var(--store-line); border-radius: var(--r);
}
.mast-burger:hover { border-color: var(--store-line-2); background: rgba(255, 255, 255, .035); }
.mast-burger .bg-bars, .mast-burger .bg-bars::before, .mast-burger .bg-bars::after {
  display: block; width: 18px; height: 2px; background: var(--store-ink); border-radius: 1px;
}
.mast-burger .bg-bars { margin: 0 auto; position: relative; }
.mast-burger .bg-bars::before, .mast-burger .bg-bars::after { content: ""; position: absolute; left: 0; }
.mast-burger .bg-bars::before { top: -6px; }
.mast-burger .bg-bars::after { top: 6px; }
.mast-open .mast-burger { border-color: var(--g-core); }

/* the one dropdown, behind BROWSE — Steam's Categories menu, at our size.
   The caret sits AFTER the label, which is where every client puts it and
   where a reader's eye leaves the word. The panel is a child of .mast-in
   (the centred wrap, position:relative) and the script writes its `left`
   from the trigger's own offset, so it hangs under the tab rather than at
   the far edge of a 1440px viewport. */
.mast-nav a[data-menu]::after {
  content: "▾"; font-size: .8em; margin-left: 6px; opacity: .75;
}
.mast-menu {
  position: absolute; z-index: 20; left: 0; top: 100%; margin-top: -1px;
  min-width: 244px; padding: 6px;
  background-color: var(--store-panel-2); background-image: linear-gradient(180deg, var(--store-panel-2) 0%, #10150f 100%);
  border: 1px solid var(--store-line-2); border-top: 0; border-radius: 0 0 var(--r) var(--r);
  box-shadow: 0 14px 34px rgba(0, 0, 0, .6);
}
.mast-menu a {
  display: block; padding: 8px 11px; border-radius: var(--r);
  color: var(--store-muted); font-size: .86rem; min-height: 34px;
}
.mast-menu a:hover { background: rgba(255, 255, 255, .05); color: var(--store-ink); text-decoration: none; }
.mast-menu a b { display: block; font-weight: 600; color: var(--store-ink); }
.mast-menu a span { display: block; font-size: .76rem; color: var(--store-dim); }

/* ── the account menu, and the way out ──────────────────────────────────
   Signed in, the face opens this. It borrows .mast-menu's whole skin so the
   corner does not grow a second panel idiom, and adds the two things a nav
   menu has no use for: who you are signed in as, and how to stop being.

   ⚠ IT SITS BELOW `.mast-menu` ON PURPOSE, and it was 100 lines above it for
   one revision. `.acct-menu` and `.mast-menu` are both single-class selectors,
   so they tie on specificity and SOURCE ORDER decides — with this block up
   beside the chip it styles, `.mast-menu { left: 0 }` won, `left: auto` lost,
   and the panel opened from the chip's LEFT edge and hung 52px off the right
   of the screen. Same cascade fault the site has now been bitten by four
   times (see §the pill, and CHANGELOG 2026-08-11). An override of a shared
   idiom goes after the idiom.

   WHITE-SPACE IS RESET HERE for a second reason that is not the cascade:
   `.mast-acct` is `nowrap` — the chip is two short lines that must never break
   mid-address — and this panel hangs INSIDE it, so every note inherited nowrap
   and the menu grew to the width of its longest sentence. The nav menus never
   showed it because they are appended to `.mast-in`, which does not set it. */
.acct-menu {
  left: auto; right: 0; min-width: 262px; max-width: 300px;
  white-space: normal;
}
.acct-who {
  margin: 0; padding: 6px 11px 8px; font-size: .74rem; color: var(--store-dim);
  border-bottom: 1px solid var(--store-line);
}
.acct-who b { color: var(--store-muted); font-weight: 600; }
.acct-out {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: none; border: 0; border-top: 1px solid var(--store-line);
  margin-top: 6px; padding: 8px 11px; border-radius: var(--r);
  font-family: inherit; font-size: .86rem; color: var(--store-muted);
}
.acct-out:hover { background: rgba(255, 255, 255, .05); }
.acct-out b { display: block; font-weight: 600; color: var(--store-ink); }
/* the caveat is the point of it — a sign-out here moves nothing and signs
   nothing, and a reader is owed that before they press it, not after */
.acct-out span {
  display: block; font-size: .74rem; color: var(--store-dim); line-height: 1.4;
}

@media (max-width: 699px) {
  /* ONE ROW ON A PHONE. Measured before this: 245px of chrome at 390px —
     brand+purse, the four props, search+wishlist, six tabs — stacked between
     the top of the screen and the first thing the store is for. A client's
     header is one row; so is this one, and everything it held is behind the
     burger.

     The band (.paper-mast::before) is dropped here rather than resized: it
     paints one 45px strip and cannot align to a nav that has wrapped. The
     base rule keeps its hand-matched height for the desk, which is the
     coupling test_site.py checks. */
  .mast-in {
    /* the install button stays on the phone's one row and the purse does not.
       A store with a client has exactly one thing to offer a visitor who has
       never been here, and it is not a balance. The purse is one tap into the
       burger; on a 390px row the two together push the wordmark to wrap. */
    grid-template-columns: auto 1fr auto;
    grid-template-areas: "brand get burger" "props props props"
                         "purse purse purse" "search search wish" "nav nav nav";
    padding-bottom: 8px; gap: 0 10px;
  }
  .mast-props, .mast-nav { flex-wrap: wrap; overflow-x: visible; height: auto; }
  .mast-props a { padding: 0 10px 0 0; }
  .mast-nav a { border-right: 0; box-shadow: none; padding: 0 13px 0 0; }
  .mast-nav a[aria-current="page"] { background: none; }
  .mast-nav a[aria-current="page"]::after { left: 0; right: auto; width: 1.6em; }
  .mast-search { width: auto; margin: 0 0 8px; }
  .mast-wish { justify-self: end; }
  .paper-mast::before { display: none; }

  .mast-burger { display: block; }
  .mast-collapsible .mast-props,
  .mast-collapsible .mast-nav,
  .mast-collapsible .mast-search,
  .mast-collapsible .mast-wish { display: none; }
  /* the chip does NOT collapse — it is the door to your own profile and your
     own money, and burying both behind a burger is the thing this change was
     made to stop. It narrows instead: the face survives, the two text lines
     fold away, which is the shape Steam's own mast takes on a phone. */
  .mast-collapsible .acct-lines { display: none; }
  .mast-collapsible .mast-in { padding-bottom: 0; }
  /* ONCE THE LINES FOLD AWAY THE FACE IS THE WHOLE CHIP, so on a phone it is
     the only thing standing between a thumb and both your profile and your
     money — and it was a 30px square, a cursor's target rather than a
     thumb's. Same reason `install` gets a floor here: these two are the only
     controls left on the collapsed row, and neither cleared 44px. Phone-only,
     so the desk keeps the mast height test_site.py couples to the band. */
  .acct-face { width: 44px; height: 44px; }
  .mast-get { min-height: 44px; }

  .mast-open .mast-props, .mast-open .mast-nav,
  .mast-open .mast-search, .mast-open .mast-wish { display: flex; }
  .mast-open .acct-lines { display: flex; }
  .mast-open .mast-search { display: block; margin: 8px 0; }
  .mast-open .mast-in { padding-bottom: 10px; }
  /* opened, the two tiers read as one list rather than as a mast that grew */
  .mast-open .mast-props, .mast-open .mast-nav {
    flex-direction: column; align-items: stretch; gap: 0;
    border-top: 1px solid var(--store-line); padding-top: 4px; margin-top: 4px;
  }
  .mast-open .mast-props a, .mast-open .mast-nav a { padding: 0; min-height: 44px; }
  /* the menu is a phone's whole width, under the row it belongs to. The
     script writes an inline `left` from the tab's offset, so this has to
     beat it — an inline style loses to !important and to nothing else. */
  .mast-menu { left: 8px !important; right: 8px; min-width: 0; }
  /* …except the account menu, whose offset parent is the chip rather than
     .mast-in, so the nav panels' inline `left` never applies to it and the
     rule above would push it 8px off its own right edge. It is the one
     control that survives the collapse, so it has to land under the face on a
     phone as well as on the desk — and it takes the width the nav panels take,
     for the same reason they take it. At the desk's 262–300px it was a
     260px column in a 390px screen with "your profile" broken over two lines
     and the sign-out caveat over five; on a phone this menu IS the account
     UI, so it gets the room the burger's panels get. */
  .acct-menu {
    left: auto !important; right: 0;
    min-width: 0; max-width: none; width: calc(100vw - 16px);
  }
}

/* ── the tape strip: the market band, read like a broker's ───────────────
   Mono figures, a live dot, and a rule under it. Nothing here is coloured by
   direction — the endpoint reports no delta, so inventing a red or a green
   arrow would be inventing the number's sign. */
.tapebar {
  display: flex; align-items: center; gap: 0 20px; flex-wrap: nowrap;
  overflow-x: auto; padding: 9px 14px; margin: 14px 0 4px;
  background-color: var(--store-panel); background-image: linear-gradient(180deg, var(--store-panel) 0%, #101410 100%);
  border: 1px solid var(--store-line); border-radius: var(--r);
  box-shadow: var(--bevel);
  font-size: .8rem; white-space: nowrap; scrollbar-width: none;
}
.tapebar::-webkit-scrollbar { display: none; }
/* THE TAPE WRAPS ON A PHONE INSTEAD OF SCROLLING, and this override sits AFTER
   the rule it corrects rather than up in the phone block with the rest of the
   mast — the phone block is earlier in this file, and at equal specificity the
   later rule wins, so an override written up there loses to the base rule it
   was written to beat. (It did, silently, for one iteration.)
   `overflow-x: auto` with `scrollbar-width: none` is a scroller with no
   affordance at all: at 390px the strip rendered "SCRY $0.000214  FDV $214,0"
   and cut the holder count off the right edge with nothing on screen saying
   there was more. A cut edge with no shadow, fade or arrow reads as a broken
   layout rather than as "drag me", and the holder count is one of the two
   numbers this audience screens a launch with. Three short figures wrap to two
   lines and are all legible; the desk keeps the single line it was drawn for. */
@media (max-width: 699px) {
  .tapebar { flex-wrap: wrap; overflow-x: visible; gap: 6px 18px; white-space: normal; }
  /* `margin-left: auto` is what holds the door at the far end of a single
     line; once the strip wraps it spends a whole third line doing it */
  .tp-more { margin-left: 0; }
}
.tp-label {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--sg); font-weight: 700; letter-spacing: .17em;
  text-transform: uppercase; font-size: .64rem; color: var(--store-ink);
}
.tp-label::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--g-live); box-shadow: 0 0 8px var(--g-live);
}
.tp { color: var(--store-dim); font-family: var(--sg); font-weight: 600; font-size: .64rem; letter-spacing: .14em; text-transform: uppercase; }
.tp .tp-v { color: var(--store-ink); font-size: .88rem; font-family: var(--mono); letter-spacing: 0; text-transform: none; margin-left: 7px; }
.tp .tp-v.dim { color: var(--store-dim); font-family: var(--body); font-size: .76rem; font-weight: 400; }
.tp-more { margin-left: auto; font-size: .72rem; font-family: var(--sg); font-weight: 600; letter-spacing: .1em; text-transform: uppercase; }

/* ── the featured module ────────────────────────────────────────────────
   Steam's front-page anatomy, and the reason it beats a full-bleed hero: art
   left, a dark sidebar right carrying the name, the blurb, the facts and the
   act. A hero with one small SVG in it reads as a page that failed to load; a
   two-column module reads as a shelf with one thing on it, which is the truth.
   The old .hero-* names are kept — nothing else styles them under this sheet,
   and the markup is one page's.

   THE ART SLOT IS 616x353, not 16:9 (corrected 2026-08-03). That is Steam's
   MAIN CAPSULE, which is the asset a store's featured slot takes; 3840x1240 is
   the LIBRARY HERO, a different asset for a different surface. The slot had
   been 16:9 and was being fed the library-hero-shaped drawing, so `cover` ate
   43% of its width. Steam's answer to a shape mismatch is never to reshape the
   slot — it is one asset per slot, at that slot's spec — so the slot now states
   the capsule ratio and takes the capsule. Module height barely moved (461 ->
   470px) because 818/1.745 and 818/1.778 are nine pixels apart. */
.hero {
  position: relative; margin-top: 16px;
  border: 1px solid var(--store-line); border-radius: var(--r); overflow: hidden;
  display: grid; grid-template-columns: minmax(0, 1fr) 330px; align-items: stretch;
  background-color: var(--store-panel-2); background-image: linear-gradient(180deg, var(--store-panel-2) 0%, var(--store-panel) 100%);
  box-shadow: var(--bevel), 0 14px 34px rgba(0, 0, 0, .5);
}
/* ⚠ `width: 100%` IS LOAD-BEARING AND THE PRICE PILL IS WHAT PROVED IT.
   `aspect-ratio` on a grid item whose height is definite (this row is stretched
   to the tallest slide) resolves `width: auto` FROM THE RATIO instead of taking
   the column — so on any slide whose right-hand column is taller than the art's
   natural height, the art grew to 823px inside a 734px column and hung 89px
   into the body. Nobody saw the picture, because the body is opaque and paints
   over it — but `.pill.price` is bottom-right of the ART at z-index 3, so the
   price tag ended up floating over the "install it" button on the front page's
   first slide. Both boxes definite, ratio ignored, `cover` does the cropping. */
.hero-art {
  position: relative; display: block; width: 100%; aspect-ratio: 616 / 353; overflow: hidden;
  background:
    radial-gradient(100% 90% at 20% 0%, rgba(16, 124, 16, .16), transparent 60%),
    linear-gradient(150deg, #1b221c 0%, #12170f 60%, #0c0f0c 100%);
}
.hero-img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  /* `cover` is kept as the guard for art that arrives off-spec, but with a
     616x353 capsule in a 616x353 slot it now crops NOTHING. There is no
     `object-position` bias any more: the one that stood here pushed the crop
     down the Y axis to save a "top crop" that was never happening — the art
     was the wider of the two, so the SIDES were going, and a Y bias cannot
     move a horizontal crop. `meter/storekeeper.py` §ART_SLOTS measures this on
     save now, so a shape mismatch reports itself instead of being reasoned
     about in a comment. */
}
/* the veil now only darkens the art's bottom edge, where the state pill sits */
.hero-veil {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg, transparent 55%, rgba(6, 8, 6, .72) 100%);
}
.hero-body {
  position: relative; z-index: 2; display: flex; flex-direction: column;
  padding: 18px 18px 16px; border-left: 1px solid var(--store-line);
  background-color: #191f1a; background-image: linear-gradient(180deg, #191f1a 0%, #10140f 100%);
}
.hero-eyebrow {
  font-family: var(--sg); font-weight: 700; font-size: .62rem;
  letter-spacing: .2em; text-transform: uppercase; color: var(--g-live);
}
.hero-name { font-family: var(--sg); font-weight: 700; font-size: clamp(1.7rem, 3.2vw, 2.15rem); line-height: 1.05; margin: 6px 0 8px; }
/* the thumb strip: the featured module's other frames, small and square */
.hero-strip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 14px; }
.hs {
  position: relative; display: block; aspect-ratio: 16 / 9; overflow: hidden;
  border: 1px solid var(--store-line); border-radius: var(--r-sm);
  background-color: #1b221c; background-image: linear-gradient(150deg, #1b221c, #0d100c);
}
.hs img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.hero-fine { color: var(--store-dim); font-size: .68rem; margin-top: 7px; }
/* a state tag riding on art — Steam's corner tag. Never a fill; the fill is
   reserved, and "in build" is the opposite of settled. */
.pill {
  position: absolute; left: 12px; bottom: 12px; z-index: 3;
  font-family: var(--sg); font-weight: 700; font-size: .62rem;
  letter-spacing: .15em; text-transform: uppercase;
  padding: 5px 9px; border-radius: var(--r-sm);
  background: rgba(6, 8, 6, .82); color: var(--store-muted);
  border: 1px solid var(--store-line-2); backdrop-filter: blur(2px);
}
.pill.build { color: var(--store-gold); border-color: rgba(201, 162, 39, .55); }
.pill.live { background: var(--g-live); color: #06180a; border-color: #0a6a11; }
/* the price rides the OTHER corner — the state pill owns bottom-left, and two
   absolutely-positioned tags on the same corner is one tag with a bug */
.pill.price {
  left: auto; right: 12px; font-family: var(--mono); letter-spacing: .06em;
  color: var(--store-ink);
}
.hero-blurb { color: var(--store-muted); font-size: .875rem; max-width: 46em; }
.hero-tags {
  color: var(--store-dim); font-size: .68rem; margin-top: 10px;
  font-family: var(--sg); font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  line-height: 1.7;
}
.hero-acts {
  display: flex; gap: 8px; flex-wrap: wrap; margin-top: auto; padding-top: 16px;
}
.hero-acts .btn { flex: 1 1 auto; }
@media (max-width: 899px) {
  .hero { grid-template-columns: 1fr; }
  .hero-body { border-left: none; border-top: 1px solid var(--store-line); }
}

/* ── chips + buttons ─────────────────────────────────────────────────────
   The green button is the store's one loud object. It is a fill, and a fill
   is the reserved form: it marks the act a page is actually for. Everything
   secondary is an outline, and an outline may be any colour in the ramp
   because an outline promises nothing. */
.chip {
  display: inline-block; font-family: var(--sg); font-weight: 700;
  font-size: .62rem; letter-spacing: .13em; text-transform: uppercase;
  padding: 4px 8px; border-radius: var(--r-sm); border: 1px solid var(--store-line);
  color: var(--store-muted); background: rgba(0, 0, 0, .35);
}
.chip.build { color: var(--store-gold); border-color: rgba(201, 162, 39, .5); background: rgba(201, 162, 39, .08); }
.chip.live { color: var(--g-live); border-color: rgba(0, 200, 5, .5); background: var(--g-wash); }
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 40px; padding: 0 18px; border-radius: var(--r);
  background-color: #00c805; background-image: linear-gradient(180deg, #1cd928 0%, #00c805 45%, #04a409 100%);
  color: #06180a; border: 1px solid #0a6a11; cursor: pointer;
  font-family: var(--sg); font-weight: 700; font-size: .84rem; letter-spacing: .01em;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .45), inset 0 -1px 0 rgba(0, 0, 0, .2), 0 1px 2px rgba(0, 0, 0, .6);
  text-shadow: 0 1px 0 rgba(255, 255, 255, .22);
  transition: filter .12s ease, box-shadow .12s ease;
}
/* ⚠ THE FOURTH TIME, SO IT IS FIXED AT THE CLASS AND NOT AT THE INSTANCE.
   `display:inline-flex` above beats the UA's [hidden] rule, so every button
   that ships hidden and is unhidden by JS renders anyway until someone adds a
   one-off guard. That has now been rediscovered by rendering three separate
   times — .crumbs (2026-08-06), .inv-connect (2026-08-04) and .hub-id — each
   time as a live surface offering a control that could not answer. A page-level
   fix cannot catch the next one; this can. The per-instance rules below are
   left in place: they are subsumed, not wrong. */
.btn[hidden] { display: none; }
.btn:hover { text-decoration: none; filter: brightness(1.1); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5), 0 0 14px rgba(0, 200, 5, .35); }
.btn:active { filter: brightness(.94); box-shadow: inset 0 2px 4px rgba(0, 0, 0, .4); }
.btn.ghost {
  background-color: var(--store-panel-2); background-image: linear-gradient(180deg, var(--store-panel-2) 0%, #141a15 100%);
  color: var(--store-ink); border-color: var(--store-line-2);
  box-shadow: var(--bevel); text-shadow: none;
}
.btn.ghost:hover { border-color: var(--g-live); color: #fff; filter: none; box-shadow: var(--bevel), 0 0 0 1px rgba(0, 200, 5, .3); }
.btn.slim { min-height: 34px; padding: 0 13px; font-size: .76rem; }
.btn[aria-disabled="true"] { opacity: .45; pointer-events: none; filter: grayscale(.6); }
/* the real attribute as well as the ARIA one: a script that sets `disabled`
   on a press (connectAcct, while the wallet's dialog is open) got no visual
   change at all, so the button looked live and unpressed while it waited. */
.btn:disabled { opacity: .45; filter: grayscale(.6); cursor: progress; }
.btn:disabled:hover { filter: grayscale(.6); box-shadow: var(--bevel); }
/* a button for a thing that is not armed yet: gold outline, never a green
   fill — the fill would promise the act works */
.btn.pending {
  background: rgba(201, 162, 39, .08); color: var(--store-gold);
  border-color: rgba(201, 162, 39, .5); box-shadow: none; text-shadow: none;
}
.btn.pending:hover { filter: none; background: rgba(201, 162, 39, .14); box-shadow: none; }

/* ── shelf rows ──────────────────────────────────────────────────────────
   The section head is the store's strongest structural tell: a rule under
   every title with a short green tick sitting on it. Steam does it with a
   coloured bar; Xbox does it with a tick; both make a page of dark panels
   read as sections rather than as a stack. */
.row { margin-top: 34px; }
.row-head {
  position: relative; display: flex; align-items: baseline; gap: 6px 14px;
  flex-wrap: wrap; margin-bottom: 14px; padding-bottom: 9px;
  border-bottom: 1px solid var(--store-line);
}
.row-head::after {
  content: ""; position: absolute; left: 0; bottom: -1px; width: 54px; height: 2px;
  background: var(--g-live); box-shadow: 0 0 10px rgba(0, 200, 5, .5);
}
.row-head h2, .row > h2 {
  font-family: var(--sg); font-weight: 700; font-size: .95rem;
  letter-spacing: .17em; text-transform: uppercase; color: var(--store-ink);
}
.row > h2 {
  margin-bottom: 14px; padding-bottom: 9px; border-bottom: 1px solid var(--store-line);
  position: relative;
}
.row > h2::after {
  content: ""; position: absolute; left: 0; bottom: -1px; width: 54px; height: 2px;
  background: var(--g-live); box-shadow: 0 0 10px rgba(0, 200, 5, .5);
}
.row-sub { color: var(--store-dim); font-size: .78rem; }
.shelf {
  display: flex; gap: 12px; overflow-x: auto; padding: 2px 2px 14px;
  scroll-snap-type: x mandatory; scrollbar-width: thin;
  scrollbar-color: var(--store-line) transparent;
}
.shelf > * { flex: 0 0 246px; scroll-snap-align: start; }
.shelf > .row-empty { flex: 1 0 auto; font-size: .85rem; padding: 8px 2px; }
@media (min-width: 900px) { .shelf > * { flex-basis: 274px; } .shelf > .row-empty { flex-basis: auto; } }

/* the short shelf. store.js sets .few when a band holds one or two titles;
   the cards then lie down into full-width strips instead of leaving most of
   the row empty. Steam's own front page does the same thing — a carousel
   when there is a lot, a strip when there is not. */
.shelf.few {
  display: grid; grid-template-columns: 1fr; gap: 12px;
  overflow: visible; padding-bottom: 2px;
}
.shelf.few > * { flex: none; }
.shelf.few .card { flex-direction: row; align-items: stretch; }
.shelf.few .capsule {
  flex: 0 0 44%; max-width: 400px; aspect-ratio: 616 / 353;
  border-bottom: none; border-right: 1px solid var(--store-line);
}
.shelf.few .card-meta { padding: 16px 18px; gap: 6px; }
.shelf.few .card-name { font-size: 1.22rem; }
.shelf.few .card-foot { justify-content: flex-start; gap: 10px; padding-top: 14px; }
.shelf.few .card-foot .btn.wish { flex: 0 0 auto; margin-top: 0; margin-left: auto; }
.card-blurb { color: var(--store-muted); font-size: .86rem; max-width: 56em; }
.card:not(.placeholder) .card-blurb { display: none; }
.shelf.few .card:not(.placeholder) .card-blurb { display: block; margin-top: 2px; }
@media (max-width: 699px) {
  .shelf.few .card { flex-direction: column; }
  .shelf.few .capsule { flex-basis: auto; max-width: none; border-right: none; border-bottom: 1px solid var(--store-line); }
  .shelf.few .card-foot .btn.wish { margin-left: 0; }
}

/* ── the capsule card ────────────────────────────────────────────────────
   Capsule on top, a darker meta shelf under it, and the whole thing lifts on
   hover with a green edge. The green is the outline kind: it says "this is
   the thing under your cursor", never "this is paid for". */
.card {
  background-color: #181e19; background-image: linear-gradient(180deg, #181e19 0%, #10140f 100%);
  border: 1px solid var(--store-line);
  border-radius: var(--r); overflow: hidden; display: flex; flex-direction: column;
  box-shadow: var(--bevel);
  transition: transform .12s ease, border-color .12s ease, box-shadow .12s ease;
}
.card:hover, .card:focus-within {
  transform: translateY(-2px); border-color: var(--g-live);
  box-shadow: 0 0 0 1px rgba(0, 200, 5, .3), 0 12px 26px rgba(0, 0, 0, .6), var(--bevel);
}
.card.build:hover, .card.build:focus-within {
  border-color: var(--store-gold);
  box-shadow: 0 0 0 1px rgba(201, 162, 39, .3), 0 12px 26px rgba(0, 0, 0, .6), var(--bevel);
}
.capsule {
  position: relative; display: block; aspect-ratio: 616 / 353; overflow: hidden;
  border-bottom: 1px solid var(--store-line);
  background:
    radial-gradient(110% 100% at 15% 0%, rgba(16, 124, 16, .16), transparent 55%),
    linear-gradient(150deg, #1b221c 0%, #141a13 60%, #0d100c 100%);
}
.cap-fallback {
  position: absolute; inset: 0; display: flex; align-items: flex-end;
  padding: 12px 14px; font-family: var(--sg); font-weight: 700; font-size: 1.1rem;
  color: var(--store-ink); text-shadow: 0 2px 8px rgba(0, 0, 0, .7);
  background: linear-gradient(180deg, transparent 45%, rgba(6, 8, 6, .6) 100%);
}
.capsule img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: transform .3s ease; }
.card:hover .capsule img { transform: scale(1.035); }
/* ⚠ A COLLECTION'S MARK IS SQUARE AND A CAPSULE IS 616:353, so `cover` crops it
   — the Hive's emblem lost its crown and halo off the top and bottom edges. A
   game capsule is authored at the capsule's own ratio and should still fill it,
   so this is scoped to the collection card rather than loosened globally.
   `pixelated` because the art is 32x32: the browser must not smooth it. */
.capsule.col-cap img { object-fit: contain; image-rendering: pixelated; }
.capsule.col-cap { background: var(--store-sunk, #0c0e0c); }
/* `contain` letterboxes, which uncovers the fallback the art is meant to hide —
   it printed the collection's name a second time in the empty margin. A
   collection capsule always has a mark, so the fallback has no job here. */
.capsule.col-cap .cap-fallback { display: none; }
.card-meta { padding: 11px 12px 12px; display: flex; flex-direction: column; gap: 4px; flex: 1; }
.card-name { font-family: var(--sg); font-weight: 700; font-size: .96rem; color: var(--store-ink); letter-spacing: .01em; }
.card-name:hover { color: var(--g-live); text-decoration: none; }
.card-kind { color: var(--store-muted); font-size: .78rem; }
.card-coins { color: var(--store-dim); font-size: .74rem; font-family: var(--mono); }
.card-foot {
  margin-top: auto; padding-top: 10px; display: flex; align-items: center;
  justify-content: space-between; gap: 8px;
}

/* the price on a capsule. Reads as money — mono, at size, in ink rather than
   muted — because a shelf that whispers its price is a shelf hiding it. Free
   is the same weight in green; "not priced yet" is dim, and a price we could
   not READ renders nothing at all (store.js::priceText). */
.card-price {
  font-family: var(--mono); font-size: .82rem; font-weight: 700;
  color: var(--store-ink); white-space: nowrap; margin-left: auto;
}
.card-price.free { color: var(--g-live); }
.card-price.dim { color: var(--store-dim); font-weight: 400; }
.rc-state .card-price { margin-left: 0; margin-right: 8px; }
.pk-n .card-price { margin-left: 10px; font-size: .78rem; }

/* a market card has no capsule — it is a ticker. Pair in mono at size, the
   role under it, the derived depth where a price would be, and a rule down
   the left in Xbox green so the row reads as instruments, not as games. */
.card.market {
  border-left: 2px solid var(--g-core);
  background-color: #161c17; background-image: linear-gradient(180deg, #161c17 0%, #0e120e 100%);
}
.card.market:hover, .card.market:focus-within { border-color: var(--g-live); border-left-color: var(--g-live); }
.card.market .card-name { font-size: 1.16rem; letter-spacing: .02em; }
.card.market .card-kind { font-size: .74rem; color: var(--store-dim); }
.card.market .card-coins { margin-top: 6px; padding-top: 8px; border-top: 1px dotted rgba(58, 69, 60, .65); font-size: .8rem; }
.card.market .card-coins:not(.dim) { color: var(--g-live); }
.card.market .card-foot { padding-top: 12px; }
.card.market .card-foot .btn { flex: 1 1 0; }

/* ── wide strips (greenlight teaser, the board) ────────────────────────── */
.wide {
  display: flex; align-items: center; gap: 16px; width: 100%;
  background-color: #181e19; background-image: linear-gradient(180deg, #181e19 0%, #10140f 100%);
  border: 1px solid var(--store-line); box-shadow: var(--bevel);
  border-radius: var(--r); padding: 16px 18px; color: var(--store-ink);
  transition: border-color .12s ease, box-shadow .12s ease;
}
.wide:hover { text-decoration: none; border-color: var(--g-live); box-shadow: 0 0 0 1px rgba(0, 200, 5, .28), var(--bevel); }
.wide.gold:hover { border-color: var(--store-gold); box-shadow: 0 0 0 1px rgba(201, 162, 39, .3), var(--bevel); }
.wide h3 { font-family: var(--sg); font-weight: 700; font-size: .98rem; margin-bottom: 5px; letter-spacing: .02em; }
.wide p { color: var(--store-muted); font-size: .86rem; max-width: 58em; }
.wide-cta {
  margin-left: auto; white-space: nowrap; color: var(--g-live);
  font-family: var(--sg); font-weight: 700; font-size: .72rem;
  letter-spacing: .12em; text-transform: uppercase;
}
@media (max-width: 699px) { .wide { flex-direction: column; align-items: flex-start; } .wide-cta { margin-left: 0; } }

/* ── page heads (greenlight, devs) ─────────────────────────────────────── */
.page-head { padding: 26px 0 4px; max-width: 780px; }
.page-head h1 { font-family: var(--sg); font-weight: 700; font-size: clamp(1.6rem, 5vw, 2.3rem); line-height: 1.1; letter-spacing: -.01em; }
.lede { color: var(--store-muted); margin-top: 10px; font-size: .93rem; }

/* ── panels ────────────────────────────────────────────────────────────── */
.panel {
  background-color: #181e19; background-image: linear-gradient(180deg, #181e19 0%, #10140f 100%);
  border: 1px solid var(--store-line); box-shadow: var(--bevel);
  border-radius: var(--r); padding: 14px 16px;
}
.panel + .panel { margin-top: 12px; }
.panel h3 {
  font-family: var(--sg); font-weight: 700; font-size: .7rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--store-muted);
  margin-bottom: 10px;
}
.panel.disarmed { border-style: dashed; color: var(--store-muted); box-shadow: none; background: rgba(22, 27, 23, .6); }
.note { color: var(--store-muted); font-size: .84rem; margin-top: 10px; max-width: 62em; }
.footnote { color: var(--store-dim); font-size: .76rem; margin-top: 10px; }

/* ── achievements: the badge list, and the evidence tag ────────────────────
   The tag is the load-bearing element on this list, not decoration. A badge
   backed by a chain predicate and a badge backed by a recognized server's
   signature make DIFFERENT promises, and rendering them identically would
   launder the weaker one — so the kind is on the badge's face, always.

   ⚠ Neither state gets a solid fill. The reservation at the top of this sheet
   is that a solid green fill means an act that moves money or a state armed on
   chain; a badge does neither, and that is precisely what makes the attested
   kind safe to render at all. Green ink is decoration and promises nothing,
   which is the correct weight here. Gold carries its usual meaning: not armed. */
.badges { list-style: none; display: grid; gap: 10px; }
.badge-row {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline; gap: 6px 12px;
  padding: 10px 12px; border: 1px solid var(--store-line); border-radius: var(--r-sm);
  background: rgba(10, 13, 10, .5);
}
.badge-row > b { color: var(--store-ink); font-size: .92rem; }
.badge-about { grid-column: 1 / -1; color: var(--store-muted); font-size: .8rem; }
.evtag {
  justify-self: end; font-family: var(--sg); font-weight: 700; font-size: .6rem;
  letter-spacing: .14em; text-transform: uppercase; white-space: nowrap;
  padding: 4px 8px; border-radius: var(--r-sm);
  border: 1px solid var(--store-line-2); color: var(--store-muted);
}
.evtag.on { color: var(--g-live); border-color: rgba(0, 200, 5, .45); }
.evtag.off { color: var(--store-gold); border-color: rgba(201, 162, 39, .55); }
@media (max-width: 560px) {
  .badge-row { grid-template-columns: 1fr; }
  .evtag { justify-self: start; }
}

/* ── the Pillar of the Town: the platform board (pillar.html) ───────────
   The tasks themselves reuse .badges/.badge-row/.evtag above, deliberately:
   this board grades the SAME three outcomes those tags already carry, so a
   fourth visual language for "done / not yet / could not check" would be a
   second dialect for one idea. The mapping is fixed in js/store.js and the
   middle case is the one that matters — a task we could not check wears the
   GOLD tag (caution, unknown), never the muted one, because folding "we could
   not look" into "you have not done it" is the one lie this board can tell.

   ⚠ The meter below is NOT the solid-green reservation at the top of this
   sheet. That fill means an act that moves money or a state armed on chain; a
   level moves nothing and gates nothing, so the bar is drawn in gold — the
   same ink the site uses for in-build — and the green stays spent on acts. */
.pil-head {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center;
  gap: 10px 18px; padding: 14px 16px; margin-bottom: 12px;
  border: 1px solid var(--store-line); border-radius: var(--r-sm);
  background: var(--store-panel);
}
.pil-level { display: flex; align-items: baseline; gap: 8px; }
.pil-lv-label {
  font-family: var(--sg); font-weight: 700; font-size: .62rem;
  letter-spacing: .14em; text-transform: uppercase; color: var(--store-dim);
}
.pil-lv { font-family: var(--sg); font-weight: 700; font-size: 1.9rem; line-height: 1; }
.pil-xp { font-size: .78rem; }
.pil-meter-wrap { min-width: 0; }
.pil-meter {
  height: 8px; border-radius: 999px; overflow: hidden;
  background: var(--store-pit); border: 1px solid var(--store-line);
}
.pil-fill {
  display: block; height: 100%; width: 0;
  background: var(--store-gold); transition: width .3s ease;
}
.pil-count { font-size: .78rem; margin-top: 7px; }
/* the floor marker — set whenever any task could not be checked, so the
   number beside it is read as "at least", never as a total */
.pil-count[data-floor="1"] { color: var(--store-gold); }
.pil-group { margin-top: 18px; }
.pil-group > h3 {
  font-family: var(--sg); font-weight: 700; font-size: .68rem;
  letter-spacing: .14em; text-transform: uppercase; color: var(--store-dim);
  margin-bottom: 8px;
}
/* how a task is checked: the contract, the topic, the call. Small, and always
   present — a board whose evidence is a hover is a board nobody recomputes. */
.pil-how {
  grid-column: 1 / -1; color: var(--store-dim); font-size: .72rem;
  overflow-wrap: anywhere;
}
.pil-how .num { color: var(--store-muted); }
@media (max-width: 700px) {
  .pil-head { grid-template-columns: 1fr; }
}

/* ── the game page: the listing, in Steam app-page anatomy ─────────────── */
.crumbs {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 14px 0 2px; font-size: .78rem;
}
/* Same trap as `.hub-id[hidden]` below, and it bit again on 2026-08-06: the
   hub's own crumb is hidden when a post is opened so the post's deeper trail
   is the only one on screen, and `display:flex` above beats the UA's [hidden]
   rule — so the JS set the attribute and the page rendered TWO breadcrumbs.
   Only a rendered page shows this; no static law can see it. */
.crumbs[hidden] { display: none; }
.crumbs a { color: var(--store-muted); }
.crumbs a:hover { color: var(--store-ink); }
.crumbs > span:last-child { color: var(--store-ink); }
.crumb-sep { color: var(--store-muted); opacity: .6; }
.gp-titlerow {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px 14px; flex-wrap: wrap; margin: 4px 0 12px;
}
.game-title { font-family: var(--sg); font-weight: 700; font-size: clamp(1.5rem, 4.5vw, 2.1rem); line-height: 1.1; }

.gp-grid { display: grid; grid-template-columns: minmax(0, 1fr) 324px; gap: 18px; align-items: start; }
@media (max-width: 899px) { .gp-grid { grid-template-columns: 1fr; } }
.gp-main { min-width: 0; display: flex; flex-direction: column; gap: 14px; }
.gp-rail { min-width: 0; display: flex; flex-direction: column; gap: 12px; }
.gp-h {
  position: relative;
  font-family: var(--sg); font-weight: 700; font-size: .78rem;
  letter-spacing: .17em; text-transform: uppercase; color: var(--store-ink);
  padding-bottom: 8px; border-bottom: 1px solid var(--store-line); margin-bottom: 12px;
}
.gp-h::after {
  content: ""; position: absolute; left: 0; bottom: -1px; width: 42px; height: 2px;
  background: var(--g-live); box-shadow: 0 0 8px rgba(0, 200, 5, .45);
}

/* the media player: main frame + thumb strip */
.mp-main {
  position: relative; aspect-ratio: 16 / 9; border: 1px solid var(--store-line);
  border-radius: var(--r); overflow: hidden;
  background:
    radial-gradient(110% 100% at 15% 0%, rgba(0, 200, 5, .10), transparent 50%),
    linear-gradient(150deg, var(--store-panel-2) 0%, #141a13 60%, #0d100c 100%);
}
.mp-main img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.mp-main .cap-fallback { font-size: clamp(1.4rem, 4vw, 2.2rem); }
.mp-chip {
  position: absolute; left: 10px; bottom: 10px; z-index: 2;
  font-family: var(--sg); font-weight: 600; font-size: .64rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--store-gold);
  background: rgba(6, 8, 6, .78); border: 1px solid rgba(201, 162, 39, .55);
  border-radius: var(--r-sm); padding: 4px 8px;
}
.mp-thumbs {
  display: flex; gap: 8px; margin-top: 8px; overflow-x: auto; padding: 2px;
  scrollbar-width: thin; scrollbar-color: var(--store-line) transparent;
}
.mp-thumb {
  position: relative; flex: 0 0 116px; aspect-ratio: 16 / 9; padding: 0;
  border: 2px solid transparent; border-radius: var(--r-sm); overflow: hidden; cursor: pointer;
  background-color: var(--store-panel-2); background-image: linear-gradient(150deg, var(--store-panel-2) 0%, #141a13 60%, #0d100c 100%);
}
.mp-thumb img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.mp-thumb:hover { border-color: var(--store-muted); }
.mp-thumb.on { border-color: var(--store-link); box-shadow: 0 0 8px rgba(0, 200, 5, .45); }

/* the state box in the buy-box slot, and the disarmed pass box.
   Steam's purchase panel, structurally: a lit top edge, a raised body, the
   act hard right. The green rule across the top is the tell that this is the
   box that does something — and it is a rule, not a fill, until the act is
   actually armed. */
.buybox {
  position: relative;
  background-color: #1d241e; background-image: linear-gradient(180deg, #1d241e 0%, #12170f 100%);
  border: 1px solid var(--store-line-2); border-radius: var(--r); padding: 16px;
  box-shadow: var(--bevel), 0 6px 18px rgba(0, 0, 0, .45);
}
.buybox::before {
  content: ""; position: absolute; left: -1px; right: -1px; top: -1px; height: 2px;
  background-color: var(--g-live); background-image: linear-gradient(90deg, var(--g-live), var(--g-core) 60%, transparent);
  border-radius: var(--r) var(--r) 0 0;
}
.buybox-title { font-family: var(--sg); font-weight: 700; font-size: 1.08rem; }
.buybox-line { color: var(--store-muted); font-size: .86rem; margin-top: 6px; max-width: 58em; }
.buybox-acts {
  display: flex; justify-content: flex-end; align-items: center;
  gap: 10px; flex-wrap: wrap; margin-top: 14px;
}
.buybox-tr { display: flex; align-items: center; justify-content: space-between; gap: 8px 10px; flex-wrap: wrap; }
.buybox.disarmed { border-style: dashed; border-color: var(--store-line); box-shadow: none; background: rgba(22, 27, 23, .55); }
.buybox.disarmed::before { background: linear-gradient(90deg, rgba(201, 162, 39, .55), transparent); }
/* a box that a TICKET has demoted (store.js::demoteBelowTicket). It keeps
   every word it had and stops competing for the eye: no lit top edge, no
   raised body. Exactly one box on a page may look like the thing that takes
   your money, and when a title is for sale that box is the ticket's. */
.buybox.secondary {
  background: rgba(22, 27, 23, .5); box-shadow: none;
  border-color: var(--store-line);
}
.buybox.secondary::before { background: none; }
.buybox.secondary .buybox-title { font-size: .98rem; }

/* ── the library shelf (library.html, and the profile's own band) ────────
   One card per copy the wallet holds. Same anatomy as a store capsule read
   sideways: art, then what it is and what you can do with it. */
/* how many games this wallet holds, above the shelf — a stranger's library is
   read as a number first and browsed second. */
.lib-count { font-size: .82rem; margin-bottom: 10px; }
.libgrid { display: grid; gap: 12px; grid-template-columns: 1fr; }
@media (min-width: 720px) { .libgrid { grid-template-columns: 1fr 1fr; } }
.libcard {
  display: flex; gap: 12px; align-items: stretch;
  background-color: #161c17; background-image: linear-gradient(180deg, #161c17 0%, #0e120e 100%);
  border: 1px solid var(--store-line-2); border-radius: var(--r); padding: 10px;
}
.lib-art {
  position: relative; flex: 0 0 116px; border-radius: var(--r-sm); overflow: hidden;
  background: #0b0e0b; display: block; min-height: 74px;
}
.lib-art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.lib-b { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1 1 auto; }
.lib-tr { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.lib-n { font-family: var(--sg); font-weight: 700; font-size: .96rem; color: var(--store-ink); }
.lib-n:hover { color: var(--g-live); text-decoration: none; }
.lib-k { color: var(--store-muted); font-size: .78rem; }
.lib-note { font-size: .76rem; }
.lib-note code { font-family: var(--mono); font-size: .74rem; color: var(--store-ink); }
.lib-acts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: auto; padding-top: 8px; }

/* about this game */
.gp-about p { font-size: .94rem; max-width: 72em; }
.gp-about p + p { margin-top: 10px; }
.about-art {
  position: relative; aspect-ratio: 16 / 9; border: 1px solid var(--store-line);
  border-radius: var(--r); overflow: hidden; margin: 14px 0;
  background-color: var(--store-panel-2); background-image: linear-gradient(150deg, var(--store-panel-2) 0%, #141a13 60%, #0d100c 100%);
}
.about-art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.feat-list { list-style: none; margin-top: 12px; }
.feat-list li { position: relative; padding: 6px 0 6px 22px; font-size: .9rem; color: var(--store-ink); }
.feat-list li::before {
  content: "◆"; position: absolute; left: 2px; top: 9px;
  color: var(--g-live); font-size: .58rem;
}

/* the described-plainly box (the mature-content slot, told truthfully) */
.describebox { background: var(--store-panel); border: 1px solid var(--store-line); border-radius: var(--r); padding: 14px 16px; }
.describebox .gp-h { margin-bottom: 8px; }
.describe-line { font-style: italic; color: var(--store-muted); font-size: .9rem; max-width: 62em; }

/* system requirements: the two tabs */
.reqtab-bar { display: flex; gap: 2px; border-bottom: 1px solid var(--store-line); margin-bottom: 12px; }
.reqtab {
  position: relative;
  font-family: var(--sg); font-weight: 700; font-size: .72rem;
  letter-spacing: .14em; text-transform: uppercase;
  background: transparent; color: var(--store-muted);
  border: 1px solid transparent; border-bottom: none; border-radius: var(--r-sm) var(--r-sm) 0 0;
  min-height: 40px; padding: 0 16px; cursor: pointer;
}
.reqtab:hover { color: var(--store-ink); background: rgba(255, 255, 255, .03); }
/* the on-tab connects to the panel below it: same fill, no bottom edge, and
   a green line across the top the way a selected VGUI tab is lit */
.reqtab.on {
  background-color: #222a23; background-image: linear-gradient(180deg, #222a23 0%, #171d18 100%);
  color: #fff; border-color: var(--store-line); margin-bottom: -1px; padding-bottom: 1px;
}
.reqtab.on::after {
  content: ""; position: absolute; left: -1px; right: -1px; top: -1px; height: 2px; background: var(--g-live);
}
.req ul { list-style: none; margin-top: 6px; }
.req li { padding: 3px 0; color: var(--store-muted); font-size: .85rem; }

/* the quote rows (the press slot, sourced from the design of record) */
.quoterow { padding: 10px 0; border-bottom: 1px solid var(--store-line); }
.quoterow:last-child { border-bottom: none; }
.quoterow blockquote { font-family: var(--sg); font-size: 1rem; color: var(--store-ink); }
.quoterow cite { display: block; margin-top: 4px; color: var(--store-muted); font-size: .76rem; font-style: normal; font-family: var(--mono); }

/* the right rail */
.rail-capsule {
  position: relative; aspect-ratio: 616 / 353; border: 1px solid var(--store-line);
  border-radius: var(--r); overflow: hidden;
  background:
    radial-gradient(110% 100% at 15% 0%, rgba(0, 200, 5, .10), transparent 50%),
    linear-gradient(150deg, var(--store-panel-2) 0%, #141a13 60%, #0d100c 100%);
}
.rail-capsule img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.rail-blurb { font-size: .85rem; color: var(--store-ink); }
.meta-rows { font-size: .8rem; }
.mr { display: flex; gap: 10px; padding: 4px 0; border-bottom: 1px dotted rgba(58, 69, 60, .65); }
.mr:last-child { border-bottom: none; }
.mr-k {
  flex: 0 0 96px; font-family: var(--sg); font-weight: 600; font-size: .64rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--store-muted); padding-top: 2px;
}
.mr-v { color: var(--store-ink); min-width: 0; overflow-wrap: anywhere; }
.tagchips { display: flex; flex-wrap: wrap; gap: 6px; }
.tagchip {
  display: inline-block; padding: 3px 10px; border-radius: var(--r-sm);
  background: var(--store-panel-2); color: var(--store-muted); font-size: .72rem;
  border: 1px solid var(--store-line);
}
a.tagchip:hover { background: var(--g-wash); border-color: rgba(0, 200, 5, .4); color: var(--g-live); text-decoration: none; }

/* a rail box with a Steam-style title bar (the rug screen rides in one) */
.railbox {
  background-color: #171d18; background-image: linear-gradient(180deg, #171d18 0%, #10140f 100%);
  border: 1px solid var(--store-line); border-radius: var(--r); overflow: hidden;
  box-shadow: var(--bevel);
}
.railbox-h {
  background-color: #222a23; background-image: linear-gradient(180deg, #222a23 0%, #171d18 100%);
  border-bottom: 1px solid #000; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05), 0 1px 0 rgba(255, 255, 255, .03);
  padding: 9px 12px; font-family: var(--sg); font-weight: 700; font-size: .66rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--store-ink);
}
.railbox-b { padding: 12px; font-size: .84rem; }
.railbox-b .note { margin-top: 0; margin-bottom: 10px; }
.railbox-b .rug { grid-template-columns: 1fr; }

/* the feature rows (icons column) */
.featrows { display: flex; flex-direction: column; }
.featrow {
  display: flex; align-items: center; gap: 10px; padding: 8px 11px;
  background-color: #181e19; background-image: linear-gradient(180deg, #181e19 0%, #131813 100%);
  border: 1px solid var(--store-line);
  border-bottom: none; color: var(--store-muted); font-size: .79rem;
}
.featrow:first-child { border-radius: var(--r) var(--r) 0 0; }
.featrow:last-child { border-bottom: 1px solid var(--store-line); border-radius: 0 0 var(--r) var(--r); }
.featrow svg { flex: 0 0 auto; color: var(--store-link); }
a.featrow { color: var(--store-muted); }
a.featrow:hover { background: var(--store-panel-2); color: var(--store-ink); text-decoration: none; }
a.featrow:hover svg { color: var(--g-live); }

/* the amber notices (the anti-cheat / EULA slots) */
.amberbox {
  background: rgba(201, 162, 39, .10); border: 1px solid rgba(201, 162, 39, .4);
  border-radius: var(--r); padding: 12px 14px; font-size: .82rem; color: var(--store-muted);
}
.amberbox b { display: block; color: var(--store-gold); font-family: var(--sg); font-size: .86rem; margin-bottom: 4px; }
/* every place a listing's source can be fetched from. A row is a link when a
   browser can follow it and a <code> string when it needs a tool (rad:,
   ipfs://) — the two must not look alike, or the second reads as a dead link
   rather than as something to copy. Long URNs wrap instead of overflowing. */
.amberbox .lic { color: var(--store-gold); font-weight: 600; }
.srclist { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.srclist li { overflow-wrap: anywhere; line-height: 1.45; }
.srclist code { font-size: .78rem; color: var(--store-ink); background: rgba(0, 0, 0, .22); padding: 1px 5px; border-radius: 4px; }

/* works in a browser (the deck-verified slot, ours honestly) */
.okbox {
  display: flex; gap: 10px; align-items: flex-start;
  background: var(--store-panel); border: 1px solid var(--store-line);
  border-radius: var(--r); padding: 12px 14px; font-size: .82rem; color: var(--store-muted);
}
.okbox-tick { color: var(--g-live); font-weight: 700; font-size: 1.05rem; line-height: 1.2; }
.okbox b { color: var(--store-ink); font-family: var(--sg); font-weight: 600; }

/* the info links list */
.infolinks { display: flex; flex-direction: column; }
.infolinks a { font-size: .82rem; min-height: 34px; display: inline-flex; align-items: center; }

/* the build record (the events-and-announcements row) */
.gp-record, .gp-more, .gp-reviews, .gp-items, .gp-stats { margin-top: 30px; }
.record-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 699px) { .record-row { grid-template-columns: 1fr; } }
.record-card {
  position: relative; display: block; border: 1px solid var(--store-line);
  border-radius: var(--r); overflow: hidden; color: var(--store-ink);
  background-color: var(--store-panel-2); background-image: linear-gradient(150deg, var(--store-panel-2) 0%, #141a13 60%, #0d100c 100%);
}
.record-card:hover { border-color: var(--g-live); text-decoration: none; }
.record-card img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.record-scrim {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(6, 8, 6, .2) 0%, rgba(6, 8, 6, .93) 78%);
}
.record-body {
  position: relative; z-index: 2; display: flex; flex-direction: column;
  justify-content: flex-end; gap: 4px; min-height: 168px; padding: 14px;
}
.record-date { color: var(--store-dim); font-size: .7rem; font-family: var(--mono); }
.record-body b { font-family: var(--sg); font-size: 1rem; line-height: 1.25; }
.record-line { color: var(--store-muted); font-size: .8rem; }

/* reviews: the honest empty frame — an empty band renders as empty */
.review-sum {
  display: flex; align-items: baseline; gap: 8px 16px; flex-wrap: wrap;
  background: var(--store-panel); border: 1px solid var(--store-line);
  border-radius: var(--r); padding: 14px 16px;
}
.review-count { font-size: 1.15rem; color: var(--store-ink); }
.review-sum p { font-size: .86rem; max-width: 58em; }

/* more on the platform (the more-like-this slot, no fake siblings) */
.more-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
@media (max-width: 899px) { .more-row { grid-template-columns: 1fr; } }
.more-row .wide { flex-direction: column; align-items: flex-start; height: 100%; }
.more-row .wide-cta { margin-left: 0; margin-top: auto; padding-top: 8px; }

.coin-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.coin-chip {
  font-family: var(--mono); font-size: .78rem; padding: 6px 10px;
  border: 1px solid var(--store-line); border-radius: var(--r-sm); background: var(--store-pit);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .4);
}
.coin-chip b { color: var(--g-live); font-weight: 700; }

/* the rug screen */
.rug { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; }
.rug-cell {
  background: var(--store-pit); border: 1px solid var(--store-line);
  border-radius: var(--r); padding: 10px 12px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .45);
}
.rug-k { font-family: var(--sg); font-weight: 600; font-size: .66rem; letter-spacing: .1em; text-transform: uppercase; color: var(--store-muted); }
.rug-v { font-family: var(--mono); font-size: 1.05rem; margin-top: 5px; word-break: break-word; color: var(--store-ink); }
.rug-v.dim { font-family: var(--body); font-size: .82rem; }
.rug-sub { color: var(--store-muted); font-size: .74rem; font-family: var(--mono); margin-top: 4px; word-break: break-word; }

/* requirements: the two columns */
.req { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
@media (max-width: 699px) { .req { grid-template-columns: 1fr; } }
.req div { background: var(--store-pit); border: 1px solid var(--store-line); border-radius: var(--r); padding: 11px 13px; font-size: .86rem; box-shadow: inset 0 1px 3px rgba(0, 0, 0, .4); }
.req b { font-family: var(--sg); color: var(--store-ink); display: block; margin-bottom: 4px; font-size: .78rem; letter-spacing: .08em; text-transform: uppercase; }

/* ── the ladder + the loop (pure CSS boxes with arrows) ────────────────── */
.ladder, .flow { list-style: none; display: flex; gap: 26px; overflow-x: auto; padding: 6px 2px 14px; counter-reset: rung; }
.ladder li, .flow li {
  position: relative; flex: 0 0 auto; max-width: 240px; min-width: 130px;
  background-color: #181e19; background-image: linear-gradient(180deg, #181e19 0%, #10140f 100%);
  border: 1px solid var(--store-line); box-shadow: var(--bevel);
  border-radius: var(--r); padding: 12px 14px; font-size: .84rem; color: var(--store-muted);
}
.ladder li b { display: block; font-family: var(--sg); font-weight: 700; color: var(--g-live); font-size: .74rem; letter-spacing: .14em; text-transform: uppercase; margin-bottom: 5px; }
.ladder li + li::before, .flow li + li::before {
  content: "\2192"; position: absolute; left: -21px; top: 50%;
  transform: translateY(-50%); color: var(--g-core); font-size: 1rem;
}
.ladder.mini li { min-width: 100px; padding: 10px 12px; }
.flow li { color: var(--store-ink); font-family: var(--sg); font-size: .82rem; }

/* ── compare table (devs) ──────────────────────────────────────────────── */
.scroll-x { overflow-x: auto; }
.compare { width: 100%; border-collapse: collapse; min-width: 480px; }
.compare th, .compare td { text-align: left; padding: 10px 12px; border: 1px solid var(--store-line); font-size: .88rem; vertical-align: top; }
.compare th { font-family: var(--sg); font-size: .74rem; letter-spacing: .08em; text-transform: uppercase; }
.compare th:first-child { color: var(--store-muted); background: var(--store-panel); }
.compare th:last-child { color: var(--g-live); background: var(--store-panel-2); }
.compare td:first-child { color: var(--store-muted); background: rgba(22, 27, 23, .6); }
.compare td:last-child { background: rgba(31, 38, 32, .55); }
.compare td b { color: var(--store-ink); }

/* ── rules list (greenlight) ───────────────────────────────────────────── */
.rules { counter-reset: rule; list-style: none; max-width: 760px; }
.rules li { counter-increment: rule; padding: 10px 0 10px 44px; position: relative; border-bottom: 1px solid var(--store-line); font-size: .92rem; }
.rules li::before {
  content: counter(rule); position: absolute; left: 0; top: 10px;
  width: 28px; height: 28px; border: 1px solid rgba(0, 200, 5, .45); border-radius: var(--r-sm);
  color: var(--g-live); background: var(--g-wash); font-family: var(--mono); font-size: .8rem;
  display: flex; align-items: center; justify-content: center;
}

/* ── CTA row ───────────────────────────────────────────────────────────── */
.cta-row { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 30px; }

/* ── footer ──────────────────────────────────────────────────────────────
   A client's footer, not a blog's: a black band under a green hairline, the
   wordmark as the identity block, and the doors in one quiet row beside it. */
.store-foot {
  position: relative; margin-top: 48px;
  border-top: 1px solid var(--store-line);
  background-color: var(--store-hull); background-image: linear-gradient(180deg, var(--store-hull) 0%, #070907 100%);
}
.store-foot::before {
  content: ""; position: absolute; left: 0; top: -1px; width: 140px; height: 2px;
  background: var(--g-core);
}
/* ── the footer (recut 2026-08-04) ───────────────────────────────────────
   It was a five-column grid of up to twelve mid-weight links, in seven
   different shapes across twenty-two pages, and nothing gated it — the same
   ONE-NAV-N-COPIES defect the mast is gated against, downstairs where nobody
   looked. It is now the shape a client's footer actually is: an identity
   block that says who this is, and ONE quiet row of small links beside it.
   The row is dim and middot-separated on purpose — a footer is where you go
   when you already know what you want, so it should read as a list and never
   compete with the page above it. */
.store-foot .foot-in {
  max-width: var(--wrap); margin: 0 auto; padding: 26px 16px 32px;
  display: grid; gap: 14px 40px; align-items: start;
  grid-template-columns: minmax(0, 22em) minmax(0, 1fr);
}
/* The identity block was a one-line creed until 2026-08-07 — a promise
   restated in the footer of all 30 pages, which is exactly what the
   say-it-once rule forbids ("say it once, agree, move on"). A footer says
   whose site this is; the promises live on one page and are linked, not
   recited. */
.foot-mark {
  position: relative; padding-left: 17px; align-self: center;
  min-height: 44px; display: inline-flex; align-items: center;
  font-family: var(--sg); font-weight: 700; font-size: 1.05rem;
  letter-spacing: .04em; color: var(--store-ink); text-decoration: none;
}
.foot-mark::before {
  content: ""; position: absolute; left: 0; top: .38em; width: 9px; height: 9px;
  border-radius: 2px; background-color: var(--g-live); background-image: linear-gradient(160deg, var(--g-live), var(--g-core));
}
.foot-mark:hover { color: var(--g-live); }
.foot-links {
  display: flex; flex-wrap: wrap; align-items: baseline;
  column-gap: 0; row-gap: 2px; justify-content: flex-end;
}
.foot-links a {
  font-size: .78rem; color: var(--store-dim);
  display: inline-flex; align-items: center;
  min-height: 44px; min-width: 44px; justify-content: center;
}
.foot-links a + a::before {
  content: "·"; color: var(--store-line-2); margin: 0 9px;
}
.foot-links a:hover { color: var(--g-live); }
@media (max-width: 799px) {
  .store-foot .foot-in { grid-template-columns: minmax(0, 1fr); }
  /* The middots are `a + a::before`, so they ride INSIDE the link box and a
     wrap carries one to the head of the next line ("· for agents"). Five
     links never fit one phone line, so on narrow the separator becomes a gap
     rather than a glyph. */
  .foot-links { justify-content: flex-start; column-gap: 18px; }
  .foot-links a + a::before { content: none; margin: 0; }
}

/* ── the items layer: the market + the inventory, honest and empty ─────────
   (docs/items/ITEM-CONTRACT.md — the frames ship dark; every arming is an
   operator act) */

/* the one true state box: prominent placement, dim voice */
.statebox {
  border: 1px dashed var(--store-line-2); border-radius: var(--r);
  background: rgba(22, 27, 23, .6); color: var(--store-muted);
  padding: 16px 18px; margin-top: 18px; font-size: .92rem;
}
/* A statebox now carries derived values — a wallet address is 42 characters
   with no break opportunity in it, and in the inventory's 324px detail column
   it ran clean off the panel. */
.statebox p { max-width: 72em; overflow-wrap: anywhere; }

/* the my-listings strip (two rows, nothing to sell) */
.listrows { display: flex; flex-direction: column; }
.listrow {
  background-color: #181e19; background-image: linear-gradient(180deg, #181e19 0%, #131813 100%);
  border: 1px solid var(--store-line);
  border-bottom: none; padding: 12px 14px; font-size: .86rem;
}
.listrow:first-child { border-radius: var(--r) var(--r) 0 0; }
.listrow:last-child { border-bottom: 1px solid var(--store-line); border-radius: 0 0 var(--r) var(--r); }

/* the market's browse anatomy: filter rail left, results right */
.mkt-grid { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 18px; align-items: start; }
@media (max-width: 699px) { .mkt-grid { grid-template-columns: 1fr; } }
.filter-rail {
  display: flex; flex-direction: column; gap: 12px;
  background-color: #181e19; background-image: linear-gradient(180deg, #181e19 0%, #10140f 100%);
  border: 1px solid var(--store-line); box-shadow: var(--bevel);
  border-radius: var(--r); padding: 14px;
}
.f-label {
  display: block; font-family: var(--sg); font-weight: 600; font-size: .66rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--store-muted);
  margin-bottom: 4px;
}
.filter-rail select, .filter-rail input, .sortbar select {
  width: 100%; min-height: 40px; padding: 0 10px;
  background: var(--store-pit); color: var(--store-ink);
  border: 1px solid var(--store-line); border-radius: var(--r); font: .86rem var(--body);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .45);
}
.filter-rail select:focus, .filter-rail input:focus, .sortbar select:focus { border-color: var(--g-live); outline: none; }
.filter-rail select:disabled, .filter-rail input:disabled, .sortbar select:disabled {
  color: var(--store-muted); opacity: .6; cursor: not-allowed;
}
.f-price { display: flex; gap: 8px; }
.sortbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding-bottom: 10px; border-bottom: 1px solid var(--store-line); margin-bottom: 12px;
}
.sortbar .f-label { margin: 0 0 0 auto; }
.sortbar select { width: auto; min-width: 130px; }
.result-count { font-size: .95rem; color: var(--store-ink); }

/* the recently-sold table: counts, and the count is zero */
.mkt-table { width: 100%; border-collapse: collapse; min-width: 420px; }
.mkt-table th, .mkt-table td {
  text-align: left; padding: 10px 12px;
  border-bottom: 1px solid var(--store-line); font-size: .88rem;
}
.mkt-table th {
  font-family: var(--sg); font-weight: 700; font-size: .66rem; letter-spacing: .15em;
  text-transform: uppercase; color: var(--store-ink);
  background-color: #222a23; background-image: linear-gradient(180deg, #222a23 0%, #171d18 100%);
  border-bottom: 1px solid #000;
}

/* the inventory: per-game tabs, the slot grid, the detail panel */
.inv-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--store-line); }
.inv-tab {
  font-family: var(--sg); font-weight: 700; font-size: .72rem;
  letter-spacing: .14em; text-transform: uppercase;
  background-color: #1b211c; background-image: linear-gradient(180deg, #1b211c 0%, #12170f 100%); color: var(--store-muted);
  border: 1px solid var(--store-line); border-bottom: none;
  border-radius: var(--r-sm) var(--r-sm) 0 0; min-height: 40px; padding: 0 16px;
}
.inv-tab:disabled { opacity: .55; cursor: not-allowed; }
.inv-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) 324px;
  gap: 18px; align-items: start; margin-top: 14px;
}
@media (max-width: 899px) { .inv-grid { grid-template-columns: 1fr; } }
.inv-cells { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
@media (max-width: 699px) { .inv-cells { grid-template-columns: repeat(3, 1fr); } }
.inv-cell {
  aspect-ratio: 1 / 1; background: var(--store-pit);
  border: 1px solid var(--store-line); border-radius: var(--r-sm);
  box-shadow: inset 0 1px 4px rgba(0, 0, 0, .5);
}
/* a cell holding a real item. The empty cell above is a slot; this is a card,
   so it stops being square and grows to whatever the derivation gave it —
   an item with two attributes and one with five both have to read. */
.inv-cell.filled {
  aspect-ratio: auto; min-height: 96px; padding: 8px 9px;
  display: flex; flex-direction: column; gap: 3px;
  background: var(--store-panel); box-shadow: none;
  font-size: .76rem; overflow-wrap: anywhere;
}
/* the Scry Hive band — a seat is a picture first, so its cell is the art with
   a label under it rather than a field list. `auto-fill`, not `auto-fit`: with
   one seat held, auto-fit stretches that card across the whole row and a 32×32
   sprite blown up to the column width is a wall of squares. */
.seat-cells { display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); }
.seat-cell {
  display: block; padding: 8px; text-decoration: none; color: inherit;
  background: var(--store-panel); border: 1px solid var(--store-line);
  border-radius: var(--r-sm);
}
/* ⚠ --store-line-2, NOT --gild. `--gild` is a DECK token and inventory.html
   loads css/store.css alone, so `var(--gild)` there resolves to nothing, the
   whole declaration is dropped, and the hover is a silent no-op that looks
   fine in every page that happens to load both sheets. */
.seat-cell:hover, .seat-cell:focus-visible { border-color: var(--store-line-2); }
/* `pixelated`, and it is not a taste call: the art is 32×32 and the browser's
   default smooth upscale turns a crisp sprite into a blur at any size a card
   actually uses. The contract's own SVG carries shape-rendering="crispEdges"
   for the same reason. */
.seat-cell img { display: block; width: 100%; height: auto; border-radius: 2px;
  background: var(--store-pit); image-rendering: pixelated; }
.seat-cell b { display: block; font-family: var(--mono); font-size: .74rem;
  margin-top: 6px; }
.seat-cell span { display: block; font-size: .66rem; text-transform: uppercase;
  letter-spacing: .1em; color: var(--store-dim); margin-top: 2px; }

.inv-cell .it-id { font-family: var(--sg); font-weight: 700; letter-spacing: .08em; }
.inv-cell .it-kind { font-size: .68rem; text-transform: uppercase; letter-spacing: .1em; }
.inv-cell .it-attr { display: flex; justify-content: space-between; gap: 6px; }
/* `.btn` sets display:inline-flex, which BEATS the UA's [hidden] rule — so the
   connect button rendered on a page with no wallet and no collection to read,
   offering a gesture that could not have answered anything. Same trap the
   .hub-id rule below documents; same fix. Caught by rendering, 2026-08-04. */
.inv-connect { margin-left: auto; align-self: center; }
.inv-connect[hidden] { display: none; }
.inv-detail { background: var(--store-panel); border: 1px solid var(--store-line); border-radius: var(--r); padding: 14px 16px; }
.inv-detail .statebox { background: var(--store-bg); }
.inv-explain { font-size: .88rem; }

/* the game page's items row: one panel, sentence left, door right */
.itemrow { display: flex; align-items: center; justify-content: space-between; gap: 10px 14px; flex-wrap: wrap; }
.itemrow p { max-width: 58em; }

/* ── the profile layer: profile.html + friends.html ────────────────────────
   (views over the register's own read; no third skin.
   What cannot be derived renders dim with its reason.) */

/* the identity header: face, handle, address, the one honest chip */
.id-head {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  background-color: #1d241e; background-image: linear-gradient(180deg, #1d241e 0%, #12170f 100%);
  border: 1px solid var(--store-line); box-shadow: var(--bevel);
  border-radius: var(--r); padding: 16px;
}
.id-face {
  position: relative; flex: 0 0 92px; width: 92px; height: 92px;
  border: 1px solid var(--store-line); border-radius: var(--r); overflow: hidden;
  background:
    radial-gradient(120% 100% at 20% 0%, rgba(201, 162, 39, .18), transparent 55%),
    linear-gradient(150deg, var(--store-panel-2) 0%, #141a13 60%, #0d100c 100%);
}
.id-tile {
  position: absolute; inset: 0; display: flex; align-items: center;
  justify-content: center; font-family: var(--sg); font-weight: 700;
  font-size: 2.4rem; color: var(--g-live);
}
.id-face img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.id-who { min-width: 0; flex: 1 1 240px; }
.id-name {
  font-family: var(--sg); font-weight: 700; line-height: 1.1;
  font-size: clamp(1.4rem, 4.5vw, 2rem); overflow-wrap: anywhere;
}
.id-addr { font-family: var(--mono); font-size: .8rem; color: var(--store-muted); margin-top: 4px; overflow-wrap: anywhere; }
.id-chips { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.id-note { font-size: .78rem; }
.id-act { margin-left: auto; align-self: flex-start; }
@media (max-width: 699px) { .id-act { margin-left: 0; } }
.pf-several { font-size: .82rem; margin-top: 10px; }
#profile-root > .gp-grid { margin-top: 18px; }
#profile-root .gp-main section > .gp-h { margin-bottom: 0; }
#profile-root .gp-main .panel { margin-top: 10px; }

/* the sign-in door, above the lookup one. The order on screen is the order of
   the two questions: whose page is this (yours, by default) and then whose
   else. It was only ever the second. */
.pf-signrow {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 14px 0 22px;
}
.pf-signrow .footnote { margin-top: 0; flex: 1 1 300px; }

/* the lookup door */
.pf-lookline { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
.pf-lookline input {
  flex: 1 1 260px; min-height: 44px; padding: 0 12px;
  background: var(--store-bg); color: var(--store-ink);
  border: 1px solid var(--store-line); border-radius: var(--r); font: .88rem var(--mono);
}
.pf-lookline input::placeholder { color: var(--store-muted); }
.pf-lookline input:focus { border-color: var(--store-link); outline: none; }

/* the rail counts list: a true zero renders, an unreadable count is a dash */
.cr {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; padding: 6px 0; border-bottom: 1px dotted rgba(58, 69, 60, .65);
  font-size: .84rem;
}
.cr:last-child { border-bottom: none; }
.cr-k {
  font-family: var(--sg); font-weight: 600; font-size: .68rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--store-muted);
}
a.cr-k:hover { color: var(--store-ink); text-decoration: none; }

/* follows: the rails are rows; a link row is a door, a dim row says why */
.listrows a.listrow { display: block; color: var(--store-ink); }
.listrows a.listrow:hover { background: var(--store-panel-2); text-decoration: none; }
.listrows a.listrow[aria-current="page"] { border-left: 2px solid var(--g-live); }
.state-act { margin-top: 12px; }
.fr-main .row { margin-top: 18px; }

/* presence: scaffolding only — fills as the hive publishes, never more */
.pres-cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
@media (max-width: 699px) { .pres-cols { grid-template-columns: 1fr; } }
.pres-empty { min-height: 64px; border: 1px dashed var(--store-line); border-radius: var(--r-sm); }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
  .card:hover, .card:focus-within { transform: none; }
}

/* ── the browse layer: the wishlist, the categories, the result rows ─────
   Added 2026-08-03 with search.html. The store's second surface — the mast
   search box submits there, tags link there, and the wishlist is a view of
   it. Nothing here is a new shell: same panel, same line, same chips. */

/* the mast's wishlist door. It takes the flex slack so the search box sits
   hard right, exactly where it did before this link existed. */
.mast-wish {
  grid-area: wish; display: inline-flex; align-items: center; gap: 7px;
  min-height: 34px; padding: 0 11px; border-radius: var(--r);
  font-family: var(--sg); font-weight: 600; font-size: .72rem;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--store-muted); border: 1px solid transparent;
}
.mast-wish:hover { text-decoration: none; color: var(--store-ink); border-color: var(--store-line); background: rgba(255, 255, 255, .03); }
.wish-n {
  min-width: 19px; padding: 1px 6px; border-radius: 9px; font-size: .7rem;
  background: var(--store-line-2); color: var(--store-ink); text-align: center;
}
/* on a phone the mast folds to three rows, not four: the wishlist rides up
   beside the wordmark rather than taking a line of its own. The grid areas
   above already say so — this only keeps the wishlist hard right. */
@media (max-width: 699px) {
  .mast-wish { justify-self: end; }
}

/* the wishlist button. Gold when set, quiet when not — it is a note to
   yourself, never a purchase, so it never outranks the page's real act. */
.btn.wish {
  background: transparent; color: var(--store-muted); box-shadow: none; text-shadow: none;
  border-color: var(--store-line); font-size: .72rem; padding: 0 12px;
}
.btn.wish:hover { color: var(--g-live); border-color: rgba(0, 200, 5, .5); filter: none; box-shadow: none; }
.btn.wish.on { color: var(--g-live); border-color: rgba(0, 200, 5, .5); background: var(--g-wash); }
/* on a 264px capsule card the foot holds a chip and an act already, so the
   wishlist button takes its own full-width line rather than squeezing the
   other two into two-line labels */
.card-foot { flex-wrap: wrap; }
.card-foot .btn.wish { flex: 1 0 100%; margin-top: 2px; }
.buybox-fine { font-size: .78rem; margin-top: 10px; }

/* browse-by-category tiles */
.cats { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 10px; }
.cat {
  display: flex; flex-direction: column; gap: 4px; padding: 14px 16px;
  background-color: #181e19; background-image: linear-gradient(180deg, #181e19 0%, #10140f 100%);
  border: 1px solid var(--store-line); box-shadow: var(--bevel);
  border-radius: var(--r); color: var(--store-ink); min-height: 76px;
  transition: border-color .12s ease, box-shadow .12s ease;
}
.cat:hover { text-decoration: none; border-color: var(--g-live); box-shadow: 0 0 0 1px rgba(0, 200, 5, .28), var(--bevel); }
.cat-n { font-family: var(--sg); font-weight: 700; font-size: .94rem; letter-spacing: .01em; }
.cat-s { color: var(--store-dim); font-size: .78rem; }

/* the filter rail's tag list and the wishlist-only check */
.f-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.f-tag {
  display: inline-block; padding: 4px 10px; border-radius: var(--r-sm);
  background: var(--store-panel-2); color: var(--store-muted); font-size: .74rem;
}
.f-tag:hover { background: var(--store-line); color: var(--store-ink); text-decoration: none; }
.f-tag.on { background: var(--g-wash); color: var(--g-live); box-shadow: inset 0 0 0 1px rgba(0, 200, 5, .35); }
.f-tag.clear { background: transparent; border: 1px dashed var(--store-line); color: var(--store-muted); }
.f-check { display: flex; align-items: center; gap: 8px; font-size: .84rem; color: var(--store-ink); min-height: 40px; }
.f-check input { width: 16px; min-height: 16px; height: 16px; flex: 0 0 auto; }

/* result rows — the browse page lists, where the shelf capsules */
.rrows { display: flex; flex-direction: column; gap: 8px; }
.rrow {
  display: flex; align-items: center; gap: 14px; padding: 10px;
  background-color: #181e19; background-image: linear-gradient(180deg, #181e19 0%, #10140f 100%);
  border: 1px solid var(--store-line); border-radius: var(--r); box-shadow: var(--bevel);
  transition: border-color .12s ease, box-shadow .12s ease;
}
.rrow:hover, .rrow:focus-within { border-color: var(--g-live); box-shadow: 0 0 0 1px rgba(0, 200, 5, .28), var(--bevel); }
.rrow-cap {
  position: relative; display: block; flex: 0 0 168px; aspect-ratio: 616 / 353;
  border-radius: var(--r-sm); overflow: hidden;
  background-color: var(--store-panel-2); background-image: linear-gradient(150deg, var(--store-panel-2) 0%, #141a13 60%, #0d100c 100%);
}
.rrow-cap .cap-fallback { font-size: .9rem; padding: 8px 10px; }
.rrow-cap img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.rrow-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.rrow-name { font-family: var(--sg); font-weight: 600; font-size: 1.02rem; color: var(--store-ink); }
.rrow-name:hover { color: var(--store-link); text-decoration: none; }
.rrow-kind { color: var(--store-muted); font-size: .82rem; }
.rrow-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.rrow-act { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; flex: 0 0 auto; }
.rrow-act .card-price { margin-left: 0; font-size: .92rem; }
@media (max-width: 699px) {
  .rrow { flex-wrap: wrap; }
  .rrow-cap { flex-basis: 120px; }
  .rrow-act { flex-direction: row; width: 100%; justify-content: space-between; align-items: center; }
}

/* ── reviews and the community hub (2026-08-03) ─────────────────────────
   The review section renders whatever /api/reviews reports and nothing it
   does not: the label is the API's call, and when it withholds one the page
   prints the floor rather than inventing a word of its own. */
.review-sum {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  background: var(--store-panel); border: 1px solid var(--store-line);
  border-radius: var(--r); padding: 14px 16px; margin-bottom: 12px;
}
.review-label { font-family: var(--sg); font-weight: 700; font-size: 1.15rem; color: var(--store-link); }
.review-label.dim { font-weight: 400; font-size: .86rem; color: var(--store-muted); }
.review-split { font-size: .82rem; }

.revs { display: flex; flex-direction: column; gap: 10px; }
.rev {
  display: flex; gap: 14px; padding: 12px 14px;
  background: var(--store-panel); border: 1px solid var(--store-line); border-radius: var(--r);
}
.rev-who { flex: 0 0 150px; display: flex; flex-direction: column; gap: 3px; }
.rev-addr { font-size: .84rem; }
.rev-rounds { font-size: .74rem; }
.rev-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.rev-verdict { font-family: var(--sg); font-weight: 600; font-size: .9rem; }
.rev-verdict.up { color: var(--store-link); }
.rev-verdict.down { color: var(--store-muted); }
.rev-text { font-size: .92rem; overflow-wrap: anywhere; }
.rev-foot { display: flex; gap: 12px; flex-wrap: wrap; font-size: .76rem; }
.rev-edited { font-style: italic; }
@media (max-width: 699px) {
  .rev { flex-direction: column; gap: 8px; }
  .rev-who { flex-basis: auto; flex-direction: row; gap: 10px; align-items: baseline; }
}

/* the community hub */
.hub-acts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.hub-posts { display: flex; flex-direction: column; gap: 10px; }
.hub-post {
  display: flex; gap: 14px; background: var(--store-panel);
  border: 1px solid var(--store-line); border-radius: var(--r); padding: 12px; align-items: flex-start;
}
/* the art is a fixed-size thumbnail, never the post. Without an explicit
   height an SVG with a viewBox expands to its own aspect and swallows the
   band — which is exactly what the first render did. */
.hub-art { flex: 0 0 180px; height: 104px; border-radius: var(--r-sm); overflow: hidden; display: block; }
.hub-art img { width: 100%; height: 100%; object-fit: cover; }
.hub-post-b { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.hub-post-b h3 { font-family: var(--sg); font-weight: 600; font-size: 1rem; }
.hub-post-b p { color: var(--store-muted); font-size: .88rem; }
.hub-date { font-size: .74rem; }
@media (max-width: 699px) {
  .hub-post { flex-direction: column; }
  .hub-art { flex-basis: auto; width: 100%; height: 150px; }
}

.hub-shots { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 10px; }
.hub-shot {
  position: relative; border: 1px solid var(--store-line); border-radius: var(--r);
  overflow: hidden; aspect-ratio: 616 / 353;
}
.hub-shot img { width: 100%; height: 100%; object-fit: cover; }
.shot-chip {
  position: absolute; left: 8px; bottom: 8px; font-family: var(--sg);
  font-size: .64rem; letter-spacing: .08em; text-transform: uppercase;
  padding: 3px 8px; border-radius: var(--r-sm);
  background: rgba(6, 8, 6, .82); color: var(--store-muted); border: 1px solid var(--store-line);
}
.hub-players { display: flex; flex-wrap: wrap; gap: 8px; }
.hub-player {
  padding: 6px 12px; border-radius: var(--r); font-size: .82rem;
  background: var(--store-panel); border: 1px solid var(--store-line);
}

/* ── the hub's identity block (news.html) ────────────────────────────────
   Steam's hub leads with a crest, a name and a row of acts, and that is the
   furniture that makes a page read as a PLACE rather than a list. Ours says
   the same three things and holds no counts: there is no follower number
   here because there is no account to follow with. */
.hub-id {
  display: flex; gap: 18px; align-items: center; padding: 22px 0 6px;
  border-bottom: 1px solid var(--store-line); margin-bottom: 22px;
}
/* the single-post view hides this block and the breadcrumb carries the
   context instead. `display:flex` above beats the UA's [hidden] rule, so
   without this line the JS sets the attribute and nothing happens — which is
   exactly what it did on the first render. */
.hub-id[hidden] { display: none; }
.hub-crest {
  flex: 0 0 96px; height: 96px; display: grid; place-items: center;
  border-radius: var(--r); border: 1px solid var(--g-core);
  background: var(--store-panel); font-family: var(--sg); font-weight: 700;
  font-size: 1.5rem; letter-spacing: .04em; color: var(--g-live);
}
.hub-id-b { min-width: 0; }
.hub-id-b h1 { margin: 2px 0 4px; }
.hub-kind {
  font-family: var(--sg); font-size: .66rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--store-muted);
}
@media (max-width: 699px) {
  .hub-id { flex-direction: column; align-items: flex-start; gap: 12px; }
  .hub-crest { flex-basis: 64px; height: 64px; font-size: 1.1rem; }
}

/* a pinned post is a HAND PICK and says so on its face — the one ordering
   on this page that is not the date, and never an engagement rank */
.hub-post.pinned { border-color: var(--g-core); }
.hub-post .pin { color: var(--store-muted); }
/* the house's own hub card carries no art, so it must not collapse narrow */
.hub-post.house .hub-post-b { min-height: 84px; justify-content: center; }

/* one post, read as a page. The measure is deliberately short — this is the
   only long-prose surface in the store and 74ch is where it stops being a
   wall. */
.post-body { max-width: 74ch; margin-top: 14px; }
.post-body h1 { font-size: 1.6rem; margin-bottom: 10px; }
.post-body h2 { font-size: 1.1rem; margin: 22px 0 8px; }
.post-body p, .post-body li { color: var(--store-ink); font-size: .95rem; line-height: 1.62; }
.post-body p, .post-body ul, .post-body ol { margin-top: 10px; }
.post-body ul, .post-body ol { padding-left: 22px; }
.post-body li { margin-top: 5px; }
.post-body a { text-decoration: underline; }
.post-body code {
  font-family: var(--mono, monospace); font-size: .88em;
  background: var(--store-panel); padding: 1px 5px; border-radius: var(--r-sm);
}
.post-near {
  display: flex; justify-content: space-between; gap: 12px; margin-top: 28px;
  padding-top: 14px; border-top: 1px solid var(--store-line); max-width: 74ch;
  font-family: var(--sg); font-size: .8rem;
}

/* a queued title the house has committed to but not yet described. It is
   deliberately not a game card: no capsule, no wishlist, no dead link. */
.card.placeholder { border-style: dashed; }
.card.placeholder .card-blurb { font-size: .84rem; margin-top: 4px; }

/* stacked paragraphs inside a panel need air — the pass page's three
   promises read as one block without it */
.panel p + p { margin-top: 10px; }
.listrow a { text-decoration: underline; }

/* ── the phone: a desk's density is not a thumb's ────────────────────────
   Everything above is tuned for a 1440 desk, where a 34px control and a 10px
   uppercase label are right. At 390 neither is, and the regression is not
   visible in a desktop browser — it shows up only in the phone oracle
   (deploy/capture_site.js MOBILE_FN: any tap target whose short side is under
   44px, any body text under 13px). Cutting the base type from 16px to 15px and
   the controls from 44px to 34px took store.html from 3 small taps to 26 in
   one pass, which is the whole reason this block exists as ONE place rather
   than as a hedge inside each rule.

   The selector list below is DERIVED, not typed: it is every rule in this
   sheet whose font-size computes under the 13px floor. Add a smaller label
   upstairs and it belongs here too. */
@media (max-width: 699px) {
  .hero-eyebrow, .pill, .chip, .tp-label, .tp,
  .mp-chip, .mr-k, .shot-chip, .railbox-h, .rug-k, .f-label, .mkt-table th,
  .hero-fine, .hero-tags, .cr-k, .panel h3, .record-date,
  .mast-nav a, .tp-more, .wide-cta, .reqtab, .tagchip, .inv-tab,
  .mast-wish, .btn.wish, .card-coins, .card.market .card-kind, .rug-sub,
  .ladder li b, .compare th, .f-tag, .rev-rounds, .hub-date, .tp .tp-v.dim,
  .btn.slim, .footnote, .quoterow cite, .rev-foot, .row-sub, .card-kind,
  .crumbs, .gp-h, .coin-chip, .req b, .foot-links a, .id-note,
  .buybox-fine, .cat-s, .featrow, .card.market .card-coins,
  .meta-rows, .record-line, .id-addr, .hub-kind { font-size: 13px; }
  .tp .tp-v { font-size: 14px; }

  /* the 44px floor, restored on every control the desk view slimmed */
  .btn, .btn.slim, .reqtab, .inv-tab { min-height: 44px; }
  .mast-wish, .mast-search input,
  .filter-rail select, .filter-rail input, .sortbar select { min-height: 44px; }
  .foot-links a, .infolinks a, .listrow a { min-height: 44px; }
  /* a hub post's title is how you open the post — on the hub it is the main
     navigation, not decoration, so it takes the same floor a card name does */
  .tp-more, .card-name, .rrow-name, .crumbs a, .wide-cta,
  .hub-post-b h3 a, .post-near a {
    min-height: 44px; display: inline-flex; align-items: center;
  }
  .tagchip, .f-tag { min-height: 44px; display: inline-flex; align-items: center; padding: 0 12px; }
  .f-check { min-height: 44px; }
  .f-check input { width: 24px; height: 24px; min-height: 24px; }
  /* a card's title is a link and now a 44px row, so the meta needs the air */
  .card-meta { gap: 5px; }
}

/* ── scryworks: the builder's portal ────────────────────────────────────
   Steamworks' shape — three doors over a set of link lists, on the store's
   own shell rather than a second one. It is a SECTION, not a subdomain, so
   the mast is unchanged and only tier two swaps to its own anchors. */
.works-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.works-door {
  display: flex; flex-direction: column; gap: 6px;
  background: var(--store-panel); border: 1px solid var(--store-line);
  border-radius: 6px; padding: 16px; color: var(--store-ink);
}
.works-door:hover { text-decoration: none; border-color: var(--g-core); background: var(--store-panel-2); }
/* both raised to the 13px floor 2026-08-05: they were the only body text on
   any live store page still under it, and each carries real words */
.wd-k {
  font-family: var(--mono); font-size: .82rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--store-muted);
}
.works-door h3 { font-family: var(--sg); font-weight: 600; font-size: 1.06rem; }
.works-door p { color: var(--store-muted); font-size: .9rem; }
.wd-go { margin-top: auto; padding-top: 8px; font-size: .82rem; color: var(--g-live); }

.works-list { display: flex; flex-direction: column; border: 1px solid var(--store-line); border-radius: 6px; overflow: hidden; }
.wl {
  display: grid; grid-template-columns: 190px 1fr; gap: 14px;
  padding: 12px 14px; border-top: 1px solid var(--store-line);
  color: var(--store-ink); min-height: 44px; align-items: baseline;
}
.wl:first-child { border-top: 0; }
.wl:hover { text-decoration: none; background: var(--store-panel-2); }
.wl b { font-weight: 600; }
.wl span { color: var(--store-muted); font-size: .88rem; }

@media (max-width: 620px) {
  .wl { grid-template-columns: 1fr; gap: 3px; }
}

/* ── the quest board (build.html) ────────────────────────────────────────
   Ported off deck-ui.css on 2026-08-05 with the page. `.quest-*` and not
   `.q*`: the front page's queue module already owns `.q-body`/`.q-art`, and
   two unrelated components sharing a prefix in one sheet is how a rule meant
   for one silently repaints the other.

   Three states told apart by colour AND by the word in the chip, never by
   colour alone — `done` also drops opacity, which is the only state that is
   history rather than work. */
.quests { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
.quests > .row-empty { grid-column: 1 / -1; font-size: .85rem; }
.quest {
  background-color: #181e19; background-image: linear-gradient(180deg, #181e19 0%, #10140f 100%);
  border: 1px solid var(--store-line); border-radius: var(--r);
  box-shadow: var(--bevel); padding: 13px 14px;
}
.quest.done { opacity: .6; }
.quest-h { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.quest-t { font-family: var(--sg); font-weight: 700; font-size: .98rem; line-height: 1.25; }
.quest-s {
  font-family: var(--mono); font-size: .62rem; letter-spacing: .1em; text-transform: uppercase;
  border: 1px solid var(--store-line); border-radius: 9px; padding: 2px 8px;
  color: var(--store-muted); white-space: nowrap; flex: 0 0 auto;
}
.quest-s.open { color: var(--g-live); border-color: rgba(0, 200, 5, .45); background: var(--g-wash); }
.quest-s.claimed { color: var(--store-ink); border-color: var(--store-line-2); }
.quest-c { font-family: var(--mono); font-size: .82rem; color: var(--g-live); margin-top: 6px; }
.quest-w { font-size: .85rem; line-height: 1.55; color: var(--store-muted); margin-top: 8px; }
.quest-m {
  font-family: var(--mono); font-size: .82rem; color: var(--store-dim); margin-top: 9px;
  padding-top: 8px; border-top: 1px solid var(--store-line); line-height: 1.6;
}
.quest-m b { color: var(--store-muted); font-weight: 400; }

/* ── the instrument pages' vocabulary, scoped to `.instr` ────────────────
   read · tools · verify · contracts came onto the store shell on 2026-08-05.
   Their markup and their readers (library.js, tools.js, verify.js) were
   written against deck-ui.css's class names — klabel, seal, doors, byline,
   mono-note — and rewriting four working renderers to a different vocabulary
   would have risked live behaviour to change a word.

   So the names come along and get store tokens, scoped under `.instr` so the
   translation cannot reach the twenty pages that never asked for it. One
   collision made the scope mandatory rather than tidy: contracts.html uses
   `.flow` for a grid of address cards and the store uses `.flow` for a row of
   steps with arrows between them. Unscoped, the two would have silently
   repainted each other.

   When a page's markup is eventually rewritten in store vocabulary, its rules
   leave this block. Nothing else should join it. */
.instr .klabel {
  font-family: var(--sg); font-weight: 700; font-size: .82rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--store-muted);
}
.instr .byline { color: var(--store-muted); font-size: .9rem; max-width: 62em; }
.instr .mono-note { font-family: var(--mono); font-size: .82rem; color: var(--store-muted); line-height: 1.65; }
/* an ABSOLUTE size, not .85em: relative sizing compounded down the nesting
   until a code span inside a .status paragraph rendered at 11.9px, under the
   site's 13px floor, and a long unbroken token (a base64 key, a path) with no
   break opportunity was what pushed the phone viewport sideways. */
.instr .mono, .instr code { font-family: var(--mono); font-size: .82rem; overflow-wrap: anywhere; }
.instr .rule { border: none; border-top: 1px solid var(--store-line); margin: 26px 0; }
.instr h2 { font-family: var(--sg); font-weight: 700; font-size: 1.06rem; margin: 22px 0 8px; }
.instr h3 { font-family: var(--sg); font-weight: 700; font-size: .95rem; margin: 4px 0 6px; }
.instr p { font-size: .9rem; line-height: 1.62; margin: 8px 0; max-width: 68em; }
.instr ul, .instr ol { margin: 8px 0 8px 20px; font-size: .9rem; line-height: 1.62; }
/* prose links only. The `:not()` list is load-bearing rather than tidy: a bare
   `.instr a` painted the green fill button's own label green-on-green at a 1:1
   contrast ratio, and every component in here that carries its own colour —
   buttons, shelf doors, list rows — would have lost it the same way. */
.instr a:not(.btn):not(.wl):not(.door):not(.works-door) { color: var(--g-live); }

/* the status chip. `seal` alone is neutral — a live label on something nobody
   has read yet is the failure this vocabulary was fixed for once already.
   ⚠ AND IT WAS EXACTLY THAT FAILURE UNTIL 2026-08-11, by way of the [hidden]
   trap at the top of this file: this rule's `display: inline-block` beat the UA
   rule, so `verify.html`'s `<span class="seal live" id="hr-live" hidden>mounted
   </span>` rendered a green MOUNTED badge on the Herald whether or not the
   probe in `js/verify.js` ever answered. A live label on something nobody has
   read — the sentence above, on the page it was written for. */
/* ⚠ UNSCOPED, and it was scoped to `.instr` until 2026-08-12 — which meant a
   bare `<span class="seal">` had NO RULE ANYWHERE on thirteen files. The pill
   version lived in deck-ui.css and went with the deck chrome on 2026-08-11;
   nothing replaced it, and an unstyled inline span does not look broken, it
   looks like text. So gardens.html rendered `live the Gardener's address is
   posted below` as one run-on sentence, with the STATUS and the SENTENCE ABOUT
   the status fused into a single grey line, on the surfaces whose whole job is
   saying which state a contract is in. Same on bank, orchard, gacha, tally,
   pools, contracts, tools and profile, plus three JS modules that emit one.
   It lives in store.css rather than money.css because eight of those pages are
   not money pages and do not load that sheet. */
span.seal, .instr .seal {
  display: inline-block; font-family: var(--mono); font-size: .82rem; letter-spacing: .1em;
  text-transform: uppercase; padding: 2px 7px; border-radius: 9px;
  border: 1px solid var(--store-line); color: var(--store-muted);
}
span.seal.live { color: var(--g-live); border-color: rgba(0, 200, 5, .45); background: var(--g-wash); }
span.seal.gate { color: var(--store-gold); border-color: rgba(201, 162, 39, .45); }
span.seal.later, span.seal.blind { color: var(--store-dim); }
span.seal.hold { color: #e0574d; border-color: rgba(224, 87, 77, .5); }
/* NOT nowrap. One seal on verify.html reads "one posted price per rail, same
   for every caller" — a sentence in a chip — and nowrap made it 99px wider
   than a phone, which pushed the whole document sideways. A status chip is
   allowed to be short; the stylesheet may not assume it is. */
.instr .seal.live { color: var(--g-live); border-color: rgba(0, 200, 5, .45); background: var(--g-wash); }
.instr .seal.gate { color: var(--store-gold); border-color: rgba(201, 162, 39, .45); }
.instr .seal.later, .instr .seal.blind { color: var(--store-dim); }
/* held back on purpose is not the same as not built yet, and the ledger says
   so in colour as well as in words */
.instr .seal.hold { color: #e0574d; border-color: rgba(224, 87, 77, .5); }

/* the shelf grid: library.js emits .doors > a.panel.door > small */
.instr .doors { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px; }
.instr .panel.door {
  display: block; padding: 12px 14px; font-family: var(--sg); font-weight: 700; font-size: .92rem;
  color: var(--store-ink); border: 1px solid var(--store-line); border-radius: var(--r);
  background-color: #181e19; background-image: linear-gradient(180deg, #181e19 0%, #10140f 100%);
  box-shadow: var(--bevel);
}
.instr .panel.door:hover { text-decoration: none; border-color: var(--g-live); }
.instr .panel.door small {
  display: block; margin-top: 5px; font-family: var(--body); font-weight: 400;
  font-size: .82rem; line-height: 1.55; color: var(--store-muted);
}

/* the stat row */
.instr .numbers-rule {
  display: flex; flex-wrap: wrap; gap: 10px 22px; padding: 12px 14px; margin: 14px 0 4px;
  border: 1px solid var(--store-line); border-radius: var(--r); background: var(--store-pit);
  font-family: var(--sg); font-size: .82rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--store-muted);
}
.instr .numbers-rule b { font-family: var(--mono); font-size: .95rem; letter-spacing: 0; color: var(--store-ink); text-transform: none; }

/* key/value rows, used by every one of these pages */
.instr .kv {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 4px 12px;
  font-family: var(--mono); font-size: .82rem; padding: 7px 0;
  border-bottom: 1px solid var(--store-line);
}
.instr .kv > * { min-width: 0; overflow-wrap: anywhere; }
.instr .kv:last-child { border-bottom: none; }
.instr .kv > span:first-child { color: var(--store-muted); }

/* the tables: the tool registry and anything else that lists rows */
.instr table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.instr table th {
  font-family: var(--sg); font-size: .82rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--store-muted); text-align: left; font-weight: 600;
  border-bottom: 1px solid var(--store-line-2); padding: 8px;
}
.instr table td { border-bottom: 1px solid var(--store-line); padding: 10px 8px; vertical-align: top; }
.instr table.census { min-width: 720px; }
.instr table.census td img { width: 30px; height: 30px; display: block; }
.instr .vowtext { font-style: italic; max-width: 34em; color: var(--store-muted); }
.instr .reg-wallet { font-family: var(--mono); font-size: .82rem; color: var(--store-muted); }
.instr .reg-tools { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; padding: 12px 0; }
.instr .reg-tools input[type="search"], .instr .reg-tools input[type="text"] {
  font-family: var(--mono); font-size: .85rem; color: var(--store-ink); background: var(--store-pit);
  border: 1px solid var(--store-line); border-radius: var(--r-sm); padding: 9px 12px; min-width: min(340px, 80vw);
}
.instr .reg-tools label { font-size: .85rem; color: var(--store-muted); display: flex; gap: 6px; align-items: center; }

/* contracts.html: a grid of address cards. `.flow` here is NOT the store's
   step row — see the note at the top of this block. */
.instr .flow { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; }
.instr .card {
  border: 1px solid var(--store-line); border-radius: var(--r); padding: 13px 14px;
  background-color: #181e19; background-image: linear-gradient(180deg, #181e19 0%, #10140f 100%);
  box-shadow: var(--bevel);
}
.instr .addrs { list-style: none; margin: 8px 0 0; padding: 0; }
.instr .addrs li {
  display: flex; flex-wrap: wrap; gap: 4px 10px; justify-content: space-between;
  font-family: var(--mono); font-size: .82rem; padding: 5px 0;
  border-top: 1px solid var(--store-line);
}
.instr .addrs li > span:first-child { color: var(--store-muted); }
.instr .addrs a { overflow-wrap: anywhere; }
.instr .status { color: var(--store-muted); font-size: .88rem; max-width: 68em; }
.instr .kicker { font-family: var(--sg); font-size: .82rem; letter-spacing: .16em; text-transform: uppercase; color: var(--g-live); }

/* verify.html's plates and gauges */
.instr .v-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 12px; }
.instr .plate {
  border: 1px solid var(--store-line); border-radius: var(--r); padding: 13px 15px;
  background: var(--store-panel);
}
.instr .v-lede { font-size: .95rem; color: var(--store-muted); max-width: 62em; }
.instr .gauges { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; }
.instr .gauge-wrap { background: var(--store-pit); border: 1px solid var(--store-line); border-radius: var(--r); padding: 11px 13px; }
.instr .gauge-title { margin-bottom: 6px; }
.instr .gauge-read { font-family: var(--mono); font-size: 1.05rem; color: var(--store-ink); }
.instr .gauge-note { font-size: .82rem; color: var(--store-muted); margin-top: 4px; }
.instr .trace-box, .instr .lookup-box {
  width: 100%; font-family: var(--mono); font-size: .82rem; color: var(--store-ink);
  background: var(--store-pit); border: 1px solid var(--store-line); border-radius: var(--r-sm);
  padding: 12px; min-height: 44px;   /* 44px is a thumb, not a preference */
}
.instr textarea.trace-box { min-height: 150px; }
.instr .trace-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; align-items: center; }
.instr .pubkey-line, .instr .mount-line {
  font-family: var(--mono); font-size: .82rem; overflow-wrap: anywhere;
  background: var(--store-pit); border: 1px solid var(--store-line); border-radius: var(--r-sm);
  padding: 9px 11px; margin-top: 8px;
}
.instr .tool-list { list-style: none; margin: 8px 0 0; padding: 0; }
.instr .tool-list li { padding: 6px 0; border-top: 1px solid var(--store-line); font-size: .85rem; }
.instr .section-div { border-top: 1px solid var(--store-line); margin-top: 28px; padding-top: 6px; }
.instr .val { font-family: var(--mono); color: var(--store-ink); }
.instr .oracle-quote, .instr .oracle {
  font-style: italic; color: var(--store-muted); border-left: 2px solid var(--store-line-2);
  padding-left: 12px; margin: 10px 0;
}
.instr .btn-quiet {
  font-family: var(--sg); font-size: .82rem; letter-spacing: .1em; text-transform: uppercase;
  padding: 5px 10px; border-radius: var(--r-sm); border: 1px solid var(--store-line);
  background: transparent; color: var(--store-muted); cursor: pointer;
}
.instr .btn-quiet:hover { color: var(--store-ink); border-color: var(--store-line-2); }

/* the scope stele. On the deck side this was a strip that OFFERED the creed
   card on click; on the store shell the clauses are simply printed, which is
   the stronger version of the same promise — a caveat you have to open is a
   caveat most readers never see. Invariant 4 is about what ships on every
   response; this is its echo on the pages that make the claim. */
.stele {
  display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: baseline;
  margin-top: 34px; padding: 12px 14px;
  border: 1px dashed var(--store-line-2); border-radius: var(--r);
  background: rgba(22, 27, 23, .6);
  font-size: .82rem; color: var(--store-muted);
}
.stele b {
  font-family: var(--sg); font-size: .82rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--store-dim);
}
.stele span + span::before { content: "· "; color: var(--store-dim); }

/* a checkbox is 13px of hit area by default, which fails a thumb. These two
   gate a PERMANENT act, so they get a label-sized target rather than a
   pixel-sized one. */
.instr .note label { display: inline-flex; align-items: center; gap: 9px; min-height: 44px; cursor: pointer; }
.instr .note label input[type="checkbox"] { width: 20px; height: 20px; flex: 0 0 auto; accent-color: var(--g-live); }

/* ── the doc reader (read.html) ──────────────────────────────────────────
   library.js adds `reading` to <body> once a scroll is open, which is what
   turns the shelf into a two-column read with the whole shelf down the side.
   Prose measure is capped at 52em: the store's own columns are wider than a
   reader wants for 15px body text. */
#doc-layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: 26px; }
body.reading #doc-layout { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 960px) { body.reading #doc-layout { grid-template-columns: 220px minmax(0, 1fr); } }
#doc-side { display: none; }
body.reading #doc-side {
  display: block; position: sticky; top: 14px; align-self: start;
  max-height: calc(100vh - 28px); overflow-y: auto; padding-right: 12px;
  border-right: 1px solid var(--store-line);
}
@media (max-width: 959px) { body.reading #doc-side { display: none; } }
#doc-side .klabel { margin: 14px 0 4px; }
#doc-side a { display: block; font-size: .82rem; line-height: 1.5; padding: 2px 0; color: var(--store-muted); }
#doc-side a:hover { color: var(--store-ink); text-decoration: none; }
#doc-side a.active { color: var(--g-live); font-weight: 700; }
#doc-side .side-home { font-family: var(--sg); font-size: .82rem; letter-spacing: .14em; text-transform: uppercase; margin-bottom: 8px; }
#doc-title { font-family: var(--sg); font-weight: 700; font-size: clamp(1.3rem, 4vw, 1.8rem); }
#doc-view { max-width: 52em; margin: 16px 0 30px; font-size: .95rem; line-height: 1.72; color: var(--store-ink); }
#doc-view h1 { font-family: var(--sg); font-size: 1.35rem; margin: 26px 0 10px; }
#doc-view h2 { font-family: var(--sg); font-size: 1.1rem; margin: 24px 0 8px; }
#doc-view h3, #doc-view h4, #doc-view h5, #doc-view h6 {
  font-family: var(--sg); font-size: .82rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--store-muted); margin: 18px 0 6px;
}
#doc-view p { margin: 9px 0; }
#doc-view code { background: var(--store-pit); border: 1px solid var(--store-line); border-radius: 3px; padding: 1px 5px; font-size: .82em; font-family: var(--mono); }
#doc-view pre { background: var(--store-pit); border: 1px solid var(--store-line); border-radius: var(--r); padding: 10px 12px; overflow-x: auto; margin: 10px 0; }
#doc-view pre code { background: none; border: none; padding: 0; font-size: .82rem; display: block; }
#doc-view blockquote { border-left: 2px solid var(--store-line-2); padding: 2px 14px; margin: 12px 0; color: var(--store-muted); font-style: italic; }
#doc-view ul, #doc-view ol { margin: 8px 0 8px 22px; }
#doc-view li { margin: 3px 0; }
#doc-view .md-tablewrap { overflow-x: auto; margin: 10px 0; }
#doc-view table { border-collapse: collapse; font-size: .82rem; font-family: var(--mono); }
#doc-view th, #doc-view td { border: 1px solid var(--store-line); padding: 5px 9px; text-align: left; vertical-align: top; }
#doc-view th { color: var(--store-muted); font-family: var(--sg); font-size: .82rem; letter-spacing: .12em; text-transform: uppercase; }
#doc-view hr { border: none; border-top: 1px solid var(--store-line); margin: 18px 0; }

/* ── forms: the launch preview and the store desk ────────────────────────
   Two pages type into the site for the first time, so this is the store's
   first form vocabulary. Two rules it exists to hold:
   · a disarmed control never wears the reserved green fill. The fill marks an
     act that moves money and works; a preview's button is `.btn.pending`
     (gold outline) and a disabled field says why in its own row.
   · every field carries a visible hint, because both pages are teaching the
     shape of something as much as collecting it. */
.form { display: flex; flex-direction: column; gap: 14px; max-width: 760px; }
/* NOT `.form.wide` — `.wide` is already the store's wide-card block (line ~490)
   and it carries `align-items: center`, which collapsed every full-width field
   to its content width. A modifier named for its own block cannot collide. */
.form-wide { max-width: none; }
.fld { display: flex; flex-direction: column; gap: 5px; }
.fld > label {
  font-family: var(--sg); font-weight: 600; font-size: .82rem;
  letter-spacing: .02em; color: var(--store-ink);
}
.fld .hint { color: var(--store-dim); font-size: .78rem; }
.fld .hint b { color: var(--store-muted); font-weight: 600; }
.fld input, .fld select, .fld textarea {
  width: 100%; min-height: 40px; padding: 9px 11px;
  background: var(--store-pit); color: var(--store-ink);
  border: 1px solid var(--store-line-2); border-radius: var(--r-sm);
  font-family: inherit; font-size: .88rem;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .55);
}
.fld textarea { min-height: 92px; resize: vertical; line-height: 1.5; }
.fld input::placeholder, .fld textarea::placeholder { color: var(--store-dim); }
.fld input:focus, .fld select:focus, .fld textarea:focus {
  border-color: var(--g-live); outline: none;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .55), 0 0 0 1px rgba(0, 200, 5, .35);
}
.fld input:disabled, .fld select:disabled, .fld textarea:disabled {
  opacity: .5; cursor: not-allowed; border-style: dashed; box-shadow: none;
}
.fld input[readonly] { color: var(--store-muted); }
.form-acts { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 4px; }
.form-two { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 620px) { .form-two { grid-template-columns: 1fr; } }

/* the running answer a form gives back: a refusal, a warning, a receipt. It is
   a live region, so a screen reader hears the refusal that a sighted reader
   sees appear under the button. */
.say { font-size: .85rem; margin-top: 4px; min-height: 1.2em; overflow-wrap: anywhere; }
.say.bad { color: #ff8f6b; }
.say.warn { color: var(--store-gold); }
.say.good { color: var(--g-live); }
.say ul { margin: 6px 0 0; padding-left: 18px; }
.say li { margin-top: 3px; }
.mono { font-family: var(--mono); font-size: .78rem; }
.pre {
  background: var(--store-pit); border: 1px solid var(--store-line);
  border-radius: var(--r-sm); padding: 10px 12px; overflow-x: auto;
  font-family: var(--mono); font-size: .76rem; color: var(--store-muted);
  white-space: pre-wrap; overflow-wrap: anywhere;
}

/* the desk's act log — one row per edit, signer and hash visible, because the
   point of signing an edit is that a stranger can check it */
.acts { display: flex; flex-direction: column; gap: 6px; }
.act {
  display: grid; grid-template-columns: 150px 1fr; gap: 12px;
  padding: 9px 11px; border: 1px solid var(--store-line);
  border-radius: var(--r-sm); background: var(--store-panel); font-size: .82rem;
}
.act-when { color: var(--store-dim); font-family: var(--mono); font-size: .74rem; }
.act-what b { color: var(--store-ink); font-weight: 600; }
.act-who { color: var(--store-muted); font-family: var(--mono); font-size: .74rem; }
@media (max-width: 620px) { .act { grid-template-columns: 1fr; gap: 3px; } }

/* fieldset is the only grouping element that carries browser chrome by
   default, and the launch preview needs four of them — the groups are what
   make a long form readable as four short ones. */
.form fieldset { border: 0; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 14px; }
.form fieldset + fieldset { padding-top: 16px; border-top: 1px solid var(--store-line); }
.form legend {
  padding: 0 0 2px; font-family: var(--sg); letter-spacing: .1em;
  text-transform: uppercase; color: var(--store-muted); font-size: .72rem;
}
.form fieldset[disabled] .fld > label { color: var(--store-muted); }

/* .ab-cta — the button row under a panel. It was in the markup on four pages
   (about, research, scryworks, launch) and in the sheet on none, so every one
   of them stacked its buttons full-width down the page. */
.ab-cta { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }

/* ══ THE FRONT PAGE (2026-08-04) ═══════════════════════════════════════════
   Steam's front page is six modules in a fixed order, and the order is the
   design: an event banner, a featured carousel, a way into the whole catalog,
   a tabbed shelf with a preview rail, then the rows. This block is those
   modules. Two of Steam's are deliberately NOT reproduced — the discovery
   queue (a recommender: we have none and are building none) and the
   top-seller tabs (a ranking, which `GATES.md` §7 forbids). What is left of
   that module after the ranking comes out is the tab bar and the preview
   rail, which are the parts that were doing the work anyway.

   Every art slot on this page that has no drawing yet is fed a PLATE from
   art/store/placeholder/ — an SVG at the slot's exact size that prints what
   should be drawn there. See watchtower/art/make_plates.py. */

/* a right-hand link in a section head — "browse everything ▸", "every hub ▸" */
.row-act { margin-left: auto; font-size: .78rem; font-family: var(--sg); font-weight: 600; }
/* the line under a module that says what the module is not. It is body copy,
   not a caveat chip: the creed set is closed and this is prose. */
.row-foot { margin-top: 12px; color: var(--store-dim); font-size: .78rem; max-width: 74em; }
.row-foot code { font-family: var(--mono); font-size: .95em; color: var(--store-muted); }

/* ── the marquee ─────────────────────────────────────────────────────────
   Art under, copy over, on a band wider than it is tall. The copy is REAL
   TEXT rather than baked into the drawing — a banner whose words are pixels
   cannot be read by a screen reader, cannot be corrected without a redraw,
   and is the single most common thing wrong with a store's hero. */
.marquee {
  position: relative; margin-top: 16px; overflow: hidden;
  border: 1px solid var(--store-line); border-radius: var(--r);
  background-color: var(--store-panel);
  background-image: linear-gradient(180deg, var(--store-panel-2), var(--store-panel));
  box-shadow: var(--bevel), 0 14px 34px rgba(0, 0, 0, .5);
  min-height: 250px; display: flex; align-items: center;
}
.mq-art { position: absolute; inset: 0; display: block; }
/* the art is held DOWN, not up. Everything readable on this band is real text
   over it, so the drawing's job is texture and the copy's job is to be read —
   and while the slot holds a plate, the plate's own lettering would otherwise
   compete with the headline for the same pixels. */
.mq-art img { width: 100%; height: 100%; object-fit: cover; opacity: .38; }
/* …but when the slot holds a GAME rather than a plate, the drawing is the
   product and holding it to .38 renders the right half of the band as an empty
   dark rectangle. The veil already carries the text side (.96 → .88 alpha
   under the copy), so lifting the art only brightens pixels no text sits on. */
.marquee[data-mq-slug] .mq-art img { opacity: .72; }
.marquee[data-mq-slug] .mq-veil {
  background: linear-gradient(90deg, rgba(10, 12, 10, .97) 0%, rgba(10, 12, 10, .9) 42%, rgba(10, 12, 10, .28) 100%);
}
.mq-veil {
  position: absolute; inset: 0;
  background: linear-gradient(90deg, rgba(10, 12, 10, .96) 0%, rgba(10, 12, 10, .88) 46%, rgba(10, 12, 10, .5) 100%);
}
.mq-body { position: relative; z-index: 2; padding: 26px 24px; max-width: 44em; }
.mq-eyebrow {
  font-family: var(--sg); font-weight: 700; font-size: .82rem; letter-spacing: .2em;
  text-transform: uppercase; color: var(--store-gold);
}
.mq-name {
  font-family: var(--sg); font-weight: 700; line-height: 1.06; margin: 8px 0 10px;
  font-size: clamp(1.5rem, 3.4vw, 2.3rem);
}
.mq-line { color: var(--store-muted); font-size: .9rem; max-width: 40em; }
.mq-acts { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 16px; }
/* the featured title's price, beside the acts rather than on the art: the
   banner already carries a state eyebrow, and money reads better next to the
   button it belongs to. Hidden until store.js has a read to put in it. */
.mq-price {
  font-family: var(--mono); font-weight: 700; font-size: 1rem;
  color: var(--store-ink); padding-left: 4px;
}

/* ── the featured carousel ───────────────────────────────────────────────
   One .hero-shaped slide showing, arrows outside it, a dot per slide. It
   advances itself every few seconds (js/store.js §AUTO_MS) and holds still
   for a pointer, for focus, for a background tab and for the pause button in
   the dot rail.

   ⚠ EVERY SLIDE IS IN THE DOM AND THEY ARE STACKED IN ONE GRID CELL. That is
   what makes the module a fixed height: `grid-area: 1 / 1` puts all of them in
   the same cell, so the row is as tall as the TALLEST slide and the page does
   not jump when the slide under the arrows changes. The old track held one
   slide at a time and was re-rendered on every move, so its height was
   whatever THAT slide's right-hand column needed — a long blurb and a short
   one are ~60px apart, and everything below the module moved by that much on
   every press. It is also why the fade below can cross: both frames are real
   at the same moment. */
.carousel { position: relative; display: flex; align-items: stretch; gap: 8px; }
.car-track { flex: 1 1 auto; min-width: 0; display: grid; }
.car-track .hero { margin-top: 0; }
.car-track > .hero {
  grid-area: 1 / 1;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .32s ease, visibility 0s linear .32s;
}
.car-track > .hero.on {
  opacity: 1; visibility: visible; pointer-events: auto; z-index: 1;
  transition: opacity .32s ease, visibility 0s;
}
.car-arrow {
  flex: 0 0 34px; align-self: stretch; cursor: pointer;
  background-color: var(--store-panel); background-image: linear-gradient(180deg, var(--store-panel-2), var(--store-panel));
  border: 1px solid var(--store-line); border-radius: var(--r); color: var(--store-muted);
  font-size: 1.4rem; line-height: 1; font-family: var(--sg);
}
.car-arrow:hover { color: var(--store-ink); border-color: var(--g-live); }
.car-arrow[disabled] { opacity: .35; cursor: default; }
.car-dots { display: flex; gap: 6px; justify-content: center; align-items: center; margin-top: 12px; }
/* the dot is a 4px bar inside a 44px target. `background-clip: content-box`
   is what lets the two disagree: the padding IS the hit area and takes no
   paint, so the tap floor is cleared without drawing a 44px slab. */
.car-dot {
  width: 44px; height: 44px; padding: 20px 7px; cursor: pointer; border: 0; border-radius: 2px;
  background: var(--store-line-2); background-clip: content-box;
}
.car-dot.on { background: var(--g-core); background-clip: content-box; }
.car-count { color: var(--store-dim); font-size: .72rem; font-family: var(--mono); margin-left: 8px; }
/* the way to stop it. A thing that moves on its own and cannot be stopped is a
   WCAG 2.2.2 failure before it is a taste question; hover and focus already
   hold the rotation, and this is the one that holds it for good. Same 44px
   floor as the dots — the text is small and the target is not. */
.car-hold {
  margin-left: 10px; padding: 0 10px; min-height: 44px; cursor: pointer;
  background: none; border: 0; color: var(--store-dim);
  font-family: var(--sg); font-weight: 600; font-size: .68rem;
  letter-spacing: .12em; text-transform: uppercase;
}
.car-hold:hover { color: var(--store-ink); }
@media (prefers-reduced-motion: reduce) {
  /* the rotation does not run at all here (js/store.js checks the same query),
     so the only thing left to disarm is the crossfade a manual press makes */
  .car-track > .hero, .car-track > .hero.on { transition: none; }
}

/* ── the catalog band ────────────────────────────────────────────────────
   Steam's queue banner shape — copy left, a fan of art right — pointed at the
   whole catalog instead of at a recommendation. */
.queue {
  display: flex; align-items: center; gap: 18px; padding: 16px 18px;
  border: 1px solid var(--store-line); border-radius: var(--r);
  background-color: #17201a;
  background-image: linear-gradient(100deg, #17201a 0%, #121613 58%, #0e120f 100%);
  box-shadow: var(--bevel);
}
.queue:hover { text-decoration: none; border-color: var(--g-live); box-shadow: 0 0 0 1px rgba(0, 200, 5, .28), var(--bevel); }
.q-body { flex: 1 1 auto; min-width: 0; }
.q-eyebrow {
  font-family: var(--sg); font-weight: 700; font-size: .6rem; letter-spacing: .2em;
  text-transform: uppercase; color: var(--g-live);
}
.q-body h3 { font-family: var(--sg); font-weight: 700; font-size: 1.05rem; margin: 4px 0 6px; }
.q-body p { color: var(--store-muted); font-size: .85rem; max-width: 54em; }
.q-art { display: flex; gap: 6px; flex: 0 0 auto; }
.q-art img {
  width: 116px; height: 66px; object-fit: cover; border-radius: var(--r-sm);
  border: 1px solid var(--store-line);
}
.q-cta { flex: 0 0 auto; color: var(--g-live); font-family: var(--sg); font-weight: 700; font-size: .8rem; }
@media (max-width: 899px) { .q-art { display: none; } }

/* ── the tab bar ─────────────────────────────────────────────────────────
   Xbox-green underline on the live tab, which is the structure green and says
   "you are here" — never the fill, which is reserved for money. */
.tabbar {
  display: flex; gap: 4px; flex-wrap: wrap; align-items: flex-end;
  border-bottom: 1px solid var(--store-line); margin-bottom: 14px;
}
.tab {
  appearance: none; cursor: pointer; background: transparent; border: 0;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  padding: 10px 12px; min-height: 44px;
  font-family: var(--sg); font-weight: 700; font-size: .82rem; letter-spacing: .06em;
  color: var(--store-muted);
}
.tab:hover { color: var(--store-ink); }
.tab.on { color: var(--store-ink); border-bottom-color: var(--g-core); }
.tab .tab-n { font-family: var(--mono); font-size: .72rem; color: var(--store-dim); margin-left: 6px; }

/* rows left, preview right */
.tabwrap { display: grid; grid-template-columns: minmax(0, 1fr) 330px; gap: 12px; align-items: start; }
.tabcol { min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.tabrows { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.rowcard {
  display: flex; align-items: center; gap: 12px; padding: 8px; text-align: left;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background-color: var(--store-panel);
  background-image: linear-gradient(180deg, var(--store-panel), #10140f);
  cursor: pointer; width: 100%; appearance: none; font: inherit; color: inherit;
}
.rowcard:hover, .rowcard:focus-visible, .rowcard.on { border-color: var(--g-live); text-decoration: none; }
.rowcard.on { background-color: #17201a; background-image: linear-gradient(180deg, #17201a, #121613); }
.rc-art {
  flex: 0 0 132px; aspect-ratio: 462 / 174; overflow: hidden; border-radius: var(--r-sm);
  background-color: var(--store-pit);
  background-image: linear-gradient(150deg, #1b221c, #0d100c);
}
.rc-art img { width: 100%; height: 100%; object-fit: cover; }
/* .rc-n and .rc-tags are <span>s and were BOTH INLINE until 2026-08-10, so the
   catalog row read "Gatessurvival, multiplayer, open source" — the name and
   the tags butted together with no space, on the live front page, for the one
   game the store lists. The `margin-top` below is the tell: it was written for
   a stacked pair and does nothing to an inline box. The column is the fix, and
   it is on the parent so neither span has to know. */
.rc-b { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.rc-n { font-family: var(--sg); font-weight: 700; font-size: .92rem; color: var(--store-ink); }
.rc-tags { color: var(--store-dim); font-size: .74rem; margin-top: 3px; }
.rc-state { flex: 0 0 auto; }
.tabrows .row-empty { padding: 14px 8px; font-size: .85rem; }

/* the preview rail — Steam's hover panel. It shows what the row already says
   plus the art; it never shows a score, because there is no score to show. */
.tabpeek {
  border: 1px solid var(--store-line); border-radius: var(--r); overflow: hidden;
  background-color: var(--store-panel-2);
  background-image: linear-gradient(180deg, var(--store-panel-2), var(--store-panel));
  position: sticky; top: 12px;
}
.pk-art { display: block; aspect-ratio: 616 / 353; background: var(--store-pit); }
.pk-art img { width: 100%; height: 100%; object-fit: cover; }
.pk-b { padding: 12px 13px 14px; display: flex; flex-direction: column; gap: 7px; }
.pk-n { font-family: var(--sg); font-weight: 700; font-size: 1.02rem; }
.pk-k { color: var(--store-muted); font-size: .8rem; }
.pk-p { color: var(--store-muted); font-size: .82rem; }
.pk-tags { display: flex; flex-wrap: wrap; gap: 5px; }
.pk-tag {
  font-size: .68rem; padding: 3px 7px; border-radius: var(--r-sm);
  border: 1px solid var(--store-line-2); color: var(--store-muted); background: var(--store-pit);
}
.pk-strip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.pk-strip span { aspect-ratio: 16 / 9; overflow: hidden; border-radius: var(--r-sm); background: var(--store-pit); }
.pk-strip img { width: 100%; height: 100%; object-fit: cover; }
.pk-acts { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 2px; }
@media (max-width: 899px) {
  .tabwrap { grid-template-columns: 1fr; }
  /* on a phone the preview rail is a second copy of the row you just tapped,
     so it is not drawn at all — the row card links straight to the listing */
  .tabpeek { display: none; }
}

/* ── the community band ──────────────────────────────────────────────────
   A hub is a place, so its card is wider than a capsule and carries a kind
   line: the house, the workshop, a game. */
/* The track has a CEILING as well as a floor, and the ceiling is the load-
   bearing half: `minmax(280px, 1fr)` looks identical with three hubs in the
   row and blows a single hub up to the full 1180px — a 680px-tall capsule for
   one game, which is what the workshop hub rendered. A hub card is a card at
   any count. */
.hubrow {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 12px; justify-content: start;
}
.hubcard {
  /* the ceiling lives on the CARD, not on the track. `minmax(280px, 380px)`
     is the obvious way to write it and is wrong twice: auto-fit counts its
     repetitions with the MAX, so three hubs in a 1148px row become two and a
     wrapped orphan — while `minmax(280px, 1fr)` with no ceiling anywhere blew
     a single hub up to the full width and gave one game a 680px capsule. */
  max-width: 400px;
  display: flex; flex-direction: column; overflow: hidden;
  border: 1px solid var(--store-line); border-radius: var(--r);
  background-color: var(--store-panel);
  background-image: linear-gradient(180deg, var(--store-panel), #10140f);
  box-shadow: var(--bevel);
}
.hubcard:hover { text-decoration: none; border-color: var(--g-live); }
/* `.hubcard.works` lived here for the Scryworks hub card and went with it on
   2026-08-06 — the house has one hub, and the dev side is a portal. */
.hc-art { display: block; aspect-ratio: 616 / 353; overflow: hidden; background: var(--store-pit); }
.hc-art img { width: 100%; height: 100%; object-fit: cover; }
.hc-b { padding: 11px 12px 13px; display: flex; flex-direction: column; gap: 4px; }
/* raised to the 13px floor 2026-08-05 with the rest of the site's small
   labels — these three sat under it on the FRONT page, the most-read surface
   on the site, which is the last place to be shipping 9.6px body text. */
.hc-k {
  font-family: var(--sg); font-weight: 700; font-size: .82rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--store-dim);
}
.hc-n { font-family: var(--sg); font-weight: 700; font-size: 1rem; color: var(--store-ink); }
.hc-p { color: var(--store-muted); font-size: .82rem; }

/* the art band — three frames, each captioned with what it actually is */
.shots { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 10px; margin-top: 12px; }
.shots .shot { border: 1px solid var(--store-line); border-radius: var(--r-sm); overflow: hidden; background: var(--store-pit); }
.shots .shot img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.shots .shot figcaption { padding: 7px 9px; color: var(--store-dim); font-size: .82rem; }

/* ── the item plates ─────────────────────────────────────────────────────
   Square, because an item plate is square. Captioned in the open with what
   the plate is, so the row cannot be misread as a shelf of things to buy. */
.plates { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.plates .plate { border: 1px solid var(--store-line); border-radius: var(--r-sm); overflow: hidden; background: var(--store-pit); }
.plates .plate img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; }
.plates .plate figcaption {
  padding: 7px 9px; color: var(--store-muted); font-size: .82rem;
  font-family: var(--sg); font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
}

/* a wide strip that carries art on its left */
.wide.art { padding: 0; overflow: hidden; }
.wide-art { flex: 0 0 200px; align-self: stretch; overflow: hidden; background: var(--store-pit); }
.wide-art img { width: 100%; height: 100%; object-fit: cover; }
.wide.art .wide-body { padding: 14px 0; }
.wide.art .wide-cta { margin-right: 16px; }
@media (max-width: 699px) {
  .wide.art { flex-direction: column; align-items: stretch; }
  .wide-art { flex-basis: auto; aspect-ratio: 616 / 353; }
  .wide.art .wide-body { padding: 0 14px; }
  .wide.art .wide-cta { margin: 0 14px 14px; }
  .marquee { min-height: 0; }
  .mq-body { padding: 18px 16px; }
  .mq-veil { background: linear-gradient(180deg, rgba(10, 12, 10, .84) 0%, rgba(10, 12, 10, .96) 55%); }
  .queue { flex-wrap: wrap; }
}

/* the thumb's floor. Everything here is a tap target or body copy that a desk
   sets small on purpose and a phone may not: 44px on the short side, 13px on
   prose. deploy/capture_site.js measures both and reports them per page. */
@media (max-width: 899px) {
  .row-act { display: inline-flex; align-items: center; min-height: 44px; }
  .car-arrow { flex: 0 0 44px; font-size: 1.2rem; }
  .row-foot { font-size: .84rem; }
  .row-act { font-size: .82rem; }
  .rc-tags { font-size: .82rem; }
  .q-cta { font-size: .84rem; }
}

/* ── the download page (download.html) ───────────────────────────────────
   A command a reader has to retype is a command they get wrong, so every one
   on that page is a real <pre> they can select whole. Mono, boxed, and it
   scrolls itself rather than the document — the apt line is 48 characters and
   a 390px phone is not. */
.cmd {
  margin: 10px 0; padding: 11px 13px; overflow-x: auto;
  background: var(--store-bg); border: 1px solid var(--store-line);
  border-left: 2px solid var(--g-core); border-radius: var(--r);
  font: .82rem/1.7 var(--mono); color: var(--store-ink);
  white-space: pre; -webkit-text-size-adjust: 100%;
}
.cmd code { font: inherit; color: inherit; }
/* the same command, sized for the inside of a money box: the owned ticket
   names `scry install <slug>` on its own line rather than sending a buyer to
   another page to find the one instruction that runs what they just bought. */
.cmdline {
  display: block; margin: 8px 0; padding: 9px 11px; overflow-x: auto;
  background: var(--store-bg); border: 1px solid var(--store-line);
  border-left: 2px solid var(--g-core); border-radius: var(--r);
  font: .8rem/1.6 var(--mono); color: var(--store-ink);
  white-space: pre; -webkit-text-size-adjust: 100%;
}
.dl-grid { display: grid; gap: 16px; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); align-items: start; }
@media (max-width: 799px) { .dl-grid { grid-template-columns: minmax(0, 1fr); } }
.dl-eyebrow {
  font-family: var(--sg); font-weight: 600; font-size: .68rem;
  letter-spacing: .14em; text-transform: uppercase; color: var(--store-muted);
}
.dl-h { font-size: 1.5rem; margin: 4px 0 8px; }
.dl-sh { font-size: 1rem; margin-bottom: 6px; }
.dl-fine { font-size: .82rem; color: var(--store-muted); margin-top: 10px; }
/* one per listed title on the install page: the name, whether a build is
   published, and the command that installs it. */
.dl-game + .dl-game { margin-top: 18px; border-top: 1px solid var(--store-line); padding-top: 16px; }
.dl-game-tr { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.dl-game-tr b { font-family: var(--sg); font-size: .98rem; }
.dl-side .meta-rows .mr-k { flex-basis: 44px; }
.dl-side .mr-v { font-size: .7rem; }
/* Two platforms side by side inside the download panel. They are SIBLINGS on
   purpose rather than a tab strip: a player on Windows should not have to
   discover that a control they did not press is hiding their download, and a
   reader comparing the two can see both at once. The rule between them is the
   only separator — a second panel border inside a panel reads as a nested box.
   They stack below the same 800px the .dl-grid does, so the page never has two
   narrow columns of code lines. */
.dl-plat { display: grid; gap: 18px; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); margin-top: 18px; }
.dl-plat > .dl-os + .dl-os { border-left: 1px solid var(--store-line); padding-left: 18px; }
.dl-osh {
  font-family: var(--sg); font-size: .74rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: var(--store-ink);
  margin: 0 0 10px; padding-bottom: 8px; border-bottom: 1px solid var(--store-line);
}
.dl-plat .cta-row { margin-top: 12px; }
/* The download buttons are the act this page is for, so they fill the column
   rather than sitting at whatever width their label happens to be. */
.dl-plat .cta-row .btn { flex: 1 1 auto; }
@media (max-width: 799px) {
  .dl-plat { grid-template-columns: minmax(0, 1fr); }
  .dl-plat > .dl-os + .dl-os { border-left: none; padding-left: 0; border-top: 1px solid var(--store-line); padding-top: 18px; }
}

/* ── the draft shelf (2026-08-10) ─────────────────────────────────────────
   `?drafts=1` renders `hidden: true` rows so the operator can look at a
   populated store while working out what a second title needs from the
   platform. Both rules below exist to make that view impossible to mistake
   for the store, because a screenshot outlives the query string that made it.

   Amber, not green: green is this store's settled-and-real colour (links,
   money, live) and a draft is neither. Amber is already the store's
   in-build / not-armed / caution signal, which is exactly what a draft is. */
.draft-bar {
  border: 1px solid var(--store-gold);
  border-left-width: 4px;
  background: rgba(201, 162, 39, .07);
  color: var(--store-ink);
  padding: 11px 14px; margin: 0 0 20px;
  font-size: 13px; line-height: 1.55;
}
.draft-bar b { color: var(--store-gold); text-transform: uppercase; letter-spacing: .06em; }
.draft-bar code {
  font-family: var(--mono); font-size: 12px;
  background: var(--store-pit); border: 1px solid var(--store-line);
  border-radius: var(--r-sm); padding: 1px 5px;
}

/* the per-card mark. It rides on the capsule rather than in the meta block so
   it survives a crop: a screenshot tight enough to lose the card's footer
   still has the word in frame. */
.card.is-draft { outline: 1px solid var(--store-gold); outline-offset: -1px; }
.card.is-draft .capsule { position: relative; }
.draft-tag {
  position: absolute; left: 8px; top: 8px; z-index: 2;
  background: var(--store-gold); color: #17140a;
  font: 600 10px/1 var(--sg); letter-spacing: .12em; text-transform: uppercase;
  padding: 5px 8px; border-radius: var(--r-sm);
}
.rowcard.is-draft { outline: 1px solid var(--store-gold); outline-offset: -1px; }
.chip.draft {
  background: var(--store-gold); color: #17140a; border-color: var(--store-gold);
  text-transform: uppercase; letter-spacing: .08em; font-weight: 600;
}

/* ── the launcher's sign-in door (js/launcher-signin.js §mount) ────────────
   The second button on every sign-in surface, and the panel it opens. Drawn
   by script rather than written into each page, so the pairing ceremony's
   sentences exist once. The code is BIG and the instruction is "type this" —
   there is no link on purpose, and js/launcher-signin.js's header says why.
   wallet.html predates this and styles its own copy (.pb-pair) inline. */
.lsi-pair {
  margin-top: 12px; display: grid; gap: 10px; justify-items: start;
  max-width: 46em;
  background: rgba(22, 27, 23, .6);
  border: 1px solid var(--store-line); border-radius: var(--r);
  padding: 14px 16px;
}
.lsi-pair[hidden] { display: none; }
.lsi-code {
  font: 700 2.1rem/1.1 var(--mono); letter-spacing: .16em; color: var(--store-ink);
}
.lsi-pair p { margin: 0; font-size: .9rem; color: var(--store-muted); }
.lsi-state { color: var(--store-dim); }
.lsi-get { font-size: .8rem; }
