/*
 * components.css — Reusable visual building blocks.
 * All color/surface values reference tokens.css so light and dark
 * themes swap automatically. Strict monochrome — no coral, no purple.
 */

/* ========================================
   FLOATING NAV PILL (top-right)
   Sits above the canvas (z-index 50) so it stays solid even when the
   hover dots pass behind it.
   ======================================== */
.floating-nav {
  position: fixed;
  top: var(--space-5);
  right: var(--space-5);
  z-index: var(--z-nav);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0.7rem var(--space-5);
  background: color-mix(in srgb, var(--color-bg) 78%, transparent);
  backdrop-filter: blur(22px) saturate(180%);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  border: 1.5px solid var(--color-hairline-strong);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: 500;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  opacity: 0;
  transform: translateY(-12px);
  transition: opacity var(--dur-medium) var(--ease-spring),
              transform var(--dur-medium) var(--ease-spring),
              background var(--dur-theme) var(--ease-out-soft);
  box-shadow: var(--shadow-sm);
}

.floating-nav.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.floating-nav a {
  color: var(--color-muted);
  transition: color var(--dur-base) var(--ease-out-soft),
              transform var(--dur-medium) var(--ease-spring);
}

.floating-nav a:hover,
.floating-nav a:focus-visible {
  color: var(--color-fg);
  transform: translateY(-1px);
}

.floating-nav .nav-sep {
  width: 1px;
  height: 14px;
  background: var(--color-hairline-strong);
}

@media (max-width: 767px) {
  .floating-nav {
    top: var(--space-4);
    right: var(--space-4);
    padding: 0.5rem var(--space-4);
    gap: var(--space-3);
  }
}

/* ========================================
   NAV ICON LINK (LinkedIn in the floating pill)
   Matches the theme-toggle geometry so both sit on the same baseline
   inside the pill. Icon is colored with currentColor so it swaps with
   the theme. Spring hover matches the theme-toggle microinteraction.
   ======================================== */
.nav-icon-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 999px;
  color: var(--color-fg);
  transition: transform var(--dur-medium) var(--ease-spring),
              color var(--dur-base) var(--ease-out-soft);
}

.nav-icon-link:hover,
.nav-icon-link:focus-visible {
  color: var(--color-fg);
  transform: scale(1.18) rotate(-8deg);
}

.nav-icon-link:active {
  transform: scale(0.92);
}

.nav-icon-link svg {
  width: 18px;
  height: 18px;
}

/* ========================================
   THEME TOGGLE (sun/moon) — bigger hit area + spring rotate on hover
   ======================================== */
.theme-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: 999px;
  background: transparent;
  border: none;
  color: var(--color-fg);
  cursor: pointer;
  transition: transform var(--dur-medium) var(--ease-spring),
              color var(--dur-base) var(--ease-out-soft);
}

.theme-toggle:hover { transform: scale(1.18) rotate(-12deg); }
.theme-toggle:active { transform: scale(0.92) rotate(0deg); }

.theme-toggle svg {
  position: absolute;
  width: 18px;
  height: 18px;
  overflow: visible;
}

.theme-toggle .tt-sun,
.theme-toggle .tt-moon {
  transition: opacity var(--dur-medium) var(--ease-spring),
              transform var(--dur-medium) var(--ease-spring);
  transform-origin: center;
}

/* Light mode (default) → show moon (click = go dark) */
.theme-toggle .tt-sun  { opacity: 0; transform: scale(0.4) rotate(180deg); }
.theme-toggle .tt-moon { opacity: 1; transform: scale(1) rotate(0deg); }

/* Dark mode → show sun (click = go light) */
[data-theme="dark"] .theme-toggle .tt-sun  { opacity: 1; transform: scale(1) rotate(0deg); }
[data-theme="dark"] .theme-toggle .tt-moon { opacity: 0; transform: scale(0.4) rotate(-180deg); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .theme-toggle .tt-sun  { opacity: 1; transform: scale(1) rotate(0deg); }
  :root:not([data-theme]) .theme-toggle .tt-moon { opacity: 0; transform: scale(0.4) rotate(-180deg); }
}

/* ========================================
   HERO
   ======================================== */
