/* ------------------------------------------------------------------
   neverdies.me — store

   Lifted out of an inline <style> so the Content-Security-Policy can
   say style-src 'self' with no 'unsafe-inline'. Nothing here may move
   back inline, and no style="..." attributes may be added to the HTML
   — CSP blocks those too. Anything that genuinely has to be computed
   at runtime is set through element.style from store.js, which CSSOM
   allows and the policy does not govern.
------------------------------------------------------------------ */

:root{
  --accent:#9382F6;
  --base:#1e1e2e;
  --panel:#232336;
  --ink:#f2f1f8;
  --ink-dim:#b9b6cc;
  --ink-mute:rgba(185,182,204,.66);
  --hair:rgba(242,241,248,.22);
  --hair-soft:rgba(242,241,248,.12);
  --danger:#f0787f;
  --ok:#7fd6a4;
  --warn:#f5c97b;
  --pad:clamp(20px, 5vw, 64px);
}

*,*::before,*::after{box-sizing:border-box}
[hidden]{display:none !important}

html{overflow-x:hidden; -webkit-text-size-adjust:100%}
html.is-locked, body.is-locked{overflow:hidden}

body{
  margin:0;
  min-height:100dvh;
  display:flex;
  flex-direction:column;
  overflow-x:hidden;
  background:var(--base);
  color:var(--ink);
  font-family:"Archivo", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size:16px;
  line-height:1.5;
  padding:
    env(safe-area-inset-top)
    env(safe-area-inset-right)
    env(safe-area-inset-bottom)
    env(safe-area-inset-left);
}

/* A flat wash rather than the landing page's video. The tiles are the
   thing to look at here and a moving background behind a grid of
   moving thumbnails is noise. */
