/* ---------------------------------------------------------------------
   Wisp — design tokens
   Ink #14151A · Paper #FAFAF8 · Signal #00C2A8 · Slate #6B7280 · Line #E7E5DF
   Single system-font stack, one accent color, weight/scale carry hierarchy.
------------------------------------------------------------------------ */
:root {
  --ink: #14151a;
  --ink-soft: #34363f;
  --paper: #fafaf8;
  --paper-raised: #ffffff;
  --line: #e7e5df;
  --line-strong: #d7d4cb;
  --signal: #00c2a8;
  --signal-ink: #003b33;
  --slate: #6b7280;
  --danger: #c8402b;
  --danger-bg: #fdf1ee;
  --warn-bg: #fff8ea;
  --warn-ink: #7a5b00;

  /* Small tint/hover surfaces used against the base backgrounds above —
     kept as their own tokens (rather than hardcoded hex at each call
     site) purely so the dark theme below only has to override them once. */
  --tint-signal: #eafaf7;
  --tint-neutral: #f1f0eb;
  --hover-neutral: #f4f3ee;
  --hover-signal: #f5fdfc;
  --placeholder: #c7c4ba;
  --desktop-bg-start: #f0efe9;
  --desktop-bg-end: #e9e7df;
  --overlay-ink: rgba(20, 21, 26, 0.05);
  --overlay-ink-soft: rgba(20, 21, 26, 0.03);

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, monospace;

  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 22px;

  --shadow-card: 0 1px 2px rgba(20, 21, 26, 0.04), 0 8px 24px -12px rgba(20, 21, 26, 0.12);
  --shadow-raised: 0 2px 4px rgba(20, 21, 26, 0.06), 0 16px 40px -16px rgba(20, 21, 26, 0.22);

  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);

  color-scheme: light;
}

/* Dark theme — toggled via the sun/moon button on the home screen (see
   js/theme.js), which sets data-theme on <html> and persists the choice
   in localStorage. Same shape as :root above, just re-pointed at a dark
   palette; every rule elsewhere in this file already reads these tokens,
   so nothing past this block needs to know a theme even exists. */
html[data-theme="dark"] {
  /* Softer than a first pass at "invert everything": near-white instead
     of pure white text, a lifted (not pitch-black) base surface with a
     visibly distinct raised-card tone on top of it, and a slightly
     desaturated signal color so it reads as an accent rather than glowing. */
  --ink: #ececee;
  --ink-soft: #b7bac1;
  --paper: #18191b;
  --paper-raised: #212226;
  --line: #2d2f34;
  --line-strong: #3d4046;
  --signal: #2ed9bc;
  --signal-ink: #eafff9;
  --slate: #8b8f98;
  --danger: #ff8a76;
  --danger-bg: #34211d;
  --warn-bg: #332a15;
  --warn-ink: #e8c158;

  --tint-signal: #163330;
  --tint-neutral: #2a2c31;
  --hover-neutral: #26282d;
  --hover-signal: #17322e;
  --placeholder: #63666d;
  --desktop-bg-start: #0e0f10;
  --desktop-bg-end: #09090a;
  --overlay-ink: rgba(255, 255, 255, 0.06);
  --overlay-ink-soft: rgba(255, 255, 255, 0.04);

  color-scheme: dark;
}

/* Redundant with the meta[name=color-scheme] tag in <head>, but some
   Android/Chromium versions only fully trust that a page manages its
   own theme (and suppress their "auto dark" repaint of text) once they
   also see an actual prefers-color-scheme media query in the CSS
   itself, rather than relying on JS + the meta tag alone. This block
   changes nothing visually — data-theme still wins — it's purely an
   extra signal to opt the page out of forced/auto-dark repainting. */
@media (prefers-color-scheme: dark) {
  :root { color-scheme: dark; }
}
@media (prefers-color-scheme: light) {
  :root { color-scheme: light; }
}

* { box-sizing: border-box; }
html, body { height: 100%; }
html { -webkit-text-size-adjust: 100%; }

/* No page scrolls by default — every screen is sized to fit within one
   viewport (see the fluid type/spacing scale below). The three long-form
   text pages (about, privacy, terms) opt back into normal scrolling via
   the .scrollable-page class on <body>; nothing else should ever need it. */
