/* ==========================================================================
   Gradia — marketing site, scene and interaction layer

   Three registers, one lamp. Every stage on this page is a room lit by a
   single warm source at x .88 / y .20 — the same coordinates the app uses in
   both registers (GRADIA_VISUAL_SYSTEM_2026-08-16 §3). The lamp is additive
   light, never a tint, and it is the only thing that crosses a seam.

   No terrain, no landform, no decorative wash. The relief lives inside the
   screenshots, where it is the app's real object.
   ========================================================================== */

:root {
  --light-shift-y: 0px;
  --motion-control-duration: 0s;
  --motion-ease: cubic-bezier(0.32, 0.72, 0, 1);
}

/* ==========================================================================
   Registers
   ========================================================================== */

/* Nocturne — the site's lead register. Black-dominant, field lift low and
   right. The lamp is NOT in this background: it is one additive element,
   declared once below, so it can never double up into a wash. */
.night-scene {
  background:
    radial-gradient(
      ellipse 120% 70% at 62% 104%,
      var(--night-field-2) 0%,
      rgb(3 31 82 / 0%) 68%
    ),
    linear-gradient(
      180deg,
      var(--night-base) 0%,
      var(--night-field-3) 34%,
      var(--night-field-1) 68%,
      var(--night-field-4) 100%
    );
}

/* Daylight — powder calibration. Materially blue; near-white is a highlight,
   not the canvas. The chroma stays in the sky field, so the band keeps its
   colour instead of washing to white. */
.day-scene {
  background:
    radial-gradient(
      ellipse 90% 70% at 18% 0%,
      var(--day-field-1) 0%,
      rgb(105 183 246 / 32%) 34%,
      rgb(105 183 246 / 0%) 70%
    ),
    linear-gradient(
      180deg,
      var(--day-base) 0%,
      var(--day-field-3) 18%,
      var(--day-field-2) 52%,
      var(--day-field-4) 100%
    );
}

/* Focused workout — the frozen room. Blue-black. */
.focus-scene {
  background: linear-gradient(
    180deg,
    var(--focus-base) 0%,
    var(--focus-field-1) 52%,
    var(--focus-field-2) 100%
  );
}

/* The lamp travels with scroll on every stage that owns one. Same event
   light on the room and on the device's edge is what makes them one space. */
[data-motion-stage] {
  isolation: isolate;
}

/* The one warm source. It is a lamp, not a wash: a small bright core with a
   fast falloff, sitting at x .88 / y .20 exactly as it does in the app, and
   composited with `screen` so it ADDS light instead of tinting the room. A
   tint over navy goes brown, which is the failure this replaces.

   The size is in px, not %, so a wide stage gets a lamp — not a bigger lamp. */
[data-motion-stage]::before,
[data-footer-close]::before,
.hero::before {
  content: "";
  position: absolute;
  top: 32%;
  left: 86%;
  z-index: 0;
  width: 400px;
  aspect-ratio: 1;
  border-radius: 50%;
  /* `plus-lighter` is true addition. `screen` at low alpha over near-black
     desaturates toward grey-brown, which is the greige failure the craft law
     rejects outright; plus-lighter keeps the hue while it dims. The core is
     bright and small so this reads as a source, not a wash. */
  background: radial-gradient(
    circle,
    rgb(var(--lamp) / 26%) 0%,
    rgb(var(--lamp) / 12%) 22%,
    rgb(var(--lamp) / 3%) 46%,
    rgb(var(--lamp) / 0%) 70%
  );
  mix-blend-mode: plus-lighter;
  pointer-events: none;
  transform: translate3d(-50%, calc(-50% + var(--light-shift-y)), 0);
  will-change: transform;
}

/* The frozen room is darker, so the same lamp reads brighter for free. It
   gets a touch more core because effort light is present during a set. */
.focus-scene[data-motion-stage]::before {
  background: radial-gradient(
    circle,
    rgb(var(--lamp) / 34%) 0%,
    rgb(var(--lamp) / 15%) 22%,
    rgb(var(--lamp) / 4%) 46%,
    rgb(var(--lamp) / 0%) 70%
  );
}

/* In a bright room the source reads as a specular highlight, not a glow — a
   glint is a point, never a stripe. Screen over powder blue would blow out,
   so daylight uses a smaller, cooler core at lower strength. */
.day-scene[data-motion-stage]::before {
  width: 320px;
  background: radial-gradient(
    circle,
    rgb(255 255 255 / 62%) 0%,
    rgb(255 255 255 / 18%) 30%,
    rgb(255 255 255 / 0%) 66%
  );
  mix-blend-mode: soft-light;
}

/* The hero's source sits at the device's upper-right shoulder, low enough
   that what you see is the falloff across the phone rather than the core
   floating on empty navy. */
.hero::before {
  top: 40%;
  left: 80%;
  width: 640px;
}

/* No crop offset. Rivet's stages translate the device down to crop it, and
   that was copied here without checking the geometry: a 567px phone in a 412px
   content box already overflows downward on its own, so every pixel of offset
   only grew the empty room above it. Measured naturally: top gap 48px (the
   stage's own padding), bottom crop 155px. The transform now carries scroll
   parallax and nothing else. */
[data-motion-stage] .phone {
  --phone-motion-y: 0px;

  position: relative;
  z-index: 1;
  transform: translate3d(0, var(--phone-motion-y), 0);
}

