/* ===== ARMBOLT design tokens — "Jobsite Aurora" 2026 =====
   Dual-axis palette:
   • Commit axis (warm amber → ember) — human actions, money, send/save/share.
   • Trust axis (periwinkle → violet) — AI surfaces, generated content,
     intelligent affordances. Keeps the tradesman warmth without flooding
     the chrome with a single hot-yellow signal. */
:root {
  /* True black — matches iOS OLED so the app surface blends with the
     status bar, home indicator and bezel without a visible seam. */
  --bg: #000000;
  --bg-deep: #000000;
  --surface: #171A21;
  --surface-raised: rgba(23, 26, 33, 0.72);

  /* Commit axis — unified to the brand green. The commit/CTA axis (money,
     send/save/share, primary buttons) now shares the brand kelly-green so
     the whole UI speaks one accent instead of an amber/green split. Deep +
     gradient end stay a notch darker to keep button depth. */
  --primary: #4E8D76;
  --primary-deep: #3C6E5C;
  --primary-soft: rgba(78, 141, 118, 0.12);
  --primary-grad: linear-gradient(135deg, #4E8D76 0%, #3C6E5C 100%);

  /* AI axis — previously periwinkle → violet, now points at the lime
     "magic" palette so every AI-touched surface (sparkle, assessment
     label, generated-image halo, AI status, placeholder bubble,
     way-off CTA) lives in one consistent accent family. Token name
     kept (`--ai-*`) so call sites don't have to be renamed; only the
     values were re-mapped. If you ever want blue back for "trust"
     surfaces, override at the call site. */
  --ai: var(--magic-deep);       /* readable lime — for ink / text */
  --ai-deep: var(--magic);        /* loud lime — for halos / fills */
  --ai-soft: var(--magic-soft);   /* whisper lime — for tonal fills */
  --ai-grad: var(--magic-grad);   /* lime gradient for hero halos */

  /* Magic axis — electric kelly-green. Shifted from yellow-lime
     (hue ~73°) toward Spotify-green (hue ~141°) and parked at ~111°
     — distinctly greener than the old #C8FF00 lime but brighter and
     more saturated than Spotify so the brand still owns "AI surprise"
     rather than "music app". Reserved for the *moment* of generative
     payoff: the after-reveal hero, the AI render badge, the assistant
     bubble, the "wow" beat. Deliberately loud — survives video
     compression and reads as "2026 AI consumer". Do NOT use this for
     money, confirm CTAs, or general primary actions — amber owns the
     commit/money axis. Green owns surprise. */
  --magic: #4E8D76;
  --magic-deep: #3C6E5C;
  --magic-soft: rgba(78, 141, 118, 0.14);
  --magic-grad: linear-gradient(135deg, #74B49C 0%, #4E8D76 60%, #3C6E5C 100%);
  --magic-glow: 0 8px 28px rgba(78, 141, 118, 0.35);

  --success: #3DDC97;
  --text: #F4F6FA;
  --muted: #8A93A6;
  --border: rgba(255, 255, 255, 0.07);
  --border-strong: rgba(255, 255, 255, 0.14);
  --border-ai: rgba(78, 141, 118, 0.35);
  --pill-bg: rgba(255, 255, 255, 0.05);
  --pill-border: rgba(255, 255, 255, 0.10);
  --error: #EA580C;
  --error-text: #FF6B6B;

  /* Lighting recipe — modern dark UIs use shadow + inset highlight to
     create the "edge-lit glass" feel. */
  --shadow-card: 0 8px 24px -12px rgba(0, 0, 0, 0.6),
                 inset 0 1px 0 rgba(255, 255, 255, 0.06);
  --shadow-glow-primary: 0 8px 32px -8px rgba(78, 141, 118, 0.45);
  --shadow-glow-ai: 0 8px 32px -8px rgba(124, 155, 255, 0.45);

  --radius-card: 14px;
  --radius-pill: 999px;
  --radius-bubble: 18px;

  --ease: cubic-bezier(.2, .8, .2, 1);
  /* CSS spring easing — settles instead of glides. Falls back to --ease
     in browsers without linear() support. */
  --ease-spring: linear(0, 0.402 7.4%, 0.711 15.1%, 0.929 23.2%,
                       1.06 31.9%, 1.097 38.2%, 1.092 44.6%,
                       1.054 55.3%, 1.001 71%, 0.991 84.9%, 1);
  --t-fast: 150ms;
  --t-mid: 220ms;

  /* SF Pro carries ALL UI text (body + headings). On Apple devices
     -apple-system/BlinkMacSystemFont already resolve to SF Pro; the
     explicit "SF Pro Text"/"SF Pro Display" names make the intent
     clear on non-Apple browsers that have the family installed, with
     Inter as the cross-platform fallback (preserves æøå legibility). */
  --font: "SF Pro Text", "SF Pro", -apple-system, BlinkMacSystemFont,
          "Inter", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: "SF Pro Display", "SF Pro", -apple-system,
                  BlinkMacSystemFont, "Inter", "Segoe UI", sans-serif;
  /* SF Mono is reserved ONLY for numbers (prices, quantities, counts,
     codes) — apply via the var(--font-mono) call sites, never as the
     global body font. */
  --font-mono: "SF Mono", ui-monospace, "JetBrains Mono", "Cascadia Mono",
               Menlo, Consolas, monospace;

  --phone-w: 430px;
  --phone-h: 932px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0;
  background: #000;
  color: var(--text);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}

body {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Ambient backdrop — a soft periwinkle bloom at the top fading into
     deep graphite. Replaces the old flat radial that bottomed out at
     pure black and made the phone frame feel cut-out. */
  background:
    radial-gradient(ellipse 80% 60% at 50% -10%, rgba(124, 155, 255, 0.10) 0%, transparent 60%),
    radial-gradient(ellipse 90% 80% at 50% 110%, rgba(78, 141, 118, 0.05) 0%, transparent 60%),
    radial-gradient(ellipse at top, #15171F 0%, #050610 60%);
  letter-spacing: -0.01em;
}

/* ===== Phone container =====
   `viewport-fit=cover` + `apple-mobile-web-app-status-bar-style=black-translucent`
   means the page paints under the iOS status bar (time, Wi-Fi, Dynamic
   Island). Without compensating safe-area padding the chat header buttons
   render *behind* the system chrome.

   We pad the phone container itself with the top inset so every screen
   inside (chat, splash, location, etc.) shifts down below the notch in one
   place. The phone background (`--bg`, deep graphite) blends with the
   translucent dark chat-header gradient, so the inset reads as part of the
   chrome rather than as a black bar.

   Height uses `100dvh` (dynamic viewport) on browsers that support it so
   the bottom composer isn't cropped by Safari's collapsing toolbar; falls
   back to `100vh` everywhere else. */
.phone {
  position: relative;
  width: 100vw;
  height: 100vh;            /* fallback for browsers without dvh */
  height: 100dvh;
  max-width: var(--phone-w);
  max-height: var(--phone-h);
  background: var(--bg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  isolation: isolate;
  padding-top: env(safe-area-inset-top, 0px);
  /* NOTE: bottom safe-area is handled INSIDE individual elements that touch
     the bottom edge (`.composer`, identity form, review-sheet footer, etc.
     — they all use `calc(N + env(safe-area-inset-bottom))`). Adding it here
     too would double-pad and float the composer above the home indicator. */
}

@media (min-width: 480px) and (min-height: 950px) {
  .phone {
    /* Softened desktop chrome: dropped the heavy 12px+13px dual bezel
       (a 2018 "phone-frame demo" aesthetic). Now reads as a floating
       hairline-bordered card on an ambient backdrop. */
    border-radius: 36px;
    box-shadow:
      inset 0 0 0 1px rgba(255, 255, 255, 0.05),
      0 30px 80px -20px rgba(0, 0, 0, 0.85),
      0 0 160px -40px rgba(124, 155, 255, 0.20);
  }
}

/* ===== Status bar / AI status pill =====
   The status bar used to host an empty fake-iOS chrome strip. In the 2026
   refresh it became real estate for the contextual AI state pill: a
   centered Liquid Glass capsule with a coloured dot + short label that
   tells the user what the agent is doing (idle, thinking, analyzing,
   rendering, writing). Idle uses amber, busy states use periwinkle and
   the dot animates with a soft AI pulse. */
.status-bar {
  /* The "Klar / Analyserer… / Tegner… / Skriver…" AI status pill was
     interesting in dev — telegraphing what the agent was doing in
     real time — but in production it read as stray chrome floating
     above an otherwise calm chat. Hidden globally. The aiStatus helper
     in app.js still mutates this node (cheaper than ripping out every
     call site), it just never paints. */
  display: none;
}
.sb-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 26px;
  padding: 0 12px 0 10px;
  border-radius: 999px;
  background: rgba(23, 26, 33, 0.55);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
  border: 1px solid var(--border);
  box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.6),
              inset 0 1px 0 rgba(255, 255, 255, 0.05);
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text);
  white-space: nowrap;
  transition: border-color var(--t-mid) var(--ease), color var(--t-mid) var(--ease);
}
.sb-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--primary);
  box-shadow: 0 0 8px rgba(78, 141, 118, 0.55);
  flex-shrink: 0;
  transition: background var(--t-mid) var(--ease), box-shadow var(--t-mid) var(--ease);
}
.sb-label {
  font-variant-numeric: tabular-nums;
  /* Subtle cross-fade between state labels */
  transition: opacity 180ms var(--ease);
}
.sb-pill.is-busy {
  border-color: var(--border-ai);
  color: var(--ai);
}
.sb-pill.is-busy .sb-dot {
  background: var(--ai);
  box-shadow: 0 0 10px rgba(124, 155, 255, 0.7);
  animation: sbDotPulse 1.4s ease-in-out infinite;
}
@keyframes sbDotPulse {
  0%, 100% { transform: scale(1);    box-shadow: 0 0 8px  rgba(124, 155, 255, 0.55); }
  50%      { transform: scale(1.35); box-shadow: 0 0 14px rgba(124, 155, 255, 0.85); }
}
/* Fade-out helper used by aiStatus.set() so the label swap doesn't pop */
.sb-pill.is-swapping .sb-label { opacity: 0; }

/* ===== Home indicator =====
   Retired in the 2026 refresh — mimicking the iOS home pill inside a web
   app reads as a costume rather than as system chrome (real iOS PWAs
   render the actual indicator over it). The selector is kept so any
   stray DOM node collapses to zero footprint. */
.home-indicator { display: none; }

/* ===== Screens ===== */
.screens {
  position: relative;
  flex: 1;
  overflow: hidden;
}
.screen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  /* Modern dark-UI transition: scale + fade in/out instead of slide-left.
     Slide is the most dated motion pattern in the app; a subtle scale
     conveys hierarchy (forward = grow, back = recede) like visionOS
     and modern iOS sheets. */
  opacity: 0;
  transform: scale(0.98);
  pointer-events: none;
  transition:
    transform 320ms var(--ease-spring, var(--ease)),
    opacity   280ms var(--ease);
}
.screen.active {
  transform: scale(1);
  opacity: 1;
  pointer-events: auto;
}
.screen.exit-left {
  transform: scale(0.96);
  opacity: 0;
}
.screen.enter-from-left {
  transform: scale(1.02);
  opacity: 0;
}

/* ===== Skeleton splash (replaces old .splash) ===== */
.skel-chat {
  flex: 1;
  display: flex;
  flex-direction: column;
  /* Match the chat scroll surface (blueprint navy) so the splash blends
     seamlessly into the chat screen on dissolve. */
  background: #0B1F2E;
  padding: 0;
  position: relative; /* anchor the hero overlay */
  /* Fast dissolve when transitioning to real screen */
  transition: opacity 180ms var(--ease);
}

/* ── Hero brand overlay — 2026 monogram ──
   The illustrated tradesperson PNG was retired (anchored the brand to a
   mid-2010s contractor-SaaS aesthetic). Replaced with an animated SVG
   monogram: a periwinkle glow ring drawn by stroke-dashoffset, an amber
   lightning bolt centered inside, and the wordmark below in Inter Tight.
   Sits transparent over the skeleton so the chat shape still telegraphs
   "this is a chat app" while loading. */
.skel-hero {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  background: transparent;
  pointer-events: none;
  animation: heroIn 320ms var(--ease) both;
}
.skel-hero-mark {
  position: relative;
  width: 132px;
  height: 132px;
  display: flex;
  align-items: center;
  justify-content: center;
  filter: drop-shadow(0 12px 40px rgba(124, 155, 255, 0.35))
          drop-shadow(0 4px 18px rgba(78, 141, 118, 0.25));
}
.skel-hero-mark svg {
  width: 100%;
  height: 100%;
  overflow: visible;
}
/* The ring is drawn once on entry, then sits as a steady halo. */
.skel-hero-mark .ring {
  stroke-dasharray: 360;
  stroke-dashoffset: 360;
  animation: ringDraw 900ms cubic-bezier(.6, .05, .25, 1) 80ms forwards;
}
@keyframes ringDraw {
  to { stroke-dashoffset: 0; }
}
/* The bolt fades+scales in just behind the closing ring, so the eye
   reads: arc traces → bolt settles in the center. */
.skel-hero-mark .bolt {
  transform-origin: center;
  transform-box: fill-box;
  opacity: 0;
  animation: boltIn 420ms var(--ease-spring, var(--ease)) 700ms forwards;
}
@keyframes boltIn {
  from { opacity: 0; transform: scale(0.6); }
  to   { opacity: 1; transform: scale(1); }
}
/* Slow ambient pulse on the ring after entry — keeps the mark "alive"
   during the brief splash hold without being distracting. */
.skel-hero-mark .ring-glow {
  transform-origin: center;
  transform-box: fill-box;
  animation: ringPulse 2.6s ease-in-out 1200ms infinite;
}
@keyframes ringPulse {
  0%, 100% { opacity: 0.35; transform: scale(1); }
  50%      { opacity: 0.75; transform: scale(1.04); }
}
.skel-hero-wordmark {
  font-family: var(--font-display);
  color: var(--text);
  font-size: 36px;
  font-weight: 700;
  letter-spacing: -0.035em;
  user-select: none;
  display: inline-flex;
  align-items: baseline;
  gap: 0;
  opacity: 0;
  animation: wordIn 500ms var(--ease) 900ms forwards;
}
.skel-hero-wordmark .dot {
  color: var(--primary);
  font-weight: 800;
}
.skel-hero-wordmark .tld {
  font-weight: 500;
  color: var(--muted);
  letter-spacing: -0.02em;
}
@keyframes wordIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes heroIn {
  from { opacity: 0; transform: scale(.96); }
  to   { opacity: 1; transform: scale(1); }
}

/* ─── Splash brand statement ─────────────────────────────────────────
   2026 redesign: drops the 3-beat teaching loop for a tight, stacked
   typographic statement of the brand promise — "Close / More / Work" —
   set in SF Pro Display (--font-display), almost stacked like blocks
   (line-height ~0.9). Each word lands on its own beat:
     • "Close" fades in
     • "More"  slides up
     • "Work"  locks in at a heavier weight (the jobsite "power" hit)
   Apple-clean typography + construction weight. ~2s, then the splash
   dissolves to chat. Background inherits the chat surface (--bg). */
.splash-teach {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 34px;
  pointer-events: none;
  animation: heroIn 320ms var(--ease) both;
}

.splash-headline {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Tight, block-stacked rhythm — the words read as one solid mass. */
  gap: 2px;
  line-height: 0.9;
  text-align: center;
  user-select: none;
}
.sh-word {
  font-family: var(--font-display);
  font-size: clamp(48px, 17vw, 66px);
  font-weight: 600;
  letter-spacing: -0.045em;
  line-height: 0.9;
  color: var(--text);
  will-change: transform, opacity;
}

/* "Close" — quiet fade-in (the opener). */
.sh-close {
  opacity: 0;
  animation: shFade 560ms var(--ease) 220ms both;
}
/* "More" — slides up into place. */
.sh-more {
  opacity: 0;
  animation: shRise 620ms var(--ease) 760ms both;
}
/* "Work" — lands last and snaps to a heavier weight: this is the payoff
   word, so it locks in bolder for the construction "power" hit. */
.sh-work {
  position: relative;
  opacity: 0;
  font-weight: 800;
  animation: shLock 680ms var(--ease-spring, var(--ease)) 1320ms both;
}

@keyframes shFade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes shRise {
  from { opacity: 0; transform: translateY(26px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* "Lock in": settle from slightly raised + looser tracking, then snap
   to full weight + tight tracking at rest. The spring easing gives the
   crisp "click into place" feel. */
@keyframes shLock {
  0%   { opacity: 0; transform: translateY(-12px) scale(1.06); letter-spacing: 0.01em; }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: translateY(0) scale(1); letter-spacing: -0.045em; }
}

@media (prefers-reduced-motion: reduce) {
  .sh-close,
  .sh-more,
  .sh-work {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
    letter-spacing: -0.045em;
  }
}

/* ── Skeleton header ── */
.skel-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px 10px;
  flex-shrink: 0;
}
.skel-header-spacer {
  width: 38px;
  height: 38px;
}
.skel-brand {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.skel-logo {
  width: 26px;
  height: 26px;
  object-fit: contain;
  opacity: 1;
  filter: drop-shadow(0 0 6px rgba(124, 155, 255, 0.35));
}
.skel-wordmark {
  font-family: var(--font-display);
  color: var(--text);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.025em;
}

/* ── Ghost messages ── */
.skel-messages {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 24px 16px;
  justify-content: flex-end;   /* stack from bottom like a real chat */
}

.skel-bubble {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 75%;
}
.skel-bubble.skel-assistant { align-self: flex-start; }
.skel-bubble.skel-user      { align-self: flex-end; }

/* Shimmer bars — the universal "content loading" pattern */
.skel-line {
  height: 12px;
  border-radius: 6px;
  background: linear-gradient(
    100deg,
    rgba(255, 255, 255, 0.04) 0%,
    rgba(255, 255, 255, 0.10) 50%,
    rgba(255, 255, 255, 0.04) 100%
  );
  background-size: 220% 100%;
  animation: shimmer 1.6s linear infinite;
}

/* Width variants */
.skel-line.w90 { width: 90%; }
.skel-line.w80 { width: 80%; }
.skel-line.w70 { width: 70%; }
.skel-line.w60 { width: 60%; }
.skel-line.w50 { width: 50%; }
.skel-line.w40 { width: 40%; }

/* User-side bubbles get the yellow tint */
.skel-bubble.skel-user .skel-line {
  background: linear-gradient(
    100deg,
    rgba(255, 204, 0, 0.06) 0%,
    rgba(255, 204, 0, 0.15) 50%,
    rgba(255, 204, 0, 0.06) 100%
  );
  background-size: 220% 100%;
  animation: shimmer 1.6s linear infinite;
}

/* Ghost chips */
.skel-chips {
  display: flex;
  gap: 8px;
  align-self: flex-start;
  margin-top: 4px;
}
.skel-chip {
  width: 72px;
  height: 36px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: linear-gradient(
    100deg,
    rgba(255, 255, 255, 0.02) 0%,
    rgba(255, 255, 255, 0.06) 50%,
    rgba(255, 255, 255, 0.02) 100%
  );
  background-size: 220% 100%;
  animation: shimmer 1.6s linear infinite;
}

/* ── Skeleton composer ── */
.skel-composer {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px 14px;
  flex-shrink: 0;
}
.skel-plus {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--border);
}
.skel-input-pill {
  flex: 1;
  height: 44px;
  border-radius: 22px;
  border: 1px solid var(--border);
  background: rgba(28, 28, 30, 0.85);
  display: flex;
  align-items: center;
  padding: 0 16px;
}

/* Shimmer keyframes */
@keyframes shimmer {
  0%   { background-position: 220% 0; }
  100% { background-position: -220% 0; }
}

/* ===== Location screen ===== */
.screen-header {
  padding: 8px 20px 12px;
  flex-shrink: 0;
}
.screen-header h1 {
  margin: 0 0 4px;
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.screen-header .muted {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
}

/* ===== Location: full-bleed map ===== */
.map-wrap {
  flex: 1;
  position: relative;
  margin: 0;
  border-radius: 0;
  border: none;
  overflow: hidden;
}

/* ===== Location: top gradient scrim for search bar ===== */
.map-wrap::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 120px;           /* covers search bar + a bit of breathing room */
  z-index: 499;            /* above map tiles, below search (z-index: 500) */
  pointer-events: none;    /* clicks pass through to map */
  background: linear-gradient(
    to bottom,
    rgba(14, 15, 17, 0.75) 0%,
    rgba(14, 15, 17, 0.45) 40%,
    rgba(14, 15, 17, 0.00) 100%
  );
  border-radius: inherit;
}
#map {
  position: absolute;
  inset: 0;
  background: #1a1f24;
}
.leaflet-container { background: #1a1f24; }

/* ---------- Leaflet attribution chip ----------
   Leaflet's default attribution is white-on-dark and pinned hard against
   the bottom-right corner — inside our dark, rounded phone shell it reads
   as a foreign white sticker and the `.location-bar` overlaps its left
   half, leaving the right edge clipped by the map's rounded corner.
   Match the rest of the chrome (frosted dark pill, muted text) and tuck
   it just above the location card so it never gets visually amputated. */
.leaflet-control-attribution.leaflet-control {
  background: rgba(20, 22, 26, 0.55);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  backdrop-filter: blur(8px) saturate(140%);
  color: rgba(255, 255, 255, 0.45);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 2px 7px;
  margin: 0 8px 8px 0;
  font-family: var(--font-mono, system-ui, sans-serif);
  font-size: 9px;
  line-height: 1.3;
  letter-spacing: 0.01em;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  opacity: 0.7;
  transition: opacity var(--t-fast) var(--ease);
}
.leaflet-control-attribution.leaflet-control:hover { opacity: 1; }
.leaflet-control-attribution.leaflet-control a {
  color: rgba(255, 255, 255, 0.55);
  text-decoration: none;
}
.leaflet-control-attribution.leaflet-control a:hover { color: var(--text); }

/* Float the attribution well above all the bottom chrome:
   - location-bar:  bottom 20px, ~108px tall → top edge ~128px
   - locate button: bottom 148px, 44px tall  → top edge ~192px
   200px clears both with breathing room so the pill never overlaps the
   selected-address text or the GPS button. */
.leaflet-bottom.leaflet-right {
  bottom: 200px;
}

/* Map overlays: search bar + locate button */
.map-search {
  position: absolute;
  top: 12px; left: 12px; right: 12px;
  z-index: 500;
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 0 12px;
  height: 44px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.35);
  color: var(--muted);
}
.map-search input {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
  height: 100%;
}
.map-search input::placeholder { color: var(--muted); }
.map-search-clear {
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  padding: 4px;
  border-radius: 50%;
  display: flex;
}
.map-search-clear:hover { color: var(--text); }

.map-search-results {
  position: absolute;
  top: 64px; left: 12px; right: 12px;
  z-index: 500;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  max-height: 240px;
  overflow-y: auto;
  box-shadow: 0 10px 24px rgba(0,0,0,0.4);
}
.map-search-item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  padding: 10px 14px;
  font-family: inherit;
  cursor: pointer;
}
.map-search-item:last-child { border-bottom: none; }
.map-search-item:hover { background: rgba(255,255,255,0.04); }
.map-search-item strong { display: block; font-size: 14px; font-weight: 600; }
.map-search-item span { display: block; font-size: 12px; color: var(--muted); margin-top: 2px; }
.map-search-status { padding: 12px 14px; font-size: 13px; color: var(--muted); }

