/* ============================================================================
   PictU â€” shared components, capture surface, editor surface
   ----------------------------------------------------------------------------
   Reads every value from css/tokens.css. If a raw colour or a hard-coded
   shadow appears below, that is a bug.

   Sourcing rules applied here:
   Â· Depth is a hairline border. Shadows exist only on genuinely floating
     elements (.drop-menu, .toast) â€” never on a card or a button.
   Â· Pills are reserved for chips and the primary action. Everything else is
     6â€“20px.
   Â· No glassmorphism, no gradient fills, no hover zoom on photographs.
   Â· Motion is 140ms (controls) / 180ms (overlays). Neutralised globally under
     prefers-reduced-motion at the foot of this file.
   ========================================================================== */

/* ===== RESET / BASE ======================================================= */

*,
*::before,
*::after {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
p {
  margin: 0;
}

img {
  max-width: 100%;
  display: block;
}

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

/* Focus is always visible. Pink on paper clears the 3:1 non-text bar. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The `hidden` attribute is a state toggle used throughout the app, but any
   author `display` (flex, grid, block) outranks the UA rule that hides it — so
   an element that is `hidden` in JS can still paint. One rule at the top of the
   cascade makes the attribute mean what it says everywhere, instead of one
   `[hidden]` patch per component. */
[hidden] {
  display: none !important;
}

/* ===== UTILITY ============================================================ */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* One icon grammar: 24px box, 1.75 stroke, round caps, currentColor. */
.ico {
  width: 1.15em;
  height: 1.15em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.noscript-note {
  max-width: 34rem;
  margin: var(--s-5) auto;
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-left: 3px solid var(--err);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-ui);
}

/* ===== APP SHELL ========================================================== */

.app {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.app-header {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  height: var(--header-h);
  padding: 0 var(--s-4);
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) var(--s-2);
  margin-left: calc(var(--s-2) * -1);
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--ink);
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  letter-spacing: -0.01em;
}

.wordmark .ico {
  width: 1.35em;
  height: 1.35em;
  color: var(--accent);
  stroke-width: 1.9;
}

.app-nav {
  display: flex;
  align-items: center;
  gap: var(--s-1);
}

.nav-link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--tap);
  padding: 0 var(--s-3);
  border-radius: var(--r-md);
  color: var(--ink-2);
  font-size: var(--fs-ui);
  font-weight: var(--fw-medium);
  text-decoration: none;
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.nav-link:hover {
  background: var(--surface);
  color: var(--ink);
}

/* Admin is a quiet destination, not a feature. Auth is enforced server-side. */
.nav-link--quiet {
  color: var(--ink-3);
}

/* Flex column so the camera page can claim the leftover height between the
   header and this box's bottom padding (it sets `flex: 1`). Content pages are
   unaffected: a block-level `.page` as a flex item still sizes to content. */
.pages {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--s-5) var(--s-4) var(--s-7);
  flex: 1;
  min-height: 0;
}

/* Camera-only measure: no cap, uses full page width minus padding.
   Stage width sets itself from viewport height, comp-area flexes. */
.pages--wide {
  max-width: none;
}

.page {
  display: none;
}

.page.active {
  display: block;
}

/* The camera page owns every pixel between the header and the bottom padding,
   so the capture row can never fall below the fold. `min-height: 0` lets the
   inner grid rows shrink instead of pushing the page past the viewport. */
#pageCamera.active {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

.page-head {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin-bottom: var(--s-5);
}

.page-title {
  font-size: var(--fs-h2);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-display);
}

/* ===== BUTTONS ============================================================
   Hierarchy: .primary (pink, filled) is the single forward action on a page.
   .primary.is-secondary demotes the capture button once the set is complete so
   "Lihat Hasil" can take over as the only pink control on screen.
   ======================================================================== */

.btn,
.primary,
.secondary,
.back-btn,
.upload {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: var(--tap);
  padding: 0 var(--s-4);
  border-radius: var(--r-md);
  border: 1px solid transparent;
  background: none;
  cursor: pointer;
  font-size: var(--fs-ui);
  font-weight: var(--fw-semibold);
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.btn--ghost,
.secondary,
.back-btn,
.upload {
  border-color: var(--line-strong);
  background: var(--surface);
  color: var(--ink);
}

.btn--ghost:hover,
.secondary:hover,
.back-btn:hover,
.upload:hover {
  border-color: var(--ink-3);
  background: var(--surface);
}

.primary,
.btn--primary {
  padding: 0 var(--s-5);
  background: var(--accent-strong);
  border-color: var(--accent-strong);
  color: var(--ink-invert);
}

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

/* Demoted capture button: still available, no longer the headline action. */
.primary.is-secondary {
  background: none;
  border-color: var(--line-strong);
  color: var(--ink);
}

.primary.is-secondary:hover {
  border-color: var(--ink-3);
  background: none;
}

.btn:disabled,
.primary:disabled,
.secondary:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.primary--compact {
  padding: 0 var(--s-4);
}

/* ===== DROPDOWNS ========================================================== */

.top-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  margin-bottom: var(--s-5);
}

