/* StashMule — one stylesheet for the whole site.
 *
 * The palette is the extension's, token for token, so the site and the tool
 * do not look like two different products. Anything changed here should be
 * changed in options.html too, or they drift. tests/palette.js asserts it.
 *
 * Three things about that palette are easy to get wrong by eye:
 *   - the background is a GRADIENT, not a flat colour;
 *   - the primary button is BLUE, not gold;
 *   - gold is for figures and accents only, never a large surface.
 *
 * The design has two layers and keeping them apart is the whole discipline:
 *
 *   CHROME  — the dark interface. Deliberately unremarkable. Its job is to
 *             disappear behind the content.
 *   CONTENT — item data, rendered in Diablo II's own information design: the
 *             name carries quality in its colour, stats read as ranges. This
 *             is the brand, and the only place the site is allowed to be loud.
 *
 * The failure this guards against is reaching for the genre instead of the
 * game. Parchment, embers and blackletter are what "Diablo" evokes to someone
 * who has not played it. Item colour is what it looks like to someone who has.
 */

:root {
  /* Straight from the extension's palette: neutral greys since 4.9, where it
     was a blue slate before. */
  --bg-grad-a: #2b2b2b;
  --bg-grad-b: #242424;
  /* Three greys, lightest to darkest: the page, a card or the header, the item
     tooltip. A card sits a touch below the page, so it reads as a surface on it
     without being mistaken for the tooltip's black. */
  --panel-bg: #1c1c1c;
  --panel-hover: #222222;
  --border: #404040;
  --border-soft: #353535;
  /* Between the page and a card, so a field reads as set into either. */
  --input-bg: #212121;
  /* Placeholder text. Its own colour rather than --text-muted (#999), because
     a grey picked against the panel is not automatically safe on an input: on
     the old slate input --text-muted measured 4.38:1 and missed AA. This is
     6.38:1 there. */
  --placeholder: #a3a3a3;
  --input-border: #4d4d4d;
  --text: #eee;
  --text-muted: #999;
  --text-soft: #ccc;
  --price: #8f8;
  --gold: #ffcc00;
  /* The chest's own gold, in favicon.svg and the extension's icons. Not the
     interface gold any more: the interface went neon in 4.9, and the artwork's
     gold is tuned against its wood at 16px, where the two values are all that
     survive. Declared so tests/favicon.js can hold the artwork to a token. */
  --mark-gold: #ffd700;
  --eth: #8fe3f0;

  /* --text-dim exists in the extension and is deliberately absent here: it
     measures 4.16:1 on the panel, under AA. Muted is the dimmest usable. */

  /* The extension's button gradient starts at #4a90d9, where white measures
     3.34:1 — under the 4.5:1 normal text needs, and the label is 14px bold,
     which is not large enough to qualify for the 3:1 allowance. Darkened to
     #3b78b8 (4.60:1). Visually near-identical; the extension has the same
     issue and should get the same change. */
  --btn: linear-gradient(90deg, #3b78b8, #3a70b9);
  /* Light grey with a dark label. */
  --btn-secondary: linear-gradient(90deg, #c8c8c8, #b8b8b8);
  /* Hover fills. The primary goes more saturated rather than lighter, because
     it carries white 14px bold text and the resting primary is already only
     4.60:1 — brightening it drops under AA, where deepening the colour and
     pushing the saturation reads as the button lighting up AND measures
     better (4.95:1).

     The secondary is light grey at rest and turns dark grey under the pointer,
     its label flipping from dark to white with it — see button.secondary. It
     went a cool blue-grey until 4.9, then neon orange for a day, which was too
     loud for a button that is by definition the lesser choice. Every stop,
     with the label it carries, is checked by tests/palette.js. */
  --btn-hover: linear-gradient(90deg, #1f6fd0, #1e67d2);
  --btn-secondary-hover: linear-gradient(90deg, #444444, #3a3a3a);
  /* The only irreversible button on the site. Dark enough that white text
     clears AA on it, which the palette tests check like any other pair. */
  --btn-danger: linear-gradient(90deg, #8f3330, #7a2b28);
  --btn-danger-hover: linear-gradient(90deg, #a83e39, #93342f);
  --danger-border: #8f3330;
  /* The primary button's first gradient stop, as a flat colour. Borders and
     rings cannot take a gradient, and --danger-border exists for the same
     reason one line above. */
  --btn-border: #3b78b8;
  --radius: 6px;

  /* Item quality, matching QUALITY_COLORS in the extension's quality.js. These
     are the game's meaning at contrast that passes AA on a panel, not the
     game's literal values. Magic is the clearest case: the game draws #6f6fff,
     which measures 3.25:1 here and is unreadable, so it is lightened.
     tests/palette.js checks both the match and the contrast.

     Nothing on the site renders an item name any more — the nine-quality
     legend on /guide was the last thing that did. These stay because the
     extension still draws them and palette.js holds this list against it, so
     they are the anchor that notices when that palette drifts. --q-unique is
     also the favicon's parent colour. */
  --q-normal: #e9edf5;
  --q-magic: #a3b1ff;
  --q-rare: #f2e34d;
  --q-set: #63e888;
  --q-unique: #cdb175;
  --q-crafted: #ffb43d;
  --q-rune: #ffb43d;
  --q-runeword: #cdb175;

  /* The RW and SET badges on an item card. Separate tokens from the quality
     colours above even though two of them currently match, because the
     extension declares them separately and this file must not merge what it
     mirrors. Also from the extension's dark theme, so the drift test covers
     them. */
  --tag-rw: #cdb175;
  --tag-set: #63e888;

  /* The item tooltip, drawn the way the game draws it: near-black, stat lines
     in the game's blue, and the base lines — defense, requirements — in white.
     The same values as the extension's --tip-* tokens; tests/palette.js checks
     the match and the contrast. The game's own blue is #6969ff, and this is a
     shade lighter so small text clears AA with room (5.52:1 on this black).

     The per-stat colours that used to live here went in 4.9 with the
     extension's default colour rules. Anyone can still add their own. */
  --tip-bg: #0c0c0c;
  --tip-stat: #7878ff;
  --tip-meta: #eeeeee;

  /* Vertical rhythm. Sections breathe more on a wide screen than a phone,
     which is what stops a long page reading as one undifferentiated column. */
  --section: clamp(52px, 8vw, 96px);
  /* The sticky bar's height. The first-screen heroes are one screen less this,
     and anchor jumps clear it, so they all read it from here. */
  --header-h: 62px;

  color-scheme: dark;
}

/* A taller bar where there is room for one. 76px gives the Create account
   button 16px above and below it instead of 9, which is what made the bar read
   as thin. Not everywhere, though: a 1366x768 laptop leaves about 660px for the
   page, and there the home hero's install row already touches the arrow at
   62px; at 76 it overlapped by 7. Measured at 1366 wide: 720, 768, 800 and 900
   tall all fit at 76. Phones keep 62 too, by width, since a phone held upright
   is tall enough to pass the height test and has the least screen to spare. */
@media (min-height: 760px) and (min-width: 561px) {
  :root { --header-h: 76px; }
}

* { box-sizing: border-box; }

/* The `hidden` attribute is display:none in the browser's own stylesheet, which
   ANY author rule setting display will beat — and `button { display: inline-block }`
   below does exactly that. Without this, every element hidden by the markup or
   by script is visible anyway: the "Open your stash" button appeared for
   people with no extension and did nothing when pressed. */
[hidden] { display: none !important; }

/* ---------- type ----------
 *
 * Space Grotesk for the two display sizes, IBM Plex Sans for everything else.
 *
 * Both are sans, so the contrast between a heading and the text under it comes
 * from the letterforms rather than from serif-against-sans: Space Grotesk has
 * flattened curves and a squared-off g and G that Plex does not, which is what
 * makes a heading read as a heading here.
 *
 * Self-hosted rather than linked. The pages are served with default-src 'self',
 * so a <link> to fonts.googleapis.com would simply be blocked — and hosting the
 * files here also means no third party is told who reads this site.
 *
 * IBM Plex Sans is a single variable file covering the whole weight range.
 * Google serves identical bytes whether you ask for 400 or 600, so shipping
 * both would have been 45kB of the same font twice. Declared as a range so the
 * browser interpolates a real semibold instead of smearing a fake one.
 *
 * font-display: swap, because on a dark page an invisible heading is worse
 * than a heading that changes shape once. Both stacks keep a real fallback for
 * the same reason.
 *
 * Licences are beside the files: both families are SIL OFL 1.1, which requires
 * the licence to travel with the font.
 */
/* One weight, 600, as a static instance. Space Grotesk also comes as a
   variable file covering 300-700, but nothing here asks for another weight and
   the single cut is 13kB against roughly thirty. */
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/fonts/space-grotesk-600.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 100 700;
  font-display: swap;
  src: url("/fonts/ibm-plex-sans.woff2") format("woff2");
}

body {
  margin: 0;
  min-height: 100vh;
  /* A column, so the footer sits at the bottom of the window on a short page.
     With the footer the same colour as the page this never showed; on its
     darker base, a 404 or a verify page would end in a band stopping halfway
     down the screen. main takes the slack below. */
  display: flex;
  flex-direction: column;
  font-family: "IBM Plex Sans", "Segoe UI", system-ui, -apple-system, Roboto, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  color: var(--text);
  /* Fixed, so a long page does not scroll out of the gradient and land on
     whatever the browser paints underneath.

     The grain sits over it and scrolls with the page, like a finish on the
     surface rather than something behind glass. It is tuned in grain.svg, and
     it is on the ground only: cards, the header and the item tooltip keep their
     flat fills, which is what lets them read as surfaces lying on top of it. */
  background:
    url("/grain.svg"),
    linear-gradient(160deg, var(--bg-grad-a) 0%, var(--bg-grad-b) 100%) fixed;
  -webkit-font-smoothing: antialiased;
}

/* The inventory grid behind the hero spans the full window, and 100vw is wider
   than the page by the width of a classic scrollbar — so on a Windows desktop
   it hung 8px past the right edge and put a 15px horizontal scrollbar under
   the page. Measured both ways before this went in.

   On the root element clip behaves as hidden: it is handed to the viewport,
   which is the page's scroller already, so the sticky header is unaffected.
   What it removes is the scrollbar and sideways scrolling by wheel or
   trackpad; a script could still nudge the page those 8px, and nothing here
   does. Nothing on this site should ever scroll sideways. */
html { overflow-x: clip; }

/* Links are the gold accent, and underlined, which is what tells them apart
   from the gold key phrases. They were the ethereal cyan, which belongs to the
   item palette, and then for a day a neon orange that looked like Halloween.
   The hover is a lighter gold, because on this ground lighter reads as
   lighting up; tests/palette.js measures both. */
a { color: var(--gold); }
a:hover { color: #ffe066; }

/* Anchored sections land under the sticky header rather than behind it. */
:target { scroll-margin-top: calc(var(--header-h) + 20px); }

.wrap { max-width: 1060px; margin: 0 auto; padding: 0 24px; }
/* main is a flex item of the body column (see body). width: 100% is load-bearing:
   an item with auto side margins shrinks to its content in a flex column, which
   would turn every .wrap main into a narrow column in the middle of the page. */
body > main { flex: 1 0 auto; width: 100%; }
.narrow { max-width: 460px; }

/* Prose is capped by characters, not by the wrap. The wrap is wide enough for
   two columns; a paragraph filling it would run past 110 characters a line,
   which is roughly half again the comfortable limit. Anything that genuinely
   wants the full width opts out with .full. */
main p, main li { max-width: 68ch; }
main p.full, main li.full, .full p, .full li { max-width: none; }

/* Focus has to be visible on every interactive thing, not just inputs. Gold,
   matching the field focus the extension already uses. */
:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: 3px;
}

/* Motion is decoration in this product, never information, so it all goes. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  /* Zeroing the duration is not enough for a transform: it makes the button
     jump to the new size instantly instead of not moving. The lighting is not
     motion and stays, so hover and press still read. */
  button:hover, .btn:hover, button:active, .btn:active,
  .pack:hover, .pack.best:hover {
    transform: none;
  }
}

/* ---- header ---- */

header {
  border-bottom: 1px solid var(--border);
  background: var(--panel-bg);
  position: sticky;
  top: 0;
  z-index: 10;
}
header .wrap { display: flex; align-items: center; gap: 18px; height: var(--header-h); }
/* The wordmark is set in the display face, not the body one. It is the only
   thing in the header, it is a name rather than a label, and leaving it in the
   text face made the header read as belonging to a different site than the
   headings underneath it.

   600 rather than 700 for the same reason every other heading is: one weight
   of the display face ships, and asking for a bolder one gets a synthesised
   approximation of it. tests/fonts.js covers this rule too — it sweeps
   anything naming the display family, not only selectors made of h1-h6. */
.brand {
  display: inline-flex;
  align-items: center;
  font-family: "Space Grotesk", "Segoe UI", system-ui, sans-serif;
  font-weight: 600;
  font-size: 19px;
  letter-spacing: -0.2px;
  color: var(--text);
  text-decoration: none;
}
/* alt="" on purpose: the wordmark right beside it already says the name, and a
   screen reader reading "StashMule StashMule" helps nobody. Sized in CSS as
   well as on the tag so the lockup does not jump before the SVG loads. */
/* The space belongs to the image, not to a flex gap. "Stash" and the gold
   "Mule" are two anonymous flex items, so a gap on .brand pushed them apart and
   the wordmark read as two words. */
.brand img {
  width: 26px;
  height: 26px;
  display: block;
  margin-right: 9px;
  /* Near the base rather than the middle, so the rattle reads as the chest
     rocking on the ground instead of spinning about its own centre. */
  transform-origin: 50% 85%;
}
/* Class, not :hover — see app.js. Hover starts the hop and the animation
   ending is what stops it, so taking the pointer away mid-air lets the chest
   land instead of snapping back to the floor. */
.brand img.pop { animation: chest-pop 738ms; }

/* Up fast, turn at the top, then down under gravity and a short rattle as it
   settles. 738ms end to end.

   No hold at the peak. There was one, 162ms and then 95ms, and it still read
   as too long both times — because the flat section is not the whole of what
   you see. The rise decelerates into the top and the fall accelerates away
   from it, so the chest lingers near its peak either way; with the flat part
   gone that easing alone is the pause. Every other phase is untouched: rise
   216ms, fall 144ms, the four rattle beats at the same offsets from landing.

   The timing function is per keyframe because one curve cannot do both
   halves — a rise decelerates and a fall accelerates, and a single ease makes
   the chest drift down like paper rather than drop. */
@keyframes chest-pop {
  0%    { transform: translateY(0)    rotate(0deg);    animation-timing-function: cubic-bezier(0.22, 0.9, 0.28, 1); }
  29.3% { transform: translateY(-9px) rotate(0deg);    animation-timing-function: cubic-bezier(0.55, 0, 0.85, 0.4); }
  48.8% { transform: translateY(0)    rotate(0deg); }
  59.8% { transform: translateY(-2px) rotate(-5deg); }
  70.7% { transform: translateY(0)    rotate(3.5deg); }
  81.7% { transform: translateY(-1px) rotate(-2deg); }
  91.5% { transform: translateY(0)    rotate(1deg); }
  100%  { transform: translateY(0)    rotate(0deg); }
}
.brand span { color: var(--gold); }

/* ---------------------------------------------------------------------------
   BETA BADGE — delete this whole block when the beta ends. Nothing else needs
   touching: there is no markup for it anywhere.

   Done as a pseudo-element on purpose. The wordmark is hand-written into the
   header of all twelve pages, so a real <span> would be twelve edits to add and
   twelve to remove, and the twelfth is the one that gets forgotten. This is one
   block in one file, and removing it leaves the lockup exactly as it was.

   The cost is that it is invisible to a screen reader, which is the right
   trade here but worth stating: pseudo-element content is not in the
   accessibility tree, so it cannot be the only place the beta is mentioned. It
   is not — /pricing says it in a banner above the packs.

   Absolutely positioned, so the header keeps its height and the wordmark does
   not shift by a pixel. The badge hangs into space the header already had.
   --------------------------------------------------------------------------- */
.brand span { position: relative; }
.brand span::after {
  content: "BETA";
  position: absolute;
  /* Centred under "Mule" rather than left-aligned to it: the badge is narrower
     than the word, and hanging it off the left edge reads as misalignment. */
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-top: -3px;

  font-size: 8.5px;
  font-weight: 700;
  line-height: 1;
  /* The tracking is what makes four letters at 8.5px read as a badge rather
     than as small text. The trailing space it adds to the last letter is
     cancelled by the indent, or the centring sits a hair to the left. */
  letter-spacing: 0.22em;
  text-indent: 0.22em;

  background-image: linear-gradient(90deg, #63e0ff, #7fb4ff);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;

  /* Both stops clear 4.5:1 on the header (9.6:1 and 6.9:1). Small bold text is
     not "large text" under WCAG, so the brighter end of blue is the one that
     works — a deeper blue looks better on paper and fails here. */

  /* The wordmark is a link. Without this the badge widens its hit area
     downwards into the header for no reason. */
  pointer-events: none;
}
/* Three zones, and now genuinely three columns.

   Auto margins in a flex row were here first, and they centre against the
   CONTENT: the free space either side of the links, with the wordmark on one
   side and the actions on the other. When those two weigh the same that is
   indistinguishable from centred, which is why it looked right signed in and
   wrong signed out — a signed-out bar carries a button the signed-in one does
   not, the right side outweighed the wordmark by about 150px, and the links
   sat visibly left of centre.

   Equal 1fr columns either side fix it by construction. nav is display:
   contents so its two groups become the grid's own items rather than one item
   holding both; the element stays in the DOM and in the accessibility tree,
   which is what it is there for.

   min-width, not a plain rule, because below 560px the nav is an absolutely
   positioned dropdown and the wrap is a flex row with a menu button in it.
   Two layouts that share nothing are better told apart than talked out of
   each other. */
header nav { display: flex; gap: 18px; align-items: center; }
.nav-links, .nav-actions { display: flex; gap: 18px; align-items: center; }

@media (min-width: 561px) {
  header .wrap { display: grid; grid-template-columns: 1fr auto 1fr; }
  header nav { display: contents; }
  .nav-links { justify-self: center; }
  .nav-actions { justify-self: end; }
}
/* The soft grey rather than the muted one: at muted the bar read as switched
   off. Hover is the site's gold. The current page keeps its own brighter white
   and its gold underline, with or without the pointer on it, so a hovered link
   never looks more current than the page you are on. */
header nav a { color: var(--text-soft); text-decoration: none; font-size: 15px; }
header nav a:not(.btn):not([aria-current="page"]):hover { color: var(--gold); }
header nav a.btn { color: #fff; }

/* Where you are, in the bar you navigate with. It was possible to be three
   pages deep with nothing on screen saying which of the three you were on.
 *
 * The links stretch to the bar's full height so the mark can sit on the
 * header's own bottom rule rather than at some measured distance under the
 * text — a distance that would be wrong the moment the font size or the bar's
 * height moved. Buttons are left out of the stretch: a bar-tall Create
 * account is not a button any more.
 *
 * Two cues, not one. Without the brighter text, hovering any other link makes
 * it lighter than the page you are actually on, which reads backwards. */
/* Stretch is a chain and it is only as good as its weakest link. .wrap is the
   bar's height; the group has to stretch to it, and each link to the group,
   before the mark at bottom: -1px lands on the header's own border.

   It has now been broken twice in two different ways. Once by moving stretch
   off nav, which left nav at its content height of 44px so everything inside
   dutifully stretched to 44. Once by leaving it off .nav-actions while a real
   page link — Sign in — was living in there, so the mark hung in mid-air on
   the one page where it appeared. The second is why /account is with the
   pages now; the group it is in is the group that stretches.

   With nav at display: contents above 560px the groups are grid items of
   .wrap directly, so this is the whole chain rather than a link in it. */
.nav-links { align-self: stretch; }
header nav a:not(.btn) {
  align-self: stretch;
  display: inline-flex;
  align-items: center;
  position: relative;
}
header nav a[aria-current="page"] { color: var(--text); }
/* ---- the mark slides ---- */
/*
 * The mark moves between pages instead of appearing somewhere else. Two
 * documents, no script: a cross-document view transition, which is the one
 * thing CSS can do across a navigation.
 *
 * Named on the ::after rather than on the link, because the link is not what
 * moves — only one link has the mark at a time, and the browser pairs an old
 * and a new box by NAME. Old page's mark and new page's mark share one name,
 * so it is one box that changed position, and the default group animation
 * carries it there.
 *
 * The root animation is turned off deliberately. Left on, the whole page
 * cross-fades through itself on every navigation, and a sticky header that is
 * identical on both sides flickers for no reason. The only thing anybody
 * asked to move is the mark.
 *
 * A browser without this ignores every line of it and the mark jumps, which
 * is what it did before. Nothing is load-bearing here.
 */
@view-transition { navigation: auto; }
header nav a[aria-current="page"]::after { view-transition-name: nav-mark; }
::view-transition-group(nav-mark) {
  animation-duration: 220ms;
  /* Out of the gate quickly and settling into the new page, rather than a
     symmetrical ease that reads as slower than it is. */
  animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
}
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}
/* Somebody who has asked for less movement is asking about this exactly. */
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}
/* ---- end the mark slides ---- */

header nav a[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  /* One pixel below the bar, which is exactly where the header's border is, so
     the gold replaces the grey for this link's width instead of stacking on it. */
  bottom: -1px;
  height: 3px;
  background: var(--gold);
}


/* The menu button. Absent above the breakpoint, where the bar holds
   everything itself. It is a <button>, so it starts with the filled blue of
   every other button on the site and has to be talked back down to an icon. */
.nav-toggle {
  display: none;
  background: none;
  border: none;
  box-shadow: none;
  margin-left: auto;
  cursor: pointer;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  /* 44 square. The bars are 22px and would make a target half that, and this
     is the one control on a phone that has no second way to reach it. */
  width: 44px;
  height: 44px;
  padding: 0;
  color: var(--text-soft);
}
.nav-toggle span {
  display: block;
  height: 2px;
  width: 22px;
  border-radius: 2px;
  background: currentColor;
  transition: transform 0.18s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.12s linear;
}
.nav-toggle:hover { color: var(--text); background: none; transform: none; }
.nav-toggle:active { transform: none; box-shadow: none; }
/* Into a cross. The two outer bars meet in the middle — each is 7px from
   centre, which is the 2px bar plus the 5px gap — and the middle one goes. */
header.nav-open .nav-toggle span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
header.nav-open .nav-toggle span:nth-child(2) { opacity: 0; }
header.nav-open .nav-toggle span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

}

/* ---- type ---- */

/* The display face stops at the two big sizes. h3 is 16px and does the work of
   a label rather than a headline; Space Grotesk's wider letterforms cost
   legibility down there and buy nothing, and the eyebrow above each section is
   already carrying the character.

   Weight 600 is stated rather than left to the browser's default bold: only
   one weight ships, so an unstated 700 would be synthesised — the browser
   smearing the 600 sideways. */
h1, h2 {
  font-family: "Space Grotesk", "Segoe UI", system-ui, sans-serif;
  font-weight: 600;
}
h1 { font-size: 30px; line-height: 1.25; margin: 34px 0 10px; letter-spacing: -0.2px; }
h2 { font-size: 24px; margin: 0 0 12px; letter-spacing: -0.2px; }
h3 { font-size: 16px; margin: 22px 0 6px; }
p { margin: 0 0 14px; }
.lead { font-size: 18px; color: var(--text-soft); margin-bottom: 26px; line-height: 1.55; }
.muted { color: var(--text-muted); }
.small { font-size: 14px; }
strong { color: var(--text); font-weight: 600; }

/* The load-bearing phrase in a paragraph, so a page of prose has somewhere for
   the eye to land when it is skimming rather than reading.
   Gold, because gold already means "the thing that matters" everywhere else
   here — the credit figure, the item values. Weight alone was not enough
   against --text-soft body copy, and a second grey would have been invisible.
   One per paragraph. Two is a highlighter, and a highlighted page emphasises
   nothing. */
.key { color: var(--gold); font-weight: 600; }

/* A short uppercase label above a heading. Cheap hierarchy: it separates
   sections without a rule across the page, and gives the eye somewhere to
   land in a column that is otherwise all one size. */
.eyebrow {
  /* flex, not block, so the icon shares the label's row rather than sitting
     above it. Still a block-level box, so the margin below behaves as before. */
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 10px;
}
/* The icon carries the accent and the label stays muted. That way the eyebrow
   still reads as a signpost rather than competing with the heading beneath it,
   and the colour is the flair rather than the shouting.

   Drawn on a 24 viewBox like every icon in the extension, rendered at 17 so it
   sits with an 11.5px label without towering over it. flex: none so a long
   label can never squash it into an ellipse. */
.eyebrow svg {
  width: 17px;
  height: 17px;
  flex: none;
  color: var(--gold);
}

/* ---- sections ---- */

section { padding-top: var(--section); }

/* ---- hero ---- */

/* Asymmetric on purpose: text left, the product's own output right. A centred
   stack of heading, subheading and two buttons is the shape every generated
   landing page has, and this page has something better to put beside it. */
.hero {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(32px, 5vw, 64px);
  align-items: center;
  /* Was clamp(44px, 7vw, 84px). Eighty-four pixels of empty page under a
     sticky header is a lot to spend before the headline, and it was the
     single biggest reason the install row fell off a 1366x768 screen. */
  padding: clamp(28px, 4vh, 60px) 0 clamp(52px, 7vh, 76px);
  /* One screen, and the bottom padding above is the arrow's room.

     svh, not vh. On a phone vh is the viewport with the URL bar HIDDEN, so a
     100vh hero is taller than what you can actually see and the bottom of it,
     which is where the arrow lives, sits under the browser's own chrome. svh
     is the small viewport: the one you get with the bar showing. dvh would be
     worse than either, because it changes as the bar slides and the whole
     section would resize while somebody scrolls it.

     vh first as the fallback, then svh for anything that understands it. */
  min-height: calc(100vh - var(--header-h));
  min-height: calc(100svh - var(--header-h));
  align-content: center;
  /* The arrow is positioned against this, and so is the grid below. */
  position: relative;
  /* So the grid's z-index: -1 stays behind this section's content and cannot
     slip behind anything outside it. */
  isolation: isolate;
}

/* A faint inventory grid behind the hero — the home page's and, since it shares
   the .hero class, the guide's: both put their one card in the right column, and
   the grid gathers around it the same way on each. The stash is the product, and
   the inventory grid is the game's own picture of one. The game's, not the genre's
   — see "reaching for the genre instead of the game" in DESIGN.md.

   Full-bleed so the fade is all the mask's doing and nothing stops at the edge
   of a box; html's overflow-x: clip is what keeps that from widening the page.
   The mask is centred behind that card, so the grid gathers around the
   thing the page is showing and has faded out before the headline on the left
   and before the next section below. Lines at 6% white: at the 4.5% first
   tried, the grain drowned them entirely. */
.hero::before {
  content: "";
  position: absolute;
  z-index: -1;
  pointer-events: none;
  top: -40px;
  bottom: -40px;
  left: calc(50% - 50vw);
  right: calc(50% - 50vw);
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.06) 1px, transparent 1px);
  background-size: 52px 52px;
  background-position: center center;
  -webkit-mask-image: radial-gradient(ellipse 38% 55% at 66% 50%, #000 10%, transparent 100%);
          mask-image: radial-gradient(ellipse 38% 55% at 66% 50%, #000 10%, transparent 100%);
}

/* Growing and shrinking with the screen means the HEIGHT too, not just the
   width. A hero told to be exactly one screen tall has no slack left: whatever
   does not fit is simply cut off, and the tallest thing here is the item card
   at 525px, which is also the one thing the page cannot do without.

   So the card scales. --card-scale is the extension's own multiplier and every
   size in the card derives from it, which makes it the one lever that shrinks
   the card without redesigning it. */
@media (max-height: 820px) {
  .hero .card-demo { --card-scale: 0.92; }
}

/* From here the left column is the constraint, not the card. Measured at 660:
   the headline, lead, buttons, the free-credits line and the install row came
   to 528px against 536px of room, and it was the eight pixels of margin
   between each of them that put the arrow past the fold. Nothing is removed,
   everything is tightened. Cutting the install button off the bottom would be
   losing the one control the page is asking them to use. */
@media (max-height: 730px) {
  .hero .card-demo { --card-scale: 0.82; }
  .hero h1 { font-size: clamp(1.75rem, 3.4vw, 2.45rem); margin-bottom: 12px; }
  .hero .lead { font-size: 16px; margin-bottom: 14px; }
  .get-ext { margin-top: 10px; padding-top: 12px; }
  .hero { padding-top: 22px; padding-bottom: 46px; }
}

@media (max-height: 660px) {
  .hero .card-demo { --card-scale: 0.74; }
  .hero h1 { font-size: clamp(1.55rem, 3vw, 2.05rem); margin-bottom: 9px; }
  .hero .lead { font-size: 15px; margin-bottom: 11px; }
  .get-ext { margin-top: 7px; padding-top: 9px; }
  .hero { padding-top: 16px; padding-bottom: 40px; }
}

/* The floor, and it is an honest one. Below about 600 of usable height there
   is no arrangement of a headline, a lead, two buttons, an install row and a
   full item card that is both one screen and readable. Squeezing further would
   be shipping six-point type to keep a promise about a rectangle. The hero
   goes back to its natural height, the page scrolls as pages do, and the arrow
   still says there is more. */
@media (max-height: 600px) {
  .hero { min-height: 0; }
}

/* Below the two-column breakpoint the hero stacks, and a stacked headline,
   lead, buttons, install row and a full item card do not fit one phone screen
   at any size a person can read. Forcing it would cut the card in half, so
   this is the one place the rule is let go: natural height, and the arrow
   still says there is more. */
@media (max-width: 860px) {
  .hero { min-height: 0; }
}

/* Back to top. Bottom right, over the page rather than in it, and only once
   there is a page above you.

   A panel rather than a button colour: the blue is the site's one action
   colour and it means "this is the thing to press". This is not that. It is a
   convenience that should be findable and never compete with an install
   button or a Buy.

   Bottom is in svh, like the hero: on a phone a vh offset puts it under the
   browser's own chrome, which is exactly where a thumb reaches for it. */
.to-top {
  position: fixed;
  right: clamp(14px, 3vw, 28px);
  bottom: clamp(14px, 3vh, 28px);
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  padding: 0;
  background: var(--panel-bg);
  border: 1px solid var(--border);
  border-radius: 50%;
  color: var(--text-soft);
  cursor: pointer;
}
.to-top:hover {
  background: var(--panel-hover);
  color: var(--text);
  border-color: var(--gold);
}

/* The arrow. transform for the bob and translate for the centring, because
   they are separate properties now and compose: one animation does not have to
   carry the other's offset. */
.hero-more {
  position: absolute;
  left: 50%;
  bottom: clamp(8px, 2vh, 20px);
  translate: -50% 0;
  display: inline-flex;
  padding: 6px;
  color: var(--text-muted);
  animation: hero-bob 2.2s ease-in-out infinite;
}
.hero-more:hover { color: var(--text); }
@keyframes hero-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(7px); }
}
/* Motion is decoration in this product and never information, so the arrow
   still points down standing still. */
@media (prefers-reduced-motion: reduce) {
  .hero-more { animation: none; }
}

/* Pricing's first screen: the head and the packs together, sized and
   positioned exactly like the hero on the other two pages. Its own selector
   rather than reusing .hero, because .hero is a two-column grid and this is
   a plain stack. */
.hero-screen {
  position: relative;
  min-height: calc(100vh - var(--header-h));
  min-height: calc(100svh - var(--header-h));
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Matched to the hero on the other two pages, which is the whole point of
     the complaint this answers: the same air above and below, so the three
     first screens read as one site rather than two roomy pages and a
     cramped one. */
  padding: clamp(28px, 4vh, 60px) 0 clamp(52px, 7vh, 76px);
}
/* The head already spends its own padding on the break above it, and inside
   a centred screen with padding of its own that is a third helping. */
.hero-screen .page-head { padding-top: 0; }

/* The cards on the first screen run a little tighter than a card further down
   the page would. Measured against the other two first screens at 900: the
   home page had 138px of air above its content and the guide 149, and this had
   61. The difference is not padding, it is that three price cards, a head and a
   payment row is simply more content than a headline and one item card, so the
   cards give some back and the centring hands it to both ends. */
.hero-screen .pack { padding: 18px 18px 20px; }
.hero-screen .pack .count { font-size: 30px; }
.hero-screen .packs { margin-top: 18px; }
@media (max-height: 600px), (max-width: 860px) {
  .hero-screen { min-height: 0; padding-bottom: 48px; }
  .hero-screen .page-head { padding-top: clamp(38px, 6vw, 68px); }
}

/* Pricing's screen used to squeeze rather than shorten.

   The first version of this ladder chased the arrow down the page by taking
   padding out: card padding to 12, the head's own space to nothing, and the
   result was a screen with the prices jammed against the top and the bottom
   while the home page and the guide both breathed. Squeezing the frame is
   the wrong lever; the right one is having less to fit.

   Two lines came out of the content instead. The lead lost a clause it did
   not need, and every card stopped repeating "Credits never expire" directly
   under a lead that had just said it. Three packs, one line each, plus the
   lead: that is most of what the padding was paying for.

   So this is gentler than it was, and only from 780 down. Above that the
   page is left alone entirely. */
@media (max-height: 780px) {
  /* The generous top padding is for a tall window. On a laptop it is 31px
     spent above a head that is already the first thing on the page, and it
     was enough to push the arrow past the fold. */
  .hero-screen { padding-top: 10px; }
  .hero-screen .page-head h1 { font-size: clamp(1.75rem, 3.6vw, 2.35rem); }
  .hero-screen .packs { margin-top: 16px; }
  .hero-screen .pack { padding: 18px 16px 20px; }
  /* The checkout status line holds 18px open for a message that is absent
     almost always. On a screen being asked to fit exactly, that is space
     between the prices and the arrow doing nothing. */
  .hero-screen #packStatus { min-height: 0; margin-top: 2px; }
}

@media (max-height: 700px) {
  .hero-screen { padding-top: 4px; }
  .hero-screen .page-head .lead { font-size: 16px; }
  .hero-screen .packs { margin-top: 12px; gap: 14px; }
  .hero-screen .pack { padding: 14px 14px 16px; }
  .hero-screen .pack .count { font-size: 29px; }
}

/* Same floor as the hero, and the same reason: below this there is no
   arrangement of a head, three cards and an arrow that is one screen and
   still readable, and the prices are the last thing that should shrink. */
@media (max-height: 640px) {
  .hero-screen { min-height: 0; }
}

/* The arrows land you here, and the header is sticky. */
#how, #steps, #questions { scroll-margin-top: calc(var(--header-h) + 16px); }
.hero h1 {
  font-size: clamp(2.1rem, 5.2vw, 3.35rem);
  line-height: 1.08;
  letter-spacing: -0.025em;
  /* 600, not 700. This said 700 when the headings were Segoe UI, which has a
     bold to give. Only one weight of the display face ships, so 700 here had
     the browser stretching the 600 sideways into a fake bold — subtly mushy at
     53px, which is exactly the size where it shows. */
  font-weight: 600;
  margin: 0 0 18px;
  max-width: 15ch;
}
.hero .lead { max-width: 46ch; margin-bottom: 20px; }
/* The install row. Its own line rather than a row of buttons: four buttons in
   a row reads as four equal choices, and installing the extension is a
   different KIND of action from making an account. */
.get-ext {
  display: flex;
  flex-wrap: wrap;
  /* flex-start rather than center, because the items here are not the same
     height: each slot is a button with a version number under it, so centring
     put the label halfway down THAT and it sat between the button and the
     number. The label gets the button's own height below and centres inside
     it, which is what lines it up with the words on the buttons. */
  align-items: flex-start;
  gap: 10px;
  margin-top: 16px;
  padding-top: 16px;
  position: relative;
  /* Centred under the paragraphs rather than hugging the left edge. The
     label and the button together are about 290px of a 500px column, so
     left-aligned they sat with 200px of nothing to their right, under two
     paragraphs that fill the measure. */
  justify-content: center;
}
/* No rule above the row any more. It was a full-width 1px line in --border-soft,
   and the inventory grid behind the hero draws 1px lines at almost exactly that
   shade — so it read as one of the grid's own lines, slightly out of step with
   the rest. The label and the space above already set the row apart. */
.get-label {
  font-size: 11.5px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-right: 2px;
  /* The button's own box, stated the way .btn.browser-btn builds it: 13.5px
     text at the 1.6 line-height every .btn is given, plus its 9px of padding
     top and bottom. If that rule's padding or size changes, change this with
     it — the two are a pair, and nothing else keeps them honest. */
  display: flex;
  align-items: center;
  min-height: calc(13.5px * 1.6 + 18px);
  /* Ahead of both buttons, whichever one home.js promotes. Without this the
     promoted button jumps in front of the label and it ends up sitting between
     the two, reading as a caption for the wrong one. */
  order: -2;
}
/* Two classes rather than one: "button, .btn" is defined further down this file
   and sets background and padding, so a single-class rule up here loses to it
   on source order. .btn.browser-btn wins on specificity from wherever it sits,
   which is the fix that does not depend on where the rule happens to live. */
/* inline-flex so the mark and the words share a baseline and a gap, rather
   than the logo sitting on the text baseline with a descender's worth of
   misalignment. */
.btn.browser-btn {
  font-size: 13.5px;
  /* Less on the left than the right. The mark is lighter than the words beside
     it, so equal padding reads as too much space before the logo and too
     little after the label. Optical centring, not measured centring. */
  padding: 9px 16px 9px 12px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
/* Mozilla asks for clear space around the mark. The gap above and the button's
   own padding provide it; this stops the SVG being stretched by any inherited
   width rule. */
.btn.browser-btn img { width: 18px; height: 18px; flex: none; display: block; }

/* Each button and its version number are one column, so the row can still be a
   row and the number still sits under the thing it describes. Left-aligned to
   the button's own left edge rather than centred: centred, a short number
   under a wide button reads as a caption for the row rather than for that
   button. */
.get-slot { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; }

/* The browsers it works in, in words. Five more logos would be five more
   marks to source, license and keep current, and none of them would answer
   the question the words answer directly, which is "what about Brave". */

/* Arriving here from an Install link somewhere else on the site.

   scroll-margin first, and it is the part that actually matters: the header is
   sticky and --header-h tall, so a bare anchor jump puts the row underneath it. The
   reader is sent to the one control they need and it is behind the navigation.

   Then the ring. Three pulses and done, rather than something that keeps
   breathing at you: it is saying "here", and once it has been read it has
   nothing left to say. Blue because that is the action colour, and the same
   ring vocabulary the chosen pack card uses rather than a new effect. */
/* Arriving here from an Install link somewhere else on the site.

   It used to be an anchor jump, which put the row at the top of the page and
   the rest of the home page below it. The ring turned out to do that job on
   its own: with something pointing at the row, the reader can land on the page
   they were going to land on anyway and still be told exactly where to look.
   So the link carries ?install=1 now, js/home.js turns that into this class,
   and nothing scrolls.

   Three pulses and done, rather than something that keeps breathing at you. It
   is saying "here"; once read it has nothing left to say. */
#get {
  border-radius: var(--radius);
}
#get.attention {
  animation: get-attention 1.4s ease-out 3;
}
@keyframes get-attention {
  from { box-shadow: 0 0 0 0 rgba(59, 120, 184, 0.5); }
  to { box-shadow: 0 0 0 16px rgba(59, 120, 184, 0); }
}
/* Motion is decoration in this product and never information, so the reduced
   version says the same thing standing still. */
