/*
 * Darya - visual identity: the calm and rhythm of the sea.
 *
 * Mobile-first: base rules target small screens (tight spacing, full-bleed
 * layout), then min-width breakpoints add breathing room for larger
 * viewports. Logical CSS properties (padding-inline, margin-block, etc.)
 * are used for general layout so the whole page correctly mirrors between
 * the Persian (RTL) and English (LTR) builds automatically. Chat bubbles
 * are a deliberate, documented exception (see note below): the user's own
 * messages stay on the physical right in *both* languages, matching how
 * most chat apps behave regardless of text direction.
 *
 * Rounding is intentionally moderate throughout: enough to feel warm and
 * approachable, without exaggerated fully-pill shapes.
 */

/* --------------------------------------------------------------------- */
/* Self-hosted fonts: no CDN, works fully offline (PWA / packaged app).  */
/* Be Vietnam Pro and Quicksand are self-hosted; Vazirmatn ships in four  */
/* static weights so the app remains fully usable offline.                */
/* --------------------------------------------------------------------- */

@font-face {
  font-family: Vazirmatn;
  src: url('../fonts/Vazirmatn-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: Vazirmatn;
  src: url('../fonts/Vazirmatn-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: Vazirmatn;
  src: url('../fonts/Vazirmatn-SemiBold.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: Vazirmatn;
  src: url('../fonts/Vazirmatn-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: Lalezar;
  src: url('../fonts/Lalezar-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: 'Be Vietnam Pro';
  src: url('../fonts/BeVietnamPro-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: 'Be Vietnam Pro';
  src: url('../fonts/BeVietnamPro-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: 'Be Vietnam Pro';
  src: url('../fonts/BeVietnamPro-SemiBold.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: 'Be Vietnam Pro';
  src: url('../fonts/BeVietnamPro-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: 'Be Vietnam Pro';
  src: url('../fonts/BeVietnamPro-Italic.woff2') format('woff2');
  font-weight: 400;
  font-style: italic;
  font-display: block;
}

@font-face {
  font-family: Quicksand;
  src: url('../fonts/Quicksand-VF.woff2') format('woff2-variations');
  font-weight: 400 700;
  font-style: normal;
  font-display: block;
}

:root {
  /* --- Color: deep ocean slate and midnight, seafoam accents --- */
  --color-abyss: #0a2029;
  --color-abyss-deep: #07151b;
  --color-tide: #143f48;
  --color-tide-soft: #194a54;
  --color-foam: #eaf3ef;
  --color-foam-dim: #9dbdb6;
  --color-coral: #e2805f;
  --color-coral-soft: #f1a98d;
  --color-seafoam: #83c3b1;
  --color-warning: #f2b987;
  /* Semantic roles keep component contrast consistent across themes.
     The surface tokens are the frosted-glass recipe - a pale top glint
     over a translucent deep-blue body - so every panel and control
     shares one glass treatment, and it stays theme-scoped via the
     --glass-* custom properties below. */
  --surface-app: var(--color-abyss-deep);
  --surface-panel: var(--glass-sheen), var(--glass-frost);
  --surface-panel-hover: var(--glass-sheen), var(--glass-frost-strong);
  --surface-control: var(--glass-sheen), var(--glass-frost);
  --surface-control-hover: var(--glass-sheen), var(--glass-frost-strong);
  --text-primary: var(--color-foam);
  --text-secondary: var(--color-foam-dim);
  --border-subtle: var(--glass-edge);
  --border-strong: var(--color-seafoam);
  --border-focus: var(--color-seafoam);
  /* Input field boundaries need their own stronger line: 1.4.11 calls
     out input boundaries explicitly, and the faint decorative line used
     on bubbles and cards is far below the 3:1 threshold. */
  --border-input: rgba(234, 243, 239, 0.45);

  /* --- Type: Persian is the default; overridden per-language below --- */
  --font-display: 'Lalezar', 'Vazirmatn', 'Tahoma', sans-serif;
  --font-body: 'Vazirmatn', 'Tahoma', 'Arial', sans-serif;

  /* --- Spacing scale (mobile-first: kept tight by default) --- */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 28px;

  /* --- Layout: design-system radii --- */
  /* Panels (picker, menu popover): 28px */
  --radius-lg: 28px;
  /* Buttons, inputs, theme picker: 18px */
  --radius-md: 18px;
  /* Chips, tags, small controls: 14px */
  --radius-sm: 14px;
  --radius-full: 999px; /* reserved for small circular icon buttons only */

  /* --- Elevation: layered, low-opacity ambient shadows. A single dark  */
  /* blob reads as heavy and smudgy on a dark sea; three stops (a tight  */
  /* contact shadow, a mid presence, and a wide diffuse lift) give the   */
  /* panes grounded depth without ever shouting. --shadow-color is an    */
  /* RGB triplet so the beach theme warms the ambient shadow to sand     */
  /* instead of a cool navy cast.                                        */
  --shadow-color: 3, 12, 15;
  --shadow-soft:
    0 1px 2px rgba(var(--shadow-color), 0.14),
    0 2px 8px rgba(var(--shadow-color), 0.1),
    0 14px 32px rgba(var(--shadow-color), 0.1);
  --shadow-elevated:
    0 2px 4px rgba(var(--shadow-color), 0.12),
    0 10px 22px rgba(var(--shadow-color), 0.1),
    0 28px 64px rgba(var(--shadow-color), 0.16);

  /* --- Glass: a calm frosted material, not layered optics. A single   */
  /* translucent deep-blue body (--glass-frost, opaque enough that      */
  /* light text keeps its contrast), a pale top-lit rim (--glass-edge), */
  /* one angled light-catch (--glass-sheen), and one top specular       */
  /* catch-light (--glass-specular). The chromatic fringe and the       */
  /* second specular inset are gone so the pane reads as softly frosted */
  /* rather than a stack of artificial reflections, and the backdrop    */
  /* blur is a whisper that grounds the pane instead of a feature.      */
  /* Depth is tiered: large chrome (cards, menus, the composer,         */
  /* dialogs) uses the richer --glass-blur, while small controls and    */
  /* chat content use --glass-blur-soft so long conversations never     */
  /* stack heavy blur. A @supports fallback near the end of this file   */
  /* raises the frost to near-opaque where blur is unavailable, and     */
  /* prefers-reduced-transparency plus prefers-contrast do the same     */
  /* for users who ask for them.                                        */
  --glass-blur: 14px;
  --glass-blur-soft: 8px;
  --glass-saturate: 120%;
  --glass-frost: rgba(20, 63, 72, 0.58);
  --glass-frost-strong: rgba(24, 74, 84, 0.74);
  /* Chat bubbles are the content layer: near-opaque so text sits on a
     stable surface instead of floating on translucent glass. */
  --bubble-frost: rgba(20, 63, 72, 0.84);
  /* A soft diagonal reflection on each bubble, echoing the panel sheen
     so the messages share the same light direction as the chrome. */
  --user-bubble-sheen: linear-gradient(
    158deg,
    rgba(255, 255, 255, 0.22) 0%,
    rgba(255, 255, 255, 0) 48%
  );
  --bot-bubble-sheen: linear-gradient(
    158deg,
    rgba(214, 244, 250, 0.14) 0%,
    rgba(214, 244, 250, 0) 52%
  );
  --glass-sheen: linear-gradient(
    158deg,
    rgba(214, 244, 250, 0.32) 0%,
    rgba(214, 244, 250, 0.08) 30%,
    rgba(214, 244, 250, 0) 65%
  );
  /* The rim is a whisper, not a drawn line: a faint lightening of the
     pane's own tint so the edge reads as light catching the glass. The
     top specular catch-light carries the rest of the definition. */
  --glass-edge: rgba(196, 238, 246, 0.14);
  --glass-specular: inset 0 1px 0 rgba(244, 253, 255, 0.32);
  --glass-specular-soft: inset 0 1px 0 rgba(244, 253, 255, 0.24);
  /* Circular icon buttons need a clearly visible rim on the dark sea:
     the faint panel edge would leave a 34px disc with no readable
     boundary. A brighter hairline keeps the circle crisp. */
  --circle-rim: rgba(196, 238, 246, 0.34);

  /* --- Motion --- */
  --ease-tide: cubic-bezier(0.37, 0, 0.24, 1);

  /* Cursor-tracked glass glint: a whisper-light radial highlight that
     follows the pointer across interactive glass (see the glint section
     at the end of this file). Kept at a whisper opacity on purpose. */
  --glint-color: rgba(255, 255, 255, 0.09);
}

/* English gets its own, equally considered type pairing rather than
   reusing the Persian faces, which aren't optimized for long-form Latin
   text: Be Vietnam Pro is warm and highly legible for body copy; Quicksand
   is a rounded, friendly display face for the wordmark, playing the same
   role Lalezar plays for Persian. */
html[lang='en'] {
  --font-display: 'Quicksand', 'Be Vietnam Pro', sans-serif;
  --font-body: 'Be Vietnam Pro', -apple-system, 'Segoe UI', sans-serif;
}

/* Beach theme: warm sand and shell tones. Dark ink surfaces sit on top of
   the bright sky so every control, menu, and panel reads clearly. The
   literal sand/surf imagery lives in the decorative .backdrop layer below. */
html[data-theme='beach'] {
  --color-abyss: #1a3c38;
  --color-abyss-deep: #0e2a26;
  --color-tide: #1f5449;
  --color-tide-soft: #28645a;
  --color-coral: #dc7f5d;
  --color-coral-soft: #eaa98b;
  --color-seafoam: #6cc4a8;
  --color-warning: #cb7e2c;
  /* Warm the ambient shadows to sand brown so they read naturally over
     the bright beach instead of casting a cool navy haze. */
  --shadow-color: 91, 61, 35;
  /* The beach composer is a translucent teal panel over sand, which
     composites to #456d5e; a light foam boundary keeps the input edge
     at 3.3:1 while the dark translucent line is invisible. */
  --border-input: rgba(240, 247, 244, 0.65);
  /* Glass frost over bright sand must stay deep-teal so light text keeps
     its contrast; the rim, sheen, and specular carry the glass feel. */
  --glass-frost: rgba(31, 84, 73, 0.86);
  --glass-frost-strong: rgba(40, 100, 90, 0.94);
  --bubble-frost: rgba(31, 84, 73, 0.94);
  --glass-sheen: linear-gradient(
    158deg,
    rgba(255, 255, 255, 0.28) 0%,
    rgba(255, 255, 255, 0.08) 30%,
    rgba(255, 255, 255, 0) 65%
  );
  --glass-edge: rgba(240, 247, 244, 0.16);
  --glass-specular: inset 0 1px 0 rgba(255, 255, 255, 0.4);
  --glass-specular-soft: inset 0 1px 0 rgba(255, 255, 255, 0.3);
  /* Light glass: small chrome (header buttons, note chips, the sound
     toggle) sits on the bright sky, so it flips to a frosted light pane
     with dark ink, mirroring Liquid Glass's light/dark adaptivity. The
     panels and bubbles keep the dark frost above so light text clears
     AA contrast. */
  --glass-light:
    linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.5) 0%,
      rgba(255, 255, 255, 0.12) 100%
    ),
    rgba(200, 221, 229, 0.66);
  --glass-light-strong:
    linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.6) 0%,
      rgba(255, 255, 255, 0.18) 100%
    ),
    rgba(212, 230, 236, 0.8);
  --glass-light-edge: rgba(255, 255, 255, 0.4);
  --glass-light-specular: inset 0 1px 0 rgba(255, 255, 255, 0.65);
  /* On the bright beach, light-glass panels are already near-white, so
     the tracked glint needs a touch more presence to stay visible. */
  --glint-color: rgba(255, 255, 255, 0.14);
}

* {
  box-sizing: border-box;
}

/* Mobile tap polish: remove the default gray tap flash (focus rings are
   still drawn via :focus-visible) and disable the double-tap zoom delay
   so buttons respond instantly on touch screens. */
button,
[role='button'],
textarea,
input,
select {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

/*
 * Critical correctness rule: `.picker` and `.app` both set their own
 * `display` (flex), which has equal CSS specificity to the browser's
 * built-in `[hidden] { display: none }` rule and comes later in the
 * cascade, so it would otherwise win and leave a "hidden" element fully
 * visible and interactive. This selector has higher specificity than
 * either, so `hidden` always actually hides the element.
 */
[hidden] {
  display: none !important;
}

html,
body {
  height: 100%;
}

html {
  /* Prevent iOS Safari from inflating text size on orientation change.
     The prefixed form covers iOS versions that predate the standard
     property; stylelint exempts it for that legacy support. */
  /* stylelint-disable-next-line property-no-vendor-prefix */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font-body);
  background: radial-gradient(
    120% 90% at 50% -10%,
    var(--color-tide) 0%,
    var(--color-abyss) 45%,
    var(--color-abyss-deep) 100%
  );
  color: var(--color-foam);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  text-align: start;
}

/* --------------------------------------------------------------------- */
/* Theme transition system: smooth crossfade between ocean and beach.    */
/* The .no-theme-transition class is added by the inline <head> script   */
/* during initial load to prevent the browser from animating from the     */
/* default (ocean) to the saved theme (beach) on page load. Once the     */
/* page is painted, the class is removed and transitions are enabled.    */
/* Transitions are scoped narrowly to body (background gradient) and     */
/* the beach-scene / bubbles / bird-shadows (opacity crossfade). Other  */
/* elements (buttons, bubbles, composer) already have their own tuned   */
/* transition declarations that must not be overridden.                  */
/* --------------------------------------------------------------------- */

html:not(.no-theme-transition) body {
  transition: color 0.45s var(--ease-tide);
}

html:not(.no-theme-transition) .beach-scene,
html:not(.no-theme-transition) .beach-scene__wave,
html:not(.no-theme-transition) .bubbles,
html:not(.no-theme-transition) .ocean-particles,
html:not(.no-theme-transition) .ocean-caustic,
html:not(.no-theme-transition) .ocean-current,
html:not(.no-theme-transition) .bird-shadows,
html:not(.no-theme-transition) .backdrop__glow,
html:not(.no-theme-transition) .backdrop__horizon {
  transition:
    opacity 0.5s var(--ease-tide),
    visibility 0.5s var(--ease-tide);
}

html:not(.no-theme-transition) .menu__trigger {
  transition:
    border-color 0.35s var(--ease-tide),
    color 0.35s var(--ease-tide),
    background 0.35s var(--ease-tide),
    box-shadow 0.35s var(--ease-tide);
}

/* Keep the contextual reveal in this higher-specificity theme rule. If the
   opacity and transform entries are omitted here, this declaration wins over
   .breathe-trigger below and the control pops into view instead of fading. */
html:not(.no-theme-transition) .breathe-trigger {
  transition:
    opacity 0.36s var(--ease-tide),
    transform 0.36s var(--ease-tide),
    visibility 0.36s var(--ease-tide),
    border-color 0.35s var(--ease-tide),
    color 0.35s var(--ease-tide),
    background 0.35s var(--ease-tide),
    box-shadow 0.35s var(--ease-tide);
}

:focus-visible {
  outline: 2.5px solid var(--border-focus);
  outline-offset: 2px;
  border-radius: 6px;
}

/* --------------------------------------------------------------------- */
/* UI chrome is not selectable. In the packaged Android/WebView build a  */
/* long-press on a menu label, dialog line, or the footer pops the       */
/* native text-selection callout, which reads as a broken, non-native    */
/* experience. Conversation bubbles and the composer input stay          */
/* selectable so users can copy what Darya (or they) wrote.              */
/* --------------------------------------------------------------------- */

.picker,
.header,
.menu__popover,
.theme-picker,
.exit-confirm-bar,
.confirm-container,
.notification-container,
.disclaimer {
  /* stylelint-disable-next-line property-no-vendor-prefix */
  -webkit-user-select: none;
  user-select: none;
}

/* --------------------------------------------------------------------- */
/* Ambient backdrop: soft glows, a faint horizon wave, and rising bubbles */
/* --------------------------------------------------------------------- */

.backdrop {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.backdrop__glow {
  position: absolute;
  width: 70vw;
  height: 70vw;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0.14;
  will-change: transform;
}

.backdrop__glow--one {
  top: -15%;
  inset-inline-end: -15%;
  background: var(--color-seafoam);
  animation: drift-one 26s ease-in-out infinite;
}

.backdrop__glow--two {
  bottom: -20%;
  inset-inline-start: -15%;
  background: var(--color-coral);
  animation: drift-two 32s ease-in-out infinite;
}

@keyframes drift-one {
  0%,
  100% {
    transform: translate(0, 0) scale(1);
  }

  50% {
    transform: translate(-4%, 6%) scale(1.08);
  }
}

@keyframes drift-two {
  0%,
  100% {
    transform: translate(0, 0) scale(1);
  }

  50% {
    transform: translate(5%, -5%) scale(1.05);
  }
}

.backdrop__horizon {
  position: absolute;
  left: -10%;
  right: -10%;
  bottom: -6%;
  width: 120%;
  height: 40vh;
  opacity: 0.1;
  animation: horizon-drift 40s ease-in-out infinite;
  transition: opacity 0.4s ease;
}

@keyframes horizon-drift {
  0%,
  100% {
    transform: translateX(0);
  }

  50% {
    transform: translateX(-2%);
  }
}

html[data-theme='beach'] .backdrop__horizon {
  opacity: 0;
}

/* --------------------------------------------------------------------- */
/* Ocean-theme bubbles: populated by ambient-visuals.js with randomized */
/* size, drift, and timing per bubble (not a repeating formula), so they */
/* never read as a sequential row.                                      */
/* --------------------------------------------------------------------- */

.bubbles {
  position: absolute;
  inset: 0;
  opacity: 1;
}

html[data-theme='beach'] .bubbles {
  opacity: 0;
}

.bubble-particle {
  position: absolute;
  bottom: -40px;
  left: var(--left, 50%);
  width: var(--size, 10px);
  height: var(--size, 10px);
  border-radius: 50%;
  background: radial-gradient(
    circle at 35% 30%,
    rgba(234, 243, 239, 0.55),
    rgba(127, 190, 176, 0.08)
  );
  animation: rise-bubble var(--duration, 16s) ease-in infinite;
  animation-delay: var(--delay, 0s);
  will-change: transform, opacity;
}

@keyframes rise-bubble {
  0% {
    transform: translateY(0) translateX(0);
    opacity: 0;
  }

  10% {
    opacity: var(--peak-opacity, 0.4);
  }

  90% {
    opacity: calc(var(--peak-opacity, 0.4) * 0.7);
  }

  100% {
    transform: translateY(-100vh) translateX(var(--drift, 12px));
    opacity: 0;
  }
}

/* --------------------------------------------------------------------- */
/* Ocean-theme bioluminescent particles: tiny drifting glowing dots that  */
/* suggest deep-sea plankton or microbial light. Each particle rises     */
/* slowly with a subtle horizontal sway, at very low opacity. Populated  */
/* by ambient.js with randomized position, size, duration, and glow,     */
/* so no two conversations look identical. Only visible in Ocean theme.  */
/* --------------------------------------------------------------------- */

.ocean-particles {
  position: absolute;
  inset: 0;
  opacity: 1;
  pointer-events: none;
}

html[data-theme='beach'] .ocean-particles {
  opacity: 0;
}

.ocean-particle {
  position: absolute;
  bottom: -10px;
  left: var(--left, 50%);
  width: var(--size, 3px);
  height: var(--size, 3px);
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 50%,
    rgba(160, 240, 220, 0.7) 0%,
    rgba(100, 220, 200, 0.15) 60%,
    transparent 100%
  );
  box-shadow: 0 0 var(--glow-radius, 4px) rgba(100, 220, 200, 0.25);
  animation: ocean-particle-rise var(--duration, 40s) ease-in-out infinite;
  animation-delay: var(--delay, 0s);
  will-change: transform, opacity;
}

@keyframes ocean-particle-rise {
  0% {
    transform: translateY(0) translateX(0) scale(0.3);
    opacity: 0;
  }

  5% {
    opacity: var(--peak-opacity, 0.35);
    transform: translateY(0) translateX(0) scale(1);
  }

  50% {
    opacity: calc(var(--peak-opacity, 0.35) * 0.8);
    transform: translateY(-45vh) translateX(var(--sway, 20px)) scale(1);
  }

  95% {
    opacity: var(--peak-opacity, 0.35);
    transform: translateY(-95vh) translateX(calc(var(--sway, 20px) * -0.6))
      scale(1);
  }

  100% {
    transform: translateY(-100vh) translateX(calc(var(--sway, 20px) * -0.8))
      scale(0.3);
    opacity: 0;
  }
}

/* --------------------------------------------------------------------- */
/* Ocean-theme caustic light patterns: faint, slowly-shifting elliptical */
/* gradients that suggest light refracting through the water surface     */
/* above. Multiple layers with different sizes, positions, and drift     */
/* speeds create an organic, ever-changing underwater light show. Only   */
/* visible in Ocean theme.                                              */
/* --------------------------------------------------------------------- */

.ocean-caustic {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  opacity: 1;
  will-change: transform, opacity;
}

html[data-theme='beach'] .ocean-caustic {
  opacity: 0;
}

.ocean-caustic--one {
  bottom: 8%;
  left: -10%;
  width: 60vw;
  height: 25vh;
  background: radial-gradient(
    ellipse at 40% 50%,
    rgba(100, 220, 200, 0.04) 0%,
    rgba(60, 180, 180, 0.02) 40%,
    transparent 70%
  );
  animation: ocean-caustic-drift 38s ease-in-out infinite;
}

.ocean-caustic--two {
  bottom: 15%;
  right: -5%;
  width: 45vw;
  height: 18vh;
  background: radial-gradient(
    ellipse at 60% 50%,
    rgba(160, 240, 220, 0.03) 0%,
    rgba(80, 200, 190, 0.015) 35%,
    transparent 65%
  );
  animation: ocean-caustic-drift 52s ease-in-out infinite reverse;
}

@keyframes ocean-caustic-drift {
  0%,
  100% {
    transform: translateX(0) scale(1);
    opacity: 1;
  }

  25% {
    transform: translateX(4%) scale(1.08);
    opacity: 0.85;
  }

  50% {
    transform: translateX(-3%) scale(0.95);
    opacity: 1;
  }

  75% {
    transform: translateX(5%) scale(1.04);
    opacity: 0.9;
  }
}

/* --------------------------------------------------------------------- */
/* Ocean-theme deep-sea current: a subtle horizontal gradient strip at   */
/* the very bottom of the viewport that drifts slowly, suggesting a      */
/* deep-water current moving beneath the surface. Ultra-low opacity so   */
/* it is barely perceptible, an atmospheric whisper, not a visual        */
/* element. Only visible in Ocean theme.                                 */
/* --------------------------------------------------------------------- */

.ocean-current {
  position: absolute;
  bottom: 0;
  left: -10%;
  width: 120%;
  height: 12vh;
  pointer-events: none;
  opacity: 1;
  background: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(60, 140, 160, 0.03) 30%,
    rgba(40, 100, 120, 0.05) 60%,
    rgba(20, 70, 90, 0.04) 100%
  );
  animation: ocean-current-drift 60s linear infinite;
}

html[data-theme='beach'] .ocean-current {
  opacity: 0;
}

@keyframes ocean-current-drift {
  0% {
    transform: translateX(0);
  }

  100% {
    transform: translateX(-5%);
  }
}

/* --------------------------------------------------------------------- */
/* Beach-theme bird shadows on the sand: subtle, horizontally-drifting   */
/* dark silhouettes suggesting distant flocks flying overhead. Birds in  */
/* each group share the same duration and delay so they appear to move   */
/* together, while small positional/scale variations keep the group      */
/* looking organic rather than pixel-perfectly aligned.                  */
/* --------------------------------------------------------------------- */

.bird-shadows {
  position: absolute;
  inset-inline: 0;
  top: 66%;
  bottom: 0;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}

html[data-theme='beach'] .bird-shadows {
  opacity: 1;
  pointer-events: auto;
}

.bird-shadow {
  position: absolute;
  top: var(--top, 10%);
  left: calc(-15vw + var(--flock-offset, 0px));
  width: calc(60px * var(--scale, 1));
  height: calc(8px * var(--scale, 1));
  border-radius: 50%;
  background: radial-gradient(
    ellipse at 45% 50%,
    rgba(91, 61, 35, 0.3) 0%,
    rgba(91, 61, 35, 0.16) 35%,
    rgba(91, 61, 35, 0.05) 62%,
    transparent 80%
  );
  filter: blur(2px);
  animation: bird-shadow-drift var(--duration, 20s) linear infinite;
  animation-delay: var(--delay, 0s);
  opacity: 0;
  will-change: transform, opacity;
}

@keyframes bird-shadow-drift {
  0% {
    transform: translateX(0);
    opacity: 0;
  }

  4% {
    opacity: var(--peak-opacity, 0.4);
  }

  30% {
    opacity: var(--peak-opacity, 0.4);
  }

  34% {
    transform: translateX(145vw);
    opacity: 0;
  }

  100% {
    transform: translateX(145vw);
    opacity: 0;
  }
}

/* --------------------------------------------------------------------- */
/* Language picker                                                       */
/* --------------------------------------------------------------------- */

.picker {
  position: relative;
  z-index: 1;
  min-height: 100vh; /* fallback for browsers without dvh support */
  min-height: 100dvh;
  display: flex;
  /* Scroll, never clip, when the content is taller than a short
     landscape viewport. Vertical centering moves to margin-block:
     auto on the child, which never clips the start edge. */
  overflow-y: auto;
  padding-top: max(var(--space-4), env(safe-area-inset-top, 0px));
  padding-bottom: max(var(--space-4), env(safe-area-inset-bottom, 0px));
  padding-inline: var(--space-4);
}

.picker__inner {
  width: 100%;
  max-width: 420px;
  margin-inline: auto;
  margin-block: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
  text-align: center;
}

.picker__intro {
  margin: 0;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-size: 20px;
  font-weight: 400;
}

.picker__intro-fa {
  font-family: Lalezar, Vazirmatn, sans-serif;
  color: var(--color-coral);
  user-select: none;
}

.picker__intro-en {
  font-family: Quicksand, 'Be Vietnam Pro', sans-serif;
  font-weight: 700;
  font-size: 1.05em;
  color: var(--color-seafoam);
  user-select: none;
}

.picker__intro-sep {
  color: var(--color-foam-dim);
}

.picker__options {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.picker__option {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-4);
  min-height: 52px;
  background: var(--surface-panel);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  color: var(--text-primary);
  box-shadow: var(--glass-specular), var(--shadow-soft);
  cursor: pointer;
  transition:
    border-color 0.2s var(--ease-tide),
    background 0.2s var(--ease-tide),
    box-shadow 0.2s var(--ease-tide);
}

.picker__option:hover {
  border-color: var(--color-seafoam);
  background: var(--surface-panel-hover);
  box-shadow: var(--glass-specular), var(--shadow-elevated);
}

.picker__option:active {
  box-shadow: var(--glass-specular), var(--shadow-soft);
}

.picker__option-title {
  font-family: var(--font-display);
  font-size: 20px;
}

#picker-fa .picker__option-title {
  font-family: Lalezar, Vazirmatn, sans-serif;
  font-size: 22px;
  /* Coral alone clears only 4.08:1 on the panel (3.48:1 on hover); the
     softer tone lifts it above the 4.5:1 AA threshold on both states. */
  color: var(--color-coral-soft);
}

#picker-en .picker__option-title {
  font-family: Quicksand, 'Be Vietnam Pro', sans-serif;
  font-weight: 700;
  font-size: 22px;
  color: var(--color-seafoam);
}

.picker__option-desc {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--color-foam-dim);
}

.picker__note-sep {
  opacity: 0.6;
}

/* --------------------------------------------------------------------- */
/* Language-lock note in the picker (relocated from the menu dropdown)   */
/* Each language variant sits on its own line.                           */
/* --------------------------------------------------------------------- */

/* The lang-lock note and theme heading both sit in a small glass chip in
   BOTH themes, so switching theme only swaps the chip tint and ink and
   never reflows the picker (a chip that appears or disappears on switch
   would visibly jump the layout). The chip also guarantees the muted
   text keeps its 4.5:1 legibility over whatever drifts behind it. */
.picker__lang-lock {
  font-size: 11.5px;
  color: var(--text-secondary);
  text-align: center;
  margin: -4px 0 0;
  line-height: 1.7;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--surface-control);
  backdrop-filter: blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
  border: 1px solid var(--border-subtle);
  box-shadow: var(--glass-specular-soft);
  opacity: 0.9;
}

