/*
 * Sqrint — design system.
 *
 * The customer this is built for is standing at a counter in a small town, on
 * a mid-range Android phone, over metered mobile data. Two consequences run
 * through everything below:
 *
 *   No web font. A downloaded typeface costs the customer money and delays the
 *   first paint on exactly the connection this product runs on. The system
 *   stack renders instantly and is what their phone already reads all day.
 *
 *   Touch targets before density. Nothing interactive is under 44px tall.
 *
 * Shops tint this system through --accent, set per shop on the page root. They
 * never supply CSS, so no shop can break the layout — and --accent-ink is
 * computed server-side from the accent's luminance, so a pale accent gets dark
 * text rather than an unreadable button.
 */

:root {
  /* Neutrals carry a slight cool cast, so they sit with the accent rather than
     looking like undecided grey. */
  --paper:        #F5F7F9;
  --surface:      #FFFFFF;
  --sunken:       #EDF1F4;
  --ink:          #10151C;
  --ink-soft:     #55636F;
  --ink-faint:    #8593A0;
  --line:         #DFE5EA;
  --line-strong:  #C3CDD5;

  /* A shop overrides these two, inline on <body>. */
  --accent:       #1D4ED8;
  --accent-ink:   #FFFFFF;

  /* Semantic, and deliberately not the accent — a shop's brand colour must
     never be what tells someone their print failed. */
  --ok:           #15803D;
  --ok-wash:      #EAF6EE;
  --warn:         #92400E;
  --warn-wash:    #FDF4E3;
  --crit:         #B3261E;
  --crit-wash:    #FDECEC;

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;

  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px;
  --s-5: 24px; --s-6: 32px; --s-7: 48px;

  --shadow-1: 0 1px 2px rgba(16, 21, 28, .05);
  --shadow-2: 0 6px 24px -8px rgba(16, 21, 28, .18);

  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --tap: 44px;
}

* { box-sizing: border-box; }

/* A class that sets `display` outranks the browser's own [hidden] rule, which
   is how a "hidden" dialog ends up on screen. Settle it once, here. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

/* Derived from --accent, and declared on `body` rather than `:root` on
   purpose: a custom property is substituted where it is declared, so a wash
   defined on :root would resolve against the *default* accent and stay blue
   however the shop overrides --accent inline on <body>. Declared here, it
   resolves against whatever body actually carries. */
body {
  --accent-wash: color-mix(in srgb, var(--accent) 10%, var(--surface));
  --accent-edge: color-mix(in srgb, var(--accent) 42%, var(--line));
  margin: 0;
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}

main { max-width: 780px; margin: 0 auto; padding: var(--s-5) var(--s-4) var(--s-7); }
main.wide { max-width: 1060px; }

h1, h2, h3 { margin: 0; line-height: 1.2; letter-spacing: -0.015em; text-wrap: balance; }
h1 { font-size: 27px; font-weight: 700; }
h2 { font-size: 19px; font-weight: 650; }
h3 { font-size: 16px; font-weight: 650; }
p { margin: 0 0 var(--s-3); }
a { color: var(--accent); }

.muted { color: var(--ink-soft); }
.small { font-size: 13px; }
.num { font-variant-numeric: tabular-nums; }

:where(a, button, input, select, textarea, label.tile):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* ── Header ─────────────────────────────────────────────────────────────── */
.site-header {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--s-2) var(--s-4);
  padding: var(--s-3) var(--s-4);
  background: var(--surface); border-bottom: 1px solid var(--line);
}
.brand { font-weight: 700; font-size: 16px; text-decoration: none; color: var(--ink); }
.site-header nav { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-2) var(--s-4); }
.site-header nav a { color: var(--ink-soft); text-decoration: none; font-size: 14px; }
.site-header nav a:hover { color: var(--ink); }
/* `.site-header nav a` is more specific than `.btn`, so it was overriding the
   button's own text colour — leaving "Start free trial" in muted grey-blue on
   a blue fill, effectively unreadable. */
.site-header nav a.btn { color: var(--accent-ink); }
/* Which section you are in. An underline rather than a filled pill: the header
   sits above pages a shop tints, and a filled marker would take the accent. */
.site-header nav a.is-current {
  color: var(--ink); font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--ink);
}

/* ── Buttons ────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--tap); padding: 0 var(--s-5);
  border: 1px solid transparent; border-radius: var(--r-md);
  background: var(--accent); color: var(--accent-ink);
  font: inherit; font-weight: 600; text-decoration: none; cursor: pointer;
}
.btn:hover { filter: brightness(1.06); }
.btn:active { transform: translateY(1px); }
.btn:disabled { background: var(--sunken); color: var(--ink-faint); cursor: default;
  filter: none; transform: none; border-color: var(--line); }
.btn-block { width: 100%; }
.btn-quiet { background: var(--surface); color: var(--ink); border-color: var(--line-strong); }
.btn-danger { background: var(--crit); color: #fff; }
.btn-small { min-height: 34px; padding: 0 var(--s-3); font-size: 13px; border-radius: var(--r-sm); }
.btn-link {
  background: none; border: 0; padding: 0; color: var(--ink-soft);
  font: inherit; font-size: 13px; text-decoration: underline; cursor: pointer;
}
.btn-link:hover { color: var(--crit); }

/* ── Surfaces ───────────────────────────────────────────────────────────── */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--s-4); margin-bottom: var(--s-4);
  box-shadow: var(--shadow-1);
}
.card > h2 { margin-bottom: var(--s-3); }
.section-label {
  font-size: 11.5px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-faint); margin: 0 0 var(--s-2);
}

/* ── Forms ──────────────────────────────────────────────────────────────── */
.form { max-width: 420px; }
.form label { display: block; margin-bottom: var(--s-4); font-weight: 600; font-size: 14px; }
.form input[type="text"], .form input[type="email"], .form input[type="password"],
.form input[type="number"], .form input[type="tel"], .form select {
  display: block; width: 100%; margin-top: var(--s-1);
  min-height: var(--tap); padding: 0 var(--s-3);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  font: inherit; background: var(--surface); color: var(--ink);
}
.form label.checkbox, .form label.radio {
  font-weight: 400; display: flex; align-items: flex-start; gap: var(--s-2);
  margin-bottom: var(--s-2); line-height: 1.4;
}
/* A radio or checkbox is a small target inside a big label; keep it aligned
   with the first line of text rather than centred against a wrapped one. */
.form label.checkbox input, .form label.radio input { margin-top: 3px; flex: none; }
/* Section headings inside a form card need air above them; `.card > h2` only
   sets space below, which is right for a heading that opens a card. */
.form h2 { margin-top: var(--s-5); margin-bottom: var(--s-3); }
.form fieldset { margin-bottom: var(--s-5); }
.form fieldset .muted { margin: var(--s-2) 0 0; }
.form > .btn { margin-top: var(--s-2); }
fieldset { border: 1px solid var(--line); border-radius: var(--r-md);
  padding: var(--s-4); margin: 0 0 var(--s-4); }
legend { font-weight: 650; font-size: 14px; padding: 0 var(--s-2); }
.inline-form { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap;
  margin-bottom: var(--s-3); }

/* ── Theme picker ───────────────────────────────────────────────────────── */
/*
 * Shops pick a colour from a palette rather than typing one, because every
 * swatch here has been checked against the text `Shop.ink_for` puts on it. The
 * free field is still there for a shop with its own brand colour — the server
 * validates that one the same way before it reaches a stylesheet.
 */
.theme-grid {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--s-2); margin: 0 0 var(--s-4);
}
.swatch { position: relative; display: block; cursor: pointer; }
/* Visually hidden, not display:none — the radio still has to be focusable and
   reachable by keyboard and screen reader. */
