/* Rucksack Roamer — the whole design system.
 *
 * Read design-brief.md before changing anything here. The five decisions are
 * pinned: condensed display + typewriter mono, kraft-and-ink palette, ZERO
 * radius, editorial density with deliberate rhythm variation, and passport
 * stamps as the recurring motif.
 *
 * tests/design.test.js enforces the mechanical subset (banned palette, banned
 * fonts, radius, stamp count, dark-mode counterparts). It cannot enforce taste.
 */

/* ---- tokens -------------------------------------------------------------- */

/* Kraft is the default for everyone, on every device. It is the brand — it is
 * what the artwork, the share card and the icons are drawn against — and it is
 * not something a device-wide OS preference gets to decide on this site's
 * behalf. Dark is a real option, reached from the Settings screen.
 *
 * ONE mechanism decides the palette: `data-theme` on <html>, always present,
 * always a concrete "light" or "dark". theme.js resolves the stored preference
 * (which may be "auto") before it writes the attribute. No colour token is set
 * by a bare `prefers-color-scheme` block anywhere in this file, and
 * tests/design.test.js fails the build if one comes back — two mechanisms that
 * can disagree is precisely how the site shipped near-black to every dark-mode
 * visitor with the manual override sitting inert beside it. */
:root {
  color-scheme: light;

  --kraft: #c9b28c;
  --kraft-light: #e0d0b4;
  --paper: #f2e9d8;
  --ink: #241f1a;
  --ink-soft: #4a4038;   /* 4.7:1 on kraft. #5c5045 measured 3.81:1 and failed AA at 12px. */
  --rule: #8a7a63;
  /* Measured, not chosen by eye. The previous set (#a33b2c / #2f4b6e /
   * #4a6146) rendered the footer link at 4.35:1 and the masthead tag at
   * 4.30:1 — both under AA for text that size, and both invisible to a
   * token-level test because the ground they sat on was set elsewhere. */
  --stamp-red: #96301f;
  --stamp-blue: #2a4363;
  --stamp-green: #3f5a3b;

  --display: Haettenschweiler, "Arial Narrow", "Franklin Gothic Bold", Impact, sans-serif;
  --mono: "Courier New", Courier, "Nimbus Mono PS", monospace;

  /* Deliberately not a uniform scale — the brief bans metronomic spacing. */
  --gap-hair: 2px;
  --gap-tight: 6px;
  --gap: 14px;
  --gap-wide: 34px;
  --gap-section: 56px;
}

/* Dark scheme. Every text/surface token above has a counterpart here — that is
 * the guard: on the previous project a more-specific selector defeated one
 * dark-mode override and made text invisible ONLY in dark mode, while every
 * light render and every string assertion passed. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --kraft: #241f1a;
  --kraft-light: #322b24;
  --paper: #2b251f;
  --ink: #ede0c8;
  --ink-soft: #b3a48c;
  --rule: #6b5d4a;
  --stamp-red: #dd8070;
  --stamp-blue: #7d9dc4;
  --stamp-green: #8aa583;
}

:root[data-theme="light"] {
  color-scheme: light;
  --kraft: #c9b28c;
  --kraft-light: #e0d0b4;
  --paper: #f2e9d8;
  --ink: #241f1a;
  --ink-soft: #4a4038;
  --rule: #8a7a63;
  --stamp-red: #96301f;
  --stamp-blue: #2a4363;
  --stamp-green: #3f5a3b;
}

/* ---- ground -------------------------------------------------------------- */

* { box-sizing: border-box; border-radius: 0; }

body {
  margin: 0;
  background-color: var(--kraft);
  /* paper grain, drawn not downloaded */
  background-image:
    repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.022) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.016) 0 1px, transparent 1px 4px);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1.55;
  -webkit-text-size-adjust: 100%;
}

h1, h2, h3, .display {
  font-family: var(--display);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  line-height: 0.92;
  margin: 0;
}

h1 { font-size: clamp(2.6rem, 9vw, 4.4rem); }
h2 { font-size: clamp(2.05rem, 5.5vw, 2.6rem); text-wrap: balance; }
h3 { font-size: 1.45rem; letter-spacing: 0.04em; }

/* Brief §4 pins ASYMMETRIC rhythm: a lot above a section head, a little below.
 * The first version set `margin: 0` and never restored it, so every heading in
 * the app sat 0px from the thing under it — "CONVERTER" and "AMOUNT" read as
 * one line. No rhythm is not the same as deliberate rhythm. */
h2 { margin: var(--gap-section) 0 var(--gap-tight); }
h3 { margin: var(--gap-wide) 0 var(--gap-hair); }
section > h2:first-of-type,
.stub__body > h3:first-child,
.rail .stub__body > h3 { margin-top: 0; }

a { color: var(--stamp-blue); text-underline-offset: 3px; }
a:hover { color: var(--stamp-red); }

.meta {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: var(--gap-hair) 0 0;
}

/* `.meta` is uppercase + letterspaced — right for a label, wrong for a sentence.
 * The honesty copy (price basis, the Schengen caveat, rate staleness) is prose,
 * and setting prose in caps at 3.8:1 made the text we most need people to read
 * the least readable thing in the app. */
.basis {
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink-soft);
  margin: var(--gap-tight) 0 0;
}

/* A basis line that is telling you the number is old. Full ink and a rule down
 * the side — the same weight the app gives any other "this is not what it looks
 * like" statement, because a stale rate quietly styled as ordinary small print
 * is a stale rate nobody reads. */
.basis--warn {
  color: var(--ink);
  border-left: 3px solid var(--stamp-red);
  padding-left: 10px;
}

.wrap { max-width: 1080px; margin: 0 auto; padding: 0 20px 90px; }

/* ---- masthead: a ticket, torn along the bottom --------------------------- */

.masthead {
  background: var(--paper);
  border: 3px solid var(--ink);
  border-bottom: none;
  margin-top: 26px;
  padding: 22px 24px 20px;
  position: relative;
  /* asymmetric on purpose — the brief bans all-centred symmetric layout */
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: end;
  gap: var(--gap);
}

.masthead__perf {
  height: 14px;
  background: var(--paper);
  border-left: 3px solid var(--ink);
  border-right: 3px solid var(--ink);
  -webkit-mask-image: radial-gradient(circle at 7px 14px, transparent 7px, #000 7.5px);
  mask-image: radial-gradient(circle at 7px 14px, transparent 7px, #000 7.5px);
  -webkit-mask-size: 14px 14px;
  mask-size: 14px 14px;
  -webkit-mask-repeat: repeat-x;
  mask-repeat: repeat-x;
}

.masthead h1 { letter-spacing: -0.01em; }
.masthead__tag {
  font-size: 0.8rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--stamp-red);
  border-top: 1px solid var(--rule);
  padding-top: 6px;
  margin-top: 8px;
}
.masthead__ref { text-align: right; }

/* ---- the rail print (the one illustrated surface) ------------------------ */
/*
 * `mix-blend-mode: multiply` is doing the real work: the generated print sits on
 * its own pale paper, and multiplying it against the kraft ground drops that
 * paper out so only the ink remains. Without it the print reads as a photo
 * pasted onto the page rather than something printed on the same stock.
 *
 * The `scale()` inside an `overflow:hidden` box crops the source's soft rounded
 * border to a hard edge — the brief pins radius to sharp, and the generator
 * does not.
 */
.masthead { grid-template-columns: 1fr 320px; align-items: stretch; }
.masthead__art { overflow: hidden; align-self: stretch; min-height: 176px; }
.masthead__art img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: 50% 45%;
  transform: scale(1.14);
  mix-blend-mode: multiply;
  filter: saturate(0.72) contrast(1.04);
}
:root[data-theme="dark"] .masthead__art img {
  mix-blend-mode: luminosity; opacity: 0.5; filter: saturate(0) contrast(1.1);
}
@media (max-width: 820px) {
  /* Fixed height, not stretch: in a grid row the print grows to ~330px and
   * swallows the wordmark. No negative margins — they created a horizontal
   * scroll region on touch. */
  .masthead__art { min-height: 0; height: 88px; align-self: start; margin-top: var(--gap); }
  .masthead__art img { transform: scale(1.1); object-position: 50% 52%; }
}

/* The empty-state mark. Clipped to the drawing's own circular vignette so it
 * lands as an ink impression on the kraft, not a picture in a box. */
