/* FreeRangeMedia (formerly Media Shelf) — oak ground, paper cards, mobile-first */

:root {
  color-scheme: light; /* native controls, placeholders, spinners and popups follow the light shelf */

  /* Surfaces. Page ground is oak. Cards and form controls stay paper. */
  --bg: #3e2c1e;
  --bg-elevated: #fffdf8;
  --bg-card: #fffdf8;
  --bg-card-hover: #f6f1e8;

  /* Lines */
  --border: #e6dccb;
  --border-strong: #d2c4ae;
  --border-control: #7d6e5e; /* form-control boundary on the light card and sheet */
  /* Lines under the pinned brand row and under the header (Settings / Back up), in Safari and
     the Home Screen app, 1px. The darkest warm brown in the palette (the theme colour, not
     #000): 18.51:1 on a card (#fffdf8), so it shows when a white card scrolls under it (the
     cream line vanished); on the oak it is a quiet dark seam (1.42:1). Oholiab/Berean, Oct 11. */
  --rule-header: #14110e;

  /* Text */
  --text: #2c241c;
  --text-muted: #5c5348; /* readable on paper #fffdf8; not used on the oak */
  --text-dim: #5c5348; /* same floor as muted; oak areas use cream, not this */
  --text-on-accent: #1a120c;

  /* Accent — primary button fill. Text on that fill is --text-on-accent. */
  --accent: #c46a3a;
  --accent-text: #9a4e24;
  --accent-hover: #d4835a;
  --accent-dim: rgba(196, 106, 58, 0.15);
  --accent-border: rgba(196, 106, 58, 0.45);

  /* Type identity: darker chip text, bright 2px card border + 28% chip tint */
  --book: #9a3b3b;
  --book-stripe: #d64545;
  --book-dim: rgba(214, 69, 69, 0.28);
  --game: #146354;
  --game-stripe: #1aaa78;
  --game-dim: rgba(26, 170, 120, 0.28);
  --movie: #8f4e12;
  --movie-stripe: #f08a1e;
  --movie-dim: rgba(240, 138, 30, 0.28);
  --music: #4c468f;
  --music-stripe: #7a72e0;
  --music-dim: rgba(122, 114, 224, 0.28);
  --comic: #1d5486; /* 5.6:1 on its chip tint over paper */
  --comic-stripe: #2f8fdc;
  --comic-dim: rgba(47, 143, 220, 0.28);
  --tabletop: #8c1d5a; /* 5.7:1 on its chip tint over paper, 8.5:1 on paper */
  --tabletop-stripe: #d63a8c;
  --tabletop-dim: rgba(214, 58, 140, 0.28);

  /* Status + feedback (text darkened for the light card) */
  --want: #345f86;
  --progress: #7a5c0c;
  --done: #176b32;
  --done-dim: rgba(23, 107, 50, 0.15);
  --done-border: rgba(23, 107, 50, 0.45);
  --dropped: #5c6570;
  --owned: #6e2f55;
  --danger: #b42318;
  --danger-bg: rgba(180, 35, 24, 0.12);
  --danger-border: rgba(180, 35, 24, 0.35);

  /* Shape + elevation */
  --radius: 12px;
  --radius-sm: 8px;
  --shadow: 0 12px 40px rgba(44, 36, 28, 0.16);
  --focus: 0 0 0 3px #c46a3a; /* 3.78:1 vs the light card */

  /* Type */
  --font: system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --fs-xs: 0.75rem;     /* 12px */
  --fs-sm: 0.8125rem;   /* 13px */
  --fs-md: 0.875rem;    /* 14px */
  --fs-base: 1rem;      /* 16px */
  --fs-lg: 1.125rem;    /* 18px */
  --fs-xl: 1.25rem;     /* 20px */
  --fs-2xl: 1.5rem;     /* 24px */
  --fw-regular: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;
  --lh-none: 1;
  --lh-tight: 1.35;
  --lh-base: 1.5;

  /* Spacing */
  --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;
  --space-12: 3rem;

  /* Controls */
  --tap: 44px;       /* mobile minimum tap target */
  --tap-sm: 40px;    /* small banner / Settings buttons on mobile */
  --pin-h: 56px;     /* pinned brand + "+ Add" row on phones */
  /* Status bar height. env() is non-zero only with viewport-fit=cover (index.html), which
     iOS needs so the pinned row sits below the status bar instead of under it (Safari
     blurs whatever sits under the status bar; black-translucent in the Home Screen app).
     One variable so the shots can simulate an inset; production always uses env(). */
  --safe-top: env(safe-area-inset-top, 0px);
  /* Top offset for anything that must sit clear of the top edge (pinned brand row, sheet tops,
     the toast over a sheet). Same as --safe-top in Safari; the Home Screen app on phones adds
     40px below the status bar (see the standalone rule after .pin-bar). */
  --clear-top: var(--safe-top);
  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%232c241c' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
}

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

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

body {
  margin: 0;
  /* viewport-fit=cover: keep content clear of the notch in landscape (0 elsewhere) */
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
  min-height: 100dvh;
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  line-height: var(--lh-base);
}

::placeholder {
  color: var(--text-dim);
  opacity: 1;
}

.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;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 1000;
  padding: var(--space-3) var(--space-4);
  background: var(--accent);
  color: var(--text-on-accent);
  font-weight: var(--fw-semibold);
  text-decoration: none;
  border-radius: 0 0 var(--radius-sm) 0;
}

.skip-link:focus {
  left: 0;
}

/* Header */
.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--bg); /* oak #3e2c1e; cream header text sits on this, not on paper */
  color: #f3ead7;
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--rule-header);
}

.header-inner {
  max-width: 960px;
  margin: 0 auto;
  padding: var(--space-4) var(--space-5);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3) var(--space-4);
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.brand-mark {
  display: flex;
  flex-shrink: 0;
}

.brand h1 {
  margin: 0;
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  letter-spacing: -0.02em;
  white-space: nowrap; /* "FreeRangeMedia" never wraps or abbreviates */
}

/* Narrowest phones only (Oholiab B1): keep "FreeRangeMedia" on one line with >=12px before
   the pinned "+ Add". At 20px it needs ~164px (Noto Sans, the widest of the SF-like
   fonts measured); 17px (~140px) leaves ~22px at 320 with the four-bar mark. */
@media (max-width: 359px) {
  .brand h1 {
    font-size: 1.0625rem; /* 17px */
  }
}

.tagline {
  margin: 0;
  font-size: var(--fs-sm);
  color: #f3ead7; /* on the oak, with the header */
}

.header-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

/* Secondary header actions stay quiet so "+ Add" is the one object in the header */
.header-actions .btn-ghost {
  border-color: transparent;
  color: #f3ead7; /* Settings and Back up sit on the oak, not accent brown */
}

.header-actions .btn-ghost:hover {
  color: var(--text);
  border-color: var(--border);
}

/* Tablet / desktop: pinned only while the header is one slim row */
@media (min-width: 560px) and (max-width: 899px) {
  .tagline {
    display: none;
  }
}

@media (min-width: 560px) and (max-height: 500px) {
  /* Landscape phones: a pinned bar would eat a fifth of the screen */
  .site-header {
    position: static;
  }
}

/* Phones: slim pinned row (brand + "+ Add"); secondary actions scroll away with the page.
   Pure CSS, no DOM change: the header is static, and the brand, "+ Add" and a bar
   behind them are position: fixed. Button IDs, roles and DOM order are untouched. */
@media (max-width: 559px) {
  html {
    scroll-padding-top: calc(var(--pin-h) + var(--safe-top) + var(--space-2));
  }

  .site-header {
    position: static;
    backdrop-filter: none; /* would otherwise become the containing block of the fixed row */
    padding-top: calc(var(--pin-h) + var(--safe-top));
  }

  .site-header::before {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 40;
    height: calc(var(--pin-h) + var(--safe-top));
    background: var(--bg); /* oak, same as the header; opaque, so no backdrop blur (it did
       nothing visible and Safari 26 skips a blurred fixed bar when tinting the status bar) */
    color: #f3ead7;
    border-bottom: 1px solid var(--rule-header);
  }

  .tagline {
    display: none;
  }

  .brand {
    position: fixed;
    top: var(--safe-top);
    left: max(var(--space-5), env(safe-area-inset-left, 0px));
    z-index: 41;
    height: var(--pin-h);
  }

  #btn-add {
    position: fixed;
    top: calc(var(--safe-top) + (var(--pin-h) - var(--tap)) / 2);
    right: max(var(--space-5), env(safe-area-inset-right, 0px));
    z-index: 41;
    min-height: var(--tap);
    padding-inline: var(--space-4);
  }

  .header-inner {
    padding: var(--space-1) var(--space-3) var(--space-2);
  }

  .header-actions {
    flex: 1 1 100%;
    flex-wrap: nowrap;
    gap: var(--space-1);
  }

  .header-actions .btn-ghost {
    flex: 1 1 0;
    min-width: 0;
    min-height: var(--tap);
    padding-inline: var(--space-2);
  }
}

/* Home Screen app on phones (iOS 27.2 beta): the same oak bar, but as a real element instead of
   .site-header::before. This was an attempt to stop iOS 27.2 beta blurring the brand and "+ Add" at
   the top of the Home Screen app, on the idea that a fixed element with a painted background at
   the very top edge makes WebKit extend that colour under the status bar instead. That idea came
   from reports about Safari's own top-bar tint (WebKit bug 301756), not Home Screen apps, and on
   Micah's iPhone (Oct 11) the blur is still there; the cause is being investigated with the
   frm-blur-test pages. Same size, colour and border as the ::before; Safari tabs keep the
   ::before. Inert and aria-hidden; it still takes taps on the bar like the ::before did. */
.pin-bar {
  display: none;
}

@media (max-width: 559px) and (display-mode: standalone) {
  .site-header::before {
    content: none;
  }

  .pin-bar {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 40;
    height: calc(var(--pin-h) + var(--safe-top));
    background-color: var(--bg); /* solid oak, no backdrop-filter */
    border-bottom: 1px solid var(--rule-header);
  }
}

/* Home Screen app on phones, option 2: iOS 27 blurs roughly the top inset + 33px of a
   standalone web app whatever the status-bar style or a fixed bar does (Micah's iPhone 17 Pro,
   iOS 27.2 beta, Oct 11). So the oak band runs 40px further down, plain (no line, glow or
   texture), and the brand row starts at its lower edge, --clear-top. The row is one tap tall
   (+ Add's 44px, the brand centred on it) plus --space-2; only the band's bottom border marks
   the pinned area, and Settings / Back up still scroll away under it. Safari tabs unchanged. */
