/* =========================================================
   photo-tools — darkroom control surface
   ========================================================= */

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }

:root {
  /* Tone */
  --bg-base: #0a0c0e;
  --bg-chrome: #141619;
  --bg-sunken: #070a0c;
  --bg-canvas: #040506;
  --bg-elev: #1a1d22;

  /* Lines */
  --line-subtle: rgba(255, 255, 255, 0.04);
  --line: rgba(255, 255, 255, 0.07);
  --line-strong: rgba(255, 255, 255, 0.13);

  /* Type tones */
  --text: #e8e9ea;
  --text-muted: #858a90;
  --text-dim: #4f545a;

  /* Accent — darkroom red, used sparingly */
  --accent: #e5493a;
  --accent-soft: rgba(229, 73, 58, 0.12);
  --accent-line: rgba(229, 73, 58, 0.55);
  --accent-glow: rgba(229, 73, 58, 0.35);
  --accent-wash: rgba(229, 73, 58, 0.04);
  --accent-vignette: rgba(229, 73, 58, 0.06);

  /* Fonts */
  --f-display: 'Fraunces', 'New York', Georgia, serif;
  --f-ui: 'Hanken Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --f-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Consolas, monospace;

  /* Geometry */
  --r-sm: 4px;
  --r-md: 6px;
  --r-lg: 10px;

  /* Layout */
  --rail-w: 80px;
  --topbar-h: 52px;
  --statusbar-h: 28px;
  --lookbar-w: 168px;
}

body {
  font-family: var(--f-ui);
  font-size: 13px;
  color: var(--text);
  background: var(--bg-base);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  display: grid;
  grid-template-rows: var(--topbar-h) 1fr var(--statusbar-h);
  min-height: 100vh;
  overflow: hidden;
}

/* =========================================================
   SCROLLBARS — one consistent darkroom skin for every overflow container.
   WebKit gets the explicit thumb styling; Firefox honors `scrollbar-color`
   on the same selectors. Inactive thumbs barely register; active/hover
   thumbs glow accent-line so the user knows the bar is interactive.
   ========================================================= */
.workshop-body,
.picker-body,
.cmdk-results,
.rail-items,
.export-errors {
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}
.workshop-body::-webkit-scrollbar,
.picker-body::-webkit-scrollbar,
.cmdk-results::-webkit-scrollbar,
.rail-items::-webkit-scrollbar,
.export-errors::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
.workshop-body::-webkit-scrollbar-track,
.picker-body::-webkit-scrollbar-track,
.cmdk-results::-webkit-scrollbar-track,
.rail-items::-webkit-scrollbar-track,
.export-errors::-webkit-scrollbar-track {
  background: transparent;
}
.workshop-body::-webkit-scrollbar-thumb,
.picker-body::-webkit-scrollbar-thumb,
.cmdk-results::-webkit-scrollbar-thumb,
.rail-items::-webkit-scrollbar-thumb,
.export-errors::-webkit-scrollbar-thumb {
  /* line-strong (rgba 0.13) reads as a faint pill on dark bg without
     looking like a default OS scrollbar. 2px transparent border + clip
     makes the visible thumb 6px wide inside a 10px gutter. */
  background: var(--line-strong);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: padding-box;
  transition: background-color 160ms;
}
.workshop-body::-webkit-scrollbar-thumb:hover,
.picker-body::-webkit-scrollbar-thumb:hover,
.cmdk-results::-webkit-scrollbar-thumb:hover,
.rail-items::-webkit-scrollbar-thumb:hover,
.export-errors::-webkit-scrollbar-thumb:hover {
  background: var(--accent-line);
}
.rail-items::-webkit-scrollbar { width: 6px; }

/* Changelog body deliberately hides its scrollbar entirely — the top/bottom
   fade overlays on .changelog-modal-inner already signal "more content".
   On macOS with "Always show scrollbars" preference enabled, even our thin
   custom skin reads as visual noise inside a reading-focused modal. */
.changelog-modal-body {
  scrollbar-width: none;          /* Firefox + Chromium 121+ */
  -ms-overflow-style: none;       /* legacy Edge/IE */
}
.changelog-modal-body::-webkit-scrollbar { display: none; }

/* Tiny film grain over the whole UI */
.grain {
  position: fixed; inset: 0;
  width: 100vw; height: 100vh;
  pointer-events: none;
  opacity: 0.025;
  mix-blend-mode: screen;
  z-index: 10000;
}

/* =========================================================
   TOP BAR
   ========================================================= */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  background: var(--bg-chrome);
  border-bottom: 1px solid var(--line);
  position: relative;
  z-index: 10;
}

.brand {
  display: flex;
  align-items: baseline;
  gap: 12px;
}

.brand-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent-glow);
  transform: translateY(-1px);
  align-self: center;
  margin-right: -4px;
  animation: brand-breathe 4.5s ease-in-out infinite;
}
@keyframes brand-breathe {
  0%, 100% { box-shadow: 0 0 5px var(--accent-glow); }
  50%      { box-shadow: 0 0 11px var(--accent-line); }
}

.brand-name {
  font: 400 20px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0;
}

.brand-sub {
  font: 500 10px/1 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--text-dim);
}

.topbar-meta {
  display: flex;
  align-items: center;
  gap: 24px;
  font: 500 11px/1 var(--f-mono);
  color: var(--text-muted);
}

.topbar-meta em {
  font-style: normal;
  color: var(--text);
  font-weight: 500;
}
.topbar-meta .meta-sep { color: var(--text-dim); margin: 0 4px; }
.topbar-meta .meta-label {
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 10px;
  margin-left: 8px;
}
.meta-kbd { display: flex; gap: 6px; align-items: center; }

.topbar-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 26px; height: 26px;
  color: var(--text-dim);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  padding: 0;
  transition: color 120ms, border-color 120ms;
}
.topbar-pill:hover {
  color: var(--text);
  border-color: var(--line-strong);
}
.topbar-pill-badge {
  position: absolute;
  top: -2px; right: -2px;
  width: 8px; height: 8px;
  background: var(--accent);
  border: 1.5px solid var(--bg-chrome);
  border-radius: 50%;
  box-shadow: 0 0 6px var(--accent-glow);
}
.topbar-pill-badge[hidden] { display: none; }

/* Mobile workshop pill — hidden by default; revealed inside the mobile
   media query. The lookbar's workshop entry is hidden on mobile (the
   bottom sheet can't fit a 4th control row) so this topbar pill is the
   discoverable replacement entry. */
.workshop-pill-mobile { display: none; }

/* Language toggle — two-segment pill, mono small caps to echo brand-sub */
.lang-seg {
  display: inline-flex;
  align-items: stretch;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 2px;
  gap: 0;
}
.lang-seg button {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font: 500 10px/1 var(--f-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 4px 9px;
  min-width: 26px;
  border-radius: 999px;
  cursor: pointer;
  transition: color 140ms ease, background 140ms ease;
}
.lang-seg button:hover { color: var(--text); }
.lang-seg button.active {
  color: var(--text);
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent-line);
}
.lang-seg button:focus-visible {
  outline: 1px solid var(--accent-line);
  outline-offset: 2px;
}

/* Keyboard caps */
kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  font: 500 10px/1 var(--f-mono);
  color: var(--text-muted);
  background: var(--bg-sunken);
  border: 1px solid var(--line-strong);
  border-bottom-width: 2px;
  border-radius: 3px;
}

/* =========================================================
   WORKSPACE — canvas dominates, narrow rail on the right.
   The lookbar pins to the bottom (outside .workspace, so it spans
   the whole shell width). All tools off the hot path live in the
   workshop drawer / pickers / cmdk overlay — none of them claim
   a permanent grid cell.
   ========================================================= */

.workspace {
  display: grid;
  grid-template-columns: var(--lookbar-w) 1fr var(--rail-w);
  min-height: 0;
  overflow: hidden;
}

.pane {
  min-height: 0;
  overflow: hidden;
}


.row { display: flex; flex-direction: column; gap: 14px; margin-bottom: 12px; }
.row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.row:last-child { margin-bottom: 0; }

.field { display: flex; flex-direction: column; gap: 7px; min-width: 0; }

.field-label {
  font: 500 10px/1 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.field-readout {
  font: 500 11px/1 var(--f-mono);
  color: var(--text);
  letter-spacing: 0.04em;
}
.field-readout .ru {
  color: var(--text-dim);
  font-weight: 400;
  font-size: 9px;
  margin-left: 3px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Soft compatibility-warning row, used under the template picker when a
   frame×template combo is known to render poorly (e.g. polaroid + slate's
   4-row mono OSD into the narrow bottom strip). Kept as a soft warning,
   not a hard disable — some users may want the combo anyway. */
.hint-row {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin-top: 6px;
  padding: 6px 8px;
  border-radius: 6px;
  font: 400 11px/1.45 var(--f-sans);
  color: var(--text-dim);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
}
.hint-row[hidden] { display: none; }
.hint-row .hint-icon {
  flex: 0 0 auto;
  color: var(--accent);
  font-weight: 600;
  line-height: 1.45;
}
.hint-row .hint-text { flex: 1 1 auto; }

/* =========================================================
   FORM INPUTS
   ========================================================= */

input[type="text"],
input[type="number"],
select {
  font: 400 13px/1 var(--f-ui);
  color: var(--text);
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 8px 10px;
  outline: none;
  transition: border-color 120ms, background 120ms;
}
input[type="text"]:hover,
input[type="number"]:hover,
select:hover {
  border-color: var(--line-strong);
}
input[type="text"]:focus,
input[type="number"]:focus,
select:focus {
  border-color: var(--accent-line);
  /* Use longhand background-color so we don't reset the chevron's
     background-image / -position / -size / -repeat (set on `select` and
     `select:focus`). The shorthand `background:` resets all bg-* sub-
     properties to initial — including `background-repeat: repeat` —
     which would tile the focused-state red chevron across the whole
     select width as a wave of Vs. */
  background-color: var(--bg-base);
}
input::placeholder {
  color: var(--text-dim);
  font-style: italic;
  font-weight: 400;
}

select {
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%234f545a' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'><path d='M1 1l4 4 4-4'/></svg>");
  background-position: right 12px center;
  background-size: 10px 6px;
  background-repeat: no-repeat;
  padding-right: 32px;
}
select:hover {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%23858a90' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'><path d='M1 1l4 4 4-4'/></svg>");
}
select:focus {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%23e5493a' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'><path d='M1 1l4 4 4-4'/></svg>");
}

/* Range slider */
input[type="range"] {
  appearance: none;
  width: 100%;
  height: 20px;
  background: transparent;
  cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 2px;
  background: var(--line-strong);
  border-radius: 2px;
}
input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  width: 12px; height: 12px;
  margin-top: -5px;
  background: var(--accent);
  border: 2px solid #fff;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.4), 0 0 6px var(--accent-glow);
  transition: transform 120ms, box-shadow 160ms;
}
input[type="range"]::-webkit-slider-thumb:active { transform: scale(0.92); }
input[type="range"]:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 1px rgba(0,0,0,0.4), 0 0 0 4px var(--accent-soft), 0 0 6px var(--accent-glow);
}
input[type="range"]::-moz-range-track {
  height: 2px;
  background: var(--line-strong);
  border-radius: 2px;
}
input[type="range"]::-moz-range-thumb {
  width: 12px; height: 12px;
  background: var(--accent);
  border: 2px solid #fff;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.4), 0 0 6px var(--accent-glow);
}

/* =========================================================
   SEGMENTED CONTROLS
   ========================================================= */

.seg {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 2px;
  gap: 2px;
}
/* Author display:grid above beats the UA [hidden] rule by specificity, so
   re-state the intent — pickers use hidden seg containers as data-only
   plumbing for wireSeg. (CLAUDE.md "Pitfalls" — author display rules
   beat UA `hidden`.) */
.seg[hidden] { display: none; }
.seg button {
  padding: 8px 4px;
  font: 500 11px/1 var(--f-mono);
  letter-spacing: 0.06em;
  color: var(--text-muted);
  background: transparent;
  border: none;
  border-radius: 3px;
  cursor: pointer;
  transition: background 120ms, color 120ms;
}
.seg button:hover { color: var(--text); background: rgba(255,255,255,0.03); }
.seg button.active {
  background: var(--bg-elev);
  color: var(--text);
  font-weight: 600;
  box-shadow:
    inset 0 0 0 1px var(--line-strong),
    inset 0 -2px 0 var(--accent);
}
.seg .seg-sep { color: var(--text-dim); margin: 0 1px; font-weight: 400; }

