/* ---------- review grid ---------- */
/* Header row: title on the left, a duplicate "Continue to render" button on the
   right -- the grid below can run to hundreds of tiles, and without this the
   only way to advance was to scroll all the way past every one of them to
   reach the button at the bottom. */
.review-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.review-continue-top { font-size: .85rem; padding: 10px 20px; flex-shrink: 0; }
/* Also .look-dialog: the dump/teaser property sheets (dialogs.html) carry .legend captions too, including
   the cadence panel's own once app-review-photo-dump.js moves it in there. */
#review-panel .legend, .look-dialog .legend { color: var(--muted); font-size: .95rem; }
#review-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; margin: 24px 0; align-items: start; }
/* A landscape photo takes two columns (3:2 across 2w is exactly as tall as a 3:4 portrait across w), so rows have no dead space. */
#review-grid .review-tile[data-orientation="landscape"] { grid-column: span 2; }
/* Photo dump mode: a fixed 4 across instead of auto-fill, so each scroll's worth of screen reads as the same
   even grid regardless of viewport width -- the landscape span-2 rule above would otherwise turn that into
   "2 real tiles per row" once only landscape photos remain (dump-wide hides everything else, app-layout.css).
   #review-grid.dump-mode, not ".dump-mode #review-grid" -- the class lands on the grid itself
   (reviewGrid.classList.toggle(), app-review-submit.js), never on an ancestor, so the descendant-combinator
   form never actually matched: this always fell back to plain auto-fill instead. */
#review-grid.dump-mode { grid-template-columns: repeat(4, minmax(0, 1fr)); }
#review-grid.dump-mode .review-tile[data-orientation="landscape"] { grid-column: span 1; }
/* A tile is a <label>: margin 0, or the page's label margin (28 px above) sets every tile below the slide card beside it. */
.review-tile { position: relative; display: block; margin: 0; background: var(--surface-2); border-radius: var(--r-md); padding: 4px; box-sizing: border-box; border: 3px solid transparent; cursor: pointer; transition: border-color .15s ease; }
.review-tile.selected { border-color: var(--accent); background: var(--selected-bg); z-index: 1;
  box-shadow: 0 0 0 2px rgba(240, 177, 85, .55), 0 10px 28px -6px rgba(240, 177, 85, .75); }
.review-tile input { accent-color: var(--gold); }
.review-tile.selected input { width: 26px; height: 26px; top: 9px; right: 9px; }
/* Photos that will share one slide (app-review-groups.js): a thin frame in the group's colour, and after the group's last
   photo a card the size of a tile with the slide drawn as the film draws it; a tap opens it large. */
.review-tile[data-group] img { outline: 2px solid var(--group); outline-offset: -2px; }
/* Every slide's frame one colour: its photos are inside it, hidden from the grid, so no colour has a match to show. */
.review-slide { position: relative; aspect-ratio: 1; place-self: stretch; min-width: 0; padding: 4px; display: grid; place-items: center; border: 3px solid var(--line-strong); border-radius: var(--r-md); background: var(--surface-2); cursor: zoom-in; overflow: hidden; }   /* a tile's box and as tall as its row: only the slide in it is dark. Stretched both ways: a grid item with a
   shape stretched one way only takes its other side from the shape, and grew past its column */
