/* Skrá stylesheet — a "quiet register": Space Grotesk for the human hand
   (labels, headings), a monospace for the record (data values, tokens, counts),
   hairline rules, and a single pine accent. Hand-written component CSS with
   design tokens; no framework, no build step. Fonts are referenced without a
   version so a swap revalidates via ETag; app.css itself is loaded with a
   cache-busting ?v= from the template. */

@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/SpaceGrotesk-Medium-2.0.0.woff2") format("woff2");
}
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/SpaceGrotesk-Bold-2.0.0.woff2") format("woff2");
}

/* ---- Design tokens ---- */
:root {
  --font-ui: "Space Grotesk", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  --paper: #f1f3f0;
  --surface: #ffffff;
  --surface-sunken: #f7f8f6;
  --ink: #191c1a;
  --muted: #616763;
  --border: #e2e5df;
  --border-strong: #d2d6cf;
  --accent: #2f5d50;
  --accent-strong: #244a40;
  --accent-fg: #ffffff;
  --danger: #b3261e;
  /* Derived from the (possibly overridden) accent, so it adapts to any accent + surface. */
  --accent-wash: color-mix(in srgb, var(--accent) 14%, var(--surface));

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;

  --radius: 10px;
  --radius-sm: 6px;
  --maxw: 62rem;
  --ring: 0 0 0 3px var(--accent-wash);
}

/* Default dark palette: applied when the OS asks for dark (unless the user
   forced light) or when the user explicitly chose dark. Scoped away from the
   Solarized flavor, which brings its own dark palette below. */
:root[data-theme="dark"]:not([data-flavor="solarized"]),
:root[data-flavor="default"][data-theme="dark"] {
  --paper: #14161a;
  --surface: #1c2024;
  --surface-sunken: #171a1d;
  --ink: #e9ece9;
  --muted: #9aa19c;
  --border: #2e3338;
  --border-strong: #3a4046;
  --danger: #ef6b62;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-flavor="solarized"]) {
    --paper: #14161a;
    --surface: #1c2024;
    --surface-sunken: #171a1d;
    --ink: #e9ece9;
    --muted: #9aa19c;
    --border: #2e3338;
    --border-strong: #3a4046;
    --danger: #ef6b62;
  }
}

/* Default accent (no explicit accent chosen) is set explicitly per mode/flavor,
   since inference across combinations is fragile. Base :root already gives the
   light default (pine). Presets carry a data-accent value, so :not([data-accent])
   keeps them from being overridden. */
/* default flavor, dark */
:root[data-theme="dark"]:not([data-flavor="solarized"]):not([data-accent]) {
  --accent: #63b9a3; --accent-strong: #7ccbb5; --accent-fg: #0c1310;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-flavor="solarized"]):not([data-accent]) {
    --accent: #63b9a3; --accent-strong: #7ccbb5; --accent-fg: #0c1310;
  }
}
/* solarized flavor, both modes: the signature Solarized blue */
:root[data-flavor="solarized"]:not([data-accent]) {
  --accent: #268bd2; --accent-strong: #1f6fa8; --accent-fg: #ffffff;
}

/* ---- Solarized flavor (Ethan Schoonover's palette) ---- */
:root[data-flavor="solarized"] {
  --paper: #fdf6e3;      /* base3  */
  --surface: #fbf1d8;
  --surface-sunken: #eee8d5; /* base2 */
  --ink: #40525b;
  --muted: #93a1a1;      /* base1 */
  --border: #e6dfc6;
  --border-strong: #d9d0b4;
}
:root[data-flavor="solarized"][data-theme="dark"] {
  --paper: #002b36;      /* base03 */
  --surface: #073642;    /* base02 */
  --surface-sunken: #00252e;
  --ink: #93a1a1;        /* base1  */
  --muted: #6b7f88;      /* ~base00 */
  --border: #0f4553;
  --border-strong: #16576a;
}
@media (prefers-color-scheme: dark) {
  :root[data-flavor="solarized"]:not([data-theme="light"]) {
    --paper: #002b36;
    --surface: #073642;
    --surface-sunken: #00252e;
    --ink: #93a1a1;
    --muted: #6b7f88;
    --border: #0f4553;
    --border-strong: #16576a;
  }
}

/* ---- Accent presets (orthogonal to mode/flavor). --accent-wash derives. ----
   Brand tones use white text; the vivid neons and soft pastels use dark text
   for contrast on the accent. */
