/* Ultraviolet, applied to markup this preview did not write.
 *
 * Three pages reuse their production script exactly as it is: `executors.js`,
 * `key.js` with `gateway.js`, and `account.js` with `emailauth.js`. Between
 * them they own the executor table, the redeem contract, the free-key gateway,
 * the sign-in flow and the licence list, and every one of those is either
 * security-shaped or money-shaped. Rewriting them to change how they look would
 * be putting a redesign in front of the flows where a mistake costs somebody
 * their account or their money.
 *
 * So the behaviour is untouched and this sheet restyles what those files emit.
 * It is a separate file rather than more of `uv.css` because the two have
 * different rules: `uv.css` is a design system and its class names are chosen,
 * while every selector here is a name somebody else picked and this file may
 * not change. If a page in this directory ever stops reusing a script, its
 * block here goes with it.
 *
 * Same tokens, same light, same restraint. Nothing here declares a colour.
 *
 * The shared shapes that used to open this sheet (.pad, .mt, .dim, .fineprint,
 * .two, .more-wrap, .tagrow, .docacts, .cols, .pane, .hint, .helpline, .kicker,
 * .code and .tick) live in uv.css since 2026-09-27, because pages that no
 * longer load this sheet draw them too.
 */

/**
 * `.field` is what key.js, emailauth.js and checkout.js build. Same treatment
 * as `.f`.
 *
 * This rule is also the one that killed the site's whole background for eight
 * days. `uv.css` called its fixed full-window ambient layer `.field` too, this
 * sheet loads second, and so the layer resolved to `position: relative` at zero
 * height on every page: no violet-to-black ground, no hairline grid, no
 * watermark, and nothing anywhere failed. The layer is `.amb` now and a test in
 * tests/theme-signatures.test.mjs refuses the two names converging again, but
 * the lesson generalises past that one pair: THIS SHEET WINS EVERY TIE WITH
 * uv.css, so a name invented here quietly takes over anything uv.css already
 * calls the same thing. Grep before adding one.
 */
.field { display: grid; gap: 9px; min-width: 0; margin-bottom: var(--s4); position: relative; }
.field > span { font: 500 12px/1.4 var(--face); letter-spacing: 0.01em; color: var(--mute); }
.field input {
  width: 100%; min-width: 0; min-height: 48px; padding: 12px 14px; border-radius: var(--r1);
  border: 1px solid var(--hair-2); background: var(--sunk); color: var(--ink);
  font: 500 14px/1.5 var(--mono); letter-spacing: 0.01em;
  transition: border-color var(--tint) ease, box-shadow var(--move) var(--out);
}
.field input::placeholder { color: var(--faint); }
.field input:focus {
  outline: none; border-color: var(--vio);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--vio) 20%, transparent);
}
.field input[readonly] { color: var(--mute); cursor: default; }
.field input[aria-invalid="true"] { border-color: var(--bad); }
.field input:disabled { opacity: 0.65; cursor: not-allowed; }
.field .ic { position: absolute; right: 13px; bottom: 13px; color: var(--faint); pointer-events: none; }
/* The shape check while somebody types. Local only, so it tells nobody whether
   a key exists: it is true of any string of the right form. */
.field[data-shape="partial"] input { border-color: var(--warn); }
.field[data-shape="ok"] input { border-color: var(--ok); box-shadow: 0 0 20px -12px var(--ok); }

/* ══ WHAT THE KEY TURNED OUT TO BE ════════════════════════════════════════
   key.js fills this after a redeem. Every row is a question the support
   channel gets asked, so printing them is the cheapest support work there is. */
.kfacts {
  display: grid; margin-top: var(--s4); border: 1px solid var(--hair);
  border-radius: var(--r2); overflow: hidden;
  animation: uv-facts var(--enter) var(--out) both;
}
@keyframes uv-facts { from { opacity: 0; transform: translateY(7px); } }
.kfact {
  position: relative; isolation: isolate;
  display: grid; grid-template-columns: auto minmax(0, 1fr) minmax(0, 1fr); gap: var(--s3);
  align-items: center; padding: var(--s4); font-size: 13.5px;
  border-bottom: 1px solid var(--hair);
  transition: background-color var(--tint) ease;
}
.kfact:last-child { border-bottom: 0; }
/* A row in a list gets a horizontal sweep rather than a circular pool: the
   shape of the light should match the shape of the thing it is lighting, and a
   round spotlight on a 900px row reads as a smudge. Only the horizontal
   position is tracked for the same reason. */
.kfact::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; opacity: 0;
  background: linear-gradient(90deg, transparent,
              color-mix(in srgb, var(--vio) 13%, transparent) var(--cx, 50%), transparent);
  transition: opacity var(--tint) ease;
}
.kfact:hover::before { opacity: 1; }
.kfact:hover { background: color-mix(in srgb, var(--vio) 6%, transparent); }
.kfact .ic { width: 15px; height: 15px; color: var(--vio-txt); }
.kfact span { color: var(--mute); }
.kfact b { font-weight: 600; text-align: right; overflow-wrap: anywhere; }

/* ══ THE FREE KEY GATEWAY ═════════════════════════════════════════════════ */

.gwsteps { list-style: none; margin: 0 0 var(--s4); padding: 0; display: grid; }
.gwstep {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s4);
  padding: var(--s4) 0; border-bottom: 1px solid var(--hair);
}
.gwstep:last-child { border-bottom: 0; }
.gwstep > i {
  width: 28px; height: 28px; flex: none; display: grid; place-items: center;
  border: 1px solid var(--hair-2); border-radius: var(--r1);
  font: 400 12px/1 var(--disp); font-style: normal; color: var(--mute);
  transition: color var(--tint) ease, border-color var(--tint) ease, box-shadow var(--move) var(--out);
}
.gwstep em { display: block; font-style: normal; font-weight: 600; font-size: 14px; }
/* Uppercase for the tags a step carries ("12 hour key"), which is the label
   role. The one `small` that holds a real sentence opts out, because uppercase
   removes the word shapes a reader navigates by. */
.gwstep small {
  display: block; margin-top: 3px; color: var(--faint);
  font: 400 10.5px/1 var(--disp); letter-spacing: 0.13em; text-transform: uppercase;
}
.gwstep small.sentence {
  text-transform: none; letter-spacing: 0; font-family: var(--face); font-size: 12.5px; line-height: 1.6;
}
/* Three visibly different states, because a list where they look alike is a
   list that does not tell you where you are. */
.gwstep[data-state="cur"] > i { color: var(--vio-txt); border-color: var(--vio); box-shadow: 0 0 14px -4px var(--vio); }
.gwstep[data-state="done"] > i { color: var(--ok); border-color: var(--ok); }
.gwstep[data-state="locked"] { opacity: 0.65; }
.gwstep[data-state="locked"] em { color: var(--mute); }
.gwback { padding: 0 0 var(--s3); }
.gwact { margin-top: var(--s3); }
.gwnote { margin: var(--s2) 0 0; color: var(--mute); font-size: 13px; }
.gwnote.bad { color: var(--warn); }
.gwnote.good { color: var(--ok); }

