/* KeepQR app stylesheet — mobile-first, logical properties only (ADR 0014 §6 /
   design-brief §4). WCAG 2.1 AA: text contrast ≥ 4.5:1, control borders ≥ 3:1
   (1.4.11), visible focus ring (2.4.7), tap targets ≥ 44px (brief §4), and
   prefers-reduced-motion honored. Light + dark via color-scheme tokens. No
   third-party fonts/assets — system font stack only (design-brief §4). */

:root {
  color-scheme: light dark;
  --bg: #ffffff;
  --fg: #1a1a1a;
  --muted: #515151; /* ≥ 7:1 on --bg */
  --accent: #295BBF; /* brand ink blue (oklch .50 .13 252) · white text 6.28:1 */
  --on-accent: #ffffff;
  --input-border: #6b6b6b; /* ≥ 3:1 on --bg (1.4.11) */
  --divider: #d4d4d4;
  --accent-tint: #eef2ff;
  --accent-hover: #21509f; /* darker ink for :hover · white text ≥ 4.5:1 */
  --accent-press: #1a4488; /* :active */
  --danger: #b00020; /* ≥ 4.5:1 on --bg */
  --warn-tint: #fff4e5;
  --radius: 0.5rem;
  /* Raised card ground for the customize sections. Two invariants: .cz-sect must
     never nest, and this token must never be redeclared on or inside .cz-sect —
     that is a genuine custom-property cycle, and the card would silently render
     transparent. */
  --surface-raised: color-mix(in srgb, var(--fg) 6%, var(--bg));
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121212;
    --fg: #ededed;
    --muted: #b3b3b3; /* ≥ 4.5:1 on --bg */
    --accent: #93b4ff;
    --on-accent: #06122b;
    --input-border: #9a9a9a; /* ≥ 3:1 on --bg */
    --divider: #3a3a3a;
    --accent-tint: #1a2238;
    --accent-hover: #a9c4ff; /* lighter for :hover · dark text ≥ 4.5:1 */
    --accent-press: #bcd2ff; /* :active */
    --danger: #ff9aa2; /* ≥ 4.5:1 on dark --bg */
    --warn-tint: #2a2113;
    --surface-raised: #1c1c1c; /* ΔL* 4.8 on --bg, matching the light card's 4.75 */
  }
}

* {
  box-sizing: border-box;
}

body {
  margin-inline: auto;
  max-inline-size: 40rem;
  padding-inline: 1rem;
  padding-block: 1.5rem;
  line-height: 1.5;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--fg);
  background: var(--bg);
}

:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding-block-end: 1rem;
  border-block-end: 1px solid var(--divider);
}

.wordmark {
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--fg);
  text-decoration: none;
}
.wordmark:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}
/* "QR" in the brand accent — matches the favicon / OG mark (#242, #286). */
.wordmark-qr {
  color: var(--accent);
}

.recover-link {
  font-size: 0.875rem;
}

h1 {
  font-size: 1.75rem;
  line-height: 1.2;
  margin-block: 1.25rem 0.5rem;
}

/* Landing hero tracking + fluid bump (design 6A-01: 28px → 32px). Scoped to the
   landing so every other page's h1 (gallery, manage, legal, …) is unchanged. */
main.landing h1 {
  letter-spacing: -0.02em;
}
@media (min-width: 30rem) {
  main.landing h1 {
    font-size: 2rem;
  }
}

h2 {
  font-size: 1.125rem;
  margin-block: 1.5rem 0.5rem;
}

.lede {
  font-size: 1.0625rem;
  max-inline-size: 34ch;
  margin-block: 0 1rem;
}

.trust,
.disclosure,
.recover-link,
footer {
  font-size: 0.875rem;
  color: var(--muted);
}

.create-form {
  margin-block: 1.5rem;
}

label {
  display: block;
  font-weight: 600;
  color: var(--fg);
  margin-block-end: 0.375rem;
}

/* Create/recover forms use `type="text" inputmode="url"` and `type="email"`, so key
   the chrome off the form scope, not a bare `input[type="url"]` (which matched nothing
   → native chrome on the hero box). :not([type="hidden"]) skips the src carry field. */
.create-form input:not([type="hidden"]),
input[type="url"] {
  display: block;
  inline-size: 100%;
  min-block-size: 44px;
  padding-inline: 0.75rem;
  padding-block: 0.5rem;
  font-size: 1rem;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--input-border);
  border-radius: var(--radius);
  transition:
    border-color 120ms ease,
    box-shadow 120ms ease;
}

/* Design 6A-01 focus ring: accent border + soft tint glow. The global 3px
   :focus-visible outline stays as the WCAG 2.4.7 indicator; this is additive. */
.create-form input:not([type="hidden"]):focus-visible,
input[type="url"]:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}

input[aria-invalid="true"] {
  border-color: #b00020;
  border-width: 2px;
}

.disclosure {
  margin-block: 0.625rem 1rem;
}

.cf-turnstile {
  margin-block: 1rem;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 44px;
  min-inline-size: 44px;
  inline-size: 100%;
  padding-inline: 1rem;
  padding-block: 0.625rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--on-accent);
  background: var(--accent);
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  transition:
    background-color 120ms ease,
    box-shadow 120ms ease;
}

/* Primary-only hover/active (scoped away from the --secondary/--ghost/--danger
   variants, which set their own rest background). Focus glow is additive on all. */
.btn:not(.btn--secondary):not(.btn--ghost):not(.btn--danger):hover {
  background: var(--accent-hover);
}
.btn:not(.btn--secondary):not(.btn--ghost):not(.btn--danger):active {
  background: var(--accent-press);
}
.btn:focus-visible {
  box-shadow: 0 0 0 3px var(--accent-tint);
}
/* Disabled state (was unstyled — a JS-disabled button looked identical to enabled,
   so a click did nothing with no visible reason). Applies to real `disabled` and the
   `aria-disabled` mirror. */
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  box-shadow: none;
}
.btn:disabled:hover,
.btn[aria-disabled="true"]:hover {
  background: var(--accent);
}

/* Landing pricing ladder (design 6A-01). Own classes so the draft pay-form's
   `.tiers`/`.tier-headline` radio cards are untouched. */
.tier-list {
  list-style: none;
  padding-inline-start: 0;
  margin-block: 0;
}

.price-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.75rem;
  align-items: center;
  padding-block: 0.75rem;
  border-block-end: 1px dashed var(--divider);
}
.price-row:last-child {
  border-block-end: 0;
}
.price-row .dur {
  font-weight: 600;
  color: var(--fg);
}
.price-row .hint {
  display: block;
  font-size: 0.8125rem;
  color: var(--muted);
  margin-block-start: 0.125rem;
}
.price-row .price {
  font-family: ui-monospace, Menlo, monospace;
  font-weight: 600;
  color: var(--fg);
}
.price-row.is-headline {
  background: var(--accent-tint);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 0.875rem;
  margin-block: 0.5rem 0.75rem;
}
.price-row.is-headline .price {
  color: var(--accent);
  font-size: 1.0625rem;
}

/* "Recommended" pill on the headline tier. */
.rec {
  display: inline-block;
  margin-inline-start: 0.5rem;
  padding: 0.0625rem 0.4375rem 0.125rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: 0.1em;
}

/* ---- Landing hero artifact (design 6A-01c · plan option B) ----
   The product's own animated QR, embedded as an `<img>` data URI. ADR 0028's
   I3 forces a solid near-white background on the symbol itself, so in dark mode
   it is unavoidably a bright block. It gets ONE border, not two: the design's
   `.qtile` is a single white, rounded, bordered tile and the <img> below IS that
   tile — white padding continues the symbol's own quiet zone out to a rounded,
   bordered edge, so the white area reads as a deliberate mounted card rather
   than a hole punched through the page. The <figure> itself is now only the
   flex column that stacks the tile over its caption: no ground, no second edge.
   Nothing here is focusable, and no CSS here animates: the motion lives inside
   the SVG's own document. That document does NOT see the user's
   prefers-reduced-motion setting when embedded via <img>, so the freeze is
   chosen out here by the <picture> below. */