.map-locate-btn {
  position: absolute;
  /* Sit above the frosted location-bar (which is bottom: 20px + ~88px tall) */
  bottom: 148px; right: 16px;
  z-index: 501;
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 6px 16px rgba(0,0,0,0.35);
  transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.map-locate-btn:hover {
  /* Layer the hover tint OVER the dark surface; using `background` shorthand
     here would drop the surface and make the button look white over light
     map tiles. */
  background-color: var(--surface);
  background-image: linear-gradient(rgba(255,255,255,0.08), rgba(255,255,255,0.08));
}
.map-locate-btn:active { transform: scale(0.95); }
.map-locate-btn.loading { color: var(--primary); animation: locate-spin 1s linear infinite; }
@keyframes locate-spin { to { transform: rotate(360deg); } }

/* ===== Location: frosted-glass bottom card ===== */
.location-bar {
  /* Pull out of flow, float over the map */
  position: absolute;
  bottom: 20px;
  left: 16px;
  right: 16px;
  z-index: 500;

  /* Frosted glass — matches your action-sheet-card recipe */
  background: rgba(31, 35, 39, 0.55);
  backdrop-filter: blur(24px) saturate(1.6);
  -webkit-backdrop-filter: blur(24px) saturate(1.6);
  border: 1px solid var(--border-strong);
  border-radius: 20px;
  box-shadow:
    0 -4px 20px rgba(0, 0, 0, 0.25),
    0  8px 32px rgba(0, 0, 0, 0.35);

  /* Inner layout */
  padding: 16px 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex-shrink: 0;
}
.location-readout {
  font-size: 13px;
  color: var(--muted);
  min-height: 18px;
  text-align: center;
}
.location-readout.has-loc { color: var(--text); }

/* ===== Buttons ===== */
.btn-primary {
  width: 100%;
  height: 52px;
  border-radius: var(--radius-pill);
  /* Molten-amber gradient — the 2026 "liquid metal" CTA. Replaces the
     flat #FFCC00 fill that read as caution-tape at large surface area. */
  background: var(--primary-grad);
  color: #FFFFFF;
  border: none;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  cursor: pointer;
  box-shadow: var(--shadow-glow-primary), inset 0 1px 0 rgba(255, 255, 255, 0.25);
  transition: filter var(--t-fast) var(--ease), transform var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.btn-primary:disabled {
  background: #3a3d42;
  color: #6b6f74;
  cursor: not-allowed;
  box-shadow: none;
}
.btn-primary:not(:disabled):hover { filter: brightness(1.05); box-shadow: 0 12px 36px -8px rgba(78, 141, 118, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.3); }
.btn-primary:not(:disabled):active { transform: scale(0.98); filter: brightness(.95); }

/* Location-picker confirm ("Ok") — uses the brand deep blue-gray CTA
   (same accent as the receipt) instead of the amber money axis, since
   confirming a map pin isn't a money/commit action. */
#confirmLocation.btn-primary {
  background: linear-gradient(135deg, #2F4F6B 0%, #243B50 100%);
  color: #FBF6EC;
  box-shadow: 0 8px 32px -8px rgba(47, 79, 107, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
#confirmLocation.btn-primary:not(:disabled):hover {
  filter: brightness(1.08);
  box-shadow: 0 12px 36px -8px rgba(47, 79, 107, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.22);
}

.btn-ghost {
  height: 44px;
  padding: 0 18px;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border-strong);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

.icon-btn {
  width: 38px; height: 38px;
  border-radius: 50%;
  background: transparent;
  border: none;
  color: var(--text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), transform 160ms var(--ease-spring, var(--ease));
}
.icon-btn:hover { background: rgba(255, 255, 255, 0.04); }
.icon-btn:active { background: rgba(255, 255, 255, 0.08); transform: scale(0.92); }
.icon-btn.ghost { color: var(--muted); }

/* Language toggle: small text pill ("EN" / "DA") reusing .icon-btn footprint
   so the chat header stays the same 38px tall and the title remains centred. */
.lang-toggle { font-size: 12px; font-weight: 700; letter-spacing: 0.08em; color: var(--muted); }
.lang-toggle:hover { color: var(--text); }
.lang-toggle-label { line-height: 1; }

/* ===== Chat header =====
   2026 refresh, ChatGPT-style: a TRANSPARENT top bar that floats over the
   chat scroll. It is absolutely positioned so messages scroll UNDER it; a
   soft top-to-bottom gradient + blur (masked to fade out at the bottom)
   keeps the burger / wordmark / new-chat controls legible over content
   without a hard divider line. */
.chat-header {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px 14px;
  /* Frosted fade: opaque-ish navy at the very top, fully transparent at the
     bottom so the blueprint grid + messages bleed through and scroll under. */
  background: linear-gradient(
    to bottom,
    rgba(11, 31, 46, 0.94) 0%,
    rgba(11, 31, 46, 0.78) 48%,
    rgba(11, 31, 46, 0) 100%);
  -webkit-backdrop-filter: blur(12px) saturate(150%);
  backdrop-filter: blur(12px) saturate(150%);
  -webkit-mask-image: linear-gradient(to bottom, #000 62%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 62%, transparent 100%);
  border-bottom: none;
  /* Let the chat scroll under the transparent fade; the buttons opt back in. */
  pointer-events: none;
}
.chat-header .icon-btn,
.chat-header .chat-identity-badge { pointer-events: auto; }
/* Larger tap targets for the header action cluster (new-chat + take-photo). */
.icon-btn--lg { width: 44px; height: 44px; }
/* Centre the wordmark; push the right-hand action cluster (take-photo +
   new-chat) to the right edge so the burger anchors the left. */
.chat-header > #headerCameraBtn { margin-left: auto; }
.chat-title {
  /* Absolutely centered inside the header so the wordmark sits in the
     true horizontal middle of the phone, regardless of how wide the
     left lang toggle or the right reset/identity-badge cluster grow.
     `pointer-events: none` keeps clicks falling through to whichever
     side button sits under it. */
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  pointer-events: none;
}
.chat-title-main {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.025em;
  display: inline-flex;
  align-items: baseline;
  gap: 0;
}
/* Mirror the splash `.st-wordmark` accenting so the chat header
   reads as the same logo, just sized down for the bar. */
.chat-title-main .dot { color: var(--magic); font-weight: 800; }
.chat-title-main .tld { color: var(--muted); font-weight: 500; letter-spacing: -0.02em; }
/* "hejbetty" wordmark: keep "hej" understated and let "Betty" carry the
   brand — colour is inherited (unchanged), only the relative size shifts. */
.chat-title-main .hej { font-size: 0.82em; font-weight: 600; color: var(--muted); margin-right: 0.12em; }
.chat-title-main .betty { font-size: 1.12em; font-weight: 700; }
.chat-title-flex { flex-shrink: 0; font-size: 18px; line-height: 1; }
/* Invisible spacer matching .icon-btn so the title stays centered now that
   the back chevron has been removed from the chat header. */
.chat-header-spacer { width: 38px; height: 38px; display: inline-block; }

/* ===== Side drawer (burger menu) =====
   ChatGPT-style slide-in panel from the left, opened by the header burger.
   Collects the affordances that used to crowd the header: profile/identity,
   privacy & data-sharing, and the language toggle. A dim scrim closes it. */
.side-drawer {
  position: absolute;
  inset: 0;
  z-index: 70;
  display: flex;
}
.side-drawer[hidden] { display: none; }
.side-drawer-scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.42);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  opacity: 0;
  animation: fadeIn 440ms cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}
.side-drawer.closing .side-drawer-scrim { animation: fadeOut 200ms var(--ease) forwards; }
.side-drawer-panel {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  width: min(82%, 300px);
  height: 100%;
  padding: 8px 10px calc(14px + env(safe-area-inset-bottom, 0px));
  /* Frosted blueprint surface, a touch lighter than the chat so it lifts. */
  background: rgba(18, 40, 56, 0.92);
  -webkit-backdrop-filter: blur(26px) saturate(170%);
  backdrop-filter: blur(26px) saturate(170%);
  border-right: 1px solid var(--border-strong);
  box-shadow: 24px 0 48px -20px rgba(0, 0, 0, 0.8);
  transform: translateX(-100%);
  animation: drawerIn 440ms cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}
.side-drawer.closing .side-drawer-panel { animation: drawerOut 200ms var(--ease) forwards; }
@keyframes drawerIn  { to { transform: translateX(0); } }
@keyframes drawerOut { to { transform: translateX(-100%); } }

.side-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 6px 10px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.side-drawer-nav {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-top: 6px;
}
.drawer-item {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 13px 12px;
  background: transparent;
  border: none;
  border-radius: 14px;
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.drawer-item:hover { background: rgba(255, 255, 255, 0.06); }
.drawer-item:active { transform: scale(0.98); background: rgba(78, 141, 118, 0.10); }
.drawer-item-icon {
  width: 24px; height: 24px;
  flex-shrink: 0;
  color: var(--primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.drawer-item-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}
.drawer-item-text {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.drawer-item-sub {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.drawer-item-value {
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--muted);
  background: var(--pill-bg, rgba(255, 255, 255, 0.06));
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px 9px;
}

/* ===== Chat =====
   The scroll area sits over an ambient aurora wash — a slowly drifting
   pair of soft periwinkle + amber blooms anchored to fixed positions
   inside the phone. They animate via background-position over ~30s so
   the app feels alive without being distracting. */
.chat-scroll {
  position: relative;
  flex: 1;
  overflow-y: auto;
  /* Extra top padding clears the now-absolute, transparent chat header so the
     first message isn't hidden underneath the floating burger / wordmark. */
  padding: 60px 16px 8px;
  -webkit-overflow-scrolling: touch;
  /* Column flex so a short conversation can pin to the BOTTOM (just above the
     composer) instead of floating at the top with a big void. `.messages` gets
     margin-top:auto below, which only consumes free space when the stack is
     shorter than the viewport; once it overflows, the auto-margin collapses
     and scrollChat()'s focus-latest-exchange logic takes over normally. */
  display: flex;
  flex-direction: column;
  /* Blueprint drafting surface — deep navy. */
  background-color: #0B1F2E;
}
.messages {
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* Top-align the stack: the first message sits just below the floating chat
     header and the conversation grows downward, leaving any free space BELOW
     the last bubble. */
  margin-top: 0;
}

/* Flexible tail below the message stream. Its height is set from JS so the
   newest message can always scroll up to the top of the viewport, pushing
   earlier messages out of view. */
.chat-spacer {
  flex: none;
  width: 100%;
  pointer-events: none;
}

.bubble {
  max-width: 82%;
  padding: 10px 14px;
  border-radius: var(--radius-bubble);
  font-size: 15.5px;
  line-height: 1.55;
  word-wrap: break-word;
  animation: bubbleIn 380ms var(--ease-spring, var(--ease));
}
@keyframes bubbleIn {
  from { opacity: 0; transform: translateY(8px) scale(0.96); }
  to   { opacity: 1; transform: translateY(0)   scale(1); }
}

.bubble.assistant {
  align-self: flex-start;
  position: relative;
  /* iMessage-style green bubble — the assistant speaks in a glossy top-lit
     green gradient (received-message side), retuned to the brand green
     (#4E8D76) with dark ink for WCAG-safe contrast and 20px rounded corners.
     The authentic curved iOS tail hooks off the bottom-LEFT (see ::after
     below), mirroring the user bubble. */
  /* iMessage-style green bubble — Betty speaks in a FLAT brand-green
     surface (received-message side). Deliberately matte: no top-lit
     gradient or glossy inner highlight, so it reads as a calmer, flatter
     counterpart to the user's glossier bubble. The authentic curved iOS
     tail hooks off the bottom-LEFT (see ::after below). */
  background: #4E8D76;
  background-repeat: no-repeat;
  color: #FFFFFF;
  padding: 12px 16px;
  max-width: 82%;
  font-size: 15.5px;
  line-height: 1.55;
  font-weight: 400;
  /* White ink on the green surface optically "blooms" and reads heavier
     than the user bubble's dark ink. Normal weight + antialiased smoothing
     thins the strokes so Betty no longer looks bolder than the user. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  border: 1px solid rgba(60, 110, 92, 0.45);
  border-radius: 20px;
  box-shadow: 0 1px 2px rgba(30, 80, 64, 0.16);
}
/* Authentic iOS speech tail (bottom-left). Drawn with clip-path so it
   renders cleanly over the patterned navy chat backdrop without needing a
   solid background to mask against. Mirrors the user-bubble tail. */
.bubble.assistant:where(:not(.bubble-address):not(.proposal):not(.proposal-actions):not(.image))::after {
  content: '';
  position: absolute;
  left: -7px;
  bottom: -0.5px;
  width: 20px;
  height: 19px;
  background: #4E8D76;
  clip-path: path('M20,0 C20,10 18,17 7,18.5 C13,16.5 14,9 14,0 Z');
  filter: none;
}

/* Personalised landing salutation — a Claude-style recognition line shown
   instantly above Betty's first message. Stripped of the green bubble skin,
   tail, border and shadow so it reads as a calm, glanceable header on the navy
   drafting surface rather than a chat utterance. Light ink for contrast, a
   touch larger and tighter than body copy. */
.bubble.assistant.greet-salute {
  background: none;
  color: var(--text, #F4F6FA);
  border: none;
  box-shadow: none;
  padding: 2px 4px 0;
  /* Drop the salutation away from the header so it gets room to breathe,
     and extra breathing room below so it reads as a calm header set apart
     from Betty's first message rather than crowding it. */
  margin-top: 18px;
  margin-bottom: 18px;
  max-width: 100%;
  font-size: 21px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
}
.bubble.assistant.greet-salute::after { content: none; }


/* ── Typewriter variant — first welcome bubble only ──────────────────
   Hero opener moment: one bubble, once per session, types out at a
   capped total duration (~600ms regardless of length). After that,
   every assistant bubble renders instantly — typing every reply would
   slow the product and fight the "instant verdict" brand promise.

   How the layout stays stable:
   - The bubble itself is the grid. The ghost holds the FULL text
     invisibly; the live span holds what's been typed so far. BOTH sit
     in the same grid cell (`grid-area: 1/1`) as block-level children,
     so they overlay perfectly. The bubble's width + line-wrap are
     therefore locked by the ghost before the first char is typed,
     and the chip row / Fortsæt button below never jumps.
   - Block-level (not inline) is critical: inline spans in a grid cell
     flow sequentially, which made the live text append after the
     ghost's last line and indent line 1 — the bug this comment exists
     to prevent reintroducing.
   - prefers-reduced-motion: the JS short-circuits and writes the full
     text immediately. */
.bubble.assistant.typewriter {
  display: inline-grid;
  /* Single column: the ghost + live text stack in the same cell. */
  grid-template-columns: 1fr;
  align-items: center;
  cursor: default;
}
.bubble.assistant.typewriter > .tw-ghost,
.bubble.assistant.typewriter > .tw-live {
  grid-column: 1;
  grid-row: 1;
  display: block;
  min-width: 0;
}
.bubble.assistant.typewriter > .tw-ghost {
  visibility: hidden;
  pointer-events: none;
}

/* Structured "how it works" guide bubble (renderHelpGuide). Inherits the
   lime assistant skin + dark ink; just lays out two short bulleted lists
   with comfortable spacing so the example jobs and the "tell me" prompts
   read as scannable groups rather than a wall of text. Discs use the dark
   ink at reduced opacity so they sit on the lime without shouting. */
.bubble.assistant.help-guide {
  max-width: 86%;
}
.bubble.assistant.help-guide .help-guide-line {
  margin: 0;
  font-weight: 600;
}
.bubble.assistant.help-guide .help-guide-list {
  margin: 6px 0 12px;
  padding-left: 20px;
  list-style: disc;
  font-weight: 500;
}
.bubble.assistant.help-guide .help-guide-list li {
  margin: 2px 0;
  line-height: 1.45;
}
.bubble.assistant.help-guide .help-guide-list li::marker {
  color: rgba(11, 31, 46, 0.5);
}
.bubble.assistant.help-guide .help-guide-outro {
  margin-top: 4px;
}

/* Clarification question bubble that offers a fixed set of choices
   (assistantSayWithOptions). The question sits on top; the choices follow
   as a scannable bulleted list inside the bubble, mirroring the tappable
   chips below. Reuses the help-guide disc treatment so the markers sit
   quietly on the lime skin. */
.bubble.assistant.has-options .bubble-question {
  margin: 0;
  /* Match the plain assistant bubble weight (400) so a clarification
     question doesn't read as bolder than a normal Betty utterance. */
  font-weight: 400;
}
.bubble.assistant.has-options .clarify-options {
  margin: 8px 0 0;
  padding-left: 20px;
  list-style: disc;
  font-weight: 500;
}
.bubble.assistant.has-options .clarify-options li {
  margin: 2px 0;
  line-height: 1.45;
}
.bubble.assistant.has-options .clarify-options li::marker {
  color: rgba(11, 31, 46, 0.5);
}

/* Address-callout variant of the assistant bubble. Used by the
   geolocation confirmation step to promote the detected street address
   above the question, so the contractor can confirm-by-glance instead
   of reading a sentence. Small dimmed question on top, big bold value
   underneath — Cal AI / Linear style "headline + caption" pattern,
   colour-inverted (dark ink on lime). */
.bubble.assistant.bubble-address {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 16px;
  max-width: 86%;
}
.bubble-address-q {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0.005em;
  opacity: 0.62;
}
.bubble-address-v {
  font-family: var(--font-display, inherit);
  font-size: 20px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.015em;
  /* Inherits the dark-on-lime colour from .bubble.assistant; no extra
     fill needed. Trade text-wrap for balance so long addresses break
     into visually even lines instead of a long top + orphan bottom. */
  text-wrap: balance;
}
.bubble.user {
  align-self: flex-end;
  position: relative;
  /* iMessage-style silvery bubble, user side. A soft top-lit gray-blue
     gradient with dark ink, rounded 20px corners and the authentic curved
     iOS "tail" hooking off the bottom-right (drawn via the ::after below).
     Reads as the human's typed message, distinct from the lime AI voice. */
  background: linear-gradient(180deg, #EAEDF3 0%, #DCE0E8 52%, #CDD3DE 100%);
  color: #15202B;
  border: 1px solid rgba(15, 31, 46, 0.10);
  border-radius: 20px;
  padding: 11px 16px;
  font-weight: 500;
  box-shadow:
    0 6px 18px -10px rgba(7, 18, 28, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.75);
}
/* Authentic iOS speech tail (bottom-right). Drawn with clip-path so it
   renders cleanly over the patterned navy chat backdrop without needing a
   solid background to mask against. Skipped for image bubbles and the
   compact confirmed-chip echo, which have their own shapes. */
.bubble.user:where(:not(.image))::after {
  content: '';
  position: absolute;
  right: -7px;
  bottom: -0.5px;
  width: 20px;
  height: 19px;
  background: linear-gradient(160deg, #DCE0E8 0%, #CDD3DE 70%);
  clip-path: path('M0,0 C0,10 2,17 13,18.5 C7,16.5 6,9 6,0 Z');
  filter: drop-shadow(1px 3px 4px rgba(7, 18, 28, 0.28));
}
.bubble.user.confirmed-chip {
  /* Compact silvery pill — same iMessage family as the full user bubble but
     tighter, with no tail, so a confirmed/skip action reads as a quick
     committed echo rather than a typed message. */
  background: linear-gradient(180deg, #EAEDF3 0%, #DCE0E8 52%, #CDD3DE 100%);
  color: #15202B;
  border: 1px solid rgba(15, 31, 46, 0.10);
  border-radius: var(--radius-pill);
  padding: 8px 16px;
  font-weight: 500;
  font-size: 15.5px;
  box-shadow:
    0 6px 18px -10px rgba(7, 18, 28, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.75);
}

/* ===== Blueprint schematic card =====
   Live technical drawing of the captured job (2026 blueprint direction).
   Deliberately a SELF-CONTAINED content surface: it owns a local navy +
   cyan blueprint palette so the metaphor stays a content layer and does
   NOT reskin the filled lime/amber bubbles or the chat base.
   The card surface is lifted one step lighter than the chat background
   (which is the SAME deep navy #0B1F2E + grid) so the card reads as a
   raised panel instead of melting into the backdrop; the inner drawing
   canvas stays darker so it reads as a recessed well.
   Drops into the message stream like a wide assistant card. */
.blueprint-card {
  --bp-navy: #15324A;       /* lifted card surface (vs #0B1F2E chat base) */
  --bp-line: #8FD6EA;       /* object outlines */
  --bp-line-soft: rgba(143, 214, 234, 0.45); /* mortar / dimension */
  --bp-accent: #56C7E0;     /* dimension text + ticks */
  --bp-grid: rgba(127, 212, 232, 0.10);

  align-self: stretch;
  width: 100%;
  margin: 4px 0 8px;
  border-radius: var(--radius-card);
  background: var(--bp-navy);
  border: 1px solid rgba(127, 212, 232, 0.38);
  box-shadow:
    0 14px 34px -16px rgba(0, 0, 0, 0.85),
    0 2px 6px -2px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(143, 214, 234, 0.16);
  overflow: hidden;
  animation: bubbleIn 380ms var(--ease-spring, var(--ease));
}

.bp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 11px 14px 8px;
}
.bp-head-left { display: inline-flex; align-items: center; gap: 7px; }
.bp-spark { color: var(--bp-accent); display: inline-flex; }
.bp-label {
  font-family: 'Space Grotesk', 'Inter', sans-serif;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #D7F0F8;
}
.bp-head-right { display: inline-flex; align-items: center; gap: 8px; }
.bp-project {
  font-size: 12px;
  font-weight: 500;
  color: var(--bp-accent);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Drawing canvas — navy + faint grid + corner registration crosshairs. */
.bp-canvas {
  position: relative;
  margin: 0 12px;
  border: 1px solid rgba(127, 212, 232, 0.18);
  border-radius: 8px;
  background-color: rgba(6, 22, 33, 0.6);
  background-image:
    linear-gradient(var(--bp-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--bp-grid) 1px, transparent 1px);
  background-size: 20px 20px;
}
.bp-svg { display: block; width: 100%; height: auto; }

/* Object outlines: crisp cyan. Mortar + dimensions: softer/dashed. */
.bp-line { fill: rgba(127, 212, 232, 0.05); stroke: var(--bp-line); stroke-width: 1.6; }
.bp-mortar { stroke: var(--bp-line-soft); stroke-width: 0.9; }
.bp-ground { stroke: var(--bp-line); stroke-width: 1.4; }
.bp-hatch line { stroke: var(--bp-line-soft); stroke-width: 1; }
.bp-ext { stroke: var(--bp-line-soft); stroke-width: 0.9; stroke-dasharray: 3 3; }
.bp-dim { stroke: var(--bp-accent); stroke-width: 1; fill: none; }
.bp-text {
  fill: var(--bp-accent);
  font-family: 'JetBrains Mono', monospace;
  font-size: 12px;
  font-weight: 600;
}

/* Corner registration ticks — the schematic "this is a drawing" cue. */
.bp-tick {
  position: absolute;
  width: 9px; height: 9px;
  border-color: rgba(127, 212, 232, 0.5);
  border-style: solid;
  border-width: 0;
}
.bp-tick--tl { top: 6px; left: 6px; border-top-width: 1.5px; border-left-width: 1.5px; }
.bp-tick--tr { top: 6px; right: 6px; border-top-width: 1.5px; border-right-width: 1.5px; }
.bp-tick--bl { bottom: 6px; left: 6px; border-bottom-width: 1.5px; border-left-width: 1.5px; }
.bp-tick--br { bottom: 6px; right: 6px; border-bottom-width: 1.5px; border-right-width: 1.5px; }

/* Spec list — the numbers the price is built on, read back as a
   key/value list under the drawing. */
.bp-specs {
  padding: 10px 16px 12px;
}
.bp-spec {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid rgba(127, 212, 232, 0.10);
}
.bp-spec--last { border-bottom: none; }
.bp-spec-k {
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.03em;
  color: rgba(191, 230, 241, 0.72);
}
.bp-spec-v {
  font-family: 'JetBrains Mono', monospace;
  font-variant-numeric: tabular-nums;
  font-size: 13.5px;
  font-weight: 600;
  color: #EAF8FC;
}

/* Typing dots
   Lime (--magic), not muted-grey. The typing indicator only ever fires
   while an OpenAI call is in flight — analyze, after-image, way-off
   retry — so the color encodes the verb. Keeping amber for money/chrome
   and lime for "AI is doing something right now" teaches users the
   system without copy. */
.typing {
  align-self: flex-start;
  display: inline-flex;
  gap: 4px;
  padding: 8px 4px;
}
.typing span {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--magic);
  /* No lime halo \u2014 a glow on a 7px dot reads as bloom rather than
     intent, and the pure-black chat surface makes any soft-edge
     shadow look smudgy. Flat dot is enough; the bounce animation
     carries the "AI is thinking" signal on its own. */
  box-shadow: none;
  animation: typing 1.2s infinite ease-in-out;
}
.typing span:nth-child(2) { animation-delay: .15s; }
.typing span:nth-child(3) { animation-delay: .3s; }
@keyframes typing {
  0%, 60%, 100% { opacity: .3; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}

/* ===== Photo-analysis "thinking" tile loader =====
   Wordless indicator that fires after the first image upload — replaces
   the old chatty intro bubble + 3-dot typing combo. Four small lime
   tiles (`--magic`) drift slowly through a 2×2 grid; each tile runs on
   its own period (7.4 / 8.3 / 9.1 / 9.8s) so the group is deliberately
   out of phase — there's no repeating pattern your eye can lock onto,
   so the motion reads as a slow, thoughtful consideration rather than
   a mechanical loop.

   Design rules baked in (per repeated user feedback):
    • No frame.  No background, border, blur, or shadow. The tiles sit
      bare on the chat surface so the indicator reads as a thought
      happening in the open, not a status badge.
    • No glow.   Inset highlight + shadow only — zero outer halo. The
      tiles look like flat lime ceramic pieces, not neon pixels.
    • Crisp.     Hard 2px corner radius, no anti-aliased fuzz, no fades.
    • Slow.      ~8s per tile cycle. Pure ease-in-out, no overshoot —
      the tile is always mid-glide so the motion never stutters or
      "blinks" into place.
    • Random.    Desynced periods + negative delays mean the system
      never returns to the same arrangement on a human timescale.
      Within each tile, the 8 waypoints visit the 4 corners in a
      different order so individual tile paths also don't loop
      visibly. */
.photo-thinking {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Tiny padding so the 20×20 grid isn't flush against the bubble
     above/below; no background/border/shadow — see rules above. */
  padding: 4px 2px;
  margin: 2px 0 4px;
}
.photo-thinking-grid {
  position: relative;
  width: 20px;
  height: 20px;
  /* No drop-shadow aura. Tested with and without; without it the
     motion reads as deliberate thinking, with it the surface looked
     like a status indicator. */
}
.photo-thinking-grid span {
  position: absolute;
  top: 0;
  left: 0;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: var(--magic);
  /* Inset highlight + shadow only — no outer glow. Gives the tile a
     hint of physical depth (top edge catches light, bottom edge in
     shadow) without any halo bleeding into the chat background. */
  box-shadow:
    inset 0 -1px 0 rgba(0, 0, 0, 0.22),
    inset 0 1px 0 rgba(255, 255, 255, 0.30);
  /* Pure ease-in-out — no overshoot, no settle. The tile is ALWAYS
     mid-glide, which is what makes the motion feel continuous instead
     of stepped. */
  animation-iteration-count: infinite;
  animation-timing-function: cubic-bezier(.45, .05, .55, .95);
  will-change: transform;
}
/* Desynced periods + negative delays so the group is permanently out
   of phase. Numbers are deliberately co-prime-ish (7.4 / 8.3 / 9.1 /
   9.8) so the system never returns to the same arrangement on a
   human-perceivable timescale. Negative delays seed each tile mid-
   animation on first paint, so the user never sees a "starting pose". */
.photo-thinking-grid span:nth-child(1) {
  animation-name: pt-drift-1;
  animation-duration: 7.4s;
  animation-delay: -1.3s;
}
.photo-thinking-grid span:nth-child(2) {
  animation-name: pt-drift-2;
  animation-duration: 8.3s;
  animation-delay: -3.5s;
}
.photo-thinking-grid span:nth-child(3) {
  animation-name: pt-drift-3;
  animation-duration: 9.1s;
  animation-delay: -5.6s;
}
.photo-thinking-grid span:nth-child(4) {
  animation-name: pt-drift-4;
  animation-duration: 9.8s;
  animation-delay: -2.4s;
}

/* Per-tile drift paths. 8 waypoints each so the tile glides through
   intermediate positions (visibly sliding past its neighbours) instead
   of teleporting between the 4 cells. Each path visits all 4 corners
   but in a different order, and the visits are spread non-uniformly
   across the cycle so even within a single tile the motion never feels
   like a metronome.
   Corner coords: TL(0,0) TR(11,0) BL(0,11) BR(11,11). */
@keyframes pt-drift-1 {
  0%   { transform: translate(0,    0); }      /* TL */
  14%  { transform: translate(11px, 0); }      /* TR */
  30%  { transform: translate(11px, 11px); }   /* BR */
  46%  { transform: translate(0,    11px); }   /* BL */
  60%  { transform: translate(11px, 11px); }   /* BR (revisit) */
  74%  { transform: translate(11px, 0); }      /* TR */
  88%  { transform: translate(0,    11px); }   /* BL */
  100% { transform: translate(0,    0); }      /* TL */
}
@keyframes pt-drift-2 {
  0%   { transform: translate(11px, 0); }      /* TR */
  16%  { transform: translate(0,    11px); }   /* BL — diagonal slide */
  32%  { transform: translate(0,    0); }      /* TL */
  48%  { transform: translate(11px, 11px); }   /* BR — diagonal slide */
  62%  { transform: translate(0,    11px); }   /* BL */
  76%  { transform: translate(11px, 11px); }   /* BR */
  90%  { transform: translate(0,    0); }      /* TL */
  100% { transform: translate(11px, 0); }      /* TR */
}
@keyframes pt-drift-3 {
  0%   { transform: translate(0,    11px); }   /* BL */
  12%  { transform: translate(11px, 11px); }   /* BR */
  28%  { transform: translate(11px, 0); }      /* TR */
  44%  { transform: translate(0,    0); }      /* TL */
  58%  { transform: translate(11px, 11px); }   /* BR — diagonal slide */
  72%  { transform: translate(0,    0); }      /* TL — diagonal slide */
  86%  { transform: translate(11px, 0); }      /* TR */
  100% { transform: translate(0,    11px); }   /* BL */
}
@keyframes pt-drift-4 {
  0%   { transform: translate(11px, 11px); }   /* BR */
  15%  { transform: translate(0,    0); }      /* TL — diagonal slide */
  30%  { transform: translate(0,    11px); }   /* BL */
  45%  { transform: translate(11px, 0); }      /* TR — diagonal slide */
  60%  { transform: translate(0,    0); }      /* TL */
  75%  { transform: translate(0,    11px); }   /* BL */
  88%  { transform: translate(11px, 0); }      /* TR */
  100% { transform: translate(11px, 11px); }   /* BR */
}

/* Reduced-motion fallback: park the four tiles at the four corners and
   pulse opacity in sequence so the surface still telegraphs "AI is
   working" without any positional motion. The pulse keyframe touches
   opacity only — the static transform on the rule wins. */
@media (prefers-reduced-motion: reduce) {
  .photo-thinking-grid span {
    animation: pt-pulse 1.8s ease-in-out infinite;
  }
  .photo-thinking-grid span:nth-child(1) { transform: translate(0, 0);       animation-delay: 0s;   }
  .photo-thinking-grid span:nth-child(2) { transform: translate(11px, 0);    animation-delay: .2s;  }
  .photo-thinking-grid span:nth-child(3) { transform: translate(0, 11px);    animation-delay: .4s;  }
  .photo-thinking-grid span:nth-child(4) { transform: translate(11px, 11px); animation-delay: .6s;  }
  @keyframes pt-pulse {
    0%, 100% { opacity: .35; }
    50%      { opacity: 1; }
  }
}

/* ===== Quick replies =====
   These are the easiest path through the flow ("Yes" / "Skip" / suggested
   replies). Sized to match the composer's 44px tap-target floor so a
   gloved finger can pick the right one without hitting its neighbour. */
.quick-replies {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 0 6px 10px;
  min-height: 0;
}
.quick-replies:empty { display: none; }
/* Chips size to their own label and wrap onto a new row once they run out
   of horizontal room. A 2-chip photo/skip prompt still sits on a single
   line, while a many-option clarification (e.g. 5 terrace materials) wraps
   cleanly instead of crushing every pill into a single overflowing row. */
.quick-replies .chip {
  flex: 0 0 auto;
  max-width: 100%;
  padding-left: 14px;
  padding-right: 14px;
  white-space: nowrap;
}
.chip {
  /* Flat brand-green chip — matches the matte `.bubble.assistant` surface so
     a suggested reply / choice reads as the same calm, flat material as the
     chat bubbles. Deliberately NOT gradient-filled and with no glossy inner
     highlight: solid fill + hairline border + soft drop shadow, exactly like
     Betty's bubble. Outline secondary variant lives below in `.chip.outline`. */
  background: #4E8D76;
  color: #FFFFFF;
  border: 1px solid rgba(60, 110, 92, 0.45);
  padding: 12px 18px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  /* Same soft drop shadow as the assistant bubble — no inner highlight, so
     the chip stays flat/matte rather than glossy. */
  box-shadow: 0 1px 2px rgba(30, 80, 64, 0.16);
  transition:
    transform 160ms var(--ease-spring, var(--ease)),
    box-shadow var(--t-fast) var(--ease),
    filter var(--t-fast) var(--ease);
  font-family: inherit;
}
.chip:hover {
  /* Stay flat on hover — a touch brighter, same matte shadow (no gloss). */
  filter: brightness(1.06);
  box-shadow: 0 1px 2px rgba(30, 80, 64, 0.16);
}
/* Unified microbounce — applied to every tappable chip/button */
.chip:active { transform: scale(0.94); }
.chip.outline {
  background: var(--pill-bg);
  border: 1px solid var(--pill-border);
  color: var(--text);
}

/* Pills shown inline in the chat stream (under an assistant bubble) */
.inline-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 4px 0 6px;
  width: 100%;
  align-self: stretch;
  animation: bubbleIn var(--t-mid) var(--ease);
}
/* Wrapper that stacks an icon-only chip with a small caption underneath. */
.inline-pill-col {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  min-width: 0;
}
.inline-pill-col .chip {
  width: 100%;
}
.inline-pill-caption {
  font-size: 11px;
  line-height: 1.2;
  color: var(--muted);
  text-align: center;
  letter-spacing: 0.01em;
}
.inline-pills .chip {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
}
.inline-pills .chip.has-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.inline-pills .chip.icon-only {
  /* Stay equal-width with sibling pills, but keep the icon centered. */
  padding: 8px 12px;
}
.inline-pills .chip.outline {
  background: transparent;
  border-color: var(--border);
  color: var(--muted);
  font-weight: 500;
  font-size: 13px;
}
.inline-pills .chip .chip-icon {
  display: inline-flex;
  align-items: center;
}
.inline-pills .chip .chip-icon svg {
  width: 32px;
  height: 32px;
}
.inline-pills .chip:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ===== Lane chooser — the first decision =====
   Two full-width "lane" cards that fork the flow along the product's core
   metaphor: the AI is *earned, not assumed*. Each card wears its own brand
   axis so the choice is legible at a glance:
     • .lane-card--ai   → AI axis (electric lime). "Let AI assist me."
     • .lane-card--know → commit axis (Dewalt amber). "I know the scope."
   Composition: a leading accent rule that draws in on entrance, a soft
   off-canvas corner bloom, a faint schematic line motif bled off the right
   edge, an icon token, the title/sub copy, a quiet secondary sub-chip for
   the alternate input modality, and a chevron. Pure-black base + hairline
   borders keep it credible and exclusive rather than loud. */
.inline-pills.lane-chooser {
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  margin: 6px 0 8px;
}

.lane-card {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  display: flex;
  align-items: flex-start;
  gap: 14px;
  width: 100%;
  text-align: left;
  padding: 17px 16px 17px 22px;
  border-radius: 18px;
  background-color: #07080B;
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.028), rgba(255, 255, 255, 0));
  border: 1px solid var(--border);
  cursor: pointer;
  font-family: inherit;
  color: var(--text);
  box-shadow: var(--shadow-card), inset 0 1px 0 rgba(255, 255, 255, 0.05);
  transition:
    transform 220ms var(--ease-spring, var(--ease)),
    border-color 220ms var(--ease),
    box-shadow 240ms var(--ease);
  animation: laneIn 380ms var(--ease) both;
  -webkit-tap-highlight-color: transparent;
}
.lane-card--ai { animation-delay: 40ms; }
.lane-card--know { animation-delay: 170ms; }
.lane-card:hover { transform: translateY(-1px); }
.lane-card:active { transform: scale(0.985); }
.lane-card:focus-visible {
  outline: none;
  border-color: var(--border-strong);
  box-shadow: var(--shadow-card), 0 0 0 3px rgba(255, 255, 255, 0.08);
}
.lane-card[aria-disabled="true"] { opacity: 0.45; }

/* Leading accent rule — "draws" downward on entrance like a schematic line. */
.lane-accent {
  position: absolute;
  left: 0;
  top: 15px;
  bottom: 15px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  transform-origin: top center;
  animation: laneAccentDraw 560ms var(--ease) both;
  animation-delay: 220ms;
}
@keyframes laneAccentDraw {
  from { transform: scaleY(0); opacity: 0; }
  to   { transform: scaleY(1); opacity: 1; }
}

/* Soft corner bloom, anchored behind the icon token. Low opacity so the
   card stays near-black and "expensive", not neon. */
.lane-bloom {
  position: absolute;
  z-index: -1;
  top: -54px;
  left: -34px;
  width: 190px;
  height: 190px;
  border-radius: 50%;
  filter: blur(36px);
  pointer-events: none;
}

/* Faint schematic line motif, bled off the right edge. */
.lane-motif-wrap {
  position: absolute;
  z-index: -1;
  right: -14px;
  top: 50%;
  transform: translateY(-50%);
  width: 116px;
  height: 116px;
  opacity: 0.07;
  pointer-events: none;
}
.lane-motif-wrap .lane-motif { width: 100%; height: 100%; }

.lane-token {
  flex-shrink: 0;
  width: 46px;
  height: 46px;
  border-radius: 13px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.lane-token svg { width: 23px; height: 23px; }

.lane-body {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  flex: 1;
}
.lane-title {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
}
.lane-sub {
  font-size: 13px;
  line-height: 1.35;
  color: var(--muted);
}

/* Quiet secondary modality (album / dictate). Outline pill, muted, sits a
   beat below the copy so it never competes with the primary lane action. */
.lane-alts {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 9px;
}
.lane-alt {
  align-self: flex-start;
  padding: 5px 11px 5px 9px;
  font-size: 12px;
  font-weight: 500;
  font-family: inherit;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border);
  color: var(--muted);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  transition: border-color 160ms var(--ease), color 160ms var(--ease), background 160ms var(--ease);
}
.lane-alt:hover {
  color: var(--text);
  border-color: var(--border-strong);
  background: rgba(255, 255, 255, 0.05);
}
.lane-alt-icon { display: inline-flex; }
.lane-alt-icon svg { width: 14px; height: 14px; }

.lane-chevron {
  flex-shrink: 0;
  align-self: center;
  color: var(--muted);
  display: inline-flex;
  transition: transform 220ms var(--ease), color 220ms var(--ease);
}
.lane-chevron svg { width: 20px; height: 20px; }
.lane-card:hover .lane-chevron { transform: translateX(2px); }

/* ---- AI axis (lime) ---- */
.lane-card--ai .lane-accent {
  background: var(--magic-grad);
  box-shadow: 0 0 14px rgba(78, 141, 118, 0.5);
}
.lane-card--ai .lane-bloom {
  background: radial-gradient(circle, rgba(78, 141, 118, 0.42), transparent 68%);
}
.lane-card--ai .lane-token {
  background: var(--magic-soft);
  border: 1px solid var(--border-ai);
  color: var(--magic);
}
.lane-card--ai .lane-motif { color: var(--magic); }
.lane-card--ai:hover { border-color: var(--border-ai); }
.lane-card--ai:hover .lane-chevron { color: var(--magic); }

/* ---- Commit axis (green) ---- */
.lane-card--know .lane-accent {
  background: var(--primary-grad);
  box-shadow: 0 0 14px rgba(78, 141, 118, 0.45);
}
.lane-card--know .lane-bloom {
  background: radial-gradient(circle, rgba(78, 141, 118, 0.36), transparent 68%);
}
.lane-card--know .lane-token {
  background: var(--primary-soft);
  border: 1px solid rgba(78, 141, 118, 0.35);
  color: var(--primary);
}
.lane-card--know .lane-motif { color: var(--primary); }
.lane-card--know:hover { border-color: rgba(78, 141, 118, 0.4); }
.lane-card--know:hover .lane-chevron { color: var(--primary); }

/* "or" divider between the two lanes — hairline rules flanking a small,
   letterspaced display label. */
.lane-or {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 2px;
  animation: laneIn 380ms var(--ease) both;
  animation-delay: 110ms;
}
.lane-or-rule {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--border) 30%, var(--border) 70%, transparent);
}
.lane-or-text {
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}

@keyframes laneIn {
  from { opacity: 0; transform: translateY(9px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .lane-card,
  .lane-or,
  .lane-accent {
    animation: none;
  }
  .lane-accent { transform: scaleY(1); opacity: 1; }
}

/* Single inline text input rendered under an assistant bubble. Used in
   place of quick-reply pills when we need a freeform answer (e.g. the
   recipient's name). Visually echoes the main composer input pill. */
.inline-input {
  display: block;
  width: 100%;
  margin: 6px 0 8px;
  animation: bubbleIn var(--t-mid) var(--ease);
}
.inline-input-row {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 6px 6px 16px;
  transition: border-color 120ms ease;
}
.inline-input-row:focus-within {
  border-color: var(--accent, #4E8D76);
}
.inline-input.error .inline-input-row {
  border-color: #FF6B6B;
  animation: shake 220ms ease;
}
.inline-input input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  outline: none;
  color: var(--text);
  font: inherit;
  font-size: 15px;
  padding: 8px 0;
}
.inline-input input::placeholder {
  color: var(--muted);
}
.inline-input-submit {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  border: none;
  background: transparent;
  padding: 0;
  color: var(--accent, #4E8D76);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.inline-input-submit:disabled {
  opacity: 0.5;
  cursor: default;
}
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}

/* ===== Composer =====
   Sized for big / gloved hands. Every directly-tappable affordance in
   this surface (the "+" button, the send arrow, the inline mic, and
   the quick-reply chips above) clears the Apple-HIG 44px minimum so a
   contractor wearing work gloves can hit them without zooming. The
   bottom padding respects the iOS home-bar safe area so the gesture
   region doesn't eat the composer. */
.composer {
  padding: 12px 14px max(14px, env(safe-area-inset-bottom));
  background: #0B1F2E;
  flex-shrink: 0;
}
/* Wraps the circular "+" button and the chat input pill side by side.
   A small horizontal margin keeps the row from hugging the phone bezel
   where the grip thumb naturally rests. */
.composer-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 2px;
}
/* Compact 3-row quick-action strip rendered above the composer bar.
   Mirrors the items in the iOS-style "+" slide-up sheet so the most
   common actions are reachable in a single tap. */
.composer-actions {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 8px;
  overflow: hidden;
  transform-origin: bottom center;
  transition: opacity 180ms var(--ease), transform 180ms var(--ease), max-height 180ms var(--ease);
  max-height: 240px;
}
.composer-actions[hidden] { display: none; }
.composer-actions.is-collapsing {
  opacity: 0;
  transform: translateY(6px) scaleY(0.96);
  max-height: 0;
  margin-bottom: 0;
  pointer-events: none;
}
.composer-action {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  background: transparent;
  border: none;
  border-radius: 10px;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.composer-action:hover:not(:disabled) { background: rgba(255, 255, 255, 0.05); }
.composer-action:active:not(:disabled) { background: rgba(78, 141, 118, 0.10); }
.composer-action:disabled { color: var(--muted); cursor: default; }
.composer-action-icon {
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--primary);
  flex-shrink: 0;
}
.composer-action:disabled .composer-action-icon { color: var(--muted); }
.composer-action-label {
  font-size: 16px;
  line-height: 1.25;
}

/* ===== Voice-first composer surface ==================================
   The mic is the headline. A wide lime pill sits between the quick
   actions and the text composer-bar so it reads as the *primary* way
   to put text into the AI — voice-to-quote is the verb tradespeople
   actually want. Uses the secondary-brand --magic lime so it doesn't
   compete with amber (which is reserved for money / confirmation).
   When SpeechRecognition isn't available the button is hidden in JS
   so we don't promise something the browser can't do. */
.voice-prompt {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 12px 14px;
  margin-bottom: 8px;
  border-radius: 14px;
  background: linear-gradient(180deg, rgba(78, 141, 118, 0.10), rgba(78, 141, 118, 0.04));
  border: 1.5px solid var(--magic);
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.005em;
  cursor: pointer;
  box-shadow: var(--magic-glow);
  -webkit-tap-highlight-color: transparent;
  transition:
    background 180ms var(--ease),
    transform 140ms var(--ease-spring, var(--ease)),
    box-shadow 200ms var(--ease);
}
.voice-prompt:hover { background: linear-gradient(180deg, rgba(78, 141, 118, 0.14), rgba(78, 141, 118, 0.06)); }
.voice-prompt:active { transform: scale(0.985); }
.voice-prompt:focus-visible {
  outline: 2px solid var(--magic);
  outline-offset: 3px;
}
.voice-prompt[hidden] { display: none; }
.voice-prompt-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--magic);
  color: #0A1500;
  flex-shrink: 0;
}
.voice-prompt-label {
  flex: 1;
  text-align: left;
}
/* Animated wave bars — visible only while listening. Each bar phases
   so they look like sound energy rather than a generic loader. */
.voice-prompt-waves {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  height: 18px;
  opacity: 0;
  transition: opacity 160ms var(--ease);
}
.voice-prompt-waves > span {
  display: block;
  width: 3px;
  height: 4px;
  border-radius: 2px;
  background: var(--magic);
  animation: voiceWave 900ms ease-in-out infinite;
}
.voice-prompt-waves > span:nth-child(2) { animation-delay: 120ms; }
.voice-prompt-waves > span:nth-child(3) { animation-delay: 240ms; }
.voice-prompt-waves > span:nth-child(4) { animation-delay: 360ms; }
.voice-prompt.is-listening {
  background: linear-gradient(180deg, rgba(78, 141, 118, 0.22), rgba(78, 141, 118, 0.10));
  animation: voicePulse 1.6s ease-in-out infinite;
}
.voice-prompt.is-listening .voice-prompt-waves { opacity: 1; }
/* Transcribing state — between user tapping stop and /api/transcribe
   returning the final text (~1–2 s on Whisper). Keeps the lime tint so
   the button still reads as "active AI" but drops the pulse so the user
   knows recording has ended. */
.voice-prompt.is-transcribing {
  background: linear-gradient(180deg, rgba(78, 141, 118, 0.14), rgba(78, 141, 118, 0.06));
  cursor: progress;
}
.voice-prompt.is-transcribing .voice-prompt-waves { opacity: 0.45; animation-duration: 2.4s; }
/* Transient error state — used when voiceCapture refuses to start
   (permission denied, network error, etc.). Falls back to idle after
   ~2.2s in JS. */
.voice-prompt.has-error {
  background: linear-gradient(180deg, rgba(234, 88, 12, 0.22), rgba(234, 88, 12, 0.10));
  border-color: rgba(234, 88, 12, 0.55);
  color: var(--error-text);
}
@keyframes voiceWave {
  0%, 100% { height: 4px; }
  50%      { height: 16px; }
}
@keyframes voicePulse {
  0%, 100% { box-shadow: 0 8px 28px rgba(78, 141, 118, 0.35); }
  50%      { box-shadow: 0 12px 36px rgba(78, 141, 118, 0.55); }
}
@media (prefers-reduced-motion: reduce) {
  .voice-prompt, .voice-prompt-waves > span { animation: none !important; }
}

.composer-plus {
  width: 48px; height: 48px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--surface-raised, var(--surface));
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid var(--border);
  color: var(--text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), transform 160ms var(--ease-spring, var(--ease));
}
.composer-plus:hover { border-color: rgba(255, 255, 255, 0.18); }
.composer-plus:active { background: rgba(255, 255, 255, 0.06); transform: scale(0.92); }
.composer-row {
  flex: 1;
  min-width: 0;
  display: flex;
  /* Centre the buttons vertically so they have equal air top and
     bottom — same look as the OpenAI composer. flex-end made the
     buttons sit flush against the bottom padding which pushed all
     the breathing room to the top. */
  align-items: center;
  gap: 10px;
  background: rgba(23, 26, 33, 0.7);
  -webkit-backdrop-filter: blur(22px) saturate(160%);
  backdrop-filter: blur(22px) saturate(160%);
  border: 1px solid var(--border);
  /* Softer, rounder corners to match the requested reference. */
  border-radius: 32px;
  /* Inner padding grown to 6px so the 44px send/mic buttons get a real
     landing pad inside the pill rather than sitting flush against the
     glass edge. Left padding 14px keeps the placeholder/text from
     hugging the rim. */
  padding: 6px 6px 6px 14px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
/* Mic ↔ Send swap: the textarea, send arrow, and inline mic share the
   trailing slot of the composer pill. When the input is empty, the mic
   is the primary CTA (voice-first product) and send is hidden so the
   user can't accidentally fire an empty submit while reaching for the
   mic. As soon as one character is typed, mic hides and send takes its
   place. The `is-empty` class is added/removed by the input listener in
   app.js. The `no-voice` class is set once at init when SpeechRecognition
   and MediaRecorder are both unavailable — in that scenario send is
   always visible because the mic will never render. */
.composer-row.is-empty .send-btn { display: none; }
.composer-row:not(.is-empty) .voice-prompt.voice-prompt--inline { display: none; }
.composer-row.no-voice .send-btn { display: inline-flex; }
.composer-row:focus-within {
  /* Neutral focus ring — the previous amber border + 3px halo lit up
     the entire composer in orange the moment the textarea was active,
     which read as a brand wash rather than a focus signal. Replaced
     with a quiet white hairline so the pill stays visually black. */
  border-color: rgba(255, 255, 255, 0.18);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
#composerInput {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  color: var(--text);
  /* 16px is the iOS Safari threshold that disables the autofocus-zoom
     gesture. Anything below pops the page to 110% on tap, then leaves
     it there — devastating for one-handed grip on a contractor's phone.
     Also: 16px is more legible in bright outdoor light. */
  font-size: 16px;
  font-family: inherit;
  padding: 10px 4px;
  min-width: 0;
  /* Multi-line auto-grow: the textarea expands to fit its content, driven
     by JS (autosize). No scrollbar — the field grows with the message,
     ChatGPT-style. */
  resize: none;
  line-height: 1.35;
  overflow: hidden;
  align-self: stretch;
  display: block;
}
#composerInput::-webkit-scrollbar { display: none; }
/* Stronger placeholder contrast — --muted (#8A93A6) on the blurred
   glass background fails APCA Lc 45 for body text in direct sun. Using
   --text at ~60% alpha keeps the "this is a hint, not user content"
   semantics while staying readable through safety glasses. */
#composerInput::placeholder { color: rgba(244, 246, 250, 0.62); }
.send-btn {
  /* Grown from 32→44px to clear the Apple-HIG fat-finger floor. The
     inner SVG (rendered at 36px) keeps the same proportions as the
     lime voice disc next to it so the two end-caps read as a matched
     pair. */
  width: 44px; height: 44px;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--primary);
  border: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  /* No drop-shadow — the disc colour is now hardcoded white in the SVG,
     so an orange halo would clash. A subtle neutral glow keeps the
     button feeling "alive" without competing with the lime voice disc. */
  filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.18));
  transition: filter var(--t-fast) var(--ease), transform 160ms var(--ease-spring, var(--ease));
}
.send-btn svg { width: 36px; height: 36px; }
.send-btn:hover { filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.32)); }
.send-btn:active { filter: brightness(.92); transform: scale(0.92); }

