/*
 * globals.css — Nari Design Token Foundation
 * ─────────────────────────────────────────────────────────────────────────────
 * Single source of truth for all CSS custom properties in nari-web.
 * Ported from nari-components.jsx Layer 1 (source of record).
 *
 * RULES & CONSTRAINTS (enforced design decisions):
 *   • Gold (--c-gold #ddb84a) is NEVER used on cream or white backgrounds —
 *     on-dark only (greenDeep, navy, midnight, moss). 7.31:1 AAA on greenDeep.
 *   • Green (#64822C) is decorative/icon accent ONLY — 3.94:1 on cream fails
 *     AA text minimum. Use --c-green-dark (#2d4a32, 8.79:1) for any text.
 *   • All body text must meet WCAG 2.1 AA (≥4.5:1). ADA contrast values are
 *     annotated inline below.
 *   • Headings: Lora serif, font-weight 500. Body: DM Sans 300. Code: DM Mono.
 *   • Surface gradients (--surface-*) are background-only tokens — never use
 *     as text color or border.
 *   • goldenSand (#a86820) is display text only (≥26px italic). Not body copy.
 * ─────────────────────────────────────────────────────────────────────────────
 */

/* ── Google Fonts ──────────────────────────────────────────────────────────── */
@import url('https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,500;1,400;1,500&family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600&family=DM+Mono:wght@400;500&family=Caveat:wght@400;500;600&display=swap');

/* ── Token Definitions ─────────────────────────────────────────────────────── */
/*
 * Compat alias layer (T4 / B4). Temporary until T4a renames --c-* → unprefixed
 * across the app and aligns font/layout token names with the package.
 * Package (@flora-nari/design-system) is the source of truth for values.
 */
:root {
  --c-cream:             var(--cream);
  --c-cream-light:       var(--cream-light);
  --c-cream-dark:        var(--cream-dark);
  --c-navy:              var(--navy);
  --c-midnight:          var(--midnight);
  --c-moss:              var(--moss);
  --c-green:             var(--green);
  --c-green-dark:        var(--green-dark);
  --c-green-deep:        var(--green-deep);
  --c-green-faint:       var(--green-faint);
  --c-light-head:        var(--light-head);
  --c-light-body:        var(--light-body);
  --c-light-mute:        var(--light-mute);
  --c-text:              var(--text);
  --c-text-mute:         var(--text-mute);
  --c-border:            var(--border);
  --c-border-mid:        var(--border-mid);
  --c-red:               var(--red);
  --c-gold:              var(--gold);
  --c-golden-sand:       var(--golden-sand);
  --c-wood-oak:          var(--wood-oak);
  --c-warm-amber:        var(--warm-amber);
  --c-in-progress-text:  var(--in-progress-text);
  --c-affirm-head:       var(--affirm-head);
  --c-affirm-body:       var(--affirm-body);
  --c-affirm-mute:       var(--affirm-mute);
  --c-affirm-accent:     var(--affirm-accent);
  --c-affirm-quote-mark: var(--affirm-quote-mark);
  --c-amber-bg:          var(--amber-bg);
  --c-amber-text:        var(--amber-text);
  --c-sage-bg:           var(--sage-bg);
  --c-sage-text:         var(--sage-text);

  --surface-forest:   var(--grad-forest);
  --surface-cream:    var(--grad-cream);
  --surface-hero:     var(--grad-hero);
  --surface-warm:     var(--grad-warm);
  --surface-lavender: var(--grad-lavender);
  --surface-cool:     var(--grad-cool);

  /* App-specific layout — no package equivalent */
  --nav-bar-height:      60px; /* the nav element's own visual height */
  /* Distance from the viewport top to just below the nav. Everything that
     clears or pins beneath the nav (page top padding, sub-navs, sticky rails,
     scroll-margins) reads this, so folding the announcement banner's height in
     here shifts them all at once. --nari-announcement-h is 0 when no banner is
     shown, so this stays exactly 60px in the common case. */
  --nav-height:          calc(var(--nav-bar-height) + var(--nari-announcement-h, 0px));
  --page-gutter:         clamp(16px, 4vw, 48px);
  /* ONE horizontal inset shared by every My Search card, across BOTH the
     /my-search page and the /my-search/jobs/[id] page (separate routes, so
     this cannot live in either page module). Cards previously each chose
     their own clamp, so their content sat on different left edges and those
     edges drifted apart as the viewport changed. */
  --ms-card-pad:         clamp(18px, 2.2vw, 24px);
  --max-width-dashboard: 1200px;
  --max-width-wide:      1440px;

  /* Fonts kept literal to preserve fallback chains (T4a: align with --font-serif/sans/mono) */
  --font-heading: 'Lora', Georgia, serif;
  --font-body:    'DM Sans', system-ui, sans-serif;
  --font-mono:    'DM Mono', 'Courier New', monospace;
  --font-script:  'Caveat', 'Lora', cursive;
}