/* Frame seg wraps on narrow screens */
.seg-frames {
  grid-auto-columns: minmax(0, 1fr);
}
.seg-frames button {
  padding: 10px 2px;
  font-size: 11px;
  font-family: var(--f-ui);
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Two-tier seg picker — top row is family/grammar tabs, bottom row is
   variants. Only variants whose data-family matches the active tab are
   un-hidden (toggled via JS). Both rows render as .seg buttons. */
.seg-stack {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.seg-family {
  /* Family tabs are quieter than primary segs — slightly smaller text,
     less visual weight, since picking a family is intermediate (the
     real commit happens at the variant level). */
  background: transparent;
  border: 1px solid var(--line);
  padding: 2px;
}
.seg-family button {
  padding: 7px 4px;
  font-size: 10px;
  font-family: var(--f-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.seg-family button.active {
  background: var(--bg-elev);
  color: var(--text);
  font-weight: 600;
  /* No underline accent on family — keep it visually subordinate to
     the variant row, which carries the active accent. */
  box-shadow: inset 0 0 0 1px var(--line-strong);
}
.seg-variants {
  grid-auto-columns: minmax(0, 1fr);
}
.seg-variants button {
  padding: 11px 4px;
  font-size: 12px;
  font-family: var(--f-ui);
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.seg-variants button[hidden] {
  /* `hidden` inside a grid layout is normalized to `display: none` via
     the UA stylesheet, but only at specificity 0,0,0,1 — any later
     button rule with class selector would override. Pin it explicitly
     so the variant filter is bulletproof (CLAUDE.md "hidden attribute"
     pitfall). */
  display: none !important;
}

/* 3×3 anchor grid for signature placement. Replaces the old 3-button
   br/bl/bc seg — gives the user the full 9-cell anchor matrix that
   real-world watermarking apps (Lightroom, NOMO) expose. The grid is
   square (aspect-ratio 1) so each cell is the same size; capped at
   ~140px so it doesn't dominate the sidebar on wide viewports. */
.pos-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  gap: 4px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  padding: 6px;
  border-radius: var(--r-sm);
  aspect-ratio: 1;
  max-width: 140px;
}
.pos-grid button {
  border: 1px solid transparent;
  background: rgba(255, 255, 255, 0.04);
  border-radius: 3px;
  cursor: pointer;
  position: relative;
  transition: background 120ms, border-color 120ms;
  /* Touch target floor — each cell on mobile must be ≥ 44×44, the
     pos-grid's max-width 140 / 3 ≈ 46.7px ensures this when the grid
     fills the field. The min-height fallback handles iPad portrait
     where the grid renders tighter due to wider sidebar columns. */
  min-height: 36px;
}
.pos-grid button:hover {
  background: rgba(255, 255, 255, 0.10);
}
.pos-grid button.active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 20%, transparent);
}
.pos-grid button.active::after {
  /* Filled-circle indicator dead-center — reads as "the signature
     anchors here". Sized to ~30% of the cell so it's visible but not
     overpowering. */
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 28%;
  height: 28%;
  background: var(--accent);
  border-radius: 50%;
}
.pos-grid button:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

/* =========================================================
   CHIPS (show-fields)
   ========================================================= */

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px 6px 8px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 999px;
  font: 500 11px/1 var(--f-ui);
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
  transition: border-color 120ms, color 120ms, background 120ms;
}
.chip input {
  appearance: none;
  width: 8px; height: 8px;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  margin: 0;
  transition: background 120ms, border-color 120ms;
}
.chip input:checked {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}
.chip:hover { color: var(--text); border-color: var(--line-strong); }
.chip:has(input:focus-visible) {
  border-color: var(--accent-line);
  box-shadow: 0 0 0 2px var(--accent-soft);
}
.chip:has(input:checked) {
  color: var(--text);
  border-color: var(--line-strong);
  background: rgba(255,255,255,0.02);
}

/* =========================================================
   FILE DROP
   ========================================================= */

.file-drop {
  display: block;
  position: relative;
  cursor: pointer;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  background: var(--bg-sunken);
  padding: 16px 14px;
  transition: border-color 150ms, background 150ms;
}
.file-drop:hover { border-color: var(--accent-line); background: var(--accent-wash); }
.file-drop input[type="file"] {
  position: absolute; inset: 0;
  opacity: 0;
  cursor: pointer;
}
.file-drop-inner {
  display: flex;
  align-items: center;
  gap: 12px;
}
.file-drop-icon {
  width: 36px; height: 36px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: var(--text-muted);
  background: var(--bg-chrome);
  border: 1px solid var(--line);
}
.file-drop:hover .file-drop-icon { color: var(--accent); }
.file-drop-copy { display: flex; flex-direction: column; gap: 4px; }
.file-drop-copy strong { font: 500 13px/1.2 var(--f-ui); color: var(--text); }
.file-drop-copy span { font: 500 10px/1.2 var(--f-mono); color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.1em; }

/* =========================================================
   EXIF
   ========================================================= */

.warn {
  font: 400 12px/1.55 var(--f-ui);
  color: #ffc27a;
  background: rgba(255, 180, 80, 0.06);
  border: 1px solid rgba(255, 180, 80, 0.3);
  border-left: 2px solid rgba(255, 180, 80, 0.8);
  padding: 10px 12px;
  border-radius: var(--r-sm);
  margin-bottom: 12px;
}
.warn[hidden] { display: none; }
.warn strong { color: #ffdca3; }

.exif summary, .frame-advanced summary {
  cursor: pointer;
  padding: 9px 12px;
  font: 500 11px/1 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-muted);
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  transition: color 120ms, border-color 120ms;
}
.exif summary::marker, .exif summary::-webkit-details-marker,
.frame-advanced summary::marker, .frame-advanced summary::-webkit-details-marker { display: none; }
.exif summary::after, .frame-advanced summary::after {
  content: '+';
  font-family: var(--f-mono);
  color: var(--text-dim);
  transition: transform 120ms;
}
.exif[open] summary::after, .frame-advanced[open] summary::after { content: '−'; }
.exif summary:hover, .frame-advanced summary:hover { color: var(--text); border-color: var(--line-strong); }
.exif .summary-hint { color: var(--text-dim); font-weight: 400; }

.frame-advanced { margin-top: 10px; }
.frame-advanced[hidden] { display: none; }
.frame-advanced > .row:first-of-type { margin-top: 10px; }
.frame-advanced #reset-bg-btn { margin-top: 8px; }

/* ──────────────────────────────────────────────────────────────────────────
   LOOK system — meta-primitive + library picker.

   Look is the "answer to: what overall vibe am I going for?". It sits ABOVE
   the four fine-tune lookchips (frame / template / aspect / quality) in the
   lookbar, both spatially and conceptually: pick a Look first, then twist.
   Visually it's larger than a lookchip, dashed-border (signaling "a slot you
   fill"), and carries a Fraunces italic name + ✦ accent mark. When the user
   diverges from the applied preset, a 4px accent dot pulses in the corner.
   ────────────────────────────────────────────────────────────────────────── */

.lookbar-look {
  appearance: none;
  -webkit-appearance: none;
  display: grid;
  grid-template-columns: 22px 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 12px 13px 13px 12px;
  background: linear-gradient(135deg, rgba(229,73,58,0.06) 0%, rgba(229,73,58,0.02) 60%, transparent 100%);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  color: var(--text);
  cursor: pointer;
  text-align: left;
  position: relative;
  transition: border-color 220ms ease, background 220ms ease, transform 160ms ease;
  flex-shrink: 0;
  /* Sits its own row in the lookbar — no chip-group enclosure, the dashed
     border is the visual container. Reads as a separate decision unit. */
  margin-top: 2px;
}
.lookbar-look:hover {
  border-style: solid;
  border-color: var(--accent-line);
  background: linear-gradient(135deg, rgba(229,73,58,0.10) 0%, rgba(229,73,58,0.03) 60%, transparent 100%);
}
.lookbar-look:active { transform: translateY(0.5px); }
.lookbar-look[data-open="true"] {
  border-style: solid;
  border-color: var(--accent);
  background: linear-gradient(135deg, rgba(229,73,58,0.14) 0%, rgba(229,73,58,0.04) 60%, transparent 100%);
  box-shadow: 0 0 0 1px var(--accent-line) inset, 0 4px 18px -8px var(--accent-glow);
}

.lookbar-look-mark {
  font-family: var(--f-display);
  font-size: 16px;
  line-height: 1;
  color: var(--accent);
  text-shadow: 0 0 8px var(--accent-glow);
  text-align: center;
  user-select: none;
}
.lookbar-look-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.lookbar-look-key {
  font: 500 9.5px/1 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--text-dim);
}
.lookbar-look-value {
  font: italic 500 14.5px/1.15 var(--f-display);
  color: var(--text);
  letter-spacing: 0.005em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lookbar-look[data-empty="true"] .lookbar-look-value {
  color: var(--text-dim);
  font-style: italic;
  font-weight: 400;
}
.lookbar-look-caret {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  color: var(--text-dim);
  transition: transform 180ms ease, color 160ms ease;
}
.lookbar-look[data-open="true"] .lookbar-look-caret {
  transform: rotate(-90deg);
  color: var(--accent);
}

/* Modified-state pulse — appears when cfg has drifted from the last-applied
   preset's snapshot. A 4px accent dot pinned to the chip's top-right corner,
   1.6s ease-in-out breath. The actual "modified" word lives in the value
   line as a faded suffix injected by JS. */
.lookbar-look-status {
  position: absolute;
  top: 9px;
  right: 9px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 6px var(--accent-glow);
  animation: look-modified-pulse 1.6s ease-in-out infinite;
}
@keyframes look-modified-pulse {
  0%, 100% { opacity: 0.55; transform: scale(1); }
  50%      { opacity: 1;    transform: scale(1.18); }
}

/* ── LOOK picker ─────────────────────────────────────────────────────────
   Wider than the narrow pickers (factory tile grid + user list both need
   room). Body has its own scroll; the footer pin stays at the bottom so
   "save / paste" never scrolls out of reach. */
.picker-look {
  width: min(440px, calc(100vw - var(--lookbar-w) - var(--rail-w) - 40px));
  max-height: min(640px, calc(100vh - var(--topbar-h) - var(--statusbar-h) - 32px));
  /* The footer needs to span edge-to-edge (with its own top border) and not
     be wrapped by the base .picker's 18px padding. So we zero that and put
     the padding back on header / body explicitly. */
  padding: 0;
}
.picker-look .picker-header {
  padding: 18px 18px 12px;
  flex: 0 0 auto;
}
.picker-look .picker-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 0 18px 14px;
}

.picker-section-count {
  font: italic 400 13px/1 var(--f-display);
  color: var(--text-dim);
  margin-left: 6px;
  font-feature-settings: 'tnum';
}

/* ── Factory grid (curated seeds) ──────────────────────────────────────── */
.look-factory-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  padding: 6px 0 4px;
}
.look-tile {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  padding: 14px 8px 12px;
  background: rgba(255,255,255,0.02);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--text);
  cursor: pointer;
  transition: border-color 160ms ease, background 160ms ease, transform 160ms ease;
  min-height: 96px;
  position: relative;
  text-align: center;
}
@media (hover: hover) and (pointer: fine) {
  .look-tile:hover {
    border-color: var(--line-strong);
    background: rgba(255,255,255,0.05);
    transform: translateY(-1px);
  }
}
.look-tile:active { transform: translateY(0.5px); }
.look-tile.active {
  border-color: var(--accent);
  background: linear-gradient(180deg, rgba(229,73,58,0.10) 0%, rgba(229,73,58,0.02) 100%);
  box-shadow: 0 0 0 1px var(--accent-line) inset;
}
.look-tile.active::before {
  /* Tiny corner mark on the active tile — a small triangle in the top-right
     that visually echoes the lookchip[data-open]::after vertical accent rule
     so "currently applied" reads as a deliberate signature element across
     the whole UI rather than just a border-color change. */
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 10px 10px 0 0;
  border-color: var(--accent) transparent transparent transparent;
}
.look-tile-emoji {
  font-size: 28px;
  line-height: 1;
  filter: saturate(1.05);
  margin-top: 2px;
}
.look-tile-label {
  font: italic 500 12.5px/1.25 var(--f-display);
  color: var(--text);
  letter-spacing: 0.005em;
  /* Allow wrap for two-line names, capped at 2 lines so tiles stay uniform. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}
.look-tile.active .look-tile-label { color: var(--accent); }

/* ── User preset list ────────────────────────────────────────────────── */
.look-user-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 4px 0 2px;
}
.look-user-list:empty { display: none; }
.look-user-empty {
  margin: 8px 0 4px;
  padding: 14px 12px;
  border: 1px dashed var(--line);
  border-radius: var(--r-md);
  font: italic 400 12px/1.5 var(--f-display);
  color: var(--text-dim);
  text-align: center;
  letter-spacing: 0.005em;
}
.look-user-empty[hidden] { display: none; }

.look-user-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 8px;
  padding: 10px 11px 10px 12px;
  background: rgba(255,255,255,0.02);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color 160ms ease, background 160ms ease;
  text-align: left;
  color: var(--text);
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  position: relative;
}
@media (hover: hover) and (pointer: fine) {
  .look-user-row:hover {
    border-color: var(--line-strong);
    background: rgba(255,255,255,0.04);
  }
}
.look-user-row.active {
  border-color: var(--accent);
  background: linear-gradient(90deg, rgba(229,73,58,0.10), rgba(229,73,58,0.02) 80%);
}
.look-user-row.active::before {
  content: '';
  position: absolute;
  left: -1px;
  top: 8px;
  bottom: 8px;
  width: 2px;
  background: var(--accent);
  border-radius: 1px;
}
.look-user-row-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.look-user-row-name {
  font: italic 500 13.5px/1.2 var(--f-display);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  letter-spacing: 0.005em;
}
.look-user-row-meta {
  font: 400 10.5px/1.2 var(--f-mono);
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.look-user-row-action {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-dim);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: all 140ms ease;
  flex: 0 0 auto;
  font-size: 14px;
  line-height: 1;
}
@media (hover: hover) and (pointer: fine) {
  .look-user-row:hover .look-user-row-action {
    border-color: var(--line);
    background: rgba(255,255,255,0.04);
  }
  .look-user-row-action:hover {
    color: var(--text);
    border-color: var(--line-strong);
    background: rgba(255,255,255,0.08);
  }
  .look-user-row-action.is-danger:hover {
    color: var(--accent);
    border-color: var(--accent-line);
    background: rgba(229,73,58,0.10);
  }
}

/* ── Picker footer (looks-specific actions) ───────────────────────────── */
.picker-footer {
  flex: 0 0 auto;
  padding: 12px 16px 14px;
  border-top: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(0,0,0,0.20), rgba(0,0,0,0.32));
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.look-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 14px;
  font: 500 12px/1 var(--f-ui);
  letter-spacing: 0.02em;
  border-radius: var(--r-md);
  min-height: 38px;
}
.look-action span[aria-hidden] {
  font-size: 14px;
  line-height: 1;
  opacity: 0.85;
}
.look-action-primary {
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--text);
}
@media (hover: hover) and (pointer: fine) {
  .look-action-primary:hover {
    background: rgba(229, 73, 58, 0.20);
    color: var(--text);
  }
}
.look-action-primary:active { transform: translateY(0.5px); }
.look-footer-secondary {
  display: flex;
  gap: 8px;
}
.look-footer-secondary .look-action { flex: 1 1 0; min-width: 0; }
.look-action[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

/* ── Mobile adaptations ──────────────────────────────────────────────── */
@media (max-width: 700px), (max-height: 500px) and (orientation: landscape) {
  .lookbar-look {
    /* Lives at the top of the mobile lookbar's first row. The mobile
       layout makes it horizontal alongside import + chips — see the
       mobile-specific rules in the lookbar mobile block below. */
    grid-template-columns: 18px 1fr auto;
    padding: 10px 11px;
  }
  .lookbar-look-value { font-size: 13.5px; }
  .picker-look {
    width: 100%;
    max-height: 80vh;
  }
  .look-factory-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: 6px;
  }
  .look-tile {
    padding: 12px 6px 10px;
    min-height: 92px;
  }
  .look-tile-emoji { font-size: 26px; }
  .look-tile-label { font-size: 12px; }
  /* Footer always visible at the bottom of the bottom-sheet; primary CTA
     sized for thumb (44px). */
  .look-action { min-height: 44px; }
}

/* ── DIY caption-overlay toggle ───────────────────────────────────────── */
.chip-label {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 4px;
  cursor: pointer;
  font-size: 12px;
  color: var(--text);
  flex-wrap: wrap;
}
.chip-label .chip-hint {
  flex: 1 1 100%;
  font-size: 10.5px;
  color: var(--text-dim);
  margin-left: 26px;
  letter-spacing: 0.01em;
}

.exif-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.exif-actions .btn-ghost { flex: 1 1 auto; }

/* ── Signature preview ─────────────────────────────────────────────────── */
.signature-preview {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 10px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: rgba(255,255,255,0.02);
}
.signature-preview[hidden] { display: none; }
.signature-preview img {
  width: 56px;
  height: 56px;
  object-fit: contain;
  background:
    linear-gradient(45deg, rgba(255,255,255,0.04) 25%, transparent 25%) 0 0/12px 12px,
    linear-gradient(-45deg, rgba(255,255,255,0.04) 25%, transparent 25%) 0 6px/12px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm, 6px);
  flex: 0 0 auto;
}
.signature-preview .btn-ghost { margin-left: auto; }

/* ── Collage slots (per-partner readouts + drop zones) ───────────────── */
.collage-slots { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.collage-slots:empty { margin-top: 0; }
.collage-readout {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: rgba(255,255,255,0.02);
}
.collage-readout .collage-name {
  font: 500 11px/1 var(--f-mono);
  color: var(--text);
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.collage-readout .btn-ghost { margin-left: auto; }
.collage-drop { padding: 12px; }

/* ── Geometry button (B · Frame entry into the crop+rotate modal) ────── */
.geometry-btn {
  width: 100%;
  text-align: left;
  padding: 10px 14px;
  font: 500 12px/1 var(--f-ui);
  letter-spacing: 0.02em;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
}
.geometry-btn::after {
  content: '↗';
  font-family: var(--f-mono);
  color: var(--text-dim);
  font-weight: 400;
  margin-left: 8px;
  transition: transform 120ms ease;
}
.geometry-btn:hover::after { transform: translate(2px, -2px); color: var(--accent); }
#geometry-readout {
  font: 500 10px/1 var(--f-mono);
  color: var(--text-dim);
  letter-spacing: 0.04em;
}
#geometry-readout em {
  font-style: normal;
  color: var(--accent);
  font-weight: 600;
}

/* ── Custom bg image (frosted-only override) ─────────────────────────── */
.custom-bg-block { margin-top: 10px; display: flex; flex-direction: column; gap: 8px; }
.custom-bg-readout {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: rgba(255,255,255,0.02);
}
.custom-bg-readout[hidden] { display: none; }
.custom-bg-readout img {
  width: 56px;
  height: 56px;
  object-fit: cover;
  border: 1px solid var(--line);
  border-radius: var(--r-sm, 4px);
  flex: 0 0 auto;
}
.custom-bg-readout .custom-bg-name {
  font: 500 11px/1.2 var(--f-mono);
  color: var(--text);
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.custom-bg-readout .btn-ghost { margin-left: auto; }

/* ── Changelog modal ──────────────────────────────────────────────────── */
.changelog-modal {
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--bg-elev);
  color: var(--text);
  padding: 0;
  width: min(640px, 92vw);
  max-height: 86vh;
  /* Suppress the OS-native scrollbar that <dialog>'s default overflow:auto
     produces when the inner content overflows by border-box's 2px. With
     global `* { box-sizing: border-box }` the dialog's max-height: 86vh
     INCLUDES its 1px top + 1px bottom border, but `.changelog-modal-inner`
     also sets max-height: 86vh, so inner's box overflows the dialog's
     content area by 2px and the UA's `dialog { overflow: auto }` shows a
     scrollbar. Body has its own internal scroll; dialog never needs one. */
  overflow: hidden;
  box-shadow: 0 20px 60px rgba(0,0,0,0.6);
}
.changelog-modal::backdrop { background: rgba(0,0,0,0.55); backdrop-filter: blur(4px); }
.changelog-modal-inner {
  display: flex;
  flex-direction: column;
  max-height: 86vh;
  min-height: 0;
  /* Anchor the top/bottom fade overlays. mask-image on the scrollable
     body makes WebKit fall back to the OS default scrollbar, which we
     don't want — the fade lives here on the wrapper instead. */
  position: relative;
}
.changelog-modal-inner::before,
.changelog-modal-inner::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: 22px;
  pointer-events: none;
  z-index: 2;
}
.changelog-modal-inner::before {
  top: 49px; /* head height ~48px */
  background: linear-gradient(to bottom, var(--bg-elev), transparent);
}
.changelog-modal-inner::after {
  bottom: 0;
  background: linear-gradient(to top, var(--bg-elev), transparent);
}
.changelog-modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255,255,255,0.02), transparent);
}
.changelog-modal-head h3 {
  margin: 0;
  font: 500 13px/1 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text);
}
.changelog-modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 22px 26px 28px;
  font: 14px/1.65 var(--f-ui);
  color: var(--text);
}
.changelog-modal-body h1 {
  font: 500 17px/1.3 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  margin: 0 0 8px;
  color: var(--text);
  letter-spacing: -0.005em;
}
.changelog-modal-body h2 {
  font: 500 13px/1.3 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--accent);
  margin: 28px 0 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--accent-line);
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.changelog-modal-body h2:first-of-type { margin-top: 8px; }
.changelog-modal-body h3 {
  font: 500 12px/1.3 var(--f-ui);
  text-transform: none;
  letter-spacing: 0.02em;
  color: var(--text);
  margin: 18px 0 8px;
}
.changelog-modal-body p {
  margin: 0 0 12px;
  color: var(--text-muted);
}
.changelog-modal-body ul {
  margin: 6px 0 16px;
  padding-left: 0;
  list-style: none;
  color: var(--text);
}
.changelog-modal-body li {
  position: relative;
  margin-bottom: 9px;
  padding-left: 18px;
  line-height: 1.65;
}
/* Square accent marker — replaces the default disc dot with something that
   matches the darkroom industrial typography. */