.gwpicker {
  position: fixed; inset: 0; z-index: 200; display: grid; place-items: center; padding: clamp(12px, 3vw, 24px);
  background: rgba(4, 3, 9, 0.72); backdrop-filter: blur(6px);
  opacity: 0; visibility: hidden;
  transition: opacity var(--move) var(--out), visibility 0s linear var(--move);
}
.gwpicker[data-open="1"] { opacity: 1; visibility: visible; transition: opacity var(--move) var(--out), visibility 0s; }
.gwdialog {
  width: min(780px, 100%); border-radius: var(--r4); overflow: hidden; position: relative;
  /* Two lengths fitted in whatever height they wanted. Seven do not, and a
     dialog taller than the phone it is on puts its own close button off the
     bottom of the screen. The head and foot stay put; the cards scroll. */
  max-height: min(88vh, 780px); max-height: min(88dvh, 780px); display: flex; flex-direction: column;
  background: var(--surf);
  border: 1px solid var(--hair-2);
  box-shadow: inset 0 1px 0 var(--rim), var(--shadow-lift);
  transform: translateY(12px) scale(0.985);
  transition: transform var(--enter) var(--spring);
}
.gwpicker[data-open="1"] .gwdialog { transform: none; }
.gwdialog::before {
  content: ""; position: absolute; inset: -1px -1px auto; height: 1px; z-index: 5;
  background: var(--vio-txt); opacity: 0.45;
  -webkit-mask-image: linear-gradient(90deg, transparent, #fff 14%, #fff 86%, transparent);
          mask-image: linear-gradient(90deg, transparent, #fff 14%, #fff 86%, transparent);
}
.gwdhead { padding: var(--s6) var(--s6) var(--s4); border-bottom: 1px solid var(--hair); flex: none; }
.gwdhead h3 { margin-bottom: var(--s2); font-size: 20px; }
.gwdhead p { margin: 0; color: var(--mute); font-size: 13px; }
/* Two flush panels, when the choice was a provider. It is a length now, and
   there can be seven of them, so they are cards in a grid that reflows: three
   across on a laptop, two on a tablet, one on a phone, with no breakpoint doing
   the arithmetic. The scroll lives here rather than on the dialog so the title
   and the Not now button stay on screen while the list moves under them. */
.gwpick {
  display: grid; gap: var(--s3); padding: var(--s5) var(--s6);
  grid-template-columns: repeat(auto-fit, minmax(min(196px, 100%), 1fr));
  min-height: 0;
  overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  /* Fades the last few pixels rather than cutting them, so a list that
     continues looks like one. mask only: nothing here needs a background. */
  -webkit-mask-image: linear-gradient(180deg, #fff calc(100% - 26px), transparent);
          mask-image: linear-gradient(180deg, #fff calc(100% - 26px), transparent);
}
.gwmethod {
  position: relative; isolation: isolate;
  display: flex; flex-direction: column; gap: var(--s2);
  text-align: left; padding: var(--s5); width: 100%; min-width: 0;
  border: 1px solid var(--hair); border-radius: var(--r3);
  background: color-mix(in srgb, var(--raise) 46%, transparent);
  box-shadow: inset 0 1px 0 var(--rim);
  transition: background-color var(--tint) ease, border-color var(--tint) ease,
              transform var(--move) var(--spring);
}
/* A pool, not a sweep: these are tall cards rather than rows, so the light takes
   the shape of the thing it is on. Same rule as .kfact, opposite answer, for the
   same reason. */
.gwmethod::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; opacity: 0;
  border-radius: inherit;
  background: radial-gradient(260px circle at var(--cx, 50%) var(--cy, 50%),
              color-mix(in srgb, var(--vio-txt) 6%, transparent), transparent 64%);
  transition: opacity var(--move) ease;
}
.gwmethod:hover::after { opacity: 1; }
/* The rail rides the left edge INSIDE the card now that a card has one. At
   left: 0 with a radius it would poke through the rounded corners. */
.gwmethod::before {
  content: ""; position: absolute; left: -1px; top: 10px; bottom: 10px; width: 2px;
  border-radius: 0 2px 2px 0;
  background: var(--vio-txt);
  transform: scaleY(0); transform-origin: center;
  transition: transform var(--move) var(--out);
}
.gwmethod:hover, .gwmethod:focus-visible {
  background: color-mix(in srgb, var(--vio) 7%, transparent);
  border-color: var(--hair-lit);
  transform: translateY(-1px);
}
.gwmethod:hover::before, .gwmethod:focus-visible::before { transform: scaleY(1); }
.gwmethod:focus-visible { outline: 2px solid var(--vio-txt); outline-offset: -3px; }
.gwmethod .gwbadge {
  display: inline-flex; align-items: center; padding: 3px 9px; margin-left: 4px;
  border-radius: var(--r1); border: 1px solid currentColor;
  font: 600 9px/1 var(--disp); letter-spacing: 0.13em; text-transform: uppercase;
  /* On the hours line rather than above it. Above, a badged card started lower
     than the two beside it and the row of numbers stopped being a row. */
  transform: translateY(-2px);
}
.gwmethod .b-rec { color: var(--ion); }
.gwmethod .b-best { color: var(--warn); }
/* The number IS the offer, so it is the biggest thing on the card and the unit
   rides its baseline rather than sitting under it. */
.gwmethod .gwhrs { display: flex; align-items: baseline; gap: 5px; flex-wrap: wrap; }
.gwmethod .gwhrs b {
  font: 700 32px/1 var(--disp); letter-spacing: -0.025em;
  color: var(--vio-txt); font-variant-numeric: tabular-nums;
}
.gwmethod .gwhrs i {
  font-style: normal; font: 600 10.5px/1 var(--disp);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint);
}
.gwmethod b { font: 600 17px/1.2 var(--disp); letter-spacing: 0.02em; }
.gwmethod .gwmeta { color: var(--faint); font: 500 11px/1.35 var(--mono); }
.gwmethod p { margin: 0; color: var(--mute); font-size: 12.5px; line-height: 1.55; }
/* Pinned to the bottom rather than pushed there by the paragraph above it. Four
   of the seven cards carry no paragraph, and on those the call to action sat
   halfway up a card the grid had stretched to match its neighbours. */
.gwmethod .gwgo { margin-top: auto; padding-top: var(--s2); color: var(--vio-txt); font: 600 12.5px/1 var(--disp); }
/* Coloured by what the run is made of, so the list can be scanned rather than
   read. Keyed on the target id, which is the same string the server uses. */
.gwmethod[data-target^="wi-"] .gwhrs b,
.gwmethod[data-target^="wi-"] .gwgo { color: var(--ion); }
.gwmethod[data-target="hybrid-36"] .gwhrs b,
.gwmethod[data-target="hybrid-36"] .gwgo { color: var(--warn); }
.gwmethod[data-target="hybrid-36"] {
  border-color: color-mix(in srgb, var(--warn) 22%, transparent);
  /* Seven cards in three columns leaves the seventh alone on its own row, half
     off the bottom of the scroll. It is also the longest key on the menu, so it
     takes the width rather than the leftovers. Harmless in one column. */
  grid-column: 1 / -1;
}
@media (min-width: 621px) {
  .gwmethod[data-target="hybrid-36"] {
    flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--s4);
  }
  .gwmethod[data-target="hybrid-36"] .gwhrs { flex: none; }
  .gwmethod[data-target="hybrid-36"] .gwmeta { flex: none; }
  .gwmethod[data-target="hybrid-36"] p { flex: 1 1 12rem; }
  /* margin-top: auto is what pins it to the bottom of a column; in this row it
     would push it off the centre line. */
  .gwmethod[data-target="hybrid-36"] .gwgo { margin: 0 0 0 auto; padding-top: 0; white-space: nowrap; }
}
/* The sign-in dialog's body, which the same gateway.js opens before the first
   step when nobody is signed in.

   One column rather than the picker's two, because these are not two options
   being weighed against each other: they are the same destination through
   whichever account somebody already has. Full width, so on the phone this was
   reported from the tap target is the whole row. */
.gwauth { padding: var(--s6); display: grid; gap: var(--s3); }
/* Laid out exactly as the account page lays the same two controls out, down to
   the arrow riding the right edge: it is the same button doing the same thing,
   and somebody who has seen one should not have to read the other. */
.gwauth .btn { width: 100%; justify-content: flex-start; }
.gwauth .btn .ic-arw { margin-left: auto; }
.gwauth .gwnote { margin: var(--s2) 0 0; text-align: center; }

/* ── adding time to a key already held ──────────────────────────────────────
   Rows under the length cards, inside the same scrolling area and spanning
   the grid. A row is one key: what it opens, when it runs out, how many
   extensions it has left, and the two amounts on offer as buttons. Quieter
   than the cards on purpose: it is the second question on the dialog. */
.gwextend { grid-column: 1 / -1; margin-top: var(--s2); padding-top: var(--s4); border-top: 1px solid var(--hair); }
.gwextend h4 { margin: 0 0 var(--s1); font: 600 15px/1.3 var(--disp); }
.gwextend > p { margin: 0 0 var(--s3); color: var(--mute); font-size: 12.5px; line-height: 1.55; }
.gwextend .gwnote { margin: 0; }
.gwext {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3); flex-wrap: wrap;
  padding: var(--s3) var(--s4); margin-bottom: var(--s2); border-radius: var(--r2);
  background: var(--sunk); border: 1px solid var(--hair);
}
.gwext:last-child { margin-bottom: 0; }
.gwextinfo { min-width: 0; flex: 1 1 12rem; }
.gwextinfo b { display: block; font: 600 14px/1.35 var(--disp); overflow-wrap: anywhere; }
.gwextinfo small { display: block; color: var(--faint); font: 400 11.5px/1.5 var(--mono); overflow-wrap: anywhere; }
.gwextacts { display: flex; gap: var(--s2); flex: none; flex-wrap: wrap; }
.gw-ext {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  min-height: 44px; padding: 8px 14px; border-radius: var(--r2); cursor: pointer;
  border: 1px solid var(--hair-2); background: var(--surf); color: var(--ink);
  font: inherit; text-align: left;
  transition: border-color var(--tint) ease, background-color var(--tint) ease, transform var(--move) var(--out);
}
.gw-ext b { font: 600 14px/1.2 var(--disp); color: var(--vio-txt); }
.gw-ext i { font: 500 10.5px/1.3 var(--mono); font-style: normal; color: var(--faint); }
.gw-ext:hover, .gw-ext:focus-visible { border-color: var(--vio); background: var(--raise); transform: translateY(-1px); }
.gw-ext:focus-visible { outline: 2px solid var(--vio-txt); outline-offset: 2px; }
/* The line above the steps naming the key an extension is for. */
.gwextfor { color: var(--mute); font-size: 13px; }
.gwextfor b { color: var(--ink); font-weight: 600; overflow-wrap: anywhere; }
.gwdfoot { padding: var(--s4) var(--s6) var(--s6); text-align: right; border-top: 1px solid var(--hair); flex: none; }
@media (max-width: 620px) {
  /* auto-fit already collapses to one column here; this is about the room the
     seven cards are given.

     A phone is 844px tall and the title, the lead and the Not now bar were
     taking a third of it in desktop sizes, so four of the seven lengths were
     below the fold before anybody had scrolled. The head still says what it
     says; it says it at the size a phone reads. */
  .gwdialog { max-height: 92vh; max-height: 92dvh; }
  .gwdhead { padding: var(--s4) var(--s4) var(--s3); }
  .gwdhead h3 { font-size: 17px; margin-bottom: 4px; }
  .gwdhead p { font-size: 12.5px; }
  .gwdfoot { padding: var(--s3) var(--s4); }
  .gwpick { padding: var(--s3) var(--s4); gap: var(--s2); }
  .gwmethod { padding: var(--s3) var(--s4); gap: var(--s1); }
  .gwmethod .gwhrs b { font-size: 26px; }
  .gwmethod .gwgo { padding-top: var(--s2); }
  .gwauth { padding: var(--s4); }
  .gwdfoot .btn { min-height: 44px; }
  .gwext { padding: var(--s3); }
  .gwextacts { width: 100%; }
  .gw-ext { flex: 1 1 0; }
}
/* Somebody who has asked for less movement still gets the state changes, just
   not the lift. */
