:root {
  --bg-deep: #0A0E1A;
  --bg-card: #141B2D;
  --bg-surface: #1A2340;
  --bg-elevated: #1F2B4A;

  --text-primary: #E8EAF6;
  --text-secondary: #9FA8DA;
  --text-muted: #5C6BC0;

  --accent-primary: #7C4DFF;
  --accent-secondary: #00E5FF;
  --accent-gold: #FFD54F;
  --accent-danger: #FF5252;
  --accent-soul: #FF4FD8;

  --accent-fire: #FF6B35;
  --accent-water: #5A6BD8;
  --accent-earth: #7BC67E;
  --accent-air: #B8E4FF;
  --accent-void: #B388FF;

  /* Myru's own accent + bubble ink, exact values from the app's
     _SpeechBubble / SketchBorder (shared/widgets/myru/myru_overlay.dart). */
  --myru-accent: #FF2ED2;
  --myru-ink: #F2EFE4;
  --myru-bubble-bg: #12082E;

  /* Crown (violet) down to root (red), the site's own brand spectrum,
     used for primary buttons. Not an app color, a Notself one. */
  --chakra-gradient: linear-gradient(
    100deg,
    #7C4DFF 0%,
    #5B4FCF 18%,
    #3B82F6 36%,
    #4CAF50 54%,
    #FFC94D 70%,
    #FF8C42 85%,
    #E5484D 100%
  );

  --font-hand: 'Patrick Hand', 'Comic Sans MS', cursive;

  --radius-lg: 24px;
  --radius-md: 16px;
  --radius-sm: 12px;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

body {
  margin: 0;
  /* Fallback only, in case #sky-canvas fails: same flat bgDeep the app
     scaffold uses (shared/theme/app_theme.dart). The sky canvas paints
     over this once JS runs, see initSky() in app.js. */
  background: var(--bg-deep);
  color: var(--text-primary);
  font-family: var(--font-hand);
  font-size: 19px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  /* The sky cycles through a bright daytime phase behind sections with no
     opaque card underneath; same fix the app's own onboarding sky page
     uses for its overlaid text (features/habits/onboarding_pages.dart,
     Shadow(color: Colors.black54, blurRadius: 6)). text-shadow inherits,
     so this covers every heading and paragraph without repeating it. */
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.45);
}

/* ---------- Sky background (features/habits/habit_time_sky.dart's
   onboarding day/night picker, simplified into an ambient backdrop: the
   same sky palette, hills, and a sun/moon riding opposite ends of one
   slowly turning wheel). ---------- */
#sky-canvas {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: -1;
  display: block;
}

/* ---------- CRT overlay (shared/widgets/crt_overlay.dart, default
   CRTSettings: scanlines .65, vignette .50) applied globally like the app.
   Scaled down from the in-app defaults since this sits over dense reading
   text rather than game art. ---------- */
.crt-overlay {
  position: fixed;
  inset: 0;
  z-index: 999;
  pointer-events: none;
}
.crt-scanlines {
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.5) 0px,
    rgba(0, 0, 0, 0.5) 1px,
    transparent 1px,
    transparent 3px
  );
  mix-blend-mode: multiply;
  opacity: 0.22;
}
.crt-bright-lines {
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.5) 0px,
    rgba(255, 255, 255, 0.5) 1px,
    transparent 1px,
    transparent 6px
  );
  mix-blend-mode: screen;
  opacity: 0.02;
}
.crt-vignette {
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle at top left, rgba(0, 0, 0, 0.10) 0%, transparent 60%),
    radial-gradient(circle at top right, rgba(0, 0, 0, 0.10) 0%, transparent 60%),
    radial-gradient(circle at bottom left, rgba(0, 0, 0, 0.10) 0%, transparent 60%),
    radial-gradient(circle at bottom right, rgba(0, 0, 0, 0.10) 0%, transparent 60%),
    radial-gradient(ellipse at center, transparent 0%, transparent 58%, rgba(0, 0, 0, 0.14) 84%, rgba(0, 0, 0, 0.28) 100%);
}
.crt-edge-glow {
  position: absolute;
  inset: 1px;
  border: 1px solid rgba(0, 255, 136, 0.05);
}
.crt-beam {
  position: absolute;
  left: 0;
  right: 0;
  height: 140px;
  background: linear-gradient(to bottom, transparent, rgba(255, 255, 255, 0.05), transparent);
  mix-blend-mode: screen;
  animation: crt-beam-sweep 12.82s linear infinite;
}
@keyframes crt-beam-sweep {
  /* Bottom to top, matching the app's phosphor beam (crt_overlay.dart,
     kCrtBeamPeriodMs = 12820, "one full travel, bottom to top"). */
  0% { top: 100%; }
  100% { top: -140px; }
}
@media (prefers-reduced-motion: reduce) {
  .crt-beam { animation: none; display: none; }
}

img { max-width: 100%; display: block; }
button, input, textarea { font-family: var(--font-hand); }

.wrap { width: 100%; max-width: 900px; margin: 0 auto; padding: 0 24px; }
a { color: inherit; text-decoration: none; }