.swatch input {
  position: absolute; width: 1px; height: 1px; opacity: 0;
  margin: 0; pointer-events: none;
}
.swatch-chip {
  display: flex; align-items: center; justify-content: center;
  height: 46px; border-radius: var(--r-sm);
  background: var(--sw); color: var(--sw-ink);
  font-weight: 700; font-size: 14px;
  border: 2px solid transparent;
  box-shadow: inset 0 0 0 1px rgba(16, 21, 28, .08);
  transition: transform .12s ease;
}
.swatch-name {
  display: block; text-align: center; font-size: 11.5px; font-weight: 500;
  color: var(--ink-soft); margin-top: 3px;
}
.swatch:hover .swatch-chip { transform: translateY(-1px); }
/* The ring is the neutral ink, not the swatch's own colour — a border in the
   same hue as the fill is invisible, which is exactly the state that has to
   be obvious. */
.swatch input:checked + .swatch-chip {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px var(--surface) inset, var(--shadow-1);
}
.swatch input:checked ~ .swatch-name { color: var(--ink); font-weight: 650; }
.swatch input:focus-visible + .swatch-chip {
  outline: 2px solid var(--ink); outline-offset: 2px;
}

/* ── Theme preview ──────────────────────────────────────────────────────── */
/* The customer's page in miniature. A row of paint chips does not tell an
   owner what their shop will look like; this does. */
.theme-preview {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--sunken); padding: var(--s-4); margin: 0 0 var(--s-5);
}
.theme-preview .tp-tile, .theme-preview .tp-btn { background-color: var(--surface); }
.theme-preview .tp-btn { background-color: var(--accent); }
.tp-head { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-4); }
.tp-head b { display: block; font-size: 15px; }
.tp-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; border-radius: var(--r-md); flex: none;
  background: var(--accent); color: var(--accent-ink);
  font-weight: 800; font-size: 17px; letter-spacing: -0.02em;
}
.tp-tiles { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2);
  margin-bottom: var(--s-4); }
.tp-tile {
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  padding: var(--s-2) var(--s-3); font-size: 12.5px; color: var(--ink-soft);
  line-height: 1.5;
}
.tp-tile b { color: var(--ink); font-size: 14px; }
.tp-tile.is-on {
  border-color: var(--accent); color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
}
.tp-tile.is-on b { color: var(--accent); }
.tp-btn {
  display: flex; align-items: center; justify-content: center;
  min-height: var(--tap); border-radius: var(--r-md);
  background: var(--accent); color: var(--accent-ink); font-weight: 600;
}

/* ── Notices ────────────────────────────────────────────────────────────── */
.notice { padding: var(--s-3) var(--s-4); border-radius: var(--r-md);
  margin: 0 0 var(--s-4); font-size: 14.5px; }
.notice-bad  { background: var(--crit-wash); color: var(--crit); }
.notice-warn { background: var(--warn-wash); color: var(--warn); }
.notice-ok   { background: var(--ok-wash);  color: var(--ok); }
.field-error { color: var(--crit); font-size: 13px; margin: var(--s-2) 0 0; }

.flashes { list-style: none; padding: 0; margin: 0 0 var(--s-4); }
.flash { padding: var(--s-3) var(--s-4); border-radius: var(--r-md); margin-bottom: var(--s-2); }
.flash-error { background: var(--crit-wash); color: var(--crit); }
.flash-success { background: var(--ok-wash); color: var(--ok); }
.flash-warning { background: var(--warn-wash); color: var(--warn); }

/* ── Badges ─────────────────────────────────────────────────────────────── */
.badge {
  display: inline-block; padding: 3px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 650; background: var(--sunken); color: var(--ink-soft);
  white-space: nowrap;
}
.badge-printed { background: var(--ok-wash); color: var(--ok); }
.badge-failed, .badge-cancelled { background: var(--crit-wash); color: var(--crit); }
.badge-partly_printed, .badge-pending_confirmation { background: var(--warn-wash); color: var(--warn); }
.badge-queued, .badge-printing { background: var(--accent-wash); color: var(--accent); }

/* ── Shop header (customer-facing) ──────────────────────────────────────── */
.shop-head { margin-bottom: var(--s-5); }
.shop-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; border-radius: var(--r-md);
  background: var(--accent); color: var(--accent-ink);
  font-weight: 800; font-size: 19px; letter-spacing: -0.02em; flex: none;
}
.shop-id { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-2); }
.shop-tagline { color: var(--ink-soft); font-size: 14.5px; margin: 0; }
.shop-facts { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-3);
  font-size: 13px; color: var(--ink-soft); margin-top: var(--s-2); }
.shop-facts .dot { color: var(--line-strong); }
.live { display: inline-flex; align-items: center; gap: 6px; color: var(--ok); font-weight: 600; }
.live::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
.live.off { color: var(--ink-faint); }
.live.off::before { background: var(--ink-faint); }

/* ── Order status (customer-facing) ─────────────────────────── */
.order-status {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--s-2) var(--s-3); margin-bottom: var(--s-4);
}
/* Where the customer goes next. An order that is over leaves them on a page
   with nothing to do, so the way back to the shop is a real button by then. */
.status-next { margin-top: var(--s-5); display: grid; gap: var(--s-3);
  justify-items: start; }
.status-next .btn { justify-self: stretch; }
.status-next p { margin: 0; }

/* ── How it works ────────────────────────────────────── */
/*
 * A customer who has just scanned a QR code has never seen this page. The
 * panel teaches the four steps before they touch anything.
 *
 * The drawing is inline SVG driven by CSS keyframes — no image, no video, no
 * library, nothing extra fetched over metered mobile data. It is an aid to the
 * text, never a replacement for it: the global prefers-reduced-motion rule at
 * the foot of this file stops every animation here, and the four steps still
 * read perfectly as a numbered list.
 *
 * One cycle is 10.4s, four scenes of 2.6s. The scenes are offset by negative
 * delays on a single shared keyframe rather than four separate ones, so the
 * timing cannot drift apart when one of them is edited.
 */
.how {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow-1);
  margin-bottom: var(--s-4); padding: 0 var(--s-4);
}
.how > summary {
  display: flex; align-items: center; gap: var(--s-2);
  min-height: var(--tap); padding: var(--s-2) 0;
  cursor: pointer; list-style: none; user-select: none;
}
.how > summary::-webkit-details-marker { display: none; }
.how-title { font-weight: 650; }
.how-hint { margin-left: auto; }
/* The chevron is the only thing that says this panel closes. */
.how > summary::after {
  content: ""; flex: none; width: 8px; height: 8px; margin-left: var(--s-2);
  border-right: 2px solid var(--ink-faint); border-bottom: 2px solid var(--ink-faint);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .18s ease;
}
.how[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }

.how-body {
  display: grid; grid-template-columns: 96px 1fr; gap: var(--s-3);
  align-items: center; padding: var(--s-3) 0;
  border-top: 1px solid var(--line);
}

.how-film { width: 96px; height: auto; flex: none; }
.how-phone  { fill: var(--sunken); stroke: var(--line-strong); stroke-width: 1.5; }
.how-screen { fill: var(--surface); }
.how-notch  { fill: var(--line-strong); }
.how-drop   { fill: var(--accent-wash); stroke: var(--accent-edge);
              stroke-width: 1.5; stroke-dasharray: 5 4; }
.how-doc    { fill: var(--surface); stroke: var(--line-strong); stroke-width: 1.5; }
.how-fold   { fill: var(--line-strong); }
.how-rule   { fill: var(--line-strong); }
.how-label  { fill: var(--sunken); }
.how-tile   { fill: var(--surface); stroke: var(--line-strong); stroke-width: 1.5; }
.how-tile.is-on { fill: var(--accent-wash); stroke: var(--accent); }
.how-total  { fill: var(--accent); }
.how-money  { fill: var(--accent-ink); font: 650 11px var(--font);
              font-variant-numeric: tabular-nums; }
.how-qr-bg  { fill: var(--surface); stroke: var(--line-strong); stroke-width: 1.5; }
.how-qr rect { fill: var(--ink); }
.how-sheet  { fill: var(--surface); stroke: var(--line-strong); stroke-width: 1.5; }
.how-printer { fill: var(--sunken); stroke: var(--line-strong); stroke-width: 1.5; }
.how-slot   { fill: var(--ink-faint); }
.how-led    { fill: var(--ok); }

