:root {
  --bg: #0f1115;
  --panel: #171a21;
  --panel-2: #1f232c;
  --border: #2a2f3a;
  --text: #e8eaed;
  --text-dim: #9aa1ac;
  --accent: #4f8cff;
  --accent-2: #7ad0ff;
  --good: #3ecf8e;
  --warn: #ffb454;
  --radius: 14px;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #f4f5f7;
    --panel: #ffffff;
    --panel-2: #f0f1f4;
    --border: #e1e3e8;
    --text: #16181d;
    --text-dim: #62676f;
    --accent: #2f6fed;
    --accent-2: #0b8fb8;
    --good: #0f9d63;
    --warn: #b5720a;
  }
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

@media (prefers-reduced-motion: no-preference) {
  :root { --transition-fast: 0.15s; --transition: 0.25s; }
}
@media (prefers-reduced-motion: reduce) {
  :root { --transition-fast: 0s; --transition: 0s; }
}

html {
  min-height: 100%;
  background: var(--bg);
}

body {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--bg);
  background-image: radial-gradient(circle at 15% -10%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 45%);
  background-repeat: no-repeat;
  background-attachment: fixed;
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  overflow-x: hidden;
}

.shell {
  display: flex;
  align-items: flex-start;
  min-height: 100vh;
  min-height: 100dvh;
}

.app {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 980px;
  margin: 0 auto;
  padding: 20px 16px 60px;
  animation: fadeIn 0.4s ease;
}

.topbar h1 {
  font-size: 1.5rem;
  margin: 0 0 6px;
  letter-spacing: -0.02em;
}

.subtitle {
  color: var(--text-dim);
  font-size: 0.92rem;
  line-height: 1.4;
  margin: 0 0 20px;
}

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  box-shadow: 0 8px 24px -16px rgba(0, 0, 0, 0.4);
}

.field label {
  display: block;
  font-weight: 600;
  font-size: 0.9rem;
  margin-bottom: 8px;
}

input[type="file"] {
  width: 100%;
  color: var(--text);
  font-size: 0.9rem;
}

.file-info {
  margin-top: 8px;
  font-size: 0.85rem;
  color: var(--text-dim);
}

.range-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

input[type="number"] {
  width: 90px;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text);
  font-size: 1rem;
  transition: border-color var(--transition-fast) ease, box-shadow var(--transition-fast) ease;
}

input[type="number"]:focus,
.sort-control select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}

.page-total {
  color: var(--text-dim);
  font-size: 0.85rem;
  margin-left: 6px;
}

button {
  background: var(--accent);
  color: white;
  border: none;
  padding: 13px 20px;
  border-radius: 10px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  width: 100%;
  transition: transform var(--transition-fast) ease, filter var(--transition-fast) ease, box-shadow var(--transition-fast) ease;
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

button:hover:not(:disabled) {
  filter: brightness(1.08);
}

button:active:not(:disabled) {
  transform: scale(0.98);
}

.progress-wrap {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.progress-bar {
  height: 10px;
  background: var(--panel-2);
  border-radius: 999px;
  overflow: hidden;
  border: 1px solid var(--border);
}

.progress-fill {
  height: 100%;
  width: 0%;
  background: var(--accent);
  background-size: 200% 100%;
  transition: width 0.2s ease;
}

/* only animate while work is actually happening - a shimmer sitting at
   0% or 100% forever reads as "stuck loading" rather than "done" */
.progress-fill.is-active {
  background-image: linear-gradient(90deg, var(--accent), var(--accent-2));
  animation: shimmer 1.4s linear infinite;
}

.progress-label {
  font-size: 0.85rem;
  color: var(--text-dim);
}

.session-header {
  margin-top: 22px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  animation: fadeInUp 0.3s ease;
}

.session-title {
  font-size: 1.1rem;
  font-weight: 600;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.change-range-btn {
  flex: 0 0 auto;
  width: auto;
  padding: 7px 14px;
  font-size: 0.82rem;
  font-weight: 500;
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--text);
}

.change-range-btn:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}

.summary {
  margin-top: 12px;
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 18px;
  font-size: 0.9rem;
  color: var(--text-dim);
  animation: fadeInUp 0.35s ease;
}

.summary b {
  color: var(--text);
  font-size: 1.1rem;
}

.empty-hint {
  grid-column: 1 / -1;
  padding: 32px 20px;
  text-align: center;
  color: var(--text-dim);
  font-size: 0.92rem;
  background: var(--panel);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  animation: fadeInUp 0.3s ease;
}

.results {
  margin-top: 18px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 12px;
}

/* grouped-by-color mode: the outer container just stacks heading+grid pairs;
   each nested .results grid keeps the normal card-grid layout above */
.results.grouped {
  display: block;
  margin-top: 0;
}

.results.grouped > .results {
  margin-top: 0;
}

.color-group-heading {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 22px 0 10px;
}

.color-group-heading:first-child {
  margin-top: 0;
}

.part-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  position: relative;
  cursor: pointer;
  user-select: none;
  animation: fadeInUp 0.3s ease backwards;
  transition: transform var(--transition-fast) ease, box-shadow var(--transition-fast) ease, border-color var(--transition-fast) ease, opacity var(--transition-fast) ease;
}

