/*
 * thermals-demo styles, against docs/UX_STYLE_GUIDE.md.
 *
 * The tokens below are the estate's, copied rather than re-derived. Every role
 * colour in slapp's client/src/styles/base.css was MEASURED against the app's
 * real surfaces - four of them sit a step darker than the brand guide's
 * published hex because the published values did not clear 4.5:1 in the
 * direction they were pointing. Inventing approximations here would throw that
 * work away and produce a second, subtly different blue for a person who moves
 * between the two apps in one afternoon.
 *
 * DARK IS THE DEFAULT, as across the estate, and light is the
 * prefers-color-scheme branch. Both blocks define every colour: a token
 * declared in only one silently inherits the wrong theme's value, which is the
 * failure the both-blocks rule exists to prevent.
 */

/*
 * The brand typeface, self-hosted. The CSP here allows no remote origin at all
 * - not even fonts.googleapis.com - so vendoring is the only way to have it,
 * and font-display: swap means a slow font never blocks first paint. One
 * variable file covers 400-700, which is every weight this app uses.
 */
@font-face {
  font-family: "Rubik";
  src: url("/fonts/rubik-latin.woff2") format("woff2");
  font-display: swap;
  font-style: normal;
  font-weight: 400 700;
}

:root {
  /*
   * THE TYPE SCALE: six steps, which the guide says is enough and is right -
   * every size added is a decision every later change has to re-make. Lengths
   * rather than colours, so one declaration serves both themes.
   */
  --fs-meta: 11px;
  --fs-small: 12px;
  --fs-body: 13px;
  --fs-head: 15px;
  --fs-title: 18px;
  --fs-hero: 24px;

  --font: "Rubik", -apple-system, system-ui, "Segoe UI", sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  --radius: 10px;

  /*
   * One reading measure, on the paragraph only. Cards, tables and the demo
   * grid get the whole column - a cap on a card is what makes a page look like
   * it failed to load.
   */
  --measure: 78ch;

  /* Dark, and the default. */
  --page: #101922;
  --surface: #182631;
  --surface-2: #1e2f3d;
  --ink: #ffffff;
  --muted: #a7bccd;
  --border: #2f4459;
  --accent: #4da3ff;
  --accent-ink: #0d1b26;
  --warning: #fab219;
  --danger: #ec7676;
  --success: #4faa72;

  /*
   * The -strong step: the same hue, for text sitting ON a wash of itself. A
   * chip filled with a 20% wash of a role and labelled in that same role
   * cannot reach 4.5:1 at any token value - measured, it lands around 3.7-4.0,
   * and the wash at which it would clear is invisible. A second step is the
   * fix; retuning the first is not. Distinct from --accent-ink, which is ink
   * on a SOLID fill: two concepts, two nouns.
   */
  --danger-strong: #ffb3b3;
  --success-strong: #a6e0bd;
}

@media (prefers-color-scheme: light) {
  :root {
    --page: #f4f8fc;
    --surface: #ffffff;
    --surface-2: #f0f5fa;
    --ink: #15354d;
    --muted: #5b7185;
    --border: #d3e0ea;
    --accent: #2770c7;
    --accent-ink: #ffffff;
    --warning: #8f6300;
    --danger: #c23b3b;
    --success: #2b7c4c;

    --danger-strong: #8f1f1f;
    --success-strong: #1f5c37;
  }
}

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  padding: 0 1rem 2.5rem;
  background: var(--page);
  color: var(--ink);
  font: var(--fs-body)/1.55 var(--font);
  -webkit-text-size-adjust: 100%;
}

main { max-width: 60rem; margin: 0 auto; }
main.narrow { max-width: 26rem; }

a { color: var(--accent); }

