/* Generated by tools/build_card_pages.py -- do not edit by hand.

   The card pages' one stylesheet.  Palette and keyword-box shapes come from
   the Twitch extension's overlay.css; the fonts and background are
   boardstate.gg's own, so a card page reads as the same site. */

@font-face{ font-family:'Roboto Slab'; src:url('../../fonts/RobotoSlab-Variable-latin.woff2') format('woff2');
            font-weight:100 900; font-style:normal; font-display:swap; }
@font-face{ font-family:'Inter'; src:url('../../fonts/Inter-Regular.woff2') format('woff2');
            font-weight:400; font-style:normal; font-display:swap; }

:root{
  color-scheme:dark;
  /* THE CAPTURE PAGE'S MATERIAL (core/capture/index.html :root, copied
     value for value): near-black cyan-leaned ground, panels a cyan-grey
     midtone the neumorphic shadows can carve into. */
  --bg:#0d1114;
  --bg-deep:#0a0d10;
  --panel-hi:#1b2328;
  --panel-lo:#141a1e;
  --panel:#171e23;
  --panel-2:#12181c;
  --line:#232c32;
  --text:#e8e8ea;
  --muted:#86929a;
  --cyan:#5fd0e0;
  --teal:#1f9ea3;
  --teal-glow:rgba(31,158,163,.16);
  --kw-line:rgba(95,208,224,.28);
  /* The light: every soft shadow pair is built from these two. */
  --lite:rgba(140,200,215,.06);
  --dark:rgba(3,6,8,.6);
  --raise:-5px -5px 12px var(--lite), 6px 6px 14px var(--dark);
  --raise-sm:-3px -3px 7px var(--lite), 3px 3px 8px var(--dark);
  --carve:inset 3px 3px 7px rgba(3,6,8,.55), inset -2px -2px 5px rgba(140,200,215,.05);
  /* A raised slab of the page material -- the capture page's .panel skin. */
  --slab:linear-gradient(148deg, var(--panel-hi), var(--panel-lo));
  --slab-edge:rgba(170,210,225,.05);
  /* THE FROSTED CONCEPT (Kevin loves this -- keep it reusable): a sticky
     surface goes translucent with a blur, so content scrolling beneath
     reads as under glass. Pair either token with
     backdrop-filter:blur(6px) wherever a bar should float over the page.
     --frost is the page ground's tint; --frost-lite is a lighter panel
     tint for a bar that must read against the ground at rest. .7 alpha:
     genuinely see-through (Kevin, 2026-09-05 -- .85 read as darker).
     The ground tint's channels also stand alone as --frost-rgb, for the
     one surface (the filter dropdown) whose alpha varies with scroll. */
  --frost-rgb:13,17,20;
  --frost:rgba(var(--frost-rgb),.7);
  --frost-lite:rgba(32,42,49,.7);
  /* The sticky site header's height: everything else that sticks (the
     finder, the card page's streamer banner) offsets below it by this. */
  --headbar-h:2.7rem;
  /* Buy mode's own colour: green says "in the basket", and keeping it off
     the teal/cyan family means selection state can never be confused with
     the site's links and accents. */
  --green:#3fca6b;
}

*{ box-sizing:border-box; }
html{ -webkit-text-size-adjust:100%; }
body{
  margin:0; color:var(--text);
  /* The capture page's ground: "a table lit from above" -- the teal
     spotlight bloom at top-centre over the dark vertical gradient, fixed
     so scrolling moves the page, not the light. (The moving-cards
     animation stays the capture page's own.) */
  background:
    radial-gradient(1100px 520px at 50% -12%, var(--teal-glow), transparent 68%),
    linear-gradient(180deg, var(--bg) 0%, var(--bg-deep) 100%);
  background-attachment:fixed;
  background-color:var(--bg);
  font-family:'Inter', system-ui, sans-serif;
  font-size:16px; line-height:1.6;
  /* The body must never scroll sideways: every wide thing below either
     wraps or gets its own scroller. */
  overflow-x:hidden;
}
img{ max-width:100%; }
/* Underlines are NOT this site's language, anywhere -- hover speaks in
   colour and weight (Kevin, 2026-09-05). */