@media (prefers-reduced-motion: reduce) {
  .gwmethod, .gwmethod::after, .gwmethod::before, .gw-ext { transition: none; }
  .gwmethod:hover, .gwmethod:focus-visible, .gw-ext:hover, .gw-ext:focus-visible { transform: none; }
}

/* The progress bar and the numbered steps key.js also draws. */
.prog { height: 3px; border-radius: var(--pill); background: color-mix(in srgb, var(--vio-txt) 14%, transparent); margin: 2px 0 var(--s4); overflow: hidden; }
.prog i { display: block; height: 100%; width: var(--w, 0); background: var(--holo); box-shadow: 0 0 10px -2px var(--bloom); transition: width var(--slow) var(--out); }
.steps li {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s3);
  padding: var(--s3) 0; border-bottom: 1px solid var(--hair); align-items: start;
}

/* ══ EXECUTORS ════════════════════════════════════════════════════════════ */

.rbxnow { padding: clamp(18px, 2.4vw, 32px); margin-bottom: var(--s6); }
.rbxnow h2 { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s4); font-size: 17px; }
.rbxgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr)); gap: var(--s4); margin: 0; }
.rbxgrid div { min-width: 0; }
.rbxgrid dt { margin-bottom: 4px; font: 400 10.5px/1 var(--disp); letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); }
.rbxgrid dd { margin: 0; font: 500 13.5px/1.4 var(--mono); overflow-wrap: anywhere; }
.rbxgrid dd.when { margin-top: 3px; color: var(--mute); font-family: var(--face); font-size: 12px; }

.exbar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  flex-wrap: wrap; padding: 0 0 var(--s5);
}
.chips { display: flex; gap: var(--s2); flex-wrap: wrap; }
.chips button {
  position: relative; min-height: 44px; padding: 10px 16px; border-radius: var(--r1);
  border: 1px solid var(--hair); color: var(--mute);
  font: 400 12px/1 var(--disp); letter-spacing: 0.06em;
  transition: color var(--tint) ease, border-color var(--tint) ease,
              background-color var(--tint) ease, transform var(--tint) var(--spring);
}
.chips button:hover { color: var(--ink); border-color: var(--hair-2); transform: translateY(-1px); }
.chips button:focus-visible { outline: 2px solid var(--vio-txt); outline-offset: 3px; }
.chips button[aria-pressed="true"] {
  color: var(--ink); border-color: var(--hair-lit); background: color-mix(in srgb, var(--vio) 13%, var(--surf));
}
.count { margin: 0; color: var(--faint); font: 500 12px/1 var(--mono); }

.exgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(19rem, 100%), 1fr)); gap: var(--s5); }
.excard {
  position: relative; display: flex; flex-direction: column; gap: var(--s4);
  padding: clamp(18px, 2.2vw, 28px); border-radius: var(--r4); min-width: 0; overflow: hidden;
  background: var(--surf); border: 1px solid var(--hair);
  box-shadow: inset 0 1px 0 var(--rim);
  container-type: inline-size;
  transition: border-color var(--tint) ease, transform var(--move) var(--out), background-color var(--tint) ease;
}
/* One pixel, not three. Every raised control in this system moves by one on
   hover and the light does the rest; three was this component disagreeing with
   the rest of the site about how much a card should jump. */
.excard:hover { border-color: var(--hair-2); background: var(--raise); transform: translateY(-1px); }
.excard::before {
  content: ""; position: absolute; left: var(--s5); right: var(--s5); top: 0; height: 1px;
  background: var(--vio-txt);
  transform: scaleX(0); transition: transform var(--move) var(--out);
}
.excard:hover::before { transform: scaleX(1); }

/**
 * The pointer spotlight, on the reused components as well.
 *
 * The shell already tracks the cursor over `.excard` and `.kfact` and writes
 * `--cx/--cy` on them, and until now nothing read those properties outside the
 * catalogue card. So the executors page and the account page had a listener
 * running and no light to show for it, and they looked like a different site
 * from the one the shop is on.
 *
 * `::after` rather than a child element, because this markup is emitted by the
 * live scripts and is not ours to add elements to.
 */
.excard::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: 0;
  border-radius: inherit;
  background: radial-gradient(320px circle at var(--cx, 50%) var(--cy, 50%),
              color-mix(in srgb, var(--vio-txt) 5%, transparent), transparent 62%);
  transition: opacity var(--move) ease;
}
.excard:hover::after { opacity: 1; }
/* Everything the card actually contains sits above the light. */
.excard > * { position: relative; z-index: 1; }
.excard.is-off { opacity: 0.7; }
.excard.is-old .exlogo { filter: saturate(0.3); }
.excard.is-old:hover .exlogo { filter: none; }
/* The owner's pick reads differently before a word is read: a warmer surface
   and the rail already lit. */
.excard.is-pick { background: var(--raise); border-color: var(--hair-2); }
.excard.is-pick::before { transform: scaleX(1); opacity: 0.75; }
.excard.is-pick:hover::before { opacity: 1; }
.expick {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px 4px 9px; border-radius: var(--r1);
  background: color-mix(in srgb, var(--vio) 12%, var(--surf)); color: var(--vio-txt);
  border: 1px solid var(--hair-lit);
  font: 600 10px/1.3 var(--disp); letter-spacing: 0.08em; text-transform: uppercase;
}
.expick .ic { width: 12px; height: 12px; color: inherit; }

.exhead { display: flex; align-items: center; gap: var(--s3); }
.exhead h3 {
  flex: 1; min-width: 0; margin: 0; font-size: clamp(17px, 5.5cqi, 20px);
  overflow-wrap: anywhere; line-height: 1.25; hyphens: none;
}
/* The logo sits over a monogram rather than replacing one, so a host that is
   slow, blocked or gone leaves a designed square instead of a broken glyph. */
.exlogo {
  position: relative; flex: none; width: 48px; height: 48px; border-radius: var(--r2);
  overflow: hidden; display: grid; place-items: center;
  background: var(--sunk); border: 1px solid var(--hair);
  transition: border-color var(--tint) ease, transform var(--move) var(--spring);
}
.exlogo i { font-style: normal; font: 600 17px/1 var(--disp); color: var(--faint); }
.exlogo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; background: var(--sunk); }
.excard:hover .exlogo { border-color: var(--hair-lit); }
.explat { margin: 0; color: var(--mute); font-size: 12.5px; }

.exflags { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.pill {
  display: inline-flex; align-items: center; gap: 5px; padding: 4px 10px;
  border-radius: var(--r1); border: 1px solid var(--hair-2); background: var(--sunk);
  color: var(--mute); font: 400 11.5px/1 var(--disp); white-space: nowrap;
  transition: border-color var(--tint) ease, color var(--tint) ease;
}
.pill .ic { width: 12px; height: 12px; color: inherit; }
.pill.mono { font-family: var(--mono); font-size: 11px; }
.pill.ok { color: var(--ok); border-color: rgba(69, 223, 160, 0.32); }
.pill.bad { color: var(--bad); border-color: rgba(255, 122, 144, 0.32); }
.pill.free { color: var(--ion); border-color: color-mix(in srgb, var(--ion) 32%, transparent); }
.pill.cost { color: var(--ink); }
/* Our own promise, so it carries the accent and a little more weight than the
   facts around it without shouting the way an uppercase badge would. */
.pill.sup { color: var(--vio-txt); border-color: var(--hair-lit); background: color-mix(in srgb, var(--vio) 13%, transparent); font-weight: 700; }

.exscores { display: grid; gap: var(--s2); }
.score { display: grid; grid-template-columns: 3.4rem minmax(0, 1fr) 2.6rem; gap: var(--s2); align-items: center; }
.score > span { color: var(--faint); font: 400 11px/1 var(--disp); letter-spacing: 0.1em; }
.score > b { text-align: right; font: 600 11.5px/1 var(--disp); font-variant-numeric: tabular-nums; }
.track { height: 6px; border-radius: var(--pill); background: color-mix(in srgb, var(--vio-txt) 13%, transparent); overflow: hidden; }
.track i {
  display: block; height: 100%; width: var(--w, 0%); border-radius: var(--pill);
  background: var(--holo); transform-origin: left center;
  animation: uv-grow var(--slow) var(--out) both;
}
@keyframes uv-grow { from { transform: scaleX(0); } }
.track i.full { box-shadow: 0 0 10px -2px var(--vio); }

.exstats { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--s3); margin: 0; padding-top: var(--s4); border-top: 1px solid var(--hair); }
.exstats div { min-width: 0; }
.exstats dt { margin-bottom: 3px; font: 400 10.5px/1 var(--disp); letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); }
.exstats dd { margin: 0; font: 600 19px/1.3 var(--disp); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.exstats dd .dim { font-size: 13px; font-weight: 500; }
.exruns { margin: 0; font-size: 13px; }
.exwhen { margin: 0; color: var(--faint); font-size: 12px; }
.exnote { margin: 0; color: var(--mute); font-size: 13px; line-height: 1.6; }
.exget { width: 100%; margin-top: auto; }
/* A sentence, not a disabled button. A greyed control reads as temporarily
   broken and invites a click. */
.exnolink { margin: auto 0 0; padding-top: var(--s3); border-top: 1px solid var(--hair); color: var(--faint); font-size: 12px; line-height: 1.6; }
.exsource { margin: var(--s5) 0 0; color: var(--faint); font-size: 12px; }
.exlegend { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s3); padding: 0 0 var(--s4); color: var(--mute); font-size: 13px; }
.tag.t-verified, .t-verified { color: var(--ok); border-color: rgba(69, 223, 160, 0.3); }
.t-supported { color: var(--ion); border-color: color-mix(in srgb, var(--ion) 30%, transparent); }
.t-partial { color: var(--warn); border-color: rgba(255, 200, 87, 0.3); }
.t-unsupported { color: var(--faint); border-color: var(--hair); }

