/* Design tokens first, so a colour is changed in one place.

   What this replaced: Bootstrap 5 plus a Font Awesome kit plus a 13-line
   stylesheet, with every colour written as style="color: #7099ca" on the
   element - about sixty times on the landing page alone. Changing the brand
   blue meant sixty edits, and two of them would have been missed.

   Light only, deliberately: there is no theme switcher, and a dark palette that
   nothing can select is dead code. Add one here if a toggle is ever built. */
:root {
  /* The logo's blue, measured off the original artwork rather than eyeballed:
     logo.png is one ink, #004CA2, painted at about 57% alpha, which composites
     on white to #769FCD with the paler parts landing on #B9D7EA. Those are the
     two values tools/build_brand.py draws the mark in, so the stylesheet and
     the SVG cannot disagree about the brand.

     Neither can carry text: white on #769FCD measures 2.9:1 and #769FCD on
     white 3.0:1, against the 4.5:1 a label needs. So the interface takes the
     logo's hue at a step dark enough to read (6.4:1 on white) and the logo's
     own colours stay where they are only ever a fill: the tints below, and the
     hero's wash. */
  --brand: #2c5f92;
  --brand-dark: #245079;
  --brand-light: #dce9f5;
  --brand-tint: #f2f7fc;
  --logo-blue: #769fcd;
  --logo-blue-pale: #b9d7ea;

  --ink: #161d26;
  --ink-muted: #66737f;
  --ink-faint: #96a2ad;

  --surface: #ffffff;
  --canvas: #f6f8fb;
  --line: #e5e9ef;
  --line-strong: #d2d9e2;
  --line-soft: #eef1f5;

  --ok: #157347;
  --ok-bg: #e6f4ec;
  --warn: #b45309;
  --warn-ink: #92400e;
  --warn-bg: #fdf6ec;
  --danger: #b42318;
  --danger-bg: #fdf1f0;

  --radius: 14px;
  --radius-sm: 9px;
  --gap: 20px;
  --shadow: 0 1px 2px rgb(16 24 40 / 0.04), 0 1px 3px rgb(16 24 40 / 0.06);
  --shadow-lg: 0 12px 30px rgb(16 24 40 / 0.12);

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

  /* The reading column. One number, so the header, the footer and every section
     line up down the page instead of each choosing its own width. */
  --measure: 1180px;
}

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

body {
  margin: 0;
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink);
  background: var(--surface);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { line-height: 1.25; margin: 0 0 8px; font-weight: 650; letter-spacing: -0.01em; }
h1 { font-size: clamp(1.9rem, 1.3rem + 2vw, 3rem); }
h2 { font-size: clamp(1.4rem, 1.1rem + 1.1vw, 2rem); }
h3 { font-size: 1.12rem; }
p { margin: 0 0 12px; }

a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: 4px;
}

code { font-family: var(--mono); font-size: 0.92em; background: var(--canvas); padding: 1px 5px; border-radius: 4px; }

.muted { color: var(--ink-muted); }
.tiny { font-size: 0.82rem; }
.mono { font-family: var(--mono); font-size: 0.92em; }
.lede { font-size: 1.08rem; color: var(--ink-muted); }
.center { text-align: center; }

/* The standard clip-out-of-view rule. `display:none` would take the text out of
   the accessibility tree too, which is the opposite of what it is for. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* --- Icons -------------------------------------------------------------------
   overflow: visible is not decoration. Most Bootstrap glyphs are drawn flush to
   the edge of their 0 0 16 16 viewBox, and an <svg> clips at its viewport by
   default - so at a fractional em size the outermost sliver of a circle is cut
   rather than antialiased, and a ring loses its bottom edge. */
.icon {
  width: 1em; height: 1em;
  fill: currentColor;
  flex: none;
  vertical-align: -0.125em;
  overflow: visible;
}

/* A flag in the switcher and the language tabs. Fixed aspect, a hairline border
   so a white flag on a white ground still has an edge. */
.flag {
  width: 18px; height: 12.6px; flex: none;
  border-radius: 2px;
  box-shadow: 0 0 0 1px rgb(16 24 40 / 0.12);
}