a{ color:var(--cyan); text-decoration:none; }
a:hover{ text-decoration:none; }
h1,h2{ font-family:'Roboto Slab', Georgia, serif; font-weight:300; }
.mono{ font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* ---------------------------------------------------------------- chrome */
/* The site header rides the top of EVERY page. TWO states: at the top of
   the scroll it is exactly the header it always was -- full padding, no
   glass, the page's spotlight glowing through. Only once scrolling LOCKS
   it (common.js stamps .stuck past a few px of scroll) does it tighten and
   frost, and it loosens back when you return to the top. */
.site-head{
  display:flex; align-items:center; justify-content:space-between;
  gap:1rem; flex-wrap:wrap;
  position:sticky; top:0; z-index:40;
  max-width:1040px; margin:0 auto; padding:1.1rem 1.25rem;
  /* The glass and the rule moved to the ::before below, which runs to
     the window's edges. The border stays here as TRANSPARENT so the
     header's height is the height it always was -- the pseudo draws the
     visible line into exactly that 1px. */
  border-bottom:1px solid transparent;
  transition:padding .25s ease, margin-bottom .25s ease;
}
/* FULL-BLEED GLASS (Kevin, 2026-09-11). The frosted box used to stop at
   the 1040px column and end abruptly part-way across a wide window. From
   a centred box, left/right of calc(50% - 50vw) reach the window's edges
   -- the trick the finder, the facets panel and the streamer banner all
   wear now -- while the bar's own box, padding and contents are
   untouched, so nothing in the header moves. z-index:-1 keeps the glass
   behind the bar's content inside the header's own stacking context
   (z-index:40 above), and bottom:-1px lands its rule on the transparent
   border the box still reserves. */
.site-head::before{
  content:""; position:absolute; z-index:-1;
  top:0; bottom:-1px; left:calc(50% - 50vw); right:calc(50% - 50vw);
  background:transparent;
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  border-bottom:1px solid var(--line);
  transition:background .25s ease;
}
/* margin-bottom:1.2rem = exactly the padding the stuck state gives up
   (.6rem top + .6rem bottom), so the header's total occupied height NEVER
   changes between states. Without this, the header growing as it
   un-sticks pushed the page down under the reader every wheel notch --
   the "can't quite reach the top" resistance. */
.site-head.stuck{
  padding:.5rem 1.25rem;
  margin-bottom:1.2rem;
}
.site-head.stuck::before{ background:var(--frost); }
.wordmark{
  font-family:'Roboto Slab', Georgia, serif; font-weight:600;
  color:var(--teal); font-size:1.05rem; letter-spacing:.01em;
  transition:color .15s ease, text-shadow .15s ease;
}
/* Never underlined: on hover the wordmark lifts to the bright cyan (the
   same teal-to-cyan step the game menu's Riftbound row takes) inside a
   soft blue backlight. */
.wordmark:hover{
  text-decoration:none; color:var(--cyan);
  text-shadow:0 0 20px rgba(95,208,224,.5);
}
/* "board state | About us": a page title beside the wordmark, one line,
   a thin divider in the same line colour, the title in the site's white
   (Kevin-decided, 2026-09-25: "Thin divider, same white"). */
.head-brand{ display:flex; align-items:center; gap:.75rem; }
/* height:1.05rem = the wordmark's size (Claude-decided, 2026-09-25). */
.head-divider{ width:1px; height:1.05rem; background:var(--line); }
/* Same face, weight and size as the wordmark; only the colour differs
   (Claude-decided, 2026-09-25; the 400 weight it had read as a different
   font next to the 600 wordmark).
   KEVIN-REQUIRED 2026-09-25 (verbatim): "The left side should be the same font and font size. The right side should also be the same font and font size. However, the left side does not need to be the same as the right side, and probably shouldnt be." */
.head-title{
  font-family:'Roboto Slab', Georgia, serif; font-weight:600;
  color:var(--text); font-size:1.05rem; letter-spacing:.01em;
}
.site-head nav{ display:flex; gap:1.25rem; flex-wrap:wrap; align-items:center; }
.site-head nav a{ color:var(--muted); font-size:.9rem; }
/* Hover speaks in weight and colour, never an underline (not our
   language). #b7bdc2 = the midpoint of the muted rest (#86929a) and full
   white, which hovered too bright. */
.site-head nav a:hover{ color:#b7bdc2; font-weight:700;
                        text-decoration:none; }
/* The page you are already on is named, not offered. */
.site-head nav .here{ color:var(--text); font-size:.9rem; }

/* The Ko-fi cup at the nav's end, on every page: rests in the nav links'
   muted grey and speaks their hover language -- an icon cannot bold, so
   the stroke thickens instead while the colour lifts to the cyan. Sized
   well past the nav type (2026-09-06: at text height the cup was
   unreadable); the flex centring puts its centre line on the nav text's
   own. */
.kofi-link{
  display:inline-flex; align-items:center; color:var(--muted);
  transition:color .15s ease;
}
.kofi-link:hover{ color:var(--cyan); }
/* Inside the site header the generic nav hover (.site-head nav a:hover,
   grey, bold) out-ranked the rule above, so the cup only went cyan on the
   front page, where it sits outside the header. Named here at higher
   specificity so every page's cup hovers the same cyan; no bold, an icon
   thickens its stroke instead (Claude-decided, 2026-09-25).
   KEVIN-REQUIRED 2026-09-25 (verbatim): "on the main landing page, hovering over the coffee turns it a token teal. However this doesnt persist on the about us page and elsewhere. Please change that." */
.site-head nav a.kofi-link:hover{ color:var(--cyan); font-weight:400; }
.kofi-cup{ width:30px; height:30px; display:block; flex:none; }
.kofi-link:hover .kofi-cup{ stroke-width:2.4; }

/* ---- the game picker --------------------------------------------------- */
/* Leads the nav: the two links after it are pages OF the chosen game. The
   name keeps the wordmark's teal, but wears the nav links' own face,
   weight and size (Inter 400 at .9rem) so the right side of the header
   is one font; colour alone marks it as the game (Claude-decided,
   2026-09-25; until then it was Roboto Slab 600 like the wordmark).
   The caret is what says "menu". The dropdown's rows keep their slab
   face (TBD, 2026-09-25).
   KEVIN-REQUIRED 2026-09-25 (verbatim): "The left side should be the same font and font size. The right side should also be the same font and font size. However, the left side does not need to be the same as the right side, and probably shouldnt be." */
.game-pick{ position:relative; }
.game-name{
  color:var(--teal); font-weight:400; font-size:.9rem;
  font-family:'Inter', system-ui, sans-serif;
  cursor:pointer; list-style:none; display:inline-flex; align-items:center;
}
.game-name::-webkit-details-marker{ display:none; }
.game-name::marker{ content:""; }
/* .7em -> 1em, ~40% bigger (Claude-decided, 2026-09-25).
   KEVIN-REQUIRED 2026-09-25 (verbatim): "make the triangle bigger in the header, which is to the right of "Riftbound"." */
.game-caret{ font-size:1em; margin-left:.35em; color:var(--muted);
             transition:transform .15s ease; }
.game-pick[open] .game-caret{ transform:rotate(180deg); }
.game-name:hover .game-caret{ color:var(--teal); }
.game-menu{
  position:absolute; top:calc(100% + .5rem); left:0; z-index:60;
  /* The .5rem gap above the menu is bridged by an invisible strip, so a
     pointer sliding from the name down into the menu never leaves the
     picker (which would close a hover-opened menu; see common.js)
     (Claude-decided, 2026-09-25). */
  min-width:13rem; padding:.35rem;
  background:var(--slab); border:1px solid var(--slab-edge);
  border-radius:14px;
  box-shadow:var(--raise), 0 14px 40px rgba(0,0,0,.55);
  display:flex; flex-direction:column; gap:.1rem;
}
.game-menu::before{
  content:""; position:absolute; left:0; right:0; top:-.6rem; height:.6rem;
}
.game-row{
  padding:.45rem .65rem; border-radius:6px; font-size:.88rem;
}
/* A game's name in the menu speaks in the same voice as the game name that
   is always visible in the header -- Roboto Slab, not the body sans. The
   "coming soon" note is supporting text and stays in the body face. */
a.game-row{ font-family:'Roboto Slab', Georgia, serif; }
/* The selected game: marked as current, and a link to its front page --
   picking the game you are in is a way home, not a dead end. */
.game-row.on{ color:var(--teal); font-weight:600; background:var(--panel-2); }
a.game-row.on:hover{ color:var(--cyan); text-decoration:none; }
/* The promise row: visibly not an option yet. */
.game-row.soon{ color:var(--muted); font-size:.78rem; font-style:italic;
                cursor:default; }

.site-foot{
  max-width:1040px; margin:3.5rem auto 0; padding:1.5rem 1.25rem 1.5rem;
  border-top:1px solid var(--line);
  color:var(--muted); font-size:.82rem;
}
/* Bottom padding halved 3rem -> 1.5rem on every page (Kevin-decided, 2026-09-25):
   KEVIN-REQUIRED 2026-09-25 (verbatim): "Halve the empty space below "boardstate.gg privacy user agreement about us"" */
/* The copyright line wears the credit's own font, color and size
   (Kevin-decided, 2026-09-13). The gap to the credit was one blank line
   (.96rem, 2026-09-13) and is now half that, .48rem:
   KEVIN-REQUIRED 2026-09-25 (verbatim): "Change the footer to have half the empty space between the copyright line and the "riftbound and all related [...]" and also remove a lot of empty space above "boardstate.gg Privacy User Agreement About us"" */
.site-foot .copyright{ margin:0 0 .48rem; font-size:.6rem; color:#5c666d; }
/* FINE print, not main print: the legal credit drops to ~60% of the
   footer's size in a darker grey -- present for anyone who looks, quiet
   for everyone else. No max-width: at this size a ch-capped measure
   would wrap it into a taller footer, and shorter is the point.
   The space above the links line drops .8rem -> .3rem (KEVIN-REQUIRED
   2026-09-25, "remove a lot of empty space above", quoted above; the
   .3rem is Claude-decided, 2026-09-25). */
.site-foot .credit{ margin:0 0 .3rem; font-size:.6rem; color:#5c666d; }

/* flex-end, not center: the Instagram mark stands taller than the link
   text, and its BOTTOM is what lines up with the text's own. */
.site-foot .links{ margin:0; display:flex; gap:1.5rem; flex-wrap:wrap;
                   align-items:flex-end; }
/* Footer links speak like select all / clear selection: dark teal at rest,
   the midpoint blue and bold on hover, never underlined. */
.site-foot a{ color:var(--teal); }
.site-foot a:hover{ text-decoration:none; color:#3fb7c2; font-weight:700; }

/* The Instagram mark: ON the links line, pushed to its right edge --
   in-line with boardstate.gg / Privacy / User Agreement / About us. Grey
   outlines at rest (the compound selector out-ranks .site-foot a's teal),
   the footer links' own blue with a thickened stroke on hover -- an
   icon's bold. Inert until the account URL is wired (see site_footer). */
.site-foot .ig-link{
  margin-left:auto; display:inline-flex; color:var(--muted); cursor:pointer;
  transition:color .15s ease;
}
.site-foot .ig-link:hover{ color:#3fb7c2; }
.ig-mark{ width:28px; height:28px; display:block; }
.ig-link:hover .ig-mark{ stroke-width:2.6; }

/* ------------------------------------------------------------- about page */
/* The body text wears the same muted grey as a card page's rules text.
   No max-width: the copy runs edge to edge of the glass slab, whose own
   padding gives it equal margins left and right. */
.about-body{ line-height:1.65; font-size:.95rem;
             color:var(--muted); }
/* The paragraphs keep their own margins at the slab's edges, so
   rectangle-to-text is padding + paragraph margin, ~31px. A halved
   version (~16px) lived for one round on 2026-09-25 and was reverted:
   KEVIN-REQUIRED 2026-09-25 (verbatim): "Lets revert the change inside the bubble rectangle. This means basically double the empty space between the text and the top and bottom of the bubble rectangle." */
/* The site's name, spoken inside body copy, wears the wordmark's own
   teal, weight and slab face -- same voice as the header. */
.about-body .site-name{
  color:var(--teal); font-weight:600;
  font-family:'Roboto Slab', Georgia, serif; letter-spacing:.01em;
}
/* No h1 any more: the title moved into the header as
   "board state | About us" (site_header's `title`).
   KEVIN-REQUIRED 2026-09-25 (verbatim): "Lets try moving the "About us" title to instead be to the right of the "board state" header at the top of the screen."
   The slab now opens the column, so it takes the top air itself
   (Claude-decided, 2026-09-25). */
/* Header-to-slab air: halved to 8px earlier on 2026-09-25, now tripled to
   24px after Kevin clarified he meant the slab's inner space
   (Claude-decided, 2026-09-25; see the .about-body p rules for his quote). */
.about-page{ padding-top:0; }
/* 24px -> 36px (KEVIN-REQUIRED 2026-09-25 (verbatim): "increase the space between the top bubble rectangle description and the header line by about 50%"). */
body:has(.about-page) > main{ padding-top:2.25rem; }
/* THE TEAM BIOS, under the coffee button on the plain page background --
   no slab (KEVIN-REQUIRED 2026-09-25 (verbatim): "The background should instead be the existing website background color").
   DESIGN UNIT: Kevin built the mock in Google Slides (720pt x 405pt).
   Every size here is N slide points via --pt, one slide point at the
   site's scale: 1350px column / 720pt = 1.875px (Claude-decided,
   2026-09-25). Retune the whole section with that one number.
   Distances are Claude-measured from the slide (Claude-decided,
   2026-09-25); font sizes are Kevin's slide sizes (Kevin-decided,
   2026-09-25). Columns are 224pt wide, edge to edge, centred; one
   column under 720px (Claude-decided, 2026-09-25). */
.about-team{
  --pt:1.875px;
  /* Column width history, all 2026-09-25: 224pt (the slide) -> 184pt
     (KEVIN-REQUIRED (verbatim): "Let's reduce the horizontal spacing between bios by about 15-20%, so that it is more similar to my google slides draft.")
     -> 222pt, solved so that kpal's diamond sits as far right of the
     header's "|" as its text sits right of the diamond
     (KEVIN-REQUIRED (verbatim): "the spacing between each bio should be increased. my google slide has a lot more. Increase it but the cap on moving is such that kevin's bullet points should have the same horizontal distance between the | in "board state | About us" and the diamond, as the same distance between the diamond and the text to the right such as "MIT 2018"").
     The solve (Claude-decided): diamond x = half the content width
     - column width - list_x, and the diamond-to-text gap is 13.3pt minus
     the glyph, so column = 655px - 102px - 17.5px - 120px = 222pt.
     -> 195.5pt pitch (-12%), as a 183.5pt card plus a 12pt gap between
     cards (KEVIN-REQUIRED 2026-09-25 (verbatim): "Make the bios about 10% to 15% closer to each other.").
     The card is as narrow as the longest bullet row allows: the lists
     start list_x left of centre, so the widest row ("not sure how I
     ended up on this project") ends ~86pt right of centre, inside a
     91.75pt half-card (Claude-decided, 2026-09-25). This overrides the
     "|"-to-diamond cap from the round before; Kevin asked for closer
     after it (TBD, 2026-09-25: re-solve if the cap should still hold). */
  /* auto-fit, not 3: on a window too narrow for three columns the outer
     bios used to run off the sides; now the third drops to a second row
     (its right-hand cell empty), and under 720px it is one column
     (Claude-decided, 2026-09-25).
     KEVIN-REQUIRED 2026-09-25 (verbatim): "When doing adaptive resolution, the outside bios just get cut in half vertically. This looks bad. This shouldnt be the case and instead can do a 2 column of bios (with cell row 2, column 2 just being empty since we have 3 bios), or just be a full single column." */
  display:grid; grid-template-columns:repeat(auto-fit, calc(183.5 * var(--pt)));
  justify-content:center; gap:calc(12 * var(--pt));
  /* No bottom margin: the footer's own 3.5rem is the bios-to-footer gap,
     about half the old 118px
     (KEVIN-REQUIRED 2026-09-25 (verbatim): "reduce the spacing between the bios and the footer top line by about 50%"). */
  margin:0;
}
@media (max-width:720px){
  .about-team{ grid-template-columns:1fr; }
  /* Stacked, the column is the whole screen and the slide's centre offset
     would start the lists mid-screen; cap it at 2rem so long rows fit
     (Claude-decided, 2026-09-25; TBD: phone layout not in the slide). */
  .about-team .checks,
  .about-team .facts{ margin-left:min(calc(50% - var(--list-x) * var(--pt)), 2rem); }
}
/* Plain columns on the page ground. Glass cards around each bio were
   tried and removed the same day (KEVIN-REQUIRED 2026-09-25 (verbatim):
   "lets remove the bubble rectangles from the bios"); the column pitch
   they came with stays. */
.about-team .member{
  display:flex; flex-direction:column; align-items:center; text-align:center;
}
.about-team .handle{
  width:max-content; max-width:100%; /* hit box = the text (see .about-team .member > *) */
  margin:0 0 calc(12 * var(--pt)); font-family:'Roboto Slab', Georgia, serif;
  font-style:italic; color:var(--muted); font-size:calc(6 * var(--pt));
}
/* 70pt: ~30% under the slide's 100pt (Kevin-decided, 2026-09-25). */
.about-team .photo{
  width:calc(70 * var(--pt)); height:calc(70 * var(--pt));
  border-radius:50%; object-fit:cover;
  display:block; margin:0 0 calc(11 * var(--pt));
}
/* .team-quote, not .quote: the card pages' .quote:last-of-type{margin-bottom:0}
   ties on specificity and comes later, which zeroed this gap
   (Claude-decided, 2026-09-25, found in the localhost preview).
   KEVIN-REQUIRED 2026-09-25 (verbatim): "All quote text should be roboto slab light, specifically"
   KEVIN-REQUIRED 2026-09-25 (verbatim): "Andreea and surya's quote text is size 8 font"
   Accent colours sampled from the slide (Claude-decided, 2026-09-25; TBD, 2026-09-25 to tune). */
.about-team .team-quote{
  width:max-content; max-width:100%; /* hit box = the text (see .about-team .member > *) */
  margin:0 0 calc(26 * var(--pt)); font-family:'Roboto Slab', Georgia, serif;
  font-weight:300; font-style:italic; font-size:calc(8 * var(--pt));
  line-height:1.33; color:var(--accent);
}
/* THE TWO LISTS SHARE ONE LEFT EDGE. Both start --list-x slide points
   left of the column centre (per member, Claude-measured from the slide,
   Claude-decided 2026-09-25), so the ☑ and ♦ glyphs line up exactly.
   KEVIN-REQUIRED 2026-09-25 (verbatim): "The checkmarks and bullets below kpal are supposed to line up exactly"
   KEVIN-REQUIRED 2026-09-25 (verbatim): "Whatchuwantb's bullet points should be moved to the right, so that the spacing between bullet and "MIT 2018" is about exactly lined up with the center of "I" in "I play pot of greed""
   KEVIN-REQUIRED 2026-09-25 (verbatim): "Kevin's quote text is too far left in your draft compared to my google slide version." */
.about-team .checks,
.about-team .facts{
  list-style:none; padding:0; text-align:left;
  /* Shrink-wrapped and anchored at the column's left, so the 50% below
     still measures the column while the box ends at the longest row
     (hit-testing: see .about-team .member > *) (Claude-decided, 2026-09-25). */
  align-self:flex-start; width:max-content; max-width:100%;
  margin-left:calc(50% - var(--list-x) * var(--pt));
}
.about-team .checks li,
.about-team .facts li{ position:relative; }
.about-team .checks li::before,
.about-team .facts li::before{ position:absolute; left:0; }
/* kpal's checklist.
   KEVIN-REQUIRED 2026-09-25 (verbatim): "Kevin's quote text is size 7 font"
   The ☑ (Kevin-decided, 2026-09-25; TBD, 2026-09-25: "we can potentially
   change that") lives in ::before with a symbol font stack, since the
   slab and Inter have no U+2611 (Claude-decided, 2026-09-25; TBD,
   2026-09-25: swap for an inline SVG if a platform still shows a box).
   8pt indent = the slide's ☑-left to text-left distance (7.5pt, glyph
   included) rounded up so the light text never touches the box; the
   box itself is set at 6pt so it reads as small as the slide's
   (Claude-decided, 2026-09-25, re-measured in the localhost preview). */
.about-team .checks{
  margin-top:0; margin-right:0; margin-bottom:calc(18 * var(--pt));
  font-family:'Roboto Slab', Georgia, serif; font-weight:300;
  font-style:italic; font-size:calc(7 * var(--pt)); line-height:1.35;
  color:var(--accent);
}
.about-team .checks li{
  padding-left:calc(8 * var(--pt)); margin:0 0 calc(5.5 * var(--pt));
}
.about-team .checks li::before{
  content:"\2611"; color:var(--text); font-style:normal; font-weight:400;
  font-size:calc(6 * var(--pt)); top:.05em;
  font-family:"Segoe UI Symbol","Apple Symbols","Noto Sans Symbols 2","Noto Sans Symbols",sans-serif;
}
/* The bullets: Inter (KEVIN-REQUIRED 2026-09-25 (verbatim): "Lets try using the Inter font"),
   each row led by a ♦ in the accent (KEVIN-REQUIRED 2026-09-25). 13.3pt
   indent = the slide's ♦-left to text-left distance, glyph included
   (Claude-decided, 2026-09-25, re-measured in the localhost preview: the
   first cut double-counted the glyph). */
.about-team .facts{
  margin-top:0; margin-right:0; margin-bottom:0;
  font-family:'Inter', system-ui, sans-serif; font-size:calc(8 * var(--pt));
  /* Muted grey, the description's own colour, not white
     (KEVIN-REQUIRED 2026-09-25 (verbatim): "change the font color of the bullets from white to instead be more gray similar to the paragraph description"). */
  line-height:1.35; color:var(--muted);
}
.about-team .facts li{
  /* Row pitch 18.4pt = Slides "double" (~2.3 x 8pt) (Claude-decided, 2026-09-25).
     KEVIN-REQUIRED 2026-09-25 (verbatim): "In my google slides, I am using double spaced for the bullet points. your draft looks larger than that." */
  padding-left:calc(13.3 * var(--pt)); margin:0 0 calc(7.6 * var(--pt));
}
.about-team .facts li::before{ content:"\2666"; color:var(--accent); }

/* The coffee button 20pt under whatchuwantb's last bullet; two classes
   out-rank the page-wide .kofi-center (Claude-decided, 2026-09-25). */
.about-team .kofi-center{ margin:calc(20 * var(--pt)) 0 0; }
/* THE CAPTURE PAGE'S PANEL: the description sits on a raised slab in the
   capture page's exact clothes (.panel there: the --slab gradient, the
   --raise shadow pair, the faint --slab-edge line, 18px corners). The
   panel is opaque, so the frosted-glass look this slab wore all day
   (the DVD card blurring behind it) is gone here; the blur lines stay
   harmless underneath (Claude-decided, 2026-09-25; TBD: bring the frost
   back by giving the gradient alpha if Kevin misses it).
   KEVIN-REQUIRED 2026-09-25 (verbatim): "Please give the paragraph description bubble rectangle a slight 3d effect, such that it has the exact same design language as capture.boardstate.gg." */
.about-glass{
  /* Top/bottom halved 1.5rem -> .75rem; with the first/last paragraph
     margins removed (.about-body p rules) the rectangle-to-text space is
     half of what it was, which is Kevin's clarified reading of both asks
     (Claude-decided, 2026-09-25).
     KEVIN-REQUIRED 2026-09-25 (verbatim): "reduce the empty space on the top and bottom of the general about bio on top, to about 50% of what it currently is" */
  padding:.75rem 1.9rem; border-radius:18px;
  /* Side margins put the slab's left edge under the header's "|" (102px
     measured at 1500px; Claude-decided, 2026-09-25; TBD, 2026-09-25 if the
     wordmark ever changes). Bottom back to 2.75rem (44px): the round-3
     cut to 22px is REVERSED, Kevin did not ask for that gap to shrink
     (Claude-decided, 2026-09-25).
     KEVIN-REQUIRED 2026-09-25 (verbatim): "Let's reduce the width of the top description such that it is roughly left justified with the | in "board state | About us" in the header." */
  /* No bottom margin: the coffee button below carries the 2.75rem/2.5rem
     gaps (Claude-decided, 2026-09-25). */
  margin-left:102px; margin-right:102px; margin-bottom:0;
  /* FROSTED AND RAISED: the capture panel's gradient at .7 alpha (the
     same see-through the header's frost wears) over a blur, so the DVD
     card shows through, dimmed and softened, while the --raise shadow
     pair and the edge line keep the 3D lift (Claude-decided, 2026-09-25).
     KEVIN-REQUIRED 2026-09-25 (verbatim): "the paragraph bubble rectangle should be frosted such that you can see the card passing through it. Try to maintain the 3d effect though" */
  background:linear-gradient(148deg, rgba(27,35,40,.7), rgba(20,26,30,.7));
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  border:1px solid var(--slab-edge);
  box-shadow:var(--raise);
}
/* The slab runs full width when stacked. This block must sit AFTER the
   rule above to win; it used to live in the earlier phone block and lost,
   which left the 102px margins on phones (Claude-decided, 2026-09-25,
   found in the localhost preview). */
@media (max-width:720px){ .about-glass{ margin-left:0; margin-right:0; } }
/* The last paragraph keeps its bottom margin like the first keeps its
   top, so the text sits the same distance from the slab's top and bottom
   edges (KEVIN-REQUIRED 2026-09-25 (verbatim): "fix the paragraph description padding back to what it was on the bottom of the bubble rectangle. Currently it looks like an asymmetric distance between bottom of paragraph and bottom rectangle edge, compared to top of paragraph and top rectangle edge."). */
/* The DVD screensaver (about.js builds it; without JS it never exists).
   Fixed at z-index:0 -- NOT negative: a negative-z child paints (and so
   hit-tests) beneath the body's content layer, and the body swallows
   every click aimed at it. At 0 the card rides above the page ground and
   stays clickable; the column and footer lift to z-index:1 to keep the
   card visually behind them, and the glass slab's backdrop blur catches
   it exactly like the sticky bars catch scrolling content. */
.dvd-card{
  position:fixed; left:0; top:0; z-index:0;
  width:104px; display:block; cursor:pointer;
  will-change:transform;
}
.dvd-card img{
  display:block; width:100%;
  /* Riot's portrait frame, asserted up front so the box has its height
     before the art arrives and each swap keeps the same footprint. */
  aspect-ratio:744/1039; object-fit:cover;
  border-radius:6px;
  box-shadow:0 4px 18px rgba(3,6,8,.55);
  transition:box-shadow .2s ease;
}
.dvd-card:hover img{
  box-shadow:0 0 0 1px rgba(95,208,224,.5), 0 0 24px rgba(95,208,224,.4);
}
/* Empty page must not eat the card's clicks: hit-testing works on boxes,
   not painted pixels, so the full-width main column would swallow every
   click aimed behind it. The column goes transparent to the pointer and
   its children opt back in. */
.about-page{ position:relative; z-index:1; pointer-events:none; }
.about-page > *{ pointer-events:auto; }
/* THE BIOS ARE A SIEVE, NOT A WALL. The team grid and each column are
   full-width boxes; opted back in above they swallowed every click aimed
   at the card passing behind the bios, text or no text. So the grid,
   the columns and the button row go transparent to the pointer again,
   and only the pieces that show something opt in -- and those pieces
   are shrink-wrapped to their text (width:max-content below) so their
   boxes are no wider than what you see (Claude-decided, 2026-09-25).
   KEVIN-REQUIRED 2026-09-25 (verbatim): "The floating around card is not clickable when passing by the bios and near the vertical center of the screen. It should basically always be hoverable and clickable." */

.about-page > .about-team, .about-team .member,
.about-page > .kofi-center{ pointer-events:none; }
.about-team .member > *, .kofi-center .kofi-btn{ pointer-events:auto; }
/* The footer seats at the WINDOW's bottom, not the content's: the page
   becomes a flex column the height of the viewport and the main column
   takes the slack, so the button-to-footer gap stretches with the window.
   .kofi-center's margin is only the minimum.*/
body:has(.about-page){
  display:flex; flex-direction:column; min-height:100vh;
}
/* Flex items with auto side margins stop stretching and shrink-wrap --
   the header, column and footer would all narrow to their content. Full
   width restores them; their own max-width + auto margins re-centre.
   Named, not > *: the DVD card is also a body child and must keep its
   own width. */
body:has(.about-page) > .site-head,
body:has(.about-page) > main,
body:has(.about-page) > .site-foot{ width:100%; }
/* ~30% wider on the About page: 1040px -> 1350px. Header and footer widen
   with the content so the footer rule is never narrower than the text
   (Claude-decided, 2026-09-25; TBD, 2026-09-25 if Kevin wants only the
   content wider).
   KEVIN-REQUIRED 2026-09-25 (verbatim): "Increase the overall width of the usable space by about 30%." */
body:has(.about-page) > .site-head,
body:has(.about-page) > main,
body:has(.about-page) > .site-foot{ max-width:1350px; }
/* The footer's text (20px inside its box) and top rule start at the
   header's "|", like the slab: 102px in from each side (Claude-decided,
   2026-09-25). Restored to full width under 720px, in its own media
   block here because it must come after the rule above to win.
   KEVIN-REQUIRED 2026-09-25 (verbatim): "The footer should also line up with the | in "board state | About us"" */
body:has(.about-page) > .site-foot{ max-width:calc(1350px - 204px); }
/* The header takes the same inset: the wordmark's "b" now starts where
   the "|" used to, and the nav's coffee cup ends on the slab's right
   edge, so everything below is aligned to the wordmark and the header's
   right edge instead of the divider. The glass and rule still run to the
   window's edges via .site-head::before (Claude-decided, 2026-09-25).
   KEVIN-REQUIRED 2026-09-25 (verbatim): "move "board state | About us" to instead be such that "board state" starts where the | originally was. This means all things below would be aligned with the left of "b" in "board state", instead of being aligned with the |."
   KEVIN-REQUIRED 2026-09-25 (verbatim): "lets move the things on the right of the header to be right justified with the bubble rectangles below. This meant the coffee button would have some padding and then be right justified aligning with the paragraph description rectangle." */
body:has(.about-page) > .site-head{ max-width:calc(1350px - 204px); }
@media (max-width:720px){
  body:has(.about-page) > .site-foot,
  body:has(.about-page) > .site-head{ max-width:100%; }
}
.about-page{ flex:1 0 auto; }
/* THE SAME FOOTER AS EVERY OTHER PAGE (Kevin, 2026-09-11): no glass and
   no trimmed padding here -- the About page's footer must read exactly
   like the one on All cards and Live now, and it used to wear the stuck
   header's frost with a shorter bottom. The bouncer's floor is the
   window's bottom again (KEVIN-REQUIRED 2026-09-25, see about.js), so the
   card now passes BEHIND the footer's text -- still no glass here
   (Kevin-decided, 2026-09-25: footer identical to every other page).
   Positioned and lifted with the column, so the text stays on top. */
.about-page + .site-foot{
  position:relative; z-index:1;
}
/* The corner hit -- the event everyone cheers for. Both axes flipped in
   one frame: the card blooms cyan for a beat, then settles. */
.dvd-card.corner img{ animation:dvd-cheer 1.2s ease; }
@keyframes dvd-cheer{
  0%{ box-shadow:0 0 0 2px rgba(95,208,224,.9), 0 0 42px rgba(95,208,224,.85); }
  100%{ box-shadow:0 4px 18px rgba(3,6,8,.55); }
}

/* The donations button, centred under the text: the buy buttons' clothes
   (solid teal gradient, bold dark type, the raise, the -1px lift) around
   a generous Ko-fi cup -- its centre line on the label's own, courtesy of
   the flex centring. Hover deepens the gradient and blooms a soft cyan
   glow, and BOTH directions ease over the same .35s -- a gradient cannot
   transition, so the deeper one lives on the ::after and crossfades in
   over the resting ::before (z-index:0 on the button walls the negative-z
   pseudos into its own stacking context). */
/* The air below the button: the team bios now follow it, so 2.5rem
   rather than the old 6rem (Claude-decided, 2026-09-25). */
.kofi-center{ display:flex; justify-content:center;
              margin:2.75rem 0 2.5rem; }
.kofi-btn{
  position:relative; z-index:0;
  display:inline-flex; align-items:center; gap:12px;
  padding:10px 20px; border-radius:12px; border:none;
  /* The buy buttons' own dark ink (a white label was tried 2026-09-06
     and read worse on the teal); the cup strokes in currentColor follow. */
  color:#03262a;
  font-family:'Inter', system-ui, sans-serif;
  font-size:14px; font-weight:700; line-height:1;
  text-decoration:none; cursor:pointer;
  box-shadow:-3px -3px 7px rgba(140,200,215,.06), 3px 3px 8px rgba(3,6,8,.6);
  transition:box-shadow .35s ease, transform .35s ease;
}
.kofi-btn::before, .kofi-btn::after{
  content:""; position:absolute; inset:0; z-index:-1; border-radius:12px;
}
.kofi-btn::before{ background:linear-gradient(148deg, #2db4ba, #17878c); }
.kofi-btn::after{
  background:linear-gradient(148deg, #1f9ba1, #0e6266);
  opacity:0; transition:opacity .35s ease;
}
.kofi-btn:hover{
  transform:translateY(-1px); text-decoration:none;
  box-shadow:-3px -3px 7px rgba(140,200,215,.06), 5px 6px 12px rgba(3,6,8,.6),
             0 0 28px rgba(95,208,224,.35), 0 0 0 1px rgba(95,208,224,.22);
}
.kofi-btn:hover::after{ opacity:1; }
.kofi-btn .kofi-cup{ width:34px; height:34px; }

main{ max-width:1040px; margin:0 auto; padding:1.75rem 1.25rem 0; }

/* --------------------------------------------------------------- preview */
.preview{ display:flex; gap:1.75rem; align-items:flex-start; flex-wrap:wrap; }
.art{ margin:0; flex:0 0 auto; width:min(100%, 320px); }
.art.landscape{ width:min(100%, 440px); }

/* DIRECT CHILD ONLY: the figure also holds the buy row, and `.art img`
   used to dress the TCGplayer fan logo in the card scan's border, radius
   and shadow -- a button inside the button. */
.art > img{
  display:block; width:100%; height:auto;
  border-radius:12px; border:1px solid var(--line);
  box-shadow:0 10px 30px rgba(0,0,0,.5);
  transition:transform .35s cubic-bezier(.2,.7,.3,1), box-shadow .35s ease;
}
/* Hover: the card lifts a little and picks up a soft cyan bloom, the same
   accent the extension's lightbox rings its art with. Small on purpose --
   1.03 and one degree read as the card being picked up, and anything more
   reads as a widget. The glow is two low-alpha shadows, not a neon ring.
     The ring is the art's OWN border going DASHED and cyan (Kevin,
   2026-09-11), in the extension's idle hover-zone colour
   (rgba(95,208,224,.68), overlay.css): hovering a card here should look
   like hovering a card on stream. It restyles the border already there
   rather than adding an outline outside it -- an outline sits off the
   card's edge, and that gap read as a second, looser box. 2px, not the
   idle 1px, because a dashed border's dash and gap lengths are drawn off
   its WIDTH and 1px drew them too fine; box-sizing:border-box (see the
   reset) keeps the art the same size, so the extra pixel cannot reflow. */
.art > img:hover{
  transform:scale(1.03) rotate(-1deg);
  border-style:dashed; border-width:2px; border-color:rgba(95,208,224,.68);
  box-shadow:0 0 38px rgba(95,208,224,.20),
             0 18px 46px rgba(0,0,0,.6);
}
/* Only the picture is clickable, and only once card.js has said so. */
body.js .art img[data-zoom]{ cursor:zoom-in; }
@media (prefers-reduced-motion: reduce){
  /* The glow stays -- it says "this does something" and costs no motion.
     The movement does not. */
  .art > img{ transition:box-shadow .2s ease; }
  .art > img:hover{ transform:none; }
}
.noart{
  display:flex; align-items:center; justify-content:center;
  aspect-ratio:744/1039; border:1px dashed var(--line); border-radius:12px;
  background:var(--bg-deep); box-shadow:var(--carve);
  color:var(--muted); font-size:.85rem; text-align:center; padding:1rem;
}
.info{ flex:1 1 320px; min-width:0; }
.info h1{ margin:0; font-size:clamp(1.5rem,4.2vw,2.1rem); line-height:1.2; }
.setline{ margin:.35rem 0 1.2rem; color:var(--muted); font-size:.9rem; }

.facts{ margin:0 0 1.25rem; display:grid; gap:.45rem .9rem; }
.fact{ display:flex; gap:.75rem; align-items:baseline; flex-wrap:wrap; }
.fact dt{
  flex:0 0 5.5rem; color:var(--muted); font-size:.75rem;
  text-transform:uppercase; letter-spacing:.08em;
}
.fact dd{ margin:0; flex:1 1 auto; min-width:0; }
/* One value sets both the chip's side padding and the space between the rune
   and its name, so the rune sits in an even gutter: edge -> rune -> name are
   all --chip-gap apart.  Change it once and the chip stays symmetrical. */
/* Squircle, not capsule (Kevin, 2026-09-05), with more air inside and
   between -- these are click targets now, not just labels. */
.chip{
  --chip-gap:.65em;
  display:inline-flex; align-items:center; gap:var(--chip-gap);
  margin:0 .4rem .4rem 0; padding:.34em .8em;
  background:var(--panel-2); border:1px solid var(--line); border-radius:9px;
  font-size:.87rem; line-height:1.35; color:var(--text);
  vertical-align:.05em;
}
/* The domain's own rune, in the chip that names it.  align-items on the chip
   centres it against the cap height -- no vertical-align nudge to re-tune
   whenever the font or the chip's padding moves. */
.chip-rune{
  width:1em; height:1em; flex:0 0 auto; display:block;
}
/* The rarity gem, inline SVG filled with its printed colour -- sized and
   seated exactly like the rune. */
.chip-gem{ width:1em; height:1em; flex:0 0 auto; display:block; }
/* A chip that is a LINK is a live search: it lifts like the small capture
   buttons do, keeps its own colours, never underlines. */
a.chip{
  color:var(--text); cursor:pointer;
  transition:transform .18s ease, box-shadow .18s ease;
}
a.chip:hover{
  text-decoration:none; transform:translateY(-1px);
  box-shadow:3px 3px 8px var(--dark);
}
/* Printing ids read as ids. */
.cid-chip{
  font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size:.76rem;
}
.stat{ font-weight:700; color:var(--cyan); }

/* The printed card text, unboxed (Kevin, 2026-09-05): it is verbatim from
   the art beside it, so it speaks quietly -- the lede's grey, no slab.
   Lines WITHIN a passage sit tight (1.45); the breaks BETWEEN passages
   open up. The library writes most cards' passage break as a <br /> inside
   one <p>, so the br itself is styled into a paragraph gap -- the same
   .6rem the true <p> boundaries get. */
.rules{
  color:var(--muted); font-size:.9rem; line-height:1.45;
  margin:0 0 1rem;
}
.rules p{ margin:0 0 .6rem; }
.rules p:last-child{ margin-bottom:0; }
.rules ul{ margin:.2rem 0 .6rem; padding-left:1.2rem; }
.flavour{
  margin:0 0 1rem; color:var(--muted); font-style:italic; max-width:52ch;
}
.printings{ margin:0; font-size:.85rem; color:var(--muted); }
.printing{
  display:inline-block; margin:0 .35rem .35rem 0; padding:.05rem .5rem;
  border:1px solid var(--line); border-radius:6px;
  font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size:.8rem;
}

/* -------------------------------------------------------------- lightbox */
/* The extension's lightbox, restated: a near-black scrim, the art ringed in
   a low-alpha cyan with a deep drop shadow, and a mono "click anywhere to
   close" hint. Bigger here than in the overlay -- a web page has the room
   the Twitch player did not, so the same 744x1039 file is shown at up to
   640px wide instead of 480.
     Inert without JS: opacity 0, visibility hidden, no pointer events. The
   visibility transition is what lets it fade OUT rather than vanish -- it
   flips at the end of the fade going away and at the start coming back. */
.lightbox{
  position:fixed; inset:0; z-index:50;
  background:rgba(5,6,8,.88);
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:14px; padding:24px; overflow:hidden;
  opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity .15s ease, visibility .15s ease;
  cursor:zoom-out;
}
.lightbox.open{ opacity:1; visibility:visible; pointer-events:auto; }
.lightbox img{
  display:block; width:auto; height:auto;
  max-width:min(90vw, 640px); max-height:82vh;
  border-radius:10px; border:1px solid var(--line); background:#000;
  box-shadow:0 0 0 1px rgba(95,208,224,.35), 0 24px 70px rgba(0,0,0,.65);
}
.lightbox-hint{
  font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size:13px; line-height:1.35; letter-spacing:.03em;
  color:var(--muted); text-align:center; pointer-events:none;
}

/* ------------------------------------------------------- keyword boxes */
.keywords{ margin-top:2.5rem; }
.keywords h2, .rulings h2{
  font-size:1.15rem; margin:0 0 1rem; color:var(--text);
  padding-bottom:.5rem; border-bottom:1px solid var(--line);
}
/* The capture / portal pages' slab, not the extension's cyan-ringed box
   (Kevin, 2026-09-05: no strong blue outline anywhere -- one panel skin
   across the whole site). Everything INSIDE -- pill colours, icons, text --
   is unchanged. */
.kw-box{
  background:var(--slab); border:1px solid var(--slab-edge);
  border-radius:18px; box-shadow:var(--raise-sm);
  /* 1rem between boxes -- the same air the rulings keep between theirs. */
  padding:1em 1.14em; margin:0 0 1rem;
}
/* The extension's label: the box leans, the letters do not. */
.kw-name{
  display:inline-block; transform:skewX(-12deg);
  border-radius:.23em; padding:.23em .77em; margin:0 .15em;
  font-weight:700; font-size:.93em; line-height:1.25;
  text-transform:uppercase; letter-spacing:.05em;
}
.kw-name > span{ display:inline-block; transform:skewX(12deg); }
.kw-summary{ margin:.55em 0 0; font-size:.95rem; line-height:1.45; }

.kw-box.small{
  display:flex; flex-direction:row; align-items:center; gap:.71em;
  padding:.71em 1em;
}
.kw-box.small .kw-summary{ margin:0; flex:1 1 auto; min-width:0; }
.kw-small-name{ font-weight:700; color:var(--cyan); }
/* space-colon-space, the site's own rubric pattern -- and the colon wears
   the name's blue (no colour override, it inherits). No em dashes. */
.kw-small-name::after{ content:"\00a0:\00a0"; }
.kw-glyph{ width:1.43em; height:1.43em; flex:0 0 auto; display:block; }

/* A quantity inside a summary: the card's own number, in the accent. */
.kw-num{ color:var(--teal); font-weight:700; }
/* An inline glyph inside a sentence, sized off the text it sits in. */
.kw-icon{
  display:inline-block; width:1.15em; height:1.15em;
  vertical-align:middle; position:relative; top:-.12em;
}
.rb-token{
  font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size:.85em; color:var(--muted);
}

/* -------------------------------------------------------------- rulings */
.rulings{ margin-top:2.5rem; }
.rulings-intro{ color:var(--muted); font-size:.85rem; margin:0 0 1.2rem; }
.empty{ color:var(--muted); }
.ruling{
  background:var(--slab); border:1px solid var(--slab-edge);
  border-radius:18px; box-shadow:var(--raise-sm);
  padding:1rem 1.15rem; margin:0 0 1rem;
}
/* An errata slab leans lightly red where the others lean blue: the slab
   gradient's own lightness, hue swapped toward the ERRATA title's red. */
.ruling-errata{
  background:linear-gradient(148deg, #281b1e, #1e1416);
  border-color:rgba(225,175,185,.05);
}
/* The ruling's head line: kind in the section-title voice (no boxes), then
   set and date as quiet grey, dot-separated. */
.ruling-head{
  display:flex; gap:.5rem; align-items:baseline; flex-wrap:wrap;
  margin-bottom:.7rem;
}
.rk{
  font-family:'Roboto Slab', Georgia, serif; font-weight:700;
  font-size:.8rem; letter-spacing:.22em; text-transform:uppercase;
}
.rk-errata{ color:#e0565c; }
.rk-faq{ color:var(--cyan); }
.rk-other{ color:var(--muted); }
.rmeta{ color:var(--muted); font-size:.8rem; font-weight:400; }
.rmeta-dot{ color:var(--muted); }
.ruling-tags{ display:flex; gap:.4rem; flex-wrap:wrap; margin-bottom:.7rem; }
.tag{
  font-size:.68rem; letter-spacing:.08em; text-transform:uppercase;
  padding:.15rem .5rem; border-radius:4px;
  background:var(--panel-2); border:1px solid var(--line); color:var(--muted);
}
.tag-errata{ background:#3a2733; border-color:#5d3a4b; color:#e6a9c0; }
.tag-faq{ background:#1b2f33; border-color:#2b4c52; color:var(--cyan); }
.tag-superseded{ background:#2a2520; border-color:#4a4034; color:#c9a86b; }

/* The FAQ's question: the SLAB VOICE (Kevin's pick from six options,
   2026-09-05) -- Roboto Slab, the titles' own face, regular weight at
   reading-headline size. Unboxed, under its QUESTION rubric. */
.faq-q{
  margin:.2rem 0 .8rem; color:var(--text);
  font-family:'Roboto Slab', Georgia, serif; font-weight:400;
  font-size:1.3rem; line-height:1.35;
}
.quote{ margin:0 0 .9rem; }
.quote:last-of-type{ margin-bottom:0; }
.quote-label{
  display:block; font-size:.7rem; letter-spacing:.09em; text-transform:uppercase;
  color:var(--muted); margin-bottom:.3rem;
}
/* A well carved into the slab: Riot's words sit IN the material. */
.quote blockquote{
  margin:0; padding:.6rem .95rem;
  background:var(--bg-deep); box-shadow:var(--carve);
  border-left:3px solid var(--teal);
  border-radius:0 8px 8px 0;
  font-size:.93rem; line-height:1.55;
  /* Riot's wording is quoted verbatim; a long unbroken string scrolls
     inside the quote rather than widening the page. */
  overflow-x:auto;
}
/* The superseded text's bar takes the ERRATA red; the current text's bar
   answers in the buy toggle's green. */
.quote-was blockquote{ border-left-color:#e0565c; }
.quote-now blockquote{ border-left-color:var(--green); }
.ruling-note{ margin:.7rem 0 0; color:var(--muted); font-size:.85rem; }
.ruling-source{ margin:.8rem 0 0; font-size:.8rem; color:var(--muted); }
/* Source links in the footer's voice: dark teal, midpoint blue and bold on
   hover, never underlined. */
.ruling-source a{ color:var(--teal); }
.ruling-source a:hover{ text-decoration:none; color:#3fb7c2; font-weight:700; }
/* On an errata slab the source link answers the ERRATA red instead of the
   site teal -- same hover move, brightened along red instead of blue. */
.ruling-errata .ruling-source a{ color:#e0565c; }
.ruling-errata .ruling-source a:hover{ color:#ee7d82; }

/* Superseded: kept, per the contract, but visibly stood down. */
.ruling.superseded{ opacity:.55; border-style:dashed; }
.ruling.superseded:hover{ opacity:1; }

/* ------------------------------------------------- page title + standfirst */
/* Not scoped to one page. Every listing page opens the same distance down
   the screen, in the same voice: title, then a muted standfirst. Scoping
   these to .index-page is exactly how the live directory ended up with the
   browser's default h1 margin and body-coloured prose. The two h1s that are
   NOT page titles -- a card's name, a streamer's name -- carry their own
   class and win on specificity. */
main h1{ margin:0 0 .3rem; font-size:clamp(1.6rem,5vw,2.2rem); }
.lede{ margin:0 0 2rem; color:var(--muted); max-width:60ch; }

/* ---------------------------------------------------------------- index */
/* The library's heading line: title left, the buy tools stacked right --
   the streamer page's grammar. */
.page-head{
  display:flex; justify-content:space-between; align-items:flex-start;
  gap:1rem; flex-wrap:wrap; margin:0 0 .3rem;
}
.page-head h1{ margin:0; }
.set h2{
  font-size:1.1rem; margin:2rem 0 .8rem; padding-bottom:.4rem;
  border-bottom:1px solid var(--line);
}
/* The first set heading rides right up under the buy bar -- its usual 2rem
   of air is for separating set from set, not for the top of the list. */
/* ONE constant gap under the bar in BOTH modes -- .9rem, the streamer
   page's own -- so toggling buy mode never moves the list below. The set
   heading's usual 2rem stays zeroed here; the bar's margin IS the gap. */

.buy-bar + .set h2{ margin-top:0; }
/* Same air over the buy bar as the streamer page's heading gives it. */
.index-page .finder{ margin-bottom:.5rem; }
.set h2 .count{ color:var(--muted); font-size:.8rem; font-weight:400; }
.cardlist{
  list-style:none; margin:0; padding:0;
  display:grid; gap:.3rem;
  grid-template-columns:repeat(auto-fill, minmax(15rem, 1fr));
}
.cardlist a{
  display:flex; justify-content:space-between; gap:.75rem; align-items:baseline;
  padding:.35rem .6rem; border-radius:6px; color:var(--text);
  border:1px solid transparent;
}
.cardlist a:hover{ background:var(--panel); border-color:var(--line); text-decoration:none; }
.cardlist .cname{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cardlist .cid{ color:var(--muted); font-size:.72rem; flex:0 0 auto; }

/* ------------------------------------------------------- search + filters */
/* The finder is inert markup until index.js removes `hidden` -- the
   attribute selector outranks the display rule below, so a search box that
   cannot search never appears.  Without JavaScript the page is the plain
   full list it was before. */
.finder[hidden]{ display:none; }
.finder{
  display:block; position:sticky; top:var(--headbar-h); z-index:20;
  /* Restored to the full 1rem/.9rem (Kevin, 2026-09-05: the .4rem trim
     shortened the bar and closed the air under the menu -- bring it back). */
  /* Full-bleed glass (Kevin, 2026-09-06): the negative side margins are
     exactly main's side padding, so the frost and rule run edge to edge
     with the same footprint as the site header, while the matching side
     padding keeps the controls on the content column. */
  margin:0 -1.25rem 1.5rem; padding:1rem 1.25rem .9rem;
  /* Transparent, like the site header's: the visible rule is drawn by
     the full-bleed ::before into this reserved 1px, so the bar's height
     never changed. */
  border-bottom:1px solid transparent;
}
/* The frosted concept: translucent ground + blur, so the list sliding
   beneath reads as under glass. The glass lives on a ::before layer, NOT
   on .finder itself: an element with backdrop-filter becomes the backdrop
   root for everything inside it, so a blur on .finder would leave the
   filter dropdown below -- a child, wearing its own glass -- nothing but
   the finder's paint to blur, and the list would slide beneath it sharp.
   The side margins above already carried the glass past main's gutter;
   the left/right here (Kevin, 2026-09-11) carry it the rest of the way,
   to the window's edges, on a window wider than the column. */
.finder::before{
  content:""; position:absolute; z-index:-1;
  top:0; bottom:-1px; left:calc(50% - 50vw); right:calc(50% - 50vw);
  background:var(--frost);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  border-bottom:1px solid var(--line);
}
.finder-top{ display:flex; gap:.75rem; align-items:center; flex-wrap:wrap; }
/* Slim enough that count, images button and Filter cards share its line. */
.search-wrap{ flex:1 1 11rem; max-width:20rem; min-width:0; display:block; }
.visually-hidden{
  position:absolute; width:1px; height:1px; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
}
/* Carved into the material, the capture page's input grammar. */
.search{
  width:100%; padding:.6rem .85rem;
  background:var(--bg-deep); color:var(--text);
  border:1px solid var(--line); border-radius:10px;
  box-shadow:var(--carve);
  font-family:inherit; font-size:.95rem;
}
.search:focus{
  outline:none; border-color:var(--teal);
  box-shadow:0 0 0 3px rgba(95,208,224,.14);
}
.search::placeholder{ color:var(--muted); }
.result-count{ margin:0; color:var(--muted); font-size:.85rem; flex:0 0 auto; }
.result-count #shown{ color:var(--text); font-weight:700; }

.clear-all{
  flex:0 0 auto; padding:.5rem .9rem;
  background:var(--panel); color:var(--muted);
  border:1px solid var(--line); border-radius:8px;
  font-family:inherit; font-size:.85rem; cursor:pointer;
}
.clear-all:hover{ color:var(--text); }
/* Lit only while there is something to clear, so the button says whether
   any filter is on without the reader hunting the rows for a highlight. */
.clear-all.active{
  color:var(--cyan); border-color:rgba(95,208,224,.5);
  background:rgba(95,208,224,.10);
}

/* The filter rows, folded away until asked for.  Six rows of buttons and
   two dropdowns are taller than the list they filter, and the common case
   is typing a name -- so the page opens with the search box alone. The
   toggle is a plain button flex item; the panel div wraps below via
   flex-basis:100% + order. */
.more-toggle{
  display:inline-flex; align-items:center; gap:.5rem;
  width:fit-content; padding:.4rem .8rem;
  background:var(--panel); color:var(--muted);
  border:1px solid var(--line); border-radius:8px;
  font-family:inherit; font-size:.85rem; cursor:pointer;
}
.more-toggle:hover{ color:var(--text); }
.more-toggle:focus-visible{
  outline:none; border-color:var(--teal);
  box-shadow:0 0 0 3px rgba(95,208,224,.14);
}
.more-toggle[aria-expanded="true"]{ color:var(--text); }
.chev{
  display:inline-block; font-size:1.1em; line-height:1;
  transition:transform .15s ease;
}
.more-toggle[aria-expanded="true"] .chev{ transform:rotate(90deg); }
/* With the panel folded, this badge is the only thing on screen saying a
   filter is still cutting the list down. */
.active-count{
  padding:.05rem .45rem; border-radius:999px;
  background:var(--cyan); color:#06222a;
  font-size:.72rem; font-weight:700;
}
.active-count[hidden]{ display:none; }

/* justify-items stays default (stretch): shrink-to-fit rows were what made
   the TAG dropdown wrap under its label while ARTIST sat inline. Only the
   clear button start-aligns itself. */
/* The open panel is a DROPDOWN, not a row: absolutely positioned off the
   sticky finder, so it drops OVER the buy bar and the list instead of
   shoving them down. (The shove was the abrupt open at the top of the
   page; scrolled down, the stuck finder already drew over the list, and
   now the two states match.) The SAME dropdown in buy mode -- its look is
   independent of the mode; placeBuyBar() adds the open panel's height to
   its measurement, since the panel left the flow, so the boxed bar still
   seats below the glass once stuck. Its ground is the finder's frost at
   --facets-a: index.js holds it a touch more opaque while the page sits
   at the very top -- nothing has scrolled under the blur yet, so the .7
   glass read thin over the buy bar -- and eases it back down to the
   finder's own .7 across the first few hundred pixels of scroll. */
.facets{
  position:absolute; top:calc(100% + 1px); left:0; right:0;
  /* FULL-BLEED GLASS (Kevin, 2026-09-11), and NOT by a ::before: this
     panel scrolls itself (overflow-y below), which would clip an
     overflowing pseudo. So the BOX runs out to the window's edges and
     the side padding puts the same distance straight back, leaving
     every control exactly where it sat. Both percentages resolve
     against the finder's padding box -- the panel's own width -- so the
     two halves cancel at every window size. */
  margin-left:calc(50% - 50vw); margin-right:calc(50% - 50vw);
  display:grid; gap:.4rem;
  padding:.55rem calc(50vw - 50% + 1.25rem) .9rem;
  background:rgba(var(--frost-rgb), var(--facets-a, .88));
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  border-bottom:1px solid var(--line);
  /* The finder is stuck while the panel hangs from it, so the page's own
     scroll can never bring a tall panel's tail into view on a short
     viewport -- the panel scrolls itself instead. */
  max-height:calc(100vh - var(--headbar-h) - 6rem);
  max-height:calc(100dvh - var(--headbar-h) - 6rem);
  overflow-y:auto;
  /* The gentle open: a fade and a small drop, in both directions.
     display rides along as a discrete transition so `hidden` can keep
     meaning display:none; a browser without transition-behavior drops
     this whole declaration and falls back to the instant toggle. */
  opacity:1; transform:translateY(0);
  transition:opacity .2s ease, transform .2s ease, display .2s allow-discrete;
}
.facets[hidden]{ display:none; opacity:0; transform:translateY(-.4rem); }
@starting-style{
  .facets{ opacity:0; transform:translateY(-.4rem); }
}
@media (prefers-reduced-motion: reduce){
  .facets{ transition:none; }

  .buy-bar{ transition:none; }
}
/* The line's right edge. */
.line-end{ margin-left:auto; }
/* Clear sits beside the Filter fold-out, inert until a filter is on. */
.clear-all:disabled{ opacity:.35; cursor:default; pointer-events:none; }
/* NO special margin on Clear: the row's one flex gap spaces every control
   identically -- a leftover tuck from the <details> era made this pair
   tighter than its neighbours. */
.facet{ display:flex; gap:.6rem; align-items:baseline; flex-wrap:wrap; }
.facet-label{
  flex:0 0 4.2rem; color:var(--muted); font-size:.7rem;
  text-transform:uppercase; letter-spacing:.08em;
}
.opts{ display:flex; gap:.3rem; flex-wrap:wrap; flex:1 1 auto; min-width:0; }
.opt{
  display:inline-flex; align-items:center; gap:.35em;
  padding:.16rem .6rem;
  background:var(--panel-2); color:var(--text);
  border:1px solid var(--line); border-radius:999px;
  font-family:inherit; font-size:.78rem; line-height:1.5; cursor:pointer;
}
/* A facet button's icon: the domain's rune or the rarity's gem, same marks
   the card-page chips carry. */
.opt-icon{ width:1em; height:1em; flex:0 0 auto; display:block; }
/* Hover affordance is the lift + shadow from the mini-button block at the
   end of this file -- NEVER a light border (the muted border-color that
   used to sit here survived the cascade and painted the banned outline). */
/* Selected is unmistakable: filled in the accent, dark text, and a ring. */
.opt[aria-pressed="true"]{
  background:var(--cyan); border-color:var(--cyan); color:#06222a;
  font-weight:700; box-shadow:0 0 0 3px rgba(95,208,224,.16);
}
/* A domain button carries its own colour when idle, and still flips to the
   accent when picked, so "selected" looks the same everywhere. */
.opt.domain-fury{ color:#ed7f84; border-color:rgba(223,22,32,.45); }
.opt.domain-calm{ color:#9ac092; border-color:rgba(72,140,56,.45); }
.opt.domain-mind{ color:#7bb0ce; border-color:rgba(15,111,166,.45); }
.opt.domain-body{ color:#f2b473; border-color:rgba(232,118,0,.45); }
.opt.domain-chaos{ color:#ad96c4; border-color:rgba(106,64,148,.45); }
.opt.domain-order{ color:#e6d673; border-color:rgba(210,180,0,.45); }
.opt[aria-pressed="true"]{ color:#06222a; }

/* .facet-sel, NEVER bare .sel: "sel" is buy mode's selected-card class on
   <li>s, and a bare .sel rule here once dressed every selected card in
   this dropdown's box (the browse-mode outline bug, 2026-09-05). */
.facet-sel{
  flex:1 1 12rem; max-width:22rem; min-width:0;
  padding:.3rem .5rem;
  background:var(--panel-2); color:var(--text);
  border:1px solid var(--line); border-radius:6px;
  font-family:inherit; font-size:.8rem;
}
.facet-sel:focus{ outline:none; border-color:var(--teal); }
/* A dropdown with a choice made is lit the same way a pressed pill is. */
.facet-sel.active{
  border-color:var(--cyan); color:var(--cyan);
  box-shadow:0 0 0 3px rgba(95,208,224,.16);
}
.img-toggle{
  flex:0 0 auto; padding:.5rem .9rem;
  background:var(--panel); color:var(--muted);
  border:1px solid var(--line); border-radius:8px;
  font-family:inherit; font-size:.85rem; cursor:pointer;
}
.img-toggle:hover{ color:var(--text); }
/* Showing images is the resting state, so "Hide images" stays grey; it is
   the HIDDEN state's "Show images" that gets the promoted blue -- the
   button advertises the way back to the art. */
.img-toggle[aria-pressed="false"]{
  background:var(--cyan); border-color:var(--cyan); color:#06222a;
  font-weight:700;
}
.img-toggle[aria-pressed="false"]:hover{ color:#06222a; }

/* Picture mode.  The art is shown at a size you can recognise a card by --
   larger than a thumbnail, well short of the card page's 320px. A fixed
   portrait box with object-fit:contain keeps the grid even while letting the
   landscape battlefields sit in it undistorted. */
.cardlist.grid{
  grid-template-columns:repeat(auto-fill, minmax(160px, 1fr));
  gap:.9rem .8rem;
}
/* NO box around a card in the grid (Kevin, 2026-09-05: the tile box around
   the card's own frame read as a double box). The art IS the tile; the
   caption hangs under it. */
.cardlist.grid a{
  flex-direction:column; align-items:stretch; gap:.35rem;
  padding:0; background:none; border-color:transparent;
}
.cardlist.grid a:hover{ background:none; border-color:transparent; }
/* The browse-mode hover: the slight pop, with a soft lift shadow on the
   art and the thumb's OWN border going 2px dashed cyan (Kevin,
   2026-09-11) -- the same ring .art > img:hover wears, so a card answers to
   the pointer identically on both pages. Scoped away from buy mode, whose
   scale states own the transform. */

.cardlist.grid:not(.buy-mode) li:hover{ transform:scale(1.04); }
.cardlist.grid:not(.buy-mode) li:hover .card-thumb{
  border-style:dashed; border-width:2px; border-color:rgba(95,208,224,.68);
  box-shadow:0 8px 22px rgba(0,0,0,.55);
}
.card-thumb{
  display:block; width:100%; aspect-ratio:744/1039; object-fit:contain;
  background:var(--panel-2); border:1px solid var(--line); border-radius:6px;
}
/* The <img>s are built once and then simply not shown in list mode, so
   flipping the toggle back does not throw the downloads away. The WRAPPER
   hides too, not just the image: buy mode hangs its wash and giant check
   off .thumb-box's pseudo-elements, and an empty-but-rendered wrapper once
   floated 6rem checkmarks over the text rows. */
.cardlist:not(.grid) .card-thumb{ display:none; }
.cardlist:not(.grid) .thumb-box{ display:none; }
.cardlist.grid .cname{ white-space:normal; font-size:.8rem; line-height:1.3; }
.cardlist.grid .cid{ font-size:.68rem; }

.no-match{ margin:1rem 0 0; color:var(--muted); }
/* A set whose every card was filtered out takes its heading with it. */
.set[hidden]{ display:none; }
.cardlist li[hidden]{ display:none; }

/* ------------------------------------------- streamer context on a card */
/* Drawn only when the page was opened with ?ch= -- i.e. from the extension's
   "learn more" button. It says whose stream you came from and offers the two
   ways onward. */
.ctx[hidden]{ display:none; }
/* The whole banner is one link to the streamer's page -- panel chrome, but
   a pointer everywhere and a lift on hover so it reads as clickable. */
.ctx{
  display:flex; align-items:center;
  gap:.8rem; flex-wrap:wrap;
  /* Frosted and sticky: the banner floats at the top of the viewport as
     the card page scrolls beneath it, in the same glass the cards page's
     finder wears. */
  position:sticky; top:calc(var(--headbar-h) + .6rem); z-index:30;
  margin:0 0 1.5rem; padding:.7rem .9rem;
  /* The lighter frost: this bar must stand off the ground BEFORE any
     scrolling puts content behind it. */
  background:var(--frost-lite);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  border:1px solid var(--slab-edge);
  border-radius:18px; box-shadow:var(--raise-sm);
  color:var(--muted); cursor:pointer;
  transition:box-shadow .18s ease, transform .18s ease;
}

/* The TCGplayer button's own hover: the -1px bounce, and a soft cyan bloom
   behind the slab instead of a hard outline. */
.ctx:hover{
  text-decoration:none; transform:translateY(-1px);
  box-shadow:var(--raise), 0 0 26px rgba(95,208,224,.16),
             0 0 0 1px rgba(95,208,224,.18);
}
.ctx-who{ display:flex; align-items:center; gap:.6rem; font-size:.9rem; min-width:0; }
.ctx-who .avatar{ width:32px; height:32px; }
.ctx-who b{ color:var(--text); }

/* The miniature stack: card-shaped slips inside the banner's height,
   rightmost fully visible, each earlier one peeking ~30% out to the left
   (later siblings paint on top, so DOM order left-to-right is depth
   order back-to-front). */
.mini-stack{ display:flex; align-items:center; flex:0 0 auto;
             padding-left:2px; }
.mini-card{
  width:22px; aspect-ratio:744/1039; height:auto;
  border-radius:3px; border:1px solid var(--line);
  object-fit:cover; display:block; background:var(--panel-2);
  box-shadow:-2px 0 4px rgba(0,0,0,.45);
}
/* Hovering the banner fans the stack out until every slip is fully
   visible, and folds it back on leave -- the overlap margin transitions
   instead of snapping, and the "..." after the stack rides right with it
   for free, being the next flex sibling. */
.mini-card + .mini-card{ margin-left:-15px;
                         transition:margin-left .35s ease; }
.ctx:hover .mini-card + .mini-card{ margin-left:3px; }
@media (prefers-reduced-motion: reduce){
  .mini-card + .mini-card{ transition:none; }
}
/* The blue backfill for a stream with fewer than five cards. */
.mini-card.ph{
  background:linear-gradient(160deg, #1c4653, #0d2833);
  border-color:rgba(95,208,224,.35);
}
.ctx-more{ color:var(--muted); letter-spacing:.12em; flex:0 0 auto; }

/* The site's own name inside a sentence: the SAME teal as the "board state"
   wordmark in the corner, at the wordmark's own 600. Not the brighter
   --cyan accent -- one name should be one colour wherever it appears, and
   the accent's job is links and highlights, not identity. */
.brand{ color:var(--teal); font-weight:600; }

.btn{
  display:inline-block; padding:.4rem .8rem;
  background:var(--panel-2); color:var(--cyan);
  border:1px solid var(--line); border-radius:8px;
  font-size:.83rem;
}
.btn:hover{ border-color:var(--cyan); text-decoration:none; }


/* ------------------------------------------------------- the buy buttons */
/* The extension's "learn more : boardstate.gg" grammar, pointed at the
   store: a mono bold rubric in the muted grey, then a panel button with the
   teal border, cyan type and the leaving-the-site arrow. Hover trades teal
   for cyan and lifts the panel, exactly as the lightbox link does.
   The anchors ship with NO href until the TCGplayer affiliation is wired
   (see buy_row in the generator) -- hover still answers, clicks do nothing. */
/* Two-column grid: label | button on the first line, and the affiliate note
   on a second line under the BUTTON only. width:0 + min-width:100% makes the
   note take the button's width instead of widening the column, so it wraps
   at the button's right edge. */
.buy-row{ display:grid; grid-template-columns:auto auto; justify-content:start; align-items:center; gap:10px; }
.buy-block{ display:flex; flex-direction:column; align-items:flex-start; }
.buy-note{ grid-column:2; width:0; min-width:100%; margin:-6px 0 0; font-size:.6rem; line-height:1.2; color:var(--muted); text-align:left; }
.art .buy-block{ margin:14px auto 0; width:fit-content; }
.buy-label{
  font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size:13px; font-weight:700; color:var(--muted); letter-spacing:.03em;
}
/* The capture page's Begin-Setup clothes: solid teal gradient, no border,
   bold dark Inter, the neumorphic raise, and the -1px lift on hover. */
.buy-btn{
  display:inline-flex; align-items:center; gap:8px;
  padding:9px 14px; border-radius:10px; border:none;
  background:linear-gradient(148deg, #2db4ba, #17878c);
  color:#03262a;
  font-family:'Inter', system-ui, sans-serif;
  font-size:13px; font-weight:700; line-height:1;
  text-decoration:none; cursor:pointer;
  box-shadow:-3px -3px 7px rgba(140,200,215,.06), 3px 3px 8px rgba(3,6,8,.6);
  transition:box-shadow .18s ease, transform .18s ease, opacity .3s ease;
}
.buy-btn:hover{
  transform:translateY(-1px); text-decoration:none;
  box-shadow:-4px -4px 9px rgba(140,200,215,.06), 5px 6px 12px rgba(3,6,8,.6);
}
/* TCGplayer's card fan, riding in front of their name. */
.buy-btn .tcg-fan{ height:1.3em; width:auto; display:block; flex:none; }
/* The price readout (buy-all only): walled off by a hairline in the text's
   own dark, wide enough for "$123.45" FROM THE START -- the button is long
   before any price arrives, so Kevin's feed filling it in never makes the
   button jump. Tabular numerals so a changing total does not wobble. */
.buy-btn .tcg-price{
  min-width:4.2em; text-align:right;
  margin-left:2px; padding-left:10px;
  border-left:1px solid rgba(3,38,42,.3);
  font-variant-numeric:tabular-nums;
}
/* The leaving-the-site signal, sized off the label like the extension's. */
.buy-btn .ext-icon{ width:1.15em; height:1.15em; stroke:currentColor;
                    fill:none; flex:none; }

/* --------------------------------------------------------------- buy mode */
/* Always visible, usable only in buy mode: the offer shows before the gate
   opens. */
.buy-btn.off{ opacity:.35; pointer-events:none; box-shadow:none; }

/* The bar under the heading rule, on every page that sells. Never absent
   (the TCGplayer button lives in it): bare in browse mode -- no chrome,
   just the button at the right edge -- and boxed in buy mode, when the
   left side (count / select all / clear selection) appears. */
.buy-bar{
  display:flex; align-items:center; gap:.6rem;
  margin:0 0 .9rem; padding:.55rem .9rem;
  background:var(--slab); border:1px solid var(--slab-edge);
  border-radius:14px; box-shadow:var(--raise-sm);
  /* index.js seats the boxed bar below the open filter dropdown with an
     inline translateY (placeBuyBar) -- a transform, so the cards under it
     never move; transitioning it is what makes the bar SLIDE down under
     the panel and back up, instead of jumping. */
  transition:transform .2s ease;
}
/* Bare keeps the boxed state's exact padding and border WIDTH (the border
   just goes transparent), so the TCGplayer button does not move a pixel
   when buy mode toggles -- it simply is not perfectly flush right in
   browse mode, which is the accepted cost (Kevin, 2026-09-05). */
.buy-bar.bare{ background:none; border-color:transparent; box-shadow:none; }
/* BOXED (buy mode): the bar becomes a frosted floater, in the streamer
   banner's exact glass, and sticks below the site header for the whole
   scroll -- the count and the buy button never leave the screen while
   picking cards. z-index UNDER the finder on purpose: opening the filter
   panel mid-buy draws over the bar instead of colliding with it. */
.buy-bar:not(.bare){
  position:sticky; top:calc(var(--headbar-h) + .6rem); z-index:19;
  background:var(--frost-lite);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
}
/* Each page stacks it below its own sticky middle layer: the library's
   finder (closed height ~3.7rem), the streamer page's condensed banner
   (~3.2rem once scrolling reveals it) -- plus the same .6rem of air the
   "See NAME's recent cards" banner keeps below the sticky menus. */
.index-page .buy-bar:not(.bare){ top:calc(var(--headbar-h) + 4.8rem); }
.streamer-page .buy-bar:not(.bare){ top:calc(var(--headbar-h) + 3.8rem); }
.buy-bar .buy-block{ margin-left:auto; }
.bar-left{ display:flex; align-items:center; gap:.6rem; flex-wrap:wrap; }
.bar-left[hidden]{ display:none; }
/* Blue text starts on the DARKER blue and hovers to the bright cyan --
   bold, never underlined. */
.linkish{
  background:none; border:none; padding:0;
  color:var(--teal); font-family:inherit; font-size:.85rem; cursor:pointer;
}
/* #3fb7c2 = the midpoint of the teal rest (#1f9ea3) and the full cyan
   (#5fd0e0), which hovered too bright. */
.linkish:hover{ text-decoration:none; color:#3fb7c2; font-weight:700; }
.bar-dot{ color:var(--muted); }
.bar-count{ color:var(--muted); font-size:.83rem; }


/* The gate itself: grey and off in browse mode, GREEN and glowing in buy
   mode -- buy mode's own colour, distinct from the site's cyan accents
   (the TCGplayer button deliberately keeps the blue). */

.buy-toggle{
  display:inline-flex; align-items:center; gap:.5rem;
  cursor:pointer; -webkit-user-select:none; user-select:none;
}
.buy-toggle input{ position:absolute; opacity:0; pointer-events:none; }
.toggle-track{
  position:relative; width:36px; height:20px; border-radius:999px;
  background:var(--panel-2); border:1px solid var(--line);
  transition:background .25s ease, border-color .25s ease, box-shadow .25s ease;
}
.toggle-knob{
  position:absolute; top:2px; left:2px; width:14px; height:14px;
  border-radius:50%; background:var(--muted);
  transition:left .25s ease, background .25s ease;
}
.buy-toggle input:checked + .toggle-track{
  background:rgba(63,202,107,.2); border-color:var(--green);
  box-shadow:0 0 16px rgba(63,202,107,.45);
}
.buy-toggle input:checked + .toggle-track .toggle-knob{
  left:18px; background:var(--green);
}
.buy-toggle input:focus-visible + .toggle-track{
  box-shadow:0 0 0 3px rgba(63,202,107,.2);
}
.toggle-label{ font-size:.85rem; color:var(--muted); }
.buy-toggle input:checked ~ .toggle-label{ color:var(--green); font-weight:600; }

/* The grid as a picker. The SIZES alone say which mode you are in: every
   tile shrinks a step in buy mode, and picking one grows it past normal. */
.thumb-box{ position:relative; display:block; }
/* On the grid's BASE state, not under .buy-mode: a transition scoped to the
   mode class dies the instant the class comes off, so entering eased and
   leaving snapped. Here it rides both directions. */
.cardlist.grid li{ transition:transform .4s ease, filter .4s ease, opacity .4s ease; }
.cardlist.buy-mode a{ cursor:pointer; }
/* Unselected: smaller and muted -- but never greyscale, which made cards
   hard to recognise. No cross (Kevin, 2026-09-04): a cross reads as "you
   cannot click this", and every card here is clickable. The dulling does
   the whole job, a notch deeper now that it works alone. */
.cardlist.buy-mode li:not(.sel){
  transform:scale(.94); filter:saturate(.62) brightness(.84); opacity:.85;
}
/* Selected: pops up a step, a touch MORE vibrant than normal under the
   green wash -- the two filters lean away from each other, so the contrast
   between picked and not is bigger than either shift alone -- with a very
   soft green glow and a large bold green check. */
.cardlist.buy-mode li.sel{
  transform:scale(1.05); filter:saturate(1.15); opacity:1;
}
.cardlist.buy-mode li.sel .thumb-box::before{
  content:""; position:absolute; inset:0; border-radius:6px;
  background:rgba(63,202,107,.26); pointer-events:none;
}
.cardlist.buy-mode li.sel .card-thumb{
  box-shadow:0 0 28px 6px rgba(63,202,107,.22), 0 0 0 1px rgba(63,202,107,.45);
}
.cardlist.buy-mode li.sel .thumb-box::after{
  content:"\2713"; position:absolute; inset:0;
  display:flex; align-items:center; justify-content:center;
  font-size:6rem; font-weight:900; color:var(--green);
  text-shadow:0 3px 16px rgba(0,0,0,.7); pointer-events:none;
}

/* Buy mode with images HIDDEN (grid mode is untouched by everything in
   this block): no size games at all -- text keeps browse mode's exact
   size. The whole story is colour: an unselected row's type drops to a
   deep grey; a picked row returns to the normal white, in the green-tinted
   highlight, with the small green check inline after the name. */
.cardlist.buy-mode:not(.grid) li,
.cardlist.buy-mode:not(.grid) li:not(.sel){
  transform:none; filter:none; opacity:1;
}
.cardlist.buy-mode:not(.grid) li:not(.sel) .cname{ color:#5b656d; }
.cardlist.buy-mode:not(.grid) li:not(.sel) .cid{ color:#49525a; }
.cardlist.buy-mode:not(.grid) li.sel .cname{ color:var(--text); }
.cardlist.buy-mode:not(.grid) li.sel a{
  background:rgba(63,202,107,.13);
  border:1px solid rgba(63,202,107,.4);
}
.cardlist.buy-mode:not(.grid) li.sel .cname::after{
  content:" \2713"; color:var(--green); font-weight:900;
}

/* The streamer grid's heading line: title left, buy-all flushed right, the
   ruled line under both so the pair reads as one header. */
.results-head{
  display:flex; align-items:center; justify-content:space-between; gap:1rem;
  flex-wrap:wrap; margin:0 0 .5rem; padding-bottom:.4rem;
  border-bottom:1px solid var(--line);
}
.results-head h2{ border-bottom:none; margin:0; padding-bottom:0; flex:1 1 auto; }

/* ------------------------------------------------------ streamer banner */
/* No top padding: the avatar's top edge lands where a page title would, so
   a streamer page starts at the same height as the cards index.
   THE BANNER IS ALSO THE BAR: it is sticky under the site header, and
   streamer.js drives its geometry (padding, glass, avatar and name sizes)
   with scroll -- the same five elements at every scroll position, only
   their sizes and seats in flux while p runs 0..1. The backdrop blur is
   constant; at rest nothing is behind the banner, so it shows only once
   the glass tint arrives. */
.banner{
  display:flex; align-items:center; gap:1.1rem;
  /* Full-bleed glass (Kevin, 2026-09-06), the finder's trick: side
     margins out by exactly main's gutter, side padding back in, so the
     tint and blur run edge to edge while the content keeps its column.
     streamer.js MEASURES these side paddings when seating the flying
     toggle and the relocated "Live now" -- change them here and the
     seats follow. */
  margin:0 -1.25rem; padding:0 1.25rem 1.4rem;
  position:sticky; top:var(--headbar-h); z-index:20;
  border-bottom:1px solid transparent;
}
/* The glass, now full-bleed (Kevin, 2026-09-11): the tint used to stop
   at main's gutter and end abruptly on a wide window. streamer.js no
   longer paints the banner's own background -- it hands this layer one
   number, --bar-a (0 at rest, 1 once the bar has condensed), and the
   stylesheet reads the tint AND the rule off it, so the morph is
   unchanged and the glass reaches the window's edges at every scroll
   position. */
.banner::before{
  content:""; position:absolute; z-index:-1;
  top:0; bottom:-1px; left:calc(50% - 50vw); right:calc(50% - 50vw);
  background:rgba(var(--frost-rgb), calc(.7 * var(--bar-a, 0)));
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  border-bottom:1px solid rgba(35,44,50, var(--bar-a, 0));
}
/* NO overflow:hidden here and NO transform ever (see the morph in
   streamer.js): the relocated "Live now" is absolutely positioned from
   inside this box against the banner, and either property would make this
   element its containing block / clip it. The ellipsis lives on the h1. */
/* Blocked view: the big tile carries the identity, so the banner goes. */
.streamer-page.is-blocked #banner,
.banner[hidden]{ display:none; }
.banner-text{ min-width:0; }
.banner-text h1{ white-space:nowrap; text-overflow:ellipsis; overflow:hidden; }
/* Twitch-style round avatar. The monogram is what shows before the picture
   arrives, when the streamer has none, or when the API is down -- never an
   empty circle. */
.avatar{
  width:76px; height:76px; flex:0 0 auto;
  border-radius:50%; overflow:hidden;
  background:var(--panel-2); border:2px solid var(--line);
  display:flex; align-items:center; justify-content:center;
  font-family:'Roboto Slab', Georgia, serif; font-size:1.6rem; color:var(--muted);
  background-size:cover; background-position:center;
  /* The hover glow eases in over .6s rather than snapping -- on the base
     class, so the directory tiles and the banner portrait breathe the same
     way. Filter rides along for the banner portrait's greyscale hover. */
  transition:box-shadow .6s ease, border-color .6s ease, filter .3s ease;
}
.avatar.live{ border-color:#e0393e; }
.banner-text{ min-width:0; }
.banner-text h1{ margin:0; font-size:clamp(1.35rem,4vw,1.9rem); line-height:1.2; }
/* The name and portrait read as the streamer until they become links to the
   stream; only then do they invite the click. */
.banner-link{ color:inherit; }
.banner-link[href]{ cursor:pointer; }
/* Hovering the streamer's name speaks Twitch purple -- the link leaves
   for Twitch, so it wears Twitch's colour. */
.banner-link[href]:hover{ color:#a970ff; text-decoration:none; }
/* Portrait hover, layered for contrast: the photo itself goes greyscale
   and dims (filter on .avatar); the blue glow moves OUT to the wrapper,
   where the filter cannot grey it; a very subtle purple wash sits on the
   wrapper's own pseudo (also out of the filter's reach); and the mark
   floats above everything in Twitch's light purple with a dark halo. */
.avatar{ position:relative; }
.banner-link[href]:hover .avatar{
  filter:grayscale(1) brightness(.7);
}
#avatar-link{
  position:relative; display:inline-block; border-radius:50%;
  transition:box-shadow .6s ease;
}
#avatar-link[href]:hover{
  box-shadow:0 0 0 2px rgba(95,208,224,.2), 0 0 52px 10px rgba(95,208,224,.24);
}
#avatar-link[href]:hover::after{
  content:""; position:absolute; inset:0; z-index:1; border-radius:50%;
  background:rgba(145,70,255,.14);
}
.tw-hover{
  position:absolute; inset:0; z-index:2;
  display:flex; align-items:center; justify-content:center;
  color:#a970ff; opacity:0; transition:opacity .25s ease;
  pointer-events:none;
  filter:drop-shadow(0 1px 3px rgba(0,0,0,.95)) drop-shadow(0 0 12px rgba(0,0,0,.8));
}
.banner-link[href]:hover .tw-hover{ opacity:1; }
/* Nudged down-right: the glitch bubble's weight sits up-left (its tail
   points down-left), so true geometric centring reads off-centre. */
.tw-hover .tw-mark{ height:30px; width:auto; transform:translate(2px,2px); }
/* After the name: Twitch's purple, ONE mark, standing on the baseline at
   the lowercase letters' own height. */
.tw-out{
  display:inline-flex; align-items:center;
  margin-left:.6rem; color:#9146ff; vertical-align:baseline;
}
.tw-out .tw-mark{ height:.55em; width:auto; flex:none; }
.banner-status{ margin:.35rem 0 0; font-size:.88rem; color:var(--muted); }

/* "Live now": a slow blink, not a flash. Two seconds, and it only ever
   dims to 35% -- an indicator that vanishes reads as a rendering fault. */
.live-dot{
  display:inline-block; width:.6em; height:.6em; margin-right:.45em;
  border-radius:50%; background:#e0393e; vertical-align:.02em;
  animation:live-pulse 2s ease-in-out infinite;
}
.live-now{ color:#f07b7f; font-weight:700; }
@keyframes live-pulse{ 0%,100%{ opacity:1; } 50%{ opacity:.35; } }
@media (prefers-reduced-motion: reduce){
  .live-dot{ animation:none; }
}
/* The grey twin: same dot, still, unlit. */
.live-dot.grey{ background:var(--muted); animation:none; }

.results{ margin-top:.5rem; }
/* The three shelves (legends, the rest, runes -- deliberately unnamed):
   a ruled break with generous air on both sides parts the neighbours, so
   they read as separate sections without headings. streamer.js shows a
   break only while both of its neighbours hold cards; an empty shelf
   collapses entirely so it leaves no stray gap. Native scroll anchoring
   keeps the reader's place when new rows push in above. */
.section-break{
  width:100%; border:none; border-top:1px solid var(--line);
  margin:1.8rem 0;
}
.section-break[hidden]{ display:none; }
.cardlist.grid:empty{ display:none; }
/* Direct child only: a heading inside .results-head hands its rule and
   spacing to the row (which draws ONE line under title and button both),
   and must not also draw its own. */
.results > h2, .live-section h2{
  font-size:1.1rem; margin:0 0 .5rem; padding-bottom:.4rem;
  border-bottom:1px solid var(--line);
}
.results-head h2{ font-size:1.1rem; }
.results-note{ margin:0 0 1rem; color:var(--muted); font-size:.83rem; }
.live-section .count, .results h2 .count{
  color:var(--muted); font-size:.8rem; font-weight:400;
}

/* ---------------------------------------------------- the live directory */
.live-section{ margin:0 0 2.5rem; }
/* The per-section "tune back in" notes read at the lede's size. */
.live-section .empty{ font-size:.9rem; }
/* "Streamed recently": the TILES are stood down, not the section -- its
   heading and note read at full strength like "Streaming now"'s. Hovering
   one tile brings that tile back, not its neighbours, easing in with the
   avatar glow rather than snapping. */
.live-section.recent .streamer-grid a{
  opacity:.38;
  /* transform included: this rule outranks the base tile rule, and without
     it the recent tiles' hover pop would snap while live tiles ease. */
  transition:opacity .6s ease, transform .4s ease;
}
.live-section.recent .streamer-grid a:hover{ opacity:1; }
.streamer-grid{
  list-style:none; margin:0; padding:0;
  display:grid; gap:.9rem .8rem;
  grid-template-columns:repeat(auto-fill, minmax(160px, 1fr));
}
/* A streamer tile is a card, in exactly the card grid's grammar: no box
   around it -- the card-shaped face IS the tile -- and the same hover pop.
   Avatar where the art goes, name where the card's name is printed. */
.streamer-grid a{
  display:flex; flex-direction:column; gap:.35rem;
  padding:0; border-radius:6px; color:var(--text);
  background:none; border:none;
  transition:transform .4s ease, opacity .6s ease;
}
.streamer-grid a:hover{ text-decoration:none; transform:scale(1.04); }
.s-tile{
  position:relative; width:100%; aspect-ratio:744/1039;
  border-radius:6px; border:1px solid var(--line);
  background:linear-gradient(160deg, var(--panel-2), #0c1014);
  display:flex; flex-direction:column; align-items:center;
  /* Pinned to the TOP, not centred (Kevin, 2026-09-06): portrait and
     name sit high on every tile whether the legends below arrived or
     not, so a row of tiles always lines up. */
  justify-content:flex-start; gap:.5rem;
  padding:.75rem .6rem .55rem; overflow:hidden;
}
.s-tile .avatar{ width:42%; height:auto; aspect-ratio:1; font-size:1.4rem; }
/* The name plate, where a card prints its title. Its own margins give it
   air on both sides (Kevin, 2026-09-06: the cards crowded the name). */
.s-tile .s-name{
  width:100%; text-align:center; font-family:'Roboto Slab', Georgia, serif;
  font-size:.8rem; line-height:1.25; color:var(--text);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  margin:.3rem 0;
}
/* "What legends will I see in this game?" -- the two legend CARDS seen
   most recently on the stream, side by side under the name, each named
   in grey beneath its art (Kevin, 2026-09-06: the art does the talking,
   so the caption stands down from the wordmark blue). Dropped a step
   below the name and parted by a wider gap, so the pair reads as its own
   band rather than crowding the plate. */
.s-legends{
  width:100%; flex:1; min-height:0;
  display:flex; justify-content:center; align-items:flex-start; gap:.7rem;
  padding-top:.3rem;
}
/* Sized to fill the band: two cards at 39% plus the gap leave only slim
   margins at the tile's edges, so the art reads at a glance (Kevin,
   2026-09-06: 34% sat too small in too much side space). */
.s-legend{
  width:39%; display:flex; flex-direction:column; align-items:center;
  gap:.25rem;
}
.s-legend-art{
  width:100%; aspect-ratio:744/1039; object-fit:cover;
  border-radius:4px; border:1px solid var(--line);
  /* A half-pixel soften on purpose (Kevin, 2026-09-06): full card art
     shrunk this far renders harsh, shimmering detail -- "unreadably
     crisp". The hover pop happened to soften it via rescaling and read
     better, so the resting state carries the same gentleness always. */
  filter:blur(.5px);
}
/* Hover re-rasterizes the scaled tile on the GPU, where a sub-pixel blur
   all but drops out and the art snapped back to crisp -- so the blur
   steps up just enough to survive the sharper raster. Tune this value
   and the resting one TOGETHER: hovering must not visibly change the
   softness. */
.streamer-grid a:hover .s-legend-art{ filter:blur(.65px); }
/* A long legend name may take a second line ("Renata Glasc") rather than
   truncating; two lines is the ceiling. */
.s-legend-name{
  max-width:100%; text-align:center; color:var(--muted);
  font-size:.66rem; line-height:1.25;
  overflow:hidden; display:-webkit-box;
  -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2;
}
.s-tile .s-live{
  position:absolute; top:.4rem; right:.45rem;
  display:inline-flex; align-items:center; gap:3px;
  font-size:.6rem; letter-spacing:.08em; text-transform:uppercase;
  color:#f07b7f; font-weight:700;
}
.s-tile .tw-glitch{ width:1.1em; height:1.1em; flex:none; }
/* Hover: the portrait glows in the signature teal/cyan and the tile's
   gradient deepens -- lighter at the top, darker at the foot, plus an inner
   shadow, so the card reads as gaining depth rather than changing colour. */
/* Soft and wide, not a ring of neon: low-alpha, big blur, a little spread,
   arriving on the .6s ease the base .avatar carries. */
.streamer-grid a:hover .avatar{
  border-color:var(--teal);
  box-shadow:0 0 0 2px rgba(95,208,224,.2), 0 0 52px 10px rgba(95,208,224,.24);
}
.streamer-grid a:hover .s-tile{
  background:linear-gradient(160deg, #1c242e, #05070a);
  box-shadow:inset 0 -22px 34px rgba(0,0,0,.45),
             0 8px 22px rgba(0,0,0,.55), 0 0 0 1px var(--kw-line);
}

@media (max-width:640px){
  .finder{ position:static; }
  .facet-label{ flex-basis:100%; }
  .avatar{ width:60px; height:60px; }
}

/* ------------------------------------------------------ the front page */
/* The coming-soon page's hero, kept -- the same slab headline and
   breathing wordmark -- now an overture: it fades in mid-screen, settles
   for a beat, glides to the top, and hands the page to three doors (the
   extension button, the live directory, the card library). */
.home-page{
  position:relative;
  display:flex; flex-direction:column; align-items:center;
  padding-bottom:2rem;
}
/* The game picker's corner seat: the exact control the site headers
   carry (same markup, same .game-pick clothes from above), parked at
   the column's top right where the header would have put it. Above the
   hero in the stacking order so its open menu drops over everything. */
.home-top{
  position:absolute; top:1.2rem; right:1.25rem; z-index:45;

  /* Two tenants now: the picker, then the Ko-fi cup at the far right --
     the same order the site headers keep. */
  display:flex; align-items:center; gap:1.25rem;
}
/* In the site header the menu hangs left-aligned off the picker; from
   this right-corner seat that would run off a narrow viewport, so here
   it hangs right-aligned instead. */
.home-top .game-menu{ left:auto; right:0; }

/* WHILE THE ENTRANCE RUNS the doors and the picker are invisible, and
   invisible must mean INERT: opacity keeps hit-testing, so without
   this a click during the choreography could land on a door nobody
   could see and quietly navigate away (found live, 2026-09-06).
   home.js stamps .entering only when the entrance actually plays
   (motion allowed), removes it when the hero's animation ends -- and
   swaps it for .entrance-done on any earlier press, which the motion
   block below reads as "cut to the settled page". */
html.entering .home-below, html.entering .home-top{ pointer-events:none; }
/* Seated high: the air above the top line equals the air between the
   wordmark and the button below it -- 2.4rem in total, .65 here plus
   main's own 1.75 of top padding. */
.hero{ position:relative; text-align:center; padding:.65rem 0 2.4rem; }
/* The soft glow is the HERO'S OWN backdrop, not the body's, so the light
   travels with the text during the entrance instead of staying parked
   where the text merely started. Very wide and very quiet on purpose --
   three stops so the falloff has no visible edge, over an area much
   larger than the text -- so the glow reads as ambient light, and its
   journey up the page (on a slight lag; see glow-follow below) reads as
   the light following the type rather than a spotlight snapping to it. */
.hero::before{
  content:""; position:absolute; z-index:-1;
  left:50%; top:50%; width:84rem; height:48rem; max-width:98vw;
  transform:translate(-50%,-50%);
  background:radial-gradient(closest-side,
             rgba(31,158,163,.12), rgba(31,158,163,.045) 45%,
             transparent 80%);
  pointer-events:none;
}
/* The coming-soon page's h1, verbatim: Light slab, two lines broken only
   at the <br>, the vw term tuned so the longer line clears a narrow
   phone. The 300 is deliberate and outranks the site-wide `main h1`
   below by specificity. */
.home-page h1{
  font-weight:300;
  font-size:clamp(1.1rem, 8vw, 3.625rem);
  white-space:nowrap; line-height:1.16; letter-spacing:.005em; margin:0;
}
/* Static teal is the baseline; the breathe animation only ADDS movement,
   so reduced-motion still gets the intended look. */
.home-page h1 .bs{
  color:var(--teal); font-weight:600;
  text-shadow:0 0 26px rgba(31,158,163,.28);
}
/* The top line's SETTLED size: .47em, measured so the whole line runs
   NARROWER than the wordmark under it -- it tucks inside "board state"
   roughly from the b's right side to the e's left (Kevin, 2026-09-06),
   an eyebrow over the wordmark rather than a rival. During the
   entrance it starts at full size (a 2.13 scale -- 2.13 x .47 = 1.0)
   and eases down while the hero rises, so the wordmark takes the lead
   as the page opens; "board state" itself never changes size. A
   transform, not an animated font-size: the line box never reflows, so
   the wordmark under it never moves. Inline-block because a transform
   needs a box; origin at the baseline centre so the shrink settles
   downward onto the wordmark rather than floating above it. */
/* Settled, it also stands down in colour -- the site's muted grey, not
   the headline white -- and sits .45em lower than its line box puts it,
   tucked close over the wordmark (the shrink left too much air there).
   Both are ALSO the entrance's destination: tc-settle animates from
   white/full-size/unshifted to exactly this, so a reduced-motion visitor
   and a landed entrance show the same hero. */
.home-page h1 .tc{
  display:inline-block; font-size:.47em; transform-origin:50% 100%;
  color:var(--muted); transform:translateY(.45em);
}

/* THE SKIP HINT (Kevin, 2026-09-11): for exactly as long as a press
   would skip the entrance, a quiet line under the hero says so, and it
   leaves with the choreography it describes. Inside the hero, so it
   rides up the page with the text and fades in behind the hero's own
   opacity. OUT OF FLOW, seated in the air the hero's bottom padding
   already holds: the settled page keeps the height it had, so nothing
   below moves. Hidden unless html.entering is stamped -- home.js stamps
   that only when the entrance genuinely plays -- so a visitor with
   JavaScript off, or with motion turned down, is never told to skip
   something that is not happening. aria-hidden for the same reason: it
   is an instruction about an animation, not page content. */
.skip-hint{
  /* ONE SEAT, AT THE BOTTOM OF THE WINDOW, AND IT NEVER LEAVES IT
     (Kevin, 2026-09-11). Every earlier version rode the hero's rise or
     drifted on its own, and all of that motion read as lag. So: fixed
     to the viewport, no transform, no transition, no movement of any
     kind -- the only thing that ever changes is its opacity.
     position:fixed is why this element sits OUTSIDE the hero in the
     markup: a fixed box inside a transformed ancestor is positioned
     against that ancestor, so the hero's entrance would have dragged it
     around again.
     The headline's typeface, spoken quietly: Roboto Slab at the h1's own
     Light weight, lowercase, in the site's muted grey. */
  position:fixed; left:0; right:0; bottom:1.5rem; margin:0; z-index:5;
  text-align:center;
  font-family:'Roboto Slab', Georgia, serif; font-weight:300;
  font-size:.85rem; letter-spacing:.02em; text-transform:lowercase;
  color:var(--muted);
  opacity:0; pointer-events:none;
}
/* OPACITY IS THE WHOLE ANIMATION. On the entrance's own 2.3s clock: up
   quickly, a hold while the choreography plays, then a long slow fade
   that reaches nothing exactly as the entrance ends -- which is also the
   moment a press stops being able to skip anything. A press before that
   drops html.entering, this animation goes with it, and the base
   opacity:0 above takes the hint straight away. */
html.entering .skip-hint{ animation:skip-fade 2.3s ease both; }
@keyframes skip-fade{
  0%  { opacity:0; }
  15% { opacity:.75; }
  45% { opacity:.75; }
  100%{ opacity:0; }
}

.home-below{
  width:100%; display:flex; flex-direction:column; align-items:center;
  gap:1.6rem;
}

/* The extension button: the CONFIGURATION page's begin-setup button in
   Twitch's clothes -- same slab type, same raise, same slow 1.1s glow
   swell on hover (and the same slow fade back out) -- purple because the
   click is a promise about Twitch. Sized to the hero's measure: about as
   wide as the headline above it. Ships with no href until EXTENSION_URL
   exists (the buy buttons' pattern): hover answers, clicks do nothing. */
.ext-btn{
  position:relative;
  display:inline-flex; align-items:center; justify-content:center; gap:11px;
  width:min(100%, 31rem);
  padding:14px 22px; border-radius:12px;
  /* The streamer tiles' own gradient grammar (site.css .s-tile:
     160deg, lit top-left falling to a dark foot), spoken in Twitch
     purple: the button previews the card faces it leads to. */
  background:linear-gradient(160deg, #a869ff, #6a2dd4);
  color:#fff;
  font-family:'Roboto Slab', Georgia, serif; font-weight:600;
  font-size:1.05rem; letter-spacing:.005em;
  cursor:pointer;
  box-shadow:var(--raise-sm), 0 0 30px rgba(145,70,255,.18);
  transition:box-shadow 1.1s ease, transform .18s ease;
  /* The button carries EXTRA air below it (the flex gap rides on top):
     the hero-to-button gap is the hero's own padding, and the button
     stands off the first door by at least that much again. */
  margin-bottom:1.3rem;
}
/* Hover deepens the gradient the way hovering a streamer tile does --
   both ends darker, the foot falling furthest. A gradient cannot
   transition, so the deeper one sits on the ::before at z-index -1
   (above the button's own background, below its text) and fades in. */
.ext-btn::before{
  content:""; position:absolute; inset:0; z-index:-1;
  border-radius:inherit;
  background:linear-gradient(160deg, #9550f6, #451495);
  opacity:0; transition:opacity .9s ease;
}
.ext-btn:hover::before{ opacity:1; }
.ext-btn:hover{
  transform:translateY(-2px); text-decoration:none; color:#fff;
  box-shadow:var(--raise), 0 0 46px rgba(145,70,255,.42),
             0 0 110px rgba(145,70,255,.20);
}
/* A press must not wade through the 1.1s glow transition. */
.ext-btn:active{
  transform:translateY(0);
  transition:box-shadow .15s ease, transform .18s ease;
}
/* The streamer pages' one mark -- Twitch's glitch bubble, eyes removed,
   out-arrow in its interior -- says both "Twitch" and "this leaves the
   site" in one glyph, here in the button's own white. */
.ext-btn .tw-mark{ height:1.15em; width:auto; flex:none; }

/* The loop under the button: the doors' width, the doors' corners, the
   same soft raise -- a panel that happens to move. height:auto keeps
   the 16:9 box the width/height attributes reserved. */
.home-loop{
  display:block; width:min(100%, 56rem); height:auto;
  border-radius:22px;
  box-shadow:var(--raise-sm), 0 0 30px rgba(0,0,0,.25);
  background:#000;
}

/* The two doors: wide slab panels, wider than the button above them.
   The whole panel is one link. Hover lifts it and blooms blue OUTSIDE
   the panel -- soft shadows only, no hard outline (not our language). */
.home-card{
  display:block; width:min(100%, 56rem);
  padding:1.25rem 1.9rem 1.35rem;
  background:var(--slab); border:1px solid var(--slab-edge);
  border-radius:22px; box-shadow:var(--raise-sm);
  color:var(--text); cursor:pointer;
  transition:transform .35s ease, box-shadow .6s ease;
}
.home-card:hover{
  text-decoration:none; transform:translateY(-6px);
  box-shadow:var(--raise), 0 0 46px rgba(95,208,224,.16),
             0 0 110px rgba(31,158,163,.12);
}
.home-card h2{ margin:0 0 .95rem; }

/* THE THEMED GREYSCALE: at rest a panel's content is monochrome IN THE
   SITE'S OWN BLUE -- grayscale first, then sepia spun round to the cyan
   family -- and hovering the panel fades the true colours in. One
   wrapper, one filter, so every child (streamer tiles, card art, keyword
   pills) desaturates and returns together. */
.home-desat{
  filter:grayscale(1) sepia(.5) hue-rotate(152deg) saturate(1.15)
         brightness(.9);
  opacity:.9;
  transition:filter .6s ease, opacity .6s ease;
}
.home-card:hover .home-desat{ filter:none; opacity:1; }


/* ---- door one: the hand -------------------------------------------- */
/* The top three "Streaming now" tiles held like a hand of cards: the
   centre card upright and on top, the outer two tucked in behind it at
   a slight lean, all pivoting from a shared point low in the hand.
   Hovering the panel spreads the fan a LITTLE -- the full spread is the
   directory's job, and this door only opens it. Like door two, only
   the tiles' upper parts show: the hand is cropped at the panel and
   fades toward the cut. */
.home-stack-row{
  display:flex; align-items:flex-start; justify-content:center;
}
.home-stack{
  /* Nudged LEFT of the panel's centre so the Live-now flag reads in the
     cleared space to the right rather than hanging off the edge. The
     generous SIDE padding is load-bearing: the outer cards' top corners
     swing sideways past their own boxes when they lean (about 50px at
     rest, nearer 70 in the hover spread), and overflow:hidden would
     shear them off without this room -- the whole hand, corners and
     all, must stay inside the box the crop clips to. */
  list-style:none; margin:0 0 0 -3rem; padding:1rem 4.5rem 0;
  display:flex; align-items:flex-start; justify-content:center;
  max-height:222px; overflow:hidden;
  -webkit-mask-image:linear-gradient(#000 45%, transparent 96%);
  mask-image:linear-gradient(#000 45%, transparent 96%);
}
.home-stack li{
  width:clamp(150px, 30vw, 210px); flex:none;
  transition:transform .45s ease;
}
/* The lean: rotate about a pivot BELOW the hand (120% down), the way
   held cards share the grip point, and sit the outer pair a touch
   lower -- the arc a fan actually makes. */
.home-stack li:nth-child(1){
  transform:rotate(-8deg) translateY(.55rem);
  transform-origin:85% 120%;
  margin-right:calc(clamp(150px, 30vw, 210px) * -.42);
}
.home-stack li:nth-child(2){ position:relative; z-index:2; }
.home-stack li:nth-child(3){
  transform:rotate(8deg) translateY(.55rem);
  transform-origin:15% 120%;
  margin-left:calc(clamp(150px, 30vw, 210px) * -.42);
}
/* The spread: the outer pair leans a couple of degrees further and
   slides aside (10 degrees, not more -- the corner swing must stay
   inside the side padding above), and the CENTRE card rises out of the
   hand a little, the way a picked card does. */
.home-card:hover .home-stack li:nth-child(1){
  transform:rotate(-10deg) translate(-.6rem, .45rem);
}
.home-card:hover .home-stack li:nth-child(2){
  transform:translateY(-.7rem);
}
.home-card:hover .home-stack li:nth-child(3){
  transform:rotate(10deg) translate(.6rem, .45rem);
}
/* The tiles are BS.streamerTile's own -- the live directory's card
   grammar untouched -- with an ambient drop shadow to part the hand. */
.home-stack .s-tile{ box-shadow:0 10px 26px rgba(0,0,0,.55); }
/* Their inner anchors are stripped of href by home.js (the PANEL is the
   link); an anchor without href underlines nothing and steals no click. */
.home-stack a{ color:var(--text); }
/* Before the directory answers -- or when it cannot -- three card-shaped
   slips in the mini-stack's blue backfill hold the hand's shape. */
.s-ghost{
  display:block; aspect-ratio:744/1039;
  border-radius:6px; border:1px solid rgba(95,208,224,.35);
  background:linear-gradient(160deg, #1c4653, #0d2833);
  box-shadow:0 10px 26px rgba(0,0,0,.55);
}
/* The live directory's own flag -- its dot, its "Live now" -- standing
   in the space the hand cleared, in the section-title register. It sits
   inside .home-desat, so the red arrives WITH the rest of the colour on
   hover; what is gated separately is the LIFE: at rest the dot holds
   still (the directory's grey-twin manner), and hovering the panel
   starts the directory's own 2s pulse, the text's glow breathing in
   step with it (same clock, started together). */
.home-live-flag{
  flex:none; align-self:center; margin-left:3.2rem;
  font-family:'Roboto Slab', Georgia, serif; font-weight:700;
  font-size:.8rem; letter-spacing:.22em; text-transform:uppercase;
  color:#f07b7f; white-space:nowrap;
}
.home-live-flag .live-dot{
  width:.8em; height:.8em; margin-right:.85em; animation:none;
}
/* The glow arrives on hover for everyone; whether it BREATHES is the
   motion block's decision below. */
.home-card:hover .home-live-flag{
  text-shadow:0 0 16px rgba(224,57,62,.45), 0 0 38px rgba(224,57,62,.2);
}

/* ---- door two: the spotlight ----------------------------------------- */
/* One card the library can define at least twice, art left and its
   keyword boxes to the right -- the extension's lightbox arrangement,
   restated in the site's own panel skin. The panel crops the bottom and
   EVERYTHING fades toward the cut -- the mask rides the container, not
   the columns, so the fade always lands exactly on the crop line and
   the card reads as sinking into the page: there is more where this
   came from. */
.home-spot{
  display:flex; justify-content:center; gap:2rem;
  max-height:290px; overflow:hidden;
  /* Headroom for the hover pick-up: the 1.03 scale and the 1-degree
     lean lift the art's top edge a few px, and without this air the
     crop shears it off mid-animation. */
  padding-top:.6rem;
  -webkit-mask-image:linear-gradient(#000 52%, transparent 98%);
  mask-image:linear-gradient(#000 52%, transparent 98%);
}
/* Sized so the 290px window shows roughly the card's top TWO-THIRDS --
   the crop tightened from 340px and the art must zoom out with it, or
   the same window reads as a closer and closer crop of the art. */
.spot-art{ margin:0; flex:0 0 auto; width:min(42%, 295px); }
.spot-art img{
  display:block; width:100%; height:auto;
  border-radius:12px 12px 0 0; border:1px solid var(--line);
  border-bottom:none;
  transition:transform .35s cubic-bezier(.2,.7,.3,1);
}
/* The card page's pick-up, on the panel's hover: 1.03 and one degree
   read as the card being lifted, anything more reads as a widget. */
.home-card:hover .spot-art img{ transform:scale(1.03) rotate(-1deg); }
.spot-defs{
  flex:0 1 24rem; min-width:0;
  display:flex; flex-direction:column; gap:1rem;
  transition:transform .35s ease;
}
/* The definitions answer the card's lift with a light pop of their own. */
.home-card:hover .spot-defs{ transform:scale(1.02); }
.spot-defs .kw-box{ margin:0; }

@media (max-width:640px){
  .home-card{ padding:1.1rem 1.1rem 1.3rem; }
  .home-spot{ gap:1.1rem; }
  .spot-art{ width:min(52%, 340px); }
  /* A phone has no room for the corner-swing padding; a sliver of
     clipped corner is the accepted cost there. */

  .home-stack{ padding:1rem 1.5rem 0; margin-left:-1rem; }
}

/* The entrance, and the wordmark's breathing -- movement only, so a
   reduced-motion visitor gets the settled page directly. */
@media (prefers-reduced-motion: no-preference){
  /* "board state" breathes between the teal and a dark gray on the
     coming-soon page's own 7.5s cycle: a light source turning down and
     back up, never a blink. It holds its breath through the entrance --
     the 2.3s delay is the choreography's full length, so the first dim
     only begins once the hero has seated (the resting teal IS the
     cycle's 0% frame, so nothing jumps when it starts). */
  .home-page h1 .bs{ animation:breathe 7.5s ease-in-out 2.3s infinite; }
  @keyframes breathe{
    0%, 100% { color:var(--teal);
               text-shadow:0 0 26px rgba(31,158,163,.28); }
    50%      { color:#123c3f;
               text-shadow:0 0 22px rgba(31,158,163,.06); }
  }
  /* The choreography, pure CSS off one 2.3s clock: the hero FADES IN
     around the middle of the viewport (translated DOWN from its real
     seat -- max() keeps a short viewport from pushing it above the
     top) over .6s, settles for just .3s (trimmed twice on Kevin's
     watch, 2026-09-06: a full second, then half of one, each still
     read as hesitation), then glides up; the rest of the page rises in
     under it just as it lands. Transform and opacity only -- the
     layout never moves, so nothing reflows mid-entrance. */
  /* Every frame is the same translateY() shape -- against `none` or a
     mismatched list some engines refuse to interpolate the calc() and
     flip discretely at the midpoint instead of gliding. */
  .hero{ animation:hero-in 2.3s cubic-bezier(.3,.1,.25,1) both; }
  @keyframes hero-in{
    0%   { opacity:0; transform:translateY(max(0px, calc(50vh - 9.5rem))); }
    26%  { opacity:1; transform:translateY(max(0px, calc(50vh - 9.5rem))); }
    39%  { opacity:1; transform:translateY(max(0px, calc(50vh - 9.5rem))); }
    100% { opacity:1; transform:translateY(0px); }
  }
  /* The glow follows on a LAG: it rides the hero (it is the hero's own
     ::before), and this counter-animation holds it behind during the
     glide -- drifting low and stretching tall as the text pulls away --
     then lets it catch up as the hero seats. Light has inertia here. */
  .hero::before{ animation:glow-follow 2.3s cubic-bezier(.3,.1,.25,1) both; }
  @keyframes glow-follow{
    0%, 39% { transform:translate(-50%,-50%) scale(1,1); }
    72%     { transform:translate(-50%, calc(-50% + 3.5rem)) scale(1.05, 1.22); }
    100%    { transform:translate(-50%,-50%) scale(1,1); }
  }
  /* The top line leads at full size in the headline's own white, and
     WHILE the hero rises it eases down to everything the static rule
     above declares: .47em (the scale starts at 2.13 = 1/.47), the muted
     grey, and the .45em drop that tucks it over the wordmark. The
     invitation stands down; the wordmark keeps the stage. */
  .home-page h1 .tc{ animation:tc-settle 2.3s cubic-bezier(.3,.1,.25,1) both; }
  @keyframes tc-settle{
    0%, 39% { transform:translateY(0em) scale(2.13); color:var(--text); }
    100%    { transform:translateY(.45em) scale(1); color:var(--muted); }
  }
  .home-below{ animation:below-in .9s ease both; animation-delay:1.95s; }
  @keyframes below-in{
    from{ opacity:0; transform:translateY(14px); }
    to  { opacity:1; transform:none; }
  }
  /* The corner picker arrives WITH the hero's landing: a plain fade on
     the same delay the content below uses -- no slide, a control taking
     its post rather than an element entering the story. */
  .home-top{ animation:top-in .9s ease both; animation-delay:1.95s; }
  @keyframes top-in{ from{ opacity:0; } to{ opacity:1; } }
  /* THE SKIP. A press during the entrance stamps .entrance-done
     (home.js), these rules strip every entrance animation, and each
     element simply IS its static rule -- which is the settled page, by
     the standing invariant that the base styles equal the animations'
     end frames. The breathe is deliberately not in this list: it is
     the settled page's own life, not part of the journey, and its 2.5s
     delay simply plays out from where it is. */
  html.entrance-done .hero,
  html.entrance-done .hero::before,
  html.entrance-done .home-page h1 .tc,
  html.entrance-done .home-below,
  html.entrance-done .home-top{ animation:none; }

  /* The flag comes alive on hover: the directory's own live-pulse on
     the dot, and the glow breathing with it -- same duration, started
     together, so dot and halo dim and return as one. */
  .home-card:hover .home-live-flag{
    animation:live-glow 2s ease-in-out infinite;
  }
  .home-card:hover .home-live-flag .live-dot{
    animation:live-pulse 2s ease-in-out infinite;
  }
  @keyframes live-glow{
    0%, 100% { text-shadow:0 0 16px rgba(224,57,62,.5),
                           0 0 38px rgba(224,57,62,.22); }
    50%      { text-shadow:0 0 10px rgba(224,57,62,.14),
                           0 0 24px rgba(224,57,62,.06); }
  }
}
@media (prefers-reduced-motion: reduce){

  .home-stack li{ transition:none; }
  .home-card, .home-desat, .spot-art img, .spot-defs{ transition:none; }
  .home-card:hover .spot-art img{ transform:none; }
  .home-card:hover{ transform:none; }

  .home-card:hover .home-stack li:nth-child(1),
  .home-stack li:nth-child(1){ transform:rotate(-8deg) translateY(.55rem); }
  .home-card:hover .home-stack li:nth-child(2){ transform:none; }
  .home-card:hover .home-stack li:nth-child(3),
  .home-stack li:nth-child(3){ transform:rotate(8deg) translateY(.55rem); }
}

/* ------------------------------------------- the capture page's voice */
/* Last in the file on purpose: these override the sizes set above.
   Page titles carry weight; section headings speak in the capture and
   portal pages' rail-label register -- Roboto Slab light, small, wide
   tracking, upper case, muted. */
/* ONE FAMILY: every page title -- Riftbound cards, Riftbound streamers,
   the streamer's name, a card's name. Settled at 500 (Kevin, 2026-09-05,
   after 700 then 600 both read too heavy). */
main h1{ font-weight:500; }
.lede{ font-size:.9rem; }
/* ONE FAMILY (Kevin, 2026-09-05): the set names, MOST RECENT CARDS,
   STREAMING NOW / STREAMED RECENTLY, DEFINITIONS and RULINGS -- and the
   front page's two door titles -- all play the same section-title role on
   their pages and are styled HERE, together -- a change to one is a
   change to all. Bold, muted (white was too bright), in the capture
   pages' rail-label register. */
.set h2, .results > h2, .results-head h2, .live-section h2,
.keywords h2, .rulings h2, .home-card h2{
  font-family:'Roboto Slab', Georgia, serif; font-weight:700;
  font-size:.8rem; letter-spacing:.22em; text-transform:uppercase;
  color:var(--muted);
}
.set h2 .count, .live-section h2 .count{ letter-spacing:normal; font-weight:400; }
/* The front page's door titles: in the family above for face, size and
   tracking, but voiced in the wordmark's own dark teal -- the doors
   speak with the brand, not the rail grey (Kevin, 2026-09-05). */
.home-card h2{ color:var(--teal); }

/* Every finder control is a MINIATURE CAPTURE BUTTON: the slab fill, the
   soft raise, the -1px hover lift, the pressed carve. Assigned colours and
   icons (domain tints and runes, rarity tints and gems, the pressed cyan,
   the promoted Show-images blue) sit on top, re-asserted below where this
   block would otherwise bury them. */
.opt, .img-toggle, .clear-all, .more-toggle, .facet-sel{
  background:var(--slab);
  border-radius:10px;
  transition:box-shadow .18s ease, transform .18s ease,
             color .15s ease, border-color .15s ease, background .18s ease;
}
/* NO light rim on the small buttons (Kevin, 2026-09-05: the white outline
   is not our language): border transparent -- kept 1px wide so the domain
   and rarity buttons' COLOURED border-colors still paint, and nothing
   shifts -- and only the dark half of the neumorphic shadow. The dropdowns
   keep their input-style edge below. */
.opt, .img-toggle, .clear-all, .more-toggle{
  border:1px solid transparent;
  box-shadow:3px 3px 8px var(--dark);
}
/* SOLID dark ground, not the slab gradient: a <select>'s popup list takes
   its colour from background-color, and a gradient leaves that unset --
   which is how the tag/artist lists opened as a white flashbang with
   white-on-white rows. Same well the capture page's Remember-this-computer
   checkbox is carved from (--bg-deep). */
.facet-sel{
  border:1px solid var(--slab-edge); box-shadow:var(--raise-sm);
  background:var(--bg-deep); color:var(--text);
}
.facet-sel option{ background:var(--bg-deep); color:var(--text); }
.opt:hover, .img-toggle:hover, .clear-all:hover, .more-toggle:hover{
  transform:translateY(-1px);
  box-shadow:5px 6px 12px var(--dark);
}
.facet-sel:hover{
  transform:translateY(-1px);
  box-shadow:-4px -4px 9px var(--lite), 5px 6px 12px var(--dark);
}
.opt:active, .img-toggle:active, .clear-all:active, .more-toggle:active{
  transform:translateY(0); box-shadow:var(--carve);
}
/* The two solid-fill states, back on top of the slab base. */
.img-toggle[aria-pressed="false"]{
  background:var(--cyan); border-color:var(--cyan); color:#06222a;
  font-weight:700;
}
.opt[aria-pressed="true"]{
  background:var(--cyan); border-color:var(--cyan); color:#06222a;
}

/* ------------------------------------------------------------------- 404 */
/* The not-found page: the Patched Porobot centred in a circular fade, one
   quiet line, the code. Same window-bottom footer trick as the About page:
   the body becomes a viewport-height flex column and the main column takes
   the slack, so the porobot floats mid-window on any screen. */
body:has(.nf-page){ display:flex; flex-direction:column; min-height:100vh; }
body:has(.nf-page) > .site-head,
body:has(.nf-page) > main,
body:has(.nf-page) > .site-foot{ width:100%; }
.nf-page{
  flex:1 0 auto;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  max-width:1040px; margin:0 auto; padding:1rem 1.25rem 2rem;
  text-align:center;
}
/* A square stage the wide art is cover-cropped into. The porobot's centre
   (tail-claw included) sits at 42% of the art's width; with a square crop
   of the 2:1 image the crop's centre lands at (p + 0.5)/2 of the art, so
   object-position p = 34% puts the bot mid-stage. */
.nf-art{ width:clamp(260px, 72vw, 460px); aspect-ratio:1; }
.nf-art img{
  width:100%; height:100%; object-fit:cover; object-position:34% 50%;
  /* The circular fade: full opacity out to 68% of the stage's half-width
     -- the whole porobot, tail to saw-arm -- easing to fully transparent
     AT the stage edge, so the art melts into the page ground with no
     seam. */
  -webkit-mask-image:radial-gradient(circle closest-side, #000 68%, transparent 100%);
  mask-image:radial-gradient(circle closest-side, #000 68%, transparent 100%);
}
.nf-line{ margin:.4rem 0 0; color:var(--muted); font-size:1.05rem; }
.blocked{ display:flex; flex-direction:column; align-items:center; padding:1.5rem 0 2rem; }
.blocked[hidden]{ display:none; }
.blocked-cols{
  width:100%; display:grid; grid-template-columns:2fr 1fr; gap:1.5rem;
  align-items:center;
}
.blocked-note{
  background:linear-gradient(148deg, #2a2817, #1d1c12);
  border:1px solid rgba(240,201,119,.16); border-radius:12px;
  box-shadow:0 0 34px rgba(240,201,119,.05);
  padding:1.4rem 1.6rem;
}
.blocked-note p{
  margin:0; color:#f0c977; font-size:1.2rem; line-height:1.5;
}
.blocked-tile{ grid-template-columns:1fr; justify-items:center; }
.blocked-tile li{ width:100%; max-width:280px; }
.s-tile.big{ gap:.8rem; padding:1.1rem .9rem .8rem; }
.s-tile.big .s-name{ font-size:1.25rem; }
.s-tile.big .s-legend-name{ font-size:.85rem; }
.blocked-art{ margin-top:1.5rem; }
@media (max-width:700px){
  .blocked-cols{ grid-template-columns:1fr; }
}
.nf-code{
  margin:.15rem 0 0; font-family:'Roboto Slab', Georgia, serif;
  font-weight:300; font-size:3rem; line-height:1.15; color:var(--text);
}

@media (max-width:640px){
  .preview{ gap:1.25rem; }
  .art, .art.landscape{ width:100%; }
  .fact dt{ flex-basis:100%; }
}

/* Domain chips, tinted with the colour sampled from that domain's
   rune-*.svg (see DOMAIN_COLORS in tools/build_card_pages.py). */
.chip.domain-fury{ background:rgba(223,22,32,0.14); border-color:rgba(223,22,32,0.45); color:#ed7f84; }
.chip.domain-calm{ background:rgba(72,140,56,0.14); border-color:rgba(72,140,56,0.45); color:#9ac092; }
.chip.domain-mind{ background:rgba(15,111,166,0.14); border-color:rgba(15,111,166,0.45); color:#7bb0ce; }
.chip.domain-body{ background:rgba(232,118,0,0.14); border-color:rgba(232,118,0,0.45); color:#f2b473; }
.chip.domain-chaos{ background:rgba(106,64,148,0.14); border-color:rgba(106,64,148,0.45); color:#ad96c4; }
.chip.domain-order{ background:rgba(210,180,0,0.14); border-color:rgba(210,180,0,0.45); color:#e6d673; }

/* Rarity chips, tinted with the colour sampled from that rarity's
   printed gem (see RARITY_GEMS in tools/build_card_pages.py). */
.chip.rarity-common{ background:rgba(208,217,211,0.14); border-color:rgba(208,217,211,0.45); color:#e5eae7; }
.chip.rarity-uncommon{ background:rgba(31,156,159,0.14); border-color:rgba(31,156,159,0.45); color:#84c9ca; }
.chip.rarity-rare{ background:rgba(179,51,138,0.14); border-color:rgba(179,51,138,0.45); color:#d58fbf; }
.chip.rarity-epic{ background:rgba(221,118,22,0.14); border-color:rgba(221,118,22,0.45); color:#ecb47f; }
.chip.rarity-showcase{ background:rgba(228,185,22,0.14); border-color:rgba(228,185,22,0.45); color:#f0d87f; }
.chip.rarity-promo{ background:rgba(160,106,52,0.14); border-color:rgba(160,106,52,0.45); color:#cbad8f; }
.opt.rarity-common:not([aria-pressed="true"]){ color:#e5eae7; border-color:rgba(208,217,211,0.45); }
.opt.rarity-uncommon:not([aria-pressed="true"]){ color:#84c9ca; border-color:rgba(31,156,159,0.45); }
.opt.rarity-rare:not([aria-pressed="true"]){ color:#d58fbf; border-color:rgba(179,51,138,0.45); }
.opt.rarity-epic:not([aria-pressed="true"]){ color:#ecb47f; border-color:rgba(221,118,22,0.45); }
.opt.rarity-showcase:not([aria-pressed="true"]){ color:#f0d87f; border-color:rgba(228,185,22,0.45); }
.opt.rarity-promo:not([aria-pressed="true"]){ color:#cbad8f; border-color:rgba(160,106,52,0.45); }

/* "live updates paused" / "watch on Twitch" (contract §4/§10). */
.poll-note{ font-size:.8rem; color:#8494a1; margin:.2rem 0 .8rem; }

/* The card page's buy row must fit ON ONE LINE under the art: smaller
   type, tighter padding and gaps, and a narrower price slot than the
   buy bar's full-size button. */
.art .buy-block{ margin-top:10px; }
.art .buy-row{ gap:7px; }
.art .buy-label{ font-size:11px; }
.art .buy-btn{ font-size:11px; padding:6px 9px; gap:6px; border-radius:8px; }
.art .buy-btn .tcg-price{ min-width:3.4em; padding-left:7px; }
/* The glitch mark every unnamed streamer wears where a face would go --
   the front page's stand-in tiles and any real channel whose identity is
   not resolved yet -- sized inside the avatar circle (the circle is
   already a centering flexbox). */
.avatar .tw-ph{ width:46%; height:46%; }