.empty--art { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: var(--gap-wide); align-items: center; }
.empty--art img {
  width: 132px;
  height: 132px;
  display: block;
  clip-path: circle(45% at 50% 49%);
  transform: scale(1.16);
  mix-blend-mode: multiply;
  filter: saturate(0.55) contrast(1.08);
}
:root[data-theme="dark"] .empty--art img {
  mix-blend-mode: screen; opacity: 0.42; filter: invert(1) saturate(0.25);
}
@media (max-width: 560px) {
  .empty--art { grid-template-columns: minmax(0, 1fr); gap: var(--gap); justify-items: center; text-align: center; }
}

/* ---- navigation: luggage tags -------------------------------------------- */

.nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin: 20px 0 var(--gap-wide);
  border-top: 3px solid var(--ink);
  border-bottom: 1px solid var(--rule);
}

.nav button {
  appearance: none;
  background: transparent;
  border: none;
  border-right: 1px solid var(--rule);
  color: var(--ink-soft);
  cursor: pointer;
  font-family: var(--display);
  font-size: 1.05rem;
  letter-spacing: 0.06em;
  padding: 11px 18px 9px;
  text-transform: uppercase;
}
.nav button:hover { color: var(--ink); background: var(--kraft-light); }
.nav button[aria-current="page"] {
  background: var(--ink);
  color: var(--paper);
}

/* ---- passport stamp: the signature element (use 1 of 4) ------------------ */

.stamp {
  display: inline-grid;
  place-items: center;
  width: 82px;
  height: 82px;
  border: 3px double var(--stamp-red);
  border-radius: 50%; /* the only curve in the product, and it is deliberate */
  color: var(--stamp-red);
  font-family: var(--mono);
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-align: center;
  text-transform: uppercase;
  transform: rotate(-8deg);
  opacity: 0.9;
  padding: 6px;
  line-height: 1.15;
}
.stamp--blue { border-color: var(--stamp-blue); color: var(--stamp-blue); transform: rotate(6deg); }
.stamp--green { border-color: var(--stamp-green); color: var(--stamp-green); transform: rotate(-4deg); }
.stamp--pending { border-style: dashed; border-width: 2px; color: var(--ink-soft); border-color: var(--rule); opacity: 1; }
.stamp--sm { width: 54px; height: 54px; font-size: 0.5rem; border-width: 2px; }

/* progress markers (use 2 of 4) */
.progress {
  display: flex;
  align-items: center;
  gap: var(--gap-tight);
  margin: 0 0 var(--gap-wide);
  flex-wrap: wrap;
}
.progress__link { flex: 1 0 12px; height: 1px; background: var(--rule); min-width: 12px; }

/* section dividers (use 3 of 4) */
.divider {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--gap);
  margin: var(--gap-section) 0 var(--gap-wide);
}
.divider::before, .divider::after { content: ""; height: 3px; background: var(--ink); }
.divider .stamp { transform: rotate(-11deg); }

/* completion states, slapped on at an angle (use 4 of 4) */
.stamp--slap {
  position: absolute;
  top: -14px;
  right: 12px;
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  transform: rotate(-14deg);
  z-index: 2;
}

/* ---- ticket stub cards --------------------------------------------------- */

.stub {
  background: var(--paper);
  border: 2px solid var(--ink);
  display: grid;
  grid-template-columns: 118px 1fr;
  margin-bottom: var(--gap);
  position: relative;
}
.stub__side {
  border-right: 2px dashed var(--rule);
  padding: 14px 12px;
  background: var(--kraft-light);
}
.stub__body { padding: 16px 18px 18px; min-width: 0; }
.stub--plain { grid-template-columns: 1fr; }

.stub__num {
  font-family: var(--display);
  font-size: 2.1rem;
  line-height: 0.85;
  color: var(--stamp-red);
}

/* ---- editorial layout: content column plus a margin rail ----------------- */

.editorial {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 250px;
  gap: var(--gap-wide);
  align-items: start;
}
.rail > * + * { margin-top: var(--gap-wide); }
.rail { position: sticky; top: var(--gap); }
@media (max-width: 820px) { .rail { position: static; } }

/* In the margin rail a stub is too narrow for a side column — the stub tears
 * along the top instead, so the perforation survives and the text doesn't end
 * up one word per line. */
.rail .stub { grid-template-columns: minmax(0, 1fr); }
.rail .stub__side {
  border-right: none;
  border-bottom: 2px dashed var(--rule);
  padding: 8px 12px 6px;
}

.stub__side { min-width: 0; }

@media (max-width: 820px) {
  .editorial { grid-template-columns: minmax(0, 1fr); }
  /* The stub tears along the TOP on narrow screens. With a fixed 84px side
   * column, the 2.8rem total painted straight across the perforation and on top
   * of the cost table — the app's most important number, unreadable on a phone. */
  .stub { grid-template-columns: minmax(0, 1fr); }
  .stub__side {
    border-right: none;
    border-bottom: 2px dashed var(--rule);
    padding: 10px 14px 8px;
    display: flex;
    align-items: baseline;
    gap: var(--gap);
    flex-wrap: wrap;
  }
  .stub__num { font-size: 1.6rem; }
  .leg { grid-template-columns: minmax(0, 1fr) auto; }
  .leg__mode { grid-column: 1 / -1; }
  .masthead { grid-template-columns: minmax(0, 1fr); }
  .masthead__ref { text-align: left; }
}

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

.field { margin-bottom: var(--gap-wide); }
.field > label,
.field > p.label,
.field > span.label {
  display: block;
  font-family: var(--display);
  font-size: 1.2rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin-bottom: var(--gap-hair);
}
.field .hint { font-size: 0.8125rem; color: var(--ink-soft); margin: 0 0 var(--gap-tight); }

/* A <summary> keeps its disclosure marker — the earlier `.field > .label` rule
 * matched it and killed display:list-item, so "Optional — pace and comfort" had
 * no triangle and nobody would ever have found it. */
summary.label {
  font-family: var(--display);
  font-size: 1.2rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
  margin-bottom: var(--gap-hair);
}
summary.label:hover { color: var(--stamp-red); }

input[type="text"], input[type="number"], input[type="date"], select {
  font-family: var(--mono);
  font-size: 1rem;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--ink);
  padding: 9px 11px;
  width: 100%;
  max-width: 420px;
}
input:focus-visible, select:focus-visible, button:focus-visible, a:focus-visible {
  outline: 3px solid var(--stamp-blue);
  outline-offset: 2px;
}

.row { display: flex; flex-wrap: wrap; gap: var(--gap); }
.row > * { flex: 1 1 190px; }
/* Buttons size to their content. Five equal flex children stretched "Log it"
 * to 371px, which reads as a rendering bug because it is one. */
.row > .btn, .row > button { flex: 0 0 auto; }

/* interest chips — flat, sharp, ink-stamped when on */
.chips { display: flex; flex-wrap: wrap; gap: var(--gap-tight); }
.chip {
  appearance: none;
  background: var(--paper);
  border: 2px solid var(--ink);
  color: var(--ink);
  cursor: pointer;
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: 0.09em;
  padding: 7px 13px;
  text-transform: uppercase;
}
.chip[aria-pressed="true"] { background: var(--ink); color: var(--paper); }

/* Destination picker. Three states, and they must be distinguishable without
 * colour alone — the ✓/✕ prefix carries the meaning for anyone who cannot tell
 * the two inks apart. */
.picker {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-tight);
  max-height: 260px;
  overflow-y: auto;
  padding: var(--gap-tight);
  border: 1px solid var(--rule);
  background: var(--paper);
}
/* Pinned uses the same ink fill as every other "on" chip in the app, on
 * purpose — a second selected-state colour would be a new thing to learn for no
 * gain. The ✓ prefix is what distinguishes it, and that survives greyscale.
 * (An earlier version set this green and it never applied: `.chip[aria-pressed]`
 * outranks a single class. A rule that cannot win is a lie in the stylesheet.) */
.chip--pinned { border-color: var(--ink); }
.chip--excluded { border-style: dashed; color: var(--ink-soft); opacity: 0.6; text-decoration: line-through; }
.chip--open:hover { background: var(--kraft-light); }