.hero-qr {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  margin-inline: 0; /* reset the UA <figure> 40px inline margin */
  margin-block: 1.5rem;
}

/* The <picture> exists only to let the EMBEDDING document pick the frozen frame
   under prefers-reduced-motion (an <img>-embedded SVG never sees the setting).
   `display: contents` keeps it out of the box tree entirely, so the <img> stays
   the direct flex item of .hero-qr and the verified 375px/1280px layout is
   unchanged by construction. Source selection is DOM-based, so it still works. */
.hero-qr-picture {
  display: contents;
}

/* The tile. `box-sizing: border-box` is global, so the 264px artifact + 0.75rem
   of white mat on each side + the 1px edge is 290px of outer width — the symbol
   itself still renders at its intrinsic size and is never upscaled. The white
   background is OURS, not the artifact's: it continues I3's mandated quiet zone
   to the rounded edge in both themes, so the tile is one continuous white card
   with a single border instead of a white rectangle inset in a grey one. */
.hero-qr-img {
  display: block;
  inline-size: 100%;
  max-inline-size: 290px;
  block-size: auto;
  padding: 0.75rem;
  background: #ffffff;
  border: 1px solid var(--divider);
  border-radius: var(--radius);
}

/* Dark mode: the tile is a bright card on a #121212 page, and --divider (#3a3a3a)
   is darker than the page it sits on, so it reads as a shadow line rather than
   an edge and the white goes straight to the background. A mid-grey rim is the
   deliberate-card cue instead: it is a step DOWN from the white tile and a step
   UP from the page, so the transition is a drawn edge in both directions. Still
   one border. */
@media (prefers-color-scheme: dark) {
  .hero-qr-img {
    border-color: #8a8a8a;
  }
}

.hero-qr-caption {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: var(--muted);
  text-align: center;
  text-wrap: balance;
}

/* Draft pay-form radio-card headline (still used by the tier-form). */
.tier-headline {
  font-weight: 700;
  background: var(--accent-tint);
  padding-inline: 0.75rem;
  border-radius: var(--radius);
}

footer {
  margin-block-start: 2rem;
  padding-block-start: 1rem;
  border-block-start: 1px solid var(--divider);
}

footer nav {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-block-start: 0.5rem;
}

/* ---- Landing desktop: two-column hero (parity P3, conservative) ----
   At wide viewports the intro/create form sits beside the pricing card instead
   of a single narrow column with empty gutters. Existing DOM + tokens only —
   no top bar, theme toggle, or elevation-token overhaul (those are a separate,
   product-gated desktop redesign). Mobile stays single-column untouched. */
@media (min-width: 60rem) {
  /* Only the landing widens; every other page keeps the 40rem column. */
  body:has(main.landing) {
    max-inline-size: 64rem;
  }
  main.landing {
    display: grid;
    grid-template-columns: 1.05fr 0.95fr;
    /* Row 1 is pinned to the headline block's own height (`min-content` freezes
       its growth limit), so the hero spanning rows 1–2 can only push row 2 —
       the create form still starts immediately under the lede. See the
       placement comment below. */
    grid-template-rows: min-content auto auto;
    column-gap: 3.5rem;
    align-items: start;
  }
  main.landing .create-form {
    max-inline-size: 28em;
  }
  main.landing h1 {
    font-size: 2.5rem;
  }
  /* Four grid children, two columns, three rows: headline block then create
     form down column 1, hero artifact then pricing card down column 2.

     DOM order is headline → hero → form → pricing, because on a phone (where
     there is no grid at all and the children fall back to block flow) the
     operator wants the symbol between the subtitle and the Destination URL
     field. The desktop columns are restored by explicit placement rather than
     `order`, which would desync the visual order from the focus order.

     The spans are what keep each column's own contents contiguous despite the
     interleaved DOM. Row 1 is `min-content` (above) so it is exactly the
     headline block; the hero spans rows 1–2 and can therefore only grow row 2;
     the form spans rows 2–3 and starts at the row-1 boundary, i.e. directly
     under the lede; the pricing card starts at the row-3 boundary, i.e.
     directly under the hero. */
  main.landing .landing-intro {
    grid-column: 1;
    grid-row: 1;
  }
  main.landing .hero-qr {
    grid-column: 2;
    grid-row: 1 / span 2;
    margin-block-start: 1.25rem; /* optically level with the h1's cap height */
  }
  main.landing .landing-create {
    grid-column: 1;
    grid-row: 2 / span 2;
  }
  /* Pricing becomes a bordered card in the right column, under the hero. */
  main.landing .pricing {
    grid-column: 2;
    grid-row: 3;
    border: 1px solid var(--divider);
    border-radius: var(--radius);
    padding: 1.5rem;
  }
  main.landing .pricing h2 {
    margin-block-start: 0;
  }
}

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

/* ---- Cross-document View Transitions (ADR 0027 P1) ----
   Same-origin navigations crossfade in Chrome 126+ / Safari 18.2+; browsers
   without support (Firefox) ignore the at-rule and snap as before. Reloads
   (navigationType "reload", e.g. the success-page poll) are excluded by the
   spec. The Polar checkout hop is cross-origin and snaps in both directions. */
@media not (prefers-reduced-motion: reduce) {
  @view-transition {
    navigation: auto;
  }
}

/* The global reduced-motion block above cannot reach the view-transition
   pseudo-tree (it hangs off :root, and `*` does not match pseudo-elements),
   so disable its animations explicitly for UAs that might still create a
   transition. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* ---- Manage page (M7b; design 6B-01) — logical properties only (ADR 0014) ---- */

/* Visually-hidden but screen-reader-available labels (WCAG 1.3.1 / 3.3.2). */
.visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.btn--secondary {
  background: var(--bg);
  color: var(--accent);
  border-color: var(--input-border);
}
.btn--ghost {
  background: transparent;
  color: var(--accent);
  border-color: transparent;
}
.btn--danger {
  background: var(--bg);
  color: var(--danger);
  border-color: var(--danger);
}

.input {
  display: block;
  inline-size: 100%;
  min-block-size: 44px;
  padding-inline: 0.75rem;
  padding-block: 0.5rem;
  margin-block-end: 0.5rem;
  font: inherit;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--input-border);
  border-radius: var(--radius);
}
textarea.input {
  block-size: auto;
}

.topbar {
  border-block-end: 0;
  padding-block-end: 0.5rem;
}
.id-badge {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: var(--muted);
  font-size: 0.875rem;
}

.m-summary {
  display: block;
  margin-block: 1rem;
  padding: 1rem;
  border: 1px solid var(--divider);
  border-radius: var(--radius);
}
.pill {
  display: inline-block;
  font-weight: 700;
  padding-inline: 0.625rem;
  padding-block: 0.25rem;
  border-radius: 999px;
  background: var(--accent-tint);
  color: var(--fg);
}
.dest {
  margin-block: 0.75rem 0.5rem;
}
.dest .label {
  display: block;
  font-size: 0.8125rem;
  color: var(--muted);
}
.dest-url {
  overflow-wrap: anywhere;
}

dl.m-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 1.5rem;
  margin-block: 0.5rem 0;
}
.m-facts .grp {
  display: flex;
  flex-direction: column;
}
.m-facts dt {
  font-size: 0.8125rem;
  color: var(--muted);
}
.m-facts dd {
  margin-inline-start: 0;
  font-weight: 600;
}

.m-action {
  margin-block: 1rem;
  padding-block-start: 1rem;
  border-block-start: 1px solid var(--divider);
}
.m-action.is-prominent {
  padding: 1rem;
  border: 2px solid var(--accent);
  border-radius: var(--radius);
}
.m-action.is-disabled {
  opacity: 0.7;
}

.m-section-h.is-danger {
  margin-block-start: 1.5rem;
  color: var(--danger);
  border-block-start: 1px solid var(--danger);
  padding-block-start: 1rem;
}