.brand-logo { display: block; height: 38px; width: auto; max-width: 100%; }
@media (max-width: 720px) { .brand-logo { height: 30px; } }

/* --- Buttons ----------------------------------------------------------------
   One shape for links and buttons alike, so "visit the site" and "save" are not
   two different-looking things doing the same job. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 18px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  font: inherit; font-size: 0.92rem; font-weight: 560;
  cursor: pointer; white-space: nowrap;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.08s ease, box-shadow 0.15s ease;
}
.btn:hover { background: var(--brand-tint); border-color: var(--brand-light); text-decoration: none; }
/* Presses down rather than growing: a button that gets bigger under the cursor
   moves the thing next to it. */
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn.is-disabled { opacity: 0.5; cursor: not-allowed; }

/* Both ends of the gradient clear 6.4:1 against the white label, so the sheen
   costs nothing in legibility. */
.btn-primary {
  background: linear-gradient(180deg, var(--brand), var(--brand-dark));
  border-color: var(--brand-dark);
  color: #fff;
  box-shadow: 0 1px 2px rgb(36 80 121 / 0.25);
}
.btn-primary:hover { background: var(--brand-dark); border-color: var(--brand-dark); box-shadow: 0 4px 12px rgb(36 80 121 / 0.28); }
.btn-ghost { border-color: transparent; background: transparent; }
.btn-ghost:hover { background: var(--brand-tint); }
.btn-danger { color: var(--danger); border-color: #e8c4c1; }
.btn-danger:hover { background: var(--danger-bg); border-color: #e8c4c1; }
.btn-block { width: 100%; }
.btn-sm { padding: 6px 12px; font-size: 0.84rem; }
.btn-lg { padding: 13px 24px; font-size: 1rem; }

/* --- Cards ------------------------------------------------------------------ */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.card.narrow { max-width: 520px; }
.card-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 15px 20px; border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, var(--brand-tint), var(--surface));
  border-radius: var(--radius) var(--radius) 0 0;
}
.card-head h2 { font-size: 1rem; margin: 0; display: flex; align-items: center; gap: 8px; }
.card-body { padding: 20px; }
.card-body.tight { padding: 0; }

/* --- Chips ------------------------------------------------------------------ */
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px; border-radius: 999px;
  background: var(--canvas); border: 1px solid var(--line);
  font-size: 0.74rem; font-weight: 600; letter-spacing: 0.02em;
  color: var(--ink-muted);
}
.chip-live { background: var(--ok-bg); border-color: #bfe3cd; color: var(--ok); }
.chip-draft { background: var(--warn-bg); border-color: #f0dcc0; color: var(--warn-ink); }
.chip-warn { background: var(--warn-bg); border-color: #f0dcc0; color: var(--warn-ink); }
a.chip:hover { text-decoration: none; border-color: var(--brand-light); }

/* --- Forms ------------------------------------------------------------------ */
.field { margin-bottom: 16px; }
.field > label {
  display: flex; align-items: center; gap: 7px;
  font-size: 0.86rem; font-weight: 600; margin-bottom: 6px; color: var(--ink);
}
.field-icon { color: var(--ink-faint); }
.req { color: var(--danger); }
.control, input[type="text"], input[type="email"], input[type="password"],
input[type="search"], input[type="url"], input[type="tel"],
input[type="datetime-local"], input[type="date"], select, textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit; font-size: 0.95rem;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.control:focus, input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-light);
}
textarea { resize: vertical; min-height: 96px; line-height: 1.6; }
.field.has-error .control, .field.has-error input, .field.has-error textarea { border-color: var(--danger); }
.field-help { font-size: 0.79rem; color: var(--ink-muted); margin: 5px 0 0; }
.field-error {
  display: flex; align-items: flex-start; gap: 6px;
  font-size: 0.8rem; color: var(--danger); margin: 5px 0 0;
}
/* A checkbox reads left to right: the input is inside its own label. */
.field input[type="checkbox"] { width: auto; margin: 0; }
/* A tag list rendered as checkboxes: a column of them is taller than the panel
   it sits in, so they wrap as chips instead. */
.field ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.field ul li label {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border: 1px solid var(--line-strong); border-radius: 999px;
  font-size: 0.84rem; cursor: pointer;
}
.field-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }
@media (max-width: 620px) { .field-row { grid-template-columns: 1fr; } }
.form-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 8px; }