/* ===== Inline voice button (mic next to send arrow) =====
   The voice CTA used to live as a big lime pill above the composer.
   That read as clunky on phone-narrow viewports and split the user's
   eye away from the input they were typing into. This compact variant
   sits inside .composer-row, immediately before .send-btn, and shares
   its visual language (circular, transparent, idle-muted, accent on
   intent). Listening state is signalled by colour + a gentle pulse —
   no waves, no label text needed. The same JS still drives the
   .is-listening / .is-transcribing / .has-error classes. */
.voice-prompt.voice-prompt--inline {
  /* Reset the lime-pill base styles we inherit from .voice-prompt above.
     Grown from 32→44px to match .send-btn and clear the gloved-finger
     tap-target floor. The two end-cap buttons now read as the same
     visual weight, with colour (lime vs. white) and icon (waveform vs.
     arrow) doing the semantic work. */
  width: 44px;
  height: 44px;
  min-height: 0;
  padding: 0;
  margin: 0;
  border-radius: 50%;
  /* Solid lime disc with a black glyph — same visual weight as the
     send-arrow, but using the brand accent so "speech mode" reads
     instantly. State classes can swap to error red without touching
     the markup. */
  background: #4E8D76;
  border: none;
  color: #FFFFFF;
  box-shadow: 0 0 8px rgba(78, 141, 118, 0.45);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  font: inherit;
  letter-spacing: normal;
  flex-shrink: 0;
  animation: none;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), transform 160ms var(--ease-spring, var(--ease));
}
.voice-prompt.voice-prompt--inline:hover {
  background: #74B49C;
  box-shadow: 0 0 12px rgba(78, 141, 118, 0.7);
}
.voice-prompt.voice-prompt--inline:active { transform: scale(0.92); }
.voice-prompt.voice-prompt--inline:focus-visible {
  outline: 2px solid rgba(78, 141, 118, 0.85);
  outline-offset: 2px;
}
.voice-prompt.voice-prompt--inline .voice-prompt-icon {
  /* Glyph scales with the larger 44px disc — same ratio as the send
     arrow's 36px SVG inside its 44px button. */
  width: 26px;
  height: 26px;
  background: none;
  box-shadow: none;
}
.voice-prompt.voice-prompt--inline .voice-prompt-icon svg {
  width: 26px;
  height: 26px;
}
/* Hide label + wave decorations entirely when the button is inline.
   Some skin variants of .voice-prompt elsewhere may still render them. */