body {
  margin: 0;
  font-family: var(--font);
  background: var(--paper);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
  overscroll-behavior: none;
}
body.scrollable-page {
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: auto;
}

img, svg { display: block; max-width: 100%; }
button, input { font-family: inherit; }

a { color: inherit; }

.app {
  max-width: 480px;
  margin: 0 auto;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  padding-top: max(clamp(10px, 3dvh, 20px), var(--safe-top));
  padding-bottom: max(clamp(10px, 3dvh, 20px), var(--safe-bottom));
  position: relative;
  overflow: hidden; /* safety net: clip rather than scroll if anything runs long */
}
/* Scrollable pages get their natural height back and are allowed to
   overflow into the body's own scroll (set above), instead of being
   capped and clipped to one viewport. */
body.scrollable-page .app {
  height: auto;
  min-height: 100dvh;
  overflow: visible;
}

.app > section {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Desktop: keep the phone-shaped experience centered rather than
   stretching the layout thin across a wide viewport. */
@media (min-width: 640px) {
  body {
    background: linear-gradient(180deg, var(--desktop-bg-start), var(--desktop-bg-end));
  }
  .app {
    margin-top: 32px;
    margin-bottom: 32px;
    height: calc(100dvh - 64px);
    background: var(--paper);
    border-radius: 28px;
    box-shadow: var(--shadow-raised);
    border: 1px solid var(--line);
  }
  body.scrollable-page .app {
    height: auto;
    min-height: calc(100dvh - 64px);
  }
}

/* ===========================================================================
   Tablet & desktop layout — everything below this stays exactly what phones
   already get; nothing above this line, and nothing outside these two
   queries, changes for a phone-width viewport.
   641–1023px : tablet — same single-column shell, just roomier: bigger
                container, type, spacing and touch targets.
   1024px+    : laptop/desktop — a real multi-column layout on the two
                screens where it earns its keep (home, send), plus the same
                roomier scale everywhere else.
=========================================================================== */

/* ------------------------------- Tablet ------------------------------- */
@media (min-width: 641px) {
  .app { max-width: 640px; }
  .screen { padding: 0 34px; }
  .topbar { padding: 6px 34px 10px; }

  h1 { font-size: 32px; }
  h2 { font-size: 22px; }
  .lede { font-size: 16px; margin-bottom: 30px; }

  .btn { min-height: 56px; font-size: 16.5px; }
  .action-card { padding: 30px 26px; }
  .dropzone { padding: 46px 24px; }

  .back-btn, .theme-toggle-btn { width: 44px; height: 44px; }
  .back-btn svg { width: 24px; height: 24px; }
}

/* ---------------------------- Desktop / laptop ---------------------------- */
@media (min-width: 1024px) {
  .app { max-width: 920px; border-radius: 32px; }
  .screen { padding: 0 60px; }
  .topbar { padding: 8px 60px 12px; }

  h1 { font-size: 38px; }
  h2 { font-size: 25px; }
  .lede { font-size: 17px; margin-bottom: 34px; }

  /* Home: the two action cards sit side by side instead of stacked. */
  .stack { flex-direction: row; gap: 22px; }
  .stack .action-card { flex: 1; }

  /* Send / choose-files screen: dropzone + file list on the left,
     transfer settings + the create button on the right, instead of one
     long stacked column. Each child gets its own explicit grid cell
     (rather than a shared named area) so independent siblings in the
     same column never need a wrapper element to stack correctly. */
  .send-screen {
    display: grid;
    grid-template-columns: 1fr 340px;
    column-gap: 44px;
    row-gap: 18px;
    align-content: start;
  }
  .send-screen .page-intro        { grid-column: 1 / -1; grid-row: 1; }
  /* Only the dropzone itself is centered within its column — everything
     else in the grid keeps its normal left/right placement and width. */
  .send-screen #dropzone          { grid-column: 1; grid-row: 2; align-self: start; max-width: 400px; width: 100%; margin: 28px auto 0; }
  .send-screen .selection-summary { grid-column: 1; grid-row: 3; }
  .send-screen .settings-section  { grid-column: 2; grid-row: 2; }
  .send-screen #one-time-toggle   { grid-column: 2; grid-row: 3; align-self: start; }
  .send-screen #choose-error      { grid-column: 1 / -1; grid-row: 4; }
  .send-screen .spacer            { grid-column: 1 / -1; grid-row: 5; min-height: 0; }
  .send-screen #create-btn        { grid-column: 2; grid-row: 6; }
}

.screen {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 0 22px;
  min-width: 0;
  min-height: 0;
}

/* -------------------------------- Header -------------------------------- */
.topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  padding: 4px 22px 8px;
}