.picker__lang-lock > span {
  display: block;
}

.picker__theme-heading {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-secondary);
  text-align: center;
  margin: 0;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--surface-control);
  backdrop-filter: blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
  border: 1px solid var(--border-subtle);
  box-shadow: var(--glass-specular-soft);
  opacity: 0.9;
}

/* Beach swaps the chip to light glass with dark ink; the box model is
   identical to ocean so the theme switch never moves the picker. */
html[data-theme='beach'] .picker__lang-lock,
html[data-theme='beach'] .picker__theme-heading {
  color: var(--color-on-sky);
  background: var(--glass-light);
  border-color: var(--glass-light-edge);
  box-shadow: var(--glass-light-specular);
}

/* --------------------------------------------------------------------- */
/* Theme picker: a small segmented control offered alongside language.   */
/* --------------------------------------------------------------------- */

.theme-picker {
  width: 100%;
  display: flex;
  gap: var(--space-2);
}

.theme-picker__option {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: var(--space-3) var(--space-2);
  min-height: 44px;
  background: var(--surface-control);
  backdrop-filter: blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  color: var(--color-foam-dim);
  cursor: pointer;
  transition:
    border-color 0.2s var(--ease-tide),
    color 0.2s var(--ease-tide),
    background 0.2s var(--ease-tide),
    box-shadow 0.2s var(--ease-tide);
}