/* --- Alerts and toasts ------------------------------------------------------ */
.alert {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 11px 14px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--canvas);
  font-size: 0.9rem; margin-bottom: 16px;
}
.alert-error { background: var(--danger-bg); border-color: #f0cfcc; color: var(--danger); }
.alert-warning { background: var(--warn-bg); border-color: #f0dcc0; color: var(--warn-ink); }

.toasts {
  position: fixed; top: 16px; right: 16px; z-index: 60;
  display: flex; flex-direction: column; gap: 10px;
  max-width: min(420px, calc(100vw - 32px));
}
.toast {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px;
  background: var(--surface); border: 1px solid var(--line-strong);
  border-left: 3px solid var(--ink-faint);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
  font-size: 0.9rem;
}
.toast-success { border-left-color: var(--ok); }
.toast-success .toast-icon { color: var(--ok); }
.toast-error { border-left-color: var(--danger); }
.toast-error .toast-icon { color: var(--danger); }
.toast-warning { border-left-color: var(--warn); }
.toast-warning .toast-icon { color: var(--warn); }
.toast-info .toast-icon { color: var(--brand); }
.toast span { flex: 1; }
.toast-close {
  border: none; background: transparent; cursor: pointer;
  color: var(--ink-faint); padding: 2px; line-height: 1;
}
.toast-close:hover { color: var(--ink); }
.toast.is-going { opacity: 0; transform: translateX(12px); transition: opacity 0.3s ease, transform 0.3s ease; }

/* --- Tables ----------------------------------------------------------------- */
/* The wrapper scrolls, not the page: a wide table inside a card otherwise pushes
   the whole document sideways on a phone. */
.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; font-size: 0.92rem; }
.table th, .table td { padding: 11px 16px; text-align: left; vertical-align: middle; }
.table thead th {
  font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--ink-muted); background: var(--canvas);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.table tbody tr + tr td { border-top: 1px solid var(--line-soft); }
.table tbody tr:hover { background: var(--brand-tint); }
.row-actions { display: flex; gap: 6px; align-items: center; }
.row-actions form { margin: 0; }

/* --- Empty states ----------------------------------------------------------- */
.empty {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 48px 24px; text-align: center; color: var(--ink-muted);
}
.empty .icon { color: var(--ink-faint); }

/* --- The language switcher --------------------------------------------------
   A segmented control: a small group where every option is visible and the
   current one is lifted out of the trough. Same shape as the qrcode and
   invoices projects, so the three sites read as one family. */
.lang-switch { display: inline-flex; align-items: center; margin: 0; }
.lang-options {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 3px; background: var(--canvas);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.lang-option {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 8px; border: none; border-radius: 6px;
  background: transparent; color: var(--ink-muted);
  font: inherit; font-size: 0.74rem; font-weight: 700; letter-spacing: 0.04em;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.lang-option:hover { background: var(--brand-tint); color: var(--ink); }
.lang-option.is-active { background: var(--surface); color: var(--brand); box-shadow: var(--shadow); }

/* --- The cookie disclosure --------------------------------------------------
   A bar at the foot of the window rather than a modal over the page: it is a
   disclosure, and blocking the page to make somebody read one is a dark pattern
   in its own right. */
/* The cookie bar, arranged as the invoicing app's is: a band across the foot of
   the window rather than a card floating over it. A floating card has to hold a
   rounded corner, a gap and a shadow away from every edge, and at that size the
   privacy summary hanging off it never had anywhere to go. A band gives the
   summary the full width of the page to open into. */
.cookie-notice {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 70;
  background: var(--surface);
  border-top: 1px solid var(--line-strong);
  box-shadow: 0 -6px 28px rgb(16 24 40 / 0.09);
}
.cookie-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 22px; padding: 14px 24px;
  max-width: var(--measure); margin: 0 auto;
}
.cookie-text strong { display: flex; align-items: center; gap: 7px; font-size: 0.94rem; }
.cookie-text p { margin: 4px 0 0; font-size: 0.85rem; color: var(--ink-muted); max-width: 68ch; }
.cookie-actions { display: flex; align-items: center; gap: 12px; flex: none; }
.cookie-actions form { display: flex; gap: 8px; margin: 0; }
.cookie-rechoose { display: flex; gap: 10px; }

/* The privacy summary that opens out of the bar. CSS only - it opens on hover
   and on focus-within, so a keyboard reaches it with Tab and a touch screen,
   which has no hover at all, opens it by tapping the button. */
.privacy-hover { position: relative; }

.privacy-trigger {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font: inherit; font-size: 0.84rem;
  color: var(--ink-muted);
  cursor: pointer;
}
.privacy-trigger:hover { color: var(--brand); border-color: var(--brand); }
.privacy-trigger svg { width: 13px; height: 13px; }

.privacy-card {
  position: absolute;
  bottom: calc(100% + 10px);
  /* Hung from the trigger's right edge rather than its left. The bar's row is
     right-aligned inside a 1180px measure, so at around 1024px the trigger sits
     far enough across that a 420px card anchored on its left ran off the side
     of the window. Anchored on the right it opens back over the bar, which is
     empty space, and there is nothing to the right of the trigger for it to run
     into. Below 780px it stops hanging off the trigger at all - see the media
     query at the end of this file. */
  right: 0;
  width: min(420px, calc(100vw - 40px));
  max-height: min(60vh, 520px);
  overflow-y: auto;
  padding: 18px 20px;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  transition: opacity 0.14s ease, transform 0.14s ease, visibility 0.14s;
  text-align: left;
}
.privacy-hover:hover .privacy-card,
.privacy-hover:focus-within .privacy-card {
  opacity: 1; visibility: visible; transform: translateY(0);
}
.privacy-card h3 { font-size: 0.95rem; margin: 0 0 12px; }
.privacy-card dl { margin: 0; }
.privacy-card dt {
  font-size: 0.78rem; font-weight: 680;
  text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink-faint);
  margin-top: 12px;
}
.privacy-card dt:first-of-type { margin-top: 0; }
.privacy-card dd { margin: 3px 0 0; font-size: 0.85rem; line-height: 1.5; }
.privacy-foot {
  margin: 14px 0 0; padding-top: 12px;
  border-top: 1px solid var(--line);
  font-size: 0.78rem; color: var(--ink-faint);
}

@media (max-width: 780px) {
  .cookie-inner { flex-direction: column; align-items: stretch; }
  .cookie-actions { justify-content: space-between; flex-wrap: wrap; }

  /* The card spans the bar rather than hanging off its trigger, which is a
     small button part-way across the row: anchored to that, a 420px card runs
     off the left of a 390px screen and half the privacy notice is unreadable,
     which is a poor thing for a privacy notice to be.

     Giving up `position: relative` here hands the containing block to
     `.cookie-notice` - itself fixed, and the full width of the window - so the
     gutters below are gutters against the viewport, and `bottom: 100%` clears
     the whole bar however many rows it has wrapped to. */
  .privacy-hover { position: static; }
  .privacy-card {
    left: 12px; right: 12px; width: auto;
    bottom: calc(100% + 10px);
    /* Shorter, because the bar underneath is taller once it stacks. The card
       scrolls inside itself, so this trims what is visible, not what is there. */
    max-height: 50vh;
  }
}

/* A phone. Four controls will not sit on one 350px row, so the row becomes two:
   the summary across the top, then the two answers side by side, each half the
   width and both an easy thumb's width tall.

   The "What is stored" link is the one thing dropped rather than rearranged.
   It is the third route to the same page from the same bar - the summary button
   opens the whole of it in place, and the footer links it from every page - and
   a bar asking a yes-or-no question should not spend a phone's width on a third
   way to read about it. */
@media (max-width: 520px) {
  .cookie-inner { padding: 12px 14px; gap: 10px; }
  .cookie-text p { font-size: 0.82rem; }
  .cookie-more { display: none; }
  .cookie-actions { width: 100%; gap: 10px; }
  .privacy-hover { flex: 1 1 100%; }
  .privacy-trigger { width: 100%; justify-content: center; }
  .cookie-actions form { flex: 1 1 auto; }
  .cookie-actions form .btn { flex: 1; }
}

/* Respect the system setting. Everything above uses transitions for feedback,
   not for meaning, so removing them costs nothing. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --- The date and time picker --------------------------------------------------
   The popup for `core/js/datepicker.js`. Anchored to the field rather than to
   the page, so it follows the input inside a scrolling column.

   It replaced `<input type="datetime-local">` on the demo form, whose Chrome
   popup is a calendar with no clock on it - the time is a faint `--:-- --` that
   reads as decoration, so the field looked like it refused to take a time. */
.datepicker-wrap { position: relative; }
/* Room for the calendar button inside the field's own box. `pointer-events` stay
   on the button alone - the input keeps the rest of the width, so clicking the
   text still puts the caret where it was clicked. */
.datepicker-wrap .control { padding-right: 38px; }
.datepicker-toggle {
  position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  border: none; border-radius: var(--radius-sm);
  background: transparent; color: var(--ink-faint); cursor: pointer;
}
.datepicker-toggle:hover { background: var(--brand-light); color: var(--brand); }
.datepicker-toggle .icon { width: 16px; height: 16px; }

.datepicker {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 20;
  width: 272px; padding: 12px;
  background: var(--surface); border: 1px solid var(--line-strong);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
}
.datepicker-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-bottom: 8px; font-size: 0.9rem;
}
/* Intl gives "august 2026" in some locales and "August 2026" in others. */
.datepicker-head strong { text-transform: capitalize; }
/* Two steps each side: a month, and a year. The year arrows are the double
   chevrons, which is the one pairing people already read as "further". */