/* ── Global Resets ─────────────────────────────────────────────────────────── */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  height: 100%;
  -webkit-text-size-adjust: 100%;
  /* Same-page anchor navigation (the public nav's /#why, /#pricing links)
   * glides instead of teleporting. Three guards keep it from leaking motion:
   * the root layout's data-scroll-behavior="smooth" attribute (Next 16 forces
   * `auto` around its route-transition scroll resets), the element-level
   * html[data-motion="static"] rule below (the descendant kill-switch sweep
   * cannot match <html> itself), and the prefers-reduced-motion first-paint
   * guard below it (data-motion only exists after hydration). */
  scroll-behavior: smooth;
  overflow-x: hidden;
  /* `overflow-x: clip` prevents horizontal scroll WITHOUT establishing a scroll
   * container; `overflow-x: hidden` alone coerces overflow-y to auto, making
   * <body> the scroll container and breaking descendant position: sticky (the
   * resume-builder sidebar and footer). `hidden` stays as the fallback for
   * browsers that predate `clip`. */
  overflow-x: clip;
  /* Background lives on <html> (not <body>) so the time-of-day ambient layer
   * can sit at `z-index: -1` between html bg and body content. The slow
   * transition makes bucket changes feel like sunrise/sunset. */
  background: var(--c-cream-light);
  transition: background 1.2s ease;
}

body {
  min-height: 100%;
  font-family: var(--font-body);
  font-weight: 300;
  background: transparent;
  color: var(--c-light-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  overflow-x: clip; /* see <html>: keeps position: sticky working; hidden fallback */
}

button {
  font-family: inherit;
  cursor: pointer;
}

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

/* ── Accessibility utilities ───────────────────────────────────────────────── */
/* Visually hide an element while keeping it available to assistive tech.
 * Use for headings that anchor `aria-labelledby` when the visible label is a
 * stylized eyebrow (SectionLabel) rather than a true heading. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Animation ─────────────────────────────────────────────────────────────── */
@keyframes nari-fadeUp {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: none; }
}

/* Slower fade with a softer easing + bigger travel so the cascade is
 * actually perceptible (was 0.45s/10px — done in <1s and easy to miss).
 * Total cascade end-to-end: ~1.4s. */
.nari-fu  { animation: nari-fadeUp 0.7s cubic-bezier(0.2, 0.65, 0.25, 1) both; }
.nari-fu1 { animation-delay: 0.08s; }
.nari-fu2 { animation-delay: 0.20s; }
.nari-fu3 { animation-delay: 0.32s; }
.nari-fu4 { animation-delay: 0.44s; }
.nari-fu5 { animation-delay: 0.56s; }
.nari-fu6 { animation-delay: 0.68s; }

