/* ===========================================================
   hearthlyapps — shared design system, v2
   "Another universe" theme: a single persistent WebGL canvas (the phone,
   five 3D objects, and an animated aurora/nebula background, all rendered
   together in assets/js/scene.js so lighting stays consistent) sits fixed
   behind the whole page. Every section here is a translucent glass panel
   floating over that canvas, not an opaque background — the canvas must
   stay visible everywhere for the "flowing through the whole page" effect
   to read, not just in one hero section.
   =========================================================== */

:root {
  --teal: #2fb89f;
  --teal-dark: #1a7364;
  --teal-glow: rgba(47, 184, 159, 0.35);
  --ink: #f3f4f8;
  --ink-soft: rgba(243, 244, 248, 0.68);
  --ink-faint: rgba(243, 244, 248, 0.45);
  --glass-bg: rgba(18, 14, 38, 0.46);
  --glass-bg-strong: rgba(14, 10, 30, 0.66);
  --glass-border: rgba(255, 255, 255, 0.14);
  --white: #ffffff;
  --line: rgba(255, 255, 255, 0.14);
  --radius-lg: 28px;
  --radius-md: 18px;
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI",
    Roboto, Helvetica, Arial, sans-serif;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: var(--font);
  color: var(--ink);
  /* Deep space base color behind the canvas, visible for a frame before
     WebGL initializes and as a safety net if it fails entirely — never
     literal black, so a canvas failure doesn't read as "broken," just
     "a bit flatter than intended." */
  background: #0a0715;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* The persistent WebGL world: fixed, full-viewport, always behind
   document content, never scrolls with the page (scroll position is read
   by scene.js via window.scrollY instead, same numbers the DOM uses). */
#scene-canvas {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  /* dvh (dynamic viewport height) tracks the *actual* visible area on
     mobile Safari, which shrinks/grows as its address bar and bottom
     toolbar collapse or reappear. Plain 100vh there is fixed to the
     largest possible viewport (toolbars fully collapsed), so a real page
     load with the toolbar still showing renders shorter than what 100vh
     assumes — Safari support only, so this line is layered on top of the
     100vh fallback above rather than replacing it. */
  height: 100dvh;
  z-index: -1;
  display: block;
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: inherit;
  text-decoration: none;
}

.container {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 24px;
}

/* ---------- Nav ---------- */

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 28px;
  color: var(--ink);
  transition: background 0.4s var(--ease-out), backdrop-filter 0.4s var(--ease-out),
    border-color 0.4s var(--ease-out), padding 0.4s var(--ease-out);
  border-bottom: 1px solid transparent;
}

.nav.is-solid {
  background: rgba(10, 7, 21, 0.62);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--glass-border);
  padding: 12px 28px;
}

.nav-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  font-size: 15px;
  letter-spacing: -0.01em;
}

.nav-brand img {
  width: 24px;
  height: 24px;
  border-radius: 6px;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 28px;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-soft);
}

.nav-links a:hover {
  color: var(--white);
}

.nav-cta {
  padding: 7px 16px;
  border-radius: 100px;
  background: var(--teal);
  color: #041712 !important;
  font-weight: 600;
  font-size: 13px;
}

/* ---------- Section scaffolding ----------
   One consistent treatment everywhere now — no more light/white/dark
   section variants. Every section is transparent so the canvas behind it
   stays visible; content that needs contrast (cards) gets its own glass
   background instead of the section carrying an opaque one. */

section {
  position: relative;
}

.eyebrow {
  display: inline-block;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--teal);
  margin-bottom: 16px;
}

h1,
h2,
h3 {
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin: 0;
  color: var(--ink);
}

.display-1 {
  font-size: clamp(40px, 7vw, 88px);
}

.display-2 {
  font-size: clamp(32px, 5vw, 56px);
}

.display-3 {
  font-size: clamp(24px, 3.4vw, 36px);
}

p.lede {
  font-size: clamp(18px, 2.2vw, 24px);
  line-height: 1.5;
  color: var(--ink-soft);
  font-weight: 400;
  max-width: 640px;
}

.center {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 14px 28px;
  border-radius: 100px;
  font-weight: 600;
  font-size: 16px;
  transition: transform 0.25s var(--ease-out), box-shadow 0.25s var(--ease-out),
    background 0.25s var(--ease-out);
}

.btn-primary {
  background: var(--teal);
  color: #041712;
}

.btn-primary:hover {
  background: #3fd0b4;
  transform: translateY(-2px);
  box-shadow: 0 14px 30px var(--teal-glow);
}