.dropdown {
  position: relative;
}

.drop-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--tap);
  padding: 0 var(--s-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-ui);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease);
}

.drop-btn:hover,
.dropdown.active .drop-btn {
  border-color: var(--ink-3);
}

.drop-btn .ico {
  color: var(--ink-2);
}

/* Chevron drawn in CSS so the JS that rewrites the delay label can keep a
   bare <span class="caret"> without needing an SVG string. */
.caret {
  width: 0.55em;
  height: 0.55em;
  margin-left: var(--s-1);
  border-right: 1.75px solid currentColor;
  border-bottom: 1.75px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
  transition: transform var(--t-med) var(--ease);
  color: var(--ink-2);
}

.dropdown.active .caret {
  transform: rotate(-135deg) translate(-2px, -2px);
  color: var(--ink);
}

/* Menus are position:fixed and positioned by main.ts so they can be clamped
   inside the viewport. That matters on a 360px phone: a menu anchored on the
   delay dropdown would otherwise extend past the right edge.
   display:none (not visibility) when closed â€” a visibility:hidden absolute
   element still contributes to document scroll width. */
.drop-menu {
  position: fixed;
  z-index: 100;
  min-width: 19rem;
  max-width: min(22rem, calc(100vw - 2rem));
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--sh-menu); /* floating element: shadow is earned */
  display: none;
}

.drop-menu.active {
  display: block;
  animation: menu-in var(--t-med) var(--ease);
}

@keyframes menu-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Compact arrangement buttons on the results page (above the final preview).
   Only the arrangements for the chosen photo count are ever rendered, so a
   small diagram is enough to tell "2 kolom" from "3 kolom" without reading
   the label. */
.grid-picker {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(4.25rem, 1fr));
  gap: var(--s-2);
}

.grid-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-1);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease),
    background-color var(--t-fast) var(--ease);
}

.grid-btn:hover {
  border-color: var(--line-strong);
  background: var(--surface-sunken);
}

.grid-btn.is-active {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.grid-btn__diagram {
  display: grid;
  gap: 2px;
  width: 2rem;
  height: 2rem;
}

.grid-btn__cell {
  border-radius: 1px;
  background: var(--line-strong);
}

.grid-btn.is-active .grid-btn__cell {
  background: var(--accent);
}

.grid-btn__label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--ink-2);
  text-align: center;
  line-height: var(--lh-tight);
}

.grid-btn.is-active .grid-btn__label {
  color: var(--accent-strong);
}

.delay-item {
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  font-size: var(--fs-ui);
  cursor: pointer;
  transition: background-color var(--t-fast) var(--ease);
}

.delay-item:hover {
  background: var(--surface-sunken);
}

.delay-item.active {
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-weight: var(--fw-semibold);
}

/* ===== TOOL RAIL (Kisi / Filter) ========================================= */

/* A vertical strip on the left: two icon+label buttons that open panels.
   72px width gives larger tap targets (BeautyPlus uses 80px). */
.tool-rail {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  width: 4.5rem; /* 72px */
  align-self: start;
}

.tool-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  width: 100%; /* fill rail 72px */
  min-height: calc(var(--tap) + 0.5rem);
  padding: var(--s-2) var(--s-1);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease),
    background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.tool-btn:hover {
  border-color: var(--line-strong);
  background: var(--surface-sunken);
  color: var(--ink);
}

/* aria-expanded is written by wireToolRail() together with the panel's
   visibility, so this state can never describe a panel that is closed. */
.tool-btn[aria-expanded='true'] {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-strong);
}

.tool-btn .ico {
  width: 1.35rem;
  height: 1.35rem;
}

.tool-btn__label {
  line-height: var(--lh-tight);
}

/* --- the floating panel ---------------------------------------------------- */

/* One shared panel; the tool buttons swap which pane is showing. A popover,
   not a modal: the camera stays visible and live to its right. */