.m-downloads {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.m-downloads .btn {
  inline-size: auto;
}

.dl-lane {
  margin-block-start: 1rem;
}
.dl-lane:first-child {
  margin-block-start: 0;
}
.dl-lane h3 {
  font-size: 0.9rem;
  margin-block-end: 0.5rem;
}
/* Both download lanes stack full-width buttons; space them consistently
   (was scoped to --styled, leaving the print lane's PNG/SVG/PDF touching). */
.dl-lane .btn {
  margin-block-end: 0.5rem;
}
.dl-lane .btn.is-recommended {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-tint);
}

.scan-pending {
  padding: 0.75rem;
  margin-block-end: 0.75rem;
  background: var(--accent-tint);
  border-radius: var(--radius);
}
.scan-pending .queued-url {
  display: block;
  margin-block-start: 0.375rem;
  overflow-wrap: anywhere;
}

.m-note {
  padding: 0.75rem;
  margin-block: 0.75rem;
  background: var(--accent-tint);
  border-radius: var(--radius);
}
.m-note.is-warn {
  background: var(--warn-tint);
}
.m-note.is-danger {
  background: color-mix(in srgb, var(--danger) 12%, var(--bg));
}

.m-foot-warn {
  margin-block-start: 2rem;
  padding-block-start: 1rem;
  border-block-start: 1px solid var(--divider);
  font-size: 0.875rem;
}
.m-foot-d11 {
  color: var(--muted);
}

/* ---- Recent-activity panel (M7j; design 6B-01g) ---- */
.m-activity {
  margin-block: 1rem;
}
.m-activity summary {
  cursor: pointer;
  font-weight: 600;
  padding-block: 0.5rem;
}
.m-activity table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: 0.8125rem;
}
.m-activity th,
.m-activity td {
  text-align: start;
  padding-inline-end: 0.75rem;
  padding-block: 0.375rem;
  border-block-end: 1px solid var(--divider);
  vertical-align: top;
}
.m-activity thead th {
  color: var(--muted);
  font-weight: 600;
}

/* Blocked-URL message revealed behind the QR when it shatters (z-index below the SVG). */
.qr-preview { position: relative; display: inline-block; max-width: 100%; }
/* Hairline on the ARTIFACT, not on the wrapper: the caption band is painted in the
   design ink (#111 for classic/soft/dots — i.e. the default), and on the dark theme's
   --bg #121212 that band is indistinguishable from the page. The white card would
   appear to simply end, with the reversed label reading as text floating on nothing.
   The preview must not lie about the delivered SVG, so the tint stays and the bounds
   become visible instead. --input-border is the token documented ≥3:1 on --bg in BOTH
   themes (the sticky rule below already uses it for the same separation job);
   --divider is ~1.7:1 on #121212 and would not survive against a #111 band. */
.qr-preview svg.qr {
  position: relative;
  z-index: 1;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--input-border);
}

/* Mobile: pin the QR preview so customization changes stay visible while the user
   scrolls the controls below (spec create-flow-customization.md §11). Full-bleed bg
   (box-shadow trick) so scrolled content never shows at the sides of the pinned box;
   a hairline + subtle shadow separates it from the scrolling content. Sticky pins
   against the page scroll (no nested scroll container). On short pages (loading / no
   controls) it has nothing to pin past, so it's inert. */
@media (max-width: 48rem) {
  .qr-preview {
    position: sticky;
    top: 0;
    z-index: 5;
    display: flex;
    justify-content: center;
    /* Fill the content column + solid page bg so scrolled controls never show behind
       the pinned QR. (The page padding strips beside it are already var(--bg), so the
       cover reads edge-to-edge without a fragile full-bleed hack.) */
    inline-size: 100%;
    background: var(--bg);
    padding-block: 8px;
    box-shadow: 0 6px 6px -6px rgba(0, 0, 0, 0.35);
    border-block-end: 1px solid var(--input-border);
  }
  /* Shrink the pinned preview a touch so it doesn't dominate small viewports and the
     controls stay reachable in the same glance. */
  .qr-preview svg.qr { max-inline-size: min(60vw, 220px); }
}
/* The manage in-place customize preview: centered + sized like the draft preview. */
.m-customize-preview { display: block; inline-size: min(260px, 70%); margin: 0 auto 1.25rem; }
/* "Your design: Soft Corners — rounded blocks…" — a statement of the stored preset,
   sitting between the SSR'd preview and the picker that edits it. No thumbnail of its
   own: the preview above IS the thumbnail. No new custom properties. */
.m-design-current {
  margin-block: 0 0.75rem;
  text-align: center;
  font-size: 0.9rem;
}
.m-design-current .label { color: var(--muted); }
.m-design-desc { display: block; color: var(--muted); font-size: 0.82rem; }
/* The no-JS-safe dismiss on the design-saved confirmation (a plain link back to the
   page without the query), set apart from the sentence it follows. */
.m-note-dismiss { margin-inline-start: 0.5rem; }
.blocked-msg {
  position: absolute; inset: 0; z-index: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; color: #b91c1c; padding: 8%; box-sizing: border-box;
}
.blocked-msg[hidden] { display: none; }
.blocked-ico { font-size: 2.5rem; line-height: 1; }
.blocked-title { font-weight: 800; margin: 0.3rem 0 0.1rem; }
.blocked-body { font-size: 0.8rem; color: #6b7280; margin: 0; }

/* Animated QR teaser + gallery (specs/animated-qr.md §6). Previews keep the
   full quiet zone and are sized by the wrapper, never by trimming margin. */
.aq-teaser .aq-preview > svg { width: 160px; height: auto; display: block; margin: 0 auto; }
.aq-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.5rem; }
.aq-tile {
  content-visibility: auto;        /* off-screen tiles neither render nor animate */
  contain-intrinsic-size: 340px;
  text-align: center;
}
/* DIRECT child only: a captioned tile nests the symbol in an inner <svg> that
   carries its own width/height (the caption wrapper's new viewport). Matching it
   with this rule would override those with 100%/auto and re-centre the symbol
   over the whole banded canvas, i.e. straight through the caption. */
/* The border is the same dark-mode hairline the `.qr-preview svg.qr` rule above
   gives the customize preview, for the one captioned surface that rule cannot
   reach: the gallery's producer is renderAnimatedQrSvg, whose root carries NO
   class, so `svg.qr` never matches it. Its band is #111 (that producer publishes
   no root fill), and on the dark theme's --bg #121212 all thirteen tiles would
   otherwise end in a strip indistinguishable from the page — the exact failure
   that rule exists to prevent. Same --input-border token, documented >=3:1 on
   --bg in both themes, and box-sizing: border-box keeps the 240px cap intact. */
.aq-tile .aq-preview > svg { width: 100%; max-width: 240px; height: auto; border: 1px solid var(--input-border); }
.aq-tile h2 { font-size: 1rem; margin: 0.5rem 0; }

/* ---- Success page: the management/access link — an emphasised card that is both
   clickable (opens /m/{token}) and copyable. Logical properties only (ADR 0014). ---- */