@media (max-width: 559px) and (display-mode: standalone) {
  :root {
    --clear-top: calc(var(--safe-top) + 40px);
    --pin-row: calc(var(--tap) + var(--space-2));
  }

  html {
    scroll-padding-top: calc(var(--clear-top) + var(--pin-row) + var(--space-2));
  }

  .site-header {
    padding-top: calc(var(--clear-top) + var(--pin-row));
  }

  .pin-bar {
    height: calc(var(--clear-top) + var(--pin-row));
  }

  .brand {
    top: var(--clear-top);
    height: var(--tap);
  }

  #btn-add {
    top: var(--clear-top);
  }
}

/* Phones (Safari tab and Home Screen app): the Settings / Back up row is a dark nav row so it
   stands apart from the oak shelf (Micah, Oct 11). --nav-bg is the warm near-black #14110e
   (cream text 15.73:1, the copper focus ring 4.90:1). A 1px divider down the middle, inset
   10px top and bottom, in cream at 40% (3.39:1 on --nav-bg; 30% was only 2.43:1), drawn on the
   row behind the 4px gap so it never shrinks either 44px target. The row has its own hover,
   pressed and focus colours: the general ghost hover turns text dark brown, and iOS keeps
   :hover after a tap. The near-black rules above and below the row merge into it, so there is
   no doubled line; the colour change is the edge. */
:root {
  --nav-bg: #14110e;
  --nav-divider: rgba(243, 234, 215, 0.4);
  --nav-hover: rgba(243, 234, 215, 0.08);
  --nav-pressed: rgba(243, 234, 215, 0.16);
}

@media (max-width: 559px) {
  .header-inner {
    background: var(--nav-bg);
  }

  .header-actions {
    background: linear-gradient(var(--nav-divider), var(--nav-divider)) center / 1px calc(100% - 20px) no-repeat;
  }

  /* Each half stays a full 44px target; the hover / pressed fill is a pill drawn 8px inside it
     on every side (::before), so it is centred in its half and never touches the divider. The
     button itself never takes a fill, so the general .btn:hover light fill can't stick. */
  .header-actions .btn-ghost,
  .header-actions .btn-ghost:hover,
  .header-actions .btn-ghost:active {
    position: relative;
    isolation: isolate;
    color: #f3ead7;
    background: transparent;
    border-color: transparent;
  }

  .header-actions .btn-ghost::before {
    content: "";
    position: absolute;
    inset: 8px;
    z-index: -1;
    border-radius: var(--radius-sm);
    background: transparent;
    transition: background 0.15s;
    pointer-events: none;
  }

  /* Hover fill only where hover is real (mouse, trackpad). On touch, iOS keeps :hover after a
     tap, so there the pill shows only while pressed, and clears on release. Keyboard focus is
     the copper ring alone (no fill), like the cards. */
  @media (hover: hover) {
    .header-actions .btn-ghost:hover::before {
      background: var(--nav-hover);
    }
  }

  .header-actions .btn-ghost:active::before {
    background: var(--nav-pressed);
  }

  .header-actions .btn-ghost:focus-visible {
    color: #f3ead7;
    box-shadow: var(--focus);
  }
}

/* Variant B: the whole top on phones (the blur-clearing band, the brand row and the nav row)
   is the same near-black, so the oak shelf only starts below Settings / Back up and the status
   bar area reads as dark, not shelf. The line between the brand row and the nav row would vanish
   in #14110e on #14110e, so it uses the divider's cream at 40% (3.39:1). Logo, copper "+ Add",
   band height and --clear-top are unchanged. */
:root {
  --nav-top: on;
  --nav-line: rgba(243, 234, 215, 0.4);
}

@media (max-width: 559px) {
  .site-header {
    background: var(--nav-bg);
  }

  .site-header::before,
  .site-header > .pin-bar {
    background: var(--nav-bg);
    border-bottom-color: var(--nav-line);
  }
}

/* Buttons */
.btn {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.btn:hover {
  background: var(--bg-card-hover);
  border-color: var(--border-strong);
}

.btn:focus-visible {
  outline: none;
  box-shadow: var(--focus);
}

.btn-primary {
  background: var(--accent);
  border-color: transparent;
  color: var(--text-on-accent);
}

.btn-primary:hover {
  background: var(--accent-hover);
  border-color: transparent;
  color: var(--text-on-accent);
}

.btn-ghost {
  background: transparent;
}

.btn-danger {
  background: var(--danger-bg);
  border-color: var(--danger-border);
  color: var(--danger);
}

.btn-danger:hover {
  background: rgba(180, 35, 24, 0.18);
}

.btn-sm {
  padding: 0.375rem var(--space-3);
  font-size: var(--fs-sm);
}

.btn-icon {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: var(--fs-2xl);
  line-height: var(--lh-none);
  padding: var(--space-1) var(--space-2);
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.btn-icon:hover {
  color: var(--text);
  background: var(--bg-card);
}

.btn-icon:focus-visible {
  outline: none;
  box-shadow: var(--focus);
}

/* Stats — a caption, not a second row of filters */
.stats {
  max-width: 960px;
  margin: 0 auto;
  padding: var(--space-4) var(--space-5) 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
}

.stat-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0;
  font-size: var(--fs-sm);
  color: #f3ead7; /* counts sit on the oak */
}

.stat-pill strong {
  color: #f3ead7;
  font-variant-numeric: tabular-nums;
}

/* Toolbar */
.toolbar {
  max-width: 960px;
  margin: 0 auto;
  padding: var(--space-4) var(--space-5) var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.type-tabs {
  position: relative;
  isolation: isolate; /* keeps the one spotlight above the oak and behind the chips */
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  /* Reserve the warm pool above the chips inside this block so the light cannot
     cover the stats, header, or notices. Margin (not padding) so the phone
     segmented paper chrome still wraps only the chips. */
  margin-top: 2rem;
}

/* One soft light behind the category row only. Fades out inside this row so it
   does not reach the cards. The 2rem above the chips fills the reserved margin,
   still #e8c99a, still centered on the category row, gone before the search field.
   No image, no second gradient. */
.type-tabs::before {
  display: none; /* Duo fix: glow removed entirely; the tabs sit flat (focus ring and active chip unchanged) */
  content: "";
  position: absolute;
  z-index: -1;
  left: 0;
  top: -2rem;
  width: 24.5rem;
  height: calc(100% + 2rem);
  background: radial-gradient(ellipse 50% 100% at center center, #e8c99a 0%, transparent 70%);
  background-position: center center;
  pointer-events: none;
}

.type-tabs-fade-start {
  display: none; /* phones only (below) */
}

.chip {
  appearance: none;
  border: 1px solid var(--border);
  background: #fffdf8; /* opaque paper; the spotlight must not show through the label */
  color: #2c241c;
  font: inherit;
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  padding: var(--space-2) var(--space-4);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.chip:hover {
  color: var(--text);
  border-color: var(--border-strong);
}

.chip.active {
  background: #fffdf8; /* solid, not an accent tint */
  border-color: var(--accent-border);
  color: var(--accent-text);
}

.chip:focus-visible {
  outline: none;
  box-shadow: var(--focus);
}

/* Phones: one segmented row of five equal columns instead of wrapping pills (no orphan
   "Music" row, no sideways scroll). Same active colours, focus ring drawn inside the
   segment so the container doesn't clip it. */
@media (max-width: 479px) {
  .type-tabs {
    /* Seven tabs (Tabletop added): one sideways-scrolling strip with a 32px right-edge fade
       (the Carry Tracker fix, e308ffb), so labels and tap targets keep their full size. */
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    scroll-padding-inline: 16px;
    gap: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-elevated);
  }

  .type-tabs::before {
    width: 100%;
  }

  .type-tabs::-webkit-scrollbar {
    display: none;
  }

  /* Scroll hint: a fade pinned to the strip's right edge; also trailing space so the last
     tab can scroll fully clear of it. Decorative only. */
  .type-tabs::after {
    content: "";
    flex: 0 0 2rem;
    position: sticky;
    z-index: 1;
    right: 0;
    margin-left: -2rem;
    background: linear-gradient(to right, rgba(255, 253, 248, 0), #fffdf8 100%);
    border-radius: 0 calc(var(--radius-sm) - 1px) calc(var(--radius-sm) - 1px) 0;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s;
  }

  /* Same fade on the left edge once tabs are scrolled out that way (app.js toggles the
     has-more-start / has-more-end classes on scroll). */
  .type-tabs-fade-start {
    display: block;
    flex: 0 0 2rem;
    position: sticky;
    z-index: 1;
    left: 0;
    margin-right: -2rem;
    background: linear-gradient(to left, rgba(255, 253, 248, 0), #fffdf8 100%);
    border-radius: calc(var(--radius-sm) - 1px) 0 0 calc(var(--radius-sm) - 1px);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s;
  }

  .type-tabs.has-more-end::after,
  .type-tabs.has-more-start .type-tabs-fade-start {
    opacity: 1;
  }

  .type-tabs .chip {
    flex: 0 0 auto;
    min-width: var(--tap);
    min-height: var(--tap);
    padding: 0 var(--space-3);
    border: 0;
    border-radius: 0;
    background: #fffdf8; /* opaque even in the segmented row */
    white-space: nowrap;
    text-align: center;
  }

  /* Divider as an inset shadow so it doesn't take width from the label. */
  .type-tabs .chip + .chip {
    box-shadow: inset 1px 0 0 var(--border);
  }

  .type-tabs .chip:first-of-type {
    border-radius: calc(var(--radius-sm) - 1px) 0 0 calc(var(--radius-sm) - 1px);
  }

  .type-tabs .chip:last-of-type {
    border-radius: 0 calc(var(--radius-sm) - 1px) calc(var(--radius-sm) - 1px) 0;
  }

  .type-tabs .chip.active {
    background: #fffdf8;
    color: var(--accent-text);
    box-shadow: inset 0 0 0 1px var(--accent-border);
  }

  .type-tabs .chip:focus-visible {
    box-shadow: inset 0 0 0 3px #c46a3a;
  }
}

/* Six segments (Comics added): below 400px the labels drop to 13px with 4px padding so
   "All Books Games Movies Music Comics" fits one row at 320 without sideways scroll. */
/* (The 13px / 4px squeeze below 400px is gone: the strip scrolls instead.) */

.filters-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

/* One material for every text control (toolbar, item form, Settings key fields) */
.select,
.search,
.item-form input,
.item-form select,
.item-form textarea,
.api-key-field input,
.import-card textarea,
.import-input {
  appearance: none;
  font: inherit;
  font-size: var(--fs-md);
  color: var(--text);
  background-color: #fffdf8; /* solid paper; labels stay #2c241c, never on the oak */
  border: 1px solid var(--border-control);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}

.select,
.item-form select {
  padding-right: var(--space-8);
  background-image: var(--chevron);
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  cursor: pointer;
}

.search-wrap {
  flex: 1 1 180px;
  min-width: 0;
}

.search {
  width: 100%;
}

.select:focus-visible,
.search:focus-visible,
.item-form input:focus-visible,
.item-form select:focus-visible,
.item-form textarea:focus-visible,
.api-key-field input:focus-visible,
.import-card textarea:focus-visible,
.import-input:focus-visible {
  outline: none;
  box-shadow: var(--focus);
  border-color: var(--accent); /* the 45% accent border fell to 2.77:1 on focus */
}

/* Field error after a failed Save (no JS needed) */
.item-form input:user-invalid {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-bg);
}

/* Focused invalid field keeps a distinct, stronger ring (re-review fix 10 polish).
   0.7 alpha like --focus: 3.74:1 vs the modal (0.45 would be only 2.27:1). */
.item-form input:user-invalid:focus-visible {
  box-shadow: 0 0 0 3px rgba(180, 35, 24, 0.55);
}

/* Main */
.main {
  max-width: 960px;
  margin: 0 auto;
  padding: var(--space-3) var(--space-5) var(--space-12);
}

.seed-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
  background: var(--accent-dim);
  border: 1px solid var(--accent-border);
  border-radius: var(--radius);
  font-size: var(--fs-md);
  color: #f3ead7; /* cream on the oak panel; muted brown is paper-only */
}

.seed-banner p {
  margin: 0;
}

/* Clear samples / Reload / Not now: cream label and border on the oak panel.
   No fill — leave the oak panel showing through (Back up now stays primary orange). */
.seed-banner .btn-ghost {
  color: #f3ead7;
  border-color: #f3ead7;
  background: transparent;
}

/* "A new version is ready." — the sample-notice look, one line plus Reload */
.update-banner .btn {
  min-height: var(--tap);
}

.item-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.item-card {
  display: flex;
  gap: var(--space-4);
  align-items: stretch;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, transform 0.1s;
}

/* Hover is signalled by the border only: the lighter hover fill pushed the Movie badge
   (4.27:1), Dropped badge and tertiary text (4.48:1) below AA. */
.item-card:hover {
  border-color: var(--border-strong);
}

/* Focus: a copper outline 3px outside the card's (category) border, so the two never merge */
.item-card:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
  box-shadow: none;
}

/* Category border: the card's whole 2px border is the category colour (Micah, Oct 11),
   replacing the 4px left stripe; same corner radius. With six or more mixed cards the full
   stripe colours were loud, so the border is the category colour at 75% strength over the
   card (same hue as the chip, whose tint and text stay full strength; Oholiab/Berean, Oct 11).
   Browsers without color-mix() keep the full colour. Hover darkens it to the category's
   text colour. The cover column already carries data-type. */
.item-card:has(.item-cover-col[data-type]) {
  border-width: 2px;
  border-color: var(--card-rule, var(--border));
  border-color: color-mix(in srgb, var(--card-rule, var(--border)) 75%, var(--bg-card));
}

.item-card:has(.item-cover-col[data-type]):hover {
  border-color: var(--card-rule-hover, var(--border-strong));
}

.item-card:has(.item-cover-col[data-type="book"]) {
  --card-rule: var(--book-stripe);
  --card-rule-hover: var(--book);
}

.item-card:has(.item-cover-col[data-type="game"]) {
  --card-rule: var(--game-stripe);
  --card-rule-hover: var(--game);
}

.item-card:has(.item-cover-col[data-type="movie"]) {
  --card-rule: var(--movie-stripe);
  --card-rule-hover: var(--movie);
}

.item-card:has(.item-cover-col[data-type="music"]) {
  --card-rule: var(--music-stripe);
  --card-rule-hover: var(--music);
}

.item-card:has(.item-cover-col[data-type="comic"]) {
  --card-rule: var(--comic-stripe);
  --card-rule-hover: var(--comic);
}

.item-card:has(.item-cover-col[data-type="tabletop"]) {
  --card-rule: var(--tabletop-stripe);
  --card-rule-hover: var(--tabletop);
}

.item-top {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
}

.item-title {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  letter-spacing: -0.01em;
}

.item-meta {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-md);
  color: var(--text-muted);
}

/* Names-only mode: a comic card names its cover / variant, quietly, under the title. */
.item-cover-name {
  margin: 2px 0 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

.item-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  flex-shrink: 1; /* was 0: at 320px the badge row overflowed the card (docW 349) */
  min-width: 0;
}

.badge {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-2);
  border-radius: 999px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap; /* v45: "Video game" stays one pill */
}