/* Four scenes on one timeline. Each is opaque for its own quarter. */
@keyframes how-scene {
  0%, 21%  { opacity: 1; }
  25%, 96% { opacity: 0; }
  100%     { opacity: 1; }
}
.how-scene { opacity: 0; animation: how-scene 10.4s linear infinite; }
.how-scene-1 { animation-delay:  0s;    }
.how-scene-2 { animation-delay: -7.8s;  }
.how-scene-3 { animation-delay: -5.2s;  }
.how-scene-4 { animation-delay: -2.6s;  }

/* The document the customer picks, falling into the dropzone. */
@keyframes how-drop {
  0%       { transform: translateY(-26px); }
  11%, 100% { transform: translateY(0); }
}
.how-doc-drop { animation: how-drop 10.4s ease-out infinite; }

/* The printed page coming out of the shop's machine. It starts behind the
   printer body, which is drawn after it, so the slot hides where it begins. */
@keyframes how-emerge {
  0%, 3%    { transform: translateY(36px); }
  20%, 100% { transform: translateY(0); }
}
.how-sheet-out { animation: how-emerge 10.4s ease-out infinite; animation-delay: -2.6s; }

@keyframes how-blink { 0%, 45% { opacity: 1; } 55%, 100% { opacity: .25; } }
.how-led { animation: how-blink 1.3s steps(1, end) infinite; }

/* The step being drawn is the step being read. */
@keyframes how-step {
  0%, 21%  { background: var(--accent-wash); }
  25%, 96% { background: transparent; }
  100%     { background: var(--accent-wash); }
}
.how-steps {
  margin: 0; padding: 0 0 0 var(--s-4); display: grid; gap: 2px;
  font-size: 13px; line-height: 1.4;
}
.how-steps li {
  padding: 5px var(--s-2); border-radius: var(--r-sm);
  animation: how-step 10.4s linear infinite;
}
.how-steps li::marker { color: var(--ink-faint); font-size: 12px; }
.how-steps li b { display: block; font-size: 14px; font-weight: 650; }
/* Qualified with .how-steps on purpose: the `animation` shorthand above sets
   animation-delay too, and at (0,1,1) it outranks a bare .how-step-2 — which
   left every step at time zero and highlighted all four at once. */
.how-steps .how-step-1 { animation-delay:  0s;   }
.how-steps .how-step-2 { animation-delay: -7.8s; }
.how-steps .how-step-3 { animation-delay: -5.2s; }
.how-steps .how-step-4 { animation-delay: -2.6s; }

/* ── Dropzone ───────────────────────────────────────────────────────────── */
.dropzone {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--s-1); min-height: 128px; padding: var(--s-5) var(--s-4);
  text-align: center; cursor: pointer;
  border: 2px dashed var(--line-strong); border-radius: var(--r-md);
  background: var(--sunken);
}
.dropzone:hover, .dropzone.is-over { border-color: var(--accent); background: var(--accent-wash); }
.dropzone-main { font-weight: 650; }
.dropzone-sub { color: var(--ink-soft); font-size: 13px; }

/* ── File cards ─────────────────────────────────────────────────────────── */
.file-list { list-style: none; padding: 0; margin: var(--s-4) 0 0;
  display: grid; gap: var(--s-3); }
.file-row {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface); overflow: hidden;
}
.file-row.is-pending { opacity: .6; }
.file-head {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4); cursor: pointer;
}
.file-name { font-weight: 650; word-break: break-word; flex: 1 1 auto; min-width: 0;
  font-size: 15px; }
.file-sum { color: var(--ink-soft); font-size: 12.5px; display: block; font-weight: 400;
  margin-top: 1px; }
.file-price { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.file-body { padding: 0 var(--s-4) var(--s-4); display: grid; gap: var(--s-4);
  border-top: 1px solid var(--line); padding-top: var(--s-4); }

/* Option tiles carry their own rate, so the price of a choice is visible at
   the moment of choosing rather than in a table further up the page. */
.tiles { display: grid; gap: var(--s-2); grid-template-columns: repeat(2, 1fr); }
.tiles.three { grid-template-columns: repeat(3, 1fr); }
.tile { position: relative; display: block; }
.tile input { position: absolute; opacity: 0; width: 0; height: 0; }
.tile > span {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; min-height: var(--tap); padding: var(--s-2) var(--s-1);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  background: var(--surface); cursor: pointer; text-align: center;
}
.tile b { font-size: 13.5px; font-weight: 650; }
.tile em { font-style: normal; font-size: 11.5px; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; }
/* What the choice feels like in the hand. Both double-sided modes cost the
   same, so the hint is what the customer is actually choosing between. */
.tile i { font-style: normal; font-size: 10.5px; line-height: 1.25;
  color: var(--ink-faint); text-wrap: balance; }
.tile input:checked + span i { color: var(--ink-soft); }
.tile input:checked + span {
  border-color: var(--accent); background: var(--accent-wash);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.tile input:checked + span em { color: var(--accent); }
.tile input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

.opt-line { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; }
.opt-line > * { flex: 0 0 auto; }
.opt-field { display: flex; align-items: center; gap: var(--s-2);
  font-size: 13px; color: var(--ink-soft); }
.opt-field input, .opt-field select {
  min-height: 38px; padding: 0 var(--s-2); border: 1px solid var(--line-strong);
  border-radius: var(--r-sm); font: inherit; font-size: 14px;
  background: var(--surface); color: var(--ink);
}
.opt-field input[type="number"] { width: 64px; text-align: center; }
.opt-field .range { width: 120px; }
.opt-grow { flex: 1 1 auto; }

/* ── Total ──────────────────────────────────────────────────────────────── */
.total-card { position: sticky; bottom: var(--s-3); box-shadow: var(--shadow-2); }
.total-row { display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--s-3); }
.total-label { color: var(--ink-soft); font-size: 14px; }
.total-value { font-size: 30px; font-weight: 800; font-variant-numeric: tabular-nums; }
.pay-note { font-size: 12.5px; color: var(--ink-soft); text-align: center;
  margin: var(--s-2) 0 0; }

.is-busy { cursor: progress; }
.is-busy .file-body, .is-busy .dropzone { opacity: .5; pointer-events: none; }

/* ── Payment sheet ──────────────────────────────────────────────────────── */
.pay-sheet {
  position: fixed; inset: 0; z-index: 50;
  background: rgba(16, 21, 28, .55);
  display: flex; align-items: flex-end; justify-content: center;
}
.pay-panel {
  background: var(--surface); border-radius: var(--r-lg) var(--r-lg) 0 0;
  padding: var(--s-5) var(--s-4) var(--s-6);
  width: 100%; max-width: 440px; text-align: center;
  max-height: 92vh; overflow-y: auto;
}
.pay-panel h2 { margin-bottom: var(--s-4); font-size: 21px; }
.upi-qr { width: 216px; height: 216px; display: block; margin: 0 auto var(--s-3);
  border: 1px solid var(--line); border-radius: var(--r-md); background: #fff;
  padding: var(--s-2); }
.upi-vpa { font-size: 14px; margin-bottom: var(--s-4); }
.upi-vpa code { background: var(--sunken); padding: 2px 8px; border-radius: 5px;
  font-size: 13px; }
.upi-help { font-size: 13px; color: var(--ink-soft); margin: var(--s-3) 0 var(--s-4); }
.pay-panel .btn { margin-bottom: var(--s-2); }

/* ── Tables (dashboard, admin) ──────────────────────────────────────────── */
.t-wrap { overflow-x: auto; margin-bottom: var(--s-4); }
table { border-collapse: collapse; width: 100%; background: var(--surface); font-size: 14.5px; }
th, td { padding: var(--s-3); text-align: left; border-bottom: 1px solid var(--line);
  vertical-align: top; }
th { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-soft); }
.rates { max-width: 460px; border: 1px solid var(--line); border-radius: var(--r-md); }
.file-line { font-size: 13.5px; margin-bottom: 2px; }

