:root {
  /* Warm Mercury palette · light mode default.
   * Inks intentionally NOT pure black: --ink lands on warm graphite #2A2622
   * so the page never reads as cold. Hairlines pick up a paper-cream cast
   * to match the cream squircle and the petal backdrop. */
  --bg: #F3F3F1;
  --ink: #2A2622;
  --ink-soft: #756C5F;
  --muted: #ADA396;
  --hairline: #E6E4DF;
  --surface: #FFFEFA;
  --accent: #6E7A53;

  /* Logo SVG colors. Live in CSS so the dark-mode media query can
   * recolor the visor stops and the eye fills without touching the
   * inline SVG attributes. */
  --mark-bg-top:     #FBFBF9;
  --mark-bg-bottom:  #E4E4E0;
  --mark-stroke:     #D4D4D0;
  --visor-top:       #2A2622;
  --visor-bottom:    #181614;
  --eye-fill:        #FFFFFF;
  --halo-color:      rgba(42, 38, 34, 0.12);
  --halo-color-mid:  rgba(42, 38, 34, 0.05);

  /* "open source" pitch chip. Deliberately a quiet tint so the dark
   * rotator pill in the headline stays the primary chip; this one
   * reads as a secondary squircle tag. */
  --em-chip-bg-top:    rgba(42, 38, 34, 0.09);
  --em-chip-bg-bottom: rgba(42, 38, 34, 0.05);
  --em-chip-fg:        var(--ink);

  /* Surfaces tied to a backdrop blur (overline, ghost button bg) need
   * to invert from cream-tinted glass to dark glass in dark mode. */
  --glass-bg:        rgba(255, 254, 250, 0.65);
  --glass-bg-strong: rgba(255, 254, 250, 0.90);
  --glass-border:    var(--hairline);

  /* Petal backdrop tuning. Dark mode dims the image hard so it reads
   * as a faint warm glow instead of a daylight wash. */
  --petal-opacity:   0.85;
  --petal-filter:    none;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* Suppress all transitions until the boot script has stamped
 * .theme-ready on <html> after the first paint. Without this, the
 * initial application of the data-theme attribute would cross-fade
 * from a transient state to the resolved theme on load, which reads
 * as a slow flicker. After the rAF guard, transitions kick in and
 * the lamp-pull swap animates as designed. */
:root:not(.theme-ready) *,
:root:not(.theme-ready) *::before,
:root:not(.theme-ready) *::after {
  transition: none !important;
}

html {
  background: var(--bg);
  transition: background-color 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

html, body {
  color: var(--ink);
  font-family: 'Manrope', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
  font-feature-settings: 'ss01';
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
  overflow-x: hidden;
  transition: color 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* The solid bg color lives on <html>. If <body> also had a solid bg
 * it would paint over body::before (z-index: -1) and the petal layer
 * would never show. Keep body bg-color transparent. */
body {
  background-color: transparent;
  background-image:
    radial-gradient(1200px 600px at 80% -10%, rgba(0,0,0,0.025), transparent 60%),
    radial-gradient(900px 500px at -10% 110%, rgba(0,0,0,0.02), transparent 60%);
  position: relative;
}

/* Soft blurred petal backdrop. Lives in a fixed pseudo-element behind
 * everything so it never scrolls and never affects page content sharpness.
 * Heavy blur + reduced opacity keeps it as a whisper, not a focal point.
 * scale(1.08) hides the blur edge bleed that shows through past the viewport. */
/* Cursor-driven parallax shift fed by JS. Declared on body (not on the
 * pseudo) so JS can set them with body.style.setProperty. */
body { --bg-shift-x: 0px; --bg-shift-y: 0px; }
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image: url("assets/background.png");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  filter: var(--petal-filter);
  opacity: var(--petal-opacity);
  transform: translate(var(--bg-shift-x), var(--bg-shift-y)) scale(1.08);
  transition: transform 600ms cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}

.page {
  min-height: 100vh;
  min-height: 100dvh;
  max-width: 1180px;
  margin: 0 auto;
  padding: 40px 40px 32px;
  display: grid;
  grid-template-rows: 1fr auto;
  gap: 48px;
}

/* ---------- Hero ---------- */
.hero {
  align-self: center;
  justify-self: center;
  max-width: 880px;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 26px;
}

/* ---------- Entrance choreography ----------
 * Each piece fades and lifts in sequence, riding the same smooth
 * ease-out curve (close to Apple's "ease-out expo"). The cascade is
 * deliberate: logo → status pill → headline strong line → soft line
 * → pitch → CTA → footer. Curve and duration are shared so the page
 * reads as one orchestrated motion, not as N independent animations.
 *
 * Note: .hero__title itself does NOT animate; its two inner lines do,
 * so that the headline reveals as two beats instead of one block.
 */
.hero__line,
.hero__title--soft,
.hero__pitch,
.cta,
.foot {
  opacity: 0;
  will-change: transform, opacity;
  animation: heroIn 1100ms cubic-bezier(0.22, 1, 0.36, 1) both;
  /* Held paused until the web font is ready. Without this the rotator
   * chip would render with the fallback font's narrower glyph metrics,
   * then jump wider mid-animation when the web font swaps in, which
   * reads as the chip "popping" right padding late. */
  animation-play-state: paused;
}
:root.fonts-ready .hero__line,
:root.fonts-ready .hero__title--soft,
:root.fonts-ready .hero__pitch,
:root.fonts-ready .cta,
:root.fonts-ready .foot {
  animation-play-state: running;
}
.hero__line         { animation-delay: 320ms; }
.hero__title--soft  { animation-delay: 440ms; }
.hero__pitch        { animation-delay: 580ms; }
.cta                { animation-delay: 740ms; }
.foot               { animation-delay: 900ms; }

/* ---------- Mark (animated logo) ----------
 * Two layers so that the entrance animation never collides
 * with the wobble:
 *   .mark-stage  → owns entrance (opacity + scale-in, runs once)
 *   .mark        → owns the wobble (rotate, idle = no transform)
 */
.mark-stage {
  display: inline-block;
  margin-bottom: 8px;
  animation: stageIn 1100ms 60ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

/* ---------- Lamp pull · theme toggle ----------
 * A thin cord descends from above the squircle to a small knob just
 * past its right edge. Click the knob and it yanks down with a spring,
 * the robot blinks once to mask the repaint, and the page flips theme.
 * The cord is anchored to .mark-stage so it tracks the squircle on
 * any width without extra math. */
.lamp-pull {
  position: absolute;
  /* Knob lands at ~85% of the squircle height: well below the visor,
   * down at "hip" level on the robot, but still inside the squircle's
   * visible area. The hand emerges from the squircle's right edge at
   * the same height to reach it. */
  top: -56px;
  right: -34px;
  width: 22px;
  height: 212px;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  z-index: 1;
  /* Match the entrance cascade so the cord arrives with the logo. */
  animation: stageIn 1100ms 240ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* Cord and knob ride a single --lamp-reveal var (0..1) that JS writes
 * from cursor proximity to the mark, with a ~1.2s linger after the
 * cursor leaves so the affordance does not strobe on quick passes.
 * The pull animation also forces it to 1 for its duration. */
.lamp-pull__cord {
  width: 1px;
  flex: 1;
  background: linear-gradient(180deg,
    transparent 0%,
    var(--ink-soft) 14%,
    var(--ink-soft) 100%);
  opacity: calc(var(--lamp-reveal, 0) * 0.40);
  transition: opacity 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.lamp-pull__knob {
  width: 12px;
  height: 12px;
  border-radius: 3.5px;
  background: linear-gradient(180deg, var(--ink-soft) 0%, var(--ink) 100%);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
  opacity: var(--lamp-reveal, 0);
  transform: translateY(0);
  transition: opacity 220ms cubic-bezier(0.22, 0.61, 0.36, 1),
              transform 240ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.lamp-pull:hover .lamp-pull__knob { transform: translateY(2px); }
/* Keyboard focus on the lamp pull button must reveal the cord regardless
 * of cursor position so the affordance is reachable via Tab. */
.mark-stage:has(:focus-visible) { --lamp-reveal: 1; }
/* Reduced-motion users get the lamp pinned visible: no proximity dance,
 * no fade. Cursor tracking is skipped on the JS side too. */
@media (prefers-reduced-motion: reduce) {
  :root { --lamp-reveal: 1; }
}
.lamp-pull:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: 4px;
}
/* Knob spring synced with the hand. Each segment carries its own
 * timing-function so the knob whips down on the strike, bounces past
 * neutral on the rebound, then settles. Outer animation curve is set
 * to linear so the per-keyframe curves win. */
.lamp-pull.is-pulling .lamp-pull__knob {
  animation: lampPullSpring 900ms linear;
}
@keyframes lampPullSpring {
  0% {
    animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
    transform: translateY(0);
  }
  25% {
    animation-timing-function: cubic-bezier(0.7, 0, 0.4, 1);
    transform: translateY(0);
  }
  /* yanked down with the strike */
  37% {
    animation-timing-function: cubic-bezier(0.4, 1.6, 0.5, 1);
    transform: translateY(16px);
  }
  /* springs past neutral on the rebound */
  50% {
    animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
    transform: translateY(-4px);
  }
  /* settles */
  62% {
    animation-timing-function: linear;
    transform: translateY(0);
  }
  100% { transform: translateY(0); }
}

/* ---------- Lamp hand ----------
 * Thin grey arm + small white sphere at the tip. Lives at the right
 * mid-side of the squircle. At rest it is doubly hidden:
 *   - z-index: -1 tucks it behind .mark inside .mark-stage's stacking
 *     context, so the part overlapping the squircle is painted under
 *     the cream surface;
 *   - opacity: 0 backs that up against any transient stacking quirk
 *     (a child of .mark gaining a transform during a wobble or press
 *     would otherwise pull .mark into a positioned-descendants step
 *     and the hand could leak through).
 * On toggle, both lift together: the arm fades in as it slides out,
 * reaches DOWN to the knob, strikes, returns up — that release moment
 * is when the page flips theme — then retracts and fades out. */
.lamp-hand {
  position: absolute;
  top: 50%;
  right: 0;
  width: 22px;
  height: 2px;
  margin-top: -1px;
  background: var(--ink-soft);
  border-radius: 1px;
  pointer-events: none;
  z-index: -1;
  opacity: 0;
  transform: translate(0, 0);
  /* Strike stays small. The cord/knob bounce sells the pull, the hand
   * only needs to *suggest* the gesture, not physically reach the knob.
   * --reach-x is how far the arm pokes past the squircle's right edge.
   * --tug-y is the small downward strike. Anticipation lift and rebound
   * overshoot are inlined in the keyframes for clarity. */
  --tug-y: 24px;
  --reach-x: 24px;
}
.lamp-hand::after {
  content: "";
  position: absolute;
  right: 0;
  top: 50%;
  width: 11px;
  height: 11px;
  margin-top: -5.5px;
  border-radius: 50%;
  background: var(--eye-fill);
  box-shadow:
    0 0 0 1px rgba(42, 38, 34, 0.12),
    0 1px 3px rgba(42, 38, 34, 0.24);
}

.mark-stage.is-handing .lamp-hand {
  /* Outer curve = linear so the per-keyframe timing functions win.
   * Each beat below carries its own curve for a cartoon feel. */
  animation: lampHand 900ms linear;
}

/* Cartoon choreography. animation-timing-function inside a keyframe
 * applies to the segment STARTING at that keyframe.
 *   0-6%   fade in still tucked at the body
 *   6-18%  SNAP OUT to --reach-x with a touch of overshoot
 *   18-25% ANTICIPATION lift up (-4px) before the strike
 *   25-37% WHIP DOWN to --tug-y (fast accel into a hard stop)
 *   37-50% REBOUND past neutral to -9px (cartoon overshoot up)
 *   50-62% settle to 0
 *   62-80% hold extended while the page swaps
 *   80-84% ANTICIPATION pull-out (+4px past --reach-x)
 *   84-92% WHIP back in to flush
 *   92-100% fade out */
@keyframes lampHand {
  0% {
    animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
    opacity: 0; transform: translate(0, 0);
  }
  6% {
    animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
    opacity: 1; transform: translate(0, 0);
  }
  18% {
    animation-timing-function: cubic-bezier(0.7, 0.05, 0.45, 1);
    opacity: 1; transform: translate(var(--reach-x), 0);
  }
  25% {
    animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1);
    opacity: 1; transform: translate(var(--reach-x), -4px);
  }
  37% {
    animation-timing-function: cubic-bezier(0.4, 1.6, 0.5, 1);
    opacity: 1; transform: translate(var(--reach-x), var(--tug-y));
  }
  50% {
    animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
    opacity: 1; transform: translate(var(--reach-x), -9px);
  }
  62% {
    animation-timing-function: linear;
    opacity: 1; transform: translate(var(--reach-x), 0);
  }
  80% {
    animation-timing-function: cubic-bezier(0.55, -0.4, 0.6, 1);
    opacity: 1; transform: translate(var(--reach-x), 0);
  }
  84% {
    animation-timing-function: cubic-bezier(0.7, 0, 0.4, 1);
    opacity: 1; transform: translate(calc(var(--reach-x) + 4px), 0);
  }
  92% {
    animation-timing-function: ease-in;
    opacity: 1; transform: translate(0, 0);
  }
  100% { opacity: 0; transform: translate(0, 0); }
}

.mark {
  width: 184px;
  height: 184px;
  border-radius: 48px;
  background: linear-gradient(180deg, var(--mark-bg-top) 0%, var(--mark-bg-bottom) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 1px 2px rgba(42, 38, 34, 0.08),
    0 14px 32px rgba(42, 38, 34, 0.10),
    0 40px 92px rgba(42, 38, 34, 0.06);
  transform-origin: 50% 65%;
  --wobble-dir: 1;
}

/* SVG color hooks. Inline SVG attributes have lower specificity than
 * a CSS rule, so these win and pick up the dark-mode override below. */
#visorShade stop:first-child { stop-color: var(--visor-top); }
#visorShade stop:last-child  { stop-color: var(--visor-bottom); }
.eye { fill: var(--eye-fill); }
.mark.is-wobbling {
  animation: markWobble 1400ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* Press feedback. Uses the individual `scale` property so it composes
 * with the wobble's `transform: rotate(...)` instead of stomping on it. */
.mark {
  cursor: pointer;
  transition: scale 220ms cubic-bezier(0.18, 0.89, 0.32, 1.28);
  -webkit-tap-highlight-color: transparent;
}
.mark.is-pressed {
  scale: 0.955;
  transition-duration: 110ms;
  transition-timing-function: cubic-bezier(0.32, 0, 0.18, 1);
}
.mark svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* The "look" group translates as a unit so both eyes glance together.
 * Smooth ease-out, no overshoot (overshoot read as "weird" before).
 */
.eyes__look {
  transition: transform 620ms cubic-bezier(0.22, 0.61, 0.36, 1);
  transform-box: fill-box;
  transform-origin: center;
  will-change: transform;
}

/* Each eye-group can blink/squint independently (enables winks).
 * Asymmetric easing: closes faster than it opens, like real eyelids.
 */
.eye-group {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 110ms cubic-bezier(0.4, 0, 0.6, 1);
}
.eye-group.is-blinking,
.eye-group.is-squinting {
  transition-duration: 80ms;
}

.eye-group.is-blinking  { transform: scaleY(0.05); }
.eye-group.is-squinting { transform: scaleY(0.45); }
/* Sleeping: eyes closed and held until the tab is visible again. Slower
 * transition than a blink so it reads as "settling" rather than blinking. */
.eyes.is-sleeping .eye-group {
  transform: scaleY(0.05);
  transition-duration: 360ms;
}
.eyes.is-sleeping .eye-group .eye--glow { opacity: 0; }
.mark__halo.is-dim {
  opacity: 0.45;
  transition: opacity 600ms ease-out;
}
/* Wide-eyed surprise. Slightly bigger + brighter glow.
 * Soft snap-open feel via a slightly longer transition. */
.eye-group.is-wide {
  transform: scale(1.12);
  transition-duration: 160ms;
}

.eye-group .eye--glow {
  transition: opacity 110ms cubic-bezier(0.4, 0, 0.6, 1);
}
.eye-group.is-blinking  .eye--glow { opacity: 0;    }
.eye-group.is-squinting .eye--glow { opacity: 0.45; }
.eye-group.is-wide      .eye--glow { opacity: 1;    }

.hero__title {
  font-size: clamp(38px, 6.4vw, 78px);
  font-weight: 600;
  letter-spacing: -0.044em;
  line-height: 1.08;
  color: var(--ink);
}
.hero__line,
.hero__title--soft {
  display: block;
}
.hero__title--soft {
  color: var(--ink-soft);
}

.hero__sub {
  max-width: 540px;
  margin: 0 auto;
  font-size: 17px;
  line-height: 1.5;
  letter-spacing: -0.012em;
  color: var(--ink-soft);
}

/* ---------- Word rotator ----------
 * Fixed-width squircle chip echoing the logo visor.
 * The hidden "sizer" sits in the same grid cell and pins
 * the chip width to the widest possible word, so nothing
 * around the rotator shifts when the word changes.
 */
.rotator {
  display: inline-grid;
  grid-template-areas: "stack";
  vertical-align: baseline;
  /* Slightly smaller than headline so chip reads as embedded tag,
   * not as a banner that hijacks the line. */
  font-size: 0.84em;
  padding: 0.16em 0.4em 0.24em;
  margin: 0 0.06em;
  /* border-radius is a first-paint fallback so the chip already looks
   * rounded before JS runs. JS then overlays a clip-path: path() that
   * traces a real superellipse-style squircle (continuous curvature
   * corners, like an iOS app icon). The squircle clip is always more
   * restrictive at the corners than this radius, so once JS runs the
   * squircle is the operative clip. The path is recomputed on font
   * load and resize. */
  border-radius: 0.3em;
  background: var(--rotator-bg, linear-gradient(180deg, #2F2A24 0%, #1B1814 100%));
  /* drop-shadow respects the squircle clip; box-shadow would not. */
  filter: drop-shadow(0 1px 2px rgba(0,0,0,0.12));
  line-height: 1;
  transform: translateY(-0.04em);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* `scale` composes with the inline `transform: translateY(...)` above.
   * `width` animates only when the rotation crosses between the long-word
   * group and the short-word group, so the chip retunes its width twice
   * per full loop instead of jittering on every swap. */
  transition:
    scale 220ms cubic-bezier(0.22, 0.61, 0.36, 1),
    width 540ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.rotator:hover { scale: 1.04; }
.rotator.is-jolt { animation: rotatorJolt 240ms cubic-bezier(0.4, 0, 0.6, 1); }
@keyframes rotatorJolt {
  0%   { transform: translate(0,         -0.04em); }
  20%  { transform: translate(-1.6px,    -0.04em); }
  40%  { transform: translate( 1.6px,    -0.04em); }
  60%  { transform: translate(-1.0px,    -0.04em); }
  80%  { transform: translate( 0.6px,    -0.04em); }
  100% { transform: translate(0,         -0.04em); }
}
.rotator > * {
  grid-area: stack;
  white-space: nowrap;
  text-align: center;
  color: var(--rotator-fg, #FFFFFF);
  font-weight: 600;
  letter-spacing: -0.022em;
}
.rotator__sizer {
  visibility: hidden;
}
.rotator__word {
  /* Words sit ABS-positioned in the grid cell so neither the entering
   * nor the leaving word inflates the grid track. Without this, a long
   * leaving word (e.g. "content") keeps the auto-track wide while the
   * chip shrinks to short-group width, and the leaving text overflows
   * the squircle clip on the right. The sizer (in flow) drives line
   * height; the chip's explicit width drives horizontal size. */
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-shadow: 0 0 22px rgba(255,255,255,0.18);
  transition:
    transform 420ms cubic-bezier(0.22, 0.61, 0.36, 1),
    opacity   300ms cubic-bezier(0.4, 0, 0.6, 1);
  will-change: transform, opacity;
}
.rotator__word.is-entering {
  transform: translateY(0.9em);
  opacity: 0;
}
.rotator__word.is-leaving {
  transform: translateY(-0.9em);
  opacity: 0;
}

/* ---------- Halo behind the logo ----------
 * Soft radial shadow that gives the squircle real presence
 * on the off-white background. Lives behind the logo via z-index.
 */
.mark-stage {
  position: relative;
  isolation: isolate;
}
.mark__halo {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 220%;
  height: 220%;
  transform: translate(-50%, -50%);
  background:
    radial-gradient(closest-side,
      var(--halo-color)     0%,
      var(--halo-color-mid) 32%,
      transparent           62%);
  z-index: -1;
  pointer-events: none;
  animation: haloIn 1500ms 180ms cubic-bezier(0.22, 1, 0.36, 1) both;
  /* Used by JS for the "shy" logo-press reaction: bumps the halo brightness
   * for ~600ms then it falls back via transition. Lives on top of haloIn
   * because that animation has finished by the time JS toggles this. */
  transition: filter 600ms cubic-bezier(0.22, 1, 0.36, 1);
}
.mark__halo.is-flash {
  filter: brightness(1.6) saturate(1.2);
  transition-duration: 220ms;
}
/* Slow ambient breathing on the halo so the logo never sits perfectly
 * still. Layered on top of haloIn (which has finished by this point)
 * via the same opacity property + a scale transform on the halo box. */
.mark__halo.is-breathing {
  animation: haloBreathe 6200ms ease-in-out infinite;
}
@keyframes haloBreathe {
  0%, 100% { opacity: 0.85; transform: translate(-50%, -50%) scale(1.00); }
  50%      { opacity: 1.00; transform: translate(-50%, -50%) scale(1.04); }
}
@keyframes haloIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ---------- Pitch line ---------- */
.hero__pitch {
  font-size: 18px;
  line-height: 1.55;
  letter-spacing: -0.012em;
  color: var(--ink);
  max-width: 560px;
  margin: -6px auto 0;
}
/* "open source" chip: same squircle treatment as the rotator above.
 * Inline-block so it can ride inside the paragraph. JS overlays a
 * clip-path: path() with continuous-curvature corners (recomputed on
 * font load + resize); the border-radius is just the first-paint
 * fallback. */
.hero__pitch em {
  display: inline-block;
  font-style: normal;
  font-weight: 600;
  font-size: 0.96em;
  letter-spacing: -0.022em;
  line-height: 1;
  white-space: nowrap;
  /* Generous top/bottom padding so the squircle corners have actual
   * height to curve through. Without this the chip is too short and
   * the squircle reads as a plain rounded rect. */
  padding: 0.34em 0.7em 0.4em;
  margin: 0 0.08em;
  /* Border-radius fallback only kicks in before JS runs. The clip-path
   * applied by JS is a pill-squircle (continuous curvature, corners
   * reach the vertical mid-edges) and overrides the radius. */
  border-radius: 999px;
  background: linear-gradient(180deg, var(--em-chip-bg-top) 0%, var(--em-chip-bg-bottom) 100%);
  color: var(--em-chip-fg);
  /* drop-shadow respects the squircle clip and gives the chip a hint of
   * lift without competing with the rotator pill above. */
  filter: drop-shadow(0 1px 1px rgba(0,0,0,0.06));
  transform: translateY(-0.1em);
  cursor: pointer;
  position: relative;
  -webkit-tap-highlight-color: transparent;
  transition: scale 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.hero__pitch em:hover { scale: 1.04; }
.hero__pitch em.is-pulsing {
  animation: pitchPulse 720ms cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes pitchPulse {
  0%, 100% { scale: 1; }
  45%      { scale: 1.07; }
}
/* Sparks: tiny dots flung from the em on click, fade and rise.
 * Appended to <body> (not the em) so the chip's squircle clip-path
 * doesn't clip them as they fly out. */
.hero__spark {
  position: fixed;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--ink);
  pointer-events: none;
  opacity: 1;
  transform: translate(-50%, -50%);
  will-change: transform, opacity;
  z-index: 5;
}
.hero__spark.is-out {
  opacity: 0;
  transition:
    transform 520ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity   520ms ease-out;
}
.hero__pitch-soft {
  display: block;
  margin-top: 6px;
  color: var(--ink-soft);
  font-size: 15px;
  letter-spacing: -0.008em;
}

/* ---------- Terminal block ---------- */
.terminal {
  width: 100%;
  max-width: 520px;
  margin: 0 auto;
  background: linear-gradient(180deg, #1A1815 0%, #0E0D0B 100%);
  border-radius: 14px;
  overflow: hidden;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.06),
    0 1px 2px rgba(28,24,18,0.10),
    0 18px 44px rgba(28,24,18,0.18);
  font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  text-align: left;
}
.terminal__bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 10px 14px 9px;
  background: rgba(255,255,255,0.025);
  border-bottom: 1px solid rgba(255,255,255,0.05);
}
.terminal__dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: rgba(255,255,255,0.16);
}
.terminal__title {
  margin-left: auto;
  font-size: 11px;
  color: rgba(255,255,255,0.36);
  letter-spacing: 0.04em;
}
.terminal__body {
  padding: 18px 18px 22px;
  display: flex;
  align-items: center;
  gap: 10px;
  color: rgba(255,255,255,0.92);
  font-size: 14px;
  letter-spacing: 0;
}
.terminal__prompt {
  color: rgba(255,255,255,0.4);
}
.terminal__caret {
  display: inline-block;
  width: 7px;
  height: 16px;
  margin-left: -4px;
  background: rgba(255,255,255,0.7);
  animation: termCaret 1.05s steps(2) infinite;
}
@keyframes termCaret { 50% { opacity: 0; } }

/* ---------- CTA · Download + GitHub ---------- */
.cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-top: 4px;
}
.cta__row {
  display: flex;
  align-items: stretch;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  font-family: inherit;
  font-weight: 500;
  letter-spacing: -0.01em;
  border-radius: 999px;
  transition:
    transform 200ms cubic-bezier(0.22, 0.61, 0.36, 1),
    background 200ms ease,
    border-color 200ms ease,
    color 200ms ease,
    box-shadow 260ms ease;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.btn:active {
  transform: scale(0.98);
}

.btn--primary {
  padding: 11px 22px 11px 18px;
  background: var(--btn-primary-bg, var(--ink));
  color: var(--btn-primary-fg, #FFFFFF);
  font-size: 15px;
  box-shadow:
    inset 0 1px 0 var(--btn-primary-highlight, rgba(255,255,255,0.10)),
    0 1px 2px var(--btn-primary-shadow-near, rgba(42,38,34,0.14)),
    0 10px 26px var(--btn-primary-shadow-far,  rgba(42,38,34,0.22));
}
.btn--primary:hover {
  background: var(--btn-primary-bg-hover, #38332D);
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 var(--btn-primary-highlight-hover, rgba(255,255,255,0.12)),
    0 2px 4px var(--btn-primary-shadow-near-hover, rgba(42,38,34,0.16)),
    0 16px 34px var(--btn-primary-shadow-far-hover,  rgba(42,38,34,0.28));
}
.btn--primary:active {
  transform: scale(0.98) translateY(0);
}

.btn--ghost {
  padding: 10px 18px;
  background: var(--glass-bg);
  color: var(--ink);
  font-size: 14px;
  border: 1px solid var(--glass-border);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 1px 1px rgba(42,38,34,0.03);
}
.btn--ghost:hover {
  background: var(--glass-bg-strong);
  border-color: var(--btn-ghost-border-hover, #D4CDC0);
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(42,38,34,0.08);
}

.btn__icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}
.btn__icon--apple {
  width: 22px;
  height: 22px;
  margin-top: -1px;
}
/* Tiny icon micro-animations toggled by JS on hover/click for life. */
.btn__icon.is-bouncing {
  animation: iconBounce 420ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.btn__icon.is-wagging {
  animation: iconWag 380ms ease-in-out;
  transform-origin: 50% 50%;
}
@keyframes iconBounce {
  0%   { transform: translateY(0); }
  40%  { transform: translateY(-3px); }
  100% { transform: translateY(0); }
}
@keyframes iconWag {
  0%   { transform: rotate(0); }
  25%  { transform: rotate(-12deg); }
  50%  { transform: rotate(12deg); }
  75%  { transform: rotate(-6deg); }
  100% { transform: rotate(0); }
}

.btn__label {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.05;
  gap: 2px;
}
.btn__label-top {
  font-size: 15px;
}
.btn__label-sub {
  font-size: 11px;
  font-weight: 400;
  letter-spacing: -0.005em;
  color: var(--btn-primary-sub-fg, rgba(255,255,255,0.6));
}

.cta__meta {
  font-size: 13px;
  font-weight: 400;
  letter-spacing: -0.012em;
  color: var(--ink-soft);
}

/* ---------- Footer ---------- */
.foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 12px;
  color: var(--muted);
}
.foot__badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--glass-bg);
  color: var(--ink);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-decoration: none;
  transition: border-color 180ms ease, color 180ms ease, background 180ms ease;
}
.foot__badge:hover {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--bg);
}
.foot__copy {
  letter-spacing: 0.01em;
}

/* ---------- Ambient stage ----------
 * Single fixed layer that holds drifting ambient pieces (leaves only).
 * Pointer-events: none so it never intercepts clicks. */
.creature-stage {
  position: fixed;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 8;
}
.creature {
  position: absolute;
  left: 0;
  top: 0;
  will-change: transform, opacity;
  opacity: 0;
  transition: opacity 380ms ease-out;
  transform-origin: 50% 50%;
}
.creature.is-in  { opacity: 1; }
.creature.is-out { opacity: 0; transition-duration: 580ms; }

/* ---------- Leaf ----------
 * Three blade silhouettes (lens, oak, narrow) in a small autumn
 * palette. Color is set per-leaf via inline custom properties so each
 * leaf can pick its own hue without bloating the stylesheet. */
.leaf { display: block; width: 100%; height: 100%; overflow: visible; }
.leaf__blade { fill:   var(--leaf-blade, rgba(120, 92, 60, 0.55)); }
.leaf__stem  { stroke: var(--leaf-stem,  rgba(80,  60, 40, 0.60)); }

/* ---------- Click ripple ----------
 * A single thin ring that expands and fades from any click point.
 * Lives at the very top of the stack so it shows over the grain too.
 * Pointer-events: none so it never intercepts the actual click. */
.click-ripple {
  position: fixed;
  width: 36px;
  height: 36px;
  margin: -18px 0 0 -18px;     /* center on (left, top) */
  border-radius: 50%;
  border: 1px solid rgba(10, 10, 10, 0.34);
  pointer-events: none;
  opacity: 1;
  transform: scale(0.5);
  z-index: 9000;
  will-change: transform, opacity;
}
.click-ripple.is-out {
  transform: scale(1.6);
  opacity: 0;
  transition:
    transform 540ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity   540ms ease-out;
}

/* ---------- Grain ---------- */
.grain {
  position: fixed;
  inset: 0;
  pointer-events: none;
  opacity: 0.03;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  z-index: 9999;
}

/* ---------- Animations ---------- */
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes heroIn {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0);    }
}
@keyframes caret {
  50% { opacity: 0; }
}
@keyframes stageIn {
  0%   { opacity: 0; transform: translateY(8px) scale(0.965); }
  100% { opacity: 1; transform: translateY(0)   scale(1);     }
}
/* Subtle wobble. Starts AND ends at rotate(0) so removing the class
 * leaves no transform behind, hence no flash/jump. Direction picked
 * via custom property so it isn't always the same. */
@keyframes markWobble {
  0%   { transform: rotate(0deg); }
  22%  { transform: rotate(calc(var(--wobble-dir) * -1.8deg)); }
  44%  { transform: rotate(calc(var(--wobble-dir) *  1.4deg)); }
  66%  { transform: rotate(calc(var(--wobble-dir) * -0.7deg)); }
  84%  { transform: rotate(calc(var(--wobble-dir) *  0.3deg)); }
  100% { transform: rotate(0deg); }
}

/* ---------- Responsive ---------- */
@media (max-width: 640px) {
  /* Switch to auto + 1fr so the hero hugs the top instead of being
   * vertically centered inside a 100dvh row, which on a phone leaves
   * a third of the screen blank above the logo. Footer rides the
   * bottom of the 1fr row. */
  .page {
    padding: 96px 18px 20px;
    gap: 20px;
    grid-template-rows: auto 1fr;
  }
  .hero {
    align-self: start;
    gap: 26px;
  }

  /* Tighter headline. Two-line wrap is fine; what we don't want is a
   * 38px headline eating five lines of vertical space. */
  .hero__title { font-size: 34px; letter-spacing: -0.038em; line-height: 1.1; }
  .hero__pitch { font-size: 16px; line-height: 1.5; max-width: 360px; }
  .hero__pitch-soft { font-size: 14px; }
  .hero__sub { font-size: 16px; }

  /* Smaller mark so the headline actually shows above the fold. */
  .mark { width: 108px; height: 108px; border-radius: 28px; }

  /* On a phone the cord is hidden at rest (no proximity tracking on
   * touch, and a stray dot below the squircle reads as a glitch) but
   * the toggle choreography itself stays available. .is-pulling /
   * .is-handing reveal them sized for the smaller 108px mark while
   * the auto-demo or a robot tap runs, then they hide again. The
   * stage-in entrance is killed so the cord doesn't fade in from
   * below the first time it appears mid-animation. */
  .lamp-pull,
  .lamp-hand { display: none; animation: none; }
  .lamp-pull.is-pulling,
  .mark-stage.is-auto-demo .lamp-pull {
    display: flex;
    pointer-events: none;
    top: -32px;
    right: -24px;
    width: 18px;
    height: 124px;
  }
  .mark-stage.is-handing .lamp-hand {
    display: block;
    width: 18px;
    --tug-y: 16px;
    --reach-x: 18px;
  }

  /* CTA: stack full-width pills with a real tap target. The two-line
   * label ("Download for Mac" + sub) stays inside the primary pill,
   * but the pill itself is centered + wide so nothing wraps awkwardly. */
  .cta { gap: 12px; width: 100%; }
  .cta__row {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 10px;
    width: 100%;
    max-width: 360px;
    margin: 0 auto;
  }
  .btn {
    width: 100%;
    justify-content: center;
    min-height: 52px;
  }
  .btn--primary { padding: 10px 22px; }
  .btn--ghost   { padding: 12px 22px; min-height: 48px; }
  .btn__label   { align-items: flex-start; }
  .btn__label-sub { font-size: 11px; }
  .cta__meta {
    font-size: 12px;
    text-align: center;
    max-width: 320px;
  }

  .foot { gap: 10px; }
}

/* On any non-hover (touch) device, the cord is decorative — the mark
 * itself is the toggle target. Hide both at rest (so they don't take
 * focus or render as stray dots), but reveal them while the toggle
 * animation runs so the auto-demo and tap-driven choreography both
 * read. Cord stays pointer-events:none even when shown so it can't
 * intercept taps meant for the squircle. Tablet portrait > 640px keeps
 * the desktop sizing; phones override it inside the max-width block. */
@media (hover: none) {
  .lamp-pull,
  .lamp-hand { display: none; animation: none; }
  .lamp-pull.is-pulling,
  .mark-stage.is-auto-demo .lamp-pull {
    display: flex;
    pointer-events: none;
  }
  .mark-stage.is-handing .lamp-hand { display: block; }
  /* Auto-demo cord reveal. The silhouette has the slow 2s reveal beat;
   * the cord is the fast follow-up that lands just before the strike,
   * so its transition is much shorter than the silhouette's. Override
   * the base 220ms (which would feel snappy/abrupt right after the
   * slow silhouette) to a still-deliberate but quick 600ms. */
  .mark-stage.is-auto-demo .lamp-pull__cord,
  .mark-stage.is-auto-demo .lamp-pull__knob {
    transition: opacity 600ms cubic-bezier(0.22, 1, 0.36, 1);
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

/* ---------- Dark mode (Premium warm) ----------
 * Inverts the page into a warm near-black with a cream foreground.
 * The logo recolors entirely: dark-warm squircle, near-black visor,
 * cream eyes, amber halo. Primary CTA flips to a cream pill with
 * dark text so it still reads as the most clickable thing on screen.
 * The petal backdrop dims hard so it stays as a glow, not a wallpaper.
 *
 * Driven by data-theme on <html>, set synchronously in the head before
 * first paint. The boot script reads localStorage["clawix-theme"], or
 * falls back to prefers-color-scheme when there's no override. So this
 * block also handles the system-dark default; no separate media query.
 */
:root[data-theme="dark"] {
    --bg:         #0F0E0C;
    --ink:        #FAF7F0;
    --ink-soft:   #B0AAA0;
    --muted:      #6E665C;
    --hairline:   #1F1C19;
    --surface:    #16140F;
    --accent:     #E8A04E;

    --mark-bg-top:    #1F1C18;
    --mark-bg-bottom: #0E0C0A;
    --mark-stroke:    #2A2622;
    --visor-top:      #08070A;
    --visor-bottom:   #020205;
    --eye-fill:       #FAF7EE;
    --halo-color:     rgba(232, 160, 78, 0.20);
    --halo-color-mid: rgba(232, 160, 78, 0.07);

    --em-chip-bg-top:    rgba(250, 247, 240, 0.11);
    --em-chip-bg-bottom: rgba(250, 247, 240, 0.06);
    --em-chip-fg:        var(--ink);

    --glass-bg:        rgba(20, 18, 16, 0.55);
    --glass-bg-strong: rgba(28, 25, 22, 0.85);
    --glass-border:    rgba(255, 247, 234, 0.10);

    --petal-opacity: 0.18;
    --petal-filter:  blur(24px) saturate(0.5) brightness(0.5);

    /* Rotator chip flips: cream pill, dark text. Reads as a soft inset
     * tag against the dark headline, mirroring the primary CTA. */
    --rotator-bg: linear-gradient(180deg, #FAF7F0 0%, #E5DCC8 100%);
    --rotator-fg: #15110A;

    /* Primary CTA: cream pill on dark page. Sub-label uses the muted
     * warm tone so it sits below the main label without competing. */
    --btn-primary-bg:           #FAF7F0;
    --btn-primary-bg-hover:     #FFFFFF;
    --btn-primary-fg:           #15110A;
    --btn-primary-highlight:    rgba(255, 255, 255, 0.50);
    --btn-primary-highlight-hover: rgba(255, 255, 255, 0.65);
    --btn-primary-shadow-near:  rgba(0, 0, 0, 0.40);
    --btn-primary-shadow-far:   rgba(0, 0, 0, 0.40);
    --btn-primary-shadow-near-hover: rgba(0, 0, 0, 0.50);
    --btn-primary-shadow-far-hover:  rgba(0, 0, 0, 0.55);
    --btn-primary-sub-fg:       rgba(21, 17, 10, 0.55);

    --btn-ghost-border-hover: rgba(255, 247, 234, 0.20);
}

/* In dark mode the robot "sleeps in the dark": only the eyes glow at
 * rest. Body, visor, halo and cord all share a single opacity var
 * --mark-reveal that JS writes from cursor distance to the mark.
 * Far cursor → 0 (invisible), near/over the mark → 1 (full dark-mode
 * look). Without JS (reduce-motion, no pointer) :hover and :focus-within
 * still drive the var to 1 so the affordance keeps working.
 *
 * The body chrome lives on a ::before so its gradient + shadow can
 * fade via opacity (gradient backgrounds don't transition). Without
 * isolating .mark and tucking ::before to z-index:-1, the absolute
 * pseudo paints on top of the static SVG and would cover the eyes. */
:root[data-theme="dark"] .mark {
  background: transparent;
  box-shadow: none;
  position: relative;
  isolation: isolate;
}
:root[data-theme="dark"] .mark-stage:hover,
:root[data-theme="dark"] .mark-stage:has(:focus-visible) {
  --mark-reveal: 1;
}
:root[data-theme="dark"] .mark::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(180deg, var(--mark-bg-top) 0%, var(--mark-bg-bottom) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 247, 234, 0.05),
    0 0 0 1px rgba(255, 247, 234, 0.04),
    0 10px 26px rgba(0, 0, 0, 0.50),
    0 36px 80px rgba(0, 0, 0, 0.40);
  opacity: var(--mark-reveal, 0);
  transition: opacity 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
  pointer-events: none;
  z-index: -1;
}
:root[data-theme="dark"] .visor {
  opacity: var(--mark-reveal, 0);
  transition: opacity 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
:root[data-theme="dark"] .mark__halo,
:root[data-theme="dark"] .mark__halo.is-breathing {
  animation: none;
  opacity: var(--mark-reveal, 0);
  transition: opacity 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* Cord and knob already obey --lamp-reveal in the base styles, which
 * works the same in dark mode. No theme-specific override needed. */

/* Auto-demo silhouette reveal (touch-only, set by JS at t=2000ms).
 * In dark mode the body, visor and halo all hang off --mark-reveal,
 * which on desktop is driven by cursor distance with a 220ms transition
 * for snap responsiveness. Mobile has no cursor, so the auto-demo pins
 * the var to 1 to "draw" the silhouette into view; we slow the
 * transition to 2000ms so it reads as the robot waking up rather than
 * popping in. The cord beat follows on its own faster transition. */
:root[data-theme="dark"] .mark-stage.is-auto-demo .mark::before,
:root[data-theme="dark"] .mark-stage.is-auto-demo .visor,
:root[data-theme="dark"] .mark-stage.is-auto-demo .mark__halo {
  transition: opacity 2000ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Subtle film over the page so the noise overlay keeps texture
 * but never lifts to a dusty gray. */
:root[data-theme="dark"] .grain {
  mix-blend-mode: overlay;
  opacity: 0.06;
}

/* Foot badge inverted hover: on dark, the cream pill flips to dark
 * cream, not "ink-on-bg" which would invert the wrong way. */
:root[data-theme="dark"] .foot__badge:hover {
  background: var(--ink);
  color: var(--bg);
}