.back-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin-left: -8px;
  border-radius: 999px;
  border: none;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
.back-btn:hover { background: var(--overlay-ink); }
.back-btn svg { width: 22px; height: 22px; }

/* Theme toggle: same circular button language as .back-btn, mirrored to
   the opposite side of the topbar (.back-btn's own -8px left margin
   already gives it a flush left edge, same as any other leading topbar
   button — no extra positioning needed here). Both icons are always in
   the DOM; which one shows is purely CSS driven off data-theme, so
   there's no flash of the wrong icon while JS loads. */
.theme-toggle-btn .icon-sun { display: block; }
.theme-toggle-btn .icon-moon { display: none; }
html[data-theme="dark"] .theme-toggle-btn .icon-sun { display: none; }
html[data-theme="dark"] .theme-toggle-btn .icon-moon { display: block; }

.topbar-title {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* -------------------------------- Type -------------------------------- */
h1 { font-size: 28px; font-weight: 700; letter-spacing: -0.02em; margin: 0 0 6px; line-height: 1.15; }
h2 { font-size: 20px; font-weight: 650; letter-spacing: -0.01em; margin: 0 0 4px; }
p { line-height: 1.5; margin: 0; }
.lede { color: var(--slate); font-size: 15px; margin-bottom: 28px; }
.eyebrow { display: none; } /* deliberately unused — see design notes */

/* -------------------------------- Buttons -------------------------------- */
.btn {
  appearance: none;
  flex-shrink: 0;
  /* Native <button> text color does NOT inherit by default — it uses
     the browser/OS control-text color, which desktop happens to render
     as black/white but which Android (Material You accent) can render
     as the phone's system accent color, purple being a common result.
     Force it to actually inherit our own ink color instead. */
  color: inherit;
  border: none;
  border-radius: var(--radius-md);
  font-weight: 600;
  font-size: 16px;
  min-height: 52px;
  padding: 0 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  cursor: pointer;
  transition: transform 0.12s ease, background-color 0.15s ease, opacity 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}
.btn:active { transform: scale(0.97); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }

.btn-primary { background: var(--ink); color: var(--paper); width: 100%; }
.btn-primary:hover { background: var(--ink-soft); }

.btn-signal { background: var(--signal); color: var(--signal-ink); width: 100%; }
.btn-signal:hover { filter: brightness(0.96); }

.btn-secondary { background: var(--paper-raised); color: var(--ink); border: 1.5px solid var(--line-strong); width: 100%; }
.btn-secondary:hover { background: var(--hover-neutral); }

.btn-ghost { background: transparent; color: var(--ink); border: 1.5px solid var(--line-strong); width: 100%; }
.btn-ghost:hover { background: var(--overlay-ink-soft); }

.btn-danger { background: var(--danger); color: #fff; width: 100%; }
.btn-danger:hover { filter: brightness(0.93); }

.btn-text {
  background: none; border: none; color: var(--slate); font-weight: 600; font-size: 15px;
  min-height: 44px; padding: 0 6px; cursor: pointer;
  flex-shrink: 0;
}
.btn-text:hover { color: var(--ink); }

/* -------------------------------- Cards -------------------------------- */
.action-card {
  color: inherit; /* same native-<button> default-color issue as .btn above */
  border-radius: var(--radius-lg);
  border: 1.5px solid var(--line);
  background: var(--paper-raised);
  padding: 26px 22px;
  text-align: left;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  box-shadow: var(--shadow-card);
  transition: border-color 0.15s ease, transform 0.12s ease;
}
.action-card:active { transform: scale(0.985); }
.action-card:hover { border-color: var(--line-strong); }

.action-card .icon-badge {
  width: 44px; height: 44px; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 12px;
}
.action-card.send .icon-badge { background: var(--tint-signal); color: var(--signal-ink); }
.action-card.receive .icon-badge { background: var(--tint-neutral); color: var(--ink); }
.action-card .icon-badge svg { width: 22px; height: 22px; }

.action-card .title { font-size: 18px; font-weight: 650; letter-spacing: -0.01em; }
.action-card .desc { font-size: 14px; color: var(--slate); }

.card {
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background: var(--paper-raised);
  padding: 18px;
}

/* -------------------------------- Forms -------------------------------- */
.field-label {
  font-size: 13px; font-weight: 600; color: var(--slate);
  margin-bottom: 10px; display: block;
}

.option-row {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 4px; border-bottom: 1px solid var(--line);
  cursor: pointer;
}
.option-group { border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.section-title { font-size: 14px; margin: 0 0 8px; font-weight: 650; }
.option-row:last-child { border-bottom: none; }
.option-row .radio {
  width: 20px; height: 20px; border-radius: 999px;
  border: 1.5px solid var(--line-strong); flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
}
.option-row.selected .radio { border-color: var(--ink); }
.option-row.selected .radio::after {
  content: ""; width: 10px; height: 10px; border-radius: 999px; background: var(--ink);
}
.option-row .option-label { font-size: 15px; font-weight: 500; }

/* Phone: the five expiry options as one stacked list (each its own full-
   width row with padding) run to roughly 250px tall — often the single
   biggest reason a short screen didn't fit. Wrap them into compact pills
   instead; tablet/desktop keep the original stacked-list look untouched. */
@media (max-width: 640px) {
  .option-group {
    display: flex; flex-wrap: wrap; gap: 8px;
    border: 0; padding: 2px 0;
  }
  .option-row {
    flex: 0 0 auto;
    gap: 6px;
    padding: 9px 14px;
    border: 1.5px solid var(--line-strong);
    border-bottom: 1.5px solid var(--line-strong);
    border-radius: 999px;
    background: var(--paper-raised);
  }
  .option-row:last-child { border-bottom: 1.5px solid var(--line-strong); }
  .option-row.selected { border-color: var(--ink); background: var(--tint-neutral); }
  .option-row .radio { width: 14px; height: 14px; }
  .option-row.selected .radio::after { width: 8px; height: 8px; }
  .option-row .option-label { font-size: 13.5px; }
  .section-title { margin: 0 0 6px; }
  .toggle-row { padding: 10px 0; }
}

.toggle-row {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; gap: 12px; padding: 16px 0; text-align: left;
  border: 0; border-bottom: 1px solid var(--line); background: transparent; color: var(--ink); cursor: pointer;
  flex-shrink: 0;
}
.toggle-row .toggle-copy { display: block; font-size: 15px; font-weight: 500; }
.toggle-row .toggle-sub { display: block; font-size: 13px; color: var(--slate); margin-top: 2px; }

.switch {
  position: relative; width: 48px; height: 28px; flex-shrink: 0;
  border-radius: 999px; background: var(--line-strong); border: none; cursor: pointer;
  transition: background-color 0.15s ease;
}
.switch::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 22px; height: 22px; border-radius: 999px; background: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,0.25);
  transition: transform 0.15s ease;
}
.toggle-row[aria-checked="true"] .switch { background: var(--signal); }
.toggle-row[aria-checked="true"] .switch::after { transform: translateX(20px); }

.code-input {
  width: 100%;
  font-family: var(--font-mono);
  font-size: 24px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-align: center;
  text-transform: uppercase;
  padding: 16px 12px;
  border-radius: var(--radius-md);
  border: 1.5px solid var(--line-strong);
  background: var(--paper-raised);
  color: var(--ink);
}
.code-input:focus { outline: none; border-color: var(--ink); }
.code-input::placeholder { color: var(--placeholder); letter-spacing: 0.04em; }

/* -------------------------------- File list -------------------------------- */
.file-list {
  display: flex; flex-direction: column; gap: 8px; margin: 4px 0;
  max-height: clamp(90px, 22dvh, 180px); overflow-y: auto; -webkit-overflow-scrolling: touch;
}
.file-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--paper-raised); border: 1px solid var(--line);
}
.file-row .file-icon { font-size: 20px; line-height: 1; width: 24px; text-align: center; flex-shrink: 0; }
.file-row .file-meta { flex: 1; min-width: 0; }
.file-row .file-name { font-size: 14px; font-weight: 550; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.file-row .file-size { font-size: 12px; color: var(--slate); }
.file-row .remove-btn {
  background: none; border: none; color: var(--slate); cursor: pointer;
  width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
  border-radius: 999px; flex-shrink: 0;
}
.file-row .remove-btn:hover { background: rgba(20,21,26,0.06); color: var(--danger); }

/* -------------------------------- History -------------------------------- */
.history-list {
  display: flex; flex-direction: column; gap: 10px; margin-top: 4px;
  max-height: 65dvh; overflow-y: auto; -webkit-overflow-scrolling: touch;
}
.history-list:empty { margin-top: 0; }

.history-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 14px;
  border-radius: var(--radius-md);
  background: var(--paper-raised);
  border: 1px solid var(--line);
}
.history-item.expired { opacity: 0.6; }

