/* ==========================================================================
   Fonts — Fraunces (headings), self-hosted. Google serves it as a
   variable font (Latin-subset only — this is an English-only site, no
   need for the Latin-Extended/Vietnamese subsets Google also offers),
   so one file each for roman/italic covers the full 400-700 weight range
   via the `font-weight` range syntax below — normal `font-weight: 500`
   etc. in the rules that use --font-heading just works, no
   font-variation-settings needed. Inter (body text) stays on the
   Google Fonts CDN link in index.html, unchanged.
   ========================================================================== */
@font-face {
  font-family: "Fraunces";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/fraunces-variable-latin.woff2") format("woff2");
}

@font-face {
  font-family: "Fraunces";
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/fraunces-variable-italic-latin.woff2") format("woff2");
}

/* ==========================================================================
   Reset
   ========================================================================== */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  min-height: 100vh;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

img, picture, svg, video {
  display: block;
  max-width: 100%;
}

input, button, textarea, select {
  font: inherit;
}

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

ul, ol {
  list-style: none;
}

/* ==========================================================================
   Custom properties — confirmed brand palette (client-approved)
   ========================================================================== */
:root {
  --color-bg: #FAF8F2;
  --color-navy: #14213D;
  --color-cream: #F5EFD9;
  --color-sage: #6B7F5E;
  --color-sage-light: #E4E9DD;
  /* Deeper sage for cream-on-sage solid fills (the primary button) —
     --color-sage itself only clears 3.78:1 against cream text, short of
     WCAG AA's 4.5:1 for normal text. This is the same shade the button
     already used on :hover; reusing it as the resting state instead of
     introducing a new color gets it to 4.74:1. */
  --color-sage-deep: #5c6f50;

  --color-text: var(--color-navy);
  --color-text-muted: #545f74;
  --color-text-inverse: var(--color-cream);
  --color-border: rgba(20, 33, 61, 0.12);

  /* Card-system aliases: dark "authority bookend" cards use --color-ink,
     light cards use --color-surface — kept distinct from --color-bg so
     the page's neutral base is visible between cards. */
  --color-ink: var(--color-navy);
  --color-surface: var(--color-cream);

  --font-heading: "Fraunces", "Georgia", serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  /* Spacing scale — every value is a multiple of 8px (0.5rem), so gaps and
     padding read as one deliberate rhythm rather than per-section guesses.
     --space-sm moved from 12px to 16px to fit that rule; md/lg/xl were
     already clean multiples (24/48/96px) and are unchanged. */
  --space-2xs: 0.5rem;  /* 8px  — tight gaps: icon+label, inline chips */
  --space-sm: 1rem;     /* 16px — within-component spacing */
  --space-md: 1.5rem;   /* 24px — between related elements/cards */
  --space-lg: 3rem;     /* 48px — card internal padding, section gaps */
  --space-xl: 6rem;     /* 96px — section block padding */

  /* Type scale — h1-h3 + body, so every heading/paragraph on the page
     pulls from one set of sizes instead of ad-hoc per-section values. */
  --fs-h1: clamp(1.75rem, 5vw, 3.25rem);   /* hero title only — sized down from 2.25-4rem so the longer "…financial independence starts here." headline isn't cramped */
  --fs-h2: clamp(1.75rem, 4vw, 2.5rem);    /* section titles */
  --fs-h3: clamp(1.25rem, 2.5vw, 1.5rem);  /* card/FAQ headings */
  --fs-body-lg: clamp(1rem, 2.2vw, 1.2rem); /* lead paragraphs/subtitles */
  --fs-body: 1rem;                          /* default body text */
  --fs-small: 0.875rem;                     /* meta/footer text */

  --card-gap: 2rem;
  --card-radius: 16px;

  --radius: 8px;
  --transition-base: 400ms ease;
  /* Faster variant for anything tightly coupled to scroll position (Why
     Ethical's active/inactive item state, its description cross-fade) —
     --transition-base's 400ms reads as laggy/delayed there, visibly
     playing catch-up behind the actual scroll input rather than tracking
     it, especially under a normal-to-fast scroll gesture. */
  --transition-fast: 180ms ease;
  --container-width: 1100px;
  /* Desktop nav is ~61px (44px touch-target links + 8px padding block).
     Referenced by .why-ethical__pin's sticky `top` and by .card's
     scroll-margin-top (so anchor jumps land clear of the sticky nav).
     Re-measured after the 44px tap-target pass — the responsive overrides
     below track the taller wrapped nav at narrow widths. */
  --nav-height: 64px;

  /* Elevation scale — every "raised" surface (light card, service card,
     contact form, the Why Ethical active card) pulls from one of these
     three, navy-tinted for consistency, instead of each declaring its own
     shadow recipe (previously 5 different ones, one of them plain black
     instead of navy-tinted). */
  --shadow-sm: 0 4px 16px rgba(20, 33, 61, 0.08);
  --shadow-md: 0 10px 28px rgba(20, 33, 61, 0.14);
  --shadow-lg: 0 16px 40px rgba(20, 33, 61, 0.24);
}

/* ==========================================================================
   Base
   ========================================================================== */
body {
  font-family: var(--font-body);
  color: var(--color-text);
  background: var(--color-bg);
}

/* Weight 500 site-wide, including the hero — matches .why-ethical__headline's
   upright, medium-weight treatment (client preference, after seeing both
   side by side; the hero was tried at italic/700 first and dropped). */
h1, h2, h3 {
  font-family: var(--font-heading);
  font-weight: 500;
  line-height: 1.2;
}

h3 {
  font-size: var(--fs-h3);
}

.container {
  width: 100%;
  max-width: var(--container-width);
  margin-inline: auto;
  padding-inline: var(--space-md);
}

/* Visually-hidden but screen-reader-accessible — used for form field
   labels where the visible affordance is a placeholder instead (the
   contact form), since placeholders alone aren't a reliable substitute
   for a real, programmatically-associated label. */
/* Skip link — hidden off-screen until focused, then pinned over the nav.
   NOT `display:none`/`visibility:hidden`: either would remove it from the tab
   order entirely, which defeats the point. It sits above the sticky nav's
   z-index (200 is the FAB, the nav is below that) so it can't appear behind it. */
.skip-link {
  position: absolute;
  top: -100px;
  left: var(--space-md);
  z-index: 300;
  padding: var(--space-sm) var(--space-md);
  background: var(--color-cream);
  color: var(--color-navy);
  font-size: var(--fs-small);
  font-weight: 600;
  border-radius: 0 0 var(--radius) var(--radius);
  box-shadow: var(--shadow-md);
  transition: top var(--transition-fast);
}

.skip-link:focus {
  top: 0;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.section {
  padding-block: var(--space-xl);
}

@media (max-width: 767px) {
  /* --space-xl (96px) top+bottom per section reads as unused chrome on a
     phone screen rather than breathing room — halve it there, unrelated
     to the card's own margin/radius mobile overrides below. */
  .section {
    padding-block: var(--space-lg);
  }
}

/* Small uppercase label above a .section__title — "SERVICES", "CONTACT",
   etc. Shared component so this eyebrow+headline pattern is consistent
   wherever it's used, not re-declared per section. */
.section__eyebrow {
  display: block;
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-sage-deep);
  text-align: center;
  margin-bottom: var(--space-sm);
}

.section__title {
  font-size: var(--fs-h2);
  margin-bottom: var(--space-lg);
  text-align: center;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: var(--space-sm) var(--space-lg);
  border-radius: 999px;
  font-weight: 600;
  font-family: var(--font-body);
  transition: transform var(--transition-base), background var(--transition-base), box-shadow var(--transition-base), border-color var(--transition-base);
}

.btn--primary {
  background: var(--color-sage-deep);
  color: var(--color-cream);
  box-shadow: 0 8px 24px rgba(107, 127, 94, 0.35);
}

.btn--primary:hover,
.btn--primary:focus-visible {
  background: #4d5f43;
  transform: translateY(-2px);
  box-shadow: 0 12px 28px rgba(107, 127, 94, 0.45);
}

/* Ghost/outline secondary — cream-on-navy is the default, correct for the
   hero (a dark card). The .card--light override just below swaps in a
   navy-based treatment, so the same class reads correctly whichever
   surface it lands on without needing a second button component. */
.btn--secondary {
  background: transparent;
  color: var(--color-cream);
  border: 1.5px solid rgba(245, 239, 217, 0.5);
}

.btn--secondary:hover,
.btn--secondary:focus-visible {
  border-color: var(--color-cream);
  background: rgba(245, 239, 217, 0.08);
  transform: translateY(-2px);
}

.card--light .btn--secondary {
  color: var(--color-navy);
  border-color: rgba(20, 33, 61, 0.35);
}

.card--light .btn--secondary:hover,
.card--light .btn--secondary:focus-visible {
  border-color: var(--color-navy);
  background: rgba(20, 33, 61, 0.06);
}

.btn__icon {
  width: 1.15em;
  height: 1.15em;
  flex-shrink: 0;
}

/* ==========================================================================
   Focus-visible — keyboard-navigation outline for every interactive
   element on the page (buttons, links, nav, and the FAQ accordion
   trigger added later). Kept as one rule so focus styling is consistent
   site-wide rather than added ad hoc per component.
   ========================================================================== */
a:focus-visible,
button:focus-visible,
.btn:focus-visible {
  outline: 2px solid var(--color-sage);
  outline-offset: 3px;
}

/* ==========================================================================
   Nav — the only full-width element on the page. Sticky (was non-sticky) —
   see the .nav rule below for why.
   ========================================================================== */