p { margin: 0 0 0.75rem; max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

h1, h2, h3 { margin: 0; font-weight: 600; line-height: 1.25; }
/* A section heading owns the gap under it. Set here rather than per-section so
 * a new panel cannot ship with its title welded to its first field. */
.card h2, .table-wrap h2 { font-size: var(--fs-head); margin-bottom: 0.75rem; }

.head { margin: 2rem 0 1.25rem; }
.head h1 { font-size: var(--fs-hero); }
.lede { margin: 0.4rem 0 0; color: var(--muted); font-size: var(--fs-head); }

/* ------------------------------------------------------------- notices --- */

/*
 * Colour never carries the meaning alone - each of these is introduced by a
 * word ("could not be sent", "is ready") and carries role="alert" or "status".
 * The wash-plus--strong-ink pairing is what keeps the label readable on its
 * own tint; see the token block.
 */
.notice {
  border-radius: var(--radius);
  padding: 0.7rem 0.9rem;
  margin: 0 0 1rem;
  border: 1px solid;
}
.notice--bad {
  background: color-mix(in srgb, var(--danger) 18%, transparent);
  border-color: var(--danger);
  color: var(--danger-strong);
}
.notice--good {
  background: color-mix(in srgb, var(--success) 18%, transparent);
  border-color: var(--success);
  color: var(--success-strong);
}

/* --------------------------------------------------------------- cards --- */

/*
 * Two up once there is room. The cards were one full-width column of ~17px
 * prose, which turned three demos into a page you scroll rather than a set you
 * compare - and comparing them is the whole job of this screen. At the app's
 * own type scale a card is about a third of its former height, so all three
 * land above the fold on a laptop.
 */
.cards {
  display: grid;
  gap: 0.75rem;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
}

.card__name { font-size: var(--fs-head); margin-bottom: 0.15rem; }
.card__link { text-decoration: none; }
/* Fires from anywhere on the card, not just over the name: the pointer is over
 * the anchor's own overlay below, and :hover matches the element that
 * generated it. The name underlines wherever the pointer sits, which is an
 * accurate statement of what a click will do. */
.card__link:hover { text-decoration: underline; }
.card__tagline { margin: 0 0 0.5rem; color: var(--ink); font-weight: 500; }
.card__body { margin: 0 0 0.5rem; color: var(--muted); }
.card__notes { margin: 0; color: var(--muted); font-size: var(--fs-small); }
.card__url {
  margin: 0.6rem 0 0;
  color: var(--muted);
  font-size: var(--fs-meta);
  font-family: var(--mono);
}

/* ------------------------------------ the card is the link's hit area --- */

/*
 * The whole card opens the demo, and the LINK is still the app's name: the
 * anchor grows a hit area over the card rather than the card becoming an
 * anchor. Wrapping the card in an <a> was the obvious move and is the wrong
 * one - it makes the accessible name all four paragraphs inside, including the
 * "Before you click" warning, so a screen reader's link list becomes three
 * paragraph-length entries. Because the overlay is part of the anchor rather
 * than a stand-in for it, middle-click, cmd-click and "copy link address" work
 * across the whole card exactly as they do across the words.
 *
 * A modifier from the template, not :has(.card__link). The card function
 * already branches on app.live twice, so a third branch on the same fact is
 * cheaper than asking the stylesheet to re-derive it - and it keeps every rule
 * here off the gate form, the request form and the admin panel, which are
 * .card too. Scoping the overlay to .card--go is also the safety property: the
 * pseudo-element cannot exist without the positioned ancestor it measures
 * itself against, and an inset: 0 resolved against the page would lay an
 * invisible link over every pixel of it.
 *
 * TEXT SELECTION INSIDE A LIVE CARD IS GONE, and that is accepted rather than
 * overlooked. The usual mitigation is to lift .card__url out with a z-index,
 * but that line is the most link-looking text on the card - mono, an address,
 * at the bottom - and lifting it turns the one thing a person is most likely
 * to click expecting navigation into a dead strip. A passive absence beats an
 * active broken promise. Right-click anywhere on the card still offers "copy
 * link address", so the address remains obtainable from any point.
 *
 * No cursor: pointer is declared and none is needed - cursor inherits, the
 * browser sets it on the anchor, and the overlay inherits it from there. A
 * second copy of that fact is what eventually drifts onto the card with no
 * link.
 */
.card--go { position: relative; }
.card--go .card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

/*
 * Hover reinforces; it never informs. The card already says it is a link with
 * no pointer anywhere near it - the name is in --accent because it IS one, and
 * the address underneath states where it goes - so this is a second telling
 * rather than the only one. Gated on a real pointer because a touchscreen
 * resolves :hover into a sticky state after the tap, lighting up the card you
 * have already left.
 */
@media (hover: hover) {
  .card--go:hover { border-color: var(--accent); }
}

/*
 * Focus stays on the anchor and so does its ring, because the anchor is what
 * Enter activates. The card then repeats the statement with a border and a
 * wash - not a second outline drawn two pixels outside the first, and not a
 * ring removed on the promise of one drawn elsewhere, which is the trade the
 * textbook version of this pattern makes and this stylesheet does not. A
 * browser too old for :has() loses the wash and keeps the ring, which is the
 * right way round to degrade.
 */
.card--go:has(.card__link:focus-visible) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--surface));
}

/* "coming soon" is a word; the badge only reinforces it. */
.badge {
  font-size: var(--fs-meta);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
  color: var(--muted);
  vertical-align: middle;
  font-weight: 500;
}