/* the check button marks a part found; the card itself opens the page viewer.
   It lives in the count row rather than floating over the part picture - the
   picture is the content you actually read off these cards. */
.part-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.part-check {
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  padding: 0;
  border-radius: 999px;
  background: transparent;
  border: 1.5px solid var(--border);
  color: transparent;
  font-size: 0.7rem;
  line-height: 1;
  transition: background var(--transition-fast) ease, color var(--transition-fast) ease, border-color var(--transition-fast) ease;
}

.part-check:hover {
  border-color: var(--good);
  color: color-mix(in srgb, var(--good) 60%, transparent);
}

.part-card.collected .part-check {
  background: var(--good);
  border-color: var(--good);
  color: #06231a;
}

.part-card.collected {
  opacity: 0.45;
}

.part-card.collected img {
  filter: grayscale(0.8);
}

.part-card.collected .part-qty {
  text-decoration: line-through;
}

.results .part-card:nth-child(n+1) { animation-delay: 0ms; }
.results .part-card:nth-child(n+2) { animation-delay: 20ms; }
.results .part-card:nth-child(n+3) { animation-delay: 40ms; }
.results .part-card:nth-child(n+4) { animation-delay: 60ms; }
.results .part-card:nth-child(n+5) { animation-delay: 80ms; }
.results .part-card:nth-child(n+6) { animation-delay: 100ms; }
.results .part-card:nth-child(n+7) { animation-delay: 120ms; }
.results .part-card:nth-child(n+8) { animation-delay: 140ms; }

.part-card:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--accent) 50%, var(--border));
  box-shadow: 0 10px 20px -12px rgba(0, 0, 0, 0.45);
}

.part-card img {
  width: 100%;
  height: 100px;
  object-fit: contain;
  background: #dceefc;
  border-radius: 8px;
}

.part-qty {
  font-weight: 700;
  font-size: 1.05rem;
  padding: 2px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}

.part-qty.unsure {
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 18%, transparent);
}

.part-pages {
  font-size: 0.72rem;
  color: var(--text-dim);
  text-align: center;
  line-height: 1.3;
}

@media (max-width: 480px) {
  .topbar h1 { font-size: 1.2rem; }
  .results { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
  .part-card img { height: 80px; }
}

/* ---------- view controls (mode toggle + sort) ---------- */

.view-controls {
  margin-top: 18px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

.mode-toggle {
  display: inline-flex;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 3px;
  gap: 3px;
}

.mode-btn {
  width: auto;
  background: transparent;
  color: var(--text-dim);
  padding: 8px 14px;
  font-size: 0.85rem;
  font-weight: 600;
  border-radius: 8px;
  transition: background var(--transition-fast) ease, color var(--transition-fast) ease;
}

.mode-btn:hover:not(.active):not(:disabled) {
  filter: none;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--text);
}

.mode-btn.active {
  background: var(--accent);
  color: white;
}

.sort-control {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
  color: var(--text-dim);
}

.sort-control select {
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text);
  font-size: 0.85rem;
}

/* ---------- step (build) mode ---------- */

.step-mode {
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.step-nav {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
}

.step-nav button {
  width: 44px;
  flex-shrink: 0;
  padding: 10px;
}

.step-pos {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.step-label {
  font-size: 0.88rem;
  font-weight: 600;
  text-align: center;
}

.step-slider-hint {
  font-size: 0.72rem;
  color: var(--text-dim);
}

input[type="range"] {
  width: 100%;
  accent-color: var(--accent);
}

.step-body {
  display: grid;
  grid-template-columns: minmax(320px, 640px) 1fr;
  grid-template-areas:
    "preview current"
    "remaining remaining";
  gap: 16px;
  align-items: start;
}

.step-page-preview { grid-area: preview; }
.step-current-block { grid-area: current; }
.step-remaining-block { grid-area: remaining; }

.step-page-preview {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  position: relative;
  cursor: zoom-in;
  overflow: hidden;
  transition: border-color var(--transition-fast) ease;
}

.step-page-preview:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}

.step-page-preview img {
  width: 100%;
  height: auto;
  border-radius: 8px;
  background: white;
  transition: transform var(--transition) ease;
}

.step-page-preview:hover img {
  transform: scale(1.015);
}

.zoom-hint {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%) translateY(4px);
  background: rgba(0, 0, 0, 0.65);
  color: white;
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 0.78rem;
  pointer-events: none;
  opacity: 0;
  backdrop-filter: blur(4px);
  transition: opacity var(--transition-fast) ease, transform var(--transition-fast) ease;
}