/* ── Scroll reveal (IntersectionObserver-driven, works in every browser) ──────
 * Add `nari-reveal` to an element. <RevealObserver> (mounted on the marketing
 * page) ARMS below-the-fold ones with `reveal-armed` (hiding them) and then adds
 * `is-revealed` when they scroll ~18% into view, so the fade plays in-view and
 * is actually noticeable. Because the hiding class is added by JS, no-JS users
 * and reduced-motion / "Static" users always see content (nothing is hidden
 * without JS present to reveal it). Time-based transition, so the global
 * html[data-motion="static"] kill-switch (transition-duration:0.01ms) also
 * snaps it instantly. Replaces the old animation-timeline: view() reveals,
 * which only ran in Chromium + Safari 26 and completed at the screen edge. */
.nari-reveal {
  transition: opacity 0.6s cubic-bezier(0.2, 0.65, 0.25, 1),
    transform 0.6s cubic-bezier(0.2, 0.65, 0.25, 1);
}
.nari-reveal.reveal-armed {
  opacity: 0;
  transform: translateY(22px);
}
.nari-reveal.reveal-armed.is-revealed {
  opacity: 1;
  transform: none;
}

/* Opacity-only reveal for elements that LIFT on hover (.quoteCard, .compareCard,
   .parentsFeature). A transform-based reveal would fight their hover transform
   (its `is-revealed { transform: none }` outranks `:hover`), so these fade only.
   Each card also appends `opacity` to its own module `transition` so the fade
   still animates even though the module's transition shorthand wins the cascade. */
.nari-reveal-fade {
  transition: opacity 0.6s cubic-bezier(0.2, 0.65, 0.25, 1);
}
.nari-reveal-fade.reveal-armed {
  opacity: 0;
}
.nari-reveal-fade.reveal-armed.is-revealed {
  opacity: 1;
}

/* Stagger variant: put `nari-reveal-stagger` on a CONTAINER and its direct
   children cascade in at 90ms steps once the container scrolls into view.
   Same arming contract as `.nari-reveal` (JS adds the hiding class, so no-JS
   users always see content), same observer (<RevealObserver> watches all three
   classes). Delays live on `.is-revealed` only, so the kill-switch's
   transition-delay zeroing plus 0.01ms duration snaps the whole cascade. */
.nari-reveal-stagger > * {
  transition: opacity 0.6s cubic-bezier(0.2, 0.65, 0.25, 1),
    transform 0.6s cubic-bezier(0.2, 0.65, 0.25, 1);
}
.nari-reveal-stagger.reveal-armed > * {
  opacity: 0;
  transform: translateY(18px);
}
.nari-reveal-stagger.reveal-armed.is-revealed > * {
  opacity: 1;
  transform: none;
}
/* :not(.reveal-done): the delays exist only for the entrance cascade.
   <RevealObserver> stamps `reveal-done` after the cascade finishes; without
   that, a residual 90-630ms delay would lag every LATER transition on the
   children (e.g. a hover lift releasing). */
.nari-reveal-stagger.is-revealed:not(.reveal-done) > :nth-child(2) { transition-delay: 90ms; }
.nari-reveal-stagger.is-revealed:not(.reveal-done) > :nth-child(3) { transition-delay: 180ms; }
.nari-reveal-stagger.is-revealed:not(.reveal-done) > :nth-child(4) { transition-delay: 270ms; }
.nari-reveal-stagger.is-revealed:not(.reveal-done) > :nth-child(5) { transition-delay: 360ms; }
.nari-reveal-stagger.is-revealed:not(.reveal-done) > :nth-child(6) { transition-delay: 450ms; }
.nari-reveal-stagger.is-revealed:not(.reveal-done) > :nth-child(7) { transition-delay: 540ms; }
.nari-reveal-stagger.is-revealed:not(.reveal-done) > :nth-child(8) { transition-delay: 630ms; }

/* ── Motion preference (user-controlled) ────────────────────────────────── */
/* Mirrors the resolved `useMotionPreference()` value onto <html>. The
 * provider resolves OS `prefers-reduced-motion` into the `data-motion`
 * attribute (when preference="auto"), so this attribute is the SINGLE
 * source of truth for "should things move." Per-component CSS should
 * never use `@media (prefers-reduced-motion: reduce)` directly — that
 * would override the user's explicit "Animated" choice.
 *
 * Using 0.01ms (not 0) so React state changes still propagate instantly —
 * a true `0s` can occasionally race with state updates that depend on
 * transitionend/animationend events. */