.management {
  margin-block: 1.5rem;
  padding: 1rem;
  background: var(--accent-tint);
  border: 1px solid var(--input-border);
  border-radius: var(--radius);
}
.management h2 {
  margin-block: 0 0.25rem;
}
.save-link-label {
  margin-block: 0 0.75rem;
  font-weight: 600;
  color: var(--fg);
}
/* The link itself, rendered as a tappable field. */
.manage-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-block-size: 44px;
  padding-inline: 0.75rem;
  padding-block: 0.5rem;
  background: var(--bg);
  border: 1px solid var(--input-border);
  border-radius: var(--radius);
  color: var(--fg);
  text-decoration: none;
}
.manage-link:hover,
.manage-link:focus-visible {
  border-color: var(--accent);
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.manage-link__icon {
  flex: none;
}
/* Truncation backstop: keep the URL on one line and ellipsis any overflow. */
.manage-link__url {
  flex: 1 1 auto;
  min-inline-size: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 0.875rem;
}
/* Host stays proportional (narrower, readable); only the opaque token is monospace. */
.manage-link__host {
  font-weight: 700;
  color: var(--fg);
}
.manage-link__path {
  color: var(--muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.manage-link__go {
  flex: none;
  color: var(--accent);
  font-weight: 700;
}
.manage-copy {
  margin-block-start: 0.625rem;
}
.manage-copy.is-copied {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.share-actions {
  margin-block: 0.75rem 0;
  font-size: 0.9rem;
}
.share-actions a {
  color: var(--accent);
}

/* ---- Success page (6A-10) — QR confirmation, activating spinner, status pill, receipt ---- */
.qr-show {
  display: flex;
  gap: 1rem;
  align-items: center;
  padding: 1rem;
  border: 1px solid var(--divider);
  border-radius: var(--radius);
  margin-block: 0 1.25rem;
}
/* The chip is sized by the ARTIFACT, so it does not letterbox. The confirmation SVG
   is 37×42 modules when captioned; fitting that into a fixed 8rem SQUARE left a
   7.5px strip down each side, which was invisible while the band was white but reads
   as a dark slab floating inside the chip now that the band is painted in the design
   ink. So the height is pinned and the width follows the viewBox: 8rem square when
   uncaptioned, ~113×128 when captioned, with the band flush to the card's bottom edge
   (and clipped by the corner radius) so it reads as a footer. Pinning the height is
   what the rejected `block-size: auto` got wrong — that made the chip a DIFFERENT
   height for captioned and uncaptioned codes in a flex row built around a square; a
   varying width in that row costs nothing. Nothing clips and nothing distorts. */
.qr-frame {
  flex: 0 0 auto;
  block-size: 8rem;
  border: 1px solid var(--divider);
  border-radius: 0.25rem;
  overflow: hidden;
  background: #fff;
}
.qr-frame svg.qr {
  display: block;
  block-size: 100%;
  inline-size: auto; /* height is definite → the viewBox ratio sets the width */
}
.qr-info {
  min-inline-size: 0;
}
.qr-code-id {
  font-family: ui-monospace, Menlo, monospace;
  font-weight: 600;
  color: var(--fg);
  margin-block: 0.375rem 0;
}
.qr-dest {
  font-size: 0.85rem;
  color: var(--muted);
  margin-block: 0.25rem 0;
  word-break: break-all;
}

.poll-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.25rem;
  padding-block: 2rem;
}
.spinner {
  inline-size: 2.25rem;
  block-size: 2.25rem;
  border: 3px solid var(--accent-tint);
  border-block-start-color: var(--accent);
  border-radius: 50%;
  margin-block-end: 0.5rem;
  animation: kq-spin 0.8s linear infinite;
}
@keyframes kq-spin {
  to {
    transform: rotate(360deg);
  }
}
@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation: none;
  }
}
.poll-timeout {
  margin-block-start: 1rem;
}
.poll-note {
  padding: 0.75rem 1rem;
  background: var(--warn-tint);
  border-radius: var(--radius);
  margin-block: 0 0.75rem;
}
.pill--active {
  margin-block-end: 0.5rem;
}
.receipt {
  margin-block: 0.5rem 0;
  font-size: 0.9rem;
  color: var(--muted);
}
.save-banner {
  display: flex;
  gap: 0.625rem;
  align-items: flex-start;
  padding: 0.875rem 1rem;
  background: var(--accent-tint);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  margin-block: 0 0.75rem;
}
.save-banner .check {
  flex: 0 0 auto;
  font-weight: 700;
  color: var(--accent);
}
.save-banner p {
  margin: 0;
}
.more-formats {
  margin-block: 0.25rem;
}
.more-formats summary {
  cursor: pointer;
  font-size: 0.9rem;
  color: var(--accent);
  padding-block: 0.5rem;
}
.print-tip {
  color: var(--muted);
}

/* ---- Shortener unfurl-and-confirm (6A-3b, D13) ---- */
.alert {
  display: flex;
  gap: 0.625rem;
  align-items: flex-start;
  padding: 0.75rem 1rem;
  border: 1px solid var(--divider);
  border-radius: var(--radius);
  background: var(--bg);
  margin-block: 0 1rem;
}
.alert p {
  margin: 0;
}
.alert__icon {
  flex: 0 0 auto;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--on-accent);
  background: var(--accent);
}
.alert--info {
  background: var(--accent-tint);
  border-color: var(--accent);
}
.alert--warn {
  background: var(--warn-tint);
  border-color: #b57700;
}
.alert--warn .alert__icon {
  background: #b57700;
  color: #fff;
}
.url-pair {
  border: 1px solid var(--divider);
  border-radius: var(--radius);
  overflow: hidden;
  margin-block: 1rem;
}
.url-row {
  display: grid;
  grid-template-columns: 6rem 1fr;
  gap: 0.25rem 0.75rem;
  align-items: baseline;
  padding: 0.75rem 0.875rem;
}
.url-row + .url-row,
.url-pair .hop-count {
  border-block-start: 1px solid var(--divider);
}
.url-row.is-resolved {
  background: var(--accent-tint);
}
.url-label {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.url-value {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 0.8125rem;
  color: var(--fg);
  word-break: break-all;
}
.url-row.is-resolved .url-value {
  font-weight: 600;
}
.hop-count {
  font-family: ui-monospace, Menlo, monospace;
  font-size: 0.75rem;
  color: var(--muted);
  padding: 0.5rem 0.875rem;
  margin: 0;
}
.shortener-confirm .btn,
.shortener-confirm .lede {
  margin-block-end: 0.75rem;
}
.keep-shortener {
  margin-block: 1.25rem 0;
  padding: 0.875rem 1rem;
  border: 1px dashed var(--input-border);
  border-radius: var(--radius);
  background: var(--accent-tint);
}
.keep-shortener > summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--fg);
}
.keep-shortener__body {
  margin-block-start: 0.75rem;
}
.keep-shortener__body p {
  font-size: 0.9rem;
  color: var(--muted);
  margin-block: 0 0.75rem;
}

/* ---- Static legal pages (M12a / #244) — readable long-form prose + provider table ---- */
.legal {
  max-inline-size: 42rem;
}
.legal h1 {
  margin-block-end: 0.25rem;
}
.legal-effective {
  margin-block-start: 0;
  color: var(--muted);
  font-size: 0.9rem;
}
.legal section {
  margin-block-start: 1.5rem;
}
.legal h2 {
  font-size: 1.15rem;
}
.legal ul {
  padding-inline-start: 1.25rem;
}
.legal-table {
  inline-size: 100%;
  border-collapse: collapse;
  margin-block: 0.5rem;
}
.legal-table th,
.legal-table td {
  text-align: start;
  padding: 0.5rem;
  border-block-end: 1px solid var(--divider);
  vertical-align: top;
}
.legal-table thead th {
  color: var(--muted);
  font-weight: 600;
}

/* ---- Create scan-progress steps (#285) — calm what+why during the check ---- */
.scan-steps {
  list-style: none;
  padding-inline-start: 0;
  margin-block: 0.75rem 0.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.scan-step {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--muted);
}
.scan-step::before {
  content: "○"; /* pending */
  color: var(--muted);
}
.scan-step[aria-current="step"] {
  color: var(--fg);
  font-weight: 600;
}
.scan-step[aria-current="step"]::before {
  content: "◍"; /* in progress */
  color: var(--accent);
}
.scan-step[data-done] {
  color: var(--muted);
}
.scan-step[data-done]::before {
  content: "✓"; /* done */
  color: var(--accent);
}
.scan-why {
  margin-block: 0.25rem 0;
  color: var(--muted);
  font-size: 0.9rem;
}

/* Horizontal check stepper (6A-06 optimistic scan gate): "Checking your link" →
   "Ready to pay". Decorative (aria-hidden) — the .scan-checking live region carries
   the screen-reader announcement. */
