/* Single Scanner — mobile-first, dark, built for one-handed use on an iPhone. */

:root {
  --bg: #14161a;
  --surface: #1c1f25;
  --surface-2: #24272e;
  --surface-3: #2c3038;
  --border: #33373f;
  --text: #e9ebee;
  --muted: #9aa1ab;
  --accent: #d66a3a;
  --accent-hi: #e9814f;
  --on-accent: #ffffff;
  --good: #46a86f;
  --warn: #d9a13b;
  --danger: #d8544f;
  --radius: 14px;
  --tap: 48px;
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  /* The side gutter of every bar and list, grown where a notch or the rounded
     corners of a phone held sideways would otherwise cut into it. */
  --gutter-l: max(12px, var(--safe-l));
  --gutter-r: max(12px, var(--safe-r));
}

* {
  box-sizing: border-box;
}

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font:
    16px/1.45 -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    system-ui,
    sans-serif;
  /* Stops the whole page rubber-banding while inner lists still scroll. */
  overscroll-behavior-y: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

/* Fills the screen including the area behind the iPhone's home indicator. */
#app {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

/* 16px minimum keeps iOS from zooming the page when a field is focused. */
input,
select,
textarea {
  font-size: 16px;
}

button {
  cursor: pointer;
  border: 0;
  background: none;
}

.hidden {
  display: none !important;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* ------------------------------------------------------------------ header */

.topbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: calc(var(--safe-t) + 8px) var(--gutter-r) 8px var(--gutter-l);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
  z-index: 3;
}

.topbar h1 {
  font-size: 17px;
  font-weight: 600;
  margin: 0;
  letter-spacing: 0.2px;
  flex: 1 1 auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.topbar .mark {
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  border-radius: 50%;
}

/* Never wraps: a two-line chip squeezed the sheet title down to "Verzameli…".
   44px is the smallest target Apple's guidelines call comfortable for a thumb. */
.chip-btn {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 14px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
  border: 1px solid var(--border);
  white-space: nowrap;
}

.chip-btn:active {
  background: var(--surface-3);
}

.chip-btn.back {
  padding-left: 10px;
}

.chip-btn.back::before {
  content: '‹';
  font-size: 20px;
  font-weight: 500;
  line-height: 0;
  margin-right: 5px;
  position: relative;
  top: 1px;
}

.chip-btn.accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* -------------------------------------------------------------- scan view */

.view {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.camera-wrap {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  background: #000;
  overflow: hidden;
}

.camera-wrap video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The frame a label photo was taken from, held over the live video while it
   is read (scanner.freeze). Sized and placed by the script; the brief flash
   says the shutter went. */
.camera-wrap .frozen-frame {
  position: absolute;
  display: block;
  max-width: none;
  animation: shutter-flash 0.32s ease-out;
}

@keyframes shutter-flash {
  from {
    filter: brightness(2.4);
  }
  to {
    filter: brightness(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .camera-wrap .frozen-frame {
    animation: none;
  }
}

/* The bracketed window the barcode should sit inside. */
.reticle {
  position: absolute;
  left: 8%;
  right: 8%;
  top: 50%;
  transform: translateY(-50%);
  height: 30%;
  border-radius: 12px;
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.42);
  pointer-events: none;
}

.reticle::before,
.reticle::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: 24px;
  border: 2px solid rgba(255, 255, 255, 0.85);
  border-radius: 12px;
}

.reticle::before {
  top: 0;
  border-bottom: 0;
}

.reticle::after {
  bottom: 0;
  border-top: 0;
}

/* A child of the reticle, so the sweep is measured against the bracketed
   window. The old version travelled a fixed 14vh either way, which is
   unrelated to the reticle's height and sent the line outside the brackets on
   most screens. Percentages here are of the reticle, and the few points of
   inset keep the line clear of its 2px borders. */
.scan-line {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 2px;
  background: var(--accent-hi);
  box-shadow: 0 0 10px var(--accent-hi);
  opacity: 0.9;
  pointer-events: none;
  animation: sweep 2.1s ease-in-out infinite;
}

@keyframes sweep {
  0%,
  100% {
    top: 4%;
  }
  50% {
    top: 92%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .scan-line {
    animation: none;
  }
}

.cam-status {
  position: absolute;
  left: 12px;
  right: 12px;
  top: 12px;
  padding: 9px 12px;
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(6px);
  font-size: 14px;
  text-align: center;
  pointer-events: none;
}

.cam-status.error {
  background: rgba(150, 40, 36, 0.86);
  pointer-events: auto;
}

.cam-overlay-btns {
  position: absolute;
  right: 12px;
  bottom: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.round-btn {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.25);
  color: #fff;
  font-size: 19px;
  display: grid;
  place-items: center;
}

.round-btn.on {
  background: var(--accent);
  border-color: var(--accent);
}

/* "2×" / "1×" is text, not a symbol, so it is set a little smaller and bold. */
.zoom-btn {
  font-size: 15px;
  font-weight: 700;
}

/* The action bar under the camera. */
.scan-actions {
  flex: 0 0 auto;
  padding: 10px var(--gutter-r) calc(10px + var(--safe-b)) var(--gutter-l);
  background: var(--bg);
  border-top: 1px solid var(--border);
  display: grid;
  /* minmax(0, …): an auto column grows to the longest unwrapped text, and a
     box called "Soul, funk & disco uit de jaren 70 - dl2" pushed the whole
     action bar past the edge of the phone. */
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
}

.btn {
  min-height: var(--tap);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--text);
  font-size: 16px;
  font-weight: 600;
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0 14px;
  width: 100%;
}

.btn:active {
  background: var(--surface-3);
}

/* The export links are anchors styled as buttons. */
a.btn {
  text-decoration: none;
}

/* "Nieuwe doos" with a plus in front that is drawn, not typed, so the label
   stays exactly the words. */
.btn-plus::before {
  content: '+';
  font-size: 1.2em;
  font-weight: 700;
  line-height: 1;
}

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

.btn.primary:active {
  background: var(--accent-hi);
}

.btn.big {
  min-height: 58px;
  font-size: 18px;
}

.btn.danger {
  color: var(--danger);
  border-color: rgba(216, 84, 79, 0.45);
}

.btn:disabled {
  opacity: 0.5;
}

.btn-row {
  display: flex;
  gap: 8px;
}

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

/* Barcode / Photo mode switch, directly under the camera. The method filter of
   a box (.seg) is the same control with more buttons. */
.mode-toggle,
.seg {
  display: flex;
  gap: 4px;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.mode-btn,
.seg-btn {
  flex: 1 1 0;
  min-width: 0;
  min-height: 44px;
  border-radius: 10px;
  font-size: 15px;
  font-weight: 600;
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.mode-btn small {
  font-size: 11px;
  font-weight: 500;
  opacity: 0.75;
}

.mode-btn[aria-pressed='true'],
.seg-btn[aria-pressed='true'] {
  background: var(--surface-3);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--border);
}

/* Four buttons share the width of a 360px phone, so the labels are a touch
   smaller and never wrap. */
.seg-btn {
  font-size: 14px;
  padding: 0 4px;
  white-space: nowrap;
}

/* The box everything scanned goes into. The same control sits on the save
   sheet for this one record. Accent border once a box is chosen. */
.box-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 0 14px;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  text-align: left;
}

.box-chip:active {
  background: var(--surface-2);
}

.box-chip.is-set {
  border-color: var(--accent);
}

.bc-label {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
}

.bc-value {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The scanner's two choices side by side: the box, and what is being scanned.
   Built the same way so they read as one row of settings. */
.scan-choices {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
}

/* Label above value: "Soort / Automatisch" did not fit beside each other in
   half a 375px phone. */
.box-chip.stacked {
  min-height: 54px;
  padding: 6px 10px;
  gap: 8px;
}

.box-chip.stacked .bc-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}

.box-chip.stacked .bc-label {
  font-size: 12px;
}

.box-chip.stacked .bc-value {
  font-size: 16px;
}

/* A picture at the front of a chip: a box for "Doos", the type for "Soort". */
.bc-icon {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  color: var(--muted);
}

.bc-icon svg {
  width: 24px;
  height: 24px;
}

.box-chip.is-set .bc-icon {
  color: var(--accent-hi);
}

/* Set by the box: a lock where the caret was, and a quieter border, because
   the chip is not the place to change it. */
.bc-lock {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  color: var(--muted);
}

.bc-lock svg {
  width: 15px;
  height: 15px;
  display: block;
}

.box-chip.is-locked {
  border-style: dashed;
}

/* The save sheet's box, while it is for another type than the record. */
.box-chip.is-conflict {
  border-color: var(--warn);
  box-shadow: inset 0 0 0 1px var(--warn);
}

/* ----------------------------------------------------- bottom sheet ---

   A panel that slides up over what is on screen, for a short choice (what
   the scanner scans). The backdrop closes it. */
.sheet.sheet-bottom {
  background: transparent;
  justify-content: flex-end;
}

.sheet-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  animation: fade-in 0.18s ease-out;
}

.bottom-panel {
  position: relative;
  width: 100%;
  max-width: 560px;
  max-height: 88vh;
  margin: 0 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 8px var(--gutter-r) calc(16px + var(--safe-b)) var(--gutter-l);
  background: var(--surface);
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: 22px 22px 0 0;
  box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.45);
  animation: slide-up 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.bottom-grip {
  width: 38px;
  height: 5px;
  margin: 2px auto 12px;
  border-radius: 3px;
  background: var(--surface-3);
}

.bottom-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}