/* Sticky, not fixed — same "always visible while scrolling" result (nav is
   the first element, so sticky's "stick once past its natural position"
   collapses to "stick immediately"), without fixed's need to manually
   compensate with padding-top elsewhere to avoid content jumping under it.
   Keeps the WhatsApp CTA and nav links reachable throughout the scroll,
   which matters more for a conversion-focused site than the page reading
   as a calmer, nav-recedes-once-you-scroll editorial piece. */
.nav {
  position: sticky;
  top: 0;
  z-index: 100;
  width: 100%;
  background: var(--color-navy);
  border-bottom: 1px solid rgba(245, 239, 217, 0.12);
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Trimmed from --space-sm (16px) to --space-2xs (8px): the nav links now
     carry their own 44px min-height for touch, so this keeps the overall
     desktop nav height roughly where it was (~60px) rather than growing it. */
  padding-block: var(--space-2xs);
}

.nav__brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* 34px, up from 26px when this became the full logo mark (logo-mark.png).
   Not a style tweak — the full mark surrounds the tree with stars, so the
   crescent only spans ~76% of the frame (measured) versus tree.png where it
   fills the width. At the old 26px the tree rendered ~40% smaller than before
   and the stars became 1–2px specks that read as dust. 26 / 0.76 ≈ 34 puts the
   tree back at its previous size. Same reasoning for the footer/About marks. */
.nav__logo-mark {
  width: 34px;
  height: auto;
}

.nav__wordmark {
  font-family: var(--font-heading);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-size: var(--fs-small); /* was a one-off 0.9rem — on the type scale now */
  color: var(--color-cream);
  white-space: nowrap;
}

.nav__links {
  display: flex;
  /* Gap trimmed to --space-2xs; the links now carry --space-2xs of inline
     padding each, so text-to-text spacing stays ~the old --space-md while
     the padding widens short links (FAQ, About) to clear the 44px touch
     minimum on width, not just height. */
  gap: var(--space-2xs);
}

.nav__links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px; /* touch target (WCAG 2.5.5 / Apple HIG) — text is only ~18px tall */
  padding-inline: var(--space-2xs); /* widens short links to >=44px too */
  font-family: var(--font-heading);
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--color-cream);
  transition: color var(--transition-base);
}

.nav__links a:hover,
.nav__links a:focus-visible {
  color: var(--color-sage-light);
}

/* Hamburger toggle — hidden on desktop (inline links are shown instead). */
.nav__toggle {
  display: none;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  gap: 5px;
  flex-direction: column;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
}

.nav__toggle-bar {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--color-cream);
  transition: transform var(--transition-base), opacity var(--transition-base);
}

/* Open state → morph the three bars into an X. */
.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}
.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(2) {
  opacity: 0;
}
.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

/* ==========================================================================
   Mobile nav (<=640px) — collapsing hamburger menu. Replaces the earlier
   "stack + wrap" fallback: with 5 links at 44px touch height, wrapping made
   the nav ~150px tall (ate the mobile viewport, broke anchor scroll-margin).
   The client now prioritises mobile, so a hamburger is the right call — the
   closed bar is a compact ~56px, the open menu is full-width 48px rows.
   ("no hamburger" was decided at 3 links, pre-mobile-priority — see
   CLAUDE.md/PROJECT_PLAN.md decision log.)
   ========================================================================== */
@media (max-width: 640px) {
  /* 3-column grid: a spacer the same width as the toggle on the left, the
     brand centred in the middle, the hamburger on the right — so the brand
     is TRULY centred (the left spacer balances the toggle), and the toggle
     stays pinned to the top row even when the menu drops to row 2. */
  .nav__inner {
    display: grid;
    grid-template-columns: 44px 1fr 44px;
    align-items: center;
    row-gap: 0;
  }

  .nav__brand {
    grid-column: 2;
    justify-self: center;
  }

  .nav__toggle {
    grid-column: 3;
    justify-self: end;
    display: inline-flex;
  }

  .nav__links {
    grid-column: 1 / -1; /* full-width second row */
    flex-direction: column;
    gap: 0;
    /* collapsed by default; initNav toggles .is-open */
    display: none;
    padding-top: var(--space-2xs);
  }

  .nav__links.is-open {
    display: flex;
  }

  .nav__links a {
    width: 100%;
    justify-content: flex-start;
    min-height: 48px;
    padding-inline: var(--space-2xs);
    border-top: 1px solid rgba(245, 239, 217, 0.1);
  }

  /* No --nav-height override needed: the closed hamburger bar is ~61px, the
     same as the desktop nav, so the base 64px token already clears it. (The
     menu opening to ~309px doesn't matter for anchor scroll-margin — tapping
     a menu link closes the menu first, so the scroll happens against the
     closed 61px bar.) */
}

/* Wordmark stays visible down to the smallest phones now — the centred brand
   has room for it (was hidden <=480px back when the brand shared a
   space-between row with the links/toggle). */

/* ==========================================================================
   Regulatory trust bar — a slim "verified" strip in the warm-white gap
   between the nav and the hero card. Understated by design: sage line-icons
   + small navy labels, no heavy borders/fills — a quiet credential signal,
   not a marketing banner. See the placement note in index.html for why it
   sits here (not between hero and Why Ethical, not inside the dark hero).
   ========================================================================== */
.trust-bar {
  padding-top: var(--space-md);
  padding-bottom: var(--space-2xs);
}

.trust-bar__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm) var(--space-lg);
}

.trust-bar__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--color-navy);
}

.trust-bar__icon {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  /* --color-sage-deep to match every other decorative icon on the site
     (feature-row, contact channel, service-accordion) — one icon system. */
  color: var(--color-sage-deep);
}

/* ==========================================================================
   Card system — every section below the nav is a contained card so the
   page's neutral --color-bg shows through between them. .card carries the
   shared shape (size/margin/radius/clipping); .card--dark / .card--light
   carry the two surface treatments ("authority bookend" vs. everyday
   content). Combine with a component class (.hero, .services, etc.) for
   anything section-specific.
   ========================================================================== */
.card {
  position: relative;
  width: calc(100% - 2 * var(--space-md));
  max-width: 1320px;
  margin: var(--card-gap) auto;
  border-radius: var(--card-radius);
  overflow: hidden;
  /* Nav is position:sticky at the top — without this, any anchor jump
     (nav click, or another page's index.html#id link) lands a section
     flush under the nav, obscuring its heading. Applies to every card
     since every anchor target (#hero, #why-ethical, #services, #faq,
     #contact) is one. */
  scroll-margin-top: var(--nav-height);
}

.card--dark {
  background: var(--color-ink);
  color: var(--color-text-inverse);
}

/* Light cards need their own shadow to read as a distinct shape —
   --color-surface (cream) is close enough to --color-bg (off-white) that
   a boundary alone wouldn't otherwise be obvious. Shadow-only, no border —
   border+shadow together read as slightly busy/dated; shadow alone is the
   more contemporary, minimal way to separate a surface from its background. */
.card--light {
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
}

/* Subtle tone alternation for visual sequencing — applied to every other
   light section so the run of content cards between the dark hero and dark
   footer doesn't read as one flat cream block. Uses the warm-white page
   tone (--color-bg) against the cream (--color-surface) of the default
   light card — the two warm neutrals differ by only a few points, so it's
   a gentle rhythm, not a stripe. The shadow still defines the card even
   though its fill matches the page background. */
.card--light.card--alt {
  background: var(--color-bg);
}

@media (max-width: 767px) {
  .card {
    margin: var(--space-md) auto;
    border-radius: 12px;
  }
}

/* ==========================================================================
   Hero card — the dark "authority bookend" card. A normal static card in
   document flow (no scroll-pin/scroll-jack — that machinery is gone along
   with the fragility it brought). Two real vertical zones stacked in flex
   flow keep text and tree from ever occupying the same space: content zone
   on top with its own height, tree zone taking the remaining space below
   and bottom-anchoring the tree with explicit breathing room (--space-lg)
   before the card's own bottom edge — the root fix for tree clipping.
   ========================================================================== */
.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 480px; /* floor only, so a short card never looks cramped — the
                        tree's own intrinsic height is what actually sizes
                        the card now, not a fight over leftover flex space */
}

.hero__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}

/* Stars — real brand asset (assets/img/star.png), scattered as independent
   instances across the full hero background, independent of the tree's
   position. Gentle ambient opacity "twinkle": each star fades between its
   own base opacity (set per-star via the inline --star-opacity custom
   property, which preserves the depth those varied values give) and ~70% of
   it, like real starlight. Ordinary CSS loop, not scroll-tied. Organic /
   non-synced via per-star inline animation-delay (phase) + varied durations
   below (period) — so they never pulse in lockstep. */
.hero__star {
  position: absolute;
  height: auto;
  pointer-events: none;
  opacity: var(--star-opacity, 0.5);
  animation: star-twinkle 3.6s ease-in-out infinite;
}

/* Mobile-only extras — the desktop hero already has its full 10-star field and
   doesn't want these on top of it. Must stay ABOVE the max-width:767px block
   that flips this to `display:block` (equal specificity → last one wins). */
.hero__star--mobile-only {
  display: none;
}

/* Vary the period per star so phase + period both differ — no two stars
   share a cycle, so the field never visibly re-syncs. */
.hero__star:nth-child(3n)   { animation-duration: 3s; }
.hero__star:nth-child(3n+1) { animation-duration: 4.2s; }
.hero__star:nth-child(3n+2) { animation-duration: 5.1s; }

/* Pulses opacity between the star's base value (100%) and ~20% of it — the
   swing has been widened twice: a 70% floor was imperceptible, 40% still read
   as too subtle on a phone, so it now nearly fades out at the trough for a
   twinkle that actually registers. Uses the per-star --star-opacity so
   brighter/dimmer stars keep their relative depth. */
@keyframes star-twinkle {
  0%, 100% { opacity: var(--star-opacity, 0.5); }
  50%      { opacity: calc(var(--star-opacity, 0.5) * 0.2); }
}