.changelog-modal-body li::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 0.78em;
  width: 5px; height: 5px;
  background: var(--accent);
  opacity: 0.65;
  border-radius: 1px;
}
.changelog-modal-body strong { color: var(--text); font-weight: 600; }
.changelog-modal-body code {
  font: 500 11.5px/1 var(--f-mono);
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  padding: 2px 6px;
  border-radius: var(--r-sm);
  color: var(--text);
  vertical-align: 1px;
  white-space: nowrap;
}
.changelog-modal-body a { color: var(--accent); text-decoration: none; border-bottom: 1px solid var(--accent-line); transition: color 120ms, border-color 120ms; }
.changelog-modal-body a:hover { color: var(--text); border-bottom-color: var(--text); }
.changelog-modal-body hr {
  border: 0;
  border-top: 1px dashed var(--line);
  margin: 22px 0;
}

/* ─────────────────────────────────────────────────────────────────────────
   Crop modal — darkroom enlarger
   Spans nearly the full viewport so the image gets its full breath; head is
   thin + numerical (a step indicator + uppercase mono title), aspect bar
   names the constraint up front, stage centers the image inside a soft
   accent vignette, and a thirds grid sits inside the rect for composition.
   ───────────────────────────────────────────────────────────────────────── */
.crop-modal {
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--bg-elev);
  color: var(--text);
  padding: 0;
  width: min(960px, 94vw);
  /* Use both min and max so the dialog claims a definite height. The inner
     uses height: 100% to make flex distribution well-defined, which is the
     only way the canvas's `max-height: 100%` actually constrains it — without
     a definite ancestor height, max-height % is a no-op and the canvas
     would overflow on tall portrait sources. */
  height: 94vh;
  max-height: 94vh;
  overflow: hidden;
  box-shadow:
    0 24px 80px rgba(0, 0, 0, 0.7),
    0 0 0 1px var(--accent-wash);
}
.crop-modal::backdrop {
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(8px) saturate(0.9);
}
.crop-modal-inner {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

.crop-modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255,255,255,0.02), transparent);
}
.crop-modal-head-l { display: flex; align-items: baseline; gap: 12px; }
.crop-modal-step {
  font: 500 10px/1 var(--f-mono);
  letter-spacing: 0.18em;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  padding: 4px 8px;
  border-radius: 3px;
}
.crop-modal-head h3 {
  margin: 0;
  font: 400 18px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  letter-spacing: 0.01em;
  color: var(--text);
}
/* Round X close button — shared across all dialog modals
   (crop, changelog, export). One source of visual truth for "close". */
.modal-x {
  appearance: none; -webkit-appearance: none;
  border: 1px solid var(--line); background: var(--bg-sunken);
  color: var(--text-muted);
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; cursor: pointer;
  padding: 0;
  transition: color 120ms, border-color 120ms, background 120ms, transform 120ms;
}
.modal-x:hover { color: var(--text); border-color: var(--accent-line); background: var(--accent-wash); }
.modal-x:active { transform: scale(0.92); }
.modal-x:focus-visible { outline: 1px solid var(--accent-line); outline-offset: 2px; }

/* =========================================================
   RAIL CONTEXT MENU — small floating popup for filmstrip actions.
   Triggered by right-click (desktop) or long-press (mobile) on a
   thumbnail. Fixed-positioned at click coordinates, dismissed by
   tap-outside / Esc / clicking a menu item.
   ========================================================= */
.rail-context-menu {
  position: fixed;
  z-index: 100;
  min-width: 160px;
  padding: 4px;
  background: rgba(20, 22, 25, 0.95);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.65);
  animation: rail-menu-in 120ms ease-out;
}
@keyframes rail-menu-in {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}
.rail-context-item {
  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 100%;
  background: transparent;
  border: 0;
  color: var(--text);
  font: 500 13px/1 var(--f-ui);
  padding: 11px 12px;
  text-align: left;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: background 120ms, color 120ms;
}
.rail-context-item:hover,
.rail-context-item:focus-visible {
  background: var(--accent-wash);
  color: var(--accent);
  outline: none;
}
/* The destructive item gets a slightly hotter hover to set it apart from
   the safe "apply" actions above the separator. */
.rail-context-item-danger:hover,
.rail-context-item-danger:focus-visible {
  background: var(--accent);
  color: #fff;
}
.rail-context-sep {
  margin: 4px 6px;
  border: 0;
  border-top: 1px solid var(--line);
}
@media (max-width: 700px), (max-height: 500px) and (orientation: landscape) {
  /* Bigger touch targets on mobile (48pt floor) */
  .rail-context-item { padding: 14px 14px; font-size: 14px; }
}

.crop-aspect-bar {
  display: flex; align-items: center; gap: 14px;
  padding: 10px 18px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-chrome);
}
.crop-aspect-label {
  font: 500 10px/1 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--text-muted);
}
.crop-aspect-seg {
  display: inline-flex;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 2px;
  gap: 0;
}
.crop-aspect-seg button {
  appearance: none; -webkit-appearance: none;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font: 500 11px/1 var(--f-mono);
  letter-spacing: 0.08em;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
  transition: color 140ms, background 140ms, box-shadow 140ms;
}
.crop-aspect-seg button:hover { color: var(--text); }
.crop-aspect-seg button.active {
  color: var(--text);
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent-line);
}
.crop-aspect-sep { color: var(--text-dim); margin: 0 1px; }

/* ── Rotation bar (sibling of aspect bar, lives above the stage) ──── */
.crop-rotate-bar {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 18px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-chrome);
}
.crop-rotate-label {
  font: 500 10px/1 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--text-muted);
  flex-shrink: 0;
}
.crop-rot-btn {
  appearance: none; -webkit-appearance: none;
  border: 1px solid var(--line);
  background: var(--bg-sunken);
  color: var(--text-muted);
  width: 28px; height: 28px;
  border-radius: 999px;
  cursor: pointer;
  font: 400 16px/1 var(--f-mono);
  display: inline-flex; align-items: center; justify-content: center;
  transition: color 120ms, border-color 120ms, background 120ms;
  flex-shrink: 0;
}
.crop-rot-btn:hover { color: var(--text); border-color: var(--accent-line); background: var(--accent-soft); }
.crop-rot-btn:active { transform: scale(0.95); }
.crop-rot-text-btn {
  appearance: none; -webkit-appearance: none;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--text-dim);
  font: 500 10px/1 var(--f-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 6px 10px;
  border-radius: 3px;
  cursor: pointer;
  transition: color 120ms, border-color 120ms;
  flex-shrink: 0;
}
.crop-rot-text-btn:hover { color: var(--text); border-color: var(--line-strong); }

/* The slider — darkroom-themed, accent-tinted progress, refined thumb */
.crop-angle-slider {
  flex: 1 1 auto;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  height: 24px;
  margin: 0;
  cursor: ew-resize;
}
.crop-angle-slider::-webkit-slider-runnable-track {
  height: 2px;
  background: linear-gradient(90deg, var(--line) 0%, var(--line-strong) 50%, var(--line) 100%);
  border-radius: 1px;
}
.crop-angle-slider::-moz-range-track {
  height: 2px;
  background: linear-gradient(90deg, var(--line) 0%, var(--line-strong) 50%, var(--line) 100%);
  border-radius: 1px;
}
.crop-angle-slider::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 12px; height: 12px;
  background: var(--accent);
  border: 2px solid #fff;
  border-radius: 50%;
  margin-top: -5px;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.4), 0 0 6px var(--accent-glow);
  cursor: ew-resize;
}
.crop-angle-slider::-moz-range-thumb {
  width: 12px; height: 12px;
  background: var(--accent);
  border: 2px solid #fff;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.4), 0 0 6px var(--accent-glow);
  cursor: ew-resize;
}
.crop-angle-val {
  font: 500 11px/1 var(--f-mono);
  color: var(--text);
  letter-spacing: 0.04em;
  min-width: 56px;
  text-align: right;
  flex-shrink: 0;
}

.crop-stage {
  position: relative;
  background:
    radial-gradient(ellipse at center, var(--accent-wash), transparent 65%),
    var(--bg-canvas);
  /* flex: 1 1 0 — basis 0 means the stage claims exactly the leftover
     vertical space after head/aspect-bar/foot. With overflow: hidden the
     canvas can't push the stage taller than its allotted box. */
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
  display: grid;
  place-items: center;
  padding: 28px;
}
.crop-stage-glow {
  position: absolute; inset: 0;
  pointer-events: none;
  background:
    radial-gradient(circle at 0% 0%, var(--accent-vignette), transparent 30%),
    radial-gradient(circle at 100% 100%, var(--accent-vignette), transparent 30%);
  z-index: 0;
}
.crop-stage canvas {
  /* The canvas's intrinsic dims are computed in JS to exactly match the
     stage's available content area (with the source bitmap pre-scaled into
     it before drawing). No CSS scaling, no object-fit padding, no
     max-width/height ambiguity — the canvas's CSS box is its visible
     image, 1:1, so the absolute-positioned crop rect always lands on the
     same pixels the user sees. */
  display: block;
  user-select: none;
  -webkit-user-drag: none;
  position: relative;
  z-index: 1;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
}

.crop-rect {
  position: absolute;
  z-index: 2;
  box-sizing: border-box;
  /* Double border evokes a real loupe / cropping easel */
  border: 1.5px solid var(--accent);
  outline: 1px solid rgba(0, 0, 0, 0.4);
  box-shadow:
    0 0 0 9999px rgba(0, 0, 0, 0.5),
    inset 0 0 0 1px rgba(255, 255, 255, 0.08);
  cursor: move;
  touch-action: none;
}

/* Rule-of-thirds grid */
.crop-grid {
  position: absolute; inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 180ms ease;
}
.crop-rect:hover .crop-grid,
.crop-rect.dragging .crop-grid { opacity: 1; }
.crop-grid-v, .crop-grid-h {
  position: absolute;
  background: rgba(255, 255, 255, 0.18);
  box-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.3);
}
.crop-grid-v { top: 0; bottom: 0; width: 1px; }
.crop-grid-h { left: 0; right: 0; height: 1px; }
.crop-grid-v:nth-child(1) { left: 33.333%; }
.crop-grid-v:nth-child(2) { left: 66.666%; }
.crop-grid-h:nth-child(3) { top: 33.333%; }
.crop-grid-h:nth-child(4) { top: 66.666%; }

/* Handles — bigger hit area than visual size, so phones can grab them */
.crop-handle {
  position: absolute;
  width: 10px; height: 10px;
  background: var(--accent);
  border: 2px solid #fff;
  border-radius: 1px;
  box-sizing: border-box;
  touch-action: none;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
/* Invisible padded hit zone */
.crop-handle::before {
  content: '';
  position: absolute;
  inset: -10px;
}
.crop-handle[data-h="nw"] { top: -6px;    left: -6px;    cursor: nwse-resize; }
.crop-handle[data-h="ne"] { top: -6px;    right: -6px;   cursor: nesw-resize; }
.crop-handle[data-h="sw"] { bottom: -6px; left: -6px;    cursor: nesw-resize; }
.crop-handle[data-h="se"] { bottom: -6px; right: -6px;   cursor: nwse-resize; }
.crop-handle-edge { width: 28px; height: 4px; border-radius: 999px; opacity: 0.85; }
.crop-handle[data-h="n"] { top: -4px;    left: 50%; transform: translateX(-50%); cursor: ns-resize; }
.crop-handle[data-h="s"] { bottom: -4px; left: 50%; transform: translateX(-50%); cursor: ns-resize; }
.crop-handle[data-h="w"] { left: -4px;   top: 50%;  transform: translateY(-50%) rotate(90deg); cursor: ew-resize; }
.crop-handle[data-h="e"] { right: -4px;  top: 50%;  transform: translateY(-50%) rotate(90deg); cursor: ew-resize; }
/* Edge handles get a subtle pill, while corners stay sharp dots */
.crop-handle[data-h="w"], .crop-handle[data-h="e"] { width: 4px; height: 28px; transform: translateY(-50%); }

.crop-modal-foot {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 18px;
  border-top: 1px solid var(--line);
  background: linear-gradient(0deg, rgba(255,255,255,0.02), transparent);
}
.crop-readouts {
  display: flex; align-items: baseline; gap: 16px;
  flex: 1 1 auto;
  font-family: var(--f-mono);
}
.crop-readout-pct {
  font: 500 13px/1 var(--f-mono);
  letter-spacing: 0.02em;
  color: var(--text);
  display: inline-flex; align-items: baseline; gap: 2px;
}
.crop-readout-pct em { font-style: normal; font-weight: 600; color: var(--accent); }
.crop-readout-x { color: var(--text-dim); margin: 0 4px; }
.crop-readout-px {
  font: 500 11px/1 var(--f-mono);
  color: var(--text-muted);
  letter-spacing: 0.04em;
}
.crop-readout-ratio {
  font: 500 10px/1 var(--f-mono);
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: 3px;
}
.crop-modal-foot .btn {
  padding: 9px 16px;
  font-size: 12px;
  min-width: 76px;
}
.crop-modal-foot .btn-primary {
  padding: 9px 22px;
  font-weight: 600;
  letter-spacing: 0.04em;
  box-shadow: 0 6px 16px var(--accent-glow);
}

@media (max-width: 600px) {
  .crop-aspect-bar { padding: 10px 12px; flex-wrap: wrap; gap: 10px; }
  .crop-aspect-seg button { padding: 6px 10px; }
  .crop-readouts { gap: 10px; flex-wrap: wrap; }
  .crop-stage { padding: 16px; }
}

/* ── EXIF GPS row (lat/lon + pick-on-map) ─────────────────────────────── */
.exif-gps-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: -4px;
}
.exif-gps-actions .btn-ghost {
  font-size: 12px;
}