.check-stepper {
  list-style: none;
  padding-inline-start: 0;
  margin-block: 1rem 0.75rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.check-node {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--muted);
  font-size: 0.9rem;
}
.check-node:first-child::after {
  content: "";
  inline-size: 1.75rem;
  block-size: 2px;
  border-radius: 2px;
  background: var(--divider);
  margin-inline-start: 0.25rem;
}
.check-node:first-child.is-done::after {
  background: var(--accent);
}
.check-node__marker {
  position: relative;
  inline-size: 1.15rem;
  block-size: 1.15rem;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 2px solid var(--input-border);
  display: grid;
  place-items: center;
}
.check-node.is-active {
  color: var(--fg);
  font-weight: 600;
}
.check-node.is-active .check-node__marker {
  border-color: var(--accent);
}
/* Pay node, active = filled accent dot. */
.check-node[data-node="pay"].is-active .check-node__marker {
  background: var(--accent);
}
/* Done = accent check. */
.check-node.is-done .check-node__marker {
  border-color: var(--accent);
  background: var(--accent);
}
.check-node.is-done .check-node__marker::after {
  content: "✓";
  color: var(--on-accent);
  font-size: 0.72rem;
  font-weight: 700;
}
/* Active check node = spinner instead of the ring. */
.check-node__spinner {
  display: none;
}
.check-node[data-node="check"].is-active .check-node__marker {
  border: 0;
}
.check-node[data-node="check"].is-active .check-node__spinner {
  display: block;
  inline-size: 1.15rem;
  block-size: 1.15rem;
  border: 2px solid var(--accent-tint);
  border-block-start-color: var(--accent);
  border-radius: 50%;
  animation: kq-spin 0.8s linear infinite;
}
.check-node[data-node="check"].is-done .check-node__spinner {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .check-node__spinner {
    animation: none;
  }
}

/* ===== Customize surface (ADR 0031) — restyle of the shipped no-JS radio markup
   to the hi-fi visual language (claudedesign/designs/integration/customize.css).
   Radios stay the semantic control; :has(input:checked) drives the selected look,
   and the native radio is visually hidden but kept focusable (focus ring lifts to
   the label). Tokens are shared with the rest of the app. ===== */
.customize {
  margin-block-start: 1.5rem;
  padding-block-start: 1.25rem;
  border-block-start: 1px solid var(--divider);
  /* Masked-glyph sources (recoloured per token via mask + background). */
  --cz-ico-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M21 21l-4-4'/%3E%3C/svg%3E");
  --cz-ico-shield: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 2l8 4v6c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6z'/%3E%3Cpath d='M9 12l2 2 4-4'/%3E%3C/svg%3E");
}
.customize > h2 {
  font-size: 1.05rem;
  margin-block: 0 0.35rem;
}
.cz-note {
  color: var(--muted);
  font-size: 0.88rem;
  margin-block: 0 1rem;
}
/* The surface toggles visibility via the [hidden] attribute (panels, filtered icon
   cells, the JS-revealed search/chips). Several of those elements set `display:`,
   which (author > UA) would defeat the UA `[hidden]{display:none}` — so force it. */
.customize [hidden] {
  display: none;
}

/* fieldset/legend reset — every control group renders as a labelled block */
.customize .cz-group {
  border: 0;
  margin: 0;
  padding: 0;
  margin-block-start: 1.1rem;
  /* A <fieldset> defaults to min-inline-size:min-content, so a group with a wide
     non-wrapping child (the horizontal-scroll .cz-style-grid) would grow to its
     content width and push the page into horizontal scroll on mobile. Reset it so
     the group tracks the content column; the grid's own overflow-x scrolls it. */
  min-inline-size: 0;
}
.customize .cz-group > legend {
  padding: 0;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 700;
  margin-block-end: 7px;
}

/* The radio inside any segment/cell/tile: transparent but COVERS the whole label,
   so a click/tap (and Playwright's .check()) lands on the input while the styled
   label shows through. Never `pointer-events:none` / 1px — that makes it
   unclickable. Focus ring lifts to the label via :has(input:focus-visible). */
.cz-opt,
.cz-icon,
.cz-style {
  position: relative;
}
.cz-opt input,
.cz-icon input,
.cz-style input {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

/* Logo-source + badge + shape: iOS-style segmented controls. The legend is the
   small label above; `.cz-seg` is the tinted track; each `.cz-opt` is a segment.
   Selected = raised surface + subtle shadow (no accent border — accent is reserved
   for the icon/style pickers), conveyed by elevation + muted→fg colour, not hue. */
.cz-seg {
  display: flex;
  gap: 4px;
  padding: 4px;
  background: color-mix(in srgb, var(--fg) 6%, var(--bg));
  border-radius: var(--radius);
}
.cz-opt {
  flex: 1 1 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-block-size: 44px;
  padding-inline: 8px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
  border-radius: calc(var(--radius) - 2px);
  cursor: pointer;
  white-space: nowrap;
  text-align: center;
}
.cz-opt-ico {
  display: inline-flex;
}
.cz-opt-ico svg {
  inline-size: 16px;
  block-size: 16px;
}
.cz-opt:hover {
  color: var(--fg);
}
.cz-opt:has(input:checked) {
  color: var(--fg);
  background: var(--bg);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.cz-opt:has(input:focus-visible) {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* Badge + Shape sit side by side (matches the hi-fi two-up row). */
.cz-row2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  margin-block-start: 1.1rem;
}
.cz-row2 > .cz-group {
  margin-block-start: 0;
  min-inline-size: 0;
}

/* Source panels (library / upload / mono) — the [hidden] ones are display:none
   via the attribute; this just spaces the visible one. */
.customize .cz-panel {
  margin-block-start: 0.9rem;
}

/* Icon search box (enhancement; [hidden] until the client reveals it). The
   magnifier is a masked pseudo so it tracks the muted token in light + dark. */
.cz-search {
  position: relative;
  margin-block-end: 0.6rem;
}
.cz-search::before {
  content: "";
  position: absolute;
  inset-inline-start: 11px;
  inset-block-start: 50%;
  transform: translateY(-50%);
  inline-size: 15px;
  block-size: 15px;
  background: var(--muted);
  pointer-events: none;
  -webkit-mask: var(--cz-ico-search) center / contain no-repeat;
  mask: var(--cz-ico-search) center / contain no-repeat;
}
.cz-icon-search {
  inline-size: 100%;
  min-block-size: 40px;
  padding-inline: 34px 10px;
  font: inherit;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--input-border);
  border-radius: var(--radius);
}
/* Category chips — horizontal scroll, pill toggles (enhancement). */
.cz-cats {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  margin-block-end: 0.7rem;
  padding-block-end: 4px;
  scrollbar-width: none;
}
.cz-cats::-webkit-scrollbar {
  display: none;
}
.cz-cat {
  flex: none;
  min-block-size: 32px;
  padding-inline: 12px;
  font: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--divider);
  border-radius: 999px;
  cursor: pointer;
}
.cz-cat[aria-pressed="true"] {
  color: var(--bg);
  background: var(--fg);
  border-color: var(--fg);
}
.cz-no-icons {
  margin-block: 0.5rem 0;
  font-size: 0.85rem;
  color: var(--muted);
}

/* Icon library — square cells in an auto-fill grid. */
.cz-icon-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(62px, 1fr));
  gap: 7px;
  max-block-size: 240px;
  overflow-y: auto;
  padding: 2px;
}
.cz-icon {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  aspect-ratio: 1;
  padding: 5px;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--divider);
  border-radius: 10px;
  cursor: pointer;
}
.cz-icon:hover {
  border-color: var(--accent);
  background: var(--accent-tint);
}
.cz-icon:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-tint);
  box-shadow: 0 0 0 2px var(--accent) inset;
}
.cz-icon:has(input:focus-visible) {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}
.cz-icon-art svg {
  inline-size: 21px;
  block-size: 21px;
}
.cz-icon-name {
  font-size: 9px;
  color: var(--muted);
  text-align: center;
}