.bottom-title {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
}

.bottom-close {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
}

.bottom-panel .notice {
  margin-bottom: 12px;
}

@keyframes slide-up {
  from {
    transform: translateY(100%);
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

/* One row per type: its picture in a tile, the name, what it means, a tick. */
.type-options {
  display: grid;
  gap: 8px;
}

.type-option {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  min-height: 66px;
  padding: 10px 14px 10px 10px;
  text-align: left;
  border-radius: 16px;
  background: var(--surface-2);
  border: 1px solid var(--border);
}

.type-option:active {
  background: var(--surface-3);
}

.type-option[aria-checked='true'] {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.type-option:disabled {
  opacity: 0.38;
}

.to-icon {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border-radius: 13px;
  background: var(--surface-3);
  color: var(--muted);
}

.to-icon svg {
  width: 30px;
  height: 30px;
}

.type-option[aria-checked='true'] .to-icon {
  background: rgba(214, 106, 58, 0.18);
  color: var(--accent-hi);
}

.to-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.to-title {
  font-size: 16px;
  font-weight: 700;
}

.to-sub {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.3;
}

/* The same drawn tick as the box picker's, only on the chosen row. */
.to-check {
  flex: 0 0 auto;
  width: 7px;
  height: 13px;
  margin: 0 6px 3px 0;
  border-right: 3px solid transparent;
  border-bottom: 3px solid transparent;
  transform: rotate(45deg);
}

.type-option[aria-checked='true'] .to-check {
  border-color: var(--accent-hi);
}

/* How many copies of this single to save. Sits beside the box chip on the save
   sheet and is built to the same shape, so the two read as one row of choices.
   A stepper rather than a plain number field: on a phone the two big targets
   are what actually gets used, and the field is there for "12". */
.qty-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 0 6px 0 14px;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
}

/* Accent border once more than one copy is being saved, like the box chip
   marks a chosen box: it is the one state worth noticing before saving. */
.qty-chip.is-set {
  border-color: var(--accent);
}

.qty {
  display: flex;
  align-items: center;
  margin-left: auto;
  gap: 2px;
}

.qty-btn {
  width: 40px;
  min-height: 38px;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 20px;
  font-weight: 600;
  line-height: 1;
}

.qty-btn:active {
  background: var(--surface-3);
}

.qty-btn:disabled {
  opacity: 0.4;
}

.qty-input {
  width: 52px;
  min-height: 38px;
  padding: 0 4px;
  text-align: center;
  font-size: 17px;
  font-weight: 700;
  border-radius: 10px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text);
}

.qty-input:focus {
  border-color: var(--accent);
  outline: none;
}

/* The box form's type: five tiles, "Geen" first, each with its picture. */
.box-type-field {
  margin-top: 18px;
}

.field-label {
  font-size: 12.5px;
  color: var(--muted);
  font-weight: 600;
  letter-spacing: 0.2px;
}

.field-label .optional {
  font-weight: 500;
  opacity: 0.8;
}

.box-type-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 6px;
}