.history-item .history-main { flex: 1; min-width: 0; }
.history-item .history-code {
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 650;
  letter-spacing: 0.06em;
}
.history-item .history-filename {
  font-size: 13px;
  color: var(--slate);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 2px;
}

.history-item .history-status { flex-shrink: 0; text-align: right; }
.history-pill {
  display: inline-block;
  font-size: 12px;
  font-weight: 650;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--tint-signal);
  color: var(--signal-ink);
  white-space: nowrap;
}
.history-item.expired .history-pill { background: var(--line); color: var(--slate); }

.history-item .remove-btn {
  background: none; border: none; color: var(--slate); cursor: pointer;
  width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
  border-radius: 999px; flex-shrink: 0;
}
.history-item .remove-btn:hover { background: rgba(20,21,26,0.06); color: var(--danger); }

.history-empty {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 6px;
  padding: 20px;
  color: var(--slate);
}
.history-empty .glyph {
  width: 56px; height: 56px; border-radius: 999px; background: var(--line);
  color: var(--slate); display: flex; align-items: center; justify-content: center;
  margin-bottom: 8px;
}
.history-empty .glyph svg { width: 26px; height: 26px; }

/* -------------------------------- Dialogs -------------------------------- */
/* A styled stand-in for window.confirm/alert, used for destructive or
   important confirmations so they look like the rest of the app instead
   of a native browser dialog. */