.exfloor { padding: clamp(18px, 2.4vw, 32px); margin-top: var(--s8); }
.exfloor h2 { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s3); font-size: 20px; }
.floorlist { list-style: none; margin: 0 0 var(--s4); padding: 0; display: grid; }
.floorlist li {
  display: grid; grid-template-columns: auto auto minmax(0, 1fr); align-items: baseline; gap: var(--s3);
  padding: var(--s3) 0; border-bottom: 1px solid var(--hair); font-size: 13.5px;
}
.floorlist li:last-child { border-bottom: 0; }
.floorlist .ic { width: 15px; height: 15px; color: var(--ok); align-self: center; }
.floorlist b { white-space: nowrap; font-weight: 600; }
.floorlist span { color: var(--mute); min-width: 0; }
@media (max-width: 620px) {
  .floorlist li { grid-template-columns: auto minmax(0, 1fr); }
  .floorlist span { grid-column: 2; }
}

/* ══ ACCOUNT ══════════════════════════════════════════════════════════════ */

/**
 * A returning visitor never sees "Sign in to see your keys" flash first.
 *
 * `account.js` renders the signed-out panel and replaces it once its own fetch
 * comes back, so for the whole length of that request a signed-in reader is
 * looking at a sign-in form. It is the same complaint as the header knowing
 * nothing: the state was only discoverable by waiting for it.
 *
 * The shell writes the last known answer onto <html> before the first paint,
 * from a cache holding a boolean and a timestamp and nothing else. When it says
 * signed in, this hides the panel and shows a skeleton in its place, which is
 * honest about what is happening rather than wrong about who you are.
 *
 * `[data-session="in"]` ONLY. Absent and "unknown" both fall through to showing
 * the panel, so a reader with no cache, no JavaScript or a failed probe still
 * gets the way to sign in. The cache can be wrong for one request; wrong in
 * this direction costs a skeleton, wrong the other way costs somebody their
 * way in.
 */
html[data-session="in"] .signedout { display: none; }
html[data-session="in"] #app:has(.signedout)::before {
  content: ""; display: block; height: 172px; margin: var(--s8) 0;
  border-radius: var(--r3); border: 1px solid var(--hair);
  background: linear-gradient(100deg, var(--surf) 30%, var(--raise) 50%, var(--surf) 70%) 0 0 / 300% 100%;
  animation: uv-skel 1.5s ease-in-out infinite;
}
@keyframes uv-skel { to { background-position: -150% 0; } }

.signedout {
  max-width: 64rem; margin: var(--s6) auto; padding: clamp(24px, 4vw, 48px);
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s5) var(--s10);
  text-align: left;
}
.signedout > * { min-width: 0; }
.signedout > b { grid-column: 1 / -1; font: 600 clamp(24px, 3vw, 32px)/1.2 var(--disp); letter-spacing: -0.03em; }
.signedout > .signin-note { grid-column: 1 / -1; max-width: 64ch;
  margin: calc(-1 * var(--s2)) 0 0; color: var(--mute); font-size: 14px; line-height: 1.7; }
.signedout > .cta { display: grid; align-content: start; gap: var(--s3); margin: 0; }
.signedout > .cta .btn { width: 100%; min-height: 48px; justify-content: flex-start; }
.signedout > .cta .btn .ic-arw { margin-left: auto; }
.signedout > .emailauth { max-width: none; margin: 0; padding-left: var(--s8); border-left: 1px solid var(--hair); align-content: start; }
@media (max-width: 780px) {
  .signedout { grid-template-columns: 1fr; padding: clamp(18px, 4vw, 28px); margin: var(--s5) 0; }
  .signedout > * { grid-column: 1; }
  .signedout > .emailauth { padding: var(--s5) 0 0; border-left: 0; border-top: 1px solid var(--hair); }
}
.emailauth { display: grid; gap: var(--s4); }
.emailauth form { display: grid; gap: var(--s3); min-width: 0; }
.eadiv {
  margin: 0; display: flex; align-items: center; gap: var(--s3); color: var(--faint);
  font: 400 10.5px/1 var(--disp); letter-spacing: 0.14em; text-transform: uppercase;
}
.eadiv::before, .eadiv::after { content: ""; flex: 1; height: 1px; background: var(--hair); }
.emailauth input[inputmode="numeric"] {
  font-family: var(--mono); letter-spacing: 0.34em; text-align: center; font-size: 18px;
}
.linkish {
  padding: 8px 0; min-height: 36px; justify-self: start; color: var(--vio-txt);
  font: 500 13px/1.5 var(--face); text-align: left; text-decoration: underline; text-underline-offset: 3px;
  transition: color var(--tint) ease;
}
.linkish:hover { color: #fff; }
.eamsg { margin: 0; font-size: 13px; }
.eamsg:empty { display: none; }
.eamsg[data-kind="ok"] { color: var(--ok); }
.eamsg[data-kind="bad"] { color: var(--warn); }

/* the signed-in half */
/* auto-fit, not a hardcoded count. A fifth figure was added and a strip
   pinned to four columns answers that by wrapping one item onto a row of its
   own with three empty cells beside it. */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(9.5rem, 100%), 1fr)); gap: var(--s3); margin-bottom: var(--s6); }
@media (max-width: 760px) { .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.keyrow {
  position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s4); align-items: center; min-width: 0;
  padding: clamp(16px, 2.2vw, 24px); margin-bottom: var(--s3); border-radius: var(--r3);
  background: var(--surf); border: 1px solid var(--hair);
  transition: border-color var(--tint) ease, background-color var(--tint) ease,
              opacity var(--move) var(--out), transform var(--move) var(--out);
}
.keyrow:hover { border-color: var(--hair-2); background: var(--raise); }
/* The same horizontal sweep the feature rows get, so a licence on Account and a
   feature on a game page respond to the pointer the same way. This is most of
   what "the pages are on the same theme" actually means: not the same colours,
   which they already were, but the same answer to the same gesture. */
.keyrow::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, transparent,
              color-mix(in srgb, var(--vio) 11%, transparent) var(--cx, 50%), transparent);
  transition: opacity var(--tint) ease;
}
.keyrow:hover::after { opacity: 1; }
.keyrow > * { position: relative; z-index: 1; }
.keyrow::before {
  content: ""; position: absolute; left: 0; top: 12px; bottom: 12px; width: 2px; border-radius: 2px;
  background: var(--holo); box-shadow: 0 0 12px var(--bloom);
  transform: scaleY(0); transition: transform var(--move) var(--out);
}
.keyrow:hover::before { transform: scaleY(1); }
.keyrow.is-confirmed::before { transform: scaleY(1); background: var(--ok); box-shadow: 0 0 12px rgba(69, 223, 160, 0.7); }
.keyrow.is-failed::before { transform: scaleY(1); background: var(--warn); box-shadow: 0 0 12px rgba(255, 200, 87, 0.6); }
.keyrow.is-removing { opacity: 0 !important; transform: translateX(var(--s4)) scale(0.985); pointer-events: none; }
.keyrow[data-void="1"] { opacity: 0.66; }
.keyrow[data-void="1"] .lic-name { color: var(--mute); }
.keyrow small { color: var(--mute); font-size: 12px; }
.lic-main { min-width: 0; }
.lic-head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2); min-width: 0; margin-bottom: 3px; }
.lic-name { display: block; font: 600 16px/1.35 var(--disp); overflow-wrap: anywhere; }
.lic-tag {
  flex: none; padding: 3px 9px; border-radius: var(--r1);
  border: 1px solid var(--hair-2); color: var(--mute);
  font: 600 9.5px/1 var(--disp); letter-spacing: 0.13em; text-transform: uppercase;
}
.lic-tag-paid { color: var(--vio-txt); border-color: var(--hair-lit); }
.lic-tag-all { color: var(--ion); border-color: color-mix(in srgb, var(--ion) 34%, transparent); }
.lic-tag-count { font-family: var(--mono); font-weight: 500; letter-spacing: 0.04em; text-transform: none; font-size: 11px; }
.lic-clock {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px 3px 8px; border-radius: var(--r1);
  border: 1px solid var(--hair-2); background: var(--sunk); color: var(--mute);
  font: 400 11.5px/1 var(--disp); font-variant-numeric: tabular-nums;
}
.lic-clock > i { width: 6px; height: 6px; flex: none; border-radius: 50%; background: currentColor; }
.lic-clock[data-state="live"] { color: var(--ok); border-color: rgba(69, 223, 160, 0.34); }
.lic-clock[data-state="live"] > i { box-shadow: 0 0 9px currentColor; animation: uv-pulse 2.2s var(--out) infinite; }
.lic-clock[data-state="lifetime"] { color: var(--vio-txt); border-color: var(--hair-lit); }
.lic-clock[data-state="expired"] { color: var(--warn); border-color: rgba(255, 200, 87, 0.34); }
.lic-foot { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; min-width: 0; margin-top: 7px; }
.lic-ref { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; color: var(--faint);
  font: 400 12px/1.5 var(--mono); overflow-wrap: anywhere; }