.scrim{
  position:fixed;
  inset:0;
  z-index:-1;
  background:
    radial-gradient(120% 80% at 50% -10%, rgba(147,130,246,.16) 0%, rgba(30,30,46,0) 60%),
    linear-gradient(180deg, #1a1a28 0%, var(--base) 40%, #1b1b29 100%);
}

/* ---------- chrome ---------- */

.topbar{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
  padding:calc(var(--pad) * .45) var(--pad) 0;
}
.topbar__right{display:flex; align-items:center; gap:10px}

.cart-btn{
  -webkit-appearance:none;
  appearance:none;
  display:inline-flex;
  align-items:center;
  gap:8px;
  min-height:40px;
  padding:0 12px 0 16px;
  border:1px solid var(--hair);
  border-radius:4px;
  background:rgba(30,30,46,.55);
  color:var(--ink);
  font:inherit;
  font-size:.875rem;
  font-weight:600;
  cursor:pointer;
  transition:background-color .15s ease, border-color .15s ease;
  -webkit-tap-highlight-color:transparent;
}
.cart-btn:hover{border-color:var(--accent); background:rgba(147,130,246,.14)}
.cart-btn:focus-visible{outline:2px solid var(--accent); outline-offset:3px}
.cart-btn__count{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-width:20px;
  height:20px;
  padding:0 6px;
  border-radius:999px;
  background:var(--hair-soft);
  color:var(--ink-dim);
  font-family:"Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.625rem;
  line-height:1;
}
/* Only coloured when it means something. A permanent purple 0 trains
   people to stop reading it. */
.cart-btn.is-full{border-color:var(--accent)}
.cart-btn.is-full .cart-btn__count{background:var(--accent); color:#16121f}

.home-link,
.dash-link{
  display:inline-flex;
  align-items:center;
  gap:8px;
  min-height:40px;
  padding:0 16px;
  border:1px solid var(--hair);
  border-radius:4px;
  background:rgba(30,30,46,.55);
  color:var(--ink);
  font-size:.875rem;
  font-weight:600;
  text-decoration:none;
  transition:background-color .15s ease, border-color .15s ease;
  -webkit-tap-highlight-color:transparent;
}
.home-link{border-color:var(--hair-soft); padding-left:12px}
.home-link img{display:block; border-radius:3px}
.home-link:hover,
.dash-link:hover{border-color:var(--accent); background:rgba(147,130,246,.14)}
.dash-link .dot{
  width:6px;
  height:6px;
  border-radius:50%;
  background:var(--accent);
  flex:none;
}

main{
  flex:1;
  width:100%;
  max-width:1240px;
  margin:0 auto;
  padding:clamp(22px, 4vw, 44px) var(--pad) var(--pad);
}

.view--narrow{max-width:60ch; margin:0 auto}

/* ---------- headings ---------- */

.store-head{max-width:58ch}

h1{
  font-size:clamp(2.25rem, 5.5vw, 3.5rem);
  font-weight:700;
  letter-spacing:-.03em;
  line-height:1;
  margin:0 0 12px;
}

.lede{
  margin:0 0 26px;
  font-size:clamp(.9375rem, 1.2vw, 1.0625rem);
  color:var(--ink-dim);
  max-width:52ch;
}

/* The thin stat rule above the grid, as in the reference layout: two
   monospaced counts pushed to opposite ends of a hairline. */
.counts{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:16px;
  padding:10px 2px;
  border-top:1px solid var(--hair-soft);
  border-bottom:1px solid var(--hair-soft);
  margin-bottom:18px;
}
.counts__item{
  font-family:"Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.625rem;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--ink-mute);
}

/* ---------- the tile grid ---------- */

.grid{
  display:grid;
  grid-template-columns:repeat(auto-fill, minmax(190px, 1fr));
  gap:12px;
}

.tile{
  -webkit-appearance:none;
  appearance:none;
  position:relative;
  display:block;
  width:100%;
  /* 3:2 because the artwork is 998x646, about 1.545:1. At 4:3 the sides
     were cropped hard enough to clip the wordmarks baked into the art. */
  aspect-ratio:3 / 2;
  padding:0;
  border:1px solid var(--hair-soft);
  border-radius:6px;
  background:
    linear-gradient(155deg, rgba(147,130,246,.22) 0%, rgba(35,35,54,.9) 55%, rgba(26,26,40,.95) 100%);
  color:var(--ink);
  font:inherit;
  overflow:hidden;
  cursor:pointer;
  transition:border-color .15s ease, transform .15s ease;
  -webkit-tap-highlight-color:transparent;
  touch-action:manipulation;
}
.tile:hover{border-color:var(--accent); transform:translateY(-2px)}
.tile:focus-visible{outline:2px solid var(--accent); outline-offset:3px}

/* The art. It does not exist yet for any product — store.js removes
   the element when it fails to load, and what is left is the gradient
   above plus the name, which is a deliberate design and not a broken
   image. Drop files into assets/store/ and they take over. */
.tile__img{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:cover;
  display:block;
}

.tile__shade{
  position:absolute;
  inset:0;
  background:linear-gradient(180deg, rgba(20,20,32,0) 32%, rgba(16,16,26,.86) 100%);
}

.tile__body{
  position:absolute;
  left:0;
  right:0;
  bottom:0;
  padding:12px 13px;
  text-align:left;
}
.tile__name{
  display:block;
  font-size:1rem;
  font-weight:700;
  letter-spacing:-.015em;
  line-height:1.15;
}
/* The artwork already carries the product name as a wordmark, so the
   overlay would print it twice. Hidden only once the image has actually
   loaded — a product with no art keeps its name, which is what makes
   the gradient fallback a design rather than a blank tile. The full
   name stays in the tile's aria-label either way. */
.tile.has-art .tile__name{display:none}

/* The link to /free. Green rather than the accent so it reads as a
   different kind of thing from the products around it. */
.tile--free{
  color:var(--ink);
  text-decoration:none;
  border-color:rgba(127,214,164,.45);
  background:
    linear-gradient(155deg, rgba(127,214,164,.24) 0%, rgba(35,35,54,.9) 55%, rgba(26,26,40,.95) 100%);
}
.tile--free:hover{border-color:var(--ok)}
.tile__sub{
  display:block;
  margin-top:4px;
  font-family:"Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.625rem;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--ok);
}
.tile.has-art .tile__sub{display:none}

/* ---------- breadcrumbs ---------- */

.crumbs{
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:8px;
  margin-bottom:18px;
  font-size:.8125rem;
  color:var(--ink-mute);
}
.crumbs a,
.crumbs__link{
  color:var(--ink-dim);
  text-decoration:none;
}
/* A button rather than a link because it changes view without a
   navigation, but it has to read and behave as a link. */
.crumbs__link{
  -webkit-appearance:none;
  appearance:none;
  padding:0;
  border:none;
  background:none;
  font:inherit;
  cursor:pointer;
}
.crumbs a:hover,
.crumbs__link:hover{color:var(--ink); text-decoration:underline}
.crumbs a:focus-visible,
.crumbs__link:focus-visible{outline:2px solid var(--accent); outline-offset:3px; border-radius:2px}
.crumbs__here{color:var(--ink)}

/* ---------- product detail ---------- */

.detail{
  display:grid;
  grid-template-columns:minmax(0, 1.25fr) minmax(0, 1fr);
  gap:clamp(20px, 3vw, 40px);
  align-items:start;
}

/* No screenshots yet for any product, so the gallery is hidden and the
   two-column split leaves the buy column stranded at 55% width against
   an empty half. With nothing beside it, one centred column.

   :has rather than a class from store.js so it follows the gallery on
   its own: probeShots unhides that element the moment art exists at
   assets/store/<id>-1.jpg, and the layout goes back to two columns
   with no code change. */
#detailView:has(.gallery[hidden]){
  max-width:680px;
  margin-inline:auto;
}
#detailView:has(.gallery[hidden]) .detail{
  grid-template-columns:minmax(0, 1fr);
}