.flyout {
  position: absolute;
  top: 0;
  left: calc(100% + var(--s-2));
  /* Above the stage (z 10) and countdown (z 20), below the header (z 50). */
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  width: clamp(15rem, 20vw, 18rem);
  max-height: min(70vh, 34rem);
  padding: var(--s-3) var(--s-4) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--sh-menu);
  overflow-y: auto;
}

.flyout__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
}

.flyout__title {
  margin: 0;
  font-size: var(--fs-ui);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-2);
}

.flyout__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  margin: calc(var(--s-2) * -1) calc(var(--s-3) * -1) 0 0;
  border: 0;
  border-radius: var(--r-md);
  background: none;
  color: var(--ink-2);
  cursor: pointer;
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.flyout__close:hover {
  background: var(--surface-sunken);
  color: var(--ink);
}

.flyout__pane {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

/* The panel's own gap spaces the fields; `.field`'s lead-in margin would
   stack with it. */
.flyout .field {
  margin-top: 0;
}

/* The filter grid re-flowed for the narrow panel: the wide-panel auto-fill
   minmax would otherwise leave a single tall column of chips. */
.flyout .filters {
  grid-template-columns: repeat(auto-fill, minmax(4.25rem, 1fr));
}

/* --- photo count chips ------------------------------------------------------ */

.count-chips {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-2);
}

.count-chip {
  min-height: var(--tap);
  border: 1px solid var(--line);
  border-radius: var(--r-pill); /* discrete choice set â€” pills are correct */
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-ui);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease),
    background-color var(--t-fast) var(--ease);
}

.count-chip:hover {
  border-color: var(--line-strong);
  background: var(--surface-sunken);
}

.count-chip.is-active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-strong);
}

/* ===== WORKSPACE: camera + composition ==================================== */

.workspace {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(14rem, 18rem);
  gap: var(--s-5);
  align-items: start;
  margin-bottom: var(--s-5);
}

.workspace--cam {
  /* Tool rail | stage + preview column. Fills viewport width, no cap. */
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  gap: var(--s-5);
  align-items: stretch;
  flex: 1 1 auto;
  min-height: 0;
  margin-bottom: 0;
}

.stage-col {
  display: grid;
  grid-template-columns: minmax(0, auto) 20.625rem; /* 330px panel */
  gap: var(--s-5);
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

/* Square is not arbitrary: capture.ts center-crops every frame to a square,
   so the framing the guest sees here is the framing they get. The width cap
   is derived from the viewport height minus everything the camera page
   stacks around the stage (header, page padding, top controls, action row,
   composition toolbar + a peek of the photo grid), so the capture row and
   the start of the composition both stay on screen at every supported
   viewport without masking anything with overflow: hidden. */
.stage {
  position: relative;
  width: min(100%, clamp(14rem, calc(100dvh - 22rem), 50rem));
  aspect-ratio: 1 / 1;
  /* Never shrink vertically: a shrunken stage would break the square the
     capture crop assumes. If the headroom estimate above is ever too small,
     the page scrolls instead of silently distorting the preview. */
  flex: none;
  border-radius: var(--r-xl);
  overflow: hidden;
  background: var(--stage);
  /* The stage is already a dark, high-contrast object — no shadow needed. */
  border: 1px solid #24242a;
  justify-self: center;
  grid-column: 1;
}

#webcam {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scaleX(-1);
  display: block;
}

/* Live adjustments overlay: sits exactly over the mirrored video and is
   drawn by src/editor/adjustPreview.ts. Hidden while the look is identity. */
.adjust-preview {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: block;
}

.adjust-preview[hidden] {
  display: none;
}

/* Live AR face overlay: transparent, above the (possibly graded) video. */
.face-overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

.face-overlay[hidden] {
  display: none;
}

.stage-state {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: var(--s-5);
  text-align: center;
  color: var(--ink-invert);
  background: var(--stage);
}

.stage[data-state='ready'] .stage-state {
  display: none;
}

.stage-state__ico {
  width: 2.5rem;
  height: 2.5rem;
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  margin-bottom: var(--s-1);
}

.stage-state__title {
  font-size: var(--fs-h3);
  font-weight: var(--fw-semibold);
  color: var(--ink-invert);
}

.stage-state__hint {
  font-size: var(--fs-sm);
  color: #b8b6b1;
  max-width: 26ch;
}

.stage-state__action {
  margin-top: var(--s-3);
  border-color: #3a3a42;
  background: transparent;
  color: var(--ink-invert);
}

.stage-state__action:hover {
  border-color: #55555f;
  background: transparent;
}

