:root {
  /* Light-locked on purpose -- do not reintroduce a dark palette here without
     redoing the whole design for it.

     This product is drawn light end to end: the logo and tagline in the header
     are fixed-colour raster images made for a white background, and the
     landing, hero and auth palettes (--landing-* and --hero-* below) are
     deliberately theme-locked light. The dark palette this file used to carry
     only ever swapped about fifteen tokens, so every fixed light surface it
     did not know about kept its light artwork while the text on top flipped to
     a light dark-mode colour -- the logo and whole blocks of copy disappeared
     for anyone whose phone is set to dark. Patching it one surface at a time
     produced a new report each time (2026-08-27, and again 2026-09-12: "this
     is a complaint from many"), so the mode is gone instead.

     The value is "only light", not plain "light". Plain "light" only states
     which schemes the page supports; the "only" keyword is what actually
     forbids the browser from applying its own dark transformation, and it is
     the documented opt-out from Chrome's Auto Dark Theme (which is what
     Android's "dark theme for websites" and the installed TWA both use). We
     shipped plain "light" first and a user on a dark-themed phone still got
     a fully inverted page 34 minutes later -- the logo image itself came
     back darkened, which no stylesheet rule can do. It also makes native
     form controls, scrollbars and the canvas behind the page render light.
     Every
     @media(prefers-color-scheme: dark) rule was deleted along with this -- the
     media query still matches on a dark phone even when the page is
     light-locked, so leaving any behind would bring the mismatch straight
     back. The HTML files carry <meta name="color-scheme" content="light"> for
     the same reason, so the first paint is light too. */
  color-scheme: only light;
  --bg: #f7f8fb;
  --panel: #ffffff;
  /* Fourteen salon-console rules (pay cards, the poster and team panels, the
     package editor, .salon-input) were written against --card, which was
     never defined -- so all of them rendered with NO background, and the
     modal panels were see-through. Found 2026-10-14 by the services menu. */
  --card: var(--panel);
  --text: #13161c;
  --muted: #626a7d;
  /* Same plum as the StylesStudio.ai landing/auth palette below (kept as a
     separate --landing-primary token too since that one is deliberately
     theme-locked -- see its comment) -- unifies every button, active tab,
     link, and focus ring app-wide with the landing page instead of the
     unrelated indigo this used to be, so the app doesn't feel like a
     different, older product once a user logs in. */
  --accent: #4c145c;
  --accent-hover: #3b0f48;
  --accent-contrast: #ffffff;
  --border: #e3e6ee;
  --error: #dc2626;
  --success: #16a34a;
  --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.05), 0 1px 3px rgba(16, 24, 40, 0.06);
  --shadow-md: 0 4px 10px rgba(16, 24, 40, 0.06), 0 8px 24px rgba(16, 24, 40, 0.08);
  --radius: 10px;
  --radius-lg: 16px;

  /* StylesStudio.ai palette -- used by the public landing page and the
     pre-login auth/forgot/reset screens it leads into (see showScreen()'s
     studio-active toggle in app.js). Deliberately separate names from the
     tokens above and never redefined in the dark-mode block below, so this
     palette stays put regardless of system theme. */
  --landing-bg: #ffffff;
  --landing-surface: #f7f2fa;
  --landing-primary: #4c145c;
  --landing-primary-hover: #3b0f48;
  --landing-text: #1f1a24;
  --landing-muted: #6b6274;
  --landing-mint-bg: #ddf4e8;
  --landing-mint-text: #1f6b45;
  --landing-border: #e8e0ee;
  --landing-shadow: 0 10px 28px rgba(76, 20, 92, 0.09), 0 2px 8px rgba(76, 20, 92, 0.06);
  --landing-radius: 18px;

  /* Hero palette. Scoped to the landing hero only -- every other section,
     the auth screens and the logged-in app keep using the --landing-*
     tokens above, which are untouched.

     Derived from the approved logo, not invented: the hex values marked
     "logo" below were sampled directly from web/brand/logo-wordmark.png
     (deep indigo #060334 dominant, violet #50079c, purple #7809a8 /
     #850aa9, magenta #980da3 / #a50f9f, warm orange #f27129). The logo's
     deep tone is indigo rather than plum, and its violet/magenta are more
     saturated than a generic purple ramp, so the hero follows the logo.

     Contrast (verified, WCAG AA needs 4.5:1 for body text, 3:1 for large):
       --hero-text     on --hero-canvas  17.1:1
       --hero-muted    on --hero-canvas   6.1:1
       #ffffff         on --hero-violet  10.2:1
       #ffffff         on --hero-magenta  6.6:1
       --hero-mint-ink on --hero-mint     6.2:1
     --hero-magenta-glow and --hero-coral are background glows only and are
     never used behind or as text. */
  --hero-canvas: #fffcfa;
  --hero-lavender: #f7f2ff;
  --hero-ink: #1a0b3d;          /* logo #060334, lifted for use as a surface */
  --hero-violet: #5c0ba6;       /* logo #50079c */
  --hero-violet-deep: #48067f;  /* pressed/hover stop */
  --hero-purple: #8a18c4;       /* logo #7809a8 / #850aa9 */
  --hero-magenta: #a50f9f;      /* logo #a50f9f */
  --hero-magenta-glow: #d946ef; /* decorative glow only, never text */
  --hero-coral: #fb7185;        /* decorative glow only, never text */
  --hero-orange: #f27129;       /* logo #f27129 */
  --hero-text: #21152a;
  --hero-muted: #685c70;
  --hero-mint: #ddf8ec;
  --hero-mint-ink: #12664a;
  --hero-line: #ece3f6;
  --hero-line-strong: #d8c9e8;
  --hero-card-shadow: 0 1px 2px rgba(33, 21, 42, 0.04), 0 12px 32px rgba(72, 6, 127, 0.08);
  --hero-cta-shadow: 0 6px 16px rgba(92, 11, 166, 0.24);
  --hero-radius: 20px;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
  /* Without this, mobile browsers (particularly Android Chrome/WebView)
     auto-boost font sizes on a page that has any horizontally-overflowing
     content, and re-adjust the boost per screen/reflow -- text sizes
     visibly grew and shrank while navigating (real user report,
     2026-08-27, from the installed Android app). Opting out keeps sizing
     exactly what the CSS specifies everywhere, matching the fixed
     overflow bugs above rather than papering over them. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

h1,
h2,
h3,
h4 {
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.25;
}

.app-shell {
  max-width: 720px;
  margin: 0 auto;
  padding: 2rem 1.25rem 4rem;
  transition: max-width 0.15s ease;
}

body.admin-console-active .app-shell {
  max-width: 1240px;
  padding: 1.5rem 1.5rem 3rem;
}

/* The salon console. Same widen-the-shell convention as
   admin-console-active above, and for the same reason: this is a business
   working screen, not a consumer funnel. A salon owner opening it on a laptop
   was being handed a 680px column with the rest of the screen spent on
   gradient, while the booking list it exists to show stacked one card per row.

   Scoped to the dashboard screen only. The studio screens a salon shares with
   individuals keep the narrower column, which is right for a flow built around
   one photograph. */
body.salon-console-active .app-shell {
  max-width: 1240px;
  padding: 1.5rem 1.5rem 3rem;
}

/* The two fixed radial brand blobs (body::before / ::after) are the app's
   consumer personality. Behind a booking list they are just saturated colour
   in the corner of a screen somebody works in for eight hours, so the console
   sits on the flat background instead. Individual screens keep them. */
body.salon-console-active::before,
body.salon-console-active::after {
  display: none;
}

/* A business console does not need to be sold to every morning. The wordmark
   and the "see your best look" tagline are consumer marketing, and on this
   screen they are the first two things between the owner and their day. */
body.salon-console-active .header-brand {
  display: none;
}

/* Three rows of navigation was the problem: the app tabs, a heading repeating
   the tab already lit, then the dashboard's own tab strip. */
body.salon-console-active #salonDashboardHeading {
  display: none;
}

/* The day's bookings, as a list that uses the width rather than a column of
   full-width cards. Auto-fill rather than a fixed count so it is three across
   on a laptop, two on a tablet and one on a phone without another breakpoint. */
body.salon-console-active .salon-lead-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 0.75rem;
  align-items: start;
}

/* A date picker stretched across 1150px looks like a mistake. Filters sit at
   the width of the value they hold, left-aligned, the way every console does
   it -- the room freed up is the point of the wider shell, not something to
   fill. */
body.salon-console-active .salon-filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: end;
}

body.salon-console-active .salon-filter {
  flex: 0 1 220px;
  min-width: 180px;
}

/* The summary tiles read as a row of numbers, not three big boxes. */
body.salon-console-active .salon-summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.75rem;
}

@media (max-width: 700px) {
  /* Back to a single column on a phone: two 320px cards side by side would
     each be unreadable, and a salon owner works from a phone. */
  body.salon-console-active .salon-lead-list {
    grid-template-columns: 1fr;
  }
  body.salon-console-active .app-shell {
    padding-left: 0.75rem;
    padding-right: 0.75rem;
  }
}

/* Read-only reference-photo gallery flanking the hairstyle-recommendation
   form once identity=="men"/"women" is picked (2026-08-20) -- purely
   visual (nothing here is clickable/selectable), so a person can see what
   a named style actually looks like. .hairstyle-guide-layout is plain
   block by default (a no-op on every other screen/identity); only becomes
   a 3-column grid once this body class is set, same widen-the-shell
   convention as admin-console-active above. */
body.hairstyle-guide-active .app-shell {
  max-width: 1400px;
  padding: 1.5rem 1.5rem 3rem;
}

body.hairstyle-guide-active .hairstyle-guide-layout {
  display: grid;
  grid-template-columns: minmax(220px, 300px) minmax(320px, 480px) minmax(220px, 300px);
  gap: 1.5rem;
  align-items: start;
  justify-content: center;
}

.app-header h1 {
  margin: 0 0 0.2rem;
  font-size: 1.5rem;
  color: var(--accent);
}

.header-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding-bottom: 1.25rem;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--border);
}

.auth-status {
  font-size: 0.85rem;
  padding-top: 0.35rem;
}

.auth-link {
  color: var(--muted);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.12s ease;
}

.auth-link:hover {
  color: var(--accent);
}

.credits-badge {
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}

.subtitle {
  color: var(--muted);
  margin-top: 0;
}

.photo-guidelines {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  font-size: 0.85rem;
  color: var(--muted);
  margin: 0 0 1.25rem;
}

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 1.75rem;
  box-shadow: var(--shadow-sm);
}

.hairstyle-guide-layout {
  display: block;
}

/* Dense 2-across thumbnail grid rather than one item per row -- with
   haircuts and beard styles now sharing the same gallery (2026-08-20), a
   single-column stack would make the side rails absurdly tall. Shorter,
   4:3 (not 1:1) thumbnails compound that density without cropping away
   the hairline/silhouette that actually identifies the style. */
.hairstyle-guide-col {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.6rem;
  align-content: start;
}

.hairstyle-guide-heading {
  grid-column: 1 / -1;
  margin: 0.5rem 0 0;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.hairstyle-guide-heading:first-child {
  margin-top: 0;
}

.hairstyle-guide-card {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.4rem;
}

.hairstyle-guide-card img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: top;
  border-radius: calc(var(--radius) - 4px);
  background: var(--bg);
}

.hairstyle-guide-card span {
  font-size: 0.72rem;
  color: var(--muted);
  text-align: center;
}

/* Both haircut and beard cards are select-only (see renderHairstyleGuide's
   buildCard) -- this highlights whichever is currently picked for the
   "Pick My Own Styles" control (see syncGalleryGenerateControl). */
.hairstyle-guide-card.selected {
  border-color: var(--accent);
  border-width: 2px;
}

/* Appears once a haircut is selected in the gallery (see
   syncGalleryGenerateControl) -- placed right after analyzeButton, same
   "near the action, not buried below the tall gallery" placement principle
   as statusMessage's fix. Real user feedback (2026-08-22): selecting a
   look and generating it needed to be two separate, deliberate steps
   rather than generating immediately on click. */
#galleryGenerateWrap {
  margin-top: 1rem;
}

.gallery-generate-summary {
  margin: 0 0 0.5rem;
  font-weight: 600;
  color: var(--text);
}

/* Result card for a gallery-triggered generation (see tryGalleryHaircut). */
#galleryTryCardWrap {
  margin-top: 1rem;
}

/* Mobile-only presentation of the same gallery data (populated by
   renderHairstyleGuide alongside the desktop rails, never both visible at
   once) -- real bug fixed 2026-08-20: earlier this width simply hid the
   flanking rails with nothing in their place, so phones -- most of this
   app's traffic -- never saw the gallery at all. A single full-width
   column in true reading order, not the rails' left/right-interleaved
   split, which would read oddly if the two rails were just stacked. */
.hairstyle-guide-mobile {
  display: none;
}

@media (max-width: 980px) {
  body.hairstyle-guide-active .hairstyle-guide-layout {
    grid-template-columns: 1fr;
  }

  .hairstyle-guide-col {
    display: none;
  }

  body.hairstyle-guide-active .hairstyle-guide-mobile {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
    gap: 0.6rem;
    margin-top: 1.5rem;
  }
}

.capture-tabs {
  display: flex;
  gap: 0.4rem;
  margin-bottom: 1.25rem;
  background: var(--bg);
  padding: 0.3rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

.tab-button {
  flex: 1;
  padding: 0.55rem 1rem;
  border: none;
  background: transparent;
  color: var(--muted);
  font-weight: 600;
  border-radius: 7px;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

.tab-button:hover {
  color: var(--text);
}

.tab-button.active {
  background: var(--panel);
  color: var(--accent);
  box-shadow: var(--shadow-sm);
}

.pane.hidden,
.hidden {
  display: none !important;
}

/* What a good photo looks like, above the picker (2026-09-25). */
.photo-guide {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.6rem;
  margin-bottom: 0.6rem;
}

.photo-guide-item {
  margin: 0;
  text-align: center;
}

.photo-guide-item svg {
  width: 100%;
  max-width: 88px;
  height: auto;
}

.photo-guide figcaption {
  margin-top: 0.25rem;
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--muted);
}

.pg-frame {
  fill: var(--panel);
  stroke: var(--border);
  stroke-width: 2;
}

.pg-head,
.pg-body,
.pg-line {
  fill: none;
  stroke: var(--muted);
  stroke-width: 2.5;
  stroke-linecap: round;
}

.pg-eye {
  fill: var(--muted);
}

.photo-guide-good .pg-head,
.photo-guide-good .pg-body,
.photo-guide-good .pg-line {
  stroke: var(--accent);
}

.photo-guide-good .pg-eye {
  fill: var(--accent);
}

.photo-guide-tick {
  color: var(--success);
  font-weight: 700;
}

.photo-guide-cross {
  color: var(--error);
  font-weight: 700;
}

.photo-guide-note {
  margin: 0 0 0.9rem;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--muted);
}

#usePhotoAnywayButton {
  margin-top: 0.5rem;
}

.file-drop {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 140px;
  border: 1.5px dashed var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  text-align: center;
  padding: 1rem;
  background: var(--bg);
  color: var(--muted);
  transition: border-color 0.12s ease, color 0.12s ease;
}

.file-drop:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.file-drop input {
  display: none;
}

#video {
  width: 100%;
  border-radius: var(--radius);
  background: #000;
  display: block;
  /* Real user report (2026-09-01): the live preview showed the raw,
     unmirrored camera feed, so moving left felt like it moved you right on
     screen -- every standard front-camera experience (phone camera apps,
     video calls) mirrors the live preview to feel like a mirror, only
     un-mirroring the actual saved/sent photo. This is a display-only CSS
     transform: canvas.drawImage(video, ...) in capturePhoto (app.js)
     samples the video element's actual decoded frames,
     not its rendered/CSS-transformed output, so the captured photo -- and
     every backend face-detection/pose check on it -- is completely
     unaffected and stays correctly oriented. This is also exactly why
     web/app.js's setCameraReadiness deliberately never gives a left/right
     hint in its guidance text -- "which side reads as left" depends on
     mirroring, and this was the missing half of that fix. */
  transform: scaleX(-1);
}

.camera-controls {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.camera-readiness {
  margin: 0.6rem 0 0;
  font-size: 0.85rem;
  font-weight: 600;
  text-align: center;
}

.camera-readiness.not-ready {
  color: var(--error);
}

.camera-readiness.ready {
  color: var(--success);
}

/* Real user request (2026-08-31): guide framing visually inside the camera
   view itself (like a passport-photo/Face-ID-style oval), not just via a
   text sentence below the video -- the oval's border color and the
   checkmark badge react live to the same once-a-second readiness poll that
   already drove the plain text note (see pollCameraReadiness/
   setCameraReadiness in app.js), so this is a presentation layer on top of
   an existing signal, not a new check. */
.camera-viewport {
  position: relative;
  line-height: 0; /* avoids extra bottom gap under the inline video */
}

/* Live face outline drawn by camera-face-guide.js. Stretched over the whole
   viewport because its coordinates come from the landmarker in normalised
   0-1 space against the video frame, so the canvas has to cover exactly what
   the video covers for the outline to land on the actual face.
   object-fit matches the video's own rule below it; without that the outline
   drifts off the face on any stream whose aspect ratio differs from the
   viewport (the "draw the mesh where the face actually is" problem the Style
   Report already hit). pointer-events:none so it never swallows a tap meant
   for the manual capture button underneath. */
.camera-face-mesh {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  z-index: 2;
}

.camera-face-guide {
  position: absolute;
  top: 50%;
  left: 50%;
  /* Sized in real pixels by sizeCameraFaceGuide() in app.js, off the
     video's actual rendered box -- these width/height are only a fallback
     for the instant before that JS runs (e.g. before the camera stream's
     metadata has loaded). A plain width%/height% here would inherit
     whatever aspect ratio the camera stream happens to render at instead of
     staying a consistent, face-like oval (real user report, 2026-08-31). */
  width: 45%;
  height: 60%;
  transform: translate(-50%, -50%);
  border: 3px solid rgba(255, 255, 255, 0.6);
  border-radius: 50%;
  box-shadow: 0 0 0 999px rgba(0, 0, 0, 0.28);
  pointer-events: none;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.camera-face-guide.checking {
  border-color: rgba(255, 255, 255, 0.6);
}

.camera-face-guide.not-ready {
  border-color: var(--error);
  box-shadow: 0 0 0 999px rgba(0, 0, 0, 0.28);
}

.camera-face-guide.ready {
  border-color: var(--success);
  box-shadow: 0 0 0 999px rgba(0, 0, 0, 0.15), 0 0 20px rgba(22, 163, 74, 0.55);
}

.camera-face-guide-check {
  position: absolute;
  left: 50%;
  bottom: -16px;
  transform: translateX(-50%) scale(0.6);
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--success);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  font-weight: 700;
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.camera-face-guide.ready .camera-face-guide-check {
  opacity: 1;
  transform: translateX(-50%) scale(1);
}

.camera-readiness-overlay {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: 0.85rem 1rem 0.65rem;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0));
  border-radius: 0 0 var(--radius) var(--radius);
  /* Neutral/"checking" default -- overridden by the higher-specificity
     .camera-readiness.ready/.not-ready color rules above when those classes
     are also present. Needed because the video behind this is always dark
     regardless of site theme, unlike previewReadinessNote (same base
     .camera-readiness class, shown on a normal page background instead). */
  color: #fff;
}

button {
  font: inherit;
}

.camera-controls button,
#analyzeButton {
  padding: 0.65rem 1.1rem;
  border-radius: 8px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, transform 0.05s ease;
}

.camera-controls button:hover,
#analyzeButton:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

.camera-controls button:active,
#analyzeButton:active {
  transform: translateY(1px);
}

.camera-controls button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

#previewWrap {
  margin-top: 1.25rem;
  text-align: center;
}

#preview {
  max-width: 100%;
  max-height: 320px;
  border-radius: var(--radius);
  margin-bottom: 0.75rem;
  box-shadow: var(--shadow-sm);
}

.occasion-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.occasion-label {
  font-size: 0.9rem;
  color: var(--muted);
}

.sub-occasion-label {
  text-align: center;
  margin: 0 0 0.4rem;
}

.occasion-button {
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  font-size: 0.85rem;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.occasion-button:hover {
  border-color: var(--accent);
}

.occasion-button.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.occasion-definition {
  text-align: center;
  color: var(--muted);
  font-size: 0.78rem;
  line-height: 1.5;
  margin: 0 0 0.75rem;
}

/* Copy of .occasion-toggle/.occasion-label/.occasion-button -- identical
   pattern, new class names since this is a separate, required selection
   (no default active button) rather than occasion's pre-selected one. */
.identity-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.identity-label {
  font-size: 0.9rem;
  color: var(--muted);
}

.identity-button {
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  font-size: 0.85rem;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.identity-button:hover {
  border-color: var(--accent);
}

.identity-button.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

/* Copy of .occasion-toggle/.occasion-label/.occasion-button -- identical
   pattern, new class names since this is a separate toggle group (haircut
   timing) from occasion. */
.timing-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.timing-label {
  font-size: 0.9rem;
  color: var(--muted);
}

.timing-button {
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  font-size: 0.85rem;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.timing-button:hover {
  border-color: var(--accent);
}

.timing-button.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

/* Copy of .occasion-toggle/.occasion-button's pattern -- the AI
   Recommended / Choose Myself mode switch, and the garment-type options
   grid that only appears in "Choose Myself" mode. */
.recommendation-mode-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.mode-button {
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  font-size: 0.85rem;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.mode-button:hover {
  border-color: var(--accent);
}

.mode-button.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.garment-type-grid {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

/* Kids garments are split into girls (left) and boys (right) columns -- see
   renderGarmentTypeGrid. The columns are flex items in the same wrapping
   container, so on a narrow phone they simply stack girls-then-boys instead
   of squeezing two unreadable columns side by side. */
.garment-type-grid-split {
  align-items: flex-start;
  gap: 1.25rem 1.75rem;
}

.garment-type-column {
  flex: 1 1 16rem;
  min-width: 0;
  max-width: 24rem;
}

.garment-type-column-heading {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: center;
  color: #6d28d9;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid rgba(109, 40, 217, 0.18);
}

.garment-type-column-items {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
}

.garment-type-button {
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  font-size: 0.85rem;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.garment-type-button:hover {
  border-color: var(--accent);
}

.garment-type-button.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.color-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
  font-size: 0.9rem;
  color: var(--muted);
  cursor: pointer;
}

.color-toggle input {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  cursor: pointer;
}

/* "Pick My Own Style" wizard step header (real user request, 2026-09-02) --
   see app.js's renderOwnStyleStep. */
.own-style-step-bar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0 0 1rem;
}

.own-style-step-label {
  margin: 0;
  font-weight: 600;
  color: var(--text);
}

#pickModeScreen .occasion-label {
  text-align: center;
  margin: 1.25rem 0 0.5rem;
}

#pickModeScreen .occasion-label:first-child {
  margin-top: 0;
}

.credits-remaining-note {
  text-align: center;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--success);
  margin: 0 0 0.75rem;
}

.credits-remaining-note.credits-remaining-low {
  color: var(--error);
}

.results-paywall-promo {
  margin-top: 1.75rem;
  padding: 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg);
  text-align: center;
}

.results-paywall-heading {
  margin: 0 0 0.9rem;
  font-weight: 700;
  color: var(--text);
}

.results-paywall-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  justify-content: center;
}

.results-paywall-actions button {
  flex: 1 1 200px;
  max-width: 260px;
}

.back-link {
  display: inline-block;
  margin-bottom: 1rem;
  padding: 0;
  border: none;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  transition: color 0.12s ease;
}

.back-link:hover {
  color: var(--accent);
}

.mode-selector {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 0.9rem;
}

.mode-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.4rem;
  padding: 1.75rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--panel);
  color: var(--text);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: border-color 0.12s ease, box-shadow 0.12s ease, transform 0.08s ease;
}

.mode-card:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

.mode-icon {
  font-size: 1.8rem;
}

.mode-title {
  font-weight: 700;
}

.mode-desc {
  font-size: 0.8rem;
  color: var(--muted);
}

/* --- Studio hero + vibrant mode cards ------------------------------------
   Real user feedback (2026-09-06): "after login page is very plain, it is
   not giving a vibe of a studio... we need to make sure we are using
   vibrant colors which attracts people." Everything below deliberately
   draws from the existing logo-derived --hero-* palette already used by the
   public landing page, so logging in now continues the same brand instead
   of dropping into a grey generic app. No layout/JS behaviour depends on
   these rules -- they are presentation only. */

.studio-hero {
  position: relative;
  overflow: hidden;
  border-radius: var(--hero-radius);
  /* Fluid rather than fixed so the hero gives vertical space back on short
     screens -- real user request, 2026-09-06: every studio option should be
     visible on landing without scrolling, on phone/tablet/laptop alike. */
  padding: clamp(1rem, 2.6vw, 1.5rem) clamp(1rem, 2.4vw, 1.35rem);
  margin-bottom: clamp(0.7rem, 1.6vw, 1.15rem);
  background:
    radial-gradient(120% 140% at 0% 0%, var(--hero-violet) 0%, transparent 55%),
    radial-gradient(120% 140% at 100% 10%, var(--hero-magenta) 0%, transparent 60%),
    linear-gradient(135deg, var(--hero-ink) 0%, var(--hero-violet-deep) 100%);
  color: #fff;
  box-shadow: var(--hero-cta-shadow);
}

/* Decorative warm glow -- purely cosmetic, never sits behind body text. */
.studio-hero::after {
  content: "";
  position: absolute;
  right: -18%;
  bottom: -60%;
  width: 60%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle, var(--hero-orange) 0%, transparent 70%);
  opacity: 0.5;
  pointer-events: none;
}

/* The "flash": a single diagonal light sweep that crosses the hero every
   few seconds. Deliberately slow, low-opacity and on a long delay between
   passes -- a glint that catches the eye once, not a strobe. Sits above the
   gradient but below the text (z-index 0 vs the content's 1). */
.studio-hero::before {
  content: "";
  position: absolute;
  top: -60%;
  left: -60%;
  width: 45%;
  height: 220%;
  background: linear-gradient(
    100deg,
    transparent 0%,
    rgba(255, 255, 255, 0.28) 50%,
    transparent 100%
  );
  transform: rotate(18deg);
  animation: studio-hero-sheen 7s ease-in-out infinite;
  pointer-events: none;
}

@keyframes studio-hero-sheen {
  0%,
  62% {
    transform: translateX(0) rotate(18deg);
  }
  100% {
    transform: translateX(560%) rotate(18deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .studio-hero::before {
    animation: none;
    opacity: 0;
  }
}

.studio-hero > * {
  position: relative;
  z-index: 1;
}

.studio-hero-greeting {
  margin: 0 0 0.15rem;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: rgba(255, 255, 255, 0.82);
}

.studio-hero-title {
  margin: 0;
  font-size: clamp(1.2rem, 3.2vw, 1.6rem);
  line-height: 1.2;
  letter-spacing: -0.01em;
}

.studio-hero-subtitle {
  margin: 0.25rem 0 0;
  font-size: clamp(0.8rem, 1.6vw, 0.9rem);
  color: rgba(255, 255, 255, 0.8);
}

.studio-hero-credits {
  display: inline-block;
  margin-top: clamp(0.5rem, 1.4vw, 0.85rem);
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  background: var(--hero-mint);
  color: var(--hero-mint-ink);
  font-size: 0.82rem;
  font-weight: 700;
}

/* Each card gets its own accent from the logo ramp (set via
   data-mode-accent in index.html) so the grid reads as a colourful studio
   rather than a row of identical white boxes. */
.mode-card {
  position: relative;
  overflow: hidden;
  --mode-accent: var(--hero-violet);
}

.mode-card[data-mode-accent="violet"] { --mode-accent: var(--hero-violet); }
.mode-card[data-mode-accent="magenta"] { --mode-accent: var(--hero-magenta); }
.mode-card[data-mode-accent="purple"] { --mode-accent: var(--hero-purple); }
.mode-card[data-mode-accent="orange"] { --mode-accent: var(--hero-orange); }

/* Colour bar along the top edge -- the cheapest way to give each card a
   distinct identity without hurting text contrast anywhere. */
.mode-card[data-mode-accent]::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 4px;
  background: var(--mode-accent);
}

/* A pill, not a fixed circle: these icons are emoji strings and some cards
   use three of them ("Hair, Beard & Outfit"), which overflowed a fixed
   3.1rem circle and collided with the title underneath. min-width keeps a
   single-emoji card perfectly round while letting a longer one grow. */
.mode-card[data-mode-accent] .mode-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.1rem;
  min-width: 3.1rem;
  height: 3.1rem;
  padding: 0 0.7rem;
  margin-bottom: 0.15rem;
  border-radius: 999px;
  font-size: 1.45rem;
  line-height: 1;
  background: color-mix(in srgb, var(--mode-accent) 13%, transparent);
}

.mode-card[data-mode-accent]:hover {
  border-color: var(--mode-accent);
}

.mode-card[data-mode-accent]:hover .mode-title {
  color: var(--mode-accent);
}

/* Locked treatment (real user request, 2026-09-06): a studio the current
   balance cannot start must read as unavailable *before* it is clicked --
   faded and padlocked -- rather than letting someone pick it and only then
   discover they cannot afford it. The card stays focusable and clickable on
   purpose: clicking explains why and offers to top up (see applyModeLocks
   in app.js), which is far more useful than an inert disabled button. */
.mode-card.mode-locked {
  opacity: 0.55;
  filter: saturate(0.55);
}

.mode-card.mode-locked:hover {
  border-color: var(--border);
  transform: none;
  box-shadow: var(--shadow-sm);
}

.mode-card.mode-locked:hover .mode-title {
  color: var(--text);
}

/* --- Fitting the studio into the viewport --------------------------------
   Real user request (2026-09-06): "when someone login, they should be able
   to see all the options properly, not hidden under anything else... i
   don't have to scroll up or down to see it properly and it should be
   working perfect in any browser whether it is phone or tab or laptop or
   ipad."

   Keyed off viewport HEIGHT, not width: a 1366x768 laptop and a landscape
   phone both have plenty of width but little height, and height is what
   actually pushes the option cards below the fold. Width-based breakpoints
   would miss both. Each step trims the decorative parts (hero padding, card
   padding, icon size) and never the options themselves. */
@media (max-height: 860px) {
  .studio-hero {
    padding: 0.9rem 1.1rem;
    margin-bottom: 0.7rem;
  }
  .mode-card {
    padding: 1.05rem 0.85rem;
  }
  .mode-card[data-mode-accent] .mode-icon {
    min-width: 2.5rem;
    height: 2.5rem;
    font-size: 1.2rem;
  }
}

@media (max-height: 700px) {
  .studio-hero-subtitle {
    display: none;
  }
  .studio-hero {
    padding: 0.75rem 1rem;
  }
  .mode-card {
    padding: 0.85rem 0.7rem;
    gap: 0.25rem;
  }
  .mode-desc {
    font-size: 0.75rem;
  }
  .mode-card[data-mode-accent] .mode-icon {
    min-width: 2.1rem;
    height: 2.1rem;
    font-size: 1.05rem;
  }
}

/* Phones: two columns rather than one so all four studios stay on one
   screen. 140px is the narrowest a card can be before the titles wrap
   badly on a 320px-wide device. */
@media (max-width: 560px) {
  .mode-selector {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.6rem;
  }
  .mode-card {
    padding: 0.95rem 0.6rem;
  }
  .mode-title {
    font-size: 0.92rem;
  }
  .mode-desc {
    font-size: 0.72rem;
  }
}

.mode-lock-badge {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  background: var(--hero-ink);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* --- Welcome flashcard --------------------------------------------------- */

.welcome-box {
  text-align: center;
}

.welcome-burst {
  font-size: 2.6rem;
  line-height: 1;
}

.welcome-lede {
  margin: 0.35rem 0 1rem;
  font-size: 1rem;
}

.welcome-panel {
  text-align: left;
  padding: 0.9rem 1rem;
  border-radius: var(--radius);
  background: var(--hero-lavender);
  border: 1px solid var(--hero-line);
}

.welcome-panel-heading {
  margin: 0 0 0.4rem;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--hero-violet);
}

.welcome-panel-heading-locked {
  margin-top: 0.9rem;
  color: var(--hero-muted);
}

.welcome-list {
  margin: 0;
  padding-left: 1.1rem;
  font-size: 0.88rem;
  color: var(--hero-text);
}

.welcome-list li {
  margin-bottom: 0.2rem;
}

.welcome-list-locked {
  color: var(--hero-muted);
}

/* Locked inline options (the beard/colour toggles on pickModeScreen) --
   same visual language as .mode-locked above, at checkbox scale. */
.color-toggle.option-locked {
  opacity: 0.55;
  cursor: pointer;
}

.option-lock-note {
  display: inline-block;
  margin-left: 0.35rem;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: var(--hero-ink);
  color: #fff;
  font-size: 0.65rem;
  font-weight: 700;
  vertical-align: middle;
}

.primary-button {
  padding: 0.65rem 1.1rem;
  border-radius: 8px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
  cursor: pointer;
  width: 100%;
  transition: background 0.12s ease, border-color 0.12s ease, transform 0.05s ease;
}

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

.primary-button:active {
  transform: translateY(1px);
}

.primary-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

/* The salon's "Message on WhatsApp" controls are real <a> elements, not
   <button>s that open a window from script: a popup blocker can refuse the
   latter, and did, on every tap, until 2026-10-11. .primary-button and
   .secondary-button were both written for <button>, which centres its own
   text and carries no underline, so these few lines are what make an anchor
   render identically to the button beside it. */