@media (prefers-reduced-motion: reduce) {
  #get.attention {
    animation: none;
    outline: 2px solid var(--btn-border);
    outline-offset: 10px;
  }
}
.get-version {
  font-size: 11.5px;
  color: var(--text-muted);
  padding-left: 2px;
}

@media (max-width: 860px) {
  .hero { grid-template-columns: 1fr; gap: 34px; }
  .hero h1 { max-width: 18ch; }
}

/* ---- the item card ----
 *
 * The signature component, and the argument the page is making. Everything
 * else here claims the tool reads items accurately; this shows one.
 *
 * These rules are a copy of the inventory page's own card, class for class and
 * value for value, so a visitor sees the real thing rather than a tidied-up
 * impression of it the product then fails to live up to. Every calc() below is
 * the extension's, --card-scale included, so the two can be diffed by eye.
 *
 * That includes the 5px quality spine down the left edge, which is a pattern
 * usually worth avoiding: a coloured stripe beside a block is the reflex
 * accent and rarely means anything. Here it does mean something, it is already
 * shipping, and options.html records why it beat a full-colour fill: at
 * sixty-plus items the saturated cards became a quilt. Matching what ships
 * wins over the general rule.
 *
 * Colour is still not the only channel. The RW / SET tag before the name says
 * the quality in letters, which is what a colour-blind trader reads.
 *
 * Scoped under .card-demo because the extension's class names collide with
 * this stylesheet's own: .tag is the pricing flag here and a quality badge
 * there, and .item-name and .item-stats are used by both.
 */