.stage-state__action[hidden] {
  display: none;
}

.stage[data-state='loading'] .stage-state__ico {
  animation: stage-pulse 1.4s var(--ease-io) infinite;
}

@keyframes stage-pulse {
  0%,
  100% {
    opacity: 0.45;
  }
  50% {
    opacity: 1;
  }
}

/* Countdown â€” a ring that re-arms once per second. The number itself does not
   bounce; reduced-motion users still see the digit change. */
.countdown-display {
  position: absolute;
  left: 50%;
  bottom: var(--s-5);
  transform: translateX(-50%);
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 5rem;
  height: 5rem;
  border-radius: 50%;
  background: rgba(23, 23, 26, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: var(--ink-invert);
  font-size: 2rem;
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--t-med) var(--ease), visibility var(--t-med) var(--ease);
}

.countdown-display.active {
  opacity: 1;
  visibility: visible;
  animation: countdown-ring 1s linear infinite;
}

@keyframes countdown-ring {
  from {
    box-shadow: 0 0 0 0 rgba(244, 91, 152, 0.5);
  }
  to {
    box-shadow: 0 0 0 18px rgba(244, 91, 152, 0);
  }
}

/* Abort a running capture. Sits in the stage's bottom-right so it never
   overlaps the centered countdown ring; only painted while capturing. */
.capture-cancel {
  position: absolute;
  right: var(--s-3);
  bottom: var(--s-3);
  z-index: 21;
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 0.45rem 0.85rem;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 999px;
  background: rgba(23, 23, 26, 0.78);
  color: var(--ink-invert);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: background-color var(--t-fast) var(--ease);
}

.capture-cancel:hover {
  background: rgba(23, 23, 26, 0.92);
}

.capture-cancel .ico {
  width: 0.95rem;
  height: 0.95rem;
  stroke-width: 2.2;
}

.noise-overlay {
  position: absolute;
  inset: 0;
  z-index: 10;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--t-med) var(--ease);
}

.noise-overlay.active {
  opacity: 0.16;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
}