/* ---------- gallery ---------- */

.gallery__frame{
  position:relative;
  aspect-ratio:16 / 9;
  border:1px solid var(--hair-soft);
  border-radius:6px;
  background:#16161f;
  overflow:hidden;
}
.gallery__img{
  width:100%;
  height:100%;
  object-fit:cover;
  display:block;
}

.gallery__nav{
  -webkit-appearance:none;
  appearance:none;
  position:absolute;
  top:calc(50% - 26px);
  width:38px;
  height:52px;
  display:flex;
  align-items:center;
  justify-content:center;
  border:1px solid var(--hair-soft);
  border-radius:4px;
  background:rgba(22,22,31,.78);
  color:var(--ink);
  font-size:1.5rem;
  line-height:1;
  cursor:pointer;
  transition:border-color .15s ease, background-color .15s ease;
  -webkit-tap-highlight-color:transparent;
}
.gallery__nav:hover{border-color:var(--accent); background:rgba(147,130,246,.2)}
.gallery__nav:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
.gallery__nav--prev{left:8px}
.gallery__nav--next{right:8px}

/* A strip of stills rather than dots. Dots tell you how many there
   are; thumbnails tell you whether the next one is worth a click. */
.thumbs{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(0, 1fr));
  gap:8px;
  margin-top:8px;
}
.thumb{
  -webkit-appearance:none;
  appearance:none;
  display:block;
  padding:0;
  aspect-ratio:16 / 9;
  border:1px solid var(--hair-soft);
  border-radius:4px;
  background:#16161f;
  overflow:hidden;
  cursor:pointer;
  opacity:.55;
  transition:opacity .15s ease, border-color .15s ease;
  -webkit-tap-highlight-color:transparent;
}
.thumb img{width:100%; height:100%; object-fit:cover; display:block}
.thumb:hover{opacity:.85}
.thumb[aria-selected="true"]{opacity:1; border-color:var(--accent)}
.thumb:focus-visible{outline:2px solid var(--accent); outline-offset:2px}

/* ---------- the buy column ---------- */

.buy h2{
  margin:0 0 8px;
  font-size:clamp(1.5rem, 2.6vw, 2rem);
  font-weight:700;
  letter-spacing:-.025em;
  line-height:1.05;
}
.buy__blurb{
  margin:0 0 18px;
  font-size:.9375rem;
  line-height:1.6;
  color:var(--ink-dim);
}

.buy__label{
  margin:0 0 8px;
  font-family:"Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.625rem;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--accent);
}
.buy__label--spaced{margin-top:26px}

/* ---------- module groups ----------

   The client's modules, one collapsed <details> per in-game category.
   A flat list of seventy names is scrolled past; five bars with counts
   on them can be read in a second and opened by whoever cares. */

.mods{
  display:flex;
  flex-direction:column;
  gap:6px;
}

.mod{
  border:1px solid var(--hair-soft);
  border-radius:6px;
  background:rgba(35,35,54,.5);
  overflow:hidden;
}
.mod[open]{border-color:var(--hair)}

.mod__head{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:10px;
  padding:10px 13px;
  cursor:pointer;
  list-style:none;
  -webkit-tap-highlight-color:transparent;
  transition:background-color .15s ease;
}
.mod__head::-webkit-details-marker{display:none}
.mod__head:hover{background:rgba(147,130,246,.1)}
.mod:focus-within > .mod__head{outline:2px solid var(--accent); outline-offset:-2px}

.mod__name{
  font-size:.8125rem;
  font-weight:600;
  letter-spacing:-.01em;
}

/* The count is the whole point of collapsing: it says how much is
   behind the bar without opening it. Doubles as the disclosure arrow. */
.mod__count{
  display:inline-flex;
  align-items:center;
  gap:8px;
  font-family:"Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.625rem;
  color:var(--ink-mute);
}
.mod__count::after{
  content:"+";
  font-family:inherit;
  font-size:.875rem;
  line-height:1;
  color:var(--accent);
}
.mod[open] .mod__count::after{content:"−"}

.mod__list{
  display:flex;
  flex-wrap:wrap;
  gap:6px;
  margin:0;
  padding:2px 13px 13px;
  list-style:none;
}
.mod__item{
  padding:4px 10px;
  border:1px solid var(--hair-soft);
  border-radius:999px;
  background:rgba(30,30,46,.6);
  font-size:.75rem;
  color:var(--ink-dim);
}

/* ---------- tiers ---------- */