/* ── GPS picker modal ─────────────────────────────────────────────────── */
.geo-modal {
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--bg-elev);
  color: var(--text);
  padding: 0;
  width: min(720px, 94vw);
  height: min(640px, 92vh);
  max-height: 92vh;
  overflow: hidden;
  box-shadow:
    0 24px 80px rgba(0, 0, 0, 0.7),
    0 0 0 1px var(--accent-wash);
}
.geo-modal::backdrop {
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(8px) saturate(0.9);
}
.geo-modal-inner {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}
.geo-modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255,255,255,0.02), transparent);
}
.geo-modal-head h3 {
  margin: 0;
  font: 400 18px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  color: var(--text);
}
.geo-stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  background: #0e1014;
}
.geo-map {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* Gaode tiles are light-themed. We let them ship as-is — the bright map
   reads as a clear "interactive island" inside the dark dialog (the same
   pattern Apple Maps and Google Maps use when embedded in dark UIs).
   Earlier we tried `filter: invert(1) hue-rotate(180deg)` to dark-theme
   the map, but Gaode's pastel land-cover plus that filter washed everything
   into a near-uniform dark grey — labels became unreadable. Trust the
   tile's native palette. */
.geo-map.leaflet-container { background: #f1ece2; font-family: var(--f-sans); }
/* Leaflet ships `mix-blend-mode: plus-lighter` on tile imgs as a workaround
   for a Chromium-2016 subpixel-seam bug (crbug 600120). The bug is long-
   gone but the workaround still ships — and on any non-white backdrop it
   makes dark ink (road labels, place names) blend INTO the surrounding
   land color, rendering the map as a uniform wash. We use `normal` so
   tiles paint as opaque PNGs the way they look in any standalone viewer. */
/* Match Leaflet's selector specificity (`.leaflet-container img.leaflet-tile`)
   so this override wins the cascade. */
.geo-map.leaflet-container img.leaflet-tile { mix-blend-mode: normal; }
.geo-map .leaflet-control-attribution {
  background: rgba(255, 255, 255, 0.9) !important;
  color: #555 !important;
  font-size: 10px;
}
.geo-map .leaflet-control-attribution a { color: #1565c0 !important; }

.geo-pin {
  background: transparent;
  border: 0;
}
.geo-pin .geo-pin-dot {
  display: block;
  width: 18px;
  height: 18px;
  margin: 6px 3px;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  background: var(--accent);
  border: 2px solid #fff;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(0,0,0,0.3);
}

.geo-loading,
.geo-error {
  position: absolute;
  inset: 0;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  text-align: center;
  font: 500 13px/1.5 var(--f-mono);
  color: var(--text-dim);
  background: rgba(14, 16, 20, 0.92);
  backdrop-filter: blur(6px);
  z-index: 500;     /* above leaflet panes (≤ 400) */
}
/* Honor the `hidden` HTML attribute. CSS `display: flex` above wins the
   cascade against UA `[hidden] { display: none }` without this — which
   meant the empty error overlay quietly painted on top of the entire map,
   blocking tiles even when the picker was working perfectly. */
.geo-loading[hidden],
.geo-error[hidden] { display: none; }
.geo-error { color: #ff8a7a; }

.geo-modal-foot {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 18px;
  border-top: 1px solid var(--line);
  background: linear-gradient(0deg, rgba(255,255,255,0.02), transparent);
}
.geo-readout {
  flex: 1 1 auto;
  font: 500 12px/1 var(--f-mono);
  color: var(--text-dim);
  letter-spacing: 0.02em;
  text-align: center;
}
.geo-modal-foot .btn { padding: 9px 16px; font-size: 12px; min-width: 76px; }
.geo-modal-foot .btn-primary {
  padding: 9px 22px;
  font-weight: 600;
  box-shadow: 0 6px 16px var(--accent-glow);
}

@media (max-width: 600px) {
  /* The <dialog> UA stylesheet applies `max-width: calc(100% - 6px - 2em)` and
     a similar max-height clamp; without overriding both, `width:100vw` still
     leaves a ~30px gap on each side. Force the dialog to fill the viewport
     edge-to-edge for mobile. */
  .geo-modal {
    width: 100vw; max-width: 100vw;
    height: 100vh; max-height: 100vh;
    border-radius: 0;
    inset: 0;
    margin: 0;
  }
  .geo-modal-foot { flex-wrap: wrap; gap: 8px; }
  .geo-readout { order: 5; flex-basis: 100%; text-align: left; }
}

/* ── Custom aspect modal ──────────────────────────────────────────────── */
.aspect-modal {
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--bg-elev);
  color: var(--text);
  padding: 0;
  width: min(420px, 94vw);
  max-height: 88vh;
  overflow: hidden;
  box-shadow:
    0 24px 80px rgba(0, 0, 0, 0.7),
    0 0 0 1px var(--accent-wash);
}
.aspect-modal::backdrop {
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(8px) saturate(0.9);
}
.aspect-modal-inner {
  display: flex;
  flex-direction: column;
}
.aspect-modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255,255,255,0.02), transparent);
}
.aspect-modal-head h3 {
  margin: 0;
  font: 400 18px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  color: var(--text);
}
.aspect-modal-body {
  padding: 20px 18px 6px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.aspect-input-row {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 14px;
}
.aspect-input-cell {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1 1 0;
  min-width: 0;
}
.aspect-input-label {
  font: 500 11px/1 var(--f-mono);
  letter-spacing: 0.06em;
  color: var(--text-dim);
  text-transform: uppercase;
}
.aspect-input-cell input[type="number"] {
  width: 100%;
  padding: 10px 12px;
  font: 500 18px/1 var(--f-mono);
  background: var(--bg-base);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--text);
  box-sizing: border-box;
  -moz-appearance: textfield;
}
.aspect-input-cell input[type="number"]:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-glow);
}
.aspect-input-cell input[type="number"]::-webkit-outer-spin-button,
.aspect-input-cell input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}
.aspect-input-sep {
  font: 500 22px/1 var(--f-mono);
  color: var(--text-dim);
  padding-bottom: 11px;
}
.aspect-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
}
.aspect-preset {
  padding: 6px 12px;
  min-height: 32px;
  font: 500 12px/1 var(--f-mono);
  background: var(--bg-base);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--text-dim);
  cursor: pointer;
  transition: border-color 120ms, color 120ms, background 120ms;
}
.aspect-preset:hover {
  border-color: var(--accent-line);
  color: var(--text);
}
.aspect-preset .seg-sep { opacity: 0.55; padding: 0 1px; }
.aspect-error {
  font: 500 12px/1.4 var(--f-mono);
  color: #ff8a7a;
  text-align: center;
  min-height: 16px;
}
.aspect-error[hidden] { display: none; }
.aspect-modal-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  padding: 14px 18px;
  border-top: 1px solid var(--line);
  background: linear-gradient(0deg, rgba(255,255,255,0.02), transparent);
}
.aspect-modal-foot .btn { padding: 9px 18px; font-size: 12px; min-width: 76px; }
.aspect-modal-foot .btn-primary {
  padding: 9px 22px;
  font-weight: 600;
  box-shadow: 0 6px 16px var(--accent-glow);
}

@media (max-width: 600px) {
  .aspect-modal {
    width: 100vw; max-width: 100vw;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    inset: auto 0 0 0;
    margin: 0;
  }
  .aspect-preset { min-height: 36px; padding: 8px 12px; }
}

/* ── Service-worker update banner ─────────────────────────────────────── */
.update-banner {
  position: fixed;
  bottom: calc(var(--statusbar-h) + 8px);
  right: 16px;
  z-index: 9999;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px 10px 14px;
  border: 1px solid var(--accent-line);
  border-radius: var(--r-md);
  background: var(--bg-elev);
  color: var(--text);
  font: 500 12px/1.2 var(--f-ui);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  animation: update-banner-in 200ms ease-out;
}
.update-banner[hidden] { display: none; }
@keyframes update-banner-in {
  from { transform: translateY(8px); opacity: 0; }
  to   { transform: translateY(0);   opacity: 1; }
}
.update-banner-btn {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: var(--accent-soft);
  color: var(--accent);
  font: 600 11px/1 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  padding: 6px 10px;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: background 120ms;
}
.update-banner-btn:hover { background: var(--accent-soft); filter: brightness(1.4); }
.update-banner-dismiss {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font: 400 16px/1 var(--f-mono);
  padding: 0 4px;
  cursor: pointer;
}
.update-banner-dismiss:hover { color: var(--text); }

/* ── Batch export progress modal ───────────────────────────────────────── */
.export-modal {
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--bg-elev);
  color: var(--text);
  padding: 0;
  width: min(420px, 90vw);
  box-shadow: 0 20px 60px rgba(0,0,0,0.6);
}
.export-modal::backdrop { background: rgba(0,0,0,0.55); backdrop-filter: blur(4px); }
.export-modal-inner { padding: 22px 22px 18px; }
.export-modal-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }
.export-modal-head h3 { font: 500 14px/1 var(--f-mono); letter-spacing: 0.1em; text-transform: uppercase; color: var(--text); margin: 0; }
.export-modal-body { display: flex; flex-direction: column; gap: 10px; }
.export-stage { font: 500 11px/1 var(--f-mono); letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-muted); }
.export-counter { font: 600 28px/1 var(--f-mono); color: var(--text); }
.export-counter em { font-style: normal; }
.export-counter .export-sep { color: var(--text-dim); margin: 0 6px; font-weight: 400; }
.export-bar { height: 4px; background: var(--bg-sunken); border-radius: 2px; overflow: hidden; box-shadow: inset 0 0 0 1px var(--line); }
.export-bar-fill {
  height: 100%; width: 0;
  background: linear-gradient(90deg, var(--accent), #f05a4b);
  box-shadow: 0 0 8px var(--accent-glow);
  transition: width 120ms ease-out;
}
.export-current { font: 500 11px/1.4 var(--f-mono); color: var(--text-muted); word-break: break-all; min-height: 16px; }
.export-errors { margin: 8px 0 0; padding-left: 18px; font-size: 11px; color: var(--accent); max-height: 120px; overflow-y: auto; }
.export-errors[hidden] { display: none; }

/* ── S3 cloud gallery modal ────────────────────────────────────────────── */
/* S3 config modal — now narrower since it's single-purpose (config only).
   Gallery moved to its own pane (.cloud-gallery-pane) — this dialog is
   the lightweight "right corner" surface for credentials + provider +
   setup guide. */
.s3-modal {
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--bg-elev);
  color: var(--text);
  padding: 0;
  width: min(540px, 96vw);
  max-width: 96vw;
  max-height: 88vh;
  overflow: hidden;
  box-shadow:
    0 24px 80px rgba(0, 0, 0, 0.7),
    0 0 0 1px var(--accent-wash);
}
.s3-modal::backdrop {
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(8px) saturate(0.9);
}
.s3-modal-inner { display: flex; flex-direction: column; max-height: inherit; }
.s3-modal-head {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255,255,255,0.02), transparent);
}
.s3-modal-head h3 {
  margin: 0;
  font: 400 18px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  flex: 1 1 auto;
}
.s3-pane {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  overflow: hidden;
}
.s3-pane-body {
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow-y: auto;
}
.s3-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.s3-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.s3-field-label {
  font: 500 10px/1 var(--f-mono);
  letter-spacing: 0.08em;
  color: var(--text-dim);
  text-transform: uppercase;
}
.s3-field input[type="text"],
.s3-field input[type="password"] {
  width: 100%;
  padding: 9px 11px;
  font: 500 13px/1 var(--f-mono);
  background: var(--bg-base);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--text);
  box-sizing: border-box;
}
.s3-field input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-glow);
}
.s3-field-hidden { display: none; }
.s3-secret-wrap { position: relative; display: flex; align-items: stretch; }
.s3-secret-wrap input { padding-right: 36px; }
.s3-secret-toggle {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  background: transparent; border: 0; color: var(--text-dim);
  cursor: pointer; padding: 4px; line-height: 0;
}
.s3-secret-toggle:hover { color: var(--text); }
.s3-provider-seg {
  display: inline-flex; gap: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  overflow: hidden;
  align-self: flex-start;
}
.s3-provider-seg button {
  background: transparent;
  border: 0;
  color: var(--text-dim);
  font: 500 11px/1 var(--f-mono);
  letter-spacing: 0.06em;
  padding: 8px 12px;
  cursor: pointer;
  transition: background 120ms, color 120ms;
}
.s3-provider-seg button + button { border-left: 1px solid var(--line); }
.s3-provider-seg button[aria-checked="true"] {
  background: var(--bg-base);
  color: var(--text);
}
.s3-error {
  font: 500 12px/1.4 var(--f-mono);
  color: #ff8a7a;
  padding: 6px 0;
}
.s3-error[hidden] { display: none; }
.s3-guide { color: var(--text-dim); }
.s3-guide summary {
  cursor: pointer;
  font: 500 11px/1 var(--f-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 8px 0;
  color: var(--text);
}
.s3-guide[open] summary { padding-bottom: 4px; }
.s3-guide-body {
  font: 400 12.5px/1.55 var(--f-ui);
  color: var(--text);
  background: var(--bg-base);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 12px 14px;
  max-height: 340px;
  overflow-y: auto;
}
.s3-guide-body h4 {
  font: 500 11px/1.3 var(--f-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 14px 0 6px;
}
.s3-guide-body h4:first-child { margin-top: 0; }
.s3-guide-body p { margin: 6px 0; color: var(--text-muted); }
.s3-guide-body ol, .s3-guide-body ul { margin: 4px 0 8px; padding-left: 20px; }
.s3-guide-body li { margin: 3px 0; color: var(--text-muted); }
.s3-guide-body li b { color: var(--text); font-weight: 600; }
.s3-guide-body code {
  font: 500 11px/1 var(--f-mono);
  background: var(--bg-elev);
  border: 1px solid var(--line);
  padding: 1px 5px;
  border-radius: 3px;
  color: var(--text);
}
.s3-guide-body pre {
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 10px 12px;
  font: 500 11px/1.55 var(--f-mono);
  color: var(--text);
  white-space: pre;
  overflow-x: auto;
  margin: 6px 0 10px;
}
.s3-guide-body a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.s3-guide-body a:hover { filter: brightness(1.2); }
.s3-pane-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  padding: 12px 18px;
  border-top: 1px solid var(--line);
  background: linear-gradient(0deg, rgba(255,255,255,0.02), transparent);
  flex: 0 0 auto;
}
.s3-pane-foot .btn { padding: 9px 16px; font-size: 12px; min-width: 76px; }
.s3-pane-foot .btn-primary { padding: 9px 22px; font-weight: 600; }

@media (max-width: 700px) {
  .s3-modal {
    width: 100vw; max-width: 100vw;
    max-height: 92vh;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    inset: auto 0 0 0;
    margin: 0;
  }
  .s3-grid { grid-template-columns: 1fr; }
  .s3-pane-foot { flex-wrap: wrap; }
}

/* ── Cloud gallery pane ──────────────────────────────────────────────────
   Occupies the same grid column as #canvas-pane (mid column of .workspace).
   Toggled visible by the "From Cloud" hero in the lookbar; canvas-pane is
   hidden simultaneously so the user reads "I'm in browse-from-cloud mode"
   without losing the rest of the chrome (lookbar / rail stay put). */
.cloud-gallery-pane {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--bg-base);
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
  min-height: 0;
  overflow: hidden;
}
.cloud-gallery-pane[hidden] { display: none; }
.gallery-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255,255,255,0.02), transparent);
  flex: 0 0 auto;
}
.gallery-back {
  appearance: none;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--text-dim);
  padding: 7px 11px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: 500 11px/1 var(--f-mono);
  letter-spacing: 0.06em;
  cursor: pointer;
  transition: border-color 120ms, color 120ms, background 120ms;
}
.gallery-back:hover { border-color: var(--accent-line); color: var(--text); }
.gallery-title {
  flex: 1 1 auto;
  margin: 0;
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  font: 400 18px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  color: var(--text);
}
.gallery-title-mark {
  align-self: center;
  color: var(--accent);
  display: inline-flex;
}
.gallery-path {
  font: 500 11px/1 var(--f-mono);
  color: var(--text-dim);
  letter-spacing: 0.04em;
}
.gallery-path:empty { display: none; }
.gallery-config-btn {
  appearance: none;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 50%;
  width: 32px; height: 32px;
  color: var(--text-dim);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: border-color 120ms, color 120ms;
}
.gallery-config-btn:hover { border-color: var(--accent-line); color: var(--accent); }

.gallery-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 18px;
  border-bottom: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.18);
  flex-wrap: wrap;
  flex: 0 0 auto;
}
.gallery-toolbar .btn { padding: 7px 12px; font-size: 11px; min-width: 0; }
.gallery-toolbar-spacer { flex: 1 1 auto; }
.gallery-status {
  font: 500 11px/1 var(--f-mono);
  color: var(--text-dim);
  min-width: 0;
}
.gallery-count {
  font: 500 11px/1 var(--f-mono);
  color: var(--text-dim);
  white-space: nowrap;
}

.gallery-grid {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 18px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 14px;
  align-content: start;
}
.gallery-cell {
  position: relative;
  appearance: none;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg-elev);
  cursor: pointer;
  padding: 0;
  overflow: hidden;
  aspect-ratio: 4 / 5;
  display: block;
  transition: border-color 120ms, transform 100ms, box-shadow 120ms;
}
.gallery-cell:hover {
  border-color: var(--line-strong);
  transform: translateY(-1px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}
.gallery-cell img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  background: linear-gradient(135deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02));
}
.gallery-cell-name {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 8px 10px;
  font: 500 11px/1.25 var(--f-mono);
  background: linear-gradient(0deg, rgba(0,0,0,0.82) 0%, rgba(0,0,0,0.4) 60%, transparent);
  color: #fff;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
  text-align: left;
}
/* Checkbox-style affordance lives in the top-right; click on the badge
   toggles selection without entering the lightbox. The cell body itself
   opens the lightbox — keeps the two interactions visually distinct. */
.gallery-cell-checkbox {
  position: absolute;
  top: 8px; right: 8px;
  width: 22px; height: 22px;
  border-radius: 6px;
  background: rgba(0,0,0,0.55);
  border: 1.5px solid rgba(255,255,255,0.65);
  display: flex;
  align-items: center;
  justify-content: center;
  color: transparent;
  font: 700 14px/1 var(--f-mono);
  cursor: pointer;
  transition: background 120ms, border-color 120ms, color 120ms;
  z-index: 2;
}
.gallery-cell-checkbox:hover {
  border-color: var(--accent);
  background: rgba(0,0,0,0.75);
}
.gallery-cell[aria-selected="true"] {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 2px var(--accent), 0 8px 24px var(--accent-glow);
}
.gallery-cell[aria-selected="true"] .gallery-cell-checkbox {
  background: var(--accent);
  border-color: var(--accent);
  color: #0a0a0a;
}
.gallery-empty {
  grid-column: 1 / -1;
  text-align: center;
  padding: 48px 12px;
  font: 500 12px/1.5 var(--f-mono);
  color: var(--text-dim);
}