.step-page-preview:hover .zoom-hint {
  opacity: 0.9;
  transform: translateX(-50%) translateY(0);
}

.step-list-block h3 {
  font-size: 0.9rem;
  margin: 0 0 8px;
  color: var(--text-dim);
}

.step-current .part-card {
  border-color: var(--accent);
}

.step-current:empty::after,
.step-remaining:empty::after {
  content: "—";
  color: var(--text-dim);
  font-size: 0.85rem;
}

@media (max-width: 720px) {
  .step-body {
    grid-template-columns: 1fr;
    grid-template-areas:
      "preview"
      "current"
      "remaining";
  }
  .step-page-preview img { max-height: 60vh; object-fit: contain; }
}

/* tablet: preview + current side by side (like desktop), remaining still full-width below */
@media (min-width: 721px) and (max-width: 980px) {
  .step-body { grid-template-columns: minmax(240px, 1fr) 1fr; }
}

/* ---------- zoom overlay ---------- */

.zoom-overlay {
  position: fixed;
  inset: 0;
  background: rgba(5, 6, 9, 0.92);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: fadeIn 0.18s ease;
}

.zoom-scroll {
  width: 100%;
  height: 100%;
  overflow: auto;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 40px 16px;
  -webkit-overflow-scrolling: touch;
}

.zoom-scroll img {
  max-width: none;
  width: min(1400px, 92vw);
  height: auto;
  border-radius: 10px;
  background: white;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}

.zoom-spinner {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: var(--text-dim);
  font-size: 0.9rem;
}

.zoom-close {
  position: fixed;
  top: 18px;
  right: 18px;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  color: white;
  font-size: 1.1rem;
  backdrop-filter: blur(6px);
  z-index: 1001;
}

.zoom-close:hover { background: rgba(255, 255, 255, 0.22); }

/* ---------- LAN / phone sharing banner ---------- */

.lan-banner {
  margin-top: 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 18%, var(--panel)), var(--panel));
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
  border-radius: var(--radius);
  padding: 12px 16px;
  font-size: 0.85rem;
  animation: fadeInUp 0.3s ease;
}

.lan-banner-text b { display: block; font-size: 0.95rem; }
.lan-banner-text span { color: var(--text-dim); }

.lan-banner button {
  width: auto;
  padding: 9px 16px;
  font-size: 0.85rem;
  flex-shrink: 0;
}

.qr-stable {
  margin-top: 10px;
  padding: 8px 10px;
  border-radius: 8px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  font-size: 0.76rem;
  color: var(--text-dim);
  line-height: 1.5;
  word-break: break-all;
}

.qr-stable b { color: var(--text); }

/* Whether the phone actually got through. The computer used to show a QR
   code and then say nothing at all, so a firewall and a phone nobody had
   picked up yet looked identical. */
.qr-status {
  margin-top: 10px;
  font-size: 0.82rem;
  color: var(--text-dim);
}

.qr-status::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 7px;
  border-radius: 50%;
  background: var(--text-dim);
  opacity: 0.5;
  animation: qr-wait 1.4s ease-in-out infinite;
}