/* Upload — dashed dropzone look around the file input. */
.cz-upload-label {
  display: block;
  padding: 22px 18px;
  text-align: center;
  font-weight: 600;
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 3%, var(--bg));
  border: 1.5px dashed var(--input-border);
  border-radius: var(--radius);
  cursor: pointer;
}
.cz-upload-label:hover,
.cz-upload-label.over {
  border-color: var(--accent);
  background: var(--accent-tint);
}
.cz-upload-label.is-busy {
  opacity: 0.6;
  pointer-events: none;
}
/* Uploaded-file preview row + remove. */
.cz-uploaded {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-block-start: 12px;
  padding: 10px;
  border: 1px solid var(--divider);
  border-radius: var(--radius);
}
.cz-uploaded-thumb {
  inline-size: 42px;
  block-size: 42px;
  object-fit: contain;
  background: #fff;
  border: 1px solid var(--divider);
  border-radius: 8px;
}
.cz-uploaded-name {
  flex: 1 1 auto;
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.85rem;
  color: var(--muted);
}
.cz-uploaded-remove {
  inline-size: auto;
  margin-inline-start: auto;
}
.cz-upload {
  display: block;
  inline-size: 100%;
  margin-block-start: 10px;
  font: inherit;
  color: var(--muted);
}
.cz-hint {
  font-size: 0.82rem;
  color: var(--muted);
  margin-block: 8px 0;
}
.cz-error {
  font-size: 0.82rem;
  color: var(--danger);
  margin-block: 8px 0;
}

/* Monogram — large centered field. */
.cz-mono {
  inline-size: 100%;
  min-block-size: 48px;
  padding: 8px;
  text-align: center;
  text-transform: uppercase;
  font-size: 1.2rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--input-border);
  border-radius: var(--radius);
}

/* Size — accent-tinted range with a live numeric readout to its right. */
.cz-slider {
  display: flex;
  align-items: center;
  gap: 12px;
}
.cz-size {
  flex: 1 1 auto;
  inline-size: auto;
  accent-color: var(--accent);
}
.cz-size-val {
  flex: none;
  inline-size: 2.5ch;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  text-align: end;
  color: var(--muted);
}

/* Scannability note — tinted callout; tightens to the warn tint at high coverage. */
.cz-scan {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 13px;
  margin-block-start: 1rem;
  font-size: 0.88rem;
  background: var(--accent-tint);
  border-radius: var(--radius);
}
.cz-scan::before {
  content: "";
  flex: none;
  inline-size: 18px;
  block-size: 18px;
  background: var(--accent);
  -webkit-mask: var(--cz-ico-shield) center / contain no-repeat;
  mask: var(--cz-ico-shield) center / contain no-repeat;
}
.cz-scan:has(.cz-scan-state[data-scan="tight"]) {
  background: var(--warn-tint);
}
.cz-scan:has(.cz-scan-state[data-scan="tight"])::before {
  background: var(--danger);
}
.cz-scan-label {
  font-weight: 700;
}
/* The derived minimum print size rides in the same live region as the grade —
   its own line so the two facts don't read as one sentence. */
.cz-scan-min {
  display: block;
  font-size: 0.82rem;
  color: var(--muted);
}

/* ---- Design (preset) picker — the four static module/finder geometries ----
   Stays inside the shipped 40rem single column (:47); the imported prototype's
   1180px two-column sticky grid and its 880px breakpoint are deliberately NOT
   ported.

   Each thumbnail is a 9×9-module crop around one finder, so the binding
   constraint is px-PER-MODULE, not column count: the art box must stay ≥ 108px
   (9 × 12). `auto-fit` + a `minmax` FLOOR enforces that at every width instead
   of a fixed track count, which only holds either side of a single breakpoint —
   4×1 at a 430px viewport (iPhone Pro Max, Pixel 7 Pro) would give a 92px tile
   and an 8.7px/module thumbnail. 8rem floor ⇒ 4 tracks at the full 40rem column
   (~145px each), 3 around 500-549px, 2 down to ~300px, and never below
   ~12.9px/module. e2e/specs/qr-build.spec.ts measures this at 390/430/640. */
.cz-presets {
  margin-block-start: 1.1rem;
}
.cz-preset-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  /* Height-stable rows: "Soft Corners" wraps to two lines in several locales and
     the row must not jump when the label above it does. */
  grid-auto-rows: 1fr;
  gap: 0.6rem;
}
.cz-preset {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  padding: 6px;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--divider);
  border-radius: var(--radius);
  cursor: pointer;
}
.cz-preset input {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.cz-preset:hover {
  border-color: var(--accent);
}
.cz-preset:has(input:checked) {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent) inset;
}
.cz-preset:has(input:focus-visible) {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}
/* Truthful thumbnail: the artifact is printed on white, so the QR field stays
   white in both schemes — only the tile chrome around it follows the theme. */
.cz-preset-art {
  display: block;
  aspect-ratio: 1;
  overflow: hidden;
  background: #fff;
  border: 1px solid var(--divider);
  border-radius: calc(var(--radius) - 2px);
}
.cz-preset-art svg {
  display: block;
  inline-size: 100%;
  block-size: 100%;
}
.cz-preset-name {
  /* Two label lines' worth of space whether or not this label wraps. */
  min-block-size: 2.4em;
  min-block-size: 2lh;
  font-size: 0.78rem;
  line-height: 1.2;
  text-align: center;
  color: var(--muted);
}
.cz-preset:has(input:checked) .cz-preset-name {
  color: var(--accent);
  font-weight: 600;
}

/* The 14 animation minis behind a disclosure — they cost more vertical budget
   than the presets can spare in a 640px column. */
.cz-anim-disclosure {
  margin-block-start: 1.1rem;
}
.cz-anim-disclosure > summary {
  min-block-size: 44px;
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 600;
  cursor: pointer;
  /* `display: flex` suppresses the native disclosure triangle in every engine,
     so the chevron below is the only affordance — do not drop one without the
     other. `::-webkit-details-marker` covers older Safari, which still paints a
     marker despite the flex display. */
  list-style: none;
}
.cz-anim-disclosure > summary::-webkit-details-marker {
  display: none;
}
.cz-anim-disclosure > summary::before {
  content: "";
  flex: none;
  inline-size: 0.5rem;
  block-size: 0.5rem;
  border-inline-end: 2px solid currentColor;
  border-block-end: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
  /* Optically centres the rotated square on the text baseline. */
  margin-block-start: -0.15rem;
  margin-inline-end: 2px;
}
.cz-anim-disclosure[open] > summary::before {
  transform: rotate(45deg);
  margin-block-start: -0.3rem;
}
@media (prefers-reduced-motion: reduce) {
  .cz-anim-disclosure > summary::before {
    transition: none;
  }
}
.cz-anim-disclosure > summary:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}
.cz-anim-disclosure .cz-styles {
  margin-block-start: 0.4rem;
}

/* Style picker — horizontal-scroll row of live mini QRs. */
.cz-styles {
  margin-block-start: 1.1rem;
}
.cz-style-grid {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding-block: 4px 8px;
  scrollbar-width: thin;
}
.cz-style {
  flex: none;
  inline-size: 72px;
  text-align: center;
  cursor: pointer;
}
.cz-style-mini {
  display: block;
  inline-size: 64px;
  block-size: 64px;
  overflow: hidden;
  background: #fff;
  border: 2px solid var(--divider);
  border-radius: 10px;
}
.cz-style-mini svg {
  display: block;
  inline-size: 100%;
  block-size: 100%;
}
.cz-style:has(input:checked) .cz-style-mini {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent);
}
.cz-style:has(input:focus-visible) .cz-style-mini {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}
.cz-style-name {
  display: block;
  margin-block-start: 5px;
  font-size: 0.72rem;
  color: var(--muted);
}
.cz-style:has(input:checked) .cz-style-name {
  color: var(--accent);
  font-weight: 600;
}

/* Badge-color panel — 7 preset swatches + a native custom picker (shown only when
   Badge = Color). Row wraps (never `overflow-x:auto` scroll-only) so it can never
   force horizontal page scroll on narrow viewports. */
.cz-swatch-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}
.cz-swatch {
  position: relative;
  display: block;
  inline-size: 44px;
  block-size: 44px;
  cursor: pointer;
}
/* Same transparent-input-covers-the-label pattern as .cz-opt/.cz-icon/.cz-style
   above: the swatch chip is purely decorative (aria-hidden), the input is the
   full 44px tap target. */