.badge-type.book { background: var(--book-dim); color: var(--book); }
.badge-type.game { background: var(--game-dim); color: var(--game); }
.badge-type.movie { background: var(--movie-dim); color: var(--movie); }
.badge-type.music { background: var(--music-dim); color: var(--music); }
.badge-type.comic { background: var(--comic-dim); color: var(--comic); }
.badge-type.tabletop { background: var(--tabletop-dim); color: var(--tabletop); }

.badge-status.want { background: rgba(52, 95, 134, 0.15); color: var(--want); }
.badge-status.owned { background: rgba(110, 47, 85, 0.15); color: var(--owned); }
.badge-status.in_progress { background: rgba(122, 92, 12, 0.15); color: var(--progress); }
.badge-status.done { background: var(--done-dim); color: var(--done); }
.badge-status.dropped { background: rgba(92, 101, 112, 0.15); color: var(--dropped); }

.badge-format {
  white-space: normal; /* long "Signed: …" names wrap inside the pill instead of overflowing */
  background: rgba(168, 159, 147, 0.12);
  color: var(--text-muted);
  text-transform: none;
  letter-spacing: 0;
  font-weight: var(--fw-medium);
}

.item-bottom {
  margin-top: var(--space-3);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  font-size: var(--fs-sm);
  color: var(--text-dim);
}

.stars {
  color: var(--accent-text);
  letter-spacing: 0.05em;
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.tag {
  padding: 0.125rem var(--space-2);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 4px;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.item-notes {
  margin: var(--space-3) 0 0;
  font-size: var(--fs-md);
  color: var(--text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Cover column on cards: always the 2:3 height, so mixed lists keep one card
   rhythm. The cover sits at the top of it. */
.item-cover-col {
  flex: 0 0 64px;
  width: 64px;
  min-height: 96px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
}

/* Cover thumbnails on cards */
.item-cover {
  width: 64px;
  height: 96px;
  border-radius: 6px;
  object-fit: cover;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
}

/* Missing cover: a neutral well — the type badge already carries the hue */
.item-cover-placeholder {
  width: 64px;
  height: 96px;
  border-radius: 6px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.item-body {
  flex: 1 1 auto;
  min-width: 0;
}

/* Album art is square: music gets a 1:1 frame at the same width */
.item-cover-col[data-type="music"] .item-cover,
.item-cover-col[data-type="music"] .item-cover-placeholder {
  height: 64px;
}

@media (min-width: 560px) {
  .item-cover-col {
    flex-basis: 72px;
    width: 72px;
    min-height: 108px;
  }

  .item-cover,
  .item-cover-placeholder {
    width: 72px;
    height: 108px;
  }

  .item-cover-col[data-type="music"] .item-cover,
  .item-cover-col[data-type="music"] .item-cover-placeholder {
    height: 72px;
  }
}

/* Empty */
.empty-state {
  text-align: center;
  padding: var(--space-12) var(--space-6);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elevated);
}

.empty-title {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
}

.empty-hint {
  margin: var(--space-2) 0 var(--space-5);
  color: var(--text-muted);
  font-size: var(--fs-md);
}

/* Licence names (CC BY-SA 4.0, CC BY-NC 4.0) and "Grand Comics Database™" never split across
   lines, in the footer, Settings and the picker credit. */
.nowrap {
  white-space: nowrap;
}

/* Footer */
.site-footer {
  max-width: 960px;
  margin: 0 auto;
  padding: 0 var(--space-5) var(--space-8);
  text-align: center;
  font-size: var(--fs-xs);
  color: #f3ead7; /* footer text sits on the oak */
}

.site-footer code {
  font-family: var(--mono);
  font-size: var(--fs-xs);
}

/* Data credits (RAWG requires an active link). Same size and cream as the footer
   text; the links stay underlined. Inline padding widens the tap area
   (~8px above and below, ~33px total; deliberately not 44) without making the line
   taller, and the negative inline margin cancels the horizontal padding so the text
   doesn't move. Desktop: one line with a visible "·". Phones: see below. */
.footer-credit {
  white-space: nowrap;
}

/* The comic data credit is a full sentence (licence terms): it wraps like text. */
#footer-comic-credit {
  white-space: normal;
}

/* Its lines are blocks, so it starts on its own line (no dangling "·" before it). */
#footer-comic-credit:has(.credit-line + .credit-line) {
  display: block;
}

.footer-sep:has(+ #footer-comic-credit .credit-line + .credit-line) {
  display: none;
}

.footer-credits a {
  color: #f3ead7; /* on the oak, not accent brown; underline stays */
  text-decoration: underline;
  text-underline-offset: 2px;
  padding: var(--space-2) var(--space-1);
  margin-inline: calc(-1 * var(--space-1));
}

.footer-credits a:hover {
  color: #f3ead7;
}

/* Last line of the page: "App version v33", the same line as at the end of Settings. Muted
   cream on the oak (#c4b8a2, 6.77:1; --text-muted is for paper, not the oak). */
.footer-version {
  margin: var(--space-4) 0 0;
  font-size: var(--fs-xs);
  line-height: 1.4;
  color: #c4b8a2;
}

/* Phones: the credits stack as centred blocks and the decorative "·" (aria-hidden) is
   hidden, so there is no stray dot. Inside a credit the lines sit at 1.4 so a wrapped
   credit reads as one (Oholiab, Oct 11). Footer links get at least 24px of tap height
   (comfort; inline links are exempt from WCAG 2.5.8) without any link's tap box
   overlapping another's: vertical padding on an inline link doesn't move the text, so
   - one-line credits: 3.5px above and below (17px text line + 7 = 24px), credits --space-3
     (12px) apart, which leaves the boxes of neighbouring credits clear of each other;
   - the comic credit's sentences, which wrap to two lines: the first link (the source
     name, on the first line) takes its 7px above, the last (the licence, on the last line)
     below, so the two lines' boxes meet but don't overlap. Between two such sentences
     both extend into the gap, which therefore needs 14.5px (--space-3 + 2.5px). At 1.4 the
     font's 17px text box is 0.2px taller than the 16.8px line, so the two lines of one
     sentence touch by 0.2px even with no padding (the tests allow 0.5px). */
@media (max-width: 559px) {
  .footer-credits {
    line-height: 1.4;
  }

  .footer-credit {
    display: block;
  }

  .footer-credit + .footer-sep + .footer-credit,
  .footer-credit + .footer-credit {
    margin-top: var(--space-3);
  }

  .footer-credits a {
    padding-block: 3.5px;
    padding-inline: 1px; /* 4px made "OMDb" and "CC BY-NC 4.0" boxes overlap across the "(" */
    margin-inline: -1px;
  }

  #footer-comic-credit .credit-line .credit-src {
    padding-block: 7px 0;
  }

  #footer-comic-credit .credit-line .credit-lic {
    padding-block: 0 7px;
  }

  #footer-comic-credit .credit-line + .credit-line {
    margin-top: calc(var(--space-3) + 2.5px);
  }

  .footer-sep {
    display: none;
  }
}