.hero__content {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  text-align: center;
  color: var(--color-text-inverse);
  padding-inline: var(--space-md);
  padding-top: var(--space-xl);
  max-width: 820px; /* widened from 720 so the longer headline wraps to fewer lines on desktop */
}

/* font-family/weight/style come from the global h1 rule — Fraunces,
   upright, weight 500, same treatment as .why-ethical__headline. Client
   preference, after also trying: italic+700, and Inter (no serif) here.
   Only hero-specific size/spacing need overriding below. */
.hero__title {
  font-size: var(--fs-h1);
  margin-bottom: var(--space-md);
}

.hero__subtitle {
  font-size: var(--fs-body-lg);
  color: rgba(245, 239, 217, 0.8);
  margin-bottom: var(--space-lg);
  max-width: 560px;
  margin-inline: auto;
}

.hero__ctas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
}

/* Tree zone — normal flow, not a fixed-height box the tree has to compete
   for space in. Height is intrinsic: the tree's own rendered height plus
   explicit top/bottom padding, so the card grows to fit it instead of
   clipping it. This is the root fix for the clipping bug — the old
   flex:1-fills-leftover-space approach could size this zone shorter than
   the tree actually needed, and overflow:hidden silently ate the canopy. */
.hero__tree-zone {
  position: relative;
  width: 100%;
  display: flex;
  justify-content: center;
  padding-block: var(--space-lg);
}

/* Tree — real brand asset (assets/img/tree.png), the hero's large centered
   anchor (same compositional role as a hero photo subject). Normal flow +
   flex-centered (no absolute positioning/translate hack needed). Scroll
   parallax is applied via JS (assets/js/main.js, initHeroParallax) — see
   there for why the old ambient CSS float was dropped (both would have
   fought over the transform property). */
.hero__tree {
  /* Slightly smaller than before (max 380px -> 340px) — client feedback
     that it felt a bit tight/cramped against the curtain-occlusion card
     rising over it; a bit more breathing room around the branches. */
  width: clamp(200px, 28vw, 340px);
  height: auto;
}

@media (max-width: 767px) {
  .hero {
    min-height: 400px;
  }

  .hero__content {
    padding-top: var(--space-lg);
  }

  .hero__tree-zone {
    padding-block: var(--space-md);
  }

  .hero__tree {
    width: clamp(160px, 44vw, 220px);
  }

  /* Hide the stars that sit in the central title/subtitle column on the
     narrow mobile hero — they crowded the centred headline. The edge stars
     (corners) stay for atmosphere. */
  .hero__star--hide-mobile {
    display: none;
  }

  /* ...and add extras back in the lower side margins, so mobile isn't left with
     a sparse 4-star field. See the markup for how these positions were picked.
     The base `display:none` for this class is declared ABOVE (with .hero__star),
     deliberately — same specificity, so whichever comes last in source order
     wins, and putting it after this block would hide these on mobile too. */
  .hero__star--mobile-only {
    display: block;
  }
}

/* ==========================================================================
   Feature row — icon + label band, now a light card like everything else
   ========================================================================== */
.feature-row {
  padding-block: var(--space-lg);
}

/* DriveEazy-style row: each item is an icon badge (left) + a two-line label
   (bold title over a muted sub) on the right, with thin dividers between
   items. gap:0 so the dividers (border-right) sit evenly, not floating in a
   flex gap. */
.feature-row__list {
  display: grid;
  /* minmax(0, 1fr), NOT 1fr: a bare `1fr` is minmax(auto, 1fr), and that `auto`
     floor is min-content — so once the container gets tight the longer labels
     refuse to shrink and the four cells stop being equal. Measured at 768px
     they came out 206.8 / 202.9 / 157.6 / 174.9px, which reads as sloppy
     rather than as a grid. minmax(0, …) removes the floor and they stay equal. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0;
}

.feature-row__item {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--space-sm);
  padding-inline: var(--space-md);
  text-align: left;
}

.feature-row__item:not(:last-child) {
  border-right: 1px solid var(--color-border);
}

.feature-row__text {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}

.feature-row__title {
  font-weight: 600;
  font-size: var(--fs-body);
  color: var(--color-navy);
}

.feature-row__sub {
  font-size: var(--fs-small);
  color: var(--color-text-muted);
}

/* Rounded-SQUARE badge (was a circle) to match the DriveEazy reference —
   sage-light fill keeps it in the site's icon system. */
.feature-row__icon-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--color-sage-light);
}

.feature-row__icon {
  width: 22px;
  height: 22px;
  color: var(--color-sage-deep);
}

@media (max-width: 767px) {
  /* The 48px (--space-lg) block padding left the card looking half-empty on a
     phone — the divided cells are short. Tighten it so the card wraps the row. */
  .feature-row {
    padding-block: var(--space-md);
  }

  /* 2x2 grid on mobile — this WAS a swipeable DriveEazy-style row, changed after
     a UX review: only ~1.6 of the 4 cells fit on a phone, so half the content sat
     behind a gesture users may never try, and the peeking cell was cut mid-word
     ("Tran"), reading as broken text rather than "there's more to see". A
     horizontal scroll earns its keep with many items; with four short ones,
     showing all four at once is simply better. The cell styling (badge +
     two-line label) is unchanged — only the container changes. */
  .feature-row__list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0; /* dividers separate the cells, not a gap */
  }

  /* Icon stacked ABOVE the label here, rather than the desktop row's badge-left.
     In a half-width grid cell the horizontal arrangement leaves the text only
     ~75px, which isn't enough for a long single word — "Independent" can't wrap,
     so it either overflowed the card or (with overflow-wrap) broke mid-word into
     "Independen / t". Stacking gives the label the cell's full width, which fits
     every label cleanly down to 320px, and centred cells scan better in a grid. */
  .feature-row__item {
    min-width: 0; /* grid items floor at min-content — let cells shrink */
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--space-md) var(--space-2xs);
    gap: var(--space-2xs);
  }

  .feature-row__text {
    min-width: 0;
    align-items: center;
  }

  /* Dividers re-mapped for a grid. The base `:not(:last-child)` rule borders
     items 1–3, which in a 2x2 draws a stray rule down the card's RIGHT edge
     (cell 2) and nothing under the top row. Instead: vertical rule on the left
     column only, horizontal rule under the top row only. */
  .feature-row__item:nth-child(even) {
    border-right: none;
  }

  .feature-row__item:nth-child(-n + 2) {
    border-bottom: 1px solid var(--color-border);
  }

  /* Deliberately NO nowrap here (the old scroll row had it): grid cells are only
     ~135px wide at 320px and "Direct WhatsApp" cannot fit on one line — nowrap
     would push it out of the card. Wrapping keeps it contained, and the grid
     equalises the heights of both cells in a row anyway. */
  .feature-row__title {
    font-size: var(--fs-small);
    font-weight: 700;
  }

  .feature-row__icon-badge {
    width: 36px;
    height: 36px;
    border-radius: 10px;
  }

  .feature-row__icon {
    width: 18px;
    height: 18px;
  }
}

/* `.card`'s `overflow: hidden` (for rounded-corner clipping everywhere else)
   breaks `position: sticky` for any descendant — any ancestor with
   `overflow` other than `visible` intercepts the sticky element's
   containing block, regardless of whether that ancestor is itself
   scrollable. `.why-ethical__pin`'s sticky was silently never sticking
   because of this. Nothing in Why Ethical overflows its own box (unlike
   the hero's tree/stars), so it's safe to opt this one card out. #why-ethical
   for specificity (beats plain `.card` regardless of source order). */
#why-ethical.card {
  overflow: visible;
}

/* The section had NO padding of its own — all its spacing came from the pin,
   which was fine while the pin was the last thing in the card. The closing
   context aside then landed flush against the card's bottom edge (measured: 0px
   beneath it), and because this card is `overflow: visible` (required for the
   sticky pin, above) its tinted background spilled past the rounded corner
   rather than being clipped by it. That's what read as "disorganised" — not the
   note's colour. Give it room to sit inside the card. */
.why-ethical {
  padding-bottom: var(--space-lg);
}

/* ==========================================================================
   Why Ethical — a LIGHT card (`.card--light`), unlike the hero. Deliberate:
   with Hero navy directly above it, an identical navy card read as flat/
   duplicated, and a subtly-different navy shade (tried first) still felt
   like two-of-the-same. Cream reads as a clean, distinct beat instead —
   the trade being that curtain occlusion now transitions from the hero's
   navy into a bright card (a deliberate reveal moment) rather than a
   same-tone "swallow." Text/border colors below are the light-card
   equivalents of what a dark card would use (`.card--light` itself
   supplies no text-color override, so headline/etc. get the inherited
   navy default; everything else here is explicit).

   Desktop (>=768px): pinned, scroll-driven list, modeled on equinix.com.
   `.why-ethical__scroller` is a tall wrapper (4 items * 50vh) that gives
   the section room to scroll through; `.why-ethical__pin` sticks to the
   viewport top for that whole range, so the two-column layout (label +
   headline on the left, list on the right) reads as "pinned" while
   `assets/js/main.js` (`initWhyEthicalPin`) toggles `.is-active` on the
   list item matching current scroll progress. See CLAUDE.md for why this
   `position: sticky` use is a deliberate, scoped exception to the
   "no scroll-pin" rule, not a quiet contradiction of it. The active item
   is a navy card (an echo of the hero) so it still pops against this
   card's now-cream background — the inverse of what it'd need on a dark
   card, where the active state was cream-on-dark instead.

   Mobile (<768px): no pin, no tall wrapper — `.why-ethical__scroller`
   and `.why-ethical__pin` fall back to normal auto-height flow, and each
   `.why-ethical__item` uses the site's shared `.reveal` fade-in (each one
   is also given the `.reveal` class in the HTML), same mechanism every
   other card uses. `initWhyEthicalPin` itself no-ops below 768px.
   ========================================================================== */
