/* Fluid Bloom: the marketing site's identity carried into the actual
   product through the SAME glass mechanic that was already working here
   (translucent blurred panels over the photo) -- this is a token
   recolor, not a mechanic replacement. Cool lavender-grey ground (not the
   old warm beige), violet accent with a brighter dark-mode variant so it
   keeps reading as violet instead of washing out (same reasoning the old
   systemBlue pair used), coral as the secondary brand color (brand-dot
   gradient, sparing use elsewhere). Body/UI text stays a plain system
   sans for density and legibility -- Fluid Bloom's serif is a marketing-
   copy choice, not right for a dense editing tool.

   This file is shared by every page (hub, login, pricing, Passport Pro,
   Culling Pro, Portrait Pro), so the light/dark system here stays exactly
   as it was -- OS preference by default, explicit [data-theme] override
   winning when a page sets one. Portrait Pro is the one page that now
   locks its OWN chrome to a fixed dark palette instead of toggling it;
   that override lives in index.html's own <style> block, not here, so it
   can't leak into any other page that also loads this stylesheet. */
:root {
  --bg: #F0EEF4;
  --panel: rgba(255,255,255,0.55);
  --panel-2: rgba(255,255,255,0.4);
  --glass-strong: rgba(255,255,255,0.85);
  --glass-blur: blur(26px) saturate(1.6);
  --border: rgba(255,255,255,0.55);
  --text: #17151C;
  --text-dim: #544F63;
  --text-faint: #8B84A0;
  --accent: #6B5CE0;
  --accent-soft: rgba(107,92,224,0.12);
  --accent-line: rgba(107,92,224,0.4);
  --accent-2: #E8785F;
  --glass-shadow: 0 15px 35px rgba(50,40,85,0.16);
  --scrim-1: rgba(255,255,255,0.3);
  --scrim-2: rgba(240,238,244,0.6);
  /* Portrait Pro's canvas-area background only -- unused by every other
     page (none of them has a .canvas-area element), kept here just so the
     token always resolves to something even if that ever changes. See the
     --canvas-bg override block and .canvas-area below. */
  --canvas-bg: #777777;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Rounded", "SF Pro Display", "SF Pro Text", "Segoe UI", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  /* Without this, the browser renders native controls (a <select>'s
     dropdown popup, scrollbars) using the OS's light-mode chrome by
     default regardless of our own dark palette -- so a dark-mode <select>
     styled with light --text color on top of that native light popup
     background reads as near-invisible light-on-light text. This tells
     the browser which native chrome to use so the popup's own default
     colors already match, before our explicit option colors below even
     factor in. */
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Obsidian Glass pass: deepened from the old #17151C toward a true
       near-black so panels read as glass CUT FROM something dark, not just
       a slightly-dim grey -- the contrast between --bg and --panel is what
       actually sells "glass," and the old pairing was too close together
       to read as a material. */
    --bg: #121017;
    --panel: rgba(255,255,255,0.07);
    --panel-2: rgba(255,255,255,0.05);
    --glass-strong: rgba(255,255,255,0.16);
    --border: rgba(255,255,255,0.13);
    --text: #F0EEF4;
    --text-dim: #B9B3CC;
    --text-faint: #726C87;
    --accent: #8C7FF0;
    --accent-soft: rgba(140,127,240,0.18);
    --accent-line: rgba(140,127,240,0.45);
    --accent-2: #F0876B;
    --glass-shadow: 0 20px 45px rgba(0,0,0,0.55);
    --scrim-1: rgba(18,16,23,0.2);
    --scrim-2: rgba(8,7,11,0.78);
  }
}

/* Explicit user choice always wins over the OS preference, in both directions. */
:root[data-theme="dark"] {
  --bg: #121017;
  --panel: rgba(255,255,255,0.07);
  --panel-2: rgba(255,255,255,0.05);
  --glass-strong: rgba(255,255,255,0.16);
  --border: rgba(255,255,255,0.13);
  --text: #F0EEF4;
  --text-dim: #B9B3CC;
  --text-faint: #726C87;
  --accent: #8C7FF0;
  --accent-soft: rgba(140,127,240,0.18);
  --accent-line: rgba(140,127,240,0.45);
  --accent-2: #F0876B;
  --glass-shadow: 0 20px 45px rgba(0,0,0,0.55);
  --scrim-1: rgba(18,16,23,0.2);
  --scrim-2: rgba(8,7,11,0.78);
  color-scheme: dark;
}
:root[data-theme="light"] {
  --bg: #F0EEF4;
  --panel: rgba(255,255,255,0.55);
  --panel-2: rgba(255,255,255,0.4);
  --glass-strong: rgba(255,255,255,0.85);
  --border: rgba(255,255,255,0.55);
  --text: #17151C;
  --text-dim: #544F63;
  --text-faint: #8B84A0;
  --accent: #6B5CE0;
  --accent-soft: rgba(107,92,224,0.12);
  --accent-line: rgba(107,92,224,0.4);
  --accent-2: #E8785F;
  --glass-shadow: 0 15px 35px rgba(50,40,85,0.16);
  --scrim-1: rgba(255,255,255,0.3);
  --scrim-2: rgba(240,238,244,0.6);
  color-scheme: light;
}

/* Portrait Pro's canvas-area background toggle (see index.html's own
   <style> block for the chrome lock, and the theme-toggle-btn click
   handler in main.js). Harmless on every other page: none of them has a
   .canvas-area element, and none of them sets [data-canvas-bg]. */