/* Capped, and centred in its column. The inventory grid is
   minmax(260px, 1fr), so a real card is rarely much wider than this; letting
   it stretch to fill the hero column would set stat lines across 420px and
   stop looking like the thing it is copying. */
.card-demo { --card-scale: 1; --card-w: 380px; max-width: var(--card-w); margin: 0 auto; }

.card-demo .inventory-item {
  background: var(--panel-bg);
  border: 1px solid var(--border);
  /* Colour set inline per item, exactly as the extension does it. */
  border-left: 5px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}

/* Lifted off the page. This card is the one thing in the hero that is a real
   artefact rather than a picture of one, and it sat flat on the background
   gradient with a hairline border as the only thing separating them.
   Two layers, both black — a tight one for the edge and a wide soft one for
   the lift. Same colour on purpose: the pricing card learned that a coloured
   glow and a black shadow overlapping in the same pixels composite into mud.
   Lightened from .30/.42: at full strength the card read as cut out and
   dropped onto the page rather than resting on it, and the dark pool under it
   was the most contrast in the hero after the headline.

   The guide's step index shares it, from one rule so the two cannot drift: it
   is the same object in the same place, the one card in its hero's right
   column, and it sat flat beside a home page whose card did not. */
.card-demo .inventory-item,
.guide-index {
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18), 0 16px 38px rgba(0, 0, 0, 0.24);
}
.card-demo .item-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: calc(8px * var(--card-scale));
  padding: calc(10px * var(--card-scale)) calc(14px * var(--card-scale));
  /* Two lines of title plus padding, so every card in a grid is the same
     height whether or not its name wraps. */
  min-height: calc(62px * var(--card-scale));
  /* The expanded state's radius: square where it meets the body below. */
  border-radius: 0 5px 0 0;
}
.card-demo .item-title { flex: 1; min-width: 0; }
.card-demo .item-name-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.card-demo .item-name {
  font-weight: bold;
  font-size: calc(16px * var(--card-scale));
  /* Colour set inline: the quality colour for this item. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
/* RW / SET / ETH. One base style, colour per type. The properties reset here
   are this stylesheet's own .tag, which is a different component entirely. */
.card-demo .tag {
  flex-shrink: 0;
  display: inline-block;
  font-size: calc(9.5px * var(--card-scale));
  font-weight: 800;
  letter-spacing: 0.09em;
  line-height: 1;
  text-transform: none;
  padding: calc(3px * var(--card-scale)) calc(6px * var(--card-scale));
  border-radius: 4px;
  margin: 0;
}
.card-demo .tag-rw {
  color: var(--tag-rw);
  border: 1px solid var(--tag-rw);
  background: color-mix(in srgb, var(--tag-rw) 14%, transparent);
}
/* Removed as dead when the only demo card was a unique, and back because the
   guide now shows a set piece. Kept beside tag-rw rather than reinvented: the
   badge is the extension's, and the two have to look like one family. */
.card-demo .tag-set {
  color: var(--tag-set);
  border: 1px solid var(--tag-set);
  background: color-mix(in srgb, var(--tag-set) 14%, transparent);
}
.card-demo .inventory-item + .inventory-item { margin-top: 12px; }

.card-demo .item-category {
  display: block;
  margin-top: calc(3px * var(--card-scale));
  font-size: calc(11px * var(--card-scale));
  font-weight: normal;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The owner's own note. Italic and muted because every other line on this card
   came off a tooltip and this one did not. */
.card-demo .item-note {
  display: block;
  margin-top: calc(3px * var(--card-scale));
  font-size: calc(11px * var(--card-scale));
  font-style: italic;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card-demo .item-header-right {
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  color: var(--text-muted);
}
.card-demo .item-price-tag {
  color: var(--price);
  font-weight: bold;
  font-size: calc(12px * var(--card-scale));
}
/* No padding: the tooltip carries its own, so its black reaches the card's
   edges the way it does in the extension. */
.card-demo .item-body {
  padding: 0;
  border-top: 1px solid var(--border-soft);
}
.card-demo .item-stats {
  font-size: calc(12px * var(--card-scale));
  /* The tooltip, as the extension draws it: a near-black block with the lines
     in the game's own colours. The colour lives on the CONTAINER so the lines
     inherit it. It once sat on `.card-demo .item-stats div`, specificity
     (0,2,1), and beat every per-line rule — an inherited value loses to any
     rule matching a line directly, which is what lets the white base line
     below win. */
  color: var(--tip-stat);
  background: var(--tip-bg);
  padding: calc(10px * var(--card-scale)) calc(14px * var(--card-scale)) calc(12px * var(--card-scale));
}
/* Stat lines are divs, and the rolled figures are bolded rather than
   recoloured. Nothing here is gold: the emphasis the tool applies is weight,
   and the page has no business inventing a brighter one. */
.card-demo .item-stats div {
  max-width: none;
}
/* What the item is rather than what it does: its defense and requirements.
   White, as the game prints them and as the extension draws them. */
.card-demo .s-meta { color: var(--tip-meta); }


/* The caption under the hero card. It has a job: without it the card could be
   mistaken for a stock screenshot rather than the tool's own output. */
.item-caption {
  margin-top: 12px;
  font-size: 12.5px;
  color: var(--text-muted);
  max-width: 44ch;
}

/* ---- page head ----
 *
 * A page that is not the landing page but is still selling: same display type
 * as the hero, no card beside it, so it opens with weight instead of dropping
 * to a document heading. */

.page-head { padding: clamp(38px, 6vw, 68px) 0 0; }
.page-head h1 {
  font-size: clamp(1.9rem, 4.4vw, 2.8rem);
  line-height: 1.1;
  letter-spacing: -0.022em;
  /* 600 for the same reason as .hero h1 above: there is no 700 of the display
     face to load, so asking for one gets a synthesised approximation. */
  font-weight: 600;
  margin: 0 0 16px;
  max-width: 18ch;
}
.page-head .lead { max-width: 54ch; margin-bottom: 0; }

/* ---- split ----
 *
 * Two columns of related-but-not-parallel content. Used for what is free
 * against what costs, where the two lists are deliberately different lengths
 * and the imbalance is the argument. */

.split {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 30px 48px;
  margin-top: 26px;
}
.split > div > h3 { margin-top: 0; }
.split ul { list-style: none; margin: 12px 0 0; padding: 0; }
.split li {
  max-width: none;
  padding: 8px 0;
  border-bottom: 1px solid var(--border-soft);
  font-size: 14.5px;
  color: var(--text-soft);
}
.split li:last-child { border-bottom: none; }
.split p { font-size: 14.5px; }
/* The caveat under a method, rather than another sentence of it. .split p is
   (0,1,1) and .small is (0,1,0), so .small alone loses here and the note came
   out the same size as the instruction above it. */
.split p.small { font-size: 13.2px; margin-top: 8px; }
/* Three ways to take a screenshot, so three columns. The two-column default
   strands the third on a row of its own, which reads as an afterthought — and
   the one it would strand is the fallback somebody reaches for precisely
   because the first two did not work for them. */
.split.three { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 900px) {
  .split.three { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  .split { grid-template-columns: 1fr; }
  /* Restated for .three, which is (0,2,0) and would otherwise hold two
     columns all the way down to a phone. */
  .split.three { grid-template-columns: 1fr; }
}

/* ---- claims ----
 *
 * The old "what it gets right" list. Five bullets read as five equal facts;
 * they are not equal, and the two that matter have their own proof above. So:
 * a plain two-column grid of short claims. Deliberately not identical
 * icon-heading-text cards, which is the shape this would become by reflex.
 */

/* Two columns for the same reason the quality grid has three: there are four
   claims, and three auto-fit columns strand the fourth on a row of its own.
   Two also gives each claim a wider measure than a third of the wrap would. */
/* A list, not a grid.

   Two columns works for an even number of short things and there are five of
   these, so one was always stranded on a row of its own beside a gap. They
   are also not equal in length: the refund answer runs to two paragraphs and
   the account one to two lines, so the grid left ragged holes between rows
   and nothing lined up with anything.

   One column with a rule between each gives every answer the same shape and
   the same rhythm down the page, and the measure stays readable because the
   column is capped rather than filling the wrap. */
.claims {
  /* Left, with the heading above it, not centred. Centred put the list 189px
     in from an eyebrow and an h2 sitting at the wrap edge, which reads as
     alignment gone wrong rather than as a centred design, and every other
     section on the site starts at the same left edge. */
  margin-top: 28px;
  max-width: 66ch;
}
/* A green check for each point, a rule between each one. The check says the
   thing the section is about — these are what it gets right — where the gold
   square it replaced only said "a list starts here".

   The elbow arrow under each point went: with the mark already showing where
   an item starts and the indent already showing the text belongs to it, a
   third mark was a third thing to look at on a list whose whole job is being
   quick to read.

   The rule came back with it. It was dropped when the elbow arrived, on the
   grounds that a divider and a bullet both say "new item" and two devices
   doing one job looks busy. That was true of three devices and is not true of
   two: the check marks the point, the rule separates the pair from the next
   pair, and they are saying different things. */
.claim + .claim {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--border-soft);
}
.claim h3 {
  position: relative;
  margin: 0 0 5px;
  padding-left: 24px;
  font-size: 15.5px;
  /* Stated, because the check is centred on the first line by arithmetic on
     it: half of what the line leaves after the icon's own 15px. */
  line-height: 1.35;
  color: var(--text);
}
/* Drawn, not typed. A check glyph is centred by its font's metrics rather than
   its shape, and the price green on a mask means the colour is the token and
   not a copy of it inside the image. A data: image, which img-src allows. */
.claim h3::before {
  content: "";
  position: absolute;
  left: 0;
  top: calc((1.35em - 15px) / 2);
  width: 15px;
  height: 15px;
  background: var(--price);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.8 8.6l3.3 3.3L13.2 4.6' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.8 8.6l3.3 3.3L13.2 4.6' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
/* Indented to the heading's text, not to the check, so the text sits under
   the words it belongs to rather than under the mark. */
.claim p {
  margin: 0;
  padding-left: 24px;
  font-size: 14.5px;
  color: var(--text-soft);
  max-width: none;
}
.claim p + p { margin-top: 8px; }

/* ---- panels ---- */

.panel {
  background: var(--panel-bg);
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  padding: 22px;
  margin: 18px 0;
}

/* ---- forms ---- */

label {
  display: block;
  font-size: 11.5px;
  color: var(--text-muted);
  margin: 14px 0 5px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
input[type="email"], input[type="password"], input[type="text"], select, textarea {
  width: 100%;
  padding: 12px 13px;
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: var(--radius);
  color: var(--text);
  font-size: 15px;
  font-family: inherit;
}
input::placeholder, textarea::placeholder { color: var(--placeholder); opacity: 1; }
/* Gold on focus, the same as the extension's fields. */
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--gold); }
input[type="password"] { letter-spacing: 0.08em; }
/* The support form's two field types, which nothing else on the site had. A
   textarea is inline by default and leaves a descender's gap under itself;
   block closes it. No resize handle: the message is capped at 1,000
   characters and the box already fits them, so the grip in the corner was
   clutter with nothing to do. */
textarea { display: block; min-height: 150px; resize: none; line-height: 1.5; }
/* The browser's own arrow sat hard against the right edge while the text sat
   13px in from the left, and it cannot be moved. This one is drawn, and set
   13px in from the right, so the two gaps match. The padding keeps a long
   option from running underneath it. A data: image, which the CSP's img-src
   allows. */
select {
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  padding-right: 38px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%23999999' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
  background-size: 12px 8px;
}

/* A quicker way than waiting for a reply, shown under the topic it answers.
   Bordered like a field rather than drawn as a second panel, because panels
   do not nest. */
.support-hint {
  margin: 12px 0 0;
  padding: 11px 13px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  font-size: 14px;
  color: var(--text-soft);
}
.field-count {
  margin-top: 5px;
  font-size: 12px;
  color: var(--text-muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.field-count.full { color: var(--gold); }
/* The support form's honeypot. Off-screen rather than display:none, because
   the scripts it exists for tend to skip fields that are not rendered. The
   markup keeps it away from keyboards and screen readers. */
.hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }

button, .btn {
  display: inline-block;
  background: var(--btn);
  color: #fff;
  border: none;
  padding: 11px 18px;
  border-radius: 5px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 700;
  font-family: inherit;
  /* Stated, because otherwise the element type decides it: an <a class="btn">
     inherits the body's 1.6 and a <button> takes the UA default, which is six
     pixels shorter at this size. Both kinds exist on this site and they sit
     side by side in the header, where "Create account" stood 44px tall next to
     an "Open your stash" that stood 38. */
  line-height: 1.6;
  text-decoration: none;
  text-align: center;
  /* How far the hover lifts. Named because two rules need it and they must
     never disagree: the transform below moves the button up by it, and the
     hit-area extension moves the bottom edge down by the same amount. */
  --lift: 4px;
  /* Anchors that extension. */
  position: relative;
  /* The fill is a gradient, and browsers do not interpolate between two of
     those, so the colour swaps instantly while the lift eases. That is listed
     here deliberately rather than hopefully: naming background-color in this
     list would look like it animates when nothing would happen. The instant
     swap is fine — the lift is what carries the motion. */
  transition:
    transform 0.16s cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 0.16s cubic-bezier(0.22, 1, 0.36, 1);
}

/* The hover takes the button's hit area up with it, uncovering the strip it
   was just sitting on. A pointer resting near the bottom edge then loses the
   button, which drops back down under the pointer, which hovers it again — a
   flicker loop, and it happens exactly where a slow mouse or a thumb comes to
   rest, because that is the edge you approach from.
 *
 * So the hit area keeps the ground it came from. This box hangs one lift below
 * the button, and when the button rises the box is what stays behind covering
 * the gap. It paints nothing — no background, no border — and a pseudo-element
 * cannot be an event target, so a click here still lands on the button.
 *
 * At rest it makes the bottom edge four pixels more forgiving than it looks,
 * which is a gift rather than a bug on a touch screen. */
button::after, .btn::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: calc(-1 * var(--lift));
  border-radius: inherit;
}
/* Hover lifts four pixels and changes colour. Four things have been tried in
   this slot and three are gone: a scale, a drop shadow, a full-face gradient,
   and a bright line along the top edge — that last one especially, which drew
   the eye to a seam rather than to the button.
 *
 * The colour swap works by redefining the fill TOKENS rather than setting a
 * background here. Each variant's own rule already says
 * "background: var(--btn-secondary)" and the like, and a custom property
 * re-resolves when it changes, so one rule recolours primary, secondary and
 * danger to their own hover fills. Setting a background here instead would
 * need a rule per variant, each having to out-specify this one, and a
 * secondary button would go primary blue the first time one was missed.
 *
 * The gold outline button is untouched by it: its fill is "none", not a
 * token, and it keeps its own tint below. */
button:hover, .btn:hover {
  --btn: var(--btn-hover);
  --btn-secondary: var(--btn-secondary-hover);
  --btn-danger: var(--btn-danger-hover);
  color: #fff;
  transform: translateY(calc(-1 * var(--lift)));
}
/* The press. Back down to the resting line, keeping the hover colour, with
   shadow falling in from the top edge to flatten the face. Fast, because a
   press that eases feels like lag rather than a button. */
button:active, .btn:active {
  transform: translateY(0);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.26);
  transition-duration: 0.06s;
}
/* Buttons had no focus ring at all — only inputs did — so the pricing page
   could be tabbed through with nothing visible. Gold, and the same offset
   idiom as the inputs' gold focus border. */
button:focus-visible, .btn:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}
button:disabled { opacity: 0.5; cursor: default; filter: none; }
/* Disabled has to beat the hover and active rules above, which it does on
   specificity: two classes-worth against one. Without this the shop-closed
   buttons on /pricing still grew and lit up under the pointer, which promises
   a click that does nothing. */
button:disabled:hover, button:disabled:active {
  transform: none;
  filter: none;
  box-shadow: none;
  /* Puts the three fills back to the resting ones. `inherit` rather than the
     literals: custom properties inherit, nothing between :root and a button
     redefines these, so this resolves to exactly what :root declares and
     cannot drift from it. Secondary and danger both get disabled in practice
     — Load more, and Permanently delete — so all three have to be undone, not
     just the primary. */
  --btn: inherit;
  --btn-secondary: inherit;
  --btn-danger: inherit;
}
/* Light grey carries a dark label, and the dark grey hover a white one. The
   hover rule has to be stated: this rule's dark label comes after the shared
   hover's white at the same specificity and would otherwise win, leaving dark
   text on the dark fill. A disabled button keeps its light fill under the
   pointer, so it keeps the dark label too.

   No lift: the light-to-dark change already says the button is live, and
   rising four pixels as well was too much for the lesser button in a row.
   Zeroing --lift rather than overriding the transform also collapses the
   hit-area extension that exists to cover the gap a lift leaves. */
button.secondary, .btn.secondary { background: var(--btn-secondary); color: #1a1a1a; --lift: 0px; }
button.secondary:not(:disabled):hover, .btn.secondary:hover { color: #fff; }
button.danger, .btn.danger { background: var(--btn-danger); }

/* Set apart rather than shouted at. A red border is enough to say this
   section is different in kind; a red panel would make the whole page feel
   like a warning. */
.danger-zone { border-color: var(--danger-border); }
.danger-zone label { display: block; margin-bottom: 6px; }
.danger-zone input { max-width: 320px; }
button.wide { width: 100%; margin-top: 18px; }

/* The extension's own button, in the header on every page, rightmost. Only
 * ever visible to someone who has the extension installed.
 *
 * Gold outline, not a gold fill. Gold is the accent in this palette and a
 * filled gold button would become the largest gold surface on the site, which
 * the palette rules out. The outline also keeps it distinguishable from the
 * blue Create account button sitting next to it, which a second blue button
 * would not be. Gold on the header panel is 10.51:1, and 7.39:1 against its
 * own hover tint, so both states clear AA comfortably. */
/* The install action, which stands in the same corner as the inventory button
   and never at the same time as it. Same geometry so the corner does not
   change size when somebody installs the extension and comes back.

   Blue rather than the inventory button's gold outline, because they are
   different kinds of thing: gold-outline is "open the tool you have", blue is
   the one action this page wants from you. */
.btn-install { padding: 10px 14px; }

.btn-inventory {
  background: none;
  border: 1px solid var(--gold);
  color: var(--gold);
  /* Same font and line-height as .btn now, and 10 of padding plus 1 of border
     is the 11 of padding .btn has, so the two come out identical without any
     number here depending on what the text measures. The horizontal padding
     stays narrower; that is the part that is meant to differ. */
  padding: 10px 14px;
}
.btn-inventory:hover {
  /* The same 14% tint the item card's quality tags use. This button's fill is
     "none" rather than a token, so the shared hover's token swap does not
     reach it and the tint is its whole colour change. The lift still comes
     from the shared rule. */
  background: color-mix(in srgb, var(--gold) 14%, transparent);
  color: var(--gold);
}
.btn-inventory:active { box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.24); }
/* One label for the header, a shorter one for a phone. Both are real text and
   only one is ever displayed, so a screen reader announces one of them.
   "Open your stash" plus the brand plus two links does not fit 375px. */
.label-narrow { display: none; }
@media (max-width: 560px) {
  .label-wide { display: none; }
  .label-narrow { display: inline; }
  .btn-inventory,
  .btn-install { padding: 8px 11px; font-size: 13px; }
}

.status { min-height: 20px; margin-top: 12px; font-size: 13px; color: var(--gold); }
/* Above the cards rather than below, so the margin goes the other way. Centred
   because it spans the three packs and reads as a banner over them, where
   #packStatus below is a line of feedback under one of them.

   The auto margins matter: main p caps at 68ch for readability, so without them
   the box sits over the first card and only its text is centred, which looks
   like a mistake rather than a caption. balance keeps the two lines even
   instead of orphaning the last word. */
.beta-notice { margin: 0 auto 22px; text-align: center; text-wrap: balance; }
.status.error { color: #ff8a8a; }
.status.ok { color: var(--price); }

/* ---- packs ---- */

/* Half the section gap the rest of the site uses, because this section follows
   a page head that has already spent 68px of its own on the same break. Two
   full gaps stacked put the cards 118px below the words, and on a 1366x768
   laptop that was enough to cut the bottom off them. */
#packs-section { padding-top: clamp(26px, 4vw, 48px); }
.packs { display: grid; grid-template-columns: repeat(auto-fit, minmax(232px, 1fr)); gap: 16px; margin: 22px 0 6px; }
/* Tighter than the generic .status, and deliberately so: this one sits under
   the cards holding a line open for checkout feedback that is absent almost
   always, where the generic rule is used where a message is the normal case. */
#packStatus { margin-top: 6px; min-height: 18px; }
/* A column, so the Buy button can be pushed to the bottom with margin-top:auto
   and the three buttons line up even though the packs list different numbers of
   things. The alternative was padding the cheapest pack's list with a filler
   line to match the others, which is a sentence written to fill a gap. */
.pack {
  display: flex;
  flex-direction: column;
  /* Top stop is --panel-bg exactly, and the gradient only goes darker from
     there. tests/palette.js measures text against the flat --panel-bg token, so
     a stop lighter than it would leave that check passing while the real
     surface underneath the text had moved. */
  background: linear-gradient(180deg, var(--panel-bg), var(--bg-grad-b));
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  padding: 22px 20px;
  text-align: center;
  transition: transform 0.18s cubic-bezier(0.22, 1, 0.36, 1),
              border-color 0.18s ease, box-shadow 0.18s ease;
  /* Layers, so a glow always sits beneath the cards beside it. A shadow belongs
     to its card, and a card paints over whatever came before it on the page, so
     the popular card's bloom lay across the card to its left and a lifted
     card's shadow across its neighbors. Plain cards sit highest, the popular
     card one below them, and the card being hovered lowest of all, so every
     glow ends up underneath every card it reaches. */
  position: relative;
  z-index: 2;
}
/* Hovering draws the edge in green, and nothing else about the lift changes. */
.pack:hover {
  z-index: 0;
  transform: translateY(-3px);
  border-color: var(--price);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.30);
}
/* The outlines that hold the cards' place while the packs load. The heights are
   the rendered cards', measured: 348px, except the cheapest pack, which has no
   saving line and stands 320px once the cards stack on a phone. Side by side
   the grid row stretches all three to the tallest anyway, so the same numbers
   hold at both widths and nothing moves when the real cards arrive. Inert:
   no hover lift, no green edge, nothing to press. */
.pack-placeholder {
  min-height: 348px;
  pointer-events: none;
}
.pack-placeholder:first-child { min-height: 320px; }
/* The flagged pack: gold edge and a gold bloom under it. The bloom is the only
   coloured shadow on the site and it is deliberately weak — at full strength
   the card glowed like a button and read as the only one you were allowed to
   press. */
.pack.best {
  z-index: 1;
  border-color: var(--gold);
  box-shadow: 0 0 22px rgba(255, 204, 0, 0.09);
}
/* ONE shadow, and gold. This used to stack the gold glow over the same black
   drop shadow the plain cards use, and the two sitting in the same few pixels
   turned the gold olive — a dirty yellow that looked like a rendering fault
   rather than a highlight. The lift is still there; translateY does that, and
   the glow does the job the drop shadow was there for. */
/* Gold at rest, and green while it is hovered, the same as the other two. The
   bloom turns green with the edge so the two never disagree about the card's
   color. rgba(136, 255, 136) is --price, which a shadow cannot take as a
   variable with an alpha of its own. */
.pack.best:hover {
  z-index: 0;
  border-color: var(--price);
  box-shadow: 0 0 30px rgba(136, 255, 136, 0.30);
}
/* The pack somebody pressed Buy on before they had an account. They get sent
   away to sign in or to their inbox and come back here, and without this they
   come back to three identical cards and have to remember which one they
   wanted.

   Blue rather than gold, and the gradient's own first stop so the mark and the
   button it points at are the same colour. Gold already means "popular", and
   the two claims can land on the same card — the card you chose is not an
   argument about which card to choose. */
.pack.chosen {
  border-color: var(--btn-border);
  box-shadow: 0 0 0 1px var(--btn-border), 0 10px 26px rgba(0, 0, 0, 0.30);
}
/* Beats .pack.best on specificity, (0,3,0) against (0,2,0), so the flagged
   card still shows which one was picked. */
.pack.best.chosen { border-color: var(--btn-border); }
/* Only on the flagged card. The others render the slot with a non-breaking
   space to keep the three figures on one line, and a pill around that would
   draw an empty badge. */
/* Two things, and the second is the reason the first is not enough.
   .pack is a flex column, so the tag would stretch the full card width and the
   pill would draw as a bar — hence align-self. And the pill's padding and
   border are on EVERY tag, transparent on the packs that have nothing to say,
   because the slot has to be the same height on all three. Colour alone on the
   flagged one made its box 24px against the others' 18px and pushed its figure
   6px down, which is the misalignment the empty slot exists to prevent. */
.pack .tag {
  align-self: center;
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 2px 10px;
}
.pack.best .tag {
  background: rgba(255, 204, 0, 0.10);
  border-color: rgba(255, 204, 0, 0.28);
}
/* What the credits actually do, per pack. Everything here has to be a
   consequence of the NUMBER bought — searching, pasting into a thread and the
   rest are free forever and are listed as such further down the page, so
   repeating them inside a paid card would read as though they were what you
   were paying for. */
.pack .perks {
  list-style: none;
  margin: 16px 0 18px;
  padding: 14px 0 0;
  border-top: 1px solid var(--border-soft);
  text-align: left;
  font-size: 13px;
  color: var(--text-muted);
  display: grid;
  gap: 7px;
}
.pack .perks li { display: flex; gap: 8px; align-items: baseline; }
.pack .perks li::before {
  content: "\2713";
  color: var(--gold);
  font-size: 12px;
  line-height: 1;
  flex: none;
}
/* The saving leads the list and is the only coloured line in it. --price is
   the green already carrying "Helps" and "Good" on /guide, so this borrows a
   measured colour rather than introducing one: tests/palette.js checks it
   against --panel-bg, which is exactly the top stop of the pack gradient.
   The tick goes green with the text — a gold tick on a green line reads as two
   marks rather than one. */
.pack .perks li.save { color: var(--price); font-weight: 600; }
.pack .perks li.save::before { color: var(--price); }

.pack button { width: 100%; margin-top: auto; }

/* The payment line, as a bordered strip rather than a sentence in the margin.
   It is the last thing read before a Buy button and it answers who takes the
   card, so it earns an edge. inline-flex so the strip is only as wide as its
   text — full width it reads as a banner, and a banner about payment security
   is the shape a scam takes. Kept to ONE short line
   for the same reason the pill shape implies: at two lines a 999px radius stops
   reading as a badge and starts reading as a rounded box of small print, so the
   terms live in the ordinary line beneath it. */
.paybar {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin: 6px 0 0;
  padding: 8px 15px;
  border: 1px solid var(--border-soft);
  border-radius: 999px;
  background: var(--panel-bg);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-muted);
}
.paybar svg { flex: none; }
/* The badge and the terms share one line, the terms pushed to the far edge so
   the row is bracketed rather than left in a ragged stack. Wrapping is the
   narrow-screen answer: below ~560px they stack, and the terms go back to the
   left margin, because right-aligned small print under a left-aligned badge
   reads as a mistake rather than a choice. */
.payrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px 20px;
  margin-top: 2px;
}
.payrow .paybar { margin: 0; }
.payterms { margin: 0; text-align: right; }
@media (max-width: 560px) {
  .payterms { text-align: left; }
}
/* Their mark sits on the line with our words rather than above or below it,
   so the row reads as one sentence. align-items on the paragraph does the
   work; a bare inline image would sit on the text baseline and hang a few
   pixels low.

   Sized under the header wordmark on purpose. Their terms say not to use
   their marks more prominently than your own, and ours is 30px tall. */