/* Hover hints that the segment is interactive before the pressed state
   takes over; the pressed rule below it wins while actually selected. */
.theme-picker__option:hover {
  border-color: rgba(127, 190, 176, 0.45);
  color: var(--color-foam);
}

.theme-picker__option[aria-pressed='true'] {
  border-color: var(--color-seafoam);
  color: var(--color-foam);
  background: var(--surface-control-hover);
  /* The same soft seafoam halo the composer uses on focus-within, so
     the selected segment and the focused input share one language. */
  box-shadow: 0 0 0 2px rgba(127, 190, 176, 0.15);
}

.theme-picker__icon {
  font-size: 19px;
  line-height: 1;
}

.theme-picker__label {
  font-size: 11.5px;
  display: flex;
  gap: 4px;
  align-items: baseline;
}

.theme-picker__sep {
  opacity: 0.5;
}

/* --------------------------------------------------------------------- */
/* Picker sound toggle (transparent icon-only)                            */
/* --------------------------------------------------------------------- */

.picker__sound-toggle {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  background: var(--surface-control);
  backdrop-filter: blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
  border: 1px solid var(--circle-rim);
  border-radius: 50%;
  color: var(--color-foam-dim);
  box-shadow: var(--glass-specular-soft), var(--shadow-soft);
  cursor: pointer;
  transition:
    color 0.2s var(--ease-tide),
    background 0.2s var(--ease-tide),
    border-color 0.2s var(--ease-tide),
    box-shadow 0.2s var(--ease-tide);
}

/* Expand the invisible tap target to the recommended 44px while keeping
   the compact 32px visual footprint (12px total, 6px per side), matching
   the other compact icon buttons (.menu__trigger, .breathe-trigger,
   .composer__send). The attention ring owns ::after, so the hit area
   lives on ::before. */
.picker__sound-toggle::before {
  content: '';
  position: absolute;
  inset: -6px;
  border-radius: 50%;
}

.picker__sound-toggle:hover {
  color: var(--color-foam);
  background: var(--surface-control-hover);
}

.picker__sound-toggle[aria-pressed='true'] {
  color: var(--color-foam);
  background: var(--surface-control-hover);
  box-shadow: 0 0 0 3px rgba(127, 190, 176, 0.12);
}

/* --------------------------------------------------------------------- */
/* App shell */
/* --------------------------------------------------------------------- */

.app {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  height: 100vh; /* fallback for browsers without dvh support */
  height: 100dvh;
  max-width: 720px;
  margin-inline: auto;
}

/*
 * A soft fade-and-rise instead of an abrupt pop whenever the picker or
 * the chat screen becomes visible, on first load and every time
 * "New chat" swaps between them. CSS animations restart automatically
 * when an element goes from `display: none` to visible, so this needs
 * no JS trigger of its own.
 */
@keyframes reveal {
  from {
    opacity: 0;
    transform: translateY(8px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.picker:not([hidden]),
.app:not([hidden]) {
  animation: reveal 0.45s var(--ease-tide) both;
}

@media (prefers-reduced-motion: reduce) {
  .picker:not([hidden]),
  .app:not([hidden]) {
    animation: none;
  }
}

/* --------------------------------------------------------------------- */
/* Header with title + menu (two controls only).                         */
/* No background, border, or blur: sits directly on the ambient gradient. */
/* --------------------------------------------------------------------- */

.header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Clear the device notch/status bar in standalone PWA mode. */
  padding-top: max(var(--space-4), env(safe-area-inset-top, 0px));
  padding-bottom: var(--space-2);
  padding-inline: var(--space-4);
}

.header__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 24px;
  color: var(--color-coral);
  letter-spacing: 0.5px;
  line-height: 1;
  font-weight: 700;
  user-select: none;
}

/* --------------------------------------------------------------------- */
/* Menu: a single icon trigger revealing New chat / export actions.      */
/* --------------------------------------------------------------------- */

.menu {
  position: relative;
}

.menu__trigger {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 34px;
  min-inline-size: 34px;
  max-inline-size: 34px;
  block-size: 34px;
  min-block-size: 34px;
  max-block-size: 34px;
  aspect-ratio: 1;
  padding: 0;
  appearance: none;
  background: var(--surface-control);
  background-clip: padding-box;
  backdrop-filter: blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
  border: 1px solid var(--circle-rim);
  color: var(--color-foam-dim);
  border-radius: 50%;
  box-shadow: var(--glass-specular-soft), var(--shadow-soft);
  cursor: pointer;
  line-height: 0;
  transition:
    border-color 0.2s var(--ease-tide),
    color 0.2s var(--ease-tide),
    background 0.2s var(--ease-tide),
    box-shadow 0.2s var(--ease-tide);
}

/* Expand the invisible tap target to the recommended 44px while keeping
   the compact 34px visual footprint (10px total, 5px per side). */
.menu__trigger::after {
  content: '';
  position: absolute;
  inset: -5px;
  border-radius: 50%;
}

.menu__trigger:hover,
.menu__trigger[aria-expanded='true'] {
  color: var(--color-foam);
  border-color: var(--color-seafoam);
  /* Brighten the glass body instead of swapping in a transparent tint,
     which used to drop the frost and let the dark backdrop flood in. */
  background: var(--surface-control-hover);
}

.menu__popover {
  position: absolute;
  top: calc(100% + 8px);
  inset-inline-end: 0;
  max-width: calc(100vw - 2 * var(--space-3));
  min-width: 216px;
  background: var(--surface-panel);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--glass-specular), var(--shadow-soft);
  padding: var(--space-2);
  z-index: 10;
  animation: menu-in 0.22s var(--ease-tide) both;
}

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

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.menu__item {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: transparent;
  border: none;
  color: var(--color-foam);
  font-family: var(--font-body);
  font-size: 13.5px;
  text-align: start;
  padding: 10px 12px;
  min-height: 44px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background 0.2s var(--ease-tide);
}