/* ── Stats (dashboard, admin) ───────────────────────────────────────────── */
.stat-row { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-bottom: var(--s-5); }
.stat { flex: 1 1 150px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: var(--s-4); }
.stat-value { display: block; font-size: 23px; font-weight: 700; line-height: 1.15; }
.stat-value.ok { color: var(--ok); }
.stat-value.bad { color: var(--crit); }
.stat-label { color: var(--ink-soft); font-size: 12.5px; }

.status { font-weight: 600; }
.status-printed { color: var(--ok); }
.status-failed, .status-past_due, .status-cancelled { color: var(--crit); }
.status-partly_printed, .status-pending_confirmation { color: var(--warn); }
.ok { color: var(--ok); } .bad { color: var(--crit); }

/* ── Marketing ──────────────────────────────────────── */
/*
 * The landing page reads to a shop owner, not to a customer mid-order, so it
 * gets more room and more contrast than the app does. It still spends the same
 * tokens: no second palette, no web font, no stock photography. What makes it
 * feel different is scale and full-width bands, which cost nothing to load.
 */
:root {
  --ground:      #0C1220;   /* the dark bands */
  --ground-ink:  #EEF2F7;
  --ground-soft: #9FB0C4;
  --ground-line: #24304A;
}

/* The app sits in a reading column; the landing page breaks out of it. */
main.bleed { max-width: none; padding: 0; }

.band { padding: var(--s-7) var(--s-4); }
.band-inner { max-width: 1080px; margin: 0 auto; }
.band-quiet { background: var(--sunken); }
.band-dark  { background: var(--ground); color: var(--ground-ink); }
.band-dark h2 { color: var(--ground-ink); }
.band-dark a:not(.btn) { color: #9FC0FF; }

.eyebrow {
  text-transform: uppercase; letter-spacing: .1em;
  font-size: 12px; font-weight: 700; color: var(--accent); margin-bottom: var(--s-2);
}
.eyebrow-on-dark { color: #7FA8FF; }
.lede-on-dark { color: var(--ground-soft); font-size: 16.5px; max-width: 52ch; }

.band-title { font-size: 27px; margin-bottom: var(--s-2); }
.band-sub { color: var(--ink-soft); font-size: 16px; max-width: 56ch;
  margin-bottom: var(--s-5); }
.band-foot { margin: var(--s-5) 0 0; font-size: 13px; }

.btn-lg { min-height: 52px; padding: 0 var(--s-5); font-size: 16px;
  border-radius: var(--r-md); }

/* Hero */
.band-hero { padding-top: var(--s-6); }
.hero-grid { display: grid; gap: var(--s-6); align-items: center; }
.hero-title {
  font-size: clamp(30px, 7.5vw, 50px); font-weight: 750;
  letter-spacing: -0.03em; line-height: 1.08; margin-bottom: var(--s-4);
}
.hero-lede { font-size: 17.5px; color: var(--ink-soft); max-width: 46ch;
  margin-bottom: var(--s-5); }
.hero-lede strong { color: var(--ink); }
.hero-cta { display: flex; flex-wrap: wrap; gap: var(--s-3); }
.hero-cta .btn { flex: 1 1 auto; }
.hero-micro { margin: var(--s-3) 0 0; font-size: 13px; color: var(--ink-faint); }
.hero-figure { margin: 0; }
.hero-figure svg { width: 100%; height: auto; color: var(--ink-faint); }

/* The hero diagram gets its own palette hooks, so the drawing stays legible
   without borrowing colours that mean something else in the app. */
.d-frame   { fill: var(--sunken); stroke: var(--line-strong); stroke-width: 1.5; }
.d-screen  { fill: var(--surface); }
.d-mark    { fill: var(--accent); }
.d-line    { fill: var(--line-strong); }
.d-faint   { fill: var(--line); }
.d-row     { fill: var(--surface); stroke: var(--line); stroke-width: 1.2; }
.d-cta     { fill: var(--accent); }
.d-queue   { fill: var(--accent); }
.d-sheet   { fill: var(--surface); stroke: var(--line-strong); stroke-width: 1.5; }
.d-printer { fill: var(--sunken); stroke: var(--line-strong); stroke-width: 1.5; }
.d-slot    { fill: var(--ink-faint); }
.d-led     { fill: var(--ok); }
.d-arrow   { fill: none; stroke: var(--ink-faint); stroke-width: 1.5; }
.d-money   { fill: none; stroke: var(--ok); stroke-width: 1.5; stroke-dasharray: 4 3; }
.d-ok-fill { fill: var(--ok); }
.d-hair    { fill: var(--line); }
.d-doc     { fill: var(--line-strong); }
/* The pay button is green, not the accent: it is the first step of the money
   path, and the eye should follow it to the bank card at the other end. */
.d-pay     { fill: var(--ok); }
.d-ui      { fill: #fff; font: 650 7.5px var(--font); }
.d-printer-top { fill: var(--line-strong); }
.d-panel   { fill: var(--ink-faint); opacity: .5; }
.d-tray    { fill: var(--sunken); stroke: var(--line-strong); stroke-width: 1.5; }
.d-card    { fill: var(--surface); stroke: var(--ok); stroke-width: 1.5; }
.d-rupee-bg { fill: var(--ok-wash); }
.d-rupee   { fill: var(--ok); font: 700 11px var(--font); }
.d-card-line { fill: var(--line-strong); }
.d-card-faint { fill: var(--line); }
.d-cap     { fill: var(--ink-soft); font: 600 9px var(--font); }
.d-edge    { fill: var(--ink-soft); font: 500 8.5px var(--font); }
.d-faint-t { fill: var(--ink-faint); }
.d-ok-t    { fill: var(--ok); font-weight: 600; }

/* The hero flow, on one 7s timeline.
 *
 * The order and the payment leave the customer together and never meet again:
 * the grey packet goes through the shop's PC to the printer, the green line
 * goes straight to the bank. That separation is the page's whole argument, so
 * the diagram performs it rather than asserting it.
 *
 * Everything that moves rests where the still diagram had it. The packet, the
 * tap ripple, the drawn payment line and the card flash are all invisible at
 * rest; the printed sheet rests in its finished position. app.css stops every
 * animation under prefers-reduced-motion, and what remains is the static
 * picture exactly as it was.
 */
.hero-figure svg { --flow: 7s; }

/* The customer taps Pay. */
@keyframes hf-tap {
  0%       { opacity: .55; transform: scale(.35); }
  9%       { opacity: 0;   transform: scale(1.5); }
  100%     { opacity: 0;   transform: scale(1.5); }
}
.hf-tap {
  fill: none; stroke: var(--ok); stroke-width: 2; opacity: 0;
  transform-origin: 48px 128px;
  animation: hf-tap var(--flow) ease-out infinite;
}

/* The order crosses the internet to the PC on the counter. */
@keyframes hf-packet {
  0%, 5%    { opacity: 0; transform: translateX(0); }
  8%        { opacity: 1; }
  /* Fades over the last stretch rather than stopping dead on the arrowhead,
     which read as a blob sitting on the tip. */
  19%       { opacity: 1; }
  24%, 100% { opacity: 0; transform: translateX(68px); }
}
.hf-packet {
  fill: var(--accent); opacity: 0;
  animation: hf-packet var(--flow) cubic-bezier(.4, 0, .3, 1) infinite;
}

/* It lands in the queue. */
@keyframes hf-job {
  0%, 25%   { fill: var(--surface); stroke: var(--line); }
  30%, 52%  { fill: var(--accent-wash); stroke: var(--accent); }
  60%, 100% { fill: var(--surface); stroke: var(--line); }
}
.hf-job { animation: hf-job var(--flow) linear infinite; }

@keyframes hf-job-line {
  0%, 25%   { fill: var(--line); }
  30%, 52%  { fill: var(--accent); }
  60%, 100% { fill: var(--line); }
}
.hf-job-line { animation: hf-job-line var(--flow) linear infinite; }

/* The page comes out of the shop's own printer. */
@keyframes hf-sheet {
  0%, 46%   { transform: translateY(38px); }
  62%, 100% { transform: translateY(0); }
}
.hf-sheet { animation: hf-sheet var(--flow) cubic-bezier(.25, .9, .3, 1) infinite; }

@keyframes hf-led {
  0%, 44%   { opacity: .3; }
  48%, 50%  { opacity: 1; }
  54%, 56%  { opacity: .3; }
  60%       { opacity: 1; }
  70%, 100% { opacity: 1; }
}
.hf-led { animation: hf-led var(--flow) steps(1, end) infinite; }

/* The money, meanwhile, goes nowhere near any of that. `pathLength="100"` in
   the markup normalises the curve, so this dash maths is independent of its
   real length. */
@keyframes hf-money {
  0%, 10%   { stroke-dashoffset: 100; opacity: 1; }
  40%       { stroke-dashoffset: 0;   opacity: 1; }
  74%       { stroke-dashoffset: 0;   opacity: 1; }
  82%, 100% { stroke-dashoffset: 0;   opacity: 0; }
}
.hf-money-draw {
  fill: none; stroke: var(--ok); stroke-width: 2.5; stroke-linecap: round;
  stroke-dasharray: 100; stroke-dashoffset: 100;
  animation: hf-money var(--flow) ease-in-out infinite;
}

/* It arrives. */
@keyframes hf-card {
  0%, 38%   { opacity: 0; }
  44%       { opacity: .5; }
  58%, 100% { opacity: 0; }
}
.hf-card-flash {
  fill: var(--ok-wash); opacity: 0;
  animation: hf-card var(--flow) ease-out infinite;
}

/* Assurances */
.assurances { list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--s-4); }
.assurances li { display: grid; gap: 2px; padding-left: var(--s-5); position: relative; }
.assurances li::before {
  content: ""; position: absolute; left: 0; top: 5px;
  width: 14px; height: 8px; border-left: 2.5px solid var(--ok);
  border-bottom: 2.5px solid var(--ok); transform: rotate(-45deg);
}
.assurances b { font-size: 15px; }
.assurances span { color: var(--ink-soft); font-size: 14px; }

/* The money band */
.money-band { display: grid; gap: var(--s-6); align-items: center; }
.money-band h2 { font-size: clamp(24px, 5vw, 34px); letter-spacing: -0.02em;
  margin-bottom: var(--s-4); line-height: 1.15; }
.money-contrast { display: grid; gap: var(--s-3); }
.money-row { padding: var(--s-4); border-radius: var(--r-md);
  border: 1px solid var(--ground-line); display: grid; gap: 4px; }
.money-label { font-size: 12px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ground-soft); }
.money-flow { font-size: 18px; font-weight: 650; }
.money-row-bad .money-flow { color: #F2A5A0; }
.money-row-bad s { opacity: .8; }
.money-row-good { border-color: var(--ok); background: rgba(21, 128, 61, .16); }
.money-row-good .money-flow { color: #7BE0A0; }
.money-note { color: var(--ground-soft); font-size: 13.5px; margin: var(--s-1) 0 0; }

/* Steps */
.steps-grid { list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--s-4); }
/* `align-content: start` matters here: the cards stretch to the tallest one in
   the row, and without it the grid rows stretch too — so a short card's title
   drifted down the card and nothing lined up across the four. */
.steps-grid li { display: grid; align-content: start; gap: 3px; padding: var(--s-4);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); }
.step-n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--accent); color: var(--accent-ink);
  font-size: 14px; font-weight: 700; margin-bottom: var(--s-1);
}
.steps-grid b { font-size: 15.5px; }
.steps-grid .muted { font-size: 14px; }