a.salon-whatsapp-link {
  display: block;
  text-align: center;
  text-decoration: none;
  /* Line-height is deliberately NOT set. The global `button { font: inherit }`
     is the `font` shorthand, which carries line-height with it, so every
     button on this page already runs at the body's line-height -- and an <a>
     inherits the same. Setting "normal" here instead made the WhatsApp
     control 44px against the 46px button beside it. Measured, not guessed. */
}

/* Real user report, 2026-09-05: after the garment-type catalogue grew
   from ~4 to ~10-14 options per category (vision.GARMENT_TYPE_OPTIONS),
   the "look" step of the "Pick My Own Style" wizard got tall enough that
   selecting a garment far down the list (e.g. "Suit") left this button
   scrolled out of view below the fold -- the selection itself worked and
   enabled the button, but with nothing visible confirming that, it read
   as "clicking Suit does nothing." Pinning this one button to the bottom
   of the viewport (not the whole step bar, which would cover content)
   means it's always reachable regardless of how long any future
   catalogue grows, without changing anything about the selection logic
   itself. */
#ownStyleNextButton {
  position: sticky;
  bottom: 0.75rem;
  box-shadow: var(--shadow-md);
}

.secondary-button {
  padding: 0.65rem 1.1rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  font-weight: 600;
  cursor: pointer;
  width: 100%;
  transition: border-color 0.12s ease, color 0.12s ease;
}

.secondary-button:hover {
  border-color: var(--accent);
  color: var(--accent);
}

#retakePhotoButton {
  margin-top: 0.75rem;
}

.reference-label {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 0.4rem;
}

#referenceInputScreen input,
#authScreen input,
#forgotPasswordScreen input,
#resetPasswordScreen input,
#changePasswordScreen input {
  width: 100%;
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  margin-bottom: 0.75rem;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

#referenceInputScreen input:focus,
#authScreen input:focus,
#forgotPasswordScreen input:focus,
#resetPasswordScreen input:focus,
#changePasswordScreen input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
}

#authScreen .capture-tabs {
  margin-bottom: 1.25rem;
}

.google-signin-wrap {
  margin-bottom: 1.25rem;
}

.google-signin-wrap #googleSignInButton {
  display: flex;
  justify-content: center;
}

.google-signin-button-shell {
  position: relative;
  display: inline-flex;
  width: 100%;
  justify-content: center;
}

/* Sits on top of Google's own iframe button (see updateGoogleSignInGate in
   app.js) -- transparent so Google's real button stays fully visible and
   legible underneath (not greyed out, which would look broken rather than
   deliberately gated), but still an opaque hit target that intercepts every
   click until the consent checkbox above is ticked. */
.google-signin-gate {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: none;
  background: transparent;
  cursor: not-allowed;
  padding: 0;
}

.auth-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1rem 0 0;
  color: var(--muted);
  font-size: 0.85rem;
}

.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.danger-zone {
  margin-top: 2rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}

.danger-zone h3 {
  color: var(--error);
  margin-bottom: 0.4rem;
}

.danger-zone .secondary-button {
  border-color: var(--error);
  color: var(--error);
}

.danger-zone .secondary-button:hover {
  background: var(--error);
  color: #ffffff;
}

.consent-field {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin: 0.25rem 0 1.1rem;
  font-size: 0.9rem;
  line-height: 1.45;
  /* Full-contrast text, not var(--muted) -- this is a mandatory field for a
     brand-new account, and low-contrast/faded styling on it reads as
     "skippable" rather than required (real user report, 2026-09-02:
     "alignment and size of the characters are not right... enterprise
     grade"). */
  color: var(--text);
  cursor: pointer;
}

/* #authScreen input's own width: 100% (ID selector) otherwise wins over a
   plain .consent-field input class selector on specificity alone regardless
   of source order -- matched here with the same #authScreen prefix so this
   rule actually applies (real bug found verifying this checkbox visually:
   it was rendering full-width, shoving its label text off past the right
   edge of the card entirely). */
#authScreen .consent-field input,
.consent-field input {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  margin: 0.1rem 0 0;
  accent-color: var(--accent);
}

.password-field {
  position: relative;
  margin-bottom: 0.75rem;
}

.password-field input {
  padding-right: 3.25rem !important;
  margin-bottom: 0 !important;
}

.password-toggle {
  position: absolute;
  top: 50%;
  right: 0.5rem;
  transform: translateY(-50%);
  border: none;
  background: none;
  color: var(--muted);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  padding: 0.3rem 0.4rem;
  line-height: 1;
}

.password-toggle:hover {
  color: var(--accent);
}

#referenceResultWrap {
  margin-top: 1rem;
}

.results {
  margin-top: 2rem;
  border-top: 1px solid var(--border);
  padding-top: 1.5rem;
}

.hair-assessment-note {
  margin: 0.75rem 0 1.25rem;
  padding: 0.9rem 1.1rem;
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border));
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 6%, var(--panel));
}

.hair-assessment-label {
  display: block;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 0.35rem;
}

.hair-assessment-note p {
  margin: 0;
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--text);
}

.recommendation-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1rem;
  margin: 1rem 0;
}

.recommendation-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem;
  background: var(--panel);
  box-shadow: var(--shadow-sm);
}

.recommendation-card h3 {
  margin: 0 0 0.4rem;
  font-size: 1rem;
}

.recommendation-card .why {
  color: var(--muted);
  font-size: 0.9rem;
}

.example-video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1rem;
  margin: 1rem 0;
}

.example-video-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
  padding: 1rem;
  cursor: pointer;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.example-video-card:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-md);
}

.example-video-card h3 {
  margin: 0 0 0.3rem;
  font-size: 1rem;
}

.example-video-card .subtitle {
  margin: 0;
}

.example-video-thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: 8px;
  overflow: hidden;
  margin-bottom: 0.75rem;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
}

.example-video-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.example-video-play-icon {
  font-size: 2rem;
  color: var(--muted);
}

.example-video-play-badge {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ffffff;
  font-size: 1.5rem;
  background: rgba(0, 0, 0, 0.25);
  opacity: 0;
  transition: opacity 0.12s ease;
}

.example-video-card:hover .example-video-play-badge {
  opacity: 1;
}

.example-video-card-admin {
  cursor: default;
}

.delete-example-button {
  margin-top: 0.5rem;
}

.admin-example-form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: 480px;
  margin-bottom: 1.5rem;
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--border);
}

.admin-example-form input {
  margin-bottom: 0;
}

.admin-example-form .primary-button {
  width: auto;
  align-self: flex-start;
  margin-top: 0.25rem;
}

.admin-access-grant-form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: 480px;
  margin-bottom: 1.5rem;
  padding-bottom: 1.5rem;
  border-bottom: 1px solid var(--border);
}

.admin-access-grant-form input {
  margin-bottom: 0;
}

.admin-access-grant-form .primary-button {
  width: auto;
  align-self: flex-start;
  margin-top: 0.25rem;
}

/* Referral program + salon dashboard (2026-08-30 feature). */

.referral-link-row {
  display: flex;
  gap: 0.5rem;
  margin: 0.75rem 0;
}

.referral-link-row input {
  flex: 1 1 auto;
  min-width: 0;
  margin-bottom: 0;
}

.referral-share-row {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.referral-share-row .secondary-button {
  width: auto;
  text-decoration: none;
  text-align: center;
}

.referral-invite-row {
  margin-bottom: 1rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--border);
}

.referral-invite-row .referral-link-row {
  margin-top: 0.35rem;
}

.referral-progress {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  margin-bottom: 1rem;
}

.referral-progress p {
  margin: 0;
}

.referrals-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.referrals-list li {
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}

.salon-qr-code {
  display: block;
  width: 200px;
  height: 200px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 0.75rem;
}

.salon-balance-row {
  display: flex;
  gap: 2rem;
  flex-wrap: wrap;
  margin: 1rem 0;
}

.salon-balance-row p {
  margin: 0;
}

/* --- Salon Revenue Operating System (2026-09-13 feature) -------------------

   Built entirely on the existing tokens (--panel, --border, --accent, --muted,
   --radius, --shadow-*), so this section inherits the app's design language
   rather than introducing a second one.

   Light-locked like the rest of this file: no @media (prefers-color-scheme:
   dark) block here, deliberately -- see the :root comment at the top for why
   dark mode was removed and must not come back one section at a time.

   Mobile-first throughout, because a salon owner works from a phone between
   customers: every grid below collapses to one column, every control is a
   full-width tap target at narrow widths, and the tab strip scrolls sideways
   instead of reflowing into ragged rows. */

/* The tabs WRAP. They used to scroll sideways with the scrollbar hidden, which
   was a real bug rather than a tidy detail: the strip grew from two tabs to
   nine, and with no scrollbar there was nothing on screen to say the last three
   existed at all -- a salon simply could not find Share, Profile or Earnings
   (reported 2026-09-14, and correctly, as "I only see six").

   Two readable rows beat one row with invisible contents. Wrapping also means
   adding a tenth tab later cannot hide anything. */
.salon-tabs {
  flex-wrap: wrap;
  gap: 0.4rem;
  row-gap: 0.4rem;
}

.salon-tabs .tab-button {
  flex: 0 1 auto;
  white-space: nowrap;
}

/* Nine tabs need to be compact enough that two rows is the worst case, not
   four. */
@media (max-width: 640px) {
  .salon-tabs .tab-button {
    padding-inline: 0.7rem;
    font-size: 0.88rem;
  }
}

/* How many enquiries are waiting, on the Leads tab itself.
   The dashboard sits open on a counter phone or tablet, so the count has to be
   readable at arm's length and from an angle -- which is why this is a filled
   pill rather than a tinted number. Rendered from the data-badge attribute so
   the count and the element are set in one place (see app.js's
   noteSalonLeadCount) and a stale badge cannot outlive its number. */
.salon-tabs .tab-button.has-badge {
  position: relative;
  /* Enough room for the pill AND a clear gap after the label. Measured rather
     than guessed: at 2.1rem the gap came out at 5px, which does not overlap
     but reads as a collision at a glance -- the badge looked stuck to the "s"
     of "Leads". This leaves about 12px. */
  padding-right: 2.6rem;
}

.salon-tabs .tab-button.has-badge::after {
  content: attr(data-badge);
  position: absolute;
  top: 50%;
  right: 0.5rem;
  transform: translateY(-50%);
  /* Sized to be read at arm's length from a counter, not at desk distance --
     that is where this dashboard actually lives. */
  min-width: 1.35rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: 0.78rem;
  font-weight: 700;
  line-height: 1.35rem;
  text-align: center;
}

/* auto-fit, not a fixed column count: the same grid has to hold three stats on
   the Analytics tab and seven on the Leads summary, at every width, without a
   breakpoint per case. */
.salon-summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 0.75rem;
  margin: 1rem 0 1.5rem;
}

.salon-stat {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 0.9rem;
  box-shadow: var(--shadow-sm);
}

.salon-stat span {
  display: block;
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.15;
  color: var(--text);
}

.salon-stat em {
  display: block;
  margin-top: 0.2rem;
  font-style: normal;
  font-size: 0.82rem;
  color: var(--muted);
}

/* A conversion rate can read "Not enough data", which is far longer than "42%"
   -- allowed to span two columns so it sets at a readable size instead of
   shrinking the number next to it. */
.salon-stat-wide {
  grid-column: span 2;
}

.salon-stat-wide span {
  font-size: 1.15rem;
}

.salon-filter-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.6rem 0.8rem;
  align-items: end;
  margin-bottom: 1.25rem;
}

/* Each filter is ONE grid item holding its own label and control. Laying the
   label and the control out as separate grid items let them wrap
   independently, so at some widths "Source" sat beside the status dropdown
   and its own dropdown started the next row. */
.salon-filter {
  display: grid;
  gap: 0.15rem;
  min-width: 0;
}

.salon-filter-row .reference-label {
  margin-bottom: 0;
}

.salon-filter-row select,
.salon-filter-row input {
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  /* Date inputs carry a wide intrinsic minimum; without this they refuse to
     shrink and push the row past the panel. */
  min-width: 0;
}

.salon-filter-row > button {
  align-self: end;
}

/* Empty states, not tables of zeroes: a salon with no leads needs to be told
   what to do next, and that instruction deserves to look deliberate. */
.salon-empty {
  background: var(--landing-surface);
  border: 1px dashed var(--border);
  border-radius: var(--radius-lg);
  padding: 1.75rem 1.25rem;
  text-align: center;
}

.salon-empty h3 {
  margin: 0 0 0.5rem;
  font-size: 1.08rem;
}

.salon-empty p {
  margin: 0 auto 1rem;
  max-width: 34rem;
  color: var(--muted);
  line-height: 1.55;
}

/* Cards at every width, not a table that becomes a card on small screens. A
   lead carries a phone number, a status, an appointment, a note and up to nine
   actions -- there is no width at which that is a table row. */
.salon-lead-list {
  display: grid;
  gap: 0.9rem;
}

/* Past looks: what was done last time, and the form for today (2026-09-26). */
.salon-looks-panel {
  margin-top: 1.25rem;
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--landing-surface);
}

.salon-looks-panel h3,
.salon-looks-panel h4 {
  margin: 0 0 0.4rem;
}

.salon-looks-panel h4 {
  margin-top: 1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--border);
}

.salon-looks-panel input,
.salon-looks-panel textarea,
.salon-looks-panel select {
  width: 100%;
  box-sizing: border-box;
  margin: 0 0 0.6rem;
  padding: 0.55rem 0.7rem;
  font: inherit;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.salon-look-card {
  padding: 0.8rem 0.9rem;
  margin-bottom: 0.6rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.salon-look-line {
  margin: 0.25rem 0;
  line-height: 1.5;
}

.salon-look-photo {
  width: 100%;
  max-width: 180px;
  border-radius: var(--radius);
  margin: 0.4rem 0;
}

.salon-lead-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 1rem 1.1rem 1.1rem;
}

.salon-lead-header {
  display: flex;
  gap: 0.6rem;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
}

.salon-lead-header h4 {
  margin: 0;
  font-size: 1.06rem;
}

.salon-lead-meta {
  margin: 0.3rem 0 0.75rem;
  font-size: 0.84rem;
  color: var(--muted);
}

.salon-lead-detail {
  margin: 0.25rem 0;
  font-size: 0.93rem;
  line-height: 1.5;
  /* A customer note can be one long unbroken string; without this it would push
     the card wider than the screen. */
  overflow-wrap: anywhere;
}

.salon-lead-appointment {
  margin-top: 0.65rem;
  padding: 0.6rem 0.75rem;
  background: var(--landing-surface);
  border-radius: var(--radius);
}

.salon-lead-preview {
  display: block;
  width: 100%;
  max-width: 220px;
  margin-top: 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.salon-lead-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.9rem;
}

/* Every action stays a comfortable tap target. min-height rather than padding
   alone so a one-line button and a <select> line up at the same height. */
.salon-lead-actions button,
.salon-lead-actions select,
.salon-lead-actions input {
  min-height: 42px;
}

.salon-lead-status-select,
.salon-inline-input {
  padding: 0.45rem 0.6rem;
  font-size: 0.92rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
  font-family: inherit;
}

.salon-inline-input {
  max-width: 10rem;
}

.salon-lead-note-wrap {
  display: grid;
  gap: 0.5rem;
  margin-top: 0.9rem;
}

.salon-lead-note {
  width: 100%;
  box-sizing: border-box;
  padding: 0.6rem;
  font-family: inherit;
  font-size: 0.92rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  resize: vertical;
}

.salon-lead-note-wrap button {
  justify-self: start;
}

.salon-chip {
  display: inline-block;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 600;
  background: var(--landing-surface);
  color: var(--accent);
  white-space: nowrap;
}

/* Only the statuses that mean "act on this" get colour. Colouring all eight
   would make the list read as a warning rather than a worklist. */
.salon-chip-new {
  background: #ddf4e8;
  color: #1f6b45;
}

.salon-chip-appointment_confirmed {
  background: #e0edff;
  color: #1d4ed8;
}

.salon-chip-completed {
  background: #eceaf2;
  color: #4a4458;
}

.salon-chip-lost,
.salon-chip-cancelled {
  background: #fdeaea;
  color: #a12020;
}

.salon-chip-due {
  background: #fff1d6;
  color: #8a5200;
}

/* Onboarding checklist. Shown above the profile form rather than as a dismissed
   banner, because it is the thing that decides whether the salon ever gets a
   lead. */
.salon-onboarding {
  background: var(--landing-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 1.1rem 1.2rem;
  margin-bottom: 1.5rem;
}

.salon-onboarding h3 {
  margin: 0 0 0.35rem;
  font-size: 1.05rem;
}

.salon-checklist {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}

.salon-checklist li {
  display: grid;
  grid-template-columns: 1.4rem 1fr;
  gap: 0.1rem 0.5rem;
  font-size: 0.94rem;
  line-height: 1.45;
}

.salon-check-mark {
  font-weight: 700;
  color: var(--muted);
}

.salon-check-done .salon-check-mark {
  color: var(--success);
}

.salon-check-done {
  color: var(--muted);
}

.salon-check-detail {
  grid-column: 2;
  font-style: normal;
  font-size: 0.84rem;
  color: var(--muted);
}

.salon-image-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1rem;
  margin-bottom: 1.25rem;
}

.salon-image-slot {
  display: grid;
  gap: 0.6rem;
  justify-items: start;
}

/* The file inputs themselves are hidden; their <label> is styled as a button so
   the upload control matches every other button on the screen. */
.salon-upload-label {
  display: inline-block;
  cursor: pointer;
  text-align: center;
}

.salon-logo-preview {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.salon-cover-preview {
  width: 100%;
  max-width: 320px;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* Tap to add a common service, then just type the price (2026-09-26). */
.salon-quick-add {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.7rem;
}

.salon-quick-add:empty {
  display: none;
}

.salon-quick-add-chip {
  min-height: 36px;
  padding: 0.3rem 0.8rem;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--accent);
  background: var(--panel);
  border: 1px solid rgba(76, 20, 92, 0.25);
  border-radius: 999px;
  cursor: pointer;
}

.salon-quick-add-chip:hover {
  border-color: var(--accent);
}

.salon-go-live-note {
  margin-top: 0.6rem;
  padding: 0.6rem 0.8rem;
  border-radius: var(--radius);
  background: var(--landing-surface);
  border-left: 4px solid var(--accent);
  font-weight: 600;
  color: var(--text);
}

.salon-services-editor,
.salon-hours-editor {
  display: grid;
  gap: 0.6rem;
  margin-bottom: 1rem;
}

.salon-service-row {
  display: grid;
  grid-template-columns: 2fr 1fr auto;
  gap: 0.5rem;
  align-items: center;
}

.salon-service-row input {
  width: 100%;
  box-sizing: border-box;
  margin: 0;
}

.salon-hours-row {
  display: grid;
  grid-template-columns: 7rem auto 1fr 1fr;
  gap: 0.5rem;
  align-items: center;
}

.salon-hours-day {
  font-weight: 600;
  font-size: 0.92rem;
}

.salon-hours-closed {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.86rem;
  color: var(--muted);
  white-space: nowrap;
}

.salon-hours-row input[type="time"] {
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 0.45rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: inherit;
}

/* Text fields in the Profile tab. This app styles form inputs per screen with
   ID selectors (see the #authScreen/#referenceInputScreen rule above) rather
   than with one global input rule, so a new screen's inputs render at the
   browser's default ~170px width until it opts in -- which on a phone is a
   form a salon owner cannot actually fill in. Same padding, radius and focus
   ring as that rule, so the Profile tab matches every other form in the app.

   Scoped to direct children so the inline controls inside a lead card
   (.salon-inline-input, the status <select>) keep their own compact sizing. */
#salonPanelProfile > input,
#salonPanelProfile > textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  margin-bottom: 0.75rem;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

#salonPanelProfile > textarea {
  resize: vertical;
}

#salonPanelProfile > input:focus,
#salonPanelProfile > textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
}

/* The service-name and price fields live inside .salon-service-row's grid, so
   they need the same treatment without the grid-breaking margin. */
.salon-service-row input {
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  font: inherit;
}

/* Buttons in the Profile tab stack full width on a phone -- a "Save services"
   button sized to its text, floating next to a full-width form, reads as
   unfinished. */
#salonPanelProfile > .primary-button,
#salonPanelProfile > .secondary-button {
  display: block;
  width: 100%;
  margin-bottom: 0.75rem;
}

.salon-founding-banner {
  background: var(--landing-mint-bg);
  color: var(--landing-mint-text);
  border-radius: var(--radius);
  padding: 0.7rem 0.9rem;
  font-size: 0.92rem;
  font-weight: 600;
  margin: 0 0 1rem;
}

/* "Book this look at <Salon>" -- sits directly under the generated result, so it
   is styled as a distinct surface rather than another paragraph of the results
   area, without shouting over the look the customer just generated. */
.salon-booking-cta {
  margin: 1.5rem 0;
  padding: 1.2rem 1.25rem;
  background: var(--landing-surface);
  border: 1px solid var(--landing-border);
  border-radius: var(--radius-lg);
}

.salon-booking-cta h3 {
  margin: 0 0 0.3rem;
  font-size: 1.12rem;
}

.salon-booking-cta-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
  margin-top: 0.9rem;
}

.salon-booking-cta-actions .primary-button,
.salon-booking-cta-actions .secondary-button {
  flex: 1 1 12rem;
  text-align: center;
  min-height: 44px;
}

.salon-booking-form {
  margin-top: 1.1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--landing-border);
}

.salon-booking-form input,
.salon-booking-form select {
  width: 100%;
  box-sizing: border-box;
}

.salon-booking-done {
  margin: 1rem 0 0;
  padding: 0.85rem 1rem;
  background: var(--landing-mint-bg);
  color: var(--landing-mint-text);
  border-radius: var(--radius);
  font-size: 0.95rem;
  line-height: 1.5;
}

/* Admin console: the salon activity table. Scrolls inside its own container so
   a wide table can never make the whole admin page scroll sideways. */
.admin-salon-activity-wrap {
  overflow-x: auto;
  margin-top: 1rem;
}

.admin-salon-activity-wrap table {
  min-width: 56rem;
}

.admin-salon-flags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

@media (max-width: 560px) {
  /* One column everywhere below this width. A two-column stat grid at 360px
     gives numbers about 140px of space including padding, which is where they
     start wrapping mid-figure. */
  .salon-stat-wide {
    grid-column: span 1;
  }

  .salon-service-row,
  .salon-hours-row {
    grid-template-columns: 1fr;
  }

  .salon-hours-row {
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--border);
  }

  .salon-inline-input {
    max-width: none;
    width: 100%;
  }

  .salon-lead-actions button,
  .salon-lead-actions select,
  .salon-lead-actions input {
    width: 100%;
  }
}

/* Video modal -- reuses the shared .modal-overlay backdrop (see
   how-to-use-box's comment) but defines its own wider box sized for a
   16:9 embed instead of a short confirm dialog. */
.video-modal-box {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  max-width: 720px;
  width: 100%;
  padding: 1.5rem;
}

.video-modal-box h3 {
  margin: 0 0 1rem;
  padding-right: 2rem;
  font-size: 1.1rem;
}

.video-modal-close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  width: 32px;
  height: 32px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}

.video-modal-close:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.video-modal-embed-wrap {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: 8px;
  overflow: hidden;
  background: #000000;
}

.video-modal-embed-wrap iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* The honest note shown with a "Describe a look" preview: what the look asks
   of the hair, what the customer's own photo shows, and the promise we keep
   (their own hairline and thickness). See pipeline._hair_reality_note. */
.reference-new-user-hint {
  margin: 0.7rem 0 0;
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--muted);
}

/* The cinematic offer under a finished preview: the preview itself, blurred
   and darkened, with the pitch over it. Costs nothing -- it is not a second
   generated picture, just a hint of the mood (app.js's
   renderMovieLookOffer). */
.movie-look-offer {
  padding: 0;
  overflow: hidden;
}

.movie-look-teaser {
  position: relative;
  min-height: 210px;
}

.movie-look-teaser img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(9px) brightness(0.42) contrast(1.15) saturate(1.1);
}

.movie-look-teaser-text {
  position: relative;
  padding: 1.1rem 1rem 1.2rem;
  text-align: center;
  color: #ffffff;
}

.movie-look-headline {
  margin: 0 0 0.3rem;
  font-size: 1.05rem;
  font-weight: 700;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.55);
}

.movie-look-sub {
  margin: 0 0 0.85rem;
  font-size: 0.82rem;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}

/* "Movie look": a second, clearly different promise from the preview above
   it -- styled like the film, for sharing (see app.js's renderMovieLook). */
.movie-look-card {
  border: 1px solid var(--border);
  margin-top: 0.75rem;
}

.reference-panel-note {
  margin: 0.15rem 0 0.4rem;
  font-size: 0.78rem;
  color: var(--muted);
}

/* Sits on the dark teaser, so it needs its own light surface rather than
   the theme's dark-on-light button colours (checked in the browser: the
   label was invisible against the blurred backdrop). */
/* More specific than .save-button, which is defined later in this file and
   would otherwise win with its transparent background. */
.movie-look-offer .movie-look-button {
  background: #ffffff;
  color: #17172a;
  border: none;
  font-weight: 700;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
}

.reference-hair-note {
  margin: 0.5rem 0 0;
  padding: 0.55rem 0.7rem;
  border-radius: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--text);
}

.beard-pairing {
  display: flex;
  gap: 0.65rem;
  align-items: flex-start;
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
  font-size: 0.9rem;
}

.beard-pairing p {
  margin: 0;
}

.beard-reference-thumb {
  width: 48px;
  height: 48px;
  border-radius: 8px;
  object-fit: cover;
  flex-shrink: 0;
}

.style-details {
  list-style: none;
  padding: 0;
  margin: 0.5rem 0 0;
  font-size: 0.8rem;
  color: var(--muted);
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.style-details strong {
  color: var(--text);
}

.outfit-fit,
.outfit-why,
.outfit-score {
  font-size: 0.8rem;
  color: var(--muted);
  margin: 0 0 0.4rem;
}

/* "Create my movie look" on a saved look (2026-09-25). */
.movie-look-actions {
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--border);
}

.movie-look-hint,
.movie-look-done {
  margin: 0.35rem 0 0;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--muted);
}

.movie-look-done {
  font-weight: 600;
  color: var(--accent);
}

.photo-highlight {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
  transition: outline-color 0.3s ease;
}

.generated-image-wrap {
  aspect-ratio: 1 / 1;
  border-radius: 8px;
  overflow: hidden;
  margin-bottom: 0.75rem;
  background: var(--bg);
}

.generated-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.image-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  color: var(--muted);
  font-size: 0.85rem;
  text-align: center;
  padding: 0.5rem;
}

.image-placeholder-with-reference {
  background-size: cover;
  background-position: center top;
  justify-content: flex-end;
  position: relative;
}

.image-placeholder-with-reference::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0) 45%);
}

.image-placeholder-with-reference .generate-look-button {
  position: relative;
  background: var(--panel);
  box-shadow: var(--shadow-sm);
}

.reference-photo-badge {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  background: rgba(0, 0, 0, 0.6);
  color: #ffffff;
  font-size: 0.7rem;
  font-weight: 600;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
}

/* A draft of the image being drawn (app.js's showGenerationProgress). Its
   face hasn't been checked, so it is never shown sharp -- only as a heavily
   blurred hint of the new hair behind the spinner. */
.image-placeholder.has-draft,
.photo-progress {
  overflow: hidden;
}

.image-placeholder.has-draft {
  position: relative;
}

.image-placeholder.has-draft > :not(.generation-draft),
.photo-progress > :not(.generation-draft) {
  position: relative;
  z-index: 1;
}

.generation-draft {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(18px) saturate(0.8);
  transform: scale(1.2);
  opacity: 0.55;
  pointer-events: none;
  z-index: 0;
}

.image-placeholder.has-draft .generation-message-note {
  color: var(--text);
}

/* A face-checked first look, shown while a better attempt is made. */
.first-look {
  position: relative;
  width: 100%;
  height: 100%;
}

.first-look-badge {
  position: absolute;
  left: 0.5rem;
  right: 0.5rem;
  bottom: 0.5rem;
  margin: 0;
  padding: 0.4rem 0.6rem;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.62);
  color: #ffffff;
  font-size: 0.78rem;
  font-weight: 600;
  text-align: center;
}

.image-placeholder .generation-message {
  color: var(--text);
  font-weight: 600;
}

.image-placeholder .generation-message-note {
  font-size: 0.78rem;
}

.image-error {
  color: var(--error);
}

.spinner {
  width: 22px;
  height: 22px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* Real user request (2026-08-31): replace the generic spinning-circle
   loader shown while a photo is generating with something more on-theme --
   a small scissors icon snipping its way along a hair strand. Plain inline
   SVG + CSS keyframes, no new dependency or image asset: the strand is a
   dashed line, the two blades are separate <g> groups rotating open/closed
   around a shared pivot (positioned via an SVG transform attribute so each
   layer's CSS transform-origin stays a simple, cross-browser-safe "0 0"),
   and the whole scissors group glides back and forth along the strand. Used
   both by imagePlaceholderHtml() (app.js) and the hair-colour/style-colour
   full-panel generating states (index.html, given the same shared markup by
   app.js on load -- see STYLIST_SPINNER_SVG). */
.stylist-spinner {
  width: 56px;
  height: 38px;
}

.stylist-spinner-strand {
  stroke: var(--border);
  stroke-width: 2;
  stroke-dasharray: 3 3;
}

.stylist-spinner-scissors {
  animation: stylist-spinner-glide 2.4s ease-in-out infinite;
}

@keyframes stylist-spinner-glide {
  0%,
  100% {
    transform: translateX(-9px);
  }
  50% {
    transform: translateX(9px);
  }
}

.stylist-spinner-pivot {
  fill: var(--accent);
}

.stylist-spinner-blade-top,
.stylist-spinner-blade-bottom {
  transform-origin: 0 0;
  transform-box: fill-box;
  animation-duration: 0.7s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

.stylist-spinner-blade-top {
  animation-name: stylist-spinner-snip-top;
}

.stylist-spinner-blade-bottom {
  animation-name: stylist-spinner-snip-bottom;
}

.stylist-spinner-blade-top line,
.stylist-spinner-blade-bottom line {
  stroke: var(--accent);
  stroke-width: 2.5;
  stroke-linecap: round;
}

@keyframes stylist-spinner-snip-top {
  0%,
  100% {
    transform: rotate(0deg);
  }
  50% {
    transform: rotate(14deg);
  }
}

@keyframes stylist-spinner-snip-bottom {
  0%,
  100% {
    transform: rotate(0deg);
  }
  50% {
    transform: rotate(-14deg);
  }
}

.before-after {
  --pos: 50%;
  position: relative;
  width: 100%;
  height: 100%;
  /* Vertical page scroll still passes through; horizontal drag is captured
     by wireBeforeAfterSlider's own pointer handlers. */
  touch-action: pan-y;
  cursor: ew-resize;
  user-select: none;
}

.before-after-img {
  position: absolute;
  inset: 0;
  /* Dragging is handled at the container level (see wireBeforeAfterSlider),
     not per-image, so clicks/drags always reach the container. */
  pointer-events: none;
}

.before-after-before {
  clip-path: inset(0 calc(100% - var(--pos)) 0 0);
}

.before-after-labels {
  position: absolute;
  inset: 0.5rem 0.5rem auto 0.5rem;
  display: flex;
  justify-content: space-between;
  pointer-events: none;
}

.before-after-label {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #fff;
  background: rgba(0, 0, 0, 0.45);
  padding: 0.15rem 0.45rem;
  border-radius: 999px;
}

.before-after-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--pos);
  width: 2px;
  background: #fff;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
}

.before-after-handle-grip {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid #fff;
  box-shadow: var(--shadow-md);
}

.before-after-handle:focus-visible {
  outline: none;
}

.before-after-handle:focus-visible .before-after-handle-grip {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.image-actions {
  margin-bottom: 0.6rem;
}

.save-button {
  width: 100%;
  padding: 0.45rem 0.75rem;
  border-radius: 7px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font-weight: 600;
  cursor: pointer;
  font-size: 0.85rem;
  transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
}

.save-button:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: rgba(79, 70, 229, 0.06);
}

.color-section {
  margin-top: 2rem;
  border-top: 1px solid var(--border);
  padding-top: 1.5rem;
}

.color-swatch {
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1px solid var(--border);
  flex-shrink: 0;
}

.color-hex {
  margin: 0 0 0.4rem;
  color: var(--muted);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.color-shade-hint {
  margin: 0;
  color: var(--success);
  font-size: 0.78rem;
  font-weight: 600;
}

.color-swatch-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.5rem 0 0.75rem;
}

.color-swatch-button {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid var(--border);
  cursor: pointer;
  padding: 0;
}

.color-swatch-button.active {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent);
}

.color-avoid-list {
  list-style: none;
  padding: 0;
  margin: 0.75rem 0;
}

.color-avoid-list li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--border);
}

.color-avoid-list li:last-child {
  border-bottom: none;
}

.outfit-picker {
  margin-top: 0.9rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border);
}

.outfit-picker:empty {
  display: none;
}

.outfit-picker h4 {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--muted);
}

.outfit-picker p {
  font-size: 0.8rem;
  color: var(--muted);
  margin: 0 0 0.4rem;
}

.outfit-category-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.5rem;
}