/* The label span holds the variable-width text. A flex item will not
   shrink below its content size unless min-width is 0, and ellipsis
   only applies to the span itself (never to a flex container), so the
   trio lives here, not on .menu__item. flex: 1 lets long labels fill
   the row; min-width: 0 lets them shrink; then the ellipsis trio clips
   overflow cleanly instead of pushing the popover wider. */
.menu__item-label {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.menu__item:hover {
  background: var(--glass-frost-strong);
}

.menu__item-icon {
  flex-shrink: 0;
  width: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-seafoam);
  font-size: 15px;
  line-height: 1;
}

/* With the separator lines removed, a small gap between items keeps each
   hover/active highlight from touching its neighbor while leaving the
   trigger's own padding as the popover's safe top and bottom margin. */
.menu__item + .menu__item {
  margin-block-start: 2px;
}

/* --------------------------------------------------------------------- */
/* Chat area */
/* --------------------------------------------------------------------- */

.chat {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-2) var(--space-3) var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  scroll-behavior: smooth;
  overscroll-behavior: contain;
}

/* The chat scrollbar is quiet at rest. On touch devices (phones, the
   Android app) it is hidden entirely, matching chat app convention:
   the jump-to-latest pill carries the where-am-I affordance, and the
   track would only crowd the narrow viewport. On pointer devices it is
   invisible until the reader actually scrolls and hides again after a
   short pause; the .chat--scrolling modifier is toggled by
   js/app/scrollbar.js from wheel, touch, and scroll-key input only,
   so the app's own autoscroll while following the live edge never
   flashes the bar. Standard properties cover Firefox and Chromium
   121+; the webkit pseudo rules cover Safari and older Chromium with
   the same look. */
@media (pointer: coarse) {
  .chat {
    scrollbar-width: none;
  }

  .chat::-webkit-scrollbar {
    width: 0;
    height: 0;
  }
}

@media (pointer: fine) {
  .chat {
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
  }

  .chat::-webkit-scrollbar {
    width: 6px;
  }

  .chat::-webkit-scrollbar-track {
    background: transparent;
  }

  .chat::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: var(--radius-full);
  }

  .chat--scrolling {
    scrollbar-color: var(--color-tide-soft) transparent;
  }

  .chat--scrolling::-webkit-scrollbar-thumb {
    background: var(--color-tide-soft);
  }

  html[data-theme='beach'] .chat--scrolling {
    scrollbar-color: rgba(31, 84, 73, 0.5) transparent;
  }

  html[data-theme='beach'] .chat--scrolling::-webkit-scrollbar-thumb {
    background: rgba(31, 84, 73, 0.5);
  }
}

/* Jump-to-latest affordance: a floating glass pill pinned to the bottom
   of the chat that appears only when the reader has scrolled away from
   the newest message, and smooth-scrolls back down on tap. It lives
   inside the scroll container as a sticky row so it never overlaps the
   composer and never reserves a scroll gap when hidden (opacity +
   visibility collapse it). */
.chat-jump {
  position: sticky;
  bottom: var(--space-3);
  align-self: flex-end;
  margin-inline-end: var(--space-3);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 8px 14px;
  min-height: 40px;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-full);
  color: var(--color-foam);
  background: var(--surface-control);
  backdrop-filter: blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
  box-shadow: var(--glass-specular-soft), var(--shadow-soft);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px) scale(0.96);
  pointer-events: none;
  transition:
    opacity 0.25s var(--ease-tide),
    transform 0.25s var(--ease-tide),
    visibility 0.25s var(--ease-tide),
    background 0.2s var(--ease-tide),
    border-color 0.2s var(--ease-tide);
}

.chat-jump:hover {
  background: var(--surface-control-hover);
  border-color: var(--color-seafoam);
}

.chat-jump--show {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}

.chat-jump svg {
  flex-shrink: 0;
}

html[data-theme='beach'] .chat-jump {
  color: #0f2e3a;
  background: var(--glass-light);
  border-color: var(--glass-light-edge);
  box-shadow:
    var(--glass-light-specular),
    0 2px 10px rgba(91, 61, 35, 0.08);
}

html[data-theme='beach'] .chat-jump:hover {
  background: var(--glass-light-strong);
}

.bubble-row {
  display: flex;
  animation: rise-in 0.3s var(--ease-tide) both;
}

/*
 * NOTE on bubble sides: the user's own messages stay on the physical
 * RIGHT and Darya's on the physical LEFT in *both* languages, matching
 * how virtually every chat app behaves (own messages on the right)
 * regardless of the interface's text direction. Because `flex-start` /
 * `flex-end` resolve to opposite physical sides depending on `dir`, the
 * two rules below are intentionally flipped between RTL and LTR to keep
 * the same physical result; this is a deliberate exception to "always
 * use logical properties", not an oversight.
 */
html[dir='rtl'] .bubble-row--user {
  justify-content: flex-start;
} /* right in RTL */
html[dir='rtl'] .bubble-row--bot {
  justify-content: flex-end;
} /* left in RTL */
html[dir='ltr'] .bubble-row--user {
  justify-content: flex-end;
} /* right in LTR */
html[dir='ltr'] .bubble-row--bot {
  justify-content: flex-start;
} /* left in LTR */

@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/*
 * The bubble + timestamp are wrapped in `.bubble-wrap`, which is the
 * actual flex item inside `.bubble-row`. The width constraint lives here
 * (not on the nested `.bubble`) deliberately: a percentage max-width on a
 * *grandchild* is ignored when the browser computes the flex item's
 * intrinsic shrink-to-fit size, which would let long messages balloon out
 * to fill the entire row and make `justify-content` look like it has no
 * effect. Putting the constraint directly on the flex item resolves the
 * percentage against the row's own (definite) width correctly, for
 * messages of any length.
 */
.bubble-wrap {
  display: flex;
  flex-direction: column;
  max-width: 84%;
  min-width: 0;
}

.bubble {
  padding: 10px 14px;
  font-size: 14.5px;
  line-height: 1.85;
  box-shadow: var(--shadow-soft);
  overflow-wrap: break-word;
  /* Numbered lists (movie/game picks) are newline-separated in the reply
     text; preserve those line breaks instead of collapsing them. */
  white-space: pre-wrap;
}

.bubble--user {
  background: var(--user-bubble-sheen), var(--color-seafoam);
  color: var(--color-abyss-deep);
  border-radius: var(--radius-md) var(--radius-md) 8px var(--radius-md);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    var(--shadow-soft);
}

.bubble--bot {
  background: var(--bot-bubble-sheen), var(--bubble-frost);
  color: var(--text-primary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md) var(--radius-md) var(--radius-md) 8px;
  position: relative;
  box-shadow: var(--glass-specular-soft), var(--shadow-soft);
  animation: rise-in 0.3s var(--ease-tide) both;
}

/* Soft entrance glow on Darya's bubbles. A pseudo-element ring whose opacity
   animates (GPU-composited) replaces the old box-shadow animation, which
   repainted the bubble shadow every frame during the 1.2s entrance. */
.bubble--bot::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 0 0 12px rgba(127, 190, 176, 0);
  animation: ripple-glow 1.2s var(--ease-tide) both;
  pointer-events: none;
}

@keyframes ripple-glow {
  0% {
    opacity: 0.4;
  }

  100% {
    opacity: 0;
  }
}

.bubble-meta {
  font-size: 10.5px;
  color: var(--color-foam-dim);
  margin-top: 3px;
  padding-inline: 4px;
}

html[data-theme='beach'] .bubble-meta {
  color: var(--color-on-sky-dim);
}

/* Meta text follows the same intentional physical-side convention as the
   bubbles themselves (see note above). */
html[dir='rtl'] .bubble-row--user .bubble-meta {
  text-align: right;
}

html[dir='rtl'] .bubble-row--bot .bubble-meta {
  text-align: left;
}

html[dir='ltr'] .bubble-row--user .bubble-meta {
  text-align: right;
}

html[dir='ltr'] .bubble-row--bot .bubble-meta {
  text-align: left;
}

/* --------------------------------------------------------------------- */
/* Quick-reply chips: tappable suggestions under a bot message (exercise  */
/* yes/no answers, mood scale ratings). Real buttons with 44px hit       */
/* targets, keyboard operability inherited from the button element, and  */
/* a soft press micro-interaction. Chips sit on the bot side of the      */
/* chat (same physical side as Darya's bubbles, see the bubble-side      */
/* note above) and wrap onto multiple lines when the mood scale renders  */
/* ten options.                                                          */
/* --------------------------------------------------------------------- */

.quick-replies {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  max-width: 84%;
  /* margin-right is physical, so it pins chips to the physical left in   */
  /* both languages, matching the bot bubbles' side convention above.    */
  margin-right: auto;
  padding-inline-start: 2px;
  animation: rise-in 0.3s var(--ease-tide) both;
}

.quick-reply {
  min-height: 44px;
  padding: 8px 16px;
  font-family: var(--font-body);
  font-size: 13.5px;
  color: var(--color-foam);
  background: var(--surface-control);
  backdrop-filter: blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-full);
  box-shadow: var(--glass-specular-soft);
  cursor: pointer;
  transition:
    border-color 0.2s var(--ease-tide),
    background 0.2s var(--ease-tide),
    color 0.2s var(--ease-tide),
    transform 0.15s var(--ease-tide);
}

.quick-reply:hover {
  border-color: var(--color-seafoam);
  background: var(--surface-control-hover);
  color: var(--color-foam);
}

.quick-reply:active {
  transform: scale(0.96);
}

html[data-theme='beach'] .quick-reply {
  color: #f0f7f4;
}

@media (prefers-reduced-motion: reduce) {
  .quick-replies {
    animation: none;
  }

  .quick-reply:active {
    transform: none;
  }
}

/* Typing indicator. The row is a flex line aligned to Darya's own side
   (the physical left in both languages, matching .bubble-row--bot) so the
   dots never appear on the user's side. Without an explicit alignment the
   inline bubble follows the page direction and, in Persian RTL, jumped to
   the physical right, which read as a flicker on the wrong side of the
   chat. */
.typing-row {
  display: flex;
  padding-inline: var(--space-3);
  padding-bottom: var(--space-1);
  flex-shrink: 0;
}

html[dir='rtl'] .typing-row {
  justify-content: flex-end; /* physical left in RTL */
}

html[dir='ltr'] .typing-row {
  justify-content: flex-start; /* physical left in LTR */
}

.bubble--typing {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 11px 16px;
  animation: rise-in 0.18s var(--ease-tide) both;
}

@media (prefers-reduced-motion: reduce) {
  .bubble--typing {
    animation: none;
  }
}

.ripple-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-seafoam);
  animation: ripple 1.1s var(--ease-tide) infinite;
}

.ripple-dot:nth-child(2) {
  animation-delay: 0.13s;
}

.ripple-dot:nth-child(3) {
  animation-delay: 0.26s;
}

@keyframes ripple {
  0%,
  60%,
  100% {
    transform: translateY(0);
    opacity: 0.5;
  }

  30% {
    transform: translateY(-5px);
    opacity: 1;
  }
}

/* --------------------------------------------------------------------- */
/* Validation hint (e.g. foreign-script input) */
/* --------------------------------------------------------------------- */