.qr-status.is-ok { color: #1f8a4c; }
.qr-status.is-ok::before { background: #1f8a4c; opacity: 1; animation: none; }

@keyframes qr-wait { 0%, 100% { opacity: 0.25; } 50% { opacity: 0.9; } }

.qr-trouble {
  margin-top: 12px;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  font-size: 0.78rem;
  color: var(--text-dim);
  line-height: 1.5;
  text-align: left;
}

.qr-trouble ol { margin: 6px 0 0; padding-left: 18px; }
.qr-trouble li { margin-bottom: 5px; }
.qr-trouble b { color: var(--text); }

.qr-alt-btn {
  width: 100%;
  margin-top: 6px;
  padding: 7px 10px;
  font-size: 0.78rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.qr-alt-btn.is-on { outline: 2px solid var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .qr-status::before { animation: none; }
}

/* ---------- per-part page viewer ---------- */

.part-overlay {
  position: fixed;
  inset: 0;
  background: rgba(5, 6, 9, 0.86);
  z-index: 1100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  animation: fadeIn 0.18s ease;
}

.part-sheet {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  width: min(980px, 100%);
  max-height: 92vh;
  max-height: 92dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: fadeInUp 0.22s ease;
}

.part-sheet-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
}

.part-sheet-head img {
  width: 64px;
  height: 52px;
  object-fit: contain;
  background: #dceefc;
  border-radius: 8px;
  flex: 0 0 auto;
}

.part-sheet-meta { flex: 1; min-width: 0; }

.part-sheet-title {
  font-weight: 600;
  font-size: 0.95rem;
  margin-bottom: 6px;
}

.part-sheet-pagelist {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  max-height: 58px;
  overflow-y: auto;
}

.part-page-chip {
  width: auto;
  padding: 2px 9px;
  font-size: 0.76rem;
  font-weight: 500;
  border-radius: 999px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--text-dim);
}

.part-page-chip:hover { color: var(--text); }

.part-page-chip.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.part-sheet-close {
  width: 32px;
  height: 32px;
  padding: 0;
  flex: 0 0 auto;
  border-radius: 999px;
  background: transparent;
  color: var(--text-dim);
  font-size: 1rem;
}

.part-sheet-close:hover { background: var(--panel-2); color: var(--text); }

.part-sheet-nav {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
}

.part-sheet-nav button {
  width: 44px;
  flex: 0 0 auto;
  padding: 8px 0;
  font-size: 1.05rem;
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--text);
}

.part-sheet-nav button:hover:not(:disabled) {
  border-color: color-mix(in srgb, var(--accent) 50%, var(--border));
}

.part-sheet-pos {
  flex: 1;
  text-align: center;
  font-size: 0.9rem;
  font-weight: 600;
}

.part-sheet-page {
  position: relative;
  flex: 1;
  min-height: 0;
  margin: 0 14px;
  border-radius: 10px;
  overflow: hidden;
  cursor: zoom-in;
  /* transparent, so the letterbox bars around a page read as part of the
     sheet rather than as a big empty white slab */
  background: transparent;
  /* the pinch belongs to the page, not to the whole interface: without this
     the browser zooms the entire sheet and the list behind it */
  touch-action: none;
}

.part-sheet-page.is-zoomed { cursor: grab; }
.part-sheet-page.is-zoomed:active { cursor: grabbing; }

/* one element carries the zoom, so the page and the frame drawn on it can
   never drift apart from each other */
.part-page-stage {
  position: absolute;
  inset: 0;
  transform-origin: center center;
  will-change: transform;
}

.part-sheet-page img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* magnified six times a page should look like paper, not like a blur */
  image-rendering: -webkit-optimize-contrast;
}

.part-page-wait {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  color: var(--text-dim);
  background: var(--panel-2);
  border-radius: 10px;
}

.part-zoom-controls {
  position: absolute;
  right: 8px;
  bottom: 8px;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  border: 1px solid var(--border);
  font-size: 0.75rem;
  color: var(--text-dim);
}

.part-zoom-controls button {
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 999px;
  font-size: 1rem;
  line-height: 1;
  background: transparent;
  color: var(--text);
}