/* Features */
.feature-grid { display: grid; gap: var(--s-4); }
.feature-grid article { background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: var(--s-4); }
.feature-grid b { display: block; font-size: 15.5px; margin-bottom: var(--s-1); }
.feature-grid p { margin: 0; font-size: 14px; }

/* Requirements */
.need-band { display: grid; gap: var(--s-5); }
.need-list { margin: 0; padding-left: var(--s-5); display: grid; gap: var(--s-3);
  color: var(--ink-soft); font-size: 15px; }
.need-list b { color: var(--ink); }

/* Plans */
.plan-grid { display: grid; gap: var(--s-4);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.plan-card { background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--s-5); position: relative;
  display: grid; align-content: start; gap: var(--s-2); }
.plan-card-lead { border-color: var(--accent); box-shadow: var(--shadow-2); }
.plan-flag { position: absolute; top: -11px; left: var(--s-5);
  background: var(--accent); color: var(--accent-ink);
  font-size: 11.5px; font-weight: 650; padding: 3px 10px; border-radius: 999px; }
.price { font-size: 34px; font-weight: 750; margin: 0; letter-spacing: -0.02em; }
.price span { font-size: 14px; font-weight: 400; color: var(--ink-soft);
  letter-spacing: 0; }
.plan-limit { margin: 0; color: var(--ink-soft); font-size: 14px; }
.plan-points { list-style: none; margin: var(--s-2) 0 var(--s-3); padding: 0;
  display: grid; gap: var(--s-2); font-size: 14px; color: var(--ink-soft); }
.plan-points li { padding-left: var(--s-4); position: relative; }
.plan-points li::before { content: ""; position: absolute; left: 0; top: 6px;
  width: 10px; height: 6px; border-left: 2px solid var(--ok);
  border-bottom: 2px solid var(--ok); transform: rotate(-45deg); }

/* FAQ */
.faq { display: grid; gap: var(--s-2); max-width: 720px; }
.faq details { background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 0 var(--s-4); }
.faq summary { display: flex; align-items: center; gap: var(--s-3);
  min-height: var(--tap); padding: var(--s-2) 0; cursor: pointer;
  font-weight: 650; font-size: 15px; list-style: none; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: ""; flex: none; margin-left: auto; width: 8px; height: 8px;
  border-right: 2px solid var(--ink-faint); border-bottom: 2px solid var(--ink-faint);
  transform: rotate(45deg) translate(-2px, -2px); transition: transform .18s ease;
}
.faq details[open] summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.faq details p { margin: 0 0 var(--s-4); color: var(--ink-soft); font-size: 14.5px;
  max-width: 60ch; }

/* Close */
.band-close { text-align: center; }
.band-close h2 { font-size: clamp(24px, 5.5vw, 36px); letter-spacing: -0.02em;
  margin-bottom: var(--s-3); }
.band-close .lede-on-dark { margin: 0 auto var(--s-5); }
.close-contact { margin: var(--s-5) 0 0; color: var(--ground-soft); font-size: 14px; }


code { background: var(--sunken); padding: 2px 6px; border-radius: 5px; font-size: 13px; }
.site-footer { border-top: 1px solid var(--line); padding: var(--s-5);
  text-align: center; color: var(--ink-faint); font-size: 13px; }
.todo { color: var(--ink-faint); font-style: italic; }

@media (min-width: 640px) {
  .how-body { grid-template-columns: 120px 1fr; gap: var(--s-5); }
  .how-film { width: 120px; }
  .how-steps { font-size: 13.5px; }

  .band { padding: var(--s-7) var(--s-5); }
  .assurances { grid-template-columns: repeat(3, 1fr); gap: var(--s-5); }
  .steps-grid { grid-template-columns: repeat(2, 1fr); }
  .feature-grid { grid-template-columns: repeat(2, 1fr); }
  .hero-cta .btn { flex: 0 0 auto; }

  .pay-sheet { align-items: center; }
  .pay-panel { border-radius: var(--r-lg); }
  .total-card { position: static; box-shadow: var(--shadow-1); }
  h1 { font-size: 31px; }
}