.noise-overlay.sparkle {
  opacity: 0.5;
  background-image: radial-gradient(3px 3px at 45px 120px, #fff, transparent),
    radial-gradient(2px 2px at 120px 340px, rgba(255, 255, 255, 0.95), transparent),
    radial-gradient(2px 2px at 230px 80px, #fff, transparent),
    radial-gradient(1px 1px at 340px 450px, rgba(255, 255, 255, 0.9), transparent),
    radial-gradient(3px 3px at 480px 200px, #fff, transparent),
    radial-gradient(2px 2px at 560px 520px, rgba(255, 255, 255, 0.95), transparent),
    radial-gradient(1px 1px at 680px 150px, #fff, transparent),
    radial-gradient(2px 2px at 750px 380px, rgba(255, 255, 255, 0.9), transparent),
    radial-gradient(3px 3px at 890px 290px, #fff, transparent),
    radial-gradient(2px 2px at 920px 480px, rgba(255, 255, 255, 0.95), transparent),
    radial-gradient(1px 1px at 1050px 110px, #fff, transparent),
    radial-gradient(2px 2px at 1180px 420px, rgba(255, 255, 255, 0.9), transparent),
    radial-gradient(3px 3px at 1250px 250px, #fff, transparent),
    radial-gradient(2px 2px at 1380px 540px, rgba(255, 255, 255, 0.95), transparent),
    radial-gradient(1px 1px at 1420px 180px, #fff, transparent),
    radial-gradient(2px 2px at 1560px 360px, rgba(255, 255, 255, 0.9), transparent),
    radial-gradient(3px 3px at 1680px 90px, #fff, transparent),
    radial-gradient(2px 2px at 1720px 470px, rgba(255, 255, 255, 0.95), transparent),
    radial-gradient(1px 1px at 1850px 310px, #fff, transparent),
    radial-gradient(2px 2px at 80px 580px, rgba(255, 255, 255, 0.9), transparent);
}

/* ===== ACTION ROW ========================================================= */

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  width: 100%;
  grid-column: 1;
}

/* The capture button is the headline control: a pill is correct here because
   it is a single, isolated primary action â€” not decoration. */
.actions .primary {
  min-width: 11rem;
  min-height: 3rem;
  border-radius: var(--r-pill);
}

.actions .next {
  min-width: 10rem;
  min-height: 3rem;
  border-radius: var(--r-pill);
}

.next {
  display: none; /* main.ts shows this when the set is complete. */
}

/* ===== COMPOSITION AREA (in the main column) ==============================
   The captured set, laid out the way the export will be. Replaces the old
   right-hand results rail: photos now live directly under the stage.
   ======================================================================== */

/* Preview panel on right: 330px for 2x2 slots @ 160px, scrolls vertically. */
.comp-area {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  width: 20.625rem; /* 330px */
  min-height: 0;
  overflow-y: auto;
  padding-left: var(--s-3);
  grid-column: 2;
  grid-row: 1 / -1;
  border-left: 1px solid var(--line);
}

/* Compact toolbar above the grid: progress on the left, contextual actions
   (Urungkan / Ambil Ulang / Unduh) on the right. Everything here is
   state-driven — buttons appear only when they apply. */
.comp-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  width: 100%;
}

.comp-toolbar__actions {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
  justify-content: flex-end;
}

.comp-progress {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--accent-strong);
  font-variant-numeric: tabular-nums;
}

/* "Urungkan": restores the photo a retake replaced. Shown only while the
   undo stack has an entry (src/camera/retakeUndo.ts). */
.comp-undo {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.25rem 0.55rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.comp-undo:hover {
  border-color: var(--accent);
  color: var(--accent-strong);
}

.comp-undo .ico {
  width: 0.9rem;
  height: 0.9rem;
  stroke-width: 2;
}

/* Toolbar-sized ghost buttons (Ambil Ulang, Unduh): quieter than the pill
   actions below the stage, same hover/focus contract as .btn--ghost. */
.comp-action {
  min-height: 2rem;
  padding-inline: var(--s-3);
  font-size: var(--fs-sm);
}

.comp-hint {
  font-size: var(--fs-sm);
  color: var(--ink-2);
  line-height: var(--lh-snug);
  width: 100%;
}

/* The composition grid. Its track count comes from JS, sized to the bounding
   box of the slots the active layout uses (so a 1-photo layout shows one
   cell, and a 2x2 layout shows a 2x2). Cells stay square via aspect-ratio. */
.photos-container {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  width: 100%;
}

.photo-slot {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  aspect-ratio: 1 / 1;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--surface-sunken);
}

.photo-slot.filled {
  border-style: solid;
  border-color: var(--accent-line);
}

.photo-slot-number {
  font-size: var(--fs-h3);
  font-weight: var(--fw-semibold);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  user-select: none;
}

.photo-item {
  position: absolute;
  inset: 0;
  animation: photo-in var(--t-med) var(--ease);
}

.photo-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}

@keyframes photo-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* Retake: keyboard-reachable, and always visible on touch (no hover there). */
.retake-btn {
  position: absolute;
  top: 3px;
  right: 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 50%;
  background: rgba(23, 23, 26, 0.78);
  color: var(--ink-invert);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease);
}

.retake-btn .ico {
  width: 0.85rem;
  height: 0.85rem;
  stroke-width: 2.2;
}

.photo-slot:hover .retake-btn,
.photo-slot:focus-within .retake-btn,
.retake-btn:focus-visible {
  opacity: 1;
}

@media (hover: none) {
  .retake-btn {
    opacity: 1;
  }
}

/* ===== PANELS / FILTERS =================================================== */

.panel {
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
}

.panel__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-3);
}

.panel__title {
  font-size: var(--fs-h3);
  font-weight: var(--fw-semibold);
}

.panel__note {
  font-size: var(--fs-xs);
  color: var(--ink-3);
}

.filters {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(4.75rem, 1fr));
  gap: var(--s-2);
}

.filter {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-1);
  padding: 6px 6px 7px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}

.filter:hover {
  border-color: var(--line-strong);
  background: var(--surface-sunken);
}

.filter.active {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* Real thumbnail of the live frame with the filter's CSS applied. Until a
   source image exists this is a neutral well â€” no fake preview. */
.filter__thumb {
  width: 100%;
  aspect-ratio: 1 / 1;
  max-width: 4rem;
  border-radius: var(--r-sm);
  background-color: var(--surface-sunken);
  background-position: center;
  background-size: cover;
}

.filter__name {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  color: var(--ink-2);
  text-align: center;
  line-height: var(--lh-tight);
}

.filter.active .filter__name {
  color: var(--accent-strong);
  font-weight: var(--fw-semibold);
}

/* Face-effect chip thumbnails: static SVG art (contain, not cover). */
.face-effect__thumb {
  width: 100%;
  aspect-ratio: 1 / 1;
  max-width: 4rem;
  border-radius: var(--r-sm);
  background-color: var(--surface-sunken);
  background-position: center;
  background-repeat: no-repeat;
  background-size: contain;
}

.face-effect-status {
  margin: var(--s-3) 0 0;
  font-size: var(--fs-xs);
  color: var(--ink-2);
  line-height: var(--lh-snug);
}

.face-effect-status[hidden] {
  display: none;
}

/* --- filter panel tabs (Preset / Penyesuaian / Efek Wajah) ----------------- */

.filter-tabs {
  display: flex;
  gap: var(--s-1);
  margin-bottom: var(--s-3);
}

.filter-tab {
  flex: 1 1 0;
  padding: 0.4rem var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease);
}

.filter-tab:hover {
  border-color: var(--line-strong);
}

.filter-tab.active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-weight: var(--fw-semibold);
}