.paybar {
  display: flex;
  align-items: center;
  gap: 8px;
}
.paybar img { display: block; }
.pack .count { font-size: 34px; font-weight: 700; color: var(--gold); line-height: 1.1; font-variant-numeric: tabular-nums; }
/* The unit sits UNDER the figure, as a spaced cap label in the same idiom as
   .eyebrow. It was beside it, and before that it was underneath at 11.5px in
   muted grey — which made the card read as a bare number, and a bare number
   beside a price invites being read as the price of something rather than the
   quantity being bought. That failure was the size and the colour, not the
   position: at --text-soft with caps and letter-spacing it reads as a label
   naming the figure, which is the job.
   Still not gold. Gold stays on the figure alone, or this becomes the largest
   gold surface on the page, which is the one thing this palette does not do. */
.pack .count-unit {
  display: block;
  margin-top: 4px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--text-soft);
}
/* 10px above, against the label’s 4px below the figure. The CREDITS label
   belongs to the number, and at 4px on both sides it sat equidistant between
   the number and the rate and grouped with neither. */
.pack .unit { font-size: 11.5px; color: var(--text-muted); margin: 10px 0 14px; font-variant-numeric: tabular-nums; }
.pack .price { font-size: 19px; margin-bottom: 14px; color: var(--text-soft); font-variant-numeric: tabular-nums; }
.tag {
  display: inline-block;
  font-size: 11px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 6px;
}