h1, h2 { font-weight: 400; line-height: 1.15; margin: 0 0 0.5em; }
h1 { font-size: clamp(2.2rem, 6vw, 3.4rem); }
h2 { font-size: clamp(1.6rem, 4vw, 2.1rem); }
.center { text-align: center; margin-left: auto; margin-right: auto; }

.section { padding: 56px 0; }

/* ---------- Buttons ---------- */
.btn {
  font-family: var(--font-hand);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 12px 24px;
  border-radius: 999px;
  border: 2px solid transparent;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease;
  font-size: 1.15rem;
}
.btn:hover { transform: translateY(-1px); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
.btn-primary {
  background: linear-gradient(135deg, var(--accent-primary), #5b32d6);
  color: #fff;
  box-shadow: 0 8px 24px rgba(124, 77, 255, 0.35);
}
.btn-ghost {
  background: transparent;
  color: var(--text-secondary);
  border-color: rgba(159, 168, 218, 0.35);
}
.btn-ghost:hover { border-color: var(--accent-secondary); color: var(--accent-secondary); }
.btn-lg { padding: 14px 32px; font-size: 1.3rem; }
.btn-small { padding: 7px 16px; font-size: 0.95rem; }

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  /* --header-bg is written every frame by initSky() in app.js, tracking
     the same day/night cycle as the sky behind it — dark navy at night,
     fading up toward near-white at solar noon. The literal value here is
     only the pre-JS/no-JS fallback. */
  background: var(--header-bg, rgba(10, 14, 26, 0.78));
  backdrop-filter: blur(10px);
  border-bottom: 1px solid rgba(159, 168, 218, 0.12);
}
.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 12px;
  padding-bottom: 12px;
}
.brand { display: flex; align-items: center; gap: 8px; font-size: 1.3rem; }
.header-right { display: flex; align-items: center; gap: 10px; }
.lang-toggle {
  display: flex;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 999px;
  padding: 3px;
  gap: 2px;
}
.lang-btn {
  font-family: var(--font-hand);
  font-size: 0.9rem;
  padding: 5px 11px;
  border-radius: 999px;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
}
.lang-btn[aria-pressed="true"] {
  background: var(--accent-primary);
  color: #fff;
}
.brand-word {
  -webkit-text-stroke: 0.4px rgba(0, 0, 0, 0.75);
  text-shadow: 0 0 0.5px rgba(0, 0, 0, 0.55);
}
.brand-mark { width: 30px; height: 30px; object-fit: contain; }

/* ---------- Sketch border ---------- */
.sketch { position: relative; }
/* .sketch-own-position is an escape hatch: a direct child of a sketch
   container that needs its OWN position value (absolute, for its own
   left/top or transform-based layout) opts out here, instead of this
   rule silently overwriting it (bit twice already: .myru-drag and
   .self-label both need position:absolute and both got forced to
   position:relative by this rule before it excluded them). */
.sketch > *:not(svg.sketch-svg):not(.sketch-own-position) { position: relative; z-index: 1; }

/* ---------- Hero ---------- */
.hero { padding: 56px 0 24px; }
.hero-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 8px;
}
.hero-copy {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  width: 100%;
  max-width: 640px;
}
.hero-copy .hangout-wrap { width: 100%; }

/* Hidden until summoned via the mystery button; see .myru-peek below for
   the idle hint that she's there at all. Screen-fixed and JS-positioned
   (left/top) once revealed, the same "floats over the viewport and
   wanders on her own" behavior as the app's Myru overlay, rather than
   living inside the hero section and scrolling away with the page. */
.hero-myru {
  display: none;
  flex-direction: column;
  align-items: center;
  position: fixed;
  z-index: 998;
  left: 0;
  top: 0;
  pointer-events: none;
  /* Same anticipation-overshoot curve as _kMyruGlide in myru_overlay.dart
     (Cubic(0.45,-0.28,0.28,1.32)): a small dip backwards before she sets
     off, then a slight overshoot past the landing spot before settling,
     instead of a flat ease. Duration is set per-hop in JS (see
     initMyruWander's glideTo), scaled to how far she's travelling. */
  transition: left 1.8s cubic-bezier(0.45, -0.28, 0.28, 1.32), top 1.8s cubic-bezier(0.45, -0.28, 0.28, 1.32);
}
.hero-myru.revealed { display: flex; }
.hero-myru .myru-bubble,
.hero-myru .myru-figure { pointer-events: auto; }
@media (prefers-reduced-motion: reduce) {
  .hero-myru { transition: none; }
}
.myru-bubble {
  position: relative;
  font-size: 1.2rem;
  background: rgba(18, 8, 46, 0.92);
  color: var(--myru-ink);
  border: 1.2px solid rgba(255, 46, 210, 0.7);
  border-radius: 12px;
  padding: 8px 12px;
  margin-bottom: 10px;
  max-width: 240px;
  min-height: 1.6em;
  text-align: center;
  box-shadow: 0 0 12px rgba(255, 46, 210, 0.25);
}
.myru-bubble .diagram { display: flex; align-items: center; justify-content: center; gap: 6px; flex-wrap: wrap; font-size: 1rem; }
.myru-bubble .diagram .dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
.myru-bubble.pop { animation: pop-in 0.35s ease; }