.voice-prompt.voice-prompt--inline .voice-prompt-label,
.voice-prompt.voice-prompt--inline .voice-prompt-waves { display: none; }
/* Listening — same lime fill, but pulsing ring so you can see at a
   glance that the mic is hot. */
.voice-prompt.voice-prompt--inline.is-listening {
  background: #74B49C;
  color: #FFFFFF;
  animation: voice-inline-pulse 1.4s ease-in-out infinite;
}
.voice-prompt.voice-prompt--inline.is-transcribing {
  background: var(--primary);
  color: #FFFFFF;
  box-shadow: 0 0 8px rgba(78, 141, 118, 0.55);
}
.voice-prompt.voice-prompt--inline.has-error {
  background: var(--danger, #ff6b6b);
  color: #0A0A0A;
  box-shadow: 0 0 8px rgba(255, 107, 107, 0.55);
}
@keyframes voice-inline-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(78, 141, 118, 0.0); }
  50%      { box-shadow: 0 0 0 5px rgba(78, 141, 118, 0.30); }
}
@media (prefers-reduced-motion: reduce) {
  .voice-prompt.voice-prompt--inline { animation: none !important; }
}

/* ===== Capture dock =================================================
   A row of three round actions sitting under the composer pill:
     • left  — choose from album (ghost/outlined)
     • centre— take photo (primary lime disc, larger)
     • right — dictate (the #voicePrompt button, ghost/outlined)
   Replaces the inline mic that used to live in the composer pill so the
   three "give Betty input" affordances read as a single, balanced set. */
.capture-dock {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 48px;
  margin-top: 12px;
  padding: 2px 0 2px;
}
.capture-dock-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  flex-shrink: 0;
  transition:
    transform 160ms var(--ease-spring, var(--ease)),
    background var(--t-fast) var(--ease),
    box-shadow var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease),
    filter var(--t-fast) var(--ease);
}
.capture-dock-btn--side {
  width: 56px;
  height: 56px;
  background: rgba(255, 255, 255, 0.04);
  border: 1.5px solid var(--border-strong, rgba(255, 255, 255, 0.16));
  color: var(--text);
}
.capture-dock-btn--side:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--magic);
}
.capture-dock-btn--side:active { transform: scale(0.92); }
.capture-dock-btn--side:focus-visible {
  outline: 2px solid var(--magic);
  outline-offset: 3px;
}
.capture-dock-btn--primary {
  width: 66px;
  height: 66px;
  background: var(--magic);
  border: none;
  color: #0A1500;
  box-shadow: var(--magic-glow, 0 8px 28px rgba(78, 141, 118, 0.4));
}
.capture-dock-btn--primary:hover { filter: brightness(1.06); }
.capture-dock-btn--primary:active { transform: scale(0.94); }
.capture-dock-btn--primary:focus-visible {
  outline: 2px solid var(--magic);
  outline-offset: 3px;
}
.capture-dock-btn svg { width: 26px; height: 26px; }
.capture-dock-btn--primary svg { width: 30px; height: 30px; }

/* #voicePrompt re-skinned as a dock side button. Resets the big lime-pill
   base styles it inherits from .voice-prompt and matches the album button,
   then recolours on listening/transcribing/error via the shared state
   classes the dictation JS already toggles. */
.voice-prompt.voice-prompt--dock {
  width: 56px;
  height: 56px;
  min-height: 0;
  padding: 0;
  margin: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.04);
  border: 1.5px solid var(--border-strong, rgba(255, 255, 255, 0.16));
  color: var(--text);
  box-shadow: none;
  gap: 0;
  justify-content: center;
  font: inherit;
  letter-spacing: normal;
  animation: none;
  flex-shrink: 0;
  transition:
    transform 160ms var(--ease-spring, var(--ease)),
    background var(--t-fast) var(--ease),
    box-shadow var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease);
}
.voice-prompt.voice-prompt--dock:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--magic);
}
.voice-prompt.voice-prompt--dock:active { transform: scale(0.92); }
.voice-prompt.voice-prompt--dock:focus-visible {
  outline: 2px solid var(--magic);
  outline-offset: 3px;
}
.voice-prompt.voice-prompt--dock .voice-prompt-icon {
  width: 26px;
  height: 26px;
  background: none;
  box-shadow: none;
  color: inherit;
}
.voice-prompt.voice-prompt--dock .voice-prompt-icon svg { width: 26px; height: 26px; }
.voice-prompt.voice-prompt--dock .voice-prompt-label,
.voice-prompt.voice-prompt--dock .voice-prompt-waves { display: none; }
.voice-prompt.voice-prompt--dock.is-listening {
  background: var(--magic);
  border-color: var(--magic);
  color: #0A1500;
  animation: voice-inline-pulse 1.4s ease-in-out infinite;
}
.voice-prompt.voice-prompt--dock.is-transcribing {
  background: var(--primary);
  border-color: var(--primary);
  color: #FFFFFF;
}
.voice-prompt.voice-prompt--dock.has-error {
  background: var(--danger, #ff6b6b);
  border-color: var(--danger, #ff6b6b);
  color: #0A0A0A;
}
@media (prefers-reduced-motion: reduce) {
  .voice-prompt.voice-prompt--dock { animation: none !important; }
}

/* ===== Inline form (client info) ===== */
.inline-form {
  align-self: stretch;
  background: rgba(20, 20, 20, 0.7);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-radius: 20px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  animation: bubbleIn var(--t-mid) var(--ease);
  position: relative;
  z-index: 2;
}
/* When an inline form is open, soften the rest of the chat behind it */
#messages.has-inline-form > *:not(.inline-form) {
  filter: blur(4px) saturate(0.85);
  opacity: 0.55;
  transition: filter var(--t-mid) var(--ease), opacity var(--t-mid) var(--ease);
  pointer-events: none;
}
#messages > *:not(.inline-form) {
  transition: filter var(--t-mid) var(--ease), opacity var(--t-mid) var(--ease);
}
.inline-form h4 {
  margin: 0 0 4px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.field { display: flex; flex-direction: column; gap: 4px; }
.field label { font-size: 12px; color: var(--muted); }
.field input, .field textarea {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 15px;
  font-family: inherit;
  outline: none;
  transition: border-color var(--t-fast) var(--ease);
}
.field input:focus, .field textarea:focus { border-color: var(--primary-deep); }
.field.error input, .field.error textarea { border-color: var(--error); }
.field .error-text {
  color: var(--error-text);
  font-size: 12px;
  margin-top: 2px;
}
.inline-form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 4px;
}
.inline-form-actions .btn-ghost {
  height: 40px;
  padding: 0 14px;
  border: none;
  background: transparent;
  color: var(--muted);
  font-weight: 500;
  border-radius: var(--radius-pill);
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.inline-form-actions .btn-ghost:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.06);
}
.inline-form-actions .btn-ghost:active {
  background: rgba(255, 255, 255, 0.10);
}

/* ===== Image bubble =====
   Shared styling for any photo bubble (uploaded photo, after-card image). */
.bubble.image {
  padding: 4px;
  background: var(--surface-raised);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border: 1px solid var(--border);
  position: relative;
  box-shadow: var(--shadow-card);
}
.bubble.image img {
  display: block;
  width: 240px;
  max-width: 100%;
  border-radius: 14px;
}

/* ===== Review sheet =====
   Replaces the old in-chat .task-card stack and .modal editor with a
   single full-bleed glass surface that animates up over the chat. Three
   sub-views live inside one container; the JS module flips
   data-view to swap which one is visible. A voice overlay sits on top
   when needed. No new layout primitives — reuses --ease-spring,
   --surface-raised, --primary-grad, --ai-soft, --border so the sheet
   feels native to the rest of the 2026 design language. */

.review-sheet {
  position: absolute;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: stretch;
  pointer-events: none;
  background: rgba(8, 10, 14, 0);
  transition: background var(--t-mid) var(--ease);
}
.review-sheet[hidden] { display: none; }
/* Hide the entire sheet (and remove it from the accessibility tree) when
   the JS hasn't marked it open. Without this, the inner view rules below
   would override the `hidden` attribute on individual <section>s and the
   summary view would render on initial page load. */
.review-sheet:not([data-open="true"]) { display: none; }
.review-sheet[data-open="true"] {
  pointer-events: auto;
  background: rgba(8, 10, 14, 0.55);
  -webkit-backdrop-filter: blur(8px) saturate(0.9);
  backdrop-filter: blur(8px) saturate(0.9);
}
/* Soften chat chrome while sheet is open so the review reads as the
   focused layer (mirrors the inline-form pattern that the modal used). */
.phone.review-open .chat-header,
.phone.review-open .chat-scroll,
.phone.review-open .composer {
  filter: blur(4px) saturate(0.85);
  opacity: 0.55;
  pointer-events: none;
  transition: filter var(--t-mid) var(--ease), opacity var(--t-mid) var(--ease);
}

.review-sheet-card {
  width: 100%;
  /* Summary view sits as a true bottom sheet at ~75% of the phone height
     so the photo from the previous step peeks behind it. Adjust/wayoff
     views expand this same surface to 100% via the data-view rule below. */
  height: 75%;
  max-height: 75%;
  display: flex;
  flex-direction: column;
  background: var(--surface-raised);
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  backdrop-filter: blur(28px) saturate(180%);
  border-top: 1px solid var(--border-strong);
  border-radius: 20px 20px 0 0;
  box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.3),
              0 -24px 60px -16px rgba(0, 0, 0, 0.6),
              inset 0 1px 0 rgba(255, 255, 255, 0.08);
  transform: translateY(100%);
  /* Default (collapse-back) is the snappier 250ms ease-in. Expansion to
     adjust/wayoff overrides to 300ms ease-out below. Transform keeps its
     own 420ms spring for the initial slide-up. */
  transition: transform 420ms var(--ease-spring, var(--ease)),
              height 250ms ease-in,
              max-height 250ms ease-in,
              border-radius 250ms ease-in,
              box-shadow 250ms ease-in;
  position: relative;
  overflow: hidden;
}
.review-sheet[data-open="true"] .review-sheet-card {
  transform: translateY(0);
}
/* When the contractor enters Adjust or Way-Off, the same surface grows
   upward to fill the phone. iPhone SE (375×667) needs the full 100% for
   the task list + RunningTotal + ConfirmBar (or textarea + keyboard) to
   breathe. Border-radius animates to 0 because rounded corners on a
   full-screen panel read as a bug, not a sheet. */
.review-sheet[data-view="adjust"] .review-sheet-card,
.review-sheet[data-view="wayoff"] .review-sheet-card {
  height: 100%;
  max-height: 100%;
  border-radius: 0;
  box-shadow: none;
  transition: transform 420ms var(--ease-spring, var(--ease)),
              height 300ms ease-out,
              max-height 300ms ease-out,
              border-radius 300ms ease-out,
              box-shadow 300ms ease-out;
}
/* Soft handle bar at the top, like an iOS sheet */
.review-sheet-card::before {
  content: '';
  position: absolute;
  top: 8px;
  left: 50%;
  transform: translateX(-50%);
  width: 40px;
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.28);
  pointer-events: none;
}
/* Once expanded to full-screen the handle no longer reads as a sheet
   affordance — fade it out so the chrome stays honest. */
.review-sheet[data-view="adjust"] .review-sheet-card::before,
.review-sheet[data-view="wayoff"] .review-sheet-card::before {
  opacity: 0;
}

/* ---------- Sheet title ----------
   The dialog's aria-labelledby target. In a full-screen modal we'd use
   24-28px; bottom sheets get a tighter 20px so the heading reads as a
   sheet header, not a page title. Only shown in the summary view —
   adjust/wayoff have their own in-view headers (back button + totals,
   or "Beskriv hvad jeg har misset"). */
.review-sheet-title {
  margin: 0;
  padding: 22px 22px 0;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: #F9FAFB;
  flex-shrink: 0;
}
.review-sheet:not([data-view="summary"]) .review-sheet-title {
  display: none;
}

/* ===== Inline review card =====
   When the task-review flow opens with { inline: true }, the same
   #reviewSheet node is relocated into the chat message stream and
   restyled from a bottom-sheet overlay into a flat inline card, so the
   review reads as part of the conversation instead of a modal that
   covers it. All inner views/handlers are unchanged. */
/* Frozen snapshot — a read-only clone left behind after the contractor
   confirms scope, so the card stays in the chat record while the flow
   moves on. Drop interactivity and hide the now-meaningless action
   controls (confirm/CTA/add/mic), keeping the informational content. */
.review-sheet--frozen { pointer-events: none; }
.review-sheet--frozen .review-confirm-bar,
.review-sheet--frozen .review-cta-stack,
.review-sheet--frozen .review-add-prompt,
.review-sheet--frozen .review-mic-btn,
.review-sheet--frozen .review-voice-overlay,
.review-sheet--frozen .review-excluded-addbtn,
.review-sheet--frozen .review-excluded-add,
.review-sheet--frozen .review-excl-remove { display: none; }
.review-sheet.is-inline {
  /* Blueprint palette — shared with .blueprint-card so the review reads as
     a sibling of the støttemur drawing. Surface is lifted one step above the
     #0B1F2E chat base so the card doesn't melt into the background. */
  --bp-navy: #15324A;
  --bp-line: #8FD6EA;
  --bp-accent: #56C7E0;
  --bp-grid: rgba(127, 212, 232, 0.10);
  position: static;
  inset: auto;
  z-index: auto;
  display: block;
  align-items: stretch;
  pointer-events: auto;
  background: none !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
  margin: 4px 0 8px;
}
.review-sheet.is-inline .review-sheet-card {
  position: static;
  width: 100%;
  height: auto !important;
  max-height: none !important;
  transform: none !important;
  /* Navy + faint grid, like the drawing canvas. */
  background:
    linear-gradient(var(--bp-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--bp-grid) 1px, transparent 1px),
    var(--bp-navy);
  background-size: 20px 20px, 20px 20px, auto;
  border: 1px solid rgba(127, 212, 232, 0.38);
  border-radius: var(--radius-card);
  box-shadow:
    0 14px 34px -16px rgba(0, 0, 0, 0.85),
    0 2px 6px -2px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(143, 214, 234, 0.16);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  overflow: hidden;
  transition: none;
}
/* The drag handle only makes sense on a draggable bottom sheet. */
.review-sheet.is-inline .review-sheet-card::before { display: none; }
.review-sheet.is-inline .review-view-adjust { flex: none; }
/* Sticky header + bottom bar belong to a full-height overlay; inline they
   just sit at the card's top/bottom and scroll with the chat. */