:root[data-canvas-bg="white"] { --canvas-bg: #ffffff; }
:root[data-canvas-bg="grey"] { --canvas-bg: #777777; }

* { box-sizing: border-box; }

/* Belt-and-suspenders on top of color-scheme above: explicitly color every
   <option> so dropdown popup text stays readable even in engines that
   don't fully theme the native popup from color-scheme alone.

   Real bug this caught: a native <select> popup is a separate OS-level
   surface with its own opaque white backing -- it is NOT layered on top of
   the page. --panel-2 is a deliberately TRANSLUCENT token (rgba(255,255,
   255,0.07) in dark mode) meant to glass-overlay our own dark --bg behind
   it; composited over the popup's default white canvas instead, that 7%
   white tint renders as nearly solid white, not dark, which is exactly why
   the dropdown text was unreadable. Options need an OPAQUE color, so --bg
   (a solid hex, not an rgba token) is the right one here, not --panel-2. */
select { color-scheme: inherit; }
select option {
  background: var(--bg);
  color: var(--text);
}

/* NOT overflow:hidden here -- this stylesheet is shared by every page
   (Portrait Pro, Passport Pro, Culling Pro, Hub, Login, Pricing), and only
   the two fixed-chrome editor pages (Portrait Pro, Passport Pro) should
   ever lock the outer page from scrolling. Each of those two sets
   `html, body { overflow: hidden; }` in its OWN page-scoped <style> block
   instead -- putting it here broke normal page scrolling on Culling Pro's
   photo grid (and would have broken Hub/Login/Pricing too) as unintended
   collateral damage from a fix that was only ever about Portrait Pro. */
html, body {
  height: 100%;
  margin: 0;
}

#app {
  height: 100%;
  margin: 0;
  display: flex;
  flex-direction: column;
  position: relative;
}

body {
  background: var(--bg);
  color: var(--text);
  transition: background 0.25s ease, color 0.25s ease;
}

.app-footer {
  position: fixed;
  bottom: 3px;
  right: 10px;
  z-index: 5;
  font-size: 9px;
  color: var(--text-faint);
  opacity: 0.6;
  letter-spacing: 0.02em;
  pointer-events: none;
}

/* ---------- Main Menu (card-deck launcher) ----------
   Lives inline in the topbar, right next to the product name, and fans a
   hand of product cards open DOWNWARD when clicked ("Prism Deck" --
   holographic foil, conic-gradient border cycling through the four
   product accent colors). Deliberately dark/theme-invariant regardless of
   the app's own light/dark toggle -- a floating control surface, same
   treatment already used for the canvas tool overlays (brush cursor, crop
   handles), not page chrome that should follow --bg/--text.
   Two-click behavior (see main-menu.js): first click opens the deck;
   clicking Main Menu AGAIN while it's open navigates to the hub page
   instead of just closing it -- clicking a card, clicking outside, or Esc
   still just close without navigating. */
.main-menu-wrap {
  position: relative;
  display: inline-flex;
}
.main-menu-btn {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px 7px 11px;
  border-radius: 9px;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #f1f1f6;
  background: #141319;
  border: 1px solid transparent;
  background-image: linear-gradient(#141319, #141319), conic-gradient(from 180deg, #7B6CFF, #3FC7B0, #F08C6A, #C9A15A, #7B6CFF);
  background-origin: border-box;
  background-clip: padding-box, border-box;
  box-shadow: 0 4px 14px rgba(0,0,0,0.35);
  cursor: pointer;
  transition: box-shadow 0.2s ease;
}
.main-menu-btn svg { width: 14px; height: 14px; flex: none; }
.main-menu-btn:hover { box-shadow: 0 4px 16px rgba(123,108,255,0.3), 0 0 0 1px rgba(255,255,255,0.08); }
.main-menu-btn.open { box-shadow: 0 4px 16px rgba(123,108,255,0.35), 0 0 0 1px rgba(255,255,255,0.12); }

/* "Icon dock" -- a frosted glass tray of gradient app-tiles, macOS-dock-style
   magnification driven by cursor proximity (see main-menu.js), name shown as
   a tooltip on hover, current tool marked with a dot underneath. */
.main-menu-fan {
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  z-index: 39;
  display: flex;
  align-items: flex-end;
  gap: 14px;
  padding: 14px 18px 18px;
  border-radius: 22px;
  background: rgba(15,14,20,0.85);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(255,255,255,0.09);
  box-shadow: 0 24px 54px rgba(0,0,0,0.55);
  opacity: 0;
  transform: translateY(-8px) scale(0.96);
  transform-origin: 8% 0%;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.28s cubic-bezier(.16,1,.3,1);
}
.main-menu-fan.open {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}
.main-menu-card {
  position: relative;
  flex: none;
  width: 64px;
  height: 64px;
  border-radius: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(160deg, color-mix(in srgb, var(--accent) 92%, white) 0%, color-mix(in srgb, var(--accent) 65%, black) 100%);
  box-shadow: 0 10px 20px -10px color-mix(in srgb, var(--accent) 60%, transparent), inset 0 1px 0 rgba(255,255,255,0.35);
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: transform 0.18s cubic-bezier(.34,1.56,.64,1);
  will-change: transform;
}
.main-menu-card .mm-icon { width: 30px; height: 30px; margin: 0; color: rgba(15,12,6,0.72); }
.main-menu-card .mm-tag, .main-menu-card .mm-foot { display: none; }
.main-menu-card .mm-name {
  position: absolute;
  bottom: calc(100% + 12px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: rgba(20,20,18,0.94);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  margin: 0;
  padding: 6px 11px;
  border-radius: 9px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.main-menu-card:hover .mm-name { opacity: 1; transform: translateX(-50%) translateY(0); }
.main-menu-card::after {
  content: "";
  position: absolute;
  bottom: -11px;
  left: 50%;
  transform: translateX(-50%);
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: transparent;
}
.main-menu-card.mm-current::after { background: var(--accent); }
.main-menu-card.mm-current { cursor: default; }

.main-menu-card.mm-violet   { --accent: #8f83ff; }
.main-menu-card.mm-teal     { --accent: #4fd6bd; }
.main-menu-card.mm-coral    { --accent: #f0906a; }
.main-menu-card.mm-gold     { --accent: #c9a15a; }
.main-menu-card.mm-magenta  { --accent: #f472b6; }

@media (max-width: 640px) {
  .main-menu-fan { gap: 10px; padding: 12px 14px 16px; }
  .main-menu-card { width: 54px; height: 54px; border-radius: 15px; }
  .main-menu-card .mm-icon { width: 26px; height: 26px; }
}

.splash-screen {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(circle at 30% 20%, rgba(107,92,224,0.2), transparent 55%),
    radial-gradient(circle at 75% 80%, rgba(232,120,95,0.15), transparent 55%),
    linear-gradient(160deg, #0e0f13 0%, #14161a 55%, #1a1d24 100%);
  transition: opacity 0.5s ease, visibility 0.5s ease;
}

.splash-screen.hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.splash-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 6px;
}

.splash-logo {
  width: 72px;
  height: 72px;
  border-radius: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #0b0d10;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  box-shadow: 0 8px 30px rgba(107,92,224,0.35);
  margin-bottom: 18px;
}

.splash-title {
  margin: 0;
  font-size: 26px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: 0.01em;
}

.splash-title span {
  display: block;
  font-size: 15px;
  font-weight: 400;
  color: var(--accent);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-top: 4px;
}

.splash-tagline {
  margin: 10px 0 28px;
  color: var(--text-dim);
  font-size: 13px;
}

.splash-spinner {
  display: flex;
  gap: 8px;
}

.splash-spinner span {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
  animation: splash-bounce 1.1s ease-in-out infinite;
}

.splash-spinner span:nth-child(2) { background: var(--accent-2); animation-delay: 0.15s; }
.splash-spinner span:nth-child(3) { animation-delay: 0.3s; }

@keyframes splash-bounce {
  0%, 80%, 100% { transform: translateY(0); opacity: 0.5; }
  40% { transform: translateY(-10px); opacity: 1; }
}

.splash-status {
  margin-top: 18px;
  font-size: 12.5px;
  color: var(--text-dim);
  min-height: 16px;
}

.splash-copyright {
  position: absolute;
  bottom: 22px;
  left: 0;
  right: 0;
  text-align: center;
  font-size: 11px;
  color: var(--text-dim);
  opacity: 0.7;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(8,9,11,0.6);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.modal-overlay:not([hidden]) {
  display: flex;
}

.modal-box {
  width: 400px;
  max-width: 90vw;
  max-height: 85vh;
  overflow-y: auto;
  background: var(--glass-strong);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 22px 22px 18px;
  box-shadow: 0 20px 60px rgba(0,0,0,0.5);
}

.modal-title {
  margin: 0 0 16px;
  font-size: 17px;
  font-weight: 600;
  color: var(--text);
}

/* confirmDialog()'s message text -- see .btn.primary.danger above for the
   rest of that component's shared styling. */
.confirm-dialog-message { margin: 0 0 18px; font-size: 14px; line-height: 1.5; color: var(--text); }

/* Photo-load overlay -- shown the instant a photo is opened, while
   detectFace()/detectPersonSegmentation() run for it (main.js
   showPhotoLoadOverlay/completePhotoLoadOverlay). Scoped to exactly the
   photo's own tile inside .canvas-wrap (position:absolute; inset:0, same
   trick as .crop-overlay right above it) rather than the whole viewport --
   this covers only the loaded image, not the topbar/sidebar chrome around
   it. Reuses the same phosphor-green dotted-mesh language as Studio
   Light's wireframe HUD (drawWireframe() in main.js) so the two "scanning"
   moments in the app read as one family instead of two different styles.
   A translucent tint + backdrop-filter dims/desaturates the actual photo
   showing through underneath (same idea as Studio Light's own
   ctx.filter="saturate(0.5) contrast(1.05)" pass on its live preview),
   rather than an opaque scrim that hides the photo entirely -- the point is
   to keep the user looking at their own photo while it's being scanned,
   not to block it. -webkit- prefix is required for Safari; see the
   cross-browser CANVAS_FILTER_SUPPORTED sweep elsewhere in this file for
   why every backdrop-filter here gets one. */
.photo-load-overlay {
  --pl-accent: #78ff8c;
  position: absolute;
  inset: 0;
  z-index: 40;
  border-radius: 4px;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background:
    radial-gradient(circle at 30% 20%, rgba(120,255,140,0.12), transparent 55%),
    radial-gradient(circle at 75% 80%, rgba(120,255,140,0.08), transparent 55%),
    linear-gradient(160deg, rgba(6,11,8,0.42) 0%, rgba(8,14,10,0.22) 55%, rgba(9,15,10,0.4) 100%);
  backdrop-filter: saturate(0.65) brightness(0.72);
  -webkit-backdrop-filter: saturate(0.65) brightness(0.72);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.6s ease, visibility 0.6s ease;
}
.photo-load-overlay.pl-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
.pl-wireframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.pl-corner {
  position: absolute;
  width: 26px;
  height: 26px;
  border-color: rgba(120,255,140,0.55);
  transition: opacity 0.4s ease 0.15s;
}
.photo-load-overlay.pl-complete .pl-corner { opacity: 0; }
.pl-corner.tl { top: 14px; left: 14px; border-top: 1.5px solid; border-left: 1.5px solid; }
.pl-corner.tr { top: 14px; right: 14px; border-top: 1.5px solid; border-right: 1.5px solid; }
.pl-corner.bl { bottom: 14px; left: 14px; border-bottom: 1.5px solid; border-left: 1.5px solid; }
.pl-corner.br { bottom: 14px; right: 14px; border-bottom: 1.5px solid; border-right: 1.5px solid; }
.pl-frame-complete {
  position: absolute;
  inset: 8px;
  border: 1.5px solid rgba(120,255,140,0.65);
  border-radius: 6px;
  box-shadow: 0 0 26px rgba(120,255,140,0.22), inset 0 0 20px rgba(120,255,140,0.08);
  opacity: 0;
  transition: opacity 0.5s ease 0.1s;
}
.photo-load-overlay.pl-complete .pl-frame-complete { opacity: 1; }
.pl-sweep {
  position: absolute;
  inset: 0;
  background: conic-gradient(from 0deg, transparent 0deg, rgba(120,255,140,0.18) 18deg, transparent 60deg);
  animation: pl-sweep-rotate 3s linear infinite;
  transition: opacity 0.4s ease;
}
.photo-load-overlay.pl-complete .pl-sweep {
  opacity: 0;
  animation-play-state: paused;
}
@keyframes pl-sweep-rotate {
  to { transform: rotate(360deg); }
}
.pl-hud {
  position: absolute;
  font-family: "SF Mono", "Consolas", monospace;
  font-size: 9.5px;
  letter-spacing: 0.08em;
  line-height: 1.5;
  color: rgba(120,255,140,0.75);
  text-transform: uppercase;
  text-shadow: 0 1px 3px rgba(0,0,0,0.85), 0 0 10px rgba(0,0,0,0.5);
  transition: opacity 0.3s ease;
}
.pl-hud .dim { color: rgba(120,255,140,0.4); }
.pl-hud.top-left { top: 12px; left: 14px; }
.pl-hud.top-right { top: 12px; right: 14px; text-align: right; }
.photo-load-overlay.pl-complete .pl-hud { opacity: 0; }
.pl-caption {
  position: relative;
  z-index: 1;
  padding: 12px 0 18px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  text-align: center;
}
.pl-wordmark {
  margin: 0;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(120,255,140,0.55);
  text-shadow: 0 1px 3px rgba(0,0,0,0.85), 0 0 10px rgba(0,0,0,0.5);
}
.pl-status {
  margin: 0;
  font-size: 11.5px;
  color: rgba(235,255,240,0.85);
  min-height: 14px;
  text-shadow: 0 1px 3px rgba(0,0,0,0.85), 0 0 10px rgba(0,0,0,0.5);
}

/* Studio Light -- full-screen light-positioning modal, not a fixed-width
   box like Export's .modal-box -- near-fullscreen so the wireframe and the
   live photo preview behind the scrim both have real room. */
.studio-light-overlay {
  background: rgba(10,16,13,0.42);
  backdrop-filter: blur(28px) saturate(160%);
  -webkit-backdrop-filter: blur(28px) saturate(160%);
}
.studio-light-box {
  /* Scoped so the sliders' fill/thumb and the Process button (both driven
     by --accent/--accent-line elsewhere in the stylesheet) render in the
     same phosphor green as the rest of the Nightvision Grid theme, without
     touching the app's global purple accent anywhere else. */
  --accent: #7cff8e;
  --accent-line: rgba(124,255,142,0.45);
  position: relative;
  width: 96vw;
  height: 92vh;
  max-width: 96vw;
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 24px 28px;
}
/* Same round "x" pattern as the Keyboard Shortcuts panel's .shortcuts-close
   -- same shape/position/colors, just anchored to this modal's own box. */
.studio-light-close {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: none;
  background: var(--panel-2);
  color: var(--text-dim);
  cursor: pointer;
  font-size: 17px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.studio-light-close:hover { color: var(--text); }
.studio-light-controls {
  width: 100%;
  max-width: 520px;
  display: flex;
  align-items: center;
  gap: 18px;
}
.studio-light-sliders {
  flex: 1;
  min-width: 0;
}
.studio-light-stage {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  min-height: 0;
}
.studio-light-elev-track {
  height: 100%;
  max-height: 420px;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Vertical range input via the classic rotate-a-horizontal-slider trick --
   keeps all of .glass-range's existing track/thumb styling intact, just
   reoriented, rather than needing separate vertical-specific CSS. */
.studio-light-elev-track input[type="range"] {
  width: 260px;
  transform: rotate(-90deg);
}
.studio-light-wire-wrap {
  /* Two fixes ago: width/height clamped independently (82vw / 78vh), which
     only matched the canvas's real 880x920 ratio by coincidence, so the box
     could end up a different shape than the canvas and stretch it.
     One fix ago: added `width: min(880px, 82vw)` + `max-height: 100%` +
     aspect-ratio -- but pairing an EXPLICIT width with a max-height
     constraint doesn't ask the browser to re-derive width when height gets
     clamped; it just used that fixed width as-is and squashed height down
     to fit max-height, which is a *different* stretch (this is what the
     last screenshot showed -- wide and short instead of the original
     narrow-and-tall stretch).
     Fix: no explicit width or height at all -- only max-width and
     max-height as the two competing constraints, with aspect-ratio
     resolving both auto dimensions together. This is the correctly-
     supported pattern for a flex item: the browser picks whichever
     constraint (82vw-wide vs. the stage's real remaining height) is
     tighter and sizes both axes from it via the ratio, so the box is
     always genuinely 880:920 and never wider/taller than either limit.
  */
  position: relative;
  max-width: min(880px, 82vw);
  max-height: 100%;
  aspect-ratio: 880 / 920;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  overflow: hidden;
  background-image:
    linear-gradient(rgba(120,255,140,0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(120,255,140,0.05) 1px, transparent 1px);
  background-size: 26px 26px;
  box-shadow: 0 0 0 1px rgba(120,255,140,0.15);
}
.studio-light-wire-wrap canvas {
  width: 100%;
  height: 100%;
  cursor: grab;
}
.studio-light-wire-wrap canvas.carrying { cursor: grabbing; }
.studio-light-corner {
  position: absolute;
  width: 32px;
  height: 32px;
  border-color: rgba(120,255,140,0.65);
  pointer-events: none;
}
.studio-light-corner.tl { top: 16px; left: 16px; border-top: 1.5px solid; border-left: 1.5px solid; }
.studio-light-corner.tr { top: 16px; right: 16px; border-top: 1.5px solid; border-right: 1.5px solid; }
.studio-light-corner.bl { bottom: 16px; left: 16px; border-bottom: 1.5px solid; border-left: 1.5px solid; }
.studio-light-corner.br { bottom: 16px; right: 16px; border-bottom: 1.5px solid; border-right: 1.5px solid; }
.studio-light-hud-label {
  position: absolute;
  pointer-events: none;
  font-family: "SF Mono", "JetBrains Mono", monospace;
  font-size: 12px;
  letter-spacing: 0.08em;
  line-height: 1.7;
  color: rgba(120,255,140,0.85);
}
.studio-light-hud-label .dim { color: rgba(120,255,140,0.45); }
.studio-light-hud-label.top-left { top: 28px; left: 28px; }
.studio-light-hud-label.top-right { top: 28px; right: 28px; text-align: right; }
/* Free-flow disc drag covers both axes at once, so the elevation/azimuth
   tracks stay in the DOM (they're the real inputs the drag writes into) but
   are not shown as separate slider rows any more. */
.sr-only-slider { display: none; }
.studio-light-azimuth-track {
  width: 100%;
  max-width: 440px;
}
.studio-light-azimuth-track input[type="range"] { width: 100%; }
.studio-light-size-row,
.studio-light-diffusion-row,
.studio-light-width-row,
.studio-light-intensity-row {
  width: 100%;
}
.btn.primary.studio-light-process-btn {
  /* .btn.primary (background: var(--accent); color: #fff) has higher
     specificity (two classes) than a single-class override, so the earlier
     attempt at this never actually applied -- the button was still
     rendering .btn.primary's own bright-green fill with white text, which
     is what read as "too bright to read". Matching its specificity here so
     this dark phosphor-terminal treatment actually wins. */
  flex: none;
  white-space: nowrap;
  background: rgba(8,18,12,0.92);
  border: 1px solid rgba(124,255,142,0.75);
  color: #8fe89f;
  box-shadow: 0 0 14px rgba(124,255,142,0.18);
}
.btn.primary.studio-light-process-btn:hover {
  background: rgba(14,28,18,0.95);
  color: #b4f5bf;
}
.studio-light-open-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}
.studio-light-open-icon {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #fff, var(--accent) 70%);
  box-shadow: 0 0 6px var(--accent-line);
}

.modal-section {
  margin-bottom: 18px;
}

.modal-section.disabled {
  opacity: 0.45;
  pointer-events: none;
}

.modal-label {
  display: block;
  font-size: 12px;
  color: var(--text-dim);
  margin-bottom: 8px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.format-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.format-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  background: var(--panel-2);
}

.format-option:has(input:checked) {
  border-color: var(--accent);
  background: rgba(108,140,255,0.12);
}

.format-option input {
  accent-color: var(--accent);
  flex-shrink: 0;
}

.format-option div {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.format-option strong {
  font-size: 13px;
  color: var(--text);
}

.format-option span {
  font-size: 11px;
  color: var(--text-dim);
}

.format-option[data-native-only].is-unavailable {
  display: none;
}

.modal-hint {
  font-size: 11px;
  color: var(--text-dim);
  margin: 8px 0 0;
  line-height: 1.4;
}

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

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 4px;
}

.topbar {
  position: relative;
  flex-shrink: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 18px;
  border-bottom: 1px solid var(--border);
  background: var(--panel);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}

/* Obsidian Glass signature detail: a hairline of light along the top edge
   of every glass surface, the way a real pane of glass catches light along
   its rim. Decorative only (absolute + pointer-events:none), so it can't
   shift layout or intercept clicks on the buttons/tabs underneath. */
.topbar::after, .sidebar::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--glass-strong), transparent);
  pointer-events: none;
}

.brand { display: flex; align-items: center; gap: 9px; }

.brand-dot {
  width: 20px;
  height: 20px;
  border-radius: 6px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  flex-shrink: 0;
}

/* Studio OS parent-brand mark (hub/login/pricing/home only -- product
   pages keep their own .brand-dot accent square, not this). Viewfinder
   corner-brackets on the shared accent gradient; theme-adaptive via the
   existing --accent/--accent-2 tokens, same as .brand-dot. */
.studio-mark { width: 22px; height: 22px; flex-shrink: 0; }

.topbar h1 {
  font-size: 15px;
  font-weight: 700;
  margin: 0;
  letter-spacing: -0.01em;
}

.topbar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.theme-toggle-btn {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text-dim);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.theme-toggle-btn:hover { color: var(--text); border-color: var(--accent-line); }
.theme-toggle-btn svg { width: 17px; height: 17px; }
/* Icon shows the action, not the current state (same convention the old
   light/dark toggle used) -- sun while on the neutral grey backdrop means
   "click to brighten to white"; moon while on white means "click to go
   back to the neutral grey reference backdrop". */
.theme-toggle-btn .icon-sun { display: block; }
.theme-toggle-btn .icon-moon { display: none; }
:root[data-canvas-bg="white"] .theme-toggle-btn .icon-sun { display: none; }
:root[data-canvas-bg="white"] .theme-toggle-btn .icon-moon { display: block; }

.update-banner {
  flex-shrink: 0;
  z-index: 29;
  display: none;
  align-items: center;
  gap: 12px;
  padding: 9px 18px;
  background: var(--accent-soft);
  border-bottom: 1px solid var(--accent-line);
  font-size: 13px;
  color: var(--text);
}

.update-banner:not([hidden]) { display: flex; }

.update-banner-link {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}
.update-banner-link:hover { text-decoration: underline; }

.update-banner-dismiss {
  margin-left: auto;
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 6px;
}
.update-banner-dismiss:hover { color: var(--text); }

.btn {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 550;
  font-family: inherit;
  cursor: pointer;
  user-select: none;
  /* .btn is applied to both <button> and <a> across every page -- without
     this, every anchor-styled button (Main Menu, Log in, Get started, etc.)
     shows the browser's default link underline. */
  text-decoration: none;
  /* Doesn't matter for a button sized to its own content, but any .btn
     stretched wider than its text (e.g. .result-actions .btn's width:100%
     on Passport Pro) needs this -- an <a> left-aligns its inline content by
     default, unlike a <button>, so an anchor-styled full-width button's
     label sat at the left edge instead of centered without it. */
  text-align: center;
}

.btn:hover:not(:disabled) { border-color: var(--accent); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; box-shadow: 0 4px 16px var(--accent-line); }
.btn.ghost { background: transparent; }
/* Destructive action override for .btn.primary -- a fixed red regardless of
   which page's --accent it's used on (gallery.html's coral, main.js's
   purple, etc.), since "delete this, permanently" should read as a warning
   everywhere, not just pick up whatever the page's brand color happens to
   be. Used by confirmDialog()'s Confirm button when danger:true is passed
   -- see gallery.js and main.js, both of which define their own
   page-scoped confirmDialog() but share this one button style. */
.btn.primary.danger { background: #c0392b; border-color: #c0392b; box-shadow: 0 4px 16px rgba(192,57,43,0.35); }
.btn.primary.danger:hover { background: #d04533; }
/* Before/After button while pinned to the original -- distinguishes "you're
   looking at an old state" from the button's normal idle look. */
#beforeAfterBtn.active { background: var(--accent-soft); border-color: var(--accent); color: var(--text); }

.workspace {
  display: flex;
  flex: 1;
  min-height: 0;
}

/* A real column again, not a floating overlay -- a bottom dock over the
   photo read as cluttered and non-standard next to how every other
   professional photo editor (Lightroom, Capture One, even Photos.app)
   actually lays out: tools live in a side panel, the photo gets the rest. */
.sidebar {
  position: relative;
  width: 272px;
  min-width: 272px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border-right: 1px solid var(--border);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  overflow: hidden;
}

.sidebar.disabled {
  display: none;
}

.tabs {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 5px;
  padding: 10px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  overflow-y: auto;
  max-height: 40%;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.tabs::-webkit-scrollbar { width: 6px; }
.tabs::-webkit-scrollbar-track { background: transparent; }
.tabs::-webkit-scrollbar-thumb { background: var(--border); border-radius: 100px; }
/* .tabs' max-height:40% + scroll was sized for the old 14-item grid sharing
   the sidebar with .panels below it. #editsTabs only ever holds 4 items
   (Crop/Heal/Advanced/Batch) -- capping it to 40% of the sidebar left a
   large dead scrollable area above the actual Crop/Heal/etc. panel content
   that had nothing to do with those 4 buttons' real height. */
#editsTabs {
  max-height: none;
  overflow: visible;
}
.tabs::-webkit-scrollbar-button { display: none; width: 0; height: 0; }

.tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text-dim);
  padding: 8px 5px;
  font-size: 10px;
  font-weight: 600;
  font-family: inherit;
  text-align: center;
  cursor: pointer;
  position: relative;
}

.tab svg { width: 15px; height: 15px; flex-shrink: 0; }

.tab:hover { color: var(--text); border-color: var(--accent-line); }
.tab.active { background: var(--accent-soft); border-color: var(--accent); color: var(--text); box-shadow: 0 4px 16px var(--accent-line); }
.tab.active svg { color: var(--accent); }

.badge-new {
  position: absolute;
  top: 3px;
  right: 4px;
  font-size: 7px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: #fff;
  background: var(--accent);
  padding: 1px 4px;
  border-radius: 5px;
}

.panels {
  flex: 1;
  min-height: 0;
  padding: 13px;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.panels::-webkit-scrollbar { width: 6px; }
.panels::-webkit-scrollbar-track { background: transparent; }
.panels::-webkit-scrollbar-thumb { background: var(--border); border-radius: 100px; }
.panels::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
/* Hidden by main.js's supertab handler while Tools is active -- Tools'
   panels live inside #toolsAccordion now (see main.js), not here, so an
   unconditionally flex:1 .panels would sit empty and still claim half the
   sidebar's height as a sibling flex item. */
.panels[hidden] { display: none; }
.panel { display: none; }
.panel.active { display: block; }

/* ---------- Supertabs (Presets / Tools / Edits) ---------- */
.supertabs {
  display: flex;
  gap: 4px;
  padding: 10px 10px 0;
  flex-shrink: 0;
}
.supertab {
  flex: 1;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 9px;
  color: var(--text-dim);
  padding: 7px 0;
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.supertab:hover { color: var(--text); }
.supertab.active { background: var(--panel-2); border-color: var(--border); color: var(--text); }

.super-body { flex-shrink: 0; }
.super-body[hidden] { display: none; }
/* Tools and History are the two groups whose content lives in the
   super-body itself rather than in .panels below -- they're the ones that
   need to grow and scroll like .panels normally does. */
.super-body[data-super-body="tools"],
.super-body[data-super-body="history"] {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.super-body[data-super-body="tools"]::-webkit-scrollbar,
.super-body[data-super-body="history"]::-webkit-scrollbar { width: 6px; }
.super-body[data-super-body="tools"]::-webkit-scrollbar-thumb,
.super-body[data-super-body="history"]::-webkit-scrollbar-thumb { background: var(--border); border-radius: 100px; }

/* Presets' one-and-only tab exists purely so the shared .tab/.panel
   active-class mechanism has something to click (see the HTML comment
   above it) -- never meant to be seen. */
.super-body[data-super-body="presets"] .tab { display: none; }

.histogram {
  position: relative;
  height: 68px;
  margin: 10px 10px 4px;
  /* Top padding is real headroom, not just a bigger box -- bar heights are
     percentages of this padding-box (box-sizing:border-box), so a 100%-tall
     bar now stops short of the container's edge instead of touching it. */
  padding: 10px 2px 0;
  border-radius: 8px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  cursor: pointer;
}
.histogram-view { width: 100%; height: 100%; }
.histogram-view[hidden] { display: none; }

.histogram-bars {
  display: flex;
  align-items: flex-end;
  gap: 1px;
}
.histogram-bars i {
  flex: 1;
  min-height: 1px;
  border-radius: 1px 1px 0 0;
  background: var(--text-faint);
  transition: height 0.1s ease-out, background 0.1s ease-out;
}
/* Bars past the shadow/highlight thresholds pick up the accent color, same
   idea as a real histogram's clipped-highlight warning -- see updateHistogram
   in main.js for the exact split. */
.histogram-bars i.hi { background: var(--accent); }

/* Lightroom-style overlaid RGB: three full-color bar layers stacked with
   mix-blend-mode:screen, which additively mixes light the same way real
   overlapping color channels do -- red+green reads as yellow, all three
   together as white, with no per-bar alpha math needed to get there. */
.histogram-rgb { position: relative; }
.histogram-rgb-layer {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  gap: 1px;
  mix-blend-mode: screen;
}
.histogram-rgb-layer i { flex: 1; min-height: 1px; border-radius: 1px 1px 0 0; transition: height 0.1s ease-out; }
#histogramRgbR i { background: #ff3b3b; }
#histogramRgbG i { background: #2ee66b; }
#histogramRgbB i { background: #3b82ff; }

/* RGB parade: R/G/B each get their own third of the width, x-axis is the
   photo's own horizontal position, y-axis is that channel's value -- a
   real scope, not decoration. Rendered as pixel density, not vector shapes
   (see renderWaveform in main.js), so it's a <canvas>, not DOM bars. */
.histogram-waveform { display: block; border-radius: 6px; }

.tool-accordion {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 8px 10px;
}
/* Full-width accordion row instead of the 2-col grid tile .tab normally
   is (that grid layout is still exactly what #editsTabs uses, unchanged --
   this only applies inside the Tools accordion specifically). */
#toolsAccordion.tool-accordion .tab {
  flex-direction: row;
  justify-content: flex-start;
  gap: 10px;
  padding: 9px 10px;
  text-align: left;
  font-size: 12.5px;
}
#toolsAccordion.tool-accordion .tab .tab-label { flex: 1; }
.acc-chev { flex-shrink: 0; color: var(--text-faint); transition: transform 0.15s ease; }
#toolsAccordion.tool-accordion .tab.active .acc-chev { transform: rotate(180deg); color: var(--accent); }

/* The panel now sits directly under its accordion row (see the relocation
   in main.js) instead of in a separate .panels area -- gets its own
   padding/border here since it lost .panels' padding by moving out of it. */
#toolsAccordion .panel {
  padding: 12px 4px 4px;
  margin: -3px 2px 2px;
  border-left: 2px solid var(--accent-line);
  padding-left: 14px;
}

/* Update status: pinned to the bottom of the sidebar column (the panels above
   take flex:1, so this stays at the bottom). Shows the running version at a
   glance, and flips to a highlighted "update available" state when a newer
   build is published. */
.sidebar-footer {
  flex-shrink: 0;
  padding: 8px 10px;
  border-top: 1px solid var(--border);
}

.update-status {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 9px;
  border-radius: 11px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  text-align: left;
  position: relative;
}
.update-status:hover { border-color: var(--accent-line); }

.update-status-icon {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-soft);
  color: var(--accent);
}
.update-status-icon svg { width: 14px; height: 14px; }
.update-status-icon .icon-download { display: none; }

.update-status-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1;
}
.update-status-title {
  font-size: 12.5px;
  font-weight: 650;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.update-status-sub {
  font-size: 10.5px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.update-status-dot {
  width: 9px;
  height: 9px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 0 var(--accent-line);
  animation: update-pulse 2s ease-out infinite;
}
.update-status-dot[hidden] { display: none; }

@keyframes update-pulse {
  0%   { box-shadow: 0 0 0 0 var(--accent-line); }
  70%  { box-shadow: 0 0 0 7px rgba(0,0,0,0); }
  100% { box-shadow: 0 0 0 0 rgba(0,0,0,0); }
}
@media (prefers-reduced-motion: reduce) {
  .update-status-dot { animation: none; }
}

/* highlighted "update available" state */
.update-status.has-update {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.update-status.has-update .update-status-icon {
  background: var(--accent);
  color: #fff;
}
.update-status.has-update .update-status-icon .icon-check { display: none; }
.update-status.has-update .update-status-icon .icon-download { display: block; }
.update-status.has-update .update-status-title { color: var(--accent); }

.hint {
  color: var(--text-dim);
  font-size: 11.5px;
  line-height: 1.45;
  margin: 0 0 10px;
}

/* ---------- History panel ---------- */
.history-list {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 10px;
}

.history-item {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 11px;
  padding: 7px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: inherit;
  width: 100%;
}
.history-item:hover { border-color: var(--accent-line); }
/* "Current" (the live, uncommitted edit state) isn't a jump target -- it's
   already where you are, so it reads as pinned/highlighted rather than
   clickable like every past step below it. */
.history-item.current { border-color: var(--accent); background: var(--accent-soft); cursor: default; }

.history-thumb {
  width: 42px;
  height: 56px;
  flex-shrink: 0;
  border-radius: 6px;
  background: var(--panel);
  object-fit: cover;
  display: block;
}
/* Shown until generateHistoryThumb finishes its (real, pipeline-run)
   thumbnail for this entry -- a flat placeholder, not a spinner, since
   most entries resolve in well under a second. */
.history-thumb.pending { background: linear-gradient(135deg, var(--panel), var(--panel-2)); }

.history-text { min-width: 0; }
.history-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.history-sub {
  font-size: 10px;
  color: var(--text-dim);
  margin-top: 1px;
}
.history-empty {
  color: var(--text-dim);
  font-size: 12px;
  padding: 20px 10px;
  text-align: center;
}

/* A vertical strip -- each Look gets a full-width row (thumbnail + name +
   description) stacked top to bottom, scrolling with the rest of the panel
   rather than competing for height as its own horizontal band. */
.looks-grid {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.look-card {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 11px;
  padding: 7px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: inherit;
  width: 100%;
}

.look-card:hover { border-color: var(--accent-line); }
.look-card:active { border-color: var(--accent); }

.look-thumb {
  width: 42px;
  height: 42px;
  flex-shrink: 0;
  border-radius: 7px;
  display: block;
  background: var(--panel);
}

.look-text { min-width: 0; }

.look-name {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.look-desc {
  font-size: 10px;
  color: var(--text-dim);
  margin-top: 1px;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Each Look is now a category of 5 variants -- the category row expands in
   place (accordion, one open at a time) to reveal them, rather than routing
   to a separate screen, so the panel stays a single scrollable list. */
.look-category-head { position: relative; }
.look-category-chevron {
  flex-shrink: 0;
  color: var(--text-faint);
  transition: transform 0.15s ease;
}
.look-category-head.expanded .look-category-chevron { transform: rotate(180deg); color: var(--accent); }
.look-category-head.expanded { border-color: var(--accent-line); }

.look-variants {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 2px 0 2px 15px;
  padding-left: 10px;
  border-left: 2px solid var(--border);
}
.look-variants[hidden] { display: none; }

.look-variant-card {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 5px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: inherit;
  width: 100%;
}
.look-variant-card:hover { border-color: var(--accent-line); }
.look-variant-card:active, .look-variant-card.active { border-color: var(--accent); }

.look-variant-thumb {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border-radius: 6px;
  display: block;
  background: var(--panel);
}

.look-variant-name {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
  min-width: 0;
}

/* ---------- Custom Looks: user-saved presets ---------- */
.look-custom-actions {
  display: flex;
  gap: 6px;
  margin: 2px 0 8px 18px;
}
.look-custom-action-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 7px 6px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.look-custom-action-btn:hover { border-color: var(--accent-line); color: var(--text); }
.look-custom-action-btn svg { width: 13px; height: 13px; flex-shrink: 0; }

.look-custom-save-row {
  display: flex;
  gap: 6px;
  margin: 0 0 8px 18px;
}
.look-custom-save-row input[type="text"] {
  flex: 1;
  min-width: 0;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 10px;
  color: var(--text);
  font-size: 12.5px;
  font-family: inherit;
}
.look-custom-save-row button {
  flex-shrink: 0;
  padding: 0 12px;
  border-radius: 8px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.look-custom-save-row button.ghost { background: transparent; color: var(--text-dim); border-color: var(--border); }

.look-variant-actions {
  display: flex;
  gap: 2px;
  flex-shrink: 0;
  margin-left: auto;
}
.look-variant-icon-btn {
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--text-faint);
  border-radius: 6px;
  cursor: pointer;
  padding: 0;
}
.look-variant-icon-btn:hover { color: var(--text); background: var(--panel); }
.look-variant-icon-btn.danger:hover { color: #e05555; }
.look-variant-icon-btn svg { width: 13px; height: 13px; }

.look-custom-empty {
  font-size: 11.5px;
  color: var(--text-faint);
  padding: 4px 2px 8px;
  line-height: 1.5;
}

.look-custom-status {
  font-size: 11px;
  color: var(--text-faint);
  padding: 0 2px 6px;
  min-height: 14px;
}

.slider-row {
  display: grid;
  /* Trailing 20px track is only populated on the Adjust tab's 9 sliders
     (see .mini-preview below) -- every other slider-row just leaves it
     empty, which is harmless. */
  grid-template-columns: 82px 1fr 26px 20px 20px;
  align-items: center;
  gap: 5px;
  font-size: 11.5px;
  color: var(--text-dim);
  margin-bottom: 11px;
}

/* .glass-range is the same treatment as .slider-row's sliders, applied
   directly to a handful of standalone range inputs elsewhere (Passport's
   Straighten slider, Filters' export-quality slider, Culling's wizard
   sliders) that don't sit inside a .slider-row grid -- same rules, just not
   gated behind that specific container so it works wherever a single
   full-width range needs it. */
.slider-row input[type="range"],
.tool-slider-row input[type="range"],
input[type="range"].glass-range {
  width: 100%;
  height: 16px;
  /* -webkit-appearance:none on the THUMB alone (below) wasn't enough to make
     it position predictably -- Chrome was still running its native layout
     algorithm for the *track/input as a whole* and placing the thumb
     relative to THAT, not simple CSS box math, which is why margin-top kept
     landing it off-center no matter what value was tried. Disabling native
     appearance on the input itself (Firefox ignores this -webkit-prefixed
     line entirely, so its unrelated ::-moz-range-progress auto-fill below
     is untouched) makes the thumb's position purely a function of the CSS
     here, which IS predictable. Trade-off: accent-color's automatic native
     track fill no longer applies once appearance is fully custom, so the
     fill is now painted explicitly via --fill below (kept in sync by
     updateSliderFill() -- in main.js for Portrait Pro, and a small
     page-local copy of the same helper for Passport/Culling/Filters, since
     those pages don't load main.js) instead of coming "free" from the
     browser. */
  -webkit-appearance: none;
  background: transparent;
  cursor: pointer;
}
.slider-row input[type="range"].carrying,
.tool-slider-row input[type="range"].carrying,
input[type="range"].glass-range.carrying { cursor: grabbing; }
.slider-row input[type="range"]::-webkit-slider-runnable-track,
.tool-slider-row input[type="range"]::-webkit-slider-runnable-track,
input[type="range"].glass-range::-webkit-slider-runnable-track {
  height: 5px;
  border-radius: 999px;
  background: linear-gradient(to right, var(--accent) 0%, var(--accent) var(--fill, 0%), var(--border) var(--fill, 0%), var(--border) 100%);
}
/* Firefox still uses its own native appearance -- ::-moz-range-progress
   auto-fills from the value with no JS needed, so it's left exactly as it
   already worked. */
.slider-row input[type="range"]::-moz-range-track,
.tool-slider-row input[type="range"]::-moz-range-track,
input[type="range"].glass-range::-moz-range-track { height: 5px; border-radius: 999px; background: var(--border); }
.slider-row input[type="range"]::-moz-range-progress,
.tool-slider-row input[type="range"]::-moz-range-progress,
input[type="range"].glass-range::-moz-range-progress { height: 5px; border-radius: 999px 0 0 999px; background: var(--accent); }
/* Sized to match Filters Pro's .insp-thumb (9px base, ~1.4x on hover/active/
   carrying) -- that page's thumb read as the "right" size, so every other
   product's native-range thumb now matches it instead of each page having
   its own slightly different feel. margin-top: (trackHeight 5px - thumbHeight
   9px) / 2 -- this formula is only reliable now that the INPUT itself has
   -webkit-appearance:none (above); it wasn't before, which is why the thumb
   kept rendering off-center regardless of what this value was set to. */
.slider-row input[type="range"]::-webkit-slider-thumb,
.tool-slider-row input[type="range"]::-webkit-slider-thumb,
input[type="range"].glass-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 1.5px var(--accent), 0 0 5px var(--accent-line), 0 1px 2px rgba(0,0,0,0.35);
  margin-top: -2px;
  transform: scale(1);
  transition: transform 0.12s ease;
}
.slider-row input[type="range"]:hover::-webkit-slider-thumb,
.slider-row input[type="range"]:active::-webkit-slider-thumb,
.slider-row input[type="range"].carrying::-webkit-slider-thumb,
.tool-slider-row input[type="range"]:hover::-webkit-slider-thumb,
.tool-slider-row input[type="range"]:active::-webkit-slider-thumb,
.tool-slider-row input[type="range"].carrying::-webkit-slider-thumb,
input[type="range"].glass-range:hover::-webkit-slider-thumb,
input[type="range"].glass-range:active::-webkit-slider-thumb,
input[type="range"].glass-range.carrying::-webkit-slider-thumb {
  transform: scale(1.4);
}
.slider-row input[type="range"]::-moz-range-thumb,
.tool-slider-row input[type="range"]::-moz-range-thumb,
input[type="range"].glass-range::-moz-range-thumb {
  width: 9px; height: 9px;
  border: none;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 1.5px var(--accent), 0 0 5px var(--accent-line), 0 1px 2px rgba(0,0,0,0.35);
  transform: scale(1);
  transition: transform 0.12s ease;
}
.slider-row input[type="range"]:hover::-moz-range-thumb,
.slider-row input[type="range"]:active::-moz-range-thumb,
.slider-row input[type="range"].carrying::-moz-range-thumb,
.tool-slider-row input[type="range"]:hover::-moz-range-thumb,
.tool-slider-row input[type="range"]:active::-moz-range-thumb,
.tool-slider-row input[type="range"].carrying::-moz-range-thumb,
input[type="range"].glass-range:hover::-moz-range-thumb,
input[type="range"].glass-range:active::-moz-range-thumb,
input[type="range"].glass-range.carrying::-moz-range-thumb {
  transform: scale(1.4);
}
/* The track itself gets a subtle glow while a slider is being carried, since
   there's no held mouse button to signal "this one's live" the way :active
   normally would. */
.slider-row input[type="range"].carrying::-webkit-slider-runnable-track,
.tool-slider-row input[type="range"].carrying::-webkit-slider-runnable-track,
input[type="range"].glass-range.carrying::-webkit-slider-runnable-track { box-shadow: 0 0 0 1px var(--accent-line); }
.slider-row input[type="range"].carrying::-moz-range-track,
.tool-slider-row input[type="range"].carrying::-moz-range-track,
input[type="range"].glass-range.carrying::-moz-range-track { box-shadow: 0 0 0 1px var(--accent-line); }

/* While a slider is picked up in carry mode, the mouse can be anywhere on
   the page (carry tracking listens on the whole document, not just the
   slider) -- swapping the cursor for a circle matching the thumb's own
   look, instead of leaving whatever cursor happens to sit under the mouse
   (a button's pointer, a text field's I-beam, etc.), keeps it visually clear
   the whole time that a slider is still "held." Applied to <html> with
   !important so it wins over every element's own cursor rule underneath;
   toggled by main.js and slider-glass.js's carry-start/stop, not CSS alone. */
html.slider-carrying, html.slider-carrying * {
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18'%3E%3Ccircle cx='9' cy='9' r='7' fill='white' stroke='%23222' stroke-width='1.5'/%3E%3C/svg%3E") 9 9, grab !important;
}

/* Scroll-wheel-to-adjust target (see the wheel listener in main.js) --
   ns-resize signals "scroll here to change this" the same way it would on
   a native number input's spinner. */
.slider-row .val,
.tool-slider-row .val {
  color: var(--text);
  text-align: right;
  font-variant-numeric: tabular-nums;
  cursor: ns-resize;
  border-radius: 4px;
}
.slider-row .val:hover,
.tool-slider-row .val:hover { background: var(--panel-2); }

.mini-reset {
  width: 20px;
  height: 20px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: transparent;
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}

.mini-reset:hover { color: var(--accent); border-color: var(--accent); }

/* Adjust tab only (see the ADJUST_PREVIEW_KEYS loop in main.js) -- hold to
   isolate this one slider's current value, every other adjustment
   temporarily zeroed, so its effect can be judged on its own. Same 20x20
   icon-button footprint as .mini-reset, sitting right next to it. */
.mini-preview {
  width: 20px;
  height: 20px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.mini-preview svg { width: 12px; height: 12px; }
.mini-preview:hover { color: var(--accent); border-color: var(--accent); }
.mini-preview.active { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

.swatches {
  display: flex;
  gap: 8px;
  margin: -8px 0 14px;
  flex-wrap: wrap;
}

.swatch {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
}

.swatch.selected { border-color: var(--accent); }

.swatch.none-swatch {
  background: repeating-linear-gradient(45deg, #444 0 3px, #222 3px 6px);
}

/* Rainbow-ring "pick anything" swatch, appended after each preset list --
   a native <input type="color"> sits transparently on top so clicking
   anywhere on the circle opens the OS color picker, while the visible
   circle itself stays under CSS's control (the input's own default
   swatch/border styling varies wildly by browser and looks nothing like
   the flat preset dots next to it). Once a color is actually picked,
   .picked swaps the rainbow ring for the picked color and hides the "+"
   -- reads as a normal swatch from then on, remembering your last pick. */
.swatch.custom-swatch {
  position: relative;
  display: inline-block;
  background: conic-gradient(from 180deg, #ff0000, #ffea00, #2bff00, #00e5ff, #1a3bff, #d600ff, #ff0000);
  overflow: hidden;
}
.swatch.custom-swatch input[type="color"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  min-width: 100%;
  min-height: 100%;
  opacity: 0;
  cursor: pointer;
  border: none;
  padding: 0;
  appearance: none;
}
.swatch.custom-swatch::after {
  content: "+";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  text-shadow: 0 1px 2px rgba(0,0,0,0.65);
  pointer-events: none;
}
.swatch.custom-swatch.picked::after {
  content: none;
}

.canvas-area {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  /* "safe" is the fix for a well-known flex/scroll trap: with plain
     center/center, scrollTop's minimum of 0 can't represent the position
     needed to reach content that overflows ABOVE center once zoomed in --
     there's full scroll range toward the bottom but the top becomes
     unreachable, which is exactly "panning always ends up at the bottom of
     the image." "safe" tells the browser to fall back to start-alignment
     on any axis that actually overflows, so scrollTop=0 means the real top
     of the content again and the full range is reachable in both
     directions. Content that still fits (e.g. Fit zoom) still centers
     normally -- this only changes behavior once something overflows.
  */
  align-items: safe center;
  justify-content: safe center;
  overflow: auto;
  /* The one thing "Switch appearance" still controls -- see --canvas-bg
     above. Everything else in the chrome is a fixed constant now; this used
     to be var(--bg) which changed with the same light/dark toggle that
     changed the whole app's chrome. */
  background: var(--canvas-bg);
  transition: background-color 0.2s ease;
}

/* Before a photo is open there's nothing behind the drop-zone card for its
   backdrop-filter glass to blur -- var(--canvas-bg) is a single flat color,
   so the "glass" card was rendering as a plain flat-tinted rectangle and
   reading as generic, not premium. Swaps in a moody ambient gradient ONLY
   for this empty state (via :has(), scoped to canvasWrap being hidden) so
   the glass effect actually has texture to catch -- once a real photo
   loads, canvas-area reverts to the neutral --canvas-bg grey/white surface
   that color-accurate editing actually needs. */
.canvas-area:has(#canvasWrap[hidden]) {
  background: radial-gradient(120% 90% at 50% 28%, color-mix(in srgb, var(--accent) 14%, var(--bg)), var(--bg) 65%);
}

.pre-upload-screen {
  display: none;
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 560px;
  padding: 24px;
}

.pre-upload-screen:not([hidden]) {
  display: flex;
}

.drop-zone {
  border: 1.5px dashed var(--border);
  border-radius: 20px;
  padding: 60px 40px;
  color: var(--text-dim);
  text-align: center;
  background: var(--panel);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-strong);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  min-height: 380px;
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease;
}

.drop-zone:hover {
  border-color: var(--accent-line);
  background: var(--glass-strong);
}

.drop-zone .cloud-icon {
  width: 22px;
  height: 22px;
  padding: 15px;
  border-radius: 16px;
  color: var(--accent);
  background: var(--accent-soft);
  box-shadow: 0 0 24px var(--accent-line);
  margin-bottom: 2px;
}

.drop-zone p:first-of-type {
  font-size: 18px;
  font-weight: 650;
  color: var(--text);
  margin: 0;
  letter-spacing: -0.01em;
}

.drop-zone-sub {
  margin: 0;
  font-size: 13px;
  color: var(--text-dim);
}

.offline-note {
  margin-top: 8px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--text-faint);
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 100px;
}

.offline-note svg { width: 12px; height: 12px; color: #34c759; flex-shrink: 0; }

/* ---------- AI panel + toggle rows (upload screen + Advanced tab) ---------- */
.ai-panel {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 16px;
  display: flex;
  flex-direction: column;
  min-height: 380px;
  overflow: hidden;
}

.ai-panel-head {
  padding: 20px 20px 14px;
  border-bottom: 1px solid var(--border);
}

.ai-panel-head h3 {
  margin: 0 0 4px;
  font-size: 16px;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.ai-panel-head p {
  margin: 0;
  font-size: 12px;
  color: var(--text-dim);
  line-height: 1.5;
}

.tool-list {
  flex: 1;
  overflow-y: auto;
  padding: 6px 8px;
}

.tool-row {
  padding: 13px 12px;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.tool-row:hover { background: rgba(255,255,255,0.03); }

.tool-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.tool-text h4 {
  margin: 0 0 2px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text);
}

.tool-text p {
  margin: 0;
  font-size: 11.5px;
  color: var(--text-dim);
  line-height: 1.4;
}

.tool-slider-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.tool-slider-row input[type="range"] {
  flex: 1;
  accent-color: var(--accent);
}

.tool-slider-row .val {
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-dim);
  width: 28px;
  text-align: right;
}

.tool-row.is-on .val { color: var(--accent); }
.tool-row.is-off { opacity: 0.5; }
.tool-row.is-off input[type="range"] { pointer-events: none; }

.tool-list { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
.tool-list::-webkit-scrollbar { width: 6px; }
.tool-list::-webkit-scrollbar-track { background: transparent; }
.tool-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 100px; }
.tool-list::-webkit-scrollbar-button { display: none; width: 0; height: 0; }

.ai-panel-foot {
  padding: 11px 20px;
  border-top: 1px solid var(--border);
  font-size: 11px;
  color: var(--text-faint);
  text-align: center;
}

.ai-panel-foot strong { color: var(--text-dim); font-weight: 600; }

.brush-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 10px;
  font-weight: 600;
  color: var(--text-dim);
  background: var(--panel);
  border: 1px solid var(--border);
  padding: 3px 8px;
  border-radius: 100px;
  width: fit-content;
}

.brush-tag svg { width: 10px; height: 10px; }

/* iOS-style toggle switch */
.switch {
  position: relative;
  width: 38px;
  height: 22px;
  flex-shrink: 0;
  margin-top: 1px;
}

.switch input { opacity: 0; width: 0; height: 0; position: absolute; }

.switch .track {
  position: absolute;
  inset: 0;
  background: var(--border);
  border-radius: 100px;
  transition: background 0.2s ease;
  cursor: pointer;
}

.switch .thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.2s ease;
  box-shadow: 0 1px 3px rgba(0,0,0,0.3);
  pointer-events: none;
}

.switch input:checked + .track { background: var(--accent); }
.switch input:checked + .track + .thumb { transform: translateX(16px); }

.upload-error {
  color: #f2c14e;
  font-size: 13px;
  margin-top: 10px;
  max-width: 320px;
}

.canvas-wrap {
  position: relative;
  margin: auto;
  flex-shrink: 0;
}

#viewCanvas {
  display: block;
  border-radius: 4px;
  box-shadow: 0 8px 30px rgba(0,0,0,0.5);
  cursor: crosshair;
  /* Safari/macOS treats a click-drag over image content as a native
     text/image-selection gesture unless explicitly told not to, which
     cancels the pointer sequence mid-drag (works fine on Windows Chrome,
     silently breaks on macOS Safari). */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-user-drag: none;
}

/* Heal/Stray-Hair brush-size cursor -- a real CSS `cursor` can't easily
   resize live as the user drags the Brush size slider (would mean
   re-encoding a data-URI cursor image on every input tick), so this is a
   fixed-position DOM circle that follows the pointer instead, sized in
   screen pixels to the brush's actual on-canvas footprint at the current
   zoom. Double-ring (light outer / dark inner) so it stays visible over
   both light and dark parts of the photo, same convention Photoshop's
   brush cursor uses. */
.brush-cursor {
  position: fixed;
  border-radius: 50%;
  border: 1.5px solid rgba(255,255,255,0.95);
  box-shadow: 0 0 0 1px rgba(0,0,0,0.65), inset 0 0 0 1px rgba(0,0,0,0.35);
  pointer-events: none;
  z-index: 50;
  transform: translate(-50%, -50%);
}
.brush-cursor[hidden] { display: none; }

/* Shown app-wide (toolbar, sidebar, the grey area around the photo, not
   just the canvas) while the photo-load overlay is scanning -- detection
   has nothing to do with which element happens to be under the pointer.
   A NATIVE cursor keyword, not a custom DOM element (see setScanningCursor
   in main.js's own comment for why): detectFace()/detectPersonSegmentation
   block the main thread for real stretches of the scan, and only a
   compositor-rendered cursor keeps moving with the actual pointer through
   that -- a JS-positioned element can't update while JS itself can't run,
   which a custom ring tried and was reported "stuck" for exactly that
   reason. Buttons/links/sliders all set their own `cursor` (pointer, grab,
   etc.), which would otherwise win over a plain `body { cursor: progress }`
   since a more specific selector beats a less specific one regardless of
   source order -- !important plus the same `*` specificity on every
   element is what actually overrides all of them. */
html.pl-scanning,
html.pl-scanning * {
  cursor: progress !important;
}

/* Alt held in Heal Brush mode: Alt+Click sets the sample point instead of
   painting, so the brush circle swaps to a dashed target/reticle -- same
   idea as Photoshop's own cursor swap for this gesture -- instead of
   staying identical to the normal paint cursor, which gave no visual clue
   the next click would do something completely different. Neutral
   white/black like the rest of this cursor, not a new color, since a loud
   color here was already tried and found distracting (see heal-source-
   cursor's own history above). */
.brush-cursor.picking-source { border-style: dashed; }
.brush-cursor.picking-source::before,
.brush-cursor.picking-source::after {
  content: "";
  position: absolute;
  background: rgba(255,255,255,0.95);
  box-shadow: 0 0 0 1px rgba(0,0,0,0.65);
}
.brush-cursor.picking-source::before { left: 50%; top: 0; width: 1.5px; height: 100%; transform: translateX(-50%); }
.brush-cursor.picking-source::after { top: 50%; left: 0; height: 1.5px; width: 100%; transform: translateY(-50%); }

/* Small crosshair marking the Heal Brush's donor point WHILE a stroke is
   actually being painted -- Photoshop shows this same small "+" only during
   the paint gesture, not on idle hover, so it doesn't clutter the canvas
   the rest of the time (see updateHealSourceCursor's isHealPainting gate).
   Plain white/black, matching brush-cursor's own double-tone convention,
   rather than a bright accent color competing with the photo underneath. */
.heal-source-cursor {
  position: fixed;
  width: 12px;
  height: 12px;
  pointer-events: none;
  z-index: 51;
  transform: translate(-50%, -50%);
}
.heal-source-cursor::before, .heal-source-cursor::after {
  content: "";
  position: absolute;
  background: rgba(255,255,255,0.95);
  box-shadow: 0 0 0 1px rgba(0,0,0,0.65);
}
.heal-source-cursor::before { left: 5px; top: 0; width: 1.5px; height: 12px; }
.heal-source-cursor::after { left: 0; top: 5px; width: 12px; height: 1.5px; }
.heal-source-cursor[hidden] { display: none; }

/* Thin dashed line from the brush to its source crosshair, shown only
   alongside it during an active stroke -- without this, two same-ish
   floating shapes near each other on a photo would read as two unrelated
   cursors, not "here's your brush, here's where it samples from" (same
   reason Photoshop draws this connector). A 1px-tall div, positioned at the
   brush and rotated to point at the source, scaled to the exact distance
   between them. */
.heal-source-line {
  position: fixed;
  height: 0;
  border-top: 1.5px dashed rgba(255,255,255,0.75);
  filter: drop-shadow(0 0 1px rgba(0,0,0,0.65));
  pointer-events: none;
  z-index: 49;
  transform-origin: 0 0;
}
.heal-source-line[hidden] { display: none; }

/* Side-by-side before/after (toggled with Y, Lightroom-style). Fills the
   stage above the ambient backdrop; each photo is a replaced <canvas> that
   scales to fit its pane on its own, so no JS resize handling is needed. */
.compare-view {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  gap: 18px;
  align-items: center;
  justify-content: center;
  padding: 28px;
}
.compare-view[hidden] { display: none; }

.compare-pane {
  position: relative;
  flex: 1;
  min-width: 0;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden; /* clips the canvas once scroll-wheel zoom scales it past the pane */
  border-radius: 8px;
  cursor: zoom-in;
}

.compare-pane canvas {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  border-radius: 8px;
  box-shadow: 0 14px 36px rgba(0,0,0,0.32), 0 0 0 1px rgba(255,255,255,0.3);
}

.compare-label {
  position: absolute;
  top: 12px;
  left: 12px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: 8px;
  color: #fff;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  pointer-events: none;
}

.zoom-bar {
  position: fixed;
  bottom: 16px;
  right: 16px;
  display: none;
  align-items: center;
  gap: 6px;
  background: var(--panel);
  border: 1px solid var(--border);
  padding: 5px 8px;
  border-radius: 12px;
  z-index: 25;
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-shadow);
}

.zoom-bar:not([hidden]) {
  display: flex;
}

.zoom-btn {
  width: 24px;
  height: 24px;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 0;
}

.zoom-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.zoom-btn:disabled { opacity: 0.4; cursor: not-allowed; }

.zoom-label {
  color: var(--text-dim);
  font-size: 12px;
  min-width: 42px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.face-status {
  position: absolute;
  top: 8px;
  left: 8px;
  font-size: 11px;
  padding: 4px 8px;
  border-radius: 4px;
  background: rgba(0,0,0,0.55);
  color: var(--text-dim);
}

.face-status.ok { color: #7ee787; }
.face-status.warn { color: #f2c14e; }

.seg-status { top: 34px; }

.crop-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
}

.crop-mask {
  position: absolute;
  background: rgba(0,0,0,0.6);
  /* Deliberately NOT pointer-events:none -- this soaks up clicks outside the
     crop rect so they don't fall through to viewCanvas's heal/pan handlers
     underneath while the crop overlay is showing. */
}

.crop-rect {
  position: absolute;
  border: 1.5px solid #fff;
  box-shadow: 0 0 0 1px var(--accent);
  cursor: move;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-user-drag: none;
}

.crop-handle {
  position: absolute;
  width: 14px;
  height: 14px;
  margin: -7px;
  background: var(--accent);
  border: 2px solid #fff;
  border-radius: 50%;
  box-shadow: 0 1px 4px rgba(0,0,0,0.5);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

.crop-handle[data-handle="nw"] { top: 0; left: 0; cursor: nwse-resize; }
.crop-handle[data-handle="ne"] { top: 0; left: 100%; cursor: nesw-resize; }
.crop-handle[data-handle="sw"] { top: 100%; left: 0; cursor: nesw-resize; }
.crop-handle[data-handle="se"] { top: 100%; left: 100%; cursor: nwse-resize; }

/* Generic segmented toggle -- same visual language as the crop-aspect-row
   buttons, reused for the Heal panel's Spot Heal/Heal Brush switch. */
.seg-toggle-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin: 10px 0;
}
.seg-toggle-btn {
  padding: 6px 12px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text-dim);
  font-size: 12px;
  cursor: pointer;
}
.seg-toggle-btn:hover { border-color: var(--accent-line); color: var(--text); }
.seg-toggle-btn.active { background: var(--accent-soft); border-color: var(--accent); color: var(--text); }

.crop-aspect-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin: 10px 0;
}

.crop-aspect-btn {
  padding: 6px 12px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text-dim);
  font-size: 12px;
  cursor: pointer;
}

.crop-aspect-btn:hover { border-color: var(--accent-line); color: var(--text); }
.crop-aspect-btn.active { background: var(--accent-soft); border-color: var(--accent); color: var(--text); }

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

.crop-actions .btn { flex: 1; }

/* ---------- Keyboard Shortcuts help panel (F1, or the "?" topbar button) --
   built and injected entirely by shortcuts-help.js, shared across every
   page that loads it. Uses the same glass tokens as the rest of the chrome
   (--panel/--glass-blur/--border) so it reads as part of this app, not a
   bolted-on dialog. */
.shortcuts-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(10, 8, 16, 0.5);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  padding: 24px;
}
.shortcuts-overlay[hidden] { display: none; }

/* Bigger, more genuinely-translucent glass than the app's everyday
   --glass-blur panels -- same heavier vibrancy recipe as the post-export
   dialog's .pxd-dialog (blur(34px) saturate(1.9)), since this is a rare,
   full-attention overlay rather than everyday chrome. The border leans on
   color-mix to fade it toward transparent rather than a flat, fully-opaque
   line, so the card's edge reads as a soft glass rim catching light instead
   of a drawn outline -- reinforced by the inset top highlight in the
   shadow, the same trick .pxd-dialog uses. */
.shortcuts-card {
  position: relative;
  width: 100%;
  max-width: 720px;
  max-height: 88vh;
  overflow-y: auto;
  padding: 40px 44px 34px;
  border-radius: 26px;
  border: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
  background: linear-gradient(180deg, var(--glass-strong), color-mix(in srgb, var(--glass-strong) 55%, transparent));
  backdrop-filter: blur(34px) saturate(1.9);
  -webkit-backdrop-filter: blur(34px) saturate(1.9);
  box-shadow: var(--glass-shadow), inset 0 1px 0 color-mix(in srgb, var(--glass-strong) 70%, transparent);
}

.shortcuts-close {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  background: var(--panel-2);
  color: var(--text-dim);
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.shortcuts-close:hover { color: var(--text); }

.shortcuts-eyebrow {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 7px;
}
.shortcuts-card h2 { font-size: 23px; margin: 0 0 28px; letter-spacing: -0.01em; color: var(--text); }

.shortcuts-group { margin-bottom: 26px; }
.shortcuts-group:last-of-type { margin-bottom: 4px; }
.shortcuts-group h3 {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0 0 14px;
}

.shortcuts-list { display: flex; flex-direction: column; gap: 15px; }
.shortcuts-row { display: flex; align-items: baseline; justify-content: space-between; gap: 20px; }
.shortcuts-keys { flex-shrink: 0; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.shortcuts-combo { display: inline-flex; align-items: center; }
.shortcuts-desc { font-size: 14.5px; color: var(--text-dim); text-align: right; }
.shortcuts-note { display: block; font-size: 12px; color: var(--text-faint); margin-top: 3px; }

.shortcuts-kbd {
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  padding: 4px 9px;
  border-radius: 7px;
  border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  background: var(--panel-2);
  color: var(--text);
}
.shortcuts-plus, .shortcuts-or { font-size: 11.5px; color: var(--text-faint); margin: 0 1px; }

.shortcuts-empty { font-size: 14.5px; color: var(--text-dim); }

.shortcuts-footer-hint {
  margin: 22px 0 0;
  padding-top: 18px;
  border-top: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  font-size: 12px;
  color: var(--text-faint);
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.shortcuts-help-btn {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  font-weight: 700;
  flex-shrink: 0;
}

/* Quiet, translucent "F1 for Shortcuts" reminder sitting in the topbar --
   deliberately understated (faint text, glass pill) so it reads as a hint,
   not another button competing with Open Photo/Export for attention. Still
   clickable (wired via data-shortcuts-open, same as .shortcuts-help-btn)
   so it's not just decorative. */
.shortcuts-hint {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 11px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text-faint);
  font: inherit;
  font-size: 11.5px;
  white-space: nowrap;
  cursor: pointer;
  flex-shrink: 0;
}
.shortcuts-hint:hover { color: var(--text-dim); border-color: var(--accent-line); }
.shortcuts-hint-kbd {
  font-size: 10.5px;
  font-weight: 700;
  padding: 1px 5px;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text-dim);
}

/* ---------- Laptop-width tightening (13" laptops -- 1280/1366px logical
   width is the common case, well above the old 640px phone breakpoint but
   still tight enough that .topbar-actions had near-zero slack: Portrait
   Pro's row of ~10 controls measured 1244px of required width against
   ~1244px available AT 1280px viewport width exactly -- any OS chrome,
   scrollbar, or non-maximized window pushed it into ad-hoc squishing
   instead of a clean, deliberate layout change. ---------- */
@media (max-width: 1440px) {
  .topbar { padding: 10px 16px; }
  .topbar-actions { gap: 6px; }
  .btn { padding: 7px 12px; font-size: 12.5px; }
  /* Least essential info in the row -- drop it first to buy back space
     before anything has to shrink or wrap. */
  [data-user-email] { display: none; }
  .shortcuts-hint-label { display: none; }
  .shortcuts-hint { padding: 5px 8px; }
}

@media (max-width: 1180px) {
  /* Squishing every button a little is worse than a clean second row --
     this is the actual fix for the reported squeeze, the rule above is
     just what delays needing it until a genuinely tight width. */
  .topbar-actions { flex-wrap: wrap; justify-content: flex-end; row-gap: 8px; }
  .sidebar { width: 260px; min-width: 260px; }
  .tab { padding: 8px 4px; font-size: 10px; }
  .tab svg { width: 15px; height: 15px; }
}

/* Show/hide password toggle (see password-toggle.js) -- wraps a password
   input so an eye-icon button can sit inside its right edge, same
   convention as every other password field on the web. Used on
   login/signup, reset-password, and My Account's password fields. */
.password-wrap { position: relative; }
.password-wrap input[type="password"],
.password-wrap input[type="text"] { padding-right: 36px; }
.password-toggle {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
  background: transparent; border: none; padding: 0; color: var(--text-faint); cursor: pointer;
}
.password-toggle:hover { color: var(--text-dim); }

/* ---------- Post-export handoff dialog (see post-export-dialog.js) ----------
   Shared across all four tools: shown after a successful export/download in
   the browser, offering the other three tools as one-click next steps plus a
   plain "Continue in X" to stay put. Deliberately heavier glass than the
   app's normal --glass-blur (blur(26px) saturate(1.6)) -- this is a rare,
   full-attention moment, so it leans further into real vibrancy than the
   everyday modal/panel chrome does. Web-only by design: the packaged desktop
   app's native Save-As flow never calls showPostExportDialog(). */
.pxd-overlay {
  position: fixed; inset: 0; z-index: 1200;
  display: flex; align-items: center; justify-content: center;
  background: rgba(8,9,11,0.55);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  animation: pxd-fade-in 0.18s ease;
}
@keyframes pxd-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes pxd-pop-in {
  from { opacity: 0; transform: scale(0.92) translateY(8px); }
  to { opacity: 1; transform: scale(1) translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .pxd-overlay, .pxd-dialog { animation: none !important; }
}

.pxd-dialog {
  width: 420px; max-width: 90vw;
  background: var(--glass-strong);
  backdrop-filter: blur(34px) saturate(1.9);
  -webkit-backdrop-filter: blur(34px) saturate(1.9);
  border: 1px solid var(--border);
  border-radius: 24px;
  padding: 24px;
  box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-strong);
  animation: pxd-pop-in 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
.pxd-check {
  width: 42px; height: 42px; border-radius: 32%; display: flex; align-items: center; justify-content: center;
  margin-bottom: 14px; color: #fff;
  background: linear-gradient(150deg, #35d6ae, #17a889);
  box-shadow: 0 6px 16px -4px rgba(23,168,137,0.55), inset 0 1px 0 rgba(255,255,255,0.5);
}
.pxd-check svg { width: 20px; height: 20px; }
.pxd-dialog h3 { margin: 0 0 3px; font-size: 17px; font-weight: 650; color: var(--text); letter-spacing: -0.01em; }
.pxd-filename { margin: 0 0 20px; font-size: 12.5px; color: var(--text-faint); word-break: break-all; }
.pxd-filename b { color: var(--text-dim); font-weight: 600; }
.pxd-label { margin: 0 0 12px; font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); }

/* flex:1, not a fixed width -- now shows up to 4 "other" tools (5 total
   tools minus whichever is current), and a fixed 33% (sized for exactly 3)
   would overflow/wrap once Video Pro made that 4. */
.pxd-app-row { display: flex; justify-content: space-between; gap: 8px; margin-bottom: 18px; }
.pxd-app-col {
  display: flex; flex-direction: column; align-items: center; gap: 8px; flex: 1; min-width: 0;
  background: none; border: none; padding: 0; font: inherit; cursor: pointer;
}
.pxd-app-icon {
  width: 56px; height: 56px; border-radius: 27%; display: flex; align-items: center; justify-content: center;
  color: #fff; transition: transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.4), 0 6px 14px -6px rgba(0,0,0,0.35);
}
.pxd-app-col:hover .pxd-app-icon { transform: translateY(-3px) scale(1.05); }
.pxd-app-icon svg { width: 23px; height: 23px; }
.pxd-app-col span:last-child { font-size: 11px; font-weight: 550; color: var(--text-dim); text-align: center; line-height: 1.3; }
.pxd-violet .pxd-app-icon, .pxd-app-icon.pxd-violet { background: linear-gradient(150deg, #9d92ff, #6B5CE0); }
.pxd-teal .pxd-app-icon, .pxd-app-icon.pxd-teal { background: linear-gradient(150deg, #4fe0c4, #22B8A0); }
.pxd-coral .pxd-app-icon, .pxd-app-icon.pxd-coral { background: linear-gradient(150deg, #f2a688, #E8785F); }
.pxd-gold .pxd-app-icon, .pxd-app-icon.pxd-gold { background: linear-gradient(150deg, #ddbb7c, #C9A15A); }
.pxd-magenta .pxd-app-icon, .pxd-app-icon.pxd-magenta { background: linear-gradient(150deg, #fa8fc8, #f472b6); }

.pxd-divider { height: 1px; background: var(--border); margin: 0 0 16px; border: none; }
.pxd-continue { width: 100%; }
.password-toggle svg { width: 16px; height: 16px; }