.lic-reflabel {
  flex: none; padding: 1px 7px; border-radius: var(--r1); border: 1px solid var(--hair);
  font: 400 9px/1.5 var(--disp); letter-spacing: 0.13em; text-transform: uppercase; color: var(--faint);
}
.lic-copy {
  justify-self: start; min-height: 44px; display: inline-flex; align-items: center; gap: 7px;
  padding: 10px 14px; border-radius: var(--r1);
  border: 1px solid var(--hair); background: var(--sunk); color: var(--mute);
  font: 500 12px/1.3 var(--face);
  transition: color var(--tint) ease, border-color var(--tint) ease, transform var(--tint) var(--spring);
}
.lic-copy:hover { color: var(--vio-txt); border-color: var(--hair-lit); transform: translateY(-1px); }
.lic-copy.copied { color: var(--ok); border-color: rgba(69, 223, 160, 0.4); }
.lic-copy .ic { width: 15px; height: 15px; }
.lic-copy:disabled { opacity: 0.55; cursor: not-allowed; }
.lic-toolbar { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s4); flex-wrap: wrap; margin-bottom: var(--s4); }

.recrow {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto auto; gap: var(--s4);
  align-items: center; padding: var(--s3) var(--s2); border-bottom: 1px solid var(--hair);
  transition: background-color var(--tint) ease;
}
.recrow > div { min-width: 0; overflow-wrap: anywhere; }
.recrow:last-child { border-bottom: 0; }
.recrow:hover { background: color-mix(in srgb, var(--vio) 5%, transparent); }
.recrow b { font-weight: 600; }
.recrow small { display: block; margin-top: 2px; color: var(--faint); font-size: 12px; }
.recwhen { color: var(--mute); font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.recvia { color: var(--faint); font: 400 10px/1 var(--disp); letter-spacing: 0.13em; text-transform: uppercase; white-space: nowrap; }
.recamt { font: 500 13.5px/1 var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
@media (max-width: 640px) {
  .recrow { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "plan amount" "date method"; gap: var(--s2) var(--s3); padding: var(--s4) 0; }
  .recrow > div { grid-area: plan; }
  .recwhen { grid-area: date; }
  .recrow > :nth-child(3) { grid-area: method; text-align: right; }
  .recamt { grid-area: amount; text-align: right; }
}

.kv { display: flex; justify-content: space-between; gap: var(--s4); padding: 9px 0; border-bottom: 1px solid var(--hair); font-size: 13.5px; }
.kv:last-of-type { border-bottom: 0; }
.kv span { color: var(--mute); flex: 0 0 auto; }
.kv b { min-width: 0; font: 500 13px/1.6 var(--mono); text-align: right; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.acctfoot { display: flex; justify-content: flex-end; padding: var(--s5) 0 0; }

/* Fixed feedback is deliberate: Reset removes the control and Delete removes
   the row, so an inline confirmation would disappear with the thing it is
   confirming. */
.acct-toasts {
  position: fixed; right: var(--s5); bottom: var(--s5); z-index: 90;
  width: min(380px, calc(100vw - var(--s10))); display: grid; gap: var(--s2); pointer-events: none;
}
.acct-toast {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: start; gap: var(--s3);
  padding: var(--s4); border-radius: var(--r2); pointer-events: auto;
  background: var(--raise); border: 1px solid var(--hair); border-left-width: 2px;
  box-shadow: var(--shadow-lift), inset 0 1px 0 var(--rim);
  opacity: 0; transform: translateY(var(--s3)) scale(0.98);
  transition: opacity var(--move) ease, transform var(--move) var(--spring);
}
.acct-toast.is-visible { opacity: 1; transform: none; }
/* Leaving. The exit animation is already the base rule reasserting itself when
   `is-visible` comes off, so this class only has to stop the toast from being
   a target on its way out: a fading control that still swallows a click is a
   click that lands on nothing, and the pointer is often already moving toward
   whatever was underneath it. */
.acct-toast.is-leaving { pointer-events: none; }
.acct-toast-success { border-left-color: var(--ok); }
.acct-toast-error { border-left-color: var(--warn); }
.acct-toast-icon { width: 18px; height: 18px; margin-top: 1px; }
.acct-toast-success .acct-toast-icon { color: var(--ok); }
.acct-toast-error .acct-toast-icon { color: var(--warn); }
.acct-toast-copy b { display: block; font: 600 13.5px/1.4 var(--disp); }
.acct-toast-copy p { margin: 3px 0 0; color: var(--mute); font-size: 12.5px; line-height: 1.55; }
.acct-toast-close {
  display: grid; place-items: center; width: 44px; height: 44px; margin: -8px -8px 0 0;
  border-radius: var(--r1); color: var(--mute);
  transition: color var(--tint) ease, background-color var(--tint) ease;
}
.acct-toast-close:hover { color: var(--ink); background: color-mix(in srgb, var(--vio) 10%, transparent); }
@media (max-width: 560px) {
  .acct-toasts { left: var(--s4); right: var(--s4); bottom: max(var(--s4), env(safe-area-inset-bottom)); width: auto; }
}

.stat b.is-updated { animation: uv-bump var(--slow) var(--out); }
@keyframes uv-bump { 0% { color: var(--ok); transform: translateY(3px) scale(1.06); } 100% { transform: none; } }
.keyrow .btn:disabled { cursor: not-allowed; opacity: 0.5; }
.keyrow .btn.is-busy { cursor: wait; }
.keyrow .btn.is-busy .ic-lead { display: none; }
.keyrow .btn.is-busy::before {
  content: ""; width: 13px; height: 13px; flex: none; border-radius: 50%;
  border: 1px solid var(--hair-2); border-top-color: var(--vio-txt);
  animation: uv-spin 0.9s linear infinite;
}
@keyframes uv-spin { to { transform: rotate(1turn); } }

@media (prefers-reduced-motion: reduce) {
  .excard:hover, .keyrow:hover, .chips button:hover, .lic-copy:hover { transform: none; }
  .track i { animation: none; }
}

/* ══ THE KEY PAGE, AS TWO ROUTES ══════════════════════════════════════════
 *
 * The two panels were "Have a key?" and "No key yet": the same information
 * arranged as a question and its disappointment, where the second reads as the
 * consolation prize. It is not one. The free key is a real key, tied to the
 * same account and locked to the same device, and the only difference is how
 * long it lasts.
 *
 * So they are numbered routes of equal weight. The number is the whole device:
 * it says "there are two of these and you are choosing", which a heading alone
 * cannot say.
 */
.keyroutes { align-items: stretch; }
.keyroute { display: flex; flex-direction: column; }
/* The fine print settles at the bottom of whichever panel is shorter, so the
   two columns end together instead of one trailing off. */
.keyroute > .fineprint.mt { margin-top: auto; padding-top: var(--s5); }

.routetag {
  display: flex; align-items: center; gap: var(--s3);
  margin: 0 0 var(--s4);
  font: 400 10.5px/1 var(--disp); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--faint);
}
.routetag i {
  width: 22px; height: 22px; flex: none; display: grid; place-items: center;
  /* 11px/20px: the box is 22px with a 1px border, so the content box is 20px and
     a line box of 11px would centre on a half pixel. */
  font-style: normal; font: 600 11px/20px var(--disp); letter-spacing: 0;
  border-radius: var(--r1); border: 1px solid var(--hair-2);
  background: var(--sunk); color: var(--vio-txt);
}
/* The lit panel is the one somebody with a key wants, so its number is lit too.
   One difference between the two, and it is a difference in emphasis rather
   than in rank. */
.keyroute.lit .routetag i {
  border-color: var(--hair-lit); color: #fff;
  background: var(--btn-fill); box-shadow: 0 0 16px -6px var(--bloom);
}

@media (max-width: 900px) {
  /* Stacked, the numbers stop being a row of choices and become a sequence,
     which would read as "do 1, then do 2". They are alternatives, so the
     numbers go and the headings carry it. */
  .routetag i { display: none; }
}

/* What is behind the sign-in form, said before the form.
 *
 * The panel used to open with a heading and go straight into four password
 * fields. Somebody who is not sure whether an account is worth making was
 * looking at the work and none of the reason. These are the three things this
 * page actually holds, and they are the argument. */
.signin-what {
  grid-column: 1 / -1; list-style: none; margin: var(--s2) 0 var(--s3); padding: var(--s5) 0;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s5); width: 100%;
  border-block: 1px solid var(--hair);
}
.signin-what li {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s3);
  align-items: start; color: var(--mute); font-size: 13px; line-height: 1.6;
}
.signin-what .ic { width: 16px; height: 16px; margin-top: 3px; color: var(--vio-txt); }
@media (max-width: 780px) {
  .signin-what { grid-template-columns: 1fr; gap: var(--s3); }
}

/* How many times a key has run, beside its reference.
 *
 * The number has been counted on every execution since the loader was written
 * and shown nowhere, so somebody holding two keys could not tell which one they
 * actually use. It sits in the foot line with the reference because both are
 * facts ABOUT the licence rather than things to act on. */
.lic-runs {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 9px 3px 7px; border-radius: var(--r1);
  border: 1px solid var(--hair); background: var(--sunk);
  font-size: 11.5px; color: var(--faint); white-space: nowrap;
}
.lic-runs .ic { width: 13px; height: 13px; color: var(--vio-txt); }
.lic-runs b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }

/* ══ THE ACCOUNT FIGURES ══════════════════════════════════════════════════
 *
 * `.stat` is the design system's tile and it styles `.fig` and `.lbl`. The
 * account page is rendered by the live script, which emits `<b>` and `<span>`
 * instead, so the tiles were getting their box and NOTHING ELSE: the number
 * came out at body size and weight with the label running on beside it, four
 * of them in a row, on the page somebody opens to check what they own.
 *
 * That is most of why the owner called this page flat. It is not that it was
 * designed badly; it is that half the design was never reaching it.
 *
 * Same hierarchy as the home page bento, expressed for the markup that
 * actually arrives.
 */
/* Scoped to the strip these pages build (`.stats`), with `:where()` so the
   specificity is unchanged. Unscoped, `.stat > span` also reached the home
   page's figures, where it quietly set the margin and colour of every label
   and number the home page had not set for itself. */