.btn-ghost {
  background: rgba(255, 255, 255, 0.08);
  color: var(--white);
  border: 1px solid var(--glass-border);
}

.btn-ghost:hover {
  background: rgba(255, 255, 255, 0.14);
  transform: translateY(-2px);
}

.btn-disabled {
  background: rgba(255, 255, 255, 0.06);
  color: var(--ink-faint);
  cursor: default;
  font-size: 14px;
  padding: 12px 22px;
  border: 1px solid var(--glass-border);
}

/* ---------- Waitlist form (pre-launch email capture) ----------
   Static site, no backend — posts directly to Formspree (or whatever
   form-handling service the action URL points at). Styled to sit in the
   same .btn-row spot the old disabled "Coming soon" button used to. */
.waitlist-form {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
  /* The form sits inside .btn-row, which centers the form itself as a
     whole within its row (.hero .btn-row's justify-content: center), but
     that doesn't touch how the form's OWN children (the email input and
     the button) line up inside it. Without this, flexbox's default
     justify-content: flex-start left-aligns the input+button as a pair —
     visible on a narrow phone screen once the input drops to its own line,
     where the input and the button below it both hugged the left edge
     instead of sitting centered like the rest of the hero content. */
  justify-content: center;
}

.waitlist-input {
  padding: 13px 20px;
  border-radius: 100px;
  border: 1px solid var(--glass-border);
  background: var(--glass-bg);
  color: var(--white);
  font-size: 15px;
  font-family: var(--font);
  min-width: 240px;
  outline: none;
  transition: border-color 0.2s var(--ease-out), background 0.2s var(--ease-out);
}

.waitlist-input::placeholder {
  color: var(--ink-faint);
}

.waitlist-input:focus {
  border-color: var(--teal);
  background: rgba(47, 184, 159, 0.1);
}

.waitlist-status {
  margin-top: 14px;
  font-size: 14px;
  color: var(--ink-soft);
  min-height: 1.2em;
}

.waitlist-status.is-success {
  color: var(--teal);
}

.waitlist-status.is-error {
  color: #ff8a8a;
}

.btn-row {
  display: flex;
  gap: 14px;
  align-items: center;
  flex-wrap: wrap;
}

/* ---------- Hero ---------- */

.hero {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Anchored near the top rather than vertically centered: the resting
     phone position (scene.js, the "hero" branch of updatePhoneAndObjects)
     sits in the lower half of the viewport specifically so the two never
     compete for the same vertical band. A centered hero text block and a
     centered phone both fighting for the middle of the screen was the
     actual overlap bug, not a z-index/stacking issue. */
  justify-content: flex-start;
  text-align: center;
  padding: 120px 24px 80px;
  position: relative;
  overflow: hidden;
}

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

/* No default opacity:0 here on purpose — content must render visible even
   if the GSAP CDN scripts fail to load or are slow. main.js uses
   gsap.from(), which sets the hidden starting point via JS immediately
   before animating, only once we already know GSAP is actually available.
   A JS hiccup means "no entrance animation," never "permanently blank
   page." */
.hero h1 {
  max-width: 900px;
  text-shadow: 0 4px 40px rgba(0, 0, 0, 0.4);
}

.hero p.lede {
  margin-top: 22px;
}

.hero .btn-row {
  margin-top: 34px;
  justify-content: center;
}

/* ---------- Cinematic reel ----------
   The phone and the five 3D objects are no longer DOM elements — they're
   rendered by scene.js in the persistent #scene-canvas, which reads each
   .reel-track's position/height and its data-screens / data-objects
   attributes directly. This section's only DOM job now is (a) taking up
   scroll height so scrolling through it drives the 3D choreography, and
   (b) showing the caption text, pinned via position: sticky while its
   .reel-track is in view — much simpler than the old version, which also
   had to lay out the phone-frame and object icons here. */

.reel-track {
  position: relative;
  height: calc(var(--steps, 4) * 130vh);
}

.reel-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 16vh;
  pointer-events: none;
}

/* .reel-caption itself no longer does any positioning — the pane's actual
   screen position is now computed by scene.js from a real 3D anchor point
   (see updatePhoneAndObjects' "attach the pane to the phone" block), not a
   CSS percentage guess. This element just holds the per-step [data-step]
   wrappers, which now only control opacity (the pane inside is what's
   actually visible and positioned). */
.reel-caption {
  position: absolute;
  inset: 0;
  z-index: 5;
}

.reel-caption .eyebrow {
  margin-bottom: 10px;
}