/* ── Lightbox ────────────────────────────────────────────────────────────
   Fills the whole gallery pane with a dimmed backdrop + center-staged
   image + prev/next nav + bottom action bar. Body-level keyboard handler
   (Esc / arrows) is wired in app.js while the lightbox is open. */
.gallery-lightbox {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: grid;
  grid-template-columns: 56px 1fr 56px;
  grid-template-rows: 1fr 60px;
  background: rgba(8, 9, 11, 0.92);
  backdrop-filter: blur(4px);
}
.gallery-lightbox[hidden] { display: none; }
.lightbox-stage {
  grid-column: 2;
  grid-row: 1;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}
.lightbox-stage img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
  background: var(--bg-base);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  box-shadow: 0 16px 48px rgba(0,0,0,0.55);
}
.lightbox-loading {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  padding: 6px 12px;
  background: rgba(0,0,0,0.6);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font: 500 11px/1 var(--f-mono);
  color: var(--text-dim);
  letter-spacing: 0.06em;
}
.lightbox-nav {
  appearance: none;
  background: rgba(0,0,0,0.4);
  border: 1px solid var(--line);
  border-radius: 50%;
  width: 40px; height: 40px;
  color: var(--text);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  align-self: center;
  justify-self: center;
  transition: background 120ms, border-color 120ms, transform 100ms;
}
.lightbox-nav:hover { background: var(--accent); border-color: var(--accent); color: #0a0a0a; }
.lightbox-nav:active { transform: scale(0.94); }
.lightbox-nav-prev { grid-column: 1; grid-row: 1; }
.lightbox-nav-next { grid-column: 3; grid-row: 1; }
.lightbox-close {
  appearance: none;
  position: absolute;
  top: 12px; right: 12px;
  z-index: 1;
  width: 36px; height: 36px;
  background: rgba(0,0,0,0.55);
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--text);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.lightbox-close:hover { background: var(--accent); border-color: var(--accent); color: #0a0a0a; }
.lightbox-footer {
  grid-column: 1 / -1;
  grid-row: 2;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border-top: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.4);
}
.lightbox-meta {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.lightbox-name {
  font: 500 13px/1.25 var(--f-mono);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lightbox-size {
  font: 500 10px/1 var(--f-mono);
  color: var(--text-dim);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.lightbox-footer .btn { padding: 9px 16px; font-size: 12px; }
.lightbox-footer .btn[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #0a0a0a;
}

@media (max-width: 700px) {
  .gallery-head { flex-wrap: wrap; padding: 10px 14px; }
  .gallery-title { font-size: 16px; gap: 6px; }
  .gallery-path { display: none; }
  .gallery-toolbar { padding: 10px 14px; }
  .gallery-grid { padding: 12px; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 8px; }
  .gallery-lightbox { grid-template-columns: 44px 1fr 44px; grid-template-rows: 1fr auto; }
  .lightbox-stage { padding: 10px; }
  .lightbox-nav { width: 32px; height: 32px; }
}

.grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 10px;
}
.grid-2 label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font: 500 10px/1 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
}
.grid-2 input { font-size: 12px; padding: 7px 9px; }
.grid-2 label.span-2 { grid-column: 1 / -1; }

/* =========================================================
   BUTTONS
   ========================================================= */

.actions { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--text);
  font: 500 12px/1 var(--f-ui);
  cursor: pointer;
  transition: border-color 120ms, background 120ms, transform 80ms;
}
.btn kbd { background: transparent; border-color: var(--line); color: var(--text-dim); }
.btn:hover:not(:disabled) { border-color: var(--line-strong); background: var(--bg-chrome); }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: 0.55; cursor: not-allowed; }

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #0a0a0a;
  font-weight: 600;
}
.btn-primary kbd {
  background: rgba(0,0,0,0.15);
  border-color: rgba(0,0,0,0.2);
  color: rgba(0,0,0,0.7);
  border-bottom-width: 1px;
}
.btn-primary:hover:not(:disabled) {
  background: #f05a4b;
  border-color: #f05a4b;
}

.btn-ghost {
  display: inline-flex;
  margin-top: 10px;
  padding: 7px 12px;
  font: 500 10px/1 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: color 120ms, border-color 120ms;
}
.btn-ghost:hover { color: var(--text); border-color: var(--line-strong); }

/* btn-secondary — same visual size as .btn / .btn-primary, transparent
   bg + outlined. Used as the non-primary partner in modal foots so
   Reset / Cancel / Apply form one consistent button row. */
.btn-secondary {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--text-muted);
  justify-content: center;
}
.btn-secondary:hover:not(:disabled) {
  background: var(--bg-elev);
  border-color: var(--line-strong);
  color: var(--text);
}

/* =========================================================
   CENTER — CANVAS
   ========================================================= */

/* The class selector below has higher specificity than the UA's `[hidden]`
   rule (0,0,1,0 vs 0,0,0,1), so the `hidden` attribute alone won't hide
   the pane (see CLAUDE.md "Pitfalls discovered during build" — `hidden`
   attribute UA stylesheet trap). Explicit rule required so `<section
   class="pane-canvas" hidden>` actually disappears when the gallery pane
   takes over the middle column. */
.pane-canvas[hidden] { display: none; }
.pane-canvas {
  background:
    radial-gradient(ellipse 60% 50% at 50% 30%, rgba(255,200,150,0.025) 0%, transparent 65%),
    radial-gradient(ellipse 40% 30% at 50% 100%, rgba(229,73,58,0.035) 0%, transparent 60%),
    var(--bg-canvas);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  /* overflow: hidden so a pinch-zoomed canvas (max 4x) clips to the pane
     instead of spilling into the lookbar / filmstrip. The vignette + frame
     badge already live inside .canvas-stage, which is 100%×100%, so they
     stay visible. */
  overflow: hidden;
  /* iOS Safari: prevent the browser's default pinch-zoom of the page
     surface — we handle pinch on the canvas ourselves. Without this, a
     pinch on the canvas would zoom the entire page including chrome. */
  touch-action: pan-x pan-y;
}

.canvas-vignette {
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse at center, transparent 30%, rgba(0,0,0,0.6) 100%),
    linear-gradient(180deg, rgba(255,255,255,0.012), transparent 140px);
  pointer-events: none;
}

.canvas-stage {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: 28px;
}

#preview-canvas {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  box-shadow:
    0 60px 120px -30px rgba(0,0,0,0.8),
    0 0 0 1px rgba(255,255,255,0.04);
  background: var(--bg-canvas);
  min-height: 240px;
  min-width: 160px;
  display: block;
  animation: prev-in 280ms cubic-bezier(0.22, 1, 0.36, 1);
  /* Pinch-zoom transform applies inline via JS — origin stays at canvas
     center so a 1-finger zoom feels symmetric (the centroid translate
     handles off-center pinches). */
  transform-origin: 50% 50%;
}
.pane-canvas[data-zoomed="true"] #preview-canvas {
  /* Lift shadow + cursor hint when zoomed so pan affordance is obvious. */
  cursor: grab;
}
#preview-canvas.empty { display: none; }

@keyframes prev-in {
  from { opacity: 0; transform: scale(0.985); }
  to   { opacity: 1; transform: scale(1); }
}

/* Loading pulse — a single red dot, shutter-style */
.preview-loading {
  position: absolute;
  top: 20px; right: 20px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  background: rgba(20, 22, 25, 0.72);
  border: 1px solid var(--line);
  border-radius: 3px;
  font: 500 10px/1 var(--f-mono);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  backdrop-filter: blur(8px) saturate(1.1);
  pointer-events: none;
  z-index: 2;
}
.preview-loading[hidden] { display: none; }
.pulse-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent-glow);
  animation: pulse 900ms ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { opacity: 0.35; transform: scale(0.85); }
  50%      { opacity: 1;    transform: scale(1); }
}

/* Empty state — aperture diagram + display-font title + mono-caps sub.
   Reads as "this tool is for photos" before any photo is even loaded. */
.empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  color: var(--text-dim);
  pointer-events: none;
  padding: 40px;
}
.empty[hidden] { display: none; }
.empty-aperture {
  color: var(--accent);
  opacity: 0.55;
  filter: drop-shadow(0 0 18px var(--accent-vignette));
  margin-bottom: 8px;
  animation: empty-iris 7s ease-in-out infinite;
}
@keyframes empty-iris {
  0%, 100% { transform: rotate(0deg) scale(1); opacity: 0.5; }
  50%      { transform: rotate(22.5deg) scale(1.04); opacity: 0.72; }
}
.empty-title {
  font: 400 22px/1.1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  font-style: italic;
  letter-spacing: -0.012em;
  color: var(--text-muted);
  margin: 0;
}
.empty-sub {
  font: 400 12px/1.5 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  font-style: italic;
  color: var(--text-dim);
  letter-spacing: -0.005em;
  margin: 0;
}

/* Frame badge — small mono-caps label hovering top-left of the canvas.
   Shows current frame style + template + (when rotated) rotation degree.
   Helps users orient when they switch photos with different cfgs. */
.canvas-frame-badge {
  position: absolute;
  top: 22px; left: 22px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 7px 12px 7px 9px;
  background: rgba(20, 22, 25, 0.72);
  border: 1px solid var(--line);
  border-radius: 999px;
  font: 500 10px/1 var(--f-mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-muted);
  backdrop-filter: blur(10px) saturate(1.2);
  -webkit-backdrop-filter: blur(10px) saturate(1.2);
  pointer-events: none;
  animation: badge-in 280ms cubic-bezier(0.22, 1, 0.36, 1);
}
.canvas-frame-badge[hidden] { display: none; }
/* Live frame swatch dot — same color rules as the lookchip-icon, just
   smaller. Reuse the same data-frame attribute from the markup. */
.canvas-badge-swatch {
  width: 12px; height: 9px;
  border-radius: 2px;
  border: 1px solid var(--line-strong);
  display: inline-block;
  flex: 0 0 auto;
  position: relative;
  overflow: hidden;
}
.canvas-frame-badge em {
  font: 400 12px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  font-style: italic;
  color: var(--text);
  letter-spacing: -0.005em;
  text-transform: none;
}
.canvas-badge-sep { color: var(--text-dim); margin: 0 2px; opacity: 0.7; }
.canvas-badge-rot { color: var(--text-dim); }
.canvas-badge-rot em { color: var(--text); font-style: normal; font-weight: 500; font-size: 11px; }
.canvas-badge-rot[hidden] { display: none; }
@keyframes badge-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Rotation flash — transient confirmation when user clicks ↶/↷.
   Center-overlay, fades in + scales + fades out in ~700ms. Pointer-events
   off so the flash never blocks subsequent interaction. */
.rotation-flash {
  position: absolute;
  top: 50%; left: 50%;
  z-index: 5;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  background: var(--accent);
  color: #fff;
  border-radius: 999px;
  font: 600 14px/1 var(--f-mono);
  letter-spacing: 0.06em;
  box-shadow: 0 8px 24px var(--accent-glow), 0 0 0 1px rgba(0,0,0,0.3);
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.9);
  transition: opacity 180ms ease-out, transform 240ms cubic-bezier(0.22, 1, 0.36, 1);
}
.rotation-flash.flash-show {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
.rotation-flash-arrow {
  font: 400 22px/1 var(--f-mono);
  display: inline-block;
}

/* Drop hint — shown on dragover */
.canvas-drop-hint {
  position: absolute;
  inset: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--accent-line);
  border-radius: var(--r-lg);
  background: var(--accent-wash);
  color: var(--accent);
  pointer-events: none;
  opacity: 0;
  transition: opacity 120ms;
  z-index: 3;
}
.canvas-drop-hint.visible { opacity: 1; }
.drop-hint-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.drop-hint-box p {
  margin: 0;
  font: 500 10px/1 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.18em;
}

/* =========================================================
   RIGHT — THUMBNAIL RAIL (contact sheet)
   ========================================================= */

.pane-rail {
  background: var(--bg-chrome);
  border-left: 1px solid var(--line);
  display: flex;
  flex-direction: column;
}

.rail-head {
  padding: 16px 14px 12px;
  border-bottom: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.rail-head-label {
  font: 400 14px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  font-style: italic;
  color: var(--text);
  letter-spacing: -0.005em;
}
.rail-head-count {
  font: 400 18px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  color: var(--accent);
  letter-spacing: -0.01em;
  font-feature-settings: 'tnum' 1;
}

.rail-items {
  flex: 1;
  overflow-y: auto;
  list-style: none;
  padding: 10px 10px 20px;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  outline: none;
}
/* Rail uses the standard scroll skin. */

.rail-item {
  position: relative;
  cursor: pointer;
  overflow: hidden;
  border-radius: var(--r-sm);
  background: var(--bg-sunken);
  border: 1px solid transparent;
  transition: border-color 120ms, transform 120ms;
}
.rail-item:hover { border-color: var(--line-strong); }
.rail-item:hover img { opacity: 1; }

.rail-item img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  opacity: 0.75;
  transition: opacity 120ms;
}

.rail-item .rail-idx {
  position: absolute;
  top: 4px; left: 6px;
  font: 500 9px/1 var(--f-mono);
  color: #fff;
  opacity: 0.9;
  text-shadow: 0 1px 3px rgba(0,0,0,0.7);
  letter-spacing: 0.04em;
}

.rail-item.active {
  border-color: var(--accent-line);
}
.rail-item.active img { opacity: 1; }
.rail-item.active::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--accent);
  box-shadow: 0 0 12px var(--accent-glow);
}

.rail-item.keyfocus {
  outline: 1px solid var(--line-strong);
  outline-offset: 2px;
}

.rail-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 32px 8px;
  text-align: center;
  color: var(--text-dim);
  font: 400 12px/1.5 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  font-style: italic;
  letter-spacing: -0.005em;
}
.rail-empty::before {
  /* Faint dashed-circle aperture mark — reads as "waiting for film". */
  content: '';
  width: 28px; height: 28px;
  border: 1px dashed var(--line-strong);
  border-radius: 50%;
  position: relative;
}

.rail-foot {
  padding: 10px 14px 12px;
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 8px;
  font: 500 9px/1 var(--f-mono);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-dim);
}
.rail-foot kbd { font-size: 9px; min-width: 14px; height: 15px; padding: 0 3px; }

/* =========================================================
   STATUS BAR
   ========================================================= */

.statusbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 18px;
  background: var(--bg-chrome);
  border-top: 1px solid var(--line);
  font: 500 11px/1 var(--f-mono);
  color: var(--text-muted);
}

.status-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--text-dim);
  transition: background 200ms;
}
.statusbar.busy .status-dot {
  background: var(--accent);
  animation: pulse 900ms ease-in-out infinite;
}

.status-text {
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.14em;
}
[lang="zh-CN"] .status-text {
  text-transform: none;
  letter-spacing: 0.04em;
}
.statusbar.busy .status-text { color: var(--text); }

/* Error state — visible enough that a user can't miss it.
   - Top border switches to accent red (was --line)
   - Subtle red wash on the left edge so the bar reads as "alarm strip"
   - Status dot becomes a small red square + pulses
   - Text turns brighter red (#ff7a6c) for readability against bg-chrome
   - Initial 600ms flash on entering err state, then settles */
.statusbar.err {
  border-top: 1px solid var(--accent);
  background:
    linear-gradient(90deg, var(--accent-soft), transparent 280px),
    var(--bg-chrome);
  animation: status-err-flash 600ms ease-out;
}
@keyframes status-err-flash {
  0%   { background-color: rgba(229, 73, 58, 0.32); }
  100% { background-color: var(--bg-chrome); }
}
.statusbar.err .status-dot {
  background: #ff7a6c;
  border-radius: 1px;
  width: 7px; height: 7px;
  box-shadow: 0 0 8px var(--accent-glow);
  animation: pulse 900ms ease-in-out infinite;
}
.statusbar.err .status-text {
  color: #ff7a6c;
  font-weight: 600;
}

.status-spacer { flex: 1; }

.status-hint {
  color: var(--text-dim);
  letter-spacing: 0.08em;
  font-size: 10px;
}

/* =========================================================
   LOOK BAR — vertical left rail, the always-visible hot path.
   Sits between the topbar and the statusbar, anchored to the
   workspace's left edge. Holds Import at top, 4 chips middle,
   Export group at bottom. Aesthetic stays consistent with the
   rest of the editorial-darkroom palette: glassy dark backdrop,
   accent halo that tracks the focused chip, Fraunces italic for
   value text.
   ========================================================= */