:where(.stats) .stat > b {
  transition: color var(--tint) ease;
  display: block;
  font: 600 clamp(24px, 2.6vw, 33px)/1 var(--disp);
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em;
  color: var(--ink);
}
:where(.stats) .stat > span {
  display: block; margin-top: var(--s2);
  font: 400 10.5px/1.3 var(--disp); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--faint);
}
/* The lit tile takes the accent edge the panels use, so "these are your
   numbers" reads before any of them is read. */
.stat.lit { overflow: hidden; }
.stat.lit::after {
  content: ""; position: absolute; inset: -1px -1px auto; height: 1px;
  background: var(--holo); opacity: 0.55;
  transition: opacity var(--move) var(--out);
}
.stat.lit:hover::after { opacity: 1; }
.stat:hover > b { color: #fff; }

/* THE KEY ITSELF, which is the thing somebody opened this page to get.
 *
 * It was 13px mono in the accent colour, the same weight as the reference
 * below it and smaller than the licence name above it: the credential was the
 * third most prominent thing in its own row. */
.lic-key { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; min-width: 0; margin-top: var(--s3); }
.lic-keyval {
  padding: 12px var(--s3); border-radius: var(--r1);
  background: var(--sunk); border: 1px solid var(--hair-2);
  color: var(--ink); font: 500 14px/1.5 var(--mono); letter-spacing: 0.025em;
  overflow-wrap: anywhere; flex: 1; min-width: 0;
  transition: border-color var(--tint) ease, background-color var(--tint) ease;
}
.keyrow:hover .lic-keyval { border-color: var(--hair-lit); background: var(--void); }
/* Keep concealed keys in place so revealing a value does not shift the row. */
.keys-private .lic-keyval { filter: blur(7px); user-select: none; }

@media (max-width: 620px) {
  /* The row stops being three columns and becomes a stack, because the actions
     squeezed into a 90px column are the thing that makes this page unusable on
     a phone. */
  .keyrow { grid-template-columns: minmax(0, 1fr); gap: var(--s3); }
  .keyrow .rst, .keyrow .del, .keyrow .ext, .keyrow .lic-copy { width: 100%; min-height: 44px; justify-content: center; }
  .lic-key { flex-direction: column; align-items: stretch; }
  .lic-toolbar > .cta { width: 100%; }
  .lic-toolbar > .cta .btn { flex: 1; }
}

/* ── the six classes the reused scripts emit and this sheet never styled ──
 *
 * Found by listing every class name those files write into markup and checking
 * it against every selector here. Ninety-one classes, six with no rule at all,
 * and each one is something a reader looks at: the line saying which device a
 * licence is locked to, the Delete control on an expired one, and the Robux
 * option in checkout, which is a MASKED IMAGE and was therefore not merely
 * unstyled but invisible.
 *
 * This is the same shape as the account figures above. The preview styles
 * markup it did not write, so anything the other side adds arrives here
 * unstyled and nothing says so.
 */

/* The Robux mark where checkout.js writes a bare span rather than a sprite
 * reference, on the price line. With no rule it has no mask and no size: an
 * empty box where the payment option should be.
 *
 * A mask rather than an <img> because `icons/Robux.png` is a black glyph on
 * transparent: drawn as an image it is a black square on every dark surface on
 * this site, which is what the owner reported the first time. As a mask the
 * alpha channel is the shape and `background-color` is the ink, so it takes
 * `currentColor` like every other icon and inherits the hover and disabled
 * rules for free.
 *
 * ONLY here. The payment picker builds its icon from a generic `'#i-' + m.icon`
 * template, and masking THAT was tried and does not work: the element it lands
 * on is the bordered box, a mask clips the box away with everything else, and
 * the symbol inside keeps painting regardless because a `<symbol
 * fill="currentColor">` carries its own fill that an inherited one cannot
 * override. The sprite carries the real mark now, so the picker needs no rule
 * at all. See the `robux` entry in scripts/fetch-icons.mjs. */
.ic-robux {
  background-color: currentColor;
  -webkit-mask: url("/icons/Robux.png") center / contain no-repeat;
          mask: url("/icons/Robux.png") center / contain no-repeat;
}

/* Which machine this licence is locked to. A quiet fact, under the reference. */
.lic-device-state { display: block; margin-top: 6px; color: var(--faint); font-size: 12px; }
.keyrow.is-confirmed .lic-device-state { color: var(--ok); }

/* Extend beside Reset, on a free key that can still take time. Stacked, so
   the second column keeps its width and a row with one control looks exactly
   as it did before there were two. */
.lic-actions { display: flex; flex-direction: column; gap: var(--s2); align-items: stretch; }
.lic-actions .btn { justify-content: center; }
.ext:hover .ic { color: var(--ok); }

/* Delete, on an expired licence. It already carries .btn .btn-s, so it needs
   only the one thing that separates it: this is the destructive one. */
.del:hover {
  color: var(--bad); border-color: rgba(255, 122, 144, 0.45);
  background: rgba(255, 122, 144, 0.08);
  box-shadow: 0 0 24px -14px var(--bad);
}
.del:hover .ic { color: var(--bad); }

/* Mobile actions span the row, leaving room for their accessible name. */
.lic-action-label { white-space: normal; }

/* A button that is really a link. Same treatment as .linkish, which this sheet
   already had under a different name because two scripts chose two names. */
.linklike {
  color: var(--vio-txt); background: none; border: 0; padding: 8px 0;
  font: inherit; cursor: pointer; text-decoration: none;
  transition: color var(--tint) ease;
}
.linklike:hover { color: #fff; text-decoration: underline; text-underline-offset: 3px; }
.linklike[aria-busy="true"] { cursor: progress; opacity: 0.7; }
.gw-change { font-size: 12.5px; }

/* The iOS zoom remedy, AGAIN, in this sheet.
 *
 * uv.css already raises `.field input` to 16px on a coarse pointer, and it does
 * not apply: this sheet redefines `.field input` at the same specificity and
 * loads afterwards, so the later rule wins and the field stays 13.5px. The gate
 * that checks per sheet found it; a check that resolved "some rule somewhere
 * says 16px" would have passed and the page would still have zoomed.
 *
 * A remedy has to live after the rule it corrects, which means in the same
 * sheet as it. */
@media (pointer: coarse) {
  .field input { font-size: 16px; }
  .linkish, .linklike { min-height: 44px; }
}

/* ── copying a key, confirmed properly ──────────────────────────────────
 *
 * Copying is the single most common thing anybody does on this page: it is why
 * they opened it. The confirmation was the label turning green, which is easy
 * to miss on a control you are already looking away from, and gives no sense
 * that the press registered.
 *
 * The tick is a second icon that lives in the button all along at zero scale,
 * so the swap costs no DOM work and cannot arrive late. Both glyphs sit in one
 * grid cell and cross-fade, which keeps the button's width fixed: a control
 * that changes size at the moment of success drags the row under the pointer.
 */
.lic-copy { position: relative; }
.lic-copy .ic { transition: transform var(--move) var(--out), opacity var(--tint) ease; }
.lic-copy::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  border: 1px solid var(--ok); opacity: 0;
}
.lic-copy.copied::after { animation: uv-copied 0.6s var(--out); }
@keyframes uv-copied {
  0% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.14); }
}
/* The glyph acknowledges the press before the label has changed. */
.lic-copy.copied .ic { transform: scale(1.22) rotate(-8deg); }

/* ══ THE MESSAGE SHAPE EVERY SCRIPT WRITES ════════════════════════════════
 *
 * `GH.say` in copy.js is how account.js, gateway.js and receipt.js report a
 * failure or an empty state, and it always writes the same two elements:
 *
 *   <b class="msg-lead"></b> <span class="msg-next"></span>
 *
 * Neither had a rule here, so every one of those messages arrived at browser
 * defaults: bold black lead, body-coloured next, no separation from whatever
 * it replaced. The gate did not catch it because it reads `class="..."` out of
 * the files listed as reused, and this markup is built inside copy.js, which
 * was not one of them. Two files, one seam, and the classes fell through it.
 * The gate now derives its list from what the pages actually load.
 *
 * The pair is styled as what it is: what happened, then what to do about it.
 * The lead carries the weight; the next line is quieter and is the one holding
 * the way out, so a link inside it has to stay findable.
 */
.msg-lead { display: block; color: var(--ink); font-weight: 600; font-size: 14px; }
.msg-next { display: block; margin-top: 4px; color: var(--mute); font-size: 13.5px; line-height: 1.55; }
.msg-next + a, .msg-next ~ a {
  display: inline-flex; align-items: center; margin-top: 6px;
  color: var(--vio-txt); font-size: 13px; text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--vio-txt) 40%, transparent);
  transition: color var(--tint) ease, border-color var(--tint) ease;
}
.msg-next + a:hover, .msg-next ~ a:hover { color: #fff; border-bottom-color: #fff; }

/* ══ THE RECEIPT ══════════════════════════════════════════════════════════
 *
 * The page somebody lands on within a second of their money leaving. It has
 * one job before anything else: say it worked, without making them read to
 * find that out. The tick is the first thing on the page and the only element
 * here that animates on arrival, because a celebration on every element is a
 * page shouting, and this one only has to say yes.
 */
.paidhead { padding: var(--s13) 0 var(--s6); }
.paidhead h1 { margin: var(--s4) 0 var(--s3); font-size: clamp(28px, 4.4vw, 44px); }
.paidhead .sub { color: var(--mute); max-width: 56ch; margin: 0; font-size: 15px; }
/* The mark follows the state receipt.js writes. Green is kept for a payment
   that has actually settled: a clock in the accent while the page is still
   asking, and amber when it could not confirm or there is nothing to confirm.
   A green tick over "confirming" was the page promising what it did not know. */
.paidhead[data-tone="wait"] .tick {
  color: var(--vio-txt);
  background: color-mix(in srgb, var(--vio) 12%, transparent);
  border-color: color-mix(in srgb, var(--vio) 38%, transparent);
  box-shadow: 0 0 30px -12px var(--vio), inset 0 1px 0 var(--rim);
}
.paidhead[data-tone="warn"] .tick {
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 12%, transparent);
  border-color: color-mix(in srgb, var(--warn) 38%, transparent);
  box-shadow: 0 0 30px -12px var(--warn), inset 0 1px 0 var(--rim);
}