@media (min-width: 940px) {
  .hero-grid { grid-template-columns: 1.05fr .95fr; gap: var(--s-7); }
  .money-band { grid-template-columns: 1.1fr .9fr; gap: var(--s-7); }
  .need-band { grid-template-columns: .9fr 1.1fr; gap: var(--s-7); }
  .steps-grid { grid-template-columns: repeat(4, 1fr); }
  .feature-grid { grid-template-columns: repeat(3, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
  .btn:active { transform: none; }

  /* The scenes are stacked and hidden by default, revealed in turn by the
     animation. Kill the animation and every one of them stays at opacity 0 —
     an empty phone. Show the first scene instead: the four steps beside it
     were always the thing carrying the meaning. */
  .how-scene-1 { opacity: 1; }
  .how-doc-drop, .how-sheet-out { transform: none; }

  /* The hero's moving parts are decorative and rest invisible, except the
     printed sheet, which rests where the animation would have left it. */
  .hf-sheet { transform: none; }
  .hf-led { opacity: 1; }
}

/* Printer chooser on the settings page. One row per printer the shop's PC
   reported, so the hit target is the whole row and not just the radio. */
.printer-list {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.printer-list li {
  border: 1px solid var(--line, #e2e8f0);
  border-radius: 6px;
}

.printer-list label {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin: 0;
  padding: 0.6rem 0.75rem;
  cursor: pointer;
}

.printer-list li:has(input:checked) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}

/* The pairing code is read off a screen and typed by hand, sometimes from a
   phone photo of a monitor. Big, monospaced, generously spaced. */
.pairing-code {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 2rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  margin: 0.25rem 0;
  color: var(--accent);
}

.agent-advanced { margin-top: 1.25rem; }
.agent-advanced summary { cursor: pointer; color: var(--muted, #64748b); }
.agent-advanced code { word-break: break-all; }

/* Agent state on the settings page. The dot carries the same information as
   the word beside it, for anyone who does not distinguish the two colours. */
.agent-status {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  margin: 0 0 0.35rem;
}

.agent-dot {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  flex: none;
}

.agent-status.is-online { color: #15803d; }
.agent-status.is-online .agent-dot { background: #16a34a; }
.agent-status.is-offline { color: #b45309; }
.agent-status.is-offline .agent-dot { background: #d97706; }

.agent-danger { margin-top: 1.25rem; }
.agent-danger summary { cursor: pointer; color: #b91c1c; font-weight: 600; }
.agent-danger[open] summary { margin-bottom: 0.5rem; }

/* ── The customer's progress track ────────────────────────────────────────────
   Four steps, one lit. It replaced a paragraph: someone standing at a counter
   wants to know where their job is, and this answers that faster than a
   sentence can be read.

   Everything below rests in a complete state. The animations add life to a
   picture that is already correct without them, which is what makes the
   reduced-motion block at the end a matter of switching them off rather than
   of drawing something different. */

.track {
  --track-line: color-mix(in srgb, var(--accent) 18%, transparent);
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  margin: var(--s-5) 0;
  padding: 0;
  position: relative;
}

/* The rail, and the part of it already travelled. Drawn behind the dots and
   inset by half a column at each end so it spans dot centres, not the box. */
.track::before,
.track::after {
  content: "";
  position: absolute;
  top: 13px;
  left: 12.5%;
  right: 12.5%;
  height: 3px;
  border-radius: 2px;
}

.track::before { background: var(--track-line); }

.track::after {
  background: var(--accent);
  transform-origin: left center;
  transform: scaleX(var(--progress, 0));
  transition: transform 700ms cubic-bezier(.4, 0, .2, 1);
}

/* Three gaps between four dots, so each completed step is a third of the rail. */
.track[data-stage="0"] { --progress: 0; }
.track[data-stage="1"] { --progress: .3333; }
.track[data-stage="2"] { --progress: .6667; }
.track[data-stage="3"] { --progress: 1; }
/* One past the last step: finished, nothing in progress. */
.track[data-stage="4"] { --progress: 1; }

.track-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 6px;
  position: relative;
  padding: 0 2px;
}

.track-dot {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--bg, #fff);
  border: 3px solid var(--track-line);
  box-sizing: border-box;
  position: relative;
  z-index: 1;
  transition: border-color 300ms ease, background 300ms ease;
}

.track-tick {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: var(--accent-ink);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* pathLength="100" on the path means these are percentages of its length,
     whatever the real geometry is. */
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
}

.track-label {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--muted, #64748b);
  transition: color 300ms ease;
}

/* The one-line explanation belongs to the step in progress. Showing all four
   at once is four sentences again, which is the thing this replaced. */
/* Taken out of flow, so three invisible captions do not reserve two lines of
   dead space under a finished track — and so appearing does not shove the rest
   of the page down. The room for the one that shows is reserved on the list. */
.track-sub {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 4px;
  font-size: 0.72rem;
  line-height: 1.3;
  color: var(--muted, #64748b);
  opacity: 0;
  transition: opacity 300ms ease;
}

.track:has(.track-step[data-state="now"]) { padding-bottom: 2.6rem; }

/* ── Done ─────────────────────────────────────────────────────────────────── */

.track-step[data-state="done"] .track-dot {
  background: var(--accent);
  border-color: var(--accent);
}

.track-step[data-state="done"] .track-tick {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 400ms ease 200ms;
}

.track-step[data-state="done"] .track-label { color: var(--accent); }

/* ── In progress ──────────────────────────────────────────────────────────── */

.track-step[data-state="now"] .track-dot {
  border-color: var(--accent);
  background: var(--bg, #fff);
}

/* A filled core, so the current step reads as active rather than merely
   outlined — and is distinguishable from a completed one without colour. */
.track-step[data-state="now"] .track-dot::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--accent);
  animation: track-beat 1.8s ease-in-out infinite;
}

/* The halo. Sized from the dot rather than given its own dimensions, so it
   stays centred if the dot ever changes. */
.track-step[data-state="now"] .track-dot::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 3px solid var(--accent);
  animation: track-halo 1.8s ease-out infinite;
}

.track-step[data-state="now"] .track-label {
  color: var(--accent);
  font-weight: 700;
}

.track-step[data-state="now"] .track-sub { opacity: 1; }

@keyframes track-beat {
  0%, 100% { transform: scale(1);   opacity: 1; }
  50%      { transform: scale(.72); opacity: .75; }
}

@keyframes track-halo {
  0%       { transform: scale(1);   opacity: .55; }
  70%, 100% { transform: scale(1.9); opacity: 0; }
}

@media (max-width: 400px) {
  .track-label { font-size: 0.74rem; }
  .track-sub { font-size: 0.68rem; max-width: 10ch; }
}

/* Reduced motion: the track is already correct standing still. Switch the
   movement off and let the current step stay filled, rather than leaving a
   pulsing core parked mid-keyframe at whatever scale it started on. */
@media (prefers-reduced-motion: reduce) {
  .track::after { transition: none; }
  .track-step[data-state="now"] .track-dot::before {
    animation: none;
    transform: none;
    opacity: 1;
  }
  .track-step[data-state="now"] .track-dot::after { display: none; }
  .track-step[data-state="done"] .track-tick { transition: none; }
}

/* ── The shop's own pages, with some life in them ─────────────────────────────
   These two pages are the only ones a customer ever sees, usually on a phone,
   usually standing in the shop. The motion here is arrival and feedback — it
   tells you the page is yours and that a tap registered. Nothing loops except
   the one thing that is genuinely still happening. */

/* A wash of the shop's own colour behind the header. Cheap, and it makes the
   page feel like the shop rather than like a form. */
.order-page > .shop-head,
.order-status-page > .shop-head {
  position: relative;
  padding: var(--s-4) var(--s-4) var(--s-4);
  margin-bottom: var(--s-5);
  border-radius: 14px;
  background:
    radial-gradient(120% 140% at 0% 0%,
      color-mix(in srgb, var(--accent) 13%, transparent) 0%,
      transparent 62%),
    color-mix(in srgb, var(--accent) 4%, transparent);
  overflow: hidden;
}

.shop-mark {
  transition: transform 400ms cubic-bezier(.2, .8, .3, 1.4);
}

.order-page .shop-head:hover .shop-mark,
.order-status-page .shop-head:hover .shop-mark {
  transform: rotate(-4deg) scale(1.06);
}

/* "Printer online" should look alive, because that is the fact it reports. */
.live { position: relative; padding-left: 14px; }

.live::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -3.5px;
  border-radius: 50%;
  background: var(--ok, #16a34a);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok, #16a34a) 60%, transparent);
  animation: live-ping 2.4s ease-out infinite;
}

.live.off::before {
  background: var(--muted, #94a3b8);
  animation: none;
  box-shadow: none;
}

@keyframes live-ping {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok, #16a34a) 55%, transparent); }
  70%, 100% { box-shadow: 0 0 0 7px transparent; }
}

/* Arrival. Each block comes up a beat after the one above it, so the page
   assembles rather than appearing all at once — and it rests visible, so a
   screenshot or a stalled script still shows a finished page. */
@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

.order-page > *,
.order-status-page > * {
  animation: rise 460ms cubic-bezier(.2, .7, .3, 1) both;
}

.order-page > *:nth-child(1),  .order-status-page > *:nth-child(1) { animation-delay: 0ms; }
.order-page > *:nth-child(2),  .order-status-page > *:nth-child(2) { animation-delay: 60ms; }
.order-page > *:nth-child(3),  .order-status-page > *:nth-child(3) { animation-delay: 120ms; }
.order-page > *:nth-child(4),  .order-status-page > *:nth-child(4) { animation-delay: 180ms; }
.order-page > *:nth-child(n+5), .order-status-page > *:nth-child(n+5) { animation-delay: 240ms; }

/* A tap should feel like it landed. */
.order-page .btn,
.order-status-page .btn {
  transition: transform 140ms ease, box-shadow 220ms ease, background 200ms ease;
}

.order-page .btn:hover,
.order-status-page .btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 18px color-mix(in srgb, var(--accent) 22%, transparent);
}

.order-page .btn:active,
.order-status-page .btn:active { transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  /* Everything above rests in its finished state, so switching the motion off
     leaves the page correct rather than empty. */
  .order-page > *,
  .order-status-page > * { animation: none; }
  .live::before { animation: none; box-shadow: none; }
  .shop-mark,
  .order-page .btn,
  .order-status-page .btn { transition: none; }
  .order-page .btn:hover,
  .order-status-page .btn:hover { transform: none; }
}

/* Sits under the track, not beside it — the track already answers "where is
   my job", and this answers the question it raises: "do I have to wait here?" */
.track-note {
  text-align: center;
  margin: 0 0 var(--s-4);
}

/* ── The shop owner's console ─────────────────────────────────────────────────
   A dashboard is scanned and operated, not read, so the craft here is
   information design rather than typography: what needs attention should be
   obvious from across a counter, and the record underneath should stay quiet.

   Every class is prefixed. The first attempt reused `.tile` and `.todo`, both
   of which already exist — `.tile` is the radio card on the order form and
   `.todo` is an italic faint note — and the page came out with boxes inside
   boxes and a heading in italics. In a single stylesheet this size, a generic
   name is a collision waiting for the next person. */

main.wide { max-width: 1080px; }

/* ── Head ─────────────────────────────────────────────────────────────────── */

.dash-head {
  padding: var(--s-5);
  margin-bottom: var(--s-5);
  border-radius: 16px;
  border: 1px solid var(--line);
  background:
    radial-gradient(110% 160% at 0% 0%,
      color-mix(in srgb, var(--accent) 14%, transparent) 0%, transparent 60%),
    var(--surface);
}

.dash-head h1 { margin: 0 0 var(--s-3); }

.dash-link {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin: 0;
  font-size: 14px;
}

.dash-link code {
  background: var(--sunken);
  padding: 4px var(--s-2);
  border-radius: var(--r-sm);
  word-break: break-all;
}

.btn-small.is-copied {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
}

/* ── What needs doing ─────────────────────────────────────────────────────── */

/* The only instruction on the page, so the only block that raises its voice:
   a left edge in the shop's colour and a warmer ground than anything below. */
.dash-todo {
  border: 1px solid var(--accent-edge);
  border-left: 4px solid var(--accent);
  border-radius: 14px;
  padding: var(--s-4) var(--s-5);
  margin-bottom: var(--s-5);
  background: color-mix(in srgb, var(--accent) 5%, var(--surface));
}

.dash-todo-title { margin: 0; font-size: 18px; font-style: normal; }
.dash-todo-sub { margin: var(--s-1) 0 var(--s-4); font-size: 14px; }

.dash-todo-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }

.dash-todo-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  padding: var(--s-3);
  border-radius: var(--r-sm);
  background: var(--surface);
  border: 1px solid var(--line);
}

.dash-todo-what {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--s-2) var(--s-3); flex: 1 1 240px;
}

.dash-todo-files { font-size: 14px; }
.dash-todo-amount { font-weight: 700; font-size: 17px; }

.dash-id {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 13px;
  background: var(--sunken);
  padding: 3px var(--s-2);
  border-radius: var(--r-sm);
}

/* ── The shift, in tiles ──────────────────────────────────────────────────── */

.dash-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--s-3);
  margin-bottom: var(--s-6);
}

.dash-stat {
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: 2px;
  box-shadow: var(--shadow-1);
}

/* Only the tile with work in it is tinted. Colouring all five would say
   everything is urgent, which is the same as saying nothing is. */
.dash-stat.is-busy {
  border-color: var(--accent-edge);
  background: color-mix(in srgb, var(--accent) 6%, var(--surface));
}

.dash-stat-value {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.01em;
}

.dash-stat-label { font-size: 13px; color: var(--ink-soft); }
.dash-stat-label a { color: var(--ink-soft); }

.dash-state { display: flex; align-items: center; gap: var(--s-2); font-size: 20px; }
.dash-state.is-online { color: var(--ok); }
.dash-state.is-offline { color: var(--ink-faint); }

/* The dot repeats the word beside it rather than replacing it, so the state
   never depends on telling green from grey. */
.dash-dot { width: 9px; height: 9px; border-radius: 50%; background: currentColor; flex: none; }
.dash-state.is-online .dash-dot { animation: live-ping 2.4s ease-out infinite; }

/* ── The record ───────────────────────────────────────────────────────────── */

.dash-section { font-size: 18px; margin: 0 0 var(--s-3); }

.dash-orders { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }

.dash-order {
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  padding: var(--s-3) var(--s-4);
  transition: border-color 160ms ease, box-shadow 160ms ease;
}

.dash-order:hover { border-color: var(--line-strong); box-shadow: var(--shadow-1); }

.dash-order-head {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-2) var(--s-3);
}

/* Pushes the money to the right on a wide row and lets it fall into line on a
   narrow one, without a media query. */
.dash-amount { margin-left: auto; font-weight: 700; }

.dash-files { list-style: none; margin: var(--s-2) 0 0; padding: 0; display: grid; gap: 2px; }

.dash-files li {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: var(--s-2); font-size: 14px;
}

/* The shared .file-name is flex: 1 1 auto, which on this row would shove the
   settings to the far edge and leave a lake of white between them. */
.dash-files .file-name { flex: 0 0 auto; }

/* ── Nothing here yet ─────────────────────────────────────────────────────── */

.dash-empty {
  border: 1px dashed var(--line-strong);
  border-radius: 14px;
  padding: var(--s-6) var(--s-5);
  text-align: center;
  background: var(--surface);
}

.dash-empty-title { font-weight: 700; margin-bottom: var(--s-2); }
.dash-empty p { max-width: 46ch; margin-inline: auto; }

@media (prefers-reduced-motion: reduce) {
  .dash-state.is-online .dash-dot { animation: none; }
  .dash-order { transition: none; }
}

/* ── The counter poster ───────────────────────────────────────────────────────
   A sheet a shop prints once and tapes where people queue. It is read from a
   metre away by someone holding a phone, so the hierarchy is: what this is,
   the code, and how to be sure you scanned the right thing.

   Sized in mm. This is the one page in the product whose real output is paper,
   and px on paper is a guess about someone's printer. */

.poster-bar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-4);
  margin-bottom: var(--s-4);
}