.outfit-category-tab {
  padding: 0.25rem 0.6rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font-size: 0.78rem;
  cursor: pointer;
}

.outfit-category-tab.active {
  border-color: var(--accent);
  color: var(--accent);
}

.outfit-category-label {
  font-size: 0.85rem;
  font-weight: 600;
  margin: 0 0 0.4rem;
}

.status-message {
  min-height: 1.2rem;
  margin-top: 1rem;
  color: var(--muted);
}

.status-error {
  color: var(--error);
}

#authProfileFields {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
}

.buy-credits-title {
  margin-bottom: 1.25rem;
}

.wallet-card {
  background: linear-gradient(135deg, var(--accent), var(--accent-hover));
  color: var(--accent-contrast);
  border-radius: var(--radius-lg);
  padding: 1.75rem 1.5rem;
  margin-bottom: 2rem;
  box-shadow: var(--shadow-md);
  text-align: center;
}

.wallet-balance {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 0.5rem;
  font-size: 3rem;
  font-weight: 700;
  line-height: 1;
}

.wallet-balance-unit {
  font-size: 1.1rem;
  font-weight: 500;
  opacity: 0.9;
}

.buy-credits-section {
  margin-bottom: 2rem;
}

.buy-credits-section.hidden {
  display: none;
}

.section-heading {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  margin-bottom: 1rem;
}

.section-heading h3 {
  margin: 0 0 0.2rem;
  font-size: 1.05rem;
}

.section-heading .subtitle {
  margin: 0;
  font-size: 0.85rem;
}

.step-badge {
  flex-shrink: 0;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-contrast);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.85rem;
}

.credit-package-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1rem;
}

.credit-package-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  text-align: center;
  padding: 1.5rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.12s ease, transform 0.08s ease;
}

.credit-package-card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

.package-price {
  font-size: 2.4rem;
  font-weight: 700;
  color: var(--accent);
  line-height: 1;
}

.package-photos {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 1rem;
}

.generation-caveat {
  font-size: 0.78rem;
  color: var(--muted);
  margin: 0 0 0.5rem;
}

.admin-photo-source {
  display: inline-block;
  margin: 0 0 0.4rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
}

.generation-caveat .link-button {
  padding: 0;
  margin: 0;
  font-size: inherit;
}

.link-button {
  border: none;
  background: none;
  color: var(--accent);
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  padding: 0.4rem 0;
  margin-top: 0.2rem;
}

.link-button:hover {
  text-decoration: underline;
}

.admin-email-issue {
  display: block;
  color: #c0392b;
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: capitalize;
}

.empty-state {
  padding: 1.25rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  text-align: center;
  color: var(--muted);
}

.payment-history-wrap {
  overflow-x: auto;
}

.payment-history-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
}

.payment-history-table th {
  text-align: left;
  padding: 0.5rem 0.75rem;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.payment-history-table td {
  padding: 0.65rem 0.75rem;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.payment-history-table tr:last-child td {
  border-bottom: none;
}

.utr-cell {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8rem;
}

.status-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-right: 0.4rem;
}

.status-dot-pending {
  background: var(--muted);
}

.status-dot-approved {
  background: var(--success);
}

.status-dot-rejected {
  background: var(--error);
}

.payment-request-list {
  margin: 0.75rem 0 1.5rem;
}

.payment-request-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
}

.payment-request-row:last-child {
  border-bottom: none;
}

.payment-status {
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: 0.72rem;
}

.payment-status-pending {
  color: var(--muted);
  background: var(--bg);
}

.payment-status-approved {
  color: var(--success);
  background: rgba(22, 163, 74, 0.12);
}

.payment-status-rejected {
  color: var(--error);
  background: rgba(220, 38, 38, 0.1);
}

.admin-actions {
  display: flex;
  gap: 0.5rem;
  margin-left: auto;
}

/* --- Admin console: sidebar + panel dashboard layout --- */

.admin-console {
  display: flex;
  align-items: flex-start;
  gap: 2rem;
}

.admin-sidebar {
  flex: 0 0 220px;
  position: sticky;
  top: 1.5rem;
}

.admin-sidebar-brand {
  font-weight: 700;
  font-size: 1.05rem;
  margin-bottom: 1.25rem;
  padding: 0 0.25rem;
  color: var(--accent);
}

.admin-sidebar-brand span {
  display: block;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-top: 0.1rem;
}

.admin-nav {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.admin-nav-item {
  text-align: left;
  padding: 0.6rem 0.75rem;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.9rem;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

.admin-nav-item:hover {
  background: var(--panel);
  color: var(--text);
}

.admin-nav-item.active {
  background: var(--panel);
  color: var(--accent);
  box-shadow: var(--shadow-sm);
}

.admin-content {
  flex: 1 1 auto;
  min-width: 0;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 1.75rem;
  box-shadow: var(--shadow-sm);
}

.admin-panel.hidden {
  display: none;
}

.admin-panel h2 {
  margin-top: 0;
}

@media (max-width: 720px) {
  .admin-console {
    flex-direction: column;
    gap: 1rem;
  }

  .admin-sidebar {
    position: static;
    flex: 1 1 auto;
    width: 100%;
  }

  .admin-nav {
    flex-direction: row;
    overflow-x: auto;
    gap: 0.4rem;
    padding-bottom: 0.25rem;
  }

  .admin-nav-item {
    flex: 0 0 auto;
    white-space: nowrap;
  }

  .admin-content {
    /* .admin-console stays align-items:flex-start (its desktop default) in
       this column layout, so without an explicit width a flex item sizes
       to its own content instead of the container -- the wide admin
       tables (e.g. the Live activity table) were stretching this, and the
       whole page along with it, far past the phone's viewport instead of
       scrolling horizontally inside .admin-users-wrap the way they're
       meant to (real user report, 2026-08-27: "sizing... going out of the
       window" on iPhone Safari and Android). */
    width: 100%;
    padding: 1.25rem;
  }
}

.admin-stats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 0.85rem;
  margin: 0 0 1.5rem;
}

.admin-stat-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  background: var(--bg);
}

.admin-stat-card strong {
  font-size: 1.5rem;
  letter-spacing: -0.01em;
}

.admin-stat-card span {
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.admin-query-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin: 0.75rem 0;
}

.admin-query-select,
.admin-query-days-field input {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
  padding: 0.5rem 0.7rem;
  font-size: 0.85rem;
}

.admin-query-select {
  min-width: 220px;
}

.admin-query-days-field {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.admin-query-days-field input {
  width: 4.5rem;
}

.admin-query-error {
  color: var(--error);
}

.admin-behaviour-charts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1rem;
  margin: 0.75rem 0 1.75rem;
}

.admin-chart-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
  background: var(--bg);
}

.admin-saved-looks-card {
  grid-column: 1 / -1;
}

.admin-saved-look {
  border-top: 1px solid var(--border);
  padding: 0.5rem 0;
}

.admin-saved-look summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  font-size: 0.85rem;
}

.admin-saved-look-badge {
  font-size: 0.75rem;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--border);
}

.admin-saved-look-badge.is-reviewed {
  border-color: currentColor;
  font-weight: 600;
}

.admin-saved-look-fields {
  display: grid;
  gap: 0.5rem;
  margin: 0.6rem 0;
}

.admin-saved-look-fields label {
  display: grid;
  gap: 0.2rem;
  font-size: 0.8rem;
}

.admin-saved-look-fields input,
.admin-saved-look-fields textarea {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
}

.admin-saved-look-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.admin-chart-card h3 {
  margin: 0 0 0.6rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
}

.admin-bar-list {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.admin-bar-row {
  display: grid;
  grid-template-columns: minmax(80px, 30%) 1fr auto;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.82rem;
}

.admin-bar-label {
  color: var(--muted);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.admin-bar-track {
  height: 10px;
  background: var(--border);
  border-radius: 6px;
  overflow: hidden;
}

.admin-bar-fill {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: 6px;
}

.admin-bar-value {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --- Admin "Daily" tab ---------------------------------------------------
   Signups and sales per day (real user request, 2026-09-13). The charts are
   inline SVG drawn by renderAdminDayColumnChart in app.js; everything the
   marks and axes wear is set here so the chart inherits the app's tokens
   instead of hard-coding colours in JS. */

.admin-daily-toolbar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.admin-daily-toolbar label {
  color: var(--muted);
  font-size: 0.82rem;
  font-weight: 600;
}

.admin-daily-date {
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
}

/* Segmented control: one row of buttons sharing a border, so the three
   groupings read as one choice rather than three separate actions. */
.admin-daily-group {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}

.admin-daily-group-button {
  padding: 0.35rem 0.75rem;
  border: 0;
  border-right: 1px solid var(--border);
  background: var(--panel);
  color: var(--muted);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}

.admin-daily-group-button:last-child {
  border-right: 0;
}

.admin-daily-group-button.active {
  background: var(--accent);
  color: var(--accent-contrast);
}

/* The totals row spans the same width as the charts and the table below it.
   The shared .admin-stats grid uses auto-fill, which leaves a ragged empty
   column when the card count does not fill the row -- the cards stopped
   short of the charts' right edge and read as misaligned. */
#adminPanelDaily .admin-stats {
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

.admin-daily-range-label {
  color: var(--muted);
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
  /* Pushed to the end of the row so the controls stay grouped together, and
     it wraps onto its own line when there is no room. */
  margin-left: auto;
}

@media (max-width: 560px) {
  /* The admin console on a phone was spending 176px of a 390px screen on
     nested padding alone -- the app shell, the panel it sits in, the content
     card and then each chart card all paying full desktop padding -- which
     left the charts 208px wide and everything cramped into a narrow column.
     The console is a dense data view, so on a small screen it gets the
     screen. Scoped to the console: the consumer-facing app keeps its roomier
     padding. */
  body.admin-console-active .app-shell {
    padding-left: 0.5rem;
    padding-right: 0.5rem;
  }

  body.admin-console-active .panel {
    padding: 0.75rem;
  }

  .admin-content {
    padding: 0.85rem;
  }

  /* Scoped by id so it wins regardless of where this block sits relative to
     the base .admin-chart-card rule below. */
  #adminPanelDaily .admin-chart-card {
    padding: 0.8rem 0.7rem 0.6rem;
  }

  /* Two date fields plus a three-way toggle cannot share a phone row at
     their natural widths, so the dates share one row and shrink evenly
     (date inputs have a wide intrinsic minimum -- without min-width:0 they
     refuse to shrink and one wraps onto a line of its own), and the
     grouping control takes the row below. */
  .admin-daily-toolbar {
    gap: 0.45rem 0.4rem;
  }

  .admin-daily-date {
    flex: 1 1 0;
    min-width: 0;
    font-size: 0.8rem;
    padding: 0.3rem 0.35rem;
  }

  .admin-daily-group {
    flex: 1 1 100%;
  }

  .admin-daily-group-button {
    flex: 1 1 0;
  }

  .admin-daily-range-label {
    flex: 1 1 100%;
    margin-left: 0;
  }

  /* Two tiles per row rather than four stacked cards pushing the charts a
     screen and a half down the page. */
  #adminPanelDaily .admin-stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.5rem;
  }

  .admin-stat-card {
    padding: 0.6rem 0.7rem;
  }

  .admin-stat-card strong {
    font-size: 1.25rem;
  }

  .admin-stat-card span {
    font-size: 0.68rem;
  }

  /* The day table is the one thing here with six columns. Rather than leave
     it scrolling sideways inside its wrapper on a phone -- which is what
     "going out of the window" feels like even when the page itself doesn't
     move -- it drops the Credits column and tightens its cells so the
     numbers that matter fit the screen. Credits is the one column that is
     not a headline number and is implied by revenue; it is still there on a
     wider screen. */
  #adminPanelDaily .admin-daily-table th:nth-child(5),
  #adminPanelDaily .admin-daily-table td:nth-child(5) {
    display: none;
  }

  #adminPanelDaily .admin-daily-table {
    font-size: 0.78rem;
  }

  #adminPanelDaily .admin-daily-table th,
  #adminPanelDaily .admin-daily-table td {
    padding: 0.45rem 0.3rem;
  }

  /* Scoped by id: the shared .admin-users-table th rule is defined later in
     this file at equal specificity, so a plain .admin-daily-table th here
     silently loses to it.

     Sentence case, no letter-spacing: the shared uppercase header style is
     about 15% wider per word, and "SIGNUPS" is a single word that cannot
     wrap, so it spilled out of its column and collided with the next
     header. Breaking long words is the last-resort safety net. */
  #adminPanelDaily .admin-daily-table th {
    font-size: 0.6rem;
    letter-spacing: 0;
    text-transform: none;
    line-height: 1.25;
    /* The shared admin table sets white-space: nowrap, which is why a
       two-word header like "New paying" ran into its neighbour instead of
       wrapping onto a second line. */
    white-space: normal;
    overflow-wrap: break-word;
  }

  #adminPanelDaily .admin-daily-table td {
    white-space: normal;
    overflow-wrap: break-word;
  }

  /* Fixed layout so the columns divide the screen instead of being sized by
     their content: "Sun, Sep 13" and a header like "NEW PAYING" otherwise
     set a minimum width between them that no phone has, and the table
     scrolls sideways however small the type gets. With fixed widths the
     long labels wrap and everything stays on screen. */
  #adminPanelDaily .admin-daily-table {
    table-layout: fixed;
  }

  #adminPanelDaily .admin-daily-table th:first-child,
  #adminPanelDaily .admin-daily-table td:first-child {
    width: 28%;
  }

  #adminPanelDaily .admin-daily-table th:not(:first-child),
  #adminPanelDaily .admin-daily-table td:not(:first-child) {
    width: 18%;
  }
}

.admin-daily-charts {
  display: grid;
  /* Side by side where there is room, stacked on a narrow screen -- never
     two half-width charts squeezed onto a phone.

     min() rather than a bare 320px: a plain minmax(320px, 1fr) track cannot
     shrink below 320px, so on a ~300px phone column the grid itself became
     wider than the screen and took the page with it. */
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 1rem;
  margin: 1.25rem 0 1.75rem;
}

.admin-chart-card {
  margin: 0;
  padding: 1rem 1rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  min-width: 0;
}

.admin-chart-title {
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-bottom: 0.5rem;
}

/* The renderer sizes the SVG in real pixels from this container's width (a
   fixed viewBox scaled by CSS shrank 11px axis text to under 7px), so the
   container only has to not clip it. The card grows with the chart rather
   than fixing a height, so the x-axis labels can never end up outside a
   clipped box. */
.admin-chart {
  min-width: 0;
}

.admin-chart svg {
  display: block;
  max-width: 100%;
  /* visible so a value label sitting just above a full-height column is not
     clipped; the SVG itself is never wider than its card. */
  overflow: visible;
}

/* Phone-sized charts: smaller type to match the tighter geometry the
   renderer switches to below 460px. */
.admin-chart-svg.is-compact .admin-chart-tick {
  font-size: 9px;
}

.admin-chart-svg.is-compact .admin-chart-value {
  font-size: 10px;
}

.admin-chart-column {
  fill: #8a18c4; /* --hero-purple, sampled from the logo */
}

/* Hairline, solid, one step off the surface: structure you can read a
   magnitude against without the grid competing with the data. */
.admin-chart-grid {
  stroke: var(--border);
  stroke-width: 1;
}

.admin-chart-axis {
  stroke: var(--border);
  stroke-width: 1;
}