/* Modal */
.modal {
  /* Sheets never reach under the status bar (viewport-fit=cover): every panel's height is
     capped so its top, with the title and X, stays at least this far down. */
  --sheet-gap: calc(var(--clear-top) + var(--space-2));
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0;
}

/* The host and cover share measured visual-viewport bounds while a sheet is open.
   inset:0 alone follows the browser's fixed-position viewport, which may lag keyboard
   dismissal. Variables are removed on close and during pinch zoom (native behavior). */
body.modal-open .modal,
body.modal-open .modal-backdrop {
  top: var(--sheet-vv-top, 0px);
  left: var(--sheet-vv-left, 0px);
  right: auto;
  bottom: auto;
  width: var(--sheet-vv-width, 100%);
  height: var(--sheet-vv-height, 100%);
}

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: #e7dcc8; /* solid paper dim; library cards do not show through */
}

.modal-panel {
  position: relative;
  width: 100%;
  max-width: 520px;
  max-height: min(92vh, 720px, calc(100vh - var(--sheet-gap))); /* fallback without dvh */
  max-height: min(92dvh, 720px, calc(var(--sheet-vv-height, 100dvh) - var(--sheet-gap)));
  overflow: auto;
  overscroll-behavior: contain;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: var(--shadow);
  padding: var(--space-5) var(--space-5) var(--space-6);
  animation: slide-up 0.2s ease-out;
}

.modal-panel-sm {
  max-width: 380px;
  max-height: min(92vh, 720px, calc(100vh - var(--sheet-gap) - 30vh)); /* fallback */
  max-height: min(92dvh, 720px, calc(var(--sheet-vv-height, 100dvh) - var(--sheet-gap) - 30vh));
  margin: auto var(--space-4) 30vh;
  border-radius: var(--radius);
  padding: var(--space-5);
}

.modal-panel-settings {
  max-width: 560px;
  max-height: min(92vh, 860px, calc(100vh - var(--sheet-gap) - 8vh)); /* fallback */
  max-height: min(92dvh, 860px, calc(var(--sheet-vv-height, 100dvh) - var(--sheet-gap) - 8vh));
  margin: auto var(--space-4) 8vh;
  border-radius: var(--radius);
  padding: var(--space-5) var(--space-5) var(--space-5);
}

@media (min-width: 560px) {
  .modal {
    --sheet-gap: max(var(--space-4), calc(var(--clear-top) + var(--space-2)));
    align-items: center;
    padding: var(--sheet-gap) var(--space-4) var(--space-4);
  }

  .modal-panel {
    max-height: min(92vh, 720px, calc(100vh - var(--sheet-gap) - var(--space-4))); /* fallback */
    max-height: min(92dvh, 720px, calc(var(--sheet-vv-height, 100dvh) - var(--sheet-gap) - var(--space-4)));
    border-radius: var(--radius);
    animation: fade-scale 0.18s ease-out;
  }

  .modal-panel-sm,
  .modal-panel-settings {
    margin: 0;
  }

  /* Duo fix: the Add / Edit sheet is ~560px wide and centered on wide and landscape screens */
  #modal > .modal-panel {
    max-width: 560px;
  }

  .modal-panel-sm {
    max-height: min(92vh, 720px, calc(100vh - var(--sheet-gap) - var(--space-4))); /* fallback */
    max-height: min(92dvh, 720px, calc(var(--sheet-vv-height, 100dvh) - var(--sheet-gap) - var(--space-4)));
  }

  .modal-panel-settings {
    max-height: min(92vh, 860px, calc(100vh - var(--sheet-gap) - var(--space-4))); /* fallback */
    max-height: min(92dvh, 860px, calc(var(--sheet-vv-height, 100dvh) - var(--sheet-gap) - var(--space-4)));
  }
}

@keyframes slide-up {
  from { transform: translateY(24px); }
  to { transform: translateY(0); }
}

@keyframes fade-scale {
  from { transform: scale(0.96); }
  to { transform: scale(1); }
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-4);
}

.modal-header h2 {
  margin: 0;
  font-size: var(--fs-lg);
}

.item-form .form-row {
  margin-bottom: var(--space-4);
}

.item-form label,
.api-key-field label {
  display: block;
  margin-bottom: var(--space-1);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
}

.req {
  color: var(--accent-text);
}

/* Checkbox groups (v1.2: "What’s on the shelf?", signed copy) */
.check-group {
  border: 0;
  padding: 0;
  margin-inline: 0;
  min-width: 0;
}

.check-group legend {
  padding: 0;
  margin-bottom: var(--space-1);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
}

.check-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-5);
}

label.check {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 36px;
  margin: 0;
  font-size: var(--fs-md);
  color: var(--text);
  cursor: pointer;
}

/* One checkbox look everywhere (form and dialogs), same on iPhone and desktop. The tick is
   drawn with borders, so it survives forced-colors / high-contrast modes. */
label.check input[type="checkbox"] {
  -webkit-appearance: none;
  appearance: none;
  display: inline-grid;
  place-content: center;
  width: 20px;
  height: 20px;
  min-height: 0;
  margin: 0;
  padding: 0;
  flex: 0 0 auto;
  border: 1.5px solid var(--border-control);
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}

label.check input[type="checkbox"]::before {
  content: "";
  width: 5px;
  height: 10px;
  margin-top: -2px;
  border: solid var(--text-on-accent);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) scale(0);
}

label.check input[type="checkbox"]:checked,
label.check input[type="checkbox"]:indeterminate {
  background: var(--accent);
  border-color: var(--accent);
}

label.check input[type="checkbox"]:checked::before {
  transform: rotate(45deg) scale(1);
}

/* Partly signed parent: a bar instead of a tick */
label.check input[type="checkbox"]:indeterminate::before {
  width: 10px;
  height: 0;
  margin-top: 0;
  border-width: 0 0 2px 0;
  transform: none;
}

label.check input[type="checkbox"]:focus-visible {
  outline: 2px solid transparent; /* becomes visible in forced-colors mode */
  outline-offset: 2px;
  box-shadow: var(--focus);
}

@media (forced-colors: active) {
  label.check input[type="checkbox"] {
    border-color: CanvasText;
  }
  label.check input[type="checkbox"]:checked,
  label.check input[type="checkbox"]:indeterminate {
    background: Highlight;
    border-color: Highlight;
  }
  label.check input[type="checkbox"]::before {
    border-color: HighlightText;
  }
}

.signed-authors {
  display: flex;
  flex-direction: column;
  padding-left: calc(18px + var(--space-2)); /* sub-toggles sit under the parent's label */
}

/* "Complete in box": a small ghost button, left-aligned, 8px under the checkbox row */
.btn-cib {
  display: block;
  margin-top: var(--space-2);
}

.item-form input,
.item-form select,
.item-form textarea {
  width: 100%;
}

.item-form textarea {
  resize: vertical;
  min-height: 72px;
}

.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}

/* Comics: Series takes the room, Issue stays short */
.form-grid.form-grid-issue {
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
}

.form-grid > .form-row {
  min-width: 0;
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

/* Duo fix: a field you tab or tap into scrolls to sit above the pinned Cancel / Save bar
   and its 24px fade (bar ~102px + fade 24px + 8px air), not underneath it. */
#modal > .modal-panel {
  scroll-padding-bottom: calc(8.25rem + env(safe-area-inset-bottom, 0px));
}

/* Save / Cancel stay visible while the sheet scrolls */
.item-form .form-actions {
  border-top-color: var(--border-strong); /* the 1px rule above Cancel / Save */
  position: sticky;
  bottom: calc(-1 * var(--space-6));
  z-index: 1;
  margin-bottom: calc(-1 * var(--space-6));
  padding-top: calc(var(--space-4) + var(--space-4));
  padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom, 0px)); /* above the home indicator */
  background: var(--bg-elevated);
}

/* Duo fix: the form fades out over 24px above the rule, so a field scrolling under the
   pinned actions never reads as sliced mid-field. Decorative; never takes taps. */
.item-form .form-actions::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 1px);
  height: var(--space-6);
  background: linear-gradient(to bottom, rgba(255, 253, 248, 0), var(--bg-elevated));
  pointer-events: none;
}

.form-actions-right {
  display: flex;
  gap: var(--space-2);
  margin-left: auto;
}

.confirm-actions {
  margin-top: var(--space-5);
  flex-wrap: wrap;
  justify-content: flex-end;
}

#confirm {
  z-index: 110; /* above Settings when restoring from there */
}

/* The backup match dialog can also open from a Settings restore, so it sits above Settings too (v41) */
#tt-match-modal {
  z-index: 110;
}

/* Duplicate prompt (v1.2): same dialog, a little wider for the Existing / Incoming rows */
#confirm-panel.is-wide {
  max-width: 480px;
}

/* Phones: three long labels don't fit one row, so stack them full width. The DOM order is
   Cancel, secondary, suggested (see openConfirm), so reversing puts the suggestion on top. */
@media (max-width: 479px) {
  #confirm-panel.is-wide .confirm-actions {
    flex-direction: column-reverse;
    align-items: stretch;
  }
}

.confirm-detail {
  margin: var(--space-3) 0 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2) var(--space-3);
  font-size: var(--fs-md);
}

.confirm-detail dt {
  color: var(--text-muted);
  font-weight: var(--fw-medium);
}