.why-ethical__scroller {
  position: relative;
}

.why-ethical__pin {
  padding-block: var(--space-lg);
}

.why-ethical__grid {
  display: grid;
  gap: var(--space-lg);
}

.why-ethical__label {
  display: block;
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-sage-deep); /* --color-sage alone is under 4.5:1 on cream at this size */
  margin-bottom: var(--space-md);
}

.why-ethical__headline {
  font-size: var(--fs-h2);
  max-width: 20ch;
}

/* Client's two intro paragraphs, under the headline in the pinned left column.
   Capped at 46ch for a readable measure — the column is wide on desktop and
   unmeasured body copy there runs to an uncomfortable line length. */
.why-ethical__lead {
  margin-top: var(--space-md);
  max-width: 46ch;
  color: var(--color-text-muted);
}

/* Client's closing regulatory-context note. Sits outside the pinned scroller
   (see index.html) so it can't fight the pin's fixed 85vh height.

   Styled as a distinct aside rather than left as loose body text: it's a
   footnote about the market, not part of the approach list, and as a bare
   paragraph trailing the card it read as something that had been forgotten
   there. The sage left-accent + sage-light tint is the same vocabulary the
   mobile active item uses, so it looks native to the section.

   NOTE: this is a child of .container, never .container itself — putting a
   max-width on .container (margin-inline:auto) re-centres it on the page
   instead of aligning it to the section's content edge. See index.html. */
.why-ethical__context {
  margin-top: var(--space-lg);
  padding: var(--space-lg);
  border-left: 3px solid var(--color-sage);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--color-sage-light);
  color: var(--color-text-muted);
  font-size: var(--fs-small);
  /* No max-width now that it's a two-column note — it spans the container,
     which is what keeps it aligned with the grid above rather than floating. */
}

.why-ethical__context-label {
  display: block;
  margin-bottom: var(--space-sm);
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  /* NOT --color-sage-deep: that's tuned for the cream page background and only
     reaches 4.42:1 on this aside's sage-light fill — a (marginal) WCAG AA fail
     at this size. This darker green measures 5.03:1 on --color-sage-light. */
  color: #556648;
}

/* Two columns so the India half and the international half read as distinct
   thoughts instead of one acronym wall. Each column keeps its own measure. */
.why-ethical__context-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-md) var(--space-xl);
}

.why-ethical__context-cols p {
  max-width: 52ch;
}

.why-ethical__context-cta {
  margin-top: var(--space-md);
}

@media (max-width: 767px) {
  /* Stack on mobile — two columns of body copy at phone width would leave
     each one a ~20-character ribbon. */
  .why-ethical__context-cols {
    grid-template-columns: 1fr;
  }
}

.why-ethical__item {
  padding-block: var(--space-md);
  padding-left: var(--space-md);
  border-left: 3px solid transparent;
  border-top: 1px solid var(--color-border);
}

.why-ethical__item:last-child {
  border-bottom: 1px solid var(--color-border);
}

.why-ethical__item-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2xs);
  /* Now a <button> (was a div) for keyboard access — reset the default
     button chrome back to how the div looked before. */
  width: 100%;
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.why-ethical__item-head:focus-visible {
  outline: 2px solid var(--color-sage);
  outline-offset: 4px;
}

.why-ethical__number {
  display: none;
}

.why-ethical__item p {
  color: var(--color-text-muted);
  margin-top: var(--space-2xs);
}

/* Desktop-only element (see the min-width:768px block) — mobile uses each
   item's own inline <p> above instead, never this shared panel. */
.why-ethical__description {
  display: none;
}

/* Mobile scroll-highlight: initWhyEthicalMobile marks the item passing the
   viewport centre as .is-active. Sage left-accent + a subtle sage-light tint
   (lighter than the desktop navy-fill stepper — right weight for a plain
   list on the cream card). Transition makes the hand-off smooth as you
   scroll. Scoped to mobile so it never collides with the desktop stepper's
   own .is-active. */
/* 1023, not 767 — this is the Why Ethical single-column treatment, and TABLETS
   use it too now that the pin needs 1024 (see the tablet gate below). Only the
   *phone*-specific type compression inside is still held at 767. */
@media (max-width: 1023px) {
  /* Same idea as the desktop short-window compression: the client's copy is
     long, and at full body size the intro alone ran most of a phone screen
     before the first approach appeared. Tighter type/leading here — the
     per-item scroll spacing below is deliberately NOT touched, since that's
     what gives each reveal its scroll path. */
  .why-ethical__lead {
    margin-top: var(--space-sm);
  }

  .why-ethical__context {
    margin-top: var(--space-md);
    padding: var(--space-sm) var(--space-md);
  }

  .why-ethical__item {
    /* Longer scroll "path" between items — collapsed titles otherwise sit ~88px
       apart, so the centre band flips from one to the next in barely any scroll.
       The extra travel is added as MARGIN (a non-tinted spacer), not padding, so
       the active item's tinted box stays wrapped tight around its text instead of
       floating in a big empty tinted rectangle. Sibling margins collapse, so this
       is the gap between rows, not double. ~2x the head-to-head travel. */
    margin-block: clamp(2rem, 8vh, 4rem);
    transition: background var(--transition-fast), color var(--transition-fast),
                border-color var(--transition-fast);
  }

  /* Navy fill for the active item — the SAME language as the desktop stepper,
     where the active approach and its description panel are both navy. Mobile
     used a sage-light tint instead, which made the two platforms speak
     differently about the same state for no real reason: navy on this site means
     "this is the emphasised/active one" (hero, footer, the desktop active step),
     and that's exactly what this is. Note this is the opposite call from the
     `.why-ethical__context` note, which stays a quiet sage aside precisely
     because it's a passive footnote, not an active state. */
  .why-ethical__item.is-active {
    background: var(--color-navy);
    border-left-color: var(--color-sage);
    border-radius: 0 var(--radius) var(--radius) 0;
    padding-right: var(--space-md);
  }

  /* Both text colours must flip with the fill — the base h3 is navy and the
     base <p> is muted grey, either of which on a navy fill is unreadable. */
  /* Colour transitions matter now that .is-active LEAVES an item while it stays
     open: without them the text would snap cream -> dark the instant the navy
     fill starts fading out, which reads as a flicker. */
  .why-ethical__item h3 {
    transition: color var(--transition-fast);
  }

  .why-ethical__item.is-active h3 {
    color: var(--color-cream);
  }

  .why-ethical__item.is-active p {
    color: rgba(245, 239, 217, 0.8);
  }
}

/* Reveal-on-scroll: each description stays collapsed (titles read as a tidy
   list) and unfolds only when its item hits the viewport centre.

   Animated with `grid-template-rows: 0fr -> 1fr`, NOT max-height. max-height was
   the original approach and it's why the reveal felt janky: you have to pick a
   ceiling taller than the tallest content (32rem/512px here), but the element
   stops growing at its OWN height — so the visible motion finishes early and the
   rest of the duration animates nothing. Measured against the real copy: item 2
   (96px) hit full size at 19% of the timeline, items 1/3 (168px) at 33%, item 4
   (336px) at 66%. Expanding finished early; COLLAPSING sat still for 81% and
   then snapped shut — and every item stalled by a different amount, so no two
   reveals felt alike. `1fr` resolves to the content's exact height, so the
   easing is honest in both directions and consistent across items.

   The <li> has exactly two children (the head button, the <p>), which is what
   makes `auto 0fr` work without adding a wrapper element.

   Guarded by prefers-reduced-motion:no-preference on purpose: initWhyEthicalMobile
   bails under reduced motion (no .is-active is ever set), so collapsing here
   unconditionally would leave every description permanently hidden. When motion
   is reduced, the descriptions stay in their default (visible) state. */
/* Phone only — a tablet's single column is ~720px wide, plenty for full-size
   lead text; shrinking it there would just make a wide column of small type. */
@media (max-width: 767px) {
  .why-ethical__lead {
    font-size: var(--fs-small);
  }
}