.part-zoom-controls span {
  min-width: 44px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.part-highlight {
  position: absolute;
  border: 3px solid var(--warn);
  border-radius: 4px;
  box-shadow: 0 0 0 2000px rgba(8, 10, 14, 0.45);
  pointer-events: none;
  animation: partPulse 1.6s ease-in-out infinite;
}

@keyframes partPulse {
  0%, 100% { border-color: var(--warn); }
  50% { border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
}

.part-sheet-hint {
  padding: 10px 14px 12px;
  font-size: 0.76rem;
  color: var(--text-dim);
  text-align: center;
}

@media (max-width: 560px) {
  .part-overlay { padding: 0; }
  .part-sheet { max-height: 100dvh; height: 100dvh; border-radius: 0; border: none; width: 100%; }
  .part-sheet-head img { width: 48px; height: 42px; }
  .part-sheet-hint { display: none; }
}

.qr-overlay {
  position: fixed;
  inset: 0;
  background: rgba(5, 6, 9, 0.85);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: fadeIn 0.18s ease;
  padding: 16px;
}

.qr-card {
  background: white;
  color: #16181d;
  border-radius: 18px;
  padding: 28px;
  max-width: 340px;
  width: 100%;
  text-align: center;
  animation: fadeInUp 0.25s ease;
  /* with the troubleshooting panel open this card is taller than a laptop
     screen, and without this the QR code itself is what scrolls off the top */
  max-height: 92vh;
  overflow-y: auto;
}

.qr-card h3 { margin: 0 0 4px; font-size: 1.1rem; }
.qr-card p { color: #62676f; font-size: 0.85rem; margin: 0 0 16px; }
.qr-card canvas, .qr-card img { margin: 0 auto; }
.qr-card .qr-url {
  margin-top: 14px;
  font-size: 0.85rem;
  word-break: break-all;
  background: #f0f1f4;
  border-radius: 8px;
  padding: 8px 10px;
}
.qr-card button {
  margin-top: 16px;
  background: #16181d;
}

/* ---------- animations ---------- */

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes fadeInUp {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes shimmer {
  from { background-position: 200% 0; }
  to { background-position: 0 0; }
}

/* ---------- session sidebar ---------- */

.sidebar {
  flex: 0 0 260px;
  width: 260px;
  background: var(--panel);
  border-right: 1px solid var(--border);
  padding: 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  overflow-y: auto;
}

.sidebar-new-btn {
  width: 100%;
  padding: 10px 12px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text);
  font-weight: 600;
  font-size: 0.88rem;
  transition: border-color var(--transition-fast) ease, background var(--transition-fast) ease;
}

.sidebar-new-btn:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  background: color-mix(in srgb, var(--accent) 10%, var(--panel-2));
}

.sidebar-sessions {
  display: flex;
  flex-direction: column;
  gap: 3px;
  overflow-y: auto;
}

.sidebar-empty {
  color: var(--text-dim);
  font-size: 0.8rem;
  padding: 8px 6px;
  line-height: 1.4;
}

.session-row {
  position: relative;
  padding: 7px 30px 7px 10px;
  border-radius: 10px;
  cursor: pointer;
  transition: background var(--transition-fast) ease;
}

.session-row:hover {
  background: var(--panel-2);
}

.session-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.session-row.active {
  background: color-mix(in srgb, var(--accent) 16%, var(--panel-2));
}

.session-row-name {
  font-weight: 600;
  font-size: 0.85rem;
  border: none;
  background: transparent;
  color: var(--text);
  width: 100%;
  padding: 1px 0;
  font-family: inherit;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.session-row-name:focus {
  outline: none;
  border-bottom: 1px solid var(--accent);
  white-space: normal;
}

.session-row-meta {
  font-size: 0.7rem;
  color: var(--text-dim);
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 3px;
}

.session-row-progress-bar {
  flex: 1;
  height: 4px;
  background: var(--border);
  border-radius: 999px;
  overflow: hidden;
}

.session-row-progress-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--good));
}

.session-row-delete {
  position: absolute;
  top: 6px;
  right: 4px;
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--text-dim);
  font-size: 0.8rem;
  line-height: 1;
  opacity: 0;
  transition: opacity var(--transition-fast) ease, background var(--transition-fast) ease;
}

.session-row:hover .session-row-delete,
.session-row:focus-within .session-row-delete {
  opacity: 1;
}

.session-row-delete:hover {
  background: color-mix(in srgb, red 20%, transparent);
  color: #ff6b6b;
}

@media (max-width: 720px) {
  .shell { flex-direction: column; }
  .sidebar {
    position: static;
    width: 100%;
    height: auto;
    max-height: 240px;
    border-right: none;
    border-bottom: 1px solid var(--border);
  }
}

/* Size in studs, next to the count. Muted rather than loud: it is a check and
   a search aid, not the number you are counting out. */
.part-studs {
  margin-left: auto;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .02em;
  color: #7d8aa3;
  background: #eef1f6;
  border-radius: 5px;
  padding: 2px 6px;
  white-space: nowrap;
}

/* A remote control has no file picker and no session list, so the layout
   should not leave a hole where the sidebar was. */
body.is-remote .shell { display: block; }
body.is-remote .sidebar { display: none; }
body.is-remote .topbar .subtitle { display: none; }

/* ---------- loading animation ---------- */
/* One 9s loop with a story in it: six bricks fall in and build a house, the
   house becomes a little figure, the figure sorts itself into a colour row,
   and then a foot comes down on it — dust, flying chips, and out of the mess
   one finished part. Every element runs the same 9s timeline with its own
   keyframes, so the beats stay together with no script at all.
   The keyframes below are generated from a table of poses (tools/gen_loader.py
   in the commit that added them); edit the table, not the percentages. */