input[type="range"] {
  width: 100%; max-width: 420px;
  appearance: none; -webkit-appearance: none;
  background: transparent;
}
input[type="range"]::-webkit-slider-runnable-track { height: 4px; background: var(--rule); }
input[type="range"]::-moz-range-track { height: 4px; background: var(--rule); }
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 18px; height: 18px; margin-top: -7px;
  background: var(--stamp-red); border: 2px solid var(--ink); cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
  width: 18px; height: 18px;
  background: var(--stamp-red); border: 2px solid var(--ink); cursor: pointer;
}

/* Left unstyled, the file picker renders as a stock grey rounded pill in a
 * system font — banned on all three counts, and the loudest thing on the dark
 * Saved screen. */
input[type="file"] { font-family: var(--mono); font-size: 0.8rem; color: var(--ink-soft); }
input[type="file"]::file-selector-button {
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  background: transparent;
  color: var(--ink);
  border: 2px solid var(--ink);
  padding: 7px 13px;
  margin-right: 12px;
  cursor: pointer;
}
input[type="file"]::file-selector-button:hover { background: var(--ink); color: var(--paper); }
.dial-ends {
  display: flex;
  justify-content: space-between;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
  max-width: 420px;
}

/* one primary action, never a two-button hero cluster */
.go {
  appearance: none;
  background: var(--stamp-red);
  border: 3px solid var(--ink);
  color: var(--paper);
  cursor: pointer;
  font-family: var(--display);
  font-size: 1.7rem;
  letter-spacing: 0.05em;
  padding: 13px 34px 11px;
  text-transform: uppercase;
}
.go:hover { background: var(--ink); }
.go[disabled] { background: var(--rule); cursor: not-allowed; }

.btn {
  appearance: none;
  background: transparent;
  border: 2px solid var(--ink);
  color: var(--ink);
  cursor: pointer;
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  padding: 7px 13px;
  text-transform: uppercase;
}
.btn:hover { background: var(--ink); color: var(--paper); }

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

.notice {
  border: 1px solid var(--rule);
  border-left: 6px solid var(--stamp-blue);
  background: var(--paper);
  padding: 11px 14px;
  margin: var(--gap) 0;
  font-size: 0.87rem;
}
.notice--warn { border-left-color: var(--stamp-red); }
.notice--good { border-left-color: var(--stamp-green); }
.notice strong { letter-spacing: 0.04em; }

/* The update bar. Green, not red: a new version being available is good news,
 * and the red stamp is reserved for things that are actually wrong. It sits
 * above the view rather than floating over it — nothing in this design covers
 * content the reader is in the middle of. */
.notice--update {
  border-left-color: var(--stamp-green);
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap);
  align-items: center;
  justify-content: space-between;
}
.go--inline { font-size: 1.05rem; padding: 7px 18px 6px; border-width: 2px; }

/* confidence is never decoration — it is the honesty rule made visible */
.confidence {
  display: inline-block;
  font-size: 0.66rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  border: 1px solid currentColor;
  padding: 1px 6px;
  margin-left: 6px;
  vertical-align: 2px;
}
.confidence--low { color: var(--stamp-red); }
.confidence--medium { color: var(--stamp-blue); }
.confidence--none { color: var(--ink-soft); }

.empty {
  border: 2px dashed var(--rule);
  color: var(--ink-soft);
  padding: 18px;
  font-size: 0.88rem;
}

/* Capability rows on the Saved screen. Was a two-column table in a 250px rail:
 * label and reason ran together into one sentence, and the yes/no floated in the
 * middle of a six-line cell. */
.capability { border-top: 1px solid var(--rule); padding: var(--gap-tight) 0; }
.capability:first-of-type { border-top: none; }
.capability__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--gap-tight);
  margin: 0;
}

/* ---- data displays ------------------------------------------------------- */

.figure {
  font-family: var(--display);
  font-size: 2.8rem;
  line-height: 0.9;
  letter-spacing: -0.005em;
}
.figure--sm { font-size: 1.8rem; }
.figure--red { color: var(--stamp-red); }
.figure--green { color: var(--stamp-green); }

table { border-collapse: collapse; width: 100%; font-size: 0.88rem; }
th, td { text-align: left; padding: 7px 10px 6px; border-bottom: 1px solid var(--rule); }
th {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-soft);
  border-bottom: 2px solid var(--ink);
}
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

.legs { list-style: none; margin: 0; padding: 0; }
.leg {
  border-bottom: 1px solid var(--rule);
  padding: 12px 0 11px;
  display: grid;
  grid-template-columns: 74px 1fr auto;
  gap: var(--gap);
  align-items: baseline;
}
.leg__mode {
  font-family: var(--display);
  font-size: 1.1rem;
  letter-spacing: 0.07em;
  color: var(--stamp-blue);
}

.day { border-top: 1px solid var(--rule); padding: var(--gap) 0; }
.day__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--gap);
}
.day ol { margin: var(--gap-tight) 0 0; padding-left: 20px; }
.day li { margin-bottom: var(--gap-tight); }
.day li .note { color: var(--ink-soft); font-size: 0.83rem; display: block; }

.checklist { list-style: none; margin: 0; padding: 0; }
.checklist li { border-bottom: 1px dotted var(--rule); padding: 7px 0; display: flex; gap: 10px; align-items: flex-start; }
.checklist input[type="checkbox"] {
  width: 18px; height: 18px; flex: none;
  appearance: none; -webkit-appearance: none;
  border: 2px solid var(--ink);
  background: var(--paper);
  display: grid;
  place-items: center;
  cursor: pointer;
  align-self: center;   /* baseline floated every box ~4px above its label */
}
.checklist input[type="checkbox"]:checked::after {
  content: "\2715";
  color: var(--stamp-green);
  font-size: 13px;
  line-height: 1;
  font-weight: 700;
}
.checklist .why { display: block; color: var(--ink-soft); font-size: 0.8125rem; line-height: 1.45; }
.checklist li.is-done label { text-decoration: line-through; color: var(--ink-soft); }

.bar { height: 16px; border: 2px solid var(--ink); background: var(--kraft-light); position: relative; }
.bar__fill { position: absolute; inset: 0 auto 0 0; background: var(--stamp-green); }
.bar__fill--over { background: var(--stamp-red); }

/* ---- footer -------------------------------------------------------------- */

.foot {
  border-top: 3px solid var(--ink);
  margin-top: var(--gap-section);
  padding-top: var(--gap);
  font-size: 0.76rem;
  color: var(--ink-soft);
  letter-spacing: 0.05em;
}

/* The feedback report preview. Monospaced and boxed so it reads as "this is the
 * literal text", not as prose we have paraphrased for you. */
.report {
  border: 1px solid var(--rule);
  border-left: 6px solid var(--stamp-blue);
  background: var(--paper);
  color: var(--ink);
  padding: 11px 14px;
  margin: var(--gap) 0;
  font-family: var(--mono);
  font-size: 0.78rem;
  line-height: 1.5;
  white-space: pre-wrap;
  /* `overflow-y` matters as much as the height cap. With `max-height` alone the
   * end of a long report was CLIPPED and unreachable — on the one screen whose
   * whole promise is "this is the entire report, exactly as it will be sent".
   * The diagnostics block and the sign-off were both past the fold. */
  overflow: auto;
  max-height: 340px;
}
.report[data-empty="true"] { color: var(--ink-soft); border-left-color: var(--rule); font-style: italic; }

.checkline { display: flex; align-items: center; gap: var(--gap-tight); cursor: pointer; }

/* The home-screen card. Pinned to the bottom on a phone, inline on a desktop —
 * and never a full-screen interstitial over the content. It appears after
 * first render, so by the time it arrives the reader has seen what it is
 * offering to keep. */
.install {
  position: sticky;
  bottom: 0;
  z-index: 20;
  margin: var(--gap-wide) 0 0;
  border: 3px solid var(--ink);
  border-bottom-width: 6px;
  background: var(--paper);
  padding: var(--gap) var(--gap) var(--gap-tight);
  box-shadow: 0 -6px 0 rgba(36, 31, 26, 0.06);
}
.install h3 { margin-top: 0; }
.install p { font-size: 0.87rem; margin: var(--gap-tight) 0 0; }
.install__actions { display: flex; flex-wrap: wrap; gap: var(--gap-tight); align-items: center; margin-top: var(--gap); }
.install__foot { color: var(--ink-soft); font-size: 0.76rem !important; letter-spacing: 0.04em; }