.slide-frame { position: relative; width: 100%; background: #111318; overflow: hidden; border-radius: 4px; }
.slide-frame.tall { width: auto; height: 100%; }
.slide-cell { position: absolute; overflow: hidden; }
.slide-cell::before { content: ""; position: absolute; inset: -12%; background: var(--photo) center / cover; filter: blur(5px) brightness(.38); }
.slide-cell img { position: relative; display: block; width: 100%; height: 100%; padding: 3%; box-sizing: border-box; object-fit: contain; }
/* Placed as the film draws it once loaded (app-review-slide-edit.js placeSlidePhoto): its own box, framed and turned. */
.slide-cell img.placed { position: absolute; padding: 0; max-width: none; object-fit: cover; transform-origin: 50% 50%; }
/* The slide's editor: the whole screen, the slide as large as fits above its tools. */
.slide-editor { width: 100vw; height: 100dvh; max-width: 100vw; max-height: 100dvh; margin: 0; padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
                border: 0; box-sizing: border-box; background: var(--bg); color: var(--ink); }
.slide-editor[open] { display: flex; flex-direction: column; gap: 10px; }
.slide-editor::backdrop { background: rgba(0, 0, 0, .85); }
.slide-editor h2 { margin: 0; font-size: 18px; }
.slide-editor-hint { margin: 0; color: var(--muted); font-size: 13px; }
.slide-editor-stage { position: relative; flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center;
                      overflow: hidden; touch-action: none; user-select: none; -webkit-user-select: none; }
.slide-editor-stage .slide-frame { width: auto; height: 100%; max-width: 100%; transform-origin: 0 0; }
.slide-editor-stage .slide-frame:not(.tall) { width: 100%; height: auto; }
.slide-editor-stage .slide-cell { cursor: grab; }
/* The chosen photo's frame is drawn over its picture: an outline of the cell itself is hidden under the photo. */
.slide-editor-stage .slide-cell.chosen { z-index: 1; }
.slide-editor-stage .slide-cell.chosen::after, .slide-editor-stage .slide-cell:focus-visible::after {
  content: ""; position: absolute; inset: 0; border: 3px solid var(--accent); pointer-events: none; }
.slide-editor-stage .slide-cell:focus-visible { outline: none; }
.slide-editor-stage.turning .slide-cell:not(.chosen) { opacity: .2; }
.slide-turn-handle { position: absolute; z-index: 2; width: 44px; height: 44px; margin: -22px 0 0 -22px; border-radius: 50%;
                     display: flex; align-items: center; justify-content: center; font-size: 22px; cursor: grab; touch-action: none;
                     background: var(--gold); color: var(--on-accent); box-shadow: 0 2px 8px rgba(0, 0, 0, .5); }
.slide-turn-handle[hidden] { display: none; }
.slide-editor-tools, .slide-editor-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
.slide-editor-tools button, .slide-editor-actions button { min-height: 44px; min-width: 44px; }
.slide-editor-actions button { flex: 1 1 0; min-width: 0; }
.slide-edit-layout.rolling { animation: slide-dice-roll .6s ease-out; }
@keyframes slide-dice-roll { 0% { transform: rotate(0); } 60% { transform: rotate(380deg) scale(1.1); } 100% { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .slide-edit-layout.rolling { animation-duration: .2s; } }
.slide-dialog .slide-edit-open { min-height: 44px; }
#review-grid .review-tile.in-slide { display: none; }   /* shown as its slide's card alone */
.slide-dialog { padding: 0; border: 0; background: transparent; max-width: 96vw; max-height: 94vh; overflow: hidden; }
.slide-dialog[open] { display: flex; flex-direction: column; align-items: center; gap: 12px; }
.slide-members { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; }
.slide-member { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.slide-member-picture { position: relative; }
.slide-member img { width: 64px; height: 64px; object-fit: cover; border-radius: 8px; display: block; }
.slide-member-number { position: absolute; top: 4px; left: 4px; min-width: 20px; padding: 2px 6px; border-radius: var(--r-pill);
                       background: rgba(10, 10, 14, .78); color: #fff; font-size: 11px; font-weight: 700; text-align: center; }
.slide-member-number:empty { display: none; }
.slide-take-out { min-height: 44px; padding: 0 14px; border-radius: var(--r-pill); border: 1px solid var(--line-strong);
                  background: var(--surface-2); color: var(--ink); font-weight: 600; cursor: pointer; }
.slide-dialog::backdrop { background: rgba(0, 0, 0, .85); }
.slide-dialog .slide-frame { width: min(96vw, calc(62vh * 16 / 9)); }
.slide-dialog .slide-frame.tall { width: auto; height: min(62vh, calc(96vw * 16 / 9)); }   /* room under it for its photos */
.review-tile.group-hot { box-shadow: 0 0 0 4px var(--group), 0 0 22px var(--group); }
/* The photo fills the tile's width; its box is 3:4 (portrait) or 3:2 (landscape, two columns wide) whatever the source shape, cropping to fill. */
.review-tile[data-orientation="portrait"] img, .review-tile[data-orientation="portrait"] .noimg { height: auto; aspect-ratio: 3 / 4; object-fit: cover; }
.review-tile[data-orientation="landscape"] img, .review-tile[data-orientation="landscape"] .noimg { height: auto; aspect-ratio: 3 / 2; object-fit: cover; }
.review-tile img { width: 100%; height: 130px; object-fit: contain; background: var(--bg-2); border-radius: 8px; display: block; }
.review-tile input { position: absolute; top: 9px; right: 9px; width: 22px; height: 22px; z-index: 1; }
.review-tile .noimg { width: 100%; height: 130px; background: var(--bg-2); display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 11px; border-radius: 8px; }
.review-tile .tile-number { position: absolute; right: 11px; bottom: 11px; z-index: 1; min-width: 22px; padding: 3px 7px; border-radius: var(--r-pill); background: rgba(10, 10, 14, .78); color: #fff; font-size: 11px; font-weight: 700; line-height: 1; text-align: center; pointer-events: none; }
/* Video badge: a film-frame icon plus the clip's length (never past 20s, see
   talemontage/review.py's tile "duration" field), overlaid on the thumbnail so
   a video is distinguishable from a still at a glance in the grid -- before
   this the tile looked identical to a photo either way. */
.review-tile .video-badge {
  position: absolute; left: 11px; bottom: 11px; z-index: 1;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 11px 6px 8px; border-radius: var(--r-pill);
  background: #1b69dc; color: #fff; box-shadow: 0 2px 8px rgba(0, 0, 0, .5);
  font-size: 13px; font-weight: 800; line-height: 1; letter-spacing: .02em;
  pointer-events: none;
}
.review-tile .video-badge svg { width: 20px; height: 20px; flex-shrink: 0; }
#review-continue { margin-top: 8px; }
/* Extra bottom room so the floating estimated-length widget (below) never
   covers the grid's last row or the continue button once scrolled to the
   bottom -- it's fixed to the viewport, not part of this panel's own flow. */
#review-panel { padding-bottom: 84px; }
.review-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-top: 18px; }
.review-filter-btn { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; padding: 0; border-radius: 50%; background: var(--surface-2); color: var(--ink-dim); border: 1px solid var(--line-strong); cursor: pointer; transition: background .15s ease, color .15s ease; }
.review-filter-btn:hover { background: var(--surface-hover); }
.review-filter-btn:not([data-filter="all"]) { color: var(--accent); border-color: var(--accent); background: rgba(240, 177, 85, .12); }
.review-filter-btn .badge { display: none; }
.review-filter-btn .badge path { stroke: var(--on-accent); }   /* the tick or cross on the funnel's badge */
.review-filter-btn[data-filter="selected"] .badge-selected, .review-filter-btn[data-filter="out"] .badge-out { display: inline; }
.filtered-out { display: none !important; }
.review-filter-caption { font-size: .85rem; color: var(--ink-dim); min-width: 7.5em; }
.review-filter-caption strong { color: var(--ink); margin-left: 2px; }
button[data-filter]:not([data-filter="all"]) + .review-filter-caption { color: var(--accent); }
#review-grid[hidden] { display: none !important; }   /* (the grid's own display would otherwise win over [hidden]) */
.review-filter-empty { color: var(--ink-dim); text-align: center; padding: 32px 0; }
.review-toolbar-btn { background: var(--surface-2); color: var(--ink); border: 1px solid var(--line-strong); border-radius: var(--r-pill); padding: 9px 16px; font-size: .82rem; cursor: pointer; transition: background .15s ease; }
.review-toolbar-btn:hover { background: var(--surface-hover); }
/* Drag right to auto-select more photos (toward the target length), drag left
   to auto-select fewer -- pure client-side re-simulation over tiles the
   /review payload already carries (relevance + estimated_seconds per tile),
   so dragging never round-trips to the server. Photos the customer already
   hand-toggled stay pinned regardless of where the slider lands. */
#review-length-slider { flex: 1; accent-color: var(--gold); cursor: pointer; }
/* Fixed to the viewport (not inside the scrolling toolbar) so the
   running total stays visible while checking/unchecking photos further down
   the grid, without needing to scroll back up to see the impact -- the whole
   point being to make each toggle's effect on runtime immediately legible.
   `#review-panel[hidden] .review-duration` is still hidden: display:none on
   the hidden ancestor hides this fixed-position descendant too. */
.review-duration {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%); z-index: 40;
  display: inline-flex; align-items: baseline; gap: 8px;
  padding: 12px 22px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--line-strong);
  box-shadow: 0 18px 40px -14px rgba(0, 0, 0, .65), 0 0 0 1px rgba(240, 177, 85, .1);
  font-size: .82rem; font-weight: 600; color: var(--ink-dim); white-space: nowrap;
}
.review-duration.beside-slider { visibility: hidden; }   /* a slider under it (app-review.js keepReviewDurationAside) */
.review-kind-counts { color: var(--ink); }
.review-kind-counts::after { content: "·"; margin-left: 8px; color: var(--ink-dim); }
.review-duration strong { font-family: "Fraunces", Georgia, serif; font-size: 1.05rem; font-weight: 600; color: var(--accent); }
/* Shared hover tooltip mechanics for both marker kinds below (the day/calendar
   marker and the note/sticky marker) -- full text revealed on hover/focus,
   dir="auto" per line so Hebrew (or any RTL) content reads correctly instead
   of the browser guessing a single reading direction for the whole block. */
.review-marker-tip { position: absolute; bottom: 100%; left: 50%; transform: translateX(calc(-50% + var(--tip-shift, 0px))) translateY(-6px); background: var(--surface-2); color: var(--ink); border: 1px solid var(--line-strong); border-radius: var(--r-md); padding: 8px 10px; font-size: .78rem; line-height: 1.4; width: max-content; max-width: 240px; visibility: hidden; opacity: 0; transition: opacity .12s; pointer-events: none; z-index: 5; text-align: start; }
.review-day:hover .review-marker-tip, .review-day:focus-within .review-marker-tip,
.review-note:hover .review-marker-tip, .review-note:focus-within .review-marker-tip { visibility: visible; opacity: 1; }

/* Day marker: which day this is, e.g. "DAY 3" -- a calendar-page icon, not a
   note, since it never carries real written content on its own (the album's
   own captions are the separate yellow note marker below). */
/* Day and note markers fill exactly the footprint of a portrait photo tile (same padding + border, 3:4 face). */
.review-day { display: flex; align-items: center; justify-content: center; padding: 7px; position: relative; }
.review-day .day-toggle, .review-note .day-toggle { cursor: pointer; }   /* the badge ticks; the face reads (app-review-markers.js) */
.review-day.empty, .review-note.empty { opacity: .55; }
.review-day.empty .sheet, .review-note.empty .sticky { filter: grayscale(1); }
.review-day.on .sheet, .review-note.on .sticky { outline: 3px solid var(--accent); outline-offset: 2px; }
.review-day:focus-visible .sheet, .review-note:focus-visible .sticky { outline: 3px solid var(--ink); outline-offset: 2px; }
.review-day .day-toggle, .review-note .day-toggle { position: absolute; top: 2px; right: 6px; z-index: 1; width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 800; line-height: 1; background: var(--surface-2); color: var(--ink-dim); border: 1px solid var(--line-strong); }
.review-day.on .day-toggle, .review-note.on .day-toggle { background: var(--gold); color: #1a1206; border-color: var(--gold); }
/* The 20 px disc answers a finger over 44 px, reaching from the marker's top right corner down and in, so it stays on the
   marker and off the photo beside it. */
.review-day .day-toggle::before, .review-note .day-toggle::before { content: ""; position: absolute; top: -6px; right: -10px; width: 44px; height: 44px; }
/* A photo selected only because its day marker was ticked: labelled and flashed so it can't go unnoticed. */
.review-tile.auto-added::before { content: "Added for " attr(data-added-for); position: absolute; top: 6px; left: 6px; z-index: 2; background: var(--gold); color: #1a1206; font-size: 10px; font-weight: 700; padding: 2px 7px; border-radius: var(--r-pill); pointer-events: none; }
.review-tile.flash { animation: review-flash 1.2s ease-out 2; }
@keyframes review-flash { 0%, 100% { box-shadow: 0 0 0 0 rgba(240, 177, 85, 0); } 40% { box-shadow: 0 0 0 8px rgba(240, 177, 85, .75); } }
/* The toasts' stack (app-review-markers.js showReviewToast): a plain word above the Undo, each as tall as its words, never
   one over the other; a toast not shown takes no room in it. */
.review-toasts { position: fixed; left: 50%; bottom: 84px; transform: translateX(-50%); z-index: 50; display: flex; flex-direction: column; align-items: center; gap: 8px; width: max-content; max-width: min(520px, calc(100vw - 32px)); pointer-events: none; }
.review-toast { max-width: 100%; box-sizing: border-box; padding: 12px 18px; border-radius: var(--r-md); background: var(--gold); color: #1a1206; font-size: .88rem; font-weight: 600; box-shadow: 0 18px 40px -14px rgba(0, 0, 0, .65); text-align: center; pointer-events: auto; }
.review-toast:not(.show) { display: none; }
.review-toast.error { background: #c42e2e; color: #fff; }   /* white on it 5.6:1 */
#review-toast-undo { order: 1; }
.review-toast-action { margin-left: 14px; padding: 6px 14px; border-radius: var(--r-pill); border: 1px solid #1a1206; background: #1a1206; color: var(--gold); font: inherit; font-weight: 700; cursor: pointer; }
.review-day .sheet { width: 100%; aspect-ratio: 3/4; background: #f6f3ec; border-radius: 4px; box-shadow: 2px 4px 12px rgba(0, 0, 0, .5); position: relative; overflow: hidden; display: flex; flex-direction: column; }
/* The calendar sheet is paper in both themes, so its red is fixed: --danger is a light red in the dark theme (made to be
   read as text on dark surfaces), and white on it was 2.5:1. */
.review-day .sheet-head { background: #c42e2e; color: #fff; font-family: "Manrope", sans-serif; font-weight: 800; font-size: .85rem; letter-spacing: .12em; text-transform: uppercase; text-align: center; padding: 6px 2px; }
.review-day .sheet-number { flex: 1; display: flex; align-items: center; justify-content: center; font-family: "Fraunces", Georgia, serif; font-weight: 600; font-size: 3.2rem; color: #2b2a28; }
.review-day .sheet::after { content: ""; position: absolute; inset: 0; border: 1px solid rgba(0, 0, 0, .12); border-radius: 4px; pointer-events: none; }

/* Note marker: the album's own written caption text (what the customer wrote
   in Google Photos), anchored at the photo it belongs to -- a yellow sticky
   note, since unlike the day marker it does carry real content, previewed on
   its face and shown in full on hover. */
.review-note { display: flex; align-items: center; justify-content: center; padding: 7px; position: relative; }
.review-note .sticky { width: 100%; aspect-ratio: 3/4; background: #f2dd7e; border: 1px solid #b89a3e; border-radius: 2px 9px 2px 2px; box-shadow: 2px 3px 10px rgba(0, 0, 0, .5); transform: rotate(-3deg); position: relative; background-image: repeating-linear-gradient(to bottom, transparent 0 8px, rgba(0, 0, 0, .14) 8px 9px); display: flex; align-items: center; justify-content: center; text-align: center; padding: 6px 3px; overflow: hidden; }
.review-note .sticky::after { content: ""; position: absolute; top: 0; right: 0; border-width: 0 9px 9px 0; border-style: solid; border-color: transparent #fffbe6 transparent transparent; opacity: .65; }
.review-note .sticky-preview { position: relative; font-family: "Fraunces", Georgia, serif; font-weight: 600; font-style: italic; font-size: .85rem; line-height: 1.3; color: #4a3c10; text-shadow: 0 1px 0 rgba(255, 255, 255, .3); overflow: hidden; max-height: calc(1.3em * 9); max-width: 100%; overflow-wrap: anywhere; padding: 0 3px; box-sizing: border-box; }   /* the slanted letters lean out of the text's box: 3 px of the card's padding is here, for them. Nine lines by height,
   not -webkit-line-clamp: Chromium centres the clamped line and then hangs its ellipsis past it, outside the card's text box */

@media (max-width: 700px) {
  .review-duration { left: 16px; right: 16px; bottom: 14px; transform: none; text-align: center; justify-content: center;
                     flex-wrap: wrap; row-gap: 2px; white-space: normal; }  /* the photo/video counts may need a second line */
}

/* The photo page's bar at the screen's foot, every mode alike (review/app-review-bar.js): what is chosen on the left --
   the running total (photos, videos, length), or Snaps' "10 selected" -- and the page's way on on the right, the real
   Next or Create moved in. It stands in for the floating Next at the top of the photo page. */
.review-bar { position: fixed; left: 12px; right: 12px; bottom: max(12px, env(safe-area-inset-bottom)); z-index: 40;
  max-width: 560px; margin: 0 auto; box-sizing: border-box; display: flex; align-items: center; gap: 12px;
  padding: 6px 6px 6px 20px; border-radius: var(--r-pill); background: var(--surface-2); border: 1px solid var(--line-strong);
  box-shadow: 0 18px 40px -14px rgba(0, 0, 0, .65), 0 0 0 1px rgba(240, 177, 85, .1); }
.review-bar[hidden] { display: none; }
/* The bar lies over the foot of the screen: a photo scrolled into view stops above it, and the page has room at its foot
   for the last row to clear it -- a photo under the bar was half hidden and a tap on it hit the bar. */
html:has(.review-bar:not([hidden])) { scroll-padding-bottom: 96px; }
body:has(.review-bar:not([hidden])) #review-panel { padding-bottom: 96px; }
.review-bar-summary { flex: 1 1 auto; min-width: 0; }
.review-bar-count { font-size: .95rem; font-weight: 700; color: var(--ink); white-space: nowrap; }
.review-bar-count[hidden] { display: none; }
/* The running total in the bar: its words and length, no floating pill of its own. */
.review-bar .review-duration { position: static; transform: none; display: flex; flex-wrap: wrap; align-items: baseline;
  column-gap: 8px; row-gap: 0; padding: 0; border: 0; background: none; box-shadow: none; white-space: normal;
  justify-content: flex-start; text-align: left; visibility: visible; }
.review-duration[hidden] { display: none; }
.review-bar .review-continue-top, .review-bar .next { flex: none; min-height: 44px; margin: 0; padding: 10px 22px;
  font-size: .95rem; white-space: nowrap; }
.review-bar .next[hidden] { display: none; }
.review-step[data-rstep="1"] > .top-next { display: none; }   /* the bar is the photo page's way on */
.review-step[data-rstep="1"] .review-step-nav:not(:has(button)) { display: none; }   /* its Next is in the bar */
/* While the guide speaks of the bar (its overview and the bar's own step) the bar stands at the screen's top, clear of the
   guide's words at the foot; on the guide's other steps it is out of sight, as the floating Next is (guide.css). */
body:has(dialog.guide[open]:is([data-part="overview"], [data-part^="header"])) .review-bar { top: 12px; bottom: auto !important; }
body:has(dialog.guide[open]:not([data-part="overview"]):not([data-part^="header"])) .review-bar { visibility: hidden; }

/* ---------- responsive ---------- */
@media (max-width: 700px) {
  .shell { width: min(100% - 24px, 1120px); }
  .hero { padding: 20px 0 20px; }
  .hero h1 { font-size: 3.1rem; margin: 16px 0 14px; }
  .intro { font-size: 1.05rem; line-height: 1.55; }
  .start-hero { margin-top: 18px; border-radius: var(--r-md); }
  .stepper .stepper-dot.active .dot-label { display: none; }
  /* The rail must fit the phone: an over-wide stepper stretches the page and pushes the card left. */
  .stepper { margin-top: 20px; width: 100%; min-width: 0; }
  .stepper-dot { padding: 8px 0; }
  .stepper-dot .dot-num { width: 30px; height: 30px; }
  .stepper-line { min-width: 2px; margin: 0 3px; }
  .shell { overflow-x: clip; }
  /* minmax(0, 1fr), not 1fr: a 1fr track will not shrink below its widest no-wrap line, which pushed the card off the phone's right edge. */
  .step { grid-template-columns: minmax(0, 1fr); gap: 14px; padding: 28px 20px; width: 100%; margin-inline: auto; }
  .step-content { max-width: none; min-width: 0; }
  .controls { grid-template-columns: minmax(0, 1fr); }
  .url-row input { border-radius: var(--r-md); }
  .url-row #inspect { justify-self: stretch; min-width: 0; }
  .service-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .start-sources-title { margin-top: 18px; }
  .get-started { width: 100%; }
  .progress-panel, #review-panel { padding: 32px 22px; }
  /* None / Default / All: the label goes above the control so the control has the whole row (it was cut off at "A"). */
  /* The status lines wrap to one line or two depending on the text ("scene 42: montage (1.3s)" vs "scene 47: still (3.2s)"):
     reserve two lines so the panel does not jump every time a message changes. */
  .progress-panel h2 { min-height: 2.4em; }
  #status-message, #status-detail { min-height: 3.2em; min-height: 2lh; }
}
@media (prefers-reduced-motion: reduce) {
  .progress-track span { transition: none; }
  .progress-track span::after { animation: none; display: none; }
}

/* "Use my own soundtrack" (step 3 and the remix panel) */
.music-upload { display: grid; gap: .4rem; }
/* Review Music tab: Our music / Your own (review/step1.html) -- the same sliding pill as the shape picker,
   with its normal corner since the hint text wraps on a phone; each side's one action is a full-width button. */
.music-source-switch { margin: 0 0 14px; border-radius: var(--r-lg); }
.music-source-switch::before { border-radius: var(--r-md); }
.music-source-switch:has(input[value="own"]:checked)::before { transform: translateX(100%); }
.music-source-switch .mode-opt { padding: 8px 12px; }
.music-source-page { display: grid; gap: .5rem; }
.music-source-page[hidden] { display: none; }
.music-source-page .legend { margin: 0; }
.music-action-btn {
  display: block; width: 100%; box-sizing: border-box; padding: 13px 16px; text-align: center; cursor: pointer;
  font: inherit; font-weight: 600; color: var(--ink); background: var(--surface-2);
  border: 1.5px solid var(--accent); border-radius: var(--r-md); transition: background .15s ease;
}
.music-action-btn:hover { background: var(--surface-hover); }
.music-action-btn:has(+ input:disabled) { opacity: .5; cursor: progress; }
.music-action-btn:focus-visible, .music-action-btn:has(+ input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The wizard's old Advanced settings on the review screen (app-review-settings.js): label over a full-width dropdown. */
.review-settings { display: grid; gap: 12px; margin-top: 14px; }
.review-settings select { width: 100%; }
.review-settings[hidden], .review-settings [data-needs-days][hidden], .review-settings [data-needs-album-text][hidden] { display: none; }
.upload-list { margin: 0; padding-left: 1.2rem; font-size: .9rem; color: var(--muted); }
.upload-list:empty { display: none; }
.check-row { display: flex; align-items: center; gap: .5rem; font-weight: 400; }
.check-row input { flex: none; width: 18px; height: 18px; padding: 0; margin: 0; }
.check-row[hidden] { display: none; }

.map-look { margin: 18px 0; border: 1px solid var(--line, #2a2f3a); border-radius: 10px; padding: 12px 16px; }
.map-look summary { cursor: pointer; font-weight: 600; }
/* One preview only: the look card's own picture (map-look-sheet.js), which also opens Customize. */
.map-look-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; margin-top: 12px; }
.map-look-fields { display: grid; gap: 10px; align-content: start; }
.map-look-fields select { width: 100%; }
/* Map look: each choice is a card that opens a resizable popup of large thumbnails. */
.look-card { box-shadow: 0 0 16px -6px rgba(240, 177, 85, .4); display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 12px; align-items: center; width: 100%;
  text-align: left; padding: 14px 18px; cursor: pointer; color: var(--ink, #f4f1ea); border-radius: var(--r-md, 14px);
  border: 1px solid var(--line-strong, #3a4150); background: linear-gradient(120deg, rgba(var(--wash-rgb), .05), rgba(var(--wash-rgb), .015));
  transition: border-color .2s, box-shadow .2s, transform .15s; }
.look-card:hover { border-color: rgba(240, 177, 85, .7); box-shadow: 0 10px 30px -16px rgba(240, 177, 85, .6); transform: translateY(-1px); }
.look-card-name { grid-column: 1 / -1; font-size: .78rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted, #9aa3b2); }
.look-card-value { min-width: 0; overflow-wrap: anywhere; font-size: 1.15rem; font-weight: 700; line-height: 1.3; }
.look-card-change { color: var(--accent); font-weight: 700; }
.look-dialog { width: min(1400px, 98vw); height: 96vh; min-width: 360px; min-height: 300px; max-width: 98vw; max-height: 98vh;
  resize: both; overflow: auto; padding: 12px 16px; color: var(--ink, #f4f1ea); background: var(--bg-2, #12151c);
  border: 1px solid var(--line-strong, #3a4150); border-radius: 18px; box-shadow: 0 30px 90px rgba(0, 0, 0, .7); }
.look-dialog[open] { display: flex; flex-direction: column; gap: 8px; }
.look-dialog::backdrop { background: rgba(4, 6, 10, .72); backdrop-filter: blur(3px); }
.look-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.look-header h3 { margin: 0; font-size: 1.2rem; }
.look-header .look-hint { flex: 1; font-size: .9rem; }
.look-close { width: 36px; height: 36px; flex: none; display: grid; place-items: center; font-size: 1.3rem; line-height: 1; cursor: pointer;
  color: var(--ink, #f4f1ea); background: rgba(var(--wash-rgb), .07); border: 1px solid var(--line-strong, #3a4150); border-radius: 50%; }
.look-close:hover { background: rgba(var(--wash-rgb), .16); }
.look-grid { flex: 1; min-height: 0; overflow-y: auto; display: grid; gap: 10px; align-content: start;
  grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr)); }
.look-option { position: relative; display: grid; gap: 8px; padding: 8px; font-size: 1rem; cursor: pointer;
  border: 2px solid rgba(240, 177, 85, .22); border-radius: 12px; background: rgba(var(--wash-rgb), .03);
  box-shadow: 0 0 14px -4px rgba(240, 177, 85, .28);
  transition: border-color .2s, background .2s, box-shadow .2s, transform .2s; }
.look-option:hover { border-color: rgba(240, 177, 85, .65); background: rgba(240, 177, 85, .06);
  box-shadow: 0 0 22px -2px rgba(240, 177, 85, .5); transform: translateY(-2px); }
.look-option:has(input:checked) { border-color: var(--accent); background: rgba(240, 177, 85, .12);
  box-shadow: 0 0 0 1px rgba(240, 177, 85, .6), 0 0 26px 2px rgba(240, 177, 85, .65), 0 0 70px 6px rgba(141, 123, 245, .35);
  animation: look-glow 2.4s ease-in-out infinite; }
@keyframes look-glow { 50% { box-shadow: 0 0 0 1px rgba(240, 177, 85, .8), 0 0 36px 5px rgba(240, 177, 85, .85), 0 0 90px 10px rgba(141, 123, 245, .45); } }
@media (prefers-reduced-motion: reduce) { .look-option:has(input:checked) { animation: none; } }
.look-option img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; background: #0a0c10; border-radius: 8px; }
.look-none { aspect-ratio: 16 / 10; display: grid; place-items: center; text-align: center; padding: 12px; border-radius: 8px;
  border: 2px dashed var(--line-strong, #3a4150); color: var(--muted, #9aa3b2); font-size: 1.05rem; }
.look-option input { position: absolute; top: 18px; left: 18px; width: 24px; height: 24px; accent-color: var(--gold); }
.look-actions { display: flex; justify-content: flex-end; gap: 12px; }
.look-cancel { padding: 12px 22px; cursor: pointer; color: var(--ink, #f4f1ea); background: none; border: 1px solid var(--line-strong, #3a4150); border-radius: var(--r-pill, 999px); }
@media (max-width: 700px) { .look-grid { --cols: 2 !important; } }

/* A day marker's question (dialogs.html #review-day-dialog, app-review-days.js): one full-width button per answer. */
.day-dialog { width: min(420px, 94vw); }
.day-dialog h3 { margin: 0 0 6px; }
.day-dialog-actions { display: grid; gap: 8px; margin-top: 14px; }
.day-dialog-actions button { width: 100%; padding: 12px 14px; font-size: .95rem; text-align: center; }
.day-dialog-actions .day-dialog-cancel { background: none; border-color: transparent; color: var(--ink-dim); }
/* One photo's motion (app-review-motion.js): a chip on the tile (bottom left, where only a video has its badge), and a picker where each choice plays on the photo. */
.review-tile .motion-chip {
  position: absolute; left: 8px; bottom: 8px; z-index: 2; min-width: 30px; height: 30px; padding: 0 8px; border-radius: var(--r-pill);
  border: 1px solid var(--line-strong); background: rgba(10, 10, 14, .8); color: #fff; font-size: 14px; line-height: 1; cursor: pointer;
}
.review-tile .motion-chip.customized { border-color: var(--gold); color: var(--gold); }   /* a chip over the photo: fixed */
#review-grid.no-motion .motion-chip { display: none; }
/* A tile is its photo and its tick (a video's mark, a stack's count): its tools are in the photo opened large
   (app-review-view.js). The chips stay on the tile, hidden: they hold each photo's choices for the rest of the review. */
#review-grid .review-tile :is(.tile-number, .alike-chip, .clip-trim-chip, .motion-chip, .review-grip) { display: none; }
#review-grid .review-tile .video-badge > span { display: none; }
.motion-dialog { width: min(520px, 96vw); }
.motion-dialog h3 { margin: 0 0 12px; }
.motion-options { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
@media (max-width: 480px) { .motion-options { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.motion-option { display: grid; gap: 4px; min-width: 0; padding: 6px; border-radius: var(--r-md); border: 1.5px solid var(--line-strong);
  background: var(--surface-2); color: var(--ink); font: inherit; font-size: .82rem; cursor: pointer; }
.motion-option.chosen { border-color: var(--accent); color: var(--accent); }
.motion-caption { display: inline-flex; align-items: center; justify-content: center; gap: 5px; }
.motion-glyph { display: inline-flex; font-size: 1rem; line-height: 1; opacity: .85; }
.motion-svg { width: 1em; height: 1em; display: block; }
.review-tile .motion-chip .motion-svg { width: 15px; height: 15px; margin: auto; }
.motion-frame { display: block; aspect-ratio: 16 / 9; overflow: hidden; border-radius: 6px; background: #000; }
/* The photo whole, in its own shape: the film moves over the frame with the whole photo in it, so the
   preview shows it the same way instead of cropping every photo to 16:9. */
.motion-frame img { width: 100%; height: 100%; object-fit: contain; display: block;
  animation: 3.4s cubic-bezier(.45, 0, .55, 1) infinite; }
[data-effect="auto"] .motion-frame img { animation-name: motion-auto; }
[data-effect="zoom_in"] .motion-frame img { animation-name: motion-zoom-in; }
[data-effect="zoom_out"] .motion-frame img { animation-name: motion-zoom-out; }
[data-effect="pan_left"] .motion-frame img { animation-name: motion-pan-left; }
[data-effect="pan_right"] .motion-frame img { animation-name: motion-pan-right; }
[data-effect="pan_up"] .motion-frame img { animation-name: motion-pan-up; }
[data-effect="pan_down"] .motion-frame img { animation-name: motion-pan-down; }
/* Forward only, like the film: each photo moves one way (with `alternate` every other pass plays backwards -- a
   zoom in reads as a zoom out, a pan left as a pan right). The move runs to 86%, the
   last frame holds, and a fade out/in at the loop point reads as the cut to the next photo, not a jump back. */
@keyframes motion-auto { 0% { transform: scale(1); opacity: 0; } 8% { opacity: .75; } 86% { transform: scale(1.1); opacity: .75; } 100% { transform: scale(1.1); opacity: 0; } }
@keyframes motion-zoom-in { 0% { transform: scale(1); opacity: 0; } 8% { opacity: 1; } 86% { transform: scale(1.22); opacity: 1; } 100% { transform: scale(1.22); opacity: 0; } }
@keyframes motion-zoom-out { 0% { transform: scale(1.22); opacity: 0; } 8% { opacity: 1; } 86% { transform: scale(1); opacity: 1; } 100% { transform: scale(1); opacity: 0; } }
@keyframes motion-pan-left { 0% { transform: scale(1.22) translateX(8%); opacity: 0; } 8% { opacity: 1; } 86% { transform: scale(1.22) translateX(-8%); opacity: 1; } 100% { transform: scale(1.22) translateX(-8%); opacity: 0; } }
@keyframes motion-pan-right { 0% { transform: scale(1.22) translateX(-8%); opacity: 0; } 8% { opacity: 1; } 86% { transform: scale(1.22) translateX(8%); opacity: 1; } 100% { transform: scale(1.22) translateX(8%); opacity: 0; } }
@keyframes motion-pan-up { 0% { transform: scale(1.22) translateY(8%); opacity: 0; } 8% { opacity: 1; } 86% { transform: scale(1.22) translateY(-8%); opacity: 1; } 100% { transform: scale(1.22) translateY(-8%); opacity: 0; } }
@keyframes motion-pan-down { 0% { transform: scale(1.22) translateY(-8%); opacity: 0; } 8% { opacity: 1; } 86% { transform: scale(1.22) translateY(8%); opacity: 1; } 100% { transform: scale(1.22) translateY(8%); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .motion-frame img { animation-duration: 6s; } }
/* The photo picker (review/photo_picker.html), the same on the Film photos and Photo dump tabs: its note line keeps
   its height when there is nothing to say (the film's "coloured frames" appear only with groups), so switching tabs
   never moves the toolbar, slider or grid. The quick-select row sits in the toolbar, which already has the gutter.
   Hidden, it keeps one line, not its text's height: the film's unsaid note runs to four lines on a phone, a blank
   block above the slider. Snaps has no second tab to line up with, so there it takes no room at all. */
.picker-note { min-height: 1.5em; }
.picker-note[hidden] { display: block !important; visibility: hidden; height: 1.5em; overflow: hidden; }   /* on purpose: reserves the line */
.review-snaps .picker-note[hidden] { display: none !important; }
.property-page .review-toolbar .teaser-quick-select, .property-page .property-page .review-toolbar .teaser-quick-select { padding: 0; margin: 0; }
/* The two Pace pages alike: the film's (review/step1.html) and the photo dump's (macros.html
   cadence_panel) both read slider row, preview, one line -- plain, no box, and the preview frame the same height
   whatever its shape (a portrait dump frame would otherwise stand three times taller than the film's). */
.property-page .property-page .review-shape { display: block; margin-top: 0; padding: 0; background: none; border: 0; }
.property-page .property-page .pace-preview { width: fit-content; max-width: 100%; margin: 12px 0 0; padding: 0 12px; align-items: stretch; }
.property-page .property-page .still-pace-frame,
.property-page .property-page .dump-preview-frame { height: 180px; width: auto; max-width: 100%; }
.property-page .property-page .pace-preview .cadence-viz { margin: 6px 0 0; height: 20px; }
.property-page .property-page .pace-preview .cadence-viz::after { content: none; }
.property-page .property-page .pace-preview .dump-preview-note { max-width: 320px; }

/* A press of the dice, Faces or Auto (app-review-flourish.js): the button's own move, then the photos it took in popping
   in one after the other and the ones it took out fading. */
/* The dice grow much larger as they roll, casino red with white pips (the owner), then settle back. */
.review-dice.flourish-dice { position: relative; z-index: 3; animation: flourish-dice-pop .8s cubic-bezier(.3, 1.4, .5, 1); }
@keyframes flourish-dice-pop { 0% { transform: scale(1); }
  25%, 70% { transform: scale(1.7); background: #c8102e; border-color: #8e0b20; color: #fff; box-shadow: 0 8px 22px rgba(200, 16, 46, .55); }
  100% { transform: scale(1); } }
.review-dice.flourish-dice svg { animation: flourish-tumble .7s cubic-bezier(.3, 1.4, .5, 1); }
@keyframes flourish-tumble { 0% { transform: rotate(0) translateY(0); } 35% { transform: rotate(280deg) translateY(-7px) scale(1.15); }
  70% { transform: rotate(520deg) translateY(0) scale(.95); } 100% { transform: rotate(720deg); } }
/* Faces and No faces: a large pop that looks one way and the other, ringed in gold, then back (the owner: "a lot
   larger when pressing"). */
.flourish-faces { position: relative; z-index: 3; animation: flourish-look .9s cubic-bezier(.3, 1.3, .5, 1); }
@keyframes flourish-look { 0% { transform: scale(1); box-shadow: none; }
  20% { transform: scale(1.8) rotate(-12deg); box-shadow: 0 0 0 8px rgba(240, 177, 85, .45); }
  45% { transform: scale(1.8) rotate(12deg); box-shadow: 0 0 0 14px rgba(240, 177, 85, .2); }
  70% { transform: scale(1.5) rotate(0); box-shadow: 0 0 0 4px rgba(240, 177, 85, .35); }
  100% { transform: scale(1); box-shadow: none; } }
/* Under the finger at once, before the press is done: the button grows. */
.review-dice:active, .teaser-quick-select button:active { transform: scale(1.3); transition: transform .08s ease-out; }
.flourish-auto svg { animation: flourish-sparkle .7s ease-out; }
@keyframes flourish-sparkle { 0% { transform: rotate(0) scale(1); filter: none; }
  50% { transform: rotate(200deg) scale(1.35); filter: drop-shadow(0 0 6px var(--gold)); } 100% { transform: rotate(360deg) scale(1); filter: none; } }
#review-grid .review-tile.flourish-in { animation: flourish-in .5s ease-out var(--flourish-delay, 0ms) both; }
@keyframes flourish-in { 0% { transform: scale(.86); box-shadow: 0 0 0 0 rgba(240, 177, 85, 0); }
  55% { transform: scale(1.05); box-shadow: 0 0 0 5px rgba(240, 177, 85, .65); } 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(240, 177, 85, 0); } }
#review-grid .review-tile.flourish-out { animation: flourish-out .45s ease-in var(--flourish-delay, 0ms) both; }
@keyframes flourish-out { 0% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.95); } 100% { opacity: 1; transform: scale(1); } }
@media (prefers-reduced-motion: reduce) {
  .review-dice.flourish-dice, .review-dice.flourish-dice svg, .flourish-faces, .flourish-auto svg { animation: none; }
  .review-dice:active, .teaser-quick-select button:active { transform: none; }
  #review-grid .review-tile.flourish-in, #review-grid .review-tile.flourish-out { animation-duration: .2s; transform: none; }
}

/* Start over with other settings (app-review-back.js): at the review's foot, a quiet line, not a button among the page's own. */
.review-start-over { margin: 32px 0 0; text-align: center; }
.review-start-over-btn { min-height: 44px; padding: 0 14px; border: 0; background: transparent; color: var(--muted); font: inherit;
  font-size: .9rem; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.review-start-over-btn:hover { color: var(--ink); }