.loader {
  display: block;
  width: 260px;
  max-width: 100%;
  /* the ratio, not a fixed height: on a narrow phone a 260x120 box holding a
     260x120 viewBox letterboxes, and then the foot parked just above the
     picture is inside the element and visible the whole time */
  aspect-ratio: 260 / 120;
  height: auto;
  margin: 4px auto 10px;
  /* clipped, not visible: the foot waits above the frame between stamps, and
     with overflow visible an SVG paints outside its box - a giant boot hung
     over the page heading */
  overflow: hidden;
}

/* rotations are about each piece's own middle; about the corner, which is the
   SVG default, a 30-degree roof brick swings halfway across the picture */
.loader .brick, .loader-part, .loader-specks rect {
  transform-box: fill-box;
  transform-origin: center;
}

.loader .brick rect { fill: currentColor; }
.loader .brick .side { fill: rgba(0, 0, 0, .22); }
.loader .b1 { color: #d94f4f; }
.loader .b2 { color: #e8a33d; }
.loader .b3 { color: #4f9dd9; }
.loader .b4 { color: #5cb85c; }
.loader .b5 { color: #8d7ce0; }
.loader .b6 { color: #e5c73c; }

.loader .brick { animation: 9s cubic-bezier(.3, .7, .3, 1) infinite; }
.loader .b1 { animation-name: b1-fly; }
.loader .b2 { animation-name: b2-fly; }
.loader .b3 { animation-name: b3-fly; }
.loader .b4 { animation-name: b4-fly; }
.loader .b5 { animation-name: b5-fly; }
.loader .b6 { animation-name: b6-fly; }

/* the foot, in profile */
.loader-foot { animation: loaderFoot 9s cubic-bezier(.4, .05, .35, 1) infinite; }
.loader-foot .loader-trouser { fill: #3b465a; }
.loader-foot .loader-cuff { fill: #4a5770; }
.loader-foot .loader-leg { fill: #e8c9a8; }
.loader-foot .loader-shoe { fill: #2f3947; }
/* a pale sole under a dark upper: that contrast is what makes a shape read as
   a shoe at 40 pixels tall, more than any amount of outline detail */
.loader-foot .loader-sole { fill: #cfd6e0; }
.loader-foot .loader-laces { stroke: #dde3ec; stroke-width: 2.5; stroke-linecap: round; fill: none; }

/* The foot is drawn above the frame (its own y runs from -72 to 21), so the
   travel here is what brings the sole down onto the ground line at y=97,
   where the bricks are standing. */
@keyframes loaderFoot {
  0%, 52%   { transform: translateY(-40px); }
  62%       { transform: translateY(76px); }
  67%       { transform: translateY(68px); }
  76%, 100% { transform: translateY(-40px); }
}

/* the commotion */
.loader-dust circle { fill: #b9c2d0; opacity: 0; transform-box: fill-box; transform-origin: center; }
.loader-dust .d1 { r: 9px; animation: dust1 9s ease-out infinite; }
.loader-dust .d2 { r: 7px; animation: dust2 9s ease-out infinite; }
.loader-dust .d3 { r: 11px; animation: dust3 9s ease-out infinite; }
.loader-dust .d4 { r: 8px; animation: dust4 9s ease-out infinite; }
.loader-dust .d5 { r: 10px; animation: dust5 9s ease-out infinite; }
.loader-dust .d6 { r: 6px; animation: dust6 9s ease-out infinite; }

.loader-specks rect { fill: #8d97a8; opacity: 0; }
.loader-specks .s1 { animation: speck1 9s cubic-bezier(.2, .6, .4, 1) infinite; }
.loader-specks .s2 { animation: speck2 9s cubic-bezier(.2, .6, .4, 1) infinite; }
.loader-specks .s3 { animation: speck3 9s cubic-bezier(.2, .6, .4, 1) infinite; }
.loader-specks .s4 { animation: speck4 9s cubic-bezier(.2, .6, .4, 1) infinite; }

/* the cartoon impact lines */
.loader-bang path {
  stroke: #98a2b3;
  stroke-width: 3;
  stroke-linecap: round;
  fill: none;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  animation: bang 9s ease-out infinite;
}
@keyframes bang {
  0%, 61%   { transform: scale(.4); opacity: 0; }
  63%       { transform: scale(1); opacity: .9; }
  68%, 100% { transform: scale(1.25); opacity: 0; }
}

/* the finished part */
.loader-part rect { fill: #4f9dd9; }
.loader-part .side { fill: rgba(0, 0, 0, .22); }
.loader-part .gloss { fill: rgba(255, 255, 255, .35); }
.loader-part { opacity: 0; animation: loaderPart 9s cubic-bezier(.2, .9, .3, 1.2) infinite; }
@keyframes loaderPart {
  0%, 74%   { transform: translateY(26px) scale(.5); opacity: 0; }
  81%       { transform: translateY(-4px) scale(1.06); opacity: 1; }
  85%       { transform: translateY(0) scale(1); opacity: 1; }
  96%       { transform: translateY(0) scale(1); opacity: 1; }
  100%      { transform: translateY(-8px) scale(.96); opacity: 0; }
}

@keyframes b1-fly {
  0% { transform: translate(79px, -60px); opacity: 0; }
  8% { transform: translate(79px, 78px); opacity: 1; }
  11% { transform: translate(79px, 72px); opacity: 1; }
  26% { transform: translate(79px, 72px); }
  34% { transform: translate(96px, 54px); }
  44% { transform: translate(96px, 54px); }
  52% { transform: translate(10px, 72px); }
  61% { transform: translate(10px, 72px); }
  63% { transform: translate(10px, 65px) rotate(-5deg); }
  70% { transform: translate(-42px, 42px) rotate(-34deg); opacity: 1; }
  76% { transform: translate(-58px, 66px) rotate(-51deg); opacity: 0; }
  100% { transform: translate(79px, -60px); opacity: 0; }
}
@keyframes b2-fly {
  0% { transform: translate(79px, -60px); opacity: 0; }
  3% { transform: translate(79px, -60px); opacity: 0; }
  11% { transform: translate(79px, 58px); opacity: 1; }
  14% { transform: translate(79px, 52px); opacity: 1; }
  26% { transform: translate(79px, 52px); }
  34% { transform: translate(96px, 36px); }
  44% { transform: translate(96px, 36px); }
  52% { transform: translate(51px, 72px); }
  61% { transform: translate(51px, 72px); }
  63% { transform: translate(51px, 65px) rotate(-5deg); }
  70% { transform: translate(21px, 28px) rotate(-20deg); opacity: 1; }
  76% { transform: translate(12px, 63px) rotate(-30deg); opacity: 0; }
  100% { transform: translate(79px, -60px); opacity: 0; }
}
@keyframes b3-fly {
  0% { transform: translate(79px, -60px) rotate(-28deg); opacity: 0; }
  6% { transform: translate(79px, -60px) rotate(-28deg); opacity: 0; }
  14% { transform: translate(79px, 34px) rotate(-28deg); opacity: 1; }
  17% { transform: translate(79px, 28px) rotate(-28deg); opacity: 1; }
  26% { transform: translate(79px, 28px) rotate(-28deg); }
  34% { transform: translate(104px, 14px); }
  44% { transform: translate(104px, 14px); }
  52% { transform: translate(174px, 72px); }
  61% { transform: translate(174px, 72px); }
  63% { transform: translate(174px, 65px) rotate(-5deg); }
  70% { transform: translate(204px, 26px) rotate(28deg); opacity: 1; }
  76% { transform: translate(213px, 63px) rotate(42deg); opacity: 0; }
  100% { transform: translate(79px, -60px) rotate(-28deg); opacity: 0; }
}
@keyframes b4-fly {
  0% { transform: translate(113px, -60px) rotate(28deg); opacity: 0; }
  9% { transform: translate(113px, -60px) rotate(28deg); opacity: 0; }
  17% { transform: translate(113px, 34px) rotate(28deg); opacity: 1; }
  20% { transform: translate(113px, 28px) rotate(28deg); opacity: 1; }
  26% { transform: translate(113px, 28px) rotate(28deg); }
  34% { transform: translate(66px, 40px) rotate(-16deg); }
  44% { transform: translate(66px, 40px) rotate(-16deg); }
  52% { transform: translate(133px, 72px); }
  61% { transform: translate(133px, 72px); }
  63% { transform: translate(133px, 80px) scaleY(.45); }
  70% { transform: translate(149px, 50px) rotate(-12deg); opacity: 1; }
  76% { transform: translate(154px, 68px) rotate(-18deg); opacity: 0; }
  100% { transform: translate(113px, -60px) rotate(28deg); opacity: 0; }
}
@keyframes b5-fly {
  0% { transform: translate(113px, -60px); opacity: 0; }
  12% { transform: translate(113px, -60px); opacity: 0; }
  20% { transform: translate(113px, 58px); opacity: 1; }
  23% { transform: translate(113px, 52px); opacity: 1; }
  26% { transform: translate(113px, 52px); }
  34% { transform: translate(126px, 40px) rotate(16deg); }
  44% { transform: translate(126px, 40px) rotate(16deg); }
  52% { transform: translate(215px, 72px); }
  61% { transform: translate(215px, 72px); }
  63% { transform: translate(215px, 65px) rotate(-5deg); }
  70% { transform: translate(273px, 38px) rotate(32deg); opacity: 1; }
  76% { transform: translate(290px, 65px) rotate(48deg); opacity: 0; }
  100% { transform: translate(113px, -60px); opacity: 0; }
}
@keyframes b6-fly {
  0% { transform: translate(113px, -60px); opacity: 0; }
  15% { transform: translate(113px, -60px); opacity: 0; }
  23% { transform: translate(113px, 78px); opacity: 1; }
  26% { transform: translate(113px, 72px); opacity: 1; }
  34% { transform: translate(105px, 72px); }
  44% { transform: translate(105px, 72px); }
  52% { transform: translate(92px, 72px); }
  61% { transform: translate(92px, 72px); }
  63% { transform: translate(92px, 80px) scaleY(.45); }
  70% { transform: translate(72px, 54px) rotate(14deg); opacity: 1; }
  76% { transform: translate(66px, 68px) rotate(21deg); opacity: 0; }
  100% { transform: translate(113px, -60px); opacity: 0; }
}
@keyframes dust1 {
  0%, 62% { transform: translate(0, 0) scale(.2); opacity: 0; }
  64%     { transform: translate(-14px, -2px) scale(.9); opacity: .85; }
  72%     { transform: translate(-46px, -6px) scale(1.7); opacity: .35; }
  78%, 100% { transform: translate(-58px, -7px) scale(2.04); opacity: 0; }
}
@keyframes dust2 {
  0%, 62% { transform: translate(0, 0) scale(.2); opacity: 0; }
  64%     { transform: translate(-8px, -5px) scale(.9); opacity: .85; }
  72%     { transform: translate(-26px, -16px) scale(1.3); opacity: .35; }
  78%, 100% { transform: translate(-32px, -19px) scale(1.56); opacity: 0; }
}
@keyframes dust3 {
  0%, 62% { transform: translate(0, 0) scale(.2); opacity: 0; }
  64%     { transform: translate(-2px, -6px) scale(.9); opacity: .85; }
  72%     { transform: translate(-8px, -20px) scale(1.1); opacity: .35; }
  78%, 100% { transform: translate(-10px, -24px) scale(1.32); opacity: 0; }
}
@keyframes dust4 {
  0%, 62% { transform: translate(0, 0) scale(.2); opacity: 0; }
  64%     { transform: translate(4px, -5px) scale(.9); opacity: .85; }
  72%     { transform: translate(14px, -17px) scale(1.4); opacity: .35; }
  78%, 100% { transform: translate(18px, -20px) scale(1.68); opacity: 0; }
}
@keyframes dust5 {
  0%, 62% { transform: translate(0, 0) scale(.2); opacity: 0; }
  64%     { transform: translate(10px, -2px) scale(.9); opacity: .85; }
  72%     { transform: translate(34px, -8px) scale(1.6); opacity: .35; }
  78%, 100% { transform: translate(42px, -10px) scale(1.92); opacity: 0; }
}
@keyframes dust6 {
  0%, 62% { transform: translate(0, 0) scale(.2); opacity: 0; }
  64%     { transform: translate(16px, 1px) scale(.9); opacity: .85; }
  72%     { transform: translate(52px, 2px) scale(1.2); opacity: .35; }
  78%, 100% { transform: translate(65px, 2px) scale(1.44); opacity: 0; }
}
@keyframes speck1 {
  0%, 62%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  64%       { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  74%       { transform: translate(-58px, -40px) rotate(-160deg); opacity: 1; }
  80%, 100% { transform: translate(-70px, -22px) rotate(-208deg); opacity: 0; }
}
@keyframes speck2 {
  0%, 62%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  64%       { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  74%       { transform: translate(-30px, -54px) rotate(120deg); opacity: 1; }
  80%, 100% { transform: translate(-36px, -30px) rotate(156deg); opacity: 0; }
}
@keyframes speck3 {
  0%, 62%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  64%       { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  74%       { transform: translate(28px, -50px) rotate(-140deg); opacity: 1; }
  80%, 100% { transform: translate(34px, -28px) rotate(-182deg); opacity: 0; }
}
@keyframes speck4 {
  0%, 62%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  64%       { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  74%       { transform: translate(58px, -34px) rotate(170deg); opacity: 1; }
  80%, 100% { transform: translate(70px, -19px) rotate(221deg); opacity: 0; }
}


@media (prefers-reduced-motion: reduce) {
  /* a moving picture is a nicety; the progress bar is the information */
  .loader { display: none; }
}