.cz-swatch input {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.cz-swatch-chip {
  display: block;
  inline-size: 28px;
  block-size: 28px;
  margin: 8px;
  border-radius: 50%;
  background: var(--sw);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15) inset;
}
.cz-swatch:has(input:checked) .cz-swatch-chip {
  box-shadow: 0 0 0 2px var(--fg), 0 0 0 4px var(--sw);
}
.cz-swatch:has(input:focus-visible) .cz-swatch-chip {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}
.cz-badge-custom-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-block-start: 10px;
  font-size: 0.85rem;
  color: var(--muted);
}
.cz-badge-custom {
  inline-size: 44px;
  block-size: 44px;
  padding: 0;
  border: 1px solid var(--input-border);
  border-radius: var(--radius);
  background: none;
  cursor: pointer;
}

/* Caption picker — the same native radios and the same .cz-opt cells as the
   segmented groups above, in a WRAPPING grid instead of one flex track. Four
   caption strings ("Scan to follow" is the longest) come to ~440px of nowrap
   segments against a ~326px column at a 390px viewport, and .cz-sect sets no
   overflow, so the segmented track would push the page into horizontal scroll.
   The floor + auto-fit is .cz-preset-grid's, already measured at 390/430/640:
   4 tracks at the full 40rem column, 2 on a phone, never a partial track. */
.cz-cta-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 0.6rem;
}
/* Cell chrome, since there is no tinted .cz-seg track behind these to read
   against — the same border/accent language as .cz-preset, which is the other
   grid on this surface. */
.cz-cta-grid .cz-opt {
  /* The segmented track sets `white-space: nowrap` so its segments never wrap
     mid-word; in a grid the track is the wrap point, so a long caption in a
     narrow column must be allowed to take two lines rather than overflow it. */
  white-space: normal;
  padding-block: 6px;
  background: var(--bg);
  border: 1px solid var(--divider);
  border-radius: var(--radius);
}
.cz-cta-grid .cz-opt:has(input:checked) {
  color: var(--fg);
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent) inset;
}


/* ---------------------------------------------------------------------------
   Customize surface — section cards.

   The reference (claudedesign/designs/customization-v2/spec.css:16) defines its
   cards with a 1px edge + an 18px radius; its card fill is only 0.8% off the
   page ground, i.e. below JND. So the edge and the radius step carry the
   structure, not the fill. We use a stronger fill than the reference because our
   ground is pure white and the reference's is not, but the edge stays.

   Appended at end of file deliberately: several rules below have the same
   specificity as ones earlier in the sheet and must win on source order.
   --------------------------------------------------------------------------- */
.customize .cz-sect {
  /* Rebind --bg locally so every descendant that paints or mixes against it
     re-resolves against the card. Without this, on a dark card .cz-seg becomes
     darker than the card it sits on and .cz-opt:has(input:checked) paints the
     page colour — turning the SELECTED segment into a recessed hole. */
  --bg: var(--surface-raised);
  background: var(--bg);
  /* Mixed against the rebound --bg, so the card edge is quieter than the
     --divider borders on the tiles inside it: outer/quiet → inner/strong. */
  border: 1px solid color-mix(in srgb, var(--fg) 8%, var(--bg));
  border-radius: 0.75rem; /* a real step above the 0.5rem tiles */
  padding: 1rem;
  /* No overflow: it would clip the outline-offset:2px focus rings on edge tiles. */
  margin-block-start: 1.5rem;
}

/* .cz-scan sits between sections 2 and 3, so an adjacent-sibling rule would
   never reach section 3. Flat margins, matched on the strip, keep all three
   boundaries equal. */
.customize .cz-scan {
  margin-block-start: 1.5rem;
  padding-inline: 1rem;
}

/* Typography applies to every section title, including the one nested inside
   <summary> — otherwise Animation would inherit the summary's 600 while its two
   peers sit at 700. */
.customize .cz-sect .cz-sect-t {
  font-size: 1rem;
  font-weight: 700;
  color: var(--fg);
}

/* The block margin is DIRECT-CHILD ONLY. On the summary-nested span it would
   become a margin on a flex item under align-items:center, shifting the title
   off the chevron's optical centre and undoing the -0.15rem tuning below. */
.customize .cz-sect > .cz-sect-t {
  margin: 0 0 0.5rem;
}

/* Scoped to the direct child above, so the summary-nested title span never picks
   up a block margin — under align-items:center that would shift the text off the
   chevron's optical centre. */
.customize .cz-sect > .cz-sect-t + * {
  margin-block-start: 0;
}

/* Splits the Logo card into source (kind/library/upload/text/size) and placement
   (badge/shape/colour) with whitespace alone — no markup, no copy, no section. */
.customize .cz-row2 {
  margin-block-start: 1.75rem;
}

/* Tracked bold micro-caps, repeated five times down one column, is the loudest
   unstyled-form signal on this surface. The `> label` half is a pre-existing
   inconsistency: the monogram and size groups use <label>, and no rule matched
   them, so they rendered at full body size in --fg beside 0.72rem muted caps.
   `:not([class])` keeps this off the labels that carry their own type scale —
   .cz-upload-label (the dropzone CTA) and .cz-badge-custom-label. Both live in
   panels that are hidden by default, so an over-match here is invisible until
   someone opens Upload or Badge → Color. */
.customize .cz-group > legend,
.customize .cz-group > label:not([class]) {
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--muted);
}

/* ---------------------------------------------------------------------------
   Apex marketing homepage.

   This surface deliberately reuses the app's brand tokens and system type but
   has its own component namespace. It is a calm, editorial explanation page;
   the state-changing create flow remains on app.keep-qr.com. No client JS, no
   web fonts, no third-party assets, and no motion are introduced here.
   --------------------------------------------------------------------------- */
body:has(main.marketing-home) {
  max-inline-size: 76rem;
  padding-inline: clamp(1rem, 3vw, 2.5rem);
  padding-block-start: 1rem;
}

.mh-header {
  align-items: center;
  gap: 1rem 1.5rem;
  padding-block: 0.5rem 1rem;
}

.mh-header .wordmark {
  font-size: 1.4rem;
  letter-spacing: -0.03em;
}

.mh-nav {
  display: flex;
  flex: 1 1 auto;
  justify-content: center;
  gap: 0.25rem 1.5rem;
}

.mh-nav a,
.mh-header-cta {
  font-size: 0.875rem;
  font-weight: 600;
}

.mh-nav a {
  min-block-size: 44px;
  display: inline-flex;
  align-items: center;
  color: var(--muted);
  text-decoration: none;
}

.mh-nav a:hover {
  color: var(--accent);
}

.mh-header .btn,
.mh-actions .btn,
.mh-price-grid .btn,
.mh-final .btn {
  inline-size: auto;
  text-decoration: none;
}

.marketing-home h1,
.marketing-home h2,
.marketing-home h3,
.marketing-home p {
  margin-block-start: 0;
}

.marketing-home h1 {
  max-inline-size: 11ch;
  margin-block: 0.35rem 1rem;
  font-size: clamp(2.8rem, 8vw, 5.75rem);
  line-height: 0.98;
  letter-spacing: -0.055em;
  text-wrap: balance;
}

.marketing-home h2 {
  max-inline-size: 18ch;
  margin-block: 0.25rem 0.8rem;
  font-size: clamp(2rem, 4.5vw, 3.5rem);
  line-height: 1.04;
  letter-spacing: -0.04em;
  text-wrap: balance;
}

.marketing-home h3 {
  margin-block-end: 0.45rem;
  font-size: 1.05rem;
  line-height: 1.25;
}

.mh-eyebrow {
  margin-block-end: 0.75rem;
  color: var(--accent);
  font-size: 0.75rem;
  font-weight: 750;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.mh-hero {
  display: grid;
  gap: 2.5rem;
  align-items: center;
  min-block-size: min(47rem, 82vh);
  padding-block: clamp(3.5rem, 9vw, 7.5rem);
}

.mh-hero-lede {
  max-inline-size: 38rem;
  margin-block-end: 1.5rem;
  color: var(--muted);
  font-size: clamp(1.1rem, 2.2vw, 1.35rem);
  line-height: 1.55;
}

.mh-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.25rem;
}