.hero {
  position: relative;
}

.hero .container {
  position: relative;
}

.hero-name {
  font-family: var(--font-display);
  font-size: var(--fs-display);
  font-weight: 900;
  line-height: 0.94;
  letter-spacing: -0.05em;
}

.hero-name .hn-stroke {
  display: inline-block;
  color: var(--color-fg);
  -webkit-text-stroke: 0;
}

.hero-tagline {
  font-size: var(--fs-lead);
  line-height: 1.5;
  color: var(--color-fg-soft);
  max-width: 38ch;
  font-weight: var(--fw-regular);
}

.hero-cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}

/* Hero intro row: avatar + tagline. On mobile the avatar stacks above
 * the paragraph so the 112px memoji + 38ch prose don't fight for the
 * 375px viewport. Desktop flips to a row with the memoji flush-left. */
.hero-intro {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
}

@media (min-width: 768px) {
  .hero-intro {
    flex-direction: row;
    align-items: center;
    gap: var(--space-5);
  }
}

.hero-avatar {
  display: block;
  width: 88px;
  height: 88px;
  object-fit: contain;
  flex-shrink: 0;
  cursor: pointer;
  filter: drop-shadow(0 8px 20px rgba(0, 0, 0, 0.18));
  /* Anchor near the bottom so the scale + tilt reads like the avatar is
   * lifting off its desk rather than orbiting around its own center. */
  transform-origin: 50% 85%;
  transition:
    transform var(--dur-slow) var(--ease-spring),
    filter var(--dur-medium) var(--ease-out-soft);
}

/* On hover: lift, scale, and tilt with a springy cubic-bezier curve
 * (--ease-spring overshoots past the target and settles, giving the
 * motion a bouncy, alive quality on both hover-in and unhover-out).
 * The filter transition adds an indigo glow that nods to the .accent
 * gradient used in the headlines.
 *
 * IMPORTANT: this uses `transition` — NOT `animation` — on purpose. The
 * entrance fade-up in animations.css sets `opacity: 0` on the base and
 * holds it at 1 via `animation: fade-up ... forwards`. If we also set
 * `animation` here on :hover, it REPLACES the fade-up (CSS animation
 * property is a single slot, not composed), opacity loses its hold, the
 * base `opacity: 0` wins the cascade, and the avatar disappears on hover.
 * Transition-based hover leaves the entrance animation alone. */
.hero-avatar:hover {
  transform: translateY(-6px) scale(1.1) rotate(-6deg);
  filter:
    drop-shadow(0 16px 32px rgba(0, 0, 0, 0.28))
    drop-shadow(0 0 22px rgba(79, 70, 229, 0.45));
}

.hero-avatar:active {
  transform: translateY(-2px) scale(1.04) rotate(-2deg);
  transition-duration: var(--dur-quick);
}

@media (prefers-reduced-motion: reduce) {
  .hero-avatar {
    transition: none;
  }
  .hero-avatar:hover {
    transform: scale(1.04);
    filter: drop-shadow(0 10px 22px rgba(0, 0, 0, 0.22));
  }
}

@media (min-width: 768px) {
  .hero-avatar {
    width: 112px;
    height: 112px;
  }
}

/* ========================================
   PILL BUTTONS — single style, springy hover
   ======================================== */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0.85rem var(--space-5);
  background: var(--pill-bg);
  color: var(--color-fg);
  border: 1px solid var(--pill-border);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: var(--pill-shadow);
  transition: border-color var(--dur-base) var(--ease-out-soft),
              color var(--dur-base) var(--ease-out-soft),
              background var(--dur-base) var(--ease-out-soft),
              box-shadow var(--dur-base) var(--ease-out-soft),
              transform var(--dur-medium) var(--ease-spring);
}

.pill:hover,
.pill:focus-visible {
  background: var(--pill-bg-hover);
  color: var(--color-bg);
  border-color: var(--color-fg);
  box-shadow: var(--pill-shadow-hover);
  transform: translateY(-2px) scale(1.03);
}

.pill:active {
  transform: translateY(0) scale(0.97);
}

/* .pill-solid — primary CTA. Solid inverse fill (same color as hover state),
 * subtle drop shadow for lift. The dominant action next to the elevated
 * charcoal secondary pill. */