html[data-motion="static"] *,
html[data-motion="static"] *::before,
html[data-motion="static"] *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
  /* Delays must be zeroed too: a 0.01ms transition that only STARTS after a
     630ms stagger delay (or a .nari-fu cascade delay) would still hide content
     for most of a second in Static mode. */
  transition-delay: 0.01ms !important;
  animation-delay: 0.01ms !important;
  scroll-behavior: auto !important;
  /* Scroll-driven (animation-timeline: view()) reveals ignore animation-duration
     entirely, so the 0.01ms trick above does NOT stop them. Revert them to the
     default document (time-based) timeline so the 0.01ms duration + fill-mode:both
     snaps each element to its final, fully-revealed state with no scroll motion.
     Without this, a user who picks "Static" while their OS is not reduced (and
     every mobile user, force-resolved to static) still sees the view() reveals. */
  animation-timeline: auto !important;
}

/* The sweep above uses a DESCENDANT combinator, which can never match <html>
 * itself, so the document scroller's `scroll-behavior: smooth` needs its own
 * element-level rule: Static users (which includes every mobile user,
 * force-resolved) get instant anchor jumps. */
html[data-motion="static"] {
  scroll-behavior: auto !important;
}

/* First-paint guard: data-motion is stamped post-hydration, so an OS
 * reduced-motion user deep-linking to /#pricing would get one animated glide
 * before the attribute exists. This is the ONE sanctioned use of the media
 * query (the motion system's own root rule, not a component override): the
 * :not() keeps an explicit "Animated" choice winning after hydration. */
@media (prefers-reduced-motion: reduce) {
  html:not([data-motion="animated"]) {
    scroll-behavior: auto;
  }
}

/* ── Time-of-day theming ────────────────────────────────────────────────── */
/* `<html data-time="dawn|morning|afternoon|evening|night">` is set by
 * <TimeOfDayProvider> on the client. Components that want to lean into the
 * time of day pick up `--c-time-accent` instead of a hard-coded brand color
 * (the journey hero's sprout, name script, and date eyebrow are the first
 * surfaces to do this).
 *
 * Default = green-dark, so SSR / initial paint matches the existing brand
 * accent. Bucket overrides ride existing tokens — no new colors introduced,
 * just a re-routing of the warm/cool spectrum we already have.
 *
 * Hierarchy (per product direction): explicit user pick → weather (future,
 * opt-in) → time-of-day → default. An explicit pick will set a higher-
 * specificity attribute and override `data-time`.
 *
 * Per `feedback_no_emotional_upsell`: time-of-day signal routes to free
 * presentation only — never bind to CTAs or paid surfaces. */
:root {
  --c-time-accent: var(--c-green-dark);
}

/* Per-bucket: accent token + subtle body wash (~5% mix into cream).
 * Wash is intentionally tiny so the page never feels "themed" — just
 * belongs to the time of day. The richer bucket feel comes from the
 * <TimeOfDayAmbient> layer (glow + atmospheric + kinetic). */
html[data-time="dawn"] {
  --c-time-accent: var(--c-golden-sand);
  background: color-mix(in srgb, var(--c-cream-light) 95%, var(--c-golden-sand));
}
html[data-time="morning"] {
  --c-time-accent: var(--c-green-dark);
  background: var(--c-cream-light);
}
/* Afternoon accent: warm-amber (golden hour warmth). Sage-green felt
 * like just another use of the brand color — warm-amber gives afternoon
 * its own emotional signature: settled, golden, grounded. */
html[data-time="afternoon"] {
  --c-time-accent: var(--c-warm-amber);
  background: color-mix(in srgb, var(--c-cream-light) 96%, var(--c-moss));
}