/* Axis and value text wears text tokens, never the mark colour. */
.admin-chart-tick {
  fill: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.admin-chart-value {
  fill: var(--text);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.admin-chart-empty {
  fill: var(--muted);
  font-size: 12px;
}

/* Invisible until hovered, then a faint wash marking the whole day -- the
   hit area is the full band so a zero day is still hoverable. */
.admin-chart-hit {
  fill: var(--text);
  fill-opacity: 0;
  transition: fill-opacity 0.12s ease;
}

.admin-chart-hit:hover {
  fill-opacity: 0.04;
}

.admin-daily-table td:not(:first-child),
.admin-daily-table th:not(:first-child) {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.admin-profile-purchases {
  border-top: 1px solid var(--border);
  padding-top: 1rem;
  margin-top: 1rem;
}

.admin-profile-purchases h4 {
  margin: 0 0 0.6rem;
  font-size: 0.85rem;
}

.admin-purchases-table {
  font-size: 0.8rem;
}

.admin-announce {
  margin: 0 0 1rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
}

.admin-announce summary {
  font-weight: 700;
  cursor: pointer;
}

.admin-announce input,
.admin-announce textarea,
.admin-announce select {
  width: 100%;
  box-sizing: border-box;
  margin: 0 0 0.6rem;
  padding: 0.55rem 0.7rem;
  font: inherit;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.admin-announce .image-actions {
  flex-wrap: wrap;
  gap: 0.5rem;
}

.admin-announce-audience,
.admin-announce-row {
  margin: 0.4rem 0;
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--muted);
}

.admin-announce-result {
  margin: 0.5rem 0 0;
  padding: 0.6rem 0.8rem;
  border-radius: var(--radius);
  border-left: 4px solid var(--success);
  background: #f0fdf4;
  color: #166534;
  font-weight: 600;
}

.admin-announce-result.status-error {
  border-left-color: var(--error);
  background: #fef2f2;
  color: #7f1d1d;
}

.admin-announce-history {
  margin-top: 0.6rem;
  border-top: 1px solid var(--border);
  padding-top: 0.5rem;
}

/* The customer-facing banner. */
.app-banner {
  display: grid;
  gap: 0.6rem;
  margin: 0 0 1rem;
  padding: 0.9rem 1rem;
  border-radius: var(--radius-lg);
  border-left: 4px solid var(--accent);
  background: var(--landing-surface);
  box-shadow: var(--shadow-sm);
}

.app-banner-text strong {
  display: block;
  margin-bottom: 0.2rem;
}

.app-banner-text span {
  color: var(--muted);
  line-height: 1.5;
}

.app-banner-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.app-banner-actions .save-button {
  width: auto;
}

.admin-push-reach {
  margin: 0 0 0.75rem;
  padding: 0.7rem 0.9rem;
  border-radius: var(--radius);
  background: var(--landing-surface);
  border-left: 4px solid var(--accent);
  color: var(--text);
  font-weight: 600;
  line-height: 1.5;
}

.admin-push-test {
  width: auto;
  margin: 0 0 0.75rem;
  min-height: 40px;
}

.admin-users-search {
  margin: 0.75rem 0 0;
  max-width: 320px;
  width: 100%;
}

.admin-users-wrap {
  overflow-x: auto;
  margin: 0.75rem 0 0;
}

.admin-users-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
}

.admin-users-table th {
  text-align: left;
  padding: 0.55rem 0.6rem;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.admin-users-table td {
  text-align: left;
  padding: 0.6rem 0.6rem;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.temp-password code {
  background: var(--bg);
  padding: 0.1rem 0.4rem;
  border-radius: 4px;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(11, 13, 19, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
  z-index: 100;
}

.modal-box {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  max-width: 380px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  padding: 1.5rem;
}

.modal-box h3 {
  margin: 0 0 0.6rem;
  font-size: 1.05rem;
}

.modal-box p {
  margin: 0 0 1.25rem;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

.modal-box ol {
  margin: 0 0 1.25rem;
  padding-left: 1.25rem;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

.modal-box ol li {
  margin-bottom: 0.5rem;
}

.modal-box ol li:last-child {
  margin-bottom: 0;
}

.signup-popup-box {
  position: relative;
  text-align: center;
  padding-top: 2rem;
}

.signup-popup-box h3 {
  font-size: 1.3rem;
  line-height: 1.3;
  padding: 0 0.75rem;
}

.signup-popup-box p {
  font-size: 0.95rem;
}

.modal-box .signup-popup-note {
  margin: 0.9rem 0 0;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--accent);
}

.signup-popup-close {
  position: absolute;
  top: 0.5rem;
  right: 0.6rem;
  width: 2rem;
  height: 2rem;
  border: none;
  background: none;
  color: var(--muted);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}

.signup-popup-close:hover {
  color: var(--text);
}

.modal-actions {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.modal-actions .primary-button {
  width: auto;
}

.modal-actions .modal-secondary-button {
  padding: 0.65rem 1.1rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font-weight: 600;
  cursor: pointer;
}

.modal-actions .modal-secondary-button:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.modal-actions .modal-link-button {
  border: none;
  background: none;
  color: var(--muted);
  font-size: 0.85rem;
  cursor: pointer;
  padding: 0.3rem;
}

.modal-actions .modal-link-button:hover {
  color: var(--text);
}

.admin-user-profile-box {
  max-width: 460px;
}

.admin-profile-fields {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.5rem 1rem;
  margin: 0 0 1.25rem;
  font-size: 0.9rem;
}

.admin-profile-fields dt {
  color: var(--muted);
  font-weight: 600;
}

.admin-profile-fields dd {
  margin: 0;
}

.admin-profile-password {
  border-top: 1px solid var(--border);
  padding-top: 1rem;
}

.admin-profile-password label {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  margin-bottom: 0.4rem;
}

.admin-profile-password-row {
  display: flex;
  gap: 0.5rem;
}

.admin-profile-password-row input {
  flex: 1;
}

.admin-profile-password #adminProfilePasswordStatus {
  margin: 0.5rem 0 0;
}

/* ==========================================================================
   Public landing page

   Scoped design tokens under #landingScreen. These were always immune to
   system dark mode, and now the whole site is (see color-scheme on :root at
   the top of this file), so the marketing page and every other screen alike
   render this light plum/lavender palette whatever the phone is set to.
   body.landing-active toggles the surrounding chrome (full-width header,
   hidden .panel wrapper) -- see showScreen() in app.js.
   ========================================================================== */

#landingScreen {
  background: var(--landing-bg);
  color: var(--landing-text);
}

/* Shown instead of the full marketing #landingScreen when the site is
   opened from inside an installed Android app (Play or the direct-download
   variant) -- someone who already installed the app doesn't need to be
   sold on installing it again, so this skips straight to "Get started"
   under the same branded header. See applyUrlRouting() in app.js. */
.app-splash {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  text-align: center;
  padding: 3.5rem 1.5rem;
  min-height: 50vh;
}

.app-splash-subcopy {
  max-width: 320px;
}

body.landing-active .app-shell {
  max-width: none;
  padding: 0;
}

body.studio-active .app-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: #ffffff;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid #ece3f0;
}

body.studio-active .header-top {
  max-width: 1180px;
  margin: 0 auto;
  padding-bottom: 0;
  border-bottom: none;
}

body.studio-active .app-header h1 {
  font-size: 1.2rem;
  color: var(--landing-primary, #4c145c);
}

body.studio-active .app-header .subtitle {
  display: none;
}

.header-brand-row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
}

.brand-logo-img {
  display: block;
  height: 68px;
  width: auto;
  max-width: 100%;
}

/* Tagline, cropped as its own image from the same source file (see
   web/brand/) rather than the whole logo-with-name lockup scaled down --
   that made the baked-in tagline illegibly tiny. Cropping it out lets it be
   sized independently for legibility while staying pixel-faithful to the
   original artwork (no font recreation). Shares .subtitle's base rule but
   overrides its studio-active display:none below so it stays visible on
   the landing/auth screens too, since the tagline is core brand identity,
   not just app-screen descriptive copy. */
.brand-tagline-img {
  display: block;
  /* height:18px alone doesn't shrink on narrow screens -- an <img> ignores
     max-width unless its own height can also give way, so both dimensions
     are "auto" here with max-height/max-width doing the actual clamping.
     At the intrinsic size (a wide, short crop) this still renders at
     18px tall wherever there's room, same as before; only on a phone-width
     screen does max-width become the binding constraint and the whole
     tagline scales down instead of overflowing the header sideways (real
     user report, 2026-08-27 -- this was pushing the whole page wider than
     the viewport on iPhone Safari and Android). */
  height: auto;
  width: auto;
  max-height: 18px;
  max-width: 100%;
  margin-top: 3px;
}

body.studio-active .app-header .brand-tagline {
  display: block;
}

body.studio-active .auth-status {
  padding-top: 0;
}

body.studio-active .auth-status {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

body.studio-active #authLink,
body.studio-active #forSalonsHeaderLink {
  border-radius: 999px;
  padding: 0.45rem 1.1rem;
  font-weight: 600;
  /* 44px minimum touch target, same as the hero's actions. */
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  transition: background-color 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

body.studio-active #authLink {
  border: 1px solid var(--landing-primary, #4c145c);
  color: var(--landing-primary, #4c145c);
}

body.studio-active #authLink:hover {
  background: var(--landing-primary, #4c145c);
  color: #ffffff;
}

body.studio-active #forSalonsHeaderLink {
  border: 1px solid var(--landing-border, #e8e0ee);
  color: var(--landing-muted, #6b6274);
}

body.studio-active #forSalonsHeaderLink:hover {
  border-color: var(--landing-primary, #4c145c);
  color: var(--landing-primary, #4c145c);
}

body.studio-active #authLink:focus-visible,
body.studio-active #forSalonsHeaderLink:focus-visible {
  outline: 3px solid var(--landing-primary, #4c145c);
  outline-offset: 3px;
}

body.landing-active .panel {
  display: none;
}

/* Auth / forgot-password / reset-password screens -- the flow the landing
   page's CTAs lead a logged-out visitor into. Recolored to the same Style
   AI Studio palette so it doesn't feel like a different, older app once
   they click through. Scoped by screen id (not a global --accent swap) so
   every other screen -- capture, results, buy credits, admin, change
   password -- keeps its original indigo accent untouched. */
#authScreen .tab-button.active {
  color: var(--landing-primary, #4c145c);
}

#authScreen .primary-button,
#forgotPasswordScreen .primary-button,
#resetPasswordScreen .primary-button {
  background: var(--landing-primary, #4c145c);
  border-color: var(--landing-primary, #4c145c);
}

#authScreen .primary-button:hover,
#forgotPasswordScreen .primary-button:hover,
#resetPasswordScreen .primary-button:hover {
  background: var(--landing-primary-hover, #3b0f48);
  border-color: var(--landing-primary-hover, #3b0f48);
}

#authScreen .auth-link,
#authScreen .password-toggle:hover,
#forgotPasswordScreen a,
#resetPasswordScreen a {
  color: var(--landing-primary, #4c145c);
}

#authScreen input:focus,
#forgotPasswordScreen input:focus,
#resetPasswordScreen input:focus {
  border-color: var(--landing-primary, #4c145c);
  box-shadow: 0 0 0 3px rgba(76, 20, 92, 0.15);
}

#forgotPasswordScreen h2,
#resetPasswordScreen h2 {
  color: var(--landing-text, #1f1a24);
}

/* Decorative page backdrop for the studio-branded auth/forgot/reset/mode
   screens -- a faint, tiled pattern of styling tools (scissors, comb,
   sparkles, a hair wave) plus two soft blurred color blobs, echoing the
   same abstract decor used inside the landing hero panel. Line art only,
   no photos or real imagery (no model release on file); :not(.landing-active)
   keeps this off the landing page itself, which already has its own decor. */
body.studio-active:not(.landing-active) {
  position: relative;
  background-color: var(--landing-bg, #ffffff);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240' viewBox='0 0 240 240'%3E%3Cg fill='none' stroke='%23d3c0da' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cg transform='translate(28,44) rotate(-18)'%3E%3Ccircle cx='0' cy='0' r='6'/%3E%3Ccircle cx='0' cy='22' r='6'/%3E%3Cpath d='M5 4L30 30M5 18L30 -8'/%3E%3C/g%3E%3Cg transform='translate(168,34) rotate(12)'%3E%3Crect x='0' y='0' width='34' height='9' rx='2'/%3E%3Cpath d='M3 9v13M9 9v13M15 9v13M21 9v13M27 9v13M33 9v13'/%3E%3C/g%3E%3Cpath d='M170 150c6-9 12-9 18 0s12 9 18 0'/%3E%3Cpath d='M30 176c6-9 12-9 18 0s12 9 18 0'/%3E%3Cpath d='M9 0l2.6 7 7 2.6-7 2.6-2.6 7-2.6-7-7-2.6 7-2.6z' fill='%23d3c0da' stroke='none' transform='translate(196,116)'/%3E%3Cpath d='M9 0l2.6 7 7 2.6-7 2.6-2.6 7-2.6-7-7-2.6 7-2.6z' fill='%23d3c0da' stroke='none' transform='translate(100,8)'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 260px 260px;
}

body.studio-active:not(.landing-active)::before,
body.studio-active:not(.landing-active)::after {
  content: "";
  position: fixed;
  z-index: -1;
  border-radius: 50%;
  pointer-events: none;
}

body.studio-active:not(.landing-active)::before {
  top: -130px;
  right: -110px;
  width: 340px;
  height: 340px;
  background: var(--landing-primary, #4c145c);
  opacity: 0.08;
}

body.studio-active:not(.landing-active)::after {
  bottom: -150px;
  left: -120px;
  width: 320px;
  height: 320px;
  background: var(--landing-mint-text, #1f6b45);
  opacity: 0.08;
}

body.studio-active .mode-card:hover {
  border-color: var(--landing-primary, #4c145c);
}

body.studio-active .mode-card:hover .mode-title {
  color: var(--landing-primary, #4c145c);
}

/* --- Signed-in app backdrop ----------------------------------------------
   Real user feedback (2026-09-06): "the white background after login should
   be vibrant with some flash... eye catching". The decor above already gave
   the studio-branded screens two colour blobs, but at 8% opacity over a pure
   white page they read as plain white in practice.

   This deliberately *overrides* that same treatment rather than adding a
   second competing layer -- body.app-active is only ever set on signed-in
   screens (see showScreen in app.js), so the pre-login auth/forgot/reset
   screens keep the softer original exactly as-is. Matching specificity to
   the rules above and sitting later in the file is what makes these win.

   The panel itself stays opaque, so body-copy contrast is completely
   unchanged -- all of this happens behind it. */
body.app-active:not(.landing-active) {
  background-color: var(--landing-surface, #f7f2fa);
}

/* Declares the pseudo-elements outright rather than only overriding the
   studio-active ones: body.studio-active is set on the mode screen but NOT
   on the other signed-in screens (buy credits, capture, results...), so
   without content/position here the glow simply never rendered on them --
   the backdrop went flat again as soon as you left the studio grid. */
body.app-active:not(.landing-active)::before,
body.app-active:not(.landing-active)::after {
  content: "";
  position: fixed;
  z-index: -1;
  border-radius: 50%;
  pointer-events: none;
  width: 42vmax;
  height: 42vmax;
  filter: blur(80px);
  opacity: 0.42;
}

body.app-active:not(.landing-active)::before {
  top: -12vmax;
  right: -10vmax;
  background: radial-gradient(circle, var(--hero-magenta-glow) 0%, transparent 70%);
  animation: app-aurora-a 26s ease-in-out infinite alternate;
}

body.app-active:not(.landing-active)::after {
  bottom: -14vmax;
  left: -10vmax;
  background: radial-gradient(circle, var(--hero-orange) 0%, transparent 70%);
  animation: app-aurora-b 32s ease-in-out infinite alternate;
}

@keyframes app-aurora-a {
  from {
    transform: translate3d(0, 0, 0) scale(1);
  }
  to {
    transform: translate3d(-5vmax, 4vmax, 0) scale(1.18);
  }
}

@keyframes app-aurora-b {
  from {
    transform: translate3d(0, 0, 0) scale(1.12);
  }
  to {
    transform: translate3d(5vmax, -4vmax, 0) scale(1);
  }
}

/* Colour without the drift for anyone who has asked their OS for reduced
   motion -- decoration is never worth triggering vestibular discomfort. */
@media (prefers-reduced-motion: reduce) {
  body.app-active:not(.landing-active)::before,
  body.app-active:not(.landing-active)::after {
    animation: none;
  }
}

/* The main content card itself was pure white (--panel), which is what made
   the options area still read as a blank sheet even once the page behind it
   had colour (real user request, 2026-09-06: "can we also use some other
   colour instead of using white where we have all the options").

   Two things are happening here: a soft violet-tinted gradient replaces the
   flat white, and a light translucency + blur lets the drifting aurora
   behind actually show through the card, which is what stops it looking
   like a sheet of paper pasted on top of a colourful page. The mode cards
   *inside* stay near-white on purpose -- they now pop forward against this
   tinted surface instead of disappearing into it.

   Kept at high alpha (0.9+) so body text keeps essentially the same
   contrast it had on white. */
body.app-active .panel {
  background: linear-gradient(158deg, rgba(253, 250, 255, 0.9) 0%, rgba(238, 226, 252, 0.86) 100%);
  border-color: rgba(255, 255, 255, 0.75);
  box-shadow: 0 10px 40px rgba(72, 6, 127, 0.1), 0 2px 8px rgba(72, 6, 127, 0.05);
  backdrop-filter: blur(18px) saturate(1.15);
  -webkit-backdrop-filter: blur(18px) saturate(1.15);
}

/* The header block sat directly above the panel as the one remaining sheet
   of pure white, which made the tinting below it look like a mistake rather
   than a choice. Same glass treatment so the two read as one surface. */
/* FULLY OPAQUE on purpose. This header is position:sticky (see
   body.studio-active .app-header above), so the whole page scrolls
   underneath it. An earlier version of this rule used a translucent
   background to match the panel's glass look, which meant the studio cards
   were visibly sliding *through* the header as you scrolled (real user
   report, 2026-09-06). A sticky element has to be opaque; keep the tint,
   drop the transparency. backdrop-filter is gone with it -- it does
   nothing behind an opaque fill, and it needlessly creates a containing
   block. */
body.app-active .app-header {
  background: linear-gradient(158deg, #fdfaff 0%, #f1e7fd 100%);
}

/* On a phone this header is the logo, the tagline, a Business login button
   and a ten-item account nav -- roughly a third of the screen. Pinned, that
   is a third of every screen permanently gone, and the studio options sit
   behind it however far you scroll (real user report with screenshot,
   2026-09-06). Unpinning it on narrow screens gives the whole viewport back
   to the actual content; the account links are still one short scroll up.
   Desktop keeps the sticky header, where it costs a much smaller share of a
   wider, shorter viewport. Scoped to app-active so the public landing/auth
   pages keep their existing sticky treatment. */
@media (max-width: 760px) {
  body.app-active .app-header {
    position: static;
  }
}


.marketing-nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  order: 2;
}

.marketing-nav a {
  color: var(--landing-text, #1f1a24);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.92rem;
  transition: color 0.12s ease;
}

.marketing-nav a:hover,
.marketing-nav a:focus-visible {
  color: var(--landing-primary, #4c145c);
}

.nav-icon {
  display: inline-block;
  margin-right: 0.3rem;
  font-size: 0.7em;
  color: var(--landing-primary, #4c145c);
}

.marketing-nav-toggle {
  order: 2;
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  width: 36px;
  height: 36px;
  border: 1px solid var(--landing-border, #e8e0ee);
  border-radius: 8px;
  background: #ffffff;
  cursor: pointer;
  margin-left: auto;
}

.marketing-nav-toggle span {
  display: block;
  height: 2px;
  margin: 0 7px;
  background: var(--landing-primary, #4c145c);
  border-radius: 1px;
}

.header-brand {
  order: 1;
}

.auth-status {
  order: 3;
}

.landing-container {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 1.25rem;
}

/* ==========================================================================
   LANDING HERO
   Scoped entirely to <section class="landing-hero" id="hero">. Every
   selector below is hero-only (verified: no other section, screen or
   component references .landing-hero*, .hero-*, .landing-primary-button or
   .landing-secondary-link).
   Colours come from the --hero-* tokens in :root, which are derived from
   the approved logo -- see the contrast table there.
   ========================================================================== */

.landing-hero {
  position: relative;
  isolation: isolate;
  /* Keeps the mobile layout free of horizontal overflow no matter how the
     glow layer or the staggered showcase cards extend past the container. */
  overflow-x: clip;
  background-color: var(--hero-canvas);
  padding: clamp(1.5rem, 0.9rem + 2.2vw, 3rem) 0 clamp(2.25rem, 1.5rem + 2.5vw, 3.75rem);
  scroll-margin-top: 4.5rem;
}

/* Background: warm off-white foundation, one soft lavender radial glow, one
   controlled magenta/coral accent glow, and a very fine editorial dot grain.
   All CSS gradients -- no image request, no animated particles, and no
   full-screen dark purple. This replaces the old 277 KB logo-icon.png
   watermark that used to be painted here as a background-image, which cost
   an image download for pure decoration. The approved logo itself is
   untouched and still renders in the header. */
.landing-hero-glow {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    radial-gradient(52rem 30rem at 82% -14%, rgba(167, 139, 250, 0.20), transparent 66%),
    radial-gradient(30rem 22rem at 2% 4%, rgba(217, 70, 239, 0.07), transparent 68%),
    radial-gradient(28rem 22rem at 98% 96%, rgba(251, 113, 133, 0.07), transparent 70%),
    linear-gradient(180deg, var(--hero-lavender), var(--hero-canvas) 42%);
}

/* Fashion-editorial grain. A single tiny repeating dot pattern at very low
   alpha -- cheap to composite (no SVG filter, no turbulence, no image). */
.landing-hero-glow::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle at 1px 1px, rgba(33, 21, 42, 0.05) 1px, transparent 0);
  background-size: 22px 22px;
  opacity: 0.5;
}

.landing-hero-grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2rem, 1rem + 3vw, 3.25rem);
  align-items: center;
  /* Comfortable maximum width -- .landing-container is wider than the hero
     wants to be, so the two columns never drift too far apart. */
  max-width: 1180px;
}

.landing-hero-copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.15rem;
  animation: heroRise 0.45s ease-out both;
}

/* --- Badge ---------------------------------------------------------------
   Deliberately small: a label above the headline, never the largest thing
   in the hero. */
.hero-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  max-width: 100%;
  padding: 0.4rem 0.85rem;
  border: 1px solid var(--hero-line-strong);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.72);
  color: var(--hero-violet);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  line-height: 1.3;
}

.hero-badge svg {
  width: 14px;
  height: 14px;
  flex: none;
}

/* --- Headline and supporting copy ---------------------------------------- */

.landing-headline {
  /* ~36px at 390px, ~47px at 768px, ~56px at 1024px and above. Solid
     colour, never a gradient -- this is the page's core message. */
  font-size: clamp(2.25rem, 1.3rem + 3.4vw, 3.5rem);
  line-height: 1.06;
  letter-spacing: -0.022em;
  font-weight: 800;
  margin: 0;
  color: var(--hero-text);
  /* Keeps the last line from being a single orphaned word. text-wrap is
     progressive enhancement; the max-width does the work everywhere else. */
  text-wrap: balance;
  max-width: 18ch;
}

.landing-subcopy {
  font-size: clamp(1rem, 0.95rem + 0.28vw, 1.15rem);
  line-height: 1.6;
  color: var(--hero-muted);
  margin: 0;
  max-width: 46ch;
}

/* --- Calls to action -----------------------------------------------------
   #landingPrimaryCta is a <button> and #landingSecondaryLink is an
   <a href="#">; both keep their existing ids, element types and handlers
   (see wireLandingInteractions in landing.js). Styling only. */

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  margin-top: 0.15rem;
}

.landing-primary-button,
.landing-secondary-link {
  /* 44x44 minimum touch target, comfortably exceeded on both axes. */
  min-height: 48px;
  min-width: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.8rem 1.5rem;
  border-radius: 999px;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.2;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 0.15s ease, border-color 0.15s ease,
    box-shadow 0.15s ease, transform 0.15s ease, color 0.15s ease;
}

.landing-primary-button {
  border: none;
  /* Violet -> magenta, both logo-derived. White text clears AA against
     either end of the ramp (10.2:1 and 6.6:1). */
  background-image: linear-gradient(135deg, var(--hero-violet), var(--hero-magenta));
  background-color: var(--hero-violet);
  color: #ffffff;
  box-shadow: var(--hero-cta-shadow);
}

.landing-primary-button svg {
  width: 18px;
  height: 18px;
  flex: none;
  transition: transform 0.15s ease;
}

.landing-primary-button:hover {
  box-shadow: 0 10px 22px rgba(92, 11, 166, 0.3);
  transform: translateY(-1px);
}

.landing-primary-button:hover svg {
  transform: translateX(3px);
}

.landing-primary-button:active {
  transform: translateY(1px);
  box-shadow: 0 3px 10px rgba(92, 11, 166, 0.26);
  background-image: linear-gradient(135deg, var(--hero-violet-deep), var(--hero-magenta));
}

/* Loading affordance. This is a styling hook only -- nothing in landing.js
   sets aria-busy today, because wiring one would mean changing the CTA's
   click behaviour, which is deliberately left exactly as it was. */
.landing-primary-button[aria-busy="true"] {
  opacity: 0.75;
  cursor: progress;
  transform: none;
}

.landing-secondary-link {
  border: 1.5px solid var(--hero-line-strong);
  background: rgba(255, 255, 255, 0.85);
  color: var(--hero-text);
}

.landing-secondary-link:hover {
  border-color: var(--hero-violet);
  background: var(--hero-lavender);
  color: var(--hero-violet-deep);
}

.landing-secondary-link:active {
  transform: translateY(1px);
}

/* Focus is always visible, on both actions, in one obvious style. */
.landing-primary-button:focus-visible,
.landing-secondary-link:focus-visible {
  outline: 3px solid var(--hero-violet);
  outline-offset: 3px;
}

/* --- Notes beside the CTAs ----------------------------------------------- */

.hero-free-note,
.hero-trust-note {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.45;
}

.hero-free-note {
  padding: 0.4rem 0.8rem 0.4rem 0.55rem;
  border-radius: 999px;
  background: var(--hero-mint);
  color: var(--hero-mint-ink);
  font-weight: 600;
}

.hero-trust-note {
  color: var(--hero-muted);
}

.hero-note-icon {
  display: inline-flex;
  flex: none;
}

.hero-note-icon svg {
  width: 16px;
  height: 16px;
}

/* --- Value indicators ---------------------------------------------------- */

.hero-value-points {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1.4rem;
  margin: 0.15rem 0 0;
  padding: 0;
}

.hero-value-points li {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 0.9rem;
  font-weight: 600;
  /* Full-strength text, not a pale purple -- each indicator also carries its
     own icon, so nothing here depends on colour alone. */
  color: var(--hero-text);
}

.hero-value-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 10px;
  background: var(--hero-lavender);
  border: 1px solid var(--hero-line);
  color: var(--hero-violet);
  flex: none;
}

.hero-value-icon svg {
  width: 16px;
  height: 16px;
}

/* ==========================================================================
   HERO VISUAL
   A product-result composition: ranked recommendation, selected-look
   preview, and the guidance that comes with it. Rendered by
   renderLandingPreview() in landing.js.
   ========================================================================== */

.landing-hero-visual {
  scroll-margin-top: 4.5rem;
  min-width: 0;
}

.hero-showcase {
  position: relative;
  display: grid;
  gap: 0.9rem;
  max-width: 34rem;
  margin-inline: auto;
  animation: heroRise 0.45s ease-out 0.12s both;
}

.hero-showcase-glow {
  position: absolute;
  inset: -5% -2% -3%;
  z-index: -1;
  pointer-events: none;
  background-image:
    radial-gradient(60% 50% at 70% 12%, rgba(138, 24, 196, 0.16), transparent 70%),
    radial-gradient(55% 45% at 18% 88%, rgba(251, 113, 133, 0.14), transparent 72%);
}

.hero-card-checks {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 0.75rem;
  margin: 0.1rem 0 0;
  padding: 0 0.2rem;
  width: 100%;
}

.hero-card-checks li {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--hero-text);
}

.hero-check-icon {
  display: inline-flex;
  flex: none;
  color: var(--hero-violet);
}

.hero-check-icon svg {
  width: 15px;
  height: 15px;
}

/* Honest about turnaround -- the hero must not imply an instant preview. */
.hero-timing-note {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0;
  padding-inline: 0.2rem;
  font-size: 0.8rem;
  color: var(--hero-muted);
}

/* ==========================================================================
   HERO SHOWCASE -- before/after cards
   Two cards. Each one holds a single person: their original photograph on the
   left, that same person's StylesStudio.ai results on the right.

   THE LAYOUT IS THE SAFEGUARD. An earlier version cross-faded a whole card
   between before and after, and ran the two cards half a cycle apart so one
   showed "Before" while the other showed "After". Side by side that reads as
   one person's before next to a different person's after -- a transformation
   that never happened. Keeping both halves of a pair on screen together, in
   the same card, means there is no moment at which the wrong two frames can
   be read as a pair.

   Motion is therefore limited to the "after" half cycling through that same
   person's results. The original never moves. All CSS -- no timers, no
   library. See renderLandingPreview() in landing.js for the markup and for
   the asset provenance.
   ========================================================================== */

.hero-showcase-caption {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
  padding-inline: 0.2rem;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  /* Deliberately NOT text-transform: uppercase -- the brand name has to read
     exactly "StylesStudio.ai", and uppercasing renders it "STYLESSTUDIO.AI". */
  color: var(--hero-violet);
}

.hero-showcase-caption svg {
  width: 13px;
  height: 13px;
  flex: none;
}

/* One card per row at every width. The two halves need enough width to read
   as faces, which a side-by-side pair of cards cannot give them. */
.ba-deck {
  display: grid;
  gap: 0.75rem;
}

/* Phones show the lead card only. The second card is a supporting example
   rather than new information, and its frames are lazy-loaded, so
   display:none also means a phone never downloads them. */
/* Both models show at every width (real user instruction, 2026-09-04: "what
   all photos i shared, those should be coming"). This used to hide the
   studio pair below 700px to keep the mobile hero short, but that silently
   dropped one of the two models the owner supplied -- half the showcase --
   on exactly the devices most visitors use. The cards stack instead. */

.ba-card {
  margin: 0;
  border-radius: var(--hero-radius);
  background: #ffffff;
  border: 1px solid var(--hero-line);
  box-shadow: var(--hero-card-shadow);
  overflow: hidden;
}

/* Two equal halves, each 4:5, so the pair is 8:5. The ratio is declared up
   front, so the box is reserved before any photograph decodes. */
.ba-frame {
  display: grid;
  grid-template-areas: "before after";
  grid-template-columns: 1fr 1fr;
  aspect-ratio: 8 / 5;
}

.ba-base {
  grid-area: before;
  position: relative;
  min-width: 0;
  /* A hairline between the halves so the split is legible even where the two
     photographs have similar backgrounds. */
  border-right: 1px solid rgba(255, 255, 255, 0.5);
}

/* Every result stacks in the "after" half and cross-fades in place. They can
   only ever appear opposite their own original, because they are inside that
   original's card. */
.ba-after {
  grid-area: after;
  position: relative;
  min-width: 0;
  opacity: 0;
}

.ba-base picture,
.ba-after picture {
  display: block;
  height: 100%;
}

.ba-base img,
.ba-after img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ba-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: 1.5rem 0.7rem 0.6rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: #ffffff;
  /* Dark scrim so the caption stays well over AA contrast against whatever
     part of the photograph sits underneath it. */
  background: linear-gradient(180deg, rgba(12, 6, 24, 0), rgba(12, 6, 24, 0.8));
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
}

/* Labels are static and always both present -- one per half. Nothing about
   which half is which depends on timing. */
.ba-tag {
  position: absolute;
  top: 0.6rem;
  z-index: 2;
  padding: 0.26rem 0.6rem;
  border-radius: 999px;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.ba-tag-before {
  grid-area: before;
  align-self: start;
  justify-self: start;
  position: relative;
  top: 0.6rem;
  left: 0.6rem;
  background: rgba(255, 255, 255, 0.94);
  color: var(--hero-text);
}

.ba-tag-after {
  grid-area: after;
  align-self: start;
  justify-self: start;
  position: relative;
  top: 0.6rem;
  left: 0.6rem;
  background-image: linear-gradient(135deg, var(--hero-violet), var(--hero-magenta));
  color: #ffffff;
}

/* --- Motion --------------------------------------------------------------
   The results cross-fade into one another with overlapping windows, so the
   "after" half is never empty. The -0.6s offset starts the first result
   already on screen rather than fading up from blank on load.

   Percentages are expressed against each variant's own cycle length: a
   result holds for one slot and hands over during the 0.6s overlap. */

.ba-after {
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-fill-mode: both;
}

.ba-after-3 {
  animation-name: baCycle3;
  animation-duration: calc(var(--ba-slot, 6s) * 3);
  animation-delay: calc(var(--ba-slot, 6s) * var(--ba-slot-index) - 0.6s);
}

.ba-after-2 {
  animation-name: baCycle2;
  animation-duration: calc(var(--ba-slot, 6s) * 2);
  animation-delay: calc(var(--ba-slot, 6s) * var(--ba-slot-index) - 0.6s);
}

@keyframes baCycle3 {
  0% { opacity: 0; }
  3.33% { opacity: 1; }
  33.33% { opacity: 1; }
  36.67% { opacity: 0; }
  100% { opacity: 0; }
}

@keyframes baCycle2 {
  0% { opacity: 0; }
  5% { opacity: 1; }
  50% { opacity: 1; }
  55% { opacity: 0; }
  100% { opacity: 0; }
}

/* ==========================================================================
   HERO MOTION
   One gentle rise on entry, one very slight float on a single layer.
   Nothing continuous or attention-grabbing, and nothing that gates input.
   ========================================================================== */

@keyframes heroRise {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .landing-hero-copy,
  .hero-showcase {
    animation: none !important;
    transform: none !important;
  }

  /* No re-layout needed: the card already shows the original and a result
     side by side. Stopping the cycle just leaves the first result up. */
  .ba-after {
    animation: none !important;
  }

  .ba-after[style*="--ba-slot-index: 0"] {
    opacity: 1;
  }

  .landing-primary-button,
  .landing-secondary-link,
  .landing-primary-button svg {
    transition: none;
  }

  .landing-primary-button:hover,
  .landing-primary-button:active,
  .landing-secondary-link:active,
  .landing-primary-button:hover svg {
    transform: none;
  }
}

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

/* Small phones (360-430px): stack the two actions full width so the primary
   CTA stays unmistakably the primary one and keeps a large tap target. */
@media (max-width: 480px) {
  .hero-actions {
    flex-direction: column;
    align-items: stretch;
  }

  .landing-primary-button,
  .landing-secondary-link {
    width: 100%;
  }

  .hero-value-points {
    flex-direction: column;
    gap: 0.55rem;
  }
}

/* 700px, not 900px (real user instruction, 2026-09-04): on a large phone
   or foldable the single-column hero pushed the before/after proof cards
   entirely below the fold, so the page opened on headline-and-buttons alone
   -- the whole point of the hero is that the proof is visible next to the
   claim. Phones narrower than 700px keep the single-column layout at full
   text size rather than being scaled down to fit -- both showcase cards
   still render there, stacked. */
@media (min-width: 700px) {
  .landing-hero-grid {
    grid-template-columns: 1.15fr 0.85fr;
    align-items: center;
  }

}

.landing-section-heading {
  text-align: center;
  font-size: 1.6rem;
  font-weight: 800;
  margin: 0 0 1.75rem;
  color: var(--landing-text, #1f1a24);
}

.landing-pricing {
  background: var(--landing-surface, #f7f2fa);
  padding: 3rem 0;
  scroll-margin-top: 4.5rem;
}

.pricing-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.1rem;
}

.pricing-card {
  background: #ffffff;
  border: 1px solid var(--landing-border, #e8e0ee);
  border-radius: var(--landing-radius, 18px);
  box-shadow: var(--landing-shadow);
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  position: relative;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.pricing-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 16px 32px rgba(76, 20, 92, 0.14), 0 4px 10px rgba(76, 20, 92, 0.08);
}

.pricing-card-icon {
  font-size: 1.6rem;
  line-height: 1;
  margin-bottom: 0.15rem;
}

.pricing-card-note {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--landing-muted, #6b6274);
  margin: -0.3rem 0 0;
}

.pricing-card-name {
  font-weight: 700;
  font-size: 1rem;
  color: var(--landing-muted, #6b6274);
}

.pricing-card-price-row {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.pricing-card-price {
  font-size: 2rem;
  font-weight: 700;
  color: var(--landing-text, #1f1a24);
}

.pricing-card-original-price {
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--landing-muted, #6b6274);
  text-decoration: line-through;
}

.pricing-card-tagline {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--landing-mint-text, #1f6b45);
  margin: 0;
}

.pricing-card-desc {
  font-size: 0.88rem;
  color: var(--landing-muted, #6b6274);
  margin: 0 0 0.2rem;
}

.pricing-card-features {
  list-style: none;
  margin: 0 0 0.2rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--landing-text, #1f1a24);
}

.pricing-card-features li {
  padding-left: 1.1rem;
  position: relative;
}

.pricing-card-features li::before {
  content: "✓";
  position: absolute;
  left: 0;
  color: var(--landing-mint-text, #1f6b45);
}

.pricing-card-per-look {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--landing-mint-text, #1f6b45);
  margin: -0.2rem 0 0;
}

.pricing-card-cta {
  margin-top: auto;
  padding: 0.7rem 1.1rem;
  border-radius: 999px;
  border: 1px solid var(--landing-primary, #4c145c);
  background: #ffffff;
  color: var(--landing-primary, #4c145c);
  font-weight: 700;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

.pricing-card-cta:hover {
  background: var(--landing-primary, #4c145c);
  color: #ffffff;
}

.pricing-card-free .pricing-card-name {
  color: var(--landing-mint-text, #1f6b45);
}

.pricing-card-popular {
  border-color: var(--landing-primary, #4c145c);
  border-width: 2px;
}

.pricing-card-best {
  background: linear-gradient(160deg, var(--landing-primary, #4c145c), var(--landing-primary-hover, #3b0f48));
  border-color: var(--landing-primary, #4c145c);
  color: #ffffff;
  box-shadow: 0 18px 36px rgba(76, 20, 92, 0.28), 0 4px 10px rgba(76, 20, 92, 0.16);
}

.pricing-card-best:hover {
  box-shadow: 0 22px 42px rgba(76, 20, 92, 0.34), 0 6px 14px rgba(76, 20, 92, 0.2);
}

.pricing-card-best .pricing-card-name,
.pricing-card-best .pricing-card-price,
.pricing-card-best .pricing-card-desc,
.pricing-card-best .pricing-card-features,
.pricing-card-best .pricing-card-original-price {
  color: #ffffff;
}

.pricing-card-best .pricing-card-desc {
  opacity: 0.85;
}

.pricing-card-best .pricing-card-features li::before {
  color: #ffffff;
}

.pricing-card-best .pricing-card-per-look,
.pricing-card-best .pricing-card-tagline {
  color: rgba(255, 255, 255, 0.85);
}

.pricing-card-best .pricing-card-original-price {
  opacity: 0.7;
}

.pricing-card-best .pricing-card-cta {
  background: #ffffff;
  color: var(--landing-primary, #4c145c);
  border-color: #ffffff;
}

.pricing-card-best .pricing-card-cta:hover {
  background: var(--landing-mint-bg, #ddf4e8);
}

.pricing-card-badge {
  position: absolute;
  top: -0.65rem;
  left: 50%;
  transform: translateX(-50%);
  background: var(--landing-mint-bg, #ddf4e8);
  color: var(--landing-mint-text, #1f6b45);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  box-shadow: var(--landing-shadow);
  white-space: nowrap;
}

.pricing-card-popular .pricing-card-badge {
  background: var(--landing-primary, #4c145c);
  color: #ffffff;
}

.landing-pricing-note {
  text-align: center;
  color: var(--landing-muted, #6b6274);
  font-size: 0.88rem;
  margin: 1.75rem 0 0;
}

.landing-pricing-currency-note {
  margin-top: 0.5rem;
  font-size: 0.8rem;
}

.landing-how {
  padding: 3rem 0;
  scroll-margin-top: 4.5rem;
}

.how-steps {
  list-style: none;
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
  margin: 0;
  padding: 0;
  counter-reset: how-step;
}

.how-step {
  display: flex;
  gap: 0.85rem;
  align-items: flex-start;
}

.how-step-icon {
  position: relative;
  flex: none;
  width: 42px;
  height: 42px;
  border-radius: 12px;
  background: var(--landing-surface, #f7f2fa);
  color: var(--landing-primary, #4c145c);
  display: flex;
  align-items: center;
  justify-content: center;
}

.how-step-icon svg {
  width: 19px;
  height: 19px;
}

.how-step-number {
  position: absolute;
  top: -7px;
  left: -7px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--landing-primary, #4c145c);
  color: #ffffff;
  font-size: 0.68rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

.how-connector {
  display: none;
}

.how-step-title {
  font-weight: 700;
  margin: 0 0 0.15rem;
  color: var(--landing-text, #1f1a24);
}

.how-step-desc {
  margin: 0;
  font-size: 0.88rem;
  color: var(--landing-muted, #6b6274);
}

.landing-trust {
  padding: 2.5rem 0 3.5rem;
  scroll-margin-top: 4.5rem;
}

.trust-strip {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  text-align: center;
  /* Without this the <p> children blockify to the container's full width and
     each line's icon+text sits hard against the left edge -- `text-align`
     cannot position a flex item, only the text inside a non-flex box, and
     each <p> is itself display:inline-flex. The three lines differ a lot in
     length, so the result read as ragged rather than centred. In the row
     direction used from 640px up this centres them vertically instead, which
     matters because the third line is long enough to wrap. */
  align-items: center;
  color: var(--landing-muted, #6b6274);
  font-size: 0.85rem;
}

.trust-strip p {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.trust-strip-icon {
  display: inline-flex;
  color: var(--landing-primary, #4c145c);
  flex: none;
}

.trust-strip-icon svg {
  width: 14px;
  height: 14px;
}

.landing-footer {
  border-top: 1px solid var(--landing-border, #e8e0ee);
  padding: 1.5rem 0;
  margin-top: 2rem;
}

/* Same Terms/Privacy/Refund/Contact links as .landing-footer, but for every
   authenticated screen (dashboard, buy credits, my photos, admin, etc.) --
   real user instruction (2026-08-27): a Razorpay reviewer signing in
   shouldn't lose access to these once past the landing page. Uses the
   app-wide tokens (not --landing-*), since those are deliberately locked to
   the landing/auth palette and this footer needs to match whatever screen
   it's on, including dark mode. Hidden on the landing screen itself, which
   already has its own .landing-footer. */
.app-footer {
  border-top: 1px solid var(--border);
  padding: 1.25rem 0 0.5rem;
  margin-top: 2rem;
}

body.landing-active .app-footer {
  display: none;
}

.app-footer-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  text-align: center;
  color: var(--muted);
  font-size: 0.78rem;
}

.app-footer-copyright {
  margin: 0;
}

.app-footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.35rem 0.85rem;
}

.app-footer-links a {
  color: var(--muted);
  text-decoration: none;
}

.app-footer-links a:hover {
  color: var(--accent);
  text-decoration: underline;
}

.landing-footer-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  text-align: center;
  color: var(--landing-muted, #6b6274);
  font-size: 0.8rem;
}

.landing-footer-copyright {
  margin: 0;
}

.landing-footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.4rem 1rem;
}

.landing-footer-links a {
  color: var(--landing-muted, #6b6274);
  text-decoration: none;
}

.landing-footer-links a:hover {
  color: var(--landing-primary, #4c145c);
  text-decoration: underline;
}

.nav-contact-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.25;
  text-align: center;
}

.contact-email {
  display: block;
  font-weight: 400;
  font-size: 0.78em;
  opacity: 0.75;
}

@media (min-width: 640px) {
  .landing-footer-inner {
    flex-direction: row;
    justify-content: space-between;
    text-align: left;
  }
}

@media (min-width: 640px) {
  .pricing-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .how-steps {
    grid-template-columns: 1fr auto 1fr auto 1fr;
    align-items: center;
  }

  .how-connector {
    display: block;
    align-self: start;
    height: 0;
    margin-top: 21px;
    border-top: 2px dotted var(--landing-border, #e8e0ee);
    width: 2.5rem;
  }

  .trust-strip {
    flex-direction: row;
    justify-content: center;
    gap: 1.75rem;
  }
}

@media (min-width: 800px) {
  .pricing-grid {
    grid-template-columns: repeat(4, 1fr);
    align-items: center;
  }

  .pricing-card-best,
  .pricing-card-popular {
    padding: 1.85rem 1.5rem;
    transform: translateY(-0.5rem);
  }
}

/* Was 860px, which left a 40px band (860-900) where the nav had already
   collapsed to a hamburger but the hero was still single-column. Aligned to
   the hero's own 700px boundary so the whole page switches at once. */
@media (max-width: 699px) {
  .marketing-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.25rem;
    background: #ffffff;
    border-bottom: 1px solid var(--landing-border, #e8e0ee);
    padding: 0.75rem 1.25rem 1rem;
    box-shadow: var(--landing-shadow);
    display: none;
  }

  .marketing-nav:not(.hidden).open {
    display: flex;
  }

  .marketing-nav:not(.open) {
    display: none;
  }

  .marketing-nav-toggle:not(.hidden) {
    display: flex;
  }

  body.landing-active .header-top {
    position: relative;
  }
}

.landing-differentiators {
  padding: 3rem 0 0.5rem;
  scroll-margin-top: 4.5rem;
  background: linear-gradient(180deg, rgba(76, 20, 92, 0.03), transparent 60%);
}

/* What is inside the Virtual Hair & Color Studio. Specifics, not adjectives:
   a studio that promises "endless possibilities" promises nothing. */
.style-color-inside {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.45rem;
}

.style-color-inside li {
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
  font-size: 0.9rem;
  color: #5f6b76;
}

/* Continues the thread from the face scan. Hidden for the majority who never
   scanned, so it never reads as an empty promise. */
.style-color-personal {
  margin: 0.75rem 0 0;
  font-size: 0.9rem;
  font-weight: 600;
  color: #7a2d6b;
}

.style-color-personal.hidden {
  display: none;
}

/* The face scan carried across the sign-up. Sits at the top of the studio,
   above the hero, because someone who signed up specifically to see the styles
   chosen for their face should meet them first rather than go looking. */
.scan-handoff {
  margin: 0 0 1.25rem;
  padding: 1.1rem 1.25rem;
  border: 1px solid rgba(76, 20, 92, 0.14);
  border-radius: 16px;
  background: linear-gradient(180deg, rgba(76, 20, 92, 0.06), rgba(76, 20, 92, 0.01));
}

.scan-handoff.hidden {
  display: none;
}

.scan-handoff-eyebrow {
  margin: 0;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #6b5570;
}

.scan-handoff-title {
  margin: 0.35rem 0 0.75rem;
  font-size: 1.1rem;
}

.scan-handoff-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.scan-handoff-list li {
  padding: 0.5rem 0;
  border-top: 1px solid rgba(76, 20, 92, 0.08);
}

.scan-handoff-list li strong {
  display: block;
  font-size: 0.95rem;
}

.scan-handoff-list li span {
  font-size: 0.82rem;
  color: #5f6b76;
}

/* The free face scan, directly under the hero. Given its own tinted panel so
   it reads as a distinct offer rather than more hero copy -- it asks for
   nothing and returns something, which is different in kind from everything
   else on the page. */
.landing-face-scan {
  padding: 2.5rem 0;
  scroll-margin-top: 4.5rem;
}

.landing-face-scan-copy {
  border: 1px solid rgba(76, 20, 92, 0.12);
  border-radius: 18px;
  padding: 1.75rem;
  background: linear-gradient(180deg, rgba(76, 20, 92, 0.05), rgba(76, 20, 92, 0.01));
}

.landing-face-scan-copy .landing-section-heading {
  margin: 0.75rem 0 0.5rem;
}

.landing-face-scan-copy .landing-subcopy {
  max-width: 46rem;
}

.face-scan-points {
  margin: 1rem 0 1.25rem;
}

/* An anchor styled as the primary button: it is a real link to a real page, so
   it keeps middle-click, long-press and screen-reader behaviour that a <button>
   with a click handler would quietly throw away. */
.landing-face-scan-copy .landing-primary-button {
  text-decoration: none;
}

@media (max-width: 640px) {
  .landing-face-scan-copy {
    padding: 1.25rem;
  }
}

.landing-examples {
  padding: 2.5rem 0 3rem;
  scroll-margin-top: 4.5rem;
}

.landing-examples.hidden {
  display: none;
}

.landing-examples .example-video-grid {
  max-width: 940px;
  margin: 0 auto;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.landing-examples .example-video-card {
  background: #ffffff;
  border-color: var(--landing-border, #e8e0ee);
}

.differentiators-subcopy {
  max-width: 620px;
  margin: 0 auto 2rem;
  text-align: center;
  color: var(--landing-muted, #6b6274);
  font-size: 0.95rem;
}

.differentiator-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.1rem;
}

.differentiator-card {
  background: #ffffff;
  border: 1px solid var(--landing-border, #e8e0ee);
  border-radius: var(--landing-radius, 18px);
  box-shadow: var(--landing-shadow);
  padding: 1.4rem;
  text-align: center;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.differentiator-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 16px 32px rgba(76, 20, 92, 0.14), 0 4px 10px rgba(76, 20, 92, 0.08);
}

.differentiator-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--landing-mint-bg, #ddf4e8), var(--landing-surface, #f7f2fa));
  color: var(--landing-primary, #4c145c);
  margin-bottom: 0.75rem;
}

.differentiator-icon svg {
  width: 1.2rem;
  height: 1.2rem;
}

.differentiator-title {
  font-size: 0.98rem;
  margin: 0 0 0.35rem;
  color: var(--landing-text, #1f1a24);
}

.differentiator-desc {
  font-size: 0.85rem;
  color: var(--landing-muted, #6b6274);
  margin: 0;
}

@media (min-width: 640px) {
  .differentiator-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 900px) {
  .differentiator-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* "How to use" popup -- reuses the shared .modal-overlay backdrop
   (unmodified, also used by the confirm/admin-profile modals) but defines
   its own wider box, since .modal-box's 380px max-width is sized for short
   confirm dialogs, not a multi-step bilingual walkthrough. */
.how-to-use-box {
  background: var(--landing-bg, #ffffff);
  border: 1px solid var(--landing-border, #e8e0ee);
  border-radius: var(--landing-radius, 18px);
  box-shadow: var(--landing-shadow);
  max-width: 640px;
  width: 100%;
  max-height: 85vh;
  overflow-y: auto;
  padding: 1.75rem;
}

.how-to-use-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 1.1rem;
}

.how-to-use-header h3 {
  margin: 0;
  font-size: 1.3rem;
  color: var(--landing-text, #1f1a24);
}

.how-to-use-lang-toggle {
  display: inline-flex;
  gap: 0.2rem;
  border: 1px solid var(--landing-border, #e8e0ee);
  border-radius: 999px;
  padding: 0.2rem;
  flex: none;
}

.how-to-use-lang-button {
  padding: 0.35rem 0.85rem;
  border-radius: 999px;
  border: none;
  background: transparent;
  color: var(--landing-muted, #6b6274);
  font-weight: 700;
  font-size: 0.8rem;
  cursor: pointer;
}

.how-to-use-lang-button.active {
  background: var(--landing-primary, #4c145c);
  color: #ffffff;
}

.how-to-use-steps {
  list-style: none;
  margin: 0 0 1.25rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.how-to-use-step {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
}

.how-to-use-step-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 50%;
  background: var(--landing-surface, #f7f2fa);
  color: var(--landing-primary, #4c145c);
  flex: none;
}

.how-to-use-step-icon svg {
  width: 1rem;
  height: 1rem;
}

.how-to-use-step-title {
  margin: 0 0 0.2rem;
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--landing-text, #1f1a24);
}

.how-to-use-step-desc {
  margin: 0;
  font-size: 0.87rem;
  line-height: 1.5;
  color: var(--landing-muted, #6b6274);
}

.how-to-use-footnote {
  margin: 0 0 1.25rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--landing-border, #e8e0ee);
  font-size: 0.82rem;
  color: var(--landing-muted, #6b6274);
}

.how-to-use-close {
  margin-top: 0.15rem;
}

/* Hair Colour Recommendation & Preview -- local-only, feature-flagged (see
   web/hair-color.js). New, additively-scoped classes only; nothing above
   this rule is modified for this feature. Reuses .color-swatch, .file-drop,
   .primary-button/.secondary-button, .subtitle, .reference-label, and
   .status-error from the existing shared vocabulary above. */

#hairColorScreen select {
  display: block;
  width: 100%;
  margin: 0 0 0.9rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel);
  font: inherit;
  color: inherit;
}

#hairColorDimensionalWrap,
#hairColorSingleWrap {
  margin-bottom: 0.9rem;
  padding: 0.85rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--bg);
}

#hairColorSelectedAccentName {
  margin: 0.4rem 0 0.6rem;
}

.hair-color-step-heading {
  margin: 1.4rem 0 0.3rem;
}

.hair-color-step-heading:first-child {
  margin-top: 0;
}

.hair-color-substep-heading {
  margin: 1rem 0 0.4rem;
  font-size: 0.95rem;
}

.hair-color-mode-toggle {
  display: flex;
  gap: 0.6rem;
  margin: 0 0 0.9rem;
}

.hair-color-mode-button {
  flex: 1;
  padding: 0.65rem 0.8rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel);
  color: inherit;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.hair-color-mode-button.active {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-contrast);
}

/* .primary-button/.secondary-button (shared, used across the whole app)
   have no :disabled styling anywhere -- a disabled button here looked
   visually identical to an enabled one, which was part of a real user
   report that clicking "Generate preview" appeared to do nothing (see
   hair-color.js's hairColorSetStatus comment for the other half of that
   fix). Scoped to this screen only, not a change to the shared button
   classes themselves. */
#hairColorScreen button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

#hairColorPreviewImg {
  display: block;
  max-width: 220px;
  border-radius: var(--radius);
  margin: 0.75rem 0;
}

.hair-color-recommendation-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1rem;
  margin: 1rem 0;
}

.hair-color-recommendation-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
  padding: 1rem;
}

.hair-color-recommendation-card h4 {
  margin: 0 0 0.5rem;
}

.hair-color-shade-name {
  font-weight: 700;
  margin: 0.35rem 0;
}

.hair-color-meta-list {
  list-style: none;
  margin: 0.5rem 0;
  padding: 0;
  font-size: 0.85rem;
  color: var(--muted);
}

.hair-color-meta-list li {
  margin-bottom: 0.2rem;
}

.hair-color-select-button {
  margin-top: 0.5rem;
}

.hair-color-swatch-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 0.6rem;
  margin: 1rem 0;
}

.hair-color-catalogue-swatch {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel);
  padding: 0.5rem 0.7rem;
  cursor: pointer;
  font: inherit;
  color: inherit;
  text-align: left;
}

.hair-color-catalogue-swatch:hover {
  border-color: var(--accent);
}

.hair-color-catalogue-swatch .color-swatch {
  width: 20px;
  height: 20px;
}

/* Selected swatch is clearly highlighted; every other swatch in the same
   grid is dimmed -- real user feedback (2026-08-27): "that color should be
   visible only on screen as selected one rest all the colors should be
   covered or not highlighted so that user can understand what he has
   choosen." Applied to both the base-colour catalogue grid and the accent
   (highlight-colour) grid via the same class names. */
.hair-color-catalogue-swatch.selected {
  border-color: var(--accent);
  border-width: 2px;
  background: var(--accent-soft, var(--panel));
  box-shadow: 0 0 0 2px var(--accent);
}

.hair-color-catalogue-swatch.dimmed {
  opacity: 0.35;
  filter: grayscale(40%);
}

.hair-color-catalogue-swatch.dimmed:hover {
  opacity: 0.7;
  filter: none;
}

/* Outfit piece color picker ("Pick My Own Style" + "Also get color &
   outfit recommendations" -- real user request, 2026-09-02). Reuses the
   .hair-color-catalogue-swatch/.hair-color-swatch-grid/.color-swatch
   classes above verbatim for each piece's own grid -- only these wrapper/
   layout rules and the "system decide" card style are new. */
.outfit-color-picker-wrap {
  margin: 0.5rem 0 0.75rem;
}

.outfit-color-piece {
  margin: 0.6rem 0;
}

.outfit-color-system-decide {
  width: 100%;
  margin-bottom: 0.75rem;
  justify-content: center;
}

.outfit-color-system-decide.active {
  border-color: var(--accent);
  border-width: 2px;
  background: var(--accent-soft, var(--panel));
  box-shadow: 0 0 0 2px var(--accent);
}

#hairColorPhotoChosenWrap #hairColorChangePhotoButton {
  margin-top: 0.4rem;
}

.hair-color-intensity-options {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.6rem;
  margin: 0 0 1rem;
}

.hair-color-intensity-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  padding: 0.75rem 0.5rem;
  cursor: pointer;
  font: inherit;
  color: inherit;
  text-align: center;
}

.hair-color-intensity-option.active {
  border-color: var(--accent);
  border-width: 2px;
  box-shadow: 0 0 0 2px var(--accent);
}

.hair-color-intensity-option-label {
  font-weight: 700;
  font-size: 0.9rem;
}

.hair-color-intensity-option-hint {
  font-size: 0.75rem;
  color: var(--muted);
  line-height: 1.3;
}

/* Generic "amount" indicator -- 3 bars, filled to different levels per
   intensity option. Not a real photo preview (none is available in this
   local-only environment); an honest coverage-amount visual instead. */
.hair-color-intensity-visual {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 28px;
}

.hair-color-intensity-visual span {
  display: block;
  width: 8px;
  border-radius: 3px;
  background: var(--accent);
  opacity: 1;
}

.hair-color-intensity-visual span:nth-child(1) {
  height: 12px;
}

.hair-color-intensity-visual span:nth-child(2) {
  height: 20px;
}

.hair-color-intensity-visual span:nth-child(3) {
  height: 28px;
}

.hair-color-intensity-option[data-value="subtle"] .hair-color-intensity-visual span:nth-child(2),
.hair-color-intensity-option[data-value="subtle"] .hair-color-intensity-visual span:nth-child(3) {
  opacity: 0.18;
}

.hair-color-intensity-option[data-value="balanced"] .hair-color-intensity-visual span:nth-child(3) {
  opacity: 0.4;
}

#hairColorAvoidWrap ul {
  padding-left: 1.1rem;
  color: var(--muted);
  font-size: 0.9rem;
}

.hair-color-brief {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  padding: 1rem;
  margin-top: 1rem;
}

.hair-color-brief h3 {
  margin: 0 0 0.5rem;
}

#hairColorResultImg {
  display: block;
  max-width: 100%;
  border-radius: var(--radius);
}

#hairColorResultWrap .secondary-button {
  margin-top: 0.75rem;
}

#hairColorGeneratingWrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  padding: 3rem 1rem;
  text-align: center;
}

/* Reuses the existing .spinner/@keyframes spin (styles.css, used elsewhere
   in the app) at a larger size for this full-panel loading state. */
.hair-color-generating-spinner {
  width: 44px;
  height: 44px;
  border: 4px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

.hair-color-generating-message {
  color: var(--muted);
  font-size: 0.95rem;
  min-height: 1.4em;
}

/* #video (shared, main capture screen) is styled by id, not a generic
   selector, so the hair-colour screen's own <video> needs its own rule.
   Real user report (2026-09-01, escalated by a tester): missing
   `display: block` here (a <video> defaults to inline) let this camera
   "spread across the UI" on mobile -- an inline replaced element sizing
   itself off a not-yet-known intrinsic aspect ratio behaves very
   differently from the main capture screen's own #video, which already had
   this rule. transform: scaleX(-1) matches that same #video's mirrored
   live-preview fix for the same reason -- consistent behavior across every
   camera in the app. */
#hairColorVideo {
  width: 100%;
  border-radius: var(--radius);
  background: #000;
  display: block;
  transform: scaleX(-1);
}

/* "Style & Colour" -- combined hairstyle + hair colour preview (web/
   style-color.js). Reuses .hair-color-catalogue-swatch/.hair-color-swatch-
   grid/.hair-color-step-heading/.hair-color-substep-heading/.hair-color-
   intensity- and video-styled-by-id patterns from the standalone hair-colour
   screen above -- only genuinely new rules are added here.
   Bug found investigating a tester-escalated report (2026-09-01): this
   comment used to contain a star followed immediately by a slash, which
   closes a CSS comment early. That silently dropped every rule up to the
   next accidental recovery point, including #styleColorVideo just below --
   so that camera had no sizing CSS at all, hence "spreading across the
   UI". Pre-existing bug, unrelated to this session's other camera work. */

/* #video (shared, main capture screen) is styled by id -- this screen's own
   <video> needs its own rule too. Same fix as #hairColorVideo above (real
   user report, 2026-09-01): missing `display: block` let this camera
   spread across the UI on mobile, and transform: scaleX(-1) matches the
   main camera's mirrored live preview. */
#styleColorVideo {
  width: 100%;
  border-radius: var(--radius);
  background: #000;
  display: block;
  transform: scaleX(-1);
}

#styleColorPreviewImg {
  display: block;
  max-width: 220px;
  border-radius: var(--radius);
  margin: 0.75rem 0;
}

#styleColorScreen button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.style-color-style-card {
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.4rem;
  padding: 0.6rem;
}

.style-color-style-card img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 6px;
}

.style-color-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
  margin: 1rem 0;
}

@media (max-width: 640px) {
  .style-color-columns {
    grid-template-columns: 1fr;
  }
}

/* Real user feedback (2026-08-27): these two panels read as plain white,
   not matching the studio's branded look. Same soft accent-tinted gradient
   the hero panel already uses, just more subtle -- scoped to this screen's
   own class, not the shared .hair-color-substep-heading used elsewhere. */
.style-color-column {
  padding: 1.25rem 1rem 1.5rem;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: linear-gradient(165deg, color-mix(in srgb, var(--accent) 9%, var(--panel)) 0%, var(--panel) 70%);
}

.style-color-column .hair-color-substep-heading {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.style-color-column .hair-color-swatch-grid {
  grid-template-columns: repeat(2, 1fr);
}

#styleColorTechniqueSelect {
  margin-top: 0.4rem;
}

/* Result reveal -- real user request (2026-08-27): the result should feel
   like a real "moment" rather than an image just appearing. A headline
   fades/slides in first, then the result card scales/fades in a beat
   later with a one-shot diagonal shimmer sweep. Classes are added (with a
   forced reflow) by style-color.js's renderStyleColorResult, so the
   animation reliably restarts on every generation, not just the first. */
.style-color-reveal-headline {
  font-weight: 700;
  font-size: 1.15rem;
  text-align: center;
  margin: 0 0 0.75rem;
  opacity: 0;
}

.style-color-reveal-headline-play {
  animation: styleColorHeadlineReveal 0.5s ease-out forwards;
}

@keyframes styleColorHeadlineReveal {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.style-color-reveal-play {
  position: relative;
  animation: styleColorImageReveal 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.15s both;
}

@keyframes styleColorImageReveal {
  from {
    opacity: 0;
    transform: scale(0.92) translateY(12px);
  }
  to {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}

.style-color-reveal-play::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 40%, rgba(255, 255, 255, 0.55) 50%, transparent 60%);
  transform: translateX(-120%);
  animation: styleColorShimmer 1s ease-out 0.35s 1;
  pointer-events: none;
}

@keyframes styleColorShimmer {
  to {
    transform: translateX(120%);
  }
}

/* Premium "colour studio" polish -- real user feedback (2026-08-27): the
   screen worked but needed to look and feel like a high-end salon, not a
   plain form. Scoped entirely to #styleColorScreen's own new elements --
   no shared/global class is touched. */

.style-color-hero {
  text-align: center;
  padding: 1.75rem 1.25rem 1.5rem;
  margin: 0.5rem 0 1.25rem;
  border-radius: var(--radius-lg);
  background: linear-gradient(
    160deg,
    color-mix(in srgb, var(--accent) 12%, var(--panel)) 0%,
    var(--panel) 70%
  );
  border: 1px solid var(--border);
}

.style-color-eyebrow {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 0.6rem;
}

.style-color-hero-title {
  margin: 0 0 0.5rem;
  font-size: 1.65rem;
}

.style-color-hero-subtitle {
  margin: 0 auto;
  max-width: 34rem;
  color: var(--muted);
  font-size: 0.95rem;
  line-height: 1.5;
}

.style-color-identity-heading {
  font-weight: 700;
  font-size: 1rem;
  margin: 1.25rem 0 0.75rem;
}

.style-color-identity-options {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.75rem;
  margin: 0 0 1.25rem;
  max-width: 24rem;
}

.style-color-identity-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  padding: 1rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--panel);
  cursor: pointer;
  font: inherit;
  font-weight: 600;
  color: inherit;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.style-color-identity-option:hover {
  border-color: var(--accent);
  transform: translateY(-1px);
}

.style-color-identity-option.active {
  border-color: var(--accent);
  border-width: 2px;
  box-shadow: var(--shadow-md);
  background: color-mix(in srgb, var(--accent) 8%, var(--panel));
}

.style-color-identity-icon {
  font-size: 1.6rem;
  line-height: 1;
}

.style-color-step-heading-wrap {
  margin: 1.75rem 0 0.5rem;
}

.style-color-step-heading-wrap:first-child {
  margin-top: 0;
}

.style-color-step-eyebrow {
  display: block;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 0.15rem;
}

.style-color-step-title {
  margin: 0;
  font-size: 1.2rem;
}

/* A little salon-boutique lift on the swatch/style cards within this
   screen only -- soft hover motion + shadow, on top of the shared
   .hair-color-catalogue-swatch selection/dimming behavior. */
#styleColorScreen .hair-color-catalogue-swatch,
#styleColorScreen .style-color-style-card {
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

#styleColorScreen .hair-color-catalogue-swatch:hover,
#styleColorScreen .style-color-style-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-sm, 0 2px 6px rgba(16, 24, 40, 0.08));
}

#styleColorGenerateButton {
  font-size: 1.02rem;
  padding: 0.9rem 1.25rem;
}

/* Credit structure popup (header link, static pricing table) -- reuses the
   shared .modal-overlay/.modal-box/.modal-actions pattern. */
.credit-structure-box {
  max-width: 460px;
}

.contact-modal-box {
  max-width: 380px;
}

.contact-modal-email {
  margin: 0 0 1.25rem;
  font-weight: 700;
  font-size: 1.05rem;
}

.contact-modal-email a {
  color: var(--accent);
}

.contact-modal-box .modal-actions .primary-button {
  text-align: center;
  text-decoration: none;
}

.credit-structure-table {
  width: 100%;
  border-collapse: collapse;
  margin: 1rem 0;
  font-size: 0.9rem;
}

.credit-structure-table td {
  padding: 0.6rem 0.4rem;
  border-bottom: 1px solid var(--border);
}

.credit-structure-table tr:last-child td {
  border-bottom: none;
}

.credit-structure-price {
  text-align: right;
  font-weight: 700;
  color: var(--accent);
  white-space: nowrap;
}

.credit-structure-footnote {
  margin-top: -0.25rem;
}

/* --- Pay by QR ---------------------------------------------------------- */
/* The escape hatch for phones with no UPI app installed (see index.html's
   #qrPayModal comment). Deliberately built from the same tokens as the rest
   of the app rather than a bespoke palette, so it reads as part of the
   product and not a bolted-on payment widget. */

.qr-pay-link-button {
  display: block;
  width: 100%;
  margin-top: 0.6rem;
  padding: 0.5rem 0.25rem;
  background: none;
  border: none;
  color: var(--muted);
  font-size: 0.82rem;
  font-weight: 600;
  line-height: 1.35;
  cursor: pointer;
  border-radius: var(--radius);
  transition: color 0.15s ease, background-color 0.15s ease;
}

.qr-pay-link-button:hover,
.qr-pay-link-button:focus-visible {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}

.qr-pay-box {
  /* Wider than the default .modal-box (380px): this code has to be scanned
     by a *second* phone held some distance away, so its physical size on
     screen is a functional requirement, not a styling preference. */
  max-width: 440px;
  text-align: center;
}

.qr-pay-subtitle {
  color: var(--muted);
  font-size: 0.88rem;
  margin: 0.35rem 0 1.1rem;
  min-height: 1.2em; /* stops the dialog jumping as this text swaps */
}

.qr-pay-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.85rem;
}

.qr-pay-frame {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Fill the dialog, capped so it stays sensible on a desktop. Fixed px
     here was the bug: a 220px code is hard to scan from another phone. */
  width: 100%;
  max-width: 340px;
  aspect-ratio: 1 / 1;
  padding: 0.75rem;
  /* Always white behind the code: a QR on a dark panel will not scan. */
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.qr-pay-image {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  /* Keep module edges hard when the source is upscaled -- smoothing a QR
     blurs the finder patterns and makes scanners work harder. */
  image-rendering: pixelated;
  transition: opacity 0.2s ease, filter 0.2s ease;
}

.qr-pay-stage.is-loading .qr-pay-image {
  opacity: 0;
}

.qr-pay-spinner {
  position: absolute;
  width: 30px;
  height: 30px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  opacity: 0;
  animation: qr-pay-spin 0.8s linear infinite;
}

.qr-pay-stage.is-loading .qr-pay-spinner {
  opacity: 1;
}

@keyframes qr-pay-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .qr-pay-spinner { animation-duration: 2.4s; }
}

/* Expired and paid both stop inviting a scan -- greyed out for expired,
   de-emphasised for paid so the success message carries the moment. */
.qr-pay-stage.is-expired .qr-pay-image {
  filter: grayscale(1);
  opacity: 0.35;
}

.qr-pay-stage.is-paid .qr-pay-frame {
  opacity: 0.45;
}

.qr-pay-amount {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 0.5rem;
}

.qr-pay-rupees {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--text);
  line-height: 1;
}

.qr-pay-credits {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
}

.qr-pay-note {
  font-size: 0.76rem;
  color: var(--muted);
  margin: 0;
  line-height: 1.45;
  max-width: 30ch;
}

.qr-pay-status {
  font-size: 0.84rem;
  font-weight: 600;
  margin-top: 1rem;
  min-height: 1.3em; /* reserve the line so state changes do not reflow */
}

.qr-pay-status.is-waiting { color: var(--muted); }
.qr-pay-status.is-success { color: #1a7f4b; }
.qr-pay-status.is-error   { color: #b4232c; }

/* Small phones: give the code the dialog's padding rather than shrinking
   it -- scannability is the whole point of this screen. */
@media (max-width: 420px) {
  .qr-pay-box {
    padding: 1.1rem;
  }

  .qr-pay-note {
    max-width: none;
  }
}

/* --- Mobile hero: proof above the fold ----------------------------------
   Real user instruction, 2026-09-04: "still i can't see the photos and i
   have to scroll down". On a phone the hero stacks copy-then-visual, and the
   copy block runs badge -> headline -> subcopy -> CTAs -> gift note -> three
   value points -> trust note before the before/after cards ever start. On a
   412px-wide phone (S24 Ultra, and most Vivo/Oppo/Motorola devices) that is
   roughly a full screen of text, so the proof images -- the whole reason the
   hero exists -- were always below the fold.

   Lowering the two-column breakpoint does not help here: two columns need
   ~700px, which only a Fold-class device reaches. The fix has to work at
   360-430px, where the great majority of visitors are.

   So on narrow screens the copy block is promoted into the hero grid with
   display:contents and the children are reordered, putting the showcase
   directly under the CTAs and pushing the secondary reassurance text below
   it. .landing-hero-copy is a plain <div> with no semantics of its own, so
   promoting it away costs nothing structurally. */
@media (max-width: 699px) {
  .landing-hero-copy {
    display: contents;
  }

  /* .landing-hero-copy's own 1.15rem gap disappears with display:contents,
     so the grid has to supply the rhythm for the promoted children. */
  .landing-hero-grid {
    row-gap: 1rem;
    /* Deliberately NO entrance animation here. .landing-hero-copy carried
       one, and display:contents removes its box along with it -- but moving
       that animation onto the grid made the whole hero disappear for anyone
       with Reduce Motion on: heroRise starts at opacity:0, the
       prefers-reduced-motion override only cancels it for
       .landing-hero-copy/.hero-showcase, and animation-fill-mode:both then
       pinned the grid at the invisible `from` state. Losing a fade-in is a
       fair trade for a hero that always renders. */
    /* The base rule centres items vertically for the two-column desktop
       layout. Promoted children each occupy their own row here, so centring
       is meaningless at best and shifts single-line items at worst -- and
       .landing-hero-copy's own `align-items: flex-start` is gone with
       display:contents, so this has to be restated rather than inherited. */
    align-items: start;
  }

  .hero-badge          { order: 1; justify-self: start; }
  .landing-headline    { order: 2; }
  .landing-subcopy     { order: 3; }
  .hero-actions        { order: 4; }
  .landing-hero-visual { order: 5; }
  .hero-free-note      { order: 6; }
  .hero-value-points   { order: 7; }
  .hero-trust-note     { order: 8; }

  /* Tighter, but never below ~31px -- this is still the page's core message
     and must not shrink into body copy. */
  .landing-headline {
    font-size: clamp(1.95rem, 1.1rem + 3.6vw, 2.4rem);
  }

  .landing-subcopy {
    font-size: 1rem;
    line-height: 1.5;
  }
}

/* The just-captured still, laid over the live video the instant the shutter
   fires (app.js's showCameraFreezeFrame). Covers the whole viewport so the
   moving camera feed is completely hidden during the multi-second thorough
   check -- before this, the camera kept running and the capture was
   invisible to the user. object-fit: cover matches how the <video> itself
   fills the same box, so the frozen frame lines up with what they were just
   looking at rather than jumping to a different crop. */
.camera-freeze-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 2;
}

/* The decision point after a captured photo fails the thorough check: the
   camera is already off and the frame frozen above, so this explains what
   went wrong and offers the way forward (app.js's showCaptureRejected).
   Replaces the previous silent re-arm, which gave the user nothing to read
   and nothing to press. */
.capture-review {
  margin-top: 12px;
  text-align: center;
}
.capture-review-message {
  margin: 0 0 10px;
  font-size: 0.95rem;
  line-height: 1.45;
  color: #b4231f;
}

/* Retake control directly beneath the preview image. The long-standing
   #retakePhotoButton sits at the very bottom of <main>, below every
   section, so on the preview screen it fell well under the fold and people
   did not realise they could just take another photo. This one is attached
   to the image it refers to. */
.preview-actions {
  display: flex;
  justify-content: center;
  margin-top: 10px;
}

/* --- Progress overlay on the chosen photo ---------------------------------
   Real user report (2026-09-07): after picking a photo the app runs a
   thorough readiness check and then the full recommendation pipeline, and
   showed nothing while it did -- "it shows the photo but nothing is
   happening". The status line at the bottom of the page was easy to miss,
   and on a phone often off-screen entirely, so the feedback now sits
   directly on the photo it refers to. */
.preview-stage {
  position: relative;
  display: inline-block;
  max-width: 100%;
}

.photo-progress {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 1rem;
  border-radius: var(--radius);
  text-align: center;
  /* Dark enough to read white text on any photo, sheer enough that the
     person can still see their own picture underneath -- the point is
     "we're working on THIS", not a loading screen that hides it. */
  background: linear-gradient(180deg, rgba(26, 11, 61, 0.55) 0%, rgba(26, 11, 61, 0.78) 100%);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  color: #fff;
  animation: photo-progress-fade 0.25s ease-out;
}

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

.photo-progress-spinner {
  width: 74px;
  max-width: 45%;
}

/* The shared scissors loader is stroked in the accent colour, which would
   disappear against this dark scrim -- restate it in white here only. */
.photo-progress-spinner .stylist-spinner-strand,
.photo-progress-spinner .stylist-spinner-scissors line,
.photo-progress-spinner .stylist-spinner-pivot {
  stroke: rgba(255, 255, 255, 0.92);
}

.photo-progress-message {
  margin: 0;
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.35;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}

@media (prefers-reduced-motion: reduce) {
  .photo-progress {
    animation: none;
  }
}

/* Post-capture confirmation: same overlay, but nothing is loading, so the
   spinner is hidden and the scrim lightens to a success tone. Auto-dismissed
   by showPhotoCaptured (app.js) after a couple of seconds. */
.photo-progress.is-done {
  background: linear-gradient(180deg, rgba(18, 102, 74, 0.55) 0%, rgba(18, 102, 74, 0.8) 100%);
}

.photo-progress.is-done .photo-progress-spinner {
  display: none;
}

/* Preview of the shareable before/after card (app.js's showShareCardPreview).
   Capped by viewport height so a 1080x1080 card can't push the modal's own
   action buttons off-screen on a phone -- the whole point of the preview is
   seeing the image AND being able to tap Share. */
.share-card-preview {
  display: block;
  width: 100%;
  max-height: 55vh;
  object-fit: contain;
  border-radius: 12px;
  margin-top: 12px;
}

/* Preview of the shareable before/after card (app.js's showShareCardPreview).
   Capped by viewport height so a 1080x1350 card cannot push the modal's own
   action buttons off-screen on a phone -- the point of the preview is seeing
   the image AND being able to tap Share. */
.share-card-preview {
  display: block;
  width: 100%;
  max-height: 55vh;
  object-fit: contain;
  border-radius: 12px;
  margin-top: 12px;
}

/* --- "Help Me Choose" public poll page ------------------------------------
   A standalone page opened from a WhatsApp link, often by someone who has
   never seen this product. Mobile-first, no navigation, no sign-in prompt:
   one question, two or three looks, one tap.

   Deliberately LIGHT. The first version put the wordmark on a deep purple
   gradient, and the logo is itself dark purple (its opaque pixels average
   about rgb(100, 37, 125)) -- so the brand mark sank into the background
   and the whole page read as muddy. A light surface is also what the app
   itself uses, so a first-time visitor sees the same product they will land
   on if they tap through. */
.poll-body {
  margin: 0;
  min-height: 100vh;
  background:
    radial-gradient(90% 55% at 50% 0%, rgba(217, 70, 239, 0.16) 0%, transparent 70%),
    radial-gradient(80% 50% at 100% 100%, rgba(242, 113, 41, 0.14) 0%, transparent 70%),
    #faf7fd;
  color: #21152a;
}

.poll-shell {
  max-width: 460px;
  margin: 0 auto;
  padding: 1.5rem 1.1rem 3rem;
}

.poll-header {
  text-align: center;
  padding: 0.25rem 0 1.25rem;
}

.poll-wordmark {
  height: 30px;
  width: auto;
}

.poll-question {
  margin: 0 0 0.35rem;
  font-size: clamp(1.4rem, 5.5vw, 1.8rem);
  line-height: 1.22;
  letter-spacing: -0.01em;
  text-align: center;
}

.poll-note {
  margin: 0.35rem 0;
  font-size: 0.88rem;
  color: #685c70;
  text-align: center;
}

.poll-image {
  display: block;
  width: 100%;
  border-radius: 20px;
  margin: 1.1rem 0 1.25rem;
  box-shadow: 0 12px 34px rgba(72, 6, 127, 0.16);
}

.poll-options {
  display: grid;
  gap: 0.65rem;
}

/* Large tap targets -- this is used one-handed, on a phone, usually while
   scrolling a group chat. */
.poll-option {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-height: 60px;
  padding: 0.9rem 1.25rem;
  border: 2px solid #e8e0ee;
  border-radius: 999px;
  background: #fff;
  color: #21152a;
  font: inherit;
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(72, 6, 127, 0.06);
  transition: border-color 0.15s ease, transform 0.06s ease, box-shadow 0.15s ease;
}

.poll-option:hover:not(:disabled) {
  border-color: #a50f9f;
  box-shadow: 0 6px 18px rgba(165, 15, 159, 0.16);
}

.poll-option:active:not(:disabled) {
  transform: translateY(1px);
}

.poll-option:focus-visible {
  outline: 3px solid #5c0ba6;
  outline-offset: 2px;
}

.poll-option:disabled {
  cursor: default;
}

.poll-option-chosen {
  border-color: #5c0ba6;
  background: #f7f2ff;
}

.poll-option-label,
.poll-option-tally {
  position: relative;
  z-index: 1;
}

.poll-option-tally {
  font-variant-numeric: tabular-nums;
  color: #5c0ba6;
}

/* The result bar sits behind the label rather than resizing the button, so
   rows never reflow as votes arrive. */
.poll-option-bar {
  position: absolute;
  inset: 0 auto 0 0;
  background: linear-gradient(90deg, rgba(92, 11, 166, 0.14), rgba(165, 15, 159, 0.18));
  z-index: 0;
  transition: width 0.45s ease;
}

.poll-card {
  background: #fff;
  border: 1px solid #e8e0ee;
  border-radius: 20px;
  padding: 1.5rem 1.25rem;
  text-align: center;
  box-shadow: 0 10px 30px rgba(72, 6, 127, 0.08);
}

.poll-thanks {
  margin-top: 1.35rem;
  padding: 1.25rem;
  border-radius: 20px;
  background: linear-gradient(135deg, #5c0ba6 0%, #a50f9f 100%);
  color: #fff;
  text-align: center;
  box-shadow: 0 10px 28px rgba(92, 11, 166, 0.28);
}

.poll-thanks .poll-note {
  color: rgba(255, 255, 255, 0.86);
}

.poll-thanks-title {
  margin: 0 0 0.2rem;
  font-size: 1.1rem;
  font-weight: 700;
}

.poll-cta {
  display: inline-block;
  margin-top: 0.7rem;
  padding: 0.85rem 1.6rem;
  border-radius: 999px;
  background: #fff;
  color: #5c0ba6;
  font-weight: 700;
  text-decoration: none;
}

.poll-footer {
  margin-top: 1.75rem;
}

.poll-skeleton {
  border-radius: 16px;
  background: linear-gradient(90deg, #efe7f6 25%, #f8f4fc 37%, #efe7f6 63%);
  background-size: 400% 100%;
  animation: poll-shimmer 1.4s ease infinite;
}

.poll-skeleton-title {
  height: 26px;
  margin-bottom: 1rem;
}

.poll-skeleton-image {
  aspect-ratio: 4 / 5;
}

@keyframes poll-shimmer {
  from { background-position: 100% 50%; }
  to { background-position: 0 50%; }
}

@media (prefers-reduced-motion: reduce) {
  .poll-skeleton { animation: none; }
  .poll-option-bar { transition: none; }
}

/* --- "Help Me Choose" in-app controls ------------------------------------ */
.poll-entry {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1rem;
}

.poll-pick-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* A picked look needs to be obvious at a glance on a phone, without moving
   the card and reflowing the grid -- hence a ring rather than a border. */
.recommendation-card.poll-picked {
  outline: 3px solid var(--hero-magenta, #a50f9f);
  outline-offset: 2px;
  border-radius: var(--radius-lg);
}

.poll-share-url {
  word-break: break-all;
  font-weight: 600;
  background: rgba(0, 0, 0, 0.06);
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
}

.poll-result-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 1rem 1.15rem;
  margin-bottom: 0.9rem;
  background: var(--panel);
}

.poll-result-row {
  display: grid;
  grid-template-columns: 5rem 1fr 2.5rem;
  align-items: center;
  gap: 0.6rem;
  margin: 0.4rem 0;
}

.poll-result-label {
  font-weight: 700;
  font-size: 0.9rem;
}

.poll-result-bar {
  display: block;
  height: 10px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}

.poll-result-bar > span {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--hero-magenta, #a50f9f);
  transition: width 0.4s ease;
}

.poll-result-count {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

@media (prefers-reduced-motion: reduce) {
  .poll-result-bar > span { transition: none; }
}

/* --- poll: recipients, links and results ---------------------------------- */
.poll-recipients {
  display: grid;
  gap: 8px;
  margin-bottom: 10px;
}
.poll-recipient-input {
  width: 100%;
}
.poll-invite-list {
  display: grid;
  gap: 10px;
  margin: 14px 0 4px;
}
.poll-invite-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 12px;
  background: rgba(124, 58, 237, 0.07);
}
.poll-invite-name {
  flex: 1;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The winning row is emphasised, but never so much that a 2-1 lead looks
   like a verdict -- the recommendation line carries that judgement. */
.poll-recommendation {
  margin-top: 12px;
  padding: 12px 14px;
  border-radius: 12px;
  background: linear-gradient(135deg, rgba(124, 58, 237, 0.12), rgba(236, 72, 153, 0.1));
  font-weight: 600;
  line-height: 1.45;
}
.poll-anon-note {
  margin-top: 8px;
  font-size: 0.78rem;
  opacity: 0.65;
}

/* --- poll results: the looks are the result ------------------------------- */
.poll-result-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.poll-result-head h3 {
  margin: 0;
  flex: 1;
}
.poll-status-chip {
  flex-shrink: 0;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  background: rgba(120, 120, 130, 0.16);
  color: #555;
}
.poll-status-open {
  background: rgba(16, 185, 129, 0.16);
  color: #0f7a5a;
}

/* Reply progress: one dot per person invited. */
.poll-progress {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 10px 0 16px;
}
.poll-dots {
  display: inline-flex;
  gap: 5px;
}
.poll-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: rgba(124, 58, 237, 0.18);
}
.poll-dot-filled {
  background: linear-gradient(135deg, #7c3aed, #ec4899);
}
.poll-progress-text {
  font-size: 0.82rem;
  opacity: 0.7;
}

/* Capped and centred. Without a max-width, two looks in a 3:4 frame stretch
   to the full card width and end up ~780px tall on a desktop -- you cannot
   compare two faces you have to scroll between. */
.poll-looks {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  max-width: 460px;
  margin: 0 auto;
}
.poll-looks-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  max-width: 620px;
}
.poll-look {
  position: relative;
  margin: 0;
  text-align: center;
}
.poll-look-frame {
  position: relative;
  overflow: hidden;
  border-radius: 16px;
  aspect-ratio: 3 / 4;
  background: rgba(124, 58, 237, 0.08);
  box-shadow: 0 6px 18px rgba(80, 20, 120, 0.12);
}
.poll-look-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* The share bar sits BELOW the photo, never over it. Tinting the leading
   look made the one image you most want to judge the hardest to see --
   exactly backwards for a feature about choosing between faces. */
.poll-look-track {
  display: block;
  height: 6px;
  margin: 2px 0 5px;
  border-radius: 999px;
  background: rgba(124, 58, 237, 0.14);
  overflow: hidden;
}
.poll-look-fill {
  display: block;
  width: 0;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, #7c3aed, #ec4899);
  transition: width 700ms cubic-bezier(0.22, 1, 0.36, 1);
}
.poll-look-winner .poll-look-frame {
  outline: 3px solid #ec4899;
  outline-offset: 2px;
}
.poll-look-crown {
  position: absolute;
  top: -8px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  font-size: 1.3rem;
  line-height: 1;
  filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.3));
}
.poll-look figcaption {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-top: 8px;
}
.poll-look-share {
  font-size: 1.5rem;
  font-weight: 800;
  line-height: 1.1;
  background: linear-gradient(135deg, #7c3aed, #ec4899);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.poll-look-share small {
  font-size: 0.85rem;
  font-weight: 700;
}
.poll-look-votes {
  font-size: 0.78rem;
  opacity: 0.7;
}
.poll-look-label {
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  opacity: 0.55;
}

/* Three looks side by side get tight on a phone; two stay comfortable. */
@media (max-width: 520px) {
  .poll-looks-3 {
    gap: 8px;
  }
  .poll-look-share {
    font-size: 1.2rem;
  }
  .poll-look-votes,
  .poll-look-label {
    font-size: 0.68rem;
  }
}

.poll-result-actions {
  display: flex;
  gap: 18px;
  margin-top: 14px;
}

@media (prefers-reduced-motion: reduce) {
  .poll-look-fill {
    transition: none;
  }
}

/* --- earlier polls: collapsed by default ---------------------------------- */
.poll-past-heading {
  margin: 26px 0 10px;
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.55;
}
.poll-past {
  border-radius: 14px;
  background: rgba(124, 58, 237, 0.06);
  margin-bottom: 8px;
  overflow: hidden;
}
.poll-past-summary {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 12px 14px;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.poll-past-summary:hover {
  background: rgba(124, 58, 237, 0.09);
}
.poll-past-question {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}
.poll-past-outcome {
  font-size: 0.8rem;
  font-weight: 700;
  color: #7c3aed;
  white-space: nowrap;
}
.poll-past-meta {
  font-size: 0.75rem;
  opacity: 0.6;
  white-space: nowrap;
}
.poll-past-chevron {
  transition: transform 200ms ease;
  opacity: 0.5;
}
.poll-past-open .poll-past-chevron {
  transform: rotate(180deg);
}
.poll-past-body {
  padding: 0 10px 10px;
}
/* On a phone the meta column is the first thing to go. */
@media (max-width: 520px) {
  .poll-past-summary {
    grid-template-columns: 1fr auto auto;
  }
  .poll-past-meta {
    display: none;
  }
}

/* --- shared live results: donut + bars ------------------------------------ */
.chart {
  display: flex;
  align-items: center;
  gap: 20px;
  margin: 18px 0 6px;
  padding: 18px 16px;
  border-radius: 18px;
  background: #fff;
  box-shadow: 0 8px 24px rgba(80, 20, 120, 0.1);
}
.chart-donut {
  position: relative;
  flex: 0 0 132px;
  width: 132px;
  height: 132px;
}
.chart-donut svg {
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}
.chart-donut-track {
  fill: none;
  stroke: rgba(124, 58, 237, 0.12);
  stroke-width: 14;
}
.chart-donut-segment {
  fill: none;
  stroke-width: 14;
  stroke-linecap: butt;
  transition: stroke-dasharray 800ms cubic-bezier(0.22, 1, 0.36, 1);
}
.chart-donut-centre {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 1.1;
}
.chart-donut-centre span {
  font-size: 1.9rem;
  font-weight: 800;
  color: #3d0a5e;
}
.chart-donut-centre small {
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  opacity: 0.55;
}
.chart-bars {
  flex: 1;
  display: grid;
  gap: 10px;
  min-width: 0;
}
.chart-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas: "label track value" "count count count";
  align-items: center;
  gap: 4px 10px;
}
.chart-row-label {
  grid-area: label;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #3d0a5e;
  opacity: 0.75;
  white-space: nowrap;
}
.chart-row-lead .chart-row-label {
  opacity: 1;
}
.chart-row-track {
  grid-area: track;
  height: 12px;
  border-radius: 999px;
  background: rgba(124, 58, 237, 0.1);
  overflow: hidden;
}
.chart-row-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  transition: width 800ms cubic-bezier(0.22, 1, 0.36, 1);
}
.chart-row-value {
  grid-area: value;
  font-size: 1rem;
  font-weight: 800;
  color: #3d0a5e;
  min-width: 44px;
  text-align: right;
}
.chart-row-count {
  grid-area: count;
  font-size: 0.72rem;
  opacity: 0.55;
}
.chart-verdict {
  margin-top: 14px;
  padding: 14px 16px;
  border-radius: 14px;
  background: linear-gradient(135deg, rgba(124, 58, 237, 0.12), rgba(236, 72, 153, 0.1));
  font-weight: 600;
  line-height: 1.45;
  color: #3d0a5e;
}

/* Stack on a phone: a 132px donut beside bars leaves the bars unreadable. */
@media (max-width: 560px) {
  .chart {
    flex-direction: column;
    gap: 16px;
  }
  .chart-bars {
    width: 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .chart-donut-segment,
  .chart-row-fill {
    transition: none;
  }
}

/* Explains why the vote buttons are unavailable. Sits ABOVE them: below the
   options it fell off the bottom of a phone screen and recipients just saw
   buttons that would not respond. */
.poll-blocked {
  margin: 0 0 14px;
  padding: 13px 15px;
  border-radius: 14px;
  background: rgba(245, 158, 11, 0.14);
  border: 1px solid rgba(245, 158, 11, 0.3);
  color: #7a4a06;
  font-weight: 600;
  line-height: 1.45;
  text-align: left;
}
/* Disabled options should look disabled, not merely unresponsive. */
.poll-option:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Each invite shows its own URL: the links differ only in a random tail, and
   a real send went to the wrong person because they looked identical. */
.poll-invite-row-block {
  display: block;
}
.poll-invite-head {
  display: flex;
  align-items: center;
  gap: 10px;
}
.poll-invite-url {
  display: block;
  margin-top: 6px;
  padding: 6px 8px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.7);
  font-size: 0.7rem;
  line-height: 1.35;
  word-break: break-all;
  color: #4a2472;
  user-select: all;
}

/* Shown in the share preview when the generation failed the quality gate.
   The button used to be hidden entirely in this case, which removed the
   feature on ~54% of generations and read as a bug. */
.share-card-warning {
  margin: 0 0 10px;
  padding: 11px 13px;
  border-radius: 12px;
  background: rgba(245, 158, 11, 0.14);
  border: 1px solid rgba(245, 158, 11, 0.3);
  color: #7a4a06;
  font-weight: 600;
  font-size: 0.86rem;
  line-height: 1.45;
  text-align: left;
}

/* --- signed-in header: WhatsApp-style bar + tabs --------------------------
   Written mobile-first: the old single row of ten dot-separated links wrapped
   into an unreadable block on a phone browser, which is where users hit it
   (real report, 2026-09-08). Identity on one line, two real destinations as
   tabs, everything else behind one button. */
#loggedInStatus:not(.hidden) {
  display: block;
  width: 100%;
}

.app-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
}
.app-bar-identity {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-size: 0.95rem;
}
.app-bar-identity strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 42vw;
}
.app-bar-more {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 7px 13px;
  border: 1px solid rgba(124, 58, 237, 0.28);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.75);
  color: #5b21b6;
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}
.app-bar-more:hover {
  background: #fff;
}
.app-bar-more-caret {
  font-size: 0.7rem;
  transition: transform 180ms ease;
}
.app-bar-more-open .app-bar-more-caret {
  transform: rotate(180deg);
}

/* Tabs. Underline-on-active, like WhatsApp's. */
.app-tabs {
  display: flex;
  gap: 4px;
  width: 100%;
  margin-top: 10px;
  border-bottom: 1px solid rgba(124, 58, 237, 0.16);
}
.app-tab {
  flex: 1;
  padding: 9px 4px 10px;
  border-bottom: 3px solid transparent;
  color: #6b5b7f;
  font-size: 0.86rem;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
}
.app-tab:hover {
  color: #5b21b6;
}
.app-tab-active {
  color: #5b21b6;
  border-bottom-color: #7c3aed;
}

/* Overflow menu. Full-width sheet on a phone; anchored card on wider screens. */
.more-menu {
  position: absolute;
  right: 12px;
  left: 12px;
  z-index: 60;
  display: flex;
  flex-direction: column;
  margin-top: 6px;
  padding: 6px;
  border: 1px solid rgba(124, 58, 237, 0.16);
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 14px 34px rgba(60, 12, 92, 0.18);
}
.more-menu.hidden {
  display: none;
}
.more-menu-item {
  padding: 12px 14px;
  border-radius: 10px;
  color: #3d0a5e;
  font-size: 0.92rem;
  font-weight: 600;
  text-decoration: none;
}
.more-menu-item:hover {
  background: rgba(124, 58, 237, 0.08);
}
.more-menu-item.hidden {
  display: none;
}
.more-menu-item-quiet {
  color: #8a5a5a;
}
.more-menu-divider {
  height: 1px;
  margin: 6px 10px;
  background: rgba(124, 58, 237, 0.14);
}

@media (min-width: 700px) {
  .more-menu {
    left: auto;
    min-width: 232px;
  }
  .app-tabs {
    gap: 6px;
    justify-content: flex-start;
  }
  .app-tab {
    flex: 0 0 auto;
    padding: 9px 18px 10px;
  }
  .app-bar-identity strong {
    max-width: none;
  }
}

/* The overflow menu is absolutely positioned; this is its containing block.
   Also lets the signed-in bar use the full header width instead of sitting
   inline like the logged-out "Log in" links do. */
.auth-status {
  position: relative;
}
.auth-status:has(#loggedInStatus:not(.hidden)) {
  width: 100%;
}

/* Anchor the overflow menu to the signed-in bar so its right edge lines up
   with the "Explore more" button that opened it. */
#loggedInStatus {
  position: relative;
}
@media (min-width: 700px) {
  .more-menu {
    right: 0;
  }
}

/* A salon's tabs carry longer names than an individual's. The short form is
   hidden until the phone breakpoint below, where the full one cannot fit. */
.app-tab-short {
  display: none;
}

/* Four tabs (Studio / Buy credits / My Photos / Polls) on a narrow phone.
   Shrinks type and padding rather than wrapping to a second row -- a tab bar
   that wraps stops reading as a tab bar. */
@media (max-width: 430px) {
  .app-tab-full {
    display: none;
  }
  .app-tab-short {
    display: inline;
  }
  .app-tab {
    padding: 9px 2px 10px;
    font-size: 0.78rem;
    letter-spacing: -0.01em;
  }
  .app-bar-more {
    padding: 6px 10px;
    font-size: 0.78rem;
  }
  .app-bar-identity {
    font-size: 0.88rem;
  }
}

/* --- Show Your Barber Mode (web/barber-mode.js) -----------------------------
   A card handed across a salon counter under bright lights, often to a
   barber who won't read small English text -- so it's deliberately
   theme-locked to a white, high-contrast sheet (never follows dark mode)
   with big type and big tap targets. */
.barber-mode-button {
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 700;
}

body.barber-mode-open {
  overflow: hidden;
}

.barber-mode {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  flex-direction: column;
  background: #ffffff;
  color: #13161c;
  color-scheme: only light;
}

.barber-mode-bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0.9rem;
  background: #4c145c;
  color: #ffffff;
  flex-shrink: 0;
}

.barber-mode-brand {
  font-weight: 800;
  font-size: 1rem;
}

.barber-mode-wake {
  font-size: 0.75rem;
  background: rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  padding: 0.15rem 0.55rem;
}

.barber-mode-close {
  margin-left: auto;
  background: transparent;
  border: 0;
  color: #ffffff;
  font-size: 2rem;
  line-height: 1;
  width: 44px;
  height: 44px;
  cursor: pointer;
}

.barber-mode-scroll {
  flex: 1;
  overflow-y: auto;
  padding: 0.9rem 1rem 1.5rem;
  max-width: 720px;
  width: 100%;
  margin: 0 auto;
  box-sizing: border-box;
}

.barber-mode-title {
  margin: 0 0 0.8rem;
  font-size: 1.6rem;
  line-height: 1.2;
  font-weight: 800;
}

.barber-angles {
  display: grid;
  grid-template-columns: 1.2fr 1fr 1fr;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

/* Styling looks (buns, braids) have no cut to diagram -- just the photo,
   centred and larger. */
.barber-angles-single {
  grid-template-columns: minmax(0, 340px);
  justify-content: center;
}

.barber-angle {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: #f7f7f8;
  border: 1px solid #e3e6ee;
  border-radius: 12px;
  padding: 0.35rem;
  overflow: hidden;
}

.barber-angle img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: 8px;
}

.barber-angle svg {
  width: 100%;
  height: auto;
}

.barber-angle figcaption {
  margin-top: 0.3rem;
  font-weight: 800;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.barber-angle figcaption span,
.barber-badge-where span {
  font-weight: 600;
  text-transform: none;
  color: #626a7d;
}

.barber-badges {
  display: grid;
  gap: 0.55rem;
  margin-bottom: 1rem;
}

.barber-badge {
  border-radius: 12px;
  padding: 0.7rem 0.9rem;
  border: 2px solid;
}

.barber-badge-top { border-color: #2563eb; background: #eff5ff; }
.barber-badge-sides { border-color: #d9480f; background: #fff4e6; }
.barber-badge-back { border-color: #0f766e; background: #ecfdf8; }

.barber-badge-where {
  font-size: 0.8rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 0.2rem;
}

.barber-badge-tool {
  font-size: 1.3rem;
  font-weight: 800;
  line-height: 1.3;
}

.barber-badge-icon {
  margin-right: 0.4rem;
}

.barber-badge-sub {
  font-size: 1rem;
  font-weight: 600;
  color: #3b3431;
}

.barber-steps {
  margin: 0 0 1rem;
  padding-left: 1.4rem;
  font-size: 1.15rem;
  font-weight: 600;
  line-height: 1.5;
}

.barber-say {
  background: #13161c;
  color: #ffffff;
  border-radius: 14px;
  padding: 1rem;
  margin-bottom: 1rem;
}

.barber-say-label {
  font-size: 0.8rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #fbbf24;
  margin-bottom: 0.4rem;
}

.barber-say-text {
  margin: 0 0 0.8rem;
  font-size: 1.12rem;
  font-weight: 600;
  line-height: 1.5;
}

.barber-say-hindi {
  margin: 0.8rem 0 0;
  font-size: 1.05rem;
  line-height: 1.5;
  color: #d6d9e0;
}

.barber-mode-actions {
  display: grid;
  gap: 0.5rem;
}

.barber-action {
  min-height: 48px;
  border-radius: 10px;
  border: 2px solid #4c145c;
  background: #ffffff;
  color: #4c145c;
  font-size: 1rem;
  font-weight: 800;
  cursor: pointer;
}

.barber-mode-foot {
  text-align: center;
  color: #626a7d;
  font-size: 0.8rem;
  margin: 1rem 0 0;
}

.barber-mode-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 3rem 0;
  color: #626a7d;
}

.barber-mode-spinner {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 4px solid #e3e6ee;
  border-top-color: #4c145c;
  animation: barber-spin 0.8s linear infinite;
}

@keyframes barber-spin {
  to { transform: rotate(360deg); }
}

.barber-mode-error {
  color: #b42318;
  font-weight: 600;
}

/* Hair readiness verdict + "today instead" instruction + care plan. */
.barber-ready {
  border-radius: 12px;
  padding: 0.75rem 0.9rem;
  margin-bottom: 1rem;
  border: 2px solid;
}
.barber-ready-ready { border-color: #16a34a; background: #effcf3; }
.barber-ready-almost { border-color: #d97706; background: #fffbeb; }
.barber-ready-grow { border-color: #b42318; background: #fef3f2; }

.barber-ready-head {
  font-size: 1.1rem;
  font-weight: 800;
}

.barber-ready-summary {
  margin: 0.35rem 0 0;
  line-height: 1.45;
}

.barber-ready-lengths {
  margin: 0.45rem 0 0;
  display: grid;
  gap: 0.15rem;
  font-size: 0.92rem;
}

.barber-say-today {
  background: #7a2e0e;
}

.barber-say-later {
  opacity: 0.9;
}

.barber-final-title {
  margin: 0.2rem 0 0.6rem;
  font-size: 1.05rem;
  font-weight: 800;
  color: #626a7d;
}

.barber-plan {
  border: 2px solid #d1fadf;
  background: #f6fef9;
  border-radius: 14px;
  padding: 0.9rem 1rem 0.5rem;
  margin-bottom: 1rem;
}

.barber-plan h4 {
  margin: 0.6rem 0 0.3rem;
  font-size: 0.95rem;
  font-weight: 800;
}

.barber-plan ul,
.barber-plan ol {
  margin: 0 0 0.4rem;
  padding-left: 1.3rem;
  line-height: 1.5;
}

/* The app's confirm modal (used for the "+0.5 credit" side & back prompt)
   normally sits at z-index 100 -- lift it above the barber card while the
   card is open, or it would open invisibly behind it. */
body.barber-mode-open .modal-overlay {
  z-index: 400;
}

/* Real side & back photo: one image holding both views side by side, each
   panel showing its half. */
.barber-real-view {
  width: 100%;
  aspect-ratio: 3 / 4;
  border-radius: 8px;
  background-repeat: no-repeat;
  background-size: 200% auto;
  background-color: #e9e9ec;
}
.barber-real-view-left { background-position: 0% 50%; }
.barber-real-view-right { background-position: 100% 50%; }

.barber-real-tag {
  display: inline-block;
  margin-left: 0.25rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: #16a34a;
  color: #ffffff !important;
  font-size: 0.62rem;
  letter-spacing: 0.02em;
  vertical-align: middle;
}

.barber-views-upsell {
  margin: -0.4rem 0 1rem;
}

.barber-views-button {
  width: 100%;
  border-style: dashed;
}

.barber-views-button:disabled {
  opacity: 0.7;
  cursor: progress;
}

.barber-views-note,
.barber-views-status {
  margin: 0.4rem 0 0;
  font-size: 0.82rem;
  color: #626a7d;
  text-align: center;
}

.barber-views-status {
  color: #b42318;
  font-weight: 600;
}

.barber-guide {
  border: 2px solid #e3e6ee;
  border-radius: 14px;
  padding: 0.9rem 1rem 0.4rem;
  margin-bottom: 1rem;
}

.barber-guide-title {
  margin: 0 0 0.6rem;
  font-size: 1.1rem;
  font-weight: 800;
}

.barber-guide-title span {
  font-weight: 600;
  color: #626a7d;
}

.barber-guide-steps {
  margin: 0;
  padding-left: 1.4rem;
}

.barber-guide-steps li {
  margin-bottom: 0.75rem;
}

.barber-guide-steps li::marker {
  font-weight: 800;
  color: #4c145c;
}

.barber-guide-step-title {
  font-weight: 800;
  font-size: 1rem;
}

.barber-guide-step-en {
  font-size: 1rem;
  line-height: 1.45;
}

.barber-guide-step-hi {
  margin-top: 0.15rem;
  font-size: 0.98rem;
  line-height: 1.5;
  color: #3b3431;
}

@media (max-width: 430px) {
  .barber-mode-title { font-size: 1.35rem; }
  .barber-badge-tool { font-size: 1.12rem; }
  .barber-say-text { font-size: 1.18rem; }
}

/* --- Slot picker (2026-09-14) ----------------------------------------------

   A grid of real, tappable times rather than a dropdown: a customer scanning
   for "something after 6" reads a grid in one glance, where a select hides
   every option but one behind a tap. auto-fill with a minimum keeps four
   across on a laptop and two on a phone without a breakpoint. */
/* The booking form's card. It sits in the shared .modal-overlay backdrop but
   needs its own box: .modal-box is capped at 380px for short confirm dialogs,
   and a form with a location, a service, a date and a grid of times needs room
   to breathe. Without this rule the box was transparent and static, so the
   whole form painted straight over the salon list underneath -- unreadable,
   and reported as "the popup is not working at all" (2026-09-14). */
/* "Did they come in?" -- the prompt on the salon's Today tab. Given its own
   ground rather than another plain card, because it is the one thing on that
   screen that is asking the salon for something. */
.salon-attend {
  margin-bottom: 1.25rem;
  padding: 1rem 1.1rem;
  border-radius: var(--radius-lg);
  background: var(--panel-alt, #f7f4fb);
  border: 1px solid var(--border);
}

.salon-attend-heading { margin: 0 0 0.25rem; font-size: 1rem; }

.salon-attend-sub {
  margin: 0 0 0.85rem;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--muted);
}

.salon-attend-list { display: grid; gap: 0.6rem; }

.salon-attend-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  padding: 0.7rem 0.85rem;
  border-radius: var(--radius-sm);
  background: var(--panel);
  border: 1px solid var(--border);
}

.salon-attend-who { flex: 1 1 12rem; min-width: 0; font-size: 0.92rem; }
.salon-attend-who b { display: block; }

.salon-attend-when {
  font-size: 0.8rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.salon-attend-row .secondary-button,
.salon-attend-row .primary-button { flex: 0 0 auto; }

.booking-modal-box {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  width: 100%;
  max-width: 460px;
  max-height: 90vh;
  overflow-y: auto;
  padding: 1.5rem;
}

.booking-modal-box h3 {
  margin: 0 2rem 0.35rem 0;
  font-size: 1.1rem;
}

.booking-modal-box .subtitle {
  margin: 0 0 1rem;
  color: var(--muted);
  font-size: 0.9rem;
}

/* The labels stack with their controls, so each field reads as one thing
   rather than a loose column of text and boxes. */
.booking-modal-box .reference-label {
  display: block;
  margin: 0.9rem 0 0.3rem;
  font-weight: 600;
  font-size: 0.85rem;
}

.booking-modal-box select,
.booking-modal-box input[type="date"],
.booking-modal-box textarea {
  width: 100%;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 0.95rem;
}

.booking-modal-box textarea {
  min-height: 72px;
  resize: vertical;
}

.booking-modal-box .consent-field {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin-top: 0.8rem;
  font-size: 0.88rem;
  line-height: 1.45;
}

.booking-modal-box .consent-field input[type="checkbox"] {
  margin-top: 0.2rem;
  flex: 0 0 auto;
}

.booking-modal-box .primary-button {
  width: 100%;
  margin-top: 1.2rem;
}

.booking-modal-box .status-message {
  margin: 0 0 0.8rem;
}

.booking-slots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 0.5rem;
  margin: 0.6rem 0 1rem;
}

.booking-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  /* 44px is the smallest comfortable tap target, and this is the one control
     in the flow a customer must hit accurately on a phone. */
  min-height: 44px;
  padding: 0.45rem 0.5rem;
  font-family: inherit;
  font-size: 0.94rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color 0.12s ease, background 0.12s ease;
}

.booking-slot:hover {
  border-color: var(--accent);
}

.booking-slot.selected {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.booking-slot em {
  font-style: normal;
  font-size: 0.7rem;
  font-weight: 500;
  /* True and useful on the last chair -- not urgency theatre, which is why it
     only ever appears when the salon genuinely has more than one seat. */
  color: var(--muted);
}

.booking-slot.selected em {
  color: var(--accent-contrast);
  opacity: 0.85;
}

/* A salon's own note that a date is shut -- informational, not an error. */
.callout-note {
  padding: 0.7rem 0.9rem;
  border-radius: var(--radius);
  background: var(--landing-surface);
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.92rem;
  margin: 0 0 1rem;
}

/* --- Salons near me (2026-09-14) ------------------------------------------- */

/* --- Admin: can customers see my salons? (2026-09-16) --------------------- */

.admin-customer-view {
  display: grid;
  gap: 0.6rem;
  margin: 0 0 1rem;
}

.admin-customer-view > .secondary-button {
  justify-self: start;
  width: auto;
}

.admin-hidden-banner {
  display: grid;
  gap: 0.6rem;
  padding: 0.9rem 1rem;
  border: 1px solid #fecaca;
  border-left: 4px solid var(--error);
  border-radius: var(--radius);
  background: #fef2f2;
}

.admin-hidden-banner p {
  margin: 0;
  font-weight: 600;
  color: #7f1d1d;
  line-height: 1.5;
}

.admin-hidden-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.admin-hidden-list .admin-show-salon-button {
  width: auto;
  min-height: 44px;
  border-color: var(--accent);
  color: var(--accent);
}

.admin-visibility-message {
  margin: 0;
  padding: 0.7rem 1rem;
  border-radius: var(--radius);
  border-left: 4px solid var(--success);
  background: #f0fdf4;
  color: #166534;
  font-weight: 600;
}

.admin-visibility-message.status-error {
  border-left-color: var(--error);
  background: #fef2f2;
  color: #7f1d1d;
}

.admin-visibility-cell {
  min-width: 9rem;
}

/* Under the label, not beside it: beside it, the button spilled into the next
   column on a laptop-width table. */
.admin-visibility-cell .save-button {
  display: block;
  margin-top: 0.35rem;
  white-space: nowrap;
}

.admin-visibility {
  font-weight: 600;
}

.admin-visibility-yes {
  color: #166534;
}

.admin-visibility-no {
  color: var(--error);
}

.admin-visibility-wait {
  color: var(--muted);
  font-weight: 500;
}

.salon-visibility {
  display: grid;
  gap: 0.2rem;
  padding: 0.8rem 1rem;
  margin: 0 0 0.75rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  border-left-width: 4px;
  background: var(--panel);
}

.salon-visibility strong {
  font-size: 1rem;
}

.salon-visibility span {
  color: var(--muted);
  line-height: 1.5;
}

.salon-visibility[data-tone="live"] {
  border-left-color: var(--success);
  background: #f0fdf4;
}

.salon-visibility[data-tone="live"] strong {
  color: #166534;
}

.salon-visibility[data-tone="pending"] {
  border-left-color: #d97706;
  background: #fffbeb;
}

.salon-visibility[data-tone="hidden"] {
  border-left-color: var(--error);
  background: #fef2f2;
}

/* One card holds every way to find a salon: the city is the headline,
   because typing where you are is what customers reach for first. */
.market-search {
  display: grid;
  gap: 0.55rem;
  padding: 1.1rem;
  margin-bottom: 1rem;
  background: var(--landing-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.market-search-label {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
}

.market-search-field {
  position: relative;
}

.market-search-icon {
  position: absolute;
  left: 0.9rem;
  top: 50%;
  width: 1.25rem;
  height: 1.25rem;
  transform: translateY(-50%);
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}

.market-search-input,
.market-search-secondary {
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  font-family: inherit;
  color: var(--text);
  background: var(--panel);
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

/* 16px or more, or iOS zooms the whole page when the box is tapped. */
.market-search-input {
  min-height: 52px;
  padding: 0.7rem 0.9rem 0.7rem 2.7rem;
  font-size: 1.05rem;
  box-shadow: var(--shadow-sm);
}

.market-search-secondary {
  min-height: 44px;
  padding: 0.55rem 0.85rem;
  font-size: 1rem;
}

.market-search-input:focus,
.market-search-secondary:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(76, 20, 92, 0.15);
}

.market-search-sublabel {
  margin-top: 0.35rem;
  font-size: 0.88rem;
  color: var(--muted);
}

.market-city-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.market-city-chips:empty {
  display: none;
}

.market-city-chip {
  min-height: 36px;
  padding: 0.35rem 0.85rem;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--accent);
  background: var(--panel);
  border: 1px solid rgba(76, 20, 92, 0.25);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

.market-city-chip:hover {
  border-color: var(--accent);
}

.market-city-chip.is-active {
  color: var(--accent-contrast);
  background: var(--accent);
  border-color: var(--accent);
}

.market-city-chip:focus-visible,
.market-near-button:focus-visible {
  outline: 3px solid rgba(76, 20, 92, 0.35);
  outline-offset: 2px;
}

.market-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0.35rem 0 0.1rem;
  font-size: 0.82rem;
  color: var(--muted);
}

.market-divider::before,
.market-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.market-mode-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
}

.market-mode-row .primary-button,
.market-mode-row .market-near-button {
  flex: 1 1 12rem;
  width: auto;
  min-height: 44px;
}

.market-location-note:empty {
  display: none;
}

.market-location-note {
  margin: 0;
}

.market-summary {
  margin: 0 0 0.6rem;
  font-weight: 600;
  color: var(--muted);
}

.market-summary:empty {
  display: none;
}

.market-mode-row select {
  flex: 0 1 11rem;
  min-height: 44px;
  padding: 0.5rem 0.7rem;
  font-family: inherit;
  font-size: 0.94rem;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* Sized in aspect-ratio rather than a fixed height so it stays proportionate
   from a phone to a laptop; capped so it never pushes the salon list itself
   below the fold, which is what the customer actually came to read. */
.market-map {
  width: 100%;
  aspect-ratio: 16 / 10;
  max-height: 380px;
  margin-bottom: 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface-sunk);
}

/* Distance sits next to the salon's name, where it is the thing a customer
   scans for. Tabular figures so a column of them lines up. */
.salon-distance {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--accent);
  white-space: nowrap;
}

/* --- Messages, follow-ups and the customer book (2026-09-14) -------------- */

.salon-placeholder-hints code {
  display: inline-block;
  padding: 1px 5px;
  margin: 0 2px 2px 0;
  border-radius: 4px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 0.84em;
}

.salon-templates-editor {
  display: grid;
  gap: 1rem;
  margin-bottom: 1.25rem;
}

.salon-template-row {
  display: grid;
  gap: 0.4rem;
  padding: 0.9rem 1rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.salon-template-row strong {
  font-size: 0.94rem;
}

.salon-template-row textarea {
  width: 100%;
  box-sizing: border-box;
  min-height: 70px;
  padding: 0.55rem 0.7rem;
  font-family: inherit;
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--text);
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  resize: vertical;
}

/* The message a follow-up will send, shown in full before it is sent -- a
   salon should never tap a button not knowing what goes out under their name. */
.salon-followup-message {
  margin: 0.6rem 0 0;
  padding: 0.7rem 0.85rem;
  background: var(--surface-sunk);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: 0.92rem;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.salon-followup-reason {
  font-family: var(--font-mono, inherit);
  font-size: 0.8rem;
  color: var(--muted);
}


/* A number WhatsApp cannot reach. Stated plainly on the card rather than left
   for the salon to discover by pressing a button that does nothing. */
.salon-unreachable {
  color: var(--fail, #a3201f);
  font-size: 0.88rem;
}

button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* My style profile.
   Each row is a value the customer can change, plus -- for anything that could
   have arrived by more than one route -- where it came from. The source line
   is deliberately quiet: it is context for a decision, not a label competing
   with the value itself. */
.style-profile-fields {
  display: grid;
  gap: 1.1rem;
  margin-block: 1.25rem;
}

.style-profile-row {
  display: grid;
  gap: 0.3rem;
}

.style-profile-row select {
  width: 100%;
}

.style-profile-source {
  color: var(--muted);
  font-size: 0.82rem;
}

/* Admin Users: "Change balance" sets an exact number (2026-09-16). */
.admin-credits-set-label {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.85rem;
  color: var(--muted);
}

/* Welcome for a newly onboarded salon.
   Same overlay shape as .modal-overlay (fixed, full-screen, z-index 100), in the
   warmer register a first welcome deserves: a soft blush wash, a bouquet, and
   petals drifting down behind the card. Light-only, like the rest of the file. */
.salon-welcome {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
  overflow: hidden;
  background: radial-gradient(circle at 50% 35%, rgba(255, 228, 238, 0.97), rgba(247, 232, 250, 0.97));
}

.salon-welcome.hidden {
  display: none;
}

.salon-welcome-card {
  position: relative;
  z-index: 1;
  max-width: 26rem;
  width: 100%;
  padding: 2rem 1.5rem 1.6rem;
  border-radius: 20px;
  background: #fff;
  box-shadow: 0 18px 50px rgba(76, 20, 92, 0.18);
  text-align: center;
  animation: salon-welcome-rise 0.5s ease-out both;
}

.salon-welcome-card h2 {
  margin: 0.4rem 0 0.2rem;
  color: var(--accent);
  line-height: 1.25;
}

.salon-welcome-bouquet {
  font-size: 3.4rem;
  line-height: 1;
  animation: salon-welcome-sway 3s ease-in-out infinite;
  transform-origin: 50% 90%;
}

.salon-welcome-gift {
  margin: 1rem 0 0.25rem;
  color: var(--text);
  font-size: 1.05rem;
}

.salon-welcome-hindi {
  margin: 0.15rem 0;
  color: var(--accent);
  font-weight: 600;
}

.salon-welcome-card .primary-button {
  margin-top: 1.1rem;
  width: 100%;
}

/* The petals sit behind the card and never take a click. */
.salon-welcome-petals {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.salon-welcome-petal {
  position: absolute;
  top: -2.5rem;
  opacity: 0.9;
  animation-name: salon-welcome-fall;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

@keyframes salon-welcome-fall {
  from {
    transform: translateY(0) rotate(0deg);
  }
  to {
    transform: translateY(110vh) rotate(320deg);
  }
}

@keyframes salon-welcome-sway {
  0%,
  100% {
    transform: rotate(-6deg);
  }
  50% {
    transform: rotate(6deg);
  }
}

@keyframes salon-welcome-rise {
  from {
    opacity: 0;
    transform: translateY(14px) scale(0.97);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Anyone who has asked their device for less motion gets the welcome without
   the falling petals or the swaying bouquet -- the flowers stay, still. */
@media (prefers-reduced-motion: reduce) {
  .salon-welcome-petal {
    display: none;
  }
  .salon-welcome-bouquet,
  .salon-welcome-card {
    animation: none;
  }
}

/* Admin Salons: how salon credits are going (2026-09-16). */
.admin-salon-credit-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.6rem;
  margin: 0.75rem 0 1rem;
}

.admin-salon-credit-summary.hidden {
  display: none;
}

.admin-salon-stat {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.65rem 0.8rem;
  border: 1px solid rgba(76, 20, 92, 0.12);
  border-radius: 12px;
  background: #fff;
}

.admin-salon-stat strong {
  font-size: 1.25rem;
  color: var(--accent);
}

.admin-salon-stat span,
.admin-salon-note {
  font-size: 0.78rem;
  color: var(--muted);
}

.admin-salon-note,
.admin-salon-credits {
  display: block;
}

/* Admin: salons waiting for approval (2026-09-25). Same shape as
   .admin-hidden-banner, in the warning register rather than the error one --
   a pending salon is a job to do, not a fault. */
.admin-pending-banner {
  margin: 0.75rem 0 1rem;
  padding: 0.85rem 1rem;
  border-left: 4px solid var(--accent);
  border-radius: 12px;
  background: rgba(118, 41, 138, 0.07);
}

.admin-pending-banner.hidden {
  display: none;
}

.admin-pending-banner p {
  margin: 0 0 0.6rem;
  font-weight: 600;
  color: var(--accent);
}

.admin-reminder-cell .link-button {
  display: block;
  margin-top: 0.2rem;
}

/* A salon's Subscription screen.
   Scoped entirely to its own classes -- an individual's Buy credits screen
   keeps every rule it had, because the founder's standing instruction is that
   these changes are for salons only. */
.subscription-status {
  background: var(--surface, #fff);
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 14px;
  padding: 16px 18px;
  margin-bottom: 16px;
}

.subscription-status-line {
  margin: 0;
  font-weight: 600;
  font-size: 1.05rem;
}

.subscription-status-detail {
  margin: 6px 0 0;
  font-size: 0.9rem;
  opacity: 0.75;
}

.subscription-wallet {
  margin-bottom: 20px;
}

.subscription-credit-split {
  margin: 6px 0 0;
  font-size: 0.85rem;
  opacity: 0.75;
}

/* Auto-fill rather than a fixed count: five plans on a laptop, one per row on
   a phone, and a salon owner works from a phone. */
.subscription-plans {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
  margin-top: 12px;
}

.subscription-plan {
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 14px;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.subscription-plan-current {
  border-color: var(--accent, #c2185b);
  box-shadow: 0 0 0 1px var(--accent, #c2185b) inset;
}

.subscription-plan-price {
  font-size: 1.4rem;
  font-weight: 700;
}

.subscription-plan-price span {
  font-size: 0.85rem;
  font-weight: 400;
  opacity: 0.7;
}

.subscription-plan-credits {
  font-weight: 600;
  font-size: 0.95rem;
}

.subscription-plan-note {
  margin: 0;
  font-size: 0.82rem;
  opacity: 0.72;
  flex: 1;
}

.subscription-plan button {
  margin-top: 4px;
  width: 100%;
}

.subscription-error {
  margin-top: 12px;
  color: #b3261e;
  font-size: 0.9rem;
}

.subscription-cancel-form {
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.subscription-cancel-form label {
  font-size: 0.85rem;
  font-weight: 600;
}

.subscription-cancel-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 4px;
}

.subscription-grant {
  font-size: 0.88rem;
  padding: 6px 0;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}

.subscription-grant:last-child {
  border-bottom: none;
}

@media (max-width: 700px) {
  .subscription-plans {
    grid-template-columns: 1fr;
  }
}

/* The scan-from-another-device panel, and a plan that was chosen but never
   paid for. Both scoped to the subscription screen: an individual's Buy
   credits screen is untouched. */
.subscription-qr {
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 14px;
  padding: 18px;
  margin-bottom: 20px;
  text-align: center;
}

.subscription-qr h3 {
  margin: 0 0 6px;
}

.subscription-qr-caption {
  margin: 0 0 12px;
  font-size: 0.88rem;
  opacity: 0.75;
}

.subscription-qr-image {
  display: block;
  margin: 0 auto 10px;
  width: 220px;
  height: 220px;
  /* A QR on a dark card is unscannable, so the quiet zone is explicit rather
     than inherited from the surface colour. */
  background: #fff;
  padding: 8px;
  border-radius: 8px;
}

/* Wraps rather than overflowing: this is a URL somebody may need to type. */
.subscription-qr-link-row {
  margin: 0 0 12px;
  font-size: 0.8rem;
  word-break: break-all;
}

/* Chosen, not paid for. Deliberately not the accent colour used for the plan
   they actually hold -- looking like the current plan is the bug this fixes. */
.subscription-plan-pending {
  border-style: dashed;
  border-color: rgba(0, 0, 0, 0.28);
}

/* Confirmation before payment, and the plan details on each card. */
.subscription-confirm {
  border: 2px solid var(--accent, #c2185b);
  border-radius: 14px;
  padding: 18px;
  margin-bottom: 20px;
}

.subscription-confirm h3 {
  margin: 0 0 8px;
}

/* The commitment. Deliberately not small print -- it says money will be taken
   every month, and that should be as readable as the price. */
.subscription-confirm-terms {
  margin: 0 0 12px;
  font-size: 0.92rem;
}

.subscription-confirm-includes,
.subscription-plan-includes {
  margin: 0 0 12px;
  padding-left: 18px;
  font-size: 0.85rem;
  opacity: 0.85;
}

.subscription-confirm-includes li,
.subscription-plan-includes li {
  margin-bottom: 4px;
}

.subscription-plan-includes {
  flex: 1;
  margin-bottom: 8px;
}

.subscription-confirm-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

/* The recommendation. An argument with a number in it, not a "most popular"
   badge we cannot evidence with one paying salon. */
.subscription-plan-value {
  margin: 0 0 6px;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--accent, #c2185b);
}

/* The admin Business tab. Scoped to its own classes; no existing admin rule
   is touched. */
.admin-insight-notes {
  margin: 0 0 20px;
  padding-left: 18px;
}

.admin-insight-notes li {
  margin-bottom: 6px;
  font-size: 0.95rem;
}

/* Auto-fit so three cards sit side by side on a laptop and stack on a phone,
   without a breakpoint to maintain. */
.admin-insight-columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
  margin-bottom: 22px;
}

.admin-insight-card {
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 12px;
  padding: 14px 16px;
}

.admin-insight-card h3 {
  margin: 0 0 10px;
  font-size: 1rem;
}

/* Label left, figure right, one pair per row. */
.admin-insight-list {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 12px;
  margin: 0;
}

.admin-insight-list dt {
  font-size: 0.85rem;
  opacity: 0.75;
}

.admin-insight-list dd {
  margin: 0;
  text-align: right;
  font-weight: 600;
  font-size: 0.92rem;
}

/* The "why this number matters" line under a figure. */
.admin-insight-hint {
  display: block;
  font-weight: 400;
  font-size: 0.75rem;
  opacity: 0.65;
}

.admin-insight-heading {
  margin: 0 0 8px;
  font-size: 1rem;
}

/* The Subscription column in the admin Salons table. */
.admin-subscription-cell {
  white-space: nowrap;
}

.admin-subscription-paying {
  display: block;
  font-weight: 700;
}

.admin-subscription-trial {
  display: block;
  font-weight: 600;
}

/* Lapsed is the one an admin should notice, so it is the only one coloured. */
.admin-subscription-lapsed {
  display: block;
  font-weight: 600;
  color: #b3261e;
}

.admin-subscription-none {
  display: block;
  opacity: 0.6;
}

.admin-subscription-hint {
  display: block;
  font-size: 0.72rem;
  opacity: 0.7;
}


/* --- The billing counter ----------------------------------------------------

   A counter screen, not a form. Everything a salon taps here is sized for a
   thumb on a phone at a busy desk: the tiles are 64px tall and the payment
   buttons 56px, which is well above the 44px minimum, because the alternative
   to tapping the right tile first time is a salon that goes back to the diary.

   No rule here is conditional on a wide screen. Salon owners work on phones. */

.salon-bill {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.salon-bill-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 14px;
  background: var(--landing-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.salon-bill-today {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}

.salon-bill-today-label {
  color: var(--muted);
  font-size: 0.9rem;
}

/* The day's takings, big. It is the number an owner opens the app for. */
.salon-bill-today-total {
  font-size: 1.6rem;
  color: var(--accent);
}

.salon-bill-today-count {
  color: var(--muted);
  font-size: 0.9rem;
}

.salon-lang {
  display: flex;
  gap: 0;
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
  background: var(--panel);
}

.salon-lang-button {
  min-height: 36px;
  padding: 6px 14px;
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 0.95rem;
  cursor: pointer;
}

.salon-lang-button.active {
  background: var(--accent);
  color: var(--accent-contrast);
}

.salon-bill-step {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 6px 0 0;
  font-size: 1rem;
  color: var(--text);
}

.salon-bill-step small {
  color: var(--muted);
  font-weight: 400;
  font-size: 0.8rem;
}

.salon-bill-empty {
  color: var(--muted);
  font-size: 0.9rem;
  margin: 0;
}

/* Service tiles. auto-fill rather than a fixed column count, so a salon with
   four services does not get four lonely tiles in a twelve-column grid. */
.salon-bill-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 10px;
}

/* Search above the tiles, borrowing .salon-bill-field's shape so it reads as
   part of the same screen rather than a bolted-on control. Full width because
   it filters the full-width grid below it. */
.salon-bill-search {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 10px;
  margin-bottom: 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.salon-bill-search input {
  flex: 1;
  min-width: 0;
  min-height: 44px;
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: 1rem;
}

/* Safari draws its own decoration inside a type="search"; this keeps the box
   the same height as every other field on the screen. */
.salon-bill-search input::-webkit-search-decoration,
.salon-bill-search input::-webkit-search-results-button {
  -webkit-appearance: none;
}

.salon-bill-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 2px;
  min-height: 64px;
  padding: 10px 12px;
  text-align: left;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}

.salon-bill-tile:active {
  background: var(--landing-surface);
}

.salon-bill-tile-name {
  font-weight: 600;
  color: var(--text);
  /* A long service name wraps instead of pushing the price out of the tile. */
  overflow-wrap: anywhere;
}

.salon-bill-tile-price {
  color: var(--accent);
  font-size: 0.9rem;
}

.salon-bill-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.salon-bill-chip {
  min-height: 44px;
  padding: 8px 16px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text);
  font-size: 0.95rem;
  cursor: pointer;
}

.salon-bill-chip.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

/* What is on the bill so far. */
.salon-bill-basket {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.salon-bill-basket-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.salon-bill-basket-name {
  flex: 1;
  color: var(--text);
  overflow-wrap: anywhere;
}

.salon-bill-basket-amount {
  width: 92px;
  min-height: 40px;
  padding: 6px 8px;
  text-align: right;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
  font-size: 1rem;
}

.salon-bill-basket-remove {
  min-width: 36px;
  min-height: 36px;
  border: 0;
  border-radius: 999px;
  background: var(--bg);
  color: var(--muted);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
}

.salon-bill-money {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.salon-bill-total-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 1.1rem;
}

.salon-bill-total-row strong {
  font-size: 1.5rem;
  color: var(--accent);
}

.salon-bill-discount {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--muted);
}

.salon-bill-discount input {
  width: 100px;
  min-height: 40px;
  margin-left: auto;
  padding: 6px 8px;
  text-align: right;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
  font-size: 1rem;
}

/* Payment: one tap puts the whole total in one method. */
.salon-bill-pay {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 8px;
}

.salon-bill-pay-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 56px;
  padding: 8px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-size: 0.95rem;
  cursor: pointer;
}

.salon-bill-pay-button.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

.salon-bill-split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
}

.salon-bill-split label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  color: var(--muted);
  font-size: 0.85rem;
}

.salon-bill-split input {
  min-height: 44px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
  font-size: 1rem;
}

.salon-bill-customer {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
}

.salon-bill-field {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.salon-bill-field input {
  flex: 1;
  min-width: 0;
  min-height: 44px;
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: 1rem;
}

/* "Asha, last visit 12 Sep, haircut with Bunty" -- the line that makes a salon
   believe the product knows their business. */
.salon-bill-known {
  margin: 0;
  padding: 8px 10px;
  background: var(--landing-mint-bg);
  color: var(--landing-mint-text);
  border-radius: var(--radius);
  font-size: 0.9rem;
}

.salon-bill-save {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 56px;
  font-size: 1.05rem;
}

.salon-bill-clear {
  align-self: center;
  color: var(--muted);
}

.salon-bill-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.salon-bill-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* Cancelled, never deleted: struck through and still on the list, because a
   deleted bill is indistinguishable from a quiet day. */
.salon-bill-row.voided {
  opacity: 0.55;
  text-decoration: line-through;
}

.salon-bill-row-amount {
  font-size: 1.1rem;
  color: var(--accent);
}

.salon-bill-row-what {
  flex: 1;
  min-width: 120px;
  color: var(--text);
  overflow-wrap: anywhere;
}

.salon-bill-row-how {
  color: var(--muted);
  font-size: 0.85rem;
}

.salon-bill-row-cancel {
  color: var(--muted);
  font-size: 0.85rem;
}

.salon-bill-row-reason {
  flex-basis: 100%;
  width: 100%;
  min-width: 0;
  font-size: 0.9rem;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
}

.salon-bill-row-error {
  flex-basis: 100%;
  color: #b42318;
  font-size: 0.85rem;
}

.salon-bill-takings {
  padding: 14px;
  background: var(--landing-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.salon-bill-takings-rows,
.salon-bill-takings-staff {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
}

.salon-bill-takings-staff {
  padding-top: 8px;
  border-top: 1px solid var(--border);
}

.salon-bill-takings-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  color: var(--text);
}


/* The WhatsApp receipt. Green, because that is the colour a salon owner
   already associates with the app they are about to be handed off to. */
.salon-bill-receipt {
  margin: 0;
  text-align: center;
}

.salon-bill-send {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 10px 20px;
  background: #25d366;
  border-radius: var(--radius);
  color: #0b3d20;
  font-size: 1rem;
  font-weight: 600;
  text-decoration: none;
}

.salon-bill-row-send {
  color: #0b7a3b;
  font-size: 0.85rem;
  text-decoration: none;
  white-space: nowrap;
}


/* The stylist on a basket line. A button, not text, because tapping it is how
   a mis-tap gets corrected -- see assignSalonBillStylist. */
.salon-bill-basket-who {
  min-height: 28px;
  padding: 2px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--landing-surface);
  color: var(--accent);
  font-size: 0.85rem;
  cursor: pointer;
}


/* --- The money on the Analytics tab -----------------------------------------
   Above the marketing funnel on purpose: an owner opens this tab to find out
   what the salon took and who brought it in. */

.salon-ana-money {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  margin-bottom: 18px;
  background: var(--landing-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.salon-ana-money-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}

.salon-ana-money-label {
  color: var(--muted);
  font-size: 0.9rem;
}

.salon-ana-money-total {
  font-size: 1.8rem;
  color: var(--accent);
}

.salon-ana-money-sub {
  color: var(--muted);
  font-size: 0.9rem;
}

.salon-ana-money-rows {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.salon-ana-money-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  color: var(--text);
  font-size: 0.95rem;
}

.salon-ana-sub-heading {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 8px 0 0;
  font-size: 0.95rem;
  color: var(--text);
}

.salon-ana-note {
  margin: 0;
  color: var(--muted);
  font-size: 0.85rem;
}

.salon-ana-stylists,
.salon-ana-days {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Name and amount on one line, the bar and the detail beneath -- so a phone
   never has to scroll sideways to read who earned what. */
.salon-ana-stylist {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px 10px;
  padding: 8px 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.salon-ana-stylist-name {
  font-weight: 600;
  color: var(--text);
  overflow-wrap: anywhere;
}

.salon-ana-stylist-amount {
  color: var(--accent);
  text-align: right;
}

.salon-ana-stylist-detail {
  grid-column: 1 / -1;
  color: var(--muted);
  font-size: 0.8rem;
}

.salon-ana-bar {
  grid-column: 1 / -1;
  display: block;
  height: 6px;
  background: var(--bg);
  border-radius: 999px;
  overflow: hidden;
}

.salon-ana-bar-fill {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: 999px;
}

.salon-ana-day {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 10px;
}

.salon-ana-day-date {
  min-width: 64px;
  color: var(--muted);
  font-size: 0.85rem;
}

.salon-ana-day .salon-ana-bar {
  grid-column: auto;
}

.salon-ana-day-amount {
  color: var(--text);
  font-size: 0.9rem;
}

/* The pending total above the Balances list. Reads as money, not as a
   subtitle: it is the one number the salon owner opens this tab for. */
.salon-balances-total {
  margin: 0 0 0.75rem;
  font-weight: 600;
  color: var(--text);
}

/* The confirm modal shows messages that go out under the salon's name -- the
   udhaar reminder, a customer's visit history -- and those are written as
   lines, not prose. Without this the itemised list collapses into one wall of
   text and the salon cannot check what they are about to send. Caught in a
   browser; no test would have seen it, because the text content is correct
   and only the rendering is wrong. */
#confirmModalBody {
  white-space: pre-wrap;
  text-align: left;
}

/* The salon's payment page. Standalone, opened from a WhatsApp reminder on a
   phone, so it is a single centred card with nothing else on it. */
.pay-page {
  display: flex;
  justify-content: center;
  padding: 1.5rem 1rem;
}
.pay-card {
  width: 100%;
  max-width: 420px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 1.5rem;
  text-align: center;
}
.pay-card h1 {
  margin: 0 0 0.25rem;
  font-size: 1.3rem;
}
.pay-amount {
  margin: 0 0 1rem;
  font-size: 1.5rem;
  font-weight: 700;
}
.pay-qr-wrap {
  margin: 1rem 0;
}
/* Bounded so a salon uploading a 3000px photo of their QR does not produce a
   page you have to scroll to scan. */
.pay-qr {
  width: 100%;
  max-width: 260px;
  height: auto;
  border-radius: 10px;
}
.pay-label {
  margin: 0.75rem 0 0.15rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.pay-upi-id {
  margin: 0 0 0.75rem;
  font-size: 1.05rem;
  font-weight: 600;
  word-break: break-all;
}
.pay-open-upi {
  display: block;
  margin-top: 0.6rem;
  text-align: center;
  text-decoration: none;
}
.pay-hint {
  color: var(--muted);
  font-size: 0.9rem;
}
.pay-footnote {
  margin-top: 1.25rem;
  font-size: 0.8rem;
  color: var(--muted);
}

/* Country code beside a phone number, never typed into it. The two are
   separate controls because a number saved without its code cannot be
   messaged at all, and that is how one customer became unreachable. */
/* A typeable code box. It was a <select>, so it sized itself to its widest
   option; an <input> defaults to about twenty characters wide and would shove
   the phone box off a phone screen, so the width is stated here. */
.salon-country-code {
  flex: 0 0 auto;
  width: 3.6rem;
  padding: 0.55rem 0.35rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card);
  color: var(--text);
  font: inherit;
  text-align: center;
}
/* The "+" lives outside the box so the box holds digits only -- which is what
   gets composed into the number, and what a pasted "+971" is reduced to. */
.salon-country-plus {
  flex: 0 0 auto;
  margin-right: 0.1rem;
  color: var(--muted);
}
.salon-bill-phone-field .salon-country-code {
  margin-right: 0.35rem;
}
/* The code box lives inside .salon-bill-field, whose `input { flex: 1 }` is
   MORE specific than .salon-country-code and so beat it: the code box took
   half the field and left 34px for a ten-digit phone number. It only showed
   at phone width, where that field is about 160px wide -- on a laptop both
   halves were roomy and it looked fine. Two classes and a type selector, so
   this wins at every width and inside the max-width:700px block too. */
.salon-bill-field input.salon-country-code,
.salon-phone-row input.salon-country-code {
  flex: 0 0 auto;
  width: 3.6rem;
  min-width: 3.6rem;
  text-align: center;
}
.salon-phone-row {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}
.salon-phone-row .salon-input {
  flex: 1 1 auto;
  min-width: 0;
}
.salon-customer-edit {
  text-align: left;
}

/* The "money received" form. The first version put a bare, unstyled number
   box next to a big primary button and pre-filled it with the full amount,
   so one tap closed an account that had only been partly paid. The amount is
   now the biggest thing in the dialog and starts empty. */
.salon-settle-form {
  text-align: left;
}
.salon-settle-pending {
  margin: 0 0 0.75rem;
  font-weight: 600;
}
.salon-settle-amount {
  width: 100%;
  font-size: 1.25rem;
  font-weight: 600;
  padding: 0.6rem 0.7rem;
}
.salon-settle-all {
  width: 100%;
  margin: 0.5rem 0 0.25rem;
}
.salon-settle-form .salon-field-label {
  display: block;
  margin-top: 0.75rem;
}

/* Introduced with the balances work and never defined, which is why the
   "how much did you receive" box rendered as a bare browser default that was
   easy to miss entirely. */
.salon-input {
  width: 100%;
  box-sizing: border-box;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card);
  color: var(--text);
  font: inherit;
}
.salon-input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.salon-field-label {
  display: block;
  margin-bottom: 0.4rem;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text);
}

/* Customer search. A salon with a few hundred regulars cannot scroll to find
   one, and on a phone the box has to be the width of the screen. */
.salon-search-label {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin-bottom: 0.5rem;
}
.salon-search-label .salon-input {
  flex: 1 1 auto;
  min-width: 0;
}

/* "Add your UPI ID" on the Balances tab. A hint, not an error: the salon has
   done nothing wrong, they just have a feature switched off that they do not
   know about. */
.salon-pay-nudge {
  margin: 0 0 0.75rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card);
  font-size: 0.9rem;
  color: var(--muted);
}

/* --- Prepaid packages ---------------------------------------------------- */

.salon-packages-actions {
  margin-bottom: 0.75rem;
}
.salon-package-editor {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1rem;
  margin-bottom: 1rem;
  background: var(--card);
}
.salon-package-editor h3 {
  margin-top: 0;
}
/* Price and validity side by side on a laptop, stacked on a phone -- a salon
   owner fills this in at the counter on a handset. */
.salon-package-row {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.salon-package-row > * {
  flex: 1 1 11rem;
  min-width: 0;
}
.salon-package-items {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}
/* One "Haircut x2" row. The quantity stays narrow so the service name, which
   is the part that is read, keeps the space. */
.salon-package-item {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.salon-package-service {
  flex: 1 1 auto;
  min-width: 0;
}
.salon-package-qty {
  flex: 0 0 4.5rem;
  text-align: center;
}
.salon-package-remove {
  flex: 0 0 auto;
  width: auto;
  padding: 0.5rem 0.7rem;
}
.salon-package-contents {
  margin: 0.2rem 0 0.5rem;
  font-weight: 600;
}

/* What this customer has already paid for, on the bill screen. Green rather
   than grey: it is good news, and it needs to be noticed before the salon
   starts adding services. */
.salon-bill-package-banner {
  margin: 0.4rem 0 0.6rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: var(--card);
}
.salon-bill-package-line {
  margin: 0;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--accent);
}

/* "On Monthly Grooming" beside a bill line. Tappable, because sometimes the
   customer would rather pay cash and keep the credit for next time. */
.salon-bill-basket-package {
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: transparent;
  color: var(--accent);
  font-size: 0.72rem;
  font-weight: 600;
  padding: 0.1rem 0.5rem;
  cursor: pointer;
}

/* "On a package" in the customer book. Deliberately not the same colour as
   the source chip beside it: they answer different questions -- how they
   reached the salon, and what they have already paid for. */
.salon-chip-package {
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 700;
}

/* How the customer paid for a package. Same four options as the till, so a
   salon is not learning a second way to say "cash". */
.salon-pay-methods {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-bottom: 0.6rem;
}
.salon-pay-method {
  flex: 1 1 4.5rem;
  width: auto;
  padding: 0.5rem 0.4rem;
}
.salon-pay-method.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

/* --- Packages and audiences on the Analytics tab (2026-10-13) ------------- */

.salon-ana-packages {
  margin-top: 1.1rem;
}
.salon-ana-row-note {
  font-style: normal;
  font-size: 0.78rem;
  color: var(--muted);
}
.salon-ana-package-list {
  display: grid;
  gap: 0.3rem;
  margin: 0.5rem 0;
}
.salon-ana-package-row {
  display: flex;
  justify-content: space-between;
  gap: 0.6rem;
  font-size: 0.9rem;
}
/* Which services each person actually did. Wraps rather than scrolls, because
   a hairstylist with nine service types is normal and a sideways scrollbar on
   a phone is not. */
.salon-ana-stylist-mix {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.2rem;
}
.salon-ana-mix-chip {
  padding: 0.1rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.76rem;
  color: var(--muted);
}
.salon-ana-mix-chip.is-package {
  border-style: dashed;
}

.salon-ana-audience {
  margin-top: 1.4rem;
}
.salon-ana-ideas {
  display: grid;
  gap: 0.6rem;
  margin: 0.8rem 0;
}
/* The segment lists. Given its own rule rather than leaning on its children's:
   a class sitting in the markup with no style of its own is a class somebody
   later assumes is doing something. A test in test_salon_billing_screen.py
   enforces that, and it caught this one. */
.salon-ana-segments {
  display: grid;
  gap: 0.1rem;
  margin-top: 0.4rem;
}
.salon-audience-idea {
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card);
}
.salon-audience-idea h4 {
  margin: 0 0 0.3rem;
  font-size: 0.95rem;
}
.salon-audience-idea p {
  margin: 0 0 0.5rem;
  font-size: 0.86rem;
  color: var(--muted);
}
.salon-audience-compose {
  margin: 0.8rem 0 1rem;
}
.salon-audience-compose textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 0.55rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card);
  color: var(--text);
  font: inherit;
  resize: vertical;
}
.salon-audience-segment {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.55rem 0.7rem;
  margin-bottom: 0.5rem;
  background: var(--card);
}
.salon-audience-segment > summary {
  cursor: pointer;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: baseline;
  justify-content: space-between;
}
.salon-audience-worth {
  font-size: 0.8rem;
  color: var(--muted);
}
.salon-audience-list {
  display: grid;
  gap: 0.45rem;
  margin-top: 0.5rem;
}
.salon-audience-row {
  display: flex;
  gap: 0.6rem;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  padding-top: 0.45rem;
  border-top: 1px solid var(--border);
}
.salon-audience-who {
  display: grid;
  gap: 0.1rem;
  min-width: 0;
  flex: 1 1 12rem;
}
.salon-audience-facts {
  font-size: 0.78rem;
  color: var(--muted);
}
/* The send button keeps its own size on a phone rather than stretching to the
   full row, which made every list look like a column of buttons. */
/* .salon-whatsapp-link is named explicitly: it is the class that sets the
   full width this has to undo. */
.salon-audience-row .primary-button,
.salon-audience-row a.primary-button,
.salon-audience-row a.salon-whatsapp-link {
  flex: 0 0 auto;
  width: auto;
  min-width: 0;
  padding: 0.35rem 0.9rem;
  font-size: 0.85rem;
}

/* The till's phone row on a phone screen.
   .salon-bill-customer is `repeat(auto-fit, minmax(150px, 1fr))`, so a 390px
   handset still gets TWO columns and the phone field only about 175px. Minus
   the icon, the "+", the country-code box and the padding, that left 32px for
   a ten-digit number -- unusable, and it got worse when the code box stopped
   being an auto-width <select>. Giving the phone its own row takes it to
   217px. Measured in Chrome, not guessed. */
@media (max-width: 700px) {
  .salon-bill-phone-field {
    grid-column: 1 / -1;
  }
}

/* --- Festival offers (2026-10-13) ---------------------------------------- */

.salon-offers { margin-top: 1.6rem; }
.salon-offer-form { display: grid; gap: 0.3rem; margin: 0.8rem 0 1.2rem; }
.salon-offer-form .salon-input { margin-bottom: 0.5rem; }
.salon-offer-form .salon-field-label em {
  font-style: normal;
  font-weight: 400;
  color: var(--muted);
}
/* Two dates side by side: they are one thought ('it runs from X to Y') and
   stacking them made the form look twice as long as it is. */
.salon-offer-dates {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.6rem;
}
.salon-offers-list { display: grid; gap: 0.8rem; }
.salon-offer-card {
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--card);
}
.salon-offer-headline { margin: 0.2rem 0 0; font-weight: 600; }
.salon-offer-chip {
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: 0.74rem;
  font-weight: 600;
}
.salon-offer-chip.is-live { background: #dcfce7; color: #166534; }
.salon-offer-chip.is-soon { background: #fef3c7; color: #92400e; }
.salon-offer-chip.is-done { background: #f1f1f4; color: #5c5c66; }
/* The poster preview is a 9:16 image; capped so a card stays a card and the
   salon taps through to see it full size. */
.salon-offer-preview { display: block; margin: 0.7rem 0; }
.salon-offer-preview img {
  max-height: 260px;
  width: auto;
  border-radius: 10px;
  border: 1px solid var(--border);
}
.salon-offer-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.salon-offer-actions .primary-button,
.salon-offer-actions .secondary-button,
.salon-offer-actions .ghost-button {
  width: auto;
  flex: 0 0 auto;
  padding: 0.4rem 0.9rem;
  font-size: 0.86rem;
}

/* The poster, shown in a panel instead of a bare PNG tab. A new tab has no
   header and no way back to the dashboard -- the founder had to close the
   window (2026-10-13). */
.salon-poster-viewer {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(16, 10, 28, 0.78);
  overflow-y: auto;
}
.salon-poster-panel {
  background: var(--card);
  border-radius: 14px;
  padding: 14px;
  max-width: 420px;
  width: 100%;
  max-height: 92vh;
  overflow-y: auto;
}
.salon-poster-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.6rem;
}
.salon-poster-head .ghost-button { width: auto; flex: 0 0 auto; padding: 0.3rem 0.8rem; }
.salon-poster-panel img {
  width: 100%;
  height: auto;
  border-radius: 10px;
  border: 1px solid var(--border);
}
.salon-poster-foot { margin-top: 0.7rem; }
.salon-poster-foot .primary-button { width: 100%; }

/* Whether customers can see it -- the sentence that was missing. */
.salon-offer-visible { margin: 0.35rem 0 0; font-size: 0.86rem; color: #166534; font-weight: 600; }
.salon-offer-hidden-note { margin: 0.35rem 0 0; font-size: 0.86rem; color: #92400e; font-weight: 600; }
/* The file input inside the upload label is hidden; the label is the button. */
.salon-offer-upload { position: relative; overflow: hidden; }
.salon-offer-upload input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.salon-offer-delete { color: #b91c1c; }
/* The preview is a button now, not a link, so it needs the chrome removed. */
button.salon-offer-preview { display: block; padding: 0; border: 0; background: none; cursor: pointer; }
.salon-audience-bulk { margin: 0.5rem 0 0.2rem; }
.salon-audience-bulk .secondary-button { width: auto; padding: 0.35rem 0.9rem; font-size: 0.85rem; }

/* --- The counter sign-in (2026-10-13) --------------------------------------
   Built for who actually uses it: staff in an unorganised sector, many with
   limited literacy, on a shared tablet at a counter, often in a hurry with a
   customer waiting.

   So: faces and initials rather than a username field, digits rather than
   words, targets big enough for a thumb, and the feedback is colour and
   movement rather than a sentence to read. The whole screen has eleven words
   on it. */

.counter-shade {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px 16px;
  background: linear-gradient(160deg, #2e1065 0%, #4c1d95 55%, #6d28d9 100%);
  overflow-y: auto;
}
.counter-card {
  width: 100%;
  max-width: 420px;
  background: #fff;
  border-radius: 22px;
  padding: 26px 22px 28px;
  box-shadow: 0 24px 60px rgba(20, 8, 46, 0.35);
  text-align: center;
}
.counter-salon {
  margin: 0 0 2px;
  font-size: 1.15rem;
  font-weight: 700;
  color: #18181c;
}
.counter-hint {
  margin: 0 0 18px;
  font-size: 0.9rem;
  color: #5c5c66;
}

/* Who are you. Big tappable faces, name underneath -- recognising your own
   initial and colour is faster than reading a list, and works if you cannot
   read the list at all. */
.counter-people {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(94px, 1fr));
  gap: 14px;
}
.counter-person {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 10px 6px;
  border: 0;
  border-radius: 16px;
  background: transparent;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.12s ease;
}
.counter-person:active { transform: scale(0.95); }
.counter-person:hover { background: #f6f2ff; }
.counter-avatar {
  width: 68px;
  height: 68px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 1.7rem;
  font-weight: 700;
  color: #fff;
  letter-spacing: 0.02em;
}
.counter-person-name {
  font-size: 0.9rem;
  font-weight: 600;
  color: #18181c;
  line-height: 1.2;
}
.counter-person-role {
  font-size: 0.74rem;
  color: #5c5c66;
}

/* The PIN step. */
.counter-back {
  position: absolute;
  left: 18px;
  top: 18px;
  border: 0;
  background: transparent;
  font-size: 1.5rem;
  line-height: 1;
  color: #5c5c66;
  cursor: pointer;
  padding: 6px 10px;
  border-radius: 10px;
}
.counter-back:active { background: #f1f1f4; }
.counter-pin-head { position: relative; padding-top: 6px; }

.counter-dots {
  display: flex;
  justify-content: center;
  gap: 14px;
  margin: 20px 0 22px;
}
.counter-dot {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #e4e4ea;
  transition: background 0.15s ease, transform 0.15s ease;
}
.counter-dot.filled { background: #6d28d9; transform: scale(1.15); }

/* Big digits. A keypad is the one control that needs no reading at all. */
.counter-keys {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.counter-key {
  height: 68px;
  border: 0;
  border-radius: 18px;
  background: #f4f2fa;
  font-size: 1.7rem;
  font-weight: 600;
  color: #18181c;
  cursor: pointer;
  transition: background 0.12s ease, transform 0.1s ease;
}
.counter-key:active { background: #e5defa; transform: scale(0.94); }
.counter-key.ghost { background: transparent; font-size: 1.4rem; color: #5c5c66; }

/* Wrong PIN: the card shakes and the dots go red. No sentence to read, and
   it is the same feedback a phone lock screen gives, which people already
   know. */
@keyframes counter-shake {
  10%, 90% { transform: translateX(-2px); }
  20%, 80% { transform: translateX(4px); }
  30%, 50%, 70% { transform: translateX(-8px); }
  40%, 60% { transform: translateX(8px); }
}
.counter-card.wrong { animation: counter-shake 0.5s ease; }
.counter-card.wrong .counter-dot { background: #dc2626; }
.counter-error {
  margin: 16px 0 0;
  min-height: 1.2em;
  font-size: 0.88rem;
  font-weight: 600;
  color: #b91c1c;
}

/* Who is on the counter right now, in the dashboard header. Also the way
   out: tapping it ends the shift. */
.counter-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 12px 5px 5px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--card);
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
}
.counter-badge-avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 0.78rem;
  font-weight: 700;
  color: #fff;
}

@media (prefers-reduced-motion: reduce) {
  .counter-card.wrong { animation: none; }
  .counter-person:active, .counter-key:active { transform: none; }
}

/* --- The team (2026-10-13) ------------------------------------------------ */

.salon-team { margin-top: 1.6rem; }
.salon-team-list { display: grid; gap: 0.7rem; margin: 0.8rem 0; }
.salon-team-card {
  padding: 0.85rem 1rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--card);
}
.salon-team-head { display: flex; align-items: center; gap: 0.7rem; margin-bottom: 0.5rem; }
.salon-team-avatar { width: 40px; height: 40px; font-size: 1rem; flex: 0 0 auto; }
.salon-counter-host { display: flex; justify-content: flex-end; margin-bottom: 0.5rem; }

/* The tab tick list. Big rows, because this is the screen where an owner
   decides who sees their money -- it should be hard to mis-tap. */
.salon-team-ticks { display: grid; gap: 0.45rem; margin: 0.5rem 0 1rem; }
.salon-team-tick {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
}
.salon-team-tick.on { border-color: #6d28d9; background: #faf5ff; }
.salon-team-tick input { margin-top: 0.2rem; width: 20px; height: 20px; flex: 0 0 auto; }
.salon-team-tick small { color: var(--muted); font-size: 0.78rem; }
/* The ones that show money. Flagged, never blocked -- it is the owner's
   business and their call, but it should never be an accident. */
.salon-team-tick.is-sensitive strong::after {
  content: " \20B9";
  color: #b45309;
  font-weight: 700;
}
.salon-team-tick.is-sensitive.on { border-color: #b45309; background: #fffbeb; }

/* --- All outlets (2026-10-14) --------------------------------------------- */

.salon-chain {
  padding: 1rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: linear-gradient(135deg, #faf5ff 0%, #f5f3ff 100%);
  margin-bottom: 1.2rem;
}
.salon-chain-head { display: grid; gap: 0.1rem; margin-bottom: 0.9rem; }
.salon-chain-outlets { display: grid; gap: 0.85rem; }
/* One row per shop: name, money, bar, then the line an owner reads. The bar
   is what makes seven outlets comparable at a glance. */
.salon-chain-outlet {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.25rem 0.6rem;
  align-items: baseline;
}
.salon-chain-name { font-weight: 600; font-size: 0.95rem; }
.salon-chain-flag {
  font-style: normal;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #7e22ce;
  background: #f3e8ff;
  padding: 0.05rem 0.35rem;
  border-radius: 999px;
}
.salon-chain-amount { font-size: 0.98rem; }
.salon-chain-outlet .salon-ana-bar { grid-column: 1 / -1; }
.salon-chain-detail { grid-column: 1 / -1; font-size: 0.78rem; color: var(--muted); }

/* --- Chain analytics: trend chips and rings (2026-10-14) ------------------ */

.salon-chain-trend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.9rem;
}
.salon-chain-trend-chip {
  font-size: 0.74rem;
  font-weight: 600;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  background: #f3e8ff;
  color: #6b21a8;
  white-space: nowrap;
}
.salon-chain-trend-chip.is-up { background: #dcfce7; color: #166534; }
.salon-chain-trend-chip.is-down { background: #fee2e2; color: #991b1b; }
.salon-chain-trend-chip.is-flat { background: #f1f5f9; color: var(--muted); }

/* Each outlet's own direction of travel and top services. */
.salon-chain-extra {
  grid-column: 1 / -1;
  font-size: 0.74rem;
  color: var(--muted);
}

/* The ring and its legend sit side by side where there is room, and stack on
   a phone -- which is where almost every salon reads this. */
.salon-ana-donut-block {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.9rem 1.2rem;
  margin-bottom: 1rem;
}
.salon-ana-donut {
  width: 132px;
  height: 132px;
  flex: 0 0 auto;
}
.salon-ana-legend {
  display: grid;
  gap: 0.3rem;
  flex: 1 1 200px;
  min-width: 0;
  /* Capped, so the share and the rupees stay beside the name they belong
     to. Left to stretch, a wide screen pushed them to the far edge and the
     eye had to travel the width of the card to pair them up. */
  max-width: 26rem;
}
.salon-ana-legend-row {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: baseline;
  gap: 0.4rem;
  font-size: 0.82rem;
}
.salon-ana-legend-dot {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 3px;
}
/* The name is the part that gives: a long service name must not push the
   share and the rupees off the row. */
.salon-ana-legend-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.salon-ana-legend-share { font-variant-numeric: tabular-nums; }
.salon-ana-legend-amount {
  color: var(--muted);
  font-size: 0.76rem;
  font-variant-numeric: tabular-nums;
}

/* The "show all outlets" row, for a chain too long to read whole. */
.salon-chain-more {
  border: 1px dashed var(--border);
  background: transparent;
  color: #6b21a8;
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0.45rem 0.6rem;
  border-radius: 10px;
  cursor: pointer;
  width: 100%;
}
.salon-chain-more:hover { background: #faf5ff; }

/* Login email + admin login access (2026-10-14) */
.salon-login-email,
.admin-salon-login-access {
  margin: 1.5rem 0;
}

.admin-login-access-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.75rem;
}

/* Same look as the Profile tab's own fields, which are styled only as direct
   children of #salonPanelProfile -- these sit inside a section. */
.salon-login-email input,
.admin-salon-login-access input {
  width: 100%;
  box-sizing: border-box;
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  margin-bottom: 0.75rem;
}

.admin-login-access-row input {
  flex: 1 1 14rem;
  width: auto;
  min-width: 0;
  margin-bottom: 0;
}

/* "Did you mean ...?" -- two real buttons, not a line of plain text. */
.email-suggestion-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.email-suggestion-actions button {
  width: auto;
  margin: 0;
  padding: 0.45rem 0.9rem;
  font-size: 0.9rem;
}

/* The ready-made services menu (2026-10-14) */
.salon-menu-open { margin-bottom: 0.75rem; }
.salon-menu-panel { max-width: 640px; display: flex; flex-direction: column; padding: 0; }
.salon-menu-panel > .salon-poster-head,
.salon-menu-panel > .subtitle,
.salon-menu-panel > .salon-menu-search { margin-left: 14px; margin-right: 14px; }
.salon-menu-panel > .salon-poster-head { margin-top: 14px; }
.salon-menu-search {
  box-sizing: border-box;
  width: calc(100% - 28px);
  padding: 0.55rem 0.75rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  font: inherit;
  margin-bottom: 0.5rem;
}
.salon-menu-body { overflow-y: auto; padding: 0 14px; flex: 1 1 auto; min-height: 0; }
.salon-menu-section { border-top: 1px solid var(--border); }
.salon-menu-section > summary {
  cursor: pointer;
  padding: 0.75rem 0;
  font-weight: 700;
}
.salon-menu-item { padding: 0.2rem 0 0.6rem; }
.salon-menu-item-name { font-weight: 600; margin: 0.3rem 0; }
.salon-menu-hint { font-weight: 400; font-size: 0.85rem; color: var(--muted, #6b7280); }
.salon-menu-options { padding-left: 0.75rem; }
.salon-menu-line {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.3rem 0.4rem;
  border-radius: 8px;
}
.salon-menu-line.is-ticked { background: rgba(124, 58, 237, 0.07); }
.salon-menu-line > span { flex: 1 1 auto; min-width: 0; }
.salon-menu-line input[type="checkbox"] { width: 1.15rem; height: 1.15rem; flex: 0 0 auto; }
.salon-menu-price {
  flex: 0 0 7.5rem;
  width: 7.5rem;
  box-sizing: border-box;
  padding: 0.4rem 0.55rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  font: inherit;
}
.salon-menu-price:disabled { background: transparent; opacity: 0.45; }
.salon-menu-line.needs-price .salon-menu-price { border-color: #f59e0b; }
.salon-menu-already { font-size: 0.82rem; color: #166534; font-weight: 600; }
.salon-menu-own { display: flex; gap: 0.5rem; padding: 0.3rem 0 0 0.75rem; }
.salon-menu-own input {
  flex: 1 1 auto;
  min-width: 0;
  box-sizing: border-box;
  padding: 0.4rem 0.55rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  font: inherit;
}
.salon-menu-own .secondary-button { width: auto; flex: 0 0 auto; padding: 0.4rem 0.9rem; margin: 0; }
.salon-menu-foot {
  border-top: 1px solid var(--border);
  padding: 10px 14px 14px;
  background: var(--card);
}
.salon-menu-status { margin: 0 0 0.5rem; font-size: 0.9rem; }
.salon-menu-foot .primary-button { width: 100%; }

/* The company behind the site (2026-10-14): Meta business verification
   matches the legal name on the incorporation certificate against the site. */
.legal-entity {
  margin: 0.4rem 0 0;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* On wide screens the landing footer is a row; the company line takes a row
   of its own underneath instead of squeezing between copyright and links. */
@media (min-width: 640px) {
  .landing-footer-inner { flex-wrap: wrap; }
  .landing-footer-inner .legal-entity { order: 3; flex-basis: 100%; }
}

/* Owner unlock with a password on the counter screen (2026-10-16). */
.counter-password {
  width: 100%;
  box-sizing: border-box;
  margin: 12px 0;
  padding: 0.7rem 0.9rem;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.95);
  color: #13161c;
  font: inherit;
}

/* =============================================================================
   Log in and sign up, 2026-10-16.

   One card, at most 440px, centred on a laptop and edge to edge on a phone.
   Fields are full width, 48px tall and 16px text -- below 16px iOS zooms the
   page on focus. The first choice is WHO is logging in (Customer / Salon /
   Staff), so nobody lands on the wrong form.
   ========================================================================== */
#authScreen,
#businessSignupScreen,
#forgotPasswordScreen,
#resetPasswordScreen {
  max-width: 440px;
  margin-left: auto;
  margin-right: auto;
}

.auth-head {
  margin-bottom: 1.1rem;
}

.auth-head h2,
#businessSignupScreen h2,
#forgotPasswordScreen h2,
#resetPasswordScreen h2 {
  font-size: clamp(1.35rem, 1.1rem + 1.2vw, 1.65rem);
  line-height: 1.2;
  margin: 0 0 0.35rem;
  text-wrap: balance;
}

.auth-sub {
  margin: 0;
  color: var(--muted);
  font-size: 0.95rem;
}

.auth-roles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;
  padding: 4px;
  margin-bottom: 1.25rem;
  border-radius: 12px;
  background: var(--landing-surface-muted, #f3eef6);
  border: 1px solid var(--border);
}

/* Business login's two tabs share the full width. */
#authBizTabs {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.auth-role {
  white-space: nowrap;
  min-height: 44px;
  border: none;
  border-radius: 9px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-weight: 600;
  font-size: 0.95rem;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;
}

.auth-role:hover {
  color: var(--text);
}

.auth-role.active {
  background: var(--panel);
  color: var(--landing-primary, #4c145c);
  box-shadow: 0 1px 3px rgba(19, 22, 28, 0.12);
}

.auth-role:focus-visible {
  outline: 2px solid var(--landing-primary, #4c145c);
  outline-offset: 2px;
}

/* Every field on these screens, the salon sign-up's included -- those used to
   render as the browser's own tiny boxes. */
#authScreen input:not([type="checkbox"]),
#businessSignupScreen input:not([type="checkbox"]),
#forgotPasswordScreen input,
#resetPasswordScreen input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  min-height: 48px;
  padding: 0.7rem 0.85rem;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 16px;
  margin-bottom: 0.9rem;
}

#businessSignupScreen input:not([type="checkbox"]):focus {
  outline: none;
  border-color: var(--landing-primary, #4c145c);
  box-shadow: 0 0 0 3px rgba(76, 20, 92, 0.15);
}

#authScreen .reference-label,
#businessSignupScreen .reference-label {
  color: var(--text);
  font-size: 0.9rem;
}

#authScreen .primary-button,
#businessSignupScreen .primary-button {
  width: 100%;
  min-height: 48px;
  font-size: 1rem;
  border-radius: 10px;
}

#authScreen #forgotPasswordLink {
  display: block;
  width: fit-content;
  margin: -0.35rem 0 1rem auto;
  font-size: 0.9rem;
}

.auth-google {
  margin-top: 0.5rem;
}

/* Log in / Sign up moved to the link under the form; the old tab row stays in
   the page only because the code addresses its buttons by id. */
#authScreen .capture-tabs {
  display: none;
}

.auth-google .auth-divider {
  margin: 0.75rem 0 1rem;
}

.auth-google .google-signin-wrap {
  margin-bottom: 0;
}

/* No Google sign-in configured: no "or" and no consent box for it. */
.auth-google:has(#googleSignInWrap.hidden) .auth-divider,
.auth-google:has(#googleSignInWrap.hidden) #authConsentField {
  display: none;
}

.auth-foot {
  margin: 1.25rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
  text-align: center;
  font-size: 0.95rem;
  color: var(--muted);
}

.auth-foot a {
  color: var(--landing-primary, #4c145c);
  font-weight: 600;
}

#authScreen .consent-field a,
#businessSignupScreen .consent-field a {
  color: var(--landing-primary, #4c145c);
  font-weight: 600;
}

.auth-foot-line {
  display: block;
  margin-top: 0.5rem;
}

.auth-foot-quiet {
  margin-top: 0.5rem;
  padding-top: 0;
  border-top: none;
}

.auth-foot-top {
  margin: 0 0 1rem;
  padding: 0 0 1rem;
  border-top: none;
  border-bottom: 1px solid var(--border);
}

#businessSignupScreen .consent-field {
  font-size: 0.9rem;
}

/* A quieter header while logging in: it does not pin to the top (it covered
   the card on a phone), and its two login links go -- the card's tabs do
   that job, and on a phone the links stacked into two rows. */
body.auth-flow .app-header,
body.studio-active.auth-flow .app-header {
  position: static;
}

body.auth-flow #authStatus {
  display: none;
}

body.auth-flow .app-header {
  padding-bottom: 0.75rem;
  margin-bottom: 1rem;
}

/* The white card hugs the form instead of spanning a tablet. */
body.auth-flow .panel {
  max-width: 520px;
  margin-left: auto;
  margin-right: auto;
}

/* Phones: less header above the form, and tabs that fit at 320px. */
@media (max-width: 480px) {
  body.auth-flow .brand-tagline-img {
    display: none;
  }

  .auth-role {
    font-size: 0.875rem;
    padding: 0 0.25rem;
  }
}

/* Salon sign-up on phones: a short heading and one line instead of the long
   pitch, so the form starts on the first screen (2026-10-16). */
.biz-signup-short {
  display: none;
}

@media (max-width: 480px) {
  #businessSignupScreen .biz-signup-short {
    display: block;
  }

  #businessSignupHeadingLegacy,
  #businessSignupHeadingOs,
  #businessSignupSubcopyLegacy,
  #businessSignupSubcopyOs {
    display: none !important;
  }
}

/* =============================================================================
   Salon dashboard on phones, 2026-10-16. "they are mostly on phones" (founder).

   Measured at 320 / 360 / 390px before this: twelve tabs in five rows above
   every screen, service tiles one per row (80 services = six screens), stat
   tiles one per row, 13px inputs (phones zoom on tap), 20px-tall "Send bill"
   links, and the Share tab scrolling sideways at 320px.
   ========================================================================== */
.salon-bottom-nav {
  display: none;
}

@media (max-width: 640px) {
  body.salon-dash-active #salonDashboardScreen .salon-tabs {
    display: none !important;
  }

  body.salon-dash-active .salon-bottom-nav {
    display: grid;
    grid-template-columns: repeat(var(--salon-nav-count, 5), minmax(0, 1fr));
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    /* Above the More sheet (950), so the bar stays put while it is open. */
    z-index: 960;
    padding: 4px 4px calc(4px + env(safe-area-inset-bottom, 0px));
    background: var(--panel);
    border-top: 1px solid var(--border);
    box-shadow: 0 -4px 16px rgba(19, 22, 28, 0.08);
  }

  body.salon-dash-active {
    padding-bottom: calc(68px + env(safe-area-inset-bottom, 0px));
  }

  /* Edge to edge: the card-in-a-card cost a third of a 320px screen. */
  body.salon-dash-active .panel {
    padding: 0.9rem 0.75rem;
    border-radius: 12px;
  }

  /* Two service tiles per row on every phone. */
  #salonDashboardScreen .salon-bill-tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }

  /* Stat tiles two per row instead of a column of zeros. */
  #salonDashboardScreen .salon-summary-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.5rem;
  }

  /* 16px or the phone zooms in when a field is tapped. */
  #salonDashboardScreen input:not([type="checkbox"]):not([type="radio"]),
  #salonDashboardScreen select,
  #salonDashboardScreen textarea {
    font-size: 16px;
  }

  /* Every dropdown and button on the dashboard is at least thumb height
     (filters, the analytics period, "Work out my lists"). */
  #salonDashboardScreen select,
  #salonDashboardScreen button.secondary-button,
  #salonDashboardScreen button.primary-button,
  #salonDashboardScreen button.ghost-button {
    min-height: 44px;
  }

  /* Filters: two per row, full width, styled like every other field
     instead of the browser's default box. */
  #salonDashboardScreen .salon-filter-row {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.5rem 0.6rem;
  }

  /* Each filter is a one-column grid; let that column shrink, or a
     dropdown's natural width pushes the row past the screen. */
  #salonDashboardScreen .salon-filter {
    grid-template-columns: minmax(0, 1fr);
    /* The laptop console gives each filter a 180px minimum
       (body.salon-console-active .salon-filter); on a phone that pushed the
       second column off the screen. */
    min-width: 0;
    flex: none;
  }

  #salonDashboardScreen .salon-filter select,
  #salonDashboardScreen .salon-filter input {
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
    padding: 0.55rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--panel);
    color: var(--text);
  }

  /* On/off switches: a bigger hit area without changing how they look. */
  #salonDashboardScreen input[type="checkbox"] {
    min-width: 22px;
    min-height: 22px;
  }

  /* Thumb-sized actions on a bill. */
  #salonDashboardScreen .salon-bill-row-send,
  #salonDashboardScreen .salon-bill-row-cancel {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* A customer or lead card's actions sit side by side, not three
     full-width buttons stacked under every name. */
  #salonDashboardScreen .salon-lead-actions > button,
  #salonDashboardScreen .salon-lead-actions > a {
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
    padding-left: 0.6rem;
    padding-right: 0.6rem;
    font-size: 0.9rem;
  }

  #salonDashboardScreen .salon-lead-card {
    padding: 0.85rem 0.9rem 0.9rem;
  }
}

/* The offer's two dates stacked on the smallest phones: side by side they
   pushed the Share tab wider than a 320px screen. */
@media (max-width: 380px) {
  #salonDashboardScreen .salon-offer-dates {
    grid-template-columns: minmax(0, 1fr);
  }

  #salonDashboardScreen .salon-offer-dates input {
    min-width: 0;
    width: 100%;
  }
}

.salon-nav-item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 54px;
  padding: 4px 2px;
  border: none;
  border-radius: 10px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
}

.salon-nav-icon {
  font-size: 20px;
  line-height: 1.2;
}

.salon-nav-label {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.salon-nav-item.active {
  color: var(--landing-primary, #4c145c);
  background: rgba(76, 20, 92, 0.08);
}

.salon-nav-item.has-badge::after,
.salon-more-item.has-badge::after {
  content: "";
  position: absolute;
  top: 6px;
  right: calc(50% - 18px);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #dc2626;
  border: 2px solid var(--panel);
}

.salon-nav-item:focus-visible,
.salon-more-item:focus-visible {
  outline: 2px solid var(--landing-primary, #4c145c);
  outline-offset: 2px;
}

.salon-more-shade {
  position: fixed;
  inset: 0;
  z-index: 950;
  display: flex;
  align-items: flex-end;
  background: rgba(16, 10, 28, 0.45);
}

.salon-more-sheet {
  width: 100%;
  max-height: 80vh;
  overflow-y: auto;
  padding: 16px 12px calc(84px + env(safe-area-inset-bottom, 0px));
  border-radius: 16px 16px 0 0;
  background: var(--panel);
}

.salon-more-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.salon-more-item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-height: 76px;
  padding: 10px 4px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.salon-more-item.active {
  border-color: var(--landing-primary, #4c145c);
  color: var(--landing-primary, #4c145c);
}

/* The running bill above the bottom bar, phones only (2026-10-16). */
.salon-bill-tray {
  display: none;
}

@media (max-width: 640px) {
  body.salon-dash-active .salon-bill-tray:not(.hidden) {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    position: fixed;
    left: 8px;
    right: 8px;
    bottom: calc(66px + env(safe-area-inset-bottom, 0px));
    z-index: 955;
    padding: 8px 8px 8px 14px;
    border-radius: 14px;
    background: var(--landing-primary, #4c145c);
    color: #ffffff;
    box-shadow: 0 6px 20px rgba(19, 22, 28, 0.25);
  }

  .salon-bill-tray-summary {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .salon-bill-tray-go {
    flex: 0 0 auto;
    min-height: 44px;
    padding: 0 14px;
    border: none;
    border-radius: 10px;
    background: #ffffff;
    color: var(--landing-primary, #4c145c);
    font: inherit;
    font-weight: 700;
    cursor: pointer;
  }

  /* Typing: the keyboard has the bottom of the screen. */
  body.salon-typing .salon-bottom-nav,
  body.salon-typing .salon-bill-tray {
    display: none !important;
  }

  body.salon-typing.salon-dash-active {
    padding-bottom: 0;
  }
}

/* --- "Who is this?" customer suggestions (2026-10-16) ----------------------
   In the page flow, under the phone/name boxes, not floating: the package
   sale opens in a modal whose body would clip a dropdown. */
.salon-suggest {
  display: grid;
  margin: 6px 0 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.salon-suggest-row {
  display: grid;
  gap: 2px;
  width: 100%;
  min-height: 48px;
  padding: 8px 12px;
  border: 0;
  border-top: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.salon-suggest-row:first-child {
  border-top: 0;
}

.salon-suggest-row:hover,
.salon-suggest-row:focus-visible {
  background: var(--landing-surface);
}

.salon-suggest-row strong {
  font-size: 1rem;
}

.salon-suggest-row span {
  color: var(--muted);
  font-size: 0.85rem;
}

.salon-pick-contact {
  width: auto;
  margin: 8px 0 0;
  min-height: 44px;
}

.salon-contacts-card {
  margin: 0.6rem 0 1rem;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
}

.salon-contacts-card h3 {
  margin: 0 0 4px;
  font-size: 1rem;
}

.salon-contacts-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.salon-contacts-actions .secondary-button {
  width: auto;
  min-height: 44px;
}

.salon-contacts-remove {
  color: var(--muted);
}

/* Contacts file import (2026-10-17): a <label> styled as the button, so the
   native file input does the opening on every browser (iPad included). */
.salon-contacts-file {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  min-height: 44px;
}

.visually-hidden-file {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  overflow: hidden;
  pointer-events: none;
}

.salon-contact-note {
  margin: 6px 0 0;
}

.salon-contacts-help {
  margin-top: 10px;
  font-size: 0.9rem;
  color: var(--muted);
}

.salon-contacts-help summary {
  cursor: pointer;
  color: var(--accent);
  font-weight: 600;
  min-height: 32px;
}

.salon-contacts-help p {
  margin: 6px 0;
}

.salon-contacts-actions .salon-contact-note {
  flex-basis: 100%;
  order: 10;
}

/* Booking alerts banner on the salon dashboard (shown until alerts are on). */
.salon-alerts-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  margin: 8px 0 12px;
  padding: 12px 14px;
  border: 1px solid #f2c97d;
  background: #fff7e6;
  border-radius: var(--radius, 10px);
}

.salon-alerts-banner.hidden {
  display: none;
}

.salon-alerts-text {
  flex: 1 1 220px;
  min-width: 0;
  margin: 0;
  font-size: 0.92rem;
  color: #5c3b00;
}

/* --- Founder engagement console (admin "Engagement" tab, 2026-10-26) ---
   Light theme only, like everything else (see :root). Tables scroll inside
   their own .admin-users-wrap so a phone never scrolls the whole page
   sideways. */

.engage-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.engage-head h2 {
  margin: 0;
}

/* .secondary-button is full width by default; here it sits beside the title. */
.engage-head .secondary-button {
  width: auto;
  flex: 0 0 auto;
}

.engage-error {
  color: var(--error);
  font-weight: 600;
}

.engage-error.hidden,
.engage-table-wrap.hidden {
  display: none;
}

.engage-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
  gap: 0.75rem;
  margin: 1rem 0 1.5rem;
}

.engage-tile {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}

.engage-tile strong {
  font-size: 1.45rem;
  letter-spacing: -0.01em;
  color: var(--text);
}

.engage-tile span {
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.3;
}

.engage-tile-warn {
  border-color: #f2c46d;
  background: #fff7e6;
}

.engage-tile-bad {
  border-color: #f3a9a9;
  background: #fdf0f0;
}

.engage-tile-bad strong {
  color: #b42318;
}

.engage-style {
  margin-bottom: 0.5rem;
}

.engage-style .engage-tiles {
  margin-top: 0.5rem;
}

.engage-tiles-small .engage-tile strong {
  font-size: 1.2rem;
}

.engage-heading {
  margin: 1.75rem 0 0.5rem;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.engage-count,
.engage-hint {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--muted);
}

p.engage-hint {
  margin: 0.5rem 0 0;
}

.engage-alerts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.engage-alert {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem 0.75rem;
  flex-wrap: wrap;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--border);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--panel);
}

.engage-alert-critical {
  border-left-color: #d92d20;
  background: #fef6f5;
}

.engage-alert-warning {
  border-left-color: #f79009;
  background: #fffbf2;
}

.engage-alert-badge {
  flex: 0 0 auto;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  background: #fde7c3;
  color: #7a4100;
}

.engage-alert-critical .engage-alert-badge {
  background: #fcd9d6;
  color: #912018;
}

.engage-alert-body {
  flex: 1 1 220px;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2rem 0.5rem;
}

.engage-alert-body strong {
  overflow-wrap: anywhere;
}

.engage-alert-type {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.engage-alert-msg {
  flex-basis: 100%;
  font-size: 0.9rem;
  color: var(--text);
}

.engage-alert-since {
  flex: 0 0 auto;
  margin-left: auto;
  font-size: 0.8rem;
  color: var(--muted);
  white-space: nowrap;
}

.engage-alerts .engage-more {
  width: auto;
}

.engage-table th[data-sort] {
  cursor: pointer;
  user-select: none;
}

.engage-table th[data-sort]:hover,
.engage-table th.engage-sorted {
  color: var(--accent);
}

.engage-table th.engage-sorted[data-dir="asc"]::after {
  content: " \2191";
}

.engage-table th.engage-sorted[data-dir="desc"]::after {
  content: " \2193";
}

.engage-row {
  cursor: pointer;
}

.engage-row:hover,
.engage-row:focus-visible,
.engage-row-open {
  background: var(--bg);
  outline: none;
}

.engage-name {
  font-weight: 600;
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.engage-stage {
  display: inline-block;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  background: #eef0f5;
  color: var(--muted);
}

.engage-stage-set_up,
.engage-stage-first_bill {
  background: #eaf2ff;
  color: #1d4ed8;
}

.engage-stage-weekly {
  background: #e7f6ec;
  color: #15803d;
}

.engage-stage-paying {
  background: #f3e8f7;
  color: var(--accent);
}

.engage-health {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}

.engage-health-track {
  display: inline-block;
  width: 64px;
  height: 8px;
  border-radius: 999px;
  background: #eceef3;
  overflow: hidden;
}

.engage-health-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: #d92d20;
}

.engage-health-fair .engage-health-fill {
  background: #f79009;
}

.engage-health-good .engage-health-fill {
  background: #16a34a;
}

.engage-health-num {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  min-width: 1.6em;
}

.engage-chips {
  white-space: normal;
  min-width: 160px;
}

.engage-chip {
  display: inline-block;
  margin: 0.1rem 0.25rem 0.1rem 0;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  background: #eef0f5;
  color: var(--text);
  white-space: nowrap;
}

.engage-chip-good {
  background: #e7f6ec;
  color: #15803d;
}

.engage-chip-warn {
  background: #fff1d6;
  color: #7a4100;
}

.engage-chip-bad {
  background: #fcd9d6;
  color: #912018;
}

.engage-muted {
  color: var(--muted);
  font-size: 0.8rem;
}

.engage-good {
  color: #15803d;
  font-weight: 600;
}

.engage-bad {
  color: #b42318;
  font-weight: 600;
}

.engage-detail td {
  background: var(--bg);
  white-space: normal;
}

.engage-detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 0.6rem 1.25rem;
  margin: 0.25rem 0;
  /* The detail row spans the whole table, which may be wider than the
     screen; renderEngageSalons sets this panel's width to the visible part
     of the table, and sticky keeps it in view while the table scrolls. */
  max-width: calc(100vw - 4rem);
  position: sticky;
  left: 0.6rem;
}

.engage-detail-grid div {
  min-width: 0;
}

.engage-detail-grid dt {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.engage-detail-grid dd {
  margin: 0.1rem 0 0;
  font-size: 0.88rem;
  overflow-wrap: anywhere;
}

@media (max-width: 720px) {
  .engage-tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .engage-tile strong {
    font-size: 1.2rem;
  }

  .engage-alert-since {
    margin-left: 0;
  }

  /* On a phone the first screenful of the table must say who, what stage and
     how healthy: the city moves under the name, and the name wraps. */
  .engage-table .engage-city {
    display: none;
  }

  .engage-table .engage-name-city {
    display: block;
  }

  .engage-table .engage-name {
    min-width: 120px;
    max-width: 150px;
    white-space: normal;
  }

  .engage-health-track {
    width: 44px;
  }
}

.engage-name-city {
  display: none;
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--muted);
}