/* Word-by-word reveal (myru_overlay.dart _BeatText): each word is laid out
   from the start (reserving its space so the bubble never resizes/jumps
   as words arrive) but painted invisible until its turn. The word
   currently "being said" gets the app's pink stroke behind its own ink
   fill — -webkit-text-stroke draws exactly that stroke-then-fill in one
   declaration, no separate stacked text needed the way Flutter required. */
.myru-beat { opacity: 0; }
.myru-beat.shown { opacity: 1; }
.myru-beat.current {
  -webkit-text-stroke: 1.1px var(--myru-accent);
  text-stroke: 1.1px var(--myru-accent);
}
.myru-bubble-extra {
  display: inline-block;
  opacity: 0;
  transition: opacity 250ms ease;
}
.myru-bubble-extra.shown { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .myru-beat { opacity: 1; }
  .myru-bubble-extra { opacity: 1; transition: none; }
}
@keyframes pop-in {
  0% { transform: scale(0.85); opacity: 0.4; }
  60% { transform: scale(1.05); }
  100% { transform: scale(1); opacity: 1; }
}

/* Exact port of myruBodySize() (myru_models.dart): 210px at scale 1.0 on
   a 420 reference short-side, adapting HALF the device-size difference,
   clamped to a 0.72-1.55 factor. Reduces to 105px + 0.25 * shortSide
   before the outer clamp. Using min(vw,vh) as the web's "device short
   side" analogue. */
.myru-figure {
  position: relative;
  width: clamp(151.2px, calc(105px + 0.25 * min(100vw, 100vh)), 325.5px);
  height: clamp(151.2px, calc(105px + 0.25 * min(100vw, 100vh)), 325.5px);
  border-radius: 50%;
  border: none;
  background: radial-gradient(circle at 50% 40%, rgba(255, 46, 210, 0.12), transparent 70%);
  padding: 0;
  cursor: grab;
  touch-action: none;
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
  animation: myru-float 5s ease-in-out infinite;
}
.myru-figure.grabbing { cursor: grabbing; animation-play-state: paused; }
@keyframes myru-float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-8px); }
}

/* Wander glide deformation: a squash-anticipation crouch, then squash-and-
   stretch ALONG the travel direction, then a small overshoot settle —
   ported from _MyruMotionRig's velocity-driven strain (rotate to align
   with travel, scale, rotate back, so the stretch is axis-aligned rather
   than a fixed shape) plus a "gather before you go" crouch of our own,
   same animation-principle spirit as the app's bounceBody squash-hop.
   --glide-angle/--glide-lean are set per-hop by initMyruWander(). */
.myru-drag.gliding {
  animation: myru-glide-pulse var(--glide-ms, 1800ms) cubic-bezier(0.45, -0.28, 0.28, 1.32) 1;
}
@keyframes myru-glide-pulse {
  0%   { transform: scale(1, 1); }
  6%   { transform: scale(0.91, 1.08); }
  38%  { transform: rotate(var(--glide-lean, 0deg)) rotate(var(--glide-angle, 0deg)) scale(1.14, 0.9) rotate(calc(-1 * var(--glide-angle, 0deg))); }
  72%  { transform: rotate(calc(var(--glide-lean, 0deg) * -0.4)) rotate(var(--glide-angle, 0deg)) scale(0.95, 1.05) rotate(calc(-1 * var(--glide-angle, 0deg))); }
  100% { transform: scale(1, 1); }
}
@media (prefers-reduced-motion: reduce) {
  .myru-drag.gliding { animation: none; }
}

/* Pulled by the pointer (rubber-banded translate): everything below this
   moves together, same as the app's grab handling in myru_overlay.dart.
   Explicit width/height rather than relying on inset:0 to stretch it,
   since the generic ".sketch > *" rule below forces this to
   position:relative (higher specificity than a plain class), and inset
   only stretches absolute/fixed boxes. */