/* ---- key box ---- */

.keybox {
  background: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: var(--radius);
  padding: 13px;
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  font-size: 13px;
  word-break: break-all;
  color: var(--gold);
}

/* ---- ledger ---- */

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th {
  text-align: left;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-muted);
  font-weight: 600;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
}
td { padding: 9px 10px; border-bottom: 1px solid var(--border-soft); color: var(--text-soft); }
td.num { text-align: right; font-variant-numeric: tabular-nums; }
.plus { color: var(--price); }
.minus { color: var(--text-muted); }

/* The button that continues the ledger, beside the room for it to say why it
   could not. On one line so the table does not end in dead space. */
.load-more { display: flex; align-items: center; gap: 12px; margin: 14px 0 0; }
/* One section on the site ends in a bare control rather than a .panel, and the
   18px under a panel is what normally parts a section from the next heading.
   So "Delete your account" landed directly on top of the Load more button.
   Put on the heading rather than on .load-more because the row stays in the DOM
   when there is nothing left to load — the button is toggled with [hidden], not
   removed — and the gap has to be there in both states. */
h2.parted { margin-top: 30px; }
.load-more button { flex: none; }

/* ---- misc ---- */

.big { font-size: 40px; font-weight: 700; color: var(--gold); line-height: 1.1; font-variant-numeric: tabular-nums; }
ul.plain, ol.plain { padding-left: 20px; margin: 0 0 14px; color: var(--text-soft); }
ul.plain li, ol.plain li { margin-bottom: 7px; }
hr { border: none; border-top: 1px solid var(--border-soft); margin: 30px 0; }