.input-hint {
  flex-shrink: 0;
  margin-inline: var(--space-3);
  padding: var(--space-2) var(--space-3);
  font-size: 12.5px;
  color: var(--color-warning);
  background: rgba(240, 185, 134, 0.12);
  border: 1px solid rgba(240, 185, 134, 0.22);
  border-radius: var(--radius-sm);
  text-align: center;
}

/* --------------------------------------------------------------------- */
/* Composer: moderately rounded rectangle, not a full pill.              */
/* --------------------------------------------------------------------- */

.composer {
  flex-shrink: 0;
  display: flex;
  /* Center the send button against the textarea: the input auto-grows
     (its height tracks scrollHeight), so bottom alignment would leave the
     button pinned to the lower edge once the text wraps to several lines. */
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
  background: var(--surface-control);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid var(--border-input);
  border-radius: var(--radius-lg);
  margin: var(--space-2) var(--space-3);
  box-shadow: var(--glass-specular), var(--shadow-soft);
  transition:
    border-color 0.2s var(--ease-tide),
    box-shadow 0.2s var(--ease-tide);
}

.composer:focus-within {
  border-color: var(--color-seafoam);
  box-shadow: 0 0 0 2px rgba(127, 190, 176, 0.15);
}

.composer__input {
  flex: 1;
  resize: none;
  appearance: none;
  overflow-y: hidden;
  scrollbar-width: none;
  /* The input is the composer's primary touch target: line-height 1.7
     at 16px plus 8px vertical padding lands at 43.2px, a whisker under
     the 44px target-size guideline. A min-height floor keeps the resting
     hit area compliant without changing the single-line look. */
  min-height: 44px;
  max-height: 140px;
  background: transparent;
  border: none;
  box-shadow: none;
  color: var(--color-foam);
  font-family: var(--font-body);
  /* 16px minimum prevents iOS Safari from auto-zooming on focus. */
  font-size: 16px;
  line-height: 1.7;
  padding: 8px 4px;
}

/* The textarea remains wheel, touch, and keyboard scrollable after it reaches
   its height cap, but its native RTL scrollbar is hidden. Chromium places
   that scrollbar on the inline-start edge, where it otherwise looks like a
   stray dark vertical border inside the composer. */
.composer__input::-webkit-scrollbar {
  width: 0;
  height: 0;
}

.composer__input::placeholder {
  color: var(--color-foam-dim);
}

.composer__input:focus {
  outline: none;
}

.composer__send {
  flex-shrink: 0;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  /* Transparent border reserves the ring so the disabled state can swap
     in a hairline without a 2px layout shift. */
  border: 1px solid transparent;
  background: var(--color-coral);
  color: var(--color-abyss-deep);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition:
    transform 0.2s var(--ease-tide),
    background 0.2s var(--ease-tide),
    border-color 0.2s var(--ease-tide),
    box-shadow 0.2s var(--ease-tide);
}

/* Pulse effect when the send button is enabled and has text. The ring is a
   pseudo-element whose opacity and scale animate, both GPU-composited
   properties, instead of animating box-shadow (which repaints every frame). */
.composer__send {
  position: relative;
}

/* Expand the send button's invisible tap target to 44px (38px visual
   + 6px, 3px per side). The pulse ring owns ::after, so the hit area
   lives on ::before. */
.composer__send::before {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: 50%;
}

.composer__send:not(:disabled)::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: 0 0 0 6px rgba(232, 135, 108, 0.25);
  animation: send-pulse 2.4s var(--ease-tide) infinite;
  pointer-events: none;
}

@keyframes send-pulse {
  0%,
  100% {
    opacity: 0;
    transform: scale(1);
  }

  50% {
    opacity: 1;
    transform: scale(1.15);
  }
}

/* The send arrow points toward each language's own "forward" (reading-end)
   direction: left in RTL, right (its natural, unflipped orientation) in LTR. */
html[dir='rtl'] .composer__send svg {
  transform: scaleX(-1);
}

.composer__send:hover:not(:disabled) {
  background: var(--color-coral-soft);
}

.composer__send:active:not(:disabled) {
  transform: scale(0.97);
}

.composer__send:disabled {
  /* Muted frosted glass instead of a dimmed coral blob: a coral disc at
     low opacity over the dark composer read as a muddy dark smudge, so
     the idle state reuses the same glass circle the other icon buttons
     share, with a dimmed arrow to signal it is not yet available. */
  background: var(--surface-control);
  color: var(--color-foam-dim);
  border-color: var(--circle-rim);
  box-shadow: var(--glass-specular-soft);
  cursor: not-allowed;
}

.disclaimer {
  flex-shrink: 0;
  text-align: center;
  font-size: 11px;
  color: var(--color-foam-dim);
  padding-inline: var(--space-4);
  padding-block: var(--space-2);
  padding-bottom: max(var(--space-3), env(safe-area-inset-bottom));
  line-height: 1.7;
}

/* --------------------------------------------------------------------- */
/* Breathing exercise overlay                                           */
/* 4-7-8 pattern (Relaxing Breath, Dr. Andrew Weil): inhale 4s, hold   */
/* 7s, exhale 8s. The circle animates with CSS transitions triggered    */
/* by JS-added classes, while a countdown timer shows remaining seconds */
/* per phase. The 4s value below is a pre-JS fallback; JS dynamically   */
/* overrides it with the correct per-phase duration via inline style.   */
/* --------------------------------------------------------------------- */

.breathe-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: rgba(4, 15, 20, 0.85);
  animation: reveal 0.35s var(--ease-tide) both;
  /* Only the close button is interactive; the backdrop and the exercise
     itself must not advertise a click with a pointer cursor. */
  cursor: default;
  backdrop-filter: blur(4px);
}

html[data-theme='beach'] .breathe-overlay {
  background: rgba(15, 46, 58, 0.82);
}

.breathe-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
}

.breathe-circle {
  position: relative;
  width: 140px;
  height: 140px;
  border-radius: 50%;
  background: radial-gradient(
    circle at 38% 32%,
    rgba(127, 190, 176, 0.55),
    rgba(127, 190, 176, 0.18)
  );
  box-shadow:
    0 0 30px rgba(127, 190, 176, 0.15),
    inset 0 0 20px rgba(127, 190, 176, 0.06);
  transition:
    transform 4s var(--ease-tide),
    box-shadow 4s var(--ease-tide);
}

/* A soft specular glint at the top-left reads as light reflecting off a
   rounded glass surface, without adding motion of its own. */
.breathe-circle::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(
    circle at 34% 28%,
    rgba(255, 255, 255, 0.28),
    rgba(255, 255, 255, 0) 46%
  );
  pointer-events: none;
}

/* The glow breathes with the phase: it brightens as the circle grows on
   the inhale and settles back as it shrinks on the exhale, so the light
   itself carries the rhythm rather than a separate animation. */
.breathe-circle--grow {
  transform: scale(1.2);
  box-shadow:
    0 0 52px rgba(127, 190, 176, 0.32),
    inset 0 0 28px rgba(127, 190, 176, 0.1);
}

.breathe-circle--shrink {
  transform: scale(1);
  box-shadow:
    0 0 30px rgba(127, 190, 176, 0.15),
    inset 0 0 20px rgba(127, 190, 176, 0.06);
}

.breathe-label {
  font-size: 16px;
  color: var(--color-foam);
  font-family: var(--font-body);
  text-align: center;
  user-select: none;
  letter-spacing: 0.3px;
}

html[data-theme='beach'] .breathe-label {
  color: #eaf3ef;
}

.breathe-countdown {
  font-size: 36px;
  font-weight: 400;
  color: var(--color-seafoam);
  font-family: var(--font-body);
  text-align: center;
  user-select: none;
  line-height: 1;
  transition:
    opacity 0.25s ease,
    transform 0.25s cubic-bezier(0.37, 0, 0.24, 1);
}

html[data-theme='beach'] .breathe-countdown {
  color: #7ac4a8;
}

.breathe-close {
  padding: 8px 20px;
  font-size: 13px;
  font-family: var(--font-body);
  background: var(--surface-control-hover);
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  border-radius: var(--radius-md);
  cursor: pointer;
  min-height: 44px;
  box-shadow: var(--glass-specular-soft);
  transition:
    border-color 0.15s var(--ease-tide),
    color 0.15s var(--ease-tide);
}

.breathe-close:hover {
  border-color: var(--color-seafoam);
  color: var(--color-foam);
}

@media (prefers-reduced-motion: reduce) {
  .breathe-circle {
    transition: none !important;
  }

  .breathe-countdown {
    transition: none !important;
  }
}

/* --------------------------------------------------------------------- */
/* Exit confirmation bar: appears above the composer when the user       */
/* first says goodbye. Two buttons (Yes/No) let them confirm or cancel   */
/* the farewell without having to type.                                  */
/* --------------------------------------------------------------------- */

.exit-confirm-bar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-inline: var(--space-3);
  margin-bottom: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-panel);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--glass-specular), var(--shadow-soft);
  animation: reveal 0.25s var(--ease-tide) both;
}

.exit-confirm-bar__label {
  font-size: 13px;
  color: var(--text-primary);
  line-height: 1.5;
  flex: 1;
}

.exit-confirm-bar__actions {
  display: flex;
  gap: var(--space-2);
  flex-shrink: 0;
}

.exit-confirm-bar__btn {
  padding: 6px 14px;
  font-size: 12.5px;
  font-family: var(--font-body);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  cursor: pointer;
  min-height: 44px;
  min-width: 64px;
  line-height: 1;
  transition:
    border-color 0.15s var(--ease-tide),
    background 0.15s var(--ease-tide),
    color 0.15s var(--ease-tide);
}

.exit-confirm-bar__btn--yes {
  background: var(--color-coral);
  color: var(--color-abyss-deep);
  border-color: var(--color-coral);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

.exit-confirm-bar__btn--yes:hover {
  background: var(--color-coral-soft);
  border-color: var(--color-coral-soft);
}

.exit-confirm-bar__btn--no {
  background: var(--surface-control-hover);
  color: var(--text-secondary);
  border-color: var(--border-subtle);
}

.exit-confirm-bar__btn--no:hover {
  color: var(--text-primary);
  border-color: var(--color-seafoam);
}

html[data-theme='beach'] .exit-confirm-bar__label {
  color: #f0f7f4;
}

html[data-theme='beach'] .exit-confirm-bar__btn--yes {
  background: #28645a;
  border-color: #28645a;
  color: #f0f7f4;
}

html[data-theme='beach'] .exit-confirm-bar__btn--yes:hover {
  background: #347a6d;
  border-color: #347a6d;
}

html[data-theme='beach'] .exit-confirm-bar__btn--no {
  color: rgba(240, 247, 244, 0.65);
}

html[data-theme='beach'] .exit-confirm-bar__btn--no:hover {
  color: #f0f7f4;
  border-color: #6cc4a8;
}

@media (prefers-reduced-motion: reduce) {
  .exit-confirm-bar {
    animation: none;
  }
}

/* --------------------------------------------------------------------- */
/* Breakpoints: add breathing room as the viewport grows (mobile-first)  */
/* --------------------------------------------------------------------- */

@media (width >= 600px) {
  .header {
    padding-top: max(var(--space-5), env(safe-area-inset-top, 0px));
    padding-bottom: var(--space-3);
    padding-inline: var(--space-5);
  }

  .header__title {
    font-size: 26px;
  }

  .chat {
    padding: var(--space-3) var(--space-5) var(--space-2);
    gap: var(--space-4);
  }

  .bubble-wrap {
    max-width: 74%;
  }

  .bubble {
    padding: 12px 16px;
    font-size: 15px;
  }

  .composer {
    margin: var(--space-3) var(--space-5);
    padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
  }

  .disclaimer {
    padding-inline: var(--space-6);
    padding-bottom: max(var(--space-4), env(safe-area-inset-bottom, 0px));
    font-size: 11.5px;
  }

  .picker__intro {
    font-size: 24px;
  }
}

@media (width >= 900px) {
  .app {
    padding-inline: var(--space-4);
  }
}

/* --------------------------------------------------------------------- */
/* New chat confirmation overlay: modal dialog that warns the user about */
/* permanent data loss before resetting the conversation. Styled as a    */
/* clean centered overlay (inspired by the breathe exercise pattern)      */
/* with a brief warning, and two clearly-labeled action buttons.         */
/* --------------------------------------------------------------------- */

.confirm-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: rgba(4, 15, 20, 0.85);
  animation: reveal 0.3s var(--ease-tide) both;
  /* The backdrop may still cancel the dialog, but the two action buttons
     are the interactive affordance; the dimmed backdrop must not show a
     pointer cursor everywhere. */
  cursor: default;
  backdrop-filter: blur(4px);
}