.reel-caption h2 {
  font-size: clamp(22px, 3.6vw, 38px);
  text-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);
}

/* The description paragraph added below each step's headline — a fuller
   sentence or two on what that screen actually does, not just the short
   headline. */
.caption-desc {
  margin: 14px 0 0;
  font-size: 15.5px;
  line-height: 1.6;
  color: var(--ink-soft);
  font-weight: 400;
}

/* The tinted glass pane the caption content lives in. Position comes from
   the [data-step] corner class below (a plain CSS layout, not a per-frame
   JS pixel calculation) — the pane's own transform only ever adds a subtle
   tilt tied to the phone's rotation (set by scene.js), damped well below
   the phone's actual tilt so the text stays comfortably readable while
   still feeling attached to it. */
.caption-pane {
  width: min(520px, 88vw);
  background: var(--glass-bg-strong);
  border: 1px solid var(--glass-border);
  backdrop-filter: blur(20px) saturate(150%);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  border-radius: var(--radius-md);
  padding: 34px 38px;
  transform-style: preserve-3d;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35);
  will-change: transform;
  transition: transform 0.4s var(--ease-out);
}

.reel-caption [data-step] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s var(--ease-out), transform 0.6s var(--ease-out);
}

.reel-caption [data-step].is-active {
  opacity: 1;
  pointer-events: auto;
}

.reel-caption [data-step].pos-tl {
  top: 14%;
  left: 6%;
  text-align: left;
}

.reel-caption [data-step].pos-tr {
  top: 14%;
  right: 6%;
  text-align: right;
}

.reel-caption [data-step].pos-bl {
  bottom: 14%;
  left: 6%;
  text-align: left;
}

.reel-caption [data-step].pos-br {
  bottom: 14%;
  right: 6%;
  text-align: right;
}

.reel-caption [data-step].pos-c {
  top: 9%;
  left: 50%;
  transform: translateX(-50%);
  text-align: center;
}

.reel-caption [data-step].pos-c .caption-pane {
  width: min(600px, 88vw);
}

/* On mobile every step's caption re-centers horizontally (no room for
   left/right corners on a narrow screen), but it's critical that each one
   KEEPS whichever vertical half (top vs bottom) it originally had, not
   collapse to one shared edge. Each reel step alternates the phone between
   the upper and lower half of the screen (scene.js's STEP_LAYOUTS), with
   the caption always placed in the *opposite* half so the two never
   overlap — pos-tl/pos-tr are the steps where the phone sits low (so the
   caption stays up top), pos-bl/pos-br/pos-c are the steps where the phone
   sits high or dead-center-low (so the caption stays at the bottom, or for
   pos-c, at the top — see its own rule above). A single earlier version of
   this media query forced every one of these to `bottom: 8%` regardless of
   which half its paired phone position used, which broke that opposite-side
   pairing for 3 of the 5 reel steps: on an actual narrow phone (where the
   longer captions also wrap onto more lines and grow taller), the
   bottom-anchored caption pane grew tall enough to visually cover most of
   the phone sitting in that same lower half, leaving only a sliver of the
   phone peeking out above the pane — most noticeable on the last
   (maintenance) step, whose phone position and caption both live in/near
   the lower half by design. */
@media (max-width: 640px) {
  .reel-caption [data-step].pos-tl,
  .reel-caption [data-step].pos-tr {
    top: 8%;
    bottom: auto;
    left: 50%;
    right: auto;
    transform: translateX(-50%);
    width: 92%;
    text-align: center;
  }

  .reel-caption [data-step].pos-bl,
  .reel-caption [data-step].pos-br {
    bottom: 8%;
    top: auto;
    left: 50%;
    right: auto;
    transform: translateX(-50%);
    width: 92%;
    text-align: center;
  }

  .reel-caption [data-step].pos-c {
    left: 50%;
    transform: translateX(-50%);
    width: 92%;
    text-align: center;
  }

  .reel-caption [data-step].pos-c .caption-pane {
    width: 100%;
  }

  /* Extra safety margin: even with the correct half restored above, a
     long caption (the maintenance step's three short paragraphs, in
     particular) can still wrap onto enough lines on a narrow phone to grow
     tall and creep toward the middle of the screen where the phone sits.
     Trimming the pane's own padding/type-scale on narrow screens keeps
     every caption noticeably more compact without touching the copy
     itself. */
  .caption-pane {
    padding: 24px 26px;
  }

  .reel-caption h2 {
    font-size: clamp(20px, 5.5vw, 26px);
  }

  .caption-desc {
    font-size: 14px;
    line-height: 1.5;
    margin-top: 10px;
  }
}