.box-type-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-height: 66px;
  padding: 8px 2px 7px;
  border-radius: 13px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
}

.box-type-btn svg {
  width: 26px;
  height: 26px;
}

.bt-label {
  max-width: 100%;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.box-type-btn[aria-checked='true'] {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
  background: rgba(214, 106, 58, 0.14);
  color: var(--accent-hi);
}

.box-type-field .hint {
  margin-top: 8px;
}

/* The bulk action of the box form, set apart from the name field above it. */
.bulk-wrap {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.bulk-wrap .hint {
  margin-top: 8px;
}

/* The same chevron as the sort menu, drawn rather than typed so it lines up. */
.bc-caret,
.field-btn::after {
  content: '';
  flex: 0 0 auto;
  width: 12px;
  height: 8px;
  background: no-repeat center / 12px 8px
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%239aa1ab' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.manual-form {
  display: flex;
  gap: 8px;
}

.manual-form input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--tap);
  padding: 0 14px;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
}

.manual-form input::placeholder {
  color: var(--muted);
}

.manual-form button {
  flex: 0 0 auto;
  width: auto;
  padding: 0 18px;
}

.hint {
  color: var(--muted);
  font-size: 13px;
  text-align: center;
  margin: 0;
}

/* ---------------------------------------------------------------- sheets */

.sheet {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

/* The box picker and the box form open over whichever sheet called them, and
   the form over the picker. They come last in the document as well. */
.sheet-top {
  z-index: 20;
}

.sheet-form {
  z-index: 21;
}

.sheet-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: calc(var(--safe-t) + 10px) var(--gutter-r) 10px var(--gutter-l);
  border-bottom: 1px solid var(--border);
}

.sheet-head h2 {
  font-size: 16px;
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A title with the live dot after it: the name gives way with an ellipsis and
   the dot stays. */
.sheet-head h2.has-dot {
  display: flex;
  align-items: center;
  overflow: visible;
}

.sheet-head h2.has-dot > :first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Beside the Dashboard and box titles: green while live updates are connected. */
.live-dot {
  flex: 0 0 auto;
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-left: 6px;
  vertical-align: middle;
  border-radius: 50%;
  background: var(--muted);
  opacity: 0.5;
  transition:
    background 0.3s,
    opacity 0.3s;
}

.live-dot.on {
  background: var(--good);
  opacity: 1;
}

/* Lists scroll vertically only. overflow-y alone makes overflow-x auto as
   well, so one over-long line used to let the whole list pan sideways. */
.sheet-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 12px var(--gutter-r) 12px var(--gutter-l);
}

.sheet-foot {
  flex: 0 0 auto;
  padding: 10px var(--gutter-r) calc(10px + var(--safe-b)) var(--gutter-l);
  border-top: 1px solid var(--border);
  background: var(--bg);
  display: grid;
  /* minmax(0, …): a long name in a chip must not stretch the column. */
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
}

/* ----------------------------------------------------------- match cards */

.match {
  display: flex;
  gap: 12px;
  width: 100%;
  text-align: left;
  padding: 10px;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  margin-bottom: 8px;
  align-items: flex-start;
  line-height: 1.3;
}

.match:active {
  background: var(--surface-2);
}

.match.selected {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.match .cover {
  width: 62px;
  height: 62px;
  flex: 0 0 auto;
  border-radius: 8px;
  background: var(--surface-3) center/cover no-repeat;
  display: grid;
  place-items: center;
  color: var(--muted);
  font-size: 11px;
  overflow: hidden;
}

.match .cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.match .meta {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  /* minmax(0, …): an auto column would grow to the full unwrapped text. */
  grid-template-columns: minmax(0, 1fr);
  gap: 2px;
  padding-top: 1px;
}

.match .artist {
  font-weight: 600;
  font-size: 15px;
}

.match .title {
  font-size: 15px;
}

.match .sub {
  color: var(--muted);
  font-size: 13px;
  margin-top: 2px;
}

/* Block, because the lines inside a <button> have to be <span>s, and
   overflow and ellipsis do nothing on an inline element: the text ran past the
   card and under the price instead of ending in "…". */
.truncate {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* For the match list, where the catalogue number and format at the end of a
   line are what tell two pressings apart: wrap once, then cut. */
.clamp-2 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------- grade and price */

.grade-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px;
  margin-bottom: 12px;
}

.grade-card h3 {
  margin: 0 0 4px;
  font-size: 17px;
}

.grade-card .sub {
  color: var(--muted);
  font-size: 13px;
}

.cond-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin: 12px 0;
}

.cond-btn {
  min-height: 52px;
  border-radius: 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-weight: 700;
  font-size: 15px;
  display: grid;
  place-items: center;
  /* Keeps the code centred when there is no price under it. */
  align-content: center;
  line-height: 1.1;
}

.cond-btn small {
  display: block;
  font-size: 10px;
  font-weight: 500;
  color: var(--muted);
  margin-top: 2px;
}

.cond-btn[aria-pressed='true'] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.cond-btn[aria-pressed='true'] small {
  color: rgba(255, 255, 255, 0.85);
}

/* "Opslaan als": the type the record gets, always on the save sheet, with where
   it came from on the line above the four buttons. */
.type-row {
  display: grid;
  gap: 6px;
  margin: 2px 0 4px;
}

.type-row-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}

.type-row-label {
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 700;
}