.lookbar {
  position: relative;
  z-index: 6;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 12px 14px;
  width: var(--lookbar-w);
  height: 100%;
  background:
    radial-gradient(ellipse 120px 320px at 100% var(--active-chip-y, 50%), rgba(229,73,58,0.06) 0%, transparent 70%),
    linear-gradient(to right, rgba(20,22,25,0.92), rgba(15,17,20,0.96));
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  border-right: 1px solid var(--line-strong);
  flex-shrink: 0;
  /* --active-chip-y is set in JS to the y-center of the open chip
     within the lookbar. The accent halo on the right edge tracks
     focus down the rail. */
  --active-chip-y: 50%;
  --active-chip-h: 0px;
}
.lookbar::before {
  /* Right-edge hairline accent — the rail's "lit edge". Tracks the
     active chip vertically; when no picker is open, fades to a soft
     glow at the chip-group's vertical center. */
  content: '';
  position: absolute;
  right: -1px;
  top: var(--active-chip-y, 50%);
  width: 1px;
  height: max(48px, var(--active-chip-h, 48px));
  transform: translateY(-50%);
  background: linear-gradient(to bottom, transparent, var(--accent-line), transparent);
  filter: blur(0.4px);
  pointer-events: none;
  transition: top 280ms cubic-bezier(0.2,0.8,0.2,1),
              height 280ms cubic-bezier(0.2,0.8,0.2,1),
              opacity 200ms ease;
  opacity: 0.55;
}
.lookbar[data-picker-open="true"]::before { opacity: 1; }

/* Import group — pairs the local "Import" hero with the cloud
   "From Cloud" hero. Same visual level (rule 13: surface-native UI —
   loading a photo locally and pulling it from a shared S3 are both
   equally first-class source actions). Compact 6px gap reads as a
   coupled "where does this photo come from?" choice. */
.lookbar-import-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex-shrink: 0;
}

/* Import — primary action at the top of the rail. Subtle ghost
   button with import icon + label; clicking triggers the hidden
   <input type="file"> via the toolbar shell. */
.lookbar-import {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 11px 12px;
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-md);
  color: var(--text);
  font: 500 12px/1 var(--f-ui);
  cursor: pointer;
  transition: all 160ms ease;
  flex-shrink: 0;
}
.lookbar-import:hover {
  background: rgba(229, 73, 58, 0.18);
  transform: translateY(-1px);
}
.lookbar-import:active { transform: translateY(0.5px); }
.lookbar-import-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  flex: 0 0 auto;
  box-shadow: 0 4px 10px -3px rgba(229,73,58,0.5);
}
.lookbar-import-label {
  letter-spacing: 0.04em;
}

/* "From Cloud" — outline variant. Same footprint as Import, but no
   filled background and a hollow glyph circle, so the two buttons read
   as a sibling pair (parallel sources) rather than primary + secondary. */
.lookbar-import-cloud {
  background: transparent;
  border-color: var(--line-strong);
}
.lookbar-import-cloud:hover {
  background: var(--accent-soft);
  border-color: var(--accent-line);
}
.lookbar-import-cloud .lookbar-import-glyph {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--accent-line);
  box-shadow: none;
}
.lookbar-import-cloud:hover .lookbar-import-glyph {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}

.chip-group {
  display: flex;
  flex-direction: column;
  background: rgba(0,0,0,0.24);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.02);
}
.chip-group .lookchip + .lookchip {
  border-top: 1px solid var(--line);
}