/* Evening: solid dark navy bg, accent = pale wood. Pushed darker (70%
 * navy, was 58%) so cream-toned text on this bg can pass AA for small
 * text (the 58% mix was in the dead-zone where neither light nor dark
 * text could get 4.5:1 contrast). The evening "feel" still reads as
 * dusk because the ambient layer carries the horizon glow. */
html[data-time="evening"] {
  --c-time-accent: #e8d4b8;
  background: color-mix(in srgb, var(--c-navy) 70%, var(--c-cream-light));
}

/* Night: solid deep navy bg, accent = soft moonlit silver-blue. Pushed
 * darker (80% navy, was 72%) for the same AA reason as evening — at 72%
 * the bg was still close to the mid-tone trap. 80% gives small cream
 * text reliable 5:1+ contrast. */
html[data-time="night"] {
  --c-time-accent: #d4dce8;
  background: color-mix(in srgb, var(--c-navy) 80%, var(--c-cream-light));
}

/* ── Dark-sky text ramp ─────────────────────────────────────────────────── */
/* Evening and night paint the whole document a dark slate, so anything
 * sitting DIRECTLY on the page background needs cream-side colours. Cards do
 * not: they keep their own light surface and their own dark text, which is
 * the treatment /my-journey already ships.
 *
 * These are deliberately NOT a re-point of --light-head / --light-body /
 * --cream-light. Those must keep their light-surface meanings, because the
 * ~40 cards on these pages are light islands with dark text, several DS
 * rules use --cream-light as a FOREGROUND (Callout icons, selected chips),
 * and journey-hero / footer already use it as a foreground in these very
 * buckets. A global flip would break all of that to fix the page background.
 * Opt-in tokens, referenced only by the rules that need them, cannot.
 *
 * Measured against the evening background rgb(82,87,91), the lighter and
 * therefore harder of the two: head 6.89:1, body 5.43:1, accent 6.01:1.
 * All clear AA. Do NOT reach for --text-mute instead of --sky-body: it
 * measures 4.40:1 on evening and fails. */
html[data-time="evening"],
html[data-time="night"] {
  --sky-head: var(--c-cream-light);
  --sky-body: color-mix(in srgb, var(--c-cream-light) 82%, var(--c-light-mute));
  --sky-accent: var(--on-dark-accent-warm);
  --sky-line: var(--border-dark);
  --sky-surface: var(--on-dark-surface-tint);
}

.cookie-consent-banner_banner__i0Iez {
  position: fixed;
  bottom: 24px;
  right: 24px;
  z-index: var(--z-toast);
  width: min(360px, calc(100vw - 32px));
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px 20px;
  background: #fff;
  color: var(--c-light-body);
  border: 1px solid var(--c-border);
  border-radius: 14px;
  box-shadow: 0 10px 30px rgba(15, 31, 28, 0.14);
  animation: cookie-consent-banner_cookieBanner-rise__MipXp 0.32s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

.cookie-consent-banner_title__g7pZi {
  font-family: var(--font-heading);
  font-size: 15px;
  font-weight: 500;
  color: var(--c-light-head);
  margin: 0;
}

.cookie-consent-banner_message__8HFFl {
  margin: 0;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 300;
  line-height: 1.55;
  color: var(--c-light-body);
  /* Mounted from the root layout, so this renders on every route: balance the
     lines rather than let the last one strand a single word. */
  text-wrap: balance;
}

.cookie-consent-banner_actions__cArf1 {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  flex-wrap: wrap;
}

@keyframes cookie-consent-banner_cookieBanner-rise__MipXp {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (max-width: 480px) {
  .cookie-consent-banner_banner__i0Iez {
    bottom: 16px;
    right: 16px;
    left: 16px;
    width: auto;
  }

  .cookie-consent-banner_actions__cArf1 {
    justify-content: stretch;
  }

  .cookie-consent-banner_actions__cArf1 > * {
    flex: 1 1;
  }
}