/* Drawn the way the extension's own Advanced disclosure is, because they are
   the same idea on two surfaces and a reader crossing between them should not
   have to learn it twice. The chevron is a rotated border box rather than a
   glyph: it points in exactly two directions and animates between them, which
   is not worth a font dependency. */
details > summary {
  display: flex;
  align-items: center;
  gap: 9px;
  width: fit-content;
  padding: 4px 2px;
  cursor: pointer;
  color: var(--text-muted);
  font-size: 14px;
  border-radius: 4px;
  list-style: none;
  transition: color 0.15s ease;
}
details > summary::-webkit-details-marker { display: none; }
details > summary:hover { color: var(--text); }
details > summary:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
details > summary::before {
  content: "";
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transform-origin: 60% 60%;
  transition: transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
  flex: none;
}
details[open] > summary::before { transform: rotate(45deg); }
details[open] > summary { margin-bottom: 6px; }
/* A fold that stands on its own between two sections, rather than inside one.
   It needs its own room above and below or it reads as the tail of whatever
   section it happens to follow. */
.aside-fold { margin: 30px 0; }
details > :not(summary) { margin-left: 15px; }
@media (prefers-reduced-motion: reduce) {
  details > summary::before { transition: none; }
}

/* The pricing FAQ. On a wide screen the questions run down the left and the
   answer to whichever one the pointer or keyboard is on shows on the right, so
   the section uses its whole width and nothing has to be clicked open.
   js/faq.js does the choosing and adds .is-live. Without it, and on a narrow
   screen, every answer sits under its question, which is the order the markup
   keeps them in.

   It was a column of <details> folds for a day: every answer a click away, in
   a list that used half the width it had. */
.faq { margin-top: 24px; }
.faq-item { border-bottom: 1px solid var(--border-soft); }
.faq-item:first-child { border-top: 1px solid var(--border-soft); }
.faq-q { margin: 0; }
/* A plain row rather than the site's blue button: everything the shared button
   rule gives a <button> is taken back here — the fill, the white label, the
   padding, the lift. */