.confirm-detail dd {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.dup-title {
  display: block;
  color: var(--text);
  font-weight: var(--fw-semibold);
}

.dup-meta,
.dup-parts {
  display: block;
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

.dup-part + .dup-part {
  margin-left: var(--space-3);
}

.dup-mark {
  color: var(--text-muted);
}

.dup-mark.is-unknown {
  color: var(--text-dim);
}

/* Comic duplicate prompt: the stored cover URL as a small thumbnail (or, names-only, the
   cover name) beside each side's details. */
.dup-with-thumb {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.dup-with-thumb > .dup-text {
  flex: 1 1 auto;
  min-width: 0;
}

.dup-thumb {
  flex: 0 0 auto;
  display: block;
  width: 48px;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--bg-card-hover);
}

.dup-cover-name {
  display: block;
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

/* Blank Incoming / Existing tile says so (Oholiab), centred in the 48px tile. */
.dup-thumb.is-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2px;
  text-align: center;
  font-size: var(--fs-xs);
  line-height: 1.2;
  color: var(--text-muted);
}

.confirm-option {
  color: var(--text-muted);
}

/* Oholiab O3: 16px of clear space between the Incoming block and the checkbox. The row
   centres the 20px box (36px row: 8px above it), and label.check's margin:0 used to win. */
label.check.confirm-option {
  margin-top: var(--space-2);
}

@media (max-width: 559px) {
  /* 44px row: 12px above the box, so 4px more makes 16 */
  label.check.confirm-option {
    margin-top: var(--space-1);
  }
}

#confirm-title {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-lg);
}

#confirm-desc {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-md);
}

/* Toast */
.toast {
  position: fixed;
  bottom: calc(var(--space-5) + env(safe-area-inset-bottom, 0px));
  left: 50%;
  transform: translateX(-50%);
  z-index: 200;
  padding: var(--space-3) var(--space-5);
  background: var(--bg-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  font-size: var(--fs-md);
  width: max-content;
  max-width: calc(100vw - 2rem);
  text-align: center;
  line-height: var(--lh-tight);
}

/* While a sheet is open the toast moves to the top so it never covers Save / Cancel */
body.modal-open .toast {
  bottom: auto;
  top: calc(var(--space-5) + var(--clear-top));
}

body.modal-open {
  overflow: hidden;
}

/* Barcode / lookup form */
.barcode-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: stretch;
}

.barcode-row input {
  flex: 1 1 140px;
  min-width: 0;
}

.barcode-row .btn {
  flex: 0 0 auto;
  flex-shrink: 0; /* Duo fix: Scan / Lookup never squeeze; the input (min-width: 0) gives way */
  white-space: nowrap;
}

/* Narrow phones: the input full width, then Scan and Lookup side by side, equal widths. */
@media (max-width: 359px) {
  .barcode-row input {
    flex-basis: 100%;
  }

  .barcode-row .btn {
    flex: 1 1 0;
  }
}

.field-hint {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-xs);
  color: var(--text-dim);
  line-height: var(--lh-tight);
}

/* A field that shows red (:user-invalid) always says why (Oholiab fix 11). */
.field-error {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-sm);
  color: var(--danger);
  line-height: var(--lh-tight);
}

.lookup-status {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.lookup-status.is-error {
  color: var(--danger);
}

.lookup-status.is-ok {
  color: var(--done);
}

.cover-preview-wrap {
  margin-top: var(--space-3);
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.cover-preview {
  width: 72px;
  height: 108px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg-card);
}

.cover-preview-placeholder {
  width: 72px;
  height: 108px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg-card);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-xs);
  color: var(--text-dim);
}

/* Google Books attribution (plain text, --text-muted, no new colour). The official mark is
   grey + Google colours on transparent, made for light pages, so the plate stays paper
   (#fffdf8) rather than the dark body-text colour. */
.gb-attr {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  margin: var(--space-2) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.gb-attr[hidden],
.gb-cover-note[hidden] {
  display: none;
}

.gb-mark-plate {
  display: inline-flex;
  padding: 2px 4px;
  border-radius: 4px;
  background: #fffdf8;
}

.gb-mark {
  display: block;
  width: 62px;
  height: 30px;
}

.gb-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  color: var(--text-muted);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.gb-link:hover {
  color: var(--text);
}

.gb-link:focus-visible {
  outline: none;
  box-shadow: var(--focus);
  border-radius: 4px;
}

.gb-cover-note {
  margin: 0;
  align-self: center;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* Music: square preview (album art is 1:1); same width as the 2:3 frames */
.cover-preview-wrap[data-type="music"] .cover-preview,
.cover-preview-wrap[data-type="music"] .cover-preview-placeholder {
  height: 72px;
}

/* Scan modal */
.qr-reader {
  width: 100%;
  min-height: 220px;
  margin: var(--space-3) 0;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #000;
}

.qr-reader video {
  width: 100% !important;
  border-radius: var(--radius-sm);
}

.scan-panel {
  max-width: 420px;
}

/* Reference links (modal + cards) */
.ref-links-row {
  margin-top: var(--space-1);
}

.ref-links-label {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  margin-bottom: var(--space-2);
}

.ref-links,
.card-refs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.ref-link,
.card-ref {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-2);
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  background: var(--bg-elevated);
  color: var(--accent-text);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  text-decoration: none;
  line-height: var(--lh-tight);
}

.ref-link:hover {
  border-color: var(--accent-border);
  background: var(--accent-dim);
}

/* On cards the pills are secondary: accent is reserved for action */
.card-ref {
  color: var(--text-muted);
  border-color: var(--border);
  background: transparent;
}

.card-ref:hover {
  color: var(--accent-text);
  border-color: var(--accent-border);
}

.ref-link:focus-visible,
.card-ref:focus-visible {
  outline: none;
  box-shadow: var(--focus);
}

.card-refs {
  margin-left: auto;
}

/* Google Books credit on saved cards (Oholiab F2): the last line of the text column, 8px
   under the chips, 12px --text-dim, always underlined. The 44px tap area is padding-block
   with a matching negative margin, so it still reads as one 18px line. */
.card-credit {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-xs);
  line-height: 18px;
  min-width: 0;
}

.card-credit-link {
  display: inline-block;
  max-width: 100%;
  padding-block: calc((var(--tap) - 18px) / 2);
  margin-block: calc((var(--tap) - 18px) / -2);
  vertical-align: top;
  color: var(--text-dim);
  text-decoration: none;
}

/* Underline and focus ring sit on the text, not on the 44px tap box. */
.card-credit-text {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  text-decoration-color: currentColor;
  border-radius: 3px;
}

.card-credit-link:focus-visible {
  outline: none;
}

/* Comic source credit without a link (no source page known) */
.card-credit-plain {
  display: inline-block;
  max-width: 100%;
  color: var(--text-dim);
  overflow-wrap: anywhere;
}

.card-credit-link:focus-visible .card-credit-text {
  box-shadow: var(--focus);
}

/* Settings: API key wizard */
.settings-intro {
  margin: 0 0 var(--space-4);
  font-size: var(--fs-md);
  color: var(--text-muted);
  line-height: var(--lh-base);
}

/* App version line at the very end of Settings ("App version v31"), quiet like the credits */
.settings-version {
  margin: var(--space-4) 0 0;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  line-height: var(--lh-tight);
  text-align: center;
}

/* Music Lookup credit under the key cards (links styled like the footer credits) */
.settings-credit {
  margin-top: var(--space-4);
}

.settings-credit a {
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.settings-credit a:hover {
  color: var(--accent-text);
}

.api-key-cards {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.api-key-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: #f4efe6; /* recessed paper inside the sheet; the page oak must not enter here */
  padding: var(--space-4);
  outline: none;
}

.api-key-card.is-focus {
  border-color: var(--accent-border);
  box-shadow: 0 0 0 2px var(--accent-dim);
}

.api-key-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

.api-key-card-head h3 {
  margin: 0;
  font-size: var(--fs-base);
}

.api-key-status {
  flex-shrink: 0;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.02em;
  padding: var(--space-1) var(--space-2);
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  background: var(--bg-elevated);
  color: var(--text-muted);
}

.api-key-status[data-status="set"] {
  color: var(--done);
  border-color: var(--done-border);
  background: var(--done-dim);
}

.api-key-blurb {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: var(--lh-base);
}

.api-key-steps {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-5);
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: var(--lh-base);
}

.api-key-steps li + li {
  margin-top: var(--space-1);
}

.api-key-field {
  margin-bottom: var(--space-3);
}

.api-key-input-row {
  display: flex;
  gap: var(--space-2);
  align-items: stretch;
}

.api-key-input-row input {
  flex: 1;
  min-width: 0;
}

.api-key-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

.api-key-actions a.btn {
  text-decoration: none;
}

/* One filled primary per area: key-card Save becomes an outlined accent button */
.api-key-cards .api-key-actions .btn-primary {
  background: transparent;
  color: var(--accent-text);
  border-color: var(--accent-border);
}

.api-key-cards .api-key-actions .btn-primary:hover {
  background: var(--accent-dim);
  color: var(--accent-text);
  border-color: var(--accent);
}

.api-key-test-status {
  margin: var(--space-3) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.api-key-test-status.is-ok {
  color: var(--done);
}

.api-key-test-status.is-fail {
  color: var(--danger);
}

.settings-done-row {
  justify-content: flex-end;
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

.api-key-hint-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin: var(--space-3) 0 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--accent-border);
  background: var(--accent-dim);
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: var(--lh-tight);
}

.api-key-hint-banner #api-key-hint-text {
  flex: 1;
  min-width: 12rem;
}

/* Backup & restore (iCloud via share sheet) */
.backup-banner-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.backup-card {
  margin-bottom: var(--space-5);
}

.backup-last {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-md);
  color: var(--text);
}

.backup-actions {
  margin-bottom: var(--space-3);
}

/* Back up/Restore and Export/Import stay together as pairs above 559px (one row when it
   fits, otherwise the pairs wrap as units so no button is orphaned). */
.backup-action-group {
  display: flex;
  flex-wrap: wrap;
  gap: inherit;
}

/* R1 (re-review): keep Settings action rows on one line at phone widths */
@media (max-width: 559px) {
  .api-key-actions {
    gap: var(--space-1);
  }

  .api-key-actions .btn-sm {
    padding-inline: var(--space-2);
  }
}

/* Oholiab spec (Sep 27): at 559px and below the four backup buttons form a 2x2 grid of
   equal-width buttons: row 1 Back up, Restore; row 2 Export, Import. The pair wrappers
   dissolve into the grid so DOM/focus order stays Back up > Restore > Export > Import. */
@media (max-width: 559px) {
  .backup-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: stretch; /* both buttons in a row share one height */
  }

  .backup-action-group {
    display: contents;
  }

  .backup-actions .btn {
    min-width: 0;
    min-height: 40px;
    text-wrap: balance; /* at 320 "Restore from file" wraps as "Restore / from file" */
  }
}

/* Step 7: games import card (same card as Backup; list preview, no chips) */
.import-field,
.import-default {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.import-field {
  margin-bottom: var(--space-3);
}

.import-field label,
.import-default label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
}

.import-card textarea {
  width: 100%;
  min-height: calc(6 * 1.5em + 2 * var(--space-2));
  resize: vertical;
  line-height: 1.5;
}