/* ---------- Glass cards (feature grid, coming-soon, pricing) ---------- */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 20px;
  margin-top: 56px;
}

.glass-card {
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-md);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
}

.feature-card {
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  border-radius: var(--radius-md);
  padding: 32px 28px;
}

.feature-card .icon {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: rgba(47, 184, 159, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 18px;
  color: var(--teal);
}

.feature-card h3 {
  font-size: 19px;
  margin-bottom: 8px;
  letter-spacing: -0.01em;
}

.feature-card p {
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink-soft);
  margin: 0;
}

/* ---------- Reveal-on-scroll (generic, non-pinned sections) ----------
   Same reasoning as .hero above: no default opacity:0 here. JS applies the
   hidden starting state itself via gsap.from() only when GSAP is confirmed
   available, so a failed/slow CDN script never leaves whole sections
   invisible. */

/* ---------- Coming-soon cards ---------- */

.soon-card {
  background: var(--glass-bg);
  border: 1px dashed var(--glass-border);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  border-radius: var(--radius-md);
  padding: 28px;
}

.soon-card .tag {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--teal);
  background: rgba(47, 184, 159, 0.16);
  padding: 4px 10px;
  border-radius: 100px;
  margin-bottom: 14px;
}

.soon-card h3 {
  font-size: 18px;
  margin-bottom: 8px;
}

.soon-card p {
  font-size: 14px;
  color: var(--ink-soft);
  line-height: 1.5;
  margin: 0;
}

/* ---------- Pricing ---------- */

.price-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 20px;
  margin-top: 48px;
}

.price-card {
  background: var(--glass-bg-strong);
  border: 1px solid var(--glass-border);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  /* Every price-card child text color is explicit below — nothing relies
     on inheriting from a section-level color rule. A real screenshot
     caught the bug that pattern causes (white-on-white "$59.99" that
     silently vanished while its neighbor span, which had its own explicit
     color, stayed visible) — colors are set directly here instead so
     there's no ambient inheritance left to get wrong. */
  color: var(--ink);
  border-radius: var(--radius-md);
  padding: 32px 28px;
  position: relative;
}

.price-card.featured {
  border: 2px solid var(--teal);
}

.price-card .badge {
  position: absolute;
  top: -12px;
  left: 28px;
  background: var(--teal);
  color: #041712;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 4px 12px;
  border-radius: 100px;
}

.price-card .plan-label {
  font-weight: 600;
  font-size: 15px;
  color: var(--ink-soft);
}

.price-card .amount {
  font-size: 40px;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 14px 0 4px;
  color: var(--white);
  /* The number and its "/year"-style suffix are two separate inline-flex
     items with a gap, rather than one text node + nested span the way it
     was before — that made the suffix look like it was floating alone
     with a large, unexplained gap in front of it whenever the number's
     color didn't render (see the color-inheritance bug fixed above); this
     layout also just reads cleaner regardless. */
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
}

.price-card .amount .suffix {
  font-size: 15px;
  font-weight: 500;
  color: var(--ink-soft);
}

.price-card .trial-note {
  font-size: 13px;
  color: var(--teal);
  font-weight: 600;
}

.price-card ul {
  list-style: none;
  padding: 0;
  margin: 20px 0 0;
  font-size: 14px;
  color: var(--ink-soft);
}

.price-card li {
  padding: 7px 0;
  border-top: 1px solid var(--glass-border);
}

.price-card li:first-child {
  border-top: none;
}

/* ---------- Footer ---------- */

footer {
  background: rgba(6, 4, 15, 0.55);
  border-top: 1px solid var(--glass-border);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  color: var(--ink-faint);
  padding: 56px 24px 32px;
  font-size: 13px;
}

footer .footer-top {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 32px;
  padding-bottom: 28px;
  border-bottom: 1px solid var(--glass-border);
  max-width: 1100px;
  margin: 0 auto;
}

footer .footer-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--white);
  font-weight: 600;
  font-size: 15px;
}

footer .footer-brand img {
  width: 22px;
  height: 22px;
  border-radius: 6px;
}

footer .footer-links {
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
}

footer .footer-links a:hover {
  color: var(--white);
}

footer .footer-bottom {
  max-width: 1100px;
  margin: 20px auto 0;
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
}

/* ---------- Responsive ---------- */

@media (max-width: 720px) {
  .nav-links {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}