.pane-subhead {
  margin: var(--s-3) 0 var(--s-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-3);
}

.pane-subhead--first {
  margin-top: 0;
}

/* --- adjustment sliders ----------------------------------------------------- */

.adjust-row {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr) 2.75rem;
  align-items: center;
  gap: var(--s-2);
  padding: 2px 0;
}

.adjust-row__label {
  font-size: var(--fs-xs);
  color: var(--ink-2);
}

.adjust-row input[type='range'] {
  width: 100%;
  min-width: 0;
  min-height: var(--tap);
  accent-color: var(--accent-strong);
  cursor: pointer;
}

.adjust-row__value {
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--ink-2);
}

.adjust-actions {
  display: flex;
  margin-top: var(--s-3);
}

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

/* ===== EDITOR (frame & sticker) =========================================== */

.editor-layout {
  display: grid;
  /* The composite is the hero of the results page, so the preview column
     gets the larger share; the side panels are controls, not content. */
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: var(--s-5);
  align-items: start;
  margin-bottom: var(--s-5);
}

.editor-preview {
  position: sticky;
  top: calc(var(--header-h) + var(--s-4));
}

/* The layout picker leads the preview column; it needs breathing room before
   the composite well below it. */
.editor-preview > .field {
  margin-bottom: var(--s-4);
}

.editor-side {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.frames-grid,
.stickers-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));
  gap: var(--s-2);
}

.frame-item,
.sticker-item {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 3.25rem;
  padding: var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}

.frame-item:hover,
.sticker-item:hover {
  border-color: var(--line-strong);
  background: var(--surface-sunken);
}

.frame-item.active,
.sticker-item.active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-weight: var(--fw-semibold);
}

/* Sticker glyphs come from src/editor/frames.ts and are text. They are a
   picker, not an icon system â€” larger type is correct here. */
.sticker-item {
  font-size: 1.5rem;
  min-height: 3.75rem;
}

/* ===== EDITOR SIDE PANEL CONTROLS ========================================= */

/* Sticker editing is direct manipulation on the preview, so the hint sits
   under the image rather than in a tooltip that disappears on drag. */
.editor-hint {
  margin: var(--s-2) 0 0;
  font-size: var(--fs-xs);
  color: var(--ink-3);
  text-align: center;
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin-top: var(--s-4);
}

/* First block under a panel head needs no lead-in space. */
.field--first,
.panel__head + .field {
  margin-top: 0;
}

.field__label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-2);
}

.field__note {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--ink-2);
}

/* Segmented control — caption on/off. A switch would need two more moving
   parts than this binary choice deserves, and the pills keep it consistent
   with the other discrete choice sets on the camera page. */
.seg {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-2);
}

.seg__btn {
  min-height: var(--tap);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-ui);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease),
    background-color var(--t-fast) var(--ease);
}

.seg__btn:hover {
  border-color: var(--line-strong);
  background: var(--surface-sunken);
}

.seg__btn.is-active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-weight: var(--fw-semibold);
}

/* --- search + category filter --------------------------------------------- */

.sticker-tools {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin-top: var(--s-4);
  margin-bottom: var(--s-3);
}

.search-field input[type='search'] {
  width: 100%;
  min-height: var(--tap);
  padding: 0 var(--s-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-ui);
  font-family: inherit;
}

.search-field input[type='search']::placeholder {
  color: var(--ink-3);
}

.search-field input[type='search']:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--sh-focus);
}

.sticker-cats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
  max-height: 6.75rem;
  overflow-y: auto;
  padding-right: 2px;
}

.sticker-cat {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  min-height: 1.75rem;
  padding: 0 var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  font-family: inherit;
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease),
    background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.sticker-cat b {
  color: var(--ink-2);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
}

.sticker-cat:hover {
  border-color: var(--line-strong);
}