.datepicker-navs { display: inline-flex; gap: 2px; }
.datepicker-nav {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-muted);
  font: inherit; line-height: 1; cursor: pointer;
}
.datepicker-nav .icon { width: 13px; height: 13px; }
.datepicker-nav:hover { background: var(--brand-tint); color: var(--brand); border-color: var(--brand-light); }

/* Seven columns, so the weekday row and the days share one grid and a month that
   starts on a Thursday cannot drift out of line with its own header. */
.datepicker-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.datepicker-weekday {
  text-align: center; padding: 4px 0;
  font-size: 0.66rem; font-weight: 600; letter-spacing: 0.02em;
  text-transform: uppercase; color: var(--ink-faint);
}
.datepicker-day {
  padding: 6px 0; border: none; border-radius: var(--radius-sm);
  background: transparent; color: var(--ink);
  font: inherit; font-size: 0.82rem; cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.datepicker-day:hover { background: var(--brand-tint); }
/* Today is outlined, the chosen day is filled: one is a reference point and the
   other is an answer, and they have to be told apart when they are the same day. */
.datepicker-day.is-today { box-shadow: inset 0 0 0 1px var(--line-strong); }
.datepicker-day.is-chosen { background: var(--brand); color: #fff; font-weight: 600; }

.datepicker-time {
  display: flex; align-items: center; gap: 6px;
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line);
}
/* `min-width: 0` as well as `flex: 1` - a <select> has an intrinsic minimum
   width that otherwise pushes the Done button out of the popup. */
.datepicker-time select {
  flex: 1; min-width: 0; padding: 6px 8px;
  font: inherit; font-size: 0.85rem;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink);
}
.datepicker-colon { color: var(--ink-faint); }
.datepicker-time > .icon { color: var(--ink-faint); flex: none; }
/* One button, and it confirms. Emptying the field is what the keyboard is for -
   a Clear beside a Done is a second decision on a row that has one. */
.datepicker-done {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 32px; height: 32px; padding: 0;
}
.datepicker-done .icon { width: 15px; height: 15px; }