/* 1023, not 767 — tablets use this reveal too now that the pin needs 1024. */
@media (max-width: 1023px) and (prefers-reduced-motion: no-preference) {
  /* The `.reveal` selector list here is NOT redundant — it is the whole fix.
     These items are class="why-ethical__item reveal". The global `.reveal` rule
     sets `transition: opacity, transform` with the SAME specificity (0,1,0) and
     sits LATER in this file, so it silently replaced this entire transition list
     and grid-template-rows was never animated at all: every description snapped
     open instantly, which is what the client saw as "choppy". Confirmed with
     CDP's matched-rules API — computed transition-property read back as just
     "opacity, transform".

     `.why-ethical__item.reveal` is (0,2,0) and wins. opacity/transform stay in
     the list because dropping them would kill the shared entrance fade these
     items also rely on.

     The desktop block below already defends against this exact collision and
     says so in its own comment; this is the same trap, one breakpoint over. */
  .why-ethical__item,
  .why-ethical__item.reveal,
  .why-ethical__item.reveal.is-visible {
    display: grid;
    grid-template-rows: auto 0fr;
    transition: grid-template-rows var(--transition-base),
                opacity var(--transition-base), transform var(--transition-base),
                background var(--transition-fast), color var(--transition-fast),
                border-color var(--transition-fast);
  }

  /* .is-open, not .is-active — it latches (see initWhyEthicalMobile). Tying the
     height to .is-active meant leaving an item RE-COLLAPSED it, and that collapse
     is above whatever you're reading next, so the list yanked upward mid-scroll
     (measured: the active item jumped up to 161px by layout alone). Expanding
     only means the growth is always below you. */
  /* Must mirror the SAME three selector forms as the collapsed rule above, or
     it loses. The collapsed rule needs `.reveal.is-visible` (0,3,0) to beat the
     global `.reveal`; these items always carry `.is-visible`, so a plain
     `.why-ethical__item.is-open` (0,2,0) would be out-ranked by it and the item
     would never open at all. Escalating one side of a pair and not the other is
     exactly how this broke once already. Desktop's `.is-active` rule mirrors its
     base rule for the same reason — keep the two in lockstep. */
  .why-ethical__item.is-open,
  .why-ethical__item.is-open.reveal,
  .why-ethical__item.is-open.reveal.is-visible {
    grid-template-rows: auto 1fr;
  }

  .why-ethical__item p {
    /* No spacing at all in the collapsed state — it moves to .is-active below.
       Neither margin NOR padding survives a collapse: margins aren't clipped by
       overflow, and padding sets a floor on the box height even at `0fr` (it
       measured 8px, a ghost gap under every collapsed title). Only the active
       item pays for its own spacing. */
    margin-top: 0;
    padding-top: 0;
    min-height: 0; /* grid items floor at min-content — without this it can't collapse */
    opacity: 0;
    overflow: hidden;
    transition: opacity var(--transition-base), color var(--transition-fast);
  }

  /* No max-height ceiling to maintain any more — `1fr` above resolves to the
     content's own height, so this can't silently clip long copy the way the old
     8rem ceiling clipped the client's 336px Shariah description. */
  .why-ethical__item.is-open p {
    /* `1fr` measures the content box including this padding, so it's part of the
       animated height rather than a jump. */
    padding-top: var(--space-2xs);
    opacity: 1;
  }
}

/* TABLET GATE — the pin needs 1024px, not 768px (raised 2026-07-16).
   At 768px the two-column pin gives each column ~288px: the navy description
   panel became a ~1100px-tall ribbon wrapping at four words a line, the intro
   column sat in a matching ribbon, and the content overflowed the 85vh pin by
   43px on an iPad portrait (768x1024) — the same spill that overprints the
   context note. It was a desktop layout crammed onto a tablet.
   Tablets now get the single-column scroll-reveal treatment instead (the same
   one phones use, which is designed for a narrow single column), and the pin is
   reserved for viewports with room for two real columns. iPad landscape (1024)
   clears the gate and keeps the pin, with ~416px columns, which measures clean.

   min-height gate alongside the width one: at 85vh pin height, a very short
   viewport (a phone/phablet in landscape — e.g. 844x390) doesn't have room for
   the intro column + a 4-item list without real overflow. Below 600px tall,
   fall back to the plain list regardless of width.
   JS (initWhyEthicalPin's isMobile()) mirrors this same compound check —
   update both together, and note initHeroParallax has its OWN unrelated
   isMobile() that must not be dragged along.

   NOTE: this was briefly raised to 780px to stop the client's longer copy
   spilling out of the pin on short windows. That was the wrong lever — it
   silently took the whole pinned design away from anything under 780px tall
   (a 1324x612 window, 1366x768 laptops), which is most windowed browsing.
   The fit problem is solved by the `max-height` compression block below
   instead, which shrinks the content to match a short pin box. Keep this at
   600. */
@media (min-width: 1024px) and (min-height: 600px) {
  .why-ethical__scroller {
    height: calc(4 * 50vh); /* 4 items — trimmed from 70vh/item, less scroll for a smaller card */
  }

  .why-ethical__pin {
    position: sticky;
    top: var(--nav-height);
    height: 85vh;
    display: flex;
    align-items: center;
    padding-block: 0;
  }

  /* The two columns are very uneven now that the client's copy is in: the
     intro is ~405px against the list column's ~651px. Left to stretch, the
     intro's text sat pinned to the top with ~245px of dead space beneath it.
     Centring it against the taller column reads as deliberate composition
     instead of a layout that ran out of content. */
  .why-ethical__intro {
    align-self: center;
  }

  .why-ethical__grid {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-xl);
    width: 100%;
  }

  /* Desktop trades the mobile plain-list look for a stepper. Each item is
     now FIXED height regardless of active state — only paint-only
     properties (background, color, border, shadow) differ between active/
     inactive, none of which affect box size, so the list never reflows as
     you scroll between steps. The active item's description used to live
     inline (animated via max-height, growing the pill into a card) — that
     caused every item below the active one to visibly jump position as
     you stepped through the list. Description now lives in the separate
     .why-ethical__description panel below the list instead (min-height
     reserved there, JS keeps it in sync with the active item — see
     initWhyEthicalPin in main.js). Each item's own <p> is display:none
     here; it stays in the DOM for mobile's plain-list fallback, which
     doesn't use the shared panel at all.

     These items also get `.is-visible` added by the shared reveal
     observer once they scroll into view (they're still `.reveal`
     elements, for the mobile fallback). The global `.reveal.is-visible`
     rule has the same specificity as a plain `.why-ethical__item.reveal`
     override and comes later in the file, so it would silently win once
     `.is-visible` lands — both the plain and `.is-visible` forms are
     listed here so desktop's look is driven by `.is-active` either way. */
  .why-ethical__item,
  .why-ethical__item.reveal,
  .why-ethical__item.reveal.is-visible {
    display: flex;
    align-items: center;
    background: transparent;
    color: var(--color-text-muted);
    border: 1.5px solid rgba(107, 127, 94, 0.4);
    border-radius: var(--radius);
    padding: var(--space-sm) var(--space-md);
    margin-bottom: var(--space-sm);
    opacity: 1;
    transform: none;
    transition: background var(--transition-fast), color var(--transition-fast),
      border-color var(--transition-fast), box-shadow var(--transition-fast);
  }

  .why-ethical__item:last-child {
    margin-bottom: 0;
  }

  .why-ethical__item-head {
    margin: 0;
  }

  .why-ethical__item h3 {
    font-size: var(--fs-body);
  }

  /* Always visible on desktop now (was active-only) — reads as a clean
     numbered stepper even before you've scrolled to activate any of them. */
  .why-ethical__number {
    display: inline;
    font-weight: 700;
  }

  .why-ethical__item.is-active,
  .why-ethical__item.is-active.reveal,
  .why-ethical__item.is-active.reveal.is-visible {
    background: var(--color-navy);
    color: var(--color-cream);
    border: none;
    border-left: 4px solid var(--color-sage);
    box-shadow: var(--shadow-md);
  }

  .why-ethical__item p {
    display: none;
  }

  .why-ethical__description {
    min-height: 100px;
    display: flex;
    align-items: center;
    margin-top: var(--space-lg);
    padding: var(--space-md) var(--space-lg);
    background: var(--color-navy);
    color: rgba(245, 239, 217, 0.85);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
    /* Cross-fade instead of an instant text snap — JS toggles .is-fading
       around the textContent swap (see initWhyEthicalPin in main.js).
       Duration here must match the setTimeout delay in that JS. */
    transition: opacity 110ms ease;
  }

  .why-ethical__description.is-fading {
    opacity: 0;
  }

  /* The active item's "pop" (transform: scale(1.02)) is declared further
     down, alongside the entrance-reveal rules — it needs higher
     specificity than the entrance reset to win once revealed, so it's
     grouped there rather than here (see that comment for why). Transform
     is otherwise unused on this element (only paint properties differ
     between active/inactive, by design, to keep the list fixed-height/
     shift-free), so the scale doesn't reintroduce any layout shift. */

  /* Entrance — fade + slide up, staggered, the first time this section
     scrolls into view (grid.is-revealed, toggled once by an
     IntersectionObserver in initWhyEthicalPin). Transform/opacity only,
     and applied to descendants of .why-ethical__pin, not the pin or the
     section itself — see the JS comment for why that distinction matters
     here (transform on an ancestor of a position:sticky element breaks
     its sticky positioning; on a sticky element's own children, it's fine). */
  .why-ethical__intro,
  .why-ethical__list .why-ethical__item {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 320ms ease, transform 320ms ease,
      background var(--transition-fast), color var(--transition-fast),
      border-color var(--transition-fast), box-shadow var(--transition-fast);
  }

  .why-ethical__grid.is-revealed .why-ethical__intro {
    opacity: 1;
    transform: none;
  }

  .why-ethical__grid.is-revealed .why-ethical__list .why-ethical__item {
    opacity: 1;
    transform: none;
  }

  /* Higher specificity than the reset above (5 classes vs. 4), so this
     correctly wins for whichever item is active once revealed — without
     it, the reset's `transform: none` (also matching active items) would
     override the scale(1.02) pop rule above despite coming later in the
     file, since it's more specific. */
  .why-ethical__grid.is-revealed .why-ethical__list .why-ethical__item.is-active {
    transform: scale(1.02);
  }

  /* transition-delay values are listed per property in the same order as
     the `transition` list above (opacity, transform, background, color,
     border-color, box-shadow) — all 6 spelled out explicitly, rather than
     e.g. 3 values, because a shorter delay list *cycles* to cover the
     remaining properties instead of defaulting them to 0, which would
     have put a stray stagger delay on the active/inactive color transition. */
  .why-ethical__list .why-ethical__item:nth-child(1) { transition-delay: 0ms, 0ms, 0ms, 0ms, 0ms, 0ms; }
  .why-ethical__list .why-ethical__item:nth-child(2) { transition-delay: 50ms, 50ms, 0ms, 0ms, 0ms, 0ms; }
  .why-ethical__list .why-ethical__item:nth-child(3) { transition-delay: 100ms, 100ms, 0ms, 0ms, 0ms, 0ms; }
  .why-ethical__list .why-ethical__item:nth-child(4) { transition-delay: 150ms, 150ms, 0ms, 0ms, 0ms, 0ms; }
}