.faq-q button {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 15px 2px;
  background: none;
  border-radius: 0;
  color: var(--text);
  font-size: 15.5px;
  font-weight: 600;
  line-height: 1.4;
  text-align: left;
  --lift: 0px;
}
/* The site's disclosure chevron, pointing across at the answer. */
.faq-q button::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-right: 1.5px solid var(--gold);
  border-bottom: 1.5px solid var(--gold);
  transform: rotate(-45deg);
}
.faq-q button:hover,
.faq-q button:active { color: var(--gold); box-shadow: none; }
.faq-a { padding: 0 2px 16px 23px; }
.faq-a p {
  margin: 0 0 10px;
  font-size: 14.5px;
  color: var(--text-soft);
  max-width: none;
}
.faq-a p:last-child { margin-bottom: 0; }

@media (min-width: 801px) {
  .faq.is-live {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    column-gap: 56px;
  }
  .faq.is-live .faq-item { grid-column: 1; }
  .faq.is-live .faq-a { display: none; }
  .faq.is-live .faq-item.is-active .faq-q button { color: var(--gold); }
  /* Out of the list's flow and into the right-hand column, top-aligned, so
     the panel stays put while the pointer moves down the questions. Its width
     is the second column's share of what the gap leaves. */
  .faq.is-live .faq-item.is-active .faq-a {
    display: block;
    position: absolute;
    top: 0;
    right: 0;
    width: calc((100% - 56px) * 7 / 12);
    padding: 22px 26px;
    background: var(--panel-bg);
    border: 1px solid var(--border-soft);
    border-radius: 10px;
  }
  .faq.is-live .faq-a p { font-size: 15.5px; }
}

/* The page steps down into a darker base rather than stopping at a hairline.
   The panel value, full width: separating layers by value is how the rest of
   the site does it, and a 1px rule under a textured page read as dated. Chosen
   by the operator from three mock-ups on the real homepage; a gold band was
   one of them, and gold stays off surfaces (DESIGN.md, Colors).

   The site links are still not repeated here. Home, Pricing and the account
   are in a header that is sticky on desktop and in the menu on phones, so a
   copy down here could never be the only route to anything; tests/smoke.js
   holds that line. Privacy, Terms and Support are here because nowhere else
   carries them. */
footer {
  margin-top: var(--section);
  padding: 52px 0 56px;
  background: var(--panel-bg);
  color: var(--text-muted);
  font-size: 14px;
}
footer a { color: var(--text-soft); text-decoration: none; }
/* Gold on hover, like the header's own links. */
footer a:hover { color: var(--gold); text-decoration: underline; text-underline-offset: 3px; }

/* Name and one line on the left, the three links on the right, wrapping under
   rather than squeezing on a narrow screen. flex-start, because the name block
   is taller than one line of links and centring would float the links. */
.footer-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 24px 48px;
  flex-wrap: wrap;
}
/* The header's lockup, not its class: .brand carries the BETA badge, which
   belongs to the header alone. Not a link either, for the reason above. One
   weight of the display face ships, so 600 (tests/fonts.js). */
.footer-mark {
  display: inline-flex;
  align-items: center;
  margin: 0;
  max-width: none;
  font-family: "Space Grotesk", "Segoe UI", system-ui, sans-serif;
  font-weight: 600;
  font-size: 21px;
  letter-spacing: -0.2px;
  color: var(--text);
}
.footer-mark img { width: 26px; height: 26px; margin-right: 8px; }
.footer-mark span { color: var(--gold); }
.footer-tag { margin: 8px 0 0; max-width: 34ch; }
.footer-legal { display: flex; gap: 8px 28px; flex-wrap: wrap; font-size: 15px; }

/* Small print, each statement on its own line (the operator's call), under the
   whole row rather than beside it now that the row has a name in it.
   --text-muted on the panel measures 5.97:1, comfortably clear of 4.5. */
.footer-note {
  max-width: none;
  margin: 36px 0 0;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--text-muted);
}
.footer-note span { display: block; }
/* Below this the two halves stop fitting side by side and the note wraps under
   the links. Right-aligned there it reads as a second column that is not
   there, so it goes back to the left edge with everything else.

   After .footer-note rather than up with the other narrow rules: same
   specificity, so source order decides, and up there it lost silently. */
@media (max-width: 700px) {
  .footer-note { text-align: left; max-width: none; }
}

@media (max-width: 560px) {
  h1 { font-size: 25px; }
  h2 { font-size: 21px; }
  header .wrap { gap: 12px; }

  /* The bar cannot hold the brand, four links and a button at this width. It
     used to cope by hiding them — the Create account button first, then "How
     it works" — which fixed the overflow by deleting the navigation, and left
     a visitor on a phone unable to reach the page that explains the product.
     Everything is in the panel now and nothing is dropped.

     Anchored to <header> rather than to .wrap so it spans the full width of
     the screen; header is already position: sticky, which makes it the
     containing block for free. */
  .nav-toggle { display: flex; }
  /* The two groups the bar splits into are one stack down here. There is no
     left and right in a dropdown, so the distinction they draw has nothing to
     say — and the divider between them falls out for free, because the
     :last-of-type rule below is per group and drops the rule under the last
     link of each. */
  .nav-links, .nav-actions {
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    margin: 0;
  }
  header nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: 8px 24px 14px;
    background: var(--panel-bg);
    border-bottom: 1px solid var(--border);
    /* The panel floats over the page, so it needs an edge the flat background
       does not give it. */
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.38);
  }
  header.nav-open nav { display: flex; }

  /* Rows, not a row. Full-width targets rather than 15px text with nothing
     around it — this is the one place on the site being tapped with a thumb. */
  header nav a {
    padding: 12px 2px;
    font-size: 16px;
    border-bottom: 1px solid var(--border-soft);
  }
  header nav a:last-of-type { border-bottom: none; }

  /* In the menu the links are rows, so an underline reads as a heavier divider
     rather than as a mark. Same 3px of gold, turned up the left edge, sitting
     in the panel's own padding where nothing else is. */
  header nav a[aria-current="page"]::after {
    top: 4px;
    bottom: 4px;
    left: -12px;
    right: auto;
    width: 3px;
    height: auto;
  }

  header nav a.btn,
  header nav .btn-inventory {
    text-align: center;
    padding: 12px 18px;
    margin-top: 8px;
    border-bottom: none;
  }
  /* Two overrides the shared ones above cannot carry. The 13px set for the
     narrow bar reaches into the open menu, where a.btn is still 14px — the
     menu's own 16px never applies to either, because .btn outranks
     "header nav a". And the border wants its pixel back here as well. */
  header nav .btn-inventory {
    font-size: 14px;
    padding: 11px 17px;
    /* The rule above clears border-bottom to drop the divider the menu puts
       under each link. On this button that divider is the bottom edge of its
       gold outline, so it comes straight back — it was an open-ended box in
       the menu, and a pixel shorter than the button above it because of it. */
    border-bottom: 1px solid var(--gold);
  }
  /* There is room for the real label down here, which there was not in the
     bar. Both rules this overrides still stand for the narrow bar itself. */
  header.nav-open .label-wide { display: inline; }
  header.nav-open .label-narrow { display: none; }
}

/* Two headed panels sharing a row. The heading stays next to its own panel in
   the source — that is the order it has to be read in — and the four children
   are placed explicitly, which is what puts the headings on one row and the
   panels on the next without reordering anything. Auto placement cannot: it
   fills row by row, so it would put the first heading beside the first panel.

   The panels stretch to the taller of the two, because two cards side by side
   with different bottom edges look like one of them failed. */
.two-up {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 30px;
}
.two-up > :nth-child(1) { grid-area: 1 / 1; }
.two-up > :nth-child(2) { grid-area: 2 / 1; }
.two-up > :nth-child(3) { grid-area: 1 / 2; }
.two-up > :nth-child(4) { grid-area: 2 / 2; }

@media (max-width: 820px) {
  /* One column, and the explicit placement has to go with it or all four
     children pile onto two cells. auto hands them back to source order, which
     is heading, panel, heading, panel.

     :nth-child(n) rather than *, and that is not a flourish: a media query
     does not raise specificity, so ".two-up > *" at (0,1,0) loses to
     ".two-up > :nth-child(1)" at (0,2,0) no matter how far below it sits, and
     the page stayed in two columns at every width. :nth-child(n) matches every
     child at the same specificity as the rules it is overriding, so source
     order decides it — the same trap .btn.browser-btn has a note about further
     up this file.

     820 rather than 760 because the left column holds a form: two labelled
     inputs and a button want more than the ~350px a 760px page leaves them. */
  .two-up { grid-template-columns: 1fr; }
  .two-up > :nth-child(n) { grid-area: auto; }
}

/* ---- the account page's identity row ---- */

/* Who you are signed in as, and the two ways to stop being. One row on a wide
   screen, stacked when there is not room, and never inside a panel: it is the
   page saying whose page it is, not one of the things on it. */
.acct-id {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 18px;
  margin: 0 0 24px;
}
.acct-id p { margin: 0; max-width: none; }
.acct-actions { display: flex; flex-wrap: wrap; gap: 10px; }

/* ---- drawn screenshots ---- */

/* The figures on /transparent-tooltips. Drawn as SVG rather than shipped as
   real screenshots: they stay sharp at any width, carry nobody's actual stash,
   and add no image weight. */