.mh-actions .btn,
.mh-final .btn,
.mh-price-grid .btn {
  padding-inline: 1.25rem;
}

.mh-text-link {
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  gap: 0.4rem;
  font-weight: 650;
  text-underline-offset: 0.2em;
}

.mh-proof {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.3rem;
  margin-block: 1.5rem 0;
  padding: 0;
  color: var(--muted);
  font-size: 0.83rem;
  list-style: none;
}

.mh-proof li::before {
  content: "✓";
  margin-inline-end: 0.4rem;
  color: var(--accent);
  font-weight: 800;
}

.mh-demo {
  margin: 0;
}

.mh-demo-stage {
  position: relative;
  display: grid;
  gap: 1.25rem;
  justify-items: center;
  padding: clamp(1.25rem, 5vw, 2.5rem);
  overflow: hidden;
  background: var(--accent-tint);
  border: 1px solid color-mix(in srgb, var(--accent) 32%, var(--bg));
  border-radius: 1.25rem;
}

.mh-print-card {
  display: grid;
  justify-items: center;
  inline-size: min(100%, 17rem);
  padding: 1rem 1rem 1.15rem;
  color: #232220;
  background: #ffffff;
  border: 1px solid #d4d4d4;
  box-shadow: 0 1rem 2.5rem color-mix(in srgb, var(--accent) 15%, transparent);
  transform: rotate(-1.5deg);
}

.mh-print-label {
  justify-self: start;
  margin-block-end: 0.5rem;
  color: #515151;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.mh-static-qr {
  display: block;
  inline-size: min(100%, 12.5rem);
  block-size: auto;
  padding: 0.25rem;
  background: #ffffff;
}

.mh-domain {
  margin-block-start: 0.5rem;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.8rem;
  font-weight: 650;
}

.mh-destinations {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: 0.5rem;
  align-items: center;
  inline-size: 100%;
}

.mh-destination {
  min-inline-size: 0;
  padding: 0.75rem;
  background: var(--bg);
  border: 1px solid var(--divider);
  border-radius: var(--radius);
}

.mh-destination span,
.mh-destination strong {
  display: block;
}

.mh-destination span {
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.mh-destination strong {
  overflow: hidden;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: clamp(0.65rem, 2vw, 0.78rem);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mh-destination.is-next {
  border-color: var(--accent);
}

.mh-route-arrow {
  color: var(--accent);
  font-size: 1.25rem;
  font-weight: 800;
}

.mh-demo figcaption {
  margin-block-start: 0.75rem;
  color: var(--muted);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.78rem;
  text-align: center;
}

.mh-section,
.mh-trust {
  padding-block: clamp(4rem, 9vw, 7rem);
  border-block-start: 1px solid var(--divider);
}

.mh-section-heading {
  max-inline-size: 43rem;
  margin-block-end: 2.25rem;
}

.mh-section-heading > p:last-child,
.mh-trust-intro > p:last-child {
  max-inline-size: 54ch;
  color: var(--muted);
  font-size: 1.05rem;
}

.mh-steps {
  display: grid;
  gap: 1px;
  margin-block: 0 1.5rem;
  padding: 1px;
  background: var(--divider);
  list-style: none;
}

.mh-steps li,
.mh-use-grid article,
.mh-trust-list article,
.mh-price-grid article {
  background: var(--bg);
}

.mh-steps li {
  padding: clamp(1.25rem, 4vw, 2rem);
}

.mh-steps li > span,
.mh-use-mark {
  display: block;
  margin-block-end: 2.5rem;
  color: var(--accent);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.78rem;
  font-weight: 700;
}

.mh-steps p,
.mh-use-grid p,
.mh-trust-list p,
.mh-price-grid article > p:last-of-type {
  color: var(--muted);
}

.mh-use-grid {
  display: grid;
  gap: 1rem;
}

.mh-use-grid article {
  display: flex;
  flex-direction: column;
  min-block-size: 20rem;
  padding: clamp(1.25rem, 4vw, 2rem);
  border: 1px solid var(--divider);
  border-radius: 0.75rem;
}

.mh-use-grid article p {
  flex: 1 1 auto;
}

.mh-use-grid article a,
.mh-trust-list article a {
  font-weight: 650;
  text-underline-offset: 0.2em;
}

.mh-trust {
  display: grid;
  gap: 2.5rem;
}

.mh-trust-list {
  border-block-start: 1px solid var(--divider);
}

.mh-trust-list article {
  padding-block: 1.25rem;
  border-block-end: 1px solid var(--divider);
}

.mh-pricing {
  background: var(--accent-tint);
  box-shadow: 0 0 0 100vmax var(--accent-tint);
  clip-path: inset(0 -100vmax);
}

.mh-price-grid {
  display: grid;
  gap: 1rem;
}

.mh-price-grid article {
  display: flex;
  flex-direction: column;
  padding: clamp(1.25rem, 4vw, 2rem);
  border: 1px solid var(--divider);
  border-radius: 0.75rem;
}

.mh-price-grid article.is-featured {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.mh-price-name {
  min-block-size: 2rem;
  font-weight: 700;
}

.mh-price {
  margin-block-end: 0.75rem;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: clamp(2rem, 6vw, 3.25rem);
  font-weight: 750;
  letter-spacing: -0.04em;
}

.mh-price-grid article > p:last-of-type {
  flex: 1 1 auto;
  min-block-size: 3.5rem;
}

.mh-price-grid .btn {
  margin-block-start: 1rem;
}

.mh-price-terms {
  margin-block: 1.75rem 0;
  color: var(--muted);
  font-size: 0.85rem;
  text-align: center;
}

.mh-faq {
  display: grid;
  gap: 1.5rem 3rem;
}

.mh-faq-list details {
  border-block-start: 1px solid var(--divider);
}

.mh-faq-list details:last-child {
  border-block-end: 1px solid var(--divider);
}

.mh-faq-list summary {
  min-block-size: 60px;
  padding-block: 1rem;
  font-weight: 700;
  cursor: pointer;
}

.mh-faq-list details p {
  max-inline-size: 58ch;
  padding-block-end: 1rem;
  color: var(--muted);
}

.mh-final {
  display: grid;
  justify-items: center;
  padding-block: clamp(4.5rem, 11vw, 8rem);
  text-align: center;
  border-block-start: 1px solid var(--divider);
}

.mh-final h2 {
  margin-block-end: 1.5rem;
}

.mh-footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1.25rem 2rem;
  padding-block: 2rem;
}

.mh-footer > div {
  max-inline-size: 32rem;
}

.mh-footer p {
  margin-block: 0.35rem 0;
}

.mh-footer-mark {
  color: var(--fg);
  font-weight: 750;
  letter-spacing: -0.03em;
}

.mh-footer nav {
  align-content: start;
  justify-content: flex-end;
  margin-block-start: 0;
}

@media (max-width: 42rem) {
  .mh-header {
    align-items: center;
  }
  .mh-nav {
    order: 3;
    flex-basis: 100%;
    justify-content: space-between;
    gap: 0.75rem;
    overflow-x: auto;
  }
  .mh-nav a {
    flex: 0 0 auto;
    font-size: 0.78rem;
  }
  .mh-header-cta {
    margin-inline-start: auto;
  }
  .mh-use-grid article {
    min-block-size: 14rem;
  }
}

@media (min-width: 48rem) {
  .mh-steps,
  .mh-use-grid,
  .mh-price-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .mh-price-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .mh-trust {
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  }
  .mh-faq {
    grid-template-columns: minmax(0, 0.75fr) minmax(0, 1.25fr);
  }
}

@media (min-width: 58rem) {
  .mh-hero {
    grid-template-columns: minmax(0, 1.1fr) minmax(23rem, 0.9fr);
    gap: clamp(3rem, 7vw, 6rem);
  }
}