.pill.pill-solid {
  background: var(--color-fg);
  color: var(--color-bg);
  border-color: var(--color-fg);
  box-shadow: var(--pill-shadow-solid);
}

.pill.pill-solid:hover,
.pill.pill-solid:focus-visible {
  background: var(--color-fg);
  color: var(--color-bg);
  border-color: var(--color-fg);
  box-shadow: var(--pill-shadow-solid-hover);
  transform: translateY(-3px) scale(1.04);
}

/* .pill-pop — the single colorful CTA on the page. Indigo gradient
 * (deep → bright indigo), white text, soft drop-shadow glow. No border,
 * no ring, no light halo around the edge. Used only on the App Store
 * install button. Lives outside the .blend group so the gradient doesn't
 * get inverted. */
.pill.pill-pop {
  color: #FFFFFF;
  background: linear-gradient(135deg, #4F46E5 0%, #818CF8 100%);
  border: none;
  padding: calc(0.85rem + 2px) calc(var(--space-5) + 2px);
  position: relative;
  isolation: isolate;
  box-shadow: 0 14px 32px -12px rgba(79, 70, 229, 0.55);
  animation: pill-pop-glow 3.6s ease-in-out infinite;
}

.pill.pill-pop:hover,
.pill.pill-pop:focus-visible {
  background: linear-gradient(135deg, #4338CA 0%, #6366F1 100%);
  color: #FFFFFF;
  border: none;
  transform: translateY(-3px) scale(1.04);
  box-shadow: 0 22px 44px -16px rgba(79, 70, 229, 0.7);
}

.pill.pill-pop:active {
  transform: translateY(0) scale(0.97);
}

.pill .arrow {
  display: inline-block;
  transition: transform var(--dur-medium) var(--ease-spring);
}

.pill:hover .arrow { transform: translateX(6px); }

/* ========================================
   DISPLAY HEADLINES — Apps section + Manifesto
   ---
   Both headlines share the same centered display-type treatment so the
   two remaining beats on the page ("The apps" and "Just build it.") rhyme
   typographically. The inline `.accent` span gets the single indigo
   gradient — the only chromatic accent on the page, tying these headlines
   to the App Store pill-pop CTA. `text-shadow: none` on the accent opts
   out of the parent's readability halo (which would otherwise bleed
   bg-colored copies through the transparent gradient fill).
   ======================================== */
.apps {
  position: relative;
}

.apps > .container:first-child {
  text-align: center;
}

.apps-headline,
.manifesto-headline {
  font-family: var(--font-display);
  font-weight: 900;
  color: var(--color-fg);
  text-wrap: balance;
}

/* Apps headline — matches .app-name scale exactly so the section header
 * feels like a sibling of Monthly/Dottie, not a smaller eyebrow.
 * margin-bottom is the "above-a-chapter" gap and is kept equal to the
 * inter-chapter gap (padding-block × 2 on .app-chapter) so every chapter
 * has the same breathing room above and below. */
.apps-headline {
  font-size: clamp(2.75rem, 6.5vw, 5.5rem);
  line-height: 1.0;
  letter-spacing: -0.04em;
  margin-bottom: var(--space-11);
}

/* Manifesto headline — editorial closer, stays at the smaller scale because
 * "Just build it." carries meaning through text weight, not size. */
.manifesto-headline {
  font-size: clamp(1.75rem, 4.5vw, 3.25rem);
  line-height: 1.18;
  letter-spacing: -0.035em;
}

.accent {
  color: #4F46E5;
  background: linear-gradient(135deg, #4F46E5 0%, #818CF8 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  display: inline-block;
}

/* ========================================
   APP CHAPTER
   ======================================== */
.app-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.app-chapter-number {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: 500;
  letter-spacing: var(--tr-wider);
  color: var(--color-muted);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-1);
}

.app-chapter-number::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--color-hairline-strong), transparent);
  max-width: 120px;
}

.app-name {
  font-family: var(--font-display);
  font-size: clamp(2.75rem, 6.5vw, 5.5rem);
  font-weight: 900;
  line-height: 1.0;
  letter-spacing: -0.04em;
  color: var(--color-fg);
}