.cta {
  margin: 1.5rem 0 0;
  padding: 1rem 1.15rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.cta h2 { font-size: var(--fs-head); margin-bottom: 0.35rem; }
.cta p { color: var(--muted); }

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

.field { display: block; margin: 0 0 0.9rem; }
.field__label { display: block; font-weight: 500; margin-bottom: 0.3rem; }
.field__hint { display: block; color: var(--muted); font-size: var(--fs-small); margin-top: 0.3rem; }
.opt { font-weight: 400; color: var(--muted); }

input, select, textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.5rem 0.6rem;
  /* WCAG 2.2 target size: padding buys the height, not font-size. */
  min-height: 2.25rem;
}
textarea { resize: vertical; min-height: 6rem; }

/* The focus ring is always visible - never removed, never hover-only. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible,
a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/*
 * The filled primary. --accent carries --accent-ink, which is the measured
 * pairing; the brand's Light Blue with white type on top is the tempting fill
 * that fails an audit at 3.64:1, and it is not reachable from these tokens.
 */
.button {
  display: inline-block;
  font: inherit;
  font-weight: 500;
  background: var(--accent);
  color: var(--accent-ink);
  border: 0;
  border-radius: var(--radius);
  padding: 0.5rem 1rem;
  min-height: 2.25rem;
  cursor: pointer;
  text-decoration: none;
}
.button:hover { filter: brightness(1.08); }

.linkish {
  font: inherit;
  background: none;
  border: 0;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
  padding: 0.25rem 0;
}
.linkish:hover { color: var(--ink); }

fieldset.field {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.7rem 0.85rem;
  margin-inline: 0;
}
.check { display: block; margin: 0.25rem 0; font-weight: 400; }
.check input { width: auto; margin-right: 0.4rem; min-height: 0; }

/* ------------------------------------------------------------- the gate --- */

.divider {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--muted);
  font-size: var(--fs-small);
  margin: 1rem 0;
}
.divider::before, .divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.button--sso { display: block; text-align: center; }

/* --------------------------------------------------------------- admin --- */

/* Wide content scrolls inside its own box; the page itself never pans. */
.table-wrap { margin-top: 1.5rem; overflow-x: auto; }
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-small);
  /* Columns of dates and counts must not jitter between rows. */
  font-variant-numeric: tabular-nums;
}
.table th, .table td {
  text-align: left;
  padding: 0.45rem 0.6rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.table th { color: var(--muted); font-weight: 500; }
.row--revoked, .row--expired { color: var(--muted); }

/*
 * The one-time code. The only place the six-size scale gives way to a figure,
 * and for the same reason the estate's scale makes that exception for a metric
 * readout: this is read aloud, retyped, and never shown again.
 */
.code {
  font-family: var(--mono);
  font-size: var(--fs-hero);
  letter-spacing: 0.06em;
  user-select: all;
  margin: 0.4rem 0;
  color: var(--ink);
}

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

.scope { margin: 0.5rem 0 0; color: var(--muted); font-size: var(--fs-small); }
.muted { color: var(--muted); }
.aside { margin: 1rem 0 0; color: var(--muted); font-size: var(--fs-small); }

.leave {
  margin: 2rem 0 0;
  display: flex;
  gap: 0.9rem;
  align-items: center;
  flex-wrap: wrap;
  font-size: var(--fs-small);
}

.foot {
  max-width: 60rem;
  margin: 2.5rem auto 0;
  padding-top: 0.8rem;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: var(--fs-meta);
}
body:has(main.narrow) .foot { max-width: 26rem; }

/* Turnstile renders into a fixed-size iframe; reserve the height so the submit
 * button does not jump when the challenge appears. */
.cf-turnstile { margin: 0 0 0.9rem; min-height: 65px; }

.skip { position: absolute; left: -9999px; }
.skip:focus {
  left: 1rem;
  top: 1rem;
  z-index: 10;
  background: var(--surface);
  padding: 0.5rem 0.8rem;
  border-radius: var(--radius);
}

/* -------------------------------------------------------- small screens --- */

/*
 * 640 only. The breakpoint ladder is a CLOSED vocabulary - 1180, 980, 900,
 * 760, 640, 480 - and a seventh value is an amendment to that table rather
 * than a per-view invention. The card grid needs no query at all, because
 * auto-fit collapses it on its own.
 */
@media (max-width: 640px) {
  .head { margin-top: 1.5rem; }
  .head h1 { font-size: var(--fs-title); }
}

/*
 * iOS zooms into any field under 16px and leaves the person to pinch their way
 * back out after every one. The floor is on the INPUT, and it is keyed to the
 * finger rather than the width - a tablet is 1024px wide and still a
 * touchscreen.
 */
@media (pointer: coarse) {
  input, select, textarea { font-size: 16px; }
  .button { min-height: 2.75rem; }
  .linkish { min-height: 1.5rem; }
  .check input { min-height: 1.25rem; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