.import-card textarea::placeholder,
.import-input::placeholder {
  color: var(--text-dim);
}

.import-message {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-md);
  color: var(--text);
}

.import-message[data-kind="error"] {
  color: var(--danger);
}

/* One row of three (Oholiab step 7). "In progress" needs ~122px of select,
   so below 560px Platform takes its own line, and below 380px each one does. */
.import-defaults {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}

@media (max-width: 559px) {
  .import-defaults {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .import-default:first-child {
    grid-column: 1 / -1;
  }
}

@media (max-width: 379px) {
  .import-defaults {
    grid-template-columns: minmax(0, 1fr);
  }
}

.import-defaults .select,
.import-defaults .import-input {
  width: 100%;
  min-width: 0;
  text-overflow: ellipsis;
}

.import-defaults-hint {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.import-summary {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

.import-rows {
  list-style: none;
  margin: 0 0 var(--space-3);
  padding: 0;
  border-top: 1px solid var(--border);
}

.import-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-md);
}

.import-row-text {
  flex: 1 1 180px;
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--text);
}

.import-row.is-dup .import-row-text,
.import-more {
  color: var(--text-muted);
}

.import-line {
  color: var(--text-dim);
  font-size: var(--fs-sm);
}

.import-meta {
  color: var(--text-muted);
}

.import-reason,
.import-dup {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-sm);
}

.import-reason {
  color: var(--danger);
}

/* Fixed width (fits "Update") so every row menu shares one left edge (Oholiab O1) */
.import-choice {
  flex: 0 0 112px;
  width: 112px;
}

/* CLZ duplicate rows: text and status line first, then the choice buttons on their own
   line. Buttons are at least 44px tall and wrap at 320px without overlapping. */
.import-row.has-choice {
  flex-direction: column;
  align-items: stretch;
}

.import-row.has-choice .import-row-text {
  flex: 0 0 auto; /* a column: don't turn the row text's flex-basis into height */
}

.clz-dup-status {
  display: block;
}

.clz-choice-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.clz-choice-btn {
  flex: 1 1 auto;
  min-height: 44px;
  min-width: 0;
  white-space: nowrap;
  border: 1px solid var(--border-strong);
  background: var(--bg-elevated);
  color: var(--text);
}

.clz-choice-btn.is-selected {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--text-on-accent);
}

/* 10. "This row:" on its own small line just above a row's buttons: they override the
   default for that row only. The buttons keep their single row at 390. */
.clz-row-label {
  flex: 1 0 100%;
  margin-bottom: calc(var(--space-1) - var(--space-2));
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* 9. Narrow phones (320): Update / Separate copy / Skip stacked full width, 44px, 8px gap. */
@media (max-width: 360px) {
  .clz-choice-group {
    flex-direction: column;
    align-items: stretch;
  }

  .clz-row-label {
    flex: 0 0 auto;
  }
}

.import-actions {
  display: flex;
  justify-content: flex-end;
}

@media (max-width: 559px) {
  .import-card .btn,
  .import-choice,
  .import-input,
  .import-default .select {
    min-height: var(--tap);
  }

  .import-actions .btn {
    width: 100%;
  }
}

/* CLZ import card: same parts as the games import card */
.clz-controls {
  grid-template-columns: minmax(0, 1fr);
  max-width: 280px;
  margin-bottom: var(--space-3);
}

.clz-subhead {
  margin: var(--space-3) 0 var(--space-1);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
}

.clz-dup-default {
  max-width: 280px;
  margin-bottom: var(--space-2);
}

.clz-dup-default .select,
.clz-mapping-fields .select {
  width: 100%;
  min-width: 0;
  text-overflow: ellipsis;
}

.clz-mapping-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}

@media (max-width: 379px) {
  .clz-mapping-fields {
    grid-template-columns: minmax(0, 1fr);
  }
}

.clz-map-row.is-missing label {
  color: var(--danger);
}

/* "Update existing" / "Add as separate copy" need more room than Skip / Add / Update */
.clz-choice {
  flex: 0 1 190px;
  width: 190px;
  max-width: 100%;
  min-width: 0;
}

.clz-actions {
  flex-wrap: wrap;
  gap: var(--space-2);
}

@media (max-width: 559px) {
  .clz-mapping-fields .select,
  .clz-dup-default .select {
    min-height: var(--tap);
  }

  .clz-choice {
    flex: 1 1 100%;
    width: 100%;
  }
}

/* Remove last import: 16px under Preview, muted line, Delete-style button */
.import-last {
  margin-top: var(--space-4);
}

.import-last[hidden] {
  display: none;
}