.app-tagline {
  font-size: var(--fs-lead);
  line-height: 1.35;
  color: var(--color-fg);
  font-style: italic;
  font-weight: var(--fw-medium);
  max-width: 32ch;
}

.app-description {
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--color-muted);
  max-width: 52ch;
}

.app-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: 500;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--color-muted);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-hairline);
  margin-top: var(--space-3);
}

.app-meta span:not(:last-child)::after {
  content: " · ";
  margin-left: var(--space-4);
  color: var(--color-faint);
}

.app-cta {
  margin-top: var(--space-4);
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
}

/* Mobile: tighter app chapter sizing */
@media (max-width: 767px) {
  .app-name {
    font-size: clamp(2rem, 8vw, 2.75rem);
  }
  .app-tagline {
    font-size: var(--fs-body);
  }
  .app-text {
    gap: var(--space-4);
  }
  .pill {
    padding: 0.7rem var(--space-4);
  }
  .pill.pill-pop {
    padding: calc(0.7rem + 2px) calc(var(--space-4) + 2px);
  }
}

/* ========================================
   APP VISUAL — Bare screenshot, no phone chrome
   The screenshots are full-bleed iOS captures and already contain the
   device's status bar and dynamic island, so wrapping them in a fake
   phone frame creates a "phone in a phone" effect. Show them bare.
   ======================================== */
.app-visual {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 500px;
}

/* ========================================
   PHONE FRAME — bare rounded screenshot container with floating shadow
   ======================================== */
.phone-frame {
  position: relative;
  width: clamp(250px, 25.6vw, 288px);
  aspect-ratio: 9 / 19.5;
  border-radius: 38px;
  overflow: hidden;
  background: transparent;
  /* Softer floating shadow — lighter than the earlier version so the phone
   * still feels lifted off the page without dominating the surrounding
   * grid. Two layers: a wide soft ambient + a tighter mid-distance. */
  box-shadow:
    0 40px 70px -28px rgba(0, 0, 0, 0.13),
    0 18px 36px -20px rgba(0, 0, 0, 0.08);
  transition:
    transform 600ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 600ms cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}

[data-theme="dark"] .phone-frame {
  box-shadow:
    0 40px 70px -28px rgba(0, 0, 0, 0.32),
    0 18px 36px -20px rgba(0, 0, 0, 0.20);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .phone-frame {
    box-shadow:
      0 40px 70px -28px rgba(0, 0, 0, 0.32),
      0 18px 36px -20px rgba(0, 0, 0, 0.20);
  }
}

/* Hover lift — Dottie-style. translateY + shadow growth on a soft curve.
 * IMPORTANT: the :hover target is the .phone-frame itself, NOT the outer
 * .app-visual grid cell. .app-visual fills its 7fr/5fr grid column (~640px
 * on desktop) while the phone is only ~288px wide and centered, so hovering
 * the empty space left/right of the phone would otherwise trigger the lift
 * from a dead zone that isn't visually the image. Keep this selector scoped
 * to the frame so the hit area exactly matches the 288×624 rounded rect. */
.phone-frame:hover {
  transform: translateY(-8px);
  box-shadow:
    0 52px 90px -30px rgba(0, 0, 0, 0.16),
    0 26px 52px -22px rgba(0, 0, 0, 0.10);
}

[data-theme="dark"] .phone-frame:hover {
  box-shadow:
    0 52px 90px -30px rgba(0, 0, 0, 0.42),
    0 26px 52px -22px rgba(0, 0, 0, 0.27);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .phone-frame:hover {
    box-shadow:
      0 52px 90px -30px rgba(0, 0, 0, 0.42),
      0 26px 52px -22px rgba(0, 0, 0, 0.27);
  }
}

.phone-screen {
  position: relative;
  width: 100%;
  height: 100%;
}

/* ========================================
   SCREENSHOT CYCLER — smooth, non-overlapping crossfade
   ---
   Every image sits at inset:0 absolutely positioned and full opacity by
   default, stacked in DOM order. Only the image with `.is-active` is
   visible; all others are `opacity: 0`. The transition runs on opacity
   ONLY (no scale, no transform) — that's cheap and GPU-composited, so
   there is no repaint jank between frames. `will-change: opacity`
   promotes each image to its own compositor layer so the crossfade
   happens on the GPU.
   ======================================== */