/* ---------- version toggle ----------

   Rust and Rust Lite share one grid tile and switch between each other
   here instead of sending the customer back to the grid to compare
   them. Same pressed/accent language as .tier just below: this is the
   same kind of choice — which thing am I buying — one step earlier.

   Drawn as a segmented switch in a sunken track rather than two loose
   pills. As loose pills it was missed: the owner said the two were
   "very hard to notice", and it is the first decision on the page, so
   it has to read as a control rather than as two words under the
   blurb. The track is what carries that — it says the two belong to
   each other and that one of them is currently on. */
.variants{
  display:flex;
  gap:4px;
  width:fit-content;
  max-width:100%;
  margin:2px 0 22px;
  padding:4px;
  border:1px solid var(--hair);
  border-radius:999px;
  background:rgba(16,16,26,.6);
  box-shadow:inset 0 1px 2px rgba(0,0,0,.35);
}
.variant{
  -webkit-appearance:none;
  appearance:none;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:1px;
  flex:1 1 auto;
  min-width:96px;
  padding:9px 22px;
  border:1px solid transparent;
  border-radius:999px;
  background:transparent;
  color:var(--ink-dim);
  font:inherit;
  font-size:.875rem;
  font-weight:600;
  line-height:1.2;
  cursor:pointer;
  transition:border-color .15s ease, background-color .15s ease, color .15s ease;
  -webkit-tap-highlight-color:transparent;
  touch-action:manipulation;
}
.variant:hover{background:rgba(242,241,248,.07); color:var(--ink)}
/* Solid accent, not the 14% wash .tier uses. A tier sits in a row of
   four and only has to look picked out of them; this sits in a row of
   two and has to answer "which one am I buying" at a glance. */
.variant[aria-pressed="true"]{
  border-color:rgba(147,130,246,.5);
  background:rgba(147,130,246,.26);
  color:var(--ink);
  cursor:default;
}
.variant:focus-visible{outline:2px solid var(--accent); outline-offset:3px}

/* The cheapest length this version sells. Smaller and dimmer than the
   name — it is the reason to press the button, not the label on it. */
.variant__from{
  font-family:"Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.5625rem;
  letter-spacing:.04em;
  font-weight:500;
  color:var(--ink-mute);
  font-variant-numeric:tabular-nums;
}
.variant[aria-pressed="true"] .variant__from{color:var(--ink-dim)}

.tiers{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(130px, 1fr));
  gap:8px;
  margin-bottom:18px;
}
.tier{
  -webkit-appearance:none;
  appearance:none;
  position:relative;
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:2px;
  padding:12px 13px;
  border:1px solid var(--hair-soft);
  border-radius:6px;
  background:rgba(30,30,46,.55);
  color:var(--ink-dim);
  font:inherit;
  text-align:left;
  cursor:pointer;
  transition:border-color .15s ease, background-color .15s ease;
  -webkit-tap-highlight-color:transparent;
  touch-action:manipulation;
}
.tier:hover:not(:disabled){border-color:var(--hair); background:rgba(242,241,248,.07)}
.tier[aria-pressed="true"]{border-color:var(--accent); background:rgba(147,130,246,.14)}
.tier:focus-visible{outline:2px solid var(--accent); outline-offset:3px}
.tier:disabled{opacity:.4; cursor:not-allowed}
/* The hand-picked length sits slightly proud even when it is not the
   one selected, which is the point of picking it. Deliberately weaker
   than the solid accent of [aria-pressed="true"], and outranked by
   both that and :hover, so "recommended" never reads as "chosen". */
.tier--pick{border-color:rgba(147,130,246,.45)}

.tier__name{
  font-family:"Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.625rem;
  letter-spacing:.05em;
  text-transform:uppercase;
  color:var(--ink-mute);
}
.tier__price{
  font-size:1.375rem;
  font-weight:700;
  letter-spacing:-.02em;
  line-height:1.1;
  color:var(--ink);
}
.tier__unit{font-size:.6875rem; color:var(--ink-mute)}
/* On a length that is not comparable with the others — today only the
   beta month, which unlocks a different build set. Given the accent
   rather than the muted ink the per-day figure gets: this one says
   WHAT YOU ARE BUYING, not how good the arithmetic is. */
.tier__unit--alt{color:var(--accent); font-weight:600}

/* The sentence under the tier row, for a length that needs one. Warn
   coloured and bordered because the only thing it ever says is "this
   is not the thing beside it" — the beta month gets stable-client
   money for beta builds, and discovering that after paying is a
   refund request. */
.tier__detail{
  margin:-8px 0 18px;
  padding:9px 11px;
  border-left:2px solid var(--warn);
  border-radius:0 4px 4px 0;
  background:rgba(35,35,54,.7);
  font-size:.75rem;
  line-height:1.5;
  color:var(--ink-dim);
}

/* ---------- quantity ----------

   A licence binds to one machine, so more than one is always for
   somebody else — a gift, or stock to resell. That makes the stepper
   a selling tool rather than a form control, which is why the running
   total sits inside it and the discount hint sits under it. */