.wisp-dialog-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 21, 26, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}
.wisp-dialog {
  background: var(--paper-raised);
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-raised);
  padding: 24px 22px;
  max-width: 340px;
  width: 100%;
}
.wisp-dialog h2 { margin: 0 0 8px; }
.wisp-dialog-message { font-size: 14px; color: var(--slate); line-height: 1.5; margin: 0 0 22px; }
.wisp-dialog-actions { display: flex; flex-direction: column; gap: 10px; }
.wisp-dialog-actions .btn-text { align-self: center; }

/* ---------------------------- Install prompt ---------------------------- */
/* Shown once, the first time the app is opened, to offer a home-screen
   shortcut. Reuses the .wisp-dialog shell so it reads as part of the app
   rather than a browser nag. */
.install-prompt .install-prompt-icon {
  width: 56px; height: 56px; border-radius: 16px; background: var(--ink);
  display: flex; align-items: center; justify-content: center; margin: 0 auto 18px;
}
.install-prompt .install-prompt-icon svg { width: 28px; height: 28px; }
.install-prompt h2, .install-prompt > .wisp-dialog-message { text-align: center; }
.install-steps { list-style: none; margin: 0 0 22px; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.install-steps li { display: flex; align-items: flex-start; gap: 12px; font-size: 14px; color: var(--ink-soft); line-height: 1.45; }
.install-steps .num {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
  background: var(--paper); border: 1px solid var(--line-strong);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 650; color: var(--slate); margin-top: 1px;
}
.install-steps .icon-inline { width: 15px; height: 15px; vertical-align: -3px; margin: 0 1px; }

/* -------------------------------- Dropzone -------------------------------- */
.dropzone {
  color: inherit; /* native <button> default-color bug, same as .btn/.action-card */
  border: 1.5px dashed var(--line-strong);
  border-radius: var(--radius-lg);
  background: var(--paper-raised);
  padding: 40px 20px;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  cursor: pointer; text-align: center;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.dropzone:hover, .dropzone.drag-over { border-color: var(--signal); background: var(--hover-signal); }
.dropzone .plus-badge {
  width: 52px; height: 52px; border-radius: 999px; background: var(--ink);
  color: var(--paper); display: flex; align-items: center; justify-content: center;
}
.dropzone .plus-badge svg { width: 24px; height: 24px; }
.dropzone .dz-title { font-size: 16px; font-weight: 650; }
.dropzone .dz-sub { font-size: 13px; color: var(--slate); }

/* -------------------------------- Progress -------------------------------- */
.progress-track {
  width: 100%; height: 8px; border-radius: 999px; background: var(--line);
  overflow: hidden;
}
.progress-fill {
  height: 100%; background: var(--signal); border-radius: 999px;
  transition: width 0.2s ease;
  width: 0%;
}
.progress-stats {
  display: flex; justify-content: space-between; font-size: 13px; color: var(--slate); margin-top: 10px;
}
.progress-percent { font-size: 40px; font-weight: 700; letter-spacing: -0.02em; margin: 18px 0 4px; }

/* -------------------------------- The code -------------------------------- */
.code-display {
  border-radius: var(--radius-lg);
  border: 1.5px solid var(--line);
  background: var(--paper-raised);
  padding: 22px 16px;
  text-align: center;
  box-shadow: var(--shadow-card);
}
.code-display {
  margin: 0;
  font-family: var(--font-mono);
  font-size: clamp(26px, 8vw, 34px);
  font-weight: 700;
  letter-spacing: 0.1em;
  word-break: break-word;
}
.code-display .code-value {
  font-family: var(--font-mono);
  font-size: 34px;
  font-weight: 700;
  letter-spacing: 0.12em;
  word-break: break-all;
}

.qr-wrap {
  display: flex; justify-content: center; padding: 20px;
  background: var(--paper-raised); border-radius: var(--radius-lg);
  border: 1.5px solid var(--line);
}
.qr-wrap img { width: 220px; height: 220px; }

/* -------------------------------- Misc -------------------------------- */
.spacer { flex: 1; }
.stack { display: flex; flex-direction: column; gap: 14px; }
.center-col { display: flex; flex-direction: column; align-items: center; text-align: center; }
.muted { color: var(--slate); font-size: 14px; }
.small-muted { color: var(--slate); font-size: 12px; }

.footer-links {
  text-align: center; font-size: 13px; color: var(--slate);
  padding: 20px 0 4px;
}
.footer-links a { text-decoration: none; }
.footer-links a:hover { text-decoration: underline; }
.footer-links .dot { margin: 0 8px; color: var(--line-strong); }

.banner {
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  font-size: 13px;
  display: flex;
  gap: 10px;
  align-items: flex-start;
  line-height: 1.4;
}
.banner.warn { background: var(--warn-bg); color: var(--warn-ink); }
.banner.danger { background: var(--danger-bg); color: var(--danger); }

.error-state {
  flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: center; text-align: center; gap: 14px; padding: 20px;
}
.error-state .glyph {
  width: 64px; height: 64px; border-radius: 999px; background: var(--danger-bg);
  color: var(--danger); display: flex; align-items: center; justify-content: center;
}
.error-state .glyph svg { width: 30px; height: 30px; }
.error-state .error-code {
  font-size: 72px; font-weight: 700; color: var(--danger); line-height: 1; margin: 0;
}
.error-state h2 { margin: 0; }

.hidden { display: none !important; }

/* -------------------------------- Page layouts -------------------------------- */
.page-intro .lede { margin-bottom: 0; }
.send-screen { gap: 18px; padding-bottom: 2px; }
/* Wraps everything above a screen's trailing spacer+button(s) in its own
   flex box that can shrink below its content height and clip internally
   (rather than the whole screen overflowing and clipping the buttons
   that come after it). display:contents on the Send screen at desktop
   removes it from the box tree so the CSS Grid rules above — which
   target these elements as direct grid children — keep working. */
.fit-guard { display: flex; flex-direction: column; gap: inherit; min-height: 0; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; }
@media (min-width: 1024px) {
  .send-screen .fit-guard { display: contents; }
}
.selection-summary .small-muted { margin-top: 8px; }
.settings-section { margin-top: 4px; }
.sticky-action { margin-top: 4px; margin-bottom: 2px; }
.upload-screen, .ready-screen {
  justify-content: safe center; /* centers when it fits; falls back to top-aligned
    (instead of overflowing upward into the topbar) when content is taller than
    the screen, e.g. on shorter phones. */
  gap: 16px;
  padding-bottom: 10px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.ready-screen { text-align: center; }
.ready-screen .lede { margin-bottom: 0; }
.ready-actions { display: flex; flex-direction: column; gap: 10px; width: 100%; }

.scan-screen {
  position: relative;
  padding: 0;
  overflow: hidden;
  background: #000;
}
.scan-screen #scan-reader { width: 100%; height: 100%; object-fit: cover; }
.scan-screen .back-btn {
  position: absolute;
  top: max(16px, var(--safe-top));
  left: 16px;
  z-index: 2;
  background: rgba(255,255,255,0.92);
}
.scan-hint {
  position: absolute;
  right: 16px;
  bottom: max(24px, var(--safe-bottom));
  left: 16px;
  z-index: 2;
  color: #fff;
  font-size: 13px;
  text-align: center;
  text-shadow: 0 1px 3px rgba(0,0,0,0.7);
}

/* -------------------------- Fluid fit-to-viewport -------------------------- */
/* Everything below scales continuously with viewport height (dvh) rather
   than snapping at a single breakpoint, so a screen that's tight on a
   short/older phone or in landscape shrinks smoothly instead of
   overflowing into a scrollbar. clamp() min values are floors chosen to
   stay legible/tappable; the vh component is what actually does the
   shrinking as the viewport gets shorter. Width-based rules (tablet/
   desktop media queries above) still win at larger sizes since they're
   more specific / come later for those widths. */
@media (max-width: 640px) {
  body:not(.scrollable-page) h1 { font-size: clamp(22px, 6dvh, 28px); }
  body:not(.scrollable-page) h2 { font-size: clamp(17px, 4.2dvh, 20px); }
  body:not(.scrollable-page) .lede { font-size: clamp(13px, 3dvh, 15px); margin-bottom: clamp(12px, 4dvh, 28px); }

  body:not(.scrollable-page) .topbar { min-height: clamp(36px, 8dvh, 44px); padding: clamp(2px, 1dvh, 4px) 22px clamp(4px, 1.5dvh, 8px); }
  body:not(.scrollable-page) .btn { min-height: clamp(44px, 9dvh, 52px); }
  body:not(.scrollable-page) .btn-text { min-height: clamp(36px, 7dvh, 44px); }

  body:not(.scrollable-page) .action-card { padding: clamp(16px, 4.5dvh, 26px) 22px; }
  body:not(.scrollable-page) .dropzone { padding: clamp(18px, 5dvh, 40px) 20px; gap: clamp(8px, 2dvh, 12px); }
  body:not(.scrollable-page) .dropzone .plus-badge { width: clamp(38px, 9dvh, 52px); height: clamp(38px, 9dvh, 52px); }

  body:not(.scrollable-page) .stack { gap: clamp(10px, 2.5dvh, 14px); }
  body:not(.scrollable-page) .send-screen { gap: clamp(10px, 3dvh, 18px); }

  body:not(.scrollable-page) .progress-percent { font-size: clamp(28px, 10dvh, 40px); margin: clamp(8px, 3dvh, 18px) 0 4px; }
  body:not(.scrollable-page) .code-display { padding: clamp(14px, 4dvh, 22px) 16px; }

  body:not(.scrollable-page) .qr-wrap { padding: clamp(10px, 3dvh, 20px); }
  body:not(.scrollable-page) .qr-wrap img { width: clamp(120px, 28dvh, 220px); height: clamp(120px, 28dvh, 220px); }

  body:not(.scrollable-page) .error-state .error-code { font-size: clamp(48px, 14dvh, 72px); }
  body:not(.scrollable-page) .error-state .glyph { width: clamp(48px, 12dvh, 64px); height: clamp(48px, 12dvh, 64px); }
}

.spinner {
  width: 18px; height: 18px; border-radius: 999px;
  border: 2.5px solid rgba(255,255,255,0.35); border-top-color: currentColor;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.fade-in { animation: fadeIn 0.25s ease both; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

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

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