.sticker-cat.active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-strong);
}

.sticker-cat.active b {
  color: var(--accent-strong);
}

/* --- sticker library grid -------------------------------------------------- */

.stickers-grid--library {
  max-height: 21rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 2px;
  margin: -2px;
}

/* Library tiles show the artwork, not a glyph, so they are taller than the
   emoji badges and carry a caption. */
.sticker-item--asset {
  flex-direction: column;
  gap: var(--s-1);
  min-height: 5rem;
  padding: var(--s-2);
}

.sticker-item--asset img {
  width: 100%;
  height: 3rem;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
}

.sticker-item__name {
  width: 100%;
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  color: var(--ink-2);
  overflow: hidden;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.sticker-item--asset.active .sticker-item__name {
  color: var(--accent-strong);
  font-weight: var(--fw-semibold);
}

.sticker-empty {
  margin: var(--s-3) 0 0;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  text-align: center;
}

.sticker-empty[hidden] {
  display: none;
}

/* --- density --------------------------------------------------------------- */

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

.density__row input[type='range'] {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--tap);
  accent-color: var(--accent-strong);
  cursor: pointer;
}

.density__value {
  min-width: 5.75rem;
  text-align: right;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--accent-strong);
}

.density__actions {
  display: flex;
  gap: var(--s-2);
}

.density__actions .btn {
  flex: 1 1 0;
  padding: 0 var(--s-2);
  font-size: var(--fs-sm);
}

/* --- frame colour palette -------------------------------------------------- */

.color-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

.color-swatch {
  position: relative;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background-color: var(--swatch, var(--surface));
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}

/* "Tidak ada warna" reads as a transparent checkerboard, not as a white chip
   that is indistinguishable from an actual white frame. */
.color-swatch--default {
  background-color: var(--surface);
  background-image:
    linear-gradient(45deg, var(--line) 25%, transparent 25%),
    linear-gradient(-45deg, var(--line) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--line) 75%),
    linear-gradient(-45deg, transparent 75%, var(--line) 75%);
  background-size: 8px 8px;
  background-position: 0 0, 0 4px, 4px -4px, -4px 0;
}

.color-swatch:hover {
  border-color: var(--ink-3);
}

.color-swatch.active {
  border-color: var(--accent-strong);
  outline: 2px solid var(--accent-strong);
  outline-offset: 2px;
}

.color-swatch:focus-visible {
  outline: none;
  box-shadow: var(--sh-focus);
}

/* ===== STICKER OVERLAY ====================================================
   Lives only in the editor DOM, layered on the rendered composite. It never
   reaches the canvas, which is why nothing here can appear in an export.
   ======================================================================== */

.sticker-overlay {
  position: absolute;
  z-index: 4;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

.sticker-overlay[hidden] {
  display: none;
}

.sticker-handle {
  position: absolute;
  box-sizing: border-box;
  transform: translate(-50%, -50%) rotate(var(--rot, 0deg));
  transform-origin: 50% 50%;
  cursor: grab;
  pointer-events: auto;
  touch-action: none;
  border: 1px dashed transparent;
  border-radius: var(--r-sm);
  transition: border-color var(--t-fast) var(--ease);
}

.sticker-handle:hover,
.sticker-handle:focus-visible,
.sticker-handle.is-dragging {
  border-color: var(--accent);
}

.sticker-handle:focus-visible {
  outline: none;
  box-shadow: var(--sh-focus);
}

.sticker-handle.is-dragging {
  cursor: grabbing;
}

.sticker-handle img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 4px;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}

/* Generated decorations are marked so the guest can tell what the slider
   produced, without a label appearing in the exported file. */
.sticker-handle.is-auto {
  border-style: dotted;
  border-color: rgba(244, 91, 152, 0.55);
}

.sticker-handle__grip {
  position: absolute;
  width: 14px;
  height: 14px;
  border: 2px solid var(--surface);
  border-radius: 50%;
  background: var(--accent-strong);
  box-shadow: 0 1px 3px rgba(32, 32, 31, 0.4);
  pointer-events: auto;
}

/* Grow the hit area to ~26px without growing the dot. */
.sticker-handle__grip::after {
  content: '';
  position: absolute;
  inset: -6px;
}

.sticker-handle__grip--resize {
  right: -7px;
  bottom: -7px;
  cursor: nwse-resize;
}

.sticker-handle__grip--rotate {
  left: 50%;
  top: -26px;
  transform: translateX(-50%);
  cursor: grab;
}