.qty{
  display:flex;
  align-items:center;
  gap:8px;
  margin-bottom:6px;
}
.qty__btn{
  -webkit-appearance:none;
  appearance:none;
  flex:none;
  width:36px;
  height:36px;
  display:flex;
  align-items:center;
  justify-content:center;
  border:1px solid var(--hair-soft);
  border-radius:6px;
  background:rgba(30,30,46,.55);
  color:var(--ink);
  font:inherit;
  font-size:1rem;
  line-height:1;
  cursor:pointer;
  transition:border-color .15s ease, background-color .15s ease;
  -webkit-tap-highlight-color:transparent;
  touch-action:manipulation;
}
.qty__btn:hover:not(:disabled){border-color:var(--accent); background:rgba(147,130,246,.14)}
.qty__btn:focus-visible{outline:2px solid var(--accent); outline-offset:3px}
.qty__btn:disabled{opacity:.35; cursor:not-allowed}

.qty__input{
  -webkit-appearance:none;
  appearance:none;
  flex:none;
  width:64px;
  height:36px;
  padding:0 8px;
  border:1px solid var(--hair-soft);
  border-radius:6px;
  background:rgba(30,30,46,.55);
  color:var(--ink);
  font:inherit;
  font-size:.9375rem;
  font-weight:600;
  text-align:center;
}
.qty__input:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-color:var(--accent)}
/* The native spinners duplicate the two buttons either side and are
   unusable at this size on a touch screen. */
.qty__input::-webkit-outer-spin-button,
.qty__input::-webkit-inner-spin-button{-webkit-appearance:none; margin:0}
.qty__input{-moz-appearance:textfield}

.qty__sum{
  font-size:.8125rem;
  font-weight:600;
  color:var(--ink-dim);
}
.qty__hint{
  margin:0 0 16px;
  min-height:1.2em;
  font-size:.75rem;
  line-height:1.5;
  color:var(--accent);
}

/* Computed from the price, never typed in: it is the saving against
   the same product's shortest tier, per day. A hand-written "-78%"
   that does not match the numbers beside it is a refund request. */
.tier__save{
  position:absolute;
  top:10px;
  right:10px;
  padding:1px 6px;
  border-radius:999px;
  background:rgba(127,214,164,.16);
  color:var(--ok);
  font-size:.625rem;
  font-weight:700;
  line-height:1.5;
}
.tier__best{
  position:absolute;
  top:-9px;
  left:12px;
  padding:1px 8px;
  border-radius:999px;
  background:var(--accent);
  color:#16121f;
  font-size:.5625rem;
  font-weight:700;
  letter-spacing:.06em;
  text-transform:uppercase;
  line-height:1.6;
}

/* ---------- add-ons ---------- */

.bundle{
  margin-top:22px;
  padding:14px;
  border:1px solid var(--hair-soft);
  border-radius:6px;
  background:rgba(35,35,54,.5);
}
.bundle__head{
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  gap:12px;
}
.bundle__head .buy__label{margin-bottom:10px}
.bundle__hint{
  margin:0 0 10px;
  font-size:.6875rem;
  color:var(--ink-mute);
}

.addon{
  -webkit-appearance:none;
  appearance:none;
  display:flex;
  align-items:flex-start;
  gap:12px;
  width:100%;
  padding:11px 12px;
  border:1px solid var(--hair-soft);
  border-radius:4px;
  background:rgba(30,30,46,.6);
  color:var(--ink);
  font:inherit;
  text-align:left;
  cursor:pointer;
  transition:border-color .15s ease, background-color .15s ease;
  -webkit-tap-highlight-color:transparent;
}
.addon + .addon{margin-top:8px}
.addon:hover{border-color:var(--hair)}
.addon[aria-pressed="true"]{border-color:var(--accent); background:rgba(147,130,246,.12)}
.addon:focus-visible{outline:2px solid var(--accent); outline-offset:3px}

/* Drawn rather than an <input type="checkbox">, so it can be styled at
   all without the CSP-blocked inline rules an appearance override
   would otherwise need. */
.addon__box{
  flex:none;
  width:18px;
  height:18px;
  margin-top:1px;
  border:1px solid var(--hair);
  border-radius:3px;
  display:flex;
  align-items:center;
  justify-content:center;
  font-size:.75rem;
  line-height:1;
  color:transparent;
}
.addon[aria-pressed="true"] .addon__box{
  background:var(--accent);
  border-color:var(--accent);
  color:#16121f;
}
.addon__text{flex:1 1 auto; min-width:0}
.addon__name{
  display:block;
  font-size:.875rem;
  font-weight:600;
  line-height:1.3;
}
.addon__blurb{
  display:block;
  margin-top:2px;
  font-size:.75rem;
  line-height:1.45;
  color:var(--ink-mute);
}
.addon__note{
  display:block;
  margin-top:4px;
  font-size:.6875rem;
  line-height:1.4;
  color:var(--warn);
}
.addon__price{
  flex:none;
  font-size:.875rem;
  font-weight:700;
}