.type-row-note {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 12.5px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A type the scanner and Discogs disagree on, or none at all: worth a look. */
.type-row-note.warn {
  color: #f0cf90;
  white-space: normal;
}

/* A box for another type: this record cannot go in it, so it reads as a stop. */
.type-row-note.block {
  color: #f0b3b0;
}

.type-seg {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}

/* The grade's heading, under the type row; the grid below brings its own margin. */
.cond-head {
  margin: 14px 0 -6px;
}

.type-btn {
  min-height: 46px;
  border-radius: 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-weight: 700;
  font-size: 15px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 0 4px;
}

.type-btn[aria-pressed='true'] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* The type of a release in the match list, in front of Discogs' format text. */
.type-badge {
  display: inline-block;
  margin-right: 6px;
  padding: 0 6px;
  border-radius: 6px;
  background: var(--surface-3);
  color: var(--text);
  font-size: 11px;
  font-weight: 700;
  line-height: 1.6;
  vertical-align: 1px;
}

.price-block {
  display: grid;
  gap: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
}

.price-main {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.price-main .amount {
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -0.5px;
}

.price-main .caption {
  color: var(--muted);
  font-size: 13px;
}

.price-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 14px;
  color: var(--muted);
}

.price-row strong {
  color: var(--text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Highest, average and lowest of Discogs' per-grade suggestions: ruled off so
   they read as one figure with a spread, apart from the listing facts below. */
.price-range {
  display: grid;
  gap: 6px;
  padding: 10px 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.price-hint {
  margin: 0;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.4;
}

.notice {
  border-radius: 12px;
  padding: 11px 12px;
  font-size: 13.5px;
  line-height: 1.4;
  border: 1px solid;
  /* Box names are user input: one long word must wrap, not widen the sheet. */
  overflow-wrap: anywhere;
}

.notice.warn {
  background: rgba(217, 161, 59, 0.12);
  border-color: rgba(217, 161, 59, 0.4);
  color: #f0cf90;
}

.notice.info {
  background: rgba(70, 168, 111, 0.1);
  border-color: rgba(70, 168, 111, 0.35);
  color: #a6dcbd;
}

.notice.error {
  background: rgba(216, 84, 79, 0.12);
  border-color: rgba(216, 84, 79, 0.4);
  color: #f0b3b0;
}

.notice a {
  color: inherit;
}

/* ------------------------------------------------------------ collection */

.coll-tools {
  flex: 0 0 auto;
  padding: 8px var(--gutter-r) 8px var(--gutter-l);
  display: grid;
  /* minmax(0, …) stops a wide child stretching the column past the screen,
     which is how the sort menu used to push the total value off the edge. */
  grid-template-columns: minmax(0, 1fr);
  gap: 6px;
  border-bottom: 1px solid var(--border);
}

.search-wrap {
  position: relative;
}

.search-wrap input[type='search'] {
  width: 100%;
  min-height: 44px;
  padding: 0 44px 0 38px;
  border-radius: 12px;
  background: var(--surface) no-repeat 12px center / 17px 17px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='8.5' cy='8.5' r='6' fill='none' stroke='%239aa1ab' stroke-width='2'/%3E%3Cpath d='M13 13l5 5' stroke='%239aa1ab' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
  border: 1px solid var(--border);
  -webkit-appearance: none;
  appearance: none;
}

.search-wrap input[type='search']::placeholder {
  color: var(--muted);
}

/* Our own clear button, which iOS does not reliably draw in a search field. */
.search-wrap input[type='search']::-webkit-search-cancel-button {
  -webkit-appearance: none;
  display: none;
}

.search-clear {
  position: absolute;
  top: 0;
  right: 0;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
}

/* The two menus: how a record was found, and the order. Half the width each. */
.coll-bar {
  display: flex;
  align-items: center;
  gap: 8px;
}

.coll-bar .sort-select {
  flex: 1 1 0;
  min-width: 0;
  max-width: none;
}

/* A menu that narrows the list is marked, like a chosen box. */
.sort-select.is-set {
  border-color: var(--accent);
}

/* The totals of what the list shows, on a line of their own above it. */
.coll-totals-bar {
  flex: 0 0 auto;
  padding: 7px var(--gutter-r) 7px var(--gutter-l);
  border-bottom: 1px solid var(--border);
}

/* Basis 0: the totals take what the sort menu leaves and wrap, rather than
   both shrinking and the menu's label being cut. */
.totals {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 1px 12px;
  font-size: 13px;
  line-height: 1.35;
  color: var(--muted);
}

.totals > span {
  white-space: nowrap;
}

.totals strong {
  color: var(--text);
}

/* 16px text like every field (smaller makes iOS zoom in on tap), with a
   chevron so it reads as a menu rather than a label. */
.sort-select {
  flex: 0 0 auto;
  max-width: 60%;
  min-height: 44px;
  padding: 0 32px 0 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  -webkit-appearance: none;
  appearance: none;
  text-overflow: ellipsis;
}

.sort-select,
.field select {
  background: var(--surface) no-repeat right 12px center / 12px 8px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%239aa1ab' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.coll-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 10px var(--gutter-r) calc(12px + var(--safe-b)) var(--gutter-l);
}

/* Wraps so the price range can take a line of its own under the three columns.
   Everything in the row is a <span>: a <button> may only hold phrasing content. */
.item {
  display: flex;
  flex-wrap: wrap;
  column-gap: 12px;
  row-gap: 8px;
  align-items: center;
  width: 100%;
  text-align: left;
  padding: 9px 12px 9px 9px;
  border-radius: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  margin-bottom: 7px;
  line-height: 1.3;
}

.item:active {
  background: var(--surface-2);
}

/* A record with no cover yet gets a disc rather than an empty grey square, so
   a row that is still being looked up reads as a record and not as a hole in
   the list. The image, once it arrives, covers it. */
.item .cover {
  width: 56px;
  height: 56px;
  flex: 0 0 auto;
  border-radius: 7px;
  background: var(--surface-3) center / 26px 26px no-repeat;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke='%239aa1ab' stroke-width='1.4' opacity='.5'/%3E%3Ccircle cx='12' cy='12' r='2.6' fill='none' stroke='%239aa1ab' stroke-width='1.4' opacity='.65'/%3E%3C/svg%3E");
  overflow: hidden;
}

.item .cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Basis 0, not auto: with wrapping on, an auto basis is the full unwrapped
   text, which would push the price column onto a line of its own. */
.item .meta {
  flex: 1 1 0;
  min-width: 0;
}

.item .artist {
  font-size: 15px;
  font-weight: 600;
}

.item .title {
  font-size: 14px;
  margin-top: 1px;
}

.item .sub {
  font-size: 12.5px;
  color: var(--muted);
  margin-top: 2px;
}

/* The price never wraps and always has its own column; the pills under it may
   wrap among themselves, which is what keeps the column narrow. */
.item .right {
  flex: 0 0 auto;
  max-width: 108px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 5px;
}

.item .val {
  font-weight: 700;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* "prijs volgt" sits where an amount will go, so it is quiet and normal
   weight: it is a note, not a figure to read. */
.item .val-waiting {
  font-size: 12px;
  font-weight: 500;
  color: var(--muted);
}

.pills {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 4px;
  max-width: 100%;
}

/* Condition, how the record was found and (in the whole collection) its box. */
.pill {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 100%;
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1.35;
  color: var(--muted);
  padding: 1px 6px;
  border-radius: 6px;
  background: var(--surface-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pill-method[data-method='barcode'] {
  color: var(--good);
}

.pill-method[data-method='ai'] {
  color: var(--accent-hi);
}

.pill-method[data-method='manual'] {
  color: var(--warn);
}

.pill-box {
  color: var(--text);
}

/* What kind of thing: on a lighter ground than the other pills, because it is
   what a box is now filtered by. */
.pill-type {
  color: var(--text);
  background: #3a3f49;
}

/* The spread of Discogs' suggestions, on a line of its own under the row: three
   equal cells, left, middle and right, each of which gives way with an ellipsis
   rather than pushing the row wider than a 360px phone. */
.item-range {
  flex: 0 0 100%;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
  padding-top: 7px;
  border-top: 1px solid var(--border);
  font-size: 12px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.item-range > span {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.item-range small {
  font-size: 11px;
  color: var(--muted);
}

.item-range .range-avg {
  text-align: center;
}

.item-range .range-low {
  text-align: right;
}

.empty {
  text-align: center;
  color: var(--muted);
  padding: 40px 20px;
  font-size: 14px;
}

/* An empty box points at the one thing to do about it. */
.empty .btn {
  margin-top: 16px;
}

/* Outlined rather than filled: the record list is what this screen is for. */
.btn.scan-into {
  min-height: 44px;
  border-color: var(--accent);
  color: var(--accent-hi);
}

.btn.scan-into:disabled {
  border-color: var(--border);
  color: var(--muted);
  opacity: 1;
}

/* ------------------------------------------------------------- dashboard */

/* The whole collection, as a card that opens it. */
.dash-summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: 14px;
  row-gap: 6px;
  width: 100%;
  text-align: left;
  padding: 14px 16px 16px;
  border-radius: var(--radius);
  /* A hint of the accent, so the collection's own card leads the screen. */
  background: linear-gradient(150deg, rgba(214, 106, 58, 0.16), rgba(214, 106, 58, 0) 60%), var(--surface);
  border: 1px solid var(--border);
  position: relative;
}

.dash-summary:active {
  background: var(--surface-2);
}

.dash-title {
  grid-column: 1 / -1;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  padding-right: 24px;
}

.dash-go {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 9px;
  height: 9px;
  border-top: 2px solid var(--muted);
  border-right: 2px solid var(--muted);
  transform: rotate(45deg);
}

.dash-stat {
  display: flex;
  flex-direction: column;
  min-width: 0;
  font-size: 13px;
  color: var(--muted);
}

.dash-stat strong {
  color: var(--text);
  font-size: clamp(22px, 7.4vw, 30px);
  line-height: 1.15;
  letter-spacing: -0.5px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* "38 singles · 4 cassettes · 2 cd's" under the collection's figures. */
.dash-types {
  grid-column: 1 / -1;
  margin-top: 2px;
  font-size: 13px;
  color: var(--muted);
  line-height: 1.4;
}

#dashPending {
  margin-top: 10px;
}

/* The button, and under it the name filter when there are enough boxes to need
   one: side by side the filter had room for about three letters on a phone. */
.dash-tools {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
  margin-top: 12px;
}

.dash-tools .search-wrap input[type='search'] {
  min-height: var(--tap);
}

/* ------------------------------------------------------- the AI budget ---

   Reading a label photo is the only thing in the app that costs money rather
   than time, so what is left of today's allowance is shown wherever a photo
   can be started: as a count on the Foto button, a bar under it, and a small
   card on the Dashboard (one line and the bar; the owner did not want the
   photos-per-day chart it had until 2026-10-27).

   The meter turns amber and then red as the day runs out, and both states are
   also written out in words beside it, so the colour is never the only thing
   saying so. */

.ai-bar {
  height: 6px;
  border-radius: 3px;
  background: var(--surface-3);
  overflow: hidden;
}

.ai-bar-fill {
  height: 100%;
  width: 0;
  border-radius: 3px;
  background: var(--accent);
  transition: width 0.3s ease;
}

.ai-bar-fill.low {
  background: var(--warn);
}

.ai-bar-fill.spent {
  background: var(--danger);
}

/* Under the photo button on the scan screen. */
.ai-meter {
  display: grid;
  gap: 6px;
}

.ai-meter-note {
  margin: 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.35;
  text-align: center;
}

.ai-meter-note.warn {
  color: #f0cf90;
}

.ai-meter-note.spent {
  color: #f0b3b0;
}

/* The Dashboard card: small, it is a meter and not a subject. */
.ai-card {
  display: grid;
  gap: 6px;
  margin-top: 10px;
  padding: 10px 14px 11px;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
}

.ai-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.ai-card-head h3 {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  min-width: 0;
}

.ai-card-left {
  font-size: 14px;
  font-weight: 700;
  white-space: nowrap;
}

.ai-card-note {
  margin: 0;
  color: var(--muted);
  font-size: 11.5px;
  line-height: 1.4;
}

.ai-card-note.warn {
  color: #f0cf90;
}

.ai-card-note.spent {
  color: #f0b3b0;
}

/* Two columns on a phone; the tile is a card with two buttons in it, the name
   and figures that open the box and the dots that manage it. */
.box-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
  margin-top: 12px;
}

.box-tile {
  position: relative;
  min-width: 0;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
}

.box-tile:active {
  background: var(--surface-2);
}

/* The box the scanner is pointed at. */
.box-tile.is-scanner {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

/* Records in no box: a bucket rather than a box, so it is drawn as an outline. */
.box-tile-none {
  border-style: dashed;
  background: transparent;
}

.box-open {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  min-height: 112px;
  padding: 12px 14px 14px;
  text-align: left;
  border-radius: var(--radius);
}

.box-name {
  display: block;
  max-width: 100%;
  font-size: 22px;
  font-weight: 700;
  line-height: 1.25;
  /* Room for the dots, which sit over the top right corner. */
  padding-right: 34px;
}

.box-tile-none .box-name {
  padding-right: 0;
}

/* What a box is for, when it has a type: its picture and "Singles". */
.box-kind {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 1px 0 2px;
  padding: 1px 8px 1px 5px;
  border-radius: 999px;
  background: rgba(214, 106, 58, 0.14);
  color: var(--accent-hi);
  font-size: 12px;
  font-weight: 700;
}

.box-kind svg {
  width: 16px;
  height: 16px;
}

.box-count {
  font-size: 13px;
  color: var(--muted);
}

.box-value {
  margin-top: 4px;
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.box-pending {
  font-size: 12px;
  color: var(--warn);
}

.box-badge {
  margin-top: 6px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 11px;
  font-weight: 700;
}

.box-more {
  position: absolute;
  top: 0;
  right: 0;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: var(--radius);
  color: var(--text);
  font-size: 26px;
  font-weight: 700;
  line-height: 1;
  opacity: 0.75;
}

.box-more:active {
  opacity: 1;
  background: var(--surface-3);
}

/* ----------------------------------------------------------- box picker */

.picker-tools {
  flex: 0 0 auto;
  padding: 10px var(--gutter-r) 10px var(--gutter-l);
  border-bottom: 1px solid var(--border);
}

.picker-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 56px;
  padding: 8px 14px;
  text-align: left;
  border-radius: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  margin-bottom: 7px;
  line-height: 1.3;
}

.picker-row:active {
  background: var(--surface-2);
}

.picker-row[aria-current='true'] {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

/* A box for another type than the record: shown, so it is not missed, but not choosable. */
.picker-row.is-blocked {
  opacity: 0.45;
}

/* The box's picture: its type when it has one, a box when not, dashed for "Geen doos". */
.pr-icon {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  color: var(--muted);
}

.pr-icon svg {
  width: 26px;
  height: 26px;
}

.picker-row[aria-current='true'] .pr-icon {
  color: var(--accent-hi);
}

.pr-main {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.pr-name {
  font-size: 16px;
  font-weight: 600;
}

.pr-sub {
  font-size: 13px;
  color: var(--muted);
}

/* A tick drawn with borders, so it needs no font to have one. */
.pr-check {
  flex: 0 0 auto;
  width: 7px;
  height: 13px;
  margin: 0 4px 3px 0;
  border-right: 3px solid var(--accent-hi);
  border-bottom: 3px solid var(--accent-hi);
  transform: rotate(45deg);
}

/* -------------------------------------------------------------- box form */

/* Box names are typed by a person, so text that quotes one (an error, a scope,
   a toast) must be able to break inside a word rather than run off the screen. */
.form-error {
  margin: 0 0 10px;
  color: var(--danger);
  font-size: 14px;
  line-height: 1.4;
  overflow-wrap: anywhere;
}

/* The browser's own focus ring is nearly black on this background; the accent
   border says where the cursor is, and red keeps saying what is wrong. */
.field input:focus,
.field select:focus,
.field textarea:focus,
.search-wrap input[type='search']:focus,
.manual-form input:focus {
  outline: none;
  border-color: var(--accent-hi);
}

.field input[aria-invalid='true'],
.field input[aria-invalid='true']:focus {
  border-color: var(--danger);
}

/* The record sheet's box: a button that looks like the fields around it. */
.field-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 10px 12px;
  border-radius: 11px;
  background: var(--surface);
  border: 1px solid var(--border);
  text-align: left;
}

.field-btn:active {
  background: var(--surface-2);
}

.field-btn > span:first-child {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Which records the export holds. */
.export-scope {
  margin: 0 0 10px;
  font-size: 17px;
  font-weight: 700;
  overflow-wrap: anywhere;
}

/* The line under the price on a record that says how it was found. */
.item-scan {
  margin: -4px 0 14px;
  text-align: left;
}

/* -------------------------------------------------------------- edit form */

.field {
  display: grid;
  /* minmax(0, …): the box button's long name must not widen the column. */
  grid-template-columns: minmax(0, 1fr);
  gap: 5px;
  margin-bottom: 11px;
}

.field label {
  font-size: 12.5px;
  color: var(--muted);
  font-weight: 600;
  letter-spacing: 0.2px;
}

.field input,
.field select,
.field textarea {
  min-height: 44px;
  padding: 10px 12px;
  border-radius: 11px;
  background-color: var(--surface);
  border: 1px solid var(--border);
  width: 100%;
  -webkit-appearance: none;
}

.field select {
  padding-right: 32px;
  text-overflow: ellipsis;
}

.field textarea {
  min-height: 74px;
  resize: vertical;
}

.field-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

/* ---------------------------------------------------------------- toasts */

#toasts {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: calc(14px + var(--safe-b));
  z-index: 50;
  display: grid;
  gap: 8px;
  pointer-events: none;
}

.toast {
  overflow-wrap: anywhere;
  padding: 12px 14px;
  border-radius: 12px;
  background: var(--surface-3);
  border: 1px solid var(--border);
  font-size: 14px;
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.5);
  animation: rise 0.18s ease-out;
}

.toast.ok {
  background: #1d3f2c;
  border-color: #2e6544;
}

.toast.err {
  background: #47211f;
  border-color: #7d3733;
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

/* --------------------------------------------------------------- spinner */

.spinner {
  width: 17px;
  height: 17px;
  border: 2px solid rgba(255, 255, 255, 0.28);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  display: inline-block;
}

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

.loading-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  color: var(--muted);
  font-size: 14px;
  padding: 18px;
}

/* ------------------------------------------------------ narrow and calm */

/* The back chip, a title and the export chip share one row; on the narrowest
   phones the chips give up a few pixels so "Hele collectie" still fits. */
@media (max-width: 380px) {
  .chip-btn {
    padding: 0 11px;
  }

  .chip-btn.back {
    padding-left: 8px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .toast,
  .bottom-panel,
  .sheet-backdrop {
    animation: none;
  }

  .live-dot {
    transition: none;
  }
}

/* ----------------------------------------------------------- login page */

.login-page {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 24px;
}

.login-card {
  width: 100%;
  max-width: 340px;
  text-align: center;
}

.login-card img {
  width: 74px;
  height: 74px;
  margin-bottom: 14px;
}

.login-card h1 {
  font-size: 21px;
  margin: 0 0 6px;
}

.login-card p {
  color: var(--muted);
  font-size: 14px;
  margin: 0 0 20px;
}

.login-card input {
  width: 100%;
  min-height: var(--tap);
  padding: 0 14px;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  text-align: center;
  margin-bottom: 10px;
}

.login-error {
  color: #f0b3b0;
  font-size: 14px;
  min-height: 20px;
  margin-top: 10px;
}

.login-card input[hidden],
.login-terms[hidden] {
  display: none;
}

.login-card .btn {
  width: 100%;
}

.login-terms {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  text-align: left;
  font-size: 14px;
  color: var(--muted);
  margin: 4px 0 14px;
}

.login-terms input {
  width: 20px;
  min-height: 20px;
  height: 20px;
  margin: 1px 0 0;
  flex: none;
  accent-color: var(--accent);
}

.login-terms a {
  color: var(--text);
}

.login-switch {
  margin-top: 8px;
  background: none;
  border: 0;
  color: var(--muted);
  font-size: 15px;
  text-decoration: underline;
  min-height: var(--tap);
  cursor: pointer;
}

/* ------------------------------------------------------------ discogs link */

/* On the Dashboard: an invitation while Discogs is not linked, one quiet
   line once it is. */
.discogs-card {
  position: relative;
  display: grid;
  gap: 6px;
  width: 100%;
  margin-top: 12px;
  padding: 14px 40px 15px 16px;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--accent);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.discogs-card.is-linked {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 40px 12px 16px;
  border-color: var(--border);
}

.discogs-card .dc-title {
  font-weight: 650;
}

.discogs-card .dc-text {
  color: var(--muted);
  font-size: 14px;
  line-height: 1.45;
}

.discogs-card .dc-line {
  color: var(--muted);
  font-size: 14px;
}

.discogs-card .dc-line strong {
  color: var(--text);
  font-weight: 600;
}

.discogs-card .dc-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--good);
}

.discogs-card .dc-go {
  position: absolute;
  top: 50%;
  right: 18px;
  width: 9px;
  height: 9px;
  margin-top: -5px;
  border-top: 2px solid var(--muted);
  border-right: 2px solid var(--muted);
  transform: rotate(45deg);
}

.discogs-why {
  margin: 0 0 18px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.5;
}

.discogs-steps {
  display: grid;
  gap: 22px;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
}

.discogs-steps > li {
  position: relative;
  display: grid;
  gap: 10px;
  padding-left: 40px;
  font-size: 15px;
  line-height: 1.5;
  counter-increment: step;
}

.discogs-steps > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--text);
  font-size: 14px;
  font-weight: 650;
}

.discogs-steps > li > strong {
  display: block;
}

.discogs-steps > li.is-waiting {
  opacity: 0.55;
}

.discogs-steps .field {
  margin: 0 0 10px;
}

.discogs-callback {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.discogs-callback code {
  flex: 1 1 auto;
  min-width: 0;
  padding: 8px 10px;
  border-radius: 10px;
  background: var(--surface-2);
  font-size: 13px;
  overflow-wrap: anywhere;
}

#discogsLinked .btn {
  width: 100%;
}

#discogsLinkedNote {
  margin-bottom: 16px;
}

.discogs-steps > li > p {
  margin: 0;
  color: var(--muted);
}

/* ------------------------------------------------- selection and archive */

/* The two cards under the collection: the selection and the archive. */
.dash-cards {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px;
  margin-top: 10px;
}

.dash-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  min-width: 0;
  padding: 11px 14px 13px;
  text-align: left;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
}

.dash-card:active {
  background: var(--surface-2);
}

/* A selection with something in it is work in progress: it is marked. */
.dash-card.is-active {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.dcard-head {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
}

.dcard-icon svg {
  display: block;
  width: 18px;
  height: 18px;
}

.dash-card.is-active .dcard-icon {
  color: var(--accent-hi);
}

.dcard-figure {
  margin-top: 4px;
  font-size: 24px;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}

.dcard-sub {
  max-width: 100%;
  font-size: 12px;
  color: var(--muted);
  line-height: 1.3;
}

/* "Dozen", above the button and the grid. */
.dash-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 18px 2px 0;
}

.dash-section-head h3 {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}

.dash-section-sub {
  font-size: 12.5px;
  color: var(--muted);
}

.dash-section-head + .dash-tools {
  margin-top: 8px;
}

/* A record in a list: its tick, then the row that opens it. */
.item-row {
  display: flex;
  align-items: stretch;
  gap: 4px;
  margin-bottom: 7px;
}

.item-row .item {
  flex: 1 1 auto;
  min-width: 0;
  margin-bottom: 0;
}

.sel-tick {
  flex: 0 0 34px;
  display: grid;
  place-items: center;
  border-radius: 12px;
  color: var(--muted);
}

.sel-tick svg {
  width: 24px;
  height: 24px;
}

.tick-ring {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
}

.tick-mark {
  fill: none;
  stroke: transparent;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.item-row.is-selected .sel-tick {
  color: var(--accent);
}

.item-row.is-selected .tick-ring {
  fill: var(--accent);
}

.item-row.is-selected .tick-mark {
  stroke: var(--on-accent);
}

.item-row.is-selected .item {
  border-color: rgba(214, 106, 58, 0.7);
  background: linear-gradient(90deg, rgba(214, 106, 58, 0.12), rgba(214, 106, 58, 0.04)), var(--surface);
}

/* "Selecteren" beside the two menus of a box. */
.round-tool {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--accent-hi);
}

.round-tool svg {
  width: 22px;
  height: 22px;
}

/* The record sheet's chip: filled while the record is in the selection. */
.chip-btn.sel-chip[aria-pressed='true'] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.chip-btn.sel-chip[aria-pressed='true']::before {
  content: '✓ ';
}

.notice.sold {
  background: rgba(154, 161, 171, 0.1);
  border-color: rgba(154, 161, 171, 0.4);
  color: var(--text);
}

.field-static {
  min-height: var(--tap);
  display: flex;
  align-items: center;
  padding: 0 12px;
  border-radius: 12px;
  background: var(--surface-2);
  color: var(--muted);
  overflow-wrap: anywhere;
}

.field label .optional {
  font-weight: 400;
  color: var(--muted);
}

.field-hint {
  text-align: left;
  margin: -4px 0 14px;
}

.select-menu {
  display: grid;
  gap: 8px;
}

.select-menu-note {
  text-align: left;
  margin-bottom: 12px;
}

.sell-summary {
  margin-bottom: 16px;
}

.sell-figures {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 18px;
  margin-bottom: 6px;
  color: var(--muted);
}

.sell-figures strong {
  color: var(--text);
  font-size: 24px;
  font-variant-numeric: tabular-nums;
}

.sell-summary .hint,
.import-file {
  text-align: left;
}

.import-file {
  margin-top: 8px;
}

#importModeWrap {
  margin-top: 6px;
}

#importModeWrap .field-label {
  display: block;
  margin-bottom: 6px;
}

.import-counts,
.import-problems {
  margin: 8px 0 0;
  padding-left: 18px;
}

.import-problems {
  font-size: 13px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.export-status {
  margin-bottom: 8px;
}

.export-hint {
  text-align: left;
  margin-bottom: 14px;
}

/* The archive: six figures, two to a row, then the lists. */
.metrics {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.metric {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 10px 12px 11px;
  border-radius: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
}

.metric:first-child {
  background: linear-gradient(150deg, rgba(214, 106, 58, 0.18), rgba(214, 106, 58, 0) 65%), var(--surface);
}

.metric-label {
  font-size: 12px;
  color: var(--muted);
}

.metric strong {
  font-size: 21px;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.metric-sub {
  font-size: 11.5px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.arch-types {
  margin: 10px 2px 0;
  font-size: 13px;
  color: var(--muted);
}

.arch-head {
  margin: 20px 2px 8px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}

.arch-list {
  display: grid;
  gap: 6px;
}

.arch-row,
.arch-all {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 52px;
  padding: 8px 14px;
  text-align: left;
  border-radius: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
}

.arch-row:active,
.arch-all:active {
  background: var(--surface-2);
}

.arch-all {
  margin-top: 10px;
  justify-content: center;
  font-weight: 600;
  color: var(--accent-hi);
}

.arch-main {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.arch-name {
  font-weight: 600;
}

.arch-sub {
  font-size: 12.5px;
  color: var(--muted);
}

.arch-figure {
  flex: 0 0 auto;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.arch-figure small {
  font-weight: 500;
  color: var(--muted);
}

/* A box that holds selected records: marked on the Dashboard with how many, so
   the selection can be found on the shelves (2026-10-27). */
.box-tile.has-selected {
  border-color: rgba(214, 106, 58, 0.75);
  background: linear-gradient(160deg, rgba(214, 106, 58, 0.16), rgba(214, 106, 58, 0) 70%), var(--surface);
}

.box-tile-none.has-selected {
  border-style: dashed;
}

.box-sel {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 6px;
  font-size: 12px;
  font-weight: 700;
  color: var(--accent-hi);
}

.box-sel svg {
  width: 15px;
  height: 15px;
}

.box-sel .tick-ring {
  fill: var(--accent);
  stroke: var(--accent);
}

.box-sel .tick-mark {
  stroke: var(--on-accent);
}

/* A shift-click selects a range; it must not select the text in between too. */
.sel-tick {
  user-select: none;
  -webkit-user-select: none;
}

/* With GroundDeck's own Discogs app an account only approves the link. */
.platform-keys > li:first-child,
.platform-keys #discogsForm,
.platform-keys > li:nth-child(2) > strong,
.platform-keys > li:nth-child(2) > p:not(.form-error) {
  display: none;
}

/* ------------------------------------------------------ market and account */

.dcard-text {
  font-size: 17px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.check-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--tap);
  margin-bottom: 10px;
  font-size: 16px;
}

.check-row input {
  width: 22px;
  height: 22px;
  flex: none;
  margin: 0;
  accent-color: var(--accent);
}

.field-check {
  align-self: end;
  margin-bottom: 14px;
}

.form-sub {
  font-size: 16px;
  margin: 26px 0 10px;
}

#marketLink a {
  color: var(--accent-hi);
}