.sticker-handle__grip--rotate::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 100%;
  width: 2px;
  height: 20px;
  margin-left: -1px;
  background: var(--accent-strong);
}

/* ===== CAPTURE FLASH ====================================================== */

.capture-flash {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: #fff;
  opacity: 0;
  pointer-events: none;
  transition: opacity 300ms var(--ease);
}

/* ===== RESPONSIVE ========================================================= */

/* Laptop and below: the camera page is two columns (tool rail | stage +
   composition); no third-column override is needed any more. */
@media (max-width: 1199px) {
  .comp-area {
    /* Slightly tighter cap so the toolbar never wraps on a 1024px laptop. */
    width: min(100%, clamp(14rem, calc(100dvh - 22rem), 44rem));
  }
}

/* Tablet: the tool rail moves above the stage; the column scrolls as one. */
@media (max-width: 900px) {
  /* Stacked, the page scrolls instead of filling the viewport: height is
     content-driven, so no column is bounded by a row the document no longer
     defines. */
  #pageCamera.active {
    flex: 0 1 auto;
    min-height: auto;
  }

  .workspace {
    grid-template-columns: minmax(0, 1fr);
  }

  /* The tool rail sits ABOVE the stage once stacked, so the viewport-height
     budget needs the extra room the desktop two-column row does not — plus
     the composition toolbar peek below the action row. */
  .stage {
    width: min(100%, calc(100dvh - 23.5rem));
  }

  .workspace--cam {
    grid-template-rows: none;
    flex: none;
  }

  /* Stacked: grid becomes flex column; the whole column flows with the document. */
  .stage-col {
    display: flex;
    flex-direction: column;
    gap: var(--s-4);
    align-items: center;
    overflow-y: visible;
  }

  /* The tool rail becomes a horizontal strip; its panel drops below it. */
  .tool-rail {
    flex-direction: row;
    align-self: stretch;
    width: 100%;
  }

  .flyout {
    left: 0;
    right: 0;
    top: calc(100% + var(--s-2));
    width: auto;
    max-height: min(60vh, 30rem);
  }

  /* Full width once stacked, so the count chips read as one row. */
  .count-chips {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }

  .grid-picker {
    grid-template-columns: repeat(auto-fit, minmax(5rem, 1fr));
  }

  .comp-area {
    width: 100%;
    padding: 0;
    grid-column: auto;
    grid-row: auto;
    border-left: none;
    border-top: 1px solid var(--line);
  }

  .editor-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .editor-preview {
    /* Not `static`: the sticker overlay is absolutely positioned against this
       box, so it has to stay a containing block once it stops sticking. */
    position: relative;
    top: auto;
  }
}

/* Phone: the composition toolbar wraps cleanly; the grid keeps its layout
   arrangement at full width (only the layout's slots are rendered). */
@media (max-width: 767px) {
  .pages {
    padding: var(--s-4) var(--s-3) var(--s-6);
  }

  .app-header {
    padding: 0 var(--s-3);
  }

  .nav-link span {
    display: none;
  }

  .nav-link {
    padding: 0 var(--s-2);
  }

  .stage {
    width: 100%;
  }

  /* The panel becomes a bottom sheet. An absolutely-positioned popover beside
     a horizontal tool rail would sit on top of the stage on a phone, which is
     the one thing the guest still needs to see. */
  .flyout {
    position: fixed;
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    width: auto;
    max-height: min(70dvh, 32rem);
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    padding-bottom: var(--s-6);
    z-index: 60;
  }

  .top-controls {
    gap: var(--s-2);
  }

  .drop-btn {
    flex: 1 1 auto;
  }

  .comp-toolbar {
    flex-wrap: wrap;
    row-gap: var(--s-2);
  }

  .comp-hint {
    text-align: center;
  }

  .actions .primary,
  .actions .next {
    flex: 1 1 100%;
  }

  /* Arrangement buttons scroll sideways rather than wrapping into a grid that
     pushes the composition preview off the fold. */
  .grid-picker {
    display: flex;
    overflow-x: auto;
    padding-bottom: var(--s-1);
  }

  .grid-btn {
    flex: 0 0 5rem;
  }

  .drop-menu {
    min-width: 0;
    width: min(20rem, calc(100vw - 2rem));
  }
}

/* ===== REDUCED MOTION =====================================================
   One switch for the whole capture surface. Digits still update; only the
   decorative motion is removed.
   ======================================================================== */

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

  .countdown-display.active,
  .stage[data-state='loading'] .stage-state__ico,
  .photo-item {
    animation: none;
  }
}