.lookchip {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 11px 9px 10px;
  background: transparent;
  border: 0;
  cursor: pointer;
  position: relative;
  transition: background 160ms ease;
  min-width: 0;
  width: 100%;
  text-align: left;
}
.lookchip:hover { background: rgba(255,255,255,0.04); }
.lookchip:active { background: rgba(255,255,255,0.06); }
.lookchip[data-open="true"] {
  background: linear-gradient(to right, var(--accent-soft), transparent 90%);
}
.lookchip[data-open="true"]::after {
  /* Vertical accent rule on the right edge — replaces the bottom underline
     from the horizontal layout. Tracks the active chip selection. */
  content: '';
  position: absolute;
  right: -1px;
  top: 8px;
  bottom: 8px;
  width: 2px;
  background: var(--accent);
  border-radius: 1px;
  box-shadow: 0 0 6px var(--accent-glow);
}
.lookchip[data-open="true"] .lookchip-caret { transform: rotate(-90deg); }
.lookchip-caret {
  /* Caret rotates from "down ▾" → "left ◂" when chip is open, signalling
     the picker pops out to the right. */
  transform: rotate(-90deg);     /* default: pointing right (collapsed) */
  transition: transform 220ms cubic-bezier(0.2,0.8,0.2,1), color 160ms ease;
  margin-left: auto;
  width: 9px; height: 9px;
  color: var(--text-dim);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.lookchip[data-open="true"] .lookchip-caret { color: var(--accent); }
.lookchip-icon {
  width: 24px;
  height: 18px;
  border-radius: 3px;
  border: 1px solid var(--line-strong);
  flex: 0 0 auto;
  position: relative;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.18);
}
.lookchip-icon[data-frame="frosted-noir"]   { background: linear-gradient(135deg, #0e1422 0%, #1a1418 50%, #0a0a10 100%); }
.lookchip-icon[data-frame="gallery-white"]  { background: #f4f3ee; border-color: rgba(0,0,0,0.18); }
.lookchip-icon[data-frame="instax"]         { background: #fffdf6; border-color: rgba(0,0,0,0.18); }
.lookchip-icon[data-frame="film-35"]        { background: #100c08; }
.lookchip-icon[data-frame="film-35"]::before {
  content: '';
  position: absolute;
  top: 1px; left: 1px; right: 1px;
  height: 1.5px;
  background-image: linear-gradient(to right, #ebdcb8 0px, #ebdcb8 1.5px, transparent 1.5px, transparent 3.2px);
  background-size: 3.2px 100%;
}
.lookchip-icon[data-frame="film-mf"]        {
  background: radial-gradient(circle at 30% 30%, #ecdcae 0%, #e0c98a 70%, #c8a86a 100%);
  border-color: rgba(95, 65, 38, 0.32);
}
.lookchip-icon[data-frame="slide-mount"]    {
  background: #e6dac0;
  box-shadow: inset 0 0 0 2px #3a1822, inset 0 0 0 3px rgba(0,0,0,0.12);
  border-color: rgba(58, 24, 34, 0.55);
}
.lookchip-icon[data-frame="torn"] {
  background: #f4ecd6;
  clip-path: polygon(0 8%, 14% 4%, 28% 10%, 44% 2%, 60% 8%, 76% 4%, 88% 10%, 100% 6%, 100% 92%, 88% 96%, 72% 88%, 56% 96%, 40% 90%, 24% 96%, 12% 88%, 0 92%);
}
.lookchip-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: flex-start;
  min-width: 0;
}
.lookchip-key {
  font: 500 8.5px/1 var(--f-mono);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.lookchip-value {
  font: 400 14px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  letter-spacing: -0.01em;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Cap to fit the 168px rail comfortably, accounting for the 24px
     swatch + 9px caret + ~24px combined padding. */
  max-width: 96px;
  transition: opacity 140ms ease;
}
.lookchip-value.is-changing {
  animation: lookchip-flash 360ms cubic-bezier(0.2,0.8,0.2,1);
}
@keyframes lookchip-flash {
  0% { opacity: 0.35; transform: translateY(-1.5px); }
  100% { opacity: 1; transform: translateY(0); }
}
.lookchip[data-open="true"] .lookchip-value {
  color: var(--accent);
}

/* Workshop entry — sits between the hot-path chips and the export
   group. Visually distinct from the chips (dashed outline = "more
   surfaces behind here") so users discover it without it competing
   with the primary picker chips. The hint line lists the tabs the
   drawer holds, doubling as a discovery affordance. */
.lookbar-workshop {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 12px;
  background: transparent;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  color: var(--text);
  cursor: pointer;
  text-align: left;
  transition: all 200ms cubic-bezier(0.2,0.8,0.2,1);
}
.lookbar-workshop:hover {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  border-style: solid;
  transform: translateY(-1px);
}
.lookbar-workshop:active { transform: translateY(0.5px); }
.lookbar-workshop-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  color: var(--text-muted);
  flex: 0 0 auto;
  transition: all 200ms ease;
}
.lookbar-workshop:hover .lookbar-workshop-glyph {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent);
}
.lookbar-workshop-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1;
  min-width: 0;
}
.lookbar-workshop-title {
  font: 400 13.5px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  font-style: italic;
  letter-spacing: -0.01em;
  color: var(--text);
}
.lookbar-workshop-hint {
  font: 500 8.5px/1.2 var(--f-mono);
  letter-spacing: 0.14em;
  color: var(--text-dim);
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lookbar-workshop-caret {
  font-size: 11px;
  color: var(--text-dim);
  flex: 0 0 auto;
  transition: transform 200ms cubic-bezier(0.2,0.8,0.2,1), color 160ms ease;
}
.lookbar-workshop:hover .lookbar-workshop-caret {
  color: var(--accent);
  transform: translate(2px, -2px);
}

.lookbar-spacer { flex: 1; min-height: 12px; }

.export-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.export-btn {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 0 14px;
  width: 100%;
  height: 44px;
  background: linear-gradient(to bottom, #f25744, var(--accent));
  border: 0;
  border-radius: var(--r-md);
  font: 600 13px/1 var(--f-ui);
  letter-spacing: 0.005em;
  color: #fff;
  cursor: pointer;
  position: relative;
  box-shadow:
    0 4px 14px -4px rgba(229,73,58,0.5),
    inset 0 1px 0 rgba(255,255,255,0.18);
  transition: all 120ms ease;
}
.export-btn:hover:not(:disabled) {
  background: linear-gradient(to bottom, #f7634f, #ec4f3f);
  box-shadow:
    0 6px 18px -4px rgba(229,73,58,0.65),
    inset 0 1px 0 rgba(255,255,255,0.22);
}
.export-btn:active:not(:disabled) { transform: translateY(0.5px); }
.export-btn:disabled {
  background: var(--bg-elev);
  color: var(--text-dim);
  box-shadow: inset 0 0 0 1px var(--line);
  cursor: not-allowed;
}
.export-btn kbd {
  font: 500 10px/1 var(--f-mono);
  padding: 3px 5px;
  background: rgba(0,0,0,0.18);
  border-radius: 3px;
  letter-spacing: 0.04em;
  margin-left: 2px;
}
.export-btn:disabled kbd { display: none; }
.export-btn-secondary {
  height: 32px;
  background: var(--bg-elev);
  color: var(--text-muted);
  box-shadow: inset 0 0 0 1px var(--line);
  font-size: 11px;
  letter-spacing: 0.06em;
}
.export-btn-secondary:hover:not(:disabled) {
  background: var(--bg-chrome);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}
.export-batch-label {
  font: 500 11px/1 var(--f-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* =========================================================
   PICKER OVERLAY — anchored above lookbar chips. Backdrop dims the
   workspace; the active picker fades in with a small upward translate.
   Pickers are mutually exclusive.
   ========================================================= */
.picker-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
}
.picker-overlay[data-open="true"] { pointer-events: auto; }
.picker-overlay::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(5,6,8,0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 220ms ease;
}
.picker-overlay[data-open="true"]::before { opacity: 1; }

.picker {
  position: absolute;
  /* Pops out to the right of the left rail, vertically anchored to the
     clicked chip's center. JS sets --picker-top to chip.y-center; the
     picker is then translated up to half its height to actually align
     center-to-center. */
  left: calc(var(--lookbar-w) + 14px);
  top: var(--picker-top, 50%);
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  box-shadow:
    0 30px 80px -20px rgba(0,0,0,0.6),
    0 0 0 1px rgba(255,255,255,0.03);
  padding: 18px;
  width: min(640px, calc(100vw - var(--lookbar-w) - var(--rail-w) - 40px));
  max-height: calc(100vh - var(--topbar-h) - var(--statusbar-h) - 32px);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  opacity: 0;
  transform: translate(-8px, -50%);
  transition: opacity 220ms ease, transform 220ms cubic-bezier(0.2,0.8,0.2,1);
  pointer-events: none;
}
.picker-overlay[data-open="true"] .picker:not([hidden]) {
  opacity: 1;
  transform: translate(0, -50%);
  pointer-events: auto;
}
.picker[hidden] { display: none; }
.picker-narrow { width: min(420px, calc(100vw - var(--lookbar-w) - var(--rail-w) - 40px)); }

.picker-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 16px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.picker-title {
  margin: 0;
  font: 400 19px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  font-style: italic;
  letter-spacing: -0.01em;
  color: var(--text);
}
.picker-subtitle {
  font: 500 10px/1 var(--f-mono);
  letter-spacing: 0.22em;
  color: var(--text-dim);
  text-transform: uppercase;
}
.picker-subtitle em {
  font-style: normal;
  color: var(--accent);
  font-weight: 600;
  margin-right: 4px;
}

.picker-body {
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  padding-right: 4px;
}

.picker-section {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.picker-section-head {
  font: 500 9.5px/1 var(--f-mono);
  letter-spacing: 0.18em;
  color: var(--text-muted);
  text-transform: uppercase;
  margin-bottom: 10px;
}

.picker-family-head {
  font: 500 9.5px/1 var(--f-mono);
  letter-spacing: 0.22em;
  color: var(--text-muted);
  text-transform: uppercase;
  margin-bottom: 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line-subtle);
  display: flex;
  gap: 10px;
  align-items: baseline;
}
.picker-family-head::before {
  content: '';
  width: 5px;
  height: 5px;
  background: var(--accent);
  border-radius: 50%;
  box-shadow: 0 0 6px var(--accent-glow);
  align-self: center;
}

.frame-family-group { margin-bottom: 14px; }
.frame-family-group:last-child { margin-bottom: 0; }
.frame-tile-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}

/* Each tile = a button rendered as a visual mini-preview of that frame. */
.frame-tile {
  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 100%;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  transition: transform 220ms cubic-bezier(0.2,0.8,0.2,1);
  position: relative;
  text-align: left;
  /* Stagger reveal: when the picker opens we cascade tiles in via a
     CSS animation, indexed by the tile's --i custom property which JS
     sets at generation time. */
  opacity: 0;
  transform: translateY(6px);
  animation: tile-rise 380ms cubic-bezier(0.2,0.8,0.2,1) forwards;
  animation-delay: calc(var(--i, 0) * 22ms + 60ms);
}
.frame-tile:hover {
  transform: translateY(-3px);
  animation: none;          /* hover overrides the entry animation */
  opacity: 1;
}
@keyframes tile-rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* Re-trigger entry stagger every time the picker opens. */
.picker[hidden] .frame-tile,
.picker[hidden] .tmpl-tile {
  animation: none;
  opacity: 0;
  transform: translateY(6px);
}
.frame-tile.active .frame-tile-preview {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.frame-tile.active::after {
  content: '✓';
  position: absolute;
  top: -6px;
  right: -6px;
  width: 18px;
  height: 18px;
  background: var(--accent);
  color: white;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 10px;
  font-weight: 600;
  box-shadow: 0 4px 10px -2px rgba(229,73,58,0.4);
}
.frame-tile-preview {
  display: block;            /* span defaults to inline; aspect-ratio + 100% width need block */
  width: 100%;
  aspect-ratio: 9 / 16;
  border-radius: var(--r-md);
  position: relative;
  overflow: hidden;
  background: var(--bg-canvas);
  border: 1px solid var(--line);
}
.frame-tile-name { display: block; }
.frame-tile-meta { display: block; }
.frame-tile-photo {
  display: block;
  position: absolute;
  inset: 14% 12%;
  border-radius: 2px;
  background:
    linear-gradient(to right, transparent 35%, rgba(0,0,0,0.5) 38%, rgba(0,0,0,0.7) 50%, rgba(0,0,0,0.5) 62%, transparent 65%),
    linear-gradient(135deg, #1d3854 0%, #29466e 30%, #1a2b3e 60%, #08111e 100%);
}
.tmpl-tile-name { display: block; }
.tmpl-tile-preview { display: block; }
.lookchip-text { display: flex; }    /* override default span inline */
/* Per-frame tile palettes */
.frame-tile[data-frame="frosted-noir"] .frame-tile-preview { background: linear-gradient(135deg, #0e1422, #1a1418, #0a0a10); }
.frame-tile[data-frame="gallery-white"] .frame-tile-preview { background: #f4f3ee; }
.frame-tile[data-frame="gallery-white"] .frame-tile-photo {
  inset: 16% 14%;
  outline: 0.5px solid rgba(0,0,0,0.3);
  outline-offset: 4px;
}
.frame-tile[data-frame="instax"] .frame-tile-preview  { background: #fffdf6; }
.frame-tile[data-frame="instax"] .frame-tile-photo    { inset: 8% 10% 38% 10%; border-radius: 1px; }
.frame-tile[data-frame="torn"] .frame-tile-preview    { background: #f4ecd6; }
.frame-tile[data-frame="torn"] .frame-tile-photo {
  inset: 14% 12%;
  clip-path: polygon(0 6%, 12% 2%, 28% 8%, 44% 0, 60% 8%, 78% 2%, 92% 8%, 100% 4%, 100% 92%, 88% 96%, 72% 88%, 56% 96%, 40% 90%, 22% 96%, 8% 88%, 0 92%);
}
.frame-tile[data-frame="film-35"] .frame-tile-preview { background: #100c08; }
.frame-tile[data-frame="film-35"] .frame-tile-preview::before,
.frame-tile[data-frame="film-35"] .frame-tile-preview::after {
  content: '';
  position: absolute;
  left: 8%;
  right: 8%;
  height: 4px;
  background-image: linear-gradient(to right, #ebdcb8 0px, #ebdcb8 3px, transparent 3px, transparent 8px);
  background-size: 8px 100%;
}
.frame-tile[data-frame="film-35"] .frame-tile-preview::before { top: 8%; }
.frame-tile[data-frame="film-35"] .frame-tile-preview::after  { bottom: 8%; }
.frame-tile[data-frame="film-35"] .frame-tile-photo { inset: 22% 12%; border-radius: 1px; }
.frame-tile[data-frame="film-mf"] .frame-tile-preview {
  background: radial-gradient(circle at 35% 30%, #ecdcae 0%, #dfc887 70%, #c5a363 100%);
}
.frame-tile[data-frame="film-mf"] .frame-tile-photo   {
  inset: 16% 14%;
  outline: 0.5px solid rgba(48, 32, 18, 0.35);
  outline-offset: 3px;
  filter: sepia(0.45) brightness(0.92) contrast(0.9);
}
.frame-tile[data-frame="slide-mount"] .frame-tile-preview {
  background: #e6dac0;
  box-shadow: inset 0 0 0 6px #3a1822;
}
.frame-tile[data-frame="slide-mount"] .frame-tile-photo {
  inset: 28% 18%;
  outline: 0.5px solid rgba(20, 12, 6, 0.55);
  box-shadow: inset 1px 1px 2px rgba(0,0,0,0.35);
}

.frame-tile-name {
  display: block;
  margin-top: 8px;
  font: 400 13px/1.25 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  font-style: italic;
  color: var(--text);
  letter-spacing: -0.012em;
}
.frame-tile.active .frame-tile-name { color: var(--accent); }

/* TEMPLATE picker tiles — typography previews instead of frame thumbs */
.tmpl-family-group { margin-bottom: 14px; }
.tmpl-family-group:last-child { margin-bottom: 0; }
.tmpl-tile-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}
.tmpl-tile {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  padding: 12px 14px 14px;
  background: var(--bg-canvas);
  transition: border-color 160ms ease, transform 220ms cubic-bezier(0.2,0.8,0.2,1), background 160ms ease;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 92px;
  text-align: left;
  color: inherit;
  /* Same stagger reveal as frame tiles. */
  opacity: 0;
  transform: translateY(6px);
  animation: tile-rise 380ms cubic-bezier(0.2,0.8,0.2,1) forwards;
  animation-delay: calc(var(--i, 0) * 22ms + 60ms);
}
.tmpl-tile:hover {
  border-color: var(--line-strong);
  transform: translateY(-2px);
  animation: none;
  opacity: 1;
}
.tmpl-tile.active {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.tmpl-tile-name {
  display: block;
  font: 400 13px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  font-style: italic;
  letter-spacing: -0.012em;
  color: var(--text);
}
.tmpl-tile.active .tmpl-tile-name { color: var(--accent); }
.tmpl-tile-preview {
  flex: 1;
  font: 500 11px/1.4 var(--f-mono);
  color: var(--text-muted);
  letter-spacing: 0.04em;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

/* ASPECT picker — narrow popover with a chip row */
.seg-aspect {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  background: transparent;
  border: 0;
  padding: 0;
}
.seg-aspect button {
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg-canvas);
  color: var(--text-muted);
  font: 500 12px/1 var(--f-mono);
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: all 120ms ease;
}
.seg-aspect button:hover {
  border-color: var(--line-strong);
  color: var(--text);
}
.seg-aspect button.active {
  border-color: var(--accent);
  color: var(--text);
  background: var(--accent-soft);
}
#aspect-custom-btn {
  border-style: dashed;
  font-style: italic;
  color: var(--text-dim);
}

/* QUALITY picker rows */
.quality-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.quality-row {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 12px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  cursor: pointer;
  transition: background 120ms ease;
  text-align: left;
  color: inherit;
}
.quality-row:hover { background: var(--line); }
.quality-row.active { background: var(--accent-soft); }
.quality-row.active .quality-name { color: var(--accent); }
.quality-name {
  font: 500 13px/1 var(--f-display);
  font-feature-settings: 'ss01';
  letter-spacing: -0.005em;
  color: var(--text);
}
.quality-spec {
  justify-self: end;
  font: 500 10px/1 var(--f-mono);
  letter-spacing: 0.04em;
  color: var(--text-dim);
}
.quality-help {
  grid-column: 1 / -1;
  font: 400 11px/1.4 var(--f-ui);
  color: var(--text-muted);
}

/* FORMAT seg inside the same picker */
.seg-format {
  display: flex;
  background: var(--bg-canvas);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  width: fit-content;
}
.seg-format button {
  background: transparent;
  border: 0;
  padding: 8px 18px;
  font: 500 11px/1 var(--f-mono);
  letter-spacing: 0.06em;
  color: var(--text-muted);
  cursor: pointer;
  transition: all 120ms ease;
}
.seg-format button + button { border-left: 1px solid var(--line); }
.seg-format button:hover { color: var(--text); }
.seg-format button.active { background: var(--accent-soft); color: var(--accent); }

/* =========================================================
   COMMAND PALETTE — ⌘K / Ctrl+K. Power-user fast path: type to
   filter all frames / templates / aspects / actions, ↑↓ Enter
   to pick. Centered modal with subtle glassy lift.
   ========================================================= */
.cmdk-trigger {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 10px 5px 9px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  font: 400 11px/1 var(--f-ui);
  cursor: pointer;
  transition: all 120ms ease;
}
.cmdk-trigger:hover {
  color: var(--text);
  border-color: var(--accent-line);
  background: var(--accent-soft);
}
.cmdk-trigger kbd {
  font: 500 10px/1 var(--f-mono);
  padding: 3px 5px;
  border-radius: 3px;
  background: var(--bg-canvas);
  border: 1px solid var(--line);
  color: var(--text-dim);
  letter-spacing: 0.04em;
}

.cmdk-overlay {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  align-items: start;
  justify-items: center;
  padding-top: 14vh;
  background: rgba(0,0,0,0.6);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms ease;
}
.cmdk-overlay[data-open="true"] {
  opacity: 1;
  pointer-events: auto;
}
.cmdk {
  width: min(620px, calc(100vw - 32px));
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  box-shadow:
    0 40px 100px -20px rgba(0,0,0,0.7),
    0 0 0 1px rgba(255,255,255,0.04);
  overflow: hidden;
  transform: translateY(-12px) scale(0.98);
  transition: transform 220ms cubic-bezier(0.2,0.8,0.2,1);
}
.cmdk-overlay[data-open="true"] .cmdk { transform: translateY(0) scale(1); }
.cmdk-input-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--line);
}
.cmdk-glyph {
  color: var(--accent);
  font: 500 17px/1 var(--f-display);
}
.cmdk-input {
  flex: 1;
  background: none;
  border: none;
  font: 500 16px/1.2 var(--f-display);
  font-feature-settings: 'ss01';
  letter-spacing: -0.005em;
  color: var(--text);
  outline: none;
  padding: 0;
}
.cmdk-input::placeholder { color: var(--text-dim); }
.cmdk-esc {
  font: 500 10px/1 var(--f-mono);
  padding: 4px 6px;
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  color: var(--text-dim);
  letter-spacing: 0.04em;
}
.cmdk-results {
  max-height: 50vh;
  overflow-y: auto;
  padding: 8px;
}
.cmdk-empty {
  padding: 24px;
  font: 400 12px/1.5 var(--f-ui);
  color: var(--text-muted);
  text-align: center;
}
.cmdk-group-title {
  padding: 10px 10px 4px;
  font: 500 9.5px/1 var(--f-mono);
  letter-spacing: 0.18em;
  color: var(--text-dim);
  text-transform: uppercase;
}
.cmdk-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  cursor: pointer;
  transition: background 100ms ease;
}
.cmdk-row.active,
.cmdk-row:hover { background: var(--accent-soft); }
.cmdk-row .ico {
  width: 22px;
  height: 16px;
  flex: 0 0 auto;
  border-radius: 2px;
  border: 1px solid var(--line-strong);
  position: relative;
  overflow: hidden;
}
.cmdk-row-name {
  font: 500 13px/1 var(--f-ui);
  color: var(--text);
  flex: 1;
}
.cmdk-row-meta {
  font: 500 10px/1 var(--f-mono);
  letter-spacing: 0.04em;
  color: var(--text-dim);
  text-transform: uppercase;
}
.cmdk-row-shortcut {
  display: inline-flex;
  gap: 3px;
}
.cmdk-row-shortcut kbd {
  font: 500 9.5px/1 var(--f-mono);
  padding: 3px 5px;
  background: var(--bg-canvas);
  border-radius: 3px;
  border: 1px solid var(--line);
  color: var(--text-muted);
}

/* =========================================================
   WORKSHOP DRAWER — slides in from the right. 5 互斥 tabs.
   ========================================================= */
.workshop-overlay {
  position: fixed;
  inset: 0;
  z-index: 70;
  pointer-events: none;
}
.workshop-overlay[data-open="true"] { pointer-events: auto; }
.workshop-overlay::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(5,6,8,0.5);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  opacity: 0;
  transition: opacity 240ms ease;
}
.workshop-overlay[data-open="true"]::before { opacity: 1; }
.workshop {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(440px, calc(100vw - 32px));
  background: var(--bg-elev);
  border-left: 1px solid var(--line-strong);
  box-shadow: -30px 0 80px -20px rgba(0,0,0,0.6);
  transform: translateX(100%);
  transition: transform 320ms cubic-bezier(0.2,0.8,0.2,1);
  display: flex;
  flex-direction: column;
}
.workshop-overlay[data-open="true"] .workshop { transform: translateX(0); }
.workshop-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 20px 12px;
  border-bottom: 1px solid var(--line);
}
.workshop-title {
  margin: 0;
  font: 400 22px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  font-style: italic;
  letter-spacing: -0.012em;
  color: var(--text);
}
.workshop-tabs {
  display: flex;
  gap: 2px;
  padding: 12px 16px 0;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.workshop-tab {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
  padding: 10px 14px;
  font: 500 11px/1 var(--f-mono);
  letter-spacing: 0.12em;
  color: var(--text-muted);
  text-transform: uppercase;
  cursor: pointer;
  position: relative;
  transition: color 120ms ease;
}
.workshop-tab:hover { color: var(--text); }
.workshop-tab.active { color: var(--text); }
.workshop-tab.active::after {
  content: '';
  position: absolute;
  bottom: -1px;
  left: 8px;
  right: 8px;
  height: 2px;
  background: var(--accent);
}
.workshop-body {
  flex: 1;
  overflow-y: auto;
  padding: 18px 20px;
}
.ws-tab { display: none; }
.ws-tab[data-active="true"] { display: block; }

.ws-section { margin-bottom: 20px; }
.ws-section:last-child { margin-bottom: 0; }
.ws-section-head {
  font: 500 9.5px/1 var(--f-mono);
  letter-spacing: 0.18em;
  color: var(--text-muted);
  text-transform: uppercase;
  margin-bottom: 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--line);
}
.ws-section-hint {
  font: 400 11px/1.45 var(--f-ui);
  color: var(--text-dim);
  margin-top: 8px;
}
.btn-wide { width: 100%; }

/* =========================================================
   TOPBAR — small additions for cmdk trigger / topbar-pill-strong.
   ========================================================= */
.topbar-pill-strong {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--text);
}
.topbar-pill-strong:hover {
  background: rgba(229,73,58,0.18);
}

/* =========================================================
   STATUSBAR — narrow strip below the lookbar with a status hint
   line. Picks up whatever the existing status helper writes.
   ========================================================= */
.statusbar {
  display: flex;
  align-items: center;
  gap: 14px;
  height: var(--statusbar-h);
  padding: 0 20px;
  border-top: 1px solid var(--line);
  background: var(--bg-base);
  flex-shrink: 0;
}
.statusbar::before {
  /* Tiny accent dot — reads as "darkroom is online". */
  content: '';
  width: 4px; height: 4px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 5px var(--accent-glow);
  margin-right: 2px;
  flex: 0 0 auto;
}
.statusbar-spacer { flex: 1; }
.statusbar-hints {
  font: 400 11px/1 var(--f-ui);
  color: var(--text-dim);
  letter-spacing: 0;
}
.statusbar-hints kbd {
  font: 500 9.5px/1 var(--f-mono);
  padding: 2px 5px;
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--text-muted);
  margin: 0 2px;
  background: var(--bg-elev);
}
#status {
  font: 400 12px/1 var(--f-display);
  font-variation-settings: "opsz" 144, "SOFT" 100;
  font-style: italic;
  color: var(--text-muted);
  letter-spacing: -0.005em;
}

/* Body grid: 3 rows · topbar / 1fr / statusbar. The workspace itself is
   a 3-column grid (lookbar / canvas / filmstrip) — see .workspace below. */
body {
  display: grid;
  grid-template-rows: var(--topbar-h) 1fr var(--statusbar-h);
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

/* =========================================================
   RESPONSIVE — narrow desktop / tablet
   ========================================================= */

@media (max-width: 1200px) {
  :root { --rail-w: 64px; }
  .grid-2 { grid-template-columns: 1fr; }
  .grid-2 label.span-2 { grid-column: auto; }
  .frame-tile-grid { grid-template-columns: repeat(3, 1fr); }
}

/* Mobile-only / desktop-only utility toggles (used by HTML twin spans) */
.file-drop-hint-mobile { display: none; }

/* =========================================================
   RESPONSIVE — phone (≤700px portrait OR landscape ≤500px tall):
   canvas-on-top + bottom sheet.
   The lookbar grows into a real bottom sheet; the picker takes over
   full-width; the workshop drawer becomes a near-full-screen sheet.

   Breakpoint chosen to EXCLUDE iPad-class tablets (smallest iPad mini
   is 744px wide in portrait): tablets stay on the desktop layout but
   inherit touch-friendly sizing via the (pointer: coarse) block below.
   The OR-clause catches landscape phones (e.g. iPhone 14 at 844×390),
   which still need bottom-sheet UX because thumb-zone is at the bottom
   regardless of orientation.
   ========================================================= */
@media (max-width: 700px), (max-height: 500px) and (orientation: landscape) {
  :root {
    --topbar-h: 44px;
    --statusbar-h: 0px;
    /* 2-row lookbar: [import + chips] above [export + ZIP]. Total content
       104px (8 padding + 44 row + 8 gap + 44 row + 0) plus safe-area-inset
       for home-indicator devices. ~40px shorter than the v0.17 first-cut
       3-row design — the canvas gets that height back. */
    /* 3-row lookbar (was 2 in 0.17): [LOOK strip] above [import + chips]
       above [export + ZIP]. LOOK strip is the first-class entry to the
       presets library — 44px tall same as the other rows. Total content
       148px (8 padding + 44 + 8 gap + 44 + 8 gap + 44 + 0) plus safe-area-
       inset for home-indicator devices. The +44px cost over 0.17 is the
       price for putting Looks one tap from the photo (vs three taps via
       the workshop · lib tab), which is the whole point of this change. */
    --lookbar-h: calc(148px + max(8px, env(safe-area-inset-bottom)));
  }
  body {
    /* On phones, the lookbar reverts to a bottom sheet so the workspace
       stays canvas-on-top + sheet-below — keeps native iOS / Android
       feel. The lookbar moves OUT of the workspace flow into a fixed-
       bottom layer, freeing the workspace to fill row 2. */
    grid-template-rows: var(--topbar-h) 1fr var(--lookbar-h);
  }

  .topbar {
    padding: 0 14px;
    padding-left: max(14px, env(safe-area-inset-left));
    padding-right: max(14px, env(safe-area-inset-right));
  }
  .brand { gap: 8px; }
  .brand-name { font-size: 16px; }
  .brand-sub,
  .topbar-meta .meta-item,
  .cmdk-trigger { display: none; }
  .topbar-meta { gap: 6px; }
  .topbar-meta .topbar-pill { width: 36px; height: 32px; padding: 0; justify-content: center; }
  /* Replacement workshop entry on mobile — see comment on the desktop
     base rule. inline-flex so it slots cleanly between the lang switcher
     and the changelog pill. */
  .workshop-pill-mobile { display: inline-flex; }

  .workspace {
    grid-template-columns: 1fr;
  }
  .pane-rail { display: none; }

  .canvas-area, .pane-canvas { padding: 12px; }
  .file-drop-hint-desktop { display: none; }
  .file-drop-hint-mobile  { display: inline; }
  .empty-sub-desktop { display: none; }

  /* Lookbar becomes a bottom sheet: 2×2 grid — [import + chips] over
     [Export + ZIP]. Grid (vs flex column) gives import + chip-group a
     shared row without DOM restructure: import sits column 1 row 1,
     chip-group spans column 2 row 1, export-group spans both columns
     row 2. */
  .lookbar {
    /* Lift out of the workspace's left grid cell — fixed-bottom layer. */
    position: fixed;
    left: 0; right: 0; bottom: 0;
    width: 100vw;
    height: var(--lookbar-h);
    display: grid;
    grid-template-columns: 44px 1fr;
    /* Row 1: LOOK strip (full width) · Row 2: import + chips · Row 3: export */
    grid-template-rows: 44px 44px 44px;
    gap: 8px;
    padding: 8px 14px;
    padding-bottom: max(8px, env(safe-area-inset-bottom));
    border-right: none;
    border-top: 1px solid var(--line-strong);
    border-radius: 16px 16px 0 0;
    background: linear-gradient(to bottom, rgba(20,22,25,0.95), rgba(15,17,20,0.99));
    z-index: 8;
  }
  /* LOOK strip on mobile: full-width row 1, sits visually like a "header"
     above the 4-chip row. Reads as a separate decision unit — same role
     as the desktop dashed-border block but compressed horizontally. */
  .lookbar-look {
    grid-row: 1; grid-column: 1 / -1;
    margin-top: 0;
    align-self: stretch;
    height: 44px;
    padding: 0 14px;
    grid-template-columns: 18px 1fr auto;
  }
  .lookbar-look-text { flex-direction: row; align-items: baseline; gap: 8px; }
  .lookbar-look-key { font-size: 9px; }
  .lookbar::before {
    /* Drag-handle pill at top of sheet — desktop accent rail rule
       gets re-purposed visually. */
    top: 6px;
    right: auto;
    left: 50%;
    width: 36px;
    height: 4px;
    transform: translateX(-50%);
    background: var(--line-strong);
    border-radius: 2px;
    filter: none;
  }
  /* Import: 44×44 square accent button on the left of row 1, sharing
     the row with the chip-group. Label hidden — the upload-arrow glyph
     in an accent-red square reads as "add photo" without text on every
     mobile photo app pattern. */
  /* On mobile the lookbar is a 3-row grid; the import-group wrapper
     needs `display: contents` so the Import button still slots into
     row 2 col 1 via its own grid placement. The cloud-import button
     hides on mobile — the topbar ☁ pill handles cloud entry where
     the 3-row grid has no spare column. */
  .lookbar-import-group { display: contents; }
  .lookbar-import {
    grid-row: 2; grid-column: 1;
    align-self: stretch;
    width: 44px; height: 44px;
    padding: 0;
    margin-top: 0;
    justify-content: center;
    flex-shrink: 0;
  }
  .lookbar-import-cloud { display: none; }
  .lookbar-import-label { display: none; }
  .lookbar-import-glyph { width: 22px; height: 22px; }
  /* Workshop entry — hidden in lookbar on mobile, exposed as a topbar
     pill instead. Mobile lookbar is focused on hot-path frame/template/
     aspect/quality + import + export; workshop (crop / EXIF / sign /
     tile / lib) is global discovery, lives next to changelog/lang. */
  .lookbar-workshop { display: none; }
  .chip-group {
    grid-row: 2; grid-column: 2;
    flex-direction: row;
    width: 100%;
    flex-shrink: 0;
  }
  .chip-group .lookchip + .lookchip { border-top: 0; border-left: 1px solid var(--line); }
  /* Lookchip: bumped vertical padding from 6px → 11px so the cell hits
     the 44px touch-target floor (was ~38px, below WCAG). Horizontal stays
     8px so 4 chips still fit on a 360px screen. */
  .lookchip { flex: 1; padding: 11px 8px; min-width: 0; min-height: 44px; }
  .lookchip-icon { width: 18px; height: 12px; }
  .lookchip-key { font-size: 7.5px; letter-spacing: 0.14em; }
  .lookchip-value { font-size: 12px; max-width: none; }
  .lookchip-caret { display: none; }
  .lookchip[data-open="true"]::after {
    /* Underline rule replaces the right-edge accent for horizontal mobile. */
    top: auto;
    bottom: -1px;
    left: 8px; right: 8px;
    width: auto; height: 2px;
  }

  .lookbar-spacer { display: none; }
  .export-group {
    grid-row: 3; grid-column: 1 / -1;
    flex-direction: row;
    gap: 6px;
    flex-shrink: 0;
  }
  /* Export / ZIP balance — was 5:1 (huge red Export, tiny ZIP pill at the
     right edge), now 2:1. ZIP gains visual weight as a real secondary
     action without competing with the primary Export. */
  .export-btn { flex: 2; height: 44px; padding: 0 14px; }
  .export-btn kbd { display: none; }
  .export-btn-secondary { flex: 1; padding: 0 12px; height: 44px; }

  /* Picker becomes a bottom sheet — overrides the desktop right-anchor
     rules. */
  .picker, .picker-narrow {
    top: auto;
    left: 0;
    right: 0;
    bottom: var(--lookbar-h);
    width: 100vw;
    max-width: 100vw;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    border-bottom: none;
    max-height: 70vh;
    transform: translate(0, 8px);
    /* Make room for the drag-handle pill above the title. */
    padding-top: 14px;
  }
  .picker-overlay[data-open="true"] .picker:not([hidden]) {
    transform: translate(0, 0);
  }
  /* Drag-handle pill at top of every bottom sheet — mirrors the lookbar
     drag-handle visual + the JS swipe-dismiss listener uses the top 36px
     as the gesture catch zone. */
  .picker::before,
  .workshop::before {
    content: '';
    position: absolute;
    top: 8px; left: 50%;
    transform: translateX(-50%);
    width: 36px; height: 4px;
    border-radius: 999px;
    background: var(--line-strong);
    pointer-events: none;
  }
  .frame-tile-grid { grid-template-columns: repeat(3, 1fr); }
  .tmpl-tile-grid  { grid-template-columns: 1fr 1fr; }

  /* Workshop drawer takes ~92% of screen on phones */
  .workshop {
    width: 100vw;
    max-width: 100vw;
    border-radius: 16px 16px 0 0;
    top: auto;
    bottom: 0;
    height: 92vh;
    height: 92dvh;
    transform: translateY(100%);
    border-left: none;
    border-top: 1px solid var(--line-strong);
    /* Top padding makes room for the drag-handle pill (::before above) so
       it sits cleanly above the workshop-head row instead of overlapping
       the title. */
    padding-top: 14px;
  }
  .workshop-overlay[data-open="true"] .workshop { transform: translateY(0); }
  .workshop-tabs { padding: 10px 14px 0; gap: 0; overflow-x: auto; }
  .workshop-tab { padding: 10px 12px; font-size: 10px; flex-shrink: 0; }

  /* Cmdk overlay starts higher on phone (less wasted top space) */
  .cmdk-overlay { padding-top: 8vh; }
  .cmdk { width: calc(100vw - 16px); }

  /* Touch-friendly form sizing — 16px to dodge iOS auto-zoom */
  input[type="text"],
  input[type="number"],
  select,
  textarea {
    font-size: 16px;
    padding: 10px 12px;
    min-height: 44px;
  }
  .grid-2 input, .grid-2 select { font-size: 15px; padding: 9px 10px; min-height: 42px; }

  input[type="range"] { height: 32px; }
  input[type="range"]::-webkit-slider-thumb {
    width: 22px; height: 22px;
    margin-top: -10px;
  }
  input[type="range"]::-moz-range-thumb { width: 22px; height: 22px; }
  .seg button { padding: 12px 4px; font-size: 12px; min-height: 44px; }
  .chips { gap: 8px; }
  /* Chip (radio/checkbox row) bumped to 44px floor; the input grows to
     18px and the label hit area expands so the whole row is tappable. */
  .chip { padding: 12px 14px 12px 12px; font-size: 13px; min-height: 44px; gap: 10px; }
  .chip input { width: 18px; height: 18px; }
  .chip input:checked { box-shadow: 0 0 0 3px var(--accent-soft); }

  .btn { padding: 13px 14px; font-size: 13px; min-height: 44px; }
  .btn-ghost { padding: 10px 14px; font-size: 11px; min-height: 38px; }

  /* No statusbar on phone — handled by --statusbar-h: 0 above + this hide */
  .statusbar { display: none; }

  /* ── Changelog modal: bottom sheet on phone ─────────────────────── */
  .changelog-modal {
    inset: auto 0 0 0;
    margin: 0;
    width: 100vw;
    max-width: 100vw;
    max-height: 92vh;
    max-height: 92dvh;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    border-left: none;
    border-right: none;
    border-bottom: none;
    box-shadow: 0 -16px 48px rgba(0,0,0,0.65);
    animation: changelog-sheet-up 320ms cubic-bezier(0.32, 0.72, 0, 1);
  }
  .changelog-modal-inner {
    max-height: 92vh;
    max-height: 92dvh;
    padding-bottom: env(safe-area-inset-bottom);
  }
  .changelog-modal-head {
    position: relative;
    flex-direction: row-reverse;
    padding-top: 22px;
  }
  .changelog-modal-head::before {
    content: '';
    position: absolute;
    top: 8px; left: 50%;
    transform: translateX(-50%);
    width: 36px; height: 4px;
    border-radius: 999px;
    background: var(--line-strong);
  }
  .changelog-modal-inner::before { top: 50px; }
  @keyframes changelog-sheet-up {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
  }

  .export-modal {
    width: 100vw;
    max-width: 100vw;
    height: 100vh;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    border: none;
    box-shadow: none;
  }
  .export-modal-inner {
    padding: 24px 20px 20px;
    padding-top: max(24px, env(safe-area-inset-top));
    padding-bottom: max(20px, env(safe-area-inset-bottom));
    height: 100%;
    display: flex;
    flex-direction: column;
  }
  .export-modal-body { flex: 1; }
  .export-counter { font-size: 36px; }
  .export-modal-head .modal-x { width: 36px; height: 36px; }
}

/* =========================================================
   SMALL-PHONE refinement (≤480px portrait): tighten spacing
   so the bottom sheet's 4-chip row, topbar pills, and lookbar
   import button all fit without horizontal overflow on
   3.5"–4.7" devices (iPhone SE, Pixel 7a, smaller Androids).
   ========================================================= */
@media (max-width: 480px) {
  /* Tighten only horizontal spacing + font scale on small phones; the
     lookbar's 2-row grid from the portrait mobile block above carries
     through unchanged. Chip-group has less horizontal space (390px −
     44 import − 28 padding − 8 gap = 310px / 4 chips = ~77px each), so
     icon + text get tighter. */
  .canvas-area, .pane-canvas { padding: 8px; }
  .lookbar { padding: 8px 10px; padding-bottom: max(8px, env(safe-area-inset-bottom)); }
  .lookbar-import-glyph { width: 18px; height: 18px; }
  .lookchip { padding: 10px 6px; gap: 6px; }
  .lookchip-icon { width: 16px; height: 11px; }
  .lookchip-key { font-size: 7px; letter-spacing: 0.12em; }
  .lookchip-value { font-size: 11.5px; }
  .topbar-meta .topbar-pill { width: 32px; height: 30px; }
  .brand-name { font-size: 14px; }
  .export-btn { font-size: 12px; }
  .export-btn-secondary { padding: 0 10px; font-size: 12px; }
  .frame-tile-grid { grid-template-columns: 1fr 1fr; }
  .picker-title { font-size: 18px; }
}

/* Landscape-phone refinement: a 112px lookbar still eats ~28% of a
   390px-tall landscape viewport. Compress button heights from 44 to
   38px so the bar stays usable but shrinks to ~92px. Touch targets
   slightly under the 44px floor on the lookbar specifically —
   acceptable trade because (a) landscape is transient on phones,
   (b) two-handed grip improves finger accuracy, (c) the alternative
   is having no canvas at all. iPad's landscape height is always ≥768,
   well above 500, so iPad doesn't match. */
@media (max-height: 500px) and (orientation: landscape) {
  :root { --lookbar-h: calc(92px + max(4px, env(safe-area-inset-bottom))); }
  .lookbar {
    grid-template-columns: 38px 1fr;
    grid-template-rows: 38px 38px;
    padding: 4px 14px;
    gap: 4px;
  }
  .lookbar::before { top: 3px; }
  .lookbar-import-group { display: contents; }
  .lookbar-import-cloud { display: none; }
  .lookbar-import { width: 38px; height: 38px; }
  .lookbar-import-glyph { width: 18px; height: 18px; }
  .lookchip { padding: 6px 6px; min-height: 38px; }
  .lookchip-key { font-size: 7px; letter-spacing: 0.1em; }
  .lookchip-value { font-size: 11px; }
  .export-btn { height: 38px; font-size: 11.5px; padding: 0 10px; }
  .export-btn-secondary { padding: 0 10px; }
}

/* =========================================================
   TABLET / TOUCH-PRIMARY DESKTOP — iPad-class devices that
   stay on the desktop layout but need touch-friendly sizing.
   Targets primary-pointer-coarse devices wider than the phone
   breakpoint: iPad portrait (744–820), iPad Pro 12.9 (1024),
   touch-screen Windows tablets in tablet mode, etc.
   ========================================================= */
@media (min-width: 701px) and (pointer: coarse) {
  /* Form controls — 16px to dodge iOS auto-zoom on focus, 44px floor for hit area. */
  input[type="text"],
  input[type="number"],
  select,
  textarea {
    font-size: 16px;
    min-height: 44px;
  }
  .grid-2 input, .grid-2 select { font-size: 15px; min-height: 42px; }
  input[type="range"] { height: 32px; }
  input[type="range"]::-webkit-slider-thumb { width: 22px; height: 22px; margin-top: -10px; }
  input[type="range"]::-moz-range-thumb { width: 22px; height: 22px; }

  .seg button { min-height: 44px; padding: 10px 8px; }
  .btn { min-height: 44px; }
  .chip { min-height: 44px; padding: 11px 13px 11px 11px; gap: 10px; }
  .chip input { width: 18px; height: 18px; }
  .chip input:checked { box-shadow: 0 0 0 3px var(--accent-soft); }
  .lookchip { padding: 10px 11px 10px 10px; min-height: 44px; }
  .crop-rot-btn { width: 36px; height: 36px; }
  .crop-aspect-seg button { padding: 9px 14px; }

  /* More columns in the picker on tablet — leverage the wider canvas to
     compare frames / templates side-by-side. */
  .frame-tile-grid { grid-template-columns: repeat(4, 1fr); }
  .tmpl-tile-grid  { grid-template-columns: repeat(2, 1fr); }
}

/* =========================================================
   TOUCH-ONLY HOVER STATE NEUTRALIZATION.
   iOS Safari (and Android Chrome's mobile mode) keep :hover sticky
   for a few hundred ms after a tap, which makes "lifted" buttons
   read like they're stuck mid-press. Reset hover transforms and
   border-style flips on devices whose primary pointer is coarse;
   :active still fires during the actual press for tactile feedback.
   ========================================================= */
@media (hover: none) {
  .topbar-pill:hover,
  .topbar-pill-strong:hover,
  .lookbar-import:hover,
  .lookbar-workshop:hover,
  .btn:hover:not(:disabled),
  .btn-primary:hover:not(:disabled),
  .btn-secondary:hover:not(:disabled),
  .frame-tile:hover,
  .tmpl-tile:hover,
  .crop-rot-btn:hover,
  .file-drop:hover,
  .update-banner-btn:hover {
    transform: none;
  }
  /* Workshop entry hover dashes→solid on desktop — undo on touch so the
     dashed "more surfaces behind here" affordance stays visible. */
  .lookbar-workshop:hover {
    border-style: dashed;
    border-color: var(--line-strong);
    background: transparent;
  }
  .lookbar-workshop:hover .lookbar-workshop-glyph {
    background: var(--bg-elev);
    border-color: var(--line);
    color: var(--text-muted);
  }
  .lookbar-workshop:hover .lookbar-workshop-caret {
    color: var(--text-dim);
    transform: none;
  }
  .geometry-btn:hover::after { transform: translate(0, 0); color: var(--text-dim); }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