/* --------------------------------------------------------------------------
   Short-window compression for the pin.

   The problem this solves: the pin box is 85vh (a function of the window) but
   its content is a fixed ~651px (the list + the description panel, which hits
   288px on the long Shariah step). On a short-but-real desktop window — a
   1324x612 browser window, a 1366x768 laptop — 85vh is only 520–653px, so the
   content doesn't fit. The pin doesn't clip, so it SPILLED out the bottom and
   overprinted the closing context paragraph.

   The fix is to make the content smaller when the window is short, so it still
   fits the pin. The tempting alternative — raising the pin's min-height gate
   until only tall windows qualify — was tried and reverted: it silently took
   the entire pinned design away from most windowed browsing, which is a much
   worse outcome than slightly tighter type. Tall windows (>860px) are
   unaffected and keep the full-size treatment.
   -------------------------------------------------------------------------- */
@media (min-width: 1024px) and (min-height: 600px) and (max-height: 860px) {
  .why-ethical__headline {
    font-size: clamp(1.5rem, 2.6vw, 2rem);
  }

  .why-ethical__lead {
    margin-top: var(--space-sm);
    font-size: var(--fs-small);
  }

  /* 4 items x 16px of trimmed padding is the single biggest saving here. */
  .why-ethical__item {
    padding-block: var(--space-2xs);
  }

  .why-ethical__description {
    min-height: 0;
    margin-top: var(--space-md);
    padding: var(--space-sm) var(--space-md);
    font-size: var(--fs-small);
  }
}

/* The shortest real windows (~600–700px tall, e.g. a 1324x612 browser window)
   were still ~11px over after the block above. Trim the last of the panel's
   own spacing rather than shrinking type any further — it's the cheapest
   remaining height and costs nothing visually at this size. */
@media (min-width: 1024px) and (min-height: 600px) and (max-height: 700px) {
  .why-ethical__item {
    padding-block: 6px;
  }

  .why-ethical__description {
    margin-top: var(--space-sm);
    padding: var(--space-2xs) var(--space-md);
  }
}

/* ==========================================================================
   Services — two deep-dive accordion cards (Retirement Planning, Wealth
   Management). Native <details>/<summary>, same no-JS/accessible pattern as
   the FAQ, but styled as full cards (the FAQ is a flat divided list) since
   each holds a lot of structured content. The +/- icon reuses the FAQ
   icon's technique.

   Note: an earlier 4-card grid (.services__grid + .service-card*) and the
   removed Testimonials section (.testimonials__grid + .testimonial-card)
   both lived here — that CSS was deleted 2026-07-14 when the grid was
   collapsed into these accordions and confirmed dead (no live markup used
   it). Recover from git history if either is ever wanted back.
   ========================================================================== */
.service-accordion {
  max-width: 820px;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.service-accordion__item {
  background: var(--color-bg);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition-base), transform var(--transition-fast);
}

.service-accordion__item[open] {
  box-shadow: var(--shadow-md);
}

/* Subtle hover lift on collapsed cards only — signals interactivity without
   spectacle. An open card doesn't lift (it's already the focus). */
.service-accordion__item:not([open]):hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.service-accordion__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-lg) var(--space-md);
  cursor: pointer;
  list-style: none;
}

.service-accordion__head::-webkit-details-marker {
  display: none;
}

.service-accordion__head-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

/* Card title — the client's own section name ("Retirement Planning" /
   "Wealth Management"). Small sage eyebrow above the hero tagline, same
   treatment as .section__eyebrow so the two service names read as labels,
   not competing headlines with the hero line below them. */
.service-accordion__title {
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-sage-deep);
}

.service-accordion__hero {
  font-family: var(--font-heading);
  font-size: var(--fs-h3);
  font-weight: 500;
  color: var(--color-ink);
  transition: color var(--transition-base);
}

.service-accordion__head:hover .service-accordion__hero,
.service-accordion__head:focus-visible .service-accordion__hero {
  color: var(--color-sage);
}

.service-accordion__summary {
  font-size: var(--fs-small);
  color: var(--color-text-muted);
}

/* +/- icon — same construction as .faq__icon, collapsing the vertical bar
   to a minus on [open]. Nudged down slightly so it aligns with the hero
   line, not the vertical centre of the taller two-line head text. */
.service-accordion__icon {
  flex: 0 0 auto;
  position: relative;
  width: 20px;
  height: 20px;
  margin-top: 4px;
  color: var(--color-sage-deep);
}

.service-accordion__icon::before,
.service-accordion__icon::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  background: currentColor;
  /* --transition-fast (~180ms), close to the 280ms body slide, so the icon
     resolves to its minus/plus in step with the panel rather than lagging on
     the slower 400ms base. */
  transition: transform var(--transition-fast);
}

.service-accordion__icon::before {
  width: 14px;
  height: 2px;
}

.service-accordion__icon::after {
  width: 2px;
  height: 14px;
}

.service-accordion__item[open] .service-accordion__icon::after {
  transform: rotate(90deg) scaleX(0);
}

.service-accordion__body {
  padding: 0 var(--space-md) var(--space-lg);
  color: var(--color-text-muted);
  /* height is animated inline by initAccordionMotion (main.js) between 0 and
     the measured height; overflow:hidden clips the padding during the slide.
     ~280ms sits in the requested 250-300ms range and reads as considered,
     not sluggish. The reduced-motion block below removes this so the JS
     bail-out leaves a clean instant toggle. */
  overflow: hidden;
  transition: height 280ms ease;
}

.service-accordion__body > p {
  max-width: 68ch;
}

.service-accordion__subhead {
  font-family: var(--font-heading);
  font-size: var(--fs-body-lg);
  font-weight: 500;
  color: var(--color-ink);
  margin-top: var(--space-lg);
  margin-bottom: var(--space-sm);
}

.service-accordion__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  max-width: 68ch;
}

.service-accordion__list > li {
  position: relative;
  padding-left: var(--space-md);
}

.service-accordion__list > li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-sage);
}

.service-accordion__list strong {
  color: var(--color-ink);
  font-weight: 600;
}

.service-accordion__sublist {
  margin-top: var(--space-sm);
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding-left: var(--space-md);
}

.service-accordion__sublist > li {
  position: relative;
  padding-left: var(--space-md);
}

.service-accordion__sublist > li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 5px;
  height: 2px;
  background: var(--color-sage);
}

/* Mobile indentation: the indent stacked three times (parent li 24 + sublist 24
   + sublist li 24 = 72px), squeezing sub-point text into a narrow column that
   wrapped every 3–4 words, and leaving a ~19px gutter between each dash and its
   text. Tightened to 16 + 8 + 16 = 40px, which keeps the nesting legible while
   giving the text back ~32px of line width and pulling the markers in close. */
@media (max-width: 767px) {
  .service-accordion__list > li {
    padding-left: var(--space-sm);
  }

  .service-accordion__sublist {
    padding-left: var(--space-2xs);
  }

  .service-accordion__sublist > li {
    padding-left: var(--space-sm);
  }
}

.service-accordion__cta {
  margin-top: var(--space-lg);
  /* Smaller text + tighter inline padding than the base button so "Book a
     Free Consultation" is a compact CTA (one line at ~390px+, a tidy two
     lines on the narrowest phones) rather than the tall wrapped pill the
     full-length label produced. */
  font-size: var(--fs-small);
  padding-inline: var(--space-md);
}

.service-accordion__disclaimer {
  margin-top: var(--space-md);
  font-size: var(--fs-small);
  color: var(--color-text-muted);
  opacity: 0.85;
}

.testimonial-card cite {
  display: block;
  margin-top: var(--space-sm);
  font-style: normal;
  color: var(--color-text-muted);
}

/* ==========================================================================
   About
   ========================================================================== */
/* Firm-level credentials line on the standalone About page (about.html) —
   deliberately not a stats grid like .about__stats below (that's numbers-
   as-hero-figures; this is a plain inline credentials strip, no individual
   bios attached). */
.about-page__credentials {
  text-align: center;
  color: var(--color-text-muted);
  font-size: var(--fs-small);
  font-weight: 500;
  letter-spacing: 0.02em;
  margin-bottom: var(--space-lg);
}

.about__copy {
  max-width: 720px;
  margin-inline: auto;
  text-align: center;
}

.about__copy p + p {
  margin-top: var(--space-md);
}

/* Homepage About is a teaser — this centres the "Learn More About Us" link
   to about.html below the hook sentence. */
.about__cta {
  margin-top: var(--space-lg);
  text-align: center;
}

.about__stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-lg);
  margin-top: var(--space-xl);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--color-border);
}

.about__stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-2xs);
}

.about__stat-value {
  font-family: var(--font-heading);
  font-size: var(--fs-h2);
  font-weight: 500;
  color: var(--color-sage);
}

.about__stat-label {
  font-size: var(--fs-small);
  color: var(--color-text-muted);
  max-width: 20ch;
}

/* Standalone stat row (about.html Credentials card) — drop the top border/
   margin that .about__stats carries for when it sits *below* other content. */