.phone-screen img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1400ms cubic-bezier(0.37, 0, 0.63, 1);
  will-change: opacity;
  pointer-events: none;
}

.phone-screen img.is-active {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .phone-frame {
    transition: none;
  }
  .phone-frame:hover {
    transform: none;
  }
  .phone-screen img {
    transition: none;
  }
}

/* ========================================
   MANIFESTO
   ======================================== */
.manifesto {
  text-align: center;
  position: relative;
}

/* Decorative vertical hairlines anchored to the headline itself (not the
 * section), so they always bracket "Just build it." symmetrically regardless
 * of section padding. .manifesto-headline already has position: relative
 * from the halo rule further down. */
.manifesto-headline::before,
.manifesto-headline::after {
  content: "";
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 1px;
  height: 48px;
  background: linear-gradient(to bottom, transparent, var(--color-hairline-strong), transparent);
  pointer-events: none;
}

.manifesto-headline::before {
  bottom: calc(100% + var(--space-5));
}

.manifesto-headline::after {
  top: calc(100% + var(--space-5));
}

/* ========================================
   FOOTER
   ======================================== */
.footer {
  position: relative;
}

.footer-bio {
  font-size: var(--fs-lead);
  color: var(--color-fg-soft);
  max-width: 42ch;
  line-height: 1.55;
}

.footer-bio strong {
  color: var(--color-muted);
  font-weight: 500;
  display: block;
  margin-bottom: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--tr-wider);
  text-transform: uppercase;
}

.footer-bio a {
  font-family: var(--font-mono);
  letter-spacing: var(--tr-wide);
}

/* footer-meta stacks the language switcher and legal links on the right
 * side of the footer grid. Right-aligned on desktop, left-aligned when
 * the grid collapses to one column on mobile. */
.footer-meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: 500;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--color-muted);
}

@media (min-width: 768px) {
  .footer-meta { align-items: flex-end; text-align: right; }
}

.footer-lang,
.footer-legal {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.footer-lang a,
.footer-legal a {
  color: var(--color-muted);
  transition: color var(--dur-base) var(--ease-out-soft);
}

.footer-lang a.is-active { color: var(--color-fg); }

.footer-lang a:hover,
.footer-lang a:focus-visible,
.footer-legal a:hover,
.footer-legal a:focus-visible { color: var(--color-fg); }

/* ========================================
   BLEND GROUP — text that inverts under hover dots
   Applied last so it overrides the per-component color rules above.
   Excludes small mono labels (nav, eyebrows, app-meta) so they stay solid
   and remain easy to read.
   ======================================== */
.hero-name,
.hero-name .hn-stroke,
/* ========================================
   READABILITY HALO — text-shadow outline in bg color
   ---
   Instead of painting a full background rectangle behind prose
   blocks (which read as visible plates around each text block),
   each glyph gets a tight blurred halo in the page background
   color via stacked text-shadows. The halo locally occludes the
   ambient dot layer only within ~5px of each letter shape —
   dots stay fully visible in every other pixel of the viewport,
   including the text's own line-height gaps and between words.
   Only the characters themselves are "above" the grid. No plate.
   ======================================== */
.hero-name,
.hero-tagline,
.apps-headline,
.manifesto-headline,
.app-name,
.app-tagline,
.app-description,
.footer-bio {
  color: var(--color-fg);
  text-shadow:
    0 0 5px var(--color-bg),
    0 0 5px var(--color-bg),
    0 0 5px var(--color-bg),
    0 0 5px var(--color-bg);
  position: relative;
}

/* The .accent span uses background-clip: text with a transparent fill to
 * show an indigo gradient. text-shadow inherited from the parent headline
 * would draw bg-colored offset copies underneath the transparent fill,
 * showing through the glyph shapes and polluting the gradient. Opt out
 * entirely — the accent is a single small word in each headline, so the
 * few dots that can peek through its letter counters don't hurt
 * readability. */
.accent {
  text-shadow: none;
}