/* ==========================================================================
   The pro card — completion light, because a plan bought is an earned state
   ========================================================================== */

.pro-card {
  position: relative;
  isolation: isolate;
  background: linear-gradient(
    148deg,
    var(--night-field-1) 0%,
    var(--night-field-3) 46%,
    var(--night-base) 100%
  );
}

.pro-card::before {
  content: "";
  position: absolute;
  top: 12%;
  left: 86%;
  z-index: 0;
  width: 380px;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgb(255 192 113 / 30%) 0%,
    rgb(255 192 113 / 10%) 30%,
    rgb(255 192 113 / 0%) 68%
  );
  mix-blend-mode: screen;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

.pro-card > * {
  position: relative;
  z-index: 1;
}

/* ==========================================================================
   Controls — chalk hairline capsules. One solid commit key per surface.
   ========================================================================== */

.primary-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 54px;
  padding: 0 24px;
  border: 1px solid var(--chalk);
  border-radius: var(--radius-control);
  background: var(--chalk-wash);
  box-shadow: 0 0 24px rgb(244 247 251 / 10%);
  color: var(--ink);
  font-size: 16px;
  font-weight: 500;
  text-align: center;
}

.primary-button[aria-disabled="true"] {
  border-color: var(--hairline);
  background: transparent;
  box-shadow: none;
  color: var(--ink-soft);
  cursor: default;
}

.quiet-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 20px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-control);
  color: var(--ink);
  font-size: 15px;
  font-weight: 500;
}

/* ==========================================================================
   FAQ affordance — a plus that becomes a minus, one hairline thick
   ========================================================================== */

.faq-list summary::before,
.faq-list summary::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 6px;
  width: 15px;
  height: 1.5px;
  border-radius: 2px;
  background: var(--ink-soft);
  transition:
    opacity var(--motion-control-duration) ease,
    transform var(--motion-control-duration) var(--motion-ease);
}

.faq-list summary::before {
  transform: translateY(-50%) rotate(90deg);
}

.faq-list summary::after {
  transform: translateY(-50%);
}

.faq-list details[open] summary::before {
  opacity: 0;
  transform: translateY(-50%) rotate(0deg);
}

.faq-answer,
.faq-answer > div {
  overflow: hidden;
}

/* ==========================================================================
   Interaction
   ========================================================================== */

.desktop-nav a,
.header-cta,
.primary-button,
.quiet-button,
.privacy-panel a,
.footer-meta a,
.faq-list summary {
  transition:
    color var(--motion-control-duration) ease,
    background-color var(--motion-control-duration) ease,
    border-color var(--motion-control-duration) ease,
    box-shadow var(--motion-control-duration) ease,
    transform var(--motion-control-duration) var(--motion-ease);
}

@media (hover: hover) and (pointer: fine) {
  .desktop-nav a:hover,
  .footer-meta a:hover {
    color: var(--ink);
    transform: translateY(-1px);
  }

  .header-cta:hover,
  .quiet-button:hover {
    border-color: rgb(244 247 251 / 34%);
    background: rgb(244 247 251 / 12%);
    transform: translateY(-1px);
  }

  .primary-button:not([aria-disabled="true"]):hover {
    background: rgb(244 247 251 / 14%);
    box-shadow: 0 0 32px rgb(244 247 251 / 16%);
    transform: translateY(-1px);
  }

  .privacy-panel a:hover {
    border-color: rgb(16 25 35 / 34%);
    background: rgb(255 255 255 / 62%);
    transform: translateY(-1px);
  }

  .faq-list summary:hover {
    color: var(--ink-soft);
  }
}

/* Press is its own duration in the app (MotionTokens.pressDuration 0.08) —
   faster than the hover settle, because a press must answer instantly. */
.header-cta:active,
.primary-button:not([aria-disabled="true"]):active,
.quiet-button:active,
.privacy-panel a:active {
  transform: translateY(1px) scale(0.985);
  transition-duration: var(--motion-press-duration, 0s);
}

/* ==========================================================================
   Scroll motion. Same cause, same effect — one curve, set in JS.
   ========================================================================== */

.motion-ready [data-reveal],
.motion-ready [data-stagger] > *,
.motion-ready [data-hero-item] {
  will-change: transform, opacity;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --light-shift-y: 0px;
  }

  .desktop-nav a,
  .header-cta,
  .primary-button,
  .quiet-button,
  .privacy-panel a,
  .footer-meta a,
  .faq-list summary,
  .faq-list summary::before,
  .faq-list summary::after {
    transition: none;
  }

  .motion-ready [data-reveal],
  .motion-ready [data-stagger] > *,
  .motion-ready [data-hero-item] {
    will-change: auto;
  }
}

/* Increased contrast: hairlines become real edges, exactly as the app does
   under Increased Contrast on the Today plate. */
@media (prefers-contrast: more) {
  :root {
    --hairline: rgb(244 247 251 / 42%);
    --hairline-quiet: rgb(244 247 251 / 32%);
    --hairline-day: rgb(16 25 35 / 42%);
    --ink-soft: #c8d3e2;
    --ink-quiet: #b3c0d2;
  }
}

@media (prefers-reduced-transparency: reduce) {
  .pro-purchase {
    background: var(--night-surface);
  }

  .privacy-panel {
    background: #fff;
  }
}