.about__stats--plain {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

@media (max-width: 767px) {
  .about__stats {
    grid-template-columns: 1fr;
    gap: var(--space-md);
  }
}

/* ==========================================================================
   About page (about.html) — dark intro band, principles, CTA. Built to echo
   the homepage's system (dark "authority" card, sage icons, stat row) so the
   sub-page reads as part of the same site.
   ========================================================================== */
.about-intro {
  position: relative;
  overflow: hidden;
  padding-block: clamp(3rem, 9vw, 6rem);
  text-align: center;
}

.about-intro__stars {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.about-intro__star {
  position: absolute;
  height: auto;
  opacity: var(--star-opacity, 0.5);
  animation: star-twinkle 3.6s ease-in-out infinite; /* shares the star-twinkle keyframe with .hero__star */
}

.about-intro__star:nth-child(3n)   { animation-duration: 3s; }
.about-intro__star:nth-child(3n+1) { animation-duration: 4.2s; }
.about-intro__star:nth-child(3n+2) { animation-duration: 5.1s; }

.about-intro__content {
  position: relative;
  z-index: 1;
}

/* 64px (was 54) — same 76%-of-frame correction as the nav/footer marks. This
   is the one slot big enough for the mark's stars to actually read. */
.about-intro__mark {
  width: 64px;
  height: auto;
  margin: 0 auto var(--space-md);
  opacity: 0.9;
}

/* Eyebrow on a dark card — the base .section__eyebrow is sage-deep, which
   disappears on navy; lift it to sage-light. */
.section__eyebrow--on-dark {
  color: var(--color-sage-light);
}

.about-intro__title {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-h1);
  color: var(--color-text-inverse);
  margin-bottom: var(--space-md);
}

.about-intro__lead {
  font-family: var(--font-heading);
  font-style: italic;
  font-weight: 500;
  font-size: var(--fs-body-lg);
  line-height: 1.4;
  color: rgba(245, 239, 217, 0.92);
  max-width: 34ch;
  margin-inline: auto;
}

.about-principles {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-lg);
  max-width: 900px;
  margin-inline: auto;
}

.about-principle__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--color-sage-light);
  color: var(--color-sage-deep);
  margin-bottom: var(--space-md);
}

.about-principle__icon svg {
  width: 24px;
  height: 24px;
}

.about-principle__title {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--fs-h3);
  margin-bottom: var(--space-sm);
}

.about-principle p {
  color: var(--color-text-muted);
}

.about-cta {
  text-align: center;
}

.about-cta__text {
  max-width: 46ch;
  margin: 0 auto var(--space-lg);
  color: var(--color-text-muted);
}

.about-cta__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
}

@media (max-width: 767px) {
  .about-principles {
    grid-template-columns: 1fr;
    gap: var(--space-lg);
  }
}

/* ==========================================================================
   FAQ — native <details>/<summary> accordion, no JS. Marker removed and
   replaced with a CSS-drawn +/x icon that rotates on [open], matching the
   nav/button hover-state treatment used everywhere else on the page.
   ========================================================================== */
.faq__list {
  max-width: 760px;
  margin-inline: auto;
}

.faq__item {
  border-bottom: 1px solid var(--color-border);
}

.faq__item:first-child {
  border-top: 1px solid var(--color-border);
}

.faq__question {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding-block: var(--space-md);
  font-family: var(--font-heading);
  font-size: var(--fs-h3);
  font-weight: 500;
  cursor: pointer;
  list-style: none;
  transition: color var(--transition-base);
}

.faq__question::-webkit-details-marker {
  display: none;
}

.faq__question:hover,
.faq__question:focus-visible {
  color: var(--color-sage);
}

.faq__icon {
  flex: 0 0 auto;
  position: relative;
  width: 20px;
  height: 20px;
}

.faq__icon::before,
.faq__icon::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  background: currentColor;
  /* --transition-fast to resolve in step with the ~280ms answer slide,
     matching the Services accordion icon. */
  transition: transform var(--transition-fast);
}

.faq__icon::before {
  width: 14px;
  height: 2px;
}

.faq__icon::after {
  width: 2px;
  height: 14px;
}

.faq__item[open] .faq__icon::after {
  transform: rotate(90deg) scaleX(0);
}

.faq__answer {
  padding-bottom: var(--space-md);
  color: var(--color-text-muted);
  max-width: 65ch;
  /* Height animated inline by initAccordionMotion (main.js), same ~280ms
     slide as the Services accordion — overflow:hidden clips the padding
     during the transition. Removed under reduced motion (below). */
  overflow: hidden;
  transition: height 280ms ease;
}

/* ==========================================================================
   Contact — two columns: info (eyebrow/headline/intro/WhatsApp/phone/email)
   on the left, a contact form on the right. .section__eyebrow/__title are
   centered by default (every other section using them wants that); this
   is the one place that needs them left-aligned instead.
   ========================================================================== */
.contact__grid {
  display: grid;
  gap: var(--space-xl);
}

/* Card treatment (was plain, unstyled text) — with the phone/email rows
   still removed pending real details, this column was just an eyebrow +
   headline + intro + one button next to the form's full boxed-in card,
   reading as sparse/unfinished rather than intentionally minimal. Matches
   .contact-form's own background/padding/radius/shadow so the two columns
   carry equal visual weight, instead of collapsing to one column — the
   channels rows (see the TODO above) will live in this same box once real
   contact details are ready, so it needs to hold together as a card either
   way, not just as a patch for the current sparse content. */
.contact__info {
  background: var(--color-bg);
  padding: var(--space-lg);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}

@media (max-width: 480px) {
  .contact__info {
    padding: var(--space-md);
  }
}

.contact__info .section__eyebrow,
.contact__info .section__title {
  text-align: left;
  margin-inline: 0;
}

.contact__info .btn {
  margin-bottom: var(--space-lg);
}

/* No trailing margin when the button is the last thing in the card (true
   right now, with .contact__channels still removed — see the TODO above
   .contact-form) — otherwise the card has empty space hanging below it. */
.contact__info .btn:last-child {
  margin-bottom: 0;
}

/* Smaller WhatsApp button on mobile — the base button's wide 48px inline
   padding made it oversized here on phones. Trims text + horizontal
   padding while keeping the vertical padding (so it stays >=44px tall). */
@media (max-width: 767px) {
  .contact__info .btn {
    font-size: var(--fs-small);
    padding-inline: var(--space-md);
  }
}

.contact__intro {
  max-width: 480px;
  margin-top: 0;
  margin-bottom: var(--space-lg);
  color: var(--color-text-muted);
}

.contact__channels {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.contact__channel {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  transition: color var(--transition-base);
}

.contact__channel:hover,
.contact__channel:focus-visible {
  color: var(--color-sage-deep);
}

.contact__channel-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--color-sage-light);
  color: var(--color-sage-deep);
}

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

.contact__channel-text {
  display: flex;
  flex-direction: column;
  font-size: var(--fs-small);
}

.contact__channel-label {
  font-weight: 600;
  color: var(--color-ink);
}

.contact__channel-value {
  color: var(--color-text-muted);
}

/* Non-interactive channel (the office address — it's a location, not a
   tel:/mailto: link), so it drops the pointer cursor and the hover colour
   shift the linked channels use. */
.contact__channel--static {
  cursor: default;
}

.contact__channel--static:hover,
.contact__channel--static:focus-visible {
  color: inherit;
}

/* Wrapping <address> — reset the browser's default italic so the address
   matches the rest of the info column. */
address.contact__channels {
  font-style: normal;
}

/* Short market-risk line under the contact grid (see the comment in
   index.html for why it's the short form, not the full footer disclaimer). */
.contact__disclaimer {
  margin-top: var(--space-lg);
  font-size: var(--fs-small);
  color: var(--color-text-muted);
  opacity: 0.85;
  text-align: center;
  max-width: 70ch;
  margin-inline: auto;
}

.contact-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  background: var(--color-bg);
  padding: var(--space-lg);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}

@media (max-width: 480px) {
  /* --space-lg (48px) on all sides, stacked with the container's and
     card's own mobile padding, leaves as little as ~130px of usable
     width for the inputs on a 320-375px phone — halve it there. */
  .contact-form {
    padding: var(--space-md);
  }
}

.contact-form__field input,
.contact-form__field textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-sm) var(--space-md);
  transition: border-color var(--transition-base);
}

.contact-form__field textarea {
  resize: vertical;
}

/* A 1px border-colour change was the ONLY focus indicator here — every other
   control on the site gets a 2px sage ring, and a keyboard user tabbing into
   the form effectively lost their place. The ring is the indicator; the border
   change stays as reinforcement. */
.contact-form__field input:focus-visible,
.contact-form__field textarea:focus-visible {
  outline: 2px solid var(--color-sage);
  outline-offset: 2px;
  border-color: var(--color-sage);
}

.contact-form__submit {
  width: 100%;
  justify-content: space-between;
}

.contact-form__status {
  font-size: var(--fs-small);
  color: var(--color-text-muted);
  text-align: center;
  min-height: 1.2em; /* reserves space so it appearing doesn't shift the form/button below it */
}

@media (min-width: 768px) {
  .contact__grid {
    grid-template-columns: 1fr 1fr;
    /* Was align-items: start — with the phone/email rows removed, the left
       column (eyebrow/headline/intro/WhatsApp button) is noticeably shorter
       than the form now, and top-aligning left it looking like it was
       missing something below. Centering it against the form reads as
       intentional rather than incomplete. */
    align-items: center;
  }
}

/* ==========================================================================
   Footer — a dark bar (unlike every other card, which is either the hero's
   navy or a light card): sits last on the page, so it reads as a closing
   "base" rather than another content card. .card--dark supplies the
   background/inherited text color; everything else here is layout.
   ========================================================================== */
.footer {
  padding-block: var(--space-lg);
  font-size: var(--fs-small);
}

.footer__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
  text-align: center;
}

.footer__brand {
  display: inline-flex;
}

/* 42px (was 32) — see .nav__logo-mark for why the full mark needs ~1.3x the
   old tree.png sizing to render the tree at the same size. */
.footer__logo-mark {
  width: 42px;
  height: auto;
}

.footer__links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2xs); /* trimmed — links carry inline padding (see below) */
}

.footer__links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px; /* touch target — the link text alone is only ~17px tall */
  padding-inline: var(--space-2xs); /* widens "About"/etc. to >=44px too */
  transition: color var(--transition-base);
}