/* ---------- features ---------- */

.buy__features{
  margin:0;
  padding:0;
  list-style:none;
  display:flex;
  flex-wrap:wrap;
  gap:6px;
}
.buy__features li{
  padding:4px 10px;
  border:1px solid var(--hair-soft);
  border-radius:999px;
  font-size:.75rem;
  font-weight:600;
  color:var(--ink-mute);
}

/* ---------- buttons ---------- */

.btn{
  -webkit-appearance:none;
  appearance:none;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:100%;
  min-height:48px;
  padding:0 20px;
  border-radius:4px;
  border:1px solid transparent;
  background:none;
  font:inherit;
  font-weight:600;
  font-size:.9375rem;
  color:var(--ink);
  text-decoration:none;
  cursor:pointer;
  transition:background-color .15s ease, border-color .15s ease;
  -webkit-tap-highlight-color:transparent;
  touch-action:manipulation;
}
.btn--primary{background:var(--accent); color:#16121f}
.btn--primary:hover:not(:disabled){background:#a596f8}
.btn--primary:active:not(:disabled){background:#8271e8}
.btn--ghost{border-color:var(--hair)}
.btn--ghost:hover{border-color:rgba(242,241,248,.5); background:rgba(242,241,248,.06)}
.btn:disabled{opacity:.45; cursor:not-allowed}
.btn:focus-visible,
.icon-btn:focus-visible,
.home-link:focus-visible,
.dash-link:focus-visible,
.foot a:focus-visible{outline:2px solid var(--accent); outline-offset:3px}

.icon-btn{
  -webkit-appearance:none;
  appearance:none;
  width:34px;
  height:34px;
  display:flex;
  align-items:center;
  justify-content:center;
  border:1px solid var(--hair-soft);
  border-radius:4px;
  background:none;
  color:var(--ink-dim);
  font:inherit;
  font-size:.875rem;
  cursor:pointer;
  transition:border-color .15s ease, color .15s ease;
  -webkit-tap-highlight-color:transparent;
}
.icon-btn:hover{border-color:var(--hair); color:var(--ink)}

.buy__note{
  margin:12px 0 0;
  font-size:.75rem;
  line-height:1.55;
  color:var(--ink-mute);
}
/* Only the subscription note carries a link, to the terms that spell
   the renewal out. Underlined rather than colour-only so it reads as a
   link against muted text. */
.buy__note a{color:var(--ink-dim); text-underline-offset:2px}
.buy__note a:hover{color:var(--ink)}
.buy__note a:focus-visible{outline:2px solid var(--accent); outline-offset:2px}

/* ---------- messages ---------- */

.msg,
.notice{
  margin:14px 0 0;
  padding:11px 13px;
  border:1px solid var(--hair-soft);
  border-left:2px solid var(--warn);
  border-radius:4px;
  background:rgba(35,35,54,.7);
  font-size:.8125rem;
  line-height:1.55;
  color:var(--ink-dim);
}
.msg--bad{border-left-color:var(--danger)}
.msg--good{border-left-color:var(--ok)}
.notice{margin:0 0 18px}

/* ---------- the cart ---------- */

.cart-backdrop{
  position:fixed;
  inset:0;
  z-index:40;
  background:rgba(12,12,20,.6);
}

.cart{
  position:fixed;
  top:0;
  right:0;
  bottom:0;
  z-index:41;
  width:min(420px, 100vw);
  display:flex;
  flex-direction:column;
  border-left:1px solid var(--hair-soft);
  background:#1b1b29;
  padding:
    env(safe-area-inset-top)
    env(safe-area-inset-right)
    env(safe-area-inset-bottom)
    0;
}

.cart__head{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
  padding:16px 18px;
  border-bottom:1px solid var(--hair-soft);
}
.cart__head h2{
  margin:0;
  display:flex;
  align-items:center;
  gap:9px;
  font-size:1.0625rem;
  font-weight:700;
  letter-spacing:-.02em;
}
.cart__count{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-width:20px;
  height:20px;
  padding:0 6px;
  border-radius:999px;
  background:var(--hair-soft);
  color:var(--ink-mute);
  font-family:"Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.625rem;
  font-weight:400;
  line-height:1;
}

.cart__body{
  flex:1 1 auto;
  overflow-y:auto;
  padding:16px 18px;
}
.cart__empty{
  margin:0;
  padding:26px 0;
  text-align:center;
  font-size:.875rem;
  color:var(--ink-mute);
}

.cart__lines{margin:0; padding:0; list-style:none}

.line{
  display:flex;
  align-items:flex-start;
  gap:12px;
  padding:12px 0;
}
.line + .line{border-top:1px solid var(--hair-soft)}
.line__text{flex:1 1 auto; min-width:0}
.line__name{
  display:block;
  font-size:.875rem;
  font-weight:600;
  line-height:1.3;
}
.line__meta{
  display:block;
  margin-top:2px;
  font-family:"Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.625rem;
  letter-spacing:.04em;
  text-transform:uppercase;
  color:var(--ink-mute);
}
.line__note{
  display:block;
  margin-top:4px;
  font-size:.6875rem;
  line-height:1.4;
  color:var(--warn);
}
/* Repeated under the name once there is more than one, because the
   line price is then a product of two numbers and the customer should
   be able to see both of them. */
.line__unit{
  display:block;
  margin-top:4px;
  font-size:.6875rem;
  line-height:1.4;
  color:var(--ink-mute);
}
.line__price{
  flex:none;
  min-width:4.5ch;
  text-align:right;
  font-size:.875rem;
  font-weight:700;
  font-variant-numeric:tabular-nums;
}

/* The same stepper as the product page, small enough to sit in a cart
   row. Somebody who meant twelve and added ten should not have to
   empty the cart and start again. */
.line__qty{
  flex:none;
  display:flex;
  align-items:center;
  gap:2px;
}
.line__step{
  -webkit-appearance:none;
  appearance:none;
  width:24px;
  height:24px;
  display:flex;
  align-items:center;
  justify-content:center;
  border:1px solid var(--hair-soft);
  border-radius:4px;
  background:none;
  color:var(--ink-dim);
  font:inherit;
  font-size:.8125rem;
  line-height:1;
  cursor:pointer;
  transition:color .15s ease, border-color .15s ease, background-color .15s ease;
  -webkit-tap-highlight-color:transparent;
  touch-action:manipulation;
}
.line__step:hover:not(:disabled){border-color:var(--accent); color:var(--ink); background:rgba(147,130,246,.14)}
.line__step:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
.line__step:disabled{opacity:.3; cursor:not-allowed}
.line__n{
  min-width:2.5ch;
  text-align:center;
  font-size:.8125rem;
  font-weight:600;
  font-variant-numeric:tabular-nums;
}
.line__x{
  -webkit-appearance:none;
  appearance:none;
  flex:none;
  width:26px;
  height:26px;
  display:flex;
  align-items:center;
  justify-content:center;
  border:none;
  border-radius:4px;
  background:none;
  color:var(--ink-mute);
  font:inherit;
  font-size:.875rem;
  cursor:pointer;
  transition:color .15s ease, background-color .15s ease;
  -webkit-tap-highlight-color:transparent;
}
.line__x:hover{color:var(--danger); background:rgba(240,120,127,.12)}
.line__x:focus-visible{outline:2px solid var(--accent); outline-offset:2px}

.cart__foot{
  flex:none;
  padding:16px 18px;
  border-top:1px solid var(--hair-soft);
  background:rgba(22,22,31,.6);
}
/* Subtotal and the bulk saving, shown only when there IS a saving.
   A definition list rather than a table: it is two label/value pairs,
   and store.js hides the whole block when the discount is nothing. */
.cart__sums{
  display:grid;
  grid-template-columns:1fr auto;
  gap:3px 12px;
  margin:0 0 10px;
  padding:0 0 10px;
  border-bottom:1px solid var(--hair-soft);
  font-size:.75rem;
}
.cart__sums dt{margin:0; color:var(--ink-mute)}
.cart__sums dd{margin:0; text-align:right; font-variant-numeric:tabular-nums}
/* Scoped to beat the dt/dd colour above on specificity rather than
   with !important — two class selectors to their one class and one
   type. */
.cart__sums .cart__save{color:var(--ok); font-weight:600}

/* The affiliate code box. Sits above the sums because it CHANGES the
   sums — a control below the number it alters reads as unrelated. */
.cart__code{
  margin:0 0 12px;
  padding:0 0 12px;
  border-bottom:1px solid var(--hair-soft);
}
.cart__coderow{display:flex; gap:8px; align-items:stretch}
/* The input is .cart__input, which is display:block;width:100%. Inside
   a flex row that would push the button out, so the width is handed
   back to flex here. */
.cart__coderow .cart__input{
  flex:1 1 auto;
  width:auto;
  min-width:0;
  margin-bottom:0;
  text-transform:uppercase;
}
.cart__coderow .cart__input::placeholder{text-transform:none}
/* .btn is width:100% and min-height:48px, which inside a flex row
   makes the button's base size the whole row and squashes the input
   to nothing. Both have to be undone, not just the width. */
.cart__codebtn{
  flex:none;
  width:auto;
  height:38px;
  min-height:38px;
  padding:0 16px;
  font-size:.8125rem;
}

/* Shown instead of the row once a code is on. */
.cart__codeon{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:10px;
  margin:0;
  padding:9px 11px;
  border:1px solid rgba(74,222,128,.35);
  border-radius:6px;
  background:rgba(74,222,128,.08);
  font-size:.8125rem;
}
.cart__codetag{color:var(--ok); font-weight:600}
.cart__codex{
  flex:none;
  padding:0;
  border:0;
  background:none;
  color:var(--ink-mute);
  font:inherit;
  font-size:.75rem;
  text-decoration:underline;
  cursor:pointer;
}
.cart__codex:hover{color:var(--ink)}
.cart__codex:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:3px}

.cart__code .msg{margin:8px 0 0; font-size:.75rem}

.cart__total{
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  gap:12px;
  margin-bottom:4px;
}
.cart__total span{font-size:.8125rem; color:var(--ink-mute)}
.cart__total strong{
  font-size:1.5rem;
  font-weight:700;
  letter-spacing:-.02em;
}
/* Guests only. There is no <form> here and there must not be — the
   Content-Security-Policy says form-action 'none' — so these are
   inputs beside a button that calls fetch. */
.cart__email{
  margin:0 0 14px;
  padding:12px 0 0;
  border-top:1px solid var(--hair-soft);
}
.cart__label{
  display:block;
  margin:0 0 5px;
  font-family:"Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.625rem;
  letter-spacing:.05em;
  text-transform:uppercase;
  color:var(--accent);
}
.cart__label + .cart__input{margin-bottom:10px}
.cart__input{
  -webkit-appearance:none;
  appearance:none;
  display:block;
  width:100%;
  box-sizing:border-box;
  height:38px;
  padding:0 11px;
  border:1px solid var(--hair-soft);
  border-radius:6px;
  background:rgba(30,30,46,.55);
  color:var(--ink);
  font:inherit;
  font-size:.875rem;
}
.cart__input::placeholder{color:rgba(185,182,204,.4)}
.cart__input:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-color:var(--accent)}

.cart__hint{
  margin:0 0 12px;
  font-size:.75rem;
  line-height:1.5;
  color:var(--ink-mute);
}
.cart__fine{
  margin:10px 0 0;
  font-size:.6875rem;
  line-height:1.5;
  color:rgba(185,182,204,.5);
}

/* ---------- order result ---------- */

.order{
  padding:clamp(20px, 3vw, 30px);
  border:1px solid var(--hair-soft);
  border-radius:6px;
  background:rgba(35,35,54,.6);
}
.order__eyebrow{
  margin:0 0 6px;
  font-family:"Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.625rem;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--accent);
}
.order h2{
  margin:0 0 10px;
  font-size:clamp(1.375rem, 2.4vw, 1.75rem);
  font-weight:700;
  letter-spacing:-.02em;
  line-height:1.1;
}
.order__body{
  margin:0 0 14px;
  font-size:.9375rem;
  line-height:1.6;
  color:var(--ink-dim);
}
.order__items{
  margin:0 0 18px;
  padding:0;
  list-style:none;
  border:1px solid var(--hair-soft);
  border-radius:4px;
  overflow:hidden;
}
.order__items li{
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  gap:12px;
  padding:9px 12px;
  background:rgba(22,22,31,.6);
  font-size:.8125rem;
}
.order__items li + li{border-top:1px solid var(--hair-soft)}
.order__items .hint{
  font-family:"Martian Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.6875rem;
  color:var(--ink-mute);
}
.order__actions{
  display:grid;
  grid-template-columns:repeat(2, minmax(0, 1fr));
  gap:10px;
}

/* ---------- footer ---------- */

.foot{
  display:flex;
  align-items:center;
  justify-content:space-between;
  flex-wrap:wrap;
  gap:10px 18px;
  padding:18px var(--pad) calc(var(--pad) * .6);
  border-top:1px solid var(--hair-soft);
  font-size:.75rem;
  color:var(--ink-mute);
}
.foot p{margin:0}
.legal-links{display:flex; flex-wrap:wrap; gap:14px}
.foot a{color:var(--ink-mute); text-decoration:none}
.foot a:hover{color:var(--ink-dim); text-decoration:underline}

/* ---------- narrow ---------- */

@media (max-width: 900px){
  .detail{grid-template-columns:minmax(0, 1fr)}
  /* Words before pictures on a phone: the buy column carries the price
     and the button, and a gallery above it pushes both below the fold. */
  .gallery{order:2}
  .buy{order:1}
}

@media (max-width: 560px){
  .grid{grid-template-columns:repeat(auto-fill, minmax(150px, 1fr)); gap:10px}
  .order__actions{grid-template-columns:minmax(0, 1fr)}
  .topbar{padding-top:calc(var(--pad) * .35)}
  .cart{width:100vw; border-left:none}
}

@media (prefers-reduced-motion: reduce){
  *{transition:none !important}
  .tile:hover{transform:none}
}