html[data-theme='beach'] .confirm-overlay {
  background: rgba(15, 46, 58, 0.82);
}

.confirm-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-6);
  background: var(--surface-panel);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  max-width: 360px;
  width: 90%;
  cursor: default;
  box-shadow: var(--glass-specular), var(--shadow-elevated);
}

.confirm-title {
  font-size: 17px;
  font-weight: 600;
  color: var(--color-warning);
  text-align: center;
  font-family: var(--font-body);
  margin: 0;
}

/* Beach warning token is tuned for light sand surfaces; the confirm
   dialog sits on a dark teal panel, so it uses the luminous amber. */
html[data-theme='beach'] .confirm-title {
  color: #f2b987;
}

.confirm-desc {
  font-size: 13.5px;
  line-height: 1.7;
  color: var(--text-primary);
  text-align: center;
  margin: 0;
  font-family: var(--font-body);
}

.confirm-actions {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

.confirm-btn {
  padding: 8px 20px;
  font-size: 13px;
  font-family: var(--font-body);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  cursor: pointer;
  min-height: 44px;
  min-width: 80px;
  line-height: 1;
  transition:
    border-color 0.15s var(--ease-tide),
    background 0.15s var(--ease-tide),
    color 0.15s var(--ease-tide);
}

.confirm-btn--yes {
  background: var(--color-coral);
  color: var(--color-abyss-deep);
  border-color: var(--color-coral);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

.confirm-btn--yes:hover {
  background: var(--color-coral-soft);
  border-color: var(--color-coral-soft);
}

.confirm-btn--no {
  background: var(--surface-control-hover);
  color: var(--text-secondary);
  border-color: var(--border-subtle);
}

.confirm-btn--no:hover {
  color: var(--text-primary);
  border-color: var(--color-seafoam);
}

html[data-theme='beach'] .confirm-btn--yes {
  background: #28645a;
  border-color: #28645a;
  color: #f0f7f4;
}

html[data-theme='beach'] .confirm-btn--yes:hover {
  background: #347a6d;
  border-color: #347a6d;
}

html[data-theme='beach'] .confirm-btn--no {
  color: rgba(240, 247, 244, 0.65);
}

html[data-theme='beach'] .confirm-btn--no:hover {
  color: #f0f7f4;
  border-color: #6cc4a8;
}

@media (prefers-reduced-motion: reduce) {
  .confirm-overlay {
    animation: none;
  }
}

/* --------------------------------------------------------------------- */
/* Notification overlay: a centered toast-style banner that appears over  */
/* the app to surface error messages and warnings. Follows the same       */
/* overlay pattern as breathe/confirm dialogs but is lighter-weight:      */
/* auto-dismisses after a configurable duration, supports info/warn/error */
/* severity levels, and includes an optional close button.                */
/* --------------------------------------------------------------------- */

.notification-overlay {
  position: fixed;
  inset: 0;
  z-index: 110;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  animation: notification-in 0.28s var(--ease-tide) both;
}

/* The badge blooms in gently at the center of the viewport: a short
   scale plus fade over GPU-only opacity/transform. Disabled under
   prefers-reduced-motion below. */
@keyframes notification-in {
  from {
    opacity: 0;
    transform: scale(0.82);
  }

  to {
    opacity: 1;
    transform: scale(1);
  }
}

.notification-container {
  /* Default accent (info teal) so the badge never renders colorless
     even if a severity modifier is missing. Custom properties lead the
     block per the project's stylelint rules. */
  --notification-accent: #94d0c1;

  pointer-events: auto;
  width: 76px;
  height: 76px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-panel);
  border: 1px solid var(--notification-accent);
  box-shadow:
    var(--glass-specular),
    0 0 0 1px rgba(0, 0, 0, 0.12),
    0 0 20px var(--notification-accent),
    var(--shadow-elevated);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
}

.notification-container--error {
  --notification-accent: #e2805f;

  border-color: #e2805f;
}

.notification-container--warn {
  --notification-accent: #f2b987;

  border-color: #f2b987;
}

.notification-container--info {
  --notification-accent: #94d0c1;

  border-color: #94d0c1;
}

/* Severity icon: the entire visual of the badge. It inherits the
   luminous severity accent via currentColor and is marked aria-hidden
   in the markup (the overlay's aria-label carries the message to
   assistive tech). */
.notification-icon {
  width: 34px;
  height: 34px;
  color: var(--notification-accent);
}

/* Beach theme override for the notification badge. The badge shares the
   same glass surface token as the ocean badge, so it reads as one
   material across themes; the beach frost is near-opaque, which keeps
   the luminous severity glow exact over the bright sand. */
html[data-theme='beach'] .notification-container {
  background: var(--surface-panel);
}

html[data-theme='beach'] .notification-container--error {
  border-color: #e2805f;
}

html[data-theme='beach'] .notification-container--warn {
  border-color: #f2b987;
}

html[data-theme='beach'] .notification-container--info {
  border-color: #94d0c1;
}

@media (prefers-reduced-motion: reduce) {
  .notification-overlay {
    animation: none;
  }
}

/* --------------------------------------------------------------------- */
/* Glass fallbacks: the frosted panes must stay readable even without     */
/* the blur that normally does the separation work.                       */
/* --------------------------------------------------------------------- */

/* Legacy engines without backdrop-filter get a near-opaque frost so text
   keeps its contrast; the pane still reads as tinted glass via the rim,
   sheen, and shadow. */
@supports not (
  (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))
) {
  :root {
    --glass-frost: rgba(20, 63, 72, 0.94);
    --glass-frost-strong: rgba(24, 74, 84, 0.97);
  }

  html[data-theme='beach'] {
    --glass-frost: rgba(31, 84, 73, 0.97);
    --glass-frost-strong: rgba(40, 100, 90, 0.99);
  }
}

/* Users who ask the OS to reduce transparency get the same near-opaque
   treatment, honouring the preference instead of forcing frosted layers. */
@media (prefers-reduced-transparency: reduce) {
  :root {
    --glass-frost: rgba(20, 63, 72, 0.95);
    --glass-frost-strong: rgba(24, 74, 84, 0.98);
  }

  html[data-theme='beach'] {
    --glass-frost: rgba(31, 84, 73, 0.98);
    --glass-frost-strong: rgba(40, 100, 90, 1);
  }
}

/* Increased contrast mirrors Apple's "Increase Contrast" material
   modifier: the pane goes opaque and the rim brightens to a clearly
   visible edge, keeping the glass readable without the translucency. */
@media (prefers-contrast: more) {
  :root {
    --glass-frost: rgba(20, 63, 72, 0.97);
    --glass-frost-strong: rgba(24, 74, 84, 1);
    --glass-edge: rgba(214, 244, 250, 0.7);
  }

  html[data-theme='beach'] {
    --glass-frost: rgba(31, 84, 73, 0.99);
    --glass-frost-strong: rgba(40, 100, 90, 1);
    --glass-edge: rgba(255, 255, 255, 0.7);
  }
}

/* --------------------------------------------------------------------- */
/* Reduced motion: honor user preference by disabling ambient animation  */
/* --------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .backdrop__glow,
  .backdrop__horizon,
  .backdrop__scrim,
  .beach-scene__ocean,
  .beach-scene__wave,
  .bubble-particle,
  .ocean-particle,
  .ocean-caustic,
  .ocean-current,
  .bird-shadow,
  .breathe-trigger,
  .breathe-trigger svg,
  .picker__sound-toggle,
  .ripple-dot,
  .bubble-row,
  .bubble--bot,
  .bubble--bot::after,
  .menu__popover,
  .menu__trigger,
  .picker__option,
  .theme-picker__option,
  .composer__send,
  .composer__send:not(:disabled)::after,
  .breathe-overlay,
  .breathe-close,
  .menu__item,
  .sound-toggle__wave,
  .sound-toggle__slash,
  .chat-jump,
  .composer,
  .exit-confirm-bar__btn,
  .confirm-btn {
    animation: none !important;
    transition: none !important;
  }

  /* The chat auto-scroll and jump-to-latest settle instantly instead of
     animating, so reduced-motion users are never scrolled through. */
  .chat {
    scroll-behavior: auto;
  }
}

/* --------------------------------------------------------------------- */
/* Inactive-theme ambient layers keep animating even when hidden, which   */
/* wastes GPU work on every frame. Pause their animations when the theme  */
/* they belong to is not active. They resume in place on theme switch,    */
/* so no visual jump occurs.                                             */
/* --------------------------------------------------------------------- */

/* Ocean-theme elements hidden in beach theme. */
html[data-theme='beach'] .backdrop__glow,
html[data-theme='beach'] .backdrop__horizon,
html[data-theme='beach'] .bubble-particle,
html[data-theme='beach'] .ocean-particle,
html[data-theme='beach'] .ocean-caustic,
html[data-theme='beach'] .ocean-current {
  animation-play-state: paused;
}

/* Beach-theme elements hidden in ocean theme. */
html:not([data-theme='beach']) .beach-scene__ocean,
html:not([data-theme='beach']) .beach-scene__wave,
html:not([data-theme='beach']) .beach-scene__sun,
html:not([data-theme='beach']) .bird-shadow {
  animation-play-state: paused;
}

/* --------------------------------------------------------------------- */
/* Reduced motion: make theme crossfades instant (skip opacity/color     */
/* transitions). Only fires on user-initiated theme switches because the */
/* .no-theme-transition guard already prevents transitions on paint.    */
/* --------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html:not(.no-theme-transition) body {
    transition: none;
  }

  html:not(.no-theme-transition) .beach-scene,
  html:not(.no-theme-transition) .bubbles,
  html:not(.no-theme-transition) .ocean-particles,
  html:not(.no-theme-transition) .ocean-caustic,
  html:not(.no-theme-transition) .ocean-current,
  html:not(.no-theme-transition) .bird-shadows,
  html:not(.no-theme-transition) .menu__trigger,
  html:not(.no-theme-transition) .breathe-trigger {
    transition: none;
  }
}

/* ======================================================================
 * Beach theme: one calm, sunny full-viewport coastal view
 * ====================================================================== */

html[data-theme='beach'] {
  --color-on-sky: #0f2e3a;
  --color-on-sky-dim: #1c404e;
  --color-on-sky-accent: #7a3f2a;
  --color-on-sky-link: #054554;
  --text-on-bright: #0f2e3a;
  --border-focus: #054554;
}

html[data-theme='beach'] body {
  color: var(--color-on-sky);
  background: #f0dec0;
}

html[data-theme='beach'] .backdrop {
  background: #f0dec0;
}

html[data-theme='beach'] .backdrop__glow,
html[data-theme='beach'] .backdrop__horizon {
  opacity: 0;
}

.beach-scene {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  z-index: 0;
}

html[data-theme='beach'] .beach-scene {
  opacity: 1;
  visibility: visible;
}