:root[data-accent="slate"]    { --accent: #3f5566; --accent-strong: #33475a; --accent-fg: #ffffff; }
:root[data-accent="indigo"]   { --accent: #4f46e5; --accent-strong: #4338ca; --accent-fg: #ffffff; }
:root[data-accent="lime"]     { --accent: #a3e635; --accent-strong: #84cc16; --accent-fg: #14210a; }
:root[data-accent="magenta"]  { --accent: #f0f; --accent-strong: #d400d4; --accent-fg: #1a001a; }
:root[data-accent="cyan"]     { --accent: #22d3ee; --accent-strong: #06b6d4; --accent-fg: #062a31; }
:root[data-accent="rose"]     { --accent: #f9a8d4; --accent-strong: #f472b6; --accent-fg: #4a1130; }
:root[data-accent="peach"]    { --accent: #fdba74; --accent-strong: #fb923c; --accent-fg: #3a1e07; }
:root[data-accent="lavender"] { --accent: #c4b5fd; --accent-strong: #a78bfa; --accent-fg: #241a4a; }

/* ---- Book colour cue ----
   A dot (and, on cards, a left spine) tinting contacts by which book they belong to.
   The bucket comes from the book's sequential id (see AddressBook.Color) and is emitted as a book-color-N class; the colours live here because the self-only CSP forbids inline styles.
   A vivid categorical palette ordered so consecutive buckets are maximally different, so books created back to back stay easy to tell apart; each colour has a brighter dark-mode value. */
.book-dot {
  display: inline-block;
  width: 0.7em;
  height: 0.7em;
  border-radius: 50%;
  background: var(--book-color, var(--muted));
  vertical-align: -0.06em;
  flex: 0 0 auto;
}
/* A larger read-only chip of the same colour, e.g. on the book edit page. */
.book-color-field { display: flex; align-items: center; gap: var(--space-2); }
.book-swatch { display: inline-block; width: 1.4rem; height: 1.4rem; border-radius: var(--radius-sm); background: var(--book-color, var(--muted)); border: 1px solid var(--border-strong); }
.book-color-0  { --book-color: #2f6fd0; } /* blue    */
.book-color-1  { --book-color: #ef7d1a; } /* orange  */
.book-color-2  { --book-color: #2e9e46; } /* green   */
.book-color-3  { --book-color: #e0403f; } /* red     */
.book-color-4  { --book-color: #9b59c7; } /* purple  */
.book-color-5  { --book-color: #caa213; } /* gold    */
.book-color-6  { --book-color: #17a8bd; } /* cyan    */
.book-color-7  { --book-color: #ec6aa8; } /* pink    */
.book-color-8  { --book-color: #9c6b4a; } /* brown   */
.book-color-9  { --book-color: #74b81f; } /* lime    */
.book-color-10 { --book-color: #d43fb0; } /* magenta */
.book-color-11 { --book-color: #647686; } /* slate   */
:root[data-theme="dark"] .book-color-0  { --book-color: #6fa3e8; }
:root[data-theme="dark"] .book-color-1  { --book-color: #ffa04d; }
:root[data-theme="dark"] .book-color-2  { --book-color: #6ed474; }
:root[data-theme="dark"] .book-color-3  { --book-color: #ff6f6b; }
:root[data-theme="dark"] .book-color-4  { --book-color: #c48ae8; }
:root[data-theme="dark"] .book-color-5  { --book-color: #f0d24a; }
:root[data-theme="dark"] .book-color-6  { --book-color: #54d6e6; }
:root[data-theme="dark"] .book-color-7  { --book-color: #ff97c8; }
:root[data-theme="dark"] .book-color-8  { --book-color: #c99878; }
:root[data-theme="dark"] .book-color-9  { --book-color: #a8de55; }
:root[data-theme="dark"] .book-color-10 { --book-color: #f072d6; }
:root[data-theme="dark"] .book-color-11 { --book-color: #9aa8b5; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .book-color-0  { --book-color: #6fa3e8; }
  :root:not([data-theme="light"]) .book-color-1  { --book-color: #ffa04d; }
  :root:not([data-theme="light"]) .book-color-2  { --book-color: #6ed474; }
  :root:not([data-theme="light"]) .book-color-3  { --book-color: #ff6f6b; }
  :root:not([data-theme="light"]) .book-color-4  { --book-color: #c48ae8; }
  :root:not([data-theme="light"]) .book-color-5  { --book-color: #f0d24a; }
  :root:not([data-theme="light"]) .book-color-6  { --book-color: #54d6e6; }
  :root:not([data-theme="light"]) .book-color-7  { --book-color: #ff97c8; }
  :root:not([data-theme="light"]) .book-color-8  { --book-color: #c99878; }
  :root:not([data-theme="light"]) .book-color-9  { --book-color: #a8de55; }
  :root:not([data-theme="light"]) .book-color-10 { --book-color: #f072d6; }
  :root:not([data-theme="light"]) .book-color-11 { --book-color: #9aa8b5; }
}

/* ---- Reset & base ---- */
*, *::before, *::after { box-sizing: border-box; }
/* Ensure the hidden attribute wins over component display rules (e.g. buttons
   set display:inline-flex, which would otherwise defeat [hidden]). */
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: var(--font-ui);
  font-weight: 500;
  line-height: 1.55;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}
img, svg { max-width: 100%; height: auto; }
a { color: var(--accent); text-underline-offset: 2px; }
a:hover { color: var(--accent-strong); }

/* Inline icons (Lucide): size to the current text, align to the baseline. */
.lucide { width: 1.15em; height: 1.15em; flex-shrink: 0; vertical-align: -0.18em; }

/* Recorded data reads as the ledger's record: monospaced. */
code, .data, a[href^="mailto:"], a[href^="tel:"] {
  font-family: var(--font-mono);
  font-size: 0.92em;
}

/* ---- Skip link ---- */
.skip-link {
  position: absolute;
  left: var(--space-3);
  top: -3rem;
  z-index: 20;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  transition: top 0.15s ease;
}
.skip-link:focus { top: var(--space-3); }

/* ---- Layout ---- */
.container {
  max-width: var(--maxw);
  margin-inline: auto;
  padding: var(--space-8) var(--space-4) var(--space-8);
}
.stack > * + * { margin-top: var(--space-5); }

/* ---- Typography ---- */
h1, h2, h3 { font-weight: 700; line-height: 1.15; letter-spacing: -0.015em; margin: 0 0 var(--space-3); }
h1 { font-size: clamp(1.6rem, 1.3rem + 1.4vw, 2.1rem); }
h2 { font-size: 1.3rem; margin-top: var(--space-6); }
h3 { font-size: 1.05rem; }
p { margin: 0 0 var(--space-3); }
.muted { color: var(--muted); }

/* Eyebrow: small uppercase mono label — the "field name" of the register. */
.eyebrow, .details dt, th, legend {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---- App bar ---- */
.appbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(1.4) blur(8px);
  border-bottom: 1px solid var(--border);
}
.appbar-right { display: flex; align-items: center; gap: var(--space-4); }
.appnav { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-4); }
.appnav a { display: inline-flex; align-items: center; gap: var(--space-1); color: var(--ink); text-decoration: none; }
.appnav a:hover { color: var(--accent); }
.appnav form { margin: 0; }

/* ---- Brand ---- */
.brand { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--ink); text-decoration: none; }
.brand-mark { height: 1.75rem; width: auto; color: var(--accent); display: block; }
.brand-word { font-weight: 700; font-size: 1.2rem; letter-spacing: -0.01em; }

/* Theme toggle: a standalone round icon control that carries no `.btn`, so the button tiers above never apply — this is its complete styling. */
.theme-toggle-form { margin: 0; }
.appbar button.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  min-height: 0;
  padding: 0;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
}
.appbar button.theme-toggle:hover { color: var(--accent); background: transparent; border-color: var(--border-strong); }
.theme-toggle svg { width: 1.15rem; height: 1.15rem; }

/* ---- Action rows ---- */
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.actions form { margin: 0; }

/* ---- Buttons ----
   Every button carries `.btn`; that class is the styling base (default = the primary accent fill).
   Variants below are single classes that come later in source, so equal specificity resolves in their favour — no attribute-selector in the base, which is what let a bare `.btn-secondary` lose the specificity race and render as the primary fill.
   Three tiers: primary (`.btn`) for the one main action, secondary (`.btn-secondary`) for supporting actions with a visible edge, and ghost (`.btn-ghost`) for the quiet, integrated actions that should recede until hovered.
   `.btn-danger` marks the destructive intent in either the solid (standalone) or ghost (`.btn-ghost.btn-danger`) form. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font: inherit;
  font-weight: 700;
  line-height: 1.2;
  gap: var(--space-2);
  cursor: pointer;
  padding: var(--space-2) var(--space-4);
  min-height: 2.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-fg);
  text-decoration: none;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease, transform 0.05s ease;
}
.btn:hover { background: var(--accent-strong); color: var(--accent-fg); }
.btn:active { transform: translateY(1px); }
.btn-secondary {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border-strong);
}
.btn-secondary:hover { background: var(--surface-sunken); color: var(--ink); border-color: var(--muted); }
/* Ghost: no fill or edge at rest, so rows of supporting actions read as quiet text-with-icon; a faint surface and the accent surface only on hover/focus. */
.btn-ghost { background: transparent; color: var(--muted); border-color: transparent; }
.btn-ghost:hover { background: var(--surface-sunken); color: var(--accent); border-color: transparent; }
.btn-danger { background: var(--danger); color: #fff; border-color: transparent; }
.btn-danger:hover { background: var(--danger); filter: brightness(0.92); color: #fff; }
/* Ghost-danger: a destructive action that stays out of the way until reached, then declares itself in red rather than shouting in solid fill at rest. */
.btn-ghost.btn-danger { background: transparent; color: var(--danger); border-color: transparent; filter: none; }
.btn-ghost.btn-danger:hover { background: color-mix(in srgb, var(--danger) 10%, var(--surface)); color: var(--danger); border-color: var(--danger); }

/* ---- Focus ---- */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
input:focus-visible, select:focus-visible, textarea:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); }

/* ---- Forms ---- */
form.stack { max-width: 26rem; }
label { display: block; font-size: 0.9rem; color: var(--ink); }
label > input, label > select, label > textarea { margin-top: var(--space-1); }
input, select, textarea {
  width: 100%;
  font: inherit;
  font-size: 1rem; /* >=16px so iOS doesn't zoom on focus */
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}
input::placeholder, textarea::placeholder { color: color-mix(in srgb, var(--muted) 80%, transparent); }
/* Inline checkbox row (e.g. birthday "no year"): box beside its label, not full-width. */
label.checkbox { display: flex; flex-direction: row; align-items: center; gap: var(--space-2); font-weight: 500; }
label.checkbox input { width: auto; margin-top: 0; }
/* Non-editable fields (e.g. the fixed username) read as locked on hover. */
input:disabled, select:disabled, textarea:disabled { cursor: not-allowed; color: var(--muted); background: var(--surface-sunken); }

fieldset { border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-4); margin: 0; background: var(--surface-sunken); }
legend { padding: 0 var(--space-2); }

/* Repeatable field group: rows plus a floating material "+" straddling the
   bottom-right corner (translated so it sits over the border). */
.field-group { position: relative; padding-bottom: var(--space-6); }
.fab-add {
  position: absolute;
  right: 0;
  bottom: 0;
  transform: translate(25%, 50%);
  width: 2.75rem;
  height: 2.75rem;
  min-height: 0;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-fg);
  cursor: pointer;
  box-shadow: 0 3px 10px -3px rgba(0, 0, 0, 0.45);
  transition: background 0.12s ease, box-shadow 0.12s ease;
}
.fab-add:hover { background: var(--accent-strong); box-shadow: 0 5px 14px -3px rgba(0, 0, 0, 0.5); }
.fab-add svg { width: 1.4rem; height: 1.4rem; }
/* The label rides along only where the FAB stretches into a full-width bar (narrow screens); on desktop the round FAB is icon-only, named via its aria-label/title. */
.fab-label { display: none; }

/* Birthday: full date picker or month/day selects, plus the "no year" toggle. */
.birthday-field { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.birthday-md { display: inline-flex; gap: var(--space-2); }
.birthday-field select, .birthday-field input[type="date"] { width: auto; }

/* Repeatable form rows (emails/phones/addresses/links). */
.field-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin-bottom: var(--space-2); }
.field-row select { width: auto; }
.field-row input { flex: 1; min-width: 8rem; }
.address-row { flex-wrap: wrap; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--space-3); background: var(--surface); }
/* Per-row remove control, pushed to the row's trailing edge. */
.row-remove {
  flex: 0 0 auto;
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  min-height: 0;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--muted);
  cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}
.row-remove:hover { color: var(--danger); border-color: var(--danger); }
.row-remove svg { width: 1.05rem; height: 1.05rem; }

/* ---- Alerts ---- */
[role="alert"] {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--danger);
  border-left-width: 4px;
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 8%, var(--surface));
}

/* ---- Toolbar (search + actions) ---- */
.toolbar { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: var(--space-3); margin-bottom: var(--space-4); }
[role="search"] { display: flex; gap: var(--space-2); margin: 0; }
[role="search"] input { width: auto; }

/* ---- Global search (dashboard) ---- */
.global-search { display: flex; gap: var(--space-2); margin: 0 0 var(--space-6); max-width: 34rem; }
.global-search input { flex: 1; }
#search-results:not(:empty) { margin-bottom: var(--space-6); }

/* Dashboard header: search fills two thirds, the stat badges stack in the last
   third; both drop to a single column on narrow screens. */
.dash-top { display: grid; grid-template-columns: 2fr 1fr; gap: var(--space-4); align-items: start; margin-bottom: var(--space-6); }
.dash-top .global-search { max-width: none; margin: 0; }
.dash-top .stats { justify-content: initial; gap: var(--space-3); margin: 0; }
.dash-top .stat { flex: 1; min-width: 0; }
@media (max-width: 40rem) { .dash-top { grid-template-columns: 1fr; } }

/* ---- Address-book page header ---- */
.book-header { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); }
.book-headings { min-width: 0; }
.book-headings h1 { margin-bottom: var(--space-1); }
.book-tools { display: flex; align-items: center; gap: var(--space-2); flex: 0 0 auto; }

/* More-actions menu: a native <details> disclosure styled as a dropdown; menu.js
   adds click-outside/Escape dismissal, but it works without JS. */
details.menu { position: relative; }
details.menu > summary { list-style: none; }
details.menu > summary::-webkit-details-marker { display: none; }
.menu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + var(--space-1));
  z-index: 20;
  min-width: 13rem;
  display: flex;
  flex-direction: column;
  padding: var(--space-1);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  box-shadow: 0 10px 28px -10px rgba(0, 0, 0, 0.4);
}
.menu-panel form { margin: 0; }
.menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.menu-item:hover { background: var(--surface-sunken); color: var(--accent); }
.menu-item-danger { color: var(--danger); }
.menu-item-danger:hover { background: color-mix(in srgb, var(--danger) 10%, var(--surface)); color: var(--danger); }

/* Full-width contact search with a leading magnifier inside the field. The extra
   bottom margin sets the contact list's "Contacts (N)" heading clearly apart. */
.book-search { display: flex; gap: var(--space-2); margin: var(--space-5) 0 var(--space-8); }
.search-field { position: relative; flex: 1; display: block; }
.search-field > svg { position: absolute; left: var(--space-3); top: 50%; transform: translateY(-50%); width: 1.1rem; height: 1.1rem; color: var(--muted); pointer-events: none; }
.search-field input { width: 100%; padding-left: 2.4rem; }

/* Controls row (primary add on the left, sort/size on the right) and footer (add + pager). */
.book-controls { display: flex; flex-wrap: wrap; align-items: end; gap: var(--space-3); margin-bottom: var(--space-4); }
/* Sort/size sit at the trailing edge whether or not the New contact button (which only managers see) is present. */
.book-controls .list-prefs { margin-bottom: 0; margin-left: auto; }
.book-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); margin-top: var(--space-5); }
.book-foot .pager { margin-top: 0; }

/* ---- Contact directory cards ---- */
.card-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: var(--space-4);
}
.contact-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color 0.12s ease, transform 0.12s ease;
}
.contact-card:hover { border-color: var(--accent); transform: translateY(-2px); }
/* "New address book" tile: a quiet dashed add-card as the last item of the book grid. */
.book-add-card { display: flex; }
.book-add-card a { display: flex; align-items: center; justify-content: center; gap: var(--space-2); width: 100%; min-height: 100%; padding: var(--space-4); border: 1px dashed var(--border-strong); border-radius: var(--radius); color: var(--muted); font-weight: 700; text-decoration: none; transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease; }
.book-add-card a:hover { border-color: var(--accent); color: var(--accent); background: var(--surface); }
.contact-card-head { display: flex; align-items: center; gap: var(--space-3); text-decoration: none; color: var(--ink); font-weight: 700; margin-bottom: var(--space-1); }
.contact-card-head:hover { color: var(--accent); }
.contact-card-name { overflow-wrap: anywhere; }
/* Book tag on a cross-book card (global search): a quiet dotted label. */
.card-book { display: inline-flex; align-items: center; gap: var(--space-1); margin-top: var(--space-1); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); text-decoration: none; }
.card-book:hover { color: var(--accent); }
/* Book name with its colour dot in the overview table. */
.book-name { display: inline-flex; align-items: center; gap: var(--space-2); }
/* Per-row manage actions in the overview table, aligned to the row's trailing
   edge. The icon buttons are sized to the text line so a row with actions is no
   taller than one without (which would otherwise jump the row height). */
.row-actions { display: flex; justify-content: flex-end; align-items: center; gap: var(--space-1); }
.row-actions form { margin: 0; }
.row-actions .btn { min-height: 0; height: 1.6rem; width: 1.6rem; padding: 0; }
.row-actions .btn svg { width: 1rem; height: 1rem; }

/* ---- Contact header + photo control ---- */
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
/* The photo file input is visually hidden; surface keyboard focus on its label. */
[data-photo-input]:focus-visible + label { outline: 2px solid var(--accent); outline-offset: 2px; }
.contact-header { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-5); margin-bottom: var(--space-5); }
.contact-headings h1 { margin-bottom: var(--space-1); }

/* Photo control: the avatar itself is the edit surface.
   A camera badge on its lower edge opens the picker; picking a file auto-submits (photo.js), so there is no separate Save step with JS.
   A trash badge (only when a photo exists) removes it.
   `.photo-form` uses display:contents so its children position against the avatar, not a form box. */
.photo-edit { display: flex; justify-content: center; }
.avatar-control { position: relative; display: inline-flex; }
.photo-form { display: contents; }
.photo-trigger, .photo-remove {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease, transform 0.05s ease;
}
.photo-trigger {
  right: 2px; bottom: 2px;
  width: 2.4rem; height: 2.4rem;
  background: var(--accent); color: var(--accent-fg);
  border: 2px solid var(--surface);
  box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.4);
}
.photo-trigger:hover { background: var(--accent-strong); }
.photo-trigger:active { transform: translateY(1px); }
.photo-trigger svg { width: 1.2rem; height: 1.2rem; }
/* Surface the visually-hidden file input's keyboard focus onto its label badge. */
[data-photo-input]:focus-visible + .photo-trigger { outline: 2px solid var(--accent); outline-offset: 2px; }
.photo-remove {
  right: 0; top: 0;
  width: 2rem; height: 2rem;
  background: var(--surface); color: var(--muted);
  border: 1px solid var(--border-strong);
}
.photo-remove:hover { color: var(--danger); border-color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, var(--surface)); }
.photo-remove svg { width: 1rem; height: 1rem; }
/* No-JS fallback: the Save button sits below the avatar (JS hides it and auto-submits instead).
   Absolutely placed so it never grows the control box that the badges are positioned against. */
.photo-save { position: absolute; top: calc(100% + var(--space-2)); left: 50%; transform: translateX(-50%); white-space: nowrap; }
.btn-sm { min-height: 2rem; padding: var(--space-1) var(--space-3); font-size: 0.85rem; }
/* Icon-only button: square, label carried by aria-label/title. */
.btn-icon { padding: var(--space-2); min-width: 2.5rem; gap: 0; }
.btn-icon svg { width: 1.15rem; height: 1.15rem; }

/* ---- Avatars ---- */
.avatar { border-radius: 50%; object-fit: cover; border: 1px solid var(--border); background: var(--surface-sunken); }
.avatar-sm { width: 52px; height: 52px; flex: 0 0 auto; }
.avatar-md { width: 96px; height: 96px; flex: 0 0 auto; font-size: 2.25rem; }
.avatar-lg { width: 128px; height: 128px; flex: 0 0 auto; font-size: 2.75rem; }
.avatar-placeholder {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 700;
  border: none;
}

/* ---- Record (definition list) ---- */
.details { display: grid; grid-template-columns: max-content 1fr; gap: 0 var(--space-6); margin: var(--space-4) 0; }
.details dt { padding: var(--space-3) 0 var(--space-1); border-top: 1px solid var(--border); align-self: end; }
.details dd { margin: 0; padding: var(--space-3) 0 var(--space-1); border-top: 1px solid var(--border); overflow-wrap: anywhere; }
.details dt:first-of-type, .details dd:first-of-type { border-top: none; }
.map-links { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1) var(--space-3); margin-top: var(--space-2); font-size: 0.85em; }
.map-links svg { width: 1em; height: 1em; color: var(--muted); }
.map-links a { color: var(--muted); text-decoration: none; }
.map-links a:hover { color: var(--accent); text-decoration: underline; }

/* Back link. */
.backlink { display: inline-flex; align-items: center; gap: var(--space-1); color: var(--muted); text-decoration: none; }
.backlink:hover { color: var(--accent); }

/* ---- List preferences (sort + page size) ---- */
.list-prefs { display: flex; flex-wrap: wrap; align-items: end; justify-content: flex-end; gap: var(--space-3); margin-bottom: var(--space-4); }
.list-prefs label { display: inline-flex; flex-direction: column; gap: var(--space-1); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted); }
.sort-control { display: inline-flex; gap: var(--space-2); margin-top: var(--space-1); }
.sort-control select { margin-top: 0; }
.sort-control .btn-icon { min-height: 2.5rem; }

/* ---- Pager ---- */
.pager { display: flex; align-items: center; gap: var(--space-4); margin-top: var(--space-5); }

/* ---- Cards & tables ---- */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-5) var(--space-6); }
/* Wide tables scroll horizontally within their own container rather than
   forcing the whole page to scroll sideways on small screens. */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: var(--space-3) var(--space-3); border-bottom: 1px solid var(--border); overflow-wrap: anywhere; }
th { padding-block: var(--space-2); }
tbody tr:hover { background: var(--surface-sunken); }
td form { margin: 0; }

/* ---- Responsive ---- */
@media (max-width: 40rem) {
  .container { padding: var(--space-5) var(--space-3) var(--space-8); }
  .appbar { flex-wrap: wrap; gap: var(--space-2) var(--space-3); }
  /* Collapse the nav to icons; labels are exposed via aria-label/title. */
  .appnav { gap: var(--space-4); font-size: 0.95rem; }
  .appnav .nav-label { display: none; }
  .details { grid-template-columns: 1fr; gap: 0; }
  .details dt { padding-bottom: 0; }
  .details dd { padding-top: var(--space-1); border-top: none; }
  .details dt:not(:first-of-type) { margin-top: var(--space-2); }
  .field-row { flex-direction: column; align-items: stretch; }
  .field-row select, .field-row input { width: 100%; }
  /* The corner FAB straddling a border is a desktop affordance; on narrow screens it becomes a clear, labelled full-width "Add …" bar in the flow. */
  .field-group { padding-bottom: 0; }
  .fab-add {
    position: static;
    transform: none;
    width: 100%;
    height: auto;
    min-height: 2.75rem;
    margin-top: var(--space-3);
    padding: var(--space-2) var(--space-4);
    gap: var(--space-2);
    border-radius: var(--radius-sm);
    box-shadow: none;
  }
  .fab-add svg { width: 1.15rem; height: 1.15rem; }
  .fab-label { display: inline; font-weight: 700; }
  th, td { padding: var(--space-2); }
  /* The toolbar's search and actions each take a full row. */
  .toolbar > form[role="search"] { display: flex; width: 100%; }
  .toolbar > form[role="search"] input { flex: 1; }
}

/* On touch devices, give interactive controls a comfortable tap target. */
@media (pointer: coarse) {
  .btn, button, select, input[type="file"] { min-height: 2.75rem; }
  .appnav a, .appnav button { min-height: 2.75rem; }
}

/* ---- Dashboard ---- */
.stats { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-4); margin-bottom: var(--space-5); }
.stat {
  display: flex; flex-direction: column;
  min-width: 8rem; padding: var(--space-4) var(--space-5);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
}
.stat-num { font-family: var(--font-mono); font-size: 2rem; font-weight: 700; line-height: 1; color: var(--accent); }
.stat-label { margin-top: var(--space-1); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted); }

/* Dashboard section headings get extra breathing room above them. */
.dash-heading { margin-top: var(--space-8); }

/* Upcoming birthdays: a row of portrait cards, the countdown as the hero. */
.bday-row { display: flex; flex-wrap: wrap; gap: var(--space-4); }
.bday-card {
  flex: 1 1 8rem;
  min-width: 8rem;
  max-width: 11rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-3) var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--ink);
  transition: border-color 0.12s ease, transform 0.12s ease;
}
.bday-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.bday-count {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3em;
  padding: var(--space-1) var(--space-3);
  border-radius: 999px;
  background: var(--accent-wash);
  color: var(--accent-strong);
}
.bday-count-num { font-family: var(--font-mono); font-size: 1.6rem; font-weight: 700; line-height: 1; }
.bday-count-unit, .bday-count-word { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase; }
.bday-count-word { font-size: 0.95rem; padding-block: 0.2rem; }
.bday-name { font-weight: 700; overflow-wrap: anywhere; }
.bday-book { display: inline-flex; align-items: center; gap: var(--space-1); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.04em; color: var(--muted); overflow-wrap: anywhere; }
.bday-date { display: inline-flex; align-items: center; gap: var(--space-1); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.04em; color: var(--muted); }
.bday-date svg { width: 0.95rem; height: 0.95rem; }

/* Recently added: a distinct horizontal strip of compact pills. */
.recent-strip { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.recent-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4) var(--space-2) var(--space-2);
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: 999px;
  text-decoration: none;
  color: var(--ink);
  max-width: 16rem;
  transition: border-color 0.12s ease, transform 0.12s ease;
}
.recent-pill:hover { border-color: var(--accent); transform: translateY(-2px); }
.recent-pill-text { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; overflow-wrap: anywhere; }
.recent-pill-name { font-weight: 700; overflow-wrap: anywhere; }
.recent-pill-book { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }

/* Book-hue spine: a card standing for a single book wears that book's colour on
   its leading edge. Defined after every card's :hover rule so it keeps the book
   colour on hover, where the base card recolours only its other edges to the
   accent. Shared by the dashboard book-legend cards and the cross-book contact
   cards (search results, upcoming birthdays, recently added); the legend cards
   also enlarge the dot. On the fully-rounded recent pills the leading edge is
   drawn as a thicker coloured cap. */
.book-spine { border-left: 4px solid var(--book-color); }
.book-spine:hover { border-left-color: var(--book-color); }
.book-card .book-dot { width: 0.85em; height: 0.85em; }

/* ---- Account settings ---- */
.notice { color: var(--accent-strong); font-weight: 700; }
section.card + section.card { margin-top: var(--space-5); }
.swatches { display: grid; grid-template-columns: repeat(auto-fill, 3.25rem); gap: var(--space-3); justify-content: start; }
.swatch { display: flex; flex-direction: column; gap: var(--space-2); cursor: pointer; font-size: 0.85rem; text-align: center; }
/* Keep the radio accessible and focusable, but let the color tile be the UI. */
.swatch input { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
.swatch-chip {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 8%, transparent);
}
.swatch input:checked ~ .swatch-chip,
.swatch input:focus-visible ~ .swatch-chip { outline: 3px solid var(--accent); outline-offset: 2px; }
.swatch-label { color: var(--muted); }
.swatch input:checked ~ .swatch-label { color: var(--ink); font-weight: 700; }
/* The "Default" preview follows the theme's default accent (pine in light,
   brightened in dark), so it changes with the chosen mode. */
.swatch-chip[data-accent-swatch="default"]  { background: #2f5d50; }
:root[data-theme="dark"]:not([data-flavor="solarized"]) .swatch-chip[data-accent-swatch="default"] { background: #63b9a3; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-flavor="solarized"]) .swatch-chip[data-accent-swatch="default"] { background: #63b9a3; }
}
/* Solarized default preview = Solarized blue (placed last so it wins in solarized+dark). */
:root[data-flavor="solarized"] .swatch-chip[data-accent-swatch="default"] { background: #268bd2; }
.swatch-chip[data-accent-swatch="pine"]     { background: #2f5d50; }
.swatch-chip[data-accent-swatch="slate"]    { background: #3f5566; }
.swatch-chip[data-accent-swatch="indigo"]   { background: #4f46e5; }
.swatch-chip[data-accent-swatch="lime"]     { background: #a3e635; }
.swatch-chip[data-accent-swatch="magenta"]  { background: #f0f; }
.swatch-chip[data-accent-swatch="cyan"]     { background: #22d3ee; }
.swatch-chip[data-accent-swatch="rose"]     { background: #f9a8d4; }
.swatch-chip[data-accent-swatch="peach"]    { background: #fdba74; }
.swatch-chip[data-accent-swatch="lavender"] { background: #c4b5fd; }

/* ---- Motion preference ---- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
}

/* ---- High contrast: firmer borders, thicker focus ring, full-contrast muted
   text. Triggered either by the OS preference or the per-user opt-in
   (data-contrast="more" on <html>). ---- */
@media (prefers-contrast: more) {
  :root { --border: var(--border-strong); }
  :focus-visible { outline-width: 3px; }
  .muted, .recent-pill-book, .bday-date, .stat-label { color: var(--ink); }
}
:root[data-contrast="more"] { --border: var(--border-strong); }
:root[data-contrast="more"] :focus-visible { outline-width: 3px; }
:root[data-contrast="more"] .muted,
:root[data-contrast="more"] .recent-pill-book,
:root[data-contrast="more"] .bday-date,
:root[data-contrast="more"] .stat-label { color: var(--ink); }

/* ---- Print ----
   Strip the app chrome and interactive controls, render black-on-white, and let
   the record itself (contact directory and contact detail) fill the page. */
@media print {
  @page { margin: 1.5cm; }
  body { background: #fff; color: #000; }
  a { color: #000; text-decoration: none; }

  /* Chrome and interactive/decorative bits that don't belong on paper. */
  .appbar, .skip-link, .toolbar, .list-prefs, .pager, .actions,
  .backlink, .photo-form, .map-links, form, .btn, button {
    display: none !important;
  }

  .container { max-width: none; margin: 0; padding: 0; }
  h1 { font-size: 18pt; }
  h2 { font-size: 14pt; }

  /* Overview: contact/address-book cards print as a compact, unbroken list. */
  .card-grid { display: block; }
  .contact-card {
    break-inside: avoid;
    border: none;
    border-bottom: 1px solid #999;
    border-radius: 0;
    padding: 0.15cm 0;
    transform: none;
  }
  .contact-card:hover { transform: none; }

  /* Contact detail: keep the photo modest and the record hairline-ruled. */
  .contact-header { gap: 0.5cm; margin-bottom: 0.4cm; }
  .avatar-lg { width: 2.4cm; height: 2.4cm; }
  .details { break-inside: avoid; }
  .details dt, .details dd { border-color: #999; }

  /* Tables print in full (no scroll box needed on paper). */
  .table-wrap { overflow: visible; }
  th, td { border-color: #999; }
}