.shot { margin: 0; }
.shot svg {
  display: block;
  width: 100%;
  height: auto;
}
.shot figcaption {
  margin-top: 10px;
  font-size: 13px;
  color: var(--text-muted);
  line-height: 1.5;
}
.shot .yes { color: var(--price); font-weight: 600; }
.shot .no { color: #ff8f8f; font-weight: 600; }

/* The game behind the tooltip, at the two levels that actually matter: the
   ground, and the interface panels sitting on it. */
/* The figure's only border, and its fill, drawn as one rect so the two round
   the corner together. non-scaling-stroke because the viewBox is 220 wide and
   renders around twice that, which would otherwise make this the only 2px
   border on the site. */
.shot .screen { fill: var(--panel-bg); stroke: var(--border-soft); vector-effect: non-scaling-stroke; }

/* The border says which figure it is, in the colour the caption under it
   already uses for the same word. Held back from full strength: a 1px line all
   the way round a card that size is otherwise the brightest thing in the
   section, competing with the gold item name it is meant to be framing.

   Not the same percentage, though, because they are not the same colour. #8f8
   is a far lighter green than #ff8f8f is a red, so equal alpha leaves the pair
   looking like one of them was decided and the other happened. These two land
   at about the same weight on this background.

   The words "Good" and "No" still carry it alone for anyone who cannot
   separate the two hues. */
.shot.good .screen { stroke: color-mix(in srgb, var(--price) 50%, transparent); }
.shot.bad .screen { stroke: color-mix(in srgb, #ff8f8f 70%, transparent); }
/* The same three greys as the real thing: the stash UI behind a touch lighter
   than the screen, the tooltip darkest of all. */
.shot .ui { fill: var(--bg-grad-b); }
/* The tooltip itself: the panel, its coloured name line, and the stats. Named
   in gold because the name's colour is the thing the reader is being taught to
   keep in frame. */
.shot .tip { fill: var(--tip-bg); stroke: var(--border); }
.shot .tip-name { fill: var(--gold); }
.shot .tip-line { fill: var(--tip-stat); }

/* The bad one needs no red cross drawn on it: it has two panels where the good
   one has one, and that is the whole lesson. The fade keeps two full-strength
   tooltips from reading as the richer of the two pictures rather than the
   wrong one. (.cut went with the figure it belonged to — an invisible rect at
   the frame's edge, back when the mistake being drawn was clipping.) */
.shot.bad .tip,
.shot.bad .tip-name,
.shot.bad .tip-line { opacity: 0.92; }

/* /transparent-tooltips draws a full inventory under the tooltip, because how
   much of it shows through is exactly what HDR breaks. The same drawing twice:
   the normal panel covers the grid, the HDR one barely does. */
.shot .cell { fill: none; stroke: var(--border-soft); }
.shot .item { fill: #3a3a3a; }
/* The panel keeps about a fifth of its darkness, and the colours drain the way
   they do on a real HDR screenshot: the gold name toward tan, the stat blue
   toward lavender. */
.shot .tip-hdr { fill: var(--tip-bg); fill-opacity: 0.22; stroke: var(--border); stroke-opacity: 0.4; }
.shot .tip-name-hdr { fill: #c9b98d; opacity: 0.8; }
.shot .tip-line-hdr { fill: #a8a8d8; opacity: 0.75; }
/* The guide's pair has nothing under it on /guide; here a paragraph follows,
   and without room it sat on the captions. */
.hdr-pair { margin-bottom: 26px; }
/* Held to the measure of the text above it: at the wrap's full width the box
   was mostly empty panel to the right of two short lines. */
.tip-cta { max-width: 640px; margin-top: 40px; }
.tip-cta p { margin: 0 0 16px; }

/* A file path, on the two pages that name one. The system monospace stack —
   nothing to download, and no webfont to wait on for a string this short. */
code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.86em;
  color: var(--text);
  overflow-wrap: break-word;
}

/* A keyboard key, for the shortcuts on /guide. Drawn as a key rather than set
   in bold because Win+Shift+S is a thing you press, and three bold words in a
   row read as emphasis instead of as three separate keys. */
kbd {
  display: inline-block;
  padding: 1px 6px 2px;
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: 4px;
  background: var(--input-bg);
  color: var(--text);
  font-family: inherit;
  font-size: 0.88em;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
}

/* ---- how it fits together, on the home page ---- */

/* Three columns because there are three places, in the order they are met.
   Each picture sits in a box of the same height so the three headings line up
   whatever each drawing measures. */
.flow {
  list-style: none;
  margin: 28px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 30px;
}
.flow > li { max-width: none; }
/* A fixed height, not a minimum. With min-height the tallest drawing (the
   stash page) set its own column height, and its heading dropped 30px below
   the other two, which read as a mistake rather than as three of a kind. */
.flow-visual {
  height: 262px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 18px;
}
/* And the three pictures are one size, not just one column height. They were
   148, 262 and 193px tall in a 236px box, so the stash page spilled out of it
   and the other two floated at different sizes. 262px is what the stash page
   needs to show its drop zone and three cards; the game screenshot's frame is
   drawn by CSS at that size with the drawing centred inside, and the d2jsp
   page lets its forum area take up the rest. */
.flow-visual > * { width: 100%; max-width: 300px; height: 262px; }
/* The same border as the two .mock pictures beside it. The green belongs to
   .shot.good on the guide, where it is one half of a good-and-bad pair and the
   colour is the point; here there is no bad counterpart, so it only made the
   first of three matching pictures look unlike the other two. The `good` class
   on this element no longer changes anything — it styles a .screen rect, and
   this drawing deliberately has none. */
.flow .shot {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--panel-bg);
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  overflow: hidden;
}
.flow .shot svg { width: 100%; height: 100%; }
.flow-visual .mock-site { display: flex; flex-direction: column; }
.flow-visual .mock-site .mock-page { flex: 1; height: auto; }
.flow h3 { display: flex; align-items: center; gap: 10px; margin: 0 0 6px; }
/* The same numbered circle the guide uses, smaller, so the strip reads as a
   preview of the steps rather than as a different idea. */
.flow-num {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--gold);
  color: var(--gold);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  /* Centring the line box left the digit's ink about 1px high, the way a
     glyph is placed by its font's metrics rather than by its shape. Measured
     off the rendered pixels. With border-box sizing, 2px of top padding moves
     the centre down by exactly the 1px. Same fix as the guide's index. */
  padding-top: 2px;
}
.flow p { margin: 0; font-size: 14.5px; color: var(--text-soft); }
.flow-cta { margin: 32px 0 0; }
@media (max-width: 800px) {
  .flow { grid-template-columns: 1fr; gap: 36px; }
  .flow-visual { height: auto; }
}

/* The cost, as a single line under a rule. */
/* The claims on the left and what it costs in a box beside them, top edges
   level. The box is a resting panel, so it is flat: the lift is kept for the
   one card in each hero (DESIGN.md, Elevation). */
.claims-row {
  display: grid;
  grid-template-columns: minmax(0, 66ch) minmax(240px, 320px);
  justify-content: space-between;
  align-items: start;
  gap: 40px;
  margin-top: 28px;
}
.claims-row .claims { margin-top: 0; }
.cost-box {
  background: var(--panel-bg);
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  padding: 22px 24px 24px;
}
.cost-box p { margin: 0; max-width: none; font-size: 15px; color: var(--text-soft); }
.cost-box .cost-key { margin-bottom: 8px; font-size: 18px; color: var(--text); }
.cost-box .btn { margin-top: 18px; }
@media (max-width: 860px) {
  .claims-row { grid-template-columns: 1fr; }
}

/* ---- drawn recreations of the extension ---- */

/* The stash page and the d2jsp panel, drawn in the page rather than
   screenshotted. The colours are the site's own tokens, which are the
   extension's tokens, so the drawing looks like the thing without being a
   picture that goes stale the day the extension changes. Quality colours come
   in per card as --q, set inline the way the real cards set theirs. */
.mock {
  /* The page's own grey, so the drawn cards and bars sit a touch darker on it
     the way real ones do. */
  background: var(--bg-grad-b);
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  overflow: hidden;
  font-size: 11px;
  line-height: 1.35;
  color: var(--text-soft);
  text-align: left;
}
.mock b { font-weight: 600; }

.mock-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 10px;
  background: var(--panel-bg);
  border-bottom: 1px solid var(--border-soft);
}
.mock-brand, .mock-panel-head { font-weight: 600; color: var(--text); }
.mock-brand b, .mock-panel-head b { color: var(--gold); }
.mock-meta { color: var(--text-muted); }
.mock-drop {
  margin: 10px;
  padding: 12px 8px;
  border: 1px dashed color-mix(in srgb, var(--gold) 55%, transparent);
  border-radius: 6px;
  text-align: center;
  font-weight: 600;
}
.mock-drop small { display: block; margin-top: 3px; font-size: 10px; font-weight: 400; color: var(--text-muted); }
.mock-card {
  display: flex;
  flex-direction: column;
  margin: 0 10px 8px;
  padding: 6px 8px;
  background: var(--panel-bg);
  border: 1px solid var(--border);
  border-left: 3px solid var(--q);
  border-radius: 5px;
}
.mock-card span, .mock-result span {
  color: var(--q);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mock-card em, .mock-result em { font-style: normal; font-size: 10px; color: var(--text-muted); }

.mock-chrome {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 7px 9px;
  background: var(--panel-bg);
  border-bottom: 1px solid var(--border-soft);
}
.mock-chrome i { width: 7px; height: 7px; border-radius: 50%; background: var(--border); }
.mock-url {
  margin-left: 8px;
  padding: 2px 10px;
  border-radius: 10px;
  background: var(--input-bg);
  color: var(--text-muted);
  font-size: 10px;
}
.mock-page { position: relative; display: flex; height: 158px; }
.mock-forum { flex: 1; min-width: 0; padding: 10px; display: flex; flex-direction: column; gap: 8px; }
.mock-post {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 7px;
  border: 1px solid var(--border-soft);
  border-radius: 4px;
}
.mock-post b { width: 38%; height: 5px; border-radius: 2px; background: #6b6b6b; }
.mock-post span { height: 4px; border-radius: 2px; background: var(--border); }
.mock-post span.short { width: 60%; }
.mock-panel {
  width: 50%;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  background: var(--panel-bg);
  border-left: 1px solid var(--border);
}
.mock-search {
  padding: 4px 6px;
  border: 1px solid var(--gold);
  border-radius: 4px;
  background: var(--input-bg);
  color: var(--text);
}
.mock-result {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 6px;
  padding: 5px 6px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--q);
  border-radius: 4px;
}
.mock-copy {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  padding: 2px 7px;
  border-radius: 3px;
  background: var(--btn);
  color: #fff;
  font-size: 10px;
  font-style: normal;
  font-weight: 700;
}
/* The launcher: the chest in a round button, bottom right, ringed the way the
   real one pulses. Only ever drawn with the panel closed, because the real
   button hides while the panel is open. */
.mock-launcher {
  position: absolute;
  right: 10px;
  bottom: 10px;
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--panel-bg);
  border: 1px solid var(--gold);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--gold) 16%, transparent);
}
.mock-launcher img { display: block; }

/* ---- the setup guide ---- */

/* The hero's second column is the index of steps. It is the table of contents
   and the answer to "how long is this?" before anybody starts.

   Drawn as a route rather than a table: one thin line through all nine
   numbers, no rules between rows, and the group names set in the text column
   so the numbers form one unbroken column down the left. The rows were
   separated by hairlines and the group labels sat flush left against the
   numbers, which broke the column into three pieces and left the panel
   looking busier than the nine short lines inside it. */
.guide-index {
  width: 100%;
  max-width: 400px;
  margin: 0 auto;
  padding: 22px 26px 20px;
  background: var(--panel-bg);
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  /* The number column and the group names line up on these two. */
  --dot: 28px;
  --dot-gap: 14px;
}
.guide-group {
  margin: 14px 0 2px;
  padding-left: calc(var(--dot) + var(--dot-gap));
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--text-muted);
}
.guide-index > .guide-group { margin-top: 0; margin-bottom: 6px; }
/* The line runs from the centre of the first number to the centre of the last,
   so it starts and ends inside a circle rather than hanging past one. A row is
   the dot plus 6px above and below, so its centre is half a dot plus 6px in. */
.guide-track { position: relative; }
.guide-track::before {
  content: "";
  position: absolute;
  left: calc(var(--dot) / 2 - 0.5px);
  top: calc(var(--dot) / 2 + 6px);
  bottom: calc(var(--dot) / 2 + 6px);
  width: 1px;
  background: var(--border);
}
.guide-index ol { list-style: none; margin: 0; padding: 0; }
.guide-index li { max-width: none; }
.guide-index a {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--dot-gap);
  padding: 6px 0;
  color: var(--text-soft);
  text-decoration: none;
  font-size: 15px;
}
.guide-index a:hover { color: var(--text); }
.guide-index a span,
.guide-num {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--border);
  color: var(--gold);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
/* Filled with the panel's own colour, so the line passes behind each number
   rather than through it. */
.guide-index a span {
  width: var(--dot);
  height: var(--dot);
  font-size: 13px;
  background: var(--panel-bg);
  /* At 13px the digits' ink sat about 1px above the circle's centre (measured
     off the rendered pixels, 0.86 to 1.03px for 1 through 9). Border-box, so
     2px of top padding lowers the centre by 1px. The 20px numbers beside each
     step measured within 0.15px and are left alone. */
  padding-top: 2px;
}
.guide-index a:hover span { border-color: var(--gold); }

/* One step: the number down the left, everything about it down the right, a
   rule between steps. The number is the thing somebody glances back at to find
   their place after switching to the game or their inbox. */
.guide { list-style: none; margin: 0; padding: 0; }
.guide-step {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr);
  gap: 0 24px;
  max-width: none;
  padding: 38px 0;
  border-top: 1px solid var(--border-soft);
  scroll-margin-top: calc(var(--header-h) + 16px);
}
.guide-step:first-child { padding-top: 0; border-top: none; }
.guide-num { width: 48px; height: 48px; font-size: 20px; border-color: var(--gold); }
.guide-body { min-width: 0; }
.guide-body h2 { margin: 10px 0 12px; font-size: 23px; }
.guide-body > p { max-width: 64ch; color: var(--text-soft); }
.guide-actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin: 18px 0 0; }
.guide-note { margin: 14px 0 0; font-size: 14px; color: var(--text-muted); }
.guide-list { list-style: none; margin: 4px 0 0; padding: 0; }
.guide-list li {
  position: relative;
  max-width: 64ch;
  padding-left: 22px;
  margin-bottom: 9px;
  color: var(--text-soft);
}
.guide-list li::before { content: "▪"; position: absolute; left: 2px; color: var(--gold); }
.guide-visual { margin-top: 22px; }
.guide-visual .mock { max-width: 420px; }
.guide-pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 20px;
  max-width: 760px;
  margin-top: 22px;
}
.guide-pair figure { margin: 0; }
.guide-pair figcaption { margin-top: 10px; font-size: 13px; line-height: 1.5; color: var(--text-muted); }
/* The install row, as a step's button rather than the hero's centred row. */
.guide-step .get-ext { justify-content: flex-start; margin-top: 18px; padding-top: 0; }
.guide-step .card-demo { margin: 22px 0 0; }
.guide-end { margin: 8px 0 0; padding-top: 30px; border-top: 1px solid var(--border-soft); color: var(--text-soft); }
@media (max-width: 560px) {
  .guide-step { grid-template-columns: 1fr; }
  .guide-num { width: 40px; height: 40px; font-size: 18px; }
}