/* Numbered instructions. Used where we tell someone which button to press on
 * their own device, so they read as a sequence rather than as prose. */
.steps { margin: var(--gap-tight) 0 0; padding-left: 1.3em; font-size: 0.87rem; line-height: 1.6; }
.steps li { margin-bottom: var(--gap-tight); }

/* A settings choice: a real target with its consequence stated, rather than a
 * row of one-word toggles you have to try to understand. */
.choices { display: grid; gap: var(--gap-hair); margin-top: var(--gap-tight); }
.choice {
  appearance: none;
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--ink);
  cursor: pointer;
  font-family: var(--mono);
  padding: 10px 13px;
}
.choice strong { display: block; font-size: 0.95rem; letter-spacing: 0.03em; }
.choice__hint { display: block; color: var(--ink-soft); font-size: 0.78rem; margin-top: 2px; }
.choice:hover { border-color: var(--ink); }
.choice[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.choice[aria-pressed="true"] .choice__hint { color: var(--kraft); }

/* Palette switch and app version. Unobtrusive by construction: footer-sized
 * type, no fill until pressed. The version is here rather than in the masthead
 * because it is the thing you go looking for when reporting a bug, not the
 * thing you need while planning a trip. */
.foot__controls { margin-top: var(--gap); }


.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* The grain and the perforation are two of the three things carrying the analog
 * vibe, and both were hardcoded black-on-light. In dark mode the grain was a
 * no-op and the tear was a 1.2:1 edge — i.e. absent. The dark-counterpart test
 * guards colour TOKENS, and neither of these is a token, so both slipped it. */
:root[data-theme="dark"] body {
  background-image:
    repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.022) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.016) 0 1px, transparent 1px 4px);
}
.masthead__perf { border-bottom: 1px solid var(--rule); }

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

/* ==========================================================================
   THE SCRAPBOOK LAYER
   --------------------------------------------------------------------------
   Everything above is the kraft-and-ink direction as originally drawn. This
   layer turns it into a travel scrapbook: torn-out journal pages, typewritten
   throughout, pasted onto a ground of faded stamps and passport marks.

   The three rules, and they are absolute:

     1. NO STRAIGHT LINES. Not one. Every rule, edge, underline and divider is
        either drawn by hand or torn. If it is a `border` it is gone.
     2. NO BOXES. Paper blocks are torn out with a displaced mask, so nothing
        on the page has four right angles.
     3. TYPEWRITER, everywhere. Headings included — the condensed display face
        is retired. Real typewriter ink spreads slightly into the paper, which
        is a zero-blur offset, not a soft shadow.

   Everything is DELIBERATE and fixed per element, keyed to nth-child. Never
   randomised: a page that rearranges itself each time you open it is broken,
   not charming.

   Appended as one block on purpose. Deleting from this comment to the end of
   the file returns the design to v1 exactly.
   ========================================================================== */

:root[data-skin="kraft"] {
  /* ---- the ground: stamps and passport marks, faded into the paper -------- */
  --marks:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='620' height='560'><g fill='none' stroke='%23a33b2c' stroke-width='2.4' opacity='0.13'><circle cx='96' cy='88' r='47'/><circle cx='96' cy='88' r='39' stroke-dasharray='7 6'/></g><g fill='none' stroke='%232f4b6e' stroke-width='2.2' opacity='0.12'><rect x='392' y='196' width='128' height='84' transform='rotate(-8 456 238)'/><rect x='400' y='204' width='112' height='68' stroke-dasharray='6 5' transform='rotate(-8 456 238)'/></g><g fill='none' stroke='%234a6146' stroke-width='2.3' opacity='0.12'><circle cx='236' cy='420' r='52'/><circle cx='236' cy='420' r='43' stroke-dasharray='8 7'/></g><g fill='none' stroke='%238a7a63' stroke-width='1.8' opacity='0.16'><path d='M470 452 h118 M470 462 h96 M470 472 h118 M470 482 h74'/></g><g fill='none' stroke='%232f4b6e' stroke-width='2' opacity='0.1'><circle cx='560' cy='60' r='34'/><circle cx='560' cy='60' r='26' stroke-dasharray='5 5'/></g><g fill='none' stroke='%23a33b2c' stroke-width='1.9' opacity='0.1'><rect x='40' y='268' width='104' height='68' transform='rotate(6 92 302)'/></g></svg>");

  /* ---- drawn rules: nothing in this file may be a border ------------------ */
  --rule-h:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='96' height='9'><path d='M0 5.4 Q 12 1.6 24 5 T 48 5.6 T 72 3.9 T 96 5.5' fill='none' stroke='%23241f1a' stroke-width='2.1' stroke-linecap='round'/></svg>");
  --rule-h-fine:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='71' height='7'><path d='M0 4 Q 9 1.4 18 3.7 T 36 4.2 T 53 2.9 T 71 4.3' fill='none' stroke='%238a7a63' stroke-width='1.25' stroke-linecap='round'/></svg>");
  --rule-h-red:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='96' height='9'><path d='M0 5.4 Q 12 1.6 24 5 T 48 5.6 T 72 3.9 T 96 5.5' fill='none' stroke='%23a33b2c' stroke-width='2.4' stroke-linecap='round'/></svg>");
  --rule-v:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='9' height='96'><path d='M5.4 0 Q 1.6 12 5 24 T 5.6 48 T 3.9 72 T 5.5 96' fill='none' stroke='%238a7a63' stroke-width='1.5' stroke-linecap='round'/></svg>");

  /* ---- torn paper: three different tears so no two blocks match ---------- */
  --torn-a:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='400' height='300' preserveAspectRatio='none'><filter id='t' x='-12%25' y='-12%25' width='124%25' height='124%25'><feTurbulence type='fractalNoise' baseFrequency='0.012 0.03' numOctaves='3' seed='17' result='n'/><feDisplacementMap in='SourceGraphic' in2='n' scale='14'/></filter><rect x='9' y='9' width='382' height='282' fill='%23000' filter='url(%23t)'/></svg>");
  --torn-b:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='400' height='300' preserveAspectRatio='none'><filter id='t' x='-12%25' y='-12%25' width='124%25' height='124%25'><feTurbulence type='fractalNoise' baseFrequency='0.015 0.026' numOctaves='3' seed='31' result='n'/><feDisplacementMap in='SourceGraphic' in2='n' scale='12'/></filter><rect x='9' y='9' width='382' height='282' fill='%23000' filter='url(%23t)'/></svg>");
  --torn-c:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='400' height='300' preserveAspectRatio='none'><filter id='t' x='-12%25' y='-12%25' width='124%25' height='124%25'><feTurbulence type='fractalNoise' baseFrequency='0.02 0.036' numOctaves='2' seed='53' result='n'/><feDisplacementMap in='SourceGraphic' in2='n' scale='9'/></filter><rect x='7' y='7' width='386' height='286' fill='%23000' filter='url(%23t)'/></svg>");
}

:root[data-skin="kraft"][data-theme="dark"] {
  --rule-h:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='96' height='9'><path d='M0 5.4 Q 12 1.6 24 5 T 48 5.6 T 72 3.9 T 96 5.5' fill='none' stroke='%23ede0c8' stroke-width='2.1' stroke-linecap='round'/></svg>");
  --rule-h-fine:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='71' height='7'><path d='M0 4 Q 9 1.4 18 3.7 T 36 4.2 T 53 2.9 T 71 4.3' fill='none' stroke='%236b5d4a' stroke-width='1.25' stroke-linecap='round'/></svg>");
  --rule-h-red:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='96' height='9'><path d='M0 5.4 Q 12 1.6 24 5 T 48 5.6 T 72 3.9 T 96 5.5' fill='none' stroke='%23d4705f' stroke-width='2.4' stroke-linecap='round'/></svg>");
  --rule-v:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='9' height='96'><path d='M5.4 0 Q 1.6 12 5 24 T 5.6 48 T 3.9 72 T 5.5 96' fill='none' stroke='%236b5d4a' stroke-width='1.5' stroke-linecap='round'/></svg>");
  --marks:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='620' height='560'><g fill='none' stroke='%23d4705f' stroke-width='2.4' opacity='0.1'><circle cx='96' cy='88' r='47'/><circle cx='96' cy='88' r='39' stroke-dasharray='7 6'/></g><g fill='none' stroke='%237d9dc4' stroke-width='2.2' opacity='0.09'><rect x='392' y='196' width='128' height='84' transform='rotate(-8 456 238)'/><rect x='400' y='204' width='112' height='68' stroke-dasharray='6 5' transform='rotate(-8 456 238)'/></g><g fill='none' stroke='%238aa583' stroke-width='2.3' opacity='0.09'><circle cx='236' cy='420' r='52'/><circle cx='236' cy='420' r='43' stroke-dasharray='8 7'/></g><g fill='none' stroke='%236b5d4a' stroke-width='1.8' opacity='0.13'><path d='M470 452 h118 M470 462 h96 M470 472 h118 M470 482 h74'/></g></svg>");
}