/* Arriving, once. `uv-io` is the class the shell adds once it is running, so a
   page whose script never loads shows the tick already in place rather than at
   zero scale forever. Same guard the rest of the sheet uses. */
html.uv-io .paidhead .tick { animation: uv-tick 0.62s var(--spring) both; }
@keyframes uv-tick {
  0% { opacity: 0; transform: scale(0.4); }
  60% { opacity: 1; transform: scale(1.08); }
  100% { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  html.uv-io .paidhead .tick { animation: none; }
}

/* What happens next: three steps, and they ARE a sequence, so they are numbered
   and the numbers mean something. The list is <ol> for the same reason. */
.paynext { list-style: none; display: grid; gap: var(--s4); margin: var(--s4) 0 0; padding: 0; }
.paynext li { display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: var(--s3); align-items: start; }
.paynext i {
  display: grid; place-items: center; width: 26px; height: 26px; border-radius: var(--pill);
  font: 500 12px/1 var(--mono); font-style: normal; font-variant-numeric: tabular-nums;
  color: var(--vio-txt);
  background: color-mix(in srgb, var(--vio) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--vio) 34%, transparent);
}
.paynext em { display: block; font-style: normal; font-weight: 600; font-size: 14px; }
.paynext small { display: block; margin-top: 3px; color: var(--mute); font-size: 13px; line-height: 1.6; }

/* ══ THE BOT CHECK ════════════════════════════════════════════════════════
 *
 * `turnstile-action.js` puts a full-screen challenge in front of checkout, the
 * key page, the shop and the account page. It has a stylesheet on the live
 * site, `turnstile-action.css`, and these pages do not load it: that sheet is
 * written against the live tokens (--sur, --acc, --ln, --fg, --rad-large), none
 * of which exist here, so loading it would style nothing and merely look like
 * it had been handled.
 *
 * Unstyled, `position: fixed` never applies. The challenge does not cover the
 * page, it lands at the bottom of the document as three bare blocks, and the
 * one moment it appears is mid-purchase. It is written here in this system's
 * own tokens instead.
 *
 * The gate did not see this either: the container class is set with
 * `overlay.className = "botcheck"`, which is not a `class="..."` literal. It
 * reads both forms now.
 */
.botcheck[hidden] { display: none; }
.botcheck {
  position: fixed; inset: 0; z-index: 10000;
  display: grid; place-items: center; padding: clamp(10px, 3vw, 24px);
  background: rgba(4, 3, 9, 0.72);
  backdrop-filter: blur(14px);
  animation: uv-botcheck-in var(--move) ease-out;
}
@keyframes uv-botcheck-in { from { opacity: 0; } to { opacity: 1; } }

.botcheck__panel {
  width: min(100%, 430px);
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s4); align-items: center;
  padding: clamp(16px, 3vw, 24px); border-radius: var(--r3);
  max-height: 92dvh; overflow-y: auto;
  background: var(--raise); border: 1px solid var(--hair-2);
  box-shadow: var(--shadow-lift), inset 0 1px 0 var(--rim);
  color: var(--ink);
}
.botcheck__panel strong { font: 600 15px/1.25 var(--disp); }
.botcheck__panel p { margin: 5px 0 0; color: var(--mute); font-size: 13px; line-height: 1.5; }

/* 46 and the mark inside it are both even, for the same reason every other
   centred glyph in this sheet is. */
.botcheck__mark {
  display: grid; place-items: center; width: 46px; height: 46px; border-radius: var(--r3);
  background: var(--btn-fill); color: #fff;
  font: 600 14px/1 var(--disp); letter-spacing: 0.08em;
  box-shadow: 0 10px 26px -12px var(--vio);
}
.botcheck__widget { grid-column: 1 / -1; min-height: 1px; min-width: 0; overflow-x: auto; }
.botcheck__cancel { grid-column: 1 / -1; justify-self: end; }
.botcheck__boundary { position: fixed; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

@media (prefers-reduced-motion: reduce) {
  .botcheck { animation: none; backdrop-filter: none; }
}

/* ══ THE LIVE FIGURES, UNDER THE KEY PAGE ═════════════════════════════════
 *
 * stats.js was orphaned. Its markup was a page of its own, then the lower half
 * of the scripts catalogue from 2026-08-02, and the v2 cutover three days
 * later replaced every page and took the markup with it. The file kept working
 * perfectly and nothing loaded it, which is the quietest way for a feature to
 * leave a site.
 *
 * It is under the key page now, and this is the half of that move which is not
 * markup: stats.css is written against the previous design's tokens (--s-6,
 * --mut, --acc2, --t-sm) and this design defines none of them, so loading it
 * here would have produced a correctly built page with no styling on it. Same
 * reason gateway.css is not loaded either.
 *
 * Two of its blocks needed nothing. `.stats` above is already the responsive
 * strip, and `.stat > b` / `.stat > span` / `.stat.lit` under THE ACCOUNT
 * FIGURES already style exactly the shape this markup uses, because account.js
 * emits the same one. Only what is genuinely new is written out here.
 */

/* ── the change of subject ────────────────────────────────────────────────
   A rule, a heading and a lot of air. This is the only thing telling a reader
   they have finished getting a key and reached a different question. */
.stlive {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s8); align-items: end;
  padding: var(--s10) 0 var(--s6);
  margin-top: var(--s6);
  border-top: 1px solid var(--hair);
}
.stlive > * { min-width: 0; }
.sthead h2 { margin: 0 0 7px; font: 600 clamp(22px, 3vw, 30px)/1.15 var(--disp); letter-spacing: -0.01em; }
.sthead .sub { margin: 0; color: var(--mute); font-size: 13.5px; max-width: 58ch; }