.poster-bar h1 { margin-bottom: var(--s-2); }
.poster-bar p { max-width: 52ch; margin: 0; }
.poster-actions { display: flex; gap: var(--s-2); flex: none; }

.poster {
  width: 148mm;              /* A5 */
  max-width: 100%;
  margin: var(--s-4) auto var(--s-7);
  padding: 14mm 12mm;
  box-sizing: border-box;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: #fff;
  color: #10151c;
  text-align: center;
  box-shadow: var(--shadow-2);
}

.poster-head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4mm;
  text-align: left;
  margin-bottom: 8mm;
}

.poster-mark {
  width: 14mm;
  height: 14mm;
  flex: none;
  border-radius: 3mm;
  display: grid;
  place-items: center;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 700;
  font-size: 6mm;
  letter-spacing: 0.02em;
}

.poster-shop { margin: 0; font-size: 6mm; line-height: 1.15; }
.poster-tagline { margin: 1mm 0 0; font-size: 3.4mm; color: #55636f; }

.poster-lead {
  margin: 0 0 6mm;
  font-size: 8mm;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--accent);
}

/* The code itself, and the only thing on the sheet that must survive a bad
   photocopy: pure black on pure white, no tint, nothing behind it. */
.poster-qr {
  width: 74mm;
  height: 74mm;
  margin: 0 auto;
  padding: 3mm;
  box-sizing: border-box;
  background: #fff;
  border: 0.4mm solid var(--accent);
  border-radius: 3mm;
  display: grid;
  place-items: center;
}