.review-sheet.is-inline .review-running-total {
  position: static;
  padding: 2px 16px 12px;
  background: transparent;
  border-bottom: 1px solid rgba(127, 212, 232, 0.14);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.review-sheet.is-inline .review-total-meta { color: rgba(191, 230, 241, 0.72); }
.review-sheet.is-inline .review-total-price { color: var(--primary); }
.review-sheet.is-inline .review-total-numbers { align-items: flex-start; }
.review-sheet.is-inline .review-task-list {
  flex: none;
  overflow: visible;
  max-height: none;
  padding: 4px 14px;
}
.review-sheet.is-inline .review-row {
  border-bottom: 1px solid rgba(127, 212, 232, 0.10);
}
.review-sheet.is-inline .review-row-title {
  color: #EAF8FC;
  cursor: text;
}
.review-sheet.is-inline .review-row-hours-val { color: #EAF8FC; }
.review-sheet.is-inline .review-row-line2 { color: rgba(191, 230, 241, 0.6); }
.review-sheet.is-inline .review-row-mats-input,
.review-sheet.is-inline .review-title-input {
  background: rgba(6, 22, 33, 0.6);
  border-color: rgba(127, 212, 232, 0.3);
  color: #EAF8FC;
}
.review-sheet.is-inline .review-confirm-bar {
  position: static;
  padding: 12px 16px 16px;
  background: transparent;
  border-top: 1px solid rgba(127, 212, 232, 0.14);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.review-sheet.is-inline .review-add-collapsed {
  border-color: rgba(58, 138, 56, 0.45);
  color: #2E7D32;
  font-weight: 600;
}
.review-sheet.is-inline .review-add-collapsed:hover {
  color: #246B22;
  border-color: rgba(58, 138, 56, 0.7);
  background: rgba(58, 138, 56, 0.06);
}
.review-sheet.is-inline .review-add-mic {
  background: rgba(58, 138, 56, 0.10);
  color: #2E7D32;
  border-color: rgba(58, 138, 56, 0.40);
}
.review-sheet.is-inline .review-add-mic:hover { background: rgba(58, 138, 56, 0.18); }
.review-sheet.is-inline .review-add-mic.is-listening {
  background: var(--danger, #C0392B);
  border-color: var(--danger, #C0392B);
  color: #FFFFFF;
}

/* ============================================================
   RECEIPT SKIN — inline task card (2026-06-13)
   Re-themes .review-sheet.is-inline from the blueprint look into a
   torn customer receipt while preserving EVERY interactive element
   (checkbox, hours slider, materials editor, exclusions, AI
   suggestions, confirm CTA). Keeps the brand palette — amber stays
   "money/ink", lime stays "AI" — but renders the surface as warm
   dark thermal paper with sawtooth torn top/bottom edges (CSS mask),
   monospace line-items, dashed section rules and a faux-barcode
   footer. Scoped entirely to .is-inline so the bottom-sheet variant
   is untouched. This block is placed AFTER the base inline rules so
   it overrides them; later .review-bp-* rules are lower specificity
   and don't fight it.
   ============================================================ */
.review-sheet.is-inline {
  /* Warm thermal-paper tokens — a real customer receipt printed on warm
     cream stock with dark blue-gray ink. The money/interaction cue is a
     DEEP BLUE-GRAY (was amber); lime/green stays the "AI" cue. */
  --rcpt-paper-a: #FBF6EC;   /* warm cream */
  --rcpt-paper-b: #EFE5D3;   /* deeper warm ecru */
  --rcpt-ink: #1B2430;
  --rcpt-ink-soft: rgba(27, 36, 48, 0.55);
  --rcpt-line: rgba(27, 36, 48, 0.20);
  /* Deep blue-gray accent: hero price + interactive cues (slider fill,
     checkbox, confirm CTA, focus rings). */
  --rcpt-accent: #2F4F6B;
  --rcpt-accent-strong: #243B50;
  --rcpt-accent-soft: rgba(47, 79, 107, 0.14);
  --rcpt-on-accent: #FBF6EC;
  --rcpt-scallop: 16px;   /* width of one scalloped bump (= circle diameter) */
  --rcpt-scallop-h: 8px;  /* bump radius / how tall the scalloped strip is */
}
.review-sheet.is-inline .review-sheet-card {
  /* Flat warm paper. No texture, no grid, no rounded corners, no border —
     the torn silhouette is the frame. */
  background: linear-gradient(180deg, var(--rcpt-paper-a) 0%, var(--rcpt-paper-b) 100%);
  background-size: auto;
  border: none;
  border-radius: 0;
  box-shadow: none;
  /* Scalloped torn edges: a solid body inset by the bump radius, plus two
     rows of half-circles that bulge out of the top and bottom strips. The
     circles tile at exactly their diameter so adjacent bumps meet in a
     crisp cusp — the classic ticket-stub / cash-receipt fringe. Layers
     composite as a union, so the result is paper plus the round bumps. */
  -webkit-mask:
    linear-gradient(#000 0 0) no-repeat 0 var(--rcpt-scallop-h) / 100% calc(100% - 2 * var(--rcpt-scallop-h)),
    radial-gradient(circle var(--rcpt-scallop-h) at 50% 100%, #000 98%, #0000) repeat-x 0 0    / var(--rcpt-scallop) var(--rcpt-scallop-h),
    radial-gradient(circle var(--rcpt-scallop-h) at 50% 0%,   #000 98%, #0000) repeat-x 0 100% / var(--rcpt-scallop) var(--rcpt-scallop-h);
          mask:
    linear-gradient(#000 0 0) no-repeat 0 var(--rcpt-scallop-h) / 100% calc(100% - 2 * var(--rcpt-scallop-h)),
    radial-gradient(circle var(--rcpt-scallop-h) at 50% 100%, #000 98%, #0000) repeat-x 0 0    / var(--rcpt-scallop) var(--rcpt-scallop-h),
    radial-gradient(circle var(--rcpt-scallop-h) at 50% 0%,   #000 98%, #0000) repeat-x 0 100% / var(--rcpt-scallop) var(--rcpt-scallop-h);
  /* box-shadow is clipped by the mask, so the paper lift comes from a
     drop-shadow filter which follows the toothed alpha silhouette. */
  filter: drop-shadow(0 12px 22px rgba(0, 0, 0, 0.55));
}

/* ---------- Receipt header (store name) ---------- */
.review-sheet.is-inline .review-bp-head {
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 18px 16px 11px;
  border-bottom: 1px dashed var(--rcpt-line);
}
.review-sheet.is-inline .review-bp-left { gap: 8px; }
.review-sheet.is-inline .review-bp-spark { color: var(--magic); }
.review-sheet.is-inline .review-bp-label {
  font-family: var(--font);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--rcpt-ink);
}
.review-sheet.is-inline .review-bp-project {
  font-family: var(--font);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.34em;
  color: var(--rcpt-ink-soft);
}
.review-sheet.is-inline .review-doctype-wrap { align-items: center; gap: 5px; }
.review-sheet.is-inline .review-doctype {
  background: rgba(0, 0, 0, 0.04);
  border: 1px solid var(--rcpt-line);
}
.review-sheet.is-inline .review-doctype-btn {
  font-family: var(--font);
  color: var(--rcpt-ink-soft);
  letter-spacing: 0.18em;
}
.review-sheet.is-inline .review-doctype-btn:hover { color: var(--rcpt-ink); }
.review-sheet.is-inline .review-doctype-btn.is-active {
  background: var(--magic);
  color: #0E2A0C;
}
.review-sheet.is-inline .review-doctype-hint {
  font-family: var(--font);
  color: var(--rcpt-ink-soft);
  letter-spacing: 0.06em;
}

/* ---------- Receipt TOTAL block ---------- */
.review-sheet.is-inline .review-running-total {
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 13px 16px;
  text-align: center;
  border-bottom: 1px dashed var(--rcpt-line);
}
/* price on top (hero), meta caption beneath — receipt total layout. */
.review-sheet.is-inline .review-total-numbers {
  flex-direction: column-reverse;
  align-items: center;
  gap: 3px;
}
.review-sheet.is-inline .review-total-price {
  font-family: var(--font-mono);
  font-size: 40px;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--rcpt-accent);
}
.review-sheet.is-inline .review-total-meta {
  font-family: var(--font);
  font-size: 10.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--rcpt-ink-soft);
}

/* ---------- Order note (editable job desc) ---------- */
.review-sheet.is-inline .review-job-desc {
  margin: 11px 16px;
  padding: 9px 12px;
  border: 1px dashed var(--rcpt-line);
  border-radius: 0;
  background: transparent;
  color: var(--rcpt-ink);
  text-align: center;
  font-family: var(--font);
  font-size: 12.5px;
  letter-spacing: 0.03em;
}
.review-sheet.is-inline .review-job-desc:hover,
.review-sheet.is-inline .review-job-desc:focus {
  border-color: var(--rcpt-accent);
}

/* ---------- Line items (tasks) ---------- */
.review-sheet.is-inline .review-task-list {
  padding: 2px 16px;
  gap: 0;
}
.review-sheet.is-inline .review-row {
  border-bottom: 1px dashed var(--rcpt-line);
  padding: 10px 0;
}
.review-sheet.is-inline .review-row:last-of-type { border-bottom: none; }
.review-sheet.is-inline .review-row-title {
  font-family: var(--font);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.005em;
  color: var(--rcpt-ink);
}
.review-sheet.is-inline .review-row-hours-val {
  font-family: var(--font-mono);
  color: var(--rcpt-ink);
}
.review-sheet.is-inline .review-row-line2 {
  font-family: var(--font);
  font-size: 11.5px;
  letter-spacing: 0.02em;
  color: var(--rcpt-ink-soft);
}
.review-sheet.is-inline .review-row-qty { color: var(--rcpt-ink); }
.review-sheet.is-inline .review-row-qty-sep { color: var(--rcpt-ink-soft); }

/* ---------- Sections: dashed rules, mono headers ---------- */
.review-sheet.is-inline .review-excluded,
.review-sheet.is-inline .review-suggest {
  margin: 0 16px;
  border-top: 1px dashed var(--rcpt-line);
}
/* "Ikke inkluderet" is hidden in the receipt skin — a customer receipt
   lists what you're paying for, not a negative-space afgrænsning block.
   Scoped to .is-inline so the excluded data still tracks underneath
   (toggled-off tasks/custom lines keep flowing to the final proposal). */
.review-sheet.is-inline .review-excluded { display: none; }
.review-sheet.is-inline .review-excluded-title,
.review-sheet.is-inline .review-suggest-title {
  font-family: var(--font);
  letter-spacing: 0.16em;
}
.review-sheet.is-inline .review-suggest-row {
  gap: 12px;
  background: #FFFDF8;
  border: 1px solid rgba(27, 36, 48, 0.10);
  border-radius: 14px;
  padding: 10px 12px;
  box-shadow: 0 1px 3px rgba(27, 36, 48, 0.07);
}
.review-sheet.is-inline .review-suggest-row:hover {
  background: #FFFFFF;
  border-color: rgba(58, 138, 56, 0.45);
  box-shadow: 0 4px 12px rgba(27, 36, 48, 0.10);
}
.review-sheet.is-inline .review-suggest-icon {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(58, 138, 56, 0.12);
  color: #2E7D32;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.review-sheet.is-inline .review-suggest-icon svg {
  width: 22px;
  height: 22px;
}
.review-sheet.is-inline .review-suggest-row,
.review-sheet.is-inline .review-suggest-row-title {
  color: var(--rcpt-ink);
}
.review-sheet.is-inline .review-suggest-row-meta,
.review-sheet.is-inline .review-suggest-sub,
.review-sheet.is-inline .review-suggest-status {
  color: var(--rcpt-ink-soft);
}
/* AI cue on white paper: lime is illegible as text, so the title + spark
   shift to a deeper forest green that still reads as the AI accent while
   clearing WCAG contrast on the receipt stock. */
.review-sheet.is-inline .review-suggest-title { color: #2E7D32; }
.review-sheet.is-inline .review-suggest-spark,
.review-sheet.is-inline .review-bp-spark { color: #3A8A38; }
.review-sheet.is-inline .review-suggest-add {
  width: 36px;
  height: 36px;
  border: 1.5px solid rgba(58, 138, 56, 0.5);
  background: transparent;
  color: #2E7D32;
  font-size: 20px;
}
.review-sheet.is-inline .review-suggest-retry {
  border-color: rgba(58, 138, 56, 0.5);
  color: #2E7D32;
}
.review-sheet.is-inline .review-suggest-retry:hover {
  background: rgba(58, 138, 56, 0.1);
}
/* Edit-mode inputs as ink-on-paper instead of the dark navy fields. */
.review-sheet.is-inline .review-row-mats-input,
.review-sheet.is-inline .review-title-input,
.review-sheet.is-inline .review-excl-input {
  background: #FFFDF8;
  border-color: rgba(27, 36, 48, 0.3);
  color: var(--rcpt-ink);
}
.review-sheet.is-inline .review-row-mats-input:focus,
.review-sheet.is-inline .review-title-input:focus,
.review-sheet.is-inline .review-excl-input:focus {
  border-color: var(--rcpt-accent);
}
.review-sheet.is-inline .review-suggest-row-title {
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 700;
}
.review-sheet.is-inline .review-suggest-row-meta {
  font-family: var(--font-display);
  font-size: 12px;
}

/* Slider on warm paper: light unfilled track + bordered thumb so both
   read against the receipt stock (deep blue-gray fill is the money cue). */
.review-sheet.is-inline .review-row-slider::-webkit-slider-runnable-track {
  background: linear-gradient(to right,
    var(--rcpt-accent) 0%, var(--rcpt-accent) var(--review-slider-fill, 50%),
    rgba(27, 36, 48, 0.18) var(--review-slider-fill, 50%), rgba(27, 36, 48, 0.18) 100%);
}
.review-sheet.is-inline .review-row-slider::-moz-range-track {
  background: rgba(27, 36, 48, 0.18);
}
.review-sheet.is-inline .review-row-slider::-moz-range-progress {
  background: var(--rcpt-accent);
}
.review-sheet.is-inline .review-row-slider::-webkit-slider-thumb {
  background: #FFFDF8;
  border: 1px solid var(--rcpt-accent);
  box-shadow: 0 2px 6px rgba(36, 59, 80, 0.35);
}
.review-sheet.is-inline .review-row-slider::-moz-range-thumb {
  background: #FFFDF8;
  border: 1px solid var(--rcpt-accent);
  box-shadow: 0 2px 6px rgba(36, 59, 80, 0.35);
}

/* ---------- Interaction accents: deep blue-gray (was amber/gold) ---------- */
/* Checkbox in its included state: the base rule paints it brand green (#4E8D76);
   on warm receipt paper the money/interaction cue is the deep blue-gray. */
.review-sheet.is-inline .review-row.is-included .review-row-toggle {
  background: var(--rcpt-accent);
  border-color: var(--rcpt-accent);
  color: var(--rcpt-on-accent);
}
.review-sheet.is-inline .review-row-toggle:focus-visible {
  outline: 2px solid var(--rcpt-accent);
  outline-offset: 2px;
}
/* Confirm CTA: base uses the amber gradient; re-skin to a deep blue-gray
   fill so the primary action matches the receipt accent. */
.review-sheet.is-inline .review-confirm-btn {
  background: linear-gradient(135deg, var(--rcpt-accent) 0%, var(--rcpt-accent-strong) 100%);
  color: var(--rcpt-on-accent);
  border-color: var(--rcpt-accent-strong);
}
.review-sheet.is-inline .review-confirm-btn:disabled {
  background: rgba(27, 36, 48, 0.10);
  color: var(--rcpt-ink-soft);
  border-color: var(--rcpt-line);
}
/* "Edited" dot: base is amber; match the receipt's blue-gray accent. */
.review-sheet.is-inline .review-row-edited-dot {
  background: var(--rcpt-accent);
}

/* ---------- Confirm bar + faux-barcode footer ---------- */
.review-sheet.is-inline .review-confirm-bar {
  border-top: 1px dashed var(--rcpt-line);
}
/* Decorative tear-off footer: barcode + brand caption. Removed during the
   Betty rebrand — the old "ARMBOLT.AI" maker mark no longer belongs on the
   task overview, so the strip stays hidden in every mode. */
.review-receipt-strip { display: none; }

/* ---------- Blueprint header (adjust view) ---------- */
.review-bp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 11px 16px 8px;
}
.review-bp-left { display: inline-flex; align-items: center; gap: 7px; }
.review-bp-spark { color: var(--bp-accent, #56C7E0); display: inline-flex; }
.review-bp-label {
  font-family: 'Space Grotesk', 'Inter', sans-serif;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #D7F0F8;
}
.review-bp-project {
  font-size: 12px;
  font-weight: 500;
  color: var(--bp-accent, #56C7E0);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* ---------- Overslag / Tilbud document-type toggle ---------- */
.review-doctype-wrap {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}
.review-doctype {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border-radius: 999px;
  background: rgba(127, 212, 232, 0.12);
  border: 1px solid rgba(127, 212, 232, 0.20);
}
.review-doctype-btn {
  appearance: none;
  border: none;
  background: transparent;
  cursor: pointer;
  padding: 4px 12px;
  border-radius: 999px;
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #9FCEDC;
  transition: background 0.15s ease, color 0.15s ease;
}
.review-doctype-btn:hover { color: #D7F0F8; }
.review-doctype-btn.is-active {
  background: var(--bp-accent, #56C7E0);
  color: #06222F;
  font-weight: 700;
}
.review-doctype-hint {
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: #9FCEDC;
}

/* ---------- Editable job description ---------- */
.review-job-desc {
  margin: 0 16px 8px;
  padding: 9px 12px;
  border: 1px solid rgba(127, 212, 232, 0.18);
  border-radius: 8px;
  background: rgba(6, 22, 33, 0.45);
  color: #DCEFF6;
  font-family: var(--font-display);
  font-size: 13px;
  line-height: 1.45;
  outline: none;
  cursor: text;
  white-space: pre-wrap;
  word-break: break-word;
  transition: border-color var(--t-fast) var(--ease);
}
.review-job-desc:hover { border-color: rgba(127, 212, 232, 0.32); }
.review-job-desc:focus { border-color: var(--bp-accent, #56C7E0); }
.review-job-desc:empty::before {
  content: attr(data-placeholder);
  color: rgba(191, 230, 241, 0.4);
}

/* ---------- Inline title editor ---------- */
.review-title-input {
  flex: 1;
  min-width: 0;
  margin-left: 12px;
  height: 32px;
  border-radius: 8px;
  padding: 4px 10px;
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 500;
  background: #1F2327;
  border: 1px solid #555;
  color: #F9FAFB;
  outline: none;
  box-sizing: border-box;
}
.review-title-input:focus { border-color: var(--bp-accent, #56C7E0); }

/* ---------- "Ikke inkluderet" section ---------- */
.review-excluded {
  margin: 2px 14px 0;
  padding: 10px 0 4px;
  border-top: 1px dashed rgba(127, 212, 232, 0.2);
}
.review-excluded-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0 2px 6px;
}
.review-excluded-title {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(191, 230, 241, 0.78);
}
.review-excluded-icon { color: rgba(143, 214, 234, 0.6); display: inline-flex; }
.review-excluded-addbtn {
  flex-shrink: 0;
  background: transparent;
  border: 1px solid rgba(127, 212, 232, 0.28);
  color: var(--bp-accent, #56C7E0);
  border-radius: 999px;
  padding: 3px 12px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.review-excluded-addbtn:hover {
  background: rgba(86, 199, 224, 0.1);
  border-color: rgba(127, 212, 232, 0.5);
}
.review-excluded-add {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 2px 8px;
}
.review-excluded-add[hidden] { display: none; }
.review-excl-input {
  flex: 1;
  height: 34px;
  border-radius: 8px;
  padding: 4px 10px;
  font: inherit;
  font-size: 13px;
  background: rgba(6, 22, 33, 0.6);
  border: 1px solid rgba(127, 212, 232, 0.3);
  color: #EAF8FC;
  outline: none;
}
.review-excl-input:focus { border-color: var(--bp-accent, #56C7E0); }
.review-excl-send {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: none;
  background: var(--bp-accent, #56C7E0);
  color: #06202B;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.review-excl-send:disabled { opacity: 0.4; cursor: not-allowed; }
.review-excl-send:active:not(:disabled) { transform: scale(0.92); }
.review-excl-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 2px;
}
.review-excl-text {
  flex: 1;
  min-width: 0;
  font-size: 12.5px;
  color: rgba(191, 230, 241, 0.72);
  overflow-wrap: anywhere;
}
.review-excl-row--task .review-excl-text {
  text-decoration: line-through;
  text-decoration-color: rgba(191, 230, 241, 0.35);
}
.review-excl-restore,
.review-excl-remove {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid rgba(127, 212, 232, 0.25);
  background: transparent;
  color: rgba(191, 230, 241, 0.7);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.review-excl-restore:hover { background: rgba(86, 199, 224, 0.12); color: var(--bp-accent, #56C7E0); }
.review-excl-remove:hover { background: rgba(229, 72, 77, 0.14); color: #FF8A8A; border-color: rgba(229, 72, 77, 0.4); }
.review-excl-restore svg,
.review-excl-remove svg { display: block; }
.review-excluded-empty {
  margin: 2px 2px 4px;
  font-size: 12px;
  color: rgba(191, 230, 241, 0.4);
}
.review-excluded-empty[hidden] { display: none; }

/* ---------- "Forslag fra AI" (suggested additions) ---------- */
.review-suggest {
  margin: 2px 14px 0;
  padding: 10px 0 4px;
  border-top: 1px dashed rgba(78, 141, 118, 0.22);
}
.review-suggest[hidden] { display: none; }
.review-suggest-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0 2px 4px;
}
.review-suggest-title {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(180, 232, 178, 0.82);
}
.review-suggest-spark { color: var(--magic, #4E8D76); display: inline-flex; }
.review-suggest-sub {
  margin: 0 2px 8px;
  font-size: 12px;
  color: rgba(191, 230, 241, 0.5);
}
.review-suggest-list {
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.review-suggest-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  background: rgba(233, 248, 252, 0.05);
  border: 1px solid rgba(78, 141, 118, 0.3);
  border-radius: 12px;
  padding: 9px 11px;
  cursor: pointer;
  font-family: inherit;
  color: #EAF8FC;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.review-suggest-row:hover {
  background: rgba(233, 248, 252, 0.09);
  border-color: rgba(78, 141, 118, 0.5);
}
.review-suggest-row:active { transform: scale(0.99); }
.review-suggest-icon {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: rgba(78, 141, 118, 0.12);
  color: #4E8D76;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.review-suggest-icon svg {
  width: 20px;
  height: 20px;
}
.review-suggest-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.review-suggest-row-title {
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
.review-suggest-row-meta {
  font-size: 11.5px;
  color: rgba(213, 240, 247, 0.85);
}
.review-suggest-add {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid rgba(78, 141, 118, 0.4);
  background: rgba(78, 141, 118, 0.14);
  color: var(--magic, #4E8D76);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  line-height: 1;
  pointer-events: none;
}
.review-suggest-status {
  margin: 4px 2px;
  font-size: 12px;
  color: rgba(191, 230, 241, 0.5);
}
.review-suggest-status[hidden] { display: none; }
.review-suggest-retry {
  margin: 4px 2px 0;
  background: transparent;
  border: 1px solid rgba(78, 141, 118, 0.35);
  color: var(--magic, #4E8D76);
  border-radius: 999px;
  padding: 4px 14px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.review-suggest-retry:hover { background: rgba(78, 141, 118, 0.12); }
.review-suggest-retry[hidden] { display: none; }
.review-sheet--frozen .review-suggest { display: none; }

/* ---------- Views ---------- */
.review-view { display: none; flex-direction: column; min-height: 0; }
.review-view[hidden] { display: none; }
.review-sheet[data-view="summary"]  .review-view-summary,
.review-sheet[data-view="adjust"]   .review-view-adjust,
.review-sheet[data-view="wayoff"]   .review-view-wayoff,
.review-sheet[data-view="identity"] .review-view-identity {
  display: flex;
  animation: reviewViewIn 320ms var(--ease-spring, var(--ease));
}
@keyframes reviewViewIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ---------- Summary view ---------- */
.review-view-summary {
  padding: 14px 22px 28px;
  gap: 14px;
  text-align: left;
}
.review-assessment-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  /* Lime, not blue. This label sits on top of an AI-generated assessment,
     so per the system (lime = "AI just did magic for you") it gets the
     --magic accent. Pairs with the lime typing dots, voice prompt, and
     after-reveal CTA. */
  color: var(--magic-deep);
}
.review-sparkle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--magic-soft);
  border: 1px solid var(--magic);
  color: var(--magic-deep);
  animation: aiPulse 2.4s ease-in-out infinite;
}
.review-description {
  margin: 0;
  font-family: var(--font-display);
  font-size: 17px;
  line-height: 1.4;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
  white-space: pre-wrap;
}
.review-stats {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.review-cta-stack {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 14px;
}
.review-cta {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 16px 18px;
  border-radius: 16px;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: transform var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease),
              box-shadow var(--t-fast) var(--ease);
  -webkit-tap-highlight-color: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  background: var(--surface);
}
.review-cta:active { transform: scale(0.985); }
.review-cta-confirm {
  background: var(--primary-grad);
  color: #FFFFFF;
  border-color: rgba(78, 141, 118, 0.55);
  box-shadow: 0 12px 28px -16px rgba(78, 141, 118, 0.7);
}
.review-cta-confirm:hover { box-shadow: 0 16px 36px -16px rgba(78, 141, 118, 0.85); }
.review-cta-adjust {
  background: var(--surface-raised);
  color: var(--text);
}
.review-cta-wayoff {
  background: transparent;
  color: var(--muted);
  border-style: dashed;
  border-color: rgba(255, 255, 255, 0.14);
}
.review-cta-wayoff:hover { color: var(--text); border-color: rgba(255, 255, 255, 0.28); }

/* Right-edge icon slot for the three review CTAs. Pure flex centering
   so the inline SVG sits on the text baseline at any line-height, and
   `flex-shrink: 0` so the icon never compresses when the label wraps
   on narrow viewports. SVGs already paint with currentColor, so they
   pick up the per-variant text colour automatically (dark amber-text
   on the confirm CTA, white on adjust, muted-grey → text on hover for
   wayoff). */
.review-cta-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  line-height: 0;
}
.review-cta-icon svg { display: block; }

/* ---------- Adjust view ---------- */
.review-view-adjust {
  flex: 1;
  min-height: 0;
}
.review-running-total {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  /* Top padding folds in the iOS safe-area inset so the sticky header
     never collides with the system status bar (time/signal/battery).
     `.review-sheet` is `position: absolute; inset: 0` over `.phone`,
     which means it lays over the phone's `padding-top:
     env(safe-area-inset-top)` rather than respecting it — the inset
     has to be re-applied here. Falls back to the original 18px on
     non-notched devices / desktop. */
  padding: calc(18px + env(safe-area-inset-top, 0px)) 18px 12px;
  background: linear-gradient(180deg, rgba(11, 13, 18, 0.95) 0%, rgba(11, 13, 18, 0.85) 100%);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  border-bottom: 1px solid var(--border);
}
.review-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: none;
  padding: 6px 8px;
  margin-left: -8px;
  border-radius: 10px;
  color: var(--muted);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.review-back:hover { color: var(--text); background: rgba(255, 255, 255, 0.04); }
.review-total-numbers {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
}
.review-total-meta {
  font-size: 11px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.review-total-price {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 34px;
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.05;
  color: var(--primary);
}
.review-task-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 8px 14px 8px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

/* ----- Task row (compact 2-line layout) -----
   No card chrome. No expand/collapse. No description / per-task price.
   Line 1: [checkbox] [title…] [edited-dot?] [hours] [slider]
   Line 2: {Materials/Materialer} {cost} kr [ ✨ | gold dot ]
*/
.review-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  padding: 8px 6px;
  min-height: 72px;
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  transition: opacity 200ms ease;
  touch-action: pan-y;
}
.review-row:last-of-type { border-bottom: none; }
.review-row.is-excluded,
.review-row.excluded { opacity: 0.4; }
.review-row.is-excluded .review-row-slider,
.review-row.excluded .review-row-slider { pointer-events: none; }
.review-row.is-excluded .review-row-line2,
.review-row.excluded .review-row-line2 { pointer-events: none; }
.review-row.is-excluded .review-row-hours-val,
.review-row.excluded .review-row-hours-val { text-decoration: line-through; }

.review-row-line1 {
  display: flex;
  align-items: center;
  width: 100%;
  gap: 0;
}

.review-row-toggle {
  position: relative;
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  border-radius: 6px;
  border: 2px solid #555;
  background: transparent;
  color: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  transition: background var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
/* 44px invisible tap target */
.review-row-toggle::before {
  content: '';
  position: absolute;
  top: -10px; left: -10px; right: -10px; bottom: -10px;
}
.review-row-toggle svg { display: block; }
.review-row.is-included .review-row-toggle {
  background: #4E8D76;
  border-color: #4E8D76;
  color: #FFFFFF;
}

.review-row-title {
  flex: 1;
  min-width: 0;
  margin-left: 12px;
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 500;
  color: #F9FAFB;
  line-height: 1.25;
  /* Allow the full title to wrap onto a second line instead of being
     truncated with an ellipsis. The siblings (hours, slider) are
     flex-shrink:0 and stay pinned to the right, vertically centered
     against the wrapped title block. overflow-wrap:anywhere protects
     against pathological single long tokens (e.g. unbroken model IDs). */
  white-space: normal;
  overflow-wrap: anywhere;
}

.review-row-edited-dot {
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #4E8D76;
  margin: 0 4px 0 8px;
}
.review-row-edited-dot[hidden] { display: none; }

.review-row-hours-val {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 16px;
  color: #F9FAFB;
  min-width: 32px;
  text-align: right;
  margin-right: 8px;
}

.review-row-slider {
  -webkit-appearance: none;
  appearance: none;
  flex: 0 0 100px;
  width: 100px;
  height: 32px;
  background: transparent;
  cursor: pointer;
  touch-action: none;
  padding: 0;
}
.review-row-slider::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 999px;
  background: linear-gradient(to right,
    #4E8D76 0%, #4E8D76 var(--review-slider-fill, 50%),
    #333 var(--review-slider-fill, 50%), #333 100%);
}
.review-row-slider::-moz-range-track {
  height: 4px;
  border-radius: 999px;
  background: #333;
}
.review-row-slider::-moz-range-progress {
  height: 4px;
  border-radius: 999px;
  background: #4E8D76;
}
.review-row-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #FFFFFF;
  border: none;
  box-shadow: 0 2px 8px rgba(0,0,0,0.3);
  margin-top: -8px;
}
.review-row-slider::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #FFFFFF;
  border: none;
  box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}

.review-row-line2 {
  display: flex;
  align-items: center;
  width: 100%;
  padding: 12px 0 12px 36px;
  min-height: 44px;
  font-size: 12px;
  color: #9CA3AF;
  cursor: pointer;
  box-sizing: border-box;
  gap: 6px;
}
.review-row-mats-text {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.review-row-qty {
  font-weight: 600;
  color: #EAF8FC;
  white-space: nowrap;
}
.review-row-qty-sep {
  color: rgba(156, 163, 175, 0.55);
  margin: 0 1px;
}
.review-row-mats-sparkle { font-size: 12px; line-height: 1; }
.review-row-mats-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #4E8D76;
  margin-left: 4px;
}

/* Inline materials editor (replaces the text in-place on tap) */
.review-row-line2.is-editing {
  cursor: default;
}
.review-row-mats-prefix,
.review-row-mats-suffix {
  font-size: 12px;
  color: #9CA3AF;
}
.review-row-mats-input {
  width: 72px;
  height: 32px;
  background: #1F2327;
  border: 1px solid #555;
  border-radius: 8px;
  text-align: right;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 14px;
  color: #F9FAFB;
  padding: 4px 8px;
  outline: none;
  box-sizing: border-box;
}
.review-row-mats-input:focus { border-color: #4E8D76; }
.review-row-mats-confirm {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: #4E8D76;
  color: #FFFFFF;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.review-row-mats-confirm svg { display: block; }

/* ----- Add row prompt ----- */
.review-add-prompt { padding: 8px 14px 0; }
.review-add-collapsed {
  width: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 14px;
  border-radius: 14px;
  background: transparent;
  border: 1px dashed rgba(255, 255, 255, 0.16);
  color: var(--muted);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.review-add-collapsed:hover { color: var(--text); border-color: rgba(255, 255, 255, 0.3); }
.review-add-collapsed[hidden] { display: none !important; }
.review-add-expanded {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 14px;
  padding: 10px 12px;
  animation: reviewViewIn 220ms var(--ease-spring, var(--ease));
}
.review-add-expanded[hidden] { display: none !important; }
.review-add-row { display: flex; align-items: center; gap: 8px; }
.review-add-input {
  flex: 1;
  background: transparent;
  border: none;
  color: var(--text);
  font: inherit;
  font-size: 14px;
  outline: none;
}
.review-add-send {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--primary-grad);
  color: #FFFFFF;
  border: none;
  cursor: pointer;
  transition: transform var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease);
}
.review-add-send:disabled { opacity: 0.4; cursor: not-allowed; }
.review-add-send:active:not(:disabled) { transform: scale(0.92); }
/* Mic affordance sitting just before the send arrow in the add row. */
.review-add-mic {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--ai-soft, rgba(124, 155, 255, 0.12));
  color: var(--ai, #7C9BFF);
  border: 1px solid var(--border-ai, rgba(124, 155, 255, 0.28));
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.review-add-mic:hover { background: rgba(124, 155, 255, 0.20); }
.review-add-mic:active { transform: scale(0.92); }
.review-add-mic[hidden] { display: none !important; }
.review-add-mic.is-listening {
  background: var(--danger, #C0392B);
  border-color: var(--danger, #C0392B);
  color: #FFFFFF;
  animation: aiPulse 1.4s ease-in-out infinite;
}
.review-add-cancel {
  align-self: flex-end;
  background: transparent;
  border: none;
  color: var(--muted);
  font-family: inherit;
  font-size: 12px;
  cursor: pointer;
  padding: 4px 6px;
}
.review-add-cancel:hover { color: var(--text); }

/* ----- Manual-add moderation/relevance feedback -----
   Inline verdict under the add row. data-kind="block" (harmful, refused) is
   tinted with the danger token; data-kind="warn" (off-topic) is a soft amber
   note that also reveals the "Tilføj alligevel" override. */
.review-add-feedback {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
  padding: 8px 10px;
  border-radius: 10px;
  font-size: 12.5px;
  line-height: 1.35;
  animation: reviewViewIn 180ms var(--ease);
}
.review-add-feedback[hidden] { display: none !important; }
.review-add-feedback-msg { flex: 1 1 auto; }
.review-add-feedback[data-kind="warn"] {
  background: color-mix(in srgb, var(--warn, #C8841A) 14%, transparent);
  color: var(--warn, #C8841A);
}
.review-add-feedback[data-kind="block"] {
  background: color-mix(in srgb, var(--danger, #C0392B) 14%, transparent);
  color: var(--danger, #C0392B);
}
.review-add-anyway {
  flex-shrink: 0;
  background: transparent;
  border: 1px solid currentColor;
  color: inherit;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  border-radius: 8px;
  padding: 4px 10px;
  cursor: pointer;
  transition: transform var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease);
}
.review-add-anyway:active { transform: scale(0.95); }
.review-add-anyway[hidden] { display: none !important; }
/* Dim the add row while the verdict round-trip is in flight. */
.review-add-prompt.is-validating .review-add-input { opacity: 0.6; }
.review-add-prompt.is-validating .review-add-send {
  opacity: 0.5;
  pointer-events: none;
}

/* ----- Confirm bar ----- */
.review-confirm-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Bottom padding mirrors the composer pattern (`.composer` at line ~1669):
     a generous minimum cushion + safe-area-inset so the mic + Confirm CTA
     never hug the phone-frame edge on desktop (no safe-area to honour) or
     the home indicator on iOS. Earlier value was a flat 18px which read
     visibly cramped on the 430×932 demo frame \u2014 buttons appeared to
     terminate at the bezel. */
  padding: 12px 16px calc(24px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--border);
  background: rgba(11, 13, 18, 0.85);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}
.review-mic-btn {
  flex-shrink: 0;
  width: 48px;
  height: 48px;
  border-radius: 14px;
  background: var(--ai-soft);
  border: 1px solid var(--border-ai);
  color: var(--ai);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
  animation: aiPulse 2.4s ease-in-out infinite;
}
.review-mic-btn:hover { background: rgba(124, 155, 255, 0.18); animation-play-state: paused; }
.review-mic-btn:active { transform: scale(0.94); }
.review-mic-btn[hidden] { display: none; }
.review-confirm-btn {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  height: 48px;
  border-radius: 14px;
  background: var(--primary-grad);
  color: #FFFFFF;
  border: 1px solid rgba(78, 141, 118, 0.55);
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: transform var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease);
}
.review-confirm-btn:active { transform: scale(0.99); }
.review-confirm-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  background: var(--surface);
  color: var(--muted);
  border-color: var(--border);
}
.review-confirm-arrow { font-size: 18px; line-height: 1; }

/* ---------- Way-off view ---------- */
.review-view-wayoff {
  /* Bottom padding includes safe-area-inset + a generous cushion so the
     Submit/Back buttons never hug the phone-frame edge or the home
     indicator. Matches the .review-confirm-bar treatment above. */
  padding: 36px 22px calc(28px + env(safe-area-inset-bottom, 0px));
  gap: 12px;
}
.review-wayoff-content {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.review-wayoff-icon {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: rgba(229, 72, 77, 0.12);
  border: 1px solid rgba(229, 72, 77, 0.45);
  color: #FF8A8A;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.review-wayoff-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--text);
}
.review-wayoff-subtitle {
  margin: 0;
  font-size: 14px;
  line-height: 1.45;
  color: var(--muted);
}
.review-wayoff-textarea {
  width: 100%;
  min-height: 110px;
  resize: vertical;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  color: var(--text);
  font: inherit;
  font-size: 14.5px;
  line-height: 1.45;
  outline: none;
  transition: border-color var(--t-fast) var(--ease);
}
.review-wayoff-textarea:focus { border-color: var(--border-strong); }
.review-wayoff-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 48px;
  border-radius: 14px;
  background: var(--primary-grad);
  color: #FFFFFF;
  border: 1px solid rgba(78, 141, 118, 0.55);
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  transition: transform var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease);
}
.review-wayoff-submit:active { transform: scale(0.99); }
.review-wayoff-submit:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  background: var(--surface);
  color: var(--muted);
  border-color: var(--border);
}
.review-wayoff-back {
  align-self: center;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: none;
  color: var(--muted);
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
  padding: 6px 10px;
  border-radius: 8px;
  transition: color var(--t-fast) var(--ease);
}
.review-wayoff-back:hover { color: var(--text); }
.review-wayoff-thinking {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(11, 13, 18, 0.7);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  animation: reviewViewIn 200ms var(--ease-spring, var(--ease));
}
/* Scale the lime-brick loader up for the full-sheet overlay context.
   In the chat stream the bricks live inside a 20-px-tall bubble where
   their native scale is right. Dropped onto a full-screen review sheet
   they look lost — bump them 2× so the indicator anchors the centre of
   the blurred surface. Pure transform: zero layout impact, zero change
   to the keyframe timings, no extra paints. */
.review-wayoff-thinking .photo-thinking-grid {
  transform: scale(2);
  transform-origin: center center;
}
.review-wayoff-thinking[hidden] { display: none; }

/* ---------- Voice overlay ---------- */
.review-voice-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(11, 13, 18, 0.92);
  -webkit-backdrop-filter: blur(28px);
  backdrop-filter: blur(28px);
  animation: reviewViewIn 220ms var(--ease-spring, var(--ease));
}
.review-voice-overlay[hidden] { display: none; }
.review-voice-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  padding: 32px 28px;
  text-align: center;
  max-width: 320px;
}
.review-voice-mic {
  position: relative;
  width: 88px;
  height: 88px;
  border-radius: 50%;
  background: var(--ai-soft);
  border: 1px solid var(--border-ai);
  color: var(--ai);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 0 rgba(124, 155, 255, 0.45);
}
.review-voice-pulse {
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  border: 2px solid rgba(124, 155, 255, 0.45);
  animation: reviewVoicePulse 1.6s ease-out infinite;
}
@keyframes reviewVoicePulse {
  0%   { transform: scale(0.85); opacity: 0.75; }
  100% { transform: scale(1.4);  opacity: 0;    }
}
.review-voice-status {
  margin: 0;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}
.review-voice-transcript {
  min-height: 36px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--muted);
  font-style: italic;
}
.review-voice-transcript.has-text { color: var(--text); font-style: normal; }
.review-voice-action {
  padding: 10px 22px;
  border-radius: 999px;
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  color: var(--text);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.review-voice-action:active { transform: scale(0.97); }

/* ---------- Identity capture view (sr-id-*) ----------
   Content swap inside the review sheet. Keeps the same sheet height as
   the summary view (so it reads as turning a page inside the same card).
   Class prefix `sr-id-` to avoid colliding with the existing review-*
   names. Input font-size is 16px to prevent iOS Safari from triggering
   the auto-zoom-on-focus that breaks the .phone container layout. */
.review-view-identity {
  padding: 14px 0 24px;
}
.sr-id-container {
  display: flex;
  flex-direction: column;
  padding: 0 24px;
}
.sr-id-title {
  font-size: 20px;
  font-weight: 700;
  color: #F9FAFB;
  margin: 24px 0 0;
  font-family: var(--font-display, inherit);
}
.sr-id-subtitle {
  font-size: 15px;
  font-weight: 400;
  color: #9CA3AF;
  margin: 4px 0 24px;
}
.sr-id-label {
  font-size: 11px;
  font-weight: 600;
  color: #9CA3AF;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin: 16px 0 6px;
}
.sr-id-label:first-of-type { margin-top: 0; }
.sr-id-input {
  width: 100%;
  height: 48px;
  background: #1F2327;
  border: 1px solid #555;
  border-radius: 12px;
  font: inherit;
  font-size: 16px; /* 16px prevents iOS Safari zoom on focus */
  color: #F9FAFB;
  padding: 0 16px;
  outline: none;
  box-sizing: border-box;
  -webkit-appearance: none;
  appearance: none;
  transition: border-color 160ms var(--ease, ease);
}
.sr-id-input::placeholder { color: #666; }
.sr-id-input:focus { border-color: #4E8D76; }
.sr-id-input.invalid { border-color: #EA580C; }
.sr-id-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  height: 48px;
  border: 0;
  border-radius: 14px;
  background: #4E8D76;
  color: #FFFFFF;
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  margin-top: 24px;
  transition: transform 100ms ease, opacity 160ms ease;
  -webkit-appearance: none;
  appearance: none;
}
.sr-id-submit:active { transform: scale(0.97); }
.sr-id-submit:disabled {
  opacity: 0.4;
  cursor: default;
  pointer-events: none;
}
/* In edit mode the arrow is removed so the button reads as "Gem"
   (commit-now) instead of "Fortsæt →" (continue to next step). */
.sr-id-submit.is-save .sr-id-submit-arrow { display: none; }
.sr-id-cancel {
  display: block;
  width: 100%;
  margin-top: 12px;
  padding: 10px;
  background: transparent;
  border: 0;
  color: #9CA3AF;
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
}
.sr-id-cancel[hidden] { display: none; }
.sr-id-cancel:hover { color: #F9FAFB; }
.sr-id-helper {
  font-size: 12px;
  color: #666;
  text-align: center;
  margin: 12px 0 0;
}

/* ---------- Chat-header identity badge ----------
   Small tappable pill on the right of the chat header showing the
   contractor's first name + a gear icon. Hidden until state.contractor
   is populated. Tapping opens the identity form pre-filled. */
.chat-identity-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 10px;
  margin-left: 2px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  color: var(--muted);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease), transform 160ms var(--ease-spring, var(--ease));
  max-width: 140px;
}
.chat-identity-badge[hidden] { display: none; }
.chat-identity-badge:hover { background: rgba(255, 255, 255, 0.07); color: var(--text); }
.chat-identity-badge:active { transform: scale(0.94); }
.chat-identity-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 90px;
}
.chat-identity-gear { display: inline-flex; opacity: 0.85; }


/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  .review-sheet,
  .review-sheet-card,
  .review-view,
  .review-add-expanded,
  .review-voice-overlay,
  .review-wayoff-thinking { animation: none !important; transition: none !important; }
  .review-sparkle,
  .review-row-ai-desc,
  .review-mic-btn { animation: none !important; }
  .review-add-mic.is-listening { animation: none !important; }
}

/* After-picture preview — blueprint theme (mirrors the inline task-review
   card so the "Efter renovering" record reads as a sibling of the
   støttemur drawing / overslag card). */
.after-card {
  /* Blueprint palette — shared with .blueprint-card + the inline review.
     Surface lifted above the #0B1F2E chat base so the card stays distinct. */
  --bp-navy: #15324A;
  --bp-line: #8FD6EA;
  --bp-accent: #56C7E0;
  --bp-grid: rgba(127, 212, 232, 0.10);

  align-self: stretch;
  background:
    linear-gradient(var(--bp-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--bp-grid) 1px, transparent 1px),
    var(--bp-navy);
  background-size: 20px 20px, 20px 20px, auto;
  border: 1px solid rgba(127, 212, 232, 0.38);
  border-radius: var(--radius-card);
  box-shadow:
    0 14px 34px -16px rgba(0, 0, 0, 0.85),
    0 2px 6px -2px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(143, 214, 234, 0.16);
  overflow: hidden;
  animation: bubbleIn var(--t-mid) var(--ease);
}
/* Blueprint header — sparkle + label + project tag, like the task card. */
.after-card .after-bp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 11px 14px 8px;
}
.after-card .after-bp-left { display: inline-flex; align-items: center; gap: 7px; }
.after-card .after-bp-spark { color: var(--bp-accent); display: inline-flex; }
.after-card .after-bp-label {
  font-family: 'Space Grotesk', 'Inter', sans-serif;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #D7F0F8;
}
.after-card .after-bp-project {
  font-size: 12px;
  font-weight: 500;
  color: var(--bp-accent);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.after-card .after-img {
  width: calc(100% - 24px);
  margin: 0 12px;
  aspect-ratio: 4 / 3;
  background-color: rgba(6, 22, 33, 0.6);
  border: 1px solid rgba(127, 212, 232, 0.18);
  border-radius: 8px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(191, 230, 241, 0.6);
  font-size: 13px;
  overflow: hidden;
}
.after-card .after-img.has-img {
  background-color: #000;
}
.after-card .after-img img { width: 100%; height: 100%; object-fit: cover; }
.after-card .after-body {
  padding: 12px 14px;
}
.after-card .after-text {
  font-size: 14px;
  color: #EAF8FC;
  line-height: 1.5;
}
.after-card .after-exclusions {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid rgba(127, 212, 232, 0.14);
}
.after-card .after-exclusions-label {
  font-size: 11px;
  color: rgba(191, 230, 241, 0.72);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  margin-bottom: 5px;
}
.after-card .after-exclusions ul {
  margin: 0;
  padding-left: 18px;
}
.after-card .after-exclusions li {
  font-size: 13px;
  color: rgba(191, 230, 241, 0.6);
  line-height: 1.45;
  margin-bottom: 2px;
}

/* ============================================================
   RECEIPT SKIN — after-card ("Efter renovering" proposal record)
   Re-skins the after-card from the blueprint look into the same torn
   thermal receipt as the inline task overview (.review-sheet.is-inline)
   so the two read as one product. Same warm paper, scalloped torn
   top/bottom edges (CSS mask), centered mono header, dashed section
   rules, amber money, lime AI, and a faux-barcode footer. Overrides the
   base .after-card rules above by virtue of being later in the file.
   ============================================================ */
.after-card {
  --rcpt-paper-a: #FBF6EC;
  --rcpt-paper-b: #EFE5D3;
  --rcpt-ink: #1B2430;
  --rcpt-ink-soft: rgba(27, 36, 48, 0.55);
  --rcpt-line: rgba(27, 36, 48, 0.20);
  --rcpt-accent: #2F4F6B;
  --rcpt-accent-strong: #243B50;
  --rcpt-accent-soft: rgba(47, 79, 107, 0.14);
  --rcpt-on-accent: #FBF6EC;
  --rcpt-scallop: 16px;
  --rcpt-scallop-h: 8px;

  background: linear-gradient(180deg, var(--rcpt-paper-a) 0%, var(--rcpt-paper-b) 100%);
  border: none;
  border-radius: 0;
  box-shadow: none;
  /* Scalloped torn edges — identical recipe to the task overview card. */
  -webkit-mask:
    linear-gradient(#000 0 0) no-repeat 0 var(--rcpt-scallop-h) / 100% calc(100% - 2 * var(--rcpt-scallop-h)),
    radial-gradient(circle var(--rcpt-scallop-h) at 50% 100%, #000 98%, #0000) repeat-x 0 0    / var(--rcpt-scallop) var(--rcpt-scallop-h),
    radial-gradient(circle var(--rcpt-scallop-h) at 50% 0%,   #000 98%, #0000) repeat-x 0 100% / var(--rcpt-scallop) var(--rcpt-scallop-h);
          mask:
    linear-gradient(#000 0 0) no-repeat 0 var(--rcpt-scallop-h) / 100% calc(100% - 2 * var(--rcpt-scallop-h)),
    radial-gradient(circle var(--rcpt-scallop-h) at 50% 100%, #000 98%, #0000) repeat-x 0 0    / var(--rcpt-scallop) var(--rcpt-scallop-h),
    radial-gradient(circle var(--rcpt-scallop-h) at 50% 0%,   #000 98%, #0000) repeat-x 0 100% / var(--rcpt-scallop) var(--rcpt-scallop-h);
  filter: drop-shadow(0 12px 22px rgba(0, 0, 0, 0.55));
}
/* Header — centered mono "store name" + project tag, dashed underline. */
.after-card .after-bp-head {
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 18px 14px 11px;
  border-bottom: 1px dashed var(--rcpt-line);
}
.after-card .after-bp-spark { color: #3A8A38; }
.after-card .after-bp-label {
  font-family: var(--font);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.22em;
  color: var(--rcpt-ink);
}
.after-card .after-bp-project {
  font-family: var(--font);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.34em;
  color: var(--rcpt-ink-soft);
}
/* Photo (when present) — slim paper frame, square corners. */
.after-card .after-img {
  margin: 12px;
  width: calc(100% - 24px);
  border: 1px dashed var(--rcpt-line);
  border-radius: 0;
  background-color: rgba(0, 0, 0, 0.35);
  color: var(--rcpt-ink-soft);
}
.after-card .after-img.has-img { background-color: #000; }
/* Body — the description reads as the receipt's order summary. The extra
   bottom padding clears the scalloped torn edge so the text never crowds it. */
.after-card .after-body { padding: 13px 16px 18px; }
.after-card .after-text {
  font-family: var(--font);
  font-size: 13px;
  line-height: 1.6;
  letter-spacing: 0.01em;
  color: var(--rcpt-ink);
}
/* Exclusions — dashed top rule, mono caption, dash-prefixed line items. */
.after-card .after-exclusions {
  margin-top: 13px;
  padding-top: 11px;
  border-top: 1px dashed var(--rcpt-line);
}
.after-card .after-exclusions-label {
  font-family: var(--font);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--rcpt-ink-soft);
  margin-bottom: 6px;
}
.after-card .after-exclusions ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.after-card .after-exclusions li {
  position: relative;
  padding-left: 16px;
  font-family: var(--font);
  font-size: 12px;
  line-height: 1.55;
  color: var(--rcpt-ink-soft);
  margin-bottom: 3px;
}
.after-card .after-exclusions li::before {
  content: "-";
  position: absolute;
  left: 2px;
  color: var(--rcpt-ink-soft);
}

.proposal-exclusions {
  margin: 0;
  padding-left: 18px;
}
.proposal-exclusions li {
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  margin-bottom: 3px;
}

/* ===== After-loader (in-chat AI generation progress) =====
   Rebuilt 2026-06-09 for "viral / bleeding edge" energy. Visual
   language: AR viewfinder + Midjourney generation pill + floating
   particles + scan beam + glowing energy bar. Lime owns this moment
   (it is THE generative payoff beat). The hard-hat is now a small
   brand watermark in the corner — not the centerpiece. */
.after-loader {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  overflow: hidden;
  margin: 8px 0;
  animation: bubbleIn var(--t-mid) var(--ease);
}
.after-loader-img {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  background:
    radial-gradient(120% 80% at 50% 40%, #1f262d 0%, #14181d 60%, #0d1014 100%);
  overflow: hidden;
  isolation: isolate; /* keep blend-modes contained */
}
.after-loader-img .after-loader-preview {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 280ms var(--ease), filter 600ms var(--ease);
  filter: blur(6px) saturate(1.05);
}
.after-loader-img.has-preview .after-loader-preview { opacity: 1; }
/* Heavier blur while we're showing the user's own before-image as a placeholder */
.after-loader-img.has-preview.is-initial .after-loader-preview {
  filter: blur(14px) saturate(1.1) brightness(0.85);
  transform: scale(1.06);
}
.after-loader-img.has-preview .shimmer {
  opacity: 0.55;
  mix-blend-mode: screen;
}

/* Subtle dot-grid — the "wireframe / capture-in-progress" undertone.
   Barely visible on its own; reads strongly when paired with the
   corner brackets and scan beam. */
.after-loader-img .after-loader-grid {
  position: absolute; inset: 0;
  pointer-events: none;
  background-image: radial-gradient(rgba(78, 141, 118, 0.16) 1px, transparent 1px);
  background-size: 14px 14px;
  background-position: 0 0;
  opacity: 0.55;
  mix-blend-mode: screen;
  mask-image: radial-gradient(120% 80% at 50% 50%, #000 40%, transparent 90%);
}

/* Scan beam — lime, with a sharp leading edge so it reads as a scanner
   rather than a gradient wash. Slower than before (6s) so the eye can
   actually track it. */
.after-loader-img .scanline {
  position: absolute;
  left: 0; right: 0;
  height: 90px;
  pointer-events: none;
  background:
    linear-gradient(
      to bottom,
      transparent 0%,
      rgba(78, 141, 118, 0.00) 30%,
      rgba(78, 141, 118, 0.45) 55%,
      rgba(138, 216, 136, 0.85) 70%,
      rgba(78, 141, 118, 0.00) 78%,
      transparent 100%
    );
  filter: blur(1px) drop-shadow(0 0 12px rgba(78, 141, 118, 0.45));
  opacity: 0;
  animation: scanlineMove 6s cubic-bezier(.4,.05,.3,1) infinite;
  mix-blend-mode: screen;
}
.after-loader-img.has-preview .scanline { opacity: 1; }
@keyframes scanlineMove {
  0%   { top: -90px; }
  100% { top: 100%; }
}

/* Soft full-area shimmer — also lime now, so the whole surface feels alive. */
.after-loader-img .shimmer {
  position: absolute; inset: 0;
  pointer-events: none;
  background: linear-gradient(
    100deg,
    transparent 0%,
    rgba(255, 255, 255, 0.04) 30%,
    rgba(78, 141, 118, 0.10) 50%,
    rgba(255, 255, 255, 0.04) 70%,
    transparent 100%
  );
  background-size: 220% 100%;
  animation: shimmer 1.8s linear infinite;
}
@keyframes shimmer {
  0%   { background-position: 120% 0; }
  100% { background-position: -120% 0; }
}

/* Floating particles — six tiny lime sparks rising bottom→top at
   staggered offsets. Reads as "material being analyzed". */
.after-loader-particles {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 2;
}
.after-loader-particles span {
  position: absolute;
  left: var(--x, 50%);
  bottom: -8px;
  width: 4px; height: 4px;
  border-radius: 50%;
  background: var(--magic);
  box-shadow: 0 0 8px rgba(78, 141, 118, 0.85),
              0 0 16px rgba(78, 141, 118, 0.45);
  opacity: 0;
  animation: aiParticleRise 4.6s linear infinite;
  animation-delay: var(--d, 0s);
}
@keyframes aiParticleRise {
  0%   { transform: translate(0, 0) scale(0.7); opacity: 0; }
  12%  { opacity: 1; }
  55%  { transform: translate(8px, -55%) scale(1); }
  88%  { opacity: 1; }
  100% { transform: translate(-4px, -110%) scale(0.5); opacity: 0; }
}

/* Corner viewfinder brackets — the strongest semiotic. Anyone who's
   ever used Snap / TikTok / a camera app reads this instantly as
   "capture in progress". Pulse the opacity, not the position, so it
   stays calm in peripheral vision. */
.after-loader-corners {
  position: absolute; inset: 10px;
  pointer-events: none;
  z-index: 3;
}
.after-loader-corners .c {
  position: absolute;
  width: 22px; height: 22px;
  border: 2px solid var(--magic);
  filter: drop-shadow(0 0 6px rgba(78, 141, 118, 0.55));
  animation: aiCornerPulse 2.4s ease-in-out infinite;
}
.after-loader-corners .tl { top: 0;    left: 0;    border-right: none; border-bottom: none; border-top-left-radius: 6px; }
.after-loader-corners .tr { top: 0;    right: 0;   border-left: none;  border-bottom: none; border-top-right-radius: 6px; }
.after-loader-corners .bl { bottom: 0; left: 0;    border-right: none; border-top: none;    border-bottom-left-radius: 6px; }
.after-loader-corners .br { bottom: 0; right: 0;   border-left: none;  border-top: none;    border-bottom-right-radius: 6px; }
@keyframes aiCornerPulse {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

/* Live-status pill — Midjourney / Pinterest pattern. Centered over the
   blurred preview so it reads as a clear "system is working" beacon,
   instead of fighting the top-left frame corner for attention. The
   translate keeps it perfectly centered regardless of pill width as
   the label is i18n-driven (GENERATING / GENERERER). */
.after-loader-live {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px 6px 10px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.12em;
  color: var(--magic);
  background: rgba(11, 13, 18, 0.72);
  border: 1px solid rgba(78, 141, 118, 0.45);
  border-radius: 999px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.6);
}
.after-loader-live .dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--magic);
  box-shadow: 0 0 8px rgba(78, 141, 118, 0.9);
  animation: aiLivePulse 1.1s ease-in-out infinite;
}
@keyframes aiLivePulse {
  0%, 100% { transform: scale(1);    opacity: 1;   }
  50%      { transform: scale(1.35); opacity: 0.6; }
}

/* Body */
.after-loader-body {
  padding: 14px 16px 16px;
}
/* No-image variant (proposal build without a before photo): the moving
   green-block "AI thinking" grid sits to the left of the title/caption,
   replacing the old text-only treatment. Reuses .photo-thinking-grid so
   the motion grammar matches every other AI-thinking moment. */
.after-loader.no-image .after-loader-body {
  display: flex;
  align-items: center;
  gap: 14px;
}
.after-loader-blocks {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
}
.after-loader-blocks .photo-thinking-grid {
  /* Scale the native 20×20 grid up rather than resizing it — the drift
     keyframes use hardcoded 11px offsets, so transform:scale keeps the
     tiles reaching the corners correctly. */
  transform: scale(1.4);
  transform-origin: center;
}
.after-loader.no-image .after-loader-title {
  margin-bottom: 4px;
}
.after-loader.no-image .after-loader-caption {
  margin-bottom: 0;
}
.after-loader-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 6px;
  letter-spacing: 0.005em;
}
.after-loader-caption {
  font-size: 13px;
  color: var(--muted, rgba(255,255,255,0.6));
  margin-bottom: 12px;
  min-height: 1.2em;
  transition: opacity 200ms ease;
  display: flex; align-items: center; gap: 4px;
}
.after-loader-caption .caret {
  display: inline-block;
  width: 7px; height: 13px;
  background: var(--magic);
  box-shadow: 0 0 6px rgba(78, 141, 118, 0.6);
  border-radius: 1px;
  animation: aiCaretBlink 1s steps(2, end) infinite;
  transform: translateY(1px);
}
@keyframes aiCaretBlink {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* Lime energy bar — replaces the old skeleton triple. One indeterminate
   track with a glowing comet sliding inside it. */
.after-loader-energy {
  position: relative;
  height: 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.05);
  overflow: hidden;
}
.after-loader-energy span {
  position: absolute;
  top: 0; bottom: 0;
  width: 38%;
  border-radius: 999px;
  background: linear-gradient(
    90deg,
    rgba(78, 141, 118, 0) 0%,
    rgba(78, 141, 118, 0.55) 35%,
    #74B49C 60%,
    rgba(78, 141, 118, 0.55) 85%,
    rgba(78, 141, 118, 0) 100%
  );
  box-shadow: 0 0 12px rgba(78, 141, 118, 0.55);
  animation: aiEnergySlide 1.6s cubic-bezier(.5,.05,.3,1) infinite;
}
@keyframes aiEnergySlide {
  0%   { left: -42%; }
  100% { left: 102%; }
}

/* Reduced-motion fallback — kill anything that pulses or spins,
   keep the static lime accents so the state still reads as "AI". */
@media (prefers-reduced-motion: reduce) {
  .after-loader-img .scanline,
  .after-loader-img .shimmer,
  .after-loader-particles span,
  .after-loader-corners .c,
  .after-loader-live .dot,
  .after-loader-caption .caret,
  .after-loader-energy span {
    animation: none !important;
  }
  .after-loader-img .scanline { opacity: 0.35; top: 40%; }
  .after-loader-energy span { left: 0; width: 60%; opacity: 0.7; }
  .after-loader-live .dot { opacity: 1; }
}

/* ===== Modal — removed; review sheet supersedes it.
   Two keyframes (aiPulse, fadeIn) and the base chat-chrome transition
   rule are preserved below because they're still referenced by the
   review sheet, the AI status pill, and any future overlay. */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
/* Smooth fade/blur for chat chrome when a sheet or overlay covers it.
   `.review-open` and any future overlay sets the active state on .phone;
   this rule just defines how to glide back when the overlay closes. */
.phone .chat-header,
.phone .chat-scroll,
.phone .composer {
  transition: filter var(--t-mid) var(--ease), opacity var(--t-mid) var(--ease);
}
/* AI ambient halo — used by .ai-status pill, .review-sparkle, .review-mic-btn,
   .review-row-ai-desc. The universal "there's intelligence here" cue. */
@keyframes aiPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(124, 155, 255, 0.0); }
  50%      { box-shadow: 0 0 18px -4px rgba(124, 155, 255, 0.55); }
}

/* ===== Proposal review ===== */
/* Wrapper holds the scroll area + a bottom fade indicator that hints at
   more content below the fold. The wrapper itself is the flex child so the
   fade can be absolutely positioned without affecting scroll height. */
.proposal-scroll-wrap {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.proposal-scroll-fade {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 28px;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(11,13,18,0) 0%, rgba(11,13,18,0.95) 100%);
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease);
}
.proposal-scroll-wrap.has-more .proposal-scroll-fade { opacity: 1; }

.proposal-scroll {
  flex: 1;
  min-height: 0; /* Critical: lets this flex item shrink so overflow-y can scroll
                    inside the fixed-height .screen column. Without this the
                    container grows past the viewport and content is clipped
                    by .screens { overflow: hidden }. */
  overflow-y: auto;
  padding: 14px 14px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

/* Sticky summary bar — pinned between the scroll area and the share buttons.
   Guarantees the headline number (total inkl. moms) is visible at all times
   regardless of scroll position. Solves the "hidden information" problem
   where the customer had to scroll to the bottom to see the price. */
.proposal-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  background:
    linear-gradient(180deg, rgba(78, 141, 118, 0.12) 0%, rgba(78, 141, 118, 0.03) 100%),
    rgba(11, 13, 18, 0.6);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  border-top: 1px solid rgba(78, 141, 118, 0.18);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 -8px 32px -12px rgba(78, 141, 118, 0.25);
  flex-shrink: 0;
}
.proposal-summary .ps-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.proposal-summary .ps-count {
  font-size: 11px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
}
.proposal-summary .ps-label {
  font-size: 13px;
  color: var(--text);
  font-weight: 600;
}
.proposal-summary .ps-amount {
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--primary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-shadow: 0 0 24px rgba(78, 141, 118, 0.35);
}

.proposal-card {
  background: var(--surface-raised);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  backdrop-filter: blur(20px) saturate(160%);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  overflow: hidden;
  box-shadow: var(--shadow-card);
  /* Critical: as a flex child of .proposal-scroll (display: flex; column),
     the card must NOT shrink — otherwise it collapses to the viewport
     height and its overflowing content (Specifikation, totals) becomes
     unreachable because the parent's scrollHeight ends up equal to
     clientHeight. flex-shrink: 0 lets the card grow to its real content
     height so vertical scrolling actually works on desktop. */
  flex-shrink: 0;
}
.proposal-hero {
  background: var(--primary-grad);
  color: #FFFFFF;
  padding: 18px 18px;
  position: relative;
  overflow: hidden;
}
.proposal-hero::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 80% 60% at 80% 0%, rgba(255, 255, 255, 0.18) 0%, transparent 60%);
  pointer-events: none;
}
.proposal-hero .hero-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.proposal-hero .hero-left { min-width: 0; flex: 1; }
.proposal-hero .label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; font-weight: 700; opacity: .7; }
.proposal-hero .title { font-size: 22px; font-weight: 800; margin-top: 4px; line-height: 1.1; }
.proposal-hero .meta { font-size: 12px; margin-top: 6px; opacity: .8; line-height: 1.35; }
.proposal-hero .hero-total {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
  flex-shrink: 0;
}
.proposal-hero .hero-total .ht-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
  opacity: .75;
}
.proposal-hero .hero-total .ht-amount {
  font-size: 20px;
  font-weight: 800;
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.proposal-section {
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}
.proposal-section:last-child { border-bottom: none; }
.proposal-section h4 {
  margin: 0 0 8px;
  font-size: 11px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
}
.proposal-section p { margin: 0; font-size: 14px; line-height: 1.5; color: var(--text); }

/* Missing-identity banner — shown inside the inline proposal card when
   the contractor has not yet completed the 2-field identity capture.
   Tappable: opens the same identity form in edit mode. Honest amber
   surface (not red) so it reads as a nudge, not an error. */
.proposal-section.proposal-from-missing { padding: 10px 12px; }
.proposal-from-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  background: linear-gradient(180deg, rgba(78, 141, 118, 0.10), rgba(78, 141, 118, 0.04));
  border: 1px solid rgba(78, 141, 118, 0.35);
  border-radius: 12px;
  color: var(--text);
  text-align: left;
  font: inherit;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), transform 160ms var(--ease-spring, var(--ease));
}
.proposal-from-banner:hover { background: linear-gradient(180deg, rgba(78, 141, 118, 0.14), rgba(78, 141, 118, 0.06)); }
.proposal-from-banner:active { transform: scale(0.99); }
.proposal-from-banner .pfb-icon {
  flex: 0 0 28px;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: rgba(78, 141, 118, 0.18);
  color: #4E8D76;
}
.proposal-from-banner .pfb-text {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.proposal-from-banner .pfb-text strong { font-size: 14px; font-weight: 600; color: var(--text); }
.proposal-from-banner .pfb-sub { font-size: 12.5px; color: var(--muted); line-height: 1.35; }
.proposal-from-banner .pfb-cta {
  flex: 0 0 auto;
  padding: 6px 12px;
  border-radius: 999px;
  background: #4E8D76;
  color: #FFFFFF;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.01em;
}

.proposal-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px dashed var(--border);
}
.proposal-row:last-child { border-bottom: none; }
.proposal-row .name { font-size: 14px; color: var(--text); font-weight: 600; flex: 1; }
.proposal-row .price { font-size: 14px; color: var(--primary); font-weight: 700; white-space: nowrap; }
.proposal-row .desc { font-size: 12px; color: var(--muted); margin-top: 2px; }
.proposal-row .calc { font-size: 11px; color: var(--muted); margin-top: 4px; font-variant-numeric: tabular-nums; }

/* Scope list — bulleted summary of what the proposal covers */
.scope-group {
  margin: 0 0 14px;
}
.scope-group:last-child { margin-bottom: 0; }
.scope-group h5 {
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 6px;
  letter-spacing: -0.01em;
}
.scope-list {
  margin: 0;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.scope-list li {
  font-size: 14px;
  line-height: 1.45;
  color: var(--text);
}
/* When scope bullets are nested inside a line-item (merged "Hvad omfatter
   tilbuddet" list), give them a little breathing room under the title row. */
.line-item > .scope-list {
  margin-top: 6px;
  padding-left: 18px;
}
.line-item > .scope-list li {
  font-size: 13px;
  color: var(--muted);
}

/* Invoice-style line items — iOS card list (Apple Wallet/Receipts pattern).
   Avoids cramped 4-column tables on a 430px-wide phone screen by stacking
   each task vertically with clear price metadata. */
.line-items-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}
.line-item {
  padding: 12px 0;
  border-bottom: 1px dashed var(--border);
  font-variant-numeric: tabular-nums;
}
.line-item:first-child { padding-top: 4px; }
.line-item:last-child { border-bottom: none; padding-bottom: 4px; }
.line-item .li-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.line-item .li-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  line-height: 1.3;
  flex: 1;
  min-width: 0;
}
.line-item .li-amount {
  font-size: 15px;
  font-weight: 700;
  color: var(--primary);
  white-space: nowrap;
}
.line-item .li-sub {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.45;
  margin-top: 4px;
}
.line-item .li-calc {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  font-size: 12px;
  color: var(--muted);
  flex-wrap: wrap;
}
.line-item .li-calc-pill {
  background: var(--pill-bg);
  border: 1px solid var(--pill-border);
  border-radius: var(--radius-pill);
  padding: 3px 9px;
  font-weight: 600;
  color: var(--text);
}
.line-item .li-calc-x {
  opacity: .6;
  font-weight: 600;
}

/* Per-task cost breakdown — labour vs. materials, with optional itemised
   materials list. Two-row grid keeps amount column right-aligned cleanly. */
.line-item .li-breakdown {
  margin-top: 10px;
  padding: 10px 12px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border);
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.line-item .li-bd-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  gap: 8px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.line-item .li-bd-label {
  font-weight: 600;
  color: var(--text);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 11px;
}
.line-item .li-bd-meta {
  color: var(--muted);
  text-align: right;
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.line-item .li-bd-amount {
  font-weight: 700;
  color: var(--text);
  font-size: 13px;
  white-space: nowrap;
}

/* ===== Invoice-style specification table =====
   Grouped layout: each task is a header row (bold, no amount) followed by
   one or two sub-rows — "Materialer" and "Arbejde". Column order matches
   the reference: Beskrivelse | Ant. | Enhed | Total.
   Implemented as a CSS grid so columns align across rows without the
   horizontal-scroll issues a real <table> would cause on a 430px screen. */
.invoice-section { padding-bottom: 4px; }
.invoice-table {
  display: flex;
  flex-direction: column;
  font-variant-numeric: tabular-nums;
  border-top: 1px solid var(--border-strong);
}
.inv-row {
  display: grid;
  grid-template-columns: minmax(0, 1.7fr) minmax(0, 0.55fr) minmax(0, 0.9fr) minmax(0, 0.85fr);
  column-gap: 10px;
  padding: 8px 0;
  align-items: baseline;
  font-size: 13px;
  color: var(--text);
}
.inv-row.inv-head {
  padding: 8px 0 6px;
  border-bottom: 1px solid var(--border-strong);
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
}
.inv-c-desc { min-width: 0; word-break: break-word; }
.inv-c-qty, .inv-c-unit, .inv-c-amt { text-align: right; white-space: nowrap; }

/* Task group — header line + indented sub-rows. The group itself adds a
   thin separator between tasks so each block reads as a unit. */
.inv-group {
  border-bottom: 1px solid var(--border);
  padding: 4px 0 8px;
}
.inv-group:last-child { border-bottom: none; }
.inv-row.inv-group-head {
  padding: 10px 0 4px;
  font-weight: 700;
  color: var(--text);
  font-size: 14px;
}
.inv-row.inv-sub-row {
  padding: 6px 0;
  color: var(--text);
}
.inv-row.inv-sub-row .inv-c-desc {
  color: var(--muted);
  padding-left: 2px;
}
.inv-row.inv-sub-row .inv-c-amt { font-weight: 600; }
.inv-row.inv-empty {
  display: block;
  padding: 14px 0;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}

/* ===== Job-recipe specification (gipsvæg pilot) =====
   An expandable, itemised breakdown that sits under a task inside the
   proposal receipt. Customer questions render as chips; changing one
   re-prices via /api/recipe-breakdown. Themed for the warm-paper receipt
   surface (--rcpt-* tokens), not the dark chat surface. */
.recipe-spec {
  margin-top: 6px;
  padding-top: 2px;
}
.recipe-spec-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  padding: 4px 0;
  margin: 0;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--rcpt-accent);
  cursor: pointer;
}
.recipe-spec-toggle .rst-chev {
  display: inline-block;
  transition: transform 160ms var(--ease, ease);
  font-size: 15px;
  line-height: 1;
}
.recipe-spec.open .recipe-spec-toggle .rst-chev { transform: rotate(90deg); }
.recipe-spec-toggle:hover .rst-text { text-decoration: underline; }

.recipe-spec-body {
  margin-top: 8px;
  padding: 10px 12px 12px;
  border: 1px solid var(--rcpt-line);
  border-radius: 12px;
  background: rgba(47, 79, 107, 0.05);
}
.recipe-spec.recomputing .recipe-spec-body { opacity: 0.55; pointer-events: none; }

/* Question chips */
.recipe-questions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-bottom: 10px;
  margin-bottom: 10px;
  border-bottom: 1px solid var(--rcpt-line);
}
.recipe-q { display: flex; flex-direction: column; gap: 6px; }
.recipe-q-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--rcpt-ink-soft);
}
.recipe-q-opts { display: flex; flex-wrap: wrap; gap: 6px; }
.recipe-chip {
  appearance: none;
  border: 1px solid var(--rcpt-line);
  background: var(--rcpt-paper-a);
  color: var(--rcpt-ink);
  border-radius: 999px;
  padding: 7px 14px;
  min-height: 34px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: transform 140ms var(--ease, ease), background 140ms var(--ease, ease), border-color 140ms var(--ease, ease);
}
.recipe-chip:hover { border-color: var(--rcpt-accent); }
.recipe-chip:active { transform: scale(0.94); }
.recipe-chip.active {
  background: var(--rcpt-accent);
  border-color: var(--rcpt-accent);
  color: #fff;
}

/* Itemised lines */
.recipe-lines { display: flex; flex-direction: column; }
.recipe-line {
  display: grid;
  grid-template-columns: minmax(0, 1.7fr) minmax(0, 0.7fr) minmax(0, 0.75fr);
  column-gap: 10px;
  align-items: baseline;
  padding: 5px 0;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--rcpt-ink);
}
.recipe-line + .recipe-line { border-top: 1px dashed var(--rcpt-line); }
.recipe-line .rl-label { min-width: 0; word-break: break-word; color: var(--rcpt-ink); }
.recipe-line .rl-qty { text-align: right; white-space: nowrap; color: var(--rcpt-ink-soft); }
.recipe-line .rl-amt { text-align: right; white-space: nowrap; font-weight: 600; }

.recipe-spec-note {
  margin-top: 8px;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--rcpt-ink-soft);
}
.recipe-spec-sub {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid var(--rcpt-line);
  font-size: 13px;
  font-weight: 700;
  color: var(--rcpt-ink);
  font-variant-numeric: tabular-nums;
}

/* Totals block — right-aligned trio (Subtotal / Moms / Total inkl. moms)
   matching the bottom-right summary in the reference. The grand total row
   uses a top rule and primary colour to draw the eye. */
.invoice-totals-section { padding-top: 10px; }
.invoice-totals {
  margin-left: auto;
  width: min(100%, 280px);
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-variant-numeric: tabular-nums;
}
.inv-tot-row {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: 16px;
  align-items: baseline;
  font-size: 14px;
}
.inv-tot-row .inv-tot-label { color: var(--muted); text-align: right; }
.inv-tot-row .inv-tot-amt { color: var(--text); min-width: 96px; text-align: right; }
.inv-tot-row.inv-tot-grand {
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--border-strong);
  font-size: 16px;
  font-weight: 800;
}
.inv-tot-row.inv-tot-grand .inv-tot-label { color: var(--text); font-weight: 800; }
.inv-tot-row.inv-tot-grand .inv-tot-amt { color: var(--primary); }

.cost-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 6px 0;
  font-size: 14px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.cost-row span:first-child { color: var(--muted); }
.cost-row.total {
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  font-weight: 700;
  font-size: 15px;
}
.cost-row.total span:first-child { color: var(--text); }
.cost-row.total span:last-child { color: var(--primary); }

.proposal-total {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px 16px;
  background: rgba(78, 141, 118, 0.06);
}
.proposal-total .label { font-size: 13px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; }
.proposal-total .amount { font-size: 22px; color: var(--primary); font-weight: 800; }

/* ============================================================
   RECEIPT SKIN — final inline proposal card
   Re-themes .proposal-card to share the warm-paper + deep blue-gray
   palette of the task receipt (.review-sheet.is-inline) so the final
   tilbud reads as the same product. Color-only: layout, spacing and
   the invoice grid are untouched. Scoped with a .proposal-card prefix
   so it overrides the base single-class rules by specificity.
   ============================================================ */
.proposal-card {
  --rcpt-paper-a: #FBF6EC;
  --rcpt-paper-b: #EFE5D3;
  --rcpt-ink: #1B2430;
  --rcpt-ink-soft: rgba(27, 36, 48, 0.55);
  --rcpt-line: rgba(27, 36, 48, 0.20);
  --rcpt-accent: #2F4F6B;
  --rcpt-accent-strong: #243B50;
  --rcpt-accent-soft: rgba(47, 79, 107, 0.14);
  --rcpt-on-accent: #FBF6EC;

  background: linear-gradient(180deg, var(--rcpt-paper-a) 0%, var(--rcpt-paper-b) 100%);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border: 1px solid rgba(27, 36, 48, 0.16);
  box-shadow: 0 12px 28px -14px rgba(0, 0, 0, 0.5);
}
/* Hero — deep blue-gray instead of amber, cream text. */
.proposal-card .proposal-hero {
  background: linear-gradient(135deg, var(--rcpt-accent) 0%, var(--rcpt-accent-strong) 100%);
  color: var(--rcpt-on-accent);
}
.proposal-card .proposal-hero::after {
  background: radial-gradient(ellipse 80% 60% at 80% 0%, rgba(255, 255, 255, 0.12) 0%, transparent 60%);
}
/* Sections — dashed paper rules, ink text. */
.proposal-card .proposal-section { border-bottom-color: var(--rcpt-line); border-bottom-style: dashed; }
.proposal-card .proposal-section h4 { color: var(--rcpt-ink-soft); }
.proposal-card .proposal-section p { color: var(--rcpt-ink); }
.proposal-card .proposal-section li,
.proposal-card .proposal-exclusions li { color: var(--rcpt-ink); }
/* Line items / rows. */
.proposal-card .proposal-row { border-bottom-color: var(--rcpt-line); }
.proposal-card .proposal-row .name { color: var(--rcpt-ink); }
.proposal-card .proposal-row .price { color: var(--rcpt-accent); }
.proposal-card .proposal-row .desc,
.proposal-card .proposal-row .calc { color: var(--rcpt-ink-soft); }
/* Scope list. */
.proposal-card .scope-group h5 { color: var(--rcpt-ink); }
.proposal-card .scope-list li { color: var(--rcpt-ink); }
/* Invoice table. */
.proposal-card .invoice-table { border-top-color: var(--rcpt-line); }
.proposal-card .inv-row { color: var(--rcpt-ink); }
.proposal-card .inv-row.inv-head { color: var(--rcpt-ink-soft); border-bottom-color: var(--rcpt-line); }
.proposal-card .inv-group { border-bottom-color: var(--rcpt-line); }
.proposal-card .inv-row.inv-group-head { color: var(--rcpt-ink); }
.proposal-card .inv-row.inv-sub-row { color: var(--rcpt-ink); }
.proposal-card .inv-row.inv-sub-row .inv-c-desc { color: var(--rcpt-ink-soft); }
.proposal-card .inv-row.inv-empty { color: var(--rcpt-ink-soft); }
/* Totals. */
.proposal-card .inv-tot-row .inv-tot-label { color: var(--rcpt-ink-soft); }
.proposal-card .inv-tot-row .inv-tot-amt { color: var(--rcpt-ink); }
.proposal-card .inv-tot-row.inv-tot-grand { border-top-color: var(--rcpt-line); }
.proposal-card .inv-tot-row.inv-tot-grand .inv-tot-label { color: var(--rcpt-ink); }
.proposal-card .inv-tot-row.inv-tot-grand .inv-tot-amt { color: var(--rcpt-accent); }
.proposal-card .cost-row { color: var(--rcpt-ink); }
.proposal-card .cost-row span:first-child { color: var(--rcpt-ink-soft); }
.proposal-card .cost-row.total { border-top-color: var(--rcpt-line); }
.proposal-card .cost-row.total span:first-child { color: var(--rcpt-ink); }
.proposal-card .cost-row.total span:last-child { color: var(--rcpt-accent); }
.proposal-card .proposal-total { background: var(--rcpt-accent-soft); }
.proposal-card .proposal-total .label { color: var(--rcpt-ink-soft); }
.proposal-card .proposal-total .amount { color: var(--rcpt-accent); }
/* Footer summary bar — kill the amber tint/glow and recolor the total.
   Triple-class selectors (.proposal-card .proposal-summary .ps-amount)
   beat the later "money stays gold" defensive block by specificity. */
.proposal-card .proposal-summary {
  background: var(--rcpt-accent-soft);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border-top: 1px solid var(--rcpt-line);
  box-shadow: none;
}
.proposal-card .proposal-summary .ps-count { color: var(--rcpt-ink-soft); }
.proposal-card .proposal-summary .ps-label { color: var(--rcpt-ink); }
.proposal-card .proposal-summary .ps-amount {
  color: var(--rcpt-accent);
  text-shadow: none;
}

.before-after {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.before-after .ba {
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--border);
  aspect-ratio: 1;
  position: relative;
  background: #0a0a0a;
}
.before-after .ba img { width: 100%; height: 100%; object-fit: cover; }
.before-after .ba .ba-label {
  position: absolute;
  top: 6px; left: 6px;
  background: rgba(0,0,0,0.6);
  color: var(--text);
  font-size: 10px;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.before-after .ba.placeholder {
  background: linear-gradient(135deg, #2a3440 0%, #1a1f24 100%);
  display: flex; align-items: center; justify-content: center;
  color: var(--muted); font-size: 12px;
}
.before-after .ba.zoomable { cursor: zoom-in; }
.before-after .ba.zoomable:active { transform: scale(0.98); }
.bubble.image.zoomable { cursor: zoom-in; }
.bubble.image.zoomable:active { transform: scale(0.98); }
.after-img.zoomable { cursor: zoom-in; }
.after-img.zoomable:active { transform: scale(0.99); }

/* ---------- Full-bleed after-reveal (swipe-to-compare) ----------
   Takes over the entire `.phone` viewport when the AI render is ready.
   Two stacked images + a draggable vertical divider. The before sits
   underneath as a regular <img>; the after is wrapped in `.ar-after-clip`
   whose `clip-path: inset(...)` is driven by pointer x-position. This is
   the most-screenshotted moment in the product, so the chrome is muted
   (small wordmark, close X, single CTA) — the IMAGE is the hero. */
.after-reveal {
  position: absolute;
  inset: 0;
  z-index: 90;
  background: #000;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: arIn 280ms var(--ease-spring, var(--ease));
  -webkit-user-select: none;
  user-select: none;
  touch-action: none;
}
.after-reveal.is-dismissing { animation: arOut 220ms ease-in forwards; }
@keyframes arIn  { from { opacity: 0; transform: scale(1.02); } to { opacity: 1; transform: scale(1); } }
@keyframes arOut { from { opacity: 1; } to { opacity: 0; } }

.after-reveal-stage {
  position: absolute;
  inset: 0;
  cursor: ew-resize;
  overflow: hidden;
}
.ar-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  -webkit-user-drag: none;
}
/* Landscape source — letterbox (contain) instead of cover so the user
   can see the full frame end-to-end. The horizontal slider still maps
   cleanly because both before and after share the same letterbox bands. */
.after-reveal-stage.is-landscape .ar-img { object-fit: contain; }
.after-reveal-stage.is-landscape { background: #000; }
.ar-after-clip {
  position: absolute;
  inset: 0;
  /* JS sets clip-path: inset(0 0 0 X%) — keep the right slice (AFTER) and
     reveal BEFORE on the left as the handle moves right. */
  clip-path: inset(0 0 0 50%);
  will-change: clip-path;
}

/* Corner labels — small, restrained, screenshot-friendly. The bottom
   offset must clear .ar-bottom (hint ~18px + gap 10 + CTA 52 + 12 top
   + 20 bottom padding + safe-area ≈ 112px + safe-area) plus breathing
   room — otherwise the pills slip under the Fortsæt button on phones
   with home-indicator gestures. */
.ar-tag {
  position: absolute;
  bottom: calc(128px + env(safe-area-inset-bottom, 0px));
  padding: 5px 10px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: #fff;
  font-family: var(--font-display, inherit);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  z-index: 3;
  pointer-events: none;
}
.ar-tag-before { left: 16px; }
.ar-tag-after  {
  /* The AFTER tag is the brand's loudest moment. Electric lime instead
     of amber so the magic axis (--magic) actually shows up — amber
     stays reserved for money/totals so the two beats don't fight. */
  right: 16px;
  background: var(--magic);
  color: #0A1500;
  border-color: var(--magic-deep);
  box-shadow: var(--magic-glow);
}

/* Vertical divider + handle. */
.ar-divider {
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  width: 0;
  transform: translateX(-1px);
  z-index: 4;
  pointer-events: none; /* drag is captured by the stage */
}
.ar-divider-line {
  position: absolute;
  top: 0; bottom: 0;
  left: 0;
  width: 2px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.0), rgba(255, 255, 255, 0.85) 18%, rgba(255, 255, 255, 0.85) 82%, rgba(255, 255, 255, 0.0));
  box-shadow: 0 0 14px rgba(0, 0, 0, 0.45);
}
.ar-handle {
  position: absolute;
  top: 50%;
  left: 0;
  transform: translate(-50%, -50%);
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: #fff;
  color: #1F2327;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45), 0 2px 6px rgba(0, 0, 0, 0.35);
  cursor: ew-resize;
  pointer-events: auto;
  outline: none;
  animation: arHandleHint 1.8s ease-out 200ms 2;
}
.ar-handle:focus-visible { box-shadow: 0 0 0 3px var(--magic), 0 8px 24px rgba(0, 0, 0, 0.45); }
@keyframes arHandleHint {
  0%   { transform: translate(-50%, -50%) scale(1); }
  18%  { transform: translate(calc(-50% - 22px), -50%) scale(1.06); }
  36%  { transform: translate(calc(-50% + 22px), -50%) scale(1.06); }
  54%  { transform: translate(-50%, -50%) scale(1); }
  100% { transform: translate(-50%, -50%) scale(1); }
}

/* Top chrome — minimal wordmark + close. */
.ar-top {
  position: relative;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Respect the iOS status bar / Dynamic Island so the wordmark and
     close button don't collide with the system clock — this view is
     full-bleed and bypasses the outer .phone safe-area padding. */
  padding: calc(14px + env(safe-area-inset-top, 0px)) 16px 8px;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0));
  pointer-events: none;
}
.ar-top > * { pointer-events: auto; }
.ar-wordmark {
  font-family: var(--font-display, inherit);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: #fff;
}
.ar-wordmark .ar-dot { color: var(--magic); }
.ar-close {
  width: 38px; height: 38px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(0, 0, 0, 0.4);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), transform 160ms var(--ease-spring, var(--ease));
}
.ar-close:active { transform: scale(0.92); }

/* Bottom CTA — single primary button, hint above. */
.ar-bottom {
  position: relative;
  z-index: 5;
  margin-top: auto;
  padding: 12px 20px calc(20px + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0));
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  pointer-events: none;
}
.ar-bottom > * { pointer-events: auto; }
.ar-hint {
  margin: 0;
  text-align: center;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: rgba(255, 255, 255, 0.78);
}
.ar-continue {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 52px;
  border: 0;
  border-radius: 16px;
  /* Lime, not amber — this CTA is "see what just happened," not
     "commit money." The lime survives video compression and reads
     as the magic beat the user just experienced. */
  background: var(--magic);
  color: #0A1500;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.01em;
  cursor: pointer;
  transition: transform 120ms ease;
  -webkit-appearance: none;
  appearance: none;
  box-shadow: var(--magic-glow);
}
.ar-continue:active { transform: scale(0.98); }

@media (prefers-reduced-motion: reduce) {
  .after-reveal,
  .after-reveal.is-dismissing,
  .ar-handle { animation: none !important; }
}

/* Image lightbox */
.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.92);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 9999;
  padding: 24px;
  animation: lb-fade .15s ease-out;
}
.lightbox.open { display: flex; }
.lightbox-stage {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  touch-action: none;          /* we handle pinch/pan ourselves */
  -ms-touch-action: none;
  overscroll-behavior: contain;
  cursor: grab;
}
.lightbox-stage:active { cursor: grabbing; }
.lightbox img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 8px;
  box-shadow: 0 20px 60px rgba(0,0,0,0.6);
  transform-origin: center center;
  will-change: transform;
  user-select: none;
  -webkit-user-select: none;
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
}
.lightbox-close {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.2);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.lightbox-close:hover { background: rgba(255,255,255,0.2); }
@keyframes lb-fade { from { opacity: 0; } to { opacity: 1; } }

/* Share bar */
.share-bar {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 14px calc(14px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}
.share-bar-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 10px;
  align-items: stretch;
}
.share-new {
  align-self: center;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--border-strong);
  color: var(--text);
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.share-new:hover { background: rgba(255, 255, 255, 0.10); }
.share-new:active { transform: scale(0.97); }
.share-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--primary-grad);
  color: #FFFFFF;
  border: none;
  padding: 14px 18px;
  border-radius: 999px;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.01em;
  cursor: pointer;
  box-shadow: var(--shadow-glow-primary), inset 0 1px 0 rgba(255, 255, 255, 0.25);
  transition: transform 160ms var(--ease-spring, var(--ease)), filter var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.share-primary:hover { filter: brightness(1.05); box-shadow: 0 12px 36px -8px rgba(78, 141, 118, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.3); }
.share-primary:active { transform: scale(0.96); }
.share-primary:disabled { opacity: 0.7; cursor: progress; }
.share-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 0 16px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--t-fast) var(--ease);
}
.share-secondary:hover { background: rgba(255,255,255,0.04); }
.share-secondary:active { background: rgba(255,255,255,0.06); }
.share-secondary:disabled { opacity: 0.6; cursor: progress; }

/* PDF export overrides — applied only while html2canvas snapshots the
   proposal card. Body text shrinks to ~12pt (16px) and surrounding
   typography is scaled to match. */
.proposal-card.pdf-export { font-size: 12pt; line-height: 1.45; }
.proposal-card.pdf-export .inv-row,
.proposal-card.pdf-export .inv-sub-row,
.proposal-card.pdf-export .scope-group,
.proposal-card.pdf-export .scope-group li,
.proposal-card.pdf-export .invoice-totals,
.proposal-card.pdf-export .proposal-section,
.proposal-card.pdf-export p,
.proposal-card.pdf-export li { font-size: 12pt; line-height: 1.45; }
.proposal-card.pdf-export h1 { font-size: 18pt; }
.proposal-card.pdf-export h2 { font-size: 15pt; }
.proposal-card.pdf-export h3,
.proposal-card.pdf-export .inv-group-head { font-size: 13pt; }
.proposal-card.pdf-export h4,
.proposal-card.pdf-export h5 { font-size: 12pt; }
.proposal-card.pdf-export .inv-tot-grand { font-size: 14pt; }

/* Map marker — Armbolt drop pin */
.leaflet-marker-icon.armbolt-pin {
  background: transparent;
  border: none;
  filter: drop-shadow(0 4px 6px rgba(0,0,0,0.35));
}

/* Current-location marker ("blue dot") */
.leaflet-marker-icon.me-pin {
  width: 18px !important; height: 18px !important;
  margin-left: -9px !important; margin-top: -9px !important;
  background: transparent;
  border: none;
  pointer-events: none;
}
.me-pin .me-pin-dot {
  position: absolute; inset: 0;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: #1A73E8;
  border: 3px solid #fff;
  box-shadow: 0 1px 4px rgba(0,0,0,0.4);
  box-sizing: border-box;
}
.me-pin .me-pin-pulse {
  position: absolute;
  left: -11px; top: -11px;
  width: 40px; height: 40px;
  border-radius: 50%;
  background: rgba(26, 115, 232, 0.25);
  animation: me-pulse 2s ease-out infinite;
}
@keyframes me-pulse {
  0%   { transform: scale(0.4); opacity: 0.8; }
  100% { transform: scale(1.2); opacity: 0; }
}

/* Hide scrollbars on chat (visual cleanliness) */
.chat-scroll::-webkit-scrollbar { width: 0; height: 0; }

/* Proposal: show a slim scrollbar so users know content scrolls */
.proposal-scroll {
  scrollbar-width: thin;
  scrollbar-color: rgba(78, 141, 118, 0.45) transparent;
}
.proposal-scroll::-webkit-scrollbar { width: 6px; }
.proposal-scroll::-webkit-scrollbar-track { background: transparent; }
.proposal-scroll::-webkit-scrollbar-thumb {
  background: rgba(78, 141, 118, 0.45);
  border-radius: 3px;
}
.proposal-scroll::-webkit-scrollbar-thumb:hover { background: rgba(78, 141, 118, 0.7); }

/* ===== Inline proposal in chat =====
   The proposal card and its action buttons live as bubbles inside the
   chat thread. These overrides let those bubbles span the full message
   column and remove the assistant bubble's default text padding. */
.bubble.assistant.proposal,
.bubble.assistant.proposal-actions {
  max-width: 100%;
  width: 100%;
  align-self: stretch;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;
}
.bubble.assistant.proposal .proposal-card {
  flex-shrink: 1;
  width: 100%;
}
/* Inline summary bar sits flush at the bottom of the proposal card,
   keeping the headline number visible without needing a sticky bar. */
.proposal-summary.inline {
  border-top: 1px solid var(--border-strong);
  border-bottom: none;
  border-radius: 0 0 var(--radius-card) var(--radius-card);
  padding: 12px 16px;
}
/* Inline share bar — sits as the next bubble below the proposal card.
   No top border, no safe-area padding (chat scroll already handles that). */
.share-bar.inline {
  border-top: none;
  padding: 4px 0 2px;
  gap: 10px;
}
.share-bar.inline .share-new { align-self: stretch; }
/* Three buttons rendered side-by-side, each taking equal width so the
   inline action bar reads as one unified pill row. Buttons keep their
   own visual hierarchy (primary yellow, secondary surface, ghost outline)
   but share identical geometry. */
.share-bar.inline .share-bar-row.triple {
  grid-template-columns: 1fr 1fr 1fr;
  gap: 8px;
  align-items: stretch;
}
.share-bar.inline .share-bar-row.triple > button {
  width: 100%;
  height: 48px;
  padding: 0 10px;
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.share-bar.inline .share-bar-row.triple > button svg {
  flex-shrink: 0;
}
/* Narrow phones (< 380px wide) — "Del tilbud (PDF)" is the longest label
   and will start to ellipsize. Hide the labels on the two secondary
   buttons so only icons remain, keeping the row visually balanced and
   leaving the primary CTA legible. The buttons keep their accessible
   names via aria-label set in JS. */
@media (max-width: 379px) {
  .share-bar.inline .share-bar-row.triple > button {
    padding: 0 8px;
    font-size: 12px;
  }
  .share-bar.inline .share-bar-row.triple > #newProposalBtn span,
  .share-bar.inline .share-bar-row.triple > #downloadPdfBtn span {
    display: none;
  }
  .share-bar.inline .share-bar-row.triple > #shareProposalBtn {
    gap: 6px;
  }
}

/* ===== Composer "+" action sheet (iOS-style slide-up) ===== */
.action-sheet {
  position: absolute;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  /* Very light scrim — keep the chat clearly visible behind the sheet so the
     user feels the panel is layered on top, not a new screen. */
  background: rgba(0, 0, 0, 0.18);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  animation: fadeIn var(--t-mid) var(--ease);
}
.action-sheet[hidden] { display: none; }
.action-sheet.closing { animation: fadeOut 200ms var(--ease) forwards; }
@keyframes fadeOut { to { opacity: 0; } }

.action-sheet-card {
  width: 100%;
  /* Translucent card with a strong blur — reads as frosted glass over the
     chat rather than an opaque sheet that hides it. */
  background: rgba(23, 26, 33, 0.55);
  backdrop-filter: blur(28px) saturate(180%);
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  border-top: 1px solid var(--border-strong);
  border-radius: 22px 22px 0 0;
  padding: 8px 10px calc(14px + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
  gap: 4px;
  animation: sheetUp 280ms var(--ease);
}
.action-sheet.closing .action-sheet-card { animation: sheetDown 200ms var(--ease) forwards; }
@keyframes sheetUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes sheetDown { to { transform: translateY(100%); } }

.action-sheet-grabber {
  width: 38px;
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.18);
  margin: 6px auto 10px;
}

/* Top media row — two large buttons (Camera / Photos) styled like the
   ChatGPT mobile composer sheet. Sits above the regular action list. */
.action-sheet-media {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  padding: 4px 4px 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
.action-sheet-media-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 14px 8px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--border);
  border-radius: 14px;
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.action-sheet-media-btn:hover { background: rgba(255, 255, 255, 0.10); }
.action-sheet-media-btn:active { transform: scale(0.97); background: rgba(78, 141, 118, 0.10); }
.action-sheet-media-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--primary);
}
.action-sheet-media-label {
  line-height: 1.2;
}

.action-sheet-item {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 14px 14px;
  background: transparent;
  border: none;
  border-radius: 12px;
  color: var(--text);
  font-family: inherit;
  font-size: 16px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.action-sheet-item + .action-sheet-item {
  border-radius: 12px;
}
.action-sheet-item:hover:not(:disabled) { background: rgba(255, 255, 255, 0.05); }
.action-sheet-item:active:not(:disabled) { background: rgba(78, 141, 118, 0.10); }
.action-sheet-item:disabled { cursor: default; color: var(--muted); }

.action-sheet-icon {
  width: 38px; height: 38px;
  flex-shrink: 0;
  color: var(--primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.action-sheet-item:disabled .action-sheet-icon {
  color: var(--muted);
}

.action-sheet-label {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.action-sheet-badge {
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  background: var(--pill-bg);
  border: 1px solid var(--pill-border);
  padding: 3px 8px;
  border-radius: var(--radius-pill);
}

.action-sheet-cancel {
  margin-top: 8px;
  width: 100%;
  padding: 14px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border);
  border-radius: 14px;
  color: var(--text);
  font-family: inherit;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.action-sheet-cancel:hover { background: rgba(255, 255, 255, 0.07); }
.action-sheet-cancel:active { background: rgba(255, 255, 255, 0.10); }

/* + button rotates while sheet is open */
.composer-plus.is-open {
  background: var(--primary);
  color: var(--surface);
  border-color: var(--primary);
  transform: rotate(45deg);
}

/* =========================================================================
   ===== Premium rebalance: yellow as a signal, not decoration ============
   Yellow (--primary) is reserved for:
     1. Primary CTAs            2. Money / totals
     3. Active AI states        4. Confirmed selections
   Everything else steps back into neutral surfaces / muted text.
   These rules are intentionally placed last so they win without
   touching the original declarations above.
   ========================================================================= */

/* --- Chips: filled brand green, deeper green when confirmed/selected --- */
.chip {
  background: var(--primary-grad);
  color: #FFFFFF;
  border-color: rgba(78, 141, 118, 0.55);
  font-weight: 500;
}
.chip:hover { filter: brightness(1.06); }
.chip.selected,
.chip[aria-pressed="true"],
.chip.is-confirmed {
  border-color: #3C6E5C;
  color: #FFFFFF;
  background: var(--primary-grad);
  font-weight: 600;
}
.inline-pills .chip { color: #FFFFFF; font-weight: 500; }
.inline-pills .chip.outline { color: var(--muted); }

/* --- Composer: drop yellow from neutral chrome --- */
.composer-action-icon { color: var(--muted); }
.composer-action:active:not(:disabled) { background: rgba(255, 255, 255, 0.08); }
.composer-plus:hover { border-color: var(--border-strong); }
.composer-plus:active { background: rgba(255, 255, 255, 0.08); }
.composer-row:focus-within { border-color: var(--border-strong); }

/* --- Send button: muted by default; yellow only when ready to send --- */
.send-btn { color: var(--muted); }
.send-btn.is-ready,
.composer-row:focus-within .send-btn { color: var(--primary); }

/* --- Action sheet media tiles: neutral icons, no yellow tap-tint --- */
.action-sheet-media-icon { color: var(--text); opacity: 0.85; }
.action-sheet-media-btn:active { background: rgba(255, 255, 255, 0.10); }

/* --- Inputs: subtle white focus instead of yellow border --- */
.field input:focus,
.field textarea:focus { border-color: var(--border-strong); }

/* --- After-card label: neutral caption (not money, not CTA) --- */
.after-card .after-label { color: var(--muted); font-weight: 600; }
.after-card .after-exclusions { border-top-color: rgba(255, 255, 255, 0.08); }

/* --- Map locate button: yellow only while actively locating (AI/active) ---
   (already correct — kept here for documentation) */

/* --- Proposal hero: keep warm gradient but desaturate for premium feel --- */
.proposal-hero {
  background: linear-gradient(135deg, #E8B400 0%, #C98A14 100%);
}

/* --- Proposal summary bar: calmer warmth, money still pops in yellow --- */
.proposal-summary {
  background: linear-gradient(180deg, rgba(255, 204, 0, 0.05) 0%, rgba(255, 204, 0, 0.015) 100%);
}

/* --- Proposal scrollbar: neutral, not yellow --- */
.proposal-scroll { scrollbar-color: rgba(255, 255, 255, 0.18) transparent; }
.proposal-scroll::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.18); }
.proposal-scroll::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.28); }

/* --- Hierarchy via opacity + weight, not color --- */
.screen-header h1 { font-weight: 800; letter-spacing: -0.02em; }
.screen-header .muted { opacity: 0.75; }
.proposal-section h4 { color: var(--muted); opacity: 0.8; }
.proposal-row .name { font-weight: 600; }
.line-item .li-title { font-weight: 600; letter-spacing: -0.005em; }

/* --- Proposal section dividers: a touch softer --- */
.proposal-section { border-bottom-color: rgba(255, 255, 255, 0.06); }

/* --- Money stays gold: explicit re-affirmation (defensive) --- */
.proposal-row .price,
.line-item .li-amount,
.cost-row.total span:last-child,
.proposal-total .amount,
.proposal-summary .ps-amount,
.inv-tot-row.inv-tot-grand .inv-tot-amt { color: var(--primary); }



/* ============================================================
   Consent modal — after-image trajectory capture (GDPR / EU)
   ------------------------------------------------------------
   Tone: trust-quiet, not jubilant. Uses the AI lime axis only
   for the optional accept CTA (the "magic" moment of opting
   in), leaves the decline path neutral so the privacy-by-
   default contract reads visually. Backdrop blurs the chat so
   the user understands this is a *gate*, not a side panel.
   ============================================================ */
.consent-backdrop {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(6, 7, 11, 0.62);
  -webkit-backdrop-filter: blur(14px) saturate(120%);
  backdrop-filter: blur(14px) saturate(120%);
  opacity: 0;
  transition: opacity 180ms ease-out;
}
.consent-backdrop.is-open { opacity: 1; }

.consent-modal {
  width: 100%;
  max-width: 360px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 22px;
  padding: 22px 22px 18px;
  color: var(--text);
  box-shadow:
    0 24px 60px -20px rgba(0, 0, 0, 0.65),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
  transform: translateY(8px) scale(0.985);
  transition: transform 220ms cubic-bezier(.2,.8,.2,1);
}
.consent-backdrop.is-open .consent-modal { transform: translateY(0) scale(1); }

.consent-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.consent-title {
  font-size: 17px;
  line-height: 1.25;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0;
  color: var(--text);
}
.consent-close {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: transparent;
  border: none;
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.consent-close:hover { color: var(--text); }

.consent-intro {
  font-size: 13.5px;
  line-height: 1.45;
  color: rgba(244, 246, 250, 0.84);
  margin: 0 0 12px;
}

.consent-points {
  list-style: none;
  padding: 0;
  margin: 0 0 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.consent-points li {
  position: relative;
  padding-left: 18px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
}
.consent-points li::before {
  content: '';
  position: absolute;
  left: 4px;
  top: 7px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--magic);
  opacity: 0.7;
}

.consent-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.consent-cta {
  width: 100%;
  padding: 12px 16px;
  border-radius: 14px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  border: 1px solid transparent;
  transition: transform 80ms ease-out, background 160ms ease-out, color 160ms ease-out;
}
.consent-cta:active { transform: scale(0.985); }

.consent-cta--accept {
  background: var(--magic-grad);
  color: #0B0D12;
  border-color: rgba(0, 0, 0, 0.08);
  box-shadow: var(--magic-glow);
}
.consent-cta--decline {
  background: transparent;
  color: var(--muted);
  border-color: var(--border-strong);
}
.consent-cta--decline:hover { color: var(--text); }


/* Settings-mode-only status line (e.g. "Currently: you are not contributing").
   Sits between the title row and the intro paragraph so the user sees
   the current state before re-reading the disclosure. */
.consent-current-state {
  margin: -4px 0 12px;
  padding: 8px 10px;
  font-size: 12px;
  line-height: 1.35;
  color: var(--text);
  background: var(--primary-soft);
  border: 1px solid rgba(78, 141, 118, 0.22);
  border-radius: 10px;
}