.beach-scene__sky {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* The sky gradient transitions from sky blue at the top to cool ocean
     blue-gray at the bottom, matching the wave layers below. The bottom
     portion uses cool blue tones instead of warm beige ones to prevent
     beige/orange colors from bleeding through the transparent portions
     of the wave SVG tiles above. The stops darken monotonically toward
     the bottom: 0% is lightest, 100% is darkest. */
  background: linear-gradient(
    to bottom,
    #b3d6e0 0%,
    #bdd9df 22%,
    #bfd9d8 42%,
    #8dbacb 59%,
    #6faec8 70%,
    #559eb9 100%
  );
}

.beach-scene__sun {
  position: absolute;
  top: 8vh;
  right: 6vw;
  width: clamp(112px, 13vw, 190px);
  height: clamp(112px, 13vw, 190px);
  animation: sun-breathe 10s ease-in-out infinite;
}

.beach-scene__sun-halo,
.beach-scene__sun-core {
  position: absolute;
  inset: 0;
  border-radius: 50%;
}

.beach-scene__sun-halo {
  background: radial-gradient(
    circle,
    rgba(255, 239, 181, 0.48) 0%,
    rgba(255, 225, 151, 0.24) 38%,
    rgba(255, 220, 145, 0.08) 62%,
    transparent 76%
  );
  transform: scale(1.5);
  filter: blur(4px);
}

.beach-scene__sun-core {
  inset: 28%;
  background: radial-gradient(
    circle at 38% 32%,
    #fff5cf 0%,
    #f9dda0 54%,
    #edc777 100%
  );
  box-shadow: 0 0 20px rgba(246, 211, 139, 0.3);
}

@keyframes sun-breathe {
  0%,
  100% {
    transform: scale(1);
    opacity: 0.94;
  }

  50% {
    transform: scale(1.04);
    opacity: 1;
  }
}

/* Each ocean layer contains an inline SVG wave that tiles seamlessly.
   The SVG is 200% wide (two identical halves) and animates translateX
   by -50%, which moves it by exactly one identical half for a perfect
   loop. Only transform is animated for GPU compositing: no layout, no
   repaint, no jitter.

   Color progression follows real ocean optics: the horizon (far/deep water)
   appears as a dark, pure indigo-blue, while the near-shore (shallow water
   reflecting sunlight off the sandy bottom) is a bright cyan-blue. No white
   foam or green/yellow tints are used; each layer is a solid, distinct
   blue-based color. */
.beach-scene__ocean {
  position: absolute;
  left: 0;
  right: 0;
  width: 100%;
  height: 24%;
  min-height: 110px;
  overflow: hidden;
  opacity: 1;
}

/* On narrow phones the wave layers used to shrink into a thin, cramped
   stripe that read as tiny ripples with no presence. Give the ocean more
   vertical room so the three blue bands stay distinct and the wave shapes
   keep their amplitude on small screens. */
@media (width < 600px) {
  .beach-scene__ocean {
    height: 28%;
    min-height: 120px;
  }

  .beach-scene__ocean--far {
    top: 30%;
  }

  .beach-scene__ocean--mid {
    top: 38%;
  }

  .beach-scene__ocean--near {
    top: 46%;
  }
}

/* The drifting wave SVG. Each ocean layer contains an inline SVG that is
   200% wide (two identical halves). The SVG animates translateX(-50%),
   which moves it by exactly one identical half, creating a seamless loop.
   Only transform is animated for GPU compositing: no layout, no repaint,
   no jitter. */
.beach-scene__wave {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 200%;
  height: 100%;
  animation-name: beach-wave-drift;
  animation-duration: var(--wave-duration, 48s);
  animation-delay: var(--wave-delay, 0s);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  will-change: transform;
  pointer-events: none;
}

@media (width < 600px) {
  .beach-scene__wave {
    width: 800%; /* Prevents wave squeezing on mobile */
  }
}

@media (600px <= width < 1024px) {
  .beach-scene__wave {
    width: 400%; /* Prevents wave squeezing on tablet screens */
  }
}

.beach-scene__ocean--far .beach-scene__wave {
  animation-name: beach-wave-drift-reverse;
}

/* Layered wave tiles. Each SVG tile is a solid blue fill that starts at a
   wavy top edge and runs down to the tile bottom; the region ABOVE the wave
   line stays transparent so the layer behind it (the sky for the far layer,
   or the previous ocean layer) shows through. That transparency is what
   makes the wavy edge visible. A background-color is intentionally NOT set
   here: a flat fill behind the tile would paint over that transparent
   region, hiding the wave shape and making each layer read as a plain
   solid stripe (the exact flat-wave bug reported by users).

   Wave geometry (path data lives in index.html): each layer alternates
   crest and trough cubic segments. Within a layer every wave shares the
   same steepness ratio (amplitude / width; far and mid 0.025, near 0.0417)
   so every joint and the tile seam at x=0 / x=1200 stays C1-smooth with a
   constant baseline slope per layer (far/mid -0.1, near -0.1667); wider
   waves are taller, narrower waves are shorter. Amplitudes therefore   vary inside each layer (far 4-7, mid 6-9, near 10-15) for an organic,
   hand-drawn swell instead of a repeated identical wave. Do not change the
   widths or amplitudes independently or a corner will appear at the seams.
   Colors are pure blues only (no green, sand, or beige tones):
   - Far (horizon, deep water):  #226a85  dark indigo-blue
   - Mid (transition zone):      #4da0c2  medium pure blue
   - Near (shore, shallow):      #9cd4e8  light bright cyan-blue

   Each layer also gets its own negative animation-delay (--wave-delay) so
   the horizontal drift desynchronizes between layers: they never slide in
   perfect unison, which reads as more natural and alive. */
.beach-scene__ocean--far {
  top: 41%;

  --wave-duration: 72s;
  --wave-delay: 0s;
}

.beach-scene__ocean--mid {
  top: 47%;

  --wave-duration: 52s;
  --wave-delay: -6s;
}

.beach-scene__ocean--near {
  top: 53%;

  --wave-duration: 34s;
  --wave-delay: -12s;
}

/* Horizontal drift: each SVG wave is 200% wide (two identical halves).
   translateX(-50%) moves it by exactly one identical half, creating a
   seamless loop. Only transform is animated for GPU compositing: no
   layout, no repaint, no jitter. */
@keyframes beach-wave-drift {
  0% {
    transform: translateX(0);
  }

  100% {
    transform: translateX(-50%);
  }
}

/* Reverse drift for the far layer: it slides left to right while the
   mid and near layers roll right to left. Opposing motion between
   distant and near water is a classic parallax depth cue. */
@keyframes beach-wave-drift-reverse {
  0% {
    transform: translateX(-50%);
  }

  100% {
    transform: translateX(0);
  }
}

/* No vertical bob: a second animated transform on the layer container
   fought the SVG's horizontal drift and produced sub-pixel stepping
   (visible jitter) where the two nested layers composited. Each layer
   now animates exactly one transform on exactly one element (the SVG
   below), which composites on the GPU as a single layer. The organic,
   non-uniform feel comes from the varied wave amplitudes inside each
   SVG tile and the randomized per-layer durations/delays set by
   ambient.js, not from vertical motion. */

/* No reflection sheen: the wave layers are intentionally solid, distinct
   blue fills so they read as clean, three-dimensional water with no foam
   or white overlay artifacts. */

.beach-scene__sand {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 35%;
  background-image:
    repeating-linear-gradient(
      to bottom,
      rgba(255, 255, 255, 0.025) 0,
      rgba(255, 255, 255, 0.025) 1px,
      transparent 1px,
      transparent 4px
    ),
    linear-gradient(to bottom, #e9ca8e 0%, #e2c180 48%, #dab774 100%);
}

.backdrop__scrim {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 110px;
  background: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(126, 91, 52, 0.06) 28%,
    rgba(91, 61, 35, 0.28) 100%
  );
  opacity: 0;
  transition: opacity 0.4s ease;
}

html[data-theme='beach'] .backdrop__scrim {
  opacity: 0;
}

html[data-theme='beach'] .picker__intro,
html[data-theme='beach'] .header {
  color: var(--color-on-sky);
}

html[data-theme='beach'] .header__title,
html[data-theme='beach'] .picker__intro-fa {
  color: var(--color-on-sky-accent);
}

html[data-theme='beach'] .picker__intro-en {
  color: var(--color-on-sky-link);
}

html[data-theme='beach'] .picker__intro-sep {
  color: var(--color-on-sky-dim);
}

/* Beach theme-picker segments sit on the bright sky, so they use dark
   ink surfaces (kept in sync with the solid opaque overrides below). */
html[data-theme='beach'] .picker__sound-toggle {
  color: var(--color-on-sky-dim);
  border-color: var(--glass-light-edge);
  background: var(--glass-light);
  backdrop-filter: blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
  box-shadow: var(--glass-light-specular);
}

html[data-theme='beach'] .picker__sound-toggle:hover {
  color: var(--color-on-sky);
}

html[data-theme='beach'] .picker__sound-toggle[aria-pressed='true'] {
  color: var(--color-on-sky);
}

/* Beach menu popover: frosted light glass over the sky, with dark ink. */
html[data-theme='beach'] .menu__popover {
  background: var(--glass-light);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border-color: var(--glass-light-edge);
  box-shadow:
    var(--glass-light-specular),
    0 8px 24px rgba(15, 46, 58, 0.18);
}

html[data-theme='beach'] .menu__item {
  color: #0f2e3a;
}

html[data-theme='beach'] .menu__item:hover {
  background: rgba(5, 69, 84, 0.1);
}

html[data-theme='beach'] .menu__item-icon {
  color: #28645a;
}

@media (prefers-reduced-motion: reduce) {
  .beach-scene__sun,
  .beach-scene__ocean,
  .beach-scene__wave {
    animation: none !important;
    will-change: auto;
  }
}

/* Native buttons otherwise use a platform font, which made the picker and
   menu feel disconnected from the conversation typography. */
button,
textarea {
  font: inherit;
}

html[data-theme='beach'] :focus-visible {
  outline-color: var(--color-on-sky-link);
}

/* The dark ink focus ring is tuned for the light sky surfaces (menu,
   picker, sand). Dark panels - the composer, exit bar, and breathe
   overlay - need a luminous ring instead so the indicator clears 3:1
   against the teal surfaces it sits on. */
html[data-theme='beach'] .composer__send:focus-visible,
html[data-theme='beach'] .exit-confirm-bar__btn:focus-visible,
html[data-theme='beach'] .breathe-close:focus-visible {
  outline-color: #a8d9cc;
}

/* A very slow center pulse gives the ocean its own punctuation of stillness.
   Separate from the bubbles and fully disabled for reduced motion.       */
.backdrop__depth-breath {
  position: absolute;
  inset: 18% 12%;
  border-radius: 50%;
  background: radial-gradient(
    ellipse at center,
    rgba(127, 190, 176, 0.18) 0%,
    rgba(21, 63, 73, 0.04) 54%,
    transparent 76%
  );
  opacity: 0.06;
  animation: depth-breathe 36s ease-in-out infinite;
  will-change: opacity, transform;
}

@keyframes depth-breathe {
  0%,
  100% {
    opacity: 0.04;
    transform: scale(0.99);
  }

  50% {
    opacity: 0.09;
    transform: scale(1.02);
  }
}

html[data-theme='beach'] .backdrop__depth-breath {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .backdrop__depth-breath {
    animation: none;
    opacity: 0.06;
    transform: none;
  }
}

/* --------------------------------------------------------------------- */
/* Breathe trigger: standalone wind icon button next to the menu.       */
/* When the menu is open the button sits at the far right in LTR and    */
/* far left in RTL because of flexbox order + justify-content in the    */
/* header. A gentle sway animation gives the wind icon a soft breeze    */
/* feel without drawing attention away from the conversation.           */
/* --------------------------------------------------------------------- */