.poster-qr svg { width: 100%; height: 100%; display: block; }
.poster-qr svg path { fill: #000; }
.poster-noqr { font-size: 3.4mm; color: #b91c1c; margin: 0; }

.poster-scan { margin: 4mm 0 1mm; font-size: 4mm; font-weight: 600; }

.poster-link {
  margin: 0 0 7mm;
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 3.6mm;
  color: #55636f;
  word-break: break-all;
}

.poster-steps {
  list-style: none;
  margin: 0 0 7mm;
  padding: 0;
  display: grid;
  gap: 2.5mm;
  text-align: left;
  font-size: 4mm;
}

.poster-steps li { display: flex; align-items: center; gap: 3mm; }

.poster-steps span {
  width: 6.5mm;
  height: 6.5mm;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 3.4mm;
  font-weight: 700;
}

.poster-foot {
  border-top: 0.3mm solid #dfe5ea;
  padding-top: 3mm;
  font-size: 3.2mm;
  color: #55636f;
  display: flex;
  justify-content: center;
  gap: 2mm;
}

/* ── On paper ─────────────────────────────────────────────────────────────── */

@media print {
  /* Everything that is chrome on screen and litter on paper. */
  .no-print,
  .site-header,
  .site-footer,
  .flash,
  .flashes { display: none !important; }

  html, body { background: #fff; }

  /* Centred on the sheet. Most shops will print this on A4 and not trim it,
     and a poster sitting in the top half with a blank bottom half looks like
     something went wrong rather than like a poster. */
  main, main.wide {
    max-width: none;
    padding: 0;
    margin: 0;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .poster {
    width: auto;
    margin: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    /* A poster split across two sheets is not a poster. */
    break-inside: avoid;
    page-break-inside: avoid;
  }

  /* Browsers strip backgrounds by default, which would print the QR frame, the
     step numbers and the shop's initials as empty outlines. */
  .poster-mark,
  .poster-steps span,
  .poster-qr {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  @page { margin: 12mm; }
}

.dash-empty-actions {
  display: flex;
  gap: var(--s-2);
  justify-content: center;
  flex-wrap: wrap;
}

/* A stopped print is a different kind of attention from a payment to check:
   something has gone wrong rather than something is waiting for you. Amber,
   not the shop's accent, so the two blocks are told apart at a glance even
   when they are both on screen. */
.dash-todo.is-warn {
  border-color: color-mix(in srgb, var(--warn) 40%, var(--line));
  border-left-color: var(--warn);
  background: var(--warn-wash);
}

.dash-todo.is-warn .dash-todo-title { color: var(--warn); }

/* The wordmark: Scan · QR · Print. Lowercase in the markup, raised here, so
   the accessible name stays one pronounceable word. */
.wordmark { letter-spacing: -0.01em; }

.wordmark-qr {
  text-transform: uppercase;
  color: var(--accent);
  /* Uppercase letterforms next to lowercase ones need the extra air or they
     read as a typo rather than as emphasis. */
  letter-spacing: 0.01em;
}

/* On the shop's own pages the accent is the shop's, and our wordmark in their
   colour reads as though we are part of their brand. */
.site-footer .wordmark-qr { color: inherit; }

/* ── The brand lockup ─────────────────────────────────────────────────────────
   The artwork ships on an opaque white ground with soft drop shadows under the
   letterforms. Keying that white out would have to guess where a shadow ends
   and the page begins, and it guesses wrong at the edges.

   `multiply` needs no guessing: white multiplies to whatever is underneath and
   disappears, and the shadows darken the page exactly as much as they darken
   the white they were drawn on. It only works on a light ground — on a dark
   band the whole mark goes to near-black — so this is never placed on one. */
.brand-lockup {
  display: block;
  height: auto;
  max-width: 100%;
  mix-blend-mode: multiply;
}

.hero-copy .brand-lockup { margin: 0 0 var(--s-4) -10px; }

/* Left, not centred. The form below it is a left-aligned card, and a centred
   mark above a left-aligned card reads as a mistake rather than a choice. */
.brand-lockup-auth { margin: 0 0 var(--s-5); }

/* A browser without mix-blend-mode paints the white box. Rather than leave a
   rectangle floating on the page ground, give it the same white deliberately,
   so it reads as a card instead of a mistake. */
@supports not (mix-blend-mode: multiply) {
  .brand-lockup {
    background: #fff;
    border-radius: 10px;
    padding: var(--s-2);
  }
}

/* ── Who made this, on a customer's page ──────────────────────────────────────
   Under a rule, at the foot, after the order is placed. The shop's accent
   tints everything above it; this block deliberately does not take that tint,
   because it is the one part of the page that is not the shop. */
.powered {
  margin-top: var(--s-6);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: var(--s-3) var(--s-4);
  flex-wrap: wrap;
}

.powered-mark {
  flex: none;
  display: block;
  line-height: 0;
  /* The artwork's white ground multiplies away against the page. */
  mix-blend-mode: multiply;
  opacity: .85;
  transition: opacity 160ms ease;
}

.powered-mark:hover,
.powered-mark:focus-visible { opacity: 1; }

.powered-mark img { display: block; height: auto; max-width: 100%; }

.powered-note {
  margin: 0;
  flex: 1 1 16rem;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink-faint);
}

.powered-note strong { color: var(--ink-soft); font-weight: 650; }

@supports not (mix-blend-mode: multiply) {
  .powered-mark { mix-blend-mode: normal; background: #fff; border-radius: 6px; }
}

@media (prefers-reduced-motion: reduce) {
  .powered-mark { transition: none; }
}

/* The three-step strip, in the brand's own hand. Marketing, so it sits with
   the attribution at the foot and not among the instructions. */
.brand-flow {
  margin: var(--s-6) 0 0;
  text-align: center;
}

.brand-flow img {
  display: block;
  width: 100%;
  max-width: 520px;
  height: auto;
  margin: 0 auto;
  /* Same opaque white ground as the lockup. */
  mix-blend-mode: multiply;
}

/* The attribution follows straight on, so it does not need its own top gap
   as well — two stacked margins here read as the page trailing off. */
.brand-flow + .powered { margin-top: var(--s-4); }

@supports not (mix-blend-mode: multiply) {
  .brand-flow img { mix-blend-mode: normal; border-radius: 10px; }
}

/* "What your customer does" — the one band on the landing page written from
   the other side of the counter. */
.customer-band { text-align: center; }
.customer-band .band-sub { margin-inline: auto; max-width: 56ch; }

.customer-flow { margin: var(--s-5) auto 0; max-width: 900px; }

.customer-flow img {
  display: block;
  width: 100%;
  height: auto;
  /* The artwork's opaque white ground multiplies away. This band is light;
     it must never be moved onto .band-dark. */
  mix-blend-mode: multiply;
}

@supports not (mix-blend-mode: multiply) {
  .customer-flow img { mix-blend-mode: normal; border-radius: 12px; }
}