/* ---- 1. the ground -------------------------------------------------------- */

:root[data-skin="kraft"] body {
  background-image: var(--marks), var(--paper-grain, none);
  background-repeat: repeat, repeat;
  background-size: 620px 560px, auto;
}

/* ---- 2. typewriter, everywhere -------------------------------------------- */
/*
 * The condensed display face is retired. Everything is struck on the same
 * machine — which is the point of a journal: one person, one typewriter, one
 * ribbon getting drier as the trip goes on.
 *
 * The offset is a zero-blur ink spread, not a drop shadow. Real typewriter ink
 * pushes into the paper fibres a fraction past the letterform.
 */
:root[data-skin="kraft"] h1,
:root[data-skin="kraft"] h2,
:root[data-skin="kraft"] h3,
:root[data-skin="kraft"] .display,
:root[data-skin="kraft"] .figure,
:root[data-skin="kraft"] .stub__num,
:root[data-skin="kraft"] .nav button,
:root[data-skin="kraft"] .go,
:root[data-skin="kraft"] .field > span.label,
:root[data-skin="kraft"] summary.label,
:root[data-skin="kraft"] .choice strong {
  font-family: var(--mono);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-shadow: 0.4px 0.35px 0 rgba(36, 31, 26, 0.22);
}
:root[data-skin="kraft"][data-theme="dark"] h1,
:root[data-skin="kraft"][data-theme="dark"] h2,
:root[data-skin="kraft"][data-theme="dark"] h3,
:root[data-skin="kraft"][data-theme="dark"] .figure,
:root[data-skin="kraft"][data-theme="dark"] .nav button { text-shadow: 0.4px 0.35px 0 rgba(237, 224, 200, 0.18); }

:root[data-skin="kraft"] h1 { font-size: clamp(1.9rem, 5.6vw, 2.9rem); line-height: 1.08; letter-spacing: 0.01em; }
:root[data-skin="kraft"] h2 { font-size: clamp(1.25rem, 3.1vw, 1.6rem); line-height: 1.2; letter-spacing: 0.04em; }
:root[data-skin="kraft"] h3 { font-size: 1.02rem; letter-spacing: 0.09em; }
:root[data-skin="kraft"] .figure { font-size: 2rem; letter-spacing: -0.01em; }
:root[data-skin="kraft"] .stub__num { font-size: 1.6rem; }

/* ---- 3. no straight lines. every border in the app, removed. ------------- */

:root[data-skin="kraft"] .masthead,
:root[data-skin="kraft"] .stub,
:root[data-skin="kraft"] .stub__side,
:root[data-skin="kraft"] .notice,
:root[data-skin="kraft"] .empty,
:root[data-skin="kraft"] .report,
:root[data-skin="kraft"] .choice,
:root[data-skin="kraft"] .install,
:root[data-skin="kraft"] .nav,
:root[data-skin="kraft"] .nav button,
:root[data-skin="kraft"] .foot,
:root[data-skin="kraft"] .rail,
:root[data-skin="kraft"] .day,
:root[data-skin="kraft"] .capability,
:root[data-skin="kraft"] .leg,
:root[data-skin="kraft"] .checklist li,
:root[data-skin="kraft"] table,
:root[data-skin="kraft"] th,
:root[data-skin="kraft"] td,
:root[data-skin="kraft"] .bar,
:root[data-skin="kraft"] .confidence,
:root[data-skin="kraft"] .leg__mode,
:root[data-skin="kraft"] .chip,
:root[data-skin="kraft"] .btn,
:root[data-skin="kraft"] .go,
:root[data-skin="kraft"] input[type="text"],
:root[data-skin="kraft"] input[type="number"],
:root[data-skin="kraft"] input[type="date"],
:root[data-skin="kraft"] select,
:root[data-skin="kraft"] textarea,
:root[data-skin="kraft"] .basis--warn,
:root[data-skin="kraft"] .divider::before,
:root[data-skin="kraft"] .divider::after,
:root[data-skin="kraft"] .progress__link {
  border: none;
  outline: none;
  box-shadow: none;
  border-image: none;
}

/* drawn rules replace every one of them */
:root[data-skin="kraft"] .divider::before,
:root[data-skin="kraft"] .divider::after { height: 9px; background: var(--rule-h) repeat-x center; background-size: 96px 9px; }
:root[data-skin="kraft"] .progress__link { height: 7px; background: var(--rule-h-fine) repeat-x center; background-size: 71px 7px; }
:root[data-skin="kraft"] .nav { background: var(--rule-h) repeat-x bottom left; background-size: 96px 9px; padding-bottom: 9px; }
:root[data-skin="kraft"] .foot { background: var(--rule-h) repeat-x top left; background-size: 96px 9px; padding-top: 21px; }
:root[data-skin="kraft"] .rail { background: var(--rule-v) repeat-y top left; background-size: 9px 96px; padding-left: 27px; }
:root[data-skin="kraft"] th { background: var(--rule-h) repeat-x bottom left; background-size: 96px 9px; }
:root[data-skin="kraft"] td,
:root[data-skin="kraft"] .leg,
:root[data-skin="kraft"] .checklist li { background: var(--rule-h-fine) repeat-x bottom left; background-size: 71px 7px; }
:root[data-skin="kraft"] .day,
:root[data-skin="kraft"] .capability { background: var(--rule-h-fine) repeat-x top left; background-size: 71px 7px; }
:root[data-skin="kraft"] .basis--warn { background: var(--rule-v) repeat-y top left; background-size: 9px 96px; padding-left: 15px; }

@media (max-width: 820px) {
  :root[data-skin="kraft"] .rail { background: var(--rule-h) repeat-x top left; background-size: 96px 9px; padding-left: 0; padding-top: 22px; }
}

/* ---- 4. no boxes. paper is torn out. -------------------------------------- */