.breathe-trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 34px;
  min-inline-size: 34px;
  max-inline-size: 34px;
  block-size: 34px;
  min-block-size: 34px;
  max-block-size: 34px;
  aspect-ratio: 1;
  padding: 0;
  appearance: none;
  background: var(--surface-control);
  background-clip: padding-box;
  backdrop-filter: blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
  border: 1px solid var(--circle-rim);
  color: var(--color-foam-dim);
  border-radius: 50%;
  box-shadow: var(--glass-specular-soft), var(--shadow-soft);
  cursor: pointer;
  flex-shrink: 0;
  line-height: 0;
  position: relative;
  /* Hidden by default: collapsed with a gentle fade-and-scale reveal when
     a heavy turn arms it. Only opacity and transform animate; visibility
     keeps the collapsed state out of the tab order and accessibility
     tree. The button stays in-flow, so the header title and menu never
     shift when it appears. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(3px) scale(0.9);
  pointer-events: none;
  transition:
    opacity 0.36s var(--ease-tide),
    transform 0.36s var(--ease-tide),
    visibility 0.36s var(--ease-tide),
    border-color 0.2s var(--ease-tide),
    color 0.2s var(--ease-tide),
    background 0.2s var(--ease-tide),
    box-shadow 0.2s var(--ease-tide);
}

.breathe-trigger[aria-hidden='false'] {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}

/* Expand the invisible tap target to the recommended 44px while keeping
   the compact 34px visual footprint (matches .menu__trigger). */
.breathe-trigger::after {
  content: '';
  position: absolute;
  inset: -5px;
  border-radius: 50%;
}

/* The breathe trigger is the menu trigger's sibling in the header, so
   it shares the same hover language: brighten the glass body and lift
   the rim, never swap in a transparent tint. (The beach theme overrides
   both with its light-glass treatment below.) */
.breathe-trigger:hover,
.breathe-trigger:focus-visible {
  cursor: pointer;
  color: var(--color-foam);
  border-color: var(--color-seafoam);
  background: var(--surface-control-hover);
}

/* Wind icon subtle animation: gentle sway */
.breathe-trigger:not(:hover) svg {
  animation: wind-sway 8s ease-in-out infinite;
}

@keyframes wind-sway {
  0%,
  100% {
    transform: translateX(0);
  }

  45% {
    transform: translateX(0);
  }

  50% {
    transform: translateX(1.5px);
  }

  55% {
    transform: translateX(0);
  }
}

html[data-theme='beach'] .breathe-trigger {
  color: #2f7384;
  border-color: var(--glass-light-edge);
  background: var(--glass-light);
  backdrop-filter: blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
  box-shadow:
    var(--glass-light-specular),
    0 2px 10px rgba(91, 61, 35, 0.08);
}

html[data-theme='beach'] .breathe-trigger:hover,
html[data-theme='beach'] .breathe-trigger:focus-visible {
  color: #286573;
  border-color: #286573;
  background: var(--glass-light-strong);
  box-shadow:
    var(--glass-light-specular),
    0 4px 14px rgba(91, 61, 35, 0.12);
}

/* --------------------------------------------------------------------- */
/* Sound toggle icon wave arcs (used inside the menu item SVG): these    */
/* two arcs represent the speaker sound waves. When sound is off they    */
/* are dimmed; when on they are at full opacity. The menu item itself    */
/* inherits .menu__item styling.                                         */
/* --------------------------------------------------------------------- */

/* Sound toggle icon: muted (speaker with slash, waves tucked in) when
   off, playing (waves radiating out) when on. Rather than a flat
   crossfade, the waves bloom outward and the slash sweeps away as sound
   turns on, and both settle back as it turns off, so the switch reads as
   a calm, continuous gesture instead of an instant swap. Only opacity
   and transform animate (GPU-composited), and the outer arc lags the
   inner one by a beat so the ripple travels outward. The default state
   is off; aria-pressed="true" (synced by app/sound.js) is the on state. */
.sound-toggle__wave,
.sound-toggle__slash {
  transition:
    opacity 0.35s var(--ease-tide),
    transform 0.35s var(--ease-tide);
  transform-box: fill-box;
  transform-origin: center;
}

.sound-toggle__wave {
  opacity: 0;
  transform: scale(0.55);
}

.sound-toggle__wave + .sound-toggle__wave {
  transition-delay: 0.06s;
}

.sound-toggle__slash {
  opacity: 1;
  transform: rotate(0deg) scale(1);
}

#menu-sound-toggle[aria-pressed='true'] .sound-toggle__wave,
.picker__sound-toggle[aria-pressed='true'] .sound-toggle__wave {
  opacity: 1;
  transform: scale(1);
}

#menu-sound-toggle[aria-pressed='true'] .sound-toggle__slash,
.picker__sound-toggle[aria-pressed='true'] .sound-toggle__slash {
  opacity: 0;
  transform: rotate(-24deg) scale(0.6);
}

/* Theme toggle icon: the ocean (waves) and beach (sun) glyphs both live
   in the menu item and CSS reveals only the theme the item switches TO
   (the opposite of the active theme), mirroring the old emoji swap. */
html[data-theme='ocean'] .theme-toggle__ocean,
html[data-theme='beach'] .theme-toggle__beach {
  display: none;
}

/* --------------------------------------------------------------------- */
/* Beach theme: menu trigger with sky-complementing blue tones. */
/* Idle and hover use darkened sky-blue inks so the trigger icon keeps
   its teal identity while staying above the 4.5:1 threshold on the pale
   sky chip (hover darkens a step further to preserve the feedback). */
html[data-theme='beach'] .menu__trigger {
  color: #1a5f6d;
  border-color: var(--glass-light-edge);
  background: var(--glass-light);
  backdrop-filter: blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
  box-shadow:
    var(--glass-light-specular),
    0 2px 10px rgba(91, 61, 35, 0.08);
}

html[data-theme='beach'] .menu__trigger:hover,
html[data-theme='beach'] .menu__trigger[aria-expanded='true'] {
  color: #14505f;
  border-color: #14505f;
  background: var(--glass-light-strong);
  box-shadow:
    var(--glass-light-specular),
    0 4px 14px rgba(91, 61, 35, 0.12);
}

html[data-theme='beach'] .input-hint {
  color: var(--color-on-sky-accent);
  background: rgba(240, 235, 218, 0.9);
  border: 1px solid rgba(15, 46, 58, 0.2);
  border-radius: var(--radius-sm);
  font-weight: 600;
  text-shadow: none;
}

html:not([data-theme='beach']) .input-hint {
  background: rgba(127, 190, 176, 0.12);
  border: 1px solid rgba(127, 190, 176, 0.22);
  border-radius: var(--radius-sm);
}

html[data-theme='beach'] .disclaimer {
  color: var(--color-on-sky);
  text-shadow: none;
}

/* Ocean theme: disclaimer and warning text need higher contrast. */
html:not([data-theme='beach']) .disclaimer {
  color: var(--color-foam);
  opacity: 0.7;
}

html:not([data-theme='beach']) .input-hint {
  color: #f5d9b0;
  background: rgba(240, 185, 134, 0.1);
  border-color: rgba(240, 185, 134, 0.18);
}

/* Beach language cards inherit the glass surface tokens (deep-teal frost
   plus the top sheen); only the hover rim and lift need sand-tuned
   values, since the bright sky sits behind the pane. */
html[data-theme='beach'] .picker__option:hover {
  border-color: var(--color-on-sky-link);
  box-shadow: 0 6px 20px rgba(91, 61, 35, 0.15);
}

/* The beach panel is a translucent dark teal over bright sand, which
   lightens the effective background enough that the ocean-tuned accent
   text (coral, seafoam, foam-dim) falls below the 4.5:1 threshold.
   Brighter variants keep the same hues and pass AA on both the idle and
   hover panels. */
html[data-theme='beach'] #picker-fa .picker__option-title {
  color: #fdd6bd;
}

html[data-theme='beach'] #picker-en .picker__option-title {
  color: #c2e6d8;
}

html[data-theme='beach'] .picker__option-desc {
  color: #d6ece5;
}

html[data-theme='beach'] .bubble--user {
  background: var(--user-bubble-sheen), var(--color-on-sky);
  color: #f0f7f4;
}

html[data-theme='beach'] .bubble--bot {
  color: #f0f7f4;
}

html[data-theme='beach'] .bubble-row--bot .bubble-meta {
  color: var(--color-on-sky-dim);
}

html[data-theme='beach'] .composer:focus-within {
  border-color: #a8d9cc;
  box-shadow: 0 0 0 2px rgba(168, 217, 204, 0.22);
}

html[data-theme='beach'] .composer__input {
  color: #f0f7f4;
}

/* The send button sits on the translucent teal composer; the stock coral
   only clears 2:1 there. A brighter shell tone keeps the warm identity
   while lifting the boundary above the 3:1 non-text threshold, and the
   dark arrow keeps its own strong contrast on the lighter fill. */
html[data-theme='beach'] .composer__send {
  background: #f5b28d;
}

html[data-theme='beach'] .composer__send:hover:not(:disabled) {
  background: #f9c1a6;
}

html[data-theme='beach'] .composer__input::placeholder {
  color: rgba(240, 247, 244, 0.55);
}

/* Beach theme-picker segments inherit the glass surface tokens; only the
   dimmer resting label needs its own ink tone. */
html[data-theme='beach'] .theme-picker__option {
  color: rgba(240, 247, 244, 0.75);
}

html[data-theme='beach'] .theme-picker__option:hover {
  border-color: rgba(5, 69, 84, 0.45);
  color: #f0f7f4;
}

html[data-theme='beach'] .theme-picker__option[aria-pressed='true'] {
  /* A luminous border keeps the selected state readable: the dark link
     ink is invisible against the teal panel (1.8:1). */
  border-color: #a8d9cc;
  color: #f0f7f4;
  /* Halo parity with the ocean pressed state and the beach composer
     focus-within ring, translated to the beach luminous foam. */
  box-shadow: 0 0 0 2px rgba(168, 217, 204, 0.2);
}

/* Beach front page intro text uses dark ink on bright sky. */
html[data-theme='beach'] .picker__intro {
  color: var(--color-on-sky);
}

html[data-theme='beach'] .picker__intro-fa {
  color: var(--color-on-sky-accent);
}

html[data-theme='beach'] .picker__intro-en {
  color: var(--color-on-sky-link);
}

/* --------------------------------------------------------------------- */
/* Cursor-tracked glass glint: a whisper-light highlight that follows    */
/* the pointer across interactive glass, so the material reads as lit    */
/* from the user's hand instead of a flat texture. Decorative only: the  */
/* highlight is pointer-events none, and js/ui/glint.js skips the        */
/* tracking on touch input and prefers-reduced-motion, so it never       */
/* competes with the calm core or with assistive tech.                  */
/* --------------------------------------------------------------------- */

/* Elements that carry the glint but are not yet a containing block.     */
.picker__option,
.theme-picker__option,
.menu__item,
.quick-reply,
.confirm-container {
  position: relative;
}

.picker__option::after,
.theme-picker__option::after,
.menu__item::after,
.menu__popover::after,
.quick-reply::after,
.confirm-container::after,
.picker__sound-toggle::after,
.menu__trigger::before,
.breathe-trigger::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(
    150px circle at var(--glint-x, 50%) var(--glint-y, 50%),
    var(--glint-color),
    transparent 70%
  );
  opacity: 0;
  transition: opacity 0.25s var(--ease-tide);
  pointer-events: none;
}

.picker__option:hover::after,
.theme-picker__option:hover::after,
.menu__item:hover::after,
.menu__popover:hover::after,
.quick-reply:hover::after,
.confirm-container:hover::after,
.picker__sound-toggle:hover::after,
.menu__trigger:hover::before,
.breathe-trigger:hover::before {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .picker__option::after,
  .theme-picker__option::after,
  .menu__item::after,
  .menu__popover::after,
  .quick-reply::after,
  .confirm-container::after,
  .picker__sound-toggle::after,
  .menu__trigger::before,
  .breathe-trigger::before {
    transition: none;
  }
}