.myru-drag {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* Spins + stretches with the body (drag angular component + squash-and-
   stretch); the face lives OUTSIDE this so it always stays level, exactly
   like shared/widgets/myru/myru_body.dart's own layer split. */
.myru-spin {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transition: filter 1150ms ease, opacity 1150ms linear;
}
.myru-figure img {
  position: relative;
  z-index: 1;
  width: 72%;
  aspect-ratio: 1 / 1;
  margin: 14% auto;
  display: block;
  filter: drop-shadow(0 12px 20px rgba(0, 0, 0, 0.4));
  -webkit-user-drag: none;
  user-select: none;
  pointer-events: none;
}
.myru-arrows {
  position: absolute;
  z-index: 2;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
/* Each arrow breathes independently while she's being spun: a lively,
   staggered pulse along its OWN radial direction (rotate to align, scale,
   rotate back), the same "arrows have their own life" feel as the app's
   propeller/extension animation. */
.myru-figure.grabbing .myru-arrow {
  animation: myru-arrow-pulse 0.55s ease-in-out infinite;
}
.myru-figure.grabbing .myru-arrow:nth-child(2) { animation-delay: 0.07s; }
.myru-figure.grabbing .myru-arrow:nth-child(3) { animation-delay: 0.14s; }
.myru-figure.grabbing .myru-arrow:nth-child(4) { animation-delay: 0.21s; }
.myru-figure.grabbing .myru-arrow:nth-child(5) { animation-delay: 0.28s; }
@keyframes myru-arrow-pulse {
  0%, 100% { transform: rotate(var(--adeg)) scale(1, 1) rotate(calc(-1 * var(--adeg))); }
  50% { transform: rotate(var(--adeg)) scale(1.28, 1) rotate(calc(-1 * var(--adeg))); }
}
.myru-figure svg.myru-face {
  position: absolute !important;
  z-index: 2;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  transition: opacity 1150ms linear, filter 1150ms ease, transform 500ms ease;
}
.myru-face .mouth { transition: d 620ms ease-in-out; }
/* Same fixed cycle as the app's blink (myru_face.dart): 2800ms open,
   80ms close, 60ms hold shut, 110ms open, total 3050ms. */
.myru-face .eye {
  fill: var(--myru-ink);
  transform-box: fill-box;
  transform-origin: center;
  animation: myru-blink 3050ms ease-in-out infinite;
}
@keyframes myru-blink {
  0%, 91.80% { transform: scaleY(1); }
  94.43%, 96.39% { transform: scaleY(0.12); }
  100% { transform: scaleY(1); }
}
@media (prefers-reduced-motion: reduce) {
  .myru-face .eye { animation: none; }
}
.myru-face .mouth {
  fill: none;
  stroke: var(--myru-ink);
  stroke-width: 0.6;
  stroke-linecap: round;
}

/* MyruEmotion.surprised: bigger, wide-open eyes (no blink while it holds)
   and the mouth switches from a stroked curve to a small filled "O" —
   summoned (see applyLaunchKick in app.js) or hauled out to ~95% of her
   pull tether and kept there (see updateStartle), same as the app. */
.myru-figure.surprised .myru-face .eye {
  animation: none;
  transition: transform 120ms ease, cx 120ms ease;
  transform: scale(1.4);
}
/* 2.5% further apart than the resting 6.8-unit gap (cx 46.6/53.4),
   surprise only — the `cx` SVG geometry property (not transform, which
   is already carrying the scale above and would otherwise have to
   compose with it) repositions each eye in its own untransformed
   coordinate space, so this is independent of the scale regardless of
   transform order. */
.myru-figure.surprised .myru-face circle.eye:first-of-type { cx: 46.515px; }
.myru-figure.surprised .myru-face circle.eye:last-of-type { cx: 53.485px; }
/* An open mouth in the app is a DARK interior (the same near-black the
   speech bubble uses) with the ink-colored lip line stroked on top of
   it — not a solid light fill. A light fill with no outline is exactly
   what read as a pale floating blob instead of an open mouth. */
.myru-figure.surprised .myru-face .mouth {
  fill: var(--myru-bubble-bg);
  stroke: var(--myru-ink);
  stroke-width: 0.5;
}
/* kMyruTonguePink (myru_face.dart) — hidden except while surprised, same
   as the app only painting a tongue once the mouth is actually open. */
.myru-face .tongue {
  fill: #F740F2;
  opacity: 0;
}
.myru-figure.surprised .myru-face .tongue {
  opacity: 1;
}
.myru-aura {
  position: absolute;
  inset: -10%;
  border-radius: 50%;
  z-index: 0;
  opacity: 0.6;
  filter: blur(6px);
}
.myru-aura.pulse { animation: aura-pulse 1.4s ease; }
@keyframes aura-pulse {
  0% { transform: scale(0.7); opacity: 0.9; }
  100% { transform: scale(1.25); opacity: 0.35; }
}
.myru-figure.bounce { animation: myru-float 5s ease-in-out infinite, myru-bounce 0.5s ease; }
@keyframes myru-bounce {
  0% { transform: scale(1); }
  35% { transform: scale(0.9, 1.1); }
  60% { transform: scale(1.08, 0.94); }
  100% { transform: scale(1); }
}

/* Launch: the same "spat out of the crest" pop the app's nav bar gives
   Myru on summon (main_nav_shade.dart's bulge-launch-tube), replayed here
   as an elastic scale/rise instead of an actual bulging bar. */
.myru-figure.launch { animation: myru-launch 700ms cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes myru-launch {
  0% { transform: scale(0.15) translateY(60px); opacity: 0; }
  55% { transform: scale(1.12) translateY(-8px); opacity: 1; }
  75% { transform: scale(0.94) translateY(4px); }
  100% { transform: scale(1) translateY(0); }
}

/* The goodbye. In the app this is ONE wrapper (_withDissolve) around the
   whole composited body INCLUDING the face — she swells, thins and blurs
   out as one coherent thing, not two layers fading on different curves.
   growth/opacity are exactly linear in time (matches kMyruDissolveGrowth
   0.55 / a plain 1-t fade); blur is held back on a squared ramp (ease-in
   approximates t²: negligible at first, arriving late) — kMyruDissolveBlur
   4.5, kMyruDissolveMs 1150. Declared here (not on the base rule) because
   the base .myru-spin transform is driven raw, per animation-frame, by
   drag/spin/calm-spin — giving it a 1.15s transition there would smear
   every ordinary spin into a laggy blur. */
.myru-figure.dissolving .myru-spin,
.myru-figure.dissolving svg.myru-face {
  transition: opacity 1150ms linear, filter 1150ms ease-in, transform 1150ms linear;
  opacity: 0;
  filter: blur(4.5px);
  transform: scale(1.55);
}
/* She can't be interacted with while leaving or gone — same as the app
   removing her gesture detector once nothing is visible to grab. Coming
   back only happens through a real re-summon (the mystery button), never
   on a timer — see initMyru's resummon()/isGone() in app.js. */
.myru-figure.dissolving { pointer-events: none; }
/* The relieved exhale the app leaves her on for the whole exit: eyes
   nearly shut (kMyruEmotion.relieved's lid factor 0.22), settled quickly
   rather than easing shut over the full fade. The mouth's own `d`
   transition (below) handles the matching relieved mouth shape. */
.myru-figure.dissolving .myru-face .eye {
  animation: none;
  transition: transform 320ms ease;
  transform: scaleY(0.22);
}
/* Scattering arrows: each one flies out along its OWN direction and
   fades, staggered, instead of the whole arrow layer scaling as one flat
   sheet, matching the app's _ScatteredArrowsPainter (myru_overlay.dart). */
.myru-figure.dissolving .myru-arrow {
  animation: myru-arrow-scatter 1150ms cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}
.myru-figure.dissolving .myru-arrow:nth-child(2) { animation-delay: 30ms; }
.myru-figure.dissolving .myru-arrow:nth-child(3) { animation-delay: 60ms; }
.myru-figure.dissolving .myru-arrow:nth-child(4) { animation-delay: 90ms; }
.myru-figure.dissolving .myru-arrow:nth-child(5) { animation-delay: 120ms; }
/* Thrown outward along the arrow's own direction (reach), then gravity
   pulls it down an extra bit past that (fall) — same 6.0:5.0 reach:fall
   ratio as kMyruDissolveArrowReach/Fall (myru_models.dart), traced as a
   3-stop arc instead of a straight radial line. */
@keyframes myru-arrow-scatter {
  0% { transform: translate(0, 0) scale(1); opacity: 1; }
  55% {
    transform: translate(calc(var(--ax) * 46px), calc(var(--ay) * 46px - 6px)) scale(1.4);
    opacity: 1;
  }
  100% {
    transform: translate(calc(var(--ax) * 46px), calc(var(--ay) * 46px + 38px)) scale(1.7);
    opacity: 0;
  }
}
.myru-hint { color: var(--text-muted); margin-top: 10px; font-size: 1rem; }

/* ---------- Demo ---------- */
.demo-section { padding-top: 24px; }
.demo-panel {
  position: relative;
  background: var(--bg-card);
  border-radius: var(--radius-lg);
  /* Extra top padding so the protruding tab (see .draw-feeling-tab) has
     room to poke out above the content without overlapping "1. draw it". */
  padding: 44px 28px 28px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  margin-bottom: 20px;
}
/* Draw-it in its own column on the left, feel+element stacked in a wider
   column right next to it — more compact than everything stacked in one
   column, and a tall canvas next to two short chip rows uses the box's
   height evenly instead of leaving the chip column mostly empty air.
   Wraps to stacked on anything too narrow for both side by side. */
.demo-columns {
  display: flex;
  flex-wrap: wrap;
  gap: 28px;
  /* stretch, not flex-start: the feel/element column needs the SAME
     height as the (now much taller) draw column so its own send-out
     button (margin-top: auto below) has somewhere to sink to. */
  align-items: stretch;
  justify-content: center;
}
.demo-step-draw { flex: 0 0 auto; }
.demo-step-feel { flex: 1 1 240px; min-width: 220px; padding-top: 4px; }
/* Parks at the bottom of the feel/element column rather than adding a
   separate full-width row below both columns — that column is usually
   short next to the tall draw canvas, so there's room to spare down
   there without growing the panel any taller. Falls back to sitting
   right after the chips, in flow, whenever that spare room isn't there
   (a narrow stacked layout, or a long chip row wrapping). */
.demo-step-send { margin-top: auto; padding-top: 10px; }
/* The protruding tab: sits half in, half out of the panel's own top
   edge, and — because it carries .sketch-own-position, opting it out of
   the ".sketch > *" rule that would otherwise pin it to z-index 1 — an
   explicit z-index here puts it above the sketch-border SVG (z-index 0)
   instead of behind it. */
.draw-feeling-tab {
  position: absolute;
  top: -20px;
  left: 28px;
  z-index: 2;
  font-family: var(--font-hand);
  font-size: 1.15rem;
  color: #fff;
  background: linear-gradient(135deg, var(--accent-primary), #5b32d6);
  padding: 12px 26px;
  border-radius: 999px;
  box-shadow: 0 8px 20px rgba(124, 77, 255, 0.4);
}
.demo-step { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.step-label { margin: 0; color: var(--accent-secondary); font-size: 1.05rem; }
/* No overflow:hidden here: this element carries its own sketch-border
   wobble (data-sketch), which needs to bulge slightly OUTSIDE its own
   box. Corner-rounding the canvas content lives on #draw-canvas itself
   instead, so the two clips don't fight each other.
   Portrait, not square — a tall canvas nudges people toward drawing an
   upright (humanoid) figure rather than a stray blob shape, the way a
   square canvas doesn't push either way.
   Width-only 10% wider (height unchanged, aspect ratio NOT preserved):
   characters already render wider than tall once they're small on the
   energy field, so a squarer canvas matches that better than scaling
   the whole 9:16 box up would have. */
.draw-wrap {
  background: #0d1220;
  border-radius: var(--radius-sm);
  /* 24% bigger than clamp(140px, 26vw, 200px), then 10% wider, then
     another 3% wider / 2% taller — 255x408 off the 248x400 attributes
     on #draw-canvas simplifies exactly to a 5:8 ratio. */
  width: clamp(196.73px, 36.52vw, 281.19px);
  aspect-ratio: 5 / 8;
  /* A bit of room to its right in this column that a plain centered
     draw-wrap wasn't using — nudged over into it. */
  margin-left: 14px;
}
/* "1. draw it" sits right above the canvas, centered on the COLUMN by
   default — same rightward nudge as .draw-wrap so it stays centered
   over the canvas instead of the canvas drifting out from under it. */
.demo-step-draw .step-label {
  margin-left: 14px;
}
#draw-canvas {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: var(--radius-sm);
  overflow: hidden;
  touch-action: none;
  cursor: crosshair;
}

.chip-row { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; max-width: min(340px, 100%); }
/* 8% smaller across the board (font/padding/icon/swatch) than the
   original 1.05rem / 7px 14px / 20px / 12px.
   Sizing is font-size + padding, not transform:scale — a transform
   wouldn't touch layout, so neighbors would just sit there while the
   picked chip visually overlapped them instead of the row actually
   making room for it. Icon/swatch use em units so they scale along
   with whichever font-size a given state lands on, picked or not,
   without needing their own separate rules per state. */
.chip {
  font-family: var(--font-hand);
  font-size: 0.966rem;
  background: var(--bg-elevated);
  /* No CSS border any more — each chip carries its own hand-drawn
     sketch-border SVG instead (see buildChips() in app.js), same as
     everything else on this site. Element chips pass their own
     element's color through data-sketch-color; emotion chips fall back
     to the shared default accent. */
  border: none;
  border-radius: 999px;
  padding: 6.4px 12.9px;
  color: var(--text-primary);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 5.5px;
  transition: font-size 240ms cubic-bezier(0.34, 1.56, 0.64, 1),
    padding 240ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.chip[aria-checked="true"] {
  /* Tints toward the SAME color as this chip's own sketch border
     (--chip-tint, set per-chip in buildChips()) rather than one fixed
     cyan on everything — an element chip picks up its own element's
     color, an emotion chip picks up the shared default accent, matching
     whatever its border is already doing. */
  background: color-mix(in srgb, var(--chip-tint, var(--accent-primary)) 16%, var(--bg-elevated));
  /* 12% bigger than the base 0.966rem / 6.4px 12.9px — the pick gets to
     look like the fun, obvious choice, not just a background tint. */
  font-size: 1.082rem;
  padding: 7.17px 14.45px;
}
/* Once something in the row is actually picked, everything else steps
   back to make room for it, a little smaller than the resting size. */
.chip-row:has(.chip[aria-checked="true"]) .chip:not([aria-checked="true"]) {
  font-size: 0.87rem;
  padding: 5.9px 11.9px;
}
.chip .swatch { width: 0.71em; height: 0.71em; border-radius: 50%; display: inline-block; }
.chip-icon { width: 1.19em; height: 1.19em; object-fit: contain; }

/* The teaser chip at the end of each row: greyed out, not a real pick —
   clicking it doesn't select anything, it just has Myru mention the rest
   live in the app. */
.chip-locked {
  color: var(--text-muted);
  background: rgba(255, 255, 255, 0.04);
  opacity: 0.65;
}
.chip-locked:hover { opacity: 0.9; }

.demo-step-send { justify-content: center; }
.demo-hint { color: var(--text-muted); font-size: 0.95rem; margin: 0; }

/* No overflow:hidden on the sketch-bordered element itself, same reason
   as .draw-wrap: it needs its wobble border to bulge past its own box.
   The canvas gets its own rounded clip layer instead. Nameplates
   (.self-label) are appended as direct children of THIS element, so
   they now float freely above the panel rather than being cut off when
   a character's energy pushes it near the top edge. */
.hangout-wrap {
  position: relative;
  border-radius: var(--radius-lg);
  /* 12% taller than the original clamp(160px, 30vw, 220px). */
  height: clamp(179.2px, 33.6vw, 246.4px);
}
.hangout-canvas-clip {
  position: absolute;
  inset: 0;
  /* 86% transparent base — the sky behind the page shows through the top
     half (above the terrain line). The canvas itself paints a near-opaque
     fill from the terrain line down to the bottom (see draw() in app.js),
     so the "floor" half still reads solid on top of this. */
  background: rgba(26, 35, 64, 0.14);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
#hangout-canvas { display: block; width: 100%; height: 100%; touch-action: none; cursor: grab; }

/* Nameplate above each self: level-of-detail matches the app's own
   character label in hangout_screen.dart, a SketchBorder name pill sitting
   right above a thin energy bar (see EnergyRateBar in hangout_energy.dart). */
.self-label {
  position: absolute;
  left: 0;
  top: 0;
  transform: translate(-50%, -100%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  pointer-events: none;
  white-space: nowrap;
}
.self-name {
  background: rgba(20, 27, 45, 0.65);
  border-radius: 8px;
  padding: 2px 6px;
  font-size: 0.62rem;
  letter-spacing: 0.02em;
  line-height: 1.3;
}
.self-energy-track {
  width: 36px;
  height: 4px;
  background: rgba(255, 255, 255, 0.12);
  border-radius: 4px;
  overflow: hidden;
}
.self-energy-fill {
  height: 100%;
  border-radius: 4px;
  transition: width 150ms linear, background-color 300ms ease;
}

@media (max-width: 480px) {
  body { font-size: 17px; }
  .wrap { padding: 0 16px; }
  .demo-panel { padding: 40px 18px 20px; gap: 20px; }
  .draw-feeling-tab { left: 18px; padding: 9px 18px; font-size: 1rem; }
  .signup-inner { padding: 24px 18px; }
  h1 { font-size: clamp(1.8rem, 9vw, 2.6rem); }
  .btn-lg { padding: 12px 24px; font-size: 1.1rem; }
}

/* ---------- Identity forms ---------- */
.signup-inner {
  background: var(--bg-card);
  border-radius: var(--radius-lg);
  padding: 40px;
  text-align: center;
}
.identity-form { display: flex; flex-direction: column; gap: 14px; align-items: center; }
.oauth-row { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
.oauth-slot { min-height: 40px; }
.divider {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--text-muted);
  font-size: 1rem;
  width: 100%;
  max-width: 320px;
}
.divider::before, .divider::after { content: ''; flex: 1; height: 1px; background: rgba(159, 168, 218, 0.2); }
.email-row { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.email-row input,
.identity-form textarea {
  background: var(--bg-elevated);
  border: 1px solid rgba(159, 168, 218, 0.25);
  border-radius: 12px;
  padding: 12px 14px;
  color: var(--text-primary);
  font-family: var(--font-hand);
  font-size: 1.1rem;
  min-width: 220px;
}
.email-row input:focus,
.identity-form textarea:focus { outline: none; border-color: var(--accent-secondary); }
.identity-form textarea { width: 100%; max-width: 480px; min-height: 80px; resize: vertical; }

.form-status { margin: 0; font-size: 1rem; min-height: 1.2em; color: var(--text-secondary); }
.form-status.is-ok { color: var(--accent-earth); }
.form-status.is-error { color: var(--accent-danger); }

.apple-btn {
  background: #000; color: #fff; border: 1px solid #333; border-radius: 999px;
  padding: 9px 18px; font-family: var(--font-hand); cursor: pointer; height: 40px;
}
.apple-btn:hover { background: #111; }

/* ---------- Comments ---------- */
.comment-locked {
  max-width: 480px;
  margin: 0 auto 32px;
  background: var(--bg-surface);
  border-radius: var(--radius-md);
  padding: 20px;
  text-align: center;
  color: var(--text-secondary);
}
.comment-form[hidden] { display: none; }
.comment-form { max-width: 560px; margin: 0 auto 40px; }
.comment-form-actions { display: flex; align-items: center; gap: 16px; }
.comment-list { max-width: 560px; margin: 0 auto; display: grid; gap: 14px; }
.comment-item {
  background: var(--bg-surface);
  border: 1px solid rgba(159, 168, 218, 0.14);
  border-radius: var(--radius-sm);
  padding: 16px 18px;
}
.comment-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; }
.comment-name { color: var(--accent-secondary); font-size: 1.05rem; }
.comment-head time { color: var(--text-muted); font-size: 0.9rem; }
.comment-body { margin: 0 0 8px; color: var(--text-primary); white-space: pre-wrap; word-break: break-word; }
.comment-empty { text-align: center; color: var(--text-muted); }

.comment-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.reaction-btn {
  background: var(--bg-elevated);
  border: 1px solid rgba(159, 168, 218, 0.2);
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 0.95rem;
  color: var(--text-secondary);
  cursor: pointer;
  display: inline-flex;
  gap: 5px;
  align-items: center;
}
.reaction-btn:hover { border-color: var(--accent-secondary); }
.reaction-btn img { width: 16px; height: 16px; object-fit: contain; }
.reaction-btn.mine { border-color: var(--accent-soul); color: var(--text-primary); }
.reply-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-family: var(--font-hand);
  font-size: 0.95rem;
  text-decoration: underline;
}
.reply-list { margin-top: 12px; padding-left: 18px; border-left: 2px solid rgba(159, 168, 218, 0.15); display: grid; gap: 10px; }
.reply-form { display: none; margin-top: 10px; gap: 8px; }
.reply-form.open { display: flex; }
.reply-form textarea {
  flex: 1;
  min-height: 46px;
  background: var(--bg-elevated);
  border: 1px solid rgba(159, 168, 218, 0.25);
  border-radius: 10px;
  padding: 8px 10px;
  color: var(--text-primary);
  font-family: var(--font-hand);
}

/* ---------- Support / donate ---------- */
.support-inner {
  background: var(--bg-card);
  border-radius: var(--radius-lg);
  padding: 32px;
  text-align: center;
  max-width: 480px;
  margin: 0 auto;
}
.support-note { color: var(--text-secondary); margin: 0 0 16px; }
#donate-chips { margin-bottom: 14px; }
#donate-custom {
  width: 110px;
  background: var(--bg-elevated);
  border: 1px solid rgba(159, 168, 218, 0.25);
  border-radius: 12px;
  padding: 12px 14px;
  color: var(--text-primary);
  font-family: var(--font-hand);
  font-size: 1.1rem;
}
#donate-custom:focus { outline: none; border-color: var(--accent-secondary); }

/* ---------- Myru, peeking from behind the mystery button ---------- */
.myru-peek {
  position: fixed;
  left: 30px;
  bottom: 52px;
  z-index: 999;
  width: 34px;
  height: 34px;
  object-fit: contain;
  pointer-events: none;
  opacity: 0;
  transform: translateY(22px) scale(0.5) rotate(-8deg);
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.55));
}
.myru-peek.peeking { animation: myru-peek-bob 2.4s ease-in-out; }
@keyframes myru-peek-bob {
  0% { opacity: 0; transform: translateY(22px) scale(0.5) rotate(-8deg); }
  28% { opacity: 1; transform: translateY(-10px) scale(1) rotate(4deg); }
  70% { opacity: 1; transform: translateY(-10px) scale(1) rotate(-4deg); }
  100% { opacity: 0; transform: translateY(22px) scale(0.5) rotate(-8deg); }
}
@media (prefers-reduced-motion: reduce) {
  .myru-peek.peeking { animation: none; }
}

/* ---------- Mystery button ---------- */
.mystery-btn {
  position: fixed;
  left: 18px;
  bottom: 18px;
  z-index: 1000;
  width: 62px;
  height: 62px;
  border-radius: 50%;
  background: rgba(18, 8, 46, 0.92);
  color: var(--myru-ink);
  border: 1.2px solid rgba(255, 46, 210, 0.7);
  font-family: var(--font-hand);
  font-size: 1.6rem;
  cursor: pointer;
  box-shadow: 0 0 14px rgba(255, 46, 210, 0.28);
}
.mystery-btn:hover { box-shadow: 0 0 20px rgba(255, 46, 210, 0.45); }

/* The get-early-access link Myru's bubble carries for the mystery-button
   message — same button classes, just small enough for the bubble's own
   max-width and a little breathing room above it. */
.myru-bubble-cta { display: inline-block; margin-top: 8px; }

/* ---------- Footer ---------- */
.site-footer {
  border-top: 1px solid rgba(159, 168, 218, 0.12);
  padding: 28px 0 40px;
  color: var(--text-muted);
  font-size: 1rem;
  text-align: center;
}


/* ---------- Discord CTA ---------- */
.discord-cta {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px dashed rgba(159, 168, 218, 0.28);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.discord-blurb {
  margin: 0;
  color: var(--text-secondary);
  font-size: 1rem;
}
.btn-discord {
  background: #5865F2;
  color: #fff;
  box-shadow: 0 8px 24px rgba(88, 101, 242, 0.32);
}
.btn-discord:hover { background: #4a56d6; }
.discord-cta.is-highlight {
  transform: scale(1.03);
}
.discord-cta.is-highlight .discord-blurb { color: var(--accent-earth); }
.footer-link { color: var(--accent-secondary); text-decoration: none; }
.footer-link:hover { text-decoration: underline; }

/* ---------- Signup counters ---------- */
.signup-stats {
  margin: -6px 0 16px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 8px;
  color: var(--text-secondary);
  font-size: 1rem;
}
.signup-stats strong {
  color: var(--accent-secondary);
  font-weight: 400;
  font-size: 1.15rem;
}
.stat-sep { opacity: 0.4; }