:root[data-skin="kraft"] .masthead,
:root[data-skin="kraft"] .stub,
:root[data-skin="kraft"] .notice,
:root[data-skin="kraft"] .empty,
:root[data-skin="kraft"] .report,
:root[data-skin="kraft"] .choice,
:root[data-skin="kraft"] .install,
:root[data-skin="kraft"] .day,
:root[data-skin="kraft"] .btn,
:root[data-skin="kraft"] .go,
:root[data-skin="kraft"] .chip,
:root[data-skin="kraft"] .confidence,
:root[data-skin="kraft"] .leg__mode,
:root[data-skin="kraft"] .bar,
:root[data-skin="kraft"] input[type="text"],
:root[data-skin="kraft"] input[type="number"],
:root[data-skin="kraft"] input[type="date"],
:root[data-skin="kraft"] select,
:root[data-skin="kraft"] textarea {
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
:root[data-skin="kraft"] .masthead,
:root[data-skin="kraft"] .stub,
:root[data-skin="kraft"] .empty,
:root[data-skin="kraft"] .install { -webkit-mask-image: var(--torn-a); mask-image: var(--torn-a); }
:root[data-skin="kraft"] .notice,
:root[data-skin="kraft"] .report,
:root[data-skin="kraft"] .day,
:root[data-skin="kraft"] .choice { -webkit-mask-image: var(--torn-b); mask-image: var(--torn-b); }
:root[data-skin="kraft"] .btn,
:root[data-skin="kraft"] .go,
:root[data-skin="kraft"] .chip,
:root[data-skin="kraft"] .confidence,
:root[data-skin="kraft"] .leg__mode,
:root[data-skin="kraft"] .bar,
:root[data-skin="kraft"] input[type="text"],
:root[data-skin="kraft"] input[type="number"],
:root[data-skin="kraft"] input[type="date"],
:root[data-skin="kraft"] select,
:root[data-skin="kraft"] textarea {
  -webkit-mask-image: var(--torn-c); mask-image: var(--torn-c);
}

/* The SELECTED option, restated after the repaint above.
 *
 * The layer paints every paper block `--kraft-light`, which is more specific
 * than `.choice[aria-pressed="true"]` and so silently killed its inked
 * background — while leaving the pale `--paper` TEXT that background existed to
 * carry. The chosen setting has therefore been near-invisible (1.26:1) since
 * this layer shipped, and no token-level test could see it: both colours are
 * correct, it is the pairing that broke. Found by sampling what the browser
 * actually painted. */
:root[data-skin="kraft"] .choice[aria-pressed="true"] { background-color: var(--ink); color: var(--paper); }
:root[data-skin="kraft"] .choice[aria-pressed="true"] .choice__hint { color: var(--kraft-light); }

/* A torn scrap needs to BE paper, or the tear has nothing to show. */
:root[data-skin="kraft"] .masthead,
:root[data-skin="kraft"] .stub,
:root[data-skin="kraft"] .notice,
:root[data-skin="kraft"] .empty,
:root[data-skin="kraft"] .report,
:root[data-skin="kraft"] .choice,
:root[data-skin="kraft"] .install,
:root[data-skin="kraft"] .day { background-color: var(--kraft-light); }
:root[data-skin="kraft"] .day { background-image: none; }
:root[data-skin="kraft"] input[type="text"],
:root[data-skin="kraft"] input[type="number"],
:root[data-skin="kraft"] input[type="date"],
:root[data-skin="kraft"] select,
:root[data-skin="kraft"] textarea,
:root[data-skin="kraft"] .chip,
:root[data-skin="kraft"] .btn,
:root[data-skin="kraft"] .confidence,
:root[data-skin="kraft"] .leg__mode { background-color: var(--paper); }
:root[data-skin="kraft"] .chip[aria-pressed="true"],
:root[data-skin="kraft"] .chip--pinned { background-color: var(--ink); color: var(--kraft-light); }
:root[data-skin="kraft"] .go { background-color: var(--stamp-red); color: var(--kraft-light); }
:root[data-skin="kraft"] .go:hover { background-color: var(--ink); }
:root[data-skin="kraft"] .btn:hover { background-color: var(--ink); color: var(--kraft-light); }
:root[data-skin="kraft"] .bar { background-color: var(--paper); }

/* Fields are filled-in blanks, so they carry a drawn baseline as well as the
 * torn edge — without it a borderless input stops reading as somewhere to
 * type, and an unrecognisable form is a worse sin than a straight line. */
:root[data-skin="kraft"] input[type="text"],
:root[data-skin="kraft"] input[type="number"],
:root[data-skin="kraft"] input[type="date"],
:root[data-skin="kraft"] select,
:root[data-skin="kraft"] textarea {
  background-image: var(--rule-h-fine);
  background-repeat: repeat-x;
  background-position: bottom left;
  background-size: 71px 7px;
  padding: 10px 13px 12px;
}
:root[data-skin="kraft"] input:focus-visible,
:root[data-skin="kraft"] select:focus-visible,
:root[data-skin="kraft"] textarea:focus-visible,
:root[data-skin="kraft"] button:focus-visible,
:root[data-skin="kraft"] a:focus-visible {
  outline: 2px solid var(--stamp-blue);
  outline-offset: 3px;
}

/* ---- 5. pasted in by hand ------------------------------------------------- */

:root[data-skin="kraft"] .wrap { padding: 0 16px 90px 25px; }
:root[data-skin="kraft"] .masthead { padding: 25px 29px 21px 24px; }
:root[data-skin="kraft"] .stub__body { padding: 16px 22px 20px 17px; }
:root[data-skin="kraft"] .stub__side { padding: 17px 12px 14px 15px; }
:root[data-skin="kraft"] .notice { padding: 12px 19px 14px 14px; }
:root[data-skin="kraft"] .editorial { gap: 29px 39px; }
:root[data-skin="kraft"] .field { margin: 32px 0 0 3px; }
:root[data-skin="kraft"] th,
:root[data-skin="kraft"] td { padding: 8px 12px 7px 9px; }

:root[data-skin="kraft"] .masthead { transform: rotate(-0.35deg); }
:root[data-skin="kraft"] .masthead__art { transform: rotate(-1.6deg) scale(1.03); }
:root[data-skin="kraft"] .stub { transform: rotate(-0.6deg); margin: 34px 9px 31px 0; }
:root[data-skin="kraft"] .rail .stub { transform: rotate(0.9deg); margin: 0 0 0 5px; }
:root[data-skin="kraft"] .day:nth-of-type(odd) { transform: rotate(0.55deg); padding-left: 9px; }
:root[data-skin="kraft"] .day:nth-of-type(even) { transform: rotate(-0.7deg); padding-left: 2px; }
:root[data-skin="kraft"] .notice:nth-of-type(odd) { transform: rotate(0.45deg); margin-left: 7px; }
:root[data-skin="kraft"] .notice:nth-of-type(even) { transform: rotate(-0.6deg); margin-right: 11px; }
:root[data-skin="kraft"] .empty { transform: rotate(-0.5deg); }
:root[data-skin="kraft"] .empty--art img { transform: rotate(-2.4deg); }
:root[data-skin="kraft"] .report { transform: rotate(0.35deg); }
:root[data-skin="kraft"] .btn { transform: rotate(-0.8deg); }
:root[data-skin="kraft"] .go { transform: rotate(0.6deg); }

:root[data-skin="kraft"] .chip:nth-child(7n+1) { transform: rotate(-2.4deg); }
:root[data-skin="kraft"] .chip:nth-child(7n+2) { transform: rotate(1.7deg); }
:root[data-skin="kraft"] .chip:nth-child(7n+3) { transform: rotate(-1.1deg); }
:root[data-skin="kraft"] .chip:nth-child(7n+4) { transform: rotate(2.6deg); }
:root[data-skin="kraft"] .chip:nth-child(7n+5) { transform: rotate(-1.9deg); }
:root[data-skin="kraft"] .chip:nth-child(7n+6) { transform: rotate(0.9deg); }
:root[data-skin="kraft"] .chip:nth-child(7n+7) { transform: rotate(-2.9deg); }

/* ---- 6. known-good behaviour this layer must not break -------------------- */

:root[data-skin="kraft"] .stub { grid-template-columns: minmax(118px, max-content) minmax(0, 1fr); }
/* ...but NOT in the rail, which is only ~228px wide. The rule above outranks
 * the base `.rail .stub` single-column rule, so a 118px side column was taking
 * more than half the rail and the body wrapped one word per line — "OFFLINE"
 * and "converter" were clipped outright. The base already knew this; the layer
 * silently undid it, and only a screenshot showed it. */
:root[data-skin="kraft"] .rail .stub { grid-template-columns: minmax(0, 1fr); }
:root[data-skin="kraft"] .stub__side { min-width: 0; background: var(--paper); }
/* The torn mask clips its own card, which ate half the slapped stamp. It sits
 * on the paper now — which is where a rubber stamp goes anyway. */
:root[data-skin="kraft"] .stub .stamp--slap { top: auto; bottom: 12px; right: 22px; }
/* The browser's focus ring on #view, which app.js focuses on every render so
 * screen readers land on the new screen. tabindex="-1" cannot be tabbed to, so
 * the ring tells a keyboard user nothing; every real control keeps its own. */
:root[data-skin="kraft"] main[tabindex="-1"]:focus,
:root[data-skin="kraft"] main[tabindex="-1"]:focus-visible { outline: none; }

@media (prefers-reduced-motion: reduce) {
  :root[data-skin="kraft"] .masthead,
:root[data-skin="kraft"] .masthead__art,
:root[data-skin="kraft"] .stub,
:root[data-skin="kraft"] .rail .stub,
:root[data-skin="kraft"] .day,
:root[data-skin="kraft"] .notice,
:root[data-skin="kraft"] .empty,
:root[data-skin="kraft"] .report,
:root[data-skin="kraft"] .chip,
:root[data-skin="kraft"] .btn,
:root[data-skin="kraft"] .go,
:root[data-skin="kraft"] .empty--art img { transform: none; }
}

/* ---- fixes found by rendering it ----------------------------------------- */

/* The torn edge ate the select's own dropdown arrow, which sits hard against
 * the right edge. Give it room inside the tear. */
:root[data-skin="kraft"] select { padding-right: 34px; }

/* The active nav item was the last hard rectangle on the page. */
:root[data-skin="kraft"] .nav button[aria-current="page"] {
  -webkit-mask-image: var(--torn-c);
  mask-image: var(--torn-c);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  padding-left: 19px;
  padding-right: 19px;
}

/* Thirty-one city chips packed edge to edge merged into one pale slab, which
 * is a box by another route. Space them so each reads as its own scrap. */
:root[data-skin="kraft"] .picker { gap: 9px 8px; }
:root[data-skin="kraft"] .chips { gap: 9px 8px; }

/* A stamp is inked ONTO something, and on the bare kraft ground the smallest
 * stamp text measured 3.18:1 — well under AA, and unreachable by a token-level
 * test because the ground came from `body`, not from the stamp. Giving it its
 * own paper is both the accessible fix and the more truthful one: nobody
 * franks a passport by stamping the air. */
:root[data-skin="kraft"] .stamp {
  background-color: color-mix(in srgb, var(--paper) 88%, transparent);
}

/* Stamps are rubber, and rubber does not print a clean rectangle either. */
:root[data-skin="kraft"] .stamp {
  -webkit-mask-image: var(--torn-c);
  mask-image: var(--torn-c);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* A torn edge eats 12–16px inward, so anything masked needs at least that much
 * padding or the tear takes a bite out of the words. It clipped "WHY THESE
 * CITIES" and the "≈ 930.35 GBP" line before this. The rule for anything that
 * gets a tear: pad past the bite, on every side. */
:root[data-skin="kraft"] .stub__body { padding: 22px 26px 44px 22px; }
:root[data-skin="kraft"] .stub__side { padding: 24px 18px 22px 24px; }
:root[data-skin="kraft"] .rail .stub .stub__body { padding: 26px 22px 24px 22px; }
:root[data-skin="kraft"] .masthead { padding: 30px 34px 26px 30px; }
:root[data-skin="kraft"] .notice { padding: 18px 24px 20px 20px; }
:root[data-skin="kraft"] .empty,
:root[data-skin="kraft"] .report,
:root[data-skin="kraft"] .choice { padding: 20px 24px 22px 20px; }
:root[data-skin="kraft"] .day { padding: 20px 6px 18px 4px; }
:root[data-skin="kraft"] .install { padding: 22px 26px 20px 24px; }

/* Extra bottom padding above buys the stamp clean paper to sit on, instead of
 * landing across the sentence that explains the buffer. */
:root[data-skin="kraft"] .stub .stamp--slap { bottom: 14px; right: 26px; }

/* ==========================================================================
   THE MODERN SKIN
   --------------------------------------------------------------------------
   An OPT-IN, reached from Settings. Kraft is still the default and still the
   brand; this exists because "I don't want a textured travel journal" is a real
   preference, and losing someone over a texture would be a poor trade.

   It is scoped entirely under `:root[data-skin="modern"]`, exactly as the
   scrapbook layer is scoped under `[data-skin="kraft"]`. The two layers never
   both apply, and neither leaks: tests/design.test.js fails on any rule in
   either block that is not scoped to its own skin.

   IT REUSES THE TOKEN NAMES ON PURPOSE. `--kraft` means "the page ground" and
   `--paper` means "raised surface" — the names are historical, the roles are
   not. Redefining the values rather than the vocabulary means every rule in the
   base already does the right thing here, and a new component gets both skins
   for free instead of needing a modern branch someone will forget to write.

   What genuinely differs, and all of it is deliberate:
     type      system sans, sentence case, real tabular figures for money
     shape     radius and soft elevation, where kraft has hard edges
     colour    cool neutral, where kraft is warm — the one axis kraft bans
     texture   none: no grain, no marks, no tears, no rotation
     stamps    demoted from motif to badge; they carry words, so they stay

   The design brief's five pinned decisions describe KRAFT. This skin breaks
   four of them, which is why it needed a decision of its own (D24) rather than
   a quiet override.
   ========================================================================== */

:root[data-skin="modern"] {
  color-scheme: light;

  --kraft: #f5f6f8;        /* page ground */
  --kraft-light: #ffffff;  /* raised surface */
  --paper: #ffffff;        /* card stock */
  --ink: #14161a;          /* 16.5:1 on white */
  --ink-soft: #55606e;     /* 6.4:1 on white, 5.9:1 on the page ground */
  --rule: #d7dbe0;         /* hairlines only — never carries text */
  --stamp-red: #b3261e;    /* 6.5:1 on white */
  --stamp-blue: #1a5fb4;   /* 6.3:1 on white; this is the link colour */
  --stamp-green: #1c6b3f;  /* 6.5:1 on white */

  /* One family for everything that is language, one for everything that is a
   * quantity. Money in a proportional face makes columns of digits wander,
   * which is the one thing this app cannot afford to be sloppy about. */
  --display: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --figures: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --radius: 10px;
  --radius-sm: 6px;
  --shadow: 0 1px 2px rgba(20, 22, 26, 0.06), 0 4px 14px rgba(20, 22, 26, 0.06);
}

/* Every text and surface token above has a counterpart here. That symmetry is
 * the guard: on the previous project a single missing dark override made text
 * invisible in dark mode only, while every light render and every string
 * assertion passed. */
:root[data-skin="modern"][data-theme="dark"] {
  color-scheme: dark;

  --kraft: #14161a;
  --kraft-light: #1c1f25;
  --paper: #1c1f25;
  --ink: #e8eaed;          /* 13.9:1 on the card */
  --ink-soft: #a8b0bb;     /* 7.5:1 on the card */
  --rule: #333944;
  --stamp-red: #ff8a80;    /* 7.2:1 */
  --stamp-blue: #8ab4f8;   /* 7.8:1 */
  --stamp-green: #7ee2a8;  /* 10.6:1 */

  --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 14px rgba(0, 0, 0, 0.34);
}

/* ---- 1. shape: rounded, elevated, untextured ----------------------------- */

/* The base sets `* { border-radius: 0 }` at specificity 0, which is the whole
 * point of the kraft brief. Restoring radius therefore has to be just as broad,
 * or every component that never anticipated a second skin keeps its hard edge. */
:root[data-skin="modern"] * { border-radius: var(--radius-sm); }

:root[data-skin="modern"] body {
  background-color: var(--kraft);
  background-image: none;  /* no paper grain */
}

:root[data-skin="modern"] .masthead,
:root[data-skin="modern"] .stub,
:root[data-skin="modern"] .notice,
:root[data-skin="modern"] .empty,
:root[data-skin="modern"] .report,
:root[data-skin="modern"] .choice,
:root[data-skin="modern"] .install,
:root[data-skin="modern"] .day {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: var(--shadow);
}

:root[data-skin="modern"] .masthead {
  border-bottom: 1px solid var(--rule);
  padding: 26px 28px 24px;
}
/* The ticket perforation is a kraft idea with nothing to say here. */
:root[data-skin="modern"] .masthead__perf { display: none; }
:root[data-skin="modern"] .masthead__tag { border-top-color: var(--rule); }

:root[data-skin="modern"] .stub__side {
  border-right: 1px solid var(--rule);
  border-radius: var(--radius) 0 0 var(--radius);
  background: var(--kraft);
}
/* In the rail, and on a phone, the stub stacks — so its side column runs along
 * the top instead of down the left, and the rounded corners have to follow it
 * or the card reads as two mismatched pieces. */
:root[data-skin="modern"] .rail .stub__side {
  border-right: none;
  border-bottom: 1px solid var(--rule);
  border-radius: var(--radius) var(--radius) 0 0;
}
@media (max-width: 820px) {
  :root[data-skin="modern"] .stub__side {
    border-right: none;
    border-bottom: 1px solid var(--rule);
    border-radius: var(--radius) var(--radius) 0 0;
  }
}

/* ---- 2. type: system sans for language, mono for quantities -------------- */

:root[data-skin="modern"] body { font-size: 15.5px; line-height: 1.6; }

/* Sentence case. ALL CAPS headings are a rail-signage idea; here they read as
 * shouting, and long city names get materially harder to scan. */
:root[data-skin="modern"] h1,
:root[data-skin="modern"] h2,
:root[data-skin="modern"] h3,
:root[data-skin="modern"] .display {
  font-family: var(--display);
  font-weight: 650;
  text-transform: none;
  letter-spacing: -0.015em;
  line-height: 1.18;
}
:root[data-skin="modern"] h1 { font-size: clamp(1.9rem, 4.6vw, 2.6rem); }
:root[data-skin="modern"] h2 { font-size: clamp(1.35rem, 2.8vw, 1.7rem); }
:root[data-skin="modern"] h3 { font-size: 1.05rem; letter-spacing: -0.005em; }

/* Anything that is a number keeps a real monospace and tabular figures, so a
 * column of prices lines up on the decimal. This is not a stylistic choice. */
:root[data-skin="modern"] .figure,
:root[data-skin="modern"] .stub__num,
:root[data-skin="modern"] table,
:root[data-skin="modern"] th,
:root[data-skin="modern"] td,
:root[data-skin="modern"] .confidence,
:root[data-skin="modern"] .bar {
  font-family: var(--figures);
  font-variant-numeric: tabular-nums;
}
:root[data-skin="modern"] .figure { font-size: 2.3rem; font-weight: 600; letter-spacing: -0.02em; }
:root[data-skin="modern"] .figure--sm { font-size: 1.5rem; }
:root[data-skin="modern"] .stub__num { font-size: 1.8rem; font-weight: 600; }

:root[data-skin="modern"] .nav button,
:root[data-skin="modern"] summary.label,
:root[data-skin="modern"] .field > label,
:root[data-skin="modern"] .field > p.label,
:root[data-skin="modern"] .field > span.label {
  font-family: var(--display);
  text-transform: none;
  letter-spacing: 0;
  font-weight: 600;
}

/* `.meta` stays a small uppercase label — that idiom is as much at home in a
 * modern interface as on a ticket — but the kraft tracking is loosened. */
:root[data-skin="modern"] .meta { letter-spacing: 0.07em; font-size: 0.72rem; }

/* ---- 3. navigation: a tab strip ------------------------------------------ */

:root[data-skin="modern"] .nav {
  border-top: none;
  border-bottom: 1px solid var(--rule);
  gap: 4px;
}
:root[data-skin="modern"] .nav button {
  border-right: none;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  font-size: 0.95rem;
  padding: 10px 14px;
}
:root[data-skin="modern"] .nav button[aria-current="page"] {
  background: transparent;
  color: var(--stamp-blue);
  box-shadow: inset 0 -2px 0 var(--stamp-blue);
}
:root[data-skin="modern"] .nav button:hover { background: var(--kraft-light); color: var(--ink); }

/* ---- 4. stamps: demoted from motif to badge ------------------------------ */

/* They are not deleted, because they carry WORDS — "PACKED", "BOOKED", "ROUTE
 * LOCKED" are state, not decoration, and a skin that silently dropped them
 * would be a skin that hides information. They stop being rubber stamps and
 * become what a modern interface calls the same thing: a pill. */
:root[data-skin="modern"] .stamp {
  width: auto;
  height: auto;
  min-width: 0;
  border: 1px solid var(--stamp-red);
  border-radius: 999px;
  transform: none;
  opacity: 1;
  padding: 4px 12px;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  background: color-mix(in srgb, var(--stamp-red) 9%, var(--paper));
}
:root[data-skin="modern"] .stamp--blue { transform: none; background: color-mix(in srgb, var(--stamp-blue) 9%, var(--paper)); }
:root[data-skin="modern"] .stamp--green { transform: none; background: color-mix(in srgb, var(--stamp-green) 9%, var(--paper)); }
:root[data-skin="modern"] .stamp--pending { border-style: dashed; border-width: 1px; background: transparent; opacity: 1; }
:root[data-skin="modern"] .stamp--sm { font-size: 0.62rem; padding: 3px 9px; border-width: 1px; }
:root[data-skin="modern"] .stamp--slap { position: static; transform: none; background: color-mix(in srgb, var(--stamp-green) 12%, var(--paper)); }
:root[data-skin="modern"] .divider .stamp { transform: none; }

/* ---- 5. rules: hairlines, not ink bars ----------------------------------- */

:root[data-skin="modern"] .divider::before,
:root[data-skin="modern"] .divider::after { height: 1px; background: var(--rule); }
:root[data-skin="modern"] .divider { margin: var(--gap-section) 0 var(--gap-wide); }
:root[data-skin="modern"] th { border-bottom: 1px solid var(--rule); font-family: var(--display); letter-spacing: 0.06em; }
:root[data-skin="modern"] .progress__link { background: var(--rule); }

:root[data-skin="modern"] .notice {
  border: 1px solid var(--rule);
  border-left: 4px solid var(--stamp-blue);
}
:root[data-skin="modern"] .notice--warn { border-left-color: var(--stamp-red); }
:root[data-skin="modern"] .notice--good { border-left-color: var(--stamp-green); }
:root[data-skin="modern"] .basis--warn { border-left: 3px solid var(--stamp-red); }

:root[data-skin="modern"] .rail {
  border-left: 1px solid var(--rule);
  padding-left: 26px;
}
@media (max-width: 820px) {
  :root[data-skin="modern"] .rail {
    border-left: none;
    border-top: 1px solid var(--rule);
    padding-left: 0;
    padding-top: 24px;
  }
}

/* ---- 6. controls --------------------------------------------------------- */

:root[data-skin="modern"] .btn,
:root[data-skin="modern"] .go,
:root[data-skin="modern"] .chip,
:root[data-skin="modern"] input[type="text"],
:root[data-skin="modern"] input[type="number"],
:root[data-skin="modern"] input[type="date"],
:root[data-skin="modern"] select,
:root[data-skin="modern"] textarea {
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  background: var(--paper);
  transform: none;
  font-family: var(--display);
  text-transform: none;
  letter-spacing: 0;
}
:root[data-skin="modern"] .go {
  background: var(--stamp-blue);
  border-color: var(--stamp-blue);
  color: #ffffff;
  font-weight: 600;
}
:root[data-skin="modern"][data-theme="dark"] .go { color: #14161a; }
:root[data-skin="modern"] .go:hover { background: color-mix(in srgb, var(--stamp-blue) 84%, #000); }
:root[data-skin="modern"] .btn:hover { background: var(--kraft-light); color: var(--ink); border-color: var(--ink-soft); }
:root[data-skin="modern"] .chip[aria-pressed="true"],
:root[data-skin="modern"] .chip--pinned {
  background: var(--stamp-blue);
  border-color: var(--stamp-blue);
  color: #ffffff;
}
:root[data-skin="modern"][data-theme="dark"] .chip[aria-pressed="true"],
:root[data-skin="modern"][data-theme="dark"] .chip--pinned { color: #14161a; }

/* Same trap as the scrapbook layer: the card rule above repaints `.choice`
 * white and outranks `.choice[aria-pressed="true"]`, leaving pale-on-pale. The
 * selected option has to be unmistakable — it is the only thing on the screen
 * telling you what you currently have. */
:root[data-skin="modern"] .choice[aria-pressed="true"] {
  background-color: color-mix(in srgb, var(--stamp-blue) 12%, var(--paper));
  border-color: var(--stamp-blue);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--stamp-blue);
}
:root[data-skin="modern"] .choice[aria-pressed="true"] .choice__hint { color: var(--ink-soft); }

:root[data-skin="modern"] input:focus-visible,
:root[data-skin="modern"] select:focus-visible,
:root[data-skin="modern"] textarea:focus-visible,
:root[data-skin="modern"] button:focus-visible,
:root[data-skin="modern"] a:focus-visible {
  outline: 2px solid var(--stamp-blue);
  outline-offset: 2px;
}
/* app.js focuses #view on every render so screen readers land on the new
 * screen; tabindex="-1" cannot be tabbed to, so the ring tells a keyboard user
 * nothing and merely draws a box round the whole page. */
:root[data-skin="modern"] main[tabindex="-1"]:focus,
:root[data-skin="modern"] main[tabindex="-1"]:focus-visible { outline: none; }

/* ---- 7. the one illustrated surface -------------------------------------- */

/* The rail print is drawn against kraft paper and cannot be recoloured, so
 * under this skin it sits in a frame rather than pretending to be printed on
 * the page. */
:root[data-skin="modern"] .masthead__art img,
:root[data-skin="modern"] .empty--art img {
  transform: none;
  border-radius: var(--radius-sm);
}