/* The headline is the total, because the block's entire subject is one number. */
.sthero-big { text-align: right; min-width: 0; }
.sthero-big b {
  display: block; letter-spacing: -0.03em;
  font: 600 clamp(38px, 7vw, 76px)/1 var(--disp);
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.sthero-big span {
  display: block; margin-top: var(--s2);
  color: var(--mute); font: 400 10.5px/1.3 var(--disp);
  text-transform: uppercase; letter-spacing: 0.14em;
}
@media (max-width: 760px) {
  .stlive { grid-template-columns: 1fr; align-items: start; gap: var(--s5); padding-top: var(--s8); }
  .sthero-big { text-align: left; }
}

/* How fresh the figures are, said out loud. A block of live numbers that does
   not say when it last heard from the server is asking to be trusted about
   something it has not mentioned. The dot stops pulsing once they go stale. */
.stfresh {
  display: flex; align-items: center; gap: 7px;
  margin: var(--s4) 0 0; color: var(--faint);
  font: 400 11.5px/1 var(--mono);
}
.stdot {
  width: 7px; height: 7px; flex: none; border-radius: var(--pill);
  background: var(--ok); box-shadow: 0 0 9px -1px var(--ok);
  animation: uv-stat-pulse 2.4s var(--out) infinite;
}
@keyframes uv-stat-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
.stfresh[data-state="stale"] .stdot { background: var(--warn); box-shadow: 0 0 9px -1px var(--warn); animation: none; }
.stfresh[data-state="down"] .stdot { background: var(--faint); box-shadow: none; animation: none; }

/* Week on week, beside this week's figure. A number with nothing to compare it
   against is a number nobody can tell is good. */
.stdelta {
  display: block; margin-top: 5px; font-style: normal;
  font: 400 11px/1 var(--mono);
  font-variant-numeric: tabular-nums; color: var(--mute);
}
.stdelta[data-dir="up"] { color: var(--ok); }
.stdelta[data-dir="down"] { color: var(--warn); }
.stdelta:empty { display: none; }

/* ── what an outage leaves behind ─────────────────────────────────────────
 * A failed read empties the chart and the podium rather than filling them
 * with zeros, and writes one honest sentence into the ranking. The two empty
 * frames above that sentence stayed: a heading over 260 pixels of nothing,
 * which reads as broken rather than as unavailable.
 *
 * Keyed off that sentence and not off `:empty`, deliberately. Both containers
 * are also empty while the request is in flight, so `:empty` would hide the
 * panes on load and pop them back in; `#top > .hint` exists only in the catch
 * branch. Browsers without `:has()` simply keep the old behaviour. */
.stfigs:has(#top > .hint) .stchart-pane,
.stfigs:has(#top > .hint) .stpodium-pane { display: none; }

/* ── the breakdown, folded ────────────────────────────────────────────────
 * The chart, the leaders and the long ranking made the key page 5,198 pixels
 * tall on a laptop and 9,199 on a phone. They sit behind one summary line now,
 * drawn like the feature groups on a game page so it reads as something that
 * opens rather than as a heading. The panes inside keep their own spacing. */
.stmore { margin-top: var(--s2); }
.stmore > summary {
  display: flex; align-items: center; gap: var(--s3); min-height: 48px;
  padding: var(--s3) var(--s5); cursor: pointer; list-style: none;
  border-radius: var(--r3); background: var(--surf); border: 1px solid var(--hair);
  box-shadow: inset 0 1px 0 var(--rim);
  font: 400 14px/1.35 var(--disp); color: var(--ink);
  transition: background-color var(--tint) ease, border-color var(--tint) ease;
}
.stmore > summary::-webkit-details-marker { display: none; }
.stmore > summary:hover { background: var(--raise); border-color: var(--hair-2); }
.stmore > summary .ic-caret { width: 16px; height: 16px; transition: transform var(--move) var(--out); }
.stmore[open] > summary { margin-bottom: var(--s4); }
.stmore[open] > summary .ic-caret { transform: rotate(180deg); }
.stmore .stfigs { display: grid; gap: var(--s4); }

/* ── the weekly chart ─────────────────────────────────────────────────────
 * The bars carry their height with no reveal gate on them.
 *
 * The previous sheet grew them from `[data-seen="1"]`, an attribute shell.js
 * sets. This page does not load shell.js, so that rule could never match and
 * thirteen bars would have sat at zero height inside a chart reporting no
 * error at all. A height that depends on a script having run is a chart that
 * can be silently empty. The transition still animates it whenever the value
 * changes, which is every time the figures land. */
.stchart-pane { padding-bottom: var(--s4); }
/* Scoped to its own pane. As a bare `.chart` it also matched the home page's
   third figure, `<div class="stat chart">`, and took 8px off its top padding,
   so that one card sat higher than the two beside it. `:where()` keeps the
   specificity exactly where it was, so nothing else in this sheet moves. */
:where(.stchart-pane) .chart {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 5px;
  align-items: end; height: 260px; padding-top: var(--s4);
}
.col { display: flex; flex-direction: column; justify-content: flex-end; height: 100%; position: relative; cursor: default; }
.col i {
  display: block; height: var(--h, 3%); border-radius: var(--r1) var(--r1) 0 0;
  background: var(--hair-lit);
  transition: height var(--slow) var(--out), background-color var(--tint) ease, box-shadow var(--move) ease;
}
.col:hover i, .col:focus-visible i { background: var(--ion); box-shadow: 0 0 18px -4px var(--ion); }
/* The most recent week is the one anybody came to see, so it is the one lit,
   and the tallest keeps a mark of its own so the peak still means something
   with the pointer nowhere near it. */
.col.is-now i { background: var(--vio); box-shadow: 0 0 22px -6px var(--vio); }
.col.is-peak i { background: var(--ion); }
.col u {
  text-align: center; font: 400 10.5px/1 var(--mono);
  color: var(--ion); text-decoration: none; padding-bottom: 6px;
  opacity: 0; transform: translateY(4px);
  transition: opacity var(--tint) ease, transform var(--move) var(--out);
}
.col:hover u, .col:focus-visible u { opacity: 1; transform: none; }
.col.is-now u { opacity: 1; transform: none; color: var(--vio-txt); }
/* A label hover can never reveal should not be laid out: the number is wider
   than a bar, and its min-content pushed the whole page sideways at 320px. */
@media (hover: none) { .col u { display: none; } }

.xaxis {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 5px;
  padding-top: 9px;
  font: 400 9.5px/1 var(--mono); color: var(--faint); text-align: center;
}
/* Thirteen dates will not fit a phone and overlapping labels are worse than
   half of them. minmax(0, 1fr) rather than 1fr is the part that matters: 1fr
   is minmax(auto, 1fr), auto as a MINIMUM is min-content, and no column could
   then shrink below the width of "May". */
@media (max-width: 700px) { .xaxis span:nth-child(even) { visibility: hidden; } }

/* ── the leaders ──────────────────────────────────────────────────────── */
.podium {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: var(--s4); margin-top: var(--s4);
}
.pod {
  position: relative; overflow: hidden;
  border: 1px solid var(--hair); border-radius: var(--r3);
  background: var(--surf); padding: var(--s4);
  display: grid; gap: var(--s3); align-content: start;
  transition: border-color var(--tint) ease, background-color var(--tint) ease,
              box-shadow var(--move) var(--out), transform var(--move) var(--out);
}
.pod:hover {
  border-color: var(--hair-lit); background: var(--raise);
  box-shadow: var(--shadow); transform: translateY(-1px);
}
.pod > * { position: relative; z-index: 2; }
.pod-top { display: flex; align-items: center; gap: var(--s3); min-width: 0; }
.pod-art {
  width: 46px; height: 46px; flex: none; border-radius: var(--r2);
  object-fit: cover; border: 1px solid var(--hair);
  filter: saturate(0.75) brightness(0.85);
  transition: filter var(--move) ease, transform var(--move) var(--out);
}
.pod:hover .pod-art { filter: none; transform: scale(1.05); }
.pod-art.none { display: grid; place-items: center; color: var(--faint); font: 600 18px/1 var(--disp); background: var(--sunk); }
.pod-name { min-width: 0; padding-right: var(--s5); }
.pod-name b { display: block; font: 600 13.5px/1.3 var(--disp); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pod-rank {
  position: absolute; top: var(--s3); right: var(--s4);
  font: 400 11px/1 var(--mono); color: var(--faint); letter-spacing: 0.14em;
}
.pod:nth-child(1) .pod-rank { color: var(--vio-txt); }
.pod-fig { display: flex; align-items: baseline; gap: var(--s2); }
.pod-fig b {
  font: 600 clamp(21px, 3vw, 27px)/1 var(--disp); letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums; color: var(--ion);
}
.pod-fig span { color: var(--mute); font-size: 11px; }
/* Thirteen weeks as an area, drawn with a clip-path from points the script
   builds. One element and one polygon, which is the right amount of machinery
   for a shape 42 pixels tall. Drawn at full height for the same reason the
   bars are: it must not depend on a reveal this page cannot fire. */
.pod-spark {
  height: 42px; position: relative; overflow: hidden;
  border-radius: var(--r1);
  background: linear-gradient(180deg, var(--vio), transparent 92%);
  opacity: 0.55;
  clip-path: polygon(var(--pts, 0% 100%, 100% 100%));
  transition: opacity var(--move) ease;
}
.pod:hover .pod-spark { opacity: 1; }
.pod-share { display: grid; gap: 5px; }
.pod-share small { color: var(--mute); font: 400 11px/1 var(--mono); }

/* ── the long tail ────────────────────────────────────────────────────────
   Two per line. The list is every script we have numbers for, and a single
   column turns a ranking into a scroll rather than a picture. */
.ranking { display: grid; grid-template-columns: 1fr 1fr; align-content: start; }
.ranking .row {
  display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto;
  gap: var(--s3); align-items: center;
  padding: var(--s3) 0; border-bottom: 1px solid var(--hair);
}
.ranking .row:nth-child(odd) { border-right: 1px solid var(--hair); padding-right: var(--s4); }
.ranking .row:nth-child(even) { padding-left: var(--s4); }
/* :last-child alone left a hanging rule: in two columns the last TWO rows sit
   on the bottom edge and only one of them is the last child. */
.ranking .row:last-child, .ranking .row:nth-last-child(2):nth-child(odd) { border-bottom: 0; }
.ranking .rank { font: 400 11px/1 var(--mono); color: var(--faint); letter-spacing: 0.1em; }
.ranking .thumb { width: 30px; height: 30px; flex: none; }
.ranking .art { width: 30px; height: 30px; border-radius: var(--r1); object-fit: cover; border: 1px solid var(--hair); }
.ranking .art.none { display: grid; place-items: center; color: var(--faint); font: 600 13px/1 var(--disp); background: var(--sunk); }
.ranking .rname { min-width: 0; }
.ranking .rname em { font-style: normal; display: block; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ranking .num { font: 400 13px/1 var(--mono); white-space: nowrap; color: var(--ion); }
/* The leaders print the same state word as the rows, and had no rule of their
   own, so on a leader it came out at body size in the text colour: the loudest
   word on the card after the figure. One rule for both. */
:is(.ranking, .pod-name) .state { font-size: 11px; margin-top: 3px; display: inline-block; color: var(--mute); }
:is(.ranking, .pod-name) .state[data-s="working"] { color: var(--ok); }
:is(.ranking, .pod-name) .state[data-s="maintenance"] { color: var(--warn); }

/* The share bars, under a leader's figure and under each row's name. Their
   width arrives inline from the script, so like the chart they draw
   themselves rather than waiting to be revealed. */
.ranking .bar, .pod-share .bar { background: var(--sunk); border-radius: var(--pill); overflow: hidden; margin-top: 5px; }
.ranking .bar, .pod-share .bar { height: 3px; }
.ranking .bar i, .pod-share .bar i {
  display: block; height: 100%; width: var(--w, 0);
  background: var(--holo); border-radius: inherit;
  transition: width var(--slow) var(--out);
}

@media (max-width: 760px) {
  /* One column on a phone. A name, a bar and a number twice over inside 360px
     is not a denser view, it is an unreadable one. */
  .ranking { grid-template-columns: 1fr; }
  .ranking .row:nth-child(odd) { border-right: 0; padding-right: 0; }
  .ranking .row:nth-child(even) { padding-left: 0; }
  .ranking .row:nth-last-child(2):nth-child(odd) { border-bottom: 1px solid var(--hair); }
}

@media (prefers-reduced-motion: reduce) {
  .stdot { animation: none; }
  .pod:hover { transform: none; }
  .pod:hover .pod-art { transform: none; }
  .kfacts, .lic-clock[data-state="live"] > i, .stat b.is-updated,
  .lic-copy.copied::after, html[data-session="in"] #app:has(.signedout)::before { animation: none; }
  .lic-copy.copied .ic { transform: none; }
  .gwdialog, .acct-toast, .keyrow, .col i, .ranking .bar i, .pod-share .bar i { transition: none; }
}