.import-last-line {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* Toast with one action (Undo) */
.toast.has-action {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-block: var(--space-2);
}

/* .toast prefix so the phone .btn-sm rule (40px) doesn't shrink it below 44px */
.toast .toast-action {
  flex: 0 0 auto;
  min-height: var(--tap);
}

.backup-help {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.backup-help summary {
  cursor: pointer;
  color: var(--accent-text);
  font-weight: var(--fw-medium);
  padding: var(--space-1) 0;
}

.backup-help summary:focus-visible {
  outline: none;
  box-shadow: var(--focus);
  border-radius: 4px;
}

.backup-help-label {
  margin: var(--space-3) 0 var(--space-1);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

.backup-help .field-hint {
  margin: var(--space-2) 0 0;
}

.settings-section-title {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-base);
}

/* Mobile tap targets */
@media (max-width: 559px) {
  .chip,
  .select,
  .search,
  .item-form input,
  .item-form select,
  .api-key-field input,
  .barcode-row .btn,
  .form-actions .btn,
  .confirm-actions .btn,
  .settings-done-row .btn {
    min-height: var(--tap);
  }

  .btn-icon {
    min-width: var(--tap);
    min-height: var(--tap);
  }

  /* v1.2 checkbox rows and the "Complete in box" button: 44px targets on phones */
  label.check,
  .btn-cib {
    min-height: var(--tap);
  }

  .card-ref,
  .ref-link {
    position: relative;
    padding: 0.375rem 0.625rem;
  }

  /* 30px pill, 44px tap area (the list click handler already ignores taps on a.card-ref) */
  .card-ref::after,
  .ref-link::after {
    content: "";
    position: absolute;
    inset: -8px 0;
  }

  .btn-sm,
  .backup-help summary {
    min-height: var(--tap-sm);
  }

  .btn-sm {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .backup-help summary {
    padding-block: 0.625rem; /* keeps the native disclosure marker (display: list-item) */
  }
}

/* Respect reduced-motion preferences */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Balanced last lines for running text (Oholiab polish): avoids one-word last lines
   like "URL." without changing line counts. Ignored by browsers without support. */
.api-key-blurb,
.settings-intro,
.api-key-steps li,
.field-hint,
.api-key-test-status,
.seed-banner p,
.site-footer p {
  text-wrap: pretty;
}

/* The hidden attribute always wins over component display rules (B1/B2, review fix 1).
   !important so no class-level display (.seed-banner, .ref-link, …) can ever override it;
   nothing in the app or in html5-qrcode shows an element while it carries `hidden`. */
[hidden] {
  display: none !important;
}

/* Comic cover picker (after a comic Lookup). Grid that wraps at any width; each cover is a
   tall tap target with its label under it and a "Variant" tag when the source says so. */
/* Comic source credit: plain small text with ordinary links (not a button). */
.comic-credit {
  margin-top: var(--space-2);
  min-width: 0;
}

.source-credit,
.source-credit-issue {
  margin: 0;
  font-size: var(--fs-xs);
  line-height: 1.45;
  color: var(--text-muted);
  overflow-wrap: break-word;
}

.source-credit-issue {
  margin-top: var(--space-1);
}

/* Comic credits as separate lines (GCD, Metron, cover sentence), --space-1 apart. */
.credit-line {
  display: block;
}

.credit-line + .credit-line {
  margin-top: var(--space-1);
}

.source-credit a,
.source-credit-issue a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.source-credit a:hover,
.source-credit-issue a:hover {
  color: var(--text);
}

.comic-picker {
  margin-top: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-card);
  min-width: 0;
}

.comic-picker-title {
  margin: 0;
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

.comic-picker-hint {
  margin: var(--space-1) 0 var(--space-3);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: var(--lh-tight);
}

.comic-cover-list {
  display: grid;
  /* Two columns on phones, three from 420px (Oholiab). */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2);
}

@media (min-width: 420px) {
  .comic-cover-list {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.comic-cover {
  appearance: none;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-1);
  min-width: 0;
  min-height: var(--tap);
  padding: var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.comic-cover img {
  display: block;
  width: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border-radius: 4px;
  background: var(--bg-card-hover);
}

/* Variant names: two lines with an ellipsis; the picked cover shows the full name. */
.comic-cover-label {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  font-size: var(--fs-xs);
  line-height: var(--lh-tight);
  overflow-wrap: anywhere;
}

.comic-cover.is-selected .comic-cover-label {
  display: block;
  -webkit-line-clamp: unset;
  line-clamp: none;
  overflow: visible;
}

/* A cover name with no image yet: a plain tile the size of a cover. */
.comic-cover-blank {
  display: block;
  width: 100%;
  aspect-ratio: 2 / 3;
  border-radius: 4px;
  border: 1px dashed var(--border-strong);
  background: var(--bg-card-hover);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-2);
}

.comic-cover-blank-text {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-align: center;
  line-height: 1.3;
}

/* Names-only mode (COMIC_COVER_IMAGES = false): no 2:3 boxes. One column, one variant
   per row (≥44px), the full name on up to two lines, the same outline + check when
   picked, and the issue link at the end of the row. */
.comic-cover-list.is-names {
  grid-template-columns: minmax(0, 1fr);
}

/* Mixed (Oholiab): only some rows have a picture. The pictured row(s) sit on top as
   ~140px 2:3 tiles with the GCD name under them; the other names follow as the names-only
   rows above. A name without a picture never gets a blank tile beside a photo. */
.comic-cover-list.is-mixed {
  grid-template-columns: minmax(0, 1fr);
}

.comic-photo-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}

.comic-photo-item {
  flex: 0 0 140px;
  width: 140px;
  max-width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.comic-cover.comic-photo {
  width: 100%;
}

/* The pictured tile's comics.org link: the names-only rows' link, under the tile (not on
   it), --space-1 below it, its own 44px tap area. */
.comic-cover-link.comic-photo-link {
  align-self: flex-end; /* right edge of its row, like the names-only rows' links */
  margin-top: var(--space-1);
}

/* A lone pictured tile: its row is the full list width, so its link lines up with the names-only
   rows' links. The tile keeps its 140px size. */
.comic-photo-item:only-child {
  flex: 1 1 100%;
  width: auto;
}
.comic-photo-item:only-child .comic-cover.comic-photo {
  width: 140px;
  max-width: 100%;
}

.comic-name-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

.comic-cover.comic-name {
  flex: 1 1 0;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 2px;
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  padding-right: calc(var(--space-3) + 26px); /* room for the check mark */
}

.comic-cover.comic-name .comic-cover-label {
  width: 100%;
  min-width: 0;
  font-size: var(--fs-sm);
  overflow-wrap: break-word;
}

.comic-cover.comic-name.is-selected::after {
  top: 50%;
  left: auto;
  right: var(--space-2);
  transform: translateY(-50%);
}

.comic-name-variant {
  flex: 0 0 auto;
  padding: 0 var(--space-1);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  background: transparent;
  color: var(--text-muted);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
}

.comic-cover-link {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  align-self: stretch;
  min-height: var(--tap);
  white-space: nowrap;
  justify-content: flex-end;
  min-width: var(--tap); /* a 44px tap area each, never overlapping the tile or another link */
  font-size: 13px;
  line-height: var(--lh-tight);
  color: var(--text-muted);
  text-decoration: underline;
  text-underline-offset: 2px;
  padding: 0 var(--space-1);
}

/* "Show all N covers": quiet full-width ghost button (.btn .btn-ghost) under the first
   five covers. */
.comic-show-all {
  grid-column: 1 / -1;
  width: 100%;
  min-height: var(--tap);
  color: var(--text-muted);
  font-size: var(--fs-sm);
}

.comic-show-all:hover {
  color: var(--text);
}

/* Busy / isn't-working line after Show all: directly under the covers. */
.comic-show-all-status {
  margin-top: var(--space-2);
}

/* The "pictures couldn't be checked" note (imagesPending) in the same spot: muted, never the error red. */
.comic-show-all-status.is-note {
  color: var(--text-muted);
}

.comic-cover-variant {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  padding: 1px var(--space-1);
  border-radius: 4px;
  background: var(--text);
  color: #fffdf8;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
}

.comic-cover:hover {
  border-color: var(--border-strong);
}

.comic-cover:focus-visible {
  outline: none;
  box-shadow: var(--focus);
}

.comic-cover.is-selected {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 2px var(--accent);
}

/* The check sits on the image's top-left corner; the Variant tag is top-right. */
.comic-cover.is-selected::after {
  content: "✓";
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--text-on-accent);
  font-size: var(--fs-sm);
  line-height: 22px;
  text-align: center;
}

.comic-picker-empty {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.comic-picker-url {
  margin-top: var(--space-3);
}

.comic-no-cover {
  margin-top: var(--space-2);
}

.comic-no-cover.is-selected {
  border-color: var(--accent);
  color: var(--accent-text);
  box-shadow: inset 0 0 0 1px var(--accent);
}

/* Save blocked until a comic cover is chosen: the disabled button plus the reason */
#btn-save:disabled,
#btn-save:disabled:hover,
#btn-save[aria-disabled="true"],
#btn-save[aria-disabled="true"]:hover {
  cursor: not-allowed;
  opacity: 0.55;
}

/* Muted while the picker is open; red only after a tap on the blocked Save (.is-error) */
.save-blocked {
  flex: 1 1 100%;
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  text-align: right;
}

.save-blocked.is-error {
  color: var(--danger);
}

/* Cover choices after Lookup. Wrap rather than overflow; each choice is a 44px tap. */
.cover-choices {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.cover-choice {
  flex: 0 0 auto;
  width: 72px;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.cover-choice img {
  display: block;
  width: 100%;
  height: 44px;
  min-height: 44px;
  object-fit: cover;
  border-radius: 4px;
}
.cover-choice.is-selected {
  outline: 2px solid var(--border-strong);
  outline-offset: 2px;
}
.btn-wrong-cover {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-top: var(--space-1);
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-muted);
  font: inherit;
  font-size: var(--fs-sm);
  cursor: pointer;
}
.btn-wrong-cover:hover {
  color: var(--text);
}
.btn-wrong-cover:focus-visible {
  outline: none;
  box-shadow: var(--focus);
}
#field-platform {
  width: 100%;
  max-width: 100%;
  min-width: 0;
}
@media (max-width: 320px) {
  .cover-choices {
    flex-wrap: wrap;
  }
  .cover-choice {
    width: calc(50% - var(--space-2));
    min-width: 44px;
  }
  #field-platform {
    font-size: var(--fs-md);
  }
}

/* CLZ cover fill (v36): a quiet count line on the oak (cream like the banners), no red, no spinner. */
.cover-queue {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-md);
  color: #f3ead7;
}
.cover-queue-line,
.cover-queue-note {
  margin: 0;
  overflow-wrap: anywhere;
}
.cover-queue-note {
  font-size: var(--fs-sm);
  opacity: 0.85;
}
.cover-queue-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 0.35em; /* 8px between wrapped rows; the space between segments */
}
.cq-seg {
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 0.3em; /* the same space before every dot */
}
/* The dot of a segment that ends its row (set by syncCoverQueueDots): kept in the text, not shown. */
.cq-dot.cq-dot-end {
  visibility: hidden;
}
.cover-queue-link {
  appearance: none;
  background: none;
  border: 0;
  margin: -12px 0; /* 44px tap height without growing the line: the gaps stay even with the other separators */
  padding: 0;
  min-height: var(--tap);
  display: inline-flex;
  align-items: center;
  font: inherit;
  color: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.cover-queue-link:focus-visible,
.cover-queue-btn:focus-visible,
.btn-pick-cover:focus-visible {
  outline: 2px solid #f3ead7;
  outline-offset: 2px;
}
.cover-queue-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.cover-queue .cover-queue-btn {
  min-height: var(--tap);
  color: #f3ead7;
  border-color: #f3ead7;
  background: transparent;
}
.btn-pick-cover {
  min-height: var(--tap);
  margin-top: var(--space-2);
  align-self: flex-start;
}

/* ---------- Tabletop tab ---------- */
.item-cover-placeholder.tabletop {
  letter-spacing: 0;
  font-size: 0.6875rem;
}

/* A lone expansion (base not in the library): small muted label above the title. */
.tt-kicker {
  margin: 0 0 2px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.tt-exp-count {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* Expansions under their base: a tight list, not cards. Indented 16px with a thin rule
   down the left in the base card's border colour; the Owned/Want badge sits right. */
.tt-exp-list {
  list-style: none;
  margin: var(--space-1) 0 0 16px;
  padding: 0;
  border-left: 2px solid color-mix(in srgb, var(--tabletop-stripe) 75%, var(--bg-card));
}

.tt-exp-row,
.tt-exp-more {
  appearance: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--tap);
  padding: var(--space-1) var(--space-3);
  border: 0;
  background: transparent;
  font: inherit;
  text-align: left;
  color: #f3ead7; /* on the oak, like the stats */
  cursor: pointer;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.tt-exp-title {
  font-size: 15px;
  font-weight: var(--fw-medium);
  min-width: 0;
  overflow-wrap: anywhere;
}

.tt-exp-more {
  font-size: var(--fs-sm);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.tt-exp-row .badge {
  flex-shrink: 0;
}

/* Badges on the oak need their paper backing to keep contrast. */
.tt-list .tt-exp-row .badge-status {
  background: #fffdf8;
}

.tt-exp-row:hover .tt-exp-title {
  text-decoration: underline;
}

.tt-exp-row:focus-visible,
.tt-exp-more:focus-visible {
  outline: none;
  box-shadow: var(--focus);
}

/* Inside the paper detail sheet the list reads dark. */
.tt-detail-body .tt-exp-row {
  color: var(--text);
}

.tt-detail-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.tt-detail-badges {
  display: flex;
  gap: var(--space-1);
  flex-wrap: wrap;
}

.tt-detail-h {
  margin: var(--space-3) 0 0;
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
}

.tt-detail-line {
  margin: 0;
  font-size: var(--fs-md);
}

.tt-muted {
  color: var(--text-muted);
}

.tt-two {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 var(--space-3);
}

@media (max-width: 359px) {
  .tt-two {
    grid-template-columns: 1fr;
  }
}

.tt-lookup-row .btn {
  min-height: var(--tap);
  align-self: flex-start;
}

.tt-dup {
  margin: var(--space-3) 0;
  padding: var(--space-3);
  border: 1px solid var(--accent-border);
  border-radius: var(--radius-sm);
  background: var(--accent-dim);
}

.tt-dup p {
  margin: 0 0 var(--space-2);
}

.tt-delete {
  color: var(--danger);
}

.tabletop-view[hidden] {
  display: none;
}

/* Tabletop status badges, the same on the card, the expansion row and the sheet:
   Owned filled magenta, Want a magenta outline. 1.5px border on both so they match in size. */
.badge.tt-status {
  border: 1.5px solid #8c1d5a;
  padding: calc(var(--space-1) - 1.5px) calc(var(--space-2) - 1.5px);
}

.badge.tt-status.owned {
  background: #8c1d5a;
  color: #ffffff;
}

.badge.tt-status.want {
  background: transparent;
  color: #8c1d5a;
}

/* Expansion rows sit on the oak, where magenta text on a clear fill would fail contrast:
   the Want outline keeps a paper backing there (Owned is already filled). */
.tt-list .tt-exp-row .badge.tt-status.want {
  background: #fffdf8;
}

.tt-list .tt-exp-row .badge.tt-status.owned {
  background: #8c1d5a;
}

/* Subtype chip (Board, RPG, Expansion…): same pink tint, squared to 4px. */
.badge-type.tabletop {
  border-radius: 4px;
}

/* Sheet: BGG id is a quiet reference line under the description. */
.tt-detail-line.tt-bgg-id {
  font-size: 13px;
  color: var(--text-muted);
}

/* ---------- Tabletop merge: same-title prompt (one match at a time) ---------- */
.tt-match-panel {
  max-width: 760px;
}

.tt-match-progress {
  margin: 0 0 var(--space-1);
  font-size: 13px;
  color: var(--text-muted);
}

.tt-match-lede {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-sm);
}

/* Phones: stacked, this shelf on top. Wider: two columns side by side. */
.tt-match-pair {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

@media (min-width: 600px) {
  .tt-match-pair {
    grid-template-columns: 1fr 1fr;
  }
}

.tt-match-side {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-card);
  min-width: 0;
}

.tt-match-label {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.tt-match-name {
  margin: 0;
  font-size: 16px;
  font-weight: var(--fw-medium);
  overflow-wrap: anywhere;
}

.tt-match-side .item-meta,
.tt-match-parent {
  margin: 0;
}

.tt-match-parent {
  font-size: var(--fs-sm);
}

/* A value that differs from the other copy: bold only, never a colour. */
.tt-match-side b.tt-diff {
  font-weight: 700;
  color: inherit;
}

.tt-match-side .badge.tt-diff {
  font-weight: 800;
}

/* The newer copy: a quiet line, no colour cue. */
.tt-match-updated {
  margin: 0;
  font-size: 13px;
  color: var(--text-muted);
}

.tt-match-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.tt-match-actions .btn {
  min-height: var(--tap);
}

/* Update existing / Skip: neutral outlines, never the danger style. */
.btn.btn-outline {
  background: transparent;
  border: 1px solid var(--text-muted); /* 7:1 on paper: the outline itself reads as a button */
  color: var(--text);
}

.tt-match-rest {
  appearance: none;
  display: block;
  min-height: var(--tap);
  margin-top: var(--space-2);
  padding: 0 var(--space-1);
  border: 0;
  background: transparent;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.tt-match-rest[hidden] {
  display: none;
}

.tt-match-rest:focus-visible {
  outline: none;
  box-shadow: var(--focus);
}

/* Prompt buttons: focus is an outline drawn outside the box (no ring shadow, no transform),
   so a focused button keeps exactly the size of the others. */
.tt-match-actions .btn,
.tt-match-actions .btn:hover,
.tt-match-actions .btn:focus,
.tt-match-actions .btn:active {
  transform: none;
}

.tt-match-actions .btn:focus {
  box-shadow: none;
}

.tt-match-actions .btn:focus-visible {
  box-shadow: none;
  outline: 2px solid #c46a3a;
  outline-offset: 2px;
}

.tt-match-progress[hidden] {
  display: none;
}

@media (max-width: 479px) {
  .tt-match-actions {
    flex-direction: column;
  }

  .tt-match-actions .btn {
    width: 100%;
  }
}

/* Re-asked match (another tab added it): same muted 13px as the 'Updated' line. */
.tt-match-late {
  margin: 0 0 var(--space-2);
  font-size: 13px;
  color: var(--text-muted);
}

.tt-match-late[hidden] {
  display: none;
}

/* Duo fix (v40): the Add / Edit sheet and every row in it stay inside the safe area,
   so nothing (Lookup, the right edge of Title / Author) runs under a side inset. */
#modal > .modal-panel {
  max-width: min(560px, calc(100vw - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px)));
  padding-left: max(var(--space-5), env(safe-area-inset-left, 0px));
  padding-right: max(var(--space-5), env(safe-area-inset-right, 0px));
}

#modal > .modal-panel .item-form,
#modal > .modal-panel .item-form > * {
  min-width: 0;
  max-width: 100%;
}

/* Duo fix (v40): iOS Safari zooms the page when a focused field's text is under 16px.
   On touch screens every text-entry control is at least 16px; desktop keeps 14px.
   (No maximum-scale / user-scalable=no: pinch zoom stays available.) */
@media (hover: none) and (pointer: coarse) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]),
  select,
  textarea {
    font-size: max(16px, 1rem) !important;
  }
}