.footer__links a:hover,
.footer__links a:focus-visible {
  color: var(--color-sage-light);
}

.footer__copyright {
  color: rgba(245, 239, 217, 0.75);
}

/* Social "find us elsewhere" row — quiet secondary signal below the
   brand/links/copyright row. Sage green to tie into the site's icon
   system (trust bar / accordion icons), brightening to sage-light on
   hover exactly like the footer text links. Static — no sticky/float. */
.footer__social {
  display: flex;
  justify-content: center;
  gap: var(--space-sm);
  margin-top: var(--space-2xs);
}

.footer__social-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px; /* touch target — was 36px (20px glyph + 8px padding) */
  color: var(--color-sage);
  transition: color var(--transition-base), transform var(--transition-base);
}

.footer__social-link:hover,
.footer__social-link:focus-visible {
  color: var(--color-sage-light);
  transform: translateY(-2px);
}

.footer__social-icon {
  width: 20px;
  height: 20px;
}

/* ==========================================================================
   Floating WhatsApp button — the persistent primary CTA (WhatsApp is the
   primary channel across the whole site). Fixed bottom-right, above content;
   56px so it clears the 44px touch minimum comfortably. Uses the site's sage
   treatment (matching every other WhatsApp CTA here — the site deliberately
   doesn't use WhatsApp's native green) with the WhatsApp glyph for instant
   recognition. `wa.me` deep-links straight into the WhatsApp app on iOS and
   Android. Hides itself (initWhatsAppFab in main.js) over the contact/CTA
   sections and the footer, so it never collides with the inline WhatsApp
   button, the form's submit, or the footer text.
   ========================================================================== */
.whatsapp-fab {
  position: fixed;
  right: clamp(1rem, 4vw, 1.5rem);
  bottom: clamp(1rem, 4vw, 1.5rem);
  z-index: 200;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--color-sage-deep);
  color: var(--color-cream);
  /* Own shadow, not --shadow-lg: that token's wide 40px navy blur got clipped
     by the viewport corner into a rounded-SQUARE patch ("boxed" on mobile).
     Negative spread pulls it in tight so it hugs the circle, and a sage tint
     makes it read as the button's own glow rather than a stray navy box. */
  box-shadow: 0 8px 22px -6px rgba(58, 74, 48, 0.5), 0 2px 6px rgba(20, 33, 61, 0.12);
  transition: transform var(--transition-base), opacity var(--transition-base),
              visibility var(--transition-base), background var(--transition-base);
}

.whatsapp-fab svg {
  width: 30px;
  height: 30px;
}

.whatsapp-fab:hover {
  background: #4f6045;
  transform: translateY(-3px) scale(1.04);
}

.whatsapp-fab:focus-visible {
  outline: 2px solid var(--color-sage);
  outline-offset: 3px;
}

/* Toggled by initWhatsAppFab when a [data-fab-hide] zone (contact/CTA/footer)
   scrolls into view — collapses out so it can't overlap those. */
.whatsapp-fab.is-hidden {
  opacity: 0;
  visibility: hidden;
  transform: scale(0.8) translateY(12px);
  pointer-events: none;
}

/* Two paragraphs now (the real SEBI/BASL/NISM disclaimer is two separate
   sentences) — divider (margin/border) belongs on the first only, so
   they don't read as two separately-boxed disclaimers.

   Color raised from rgba(cream, 0.6) — that already cleared WCAG AA
   (~5.85:1 against navy) on paper, but cream at low opacity over dark
   navy reads as a dim, desaturated grey rather than legible cream, and
   this is mandatory regulatory text (SEBI/BASL/NISM). 0.75 gives a more
   comfortable margin and actually looks legible, not just technically compliant. */
.footer__disclaimer {
  text-align: center;
  color: rgba(245, 239, 217, 0.75);
  font-size: var(--fs-small);
}

.footer__disclaimer + .footer__disclaimer {
  margin-top: var(--space-sm);
}

.footer__disclaimer:first-of-type {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid rgba(245, 239, 217, 0.15);
}

@media (min-width: 768px) {
  /* 3-column grid (equal 1fr flanks) so the links sit dead-centre in the
     footer regardless of the brand/copyright widths — space-between only
     centred them when those two happened to match. */
  .footer__inner {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    text-align: left;
  }

  .footer__brand {
    justify-self: start;
  }

  .footer__links {
    justify-self: center;
  }

  .footer__copyright {
    justify-self: end;
  }
}

/* ==========================================================================
   Scroll-reveal utility (JS toggles .is-visible — see assets/js/main.js)
   ========================================================================== */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity var(--transition-base), transform var(--transition-base);
}

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

/* ==========================================================================
   Reduced motion — reveals show instantly, hero parallax/twinkle disabled
   (JS also skips initHeroParallax entirely under this media query)
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .hero__tree,
  .hero__content {
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
  }

  /* Stop the twinkle but keep each star's resting --star-opacity (do NOT
     force opacity:1 — that made them full-bright instead of subtle). */
  .hero__star {
    animation: none !important;
    transform: none !important;
  }

  .about-intro__star {
    animation: none !important;
  }

  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .btn:hover,
  .footer__social-link:hover,
  .footer__social-link:focus-visible {
    transform: none;
  }

  /* FAB still hides/shows (a functional visibility change, not decorative
     motion) but without the slide/scale — snap it, and no hover lift. */
  .whatsapp-fab {
    transition: opacity var(--transition-base), visibility var(--transition-base);
  }

  .whatsapp-fab:hover {
    transform: none;
  }

  .whatsapp-fab.is-hidden {
    transform: none;
  }

  .faq__icon::before,
  .faq__icon::after,
  .service-accordion__icon::before,
  .service-accordion__icon::after,
  .nav__toggle-bar {
    transition: none;
  }

  /* initAccordionMotion bails under reduced motion (native instant toggle
     stands) — make sure no height transition is left declared to fire, and
     the collapsed-card hover lift is neutralised too. */
  .service-accordion__body,
  .faq__answer {
    transition: none;
  }

  .service-accordion__item:not([open]):hover {
    transform: none;
  }

  /* Why Ethical: collapse the tall pinned scroller back to a normal-height
     section and show every item fully expanded at once — no pin, no
     per-item activation, matching what initWhyEthicalPin itself already
     skips at the JS layer under this same media query. */
  .why-ethical__scroller {
    height: auto !important;
  }

  .why-ethical__pin {
    position: static !important;
    height: auto !important;
  }

  /* initWhyEthicalPin bails out entirely under reduced-motion (its very
     first line), so the entrance IntersectionObserver never runs and
     `.why-ethical__grid.is-revealed` never gets added — without these,
     the entrance rules' base state (opacity:0, translateY(16px), applied
     unconditionally within the desktop media query) would leave the
     intro block and every list item permanently invisible/offset. */
  .why-ethical__intro {
    opacity: 1 !important;
    transform: none !important;
  }

  .why-ethical__description {
    display: none !important;
  }
}

/* Reduced-motion fallback for the DESKTOP stepper only — scoped to
   min-width:768px deliberately.

   These rules force each item into a navy card with cream text, because on
   desktop that's the only way to show the descriptions once the pin is off
   (the pin normally moves them into the shared .why-ethical__description
   panel, and initWhyEthicalPin bails out entirely under reduced motion).

   They used to live in the unscoped reduced-motion block above, which meant
   MOBILE inherited them: anyone browsing on a phone with "Reduce Motion" on
   (a common iOS setting) got all four items as navy cards instead of the
   light sage-accented list the mobile design actually calls for. Mobile needs
   none of this — its descriptions are already visible under reduced motion,
   because the collapse that hides them is itself guarded behind
   `prefers-reduced-motion: no-preference`.

   Note these must stay grouped: the <p>'s cream colour only works BECAUSE the
   item is forced navy. Applying one without the other gives cream-on-cream
   invisible text. */
/* Must track the PIN's gate (1024), not the old 768 — these navy-card rules
   only make sense where the pin exists. Left at 768 they'd hand tablets navy
   cards with no pin to justify them. */
@media (prefers-reduced-motion: reduce) and (min-width: 1024px) {
  .why-ethical__item {
    /* Base desktop rule is `display: flex` (row — number/title beside each
       other, description hidden). Forcing the paragraph visible below
       needs column stacking here, or it'd sit beside the title instead of
       under it. */
    flex-direction: column;
    align-items: flex-start;
    opacity: 1 !important;
    transform: none !important;
    max-height: none !important;
    background: var(--color-navy) !important;
    color: var(--color-cream) !important;
    padding: var(--space-sm) var(--space-md) !important;
    border-left-color: var(--color-sage) !important;
    transition: none !important;
  }

  .why-ethical__number {
    display: inline !important;
  }

  .why-ethical__item h3 {
    font-size: var(--fs-h3) !important;
  }

  /* On desktop widths, the base rule for this is now `display: none` (each
     item's own description moved to the shared .why-ethical__description
     panel — see the min-width:768px block above). JS populates that panel,
     but initWhyEthicalPin bails out entirely under reduced-motion, so it'd
     stay empty. Show each item's own <p> directly instead, same as mobile
     — simplest correct fallback, and keeps the shared panel irrelevant
     here rather than needing to fake-populate it. */
  .why-ethical__item p {
    display: block !important;
    max-height: none !important;
    opacity: 1 !important;
    overflow: visible !important;
    margin-top: var(--space-2xs) !important;
    color: rgba(245, 239, 217, 0.8) !important;
    transition: none !important;
  }
}

/* Honeypot — must be invisible to humans but still SUBMITTED by the browser, so
   `display:none` on the input itself is fine here (unlike the skip link, where
   it would break tabbing). Positioned off-canvas rather than hidden so that a
   bot parsing computed styles is less likely to spot it as a trap. */
.honeypot-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
