/* ============================================================
   Fovea, marketing site
   Tokens & components mirror the app's Swift Theme (Theme.swift)
   and the real cull layout (CullView / TopBar / BigPreview /
   CameraRoll / KeyboardFooter). Near-black gallery, SF Pro,
   photos are the only color, chrome recedes.
   ============================================================ */

/* ---- Self-hosted fonts (SIL Open Font License) ----
   Served from our own origin so no visitor IP reaches a third party
   before they have chosen to be here. Variable files: one per style,
   covering the whole weight range. latin-ext is only fetched if a
   glyph in its unicode-range is actually used. ---- */
@font-face {
  font-family: 'Inter';
  font-style: italic;
  font-weight: 400 500;
  font-display: swap;
  src: url(fonts/inter-italic-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: italic;
  font-weight: 400 500;
  font-display: swap;
  src: url(fonts/inter-italic-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(fonts/inter-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(fonts/inter-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400 500;
  font-display: swap;
  src: url(fonts/newsreader-italic-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400 500;
  font-display: swap;
  src: url(fonts/newsreader-italic-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(fonts/newsreader-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(fonts/newsreader-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: 72px; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
img, svg { display: block; max-width: 100%; }
button, input { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 3px; }
::selection { background: color-mix(in oklab, var(--brand) 45%, transparent); color: #fff; }

/* ---- Tokens (exact from Sources/Fovea/Theme/Theme.swift) ---- */
:root {
  --bg:        #0a0a0b;   /* Theme.bg */
  --chrome:    #0e0e0f;   /* Theme.chrome */
  --hairline:  #1c1c1f;   /* Theme.hairline */
  --group:     #202024;   /* Theme.groupActive */
  --lift:      #131316;

  --ink:       #dedeDf;   /* Theme.ink */
  --dim:       #9a9aa1;   /* Theme.inkDim */
  --faint:     #6a6a70;   /* Theme.inkFaint */
  --ring:      #e6e6ea;   /* Theme.ring, neutral selection */

  --keep:      #34c77b;   /* Theme.keep, emerald "kept" */
  --keep-dk:   #1f8f56;
  --maybe:     #e0a83d;   /* Theme.maybe, amber */
  --reject:    #d98a8a;   /* Theme.reject, rose */
  --brand:     #8e72ff;   /* Theme.brand, signature violet (the app icon) */
  --brand-dk:  #5b36d9;

  --badge-bg:  #191a1d;   /* Theme.badgeBg */
  --badge-ink: #c6c6cd;   /* Theme.badgeInk */
  --kc-bg:     #171719;   /* KbdKey background */

  /* Colored tags — muted hues from Theme.tagHex, the one place saturated
     color is allowed in the chrome (it's user data, like the photos). */
  --tag-emerald: #5fb981;
  --tag-azure:   #5c9ae0;
  --tag-coral:   #e08a5c;

  --f-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Inter", "Helvetica Neue", Arial, sans-serif;
  --wrap: 1120px;
  --gutter: clamp(1.25rem, 5vw, 3.5rem);
  --ease: cubic-bezier(0.33, 1, 0.68, 1);
}

body {
  background: var(--bg); color: var(--ink); font-family: var(--f-sans);
  font-size: 1rem; line-height: 1.6; letter-spacing: -0.011em;
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
  font-variant-numeric: tabular-nums; overflow-x: hidden;
}
h1, h2, h3, h4 { font-weight: 600; letter-spacing: -0.026em; line-height: 1.06; color: var(--ink); }
h1 { font-size: clamp(2.5rem, 1.4rem + 4vw, 4.4rem); letter-spacing: -0.035em; line-height: 1.02; }
h2 { font-size: clamp(1.85rem, 1.2rem + 2.4vw, 3rem); }
h3 { font-size: clamp(1.2rem, 1rem + 0.7vw, 1.55rem); letter-spacing: -0.02em; }
p { text-wrap: pretty; }
strong { font-weight: 600; color: var(--ink); }
.mono-num { font-variant-numeric: tabular-nums; }

/* ---- Layout ---- */
.wrap { width: min(100% - var(--gutter) * 2, var(--wrap)); margin-inline: auto; }
.wrap-wide { width: min(100% - var(--gutter) * 2, 1320px); margin-inline: auto; }
section { position: relative; }
.pad { padding-block: clamp(4rem, 8vw, 7.5rem); }
.rule { border: 0; border-top: 1px solid var(--hairline); }

.label { display: inline-block; font-size: 0.7rem; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase; color: var(--faint); }
.label .kp { color: var(--brand); }
.lede { font-size: clamp(1.05rem, 1rem + 0.4vw, 1.3rem); color: var(--dim); line-height: 1.5; letter-spacing: -0.014em; max-width: 46ch; }
.lede strong { color: var(--ink); font-weight: 500; }
.dim { color: var(--dim); }

/* ---- Buttons ---- */
.btn { display: inline-flex; align-items: center; gap: 0.5em; height: 42px; padding: 0 1.05rem; border-radius: 9px;
  font-size: 0.94rem; font-weight: 500; letter-spacing: -0.01em; border: 1px solid transparent; cursor: pointer; white-space: nowrap;
  transition: background 0.22s var(--ease), border-color 0.22s var(--ease), color 0.22s, transform 0.22s var(--ease); }
.btn svg { width: 1.02em; height: 1.02em; }
/* Apple logo is taller than wide; size by height and nudge up optically. */
.btn svg.ico-apple { width: auto; height: 1.12em; margin-top: -0.14em; }
.btn-primary { background: var(--ring); color: #08080a; }
.btn-primary:hover { background: #fff; }
.btn-ghost { border-color: #2a2a2e; color: var(--ink); }
.btn-ghost:hover { border-color: #3a3a40; background: rgba(255,255,255,0.03); }
.btn-lg { height: 48px; padding: 0 1.35rem; font-size: 1rem; border-radius: 10px; }
.btn-sm { height: 34px; padding: 0 0.85rem; font-size: 0.85rem; border-radius: 8px; }
.tlink { display: inline-flex; align-items: center; gap: 0.35em; color: var(--dim); font-size: 0.94rem; transition: color 0.2s; }
.tlink svg { width: 1em; height: 1em; transition: transform 0.25s var(--ease); }
.tlink:hover { color: var(--ink); }
.tlink:hover svg { transform: translateX(3px); }

/* Phones cannot run the installer. Keep the platform visible without presenting
   a download action that cannot be completed on the current device. */
.platform-note { display: none; }
@media (max-width: 640px) {
  .download-cta { display: none; }
  .platform-note { display: inline-flex; align-items: center; min-height: 34px;
    color: var(--dim); font-size: 0.85rem; font-weight: 500; white-space: nowrap; }
  .platform-note-nav { color: rgba(255,255,255,0.68); }
  .nav.scrolled .platform-note-nav { color: var(--dim); }
  .nav:not(.scrolled) .nav-right > .platform-note.platform-note-nav { opacity: 1; pointer-events: auto; }
}

/* ---- Download compatibility ---- */
.compat-notice {
  position: fixed; z-index: 120; left: 50%; bottom: 18px; width: min(calc(100% - 32px), 680px);
  transform: translateX(-50%); display: flex; align-items: center; gap: 0.9rem; padding: 11px 12px 11px 16px;
  color: var(--dim); background: color-mix(in oklab, var(--lift) 94%, transparent); border: 1px solid #303036;
  border-radius: 12px; box-shadow: 0 18px 60px rgba(0,0,0,0.48); backdrop-filter: blur(20px) saturate(1.2);
  font-size: 0.85rem; line-height: 1.35;
}
.compat-notice > div { flex: 1; }
.compat-notice strong { color: var(--ink); }
.compat-learn, .compat-notice-x, .compat-dialog-x {
  appearance: none; border: 0; background: none; color: inherit; cursor: pointer;
}
.compat-learn { color: var(--ink); font-weight: 500; white-space: nowrap; }
.compat-learn:hover { color: #fff; }
.compat-notice-x { width: 28px; height: 28px; color: var(--faint); font-size: 1.25rem; line-height: 1; }
.compat-notice-x:hover { color: var(--ink); }

.compat-dialog {
  position: fixed; inset: 0; width: min(calc(100% - 32px), 510px); margin: auto; padding: 30px;
  color: var(--ink); background: var(--lift); border: 1px solid #303036; border-radius: 16px;
  box-shadow: 0 28px 100px rgba(0,0,0,0.72);
}
.compat-dialog::backdrop { background: rgba(0,0,0,0.7); backdrop-filter: blur(5px); }
.compat-dialog-x { position: absolute; top: 13px; right: 13px; width: 34px; height: 34px; color: var(--faint); font-size: 1.5rem; line-height: 1; }
.compat-dialog-x:hover { color: var(--ink); }
.compat-dialog h2 { margin-top: 0.8rem; font-size: clamp(1.6rem, 1.35rem + 1vw, 2rem); }
.compat-dialog-copy { margin-top: 1rem; color: var(--dim); line-height: 1.55; }
.compat-actions { display: flex; flex-wrap: wrap; gap: 0.7rem; margin-top: 1.5rem; }
.compat-dialog-note { margin-top: 1rem; color: var(--faint); font-size: 0.78rem; line-height: 1.45; }

@media (max-width: 560px) {
  .compat-notice { align-items: flex-start; flex-wrap: wrap; gap: 0.35rem 0.8rem; padding-right: 42px; }
  .compat-notice > div { flex-basis: 100%; }
  .compat-notice-x { position: absolute; top: 7px; right: 7px; }
  .compat-dialog { padding: 26px 22px; }
}

/* ---- Keycap (KbdKey: bg #171719, radius 4, inkDim, 10pt, pad 6/2) ---- */
.kc { display: inline-grid; place-items: center; min-width: 20px; height: 19px; padding: 0 6px;
  font-size: 0.7rem; font-weight: 500; color: var(--dim); background: var(--kc-bg); border-radius: 4px;
  transition: background 0.14s var(--ease), color 0.14s; }
.kc.wide { min-width: 42px; }
.kc.is-on { background: color-mix(in oklab, var(--keep) 30%, var(--kc-bg)); color: #d6f4e3; }
.kc.is-rej { background: color-mix(in oklab, var(--reject) 30%, var(--kc-bg)); color: #f3d7d7; }

/* footer/inline hint: key + faint label */
.hint { display: inline-flex; align-items: center; gap: 5px; }
.hint .lbl { color: var(--faint); font-size: 0.78rem; }

/* ---- Humble badge (ultraThinMaterial capsule, white .12 border, ink) ---- */
.badge { display: inline-flex; align-items: center; height: 26px; padding: 0 11px; font-size: 0.8rem; color: var(--ink);
  background: rgba(42,42,48,0.5); border: 1px solid rgba(255,255,255,0.12); border-radius: 999px; backdrop-filter: blur(14px) saturate(1.2); }

/* ---- Nav ---- */
.nav { position: fixed; inset: 0 0 auto 0; z-index: 100; height: 60px; transition: background 0.3s, border-color 0.3s, backdrop-filter 0.3s; border-bottom: 1px solid transparent; }
.nav.scrolled { background: color-mix(in oklab, var(--bg) 72%, transparent); backdrop-filter: blur(20px) saturate(1.3); border-bottom-color: var(--hairline); }
.nav-inner { display: flex; align-items: center; justify-content: space-between; height: 100%; }
.brand { display: inline-flex; align-items: center; gap: 0.55em; font-size: 1.06rem; font-weight: 600; letter-spacing: -0.02em; color: var(--ink); }
.brand img { width: 23px; height: 23px; border-radius: 5px; transition: transform 0.5s var(--ease); }
.brand:hover img { transform: scale(1.07); }
.nav-right { display: flex; align-items: center; gap: 1.1rem; }
.nav-link { font-size: 0.9rem; color: var(--dim); transition: color 0.2s; }
.nav-link:hover { color: var(--ink); }

/* ---- Hero ---- */
.hero { padding-top: clamp(6.5rem, 13vh, 9rem); }
.hero-copy { max-width: 40rem; }
.hero h1 { margin-top: 1.25rem; }
.hero .lede { margin-top: 1.5rem; }
.cta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.8rem; margin-top: 2rem; }
.cta .tlink { margin-left: 0.4rem; }
.hero-note { margin-top: 1.15rem; color: var(--faint); font-size: 0.82rem; }
.hero-stage { margin-top: clamp(3rem, 6vw, 5rem); }

/* ---- Loupe (the real app window: TopBar + preview + roll + footer) ---- */
.loupe { background: var(--chrome); border: 1px solid var(--hairline); border-radius: 12px; overflow: hidden;
  box-shadow: 0 30px 90px -45px rgba(0,0,0,0.95); }

/* TopBar, height 46, leading inset clears the traffic lights */
.loupe-top { display: flex; align-items: center; gap: 12px; height: 46px; padding: 0 16px 0 14px; border-bottom: 1px solid var(--hairline); }
.traffic { display: inline-flex; gap: 8px; margin-right: 8px; }
.traffic i { width: 12px; height: 12px; border-radius: 50%; background: #3a3a40; }
.traffic i:nth-child(1) { background: #e0655b; } .traffic i:nth-child(2) { background: #e0a83d; } .traffic i:nth-child(3) { background: #34c77b; }
.loupe-top .folder { font-size: 0.84rem; font-weight: 600; color: var(--ink); }
.loupe-top .counts { font-size: 0.8rem; color: var(--dim); }
.loupe-top .close { color: var(--faint); font-size: 0.78rem; }
.loupe-top .sp { flex: 1; }
.loupe-top .barbtn { font-size: 0.76rem; color: var(--dim); }
.loupe-top .barbtn.danger { color: var(--dim); }
.loupe-top .bardiv { width: 1px; height: 18px; background: var(--hairline); }

/* preview, big photo, letterboxed on black (padding 28), EXIF capsule + badges on reveal */
.loupe-body { display: grid; grid-template-columns: 1fr 150px; }
.loupe-photo { position: relative; background: #000; display: grid; place-items: center; padding: clamp(12px, 2vw, 26px); aspect-ratio: 16 / 10; }
.loupe-body.solo { grid-template-columns: 1fr; }
.loupe-photo > img { width: 100%; height: 100%; object-fit: contain; }
.loupe-photo .exif { position: absolute; top: 12px; left: 12px; font-size: 0.72rem; color: rgba(255,255,255,0.72);
  background: rgba(0,0,0,0.45); padding: 4px 10px; border-radius: 999px; letter-spacing: 0; }
.loupe-photo .exif .n { color: rgba(255,255,255,0.85); }
.loupe-photo .badges { position: absolute; left: 12px; right: 12px; bottom: 12px; display: flex; flex-wrap: wrap; gap: 6px; }

/* camera roll, chrome band, 150 wide, quiet scene headers, 1.9:1 thumbs, radius 5 */
.roll { background: var(--chrome); border-left: 1px solid var(--hairline); padding: 4px 12px 10px; display: flex; flex-direction: column; gap: 4px; }
.roll .scene { font-size: 0.68rem; color: var(--faint); padding: 8px 2px 2px; }
.roll .datesep { border-top: 1px solid var(--hairline); margin: 6px 2px 0; padding-top: 6px; font-size: 0.72rem; font-weight: 600; color: var(--ink); }
.roll .thumb { position: relative; aspect-ratio: 1.92; border-radius: 5px; overflow: hidden; border: 2px solid transparent; }
.roll .thumb img { width: 100%; height: 100%; object-fit: cover; }
.roll .thumb.sel { border-color: var(--ring); }
.roll .thumb.rej img { opacity: 0.4; }
/* Verdict chip (PhotoRowItem: 16px, r4, inset 5). Hidden until a decision is made
   so undecided thumbs stay clean; a decision adds .keep / .maybe. */
.roll .thumb .mk { position: absolute; width: 16px; height: 16px; border-radius: 4px; display: none; place-items: center;
  box-shadow: 0 0 3px rgba(0,0,0,0.35); }
.roll .thumb .mk svg { width: 10px; height: 10px; }
.roll .thumb .mk.keep, .roll .thumb .mk.maybe { display: grid; }
.roll .thumb .mk.keep { top: 5px; right: 5px; background: var(--keep); color: #0a0a0b; }
.roll .thumb .mk.maybe { top: 5px; right: 5px; background: var(--maybe); color: #0a0a0b;
  font-size: 11px; font-weight: 800; line-height: 1; }   /* the "?" glyph, ≈ SF bold */
.roll .thumb .mk.tutor { left: 5px; bottom: 5px; background: var(--badge-bg); color: var(--badge-ink); }
.roll .thumb .mk.dev { right: 5px; bottom: 5px; background: var(--badge-bg); color: var(--badge-ink); }

/* KeyboardFooter, height 34, chrome, hairline top, 16 gap */
.loupe-foot { display: flex; align-items: center; gap: 16px; min-height: 34px; padding: 7px 16px; border-top: 1px solid var(--hairline); flex-wrap: wrap; }
.loupe-foot .right { margin-left: auto; }

@media (max-width: 640px) {
  .loupe-top { flex-wrap: nowrap; overflow: hidden; }
  .loupe-top .barbtn, .loupe-top .bardiv, .loupe-top .close { display: none; }
  .loupe-top .counts { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .loupe-body { grid-template-columns: 1fr; }
  .roll { flex-direction: row; overflow: hidden; border-left: 0; border-top: 1px solid var(--hairline); }
  .roll .scene, .roll .datesep { display: none; }
  .roll .thumb { flex: 0 0 22%; }
  .loupe-foot .hint:nth-child(n+6) { display: none; }
}

/* ---- Contact strip ---- */
.strip { overflow: hidden; border-block: 1px solid var(--hairline);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent); }
.strip-track { display: flex; gap: 8px; width: max-content; padding: 12px 0; animation: roll 60s linear infinite; }
.strip:hover .strip-track { animation-play-state: paused; }
.strip-track .f { flex: 0 0 auto; width: 148px; aspect-ratio: 3/2; border-radius: 5px; overflow: hidden; }
.strip-track .f img { width: 100%; height: 100%; object-fit: cover; filter: saturate(0.95); transition: filter 0.4s; }
.strip-track .f:hover img { filter: saturate(1.08); }
@keyframes roll { to { transform: translateX(calc(-50% - 4px)); } }
@media (prefers-reduced-motion: reduce) { .strip-track { animation: none; } }

/* ---- Feature rows ---- */
.row { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2rem, 5vw, 5rem); align-items: center; }
.row.rev .col-visual { order: 2; }
.row h2 { margin-top: 1.1rem; }
.row .lede { margin-top: 1.3rem; }
.pts { margin-top: 1.9rem; display: grid; gap: 1.4rem; }
.pts li { list-style: none; }
.pts b { display: block; color: var(--ink); font-weight: 600; font-size: 1rem; letter-spacing: -0.015em; }
.pts p { color: var(--dim); font-size: 0.95rem; margin-top: 0.25rem; max-width: 42ch; }
.foot-keys { margin-top: 2rem; display: flex; flex-wrap: wrap; align-items: center; gap: 1.3rem; }
@media (max-width: 800px) { .row { grid-template-columns: 1fr; gap: 2.25rem; } .row.rev .col-visual { order: 0; } }

/* compare */
.compare { background: var(--chrome); border: 1px solid var(--hairline); border-radius: 12px; overflow: hidden; box-shadow: 0 30px 90px -45px rgba(0,0,0,0.95); }
.compare-top { padding: 0.85rem 1.1rem; border-bottom: 1px solid var(--hairline); font-size: 0.8rem; color: var(--dim); display: flex; justify-content: space-between; }
.compare-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; padding: 12px; }
.compare-grid .c { position: relative; aspect-ratio: 3/4; border-radius: 6px; overflow: hidden; border: 2px solid transparent; }
.compare-grid .c img { width: 100%; height: 100%; object-fit: cover; }
.compare-grid .c.win { border-color: var(--ring); }
.compare-grid .c.win .mk { position: absolute; top: 5px; right: 5px; width: 16px; height: 16px; border-radius: 4px; background: var(--keep); color: #0a0a0b; display: grid; place-items: center; box-shadow: 0 0 3px rgba(0,0,0,0.4); }
.compare-grid .c.win .mk svg { width: 10px; height: 10px; }
.compare-grid .c.out img { opacity: 0.4; }
.compare-cap { padding: 0.2rem 1.1rem 1rem; font-size: 0.8rem; color: var(--dim); }

/* ---- Develop showcase — mirrors the real Develop panel (DevelopPanel.swift): a docked
   tool panel of collapsible groups + sliders and a live histogram, with the guided "edit
   tips" coach floating over the photo, naming a control and a range to try but applying
   nothing itself (GuidanceCard.swift). ---- */
/* The copy pins while the tall Develop panel scrolls past, like the Critiques section. */
#develop .row { align-items: start; }
#develop .col-copy { position: sticky; top: clamp(88px, 13vh, 150px); align-self: start;
  transition: opacity 0.25s ease; will-change: opacity; }
.dev-stage { display: grid; grid-template-columns: 1fr 238px; }
.dev-photo { position: relative; overflow: hidden; background: #000; border-right: 1px solid var(--hairline); min-height: 340px; }
.dev-photo > img { width: 100%; height: 100%; object-fit: cover; }

/* Floating guided-edit coach card */
.coach { position: absolute; left: 12px; bottom: 12px; width: 212px; padding: 9px 11px 10px; border-radius: 9px; background: rgba(14,14,15,0.9); border: 1px solid #2a2a2e; backdrop-filter: blur(12px); box-shadow: 0 16px 40px -20px rgba(0,0,0,0.95); }
.coach-head { display: flex; align-items: center; justify-content: space-between; font-size: 0.58rem; font-weight: 600; letter-spacing: 0.06em; color: var(--faint); }
.coach-head .x { color: var(--faint); font-size: 0.72rem; }
.coach-title { margin-top: 5px; display: flex; align-items: center; gap: 0.4em; font-size: 0.88rem; font-weight: 600; letter-spacing: -0.01em; color: var(--ink); }
.coach-title svg { width: 11px; height: 11px; color: var(--faint); flex: 0 0 auto; }
.coach-target { margin-top: 4px; font-size: 0.75rem; font-family: ui-monospace, SFMono-Regular, monospace; font-variant-numeric: tabular-nums; color: var(--dim); }
.coach-how { margin-top: 5px; font-size: 0.73rem; line-height: 1.38; color: var(--dim); }
.coach-foot { margin-top: 8px; display: flex; justify-content: flex-end; }
.coach-next { display: inline-flex; align-items: center; gap: 0.4em; font-size: 0.72rem; color: var(--ink); background: var(--group); border-radius: 6px; padding: 3px 8px; }
.coach-next .k { font-family: ui-monospace, monospace; font-size: 0.64rem; color: var(--dim); background: var(--kc-bg); border-radius: 3px; padding: 1px 4px; }

/* The docked develop panel */
.dev-panel { background: var(--chrome); display: flex; flex-direction: column; min-width: 0; }
.dp-head { display: flex; align-items: center; gap: 0.5em; padding: 11px 13px; border-bottom: 1px solid var(--hairline); }
.dp-head .t { font-size: 0.82rem; font-weight: 600; color: var(--ink); }
.dp-head .f { font-size: 0.68rem; color: var(--faint); font-family: ui-monospace, monospace; }
.dp-head .sp { flex: 1; }
.dp-head .a { font-size: 0.72rem; color: var(--dim); }
.dp-tips { display: flex; align-items: center; gap: 8px; padding: 10px 13px; border-bottom: 1px solid var(--hairline); }
.dp-tips .cap { font-size: 0.72rem; font-weight: 500; color: var(--bg); background: var(--brand); border-radius: 999px; padding: 4px 11px; }
.dp-tips .chip { margin-left: auto; font-size: 0.68rem; color: var(--faint); background: var(--badge-bg); border-radius: 999px; padding: 3px 8px; }
.dp-tips .chip b { color: var(--ink); font-family: ui-monospace, monospace; font-weight: 500; }
.dp-hist { height: 42px; margin: 11px 13px 3px; border-radius: 5px; background: rgba(255,255,255,0.02); border: 1px solid var(--hairline); overflow: hidden; }
.dp-hist svg { width: 100%; height: 100%; display: block; }
.dp-body { padding: 4px 13px 13px; display: flex; flex-direction: column; }
.dp-grp + .dp-grp .gh { border-top: 1px solid var(--hairline); }
.dp-grp .gh { display: flex; align-items: center; padding: 9px 2px; font-size: 0.79rem; font-weight: 500; color: var(--dim); }
.dp-grp.open .gh { color: var(--ink); }
.dp-grp .gh .cv { margin-left: auto; color: var(--faint); font-size: 0.85rem; }
.dp-grp .rows { display: flex; flex-direction: column; gap: 12px; padding: 5px 0 11px; }
.sld { display: grid; grid-template-columns: 58px 1fr 30px; align-items: center; gap: 9px; }
.sld .nm { font-size: 0.75rem; color: var(--dim); }
.sld .tk { position: relative; height: 3px; border-radius: 3px; background: var(--group); }
.sld .tk b { position: absolute; top: 50%; width: 11px; height: 11px; border-radius: 50%; background: #c9c9d2; translate: -50% -50%; }
.sld .vl { font-size: 0.71rem; color: var(--faint); text-align: right; font-variant-numeric: tabular-nums; }
.sld.hot .nm { color: #ece8ff; }
/* The guided "move it to here" band on the track (BipolarTrack.swift): a brand segment
   marking the suggested range. The thumb stays at its current value, so the gap between
   the grey thumb and the violet band is exactly the move the tip is asking for. */
.sld .tk .band { display: none; position: absolute; top: 50%; height: 5px; border-radius: 3px; background: var(--brand); translate: 0 -50%; }

/* The four Light sliders are live: drag or arrow-key them and the photo responds
   (see app.js). Everything below the fold (Color, Tone Curve, Masking, …) opens in
   the app, not on the page — the group headers stay inert here on purpose. */
.dev-photo > img { transition: filter 0.09s linear; }
.sld[data-field] { cursor: default; }
.sld[data-field] .tk { cursor: ew-resize; touch-action: none; }
.sld[data-field] .tk::before { content: ""; position: absolute; inset: -10px 0; }   /* fat hit area over the 3px track */
.sld[data-field] .tk b { cursor: grab; transition: background 0.15s ease, box-shadow 0.15s ease, scale 0.1s ease; }
.sld[data-field]:hover .tk { background: color-mix(in oklab, var(--group) 60%, #fff 8%); }
.sld[data-field]:hover .tk b { background: #e6e6ee; }
.sld[data-field]:active .tk b { cursor: grabbing; scale: 1.12; }
.sld[data-field]:focus { outline: none; }
.sld[data-field]:focus-visible .tk { background: color-mix(in oklab, var(--group) 55%, var(--brand) 22%); }
.sld[data-field]:focus-visible .tk b { box-shadow: 0 0 0 4px color-mix(in oklab, var(--brand) 38%, transparent); }
/* Reset lives in the header ("Auto · Reset") and restores the demo's starting recipe. */
.dp-head .dp-reset { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
.dp-head .dp-reset:hover { color: var(--ink); }
/* Deeper groups are display-only here — no pointer affordance, no expand. */
.dp-grp:not(.open) .gh { cursor: default; }

/* ---- Guided edits: 3 tips applied one by one, the last inside a pre-selected mask ----
   Tips 1–2 are global (the base image filters). Tip 3 enters the mask: a clipped copy of
   the photo (.mask-layer) takes the local edit, the marching-ants contour shows what's
   selected, and the panel swaps the Light group for the mask's local slider. */
.coach.hidden { display: none; }
/* While a slider is being dragged the tip fades out of the way so you can see the
   photo change, then eases back once the drag settles (see app.js). */
.coach { transition: opacity 0.22s ease; }
.coach.dragging { opacity: 0; pointer-events: none; }
.coach-head .x { font: inherit; background: none; border: 0; padding: 0; line-height: 1; cursor: pointer; }
.coach-head .x:hover { color: var(--ink); }
.coach-next { font: inherit; border: 0; cursor: pointer; }
.dp-tips .cap { cursor: pointer; }

/* Before/after: press-and-hold the pill (or Space/Enter) to drop every edit and see the
   original frame; release to snap back to your version. */
.cmp { position: absolute; top: 10px; right: 10px; z-index: 4; display: inline-flex; align-items: center; gap: 0.4em; font: inherit; font-size: 0.68rem; font-weight: 500; color: var(--ink); background: rgba(14,14,15,0.62); border: 1px solid #2a2a2e; border-radius: 999px; padding: 4px 9px 4px 7px; cursor: pointer; user-select: none; -webkit-user-select: none; -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease; }
.cmp svg { width: 13px; height: 13px; }
.cmp:hover { color: #fff; border-color: #3a3a40; }
.cmp:focus-visible { outline: none; border-color: var(--brand); }
.dev-stage.comparing .cmp { background: rgba(255,255,255,0.16); color: #fff; border-color: #4a4a52; }
/* !important beats the inline develop filters, so the hold reverts to the untouched photo. */
.dev-stage.comparing .dev-photo > img.base { filter: none !important; }
.dev-stage.comparing .mask-layer { opacity: 0 !important; }
.dev-stage.comparing .mask-edge { opacity: 0 !important; }

/* The pre-selected region: a clipped clone that carries the local edit, plus its contour. */
.dev-photo > img.mask-layer { position: absolute; inset: 0; opacity: 0; pointer-events: none; transition: opacity 0.28s ease, filter 0.09s linear; }
.dev-photo > img.mask-layer.applied, .dev-stage.mask-active .dev-photo > img.mask-layer { opacity: 1; }
.mask-edge { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; opacity: 0; transition: opacity 0.3s ease; }
.dev-stage.mask-active .mask-edge { opacity: 1; }
/* Marching ants, matched to the app's MaskOverlayView: a soft brand halo under
   white + black 6px dashes that march at 12px/s (one dash cycle per second). */
.mask-edge path { fill: none; vector-effect: non-scaling-stroke; stroke-linejoin: round; }
.mask-edge .me-fill { fill: color-mix(in oklab, var(--brand) 11%, transparent); stroke: none; }
.mask-edge .me-halo { stroke: color-mix(in oklab, var(--brand) 65%, transparent); stroke-width: 5; filter: blur(2.5px); }
.mask-edge .me-white { stroke: #fff; stroke-width: 1.75; stroke-dasharray: 6 6; animation: mask-ants 1s linear infinite; }
.mask-edge .me-black { stroke: rgba(0, 0, 0, 0.8); stroke-width: 1.75; stroke-dasharray: 6 6; stroke-dashoffset: 6; animation: mask-ants-b 1s linear infinite; }
.dev-stage:not(.mask-active) .mask-edge path { animation-play-state: paused; }
@keyframes mask-ants { to { stroke-dashoffset: -12; } }
@keyframes mask-ants-b { to { stroke-dashoffset: -6; } }
@media (prefers-reduced-motion: reduce) { .mask-edge path { animation: none; } }

/* Body and mask editor share one grid cell, so the panel — and therefore the photo —
   keeps a constant height when Tip 3 swaps the globals for the mask's local sliders. */
.dp-editors { display: grid; }
.dp-editors > .dp-body, .dp-editors > .dp-mask { grid-area: 1 / 1; }
.dp-mask { display: flex; flex-direction: column; padding: 6px 13px 14px; visibility: hidden; }
.dev-stage.mask-active .dp-body { visibility: hidden; }
.dev-stage.mask-active .dp-mask { visibility: visible; }
.dp-mask .rows { display: flex; flex-direction: column; gap: 12px; padding: 4px 0 0; }

/* ---- Library showcase (labels · places · collections) ---- */
.lib-stage { display: grid; grid-template-columns: 200px 1fr; }
.lib-side { padding: 12px 12px 14px; background: var(--chrome); border-right: 1px solid var(--hairline); display: flex; flex-direction: column; gap: 2px; }
.ls-grp { font-size: 0.66rem; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase; color: var(--faint); padding: 15px 8px 6px; }
.ls-grp:first-child { padding-top: 4px; }
.ls-row { display: flex; align-items: center; gap: 0.6em; padding: 6px 9px; border-radius: 6px; font-size: 0.9rem; color: var(--dim); }
.ls-row em { margin-left: auto; font-style: normal; font-size: 0.76rem; color: var(--faint); font-variant-numeric: tabular-nums; }
.ls-row.on { background: var(--group); color: var(--ink); }
.ls-row.on .ls-ic { color: var(--ink); }
.ls-ic { width: 16px; height: 16px; flex: 0 0 auto; color: var(--faint); }
/* Colored tag swatch — a filled dot in a 16px slot, mirroring the app's
   TagSidebarRow (10px circle in a 16px frame). --tag carries the hue. */
.ls-dot { width: 16px; height: 16px; flex: 0 0 auto; display: grid; place-items: center; }
.ls-dot::after { content: ""; width: 10px; height: 10px; border-radius: 50%; background: var(--tag, var(--faint)); }
.lib-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; padding: 6px; background: var(--bg); }
.lib-grid .cell { position: relative; aspect-ratio: 1 / 1; overflow: hidden; border-radius: 4px; background: #000; }
.lib-grid .cell img { width: 100%; height: 100%; object-fit: cover; }

/* Pin the copy and fade it out as the tall grid scrolls past — same as #develop / #critiques / #cull. */
#library .row { align-items: start; }
#library .col-copy { position: sticky; top: clamp(88px, 13vh, 150px); align-self: start;
  transition: opacity 0.25s ease; will-change: opacity; }
/* Give the library panel a touch more room than a straight 50/50 split — the grid
   + sidebar want the width more than the short copy does. Desktop only so the
   800px single-column stack (below) stays intact. */
@media (min-width: 801px) { #library .row { grid-template-columns: 0.85fr 1.15fr; } }

@media (max-width: 800px) {
  .dev-stage, .lib-stage { grid-template-columns: 1fr; }
  .dev-photo { border-right: 0; border-bottom: 1px solid var(--hairline); }
  .lib-side { border-right: 0; border-bottom: 1px solid var(--hairline); }
  #develop .row { align-items: stretch; }
  #develop .col-copy { position: static; opacity: 1 !important; }
  #library .row { align-items: stretch; }
  #library .col-copy { position: static; opacity: 1 !important; }
}

/* ---- Tutor critique (mirrors TutorPanel.swift's rating /10 + summary + "Suggested edits" +
   what works / to improve). The full frame sits on top, uncropped, with the read below it, so
   the whole photo shows and the IssueBlob can wrap the real subject. A localizable note carries a
   viewfinder and lights its region on the photo. ---- */
.loupe-body.crit { display: flex; flex-direction: column; }
/* the full frame at its native 3:2, no crop — the blob is positioned in % of the image */
.loupe-body.crit .loupe-photo { aspect-ratio: 3 / 2; min-height: 0; padding: 0; overflow: hidden; }
.loupe-body.crit .loupe-photo > img { object-fit: cover; object-position: center; }

/* the lit region for a note — the real IssueBlob (BigPreviewView): a soft, wobbling, pulsing glow,
   keep-green for a strength / brand-violet for an issue. The d-morph lives in SMIL (BlobShape's
   phase loop); the breathe is the CSS pulse. app.js moves it between the notes' regions (a slow
   cycle until the reader hovers a note, then it follows the pointer — like tutorHighlight). The
   wrapper carries position + show/hide; the inner svg carries the pulse, so fades never fight it. */
.issue-blob { position: absolute; top: 63%; left: 4%; width: 30%; height: 35%; }
.issue-blob svg { width: 100%; height: 100%; overflow: visible;
  animation: blobPulse 2.6s ease-in-out infinite alternate; transform-origin: center; }
.issue-blob .ib-halo { fill: none; stroke: var(--keep); stroke-width: 9; vector-effect: non-scaling-stroke;
  opacity: 0.85; filter: blur(5px); }
.issue-blob .ib-line { fill: none; stroke: var(--keep); stroke-width: 1.7; stroke-linecap: round;
  vector-effect: non-scaling-stroke; filter: blur(0.4px); }
.issue-blob.neg .ib-halo, .issue-blob.neg .ib-line { stroke: var(--brand); }
@keyframes blobPulse { from { opacity: 0.82; transform: scale(0.985); } to { opacity: 1; transform: scale(1.03); } }

/* Once app.js is running it owns the blob: hidden by default, shown (.on) over the active note,
   and it glides between regions. Without JS the blob just sits on its default region, visible. */
.crit-js .issue-blob { opacity: 0;
  transition: opacity 0.35s ease, left 0.5s var(--ease), top 0.5s var(--ease), width 0.5s var(--ease), height 0.5s var(--ease); }
.crit-js .issue-blob.on { opacity: 1; }

/* the notes that carry a region are the interactive hit targets (like TutorPanel's rows) */
.tp-row[data-region] { cursor: pointer; }
.tp-row[data-region]:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* The read is far taller than the copy, so centring left it floating. Instead the copy pins to
   the top of its column and rides down with the scroll while the frame + notes pass by, then
   app.js fades it out as the section ends. align-self:start gives the sticky its full range. */
#critiques .row { align-items: start; }
#critiques .col-copy { position: sticky; top: clamp(88px, 13vh, 150px); align-self: start;
  transition: opacity 0.25s ease; will-change: opacity; }
/* stacked layout: back to normal flow, no pinning, no JS fade */
@media (max-width: 800px) {
  #critiques .row { align-items: stretch; }
  #critiques .col-copy { position: static; opacity: 1 !important; }
}

@media (prefers-reduced-motion: reduce) {
  .issue-blob svg { animation: none; }
  .crit-js .issue-blob { transition: opacity 0.2s ease; }
}

.tutor-panel { background: var(--chrome); border-top: 1px solid var(--hairline); display: flex; flex-direction: column; min-width: 0; }
.tp-head { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--hairline); }
.tp-title { font-size: 0.9rem; font-weight: 600; color: var(--ink); }
.tp-file { flex: 1; min-width: 0; font-size: 0.72rem; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rating { display: inline-flex; align-items: baseline; gap: 1px; padding: 2px 9px; border-radius: 999px; }
.rating b { font-size: 1.05rem; font-weight: 600; line-height: 1; }
.rating .den { font-size: 0.7rem; color: var(--faint); }
.rating.ink { background: color-mix(in oklab, var(--ink) 12%, transparent); }
.rating.ink b { color: var(--ink); }
.rating.keep { background: color-mix(in oklab, var(--keep) 14%, transparent); }
.rating.keep b { color: var(--keep); }
.tp-body { padding: 14px; display: flex; flex-direction: column; gap: 14px; overflow: hidden; }
.tp-sum { font-size: 0.85rem; line-height: 1.5; color: var(--ink); max-width: 68ch; }
.tp-suggest { align-self: flex-start; font-size: 0.76rem; font-weight: 500; color: var(--bg); background: var(--brand); padding: 5px 13px; border-radius: 999px; }
.tp-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 26px; }
.tp-sec { display: flex; flex-direction: column; gap: 7px; }
.tp-lbl { font-size: 0.62rem; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase; color: var(--faint); }
.tp-row { display: flex; gap: 8px; padding: 3px 5px; border-radius: 6px; margin: 0 -1px; }
.tp-row.active { background: var(--group); }
.tp-row .dot { flex: 0 0 auto; width: 5px; height: 5px; border-radius: 50%; margin-top: 6px; }
.tp-row .dot.pos { background: var(--keep); }
.tp-row .dot.neg { background: var(--reject); }
.tp-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.tp-txt b { font-size: 0.8rem; font-weight: 600; color: var(--ink); display: inline-flex; align-items: center; gap: 5px; }
.tp-txt > span { font-size: 0.75rem; line-height: 1.4; color: var(--dim); }
.tp-txt .vf { width: 11px; height: 11px; color: var(--faint); flex: 0 0 auto; }

@media (max-width: 640px) {
  .tp-cols { grid-template-columns: 1fr; gap: 12px; }
}

/* ---- Mini feature grid ---- */
.mini { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2.4rem 2rem; margin-top: 3rem; }
.mini .m b { display: block; color: var(--ink); font-weight: 600; font-size: 0.98rem; }
.mini .m p { color: var(--dim); font-size: 0.88rem; margin-top: 0.3rem; }
.mini .m .ic { width: 22px; height: 22px; color: var(--dim); margin-bottom: 0.8rem; }
@media (max-width: 820px) { .mini { grid-template-columns: 1fr 1fr; } }
@media (max-width: 460px) { .mini { grid-template-columns: 1fr; } }

/* ---- Interactive cull demo (the real app window: preview + live roll) ----
   Explainer on the left, app window on the right, so the section stays short
   enough to fit the viewport. The window body is a FIXED height so the roll
   SCROLLS within it (like the app's NSScrollView) instead of its 8 thumbs
   stretching the row, blowing out the preview, and bleeding past the footer. */
/* Pin the explainer to the top and let it ride down while the taller app window
   scrolls past, then app.js fades it out as the section ends — exactly like
   #critiques. align-self:start gives the sticky its full range. */
.cull-row { align-items: start; }
@media (min-width: 801px) {
  .cull-row { grid-template-columns: minmax(0, 34%) 1fr; }
  #cull .col-copy { position: sticky; top: clamp(88px, 13vh, 150px); align-self: start;
    transition: opacity 0.25s ease; will-change: opacity; }
}
@media (max-width: 800px) { #cull .col-copy { position: static; opacity: 1 !important; } }
.cull-row .cull-note { margin-top: 1.6rem; font-size: 0.9rem; }
.cull-row .cull-note .cull-try { color: var(--faint); }

.cull-demo { width: 100%; }   /* click no longer required; it goes live when on screen */
/* minmax(0,1fr) caps the single row at the body height so the roll's thumbs
   can't push the row past it (which would overlap / bleed over the footer). */
.cull-demo .loupe-body { height: clamp(340px, 30vw, 470px); grid-template-rows: minmax(0, 1fr); }
.cull-demo .loupe-photo { aspect-ratio: auto; min-width: 0; min-height: 0; overflow: hidden; }
/* Absolute-fill so height:100% resolves against the fixed-height photo cell.
   The cell is display:grid; its single implicit row auto-sizes to the image, so
   height:100% never resolved and the img fell back to width:100%/height:auto:
   object-fit:contain became a no-op and squares/portraits overflowed and got
   cropped. Positioning against the (position:relative) cell restores a definite
   height, so contain aspect-fits the WHOLE frame on black, mirroring
   BigPreviewView.fittedRect. */
.cull-demo .loupe-photo > img { position: absolute; inset: 0; transition: opacity 0.18s var(--ease); }   /* soft swap, echoing BigPreview's dissolve */
/* The roll scrolls within the fixed-height window, and the current thumb
   scrolls itself into view — like CameraRoll's syncSelection. */
.cull-demo .roll { overflow-y: auto; min-height: 0; }
.cull-demo .hint[data-key] { cursor: pointer; }
/* No persistent focus/live indicator (the window drives the keyboard when it's
   on screen; there's nothing to advertise). No browser focus ring either. */
.cull-demo:focus, .cull-demo:focus-visible { outline: none; }
/* One-time cue: a whisper-faint neutral ripple the first time you interact, then
   nothing. The window's own box-shadow animates (its overflow:hidden doesn't clip
   it), keeping the base drop shadow while a barely-there ring expands and fades. */
@keyframes cullPulse {
  from { box-shadow: 0 30px 90px -45px rgba(0,0,0,0.95), 0 0 0 0 rgba(230,230,234,0.11); }
  to   { box-shadow: 0 30px 90px -45px rgba(0,0,0,0.95), 0 0 0 4px rgba(230,230,234,0); }
}
.cull-demo.pulse { animation: cullPulse 0.5s var(--ease); }
@media (prefers-reduced-motion: reduce) { .cull-demo.pulse { animation: none; } }
.cull-note { color: var(--dim); font-size: 0.9rem; max-width: 60ch; }

/* Success state: a quiet summary over the preview once every frame is reviewed.
   A summary, not a celebration — mirrors the app's CompleteView tone. */
.cull-done { position: absolute; inset: 0; z-index: 2; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 0.7rem; text-align: center; padding: 1.5rem;
  background: color-mix(in oklab, #000 80%, transparent); backdrop-filter: blur(7px);
  opacity: 0; pointer-events: none; transition: opacity 0.5s var(--ease); }
.cull-done.show { opacity: 1; pointer-events: auto; }
/* Scaled-up version of the app's "kept" chip (PhotoRowItem): a solid green rounded
   square with a dark bold check, gradient + glow so it reads as premium, not a
   generic success circle. */
.cull-done-check { width: 46px; height: 46px; border-radius: 14px; display: grid; place-items: center;
  color: #0a0a0b;
  background: linear-gradient(180deg, color-mix(in oklab, var(--keep) 86%, #fff), var(--keep));
  box-shadow: 0 12px 34px -12px color-mix(in oklab, var(--keep) 60%, transparent),
              inset 0 1px 0 rgba(255,255,255,0.5);
  transform: scale(0.9); transition: transform 0.5s var(--ease); }
.cull-done.show .cull-done-check { transform: scale(1); }
.cull-done-check svg { width: 24px; height: 24px; }
.cull-done-title { font-family: var(--f-serif); font-weight: 500; font-size: 1.5rem; letter-spacing: -0.01em; color: var(--ink); }
.cull-done-tally { font-size: 0.9rem; color: var(--dim); }
.cull-done-tally .k { color: var(--keep); }
.cull-done-tally .mb { color: var(--maybe); }

/* ---- Closing ---- */
.closing { text-align: center; }
.closing h2 { max-width: 18ch; margin-inline: auto; }
.closing .lede { margin: 1.2rem auto 0; text-align: center; }
.closing .cta { justify-content: center; }
.closing .hero-note { text-align: center; }

/* ---- Footer ---- */
.footer { border-top: 1px solid var(--hairline); padding-block: 3.5rem 2.5rem; }
.footer-grid { display: grid; grid-template-columns: 1.7fr repeat(3, 1fr); gap: 2.5rem; }
.footer .brand { margin-bottom: 0.9rem; }
.footer .blurb { color: var(--dim); font-size: 0.88rem; max-width: 32ch; }
.footer h4 { font-size: 0.72rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--faint); margin-bottom: 0.9rem; font-weight: 600; }
.footer ul { list-style: none; display: grid; gap: 0.65rem; }
.footer ul a { color: var(--dim); font-size: 0.9rem; transition: color 0.2s; }
.footer ul a:hover { color: var(--ink); }
.footer-bottom { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--hairline); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.8rem; color: var(--faint); font-size: 0.8rem; }
/* Minimal footer: brand + single line, no divider/top gap. */
.footer-min .footer-bottom { margin-top: 0; padding-top: 0; border-top: 0; align-items: center; }
.footer-min .brand { margin-bottom: 0; }
/* Legal links sit between the brand and the copyright line. */
.footer-legal { display: flex; flex-wrap: wrap; gap: 1.15rem; }
.footer-legal a { color: var(--faint); font-size: 0.8rem; transition: color 0.2s; }
.footer-legal a:hover { color: var(--ink); }
@media (max-width: 720px) { .footer-grid { grid-template-columns: 1fr 1fr; gap: 2rem; } }
@media (max-width: 640px) { .footer-min .footer-bottom { flex-direction: column; align-items: flex-start; gap: 1.1rem; } }

/* ---- Pricing ---- */
.price-hero { text-align: center; padding-top: clamp(7.5rem, 15vh, 10rem); }
.price-hero h1 { margin-top: 1rem; }
.price-hero .lede { margin: 1.4rem auto 0; text-align: center; }
.tiers { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 3.25rem; align-items: stretch; }
.tier { background: var(--lift); border: 1px solid var(--hairline); border-radius: 12px; padding: 1.75rem 1.6rem; display: flex; flex-direction: column; position: relative; transition: border-color 0.3s var(--ease); }
.tier:hover { border-color: #28282c; }
/* Premium buy card — lifts off the near-black page: a refined charcoal gradient,
   a hairline that catches light at the top, a gradient border with a faint violet
   kiss, a soft violet aura, and filled check chips. Both buy cards use .feat.
   Violet stays an accent (chrome recedes); the depth does the premium work. */
.tier.feat {
  border-color: transparent;
  padding: 2.1rem 1.9rem;
  background:
    radial-gradient(130% 90% at 50% -20%, color-mix(in oklab, var(--brand) 10%, transparent), transparent 55%),
    linear-gradient(180deg, #191920, #101013);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.06),
    0 44px 100px -55px rgba(0,0,0,0.98),
    0 0 70px -28px color-mix(in oklab, var(--brand) 52%, transparent);
  transition: transform 0.45s var(--ease), box-shadow 0.45s var(--ease);
}
/* Gradient border ring: brighter violet at the top, fading to hairline. */
.tier.feat::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px; pointer-events: none;
  background: linear-gradient(180deg,
    color-mix(in oklab, var(--brand) 80%, #fff) 0%,
    color-mix(in oklab, var(--brand) 30%, transparent) 32%,
    var(--hairline) 70%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}
.tier.feat:hover {
  transform: translateY(-3px);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.08),
    0 54px 110px -55px rgba(0,0,0,0.98),
    0 0 92px -26px color-mix(in oklab, var(--brand) 62%, transparent);
}
@media (prefers-reduced-motion: reduce) { .tier.feat, .tier.feat:hover { transition: none; transform: none; } }

/* Featured tag: a filled violet pill, not a hollow outline. */
.tier.feat .tag-badge { color: #d3c6ff; background: color-mix(in oklab, var(--brand) 20%, transparent);
  border-color: color-mix(in oklab, var(--brand) 45%, transparent); }

/* Bigger hero price on the premium card. */
.tier.feat .t-price .amt { font-size: 3rem; }

/* Gradient divider that fades at both edges, instead of a flat hairline. */
.tier.feat ul { border-top: 0; margin-top: 1.75rem; padding-top: 1.75rem; position: relative; gap: 1.1rem; }
.tier.feat ul::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, transparent, color-mix(in oklab, var(--brand) 30%, var(--hairline)) 25%, var(--hairline) 55%, transparent); }

/* Check chips with real substance — the app's "kept" gem treatment (a violet gradient fill,
   a top light-catch, and a soft glow), so they read premium, not like flat form checkboxes. */
.tier.feat li { grid-template-columns: 20px 1fr; gap: 0.78rem; align-items: start; }
.tier.feat li svg { width: 20px; height: 20px; padding: 4.5px; border-radius: 6px; margin-top: 2px; color: #f3efff;
  background: linear-gradient(180deg, var(--brand), var(--brand-dk));
  box-shadow: 0 4px 10px -6px color-mix(in oklab, var(--brand) 40%, transparent), inset 0 1px 0 rgba(255,255,255,0.22); }
.tier .tag-badge { position: absolute; top: 1.5rem; right: 1.5rem; font-size: 0.66rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--brand); border: 1px solid color-mix(in oklab, var(--brand) 55%, transparent); border-radius: 999px; padding: 0.2em 0.6em; }
.tier .t-name { font-size: 1.3rem; font-weight: 600; letter-spacing: -0.02em; }
.tier .t-sub { color: var(--dim); font-size: 0.9rem; margin-top: 0.4rem; min-height: 2.7em; }
.tier .t-price { margin-top: 1.4rem; display: flex; align-items: baseline; gap: 0.4em; }
.tier .t-price .amt { font-size: 2.7rem; font-weight: 600; letter-spacing: -0.04em; }
.tier .t-price .per { color: var(--faint); font-size: 0.85rem; }
.tier .t-cta { margin-top: 1.4rem; }
.tier .t-cta .btn { width: 100%; justify-content: center; }
.tier ul { list-style: none; margin-top: 1.6rem; padding-top: 1.5rem; border-top: 1px solid var(--hairline); display: grid; gap: 0.8rem; }
.tier li { display: grid; grid-template-columns: 16px 1fr; gap: 0.6rem; align-items: start; font-size: 0.92rem; line-height: 1.4; text-wrap: balance; color: var(--ink); }
.tier li svg { width: 15px; height: 15px; color: var(--brand); margin-top: 3px; }
.tier li.off { color: var(--faint); }
.tier li.off svg { color: var(--faint); }
@media (max-width: 860px) { .tiers { grid-template-columns: 1fr; } }

.packs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 2.5rem; }
.pack { background: var(--bg); border: 1px solid var(--hairline); border-radius: 12px; padding: 1.5rem; display: flex; flex-direction: column; transition: border-color 0.3s; }
.pack:hover { border-color: #28282c; }
.pack .pk { font-size: 0.7rem; letter-spacing: 0.13em; text-transform: uppercase; color: var(--dim); }
.pack .amt { font-size: 1.9rem; font-weight: 600; letter-spacing: -0.03em; margin-top: 0.5rem; }
.pack .d { color: var(--dim); font-size: 0.88rem; margin-top: 0.45rem; }
.pack .rate { margin-top: auto; padding-top: 1rem; color: var(--faint); font-size: 0.78rem; }
@media (max-width: 720px) { .packs { grid-template-columns: 1fr; } }

.faq { max-width: 760px; margin-inline: auto; }
.faq-item { border-top: 1px solid var(--hairline); }
.faq-item:last-child { border-bottom: 1px solid var(--hairline); }
.faq-q { width: 100%; text-align: left; background: none; border: 0; cursor: pointer; padding: 1.3rem 0; display: flex; justify-content: space-between; align-items: center; gap: 1rem; font-size: 1.06rem; font-weight: 500; letter-spacing: -0.015em; color: var(--ink); }
.faq-q .pm { flex: 0 0 auto; width: 18px; height: 18px; position: relative; }
.faq-q .pm::before, .faq-q .pm::after { content: ""; position: absolute; top: 50%; left: 50%; translate: -50% -50%; background: var(--dim); transition: transform 0.3s var(--ease); }
.faq-q .pm::before { width: 12px; height: 1.5px; }
.faq-q .pm::after { width: 1.5px; height: 12px; }
.faq-item.open .pm::after { transform: translate(-50%,-50%) scaleY(0); }
.faq-a { overflow: hidden; max-height: 0; transition: max-height 0.35s var(--ease); }
.faq-a p { color: var(--dim); padding-bottom: 1.3rem; max-width: 64ch; font-size: 0.96rem; }

/* ---- Reveal ---- */
.rv { opacity: 0; transform: translateY(14px); transition: opacity 0.7s var(--ease), transform 0.7s var(--ease); }
.rv.in { opacity: 1; transform: none; }
.rv.d1 { transition-delay: 0.06s; } .rv.d2 { transition-delay: 0.12s; } .rv.d3 { transition-delay: 0.18s; }
@media (prefers-reduced-motion: reduce) { .rv { opacity: 1; transform: none; transition: none; } }

/* ============================================================
   Premium layer, atmosphere, light, choreography, staging.
   Restraint is the point: a whisper of the brand violet, a
   breath of grain, slow considered motion. Nothing shouts.
   ============================================================ */
:root { --ease-lux: cubic-bezier(0.16, 1, 0.3, 1); }

/* content sits above the atmosphere */
main, .footer { position: relative; z-index: 2; }

/* Pure dark. No colored haze — just a gentle neutral vignette for depth. */
.atmos {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background: radial-gradient(150% 100% at 50% -8%, transparent 52%, rgba(0,0,0,0.55) 100%);
}
@keyframes atmos-drift {
  from { opacity: 0.85; transform: translate3d(0, 0, 0) scale(1); }
  to   { opacity: 1;    transform: translate3d(0, -14px, 0) scale(1.03); }
}
/* Fine grain, kills banding, adds material richness. ~3%, soft-light. */
.grain {
  position: fixed; inset: 0; z-index: 1; pointer-events: none; opacity: 0.035; mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Refined hero rhythm */
.hero { padding-top: clamp(7.5rem, 15vh, 10.5rem); }
.hero-copy { max-width: 42rem; }
.hero .label { letter-spacing: 0.2em; color: var(--dim); }
.hero .label .kp { color: var(--brand); }
.hero h1 {
  font-size: clamp(2.9rem, 1.4rem + 5.3vw, 5.6rem); letter-spacing: -0.046em; line-height: 0.98; font-weight: 600;
  margin-top: 1.5rem;
  background: linear-gradient(176deg, #ffffff 8%, #ececf1 52%, #c4c4cf 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  padding-bottom: 0.06em;   /* room for descenders under the clip */
}
.hero .lede { margin-top: 1.7rem; color: #b7b7be; max-width: 40ch; line-height: 1.56; }
.hero .cta { margin-top: 2.3rem; }
.hero-note { color: var(--faint); }

/* Buttons, a considered surface, a soft lift on hover */
.btn-primary {
  background: linear-gradient(180deg, #fcfcfe, #e7e7ec);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.75), 0 8px 24px -10px rgba(0,0,0,0.55);
}
.btn-primary:hover {
  background: linear-gradient(180deg, #ffffff, #f1f1f4); transform: translateY(-1px);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.85), 0 14px 34px -12px rgba(0,0,0,0.7);
}
.btn-ghost { background: rgba(255,255,255,0.02); }
.btn-ghost:hover { background: rgba(255,255,255,0.05); }

/* Product staging, floated, glowing, elevated */
.hero-stage { position: relative; isolation: isolate; margin-top: clamp(3.5rem, 7vw, 6rem); }
.hero-stage::before {
  content: ""; position: absolute; z-index: -1; inset: -8% -6% -22% -6%;
  background: radial-gradient(58% 62% at 50% 26%, rgba(142,114,255,0.20), rgba(142,114,255,0.05) 46%, transparent 72%);
  filter: blur(36px);
}
.hero-stage .loupe {
  box-shadow:
    0 52px 150px -50px rgba(0,0,0,0.95),
    0 30px 80px -40px rgba(46,30,92,0.55),
    inset 0 1px 0 rgba(255,255,255,0.055);
  transform: translate3d(var(--px, 0px), var(--py, 0px), 0);
  transition: transform 0.5s var(--ease-lux);
}

/* Entrance choreography, on load, staggered, blur-to-sharp */
@keyframes lux-rise {
  from { opacity: 0; transform: translateY(24px); filter: blur(7px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}
@keyframes lux-stage {
  from { opacity: 0; transform: translateY(52px) scale(0.985); filter: blur(10px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}
.a-1, .a-2, .a-3, .a-4, .a-5 { opacity: 0; animation: lux-rise 1s var(--ease-lux) forwards; }
.a-1 { animation-delay: 0.08s; }
.a-2 { animation-delay: 0.20s; animation-duration: 1.1s; }
.a-3 { animation-delay: 0.36s; }
.a-4 { animation-delay: 0.50s; }
.a-5 { animation-delay: 0.60s; }
.a-stage { opacity: 0; animation: lux-stage 1.4s var(--ease-lux) 0.66s forwards; }

@media (prefers-reduced-motion: reduce) {
  .atmos { animation: none; }
  .a-1, .a-2, .a-3, .a-4, .a-5, .a-stage { opacity: 1; animation: none; }
  .hero-stage .loupe { transition: none; }
}

/* ---- Boot gate ------------------------------------------------------------
   The page is held blank until the hero photo has decoded, then everything
   above the fold fades up together. Without it the hero paints as black, then
   the photo pops, then the nav, then the headline — an arrival in pieces.
   <html class="booting"> is set by an inline script in the head, so a reader
   with JS off never sees the gate. */
body { opacity: 1; transition: opacity 0.75s var(--ease-lux); }
html.booting body { opacity: 0; }
/* Hold the entrance choreography at frame 0 so it plays *with* the fade-in
   rather than finishing invisibly behind it. */
html.booting .a-1, html.booting .a-2, html.booting .a-3,
html.booting .a-4, html.booting .a-5, html.booting .a-stage { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { body { transition: none; } }

/* Scroll reveals share the hero's blur-to-sharp quality */
.rv { transform: translateY(18px); filter: blur(4px);
  transition: opacity 0.85s var(--ease-lux), transform 0.85s var(--ease-lux), filter 0.85s var(--ease-lux); }
.rv.in { filter: blur(0); }
@media (prefers-reduced-motion: reduce) { .rv { filter: none; transition: none; } }

/* ============================================================
   Photo-forward hero, the photograph is the hero. One line,
   one button, no noise. The app is shown below, not told here.
   ============================================================ */
.hero { position: relative; min-height: 100vh; min-height: 100svh; display: flex; align-items: flex-end; padding: 0; overflow: hidden; }
.hero-media { position: absolute; inset: 0; z-index: 0; }
.hero-media img { width: 100%; height: 100%; object-fit: cover; transform: scale(1.06); animation: hero-zoom 18s var(--ease-lux) forwards; }
@keyframes hero-zoom { to { transform: scale(1); } }
.hero-scrim {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(180deg, rgba(10,10,11,0.5) 0%, rgba(10,10,11,0) 18%, rgba(10,10,11,0) 38%, rgba(10,10,11,0.58) 66%, rgba(10,10,11,0.96) 100%),
    linear-gradient(90deg, rgba(10,10,11,0.74) 0%, rgba(10,10,11,0.22) 38%, transparent 64%);
}
.hero-inner {
  position: relative; z-index: 2;
  margin-left: clamp(1.5rem, 5vw, 4rem);       /* pinned bottom-left like the preview, not centered */
  padding-bottom: clamp(3rem, 8vh, 6rem);
}
.hero h1 {
  margin: 0; max-width: 15ch;
  font-size: clamp(2.6rem, 1.3rem + 4.6vw, 5.2rem); letter-spacing: -0.042em; line-height: 1.0; font-weight: 600;
  background: none; color: #fff; -webkit-text-fill-color: #fff;
  text-shadow: 0 2px 40px rgba(0,0,0,0.4);
}
.hero-tag {
  margin-top: 1.1rem; color: rgba(255,255,255,0.82); letter-spacing: -0.012em;
  font-size: clamp(1.02rem, 0.95rem + 0.4vw, 1.24rem); text-shadow: 0 1px 16px rgba(0,0,0,0.45);
}
.hero .btn-primary { margin-top: 2rem; }

/* nav sits over the photo, keep it legible, let it settle on scroll */
.nav-link { color: rgba(255,255,255,0.74); }
.nav-link:hover { color: #fff; }
.brand { color: #fff; }

@media (prefers-reduced-motion: reduce) {
  .hero-media img { animation: none; transform: none; }
}

/* ============================================================
   Hero polish, no overscroll gap, nav appears on scroll, and
   the photograph "develops" itself: regions of grade breathing
   in and out, the way the app adjusts an image.
   ============================================================ */
html { overscroll-behavior: none; background: #0a0a0b; }

/* A 222-byte blurred stand-in for the hero, inlined so it paints on the very
   first frame. The boot gate's wait is then the photograph arriving out of
   focus rather than a black rectangle. It sits on <html>, behind the body
   fade, and the opaque body covers it for good once the fade completes. */
html.hero-boot::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: #0a0a0b url("data:image/webp;base64,UklGRtYAAABXRUJQVlA4IMoAAAAQBgCdASogABUAPrFMokunJCMhqAqo4BYJZQCdMoAlwyGaxnozR2H+wT30G9bTfXThaIT73NkpdxgA/unV0ds78PFgs9O1oGd3swFlhJGxeQb0ZgtHA3CbCBYyMvdYzcs7ycM3I8h0eXk29ijH7tyfDGAgF8My1zbFQZj0NC+o/qOluKWvDzMw4/yXhfMWJ1wfHZ6dTayEa7bqQnFpbFeTFvegEh+Har9krhy8i63kZDXOdp89TXIySHorhfBfOOCvKRjJFYwTYAAA") center / cover no-repeat;
  /* brightness is tuned to the settled hero (scrim + vignette included), so the
     sharp photo lands on top of the blur without a jump in exposure. */
  filter: blur(26px) saturate(1.1) brightness(0.68); transform: scale(1.1);
}

/* Over the hero only a faded brand mark shows; it firms up to full and the
   rest of the bar (links + download) materializes once you scroll past. */
.nav {
  transition: background 0.3s, border-color 0.3s, backdrop-filter 0.3s;
}
/* The mark is hidden by default (the mid-scroll band) so it never collides
   with the headline rising into the nav row. It shows faded + grayscale only
   at the very top, then fades back in solid + color once the bar settles. */
.nav .brand {
  opacity: 0; text-shadow: 0 1px 20px rgba(0,0,0,0.55);
  transition: opacity 0.4s var(--ease-lux), text-shadow 0.3s;
}
.nav .brand img { opacity: 1; filter: grayscale(1); transition: opacity 0.4s var(--ease-lux), filter 0.55s var(--ease-lux); }
.nav.at-top .brand { opacity: 0.5; }
.nav.at-top .brand img { opacity: 0.72; }
/* Nav right: over the hero, ONLY the Download shows, as a very subtle liquid-glass chip;
   Pricing (and anything added later) is hidden until you scroll. Once the bar settles the
   glass firms into the solid button and the rest fades in — mirroring the brand mark. */
.nav .nav-right { opacity: 1; }
.nav-right .btn-primary, .nav-right .nav-link {
  transition: background 0.4s var(--ease-lux), color 0.4s var(--ease-lux),
    border-color 0.4s var(--ease-lux), box-shadow 0.4s var(--ease-lux),
    -webkit-backdrop-filter 0.4s var(--ease-lux), backdrop-filter 0.4s var(--ease-lux),
    opacity 0.4s var(--ease-lux), text-shadow 0.3s;
}
/* hide everything except the download while over the hero (future items get hidden too) */
.nav:not(.scrolled) .nav-right > :not(.btn-primary) { opacity: 0; pointer-events: none; }
/* the download: barely-there glass — it frosts and faintly lifts the photo behind it, with a
   thin edge sheen and low-opacity text. Almost invisible until you look for it. */
.nav:not(.scrolled) .nav-right .btn-primary {
  background: rgba(255,255,255,0.03); color: rgba(255,255,255,0.68); border-color: rgba(255,255,255,0.08);
  -webkit-backdrop-filter: blur(9px) saturate(1.15) brightness(1.02);
          backdrop-filter: blur(9px) saturate(1.15) brightness(1.02);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.13);
  text-shadow: 0 1px 8px rgba(0,0,0,0.4);
}
/* the top-bar download stays flat on hover — no lift, no drop shadow (elevation feels wrong here) */
.nav .nav-right .btn-primary:hover { transform: none; box-shadow: none; }
.nav:not(.scrolled) .nav-right .btn-primary:hover {
  background: rgba(255,255,255,0.12); border-color: rgba(255,255,255,0.22); color: #fff;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.16);
}
.nav.scrolled .brand { opacity: 1; text-shadow: none; }
.nav.scrolled .brand img { opacity: 1; filter: grayscale(0); }
.nav.scrolled .nav-right { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .nav .brand, .nav .brand img, .nav .nav-right { transition: none; }
}

/* Progressive AI-selection develop. The photo loads in full color and stays CONSTANT.
   Fovea selects one region at a time and applies a local develop to just it — right
   buildings, left buildings, sky, road — its violet contour visible while it edits, then
   fading out before the next. The region cutouts + contours are the app's real AI masks
   (from the heromask tool); each cutout aligns via object-fit: cover, and the contour SVG
   uses viewBox + slice so both track the same crop as the base at every viewport. */
.hero-media { animation: hero-zoom 22s var(--ease-lux) forwards; transform-origin: 52% 46%; }
.hero-media img { position: absolute; inset: 0; transform: none; animation: none; }   /* stack the layers; kill per-img zoom */
.hero-media img.base { filter: none; }                         /* the photo, constant */
.hero-media img.region { will-change: filter, opacity; }
.hero-media img.region.r1, .hero-media img.region.r2, .hero-media img.region.r4 { filter: none; }  /* identical to base until edited */
.hero-media img.region.r3 { opacity: 0; }                      /* blue-sky layer, hidden until its edit */

.hero.play img.region.r1 { animation: editWarm 0.8s cubic-bezier(0.4,0,0.2,1) 1.0s both; }   /* right buildings */
.hero.play img.region.r2 { animation: editWarm 0.8s cubic-bezier(0.4,0,0.2,1) 3.2s both; }   /* left buildings  */
.hero.play img.region.r3 { animation: editSky  1.0s cubic-bezier(0.4,0,0.2,1) 5.4s both; }   /* sky             */
.hero.play img.region.r4 { animation: editRoad 0.8s cubic-bezier(0.4,0,0.2,1) 7.6s both; }   /* road (ground)   */
@keyframes editWarm { to { filter: brightness(1.2) contrast(1.12) saturate(1.62) sepia(0.16); } }  /* golden relight */
@keyframes editRoad { to { filter: brightness(1.14) contrast(1.2) saturate(1.12); } }              /* lift the cobbles */
@keyframes editSky  { to { opacity: 0.7; } }                                                       /* soft blue fades in */

/* The AI selection contour, in the app's brand violet. Draws on, holds while the region
   develops, then fades out before the next region is picked. */
.hero-sel { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none; overflow: visible; }
.hero-sel .edge { fill: none; stroke: var(--brand); stroke-width: 6; stroke-linecap: round; stroke-linejoin: round; opacity: 0;
  filter: drop-shadow(0 0 7px rgba(142,114,255,0.95)) drop-shadow(0 0 20px rgba(142,114,255,0.5)); }
.hero.play .edge.e1 { animation: heroEdge 1.9s cubic-bezier(0.45,0,0.35,1) 0.4s both; }
.hero.play .edge.e2 { animation: heroEdge 1.9s cubic-bezier(0.45,0,0.35,1) 2.6s both; }
.hero.play .edge.e3 { animation: heroEdge 1.9s cubic-bezier(0.45,0,0.35,1) 4.8s both; }
.hero.play .edge.e4 { animation: heroEdge 1.9s cubic-bezier(0.45,0,0.35,1) 7.0s both; }
@keyframes heroEdge {
  0%   { stroke-dashoffset: var(--len); opacity: 0; }
  12%  { opacity: 1; }
  30%  { stroke-dashoffset: 0; opacity: 1; }   /* drawn */
  72%  { stroke-dashoffset: 0; opacity: 1; }   /* held while the region edits */
  100% { stroke-dashoffset: 0; opacity: 0; }   /* fade out before the next */
}

/* Reduced motion / no-JS fallback: pin the finished develop, no contour, no motion. */
@media (prefers-reduced-motion: reduce) {
  .hero-media { animation: none; transform: none; }
  .hero-media img.region.r1, .hero-media img.region.r2 { filter: brightness(1.2) contrast(1.12) saturate(1.62) sepia(0.16); }
  .hero-media img.region.r4 { filter: brightness(1.14) contrast(1.2) saturate(1.12); }
  .hero-media img.region.r3 { opacity: 0.7; }
  .hero-sel { display: none; }
}

/* ============================================================
   Copy pass: the cheeky muted "(again)", the "maybe" verdict,
   and room for the longer headline.
   ============================================================ */
.hero h1 { max-width: 22ch; font-size: clamp(2.3rem, 1.3rem + 3.3vw, 4.15rem); font-weight: 600; letter-spacing: -0.04em; line-height: 1.02; }
/* "again" is a full-size italic aside, softly dimmed. A comma sets it off, not
   shrunken parens. -webkit-text-fill-color is needed because the headline pins it
   to #fff, which would otherwise win over plain `color`. */
.hero h1 .again {
  font-style: italic; font-weight: 420; letter-spacing: -0.01em;
  color: rgba(247,244,255,0.62); -webkit-text-fill-color: rgba(247,244,255,0.62);
}

/* maybe (amber) key flash in the live cull demo */
.kc.is-maybe { background: color-mix(in oklab, var(--maybe) 32%, var(--kc-bg)); color: #f4e2bd; }

/* ============================================================
   Editorial serif for the display headings. Body, UI, labels,
   buttons, and the app mockups stay in Inter/SF Pro so the
   product still reads as itself. And a smaller, quieter hero CTA.
   ============================================================ */
:root { --f-serif: "Newsreader", "Iowan Old Style", Georgia, "Times New Roman", serif; }

h1, h2, h3 {
  font-family: var(--f-serif); font-weight: 500; letter-spacing: -0.012em; line-height: 1.08;
}

.hero h1 {
  font-family: var(--f-serif); font-weight: 500; letter-spacing: -0.02em; line-height: 1.04;
  font-size: clamp(2.3rem, 1.3rem + 3.4vw, 4.2rem);   /* slightly smaller, matching the preview */
}
.hero h1 .again { font-weight: 450; }   /* the serif italic aside, kept dimmed from before */

/* section headings breathe a touch more as a serif */
h2 { line-height: 1.06; }

/* Hero CTA: smaller and flatter so it doesn't fight the headline */
.hero .btn-primary {
  height: 38px; padding: 0 1.05rem; font-size: 0.85rem; font-weight: 500; border-radius: 9px;
  background: rgba(248,248,250,0.94); box-shadow: none;
}
.hero .btn-primary:hover { background: #fff; box-shadow: none; transform: translateY(-1px); }

/* ============================================================
   Kinetic hero: "Focus on taking photos. Not <word>." The word
   cycles; it sits at the line end so its width never reflows.
   ============================================================ */
.hero h1 { max-width: none; }
/* Second line is a smaller, secondary beat so the two sentences aren't equal-weight. */
.hero h1 .l2 { display: block; font-size: 0.62em; line-height: 1.14; margin-top: 0.14em; }
.swap { display: inline-block; font-style: italic;
  color: #b3a0ff; -webkit-text-fill-color: #b3a0ff;
  text-shadow: 0 2px 30px rgba(0,0,0,0.45), 0 0 26px rgba(142,114,255,0.4);
  will-change: transform, opacity, filter; }
.swap.out { animation: swapOut 0.4s var(--ease-lux) forwards; }
.swap.in  { animation: swapIn 0.5s var(--ease-lux); }
@keyframes swapOut { to { opacity: 0; transform: translateY(-0.3em); filter: blur(5px); } }
@keyframes swapIn  { from { opacity: 0; transform: translateY(0.32em); filter: blur(5px); } to { opacity: 1; transform: none; filter: blur(0); } }
@media (prefers-reduced-motion: reduce) { .swap.out, .swap.in { animation: none; } }

/* ============================================================
   Pricing page, second pass: the app and the critique refill
   stacked in time order, the per-photo math, and the update year
   spelled out on a timeline instead of buried in an FAQ.
   ============================================================ */

/* Every section on this page opens label → h2 → lede on the same scale. The
   .row sections already get this from .row h2 / .row .lede; this matches it for
   the full-width ones, which were drifting to their own inline values. */
.sec-h2 { margin-top: 1.1rem; }
.sec-h2 + .lede { margin-top: 1.3rem; }

/* One column, one card wide. Two cards abreast read as "pick one" and this is
   not a choice: you buy the app, then months later you might top up. Bottom
   margin matches .pad so the block sits on the same vertical rhythm as every
   other section instead of butting into the divider below it. */
.buy-stack { max-width: 560px; margin-inline: auto;
  margin-top: 3.25rem; margin-bottom: clamp(4rem, 8vw, 7.5rem); }

/* The connector carries the "and then, later" beat that side-by-side lost. */
.then { display: grid; justify-items: center; padding: 1.5rem 0; }
.then i { display: block; width: 1px; height: 40px;
  background: linear-gradient(180deg, color-mix(in oklab, var(--brand) 45%, transparent), var(--hairline)); }

/* Refill panel: darker than the buy card and un-glowed, so it reads as a
   footnote to the purchase rather than a second thing to weigh up. Side
   padding matches .tier.feat exactly, or the two stacked panels would put
   their text columns 5px apart. */
.refill { border: 1px solid var(--hairline); border-radius: 12px; padding: 1.7rem 1.9rem 1.8rem;
  background: var(--bg); }
.refill .label { display: block; margin-bottom: 0.85rem; }
.refill-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.refill-head .rt { font-size: 1.08rem; font-weight: 600; letter-spacing: -0.02em; color: var(--ink); }
.refill .rolls { margin-top: 1.15rem; }
.refill .roll-row:last-child { border-bottom: 0; padding-bottom: 0; }
.refill .note { margin-top: 1.15rem; color: var(--faint); font-size: 0.83rem; line-height: 1.55; }

/* The badge is inset 1.5rem by default, which on the roomier .feat card leaves
   it sitting closer to the corner than any other content. Pin it to the card's
   own padding, and to the title's optical centre. */
.tier.feat .tag-badge { top: 2.3rem; right: 1.9rem; }
/* On a narrow card the absolute badge would sit on top of the name. */
@media (max-width: 560px) {
  .tier .tag-badge { position: static; align-self: flex-start; margin-bottom: 0.9rem; }
}

.t-alt { text-align: center; color: var(--faint); font-size: 0.85rem; margin-top: 0.85rem; }
.t-alt a { color: var(--dim); text-decoration: underline; text-underline-offset: 2px; transition: color 0.2s; }
.t-alt a:hover { color: var(--ink); }

/* Roll sizes: a receipt, not three cards competing for the click. */
.rolls { margin-top: 1.5rem; border-top: 1px solid var(--hairline); }
.roll-row { display: flex; align-items: baseline; gap: 0.6rem; padding: 0.72rem 0;
  border-bottom: 1px solid var(--hairline); font-size: 0.93rem; }
.roll-row .rn { color: var(--ink); }
.roll-row .rc { color: var(--faint); font-size: 0.8rem; }
.roll-row .rp { margin-left: auto; font-weight: 600; }

/* Update timeline. The line fades out to the right: after the year, nothing
   more arrives, and the dots go from solid to hollow to say the same thing. */
.tl { list-style: none; position: relative; display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 2rem; margin-top: 3.2rem; }
.tl::before { content: ""; position: absolute; top: 4px; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, color-mix(in oklab, var(--brand) 60%, transparent), var(--hairline) 46%, transparent); }
.tl-step { position: relative; padding-top: 2.3rem; }
.tl-step::before { content: ""; position: absolute; top: 0; left: 0; width: 9px; height: 9px; border-radius: 50%;
  background: var(--brand);
  box-shadow: 0 0 0 5px var(--bg), 0 0 18px -2px color-mix(in oklab, var(--brand) 70%, transparent); }
.tl-step:nth-child(2)::before { background: var(--bg); box-shadow: 0 0 0 5px var(--bg);
  border: 1.5px solid color-mix(in oklab, var(--brand) 55%, transparent); }
.tl-step:nth-child(3)::before { background: var(--bg); box-shadow: 0 0 0 5px var(--bg);
  border: 1.5px solid var(--hairline); }
.tl-when { display: block; font-size: 0.7rem; font-weight: 600; letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--faint); }
.tl-step b { display: block; margin-top: 0.55rem; font-size: 1.12rem; font-weight: 600;
  letter-spacing: -0.02em; color: var(--ink); }
.tl-step p { margin-top: 0.5rem; color: var(--dim); font-size: 0.94rem; max-width: 38ch; }
@media (max-width: 860px) {
  .tl { grid-template-columns: 1fr; gap: 0; }
  .tl::before { top: 0; bottom: 0; left: 4px; right: auto; width: 1px; height: auto;
    background: linear-gradient(180deg, color-mix(in oklab, var(--brand) 60%, transparent), var(--hairline) 46%, transparent); }
  .tl-step { padding: 0 0 2.1rem 1.9rem; }
  .tl-step:last-child { padding-bottom: 0; }
  .tl-step::before { top: 4px; }
}

/* ---- Legal pages (privacy, terms, refunds, legal notice) ----
   Long-form prose. Nothing else on the site sets body-copy rhythm at this
   length, so measure and vertical spacing are defined here rather than reused. */
/* Hero shares the prose measure so the h1 sits flush with the body column
   below it, rather than starting at the full 1120px wrap edge. */
.legal-hero { max-width: 68ch; padding-top: clamp(7rem, 14vh, 9.5rem); padding-bottom: 1rem; }
.legal-hero h1 { font-size: clamp(2rem, 1.6rem + 1.9vw, 3rem); }
.legal-hero .updated { margin-top: 1rem; color: var(--faint); font-size: 0.82rem; }
.legal { max-width: 68ch; margin-inline: auto; padding-bottom: clamp(4rem, 8vw, 7rem); }
.legal > p:first-child { margin-top: 0; }
.legal h2 { font-size: 1.3rem; letter-spacing: -0.022em; margin: 2.9rem 0 0.9rem; }
.legal h2:first-child { margin-top: 0; }
.legal h3 { font-size: 1rem; font-weight: 600; color: var(--ink); margin: 1.9rem 0 0.55rem; }
.legal p { color: var(--dim); font-size: 0.95rem; line-height: 1.68; margin-top: 0.9rem; }
.legal ul, .legal ol { margin: 0.9rem 0 0 1.15rem; display: grid; gap: 0.5rem; }
.legal li { color: var(--dim); font-size: 0.95rem; line-height: 1.6; }
.legal a { color: var(--ink); text-decoration: underline; text-underline-offset: 2.5px;
  text-decoration-color: #3a3a40; transition: text-decoration-color 0.2s; }
.legal a:hover { text-decoration-color: var(--ink); }
/* A button inside prose is still a button. Without this the .legal link styling above repaints it
   as underlined body text in --ink, which on the primary button's own light fill is unreadable. */
.legal a.btn { text-decoration: none; }
.legal a.btn-primary { color: #08080a; }
.legal a.btn-ghost { color: var(--ink); }
.legal strong { color: var(--ink); }

/* Fact table (subprocessors, what we hold, imprint rows) */
.legal table { width: 100%; border-collapse: collapse; margin-top: 1.2rem; }
.legal th { text-align: left; font-size: 0.7rem; letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--faint); font-weight: 600; padding: 0 1rem 0.7rem 0; border-bottom: 1px solid var(--hairline); }
.legal td { color: var(--dim); font-size: 0.9rem; line-height: 1.55; vertical-align: top;
  padding: 0.85rem 1rem 0.85rem 0; border-bottom: 1px solid var(--hairline); }
.legal td:last-child, .legal th:last-child { padding-right: 0; }
.legal td strong { display: block; color: var(--ink); font-weight: 500; }
@media (max-width: 620px) {
  .legal table, .legal tbody, .legal tr, .legal td { display: block; width: 100%; }
  .legal thead { display: none; }
  .legal tr { border-bottom: 1px solid var(--hairline); padding: 0.9rem 0; }
  .legal td { border: 0; padding: 0.15rem 0; }
}

/* Callout: the plain-English summary at the top of a policy. */
.legal-note { border: 1px solid var(--hairline); border-left: 2px solid var(--brand);
  border-radius: 0 10px 10px 0; background: var(--lift); padding: 1.15rem 1.35rem; margin-top: 1.6rem; }
.legal-note p { margin-top: 0.55rem; color: var(--dim); }
.legal-note p:first-child { margin-top: 0; }

/* Unfilled entity detail. Loud on purpose: this must never reach production
   unnoticed, so it renders as a warning chip rather than plain text. */
.todo { display: inline-block; background: color-mix(in oklab, var(--maybe) 22%, transparent);
  border: 1px dashed color-mix(in oklab, var(--maybe) 55%, transparent); border-radius: 4px;
  padding: 0.05em 0.45em; color: #f0d9a8; font-size: 0.86em; font-weight: 500;
  font-family: ui-monospace, "SF Mono", Menlo, monospace; }

/* Index of policies on /legal */
.legal-index { display: grid; gap: 10px; margin-top: 2rem; }
.legal-index a { display: block; background: var(--lift); border: 1px solid var(--hairline);
  border-radius: 11px; padding: 1.15rem 1.35rem; text-decoration: none; transition: border-color 0.25s; }
.legal-index a:hover { border-color: #2c2c31; }
.legal-index b { display: block; font-size: 1rem; font-weight: 600; color: var(--ink); letter-spacing: -0.015em; }
.legal-index span { display: block; margin-top: 0.3rem; color: var(--dim); font-size: 0.88rem; line-height: 1.5; }
/* The refill card matches the buy card's padding so both titles and prices sit
   on the same baselines, and its checklist drops the extra rule that would
   double up with the roll receipt's last row. */
/* On a phone the "one-time · tax included" qualifier gets its own line instead
   of squeezing into the gutter beside the amount. */
@media (max-width: 560px) {
  .tier .t-price { flex-wrap: wrap; }
  .tier .t-price .per { flex-basis: 100%; }
}