/* ---------- Tabletop barcode lookup (flag off: none of this is in the page) ---------- */
.tt-upc .tt-upc-go {
  min-height: var(--tap);
  align-self: flex-start;
}

.tt-upc-panel {
  margin-top: var(--space-3);
}

.tt-upc-panel[hidden] {
  display: none;
}

.tt-upc-msg {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

.tt-upc-err {
  color: var(--danger);
}

.tt-upc-spin {
  flex: none;
  width: 16px;
  height: 16px;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: tt-upc-spin 0.9s linear infinite;
}

@keyframes tt-upc-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .tt-upc-spin {
    animation: none;
    border-top-color: var(--border-strong);
  }
}

.tt-upc-h {
  margin: 0 0 var(--space-1);
  font-size: 1rem;
}

.tt-upc-h:focus {
  outline: none;
}

.tt-upc-h:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.tt-upc-list {
  list-style: none;
  margin: var(--space-2) 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

.tt-upc-cand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--tap);
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.tt-upc-cand:hover {
  border-color: var(--accent-border);
}

.tt-upc-cand:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.tt-upc-cand-static {
  cursor: default;
  margin: var(--space-2) 0;
}

.tt-upc-thumb {
  flex: none;
  width: 48px;
  height: 48px;
  object-fit: contain;
  border-radius: 4px;
  background: var(--border);
}

.tt-upc-cand-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.tt-upc-cand-name {
  overflow-wrap: anywhere;
}

.tt-upc-cand-year {
  font-size: 13px;
  color: var(--text-muted);
}

/* "None of these": a text button under the list, not a card (same look as .tt-match-rest). */
.tt-upc-secondary {
  appearance: none;
  display: inline-flex;
  align-items: center;
  min-height: var(--tap); /* 44px tap target; looks the same */
  margin-top: var(--space-1);
  padding: 0 var(--space-1);
  border: 0;
  background: transparent;
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.tt-upc-secondary:focus-visible {
  outline: none;
  box-shadow: var(--focus);
}

/* Match line ("Found …"): body size and ink, not a muted hint. */
.tt-upc-msg.tt-upc-found {
  font-size: var(--fs-md);
  color: var(--text);
}

/* Send-ask game row: same bordered row as the Confirm card; barcode never breaks. */
.tt-upc-nowrap {
  white-space: nowrap;
}

/* 12px from Look up barcode to its status line / panel; 24px from a shown panel to the next label. */
.tt-upc .tt-upc-panel {
  margin-top: var(--space-3);
}

.item-form .form-row.tt-upc:has(.tt-upc-panel:not([hidden])) {
  margin-bottom: var(--space-6);
}

.tt-upc-panel > :last-child,
.tt-upc-confirm > :last-child {
  margin-bottom: 0;
}

/* Lookup on only (#tt-upc exists): the Tabletop sheet's Cancel / Save bar is pinned like the
   v39 Add sheet bar (.item-form .form-actions): 1px rule, 24px fade above it. Flag off: no
   #tt-upc, so none of this applies and the sheet stays exactly as v39. */
#tt-form-modal > .modal-panel:has(#tt-upc) {
  scroll-padding-bottom: calc(8.25rem + env(safe-area-inset-bottom, 0px));
}

#tt-form:has(#tt-upc) > .form-actions-right {
  position: sticky;
  bottom: calc(-1 * var(--space-6));
  z-index: 1;
  margin-top: var(--space-5);
  margin-bottom: calc(-1 * var(--space-6));
  padding-top: calc(var(--space-4) + var(--space-4));
  padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--border-strong);
  background: var(--bg-elevated);
  justify-content: flex-end;
}

#tt-form:has(#tt-upc) > .form-actions-right::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 1px);
  height: var(--space-6);
  background: linear-gradient(to bottom, rgba(255, 253, 248, 0), var(--bg-elevated));
  pointer-events: none;
}

/* While the Confirm card or the send-ask is open, Save is an outline (one filled button on
   screen). Back to filled as soon as the question is answered (the card is gone). */
#tt-form:has(.tt-upc-confirm) #tt-save,
#tt-form:has(.tt-upc-confirm) #tt-save:hover {
  background: transparent;
  border: 1px solid var(--text-muted);
  color: var(--text);
}

.tt-upc-confirm p {
  margin: 0 0 var(--space-2);
}

.tt-upc-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.tt-upc-actions .btn {
  min-height: var(--tap);
}

/* Narrow phones (320): Cancel / Save only / Save and send stay on one row. */
@media (max-width: 359px) {
  .tt-upc-actions {
    flex-wrap: nowrap;
  }

  .tt-upc-actions .btn {
    padding-left: var(--space-2);
    padding-right: var(--space-2);
    white-space: nowrap;
  }
}

/* BoardGameGeek credit ("Powered by BGG", BGG's official art, never recoloured). Built only
   when GAMEUPC_LOOKUP.enabled is true. The link is the tap target (>= 44px tall via padding);
   the img's width/height attributes reserve its box, so the result panel doesn't shift. 36px
   tall on phones, 40px from 601px. */
.tt-bgg-logo {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 4px 0;
  box-sizing: border-box;
  border-radius: var(--radius-sm);
  max-width: 100%;
}
.tt-bgg-logo img {
  display: block;
  height: 36px;
  width: auto;
  max-width: 100%;
}
.tt-bgg-logo:focus-visible {
  outline: 2px solid var(--accent-text);
  outline-offset: 2px;
}
.tt-upc-panel > .tt-bgg-logo {
  margin-top: var(--space-2);
}
/* Footer line under the saved list. The list sits on the oak (dark), so the navy lettering
   goes on a white chip (8px padding); the text is the muted cream used on the oak. */
.tt-bgg-credit {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2) var(--space-3);
  margin: var(--space-6) 0 0;
  padding-inline: max(0px, env(safe-area-inset-left)) max(0px, env(safe-area-inset-right));
  font-size: 13px;
  line-height: 1.4;
  color: #c4b8a2;
  text-align: center;
}
.tt-bgg-credit .tt-bgg-logo {
  padding: 8px;
  min-height: 52px;
  background: #fff;
}
.tt-bgg-credit .tt-bgg-logo:focus-visible {
  outline-color: #f3ead7;
}
@media (min-width: 601px) {
  .tt-bgg-logo img {
    height: 40px;
  }
}

/* Game choices use the same restrained, bordered confirmation-row language as Tabletop.
   A cover and the no-cover tile always occupy the same frame, including image failures. */
#game-match-picker { margin-block: var(--space-4); }
.game-match-list { display: grid; gap: var(--space-2); margin-block: var(--space-3); }
.game-match { display: grid; grid-template-columns: 64px minmax(0, 1fr); align-items: center; gap: 12px; padding: 12px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg-elevated); color: var(--text); width: 100%; text-align: left; font: inherit; min-height: 44px; cursor: pointer; }
.game-match-cover, .game-match-no-cover { width: 64px; height: 96px; border-radius: 4px; }
.game-match-cover { display: block; flex-shrink: 0; }
.game-match-cover img { width: 64px; height: auto; max-height: 96px; object-fit: contain; display: block; border-radius: 4px; }
.game-match-no-cover { display: flex; align-items: center; justify-content: center; border: 1px solid var(--border); color: var(--text-muted); font-size: 9px; text-align: center; }
.game-match-name { display: block; font-size: 14px; color: var(--text); }
.game-match-meta { display: block; font-size: 13px; color: var(--text-muted); }
.game-match[aria-pressed="true"] { outline: 2px solid var(--accent); outline-offset: 1px; }
.game-match:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.game-match:disabled { cursor: not-allowed; }
#modal:has(#game-match-picker:not([hidden])) .form-actions .btn-primary { background: transparent; color: var(--text); border-color: var(--border-strong); transition: none; }
