/*
 * Himalayan Practice - base stylesheet.
 *
 * One file, no build step, on purpose: every extra stylesheet is an extra
 * HTTP request with no bundler to merge them. Use the section banners to
 * navigate. Full palette/type documentation and CSS coding standards live
 * in /design_system.md - read that before adding to this file.
 * resources/css/app.css mirrors this file (see CLAUDE.md).
 */

/* ==========================================================================
   1. Design tokens
   ========================================================================== */

:root {
    /* -- Color: brand -------------------------------------------------------- */
    --color-primary: #33453b;
    --color-primary-hover: #0a2f1b;
    --color-primary-contrast: #ffffff;
    --color-secondary: #ffffff;

    /* -- Color: base neutrals -------------------------------------------------- */
    --color-white: #ffffff;
    --color-black: #1a1a1a;

    /* -- Color: surfaces ----------------------------------------------------- */
    --color-tertiary: #f2ede7;
    --color-surface: var(--color-white);
    --color-surface-inverse: var(--color-black);
    --color-control-surface: #edf1ee;
    /* Figma-confirmed - a card surface for sitting on top of the
       `--color-tertiary` page background (e.g. `.own-course-card`),
       distinct from both that page background and plain white
       `--color-surface`. */
    --color-card-surface: #f5f2ee;

    /* -- Color: text ----------------------------------------------------------- */
    --color-heading: #292b28;
    --color-text: #545454;
    --color-text-muted: #8b8987;
    --color-text-inverse: #faf8f5;
    /* Body copy on a dark/inverted section (e.g. "Our Offerings") - sampled
       from that section's own design screenshot, not yet Figma-confirmed.
       `--color-text-inverse` is the *heading* color on dark sections, too
       light/flat for body copy at that size - this is a deliberately
       dimmer companion, same relationship `--color-text-muted` has to
       `--color-heading` on light sections. */
    --color-text-muted-inverse: #b7b3ad;

    /* -- Color: accent ----------------------------------------------------------- */
    /* Sampled directly from the "Our Offerings" design screenshot's "01"/
       "02" numbered-list labels (not a guess) - not yet Figma-confirmed.
       No other section uses this yet; add a usage note here if one does. */
    --color-accent: #5a2112;

    /* A muted, not-fully-saturated gold - chosen to sit with this
       palette's other warm/earthy tones rather than a bright consumer-app
       star-rating yellow. Used by `.testimonial-card__stars`; not yet
       Figma-confirmed. */
    --color-gold: #c9a227;

    /* Cream/tan accent for the course-detail "How It Works" step numerals
       (01-06) - sampled from that section's own design screenshot, not
       yet Figma-confirmed, same "no other section uses this yet" caveat
       as `--color-accent` above. */
    --color-number-accent: #efe0cf;

    /* -- Color: borders & state ----------------------------------------------- */
    --color-border: #e6e0d6;
    --color-danger: #a33a3a;
    --color-success-bg: #e7f3ee;
    --color-success-border: #bcdccb;
    --color-success-text: #1f4038;
    --color-danger-bg: #fbeaea;
    --color-danger-border: #eec5c5;
    --color-danger-text: #7a2c2c;

    /* -- Typography: families -------------------------------------------------- */
    /* See "Lyon font status" in design_system.md - the @font-face rules
       below are a temporary dev placeholder, not the licensed typeface. */
    --font-family-text: "Lyon Text", Georgia, "Times New Roman", "Noto Serif", serif;
    --font-family-display: "Lyon Display", Georgia, "Times New Roman", "Noto Serif", serif;
    --font-heading: var(--font-family-text);
    --font-body: var(--font-family-text);
    --font-caption: var(--font-family-display);
    --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

    /* -- Typography: UI chrome scale (nav, buttons, labels, hints) ----------- */
    --text-sm: 0.875rem; /* 14px */

    /* -- Header nav ------------------------------------------------------------ */
    --nav-link-size: 1.125rem; /* 18px */
    --nav-link-gap: var(--space-10); /* 40px */
    --nav-button-gap: 4.375rem; /* 70px - space between the nav links and the button */
    --nav-button-padding-y: var(--space-3); /* 12px */
    --nav-button-padding-x: 1.875rem; /* 30px */

    /* -- Typography: editorial scale -------------------------------------------
       --text-banner-size/-leading, --text-eyebrow-size/-leading, and
       --text-h2-/-h3-size/-leading are ALL mobile-first now (see CSS
       coding standard #8 in design_system.md) - the values here are the
       small-screen baseline; `min-width: 768px` below scales every one
       of them up to the confirmed desktop size in one place, so nothing
       building with these tokens has to remember to do it per-component.
       --text-h4-size, --text-body-size, and --text-caption-size are
       deliberately NOT mobile-first - 20px/18px/16px already read fine
       on a phone; shrinking body copy further would hurt readability for
       a mobile-first-CSS purity that doesn't actually buy anything here. */
    --text-banner-size: 2.25rem; /* 36px on mobile, 50px from 768px up */
    --text-banner-leading: 2.75rem; /* 44px on mobile, 58px from 768px up */
    /* Flat 16px at every width, deliberately not mobile-first like the
       rest of this editorial scale below - explicit instruction (not a
       Figma value), applies everywhere `.eyebrow`/`.eyebrow--banner` are
       used. Going forward, any new eyebrow-style label reuses this same
       token rather than a one-off size. */
    --text-eyebrow-size: 1rem; /* 16px */
    --text-eyebrow-leading: 1.5rem; /* 24px on mobile, 32px from 768px up - eyebrow everywhere except the banner */
    --text-eyebrow-banner-leading: 2.125rem; /* 34px - eyebrow above the banner, desktop only (`.eyebrow--banner` has its own separate mobile override below, not this token) */
    /* Mobile-only, not yet Figma-confirmed - at 16px, sharing h1's
       typeface and weight, the banner eyebrow read as a second headline
       stacked on the real one instead of a quiet label. Paired with
       `--text-sm` (14px) + uppercase + letter-spacing on `.eyebrow--banner`
       below; `min-width: 768px` restores the regular case/weight (font
       size itself no longer changes at that breakpoint, see
       `--text-eyebrow-size` above). */
    --text-eyebrow-banner-mobile-leading: 1.25rem; /* 20px */
    --text-h2-size: 1.75rem; /* 28px on mobile, 44px from 768px up */
    --text-h2-leading: 2.25rem; /* 36px on mobile, 52px from 768px up */
    --text-h3-size: 1.375rem; /* 22px on mobile, 26px from 768px up */
    --text-h3-leading: 1.75rem; /* 28px on mobile, 32px from 768px up */
    --text-h4-size: 1.25rem; /* 20px */
    --text-h4-leading: 1.75rem; /* 28px */
    --text-body-size: 1.125rem; /* 18px */
    --text-body-leading: 1.625rem; /* 26px */
    --text-caption-size: 1rem; /* 16px - Lyon Display, not Lyon Text */
    --text-caption-leading: 1.5rem; /* 24px */

    /* -- Typography: weights -------------------------------------------------- */
    --font-weight-regular: 400;
    --font-weight-medium: 500;
    --font-weight-semibold: 600;
    --font-weight-bold: 700;

    /* -- Spacing (4px base unit) --------------------------------------------- */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.25rem;
    --space-6: 1.5rem;
    --space-8: 2rem;
    --space-10: 2.5rem;
    --space-12: 3rem;
    --space-16: 4rem;

    /* -- Radius -------------------------------------------------------------- */
    --radius-sm: 6px;
    --radius-md: 8px;
    --radius-lg: 16px;
    --radius-pill: 999px;

    /* -- Shadow -------------------------------------------------------------- */
    --shadow-sm: 0 1px 2px rgba(41, 43, 40, 0.06);
    --shadow-md: 0 4px 16px rgba(41, 43, 40, 0.08);
    /* Figma-confirmed (node 207:444 etc., the week strip's day cards) -
       not an approximation like the two above. Explicit black, not
       `--color-heading`, per that spec. */
    --shadow-day-card: 0 0 3px 0 rgba(0, 0, 0, 0.15);

    /* -- Motion ------------------------------------------------------------------
       Duration-only, deliberately - the CSS default timing-function is
       already `ease`, so a bare `transition: color var(--transition-fast);`
       elsewhere behaves identically with or without the word "ease" baked
       into the token. It matters here because a handful of rules pair one
       of these with `--ease-smooth` (e.g. `transform var(--transition-base)
       var(--ease-smooth)`) - a token of "200ms ease" would make that
       "200ms ease cubic-bezier(...)", two timing-functions in one
       declaration, which is invalid CSS. The whole `transition` value gets
       dropped when that happens, and the property just snaps instantly
       instead of easing - exactly what happened here until this fix. */
    --transition-fast: 150ms;
    --transition-base: 200ms;
    --transition-slow: 400ms; /* premium/settled entrances - scroll-reveal, card/tile hover */
    --transition-menu: 320ms; /* mobile nav panel open/close */
    --ease-smooth: cubic-bezier(0.16, 1, 0.3, 1); /* expo-out - smooth deceleration */

    /* -- Layout -------------------------------------------------------------- */
    /* box-sizing: border-box (below) keeps each container's side padding
       inside this max-width, so content never touches the viewport edge
       even at exactly 1312px. */
    --content-width: 1312px;

    /* -- Z-index --------------------------------------------------------------- */
    --z-hero-header: 20;
    --z-nav-backdrop: 40;
    --z-nav-drawer: 50;
}

@media (min-width: 768px) {
    :root {
        --text-banner-size: 3.125rem; /* 50px */
        --text-banner-leading: 3.625rem; /* 58px */
        --text-eyebrow-leading: 2rem; /* 32px */
        --text-h2-size: 2.75rem; /* 44px */
        --text-h2-leading: 3.25rem; /* 52px */
        --text-h3-size: 1.625rem; /* 26px */
        --text-h3-leading: 2rem; /* 32px */
    }
}

/* ==========================================================================
   2. Font faces
   ========================================================================== */

/* Temporary dev placeholder, not the licensed Commercial Type Lyon Text /
   Lyon Display typeface - see "Lyon font status" in design_system.md.
   public/fonts/ is gitignored, so this never ships anywhere by accident. */
@font-face {
    font-family: "Lyon Text";
    src: url("../fonts/lyon-text-regular.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Lyon Text";
    src: url("../fonts/lyon-text-bold.woff2") format("woff2");
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Lyon Text";
    src: url("../fonts/lyon-text-italic.woff2") format("woff2");
    font-weight: 400;
    font-style: italic;
    font-display: swap;
}

@font-face {
    font-family: "Lyon Display";
    src: url("../fonts/lyon-display-regular.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

/* ==========================================================================
   3. Base / reset
   ========================================================================== */

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    /* Guards against `.full-bleed`'s 100vw trick overflowing by a
       scrollbar's width on some browsers. */
    overflow-x: hidden;
    font-family: var(--font-body);
    font-size: var(--text-body-size);
    line-height: var(--text-body-leading);
    color: var(--color-text);
    background-color: var(--color-tertiary);
}

h1,
h2,
h3,
h4,
.hero-banner__heading {
    font-family: var(--font-heading);
    font-weight: var(--font-weight-regular);
    color: var(--color-heading);
    margin: 0 0 var(--space-3);
}

/* h1 doubles as the marketing "banner" headline - there's no separate
   smaller size for a plain page title. `.hero-banner__heading` shares
   this same size/leading for the hero slides that aren't the page's one
   real `<h1>` (see welcome.blade.php) - a page should have exactly one,
   for SEO/document-outline correctness, not five identically-styled
   ones just because there are five slides. */
h1,
.hero-banner__heading {
    font-size: var(--text-banner-size);
    line-height: var(--text-banner-leading);
}

h2 {
    font-size: var(--text-h2-size);
    line-height: var(--text-h2-leading);
}

h3 {
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
}

h4 {
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
}

/* `.eyebrow--banner` widens the line-height for the eyebrow directly above
   the hero banner; plain `.eyebrow` covers every other case. */
.eyebrow {
    display: block;
    font-size: var(--text-eyebrow-size);
    line-height: var(--text-eyebrow-leading);
}

/* Mobile-first override, see the token comment above. */
.eyebrow--banner {
    font-size: var(--text-sm);
    line-height: var(--text-eyebrow-banner-mobile-leading);
    font-weight: var(--font-weight-medium);
    text-transform: uppercase;
    letter-spacing: 0.06em; /* not tokenized - only current use, see .form's max-width for the same precedent */
}

@media (min-width: 768px) {
    .eyebrow--banner {
        font-size: var(--text-eyebrow-size);
        line-height: var(--text-eyebrow-banner-leading);
        font-weight: var(--font-weight-regular);
        text-transform: none;
        letter-spacing: normal;
    }
}

/* For `.eyebrow` on a dark/inverted section (e.g. "Our Offerings") - the
   plain `.eyebrow` has no color of its own (inherits `--color-text`, dark,
   illegible on a photo background), this overrides it. */
.eyebrow--inverse {
    color: var(--color-text-muted-inverse);
}

.caption {
    font-family: var(--font-caption);
    font-size: var(--text-caption-size);
    line-height: var(--text-caption-leading);
}

a {
    color: var(--color-primary);
    text-decoration: none;
}

/* Color shift instead of underline on hover/focus - underline was
   inheriting onto every text line inside a whole-card link
   (`.course-card`, `.teacher-card`, `.practice-card`, etc. are each one
   big `<a>`), underlining the title/description/meta text on hover. Color
   gives the same "this is interactive" feedback without that, and reuses
   the confirmed hover token already shared by both button variants. */
a:hover,
a:focus-visible {
    color: var(--color-primary-hover);
}

/* ==========================================================================
   4. Layout
   ========================================================================== */

/* Constrains and centers content to --content-width with a side gutter
   that's always preserved (box-sizing: border-box keeps the padding
   inside the max-width) - shared by the header, main content, and footer. */
.container {
    max-width: var(--content-width);
    margin: 0 auto;
    padding-left: var(--space-6);
    padding-right: var(--space-6);
}

/* Escapes any ancestor's max-width to span the full viewport - e.g. a
   hero banner photo. Nest a `.container` inside it to re-align its
   content (headline, nav, CTAs) with the rest of the site's grid. */
.full-bleed {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
}

.site-header {
    position: relative;
    background: transparent;
}

.site-header__inner {
    padding-top: var(--space-2);
    padding-bottom: var(--space-2);
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.brand {
    display: flex;
    align-items: center;
}

/* Mobile-first: 94px reads oversized against a 320-390px header (it eats
   most of the header's height on the smallest phones) - scales up from
   768px, see CSS coding standard #8 in design_system.md. */
.brand__logo {
    height: 56px;
    width: auto;
    display: block;
}

@media (min-width: 768px) {
    .brand__logo {
        height: 94px;
    }
}

.nav {
    display: flex;
    align-items: center;
    gap: var(--nav-link-gap);
    font-size: var(--nav-link-size);
    font-weight: var(--font-weight-regular);   
}

.nav a {
    text-decoration: none;
}

.nav .btn {
    padding: var(--nav-button-padding-y) var(--nav-button-padding-x);
    font-size: var(--nav-link-size);
    font-weight: var(--font-weight-regular);
}

/* The button sits further from the links than the links sit from each
   other - added on top of `.nav`'s own gap, and only on the direct flex
   child (not the button nested inside `.nav__logout-form`), so it isn't
   double-applied when logged in. */
.nav > .btn,
.nav > .nav__logout-form {
    margin-left: calc(var(--nav-button-gap) - var(--nav-link-gap));
}

/* Hamburger toggle - hidden until the mobile breakpoint below shows it.
   Morphs into an X off the button's own aria-expanded state (set by JS),
   not a separate class. */
.nav-toggle {
    display: none;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 40px;
    height: 40px;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
    transition: transform var(--transition-fast) ease;
}

/* Both only exist for the drawer (mobile breakpoint below) - hidden on
   the desktop horizontal nav. */
.nav-backdrop {
    display: none;
}

.nav__drawer-header {
    display: none;
}

.nav-toggle:active {
    transform: scale(0.9);
}

.nav-toggle__bar {
    display: block;
    width: 100%;
    height: 2px;
    background-color: var(--color-heading);
    border-radius: var(--radius-sm);
    transition: transform var(--transition-base) var(--ease-smooth), opacity var(--transition-base) var(--ease-smooth);
}

.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;
    transform: scale(0);
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
}

/* Mobile/tablet: collapse into a hamburger-triggered slide-in drawer. */
@media (max-width: 1023px) {

    .nav-toggle {
        display: flex;
    }

    /* Dims the page behind the open drawer; also gives the existing
       outside-click-to-close handler something to click, since it covers
       everything except the drawer itself. */
    .nav-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        background-color: color-mix(in srgb, var(--color-black) 40%, transparent);
        opacity: 0;
        pointer-events: none;
        transition: opacity var(--transition-menu) var(--ease-smooth);
        z-index: var(--z-nav-backdrop);
    }

    .nav-backdrop.is-open {
        opacity: 1;
        pointer-events: auto;
    }

    /* Full-height drawer, flush against the top/bottom/right edges of the
       screen - not a floating card. 80% wide on mobile, 50% on tablet
       (see the min-width:768px override below). Slides in on `transform`
       only (compositor-friendly); `overflow-y: auto` lets a tall link list
       scroll independently of the page underneath. */
    .nav {
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        width: 80%;
        flex-direction: column;
        align-items: stretch;
        gap: 0;
        background-color: var(--color-surface);
        box-shadow: var(--shadow-md);
        padding: var(--space-6) var(--space-6) var(--space-4);
        overflow-y: auto;
        transform: translateX(100%);
        transition: transform var(--transition-menu) var(--ease-smooth);
        z-index: var(--z-nav-drawer);
    }

    /* Frosted glass where supported; a solid surface everywhere else -
       never a transparent panel with unreadable text under it. */
    @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
        .nav {
            background-color: color-mix(in srgb, var(--color-white) 88%, transparent);
            backdrop-filter: blur(20px);
            -webkit-backdrop-filter: blur(20px);
        }
    }

    .nav.is-open {
        transform: translateX(0);
    }

    /* The drawer's own logo + close button - only shown here, since the
       drawer covers the real site header (including its hamburger
       button) once open. */
    .nav__drawer-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: var(--space-5);
        padding-bottom: var(--space-4);
        border-bottom: 1px solid var(--color-border);
    }

    .nav__drawer-logo {
        height: 40px;
        width: auto;
        display: block;
    }

    .nav-close {
        position: relative;
        width: 40px;
        height: 40px;
        padding: 0;
        border: none;
        background: none;
        cursor: pointer;
        flex-shrink: 0;
    }

    .nav-close::before,
    .nav-close::after {
        content: "";
        position: absolute;
        top: 50%;
        left: 50%;
        width: 22px;
        height: 2px;
        background-color: var(--color-heading);
        border-radius: var(--radius-sm);
    }

    .nav-close::before {
        transform: translate(-50%, -50%) rotate(45deg);
    }

    .nav-close::after {
        transform: translate(-50%, -50%) rotate(-45deg);
    }

    /* Every direct nav item reveals one at a time as the drawer slides in
       (`.nav__drawer-header` excluded - it's the drawer's own persistent
       header, shown immediately, not part of the staggered list).
       `:nth-child`, not `:nth-of-type` - counts position among *all*
       direct children regardless of tag, so it stays correct even if a
       future nav item isn't a plain `<a>` (already needed once, for a
       dropdown wrapper `<div>` that's since been removed - kept this
       version rather than reverting, since it's the more robust one). */
    .nav > *:not(.nav__drawer-header) {
        opacity: 0;
        transform: translateX(16px);
        transition: opacity var(--transition-base) var(--ease-smooth), transform var(--transition-base) var(--ease-smooth);
    }

    .nav a {
        padding: var(--space-3) 0;
        border-bottom: 1px solid var(--color-border);
    }

    .nav__drawer-header a{
        padding: 0;
        border-bottom: 0;
    }

    .nav.is-open > *:not(.nav__drawer-header) {
        opacity: 1;
        transform: translateX(0);
    }

    /* Staggered entrance - each item a beat behind the last. Position is
       counted among *all* direct children (drawer-header is always child
       1), not per-tag-type. */
    .nav > *:not(.nav__drawer-header):nth-child(2) {
        transition-delay: 80ms;
    }

    .nav > *:not(.nav__drawer-header):nth-child(3) {
        transition-delay: 120ms;
    }

    .nav > *:not(.nav__drawer-header):nth-child(4) {
        transition-delay: 160ms;
    }

    .nav > *:not(.nav__drawer-header):nth-child(5) {
        transition-delay: 200ms;
    }

    .nav > *:not(.nav__drawer-header):nth-child(6) {
        transition-delay: 240ms;
    }

    .nav > *:not(.nav__drawer-header):nth-child(7) {
        transition-delay: 280ms;
    }

    .nav > *:not(.nav__drawer-header):nth-child(8) {
        transition-delay: 320ms;
    }

    .nav .btn,
    .nav .nav__logout-form {
        width: 100%;
        margin-top: var(--space-3);
        margin-left: 0;
    }

    .nav .nav__logout-form {
        display: block;
    }

    .nav .btn {
        text-align: center;
    }
}

/* Tablet only: narrower drawer than the mobile default above. */
@media (min-width: 768px) and (max-width: 1023px) {
    .nav {
        width: 50%;
    }
}

/* Locks background scroll while the drawer is open (class toggled by JS). */
body.nav-open {
    overflow-y: hidden;
}

/* Instant show/hide, no transform/opacity/stagger choreography. */
@media (prefers-reduced-motion: reduce) {
    .nav-toggle,
    .nav-toggle__bar,
    .nav-backdrop,
    .nav,
    .nav > * {
        transition: none;
    }
}

main.page {
    padding-top: var(--space-12);
    padding-bottom: var(--space-12);
}

/* Pages with a full-bleed hero (see "Hero banner" in Components) need the
   header to float transparently on top of the image instead of pushing it
   down - `body.has-hero` is set via `@section('body-class', 'has-hero')`
   in the view. Scoped to this one modifier so every other page keeps the
   header in normal flow, unaffected. */
body.has-hero .site-header {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: var(--z-hero-header);
}

body.has-hero main.page {
    padding-top: 0;
}

.site-footer {
    border-top: 1px solid var(--color-border);
}

.site-footer__inner {
    padding-top: var(--space-12);
    padding-bottom: var(--space-12);
}

@media (min-width: 768px) {
    .site-footer__inner {
        padding-top: var(--space-16);
        padding-bottom: var(--space-16);
    }
}

/* Mobile-first 1 -> 2 (640px) -> 4 (1024px) columns, same progression as
   `.teachers-grid`. */
.site-footer__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10);
}

@media (min-width: 640px) {
    .site-footer__grid {
        grid-template-columns: 1fr 1fr;
    }
}

/* Brand and Visit & Connect wider than Explore/Support - an explicit
   adjustment, not re-derived from the screenshot measurement (see git
   history for that version). `minmax(0, ...)` on every track, not a bare
   `fr`, so a track can't blow out past its share if a column's content
   ever gets wider than expected - see `.newsletter-section__grid`'s
   comment for why that matters. */
@media (min-width: 1024px) {
    .site-footer__grid {
        grid-template-columns: minmax(0, 1.3fr) minmax(0, .8fr) minmax(0, .8fr) minmax(0, 1.1fr);
        gap: var(--space-6);
    }
}

/* A deliberately bigger, more prominent brand moment than the compact nav
   logo (`.brand__logo`, 56-94px) - not a typo. */
.site-footer__logo {
    height: 100px;
    width: auto;
    display: block;
}

.site-footer__tagline {
    margin: var(--space-4) 0 0;
    color: var(--color-text);
    width: 80%;
}

.site-footer__social {
    display: flex;
    gap: var(--space-3);
    margin-top: var(--space-5);
}

.site-footer__heading {
    margin: 0 0 var(--space-4);
}

.site-footer__links {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin: 0;
    padding: 0;
}

.site-footer__links a {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--color-text);
}

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

.site-footer__links i {
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

.site-footer__contact {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.site-footer__contact-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    margin: 0;
    color: var(--color-text);
}

.site-footer__contact-icon {
    flex-shrink: 0;
    margin-top: var(--space-1);
    color: var(--color-black);
}

/* Icon fonts (the envelope `<i>`) size themselves via `font-size`; the
   hand-drawn pin `<svg>` needs an explicit box - `1em` keeps it matching
   the surrounding text size the same way the icon font does. */
svg.site-footer__contact-icon {
    width: 1em;
    height: 1em;
}

.site-footer__contact-row a {
    color: var(--color-text);
}

.site-footer__badges {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-5);
}

/* The real official Google Play / App Store badge artwork (`images/icons/`)
   - rounded corners are already baked into the source PNG/WebP's alpha
   channel (confirmed by reading the actual corner pixels, not guessed), so
   no extra radius/overflow needed here, just sizing. Height is
   pixel-measured against the design screenshot, same process as
   `.site-footer__logo`. */
.site-footer__badge {
    display: block;
    height: 40px;
    width: auto;
}

.site-footer__bottom {
    margin-top: var(--space-10);
    text-align: center;
    color: var(--color-text-muted);
    font-size: var(--text-sm);
}

/* ==========================================================================
   5. Components
   ========================================================================== */

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

.hero {
    padding: var(--space-8) 0 var(--space-12);
}

.hero p {
    color: var(--color-text-muted);
    max-width: 40rem;
    font-size: var(--text-body-size);
    line-height: var(--text-body-leading);
}

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

/* `.btn` is the secondary button (white, sits next to a primary button);
   `.btn--primary` is the primary button. Both hover to the same
   --color-primary-hover rather than each computing their own shade. */
.btn {
    display: inline-block;
    padding: var(--space-2) var(--space-5);
    border-radius: var(--radius-pill);
    border: 1px solid var(--color-primary);
    background-color: var(--color-secondary);
    color: var(--color-primary);
    font-family: var(--font-heading);
    font-size: var(--text-sm);
    text-align: center;
    cursor: pointer;
    transition: background-color var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast);
}

.btn:hover,
.btn:focus-visible {
    text-decoration: none;
    background-color: var(--color-primary-hover);
    border-color: var(--color-primary-hover);
    color: var(--color-primary-contrast);
    transform: translateY(-2px);
}

.btn--primary {
    background-color: var(--color-primary);
    color: var(--color-primary-contrast);
}

.btn--primary:hover,
.btn--primary:focus-visible {
    background-color: var(--color-primary-hover);
    border-color: var(--color-primary-hover);
}

.btn--ghost {
    border-color: var(--color-border);
    color: var(--color-text);
}

.btn--ghost:hover,
.btn--ghost:focus-visible {
    background-color: var(--color-border);
    color: var(--color-text);
}

.btn--danger {
    border-color: var(--color-danger);
    color: var(--color-danger);
}

.btn--danger:hover,
.btn--danger:focus-visible {
    background-color: var(--color-danger);
    color: var(--color-primary-contrast);
}

.link-button {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--color-primary);
    cursor: pointer;
}

.link-button:hover,
.link-button:focus-visible {
    color: var(--color-primary-hover);
}

.nav__logout-form {
    display: inline;
}

/* -- Card -------------------------------------------------------------------- */

.card {
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--space-6);
}

.card + .card {
    margin-top: var(--space-4);
}

/* -- Forms --------------------------------------------------------------------- */

.form {
    max-width: 28rem;
}

.form + .form {
    margin-top: var(--space-6);
}

.field {
    margin-bottom: var(--space-4);
}

.field label {
    display: block;
    font-weight: var(--font-weight-semibold);
    margin-bottom: var(--space-2);
    font-size: var(--text-sm);
}

.field input {
    width: 100%;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    font: inherit;
    background-color: var(--color-surface);
    color: var(--color-text);
}

.field input:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 1px;
}

.field-error {
    color: var(--color-danger);
    font-size: var(--text-sm);
    margin-top: var(--space-1);
}

.field-hint {
    color: var(--color-text-muted);
    font-size: var(--text-sm);
    margin-top: var(--space-1);
}

/* Visually hidden but still reachable by screen readers/keyboard nav - the
   standard clip-based technique (not `display: none`, which pulls it from
   the accessibility tree too). First use: `.contact-form`'s labels, which
   are visible on-screen only as each input's placeholder text. */
.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;
}

/* `flex-wrap: wrap` matters once there are 3+ buttons or a longer label
   (the settings page's two-factor "enabled" row has three) - without it,
   buttons on a narrow phone shrink below their own content width instead
   of wrapping to a new row, wrapping the *text* inside each pill instead
   and leaving them oddly, inconsistently tall. */
.form-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-5);
}

.form-links {
    margin-top: var(--space-4);
    font-size: var(--text-sm);
}

/* -- Alerts ------------------------------------------------------------------- */

.alert {
    border-radius: var(--radius-md);
    padding: var(--space-3) var(--space-4);
    margin-bottom: var(--space-5);
    font-size: var(--text-sm);
}

.alert--success {
    background-color: var(--color-success-bg);
    border: 1px solid var(--color-success-border);
    color: var(--color-success-text);
}

.alert--error {
    background-color: var(--color-danger-bg);
    border: 1px solid var(--color-danger-border);
    color: var(--color-danger-text);
}

/* -- Section / list scaffolding (settings-style pages) ------------------------- */

.section {
    margin-bottom: var(--space-10);
}

.section h2 {
    margin-bottom: var(--space-1);
}

.section-hint {
    color: var(--color-text-muted);
    margin-top: 0;
    margin-bottom: var(--space-4);
}

.list-plain {
    list-style: none;
    margin: 0;
    padding: 0;
}

.list-plain li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--color-border);
}

.list-plain li:last-child {
    border-bottom: none;
}

.divider {
    border: none;
    border-top: 1px solid var(--color-border);
    margin: var(--space-6) 0;
}

code.recovery-code {
    display: block;
    font-family: var(--font-mono);
    padding: 0.125rem 0;
}

/* -- Hero banner (Swiper) ------------------------------------------------------ */

/* Full-bleed and flush against the very top of the viewport - see the
   `body.has-hero` overrides in "Layout" above, which pull `.site-header`
   out of flow (transparent, absolutely positioned) so it floats on top of
   the image instead of pushing it down. `--swiper-*` variables are
   Swiper's own theming hooks (see swiper-bundle.min.css via CDN, pulled in
   only on views that render `.hero-swiper` - see "Frontend asset
   strategy" in CLAUDE.md for the CDN-with-SRI pattern). Back to a
   multi-slide carousel after a brief single-image stint - restored rather
   than re-derived, see git history for that intermediate version. */
.hero-swiper {
    --swiper-theme-color: var(--color-primary);
    --swiper-navigation-size: var(--space-4);
    height: clamp(520px, 60vw, 780px);
}

.page-banner {
    position: relative;
}

/* `.page-banner`'s plain `<picture><img></picture>` (about.blade.php,
   courses.blade.php, live-studio.blade.php - not `.hero-swiper`, which
   already sizes itself via its own `height: clamp(...)` above) had no
   sizing rule at all until now - the `<img>` rendered at its own raw
   intrinsic pixel size instead of scaling with the viewport. That went
   unnoticed on about/courses because both of their source banner photos
   happen to be exactly 1512x817, close enough to a typical desktop
   viewport width to look intentional - but it's why live-studio's own
   banner (a differently-sized source photo) rendered far taller than the
   other two, and why all three would have behaved badly on a phone
   (overflowing sideways at a fixed 1512px width, not shrinking to fit).
   `aspect-ratio: 1512 / 817` fixes this properly and consistently: every
   `.page-banner` photo now scales with `width: 100%`, height derived from
   that same 1512:817 ratio every current banner photo is already cropped
   to (see "Image folders" in design_system.md), with `object-fit: cover`
   in case a future banner photo doesn't match it exactly. */
.page-banner img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1512 / 817;
    object-fit: cover;
}

.hero-swiper .swiper-slide {
    overflow: hidden;
}

.hero-swiper img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Headline/eyebrow/CTAs overlay - one per slide (see welcome.blade.php),
   so the copy and the image change together with no JS-driven text-swap
   needed. The two-layer wash lives on this element's own background,
   not a separate sibling of `.swiper-wrapper` - Swiper's own
   `.swiper-wrapper` sets `z-index: 1` on itself (not `auto`), which ties
   with anything else at `z-index: 1` outside a slide and loses that tie
   to DOM order, rendering invisibly behind every slide's image. Inside a
   slide, though, this element's own `position: absolute` already beats
   its non-positioned `<picture>` sibling with no z-index gymnastics
   needed, so painting the wash as its background sidesteps the problem
   entirely. A top band covers the transparent header floating on top of
   the hero; a left band covers the headline/CTA text. Both are
   approximations pending Figma confirmation of the exact treatment, see
   design_system.md "What to verify against Figma". `pointer-events: none`
   on the wrapper so clicks/drags over its empty space still reach the
   swiper underneath; re-enabled only on the actual buttons. */
.hero-banner__content {
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(180deg, color-mix(in srgb, var(--color-tertiary) 85%, transparent) 0%, color-mix(in srgb, var(--color-tertiary) 40%, transparent) 22%, transparent 38%),
        linear-gradient(90deg, color-mix(in srgb, var(--color-tertiary) 94%, transparent) 0%, color-mix(in srgb, var(--color-tertiary) 78%, transparent) 45%, color-mix(in srgb, var(--color-tertiary) 30%, transparent) 75%, transparent 95%);
    pointer-events: none;
}

.hero-banner__content h1,
.hero-banner__content .hero-banner__heading {
    max-width: 768px;
    width: 100%;
}

/* Vertically centers via `top: 50%` + `transform`, deliberately *not*
   flexbox - `.container`'s own `margin: 0 auto` on a flex item with
   `align-items: stretch` (the default) disables stretch and instead
   shrink-wraps the item to its content, then centers *that* box with the
   auto margins. Every slide's `<h1>` is a different length, so each one
   shrink-wrapped to a different width and centered at a different
   offset - only the longest heading happened to land close to the grid's
   real left edge. Position + transform sidesteps the whole flex/auto-margin
   interaction, so `.container` centers itself exactly as it does
   everywhere else on the site. */
.hero-banner__inner {
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    transform: translateY(-50%);
}

/* Mobile-first: on the narrowest phones (~320px) the two CTAs wrap onto
   a second line ("Start Free Trial" + "Explore Classes" don't both fit
   on one row), which pushed the wrapped second button down into the
   swiper's prev/next arrows below it. `--space-4` (not `--space-8`)
   keeps that wrapped state clear of the arrows; `min-width: 768px`
   restores the roomier desktop margin once wrapping stops happening at
   all. See CSS coding standard #8 in design_system.md. */
.hero-banner__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    margin-top: var(--space-4);
    pointer-events: auto;
}

.hero-banner__actions a {
    font-size: var(--text-body-size);
}

/* Both arrows grouped bottom-left, aligned to the site grid via a nested
   `.container` rather than Swiper's own edge-to-edge default positions -
   see design_system.md "Layout" for the full-bleed + nested-container
   pattern this reuses. Mobile-first `--space-6` (not `--space-8`) buys a
   little extra clearance from the CTAs above, for the same reason as
   `.hero-banner__actions`'s margin-top. */
.hero-swiper__controls {
    position: absolute;
    left: 0;
    right: 0;
    bottom: var(--space-6);
    z-index: 3;
    display: flex;
    gap: var(--space-3);
}

@media (min-width: 768px) {
    .hero-banner__actions {
        margin-top: var(--space-8);
    }

    .hero-swiper__controls {
        bottom: var(--space-8);
    }
}

/* Color is fixed per direction, not per state - `.hero-swiper` has
   `loop: true` (see welcome.blade.php), so a button is never actually
   `.swiper-button-disabled`; there's always another slide to go to. */
.hero-swiper .swiper-button-next,
.hero-swiper .swiper-button-prev {
    position: static;
    top: auto;
    margin: 0;
    width: var(--space-8);
    height: var(--space-8);
    border-radius: var(--radius-pill);
    border: 0;
}

.hero-swiper .swiper-button-prev {
    background-color: var(--color-primary);
    color: var(--color-white);
}

.hero-swiper .swiper-button-next {
    background-color: var(--color-control-surface);
    color: var(--color-primary);
}

/* Swiper's own icon is unused here - a Font Awesome `<i>` inside the
   button (see welcome.blade.php) replaces it. Its `::after` glyph is
   suppressed, and separately, Swiper's navigation module also always
   injects its own `<svg class="swiper-navigation-icon">` into the button
   regardless of what's already inside it - that gets hidden outright,
   otherwise both icons render stacked on top of each other. */
.hero-swiper .swiper-button-next::after,
.hero-swiper .swiper-button-prev::after {
    content: none;
}

.hero-swiper .swiper-button-next .swiper-navigation-icon,
.hero-swiper .swiper-button-prev .swiper-navigation-icon {
    display: none;
}

.hero-swiper .swiper-button-next i,
.hero-swiper .swiper-button-prev i {
    font-size: var(--space-4);
}

/* -- Stats bar --------------------------------------------------------------- */

/* Background is an unconfirmed placeholder (plain white) pending Figma -
   only the padding and the two font sizes were specified.

   Mobile-first: the unprefixed rules below are the small-screen baseline
   (two columns, h3-sized numbers - h2 reads too large next to a label
   wrapping across 2-3 lines in a ~150px-wide mobile column), and
   `min-width: 768px` layers on the wider treatment. This is the house
   convention going forward for every new component - see "CSS coding
   standards" in design_system.md. */
.stats-bar {
    padding: var(--space-10) 0;
    background-color: var(--color-surface);
}

.stats-bar__row {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--space-6) var(--space-4);
}

.stats-bar__item {
    flex: 1 1 calc(50% - var(--space-4) / 2);
}

/* Staggered scroll-reveal - each stat a beat behind the last, same
   nth-child stagger technique as the nav drawer's link entrance. Only
   takes effect combined with `.reveal`'s transition further down. */
.stats-bar__item:nth-child(2) {
    transition-delay: 80ms;
}

.stats-bar__item:nth-child(3) {
    transition-delay: 160ms;
}

.stats-bar__item:nth-child(4) {
    transition-delay: 240ms;
}

.stats-bar__number {
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
    color: var(--color-heading);
    margin: 0;
}

/* `em` (not a `--text-*` token) is deliberate here - it's a fraction of
   whatever `.stats-bar__number` is set to, so the star stays
   proportionate at both the mobile and desktop number size instead of
   rendering at the full, oversized number font-size like the plain "★"
   character it replaces did. */
.stats-bar__number-icon {
    font-size: 0.5em;
    vertical-align: middle;
    margin-left: var(--space-1);
}

.stats-bar__label {
    font-size: var(--text-body-size);
    line-height: var(--text-body-leading);
    color: var(--color-text);
    margin: var(--space-4) 0 0;
}

/* Tablet and up: four content-width items spread across the full row
   instead of two even columns. */
@media (min-width: 768px) {
    .stats-bar__row {
        gap: var(--space-6);
    }

    .stats-bar__item {
        flex: initial;
    }

    .stats-bar__number {
        font-size: var(--text-h2-size);
        line-height: var(--text-h2-leading);
    }
}

/* -- About section ------------------------------------------------------------ */

/* `position: relative` anchors the mural; padding is an estimate from the
   design screenshot, not yet Figma-confirmed. */
.about-section {
    position: relative;
    overflow: hidden;
    padding: var(--space-12) 0;
}

/* `text-align: center` here is inherited by `.eyebrow`/`h2` inside it too -
   neither sets its own `text-align`, so no extra rule is needed for them. */
.about-section__inner {
    max-width: 46rem;
    text-align: center;
    margin: 0 auto;
}

.about-section__text {
    font-size: var(--text-body-size);
    line-height: var(--text-body-leading);
    color: var(--color-text);
    margin: var(--space-4) 0 0;
}

.about-section__text + .btn {
    margin-top: var(--space-6);
}

/* Decorative, so it sits behind the copy and never intercepts clicks.
   `z-index: -1` (not `0`) is deliberate - a positioned descendant with
   `z-index: 0`/`auto` still paints *above* its non-positioned siblings
   regardless of DOM order, so `0` would put this on top of the text, not
   behind it. The source PNG/WebP already bakes in a very low alpha
   (~14% max), so no CSS opacity is layered on top. Mobile-first: smaller
   and tucked toward the corner below 768px so it doesn't visually
   compete with the centered text column on a narrow screen. */
/* Hidden site-wide by request - `display: none` rather than removing the
   markup, so re-enabling any flower mural later is a one-line revert. */
.about-section__mural {
    display: none;
    position: absolute;
    right: -120px;
    bottom: 0;
    width: 200px;
    z-index: -1;
    pointer-events: none;
}

.about-section__mural img {
    display: block;
    width: 100%;
    height: auto;
}

@media (min-width: 768px) {
    .about-section__mural {
        width: 400px;
    }
}

/* -- Practices grid ("What Brings You Here Today") --------------------------- */

/* Padding/copy sizes are estimates from the design screenshot, not yet
   Figma-confirmed. Mobile-first: one column below 768px, two from
   768px, three from 1024px (matching the nav's existing tablet/desktop
   split, see "Header/footer/nav" in design_system.md) - see CSS coding
   standard #8. */
/* Mobile-first (see CSS coding standard #8) - the 64px `--space-16` figure
   is from the design screenshot at desktop width; applied flat it made
   this section's vertical rhythm noticeably heavier than its neighbors
   (stats bar 40px, about section 48px) on a phone. `--space-12` (48px)
   below 768px matches the about-section directly above it instead. */
.practices-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .practices-section {
        padding: var(--space-16) 0;
    }
}

.practices-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-6);
    margin-top: var(--space-8);
}

@media (min-width: 1024px) {
    .practices-grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

/* `aspect-ratio` (not a `--space-*` token) is a one-off, same precedent
   as `.form`'s `max-width` - it's not a color/font/spacing/radius/shadow/
   transition value the coding standards require tokenizing. */
.practice-card {
    position: relative;
    display: block;
    aspect-ratio: 7 / 8;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

/* Staggered scroll-reveal by grid column (up to 3 columns at 1024px) -
   same nth-child stagger technique as the stats bar above. */
.practice-card:nth-child(3n+2) {
    transition-delay: 80ms;
}

.practice-card:nth-child(3n) {
    transition-delay: 160ms;
}

.practice-card__media,
.practice-card__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Subtle hover zoom on the photo - `overflow: hidden` on `.practice-card`
   above keeps the zoomed image clipped to the card's rounded corners. */
.practice-card__media img {
    transition: transform var(--transition-slow) var(--ease-smooth);
}

.practice-card:hover .practice-card__media img {
    transform: scale(1.05);
}

/* Dark-to-transparent wash behind the title/description, same
   `color-mix()`-with-transparent technique as the hero's own legibility
   gradient (see "Hero banner" in design_system.md) - reused here for a
   photo card instead of a full-bleed banner. */
.practice-card__overlay {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: var(--space-2) var(--space-3) var(--space-3);
    background-image: linear-gradient(180deg, transparent 40%, color-mix(in srgb, var(--color-black) 80%, transparent) 100%);
}

@media (min-width: 768px) {
    .practice-card__overlay {
        padding: var(--space-4) var(--space-5) var(--space-5);
    }
}

.practice-card__title {
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    color: var(--color-text-inverse);
    margin: 0 0 var(--space-1);
}

/* Hidden below 768px - two 2-column card rows plus a description each
   made the grid feel cramped/text-heavy on a phone; the title alone is
   enough to identify the practice at that size. */
.practice-card__description {
    display: none;
    font-size: var(--text-body-size);
    line-height: var(--text-body-leading);
    color: var(--color-text-inverse);
    margin: 0;
}

@media (min-width: 768px) {
    .practice-card__description {
        display: block;
    }
}

/* -- Explore practices (icon tile grid) --------------------------------------- */

/* Mirrors `.about-section__inner`'s centered-copy pattern - `.eyebrow`/`h2`
   inside don't need their own `text-align` rule, this is inherited. Section
   padding, tile styling, and copy are from a design screenshot, not yet
   Figma-confirmed. */
.explore-practices {
    padding: var(--space-12) 0;
    text-align: center;
}

.explore-practices__header {
    max-width: 46rem;
    margin: 0 auto;
}

/* Mobile-first: one column below 768px - each tile stacks icon above
   title/description/link (see `.practice-tile` below), so (unlike the
   photo-only `.practices-grid` above) it doesn't need the row-width a
   side-by-side icon+text layout would. */
.explore-practices__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-6);
    margin-top: var(--space-10);
    text-align: left;
}

@media (min-width: 768px) {
    .explore-practices__grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 1024px) {
    .explore-practices__grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

/* Icon + text side by side (welcome.blade.php's homepage grid - label +
   title, no description). about.blade.php reuses this same component with
   a different content shape (icon + title + description + link, stacked
   vertically) - that variant is scoped under `.explore-practices--detailed`
   below, NOT applied here, so it can never affect this page's layout.
   Same nth-child stagger technique as the stats bar/practice cards above -
   grouped by the 3-column desktop layout. Hover lift uses `transform`
   only (coding standard #6); `-4px` is a one-off literal, same precedent
   as the nav drawer's `translateX(16px)` link entrance. */
.practice-tile {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-6);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    transition: transform var(--transition-slow) var(--ease-smooth);
}

.practice-tile:nth-child(3n+2) {
    transition-delay: 80ms;
}

.practice-tile:nth-child(3n) {
    transition-delay: 160ms;
}

.practice-tile:hover {
    transform: translateY(-4px);
}

.practice-tile__icon img {
    display: block;
    width: var(--space-10);
    height: var(--space-10);
}

/* Not tokenized - same one-off `letter-spacing` precedent as
   `.eyebrow--banner` (see `.form`'s `max-width` for the original
   precedent this follows). */
.practice-tile__label {
    font-size: var(--text-sm);
    line-height: 1.4;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin: 0 0 var(--space-1);
}

.practice-tile__title {
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    margin: 0;
}

.explore-practices__actions {
    margin-top: var(--space-10);
}

/* -- Explore practices, detailed variant (about page) --------------------------
   about.blade.php's "Continue Your Journey" section reuses `.practice-tile`
   above, but with icon + title + description + link stacked vertically
   instead of welcome.blade.php's icon + label + title row - scoped under
   this modifier class (added to about.blade.php's `<section>`) so none of
   it touches the shared rules welcome.blade.php's own `.explore-practices`
   still relies on. */
.explore-practices--detailed .practice-tile {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-5);
}

.explore-practices--detailed .practice-tile__icon img {
    width: var(--space-8);
    height: var(--space-8);
}

/* `flex: 1` here, plus `.practice-tile__description`'s own `flex: 1`
   below, are what pin the link to each card's bottom edge regardless of
   how many lines that card's title/description wrap to - grid items
   already stretch to a shared row height by default, so every card in a
   row ends up the same height, and both flex children absorb whatever
   space is left so the link lands at the same bottom position on every
   card in that row. Same pattern as `.teacher-card__body`/
   `.teacher-card__specialties` above. */
.explore-practices--detailed .practice-tile__text {
    display: flex;
    flex: 1;
    flex-direction: column;
}

/* Overrides the base `.practice-tile__title` above (h4-size) up to
   h3-size for this variant's larger heading treatment. */
.explore-practices--detailed .practice-tile__title {
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
    margin: 0 0 var(--space-2);
}

/* No font-size override - inherits the body default (`--text-body-size`,
   18px), same as `.why-feature__description`/`.teacher-card__specialties`
   above, for a consistent description-text size across the page. */
.explore-practices--detailed .practice-tile__description {
    flex: 1;
    color: var(--color-text-muted);
    margin: 0;
}

.explore-practices--detailed .practice-tile__link {
    display: inline-block;
    margin-top: var(--space-4);
    font-size: var(--text-eyebrow-size);
    font-weight: var(--font-weight-medium);
}

/* -- Courses swiper ------------------------------------------------------------

   Plain container section (not full-bleed itself, same as `.practices-section`/
   `.explore-practices` above) with one full-bleed child (`.courses-swiper-outer`)
   so the card row can peek past the site grid's right edge - the design shows
   the last visible card cut off by the *viewport*, not the grid, hinting
   there's more to scroll. Section padding matches `.practices-section`
   directly above it (48px mobile, 64px desktop - see CSS coding standard #8
   in design_system.md); copy/card sizing are from a design screenshot, not
   yet Figma-confirmed. */
/* White section background via a full-bleed `::before`, not `.full-bleed`
   on `.courses-section` itself - the swiper's own peek-past-the-grid
   alignment above (`.courses-swiper-outer`) is computed relative to this
   section staying container-constrained; making the section itself
   full-bleed would change what "50%" resolves to for that nested
   full-bleed math. A `position: relative` + absolutely positioned,
   `z-index: -1` pseudo-element paints edge-to-edge behind the content
   without touching the section's own box model - same "z-index: -1
   decorative layer behind contained content" technique as the mural
   pictures on `.quote-section`, just a flat color instead of an image. */
.courses-section {
    position: relative;
    padding: var(--space-12) 0;
}

.courses-section::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 100vw;
    transform: translateX(-50%);
    background-color: var(--color-surface);
    z-index: -1;
}

@media (min-width: 768px) {
    .courses-section {
        padding: var(--space-16) 0;
    }
}

/* Mirrors `.about-section__inner`'s centered-copy header pattern. */
.courses-section__header {
    max-width: 46rem;
    text-align: center;
    margin: 0 auto;
}

/* `padding-left` (not a nested `.container`) re-aligns the first card with
   the grid's left edge while leaving the right side unconstrained for the
   peek - a nested `.container` would clip its own overflow, defeating the
   point. Same `max()` formula the site already uses to align full-bleed
   content to the grid (see `.hero-banner__inner` and its comment in
   design_system.md "Hero banner"), so it degrades to a flat `--space-6`
   gutter below `--content-width` instead of a negative offset. Swiper's own
   CSS already sets `overflow: hidden` on `.swiper`, so nothing beyond the
   visible slides needs a separate clip here - and `body`'s own
   `overflow-x: hidden` (see "Base / reset") is the same guard every other
   `.full-bleed` element already relies on.

   Qualified with the `.courses-swiper-outer` ancestor (not just
   `.courses-swiper` alone) deliberately - Swiper's own CDN stylesheet (see
   "Frontend asset strategy" in CLAUDE.md) loads *after* this file, via
   `@push('styles')` in welcome.blade.php, and its own base rule
   (`.swiper { padding: 0; ... }`) is the exact same specificity a bare
   `.courses-swiper` rule would be. Equal specificity + later source order
   means Swiper's `0` silently wins and zeroes this padding out - confirmed
   by hand, not a hypothetical. Two classes beats it outright regardless of
   source order. */
.courses-swiper-outer .courses-swiper {
    padding-left: max(var(--space-6), calc((100vw - var(--content-width)) / 2 + var(--space-6)));
    margin-top: var(--space-8);
}

/* Swiper equalizes each slide's height to the tallest in the row by default
   (no `autoHeight` set) - `height: auto` lets it do that instead of the
   library's own default fixed `height: 100%` fighting it, and `.course-card`
   below stretches to fill whatever height Swiper settles on. */
.courses-swiper .swiper-slide {
    height: auto;
}

/* Flex column with the "Read More" link pinned to the bottom via
   `margin-top: auto` (below) - course descriptions run different lengths,
   and this keeps every link in the row on one baseline without hardcoding a
   min-height that would break the moment real copy changes. Hover
   underline is suppressed here (unlike the plain `a` default) since the
   whole card is the click target and the design only shows the "Read More"
   link itself gaining emphasis on hover, not the title/description text. */
.course-card {
    display: flex;
    flex-direction: column;
    height: 100%;
}

.course-card:hover,
.course-card:focus-visible {
    text-decoration: none;
}

/* One-off `aspect-ratio`, same precedent as `.practice-card`'s `7 / 8` -
   this card's own image shape, not a token the coding standards require. */
.course-card__media {
    display: block;
    aspect-ratio: 4 / 3;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.course-card__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--transition-slow) var(--ease-smooth);
}

.course-card:hover .course-card__media img {
    transform: scale(1.05);
}

/* Same uppercase/letter-spacing/muted treatment as `.practice-tile__label` -
   text-transform normalizes the source content export's inconsistent
   casing between courses (see the `$courses` comment in welcome.blade.php)
   instead of preserving that as a visual difference. Explicit color is
   required here (not inherited from `body`) because this element's parent
   is an `<a>`, and the global `a { color: var(--color-primary) }` rule (see
   "Base / reset") wins the inheritance chain otherwise. */
.course-card__meta {
    font-size: var(--text-sm);
    line-height: 1.4;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin: var(--space-4) 0 0;
}

/* Font-size/line-height/color come from the global `h3` rule (see "Base /
   reset") - only the margin needs overriding from that rule's default. */
.course-card__title {
    margin: var(--space-2) 0 0;
}

/* Font-size/line-height are inherited from `body` - only color (see
   `.course-card__meta` above for why) and margin need setting here. */
.course-card__description {
    color: var(--color-text);
    margin: var(--space-2) 0 0;
}

.course-card__link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: auto;
    padding-top: var(--space-4);
    color: var(--color-heading);
    font-weight: var(--font-weight-medium);
}

.course-card__link i {
    transition: transform var(--transition-fast);
}

.course-card:hover .course-card__link {
    color: var(--color-primary);
}

.course-card:hover .course-card__link i {
    transform: translateX(var(--space-1));
}

/* -- Video card (reusable - first shipped in the courses/show.blade.php
      chapter accordion, meant for any video listing sitewide) -------------

   Same media/meta/title/description stack as `.course-card` above, plus a
   duration chip overlaid on the photo - `color-mix()` against
   `--color-black` rather than a new token, same technique `.nav-backdrop`
   already uses for a translucent-dark surface. `--color-text-muted`
   uppercase label matches `.course-card__meta`'s own treatment exactly,
   not a new "level" color - one fewer one-off to maintain. */
.video-card__media {
    display: block;
    position: relative;
    aspect-ratio: 3 / 2;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.video-card__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.video-card__duration {
    position: absolute;
    left: var(--space-4);
    bottom: var(--space-4);
    margin: 0;
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill);
    background-color: color-mix(in srgb, var(--color-black) 55%, transparent);
    color: var(--color-white);
    font-size: var(--text-sm);
}

.video-card__level {
    font-size: var(--text-sm);
    line-height: 1.4;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin: var(--space-4) 0 0;
}

.video-card__title {
    margin: var(--space-2) 0 0;
}

.video-card__description {
    color: var(--color-text);
    margin: var(--space-2) 0 0;
}

/* Grouped and centered below the card row, same pill treatment as the hero
   swiper's own controls (`--color-primary` / `--color-control-surface`,
   see "Hero banner (Swiper)" above) - but in normal flow rather than an
   absolute overlay. This carousel's height is content-driven (unlike the
   hero's fixed `clamp()` height), so an absolute overlay would sit on top
   of whichever card's "Read More" link is shortest. */
.courses-swiper__controls {
    display: flex;
    justify-content: center;
    gap: var(--space-3);
    margin-top: var(--space-8);
}

.courses-swiper__controls .swiper-button-prev,
.courses-swiper__controls .swiper-button-next {
    position: static;
    top: auto;
    margin: 0;
    width: var(--space-8);
    height: var(--space-8);
    border-radius: var(--radius-pill);
    border: 0;
}

.courses-swiper__controls .swiper-button-prev {
    background-color: var(--color-primary);
    color: var(--color-white);
}

.courses-swiper__controls .swiper-button-next {
    background-color: var(--color-control-surface);
    color: var(--color-primary);
}

/* Swiper's own icon is unused here - a Font Awesome `<i>` inside the
   button (see welcome.blade.php) replaces it. Its `::after` glyph is
   suppressed, and separately, Swiper's navigation module also always
   injects its own `<svg class="swiper-navigation-icon">` into the button
   regardless of what's already inside it - that gets hidden outright too
   below, otherwise both icons render stacked on top of each other. */
.courses-swiper__controls .swiper-button-next::after,
.courses-swiper__controls .swiper-button-prev::after {
    content: none;
}

.courses-swiper__controls .swiper-navigation-icon {
    display: none;
}

.courses-swiper__controls i {
    font-size: var(--space-4);
}

.courses-section__actions {
    margin-top: var(--space-10);
    text-align: center;
}

/* -- Why Himalayan Practices (icon + text feature list) ------------------------

   Plain container section (not full-bleed), same padding rhythm as
   `.practices-section`/`.courses-section` above. First two-column
   image+copy layout on the page - mobile-first single column (photo on
   top, copy below), two columns from 1024px (matching the icon-tile
   grid's own desktop breakpoint in "Explore practices" above), since a
   narrower two-column split would cramp the icon+title+description rows
   below it. Even `1fr 1fr` split. Section padding and copy are from a
   design screenshot, not yet Figma-confirmed. */
.why-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .why-section {
        padding: var(--space-16) 0;
    }
}

.why-section__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
}

/* One-off `aspect-ratio` matching the source photo's own crop (630 / 700),
   same precedent as `.practice-card`'s `7 / 8` - the mobile (single-column)
   sizing only; overridden to `height: 100%` on the two-column desktop
   layout below. `position: relative` anchors the absolutely-positioned
   `img` below in both cases.

   Deliberately declared *before* its own `min-width: 1024px` override
   below, not after - already got this backwards once. Both rules are a
   single class each (equal specificity), so whichever comes later in the
   file wins regardless of the media query's condition; putting this base
   rule after the override made the override dead code at every width,
   confirmed via `getComputedStyle` still reporting `9 / 10` at 1440px
   wide. Mobile-first (base rule, then `min-width` layering on top) isn't
   just a style convention here - getting the order backwards silently
   breaks the desktop behavior entirely. */
.why-section__media {
    display: block;
    position: relative;
    aspect-ratio: 9 / 10;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

/* `position: absolute` (not `display: block`) is deliberate - see the
   `min-width: 1024px` comment below for why taking the image out of flow
   is what makes the desktop height-matching actually work, not just a
   stretch-to-fill convenience. */
.why-section__media img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

@media (min-width: 1024px) {
    .why-section__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-12);
    }

    /* Desktop only: the image drops its own independent `aspect-ratio`
       (above) and instead stretches to `height: 100%` of its grid cell,
       so it starts and ends exactly level with the content column beside
       it, whatever height that column's copy happens to need - rather
       than the two columns landing at two different, independently-sized
       heights. Confirmed against a real browser: an earlier version of
       this rule assumed CSS Grid would ignore this column's percentage
       height when computing the row's auto height, leaving the content
       column to drive it alone - wrong, and visibly wrong once checked -
       `<img>` is a replaced element, so absent an override its own
       intrinsic aspect ratio is what a percentage height falls back to
       (auto height = width ÷ intrinsic ratio), and that height (~675px at
       this column's width) was very often taller than the copy, leaving
       the image overhanging past the last feature row instead of ending
       level with it. The `img` being `position: absolute` (above) is
       what actually fixes it: taken out of flow, it contributes nothing
       to this element's own intrinsic size, so *this* element (still in
       normal flow) has no content-driven height of its own left to
       contribute to the row's auto-sizing - only then does the row's
       height come from the content column alone, for `height: 100%` here
       to stretch into. Needs the two-column grid already active; on the
       single-column mobile layout above there's no sibling in the same
       row to borrow a height from, so `aspect-ratio` (above) is what
       gives the image a height there instead. */
    .why-section__media {
        aspect-ratio: auto;
        height: 100%;
    }
}

.why-section__content {
    padding: var(--space-4) 0;
}

.why-feature-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
    margin-top: var(--space-10);
}

.why-feature {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
}

/* The source PNG/WebP is already a complete circular badge (line icon +
   white fill + hairline border baked in, see public/images/icons) - not a
   bare line-art icon needing its own badge wrapper, unlike
   `.practice-tile__icon` above. Sized down slightly from its native 54px
   to the nearest spacing token (`--space-12`, coding standard #2 - no
   one-off pixel value) rather than rendering at native size. */
.why-feature__icon {
    display: block;
    flex-shrink: 0;
}

.why-feature__icon img {
    display: block;
    width: var(--space-12);
    height: var(--space-12);
}

/* Font-size/line-height come from the global `h3` rule (see "Base /
   reset"), then scaled down to `--text-h4-size` - same "smaller heading,
   correct nesting" pattern as `.practice-tile__title`/`.practice-card__title`
   (this is structurally the same icon+title+description row as
   `.practice-tile`, just without the card border, so it gets the same
   title treatment for consistency). */
.why-feature__title {
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    margin: 0;
}

.why-feature__description {
    color: var(--color-text);
    margin: var(--space-1) 0 0;
}

/* -- Our Story (about page) - numbered timeline variant of `.why-feature*` --
   The about page's "Our Story" section reuses this component, but its icon
   is a plain number (`<span>1</span>`, see about.blade.php's `$whyFeatures`)
   instead of the badge PNG `.why-feature__icon img` above expects, and adds
   a connecting vertical line - so it needs its own circular badge styling
   and a positioned line, scoped under `.our-story-section` rather than
   changing the shared base rules above (welcome.blade.php's own
   `.why-section` still uses the base image-icon version untouched). */
.our-story-section .why-feature-list {
    position: relative;
    /* Establishes a local stacking context so the connecting line's
       `z-index: -1` below stays behind this list's own numbered circles
       without escaping to the page's root stacking context. */
    z-index: 0;
}

/* Connecting line running the full height of the list, behind every
   circle (`z-index: -1`, contained by the stacking context above) so
   each opaque circle visually "breaks" the line at its own position.
   Centered under a `--space-10` (40px) circle, so offset by half that.
   `border-left` (not the `border` shorthand) is deliberate - the element
   has no explicit `width` (shrinks to 0), so an all-sides border would
   render as two adjacent 0.5px edges (a 1px-thick line) instead of the
   single 0.5px hairline intended. */
.our-story-section .why-feature-list::before {
    content: '';
    position: absolute;
    top: 0;
    left: calc(var(--space-10) / 2);
    height: 100%;
    border-left: 0.5px solid var(--color-primary);
    z-index: -1;
}

.our-story-section .why-feature {
    gap: var(--space-8);
}

.our-story-section .why-feature__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--space-10);
    height: var(--space-10);
    background-color: var(--color-surface);
    border-radius: var(--radius-pill);
}

.our-story-section .why-feature__title {
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
}

/* -- Beginners Playlist ---------------------------------------------------------

   Full-bleed (unlike `.why-section` above) purely to host the
   `lines-murial` background art edge-to-edge - the actual copy still sits
   in a nested `.container`, same pattern as `.hero-banner`/`.about-section`
   above. `position: relative` + `overflow: hidden` anchors and clips the
   mural to this section's own bounds. `background-color` is the same
   token `.stats-bar` uses for its own "plain white section" background -
   not yet Figma-confirmed, same as that one.

   `isolation: isolate` is load-bearing, not defensive - confirmed as an
   actual bug, not a hypothetical. `position: relative` alone does NOT
   make this element establish its own stacking context (that needs an
   explicit `z-index`, or `isolation`, or a transform/opacity, etc.)., so
   `.playlist-section__mural`'s `z-index: -1` was being resolved against
   the nearest ancestor that *does* form one - effectively the document
   root - instead of staying contained within this section. That's
   invisible as long as this section has no background of its own
   (nothing else opaque happens to overlap that screen region), but the
   moment `background-color` above was added, that background paints at
   the *normal* stacking layer - above the mural's hoisted-to-root
   negative-z-index layer - and silently covered it completely. Confirmed
   by replicating the exact `object-fit: cover` crop in a canvas (which
   showed the art perfectly fine) before finding the real cause here.
   `isolation: isolate` fixes it by giving this element its own stacking
   context, so the negative z-index child stays contained behind *this
   section's* own content instead of escaping to the root. */
.playlist-section {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: var(--space-12) 0;
    background-color: var(--color-surface);
}

@media (min-width: 768px) {
    .playlist-section {
        padding: var(--space-16) 0;
    }
}

/* Decorative, so it sits behind the copy and never intercepts clicks -
   same `z-index: -1` reasoning as `.about-section__mural` (a positioned
   descendant at `z-index: 0`/`auto` still paints above non-positioned
   siblings regardless of DOM order). Unlike that corner accent, this mural
   covers the *entire* section: `inset: 0` + `object-fit: cover` stretches/
   crops the source image (1512×619) to whatever height this section's
   actual content needs, rather than rendering at its own native
   proportions.

   Hidden below 768px (mobile-first: `display: none` here, restored by the
   `min-width: 768px` override below) - the source is a short, wide strip
   never designed for a single narrow mobile column, and `object-fit: cover`
   stretching it to a much taller, narrower crop there mostly showed a
   near-empty stretch of the pattern anyway, adding weight without the
   texture it's meant to provide. */
.playlist-section__mural {
    display: none;
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
}

@media (min-width: 768px) {
    .playlist-section__mural {
        display: block;
    }
}

/* No `mix-blend-mode` needed - unlike the first version of this rule
   assumed, the `lines-murial` source was never an opaque white background;
   it was genuinely transparent already (confirmed by reading its actual
   alpha channel, not just previewing it - an image viewer compositing
   transparency onto white for display is easy to mistake for the image
   itself having a white background). The real defect was the alpha
   channel itself topping out at ~31/255 (~12% opacity) - far too faint to
   read on any background - so the source PNG/WebP were re-exported with
   their alpha scaled up (~5x, now maxing around 155/255) instead of
   papering over it with a blend mode. Same "already bakes in its own
   alpha, no extra CSS needed" pattern as `.about-section__mural`'s flower
   art now. */
.playlist-section__mural img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0.2;
}

.playlist-section__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10);
}

@media (min-width: 1024px) {
    .playlist-section__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-12);
    }
}

.playlist-section__text {
    color: var(--color-text);
    margin: var(--space-4) 0 0;
}

.playlist-section__text + .btn {
    margin-top: var(--space-6);
}

/* Divider between the two columns is a `border-left` on this column, not a
   separate absolutely-positioned line - it then naturally spans exactly
   this column's own content height (top of the "What You'll Explore"
   heading to the bottom of the list) with no extra markup, and the
   `min-width: 1024px` gap above already provides the breathing room on
   both sides of it. No divider below that width - the columns are stacked,
   so there's nothing for a vertical rule to separate. */
@media (min-width: 1024px) {
    .playlist-section__explore {
        border-left: 1px solid var(--color-border);
        padding-left: var(--space-12);
    }
}

.playlist-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin-top: var(--space-6);
    padding-left: 10px;
}

/* Custom dot instead of the native `::marker` glyph - gives an exact,
   consistent size/color across browsers rather than relying on how each
   renders the default disc glyph at the body font size. `6px`/`0.55em` are
   one-off literals, same precedent as `.eyebrow--banner`'s letter-spacing -
   this bullet shape isn't reused anywhere else yet. */
.playlist-list li {
    position: relative;
    padding-left: var(--space-4);
    color: var(--color-text);
}

.playlist-list li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.55em;
    width: 6px;
    height: 6px;
    border-radius: var(--radius-pill);
    background-color: var(--color-primary);
}

/* Title + description pairs - first use is the "What You'll Explore" list
   (see welcome.blade.php); `.pricing-card__features`'s plain-string items
   don't use these, so they're unaffected. */
.playlist-list__title {
    display: block;
    color: var(--color-heading);
    font-weight: var(--font-weight-semibold);
}

.playlist-list__description {
    display: block;
    margin-top: var(--space-1);
}

/* -- Our Offerings (dark inverted CTA band) -------------------------------------

   Full-bleed, dark/inverted, background photo + wash - same structural
   shape as `.hero-banner`/`.playlist-section` above (full-bleed section,
   background art absolutely positioned behind a nested `.container` of
   real content). `isolation: isolate` from the very first draft this
   time, not added after the fact - see the `.playlist-section` postmortem
   above for exactly why a `position: relative` parent with a `z-index: -1`
   decorative child needs it (also CSS coding standard #9). Unlike
   `.playlist-section__mural`, the background image here has no transparency
   of its own to blend with the page - the darkening comes entirely from
   `.offerings-section__media::after`'s own overlay, not the source photo. */
.offerings-section {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .offerings-section {
        padding: var(--space-16) 0;
    }
}

.offerings-section__media {
    position: absolute;
    inset: 0;
    z-index: -1;
}

.offerings-section__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* The dark wash that makes the white/light copy readable over the photo -
   sampled from the design screenshot as very close to opaque black, not a
   subtle tint. `color-mix()` against `--color-surface-inverse` rather than
   a plain hex, same technique as the hero banner's own legibility wash. */
.offerings-section__media::after {
    content: "";
    position: absolute;
    inset: 0;
    background-color: color-mix(in srgb, var(--color-surface-inverse) 88%, transparent);
}

.offerings-section__header {
    max-width: 46rem;
    text-align: center;
    margin: 0 auto;
}

.offerings-section__heading {
    color: var(--color-text-inverse);
}

.offerings-section__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10);
    margin-top: var(--space-10);
}

@media (min-width: 1024px) {
    .offerings-section__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-12);
    }

    /* Divider between the two columns - only ever two offerings, so
       `:nth-child(2)` (not a dedicated class) is enough to target "the
       second column"; same border-as-divider technique as
       `.playlist-section__explore` above. */
    .offerings-column:nth-child(2) {
        border-left: 1px solid color-mix(in srgb, var(--color-white) 18%, transparent);
        padding-left: var(--space-12);
    }
}

.offerings-column__number {
    color: var(--color-accent);
    font-weight: var(--font-weight-semibold);
    margin: 0 0 var(--space-2);
}

.offerings-column__title {
    color: var(--color-text-inverse);
}

.offerings-column__description {
    color: var(--color-text-muted-inverse);
    margin: var(--space-3) 0 var(--space-8);
}

.offerings-feature-grid {
    display: grid;
    grid-template-columns: 1fr;
    margin-bottom: var(--space-8);
}

@media (min-width: 640px) {
    .offerings-feature-grid {
        grid-template-columns: 1fr 1fr;
        column-gap: var(--space-8);
    }
}

.offerings-feature {
    padding: var(--space-3) 0;
    color: var(--color-text-inverse);
    border-bottom: 1px solid color-mix(in srgb, var(--color-white) 18%, transparent);
}

.offerings-column__link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--color-text-inverse);
    font-weight: var(--font-weight-medium);
}

.offerings-column__link i {
    transition: transform var(--transition-fast);
}

.offerings-column__link:hover,
.offerings-column__link:focus-visible {
    color: var(--color-accent);
}

.offerings-column__link:hover i,
.offerings-column__link:focus-visible i {
    transform: translateX(var(--space-1));
}

/* -- This Week Live (schedule rows) ---------------------------------------------

   Plain container section (not full-bleed), same padding rhythm as
   `.practices-section`/`.courses-section` above. */
.schedule-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .schedule-section {
        padding: var(--space-16) 0;
    }
}

.schedule-section__header {
    max-width: 46rem;
    text-align: center;
    margin: 0 auto;
}

.schedule-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    margin-top: var(--space-8);
}

/* Each row is its own white card now, not a zebra-striped continuous
   list - background/border/radius/padding are exactly `.card`'s own
   values (see that component above), reused here rather than invented
   fresh, so every "white card" on the site shares one visual language.
   `--radius-lg` (16px) stands in for a requested 15px - tokens only, no
   magic values, per CSS coding standard #2; 16px is the nearest existing
   rung and reads as visually identical to 15px.

   Mobile-first: the photo spans all three info rows on the left
   (`grid-row: 1 / 4`) with date/title/teacher stacked in one column next
   to it; from 1024px it becomes a single classic row - photo, datetime,
   title/description, teacher - matching the design screenshot. `row-gap`
   is the *only* thing spacing those three stacked blocks apart on mobile
   - they used to each carry their own ad hoc `margin-top` too, which both
   duplicated the job and made the rhythm inconsistent between blocks;
   removed those in favor of this one shared gap (see `.schedule-row__content`/
   `.schedule-row__teacher` below). */
.schedule-row {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: var(--space-4);
    row-gap: var(--space-4);
    align-items: start;
    padding: var(--space-6);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
}

.schedule-row__media {
    grid-row: 1 / 4;
    grid-column: 1;
    display: block;
    /* One-off size/ratio (not a token), same precedent as `.practice-card`'s
       `aspect-ratio: 7 / 8` - this row's own thumbnail shape. */
    width: 5.5rem;
    aspect-ratio: 10 / 9;
    border-radius: var(--radius-md);
    overflow: hidden;
}

.schedule-row__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.schedule-row__date {
    font-size: var(--text-sm);
    color: var(--color-text-muted);
    margin: 0;
}

.schedule-row__day {
    font-size: var(--text-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-heading);
    margin: var(--space-1) 0 0;
}

.schedule-row__duration {
    font-size: var(--text-sm);
    color: var(--color-text-muted);
    margin: var(--space-1) 0 0;
}

/* No `margin-top` here - the mobile stacking gap between this, `.schedule-row__datetime`,
   and `.schedule-row__teacher` comes entirely from `.schedule-row`'s own
   `row-gap` now (see that rule's comment above), not a per-block margin. */
.schedule-row__content {
    margin-top: 0;
}

.schedule-row__title {
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    margin: 0;
}

.schedule-row__description {
    color: var(--color-text);
    margin: var(--space-2) 0 0;
}

/* No `margin-top` here either, same reasoning as `.schedule-row__content`
   above. */
.schedule-row__teacher {
    margin-top: 0;
}

.schedule-row__teacher-name {
    font-weight: var(--font-weight-semibold);
    color: var(--color-heading);
    margin: 0;
}

.schedule-row__teacher-specialty {
    font-size: var(--text-sm);
    color: var(--color-text-muted);
    margin: var(--space-1) 0 0;
}

@media (min-width: 1024px) {
    /* Second column is a fixed width, not `auto` - each `.schedule-row` is
       its own independent grid (there's no shared/subgrid across rows), so
       an `auto` column sizes to *that row's own* longest line of text.
       "Wednesday · 7:00" (~100px rendered) vs "Friday · 7:00" (~72px)
       meant the title column after it started at a different x-offset on
       every single row - confirmed by measuring actual rendered widths,
       not a guess. A fixed 8rem (128px, comfortably above the longest
       current string) makes every row's title column start at the exact
       same x regardless of that row's own date/time text length. */
    .schedule-row {
        grid-template-columns: auto 8rem 1fr auto;
        column-gap: var(--space-6);
        align-items: center;
    }

    .schedule-row__media {
        grid-row: auto;
        grid-column: 1;
    }

    .schedule-row__datetime {
        grid-column: 2;
    }

    .schedule-row__content {
        grid-column: 3;
    }

    .schedule-row__teacher {
        grid-column: 4;
        text-align: right;
    }
}

.schedule-section__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-4);
    margin-top: var(--space-10);
}

/* -- Meet Your Teachers (teacher grid) -------------------------------------------

   Plain container section (not full-bleed), same padding rhythm as
   `.practices-section`/`.courses-section` above. */
.teachers-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .teachers-section {
        padding: var(--space-16) 0;
    }
}

.teachers-section__header {
    max-width: 46rem;
    text-align: center;
    margin: 0 auto;
}

/* Teachers directory page's filters bar sits right-aligned under the
   centered heading, unlike Live Studio's own left-aligned copy of this
   same `.studio-filters` component - scoped to `.teachers-section` so
   live-studio.blade.php's usage is unaffected. Still wraps to flex-start
   below the breakpoint where the fields stack, so a lone search field
   doesn't get pinned to the right edge on narrow screens. */
.teachers-section .studio-filters {
    justify-content: flex-end;
}

/* Both fields match the same width here rather than each shrinking to fit
   its own content (`select` in particular would otherwise size to "All
   styles" and end up noticeably narrower than the search field) - the
   design shows the search field and style dropdown as two equal-width
   pills side by side. */
.teachers-section .studio-filters__field {
    width: 13.75rem;
}

@media (max-width: 639px) {
    .teachers-section .studio-filters {
        justify-content: flex-start;
    }
}

/* Mobile-first: one column below 640px, two from 640px, four from
   1024px - skips a 3-column tier deliberately, unlike `.practices-grid`'s
   2-then-3 progression. There are exactly 4 teacher cards, and 3 doesn't
   evenly divide 4 - it would leave one card alone on its own row at that
   breakpoint. 1 → 2 → 4 (each step exactly doubling) never does. */
.teachers-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-6);
    margin-top: var(--space-8);
}

@media (min-width: 640px) {
    .teachers-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 1024px) {
    .teachers-grid {
        grid-template-columns: repeat(4, 1fr);
    }
}

/* Background/border/radius are exactly `.card`'s own values (see that
   component) reused, not invented fresh - same "white card" convention
   `.schedule-row` also follows now. */
.teacher-card {
    display: flex;
    flex-direction: column;
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.teacher-card:hover,
.teacher-card:focus-visible {
    text-decoration: none;
}

/* One-off `aspect-ratio` matching the source photos' own crop (282×235),
   same precedent as `.practice-card`'s `7 / 8`. Photo sits inset on padding
   inside the card's white chrome (not flush against its edges), so it
   carries its own radius rather than relying on the card's own
   `overflow: hidden` corner-clipping, which no longer reaches it. Same
   component everywhere `.teacher-card` appears - homepage, about page, and
   the teachers directory page. */
.teacher-card__media {
    display: block;
    aspect-ratio: 6 / 5;
    padding: var(--space-3);
}

.teacher-card__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: var(--radius-md);
    transition: transform var(--transition-slow) var(--ease-smooth);
}

.teacher-card:hover .teacher-card__media img {
    transform: scale(1.05);
}

/* `flex: 1` here, plus `.teacher-card__specialties`'s own `flex: 1`
   below, are what pin the arrow to each card's bottom edge regardless of
   how many lines that card's specialties list wraps to - grid items
   already stretch to a shared row height by default, so every card in a
   row ends up the same height, and both flex children absorb whatever
   space is left so the arrow lands at the same bottom position on every
   card in that row. */
.teacher-card__body {
    display: flex;
    flex: 1;
    flex-direction: column;
    padding: var(--space-4) var(--space-5) var(--space-5);
}

.teacher-card__header {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-2);
}

/* Overridden down from the global `h3` default to `--text-h4-size` -
   same "smaller heading, correct nesting" pattern as
   `.practice-card__title`/`.practice-tile__title`, chosen over
   `.course-card__title`'s "keep full h3" precedent for a more compact
   look consistent with this site's other grid-card titles. */
.teacher-card__name {
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    margin: 0;
}

/* Plain italic for the "Classes" label; the `<strong>` count inside
   overrides back to upright/semibold/heading-color, matching the design
   screenshot's two-weight treatment without a second element. */
.teacher-card__classes {
    flex-shrink: 0;
    font-size: var(--text-sm);
    font-style: italic;
    color: var(--color-text-muted);
    margin: 0;
}

.teacher-card__classes strong {
    font-style: normal;
    font-weight: var(--font-weight-semibold);
    color: var(--color-heading);
}

/* `flex: 1` (not just `margin-top: auto` on the arrow below) is what pins
   the arrow to each card's bottom edge - this box grows to absorb
   whatever vertical space is left in `.teacher-card__body` (itself
   `flex: 1` in the card's own column, see above), so a one-line
   specialties list (Neel Joshi's) and a two-line one (Anand Mehrotra's)
   both push their card's arrow to the exact same bottom position. */
.teacher-card__specialties {
    flex: 1;
    color: var(--color-text);
    margin: var(--space-2) 0 0;
}

/* `--space-8` (32px) - the same size token the hero swiper's own circular
   prev/next controls use (see "Hero banner" above) for this same "small
   circular icon button" shape, kept outline-only at rest and filled on
   hover/focus rather than solid like the hero's, since four of these
   sit on plain white cards rather than over a photo needing constant
   contrast. */
.teacher-card__arrow {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--space-8);
    height: var(--space-8);
    flex-shrink: 0;
    margin-top: var(--space-2);
    align-self: flex-end;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    color: var(--color-heading);
    transition: background-color var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}

.teacher-card:hover .teacher-card__arrow,
.teacher-card:focus-visible .teacher-card__arrow {
    background-color: var(--color-primary);
    border-color: var(--color-primary);
    color: var(--color-primary-contrast);
}

.teacher-card__arrow i {
    font-size: var(--text-sm);
}

.teachers-section__actions {
    margin-top: var(--space-10);
    text-align: center;
}

/* -- Teacher Profile (teachers/show.blade.php) -----------------------------------

   The one teacher detail page's photo+bio layout - sits inside a reused
   `.teachers-section` (see that class above), so only the two-column grid
   itself is new here. Mobile-first single column (photo on top, copy
   below), even 1fr/1fr split from 1024px - same two-column photo+copy
   technique as `.why-section__grid`, just without that section's
   height-matching trick, since this photo keeps its own fixed portrait
   crop at every width rather than stretching to match the copy column. */
.teacher-profile__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
    margin-top: var(--space-10);
}

@media (min-width: 1024px) {
    .teacher-profile__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-12);
        align-items: start;
    }
}

/* One-off `aspect-ratio` matching the source photo's own crop (404×480),
   same precedent as `.practice-card`'s `7 / 8`. */
.teacher-profile__media {
    display: block;
    aspect-ratio: 5 / 6;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.teacher-profile__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.teacher-profile__label {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--text-eyebrow-size);
}

.teacher-profile__name {
    margin: var(--space-2) 0 0;
}

/* Bumped up from the plain `h3` default (26px desktop) to `--text-h2-size`
   from 768px up - same "h3 base, h2 from desktop" upgrade `.stats-bar__number`
   already uses - so the name reads with the prominence the design gives it
   as this page's real subject, not sized like a nested card title. */
@media (min-width: 768px) {
    .teacher-profile__name {
        font-size: var(--text-h2-size);
        line-height: var(--text-h2-leading);
    }
}

.teacher-profile__title {
    margin: var(--space-2) 0 0;
    font-style: italic;
    color: var(--color-text-muted);
}

/* Bumped up from the plain `h4` default (20px, barely bigger than the
   18px body copy right below it) to `--text-h3-size` - `<h4>` is kept as
   the tag for correct nesting under `.teacher-profile__name`'s `<h3>`, but
   this needs to read as a clear sub-heading against the bio paragraphs,
   not blend into them. */
.teacher-profile__heading {
    margin: var(--space-8) 0 0;
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
}

.teacher-profile__bio {
    margin: var(--space-4) 0 0;
}

.teacher-profile__stats {
    margin-top: var(--space-8);
}

.teacher-profile__stat {
    display: flex;
    align-items: baseline;
    gap: var(--space-4);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--color-border);
}

.teacher-profile__stat-label {
    color: var(--color-primary);
}

.teacher-profile__stat-dash {
    color: var(--color-text-muted);
}

/* Regular weight, not bold - color alone (vs. `__stat-label`'s primary tone
   above) is what distinguishes the value in the design, matching this
   site's general restraint around bold weight elsewhere. */
.teacher-profile__stat-value {
    color: var(--color-text);
}

/* -- Teacher Topics (teachers/show.blade.php) -------------------------------------

   "Discover the Practices..." two-column category list further down the
   teacher detail page - sits inside a reused `.teachers-section` (see
   above), so only the two-column list itself is new here. Mobile-first
   single column, even 1fr/1fr split from 1024px - same technique as
   `.teacher-profile__grid`/`.why-section__grid` above. */
.teacher-topics__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0 var(--space-12);
    margin-top: var(--space-10);
}

@media (min-width: 1024px) {
    .teacher-topics__grid {
        grid-template-columns: 1fr 1fr;
    }
}

/* Each row is a whole-item `<a>` (its own future video-listing page, not
   built yet - see the Blade comment above) - `text-decoration: none` stops
   the plain `a` default from underlining every line of the row on
   hover/focus, same reasoning as `.teacher-card`'s own override. */
.teacher-topics__item {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-4) 0;
    border-bottom: 1px solid var(--color-border);
}

.teacher-topics__item:hover,
.teacher-topics__item:focus-visible {
    text-decoration: none;
}

.teacher-topics__label {
    font-family: var(--font-heading);
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    color: var(--color-heading);
    transition: color var(--transition-fast);
}

.teacher-topics__item:hover .teacher-topics__label,
.teacher-topics__item:focus-visible .teacher-topics__label {
    color: var(--color-primary);
}

.teacher-topics__count {
    flex-shrink: 0;
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

/* -- Recent Classes (teachers/show.blade.php) --------------------------------------

   Sits inside a reused `.teachers-section` (see above). Header row is
   left-aligned (unlike every other section header on this page, which is
   centered via `.teachers-section__header`) - title+subtitle on the left,
   search on the right - so it gets its own header class rather than
   reusing that one. Mobile-first: stacks to one column, search field
   dropping below the title, until there's room for both side by side. */
.teacher-videos__header {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

@media (min-width: 640px) {
    .teacher-videos__header {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
    }
}

.teacher-videos__header h2 {
    margin: 0;
}

.teacher-videos__subtitle {
    margin: var(--space-1) 0 0;
    color: var(--color-text-muted);
}

/* Fixed 3-column desktop grid, same "1 → 2 → 3" progression as
   `.articles-grid` - every page using this (this section, and
   vinyasa.blade.php's own class grid) always holds a multiple of 3
   classes (full rows only), unlike `.chapter-item__content`'s own
   `auto-fill` grid, which needs to flex for a chapter's varying video
   count. */
.teacher-videos__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
    margin-top: var(--space-10);
}

@media (min-width: 640px) {
    .teacher-videos__grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 1024px) {
    .teacher-videos__grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

/* -- Vinyasa (vinyasa.blade.php) ---------------------------------------------------

   Sits inside a reused `.teachers-section` (see above) - only the intro
   text and search field below are new. */
.vinyasa-intro__text {
    margin: var(--space-4) 0 0;
    color: var(--color-text-muted);
}

/* Centered, wider than the compact `.studio-filters__field` instances
   elsewhere (the teachers listing/detail pages pair theirs with other
   fields in a row) - this page shows it alone, so it reads as a real
   search bar rather than a small filter chip. */
.vinyasa-search {
    max-width: 26rem;
    margin: var(--space-8) auto 0;
}

.vinyasa-search .studio-filters__field {
    width: 100%;
}

/* -- Book Card (audiobooks.blade.php) ----------------------------------------------

   Sits inside a reused `.teachers-section` (see above). Mobile-first
   single column, even 1fr/1fr split from 768px - same two-column
   technique as `.why-section__grid` above, just without that section's
   height-matching trick (this card's photo keeps its own fixed book-cover
   crop, not a stretched one). */
.book-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10) var(--space-8);
    margin-top: var(--space-10);
}

@media (min-width: 768px) {
    .book-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

.book-card {
    display: flex;
    gap: var(--space-6);
    background-color: var(--color-card-surface);
    padding: var(--space-6);
    border-radius: var(--radius-lg);
}

/* One-off `aspect-ratio` matching the source cover's own crop (217×293),
   same precedent as `.practice-card`'s `7 / 8`. Fixed width (rather than
   `.course-row__media`'s 428px, tuned for a single full-width list row)
   keeps the cover book-sized inside this section's narrower two-up grid
   card. */
.book-card__media {
    display: block;
    flex-shrink: 0;
    width: 11rem;
    aspect-ratio: 217 / 293;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.book-card__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.book-card__body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.book-card__top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
}

.book-card__meta {
    color: var(--color-text-muted);
    font-size: var(--text-sm);
}

.book-card__rating {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    flex-shrink: 0;
    color: var(--color-heading);
    font-size: var(--text-sm);
}

.book-card__rating i {
    color: var(--color-gold);
}

/* Downsized from the global `h3` default (26px) to `--text-h4-size` - same
   "smaller heading, correct nesting" pattern as `.teacher-card__name`/
   `.why-feature__title` above, for a more compact look consistent with
   this card's narrow text column (fixed 11rem cover + two-up grid leaves
   much less room than `.course-row`'s own single-column row). */
.book-card__title {
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    margin: var(--space-2) 0 0;
}

/* Clamped to 2 lines - same "vary a lot in length, needs a hard cap"
   reasoning as `.course-row__description`, just clamped one line shorter
   to fit this card's narrower text column. `--text-eyebrow-size` here is
   the closest existing token to the requested 16px, same reuse precedent
   as `.contact-method__label` - no font/letter-spacing baggage attached
   to it, unlike `--text-caption-size` (tied to the Lyon Display face). */
.book-card__description {
    display: -webkit-box;
    overflow: hidden;
    color: var(--color-text);
    font-size: var(--text-eyebrow-size);
    margin: var(--space-2) 0 0;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
}

/* Same "pin to the bottom of a variable-height card" `margin-top: auto`
   technique as `.course-row__footer`. Price sits on its own row above the
   action links (not inline with them, unlike `.course-row__footer`) - a
   `flex-direction: column` here, not `row`, is what pushes "Read More"
   down instead of crowding it right next to the price. */
.book-card__footer {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin-top: auto;
    padding-top: var(--space-4);
}

.book-card__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3) var(--space-4);
}

.book-card__price {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-2);
    margin: 0;
    font-family: var(--font-heading);
}

.book-card__price-old {
    color: var(--color-text-muted);
    text-decoration: line-through;
}

.book-card__price-amount {
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    color: var(--color-heading);
}

.book-card__price-currency {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    color: var(--color-text);
}

/* -- Book Hero (audiobooks/show.blade.php) -----------------------------------------

   The one audiobook's detail hero - sits inside a reused `.teachers-section`
   (see above). Mobile-first single column (cover on top, copy below), row
   layout from 768px with a fixed-width cover - same "fixed-width media,
   flexible body" flex technique as `.book-card`/`.course-row`, not
   `.teacher-profile__grid`'s 50/50 grid split, since this cover reads much
   narrower than the text column next to it. */
.book-hero {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
}

@media (min-width: 768px) {
    .book-hero {
        flex-direction: row;
        align-items: flex-start;
        gap: var(--space-10);
    }
}

.book-hero__media {
    display: block;
    max-width: 22rem;
}

@media (min-width: 768px) {
    .book-hero__media {
        flex-shrink: 0;
        width: 22rem;
    }
}

/* Same "same photo, blurred, as a backdrop behind the sharp one" frame
   whether this renders as a single static image or a Swiper slide (see
   the Blade template's `@if (count($book['images']) > 1)`). `--space-12`
   (48px) is the nearest spacing token to the design's ~50px inset - same
   "round to the nearest token rather than a literal pixel value"
   precedent as `.why-feature__icon`'s icon sizing. */
.book-hero__frame {
    position: relative;
    padding: var(--space-12);
    border-radius: var(--radius-lg);
    overflow: hidden;
    background-color: var(--color-black);
}

/* `scale(1.15)` keeps the blur's own soft edge from showing past the
   frame's corners - `filter: blur()` doesn't clip to its own box, so
   without the extra scale a faint unblurred sliver of the source photo
   could peek through at the very edge. 5px is a light blur by design -
   the source photo stays recognizable behind the sharp cover, not washed
   into a flat wash. */
.book-hero__backdrop {
    position: absolute;
    inset: 0;
    z-index: 0;
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: blur(5px) brightness(0.5);
    transform: scale(1.15);
}

/* One-off `aspect-ratio` matching the source cover's own crop (311×440),
   same precedent as `.practice-card`'s `7 / 8`. */
.book-hero__image {
    position: relative;
    z-index: 1;
    display: block;
    aspect-ratio: 311 / 440;
    border-radius: var(--radius-md);
    overflow: hidden;
    box-shadow: var(--shadow-md);
}

.book-hero__image img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.book-hero-swiper .swiper-slide {
    height: auto;
}

/* Swiper's own pagination bullets, restyled to match this site's small
   pill-dot convention (the active one wider, not just a different color) -
   overlaid near the bottom of the frame, on top of the blurred backdrop,
   matching the design. White-based colors (not the usual `--color-primary`
   dot treatment elsewhere on the site) are what read against that dark
   backdrop - the usual dark-green active dot would have poor contrast
   here. Only ever rendered when this book has more than one photo (see
   the Blade `@if`), so there's nothing to style in the single-image case. */
.book-hero__dots {
    position: absolute;
    left: 0;
    right: 0;
    bottom: var(--space-4);
    z-index: 2;
    display: flex;
    justify-content: center;
    gap: var(--space-2);
}

.book-hero__dots .swiper-pagination-bullet {
    width: var(--space-2);
    height: var(--space-2);
    margin: 0;
    border-radius: var(--radius-pill);
    background-color: rgb(255 255 255 / 45%);
    opacity: 1;
    transition: width var(--transition-fast), background-color var(--transition-fast);
}

.book-hero__dots .swiper-pagination-bullet-active {
    width: var(--space-6);
    background-color: var(--color-white);
}

/* Capped rather than left to `flex: 1`'s full remaining width - on a wide
   viewport this column has far more text than it needs to fill, which
   read as an oversized block next to the cover compared to the design. */
.book-hero__body {
    max-width: 30rem;
}

.book-hero__meta {
    margin: var(--space-2) 0 0;
    color: var(--color-text-muted);
}

.book-hero__price {
    margin-top: var(--space-6);
}

.book-hero__rating {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-4);
    color: var(--color-heading);
}

.book-hero__rating i {
    color: var(--color-gold);
}

.book-hero__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    margin-top: var(--space-6);
}

/* -- Book Tabs (audiobooks/show.blade.php) -----------------------------------------

   Sits inside a reused `.teachers-section` (see above). Overview/Chapters/
   What You'll Take Away/Reviews - only one `.book-tabs__panel` visible at
   a time, toggled by the click handler at the bottom of the Blade
   template (same show/hide technique as `.course-filters__tab`). Full
   width with `justify-content: space-between` - the four tabs spread
   edge-to-edge rather than clustering with a fixed gap, matching the
   design. `overflow-x: auto` is a narrow-screen escape hatch, not the
   normal case: `flex-shrink: 0` tabs still won't wrap onto an awkward
   second line ("What You'll Take Away" is the long one) if they ever
   can't all fit spaced out.

   Tab buttons themselves reuse `.course-filters__tab`/`--active` verbatim
   (courses.blade.php) rather than a new size/color/weight scale invented
   for this page - this site already has an underline-tab component, and
   the first pass here didn't reuse it (too large a font, too much
   padding, and the wrong active-state color). `margin-bottom: -1px`
   overlaps each tab's own underline with this row's divider below it,
   which `.course-filters__tab` doesn't need on its own (its wrapper has
   no matching border-bottom). */
.book-tabs__nav {
    display: flex;
    justify-content: space-between;
    overflow-x: auto;
    border-bottom: 1px solid var(--color-border);
}

/* Padding left/right only here, scoped to this page's tab bar - the base
   `.course-filters__tab` (courses.blade.php) has none by design, so this
   doesn't change that page's own tabs. */
.book-tabs__nav .course-filters__tab {
    padding-left: var(--space-6);
    padding-right: var(--space-6);
    margin-bottom: -1px;
}

.book-tabs__panel {
    padding-top: var(--space-10);
}

/* Downsized from the plain `h2` default (44px) to `--text-h3-size` - same
   "smaller heading, correct nesting" pattern as `.teacher-profile__heading`
   above, so this reads as a heading within the tab content, not a full
   page section title the size of "Explore Our Audiobook Collection". */
.book-tabs__panel h2 {
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
}

.book-tabs__text {
    max-width: 50rem;
    color: var(--color-text);
    margin: var(--space-4) 0 0;
}

/* -- Book Chapters (Chapters tab) --------------------------------------------------

   Plain list, not an accordion like `.chapter-item` (courses/show.blade.php) -
   this design shows every session's description already expanded, nothing
   to click open. `__number` is an outline circle rather than
   `.how-it-works__step-number`'s filled badge, since there's no dashed
   connector thread running between rows here. */
.book-chapter-list {
    margin-top: var(--space-8);
}

.book-chapter {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
    padding: var(--space-6) 0;
    border-bottom: 1px solid var(--color-border);
}

.book-chapter:first-child {
    padding-top: 0;
}

.book-chapter:last-child {
    border-bottom: none;
}

/* `--text-h4-size` (not `--text-sm`) - at `--text-sm` this numeral read as
   a tiny, lost mark inside its own 40px circle. Matches `.book-chapter__title`'s
   own size below, so the number carries equal visual weight to the title
   beside it instead of reading as a minor label. */
.book-chapter__number {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--space-10);
    height: var(--space-10);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    color: var(--color-heading);
    font-size: var(--text-h4-size);
}

/* Downsized from the plain `h3` default (26px, the exact same size the
   panel's own `h2` above renders at after its own downsize) to
   `--text-h4-size` - same "smaller heading, correct nesting" pattern as
   `.teacher-card__name` elsewhere, so each session title reads as a list
   item under "Eight Gently Paced Sessions", not a second copy of it. */
.book-chapter__title {
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    margin: 0;
}

.book-chapter__description {
    color: var(--color-text-muted);
    margin: var(--space-1) 0 0;
}

/* -- Book Takeaway (What You'll Take Away tab) -------------------------------------

   Mobile-first single column, 1 → 2 → 3 progression - same technique as
   `.articles-grid` above. */
.book-takeaway__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10) var(--space-8);
    margin-top: var(--space-10);
}

@media (min-width: 768px) {
    .book-takeaway__grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 1024px) {
    .book-takeaway__grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

.book-takeaway__item {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
}

.book-takeaway__item i {
    flex-shrink: 0;
    margin-top: var(--space-1);
    color: var(--color-primary);
}

.book-takeaway__item p {
    margin: 0;
    color: var(--color-text);
}

/* -- Book Reviews (Reviews tab) -----------------------------------------------------*/

.book-reviews__summary {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    margin-top: var(--space-8);
}

/* `--text-h3-size` (not `--text-banner-size`, this page's own hero already
   uses that for the real headline) upgrading to `--text-h2-size` from
   768px - same progressive "big number" scale `.stats-bar__number`
   already uses on the homepage, reused here rather than reaching for the
   banner scale for a single stat. */
.book-reviews__score {
    font-family: var(--font-heading);
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
    color: var(--color-heading);
    margin: 0;
}

@media (min-width: 768px) {
    .book-reviews__score {
        font-size: var(--text-h2-size);
        line-height: var(--text-h2-leading);
    }
}

.book-reviews__stars i {
    color: var(--color-gold);
}

.book-reviews__count {
    color: var(--color-text-muted);
    margin: 0;
}

.book-reviews__list {
    margin-top: var(--space-6);
}

.book-reviews__item {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
    padding: var(--space-6) 0;
    border-bottom: 1px solid var(--color-border);
}

.book-reviews__item:first-child {
    padding-top: 0;
}

.book-reviews__item:last-child {
    border-bottom: none;
}

/* Same outline-circle treatment as `.book-chapter__number` above, not
   `.testimonial-card__avatar`'s filled one - this design shows an
   outlined initial, not a solid badge. */
.book-reviews__avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--space-10);
    height: var(--space-10);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    color: var(--color-heading);
    font-family: var(--font-heading);
}

.book-reviews__body {
    flex: 1;
    min-width: 0;
}

/* Same downsize as `.book-chapter__title` above and for the same reason -
   the plain `h3` default matched the tab panel's own heading size. */
.book-reviews__name {
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    margin: 0;
}

.book-reviews__meta {
    color: var(--color-text-muted);
    font-size: var(--text-sm);
    margin: var(--space-1) 0 0;
}

.book-reviews__quote {
    color: var(--color-text);
    margin: var(--space-2) 0 0;
}

.book-reviews__rating {
    flex-shrink: 0;
    color: var(--color-heading);
}

.book-reviews__rating i {
    color: var(--color-gold);
}

.book-reviews__pagination {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-3);
    margin-top: var(--space-6);
}

/* Same small circular icon-button shape as `.teacher-card__arrow`/
   `.contact-social__link`, filled solid instead of outline-at-rest - the
   design shows these as dark filled circles, not the outline-then-fill
   hover pattern those two use. */
.book-reviews__nav-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--space-10);
    height: var(--space-10);
    border: none;
    border-radius: var(--radius-pill);
    background-color: var(--color-primary);
    color: var(--color-primary-contrast);
    font-size: var(--text-sm);
    cursor: pointer;
    transition: background-color var(--transition-fast);
}

.book-reviews__nav-btn:hover,
.book-reviews__nav-btn:focus-visible {
    background-color: var(--color-primary-hover);
}

/* -- Our Values (about page) -----------------------------------------------------

   Plain container section (not full-bleed), same padding rhythm as
   `.practices-section`/`.courses-section`/`.teachers-section` above.
   Flexbox (not CSS Grid) is deliberate: there are 5 values, and grid has
   no way to center an incomplete last row on its own - flex-wrap with
   `justify-content: center` naturally centers row two's leftover 2 items
   under row one's 3, matching the design screenshot. No explicit
   breakpoints are needed for the column count either: each item's fixed
   `flex-basis` (below) just wraps to however many fit the container's
   width at any size - 1 per row on a narrow phone, 2 on tablet, 3 on
   desktop. */
.values-section {
    padding: var(--space-12) 0;
    text-align: center;
}

@media (min-width: 768px) {
    .values-section {
        padding: var(--space-16) 0;
    }
}

.values-section__header {
    max-width: 46rem;
    margin: 0 auto;
}

.values-grid {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-12) var(--space-8);
    margin-top: var(--space-10);
}

/* `flex-basis` in `rem` (not a percentage) is what makes the auto-reflow
   above work - `flex-shrink: 1` lets an item narrow to fit a phone
   screen, `flex-grow: 0` keeps it from stretching into leftover row
   space, so items stay a consistent, centered size at every width.
   One-off value, same precedent as `.values-section__header`'s own
   `max-width: 46rem` above. */
.value-item {
    flex: 0 1 20rem;
}

/* `height` only, `width: auto` - unlike the other icon sets on this page,
   these 5 source PNGs aren't all square (the mountain and sunrise icons
   are noticeably wider than tall), so forcing both dimensions to
   `--space-10` was stretching/squashing them. Fixing just the height
   keeps every icon's own aspect ratio while still lining them up at a
   consistent height across the row; `margin: 0 auto` still centers a
   block-level image with an auto width. */
.value-item__icon img {
    display: block;
    width: auto;
    height: var(--space-10);
    margin: 0 auto;
}

/* Font-size/line-height come from the global `h3` default - same size as
   `.our-story-section .why-feature__title`/`.explore-practices--detailed
   .practice-tile__title` elsewhere on this page, for a consistent scale
   across all three icon+title+description sections. */
.value-item__title {
    margin: var(--space-4) 0 0;
}

/* No font-size override - inherits the body default (`--text-body-size`,
   18px), same as `.why-feature__description`/`.teacher-card__specialties`/
   `.explore-practices--detailed .practice-tile__description` above. */
.value-item__description {
    color: var(--color-text-muted);
    margin: var(--space-2) 0 0;
}

/* -- Beyond The Mat (about page) --------------------------------------------------

   `.full-bleed` (added in the markup) so the photo can reach the actual
   viewport edge on the right, not just the site grid's right edge - the
   design has the photo touching the section's top, right, and bottom with
   no gutter at all, unlike every other photo on this page. Content first,
   photo second in the markup, so it lands on the right without needing an
   `order` override - same pairing as `.why-section__grid` above, just
   full-bleed instead of contained.

   The section itself carries no vertical padding, on purpose: the photo
   needs to touch its top/bottom edges directly, so normal section
   breathing room lives on `.beyond-mat-section__content` (below) instead
   - the photo still ends up the same height as the content column via the
   `height: 100%` grid-row-matching technique (see `.why-section__media`'s
   own comment for why that needs `position: absolute`, not just
   `display: block`), it just now includes that column's own padding in
   the height it matches.

   `background-color` is explicit `--color-surface` (white) - the design
   shows this section as a distinct white block, not blended into the
   page's own cream `body` background like every other section on this
   page (all of them are transparent, see e.g. `.why-section`/
   `.values-section` above), so it needs its own background rather than
   inheriting one. */
.beyond-mat-section {
    background-color: var(--color-surface);
}

.beyond-mat-section__grid {
    display: grid;
    grid-template-columns: 1fr;
}

@media (min-width: 1024px) {
    .beyond-mat-section__grid {
        grid-template-columns: 1fr 1fr;
    }
}

/* `padding-left` uses the same `max()` gutter formula as
   `.courses-swiper-outer .courses-swiper` above, to re-align this text
   column with the site grid's left edge even though the section itself
   now starts at the true viewport edge (`.full-bleed`) - not a nested
   `.container`, which would also constrain (and clip) the photo column
   next to it. `padding-right` only appears at the desktop breakpoint,
   once there's a photo column beside this one that needs the gap. */
.beyond-mat-section__content {
    padding: var(--space-12) var(--space-6);
    padding-left: max(var(--space-6), calc((100vw - var(--content-width)) / 2 + var(--space-6)));
}

@media (min-width: 768px) {
    .beyond-mat-section__content {
        padding-top: var(--space-16);
        padding-bottom: var(--space-16);
    }
}

@media (min-width: 1024px) {
    .beyond-mat-section__content {
        padding-right: var(--space-12);
    }
}

.beyond-mat-section__intro {
    color: var(--color-text-muted);
    margin: var(--space-4) 0 0;
}

.beyond-mat-list {
    margin-top: var(--space-8);
}

/* Border-bottom divider between rows, dropped on the last one; the first
   row's own top padding is dropped too so the list's top edge lines up
   with `.beyond-mat-section__intro` above it instead of leaving a double
   gap. */
.beyond-mat-feature {
    display: flex;
    align-items: flex-start;
    gap: var(--space-5);
    padding: var(--space-5) 0;
    border-bottom: 1px solid var(--color-border);
}

.beyond-mat-feature:first-child {
    padding-top: 0;
}

.beyond-mat-feature:last-child {
    padding-bottom: 0;
    border-bottom: none;
}

/* The source PNG/WebP already bakes in the circular badge (solid fill +
   white line icon), same "complete badge, no wrapper needed" convention
   as `.why-feature__icon` above. */
.beyond-mat-feature__icon img {
    display: block;
    width: var(--space-16);
    height: var(--space-16);
}

/* Downsized from the global `h3` default to `--text-h4-size` - same
   "smaller heading, correct nesting" pattern as `.why-feature__title`/
   `.value-item__title` above. */
.beyond-mat-feature__title {
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    margin: 0 0 var(--space-1);
}

.beyond-mat-feature__description {
    color: var(--color-text-muted);
    margin: 0;
}

/* One-off `aspect-ratio` matching the source photo's own crop
   (669 / 973), same precedent as `.why-section__media`'s `9 / 10`
   above. No `border-radius` here, deliberately - unlike every other photo
   on this page, this one is meant to sit flush against the section's
   edges (see the `.beyond-mat-section__grid` comment above), and a
   rounded corner would look wrong flush against a hard edge. */
.beyond-mat-section__media {
    display: block;
    position: relative;
    aspect-ratio: 669 / 973;
}

.beyond-mat-section__media img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

@media (min-width: 1024px) {
    .beyond-mat-section__media {
        aspect-ratio: auto;
        height: 100%;
    }
}

/* -- Our Community (about page) ----------------------------------------------

   Full-bleed purely to host the `lines-murial` background art edge-to-
   edge, exact same asset/technique as `.playlist-section` above (see that
   component's comment for the full `isolation: isolate` postmortem) -
   `position: relative` + `isolation: isolate` + `overflow: hidden` here
   for the identical reason: without `isolation: isolate`, this section's
   own `background-color` would paint at the normal stacking layer, above
   the mural's `z-index: -1` layer once that escapes to the document root,
   silently covering it. `background-color` is `--color-primary` (the
   design's dark-green background), not `.playlist-section`'s white, so
   text inside needs the `--inverse` tokens instead. */
.community-section {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: var(--space-12) 0;
    background-color: var(--color-primary);
}

@media (min-width: 768px) {
    .community-section {
        padding: var(--space-16) 0;
    }
}

/* Decorative, so it sits behind the copy and never intercepts clicks -
   same `z-index: -1` reasoning as `.playlist-section__mural` above,
   `inset: 0` + `object-fit: cover` the same way, and the same `opacity: 0.2`
   on the `img` below so the two murals read consistently across pages.
   Not hidden below 768px here, unlike that section - this one has no
   second `.container` column fighting it for width, so even a narrower
   crop still reads as texture rather than an empty stretch. */
.community-section__mural {
    display: block;
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
}

.community-section__mural img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0.2;
}

.community-section__header {
    max-width: 46rem;
    text-align: center;
    margin: 0 auto;
}

.community-section__header h2 {
    color: var(--color-text-inverse);
}

/* Mobile-first two-column wrap, same mechanics as `.stats-bar__row`/
   `.stats-bar__item` above - `flex: initial` content-width items spread
   across the full row with `justify-content: space-between` from tablet
   up, instead of two even columns. */
.community-stats {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--space-8) var(--space-4);
    margin-top: var(--space-10);
}

.community-stats__item {
    flex: 1 1 calc(50% - var(--space-4) / 2);
}

.community-stats__item:nth-child(2) {
    transition-delay: 80ms;
}

.community-stats__item:nth-child(3) {
    transition-delay: 160ms;
}

.community-stats__item:nth-child(4) {
    transition-delay: 240ms;
}

.community-stats__item:nth-child(5) {
    transition-delay: 320ms;
}

@media (min-width: 768px) {
    .community-stats__item {
        flex: initial;
    }
}

.community-stats__number {
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
    color: var(--color-text-inverse);
    margin: 0;
}

@media (min-width: 768px) {
    .community-stats__number {
        font-size: var(--text-h2-size);
        line-height: var(--text-h2-leading);
    }
}

.community-stats__label {
    font-size: var(--text-body-size);
    line-height: var(--text-body-leading);
    color: var(--color-text-muted-inverse);
    margin: var(--space-2) 0 0;
}

/* -- The Journey (about page) --------------------------------------------------

   Mobile-first single column that becomes a staggered two-column zigzag
   from 1024px - `align-self` on each `.journey-step` (left/right, set via
   an explicit `--left`/`--right` modifier class in the loop, not a
   structural `nth-child`/`nth-of-type` pseudo-class) is what creates the
   offset, inside one `flex-direction: column` list rather than a CSS
   Grid - simpler than explicit grid-row placement, and doesn't depend on
   all four cards being the same height. The modifier class is needed
   because `.journey-arrow` divs are interleaved as siblings between the
   steps; `nth-of-type` would count those too and throw off the odd/even
   alternation. */
.journey-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .journey-section {
        padding: var(--space-16) 0;
    }
}

.journey-section__header {
    max-width: 46rem;
    text-align: center;
    margin: 0 auto;
}

/* Mobile-first single column; from 1024px this becomes a 2-column,
   4-row grid with each step explicitly placed (`.journey-step--1..4`
   below) rather than relying on source order or `nth-child` - simpler to
   reason about once the connector arrows (also grid items, spanning
   both columns to center themselves) are added into the same mix.
   `row-gap` is intentionally small: the design has consecutive rows
   almost touching (row N+1's card starts right where row N's card ends),
   not spread across generous whitespace - a wide gap here was the main
   thing that made an earlier version of this section look nothing like
   the design. */
.journey-list {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-6);
    margin-top: var(--space-10);
}

/* Measured directly off the design screenshot (not eyeballed): each card
   fills ~44.5% of the row, with an ~11% gutter between them - `column-gap:
   11%` plus unconstrained `1fr` columns reproduces that split exactly,
   rather than a fixed-token gap next to a narrower fixed-width card (an
   earlier version of this section did that, and it looked nothing like
   the design - way more empty space in the middle, way narrower cards).
   `row-gap` is 0 for the same reason: consecutive rows in the screenshot
   are touching or very nearly so (row 3 starts exactly where row 2 ends;
   row 2 even starts slightly *before* row 1 ends), not separated by a
   token-sized gap. */
@media (min-width: 1024px) {
    .journey-list {
        grid-template-columns: 1fr 1fr;
        column-gap: 11%;
        row-gap: 0;
    }

    .journey-step--1 {
        grid-column: 1;
        grid-row: 1;
    }

    .journey-step--2 {
        grid-column: 2;
        grid-row: 2;
    }

    .journey-step--3 {
        grid-column: 1;
        grid-row: 3;
    }

    .journey-step--4 {
        grid-column: 2;
        grid-row: 4;
    }
}

.journey-step {
    background-color: var(--color-surface);
    border-radius: var(--radius-lg);
    padding: var(--space-6);
}

.journey-step__number {
    font-family: var(--font-heading);
    font-size: var(--text-h2-size);
    line-height: 1;
    color: var(--color-heading);
    margin: 0 0 var(--space-4);
}

.journey-step__title {
    margin: 0 0 var(--space-2);
}

.journey-step__description {
    color: var(--color-text-muted);
    margin: 0;
}

/* Decorative flow indicator between steps, hidden below 1024px - the
   single mobile column doesn't zigzag, so there's nothing to connect.
   `--right`/`--left` mirror which step the arrow is heading into next.
   Position/rotation are an eyeballed approximation of the design
   screenshot's hand-drawn curve, not pixel-measured - revisit once
   Figma-confirmed. */
.journey-arrow {
    display: none;
}

/* Source PNG/WebP (124 × 106) is the curved connector from the Figma
   file itself, not a generic icon-font glyph - `width` fixed, `height:
   auto` keeps its own aspect ratio rather than the square sizing every
   other icon on this page uses. */
.journey-arrow img {
    display: block;
    width: 4.5rem;
    height: auto;
}

@media (min-width: 1024px) {
    /* Each arrow is placed in the *later* of the two rows it connects
       (`--1` sits in row 2, between steps 1 and 2, and so on), spanning
       both columns so `justify-self: center` lands it in the gap between
       them regardless of exactly how wide each card renders. The
       negative `margin-top` is what pulls it up to straddle the row
       boundary instead of sitting flush against the top of its own row -
       anchored to that grid line rather than to any card's actual
       pixel height, so it stays correctly placed even if one step's
       text wraps to an extra line and makes its row taller than the
       others. */
    .journey-arrow {
        display: block;
        grid-column: 1 / 3;
        justify-self: center;
        align-self: start;
        margin-top: -6rem;
    }

    .journey-arrow--1 {
        grid-row: 2;
    }

    .journey-arrow--2 {
        grid-row: 3;
    }

    .journey-arrow--3 {
        grid-row: 4;
    }

    /* The source art already curves "right, then down", fitting the
       step 1 → 2 and 3 → 4 transitions as-is; `scaleX(-1)` mirrors it
       into "left, then down" for step 2 → 3 - a horizontal flip, not a
       rotation, is what's needed to reverse a curve's left/right
       handedness without also flipping which way it points vertically
       (a 180deg rotation would point it back up instead of down). */
    .journey-arrow--left {
        transform: scaleX(-1);
    }
}

/* -- How It Works (membership vs. course step-by-step card) --------------------

   Plain container section (not full-bleed), same padding rhythm as
   `.practices-section`/`.courses-section`/`.teachers-section` above.
   `.how-it-works__card` reuses `.card`'s exact background/border/radius
   values (see that component) rather than inventing a fresh white-card
   look, plus `--shadow-md` - the one existing shadow token, previously
   only used by the nav drawer, fits this card's own subtle lift exactly
   so no new shadow value was needed. */
.how-it-works {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .how-it-works {
        padding: var(--space-16) 0;
    }
}

.how-it-works__header {
    max-width: 46rem;
    text-align: center;
    margin: 0 auto;
}

.how-it-works__card {
    margin-top: var(--space-10);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    padding: var(--space-6);
}

@media (min-width: 768px) {
    .how-it-works__card {
        padding: var(--space-10);
    }
}

.how-it-works__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10);
}

@media (min-width: 1024px) {
    .how-it-works__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-12);
    }

    /* Divider between the two columns - same border-as-divider technique
       as `.playlist-section__explore`/`.offerings-column:nth-child(2)`
       above, `:nth-child(2)` rather than a dedicated class since there
       are always exactly two paths. */
    .how-it-works__column:nth-child(2) {
        border-left: 1px solid var(--color-border);
        padding-left: var(--space-12);
    }
}

.how-it-works__column h3 {
    margin-bottom: var(--space-1);
}

/* From 1024px, the two columns sit side by side and CSS Grid's default
   `align-items: stretch` already makes both `.how-it-works__column`s the
   same height (matching the taller one - Membership's 5 steps vs.
   Course's 4). Turning the column into a flex column lets
   `.how-it-works__cta` claim that leftover height with `margin-top: auto`,
   so both CTAs land on the same baseline regardless of step count - same
   "flex-fill to align a trailing element" technique as
   `.course-card__link`'s `margin-top: auto` and
   `.teacher-card__specialties`/`.teacher-card__body`'s `flex: 1`. Not
   applied below 1024px, where the columns stack and there's no shared
   row height to align against. */
@media (min-width: 1024px) {
    .how-it-works__column {
        display: flex;
        flex-direction: column;
    }

    .how-it-works__cta {
        margin-top: auto;
    }
}

.how-it-works__subtitle {
    color: var(--color-text-muted);
    font-size: var(--text-sm);
    margin: 0 0 var(--space-4);
}

.how-it-works__description {
    margin: 0 0 var(--space-8);
}

.how-it-works__steps {
    display: flex;
    flex-direction: column;
    margin-bottom: var(--space-10);
}

/* Each step's number badge is `--space-12` (48px) square, same size
   token `.why-feature__icon` renders its own circular badge at - the
   dashed connector below is positioned off that same token so the two
   stay in sync if the badge size ever changes. Badge background is
   `--color-tertiary`, the same beige as the page body itself (see base
   styles) - reused deliberately, not a new token, since this card's
   white background already gives it the contrast it needs. */
.how-it-works__step {
    position: relative;
    display: flex;
    gap: var(--space-4);
    padding-bottom: var(--space-6);
}

.how-it-works__step:last-child {
    padding-bottom: 0;
}

.how-it-works__step-number {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--space-12);
    height: var(--space-12);
    border-radius: var(--radius-pill);
    background-color: var(--color-tertiary);
    color: var(--color-heading);
    font-weight: var(--font-weight-semibold);
}

/* Dashed thread running down behind each column's badges, from the
   bottom of one badge to the top of the next - `top: var(--space-12)`
   starts it right at the current badge's own bottom edge, and
   `bottom: 0` lets it run to the end of this step's `padding-bottom`,
   which is exactly where the next step's badge begins. Omitted on the
   last step (no badge below it to connect to). */
.how-it-works__step:not(:last-child)::before {
    content: "";
    position: absolute;
    top: var(--space-12);
    bottom: 0;
    left: calc(var(--space-12) / 2);
    border-left: 1px dashed var(--color-border);
}

.how-it-works__step-text h4 {
    margin: 0 0 var(--space-1);
}

.how-it-works__step-text p {
    color: var(--color-text-muted);
    margin: 0;
}

.how-it-works__cta {
    text-align: center;
}

/* -- Audio Book (single product card) -------------------------------------------

   Plain container section (not full-bleed), same padding rhythm as
   `.how-it-works`/`.teachers-section` above. `.audio-book__card` reuses
   the exact same white-card treatment `.how-it-works__card` introduced
   (background/border/radius/shadow) - same visual language, not a fresh
   one - plus `isolation: isolate` and `overflow: hidden`, needed here
   specifically because this card (unlike `.about-section`, which has no
   background of its own) has its own opaque `background-color` behind a
   `z-index: -1` decorative mural - see CSS coding standard #9 and the
   `.playlist-section__mural` postmortem in design_system.md for exactly
   why that pairing is load-bearing, not defensive; `overflow: hidden`
   additionally clips the mural's bleed to the card's own rounded corners
   instead of letting it spill past them. */
.audio-book {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .audio-book {
        padding: var(--space-16) 0;
    }
}

.audio-book__header {
    max-width: 46rem;
    text-align: center;
    margin: 0 auto;
}

.audio-book__card {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    margin-top: var(--space-10);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    padding: var(--space-6);
}

@media (min-width: 768px) {
    .audio-book__card {
        padding: var(--space-10);
    }
}

/* Decorative flower line art bleeding from the card's bottom-right corner,
   same asset family/technique as `.about-section__mural` - `flower-murial-two`
   rather than `-one` (already used by the about section) so the two don't
   look like a copy-pasted repeat of each other. Size/position are approximate,
   not yet Figma-confirmed, same as every other mural placement on this page. */
/* Hidden site-wide by request - see `.about-section__mural`'s comment. */
.audio-book__mural {
    display: none;
    position: absolute;
    right: -40px;
    bottom: -20px;
    width: 220px;
    z-index: -1;
    pointer-events: none;
}

.audio-book__mural img {
    display: block;
    width: 100%;
    height: auto;
}

@media (min-width: 768px) {
    .audio-book__mural {
        right: -60px;
        bottom: -30px;
        width: 360px;
    }
}

/* Mobile-first: cover stacked above the copy, centered; from 1024px the
   cover becomes a fixed-width column beside the copy, top-aligned (not
   stretched to match height like `.why-section__media` - a book cover has
   its own fixed intrinsic aspect ratio that shouldn't be cropped to fit a
   row height). */
.audio-book__grid {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-8);
}

@media (min-width: 1024px) {
    .audio-book__grid {
        flex-direction: row;
        align-items: flex-start;
        gap: var(--space-10);
    }
}

/* The cover image is shown in full, never cropped - `height: auto`
   preserves its real aspect ratio, unlike the `aspect-ratio` +
   `object-fit: cover` treatment every photo card on this page uses,
   since those are free to crop but a book cover's whole design needs to
   stay intact. */
.audio-book__cover {
    display: block;
    width: 100%;
    max-width: 220px;
    margin: 0 auto;
    border-radius: var(--radius-md);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
}

.audio-book__cover img {
    display: block;
    width: 100%;
    height: auto;
}

@media (min-width: 1024px) {
    .audio-book__cover {
        flex: 0 0 220px;
        margin: 0;
    }
}

.audio-book__text {
    color: var(--color-text);
    margin: var(--space-4) 0 0;
}

.audio-book__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    margin-top: var(--space-6);
}

/* -- Is This For You (checklist + photo) ----------------------------------------

   Plain container section (not full-bleed), same padding rhythm as
   `.why-section`/`.audio-book` above. `.for-you-section__grid` reuses the
   exact same media/content two-column technique as `.why-section__grid` -
   see that component's own comments for the full "why `position: absolute`
   on the image is load-bearing, not a convenience" explanation - just
   mirrored: copy is first in the DOM/first column here, photo second,
   instead of `.why-section`'s photo-first order, so no `order` override is
   needed to get copy-left/photo-right on desktop. Mobile stacks copy above
   photo as a result (the reverse of `.why-section`'s stacking order) - a
   deliberate, reasonable default given this section leads with a checklist
   rather than a photo, not a mistake to reconcile with `.why-section`. */
.for-you-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .for-you-section {
        padding: var(--space-16) 0;
    }
}

.for-you-section__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10);
}

@media (min-width: 1024px) {
    .for-you-section__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-12);
        align-items: stretch;
    }
}

.for-you-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    margin-top: var(--space-8);
}

.for-you-item {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
}

/* Same "complete badge baked into the source PNG" convention as
   `.why-feature__icon` (circle/border/icon all baked in, nothing drawn in
   CSS) - `for-you-icon-{1..8}`, see public/images/icons. Sized at
   `--space-10` rather than that component's `--space-12` to match this
   list's own, slightly smaller proportions. */
.for-you-item__icon {
    display: block;
    flex-shrink: 0;
}

.for-you-item__icon img {
    display: block;
    width: var(--space-10);
    height: var(--space-10);
}

.for-you-item__text {
    margin: 0;
    padding-top: var(--space-1);
}

/* Same mobile-first `aspect-ratio` → desktop `height: 100%` swap as
   `.why-section__media`, including keeping the `img` `position: absolute`
   in both cases - see that component's comments for exactly why the image
   must be taken out of flow for the desktop height-match to work at all. */
.for-you-section__media {
    display: block;
    position: relative;
    aspect-ratio: 9 / 10;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.for-you-section__media img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

@media (min-width: 1024px) {
    .for-you-section__media {
        aspect-ratio: auto;
        height: 100%;
    }
}

/* -- Pull quote (murals in both corners) ----------------------------------------

   Full-bleed, plain white section - same `position: relative` +
   `isolation: isolate` + `overflow: hidden` + `background-color:
   var(--color-surface)` combination as `.playlist-section` above, needed
   here for the same reason: two `z-index: -1` decorative murals sitting
   behind this section's own opaque background (see CSS coding standard #9
   and the `.playlist-section__mural` postmortem in design_system.md).
   Extra-generous vertical padding (`--space-16` mobile, stacking on top of
   `--space-8` from 768px up - built from the token scale via `calc()`,
   not a new magic value) - a short pull-quote band reads cramped at the
   same padding rhythm as a content-heavy section, confirmed against the
   Figma reference. The quote is a real `<h2>` (this section's own document-
   outline heading, same "one `<h2>` per section" convention every other
   section on this page follows) at its default full size/weight/color -
   `54rem` on `.quote-section__inner` (wider than this page's usual `46rem`
   "readable header" width) is what makes this specific sentence, at that
   size, wrap across three lines rather than two or four - confirmed
   in-browser against the Figma reference, not derived analytically. */
.quote-section {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: var(--space-16) 0;
    background-color: var(--color-surface);
}

@media (min-width: 768px) {
    .quote-section {
        padding: calc(var(--space-16) + var(--space-8)) 0;
    }
}

.quote-section__inner {
    max-width: 54rem;
    text-align: center;
    margin: 0 auto;
}

/* courses/show.blade.php's own request: its "Moksha" quote needs the
   full `.container` width (`--content-width`) instead of the 54rem
   Figma-confirmed width every other `.quote-section` usage keeps above -
   that quote is already on the same element as `.container`, so this
   just removes the narrower override for it. */
.quote-section__inner--full {
    max-width: var(--content-width);
}

.quote-section__quote {
    margin: 0;
    font-family: var(--font-heading);
    font-weight: var(--font-weight-regular);
    color: var(--color-heading);
}

/* Unlike every other mural on this page, these two *do* need a CSS
   `opacity` on top of their baked-in alpha - measured directly (reading
   actual pixel alpha, not eyeballing a preview, same method already used
   once for `.playlist-section__mural`'s own alpha fix): `flower-murial-left`
   and `flower-murial-two` peak at alpha 68/255 and 86/255 respectively,
   vs. `flower-murial-one`'s 16/255 (the ~14% design_system.md documents
   for that one) - roughly 4-5x stronger, which read as visibly heavier/
   darker than the Figma reference once placed at this size. `opacity: 0.4`
   brings both down close to that same already-confirmed-subtle level
   without needing new exports. */
/* Hidden site-wide by request - see `.about-section__mural`'s comment.
   Covers both `--left` and `--right` since they only modify this base
   class's position, not its own `display`. */
.quote-section__mural {
    display: none;
    position: absolute;
    z-index: -1;
    width: 220px;
    opacity: 0.4;
    pointer-events: none;
}

.quote-section__mural img {
    display: block;
    width: 100%;
    height: auto;
}

.quote-section__mural--left {
    left: -60px;
    bottom: -20px;
}

.quote-section__mural--right {
    right: -60px;
    top: -20px;
}

@media (min-width: 768px) {
    .quote-section__mural {
        width: 360px;
    }
}

/* explore.blade.php's own request for a visible mural behind this section
   - same full-bleed-background asset AND opacity as `.playlist-section__mural`
   (see that class's own comment): both sit on the same `--color-surface`
   background, and `lines-murial`'s own PNG/WebP already bakes in a
   boosted alpha (~155/255, see that comment) - `opacity: 0.2` on top of
   that is what "reads as a visible texture, not overpowering" actually
   looks like for this asset, confirmed by that already-shipped section. */
.quote-section__lines {
    display: block;
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
}

.quote-section__lines img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0.2;
}

/* -- Membership pricing (three-card grid) ----------------------------------------

   Plain container section (not full-bleed), same padding rhythm as every
   other content section on this page. `.pricing-grid` skips a two-column
   tier the same way `.teachers-grid` skips a three-column one - three
   cards don't divide evenly into two, so a `768px` two-up tier would
   strand the third card alone on its own row; goes straight from one
   column to three at `1024px` instead. `.pricing-card` reuses `.card`'s
   background/border/radius exactly (see that component), no shadow -
   flatter than `.how-it-works__card`/`.audio-book__card`, matching this
   screenshot's plainer card treatment. `.pricing-card__features` reuses
   `.playlist-list` verbatim for the dotted bullet styling (see "Beginners
   Playlist" above) rather than duplicating it - only adds the
   `margin-bottom` this context needs before the CTA. */
.pricing-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .pricing-section {
        padding: var(--space-16) 0;
    }
}

.pricing-section__header {
    max-width: 46rem;
    text-align: center;
    margin: 0 auto;
}

.pricing-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-6);
    margin-top: var(--space-10);
}

@media (min-width: 1024px) {
    .pricing-grid {
        grid-template-columns: repeat(3, 1fr);
        gap: var(--space-8);
        align-items: stretch;
    }
}

/* Flex column so `.pricing-card__cta` can pin to the bottom via
   `margin-top: auto` - same "flex-fill to align a trailing element"
   technique as `.how-it-works__column`/`.how-it-works__cta` above, so all
   three CTAs land on the same baseline even though the Gift plan's price
   line ("Choose a Plan") and subtitle run a different length than the
   Monthly/Yearly dollar amounts. */
.pricing-card {
    position: relative;
    display: flex;
    flex-direction: column;
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--space-6);
}

/* The "Best Value" plan - thicker `--color-primary` border (not a separate
   accent color) plus a soft primary-tinted drop shadow. Raised ~20px above
   the other two cards on desktop only (see the 1024px block below) so it
   reads as the featured, "standing" option - stacked full-width on mobile,
   where that offset would just look like a stray gap. */
.pricing-card--highlight {
    border-width: 2px;
    border-color: var(--color-primary);
    box-shadow: 0 0 10px 0 color-mix(in srgb, var(--color-primary) 80%, transparent);
}

@media (min-width: 1024px) {
    .pricing-card--highlight {
        margin-top: -20px;
    }
}

.pricing-card__title {
    margin-bottom: var(--space-2);
}

/* Corner-ribbon shape via `clip-path` - a small triangular notch cut into
   the left edge, pointing into the badge, rather than a plain rectangle.
   Pixel-measured proportions from the design screenshot: the notch point
   sits at roughly 15% of the badge's own width. Pinned to the card's own
   top-right corner (`.pricing-card` is the `position: relative` anchor
   above) rather than sitting inline next to the title, so the title's own
   width stays unconstrained regardless of how long the badge text runs. */
.pricing-card__badge {
    position: absolute;
    top: var(--space-4);
    right: 0;
    display: inline-flex;
    align-items: center;
    padding: var(--space-1) var(--space-4) var(--space-1) var(--space-6);
    background-color: var(--color-primary);
    color: var(--color-primary-contrast);
    font-size: var(--text-sm);
    white-space: nowrap;
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 15% 50%);
}

.pricing-card__subtitle {
    color: var(--color-text-muted);
    font-size: var(--text-sm);
    margin: 0;
}

.pricing-card__price {
    margin: var(--space-6) 0 0;
    font-family: var(--font-heading);
    font-weight: var(--font-weight-regular);
    color: var(--color-heading);
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
}

@media (min-width: 768px) {
    .pricing-card__price {
        font-size: var(--text-h2-size);
        line-height: var(--text-h2-leading);
    }
}

.pricing-card__price-note {
    color: var(--color-text-muted);
    font-size: var(--text-sm);
    margin: var(--space-1) 0 0;
}

.pricing-card__features {
    margin-bottom: var(--space-8);
}

.pricing-card__cta {
    margin-top: auto;
}

/* Full-width, unlike every other `.btn` on this page (all content-width
   pills) - this screenshot's CTAs span the card's own content width. */
.pricing-card__cta .btn {
    display: block;
    width: 100%;
    text-align: center;
}

/* -- Membership page: Membership Plans (stacked split-cards) -------------------

   Distinct from `.pricing-card` above (that's the homepage/About's
   three-up grid) - this is the dedicated Membership page's own screenshot:
   full-width cards stacked one per row, each split into a left "plan
   info" column and a right "what's included" checklist column by a
   vertical `.plan-card__divider` line, stacked instead (info above
   details, divider hidden) below 768px where a mid-card vertical rule
   would just look like a stray line. `.plan-card__features` uses CSS
   multi-column (`column-count: 2`) rather than CSS grid specifically so
   the list fills top-to-bottom-then-next-column on its own regardless of
   item count (4 items for the Gift plan, 5 for Monthly/Yearly) - see the
   Blade comment above `$membershipFeatures` for why the array's order
   itself is what reproduces the design's exact column split, not the CSS. */
.membership-plans-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .membership-plans-section {
        padding: var(--space-16) 0;
    }
}

.membership-plans-section__heading {
    text-align: center;
    margin: 0 0 var(--space-10);
}

.plan-card-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
}

.plan-card {
    position: relative;
    background-color: var(--color-card-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--space-6);
    overflow: hidden;
}

@media (min-width: 768px) {
    .plan-card {
        padding: var(--space-10);
    }
}

/* Same "featured plan" treatment as `.pricing-card--highlight` - thicker
   `--color-primary` border instead of a separate accent color, applied
   whenever a plan carries a badge (only the Yearly plan today) rather than
   a one-off class only that card gets, so any future badge-carrying plan
   picks this up automatically. */
.plan-card--highlight {
    border-width: 2px;
    border-color: var(--color-primary);
}

/* Same corner-ribbon idea as `.pricing-card__badge`, but flush with the
   card's own top-right corner (`top: 0; right: 0`) rather than inset,
   since this screenshot's tag sits tucked directly into the corner - the
   flush corner-ribbon shape via the same `clip-path` notch as
   `.pricing-card__badge` (see that component's own comment for the exact
   polygon), not a rounded rectangle - pixel-checked against the design
   screenshot, which cuts an arrow-shaped notch into the badge's left edge.
   The badge's own top-right corner is left square in the polygon; `.plan-
   card`'s `overflow: hidden` (further down) clips it to the card's
   rounded corner for free, since the badge sits flush at `top: 0; right:
   0`. */
.plan-card__badge {
    position: absolute;
    top: 0;
    right: 0;
    display: inline-flex;
    align-items: center;
    padding: var(--space-2) var(--space-4) var(--space-2) var(--space-6);
    background-color: var(--color-primary);
    color: var(--color-primary-contrast);
    font-size: var(--text-sm);
    font-weight: var(--font-weight-medium);
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 15% 50%);
}

.plan-card__body {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
}

@media (min-width: 768px) {
    .plan-card__body {
        grid-template-columns: minmax(0, 3fr) 1px minmax(0, 4fr);
        gap: var(--space-10);
        align-items: center;
    }
}

.plan-card__divider {
    display: none;
}

@media (min-width: 768px) {
    .plan-card__divider {
        display: block;
        width: 1px;
        height: 100%;
        background-color: var(--color-border);
    }
}

/* Dark green (`--color-primary`), not the page's usual `--color-heading`
   charcoal - pixel-checked against the design screenshot, which sets this
   title apart from a plain section heading like "Membership Plans"
   above. */
.plan-card__title {
    margin: 0 0 var(--space-2);
    color: var(--color-primary);
}

.plan-card__subtitle {
    margin: var(--space-2) 0 0;
    color: var(--color-text-muted);
    font-size: var(--text-sm);
}

/* h2 size, not h3 - the price is this card's own focal point when
   scanning down the page (the number a reader is actually comparing
   plan-to-plan on), so it needs to out-rank the plan title, not match
   it. */
.plan-card__price {
    margin: var(--space-8) 0 0;
    font-family: var(--font-heading);
    font-weight: var(--font-weight-regular);
    color: var(--color-heading);
    font-size: var(--text-h2-size);
    line-height: var(--text-h2-leading);
}

.plan-card__price-note {
    font-family: var(--font-body);
    color: var(--color-text-muted);
    font-size: var(--text-sm);
}

.plan-card__description {
    margin: var(--space-4) 0 0;
    color: var(--color-text);
}

.plan-card__cta {
    margin-top: var(--space-8);
}

/* `--text-caption-size` (16px), not `.btn`'s own `--text-sm` (14px)
   default - same explicit bump as `.split-form-page__actions .btn` on the
   Support page. */
.plan-card__cta .btn {
    font-size: var(--text-caption-size);
}

/* Dark green (`--color-primary`), not `--color-text-muted` - pixel-checked
   against the design screenshot, which matches this label to the plan
   title's own color rather than treating it as de-emphasized helper
   text. */
.plan-card__details-label {
    margin: 0 0 var(--space-5);
    padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--color-border);
    color: var(--color-primary);
    font-size: var(--text-caption-size);
}

/* Single column on mobile - the two-column split (CSS grid with an
   explicit row count, `--feature-rows`, set inline per list in Blade from
   `ceil(count($features) / 2)`) is a 768px-up enhancement only. A fixed
   row count is used rather than `column-count: 2` since a height-balancing
   multi-column would reshuffle which item lands in which column the
   moment any item's text wraps to a second line (as "Unlimited access to
   1000+ yoga classes" does here), breaking the design's exact column
   split - `grid-auto-flow: column` with a fixed row count keeps that
   split stable regardless of wrapping. */
.plan-card__features {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

@media (min-width: 768px) {
    .plan-card__features {
        display: grid;
        grid-auto-flow: column;
        grid-template-rows: repeat(var(--feature-rows, 3), auto);
        column-gap: var(--space-8);
        row-gap: var(--space-5);
    }
}

/* `--color-heading`, not the inherited `--color-text` body gray - pixel-
   checked against the design screenshot, which renders this list in the
   same dark, near-black tone as the price line rather than as muted body
   copy. */
.plan-card__feature {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    color: var(--color-heading);
}

.plan-card__feature-icon {
    flex-shrink: 0;
    margin-top: 0.2em;
    color: var(--color-primary);
}

/* -- The Sangha Speaks (auto-scrolling testimonial marquee) --------------------

   Full-bleed (`.testimonials-section`), same "eyebrow + h2, centered,
   46rem" header pattern as every other section, wrapped in its own nested
   `.container` since the marquee rows below deliberately bleed edge to
   edge instead. Each row is a fixed-content `.testimonial-marquee__track`
   holding two back-to-back copies of the same four testimonials (see the
   Blade comment above `$testimonialRows`) - `translateX(-50%)` shifts by
   exactly one copy's width, so the instant it "wraps," the second copy is
   sitting in exactly the first copy's original position and the loop
   reads as continuous, not restarting. One keyframe drives both rows;
   `.testimonial-marquee--ltr` plays it in reverse rather than needing a
   second, near-duplicate keyframe just to run the other direction - see
   CSS coding standard #3 (no duplicated declarations). `width: max-content`
   is load-bearing on the track: without it the flex row would shrink to
   its parent's width and wrap/compress the cards instead of laying them
   all out in one unbroken line for the animation to slide. */
.testimonials-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .testimonials-section {
        padding: var(--space-16) 0;
    }
}

.testimonials-section__header {
    max-width: 46rem;
    text-align: center;
    margin: 0 auto;
}

.testimonial-marquee {
    overflow: hidden;
    margin-top: var(--space-10);
}

.testimonial-marquee + .testimonial-marquee {
    margin-top: var(--space-6);
}

.testimonial-marquee__track {
    display: flex;
    width: max-content;
    gap: var(--space-6);
    animation: testimonial-marquee-scroll 100s linear infinite;
}

.testimonial-marquee--ltr .testimonial-marquee__track {
    animation-direction: reverse;
}

@keyframes testimonial-marquee-scroll {
    from {
        transform: translateX(0);
    }

    to {
        transform: translateX(-50%);
    }
}

/* Same reduced-motion guarantee as the hero swiper's autoplay and the
   `.reveal` scroll-entrance above - an endlessly auto-scrolling row is
   exactly the kind of motion `prefers-reduced-motion` exists to suppress. */
@media (prefers-reduced-motion: reduce) {
    .testimonial-marquee__track {
        animation: none;
    }
}

/* `27rem` is a one-off card width (not a spacing token) - same precedent
   as `.schedule-row__media`'s `5.5rem`, this component's own shape rather
   than a value from the spacing scale. Widened twice now (from an initial
   `20rem`, then `23rem`): the quote text was still wrapping to enough
   lines to run the card noticeably taller than it was wide. */
.testimonial-card {
    flex: 0 0 auto;
    width: 27rem;
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--space-6);
}

.testimonial-card__header {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.testimonial-card__avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--space-10);
    height: var(--space-10);
    border-radius: var(--radius-pill);
    background-color: var(--color-tertiary);
    color: var(--color-heading);
    font-family: var(--font-heading);
    font-weight: var(--font-weight-semibold);
}

/* Real `<h3>` scaled to `--text-h4-size` - same "smaller heading, correct
   nesting" pattern as `.teacher-card__name`/`.course-card__title`, this
   section's own `<h2>` is the parent heading each card name nests under. */
.testimonial-card__name {
    margin: 0;
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
}

.testimonial-card__meta {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--text-sm);
}

.testimonial-card__stars {
    display: flex;
    gap: var(--space-1);
    margin-top: var(--space-4);
    color: var(--color-gold);
    font-size: var(--text-sm);
}

.testimonial-card__quote {
    margin: var(--space-4) 0 0;
    color: var(--color-text);
}

/* -- Insights & Articles (blog card grid) ---------------------------------------

   Plain container section (not full-bleed), same padding rhythm as every
   other content section. `.articles-grid` skips a two-column tier the
   same way `.pricing-grid`/`.teachers-grid` do - three cards don't divide
   evenly into two. `.article-card__media` reuses `.schedule-row__media`'s
   `10 / 9` aspect ratio (this card's own photo crop, measured against the
   screenshot - closer to square than `.course-card__media`'s `4 / 3`).
   `.article-card__divider` (the `<hr>` between excerpt and byline) gets
   `margin-top: auto` on a flex-column card - same "flex-fill to align a
   trailing element" technique as `.how-it-works__cta`/`.pricing-card__cta`
   above, so all three cards' byline rows land on the same baseline
   regardless of how many lines each title/excerpt wraps to. */
.articles-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .articles-section {
        padding: var(--space-16) 0;
    }
}

.articles-section__header {
    max-width: 46rem;
    text-align: center;
    margin: 0 auto;
}

.articles-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
    margin-top: var(--space-10);
}

@media (min-width: 1024px) {
    .articles-grid {
        grid-template-columns: repeat(3, 1fr);
        align-items: stretch;
    }
}

.article-card {
    display: flex;
    flex-direction: column;
}

/* Without this, the `[hidden]` attribute blog.blade.php's filter script
   toggles has no effect - same equal-specificity tie as `.course-row`
   vs. the UA `[hidden] { display: none }` rule, see that rule's comment. */
.article-card[hidden] {
    display: none;
}

/* Denser variant for blog.blade.php's 12-card grid (vs. the homepage/about
   page's 3-card showcase this component was designed for) - same
   "smaller heading, correct nesting" treatment `.practice-tile__title`
   uses, extended to the excerpt/byline so the whole card reads
   proportionately at this scale. An explicit, requested exception to this
   site's usual "don't touch font-size, fix hierarchy with spacing/color
   instead" practice - scoped to `.article-card--compact` so the shared
   3-card sections elsewhere are untouched. */
.article-card--compact .article-card__title {
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
}

/* 16px, not `--text-sm`'s 14px - uses `--text-caption-size`/`-leading` for
   the size/leading only (not `.caption`'s Lyon Display `font-family`,
   which would change the typeface, not just the size). */
.article-card--compact .article-card__excerpt {
    font-size: var(--text-caption-size);
    line-height: var(--text-caption-leading);
}

.article-card--compact .article-card__author-name {
    font-size: var(--text-sm);
}

.article-card__media {
    display: block;
    aspect-ratio: 10 / 9;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.article-card__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--transition-slow) var(--ease-smooth);
}

.article-card__media:hover img {
    transform: scale(1.05);
}

.article-card__meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    margin-top: var(--space-4);
}

/* Same uppercase/letter-spacing/muted treatment as `.course-card__meta`/
   `.practice-tile__label`. */
.article-card__category {
    margin: 0;
    font-size: var(--text-sm);
    line-height: 1.4;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

/* Small outline circular icon button - same shape/hover-fill technique as
   `.teacher-card__arrow`, just a standalone control here rather than
   nested inside the card's own link (this card, unlike `.course-card`, is
   never one single big `<a>` - see the Blade comment on `.article-card`
   for why: a `<button>` nested inside an `<a>` is invalid HTML). */
.article-card__share {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--space-8);
    height: var(--space-8);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    background-color: transparent;
    color: var(--color-heading);
    cursor: pointer;
    transition: background-color var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}

.article-card__share:hover,
.article-card__share:focus-visible {
    background-color: var(--color-primary);
    border-color: var(--color-primary);
    color: var(--color-primary-contrast);
}

.article-card__share i {
    font-size: var(--text-sm);
}

.article-card__title {
    margin: var(--space-2) 0 0;
}

/* Explicit color needed - the global `a { color: var(--color-primary) }`
   rule (see "Base / reset") wins otherwise, same reasoning as
   `.course-card__meta`'s own comment. */
.article-card__title a {
    color: var(--color-heading);
}

.article-card__title a:hover,
.article-card__title a:focus-visible {
    color: var(--color-primary);
}

/* Clamped to 2 lines regardless of how long the excerpt runs, so all
   three cards' divider/byline rows start from a consistent point instead
   of drifting further apart the longer an excerpt happens to be -
   `-webkit-line-clamp` inserts its own ellipsis automatically once
   `overflow: hidden` + `display: -webkit-box` are both set, no
   `text-overflow` needed (that's only for single-line truncation). */
.article-card__excerpt {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    margin: var(--space-2) 0 var(--space-4);
    color: var(--color-text);
}

.article-card__divider {
    margin: auto 0 var(--space-4);
    border: none;
    border-top: 1px solid var(--color-border);
}

.article-card__author {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.article-card__avatar {
    display: block;
    flex-shrink: 0;
    width: var(--space-8);
    height: var(--space-8);
    border-radius: var(--radius-pill);
    overflow: hidden;
}

.article-card__avatar img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.article-card__author-name {
    font-weight: var(--font-weight-medium);
    color: var(--color-heading);
}

.article-card__date {
    margin-left: auto;
    color: var(--color-text-muted);
    font-size: var(--text-sm);
}

.articles-section__actions {
    margin-top: var(--space-10);
    text-align: center;
}

/* -- FAQ accordion ("What You May Wish To Know") --------------------------------

   Plain container section (not full-bleed), same padding rhythm as every
   other content section. Two-column grid, same mobile-first
   `1fr` -> `1fr 1fr` (1024px) pattern as `.playlist-section__grid`, with the
   same border-left-as-divider technique as `.playlist-section__explore` on
   the second column - no separate absolutely-positioned line needed.

   `<details>`/`<summary>` is used instead of a JS-driven accordion - it's
   free keyboard/screen-reader support and works with no JS at all (see
   CLAUDE.md "Frontend asset strategy" - keep JS minimal). The shared
   `name="faq"` attribute (set in Blade) makes the group behave as a true
   accordion - opening one closes the others - via native HTML, not a
   listener; browsers that don't yet support grouped `<details>` just fall
   back to allowing more than one open at once, which still works fine, just
   without the mutual-exclusivity. Only the chevron rotates open vs. closed
   (`transform`, not a separate icon swap) - one less thing to keep in sync. */
.faq-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .faq-section {
        padding: var(--space-16) 0;
    }
}

.faq-section__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10);
}

@media (min-width: 1024px) {
    .faq-section__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-12);
    }
}

.faq-section__text {
    margin: var(--space-4) 0 0;
    color: var(--color-text);
}

@media (min-width: 1024px) {
    .faq-list {
        padding-left: var(--space-12);
    }
}

.faq-item {
    border-bottom: 1px solid var(--color-border);
    padding: var(--space-5) 0;
}

/* `list-style: none` removes Firefox's native disclosure marker;
   `::-webkit-details-marker` is the separate, older mechanism Chrome/Safari
   still use for the same glyph - both are needed, neither alone covers all
   browsers. */
.faq-item__summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    list-style: none;
    cursor: pointer;
}

.faq-item__summary::-webkit-details-marker {
    display: none;
}

.faq-item__question {
    margin: 0;
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    color: var(--color-heading);
}

.faq-item__icon {
    flex-shrink: 0;
    color: var(--color-heading);
    font-size: var(--text-sm);
    transition: transform var(--transition-fast);
}

.faq-item[open] .faq-item__icon {
    transform: rotate(180deg);
}

.faq-item__answer {
    margin: var(--space-3) 0 0;
    color: var(--color-text);
}

.faq-section__actions {
    margin-top: var(--space-8);
}

/* -- Contact ("We're here to help you begin") ------------------------------------

   Figma-confirmed: the band behind both columns is `--color-tertiary`
   (#f2ede7, the same cream as the page body everywhere else - see
   `.schedule-row`/`.teacher-card` for that usual relationship), and
   `.contact-card` sits one step lighter on top of it at `--color-card-surface`
   (#f5f2ee, same card-on-cream-page token `.own-course-card` uses).

   Same mobile-first `1fr` -> `1fr 1fr` (1024px) two-column grid as
   `.faq-section__grid`/`.playlist-section__grid`. No border-left divider on
   the second column this time, unlike those two - `.contact-card` is
   already its own distinct-colored block, so a second divider right next to
   it would be redundant.

   `.contact-form` isn't wired to a backend endpoint yet - no route/controller
   exists to receive it (this phase is Blade view redesign only, see
   CLAUDE.md). It's a real `<form>` so the field markup/labels/validation
   attributes are in place ahead of that work, but submitting it today just
   does a plain GET back to this same page - the same "not yet functional"
   state as every other CTA here (`href="#"`), just via a form element
   instead of a link. */
.contact-section {
    padding: var(--space-12) 0;
    background-color: var(--color-tertiary);
}

@media (min-width: 768px) {
    .contact-section {
        padding: var(--space-16) 0;
    }
}

.contact-section__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10);
}

@media (min-width: 1024px) {
    .contact-section__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-12);
    }
}

.contact-section__text {
    margin: var(--space-4) 0 0;
    color: var(--color-text);
}

/* `border-top` on the list plus a `border-bottom` on each row reproduces a
   line above the first method and below every one after it (including the
   last, ahead of the social row) with no extra markup - same border-as-
   divider technique as `.faq-item`. */
.contact-methods {
    margin-top: var(--space-6);
    border-top: 1px solid var(--color-border);
}

.contact-method {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-4) 0;
    border-bottom: 1px solid var(--color-border);
}

/* Visit is the last row - no divider needed between it and the social
   icons below. */
.contact-method:last-child {
    border-bottom: none;
}

.contact-method__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--space-10);
    height: var(--space-10);
    flex-shrink: 0;
    border: 1px solid var(--color-primary);
    border-radius: var(--radius-pill);
    color: var(--color-primary);
}

.contact-method__label {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--text-eyebrow-size); /* 16px - the closest existing token, no font/letter-spacing baggage attached to reuse it here */
}

.contact-method__value {
    margin: 0;
    color: var(--color-heading);
}

/* Same circular, outline-fills-on-hover shape as `.teacher-card__arrow`/
   `.article-card__share` - a third independent instance, kept local to this
   component rather than factored out, same precedent those two already set. */
.contact-social {
    display: flex;
    gap: var(--space-3);
    margin-top: var(--space-6);
}

.contact-social__link {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--space-8);
    height: var(--space-8);
    flex-shrink: 0;
    border: 1px solid var(--color-primary);
    border-radius: var(--radius-pill);
    color: var(--color-primary);
    transition: background-color var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}

.contact-social__link:hover,
.contact-social__link:focus-visible {
    background-color: var(--color-primary);
    border-color: var(--color-primary);
    color: var(--color-primary-contrast);
}

/* Lighter card on the section's own cream background - see the comment on
   `.contact-section` above for the token reasoning. No border: the
   screenshot's card-to-background edge is a clean 1px color transition
   with no separate stroke, so a `--color-border` outline here would be an
   invented detail, not a reproduced one. */
.contact-card {
    background-color: var(--color-card-surface);
    border-radius: var(--radius-lg);
    padding: var(--space-6);
}

@media (min-width: 768px) {
    .contact-card {
        padding: var(--space-10);
    }
}

/* Bumped up to the h2 scale - the default h3 size read smaller than the
   Figma design for this card title. */
.contact-card__title {
    margin: 0 0 var(--space-6);
    font-size: var(--text-h2-size);
    line-height: var(--text-h2-leading);
}

/* `position: relative` only matters for a field holding a `<select>`
   (positions `.contact-form__field-icon`) - harmless on a plain
   input/textarea field. */
.contact-form__field {
    position: relative;
    margin-bottom: var(--space-5);
}

/* A fixed 2px `border-bottom` in both states, only the color changes on
   focus - swapping the border *width* instead would shift the field's
   height by a pixel every time focus moves, the classic underline-input
   jank. Real `<label>`s exist per field (`.sr-only`) - the visible label
   here is each field's `placeholder` text, matching the design. `select`
   added alongside input/textarea for support.blade.php's "Select Reason"
   field - same underline treatment, not a separate field style. */
.contact-form__field input,
.contact-form__field select,
.contact-form__field textarea {
    display: block;
    width: 100%;
    border: none;
    border-bottom: 2px solid var(--color-border);
    background: none;
    padding: var(--space-2) 0;
    font: inherit;
    color: var(--color-heading);
}

.contact-form__field textarea {
    resize: vertical;
    min-height: var(--space-16);
}

/* Hides the native dropdown arrow in favor of `.contact-form__field-icon`
   below - same "appearance: none + Font Awesome chevron" swap as
   `.blog-sort__select`. */
.contact-form__field select {
    appearance: none;
    padding-right: var(--space-6);
    cursor: pointer;
}

.contact-form__field-icon {
    position: absolute;
    right: 0;
    bottom: var(--space-3);
    color: var(--color-text-muted);
    font-size: var(--text-sm);
    pointer-events: none;
}

.contact-form__field input::placeholder,
.contact-form__field textarea::placeholder {
    color: var(--color-text);
}

.contact-form__field input:focus-visible,
.contact-form__field select:focus-visible,
.contact-form__field textarea:focus-visible {
    outline: none;
    border-bottom-color: var(--color-primary);
}

.contact-form__actions {
    margin-top: var(--space-2);
    text-align: center;
}

/* -- The App ---------------------------------------------------------------------

   Plain container section (not full-bleed) - same tertiary background as
   the page itself, confirmed by pixel-sampling the design screenshot, so
   no need to escape `.container` the way `.cta-banner`/`.offerings-section`
   do. Same mobile-first `1fr` -> `1fr 1fr` (1024px) split as
   `.faq-section__grid`/`.contact-section__grid`, vertically centered since
   the two columns' content isn't the same height. Image on the right
   (unlike `.why-section__grid`'s photo-on-the-left), reusing the
   `.playlist-list` dot-bullet component for the three feature lines rather
   than a new list style - here each bullet is one flowing sentence with an
   inline bold lead-in (`<strong>`), not the stacked title/description
   block `.playlist-list__title`/`__description` add for the newsletter
   section's explore list, so those two classes aren't used here. */
.app-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .app-section {
        padding: var(--space-16) 0;
    }
}

.app-section__grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-8);
    align-items: center;
}

@media (min-width: 1024px) {
    .app-section__grid {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: var(--space-12);
    }
}

.app-section__content {
    width: 90%;
}

.app-section__text {
    margin: var(--space-4) 0 0;
    color: var(--color-text);
}

.app-section__grid .playlist-list {
    margin-top: var(--space-6);
}

.app-section__badges {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-6);
}

/* Placeholder, not a real scannable code - see the Blade comment on
   `.app-qr` for why. Same bordered-box treatment as a plain `.card`, just
   sized down to sit level with the two badge images beside it. */
.app-qr {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    color: var(--color-heading);
    font-size: var(--text-sm);
}

.app-qr i {
    font-size: var(--text-h4-size);
}

/* A transparent-background device illustration, not a photo - no
   `object-fit: cover`/height-matching treatment like `.why-section__media`
   needs, it just scales naturally within its column. */
.app-section__media img {
    display: block;
    width: 100%;
    height: auto;
}

/* -- CTA banner ("The path is you, the time is now") -----------------------------

   Full-bleed dark photo band, same `position: relative` + `isolation:
   isolate` + `z-index: -1` media layer + `color-mix()` legibility wash
   technique as `.offerings-section` above - reuses it exactly rather than
   inventing a second one. Centered copy instead of a two-column grid,
   closer to `.about-section__inner`'s centered-content pattern than
   `.offerings-section__grid`. */
.cta-banner {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: var(--space-12) 0;
    text-align: center;
}

@media (min-width: 768px) {
    .cta-banner {
        padding: var(--space-16) 0;
    }
}

.cta-banner__media {
    position: absolute;
    inset: 0;
    z-index: -1;
}

.cta-banner__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* A much lighter wash than `.offerings-section__media::after`'s 88% - this
   photo (a dusk shot) is already very dark on its own (measured mean
   brightness ~21/255), so the same heavy wash used there would crush it to
   near-solid black and lose the temple/riverside detail entirely. */
.cta-banner__media::after {
    content: "";
    position: absolute;
    inset: 0;
    background-color: color-mix(in srgb, var(--color-surface-inverse) 35%, transparent);
}

.cta-banner__inner {
    max-width: 46rem;
    margin: 0 auto;
}

/* Italic is a one-off treatment for this single headline, not a new type
   scale rung - still the same Lyon Text family/size/weight as every other
   `<h2>`, just with `font-style: italic` and the inverse text color needed
   to read over the dark wash. */
.cta-banner__heading {
    font-style: italic;
    color: var(--color-text-inverse);
}

.cta-banner__text {
    margin: var(--space-4) 0 0;
    color: var(--color-text-muted-inverse);
}

.cta-banner__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-4);
    margin-top: var(--space-8);
}

/* A transparent "ghost" button for a dark/photo section - `.btn--ghost`
   isn't this: its base `.btn` still carries a solid white
   `--color-secondary` fill (see `.btn`), fine on a light section but not
   the see-through-to-the-photo outline this design shows. Border is
   `--color-primary` (not `--color-text-inverse`) to match the Figma
   "Read Our Story" button spec exactly - a white border on a dark photo
   read with much higher contrast/visual weight than the filled primary
   button next to it, which is what made it look larger despite both
   buttons being pixel-identical in size. */
.btn--inverse {
    background-color: transparent;
    border-color: var(--color-primary);
    color: var(--color-text-inverse);
}

.btn--inverse:hover,
.btn--inverse:focus-visible {
    background-color: var(--color-text-inverse);
    border-color: var(--color-text-inverse);
    color: var(--color-primary);
}

/* -- Newsletter ("Stay Connected") ------------------------------------------------

   Plain container section (not full-bleed) - the design screenshot's
   background is the same tertiary tone as the page itself, not a distinct
   band, so unlike `.cta-banner`/`.offerings-section` there's no need to
   escape `.container`. Same mobile-first `1fr` -> `1fr 1fr` (1024px) split
   as `.faq-section__grid`/`.contact-section__grid`, vertically centered
   since the two columns' content isn't the same height. */
.newsletter-section {
    padding: var(--space-12) 0 0 0;
}

@media (min-width: 768px) {
    .newsletter-section {
        padding: var(--space-16) 0 0 0;
    }
}

/* `minmax(0, 1fr)`, not a bare `1fr` - a bare `1fr` track has an implicit
   `minmax(auto, 1fr)`, so it won't shrink below its content's min-content
   width. `.newsletter-form`'s flex row (a borderless input plus an embedded
   `.btn`) has a min-content width wider than a narrow phone column, so
   without this the grid track refused to shrink and the pill overflowed
   past the viewport edge instead of the input shrinking as intended
   (confirmed via computed layout, not just visually - `.newsletter-form`
   was rendering ~20px wider than its own grid track). */
.newsletter-section__grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-8);
    align-items: center;
}

@media (min-width: 1024px) {
    .newsletter-section__grid {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: var(--space-12);
    }
}

.newsletter-section__text {
    margin: var(--space-3) 0 0;
    color: var(--color-text);
}

/* One pill-shaped container holding a borderless input and a `.btn` -
   pixel-sampled from the design screenshot: the input has no fill/border of
   its own (just page background showing through), only the outer pill has
   a `--color-border` stroke, and the button sits flush inside it. Reuses
   `.btn.btn--primary` as-is rather than a new button variant. */
.newsletter-form {
    display: flex;
    align-items: center;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    padding: var(--space-1);
    transition: border-color var(--transition-fast);
}

.newsletter-form:focus-within {
    border-color: var(--color-primary);
}

.newsletter-form input {
    flex: 1;
    min-width: 0;
    border: none;
    background: none;
    padding: var(--space-2) var(--space-5);
    font: inherit;
    color: var(--color-heading);
}

.newsletter-form input::placeholder {
    color: var(--color-text);
}

/* The container's own `:focus-within` border already signals focus (same
   swap-not-remove reasoning as `.contact-form__field`), so the input's
   default ring would be a redundant second indicator here. */
.newsletter-form input:focus-visible {
    outline: none;
}

.newsletter-form .btn {
    flex-shrink: 0;
}

.newsletter-form__hint {
    margin: var(--space-3) 0 0;
    color: var(--color-text-muted);
    font-size: var(--text-sm);
}

/* -- Scroll reveal (fade + rise) ----------------------------------------------
   Progressive enhancement: `.reveal` elements are visible by default
   (plain `opacity: 1`, no rule needed) so content never depends on JS to
   appear. `js-enabled` is added unconditionally by app.js on every page
   load (see that file) - only once it's present does the hidden starting
   state apply, and app.js's IntersectionObserver adds `.is-visible` the
   first time each element scrolls into view (one-shot, not re-triggered
   scrolling back up). The `prefers-reduced-motion` block below is a second,
   independent guarantee - app.js also skips the observer and reveals
   everything immediately under that preference, but this rule doesn't
   depend on that JS logic being correct. transform/opacity only, per CSS
   coding standard #6; `--space-4` (16px) reuses the same rise distance as
   the nav drawer's `translateX(16px)` link entrance. `--transition-slow`
   (400ms) rather than `--transition-base` - a page-scroll entrance reads
   better slower/more settled than a hover/click micro-interaction does.

   `:where()` strips this rule's own specificity down to just `.js-enabled`
   (1 class) - without it, `.js-enabled .reveal.is-visible` (3 classes)
   would permanently beat a component's own single-class `:hover` rule
   (e.g. `.practice-tile:hover`, 2 classes) once revealed, since both set
   `transform` and specificity - not source order - decides. */
.js-enabled :where(.reveal) {
    opacity: 0;
    transform: translateY(var(--space-4));
    transition: opacity var(--transition-slow) var(--ease-smooth), transform var(--transition-slow) var(--ease-smooth);
}

.js-enabled :where(.reveal.is-visible) {
    opacity: 1;
    transform: none;
}

@media (prefers-reduced-motion: reduce) {
    .reveal {
        opacity: 1;
        transform: none;
        transition: none;
    }
}

/* -- Course listing (courses.blade.php - filter tabs + course rows) ----------

   Matches the Figma "Course Listing Page" frame (node 176:3650) - a filter
   bar (categories left, a "Most Popular" sort label right) above a stack
   of full-width course rows (photo + copy + price/actions), each separated
   by a hairline divider. Distinct from welcome.blade.php's `.course-card`
   (an image-on-top swiper slide with a single "Read More" link) - this is
   a wider catalog row with its own price/Buy Now/Gift a Friend actions, so
   it earns its own component rather than reusing that one.

   Mobile-first per CSS coding standard #8: base rules below are the
   small-screen layout (stacked row, horizontally-scrolling tabs);
   `min-width: 768px` layers on the desktop two-column row the design
   shows. Typography follows this site's own type scale/hierarchy rather
   than Figma's literal pixel values - see the `.course-row__title`
   comment below for why. */

/* `var(--space-12) 0` -> `var(--space-16) 0` at 768px, same mobile-first
   top/bottom rhythm as every other section that follows a hero/banner
   (`.courses-section`, `.schedule-section`, `.teachers-section`,
   `.why-section`, etc.) - not the tighter, flat `var(--space-8)` top this
   used to have, which read as noticeably less breathing room above the
   filter bar than the rest of the site gives a section right after its
   hero (see the same fix on `.studio-schedule`/`.explore-listing`). */
.course-listing {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .course-listing {
        padding: var(--space-16) 0;
    }
}

.course-filters-bar {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-border);
}

@media (min-width: 1024px) {
    .course-filters-bar {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
    }
}

/* Horizontal scroll instead of wrapping - six tabs don't fit a phone
   viewport, and wrapping would push the course list down by a variable,
   layout-shifting amount depending on how many rows the tabs wrap to. */
.course-filters {
    display: flex;
    gap: var(--space-6);
    overflow-x: auto;
}

.course-filters__tab {
    flex-shrink: 0;
    padding: 0 0 var(--space-2);
    border: none;
    border-bottom: 2px solid transparent;
    background: none;
    color: var(--color-text);
    font-family: var(--font-heading);
    font-size: var(--text-h4-size);
    cursor: pointer;
    transition: color var(--transition-fast), border-color var(--transition-fast);
}

.course-filters__tab:hover,
.course-filters__tab:focus-visible {
    color: var(--color-primary);
}

.course-filters__tab--active {
    border-color: var(--color-primary);
    color: var(--color-primary);
    font-weight: var(--font-weight-bold);
}

/* Plain label, not a real `<select>` - see the Blade comment above this
   markup for why sorting isn't wired to anything yet. Styled to read as a
   dropdown (underline + chevron) because that's what the design shows. */
.course-sort {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    align-self: flex-start;
    padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--color-border);
    color: var(--color-text);
    font-size: var(--text-h4-size);
    white-space: nowrap;
}

.course-list {
    display: flex;
    flex-direction: column;
    margin-top: var(--space-8);
}

.course-row {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--space-12) 0;
    border-bottom: 1px solid var(--color-border);
}

/* Without this, the `[hidden]` attribute the filter script (see
   courses.blade.php) toggles has no effect: the UA stylesheet's
   `[hidden] { display: none }` and this rule's `.course-row { display:
   flex }` are equal specificity, and author styles beat UA styles on a
   tie - so `display: flex` silently wins and the row stays visible. */
.course-row[hidden] {
    display: none;
}

.course-row:first-child {
    padding-top: 0;
}

.course-row__media {
    display: block;
    aspect-ratio: 4 / 3;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.course-row__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.course-row__body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.course-row__top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
}

.course-row__category {
    font-family: var(--font-heading);
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    color: var(--color-heading);
}

.course-row__rating {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    flex-shrink: 0;
    font-family: var(--font-heading);
    font-size: var(--text-h4-size);
    color: var(--color-heading);
}

.course-row__rating i {
    color: var(--color-gold);
}

/* Plain `<h3>` - no size override. Figma measures this row title at 44px
   (this site's `--text-h2-size`), but `--text-h2-size` is reserved for one
   section heading per section elsewhere on the site (see `.courses-section__header`,
   `.offerings-section__header`, etc.) - giving it to a title repeated six
   times in one list would outrank the page's own `<h1>` visually and read
   as six section headings, not a list. The default `h3` rule (see "Base /
   reset") already gives every other card title on the site
   (`.course-card__title`, welcome.blade.php) this same size, so this row
   just follows that precedent instead of Figma's literal pixel value. */
.course-row__title {
    margin: var(--space-2) 0 0;
}

/* Clamped to 3 lines - descriptions vary a lot in length (see the
   $courseListing array in courses.blade.php), and an unclamped one can run
   long enough to push the meta/price/actions well past where the photo
   ends. `-webkit-line-clamp` is the only cross-browser way to do this
   without JS truncation logic; it's supported in every current
   evergreen browser despite the prefix. */
.course-row__description {
    display: -webkit-box;
    overflow: hidden;
    color: var(--color-text);
    margin: var(--space-2) 0 0;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
}

.course-row__meta {
    color: var(--color-text);
    margin: var(--space-4) 0 0;
}

/* `margin-top: auto` (rather than a fixed spacing value) pins this to the
   bottom of `.course-row__body` - which `.course-row`'s default cross-axis
   `align-items: stretch` (desktop row layout below) stretches to match
   `.course-row__media`'s height. Without it, the footer would sit right
   after the description/meta text at whatever height that content
   happens to reach, landing at a different vertical position on every
   row instead of lining up with the bottom of the photo - same
   "pin to bottom of a variable-height card" pattern as
   `.course-card__link`'s own `margin-top: auto` (see welcome.blade.php). */
.course-row__footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4) var(--space-8);
    margin-top: auto;
    padding-top: var(--space-6);
}

.course-row__price {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-2);
    margin: 0 auto 0 0;
    font-family: var(--font-heading);
}

/* `--text-h2-size` here isn't the "borrowing heading weight" problem the
   row title ran into above - this is a plain number in a `<span>`, not an
   `<h1>`-`<h6>` tag, so it doesn't compete with the page's own heading
   hierarchy the way an oversized `<h3>` would. */
.course-row__price-amount {
    font-size: var(--text-h2-size);
    line-height: var(--text-h2-leading);
    color: var(--color-heading);
}

.course-row__price-currency {
    font-family: var(--font-body);
    font-size: var(--text-body-size);
    color: var(--color-text);
}

.course-row__buy,
.course-row__gift {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--color-primary);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
}

.course-row__buy i {
    transition: transform var(--transition-fast);
}

.course-row__buy:hover i,
.course-row__buy:focus-visible i {
    transform: translateX(var(--space-1));
}

.course-list__empty {
    margin-top: var(--space-8);
    text-align: center;
    color: var(--color-text-muted);
}

@media (min-width: 768px) {
    .course-row {
        flex-direction: row;
        gap: var(--space-8);
    }

    .course-row__media {
        flex-shrink: 0;
        width: 26.75rem;
    }
}

/* -- Explore listing (explore.blade.php - filter tabs + practice grid) -------

   Matches the Figma "Explore Page" frame (node 542:4746). Distinct from
   `.course-listing`'s single-column rows - this is a 3-up photo grid, each
   card's image bleeding to the card's own top corners with a duration
   badge overlaid on the photo, category/title/description sitting on the
   plain page background below (same "photo has its own radius, not a
   boxed card" convention as `.course-card`, not `.teacher-card`'s "white
   card, photo clipped inside" one). Mobile-first per CSS coding standard
   #8. */

/* Same mobile-first top/bottom rhythm as `.course-listing` above - see
   that rule's comment. */
.explore-listing {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .explore-listing {
        padding: var(--space-16) 0;
    }
}

.explore-filters-bar {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-border);
}

@media (min-width: 1024px) {
    .explore-filters-bar {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
    }
}

/* Horizontal scroll instead of wrapping - same reasoning as
   `.course-filters`: seven tabs don't fit a phone viewport, and wrapping
   would push the grid down by a variable amount depending on how many
   rows the tabs wrap to. */
.explore-filters {
    display: flex;
    gap: var(--space-6);
    overflow-x: auto;
}

.explore-filters__tab {
    flex-shrink: 0;
    padding: 0 0 var(--space-2);
    border: none;
    border-bottom: 2px solid transparent;
    background: none;
    color: var(--color-text);
    font-family: var(--font-heading);
    font-size: var(--text-sm);
    cursor: pointer;
    transition: color var(--transition-fast), border-color var(--transition-fast);
}

.explore-filters__tab:hover,
.explore-filters__tab:focus-visible {
    color: var(--color-primary);
}

.explore-filters__tab--active {
    border-color: var(--color-primary);
    color: var(--color-primary);
    font-weight: var(--font-weight-bold);
}

.explore-search {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-shrink: 0;
    padding: var(--space-2) var(--space-5);
    border: 1px solid var(--color-primary);
    border-radius: var(--radius-pill);
    color: var(--color-text-muted);
}

.explore-search input {
    flex: 1;
    min-width: 0;
    border: none;
    background: none;
    padding: 0;
    font: inherit;
    font-size: var(--text-sm);
    color: var(--color-text);
}

.explore-search input::placeholder {
    color: var(--color-text-muted);
}

.explore-search input:focus-visible {
    outline: none;
}

.explore-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-16) var(--space-6);
    margin-top: var(--space-8);
}

@media (min-width: 640px) {
    .explore-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 1024px) {
    .explore-grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

.explore-card {
    display: flex;
    flex-direction: column;
}

.explore-card__media {
    position: relative;
    display: block;
    aspect-ratio: 412 / 292;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    overflow: hidden;
}

.explore-card__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--transition-slow) var(--ease-smooth);
}

.explore-card:hover .explore-card__media img {
    transform: scale(1.05);
}

/* A flat dark tint (not the swiper card's hover-only treatment) so the
   white duration badge below stays readable against any photo, dark or
   light - matches the design's own overlay on most (not literally all) of
   its cards; applied uniformly here for a consistent, always-legible
   result rather than reproducing that one inconsistency. */
.explore-card__overlay {
    position: absolute;
    inset: 0;
    background-color: rgba(0, 0, 0, 0.2);
}

.explore-card__duration {
    position: absolute;
    left: var(--space-3);
    bottom: var(--space-3);
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill);
    background-color: rgba(0, 0, 0, 0.4);
    backdrop-filter: blur(5px);
    -webkit-backdrop-filter: blur(5px);
    color: var(--color-white);
    font-size: var(--text-sm);
}

.explore-card__category {
    margin: var(--space-5) 0 0;
    color: var(--color-primary);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: var(--text-sm);
}

.explore-card__title {
    margin: var(--space-3) 0 0;
}

.explore-card__description {
    margin: var(--space-3) 0 0;
    color: var(--color-text);
}

/* -- Live Studio schedule header (heading + "Special Events" + week nav) ---------

   The hero itself reuses `.page-banner.hero-banner` as-is (see
   about.blade.php/courses.blade.php) - matches the Figma "Live studio
   Page" frame (node 201:1883) exactly, which is one full-bleed photo with
   the header floating transparently on top, not a split cream-panel-plus-
   photo layout - no page-specific hero CSS needed here. */

/* `var(--space-12) 0` -> `var(--space-16) 0` at 768px, same mobile-first
   top/bottom rhythm as every other section that follows a hero/banner
   (`.practices-section`, `.explore-practices`, `.courses-section`,
   `.schedule-section`, `.teachers-section`, `.why-section`) - not the
   tighter, flat `var(--space-8)` top `.course-listing`/`.explore-listing`
   use, which reads as noticeably less breathing room above "Upcoming Live
   Streams" than the rest of the site gives a section right after its
   hero. */
.studio-schedule {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .studio-schedule {
        padding: var(--space-16) 0;
    }
}

.studio-schedule__header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
}

.studio-schedule__header h2 {
    margin: 0;
}

.studio-schedule__header-actions {
    display: flex;
    align-items: center;
    gap: var(--space-8);
}

/* `.btn` is `display: inline-block` (see "Buttons" above), so an icon and
   the label text inside it need an explicit gap - this is the only `.btn`
   on the site paired with a leading icon like this. Font size bumped to
   body size, matching `.hero-banner__actions a`'s own override on this
   page's hero CTA - the plain `.btn` default (`--text-sm`, 14px) reads as
   noticeably smaller than every other prominent pill CTA on this page. */
.studio-schedule__header-actions .btn {
    font-size: var(--text-body-size);
}

.studio-schedule__header-actions .btn i {
    margin-right: var(--space-2);
}

/* "This Week / Jun 15 - 21, 2026" - sits between the prev/next week
   arrows (see `.studio-week-nav` below), after the "Special Events" CTA. */
.studio-week-range {
    text-align: center;
}

.studio-week-range__label {
    margin: 0;
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.06em; /* not tokenized - same one-off precedent as `.eyebrow--banner` */
    color: var(--color-text-muted);
}

.studio-week-range__dates {
    margin: var(--space-1) 0 0;
    font-weight: var(--font-weight-semibold);
    color: var(--color-heading);
}

/* Prev arrow, the "This Week" range text, then the next arrow - the range
   text sits sandwiched between the two buttons, not grouped off to their
   side. */
.studio-week-nav {
    display: flex;
    align-items: center;
    gap: var(--space-4);
}

/* Same circular pill-button shape/color pair as `.hero-swiper__controls`'s
   prev/next arrows - reused here rather than a third variant. Not wired to
   real week-pagination yet - only one week of data exists (see
   `$weekStrip`/`$scheduleDays` in live-studio.blade.php), same "Blade view
   redesign only" scope as `.course-sort`. */
.studio-week-nav__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--space-8);
    height: var(--space-8);
    flex-shrink: 0;
    border-radius: var(--radius-pill);
    border: 1px solid var(--color-primary);
    background-color: var(--color-secondary);
    color: var(--color-primary);
    cursor: pointer;
    transition: background-color var(--transition-fast), color var(--transition-fast);
}

.studio-week-nav__btn:hover,
.studio-week-nav__btn:focus-visible {
    background-color: var(--color-primary);
    color: var(--color-primary-contrast);
}

/* -- Live Studio week strip (7-day selector) -------------------------------------

   Horizontal scroll instead of wrapping below 768px - seven day cards
   don't fit a phone viewport, same reasoning as `.course-filters`. Not
   wired to filter the list below yet - `.studio-day-card--active` is a
   static marker on today's card, same static-control scope as the week
   nav arrows above. */
.studio-week-strip {
    display: flex;
    gap: var(--space-3);
    overflow-x: auto;
    margin-top: var(--space-8);
    padding-bottom: var(--space-1);
}

/* `border: none` overrides the browser's default `<button>` chrome (this
   is a `<button>`, not a `<div>` - see live-studio.blade.php) - the design
   has no border here at all, only the drop shadow below. */
/* A real `<button>` (see live-studio.blade.php) - clicking one moves the
   active highlight here and scrolls to that day's section below (see
   that view's own `@push('scripts')`). `transform` on hover/active is the
   same lift affordance `.practice-tile`/course-filters__tab elsewhere on
   the site use for a clickable card. */
.studio-day-card {
    flex: 1 0 6.5rem;
    padding: var(--space-4) var(--space-2);
    border: none;
    border-radius: var(--radius-lg);
    background-color: var(--color-surface);
    box-shadow: var(--shadow-day-card);
    text-align: center;
    cursor: pointer;
    transition: transform var(--transition-fast);
}

.studio-day-card:hover,
.studio-day-card:focus-visible {
    transform: translateY(-2px);
}

.studio-day-card--active {
    background-color: var(--color-primary);
    box-shadow: none;
}

.studio-day-card__weekday {
    margin: 0;
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.06em; /* not tokenized - same one-off precedent as `.eyebrow--banner` */
    color: var(--color-text-muted);
}

.studio-day-card--active .studio-day-card__weekday {
    color: var(--color-text-inverse);
}

.studio-day-card__date {
    margin: var(--space-2) 0 0;
    font-size: var(--text-h2-size);
    line-height: var(--text-h2-leading);
    color: var(--color-heading);
}

.studio-day-card--active .studio-day-card__date {
    color: var(--color-primary-contrast);
}

.studio-day-card__count {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

.studio-day-card--active .studio-day-card__count {
    color: var(--color-text-muted-inverse);
}

/* -- Live Studio filters (search / timezone / practice type / clear) ------------

   Mirrors `.explore-search`'s pill-shaped bordered field, repeated for
   three fields plus a plain-text "Clear Filters" action. Not wired to real
   search/filtering yet - see the week strip comment above for the same
   scope note. `appearance: none` on the `<select>` fields suppresses the
   browser's own native arrow so the single Font Awesome chevron already in
   the field (matching the design) doesn't end up doubled. */
.studio-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-8);
}

.studio-filters__field {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--color-primary);
    border-radius: var(--radius-pill);
    color: var(--color-text-muted);
}

.studio-filters__field i {
    font-size: var(--text-sm);
}

.studio-filters__field input,
.studio-filters__field select {
    appearance: none;
    border: none;
    background: none;
    padding: 0;
    font: inherit;
    font-size: var(--text-sm);
    color: var(--color-text);
}

.studio-filters__field input {
    min-width: 0;
}

.studio-filters__field input::placeholder {
    color: var(--color-text-muted);
}

.studio-filters__field input:focus-visible,
.studio-filters__field select:focus-visible {
    outline: none;
}

.studio-filters__clear {
    margin-left: auto;
    border: none;
    background: none;
    padding: 0;
    font: inherit;
    font-weight: var(--font-weight-bold);
    color: var(--color-primary);
    text-decoration: underline;
    cursor: pointer;
}

.studio-filters__clear:hover,
.studio-filters__clear:focus-visible {
    color: var(--color-primary-hover);
}

/* -- Live Studio class list (day groups + class rows) ----------------------------

   Matches the Figma "Live studio Page" frame (node 201:1883) exactly: each
   `.studio-day-group` is a horizontal date/weekday *heading line* (large
   date number + "MONTH YEAR · WEEKDAY" label + an optional "Today" pill)
   sitting above that day's stack of `.studio-class-row` cards - not a
   side-by-side date column next to the list, which an earlier pass of this
   page got wrong. A day can carry more than one class (see June 15 in
   `$scheduleDays`). Mobile-first, same two-column-photo-spans-two-rows
   technique as `.schedule-row`: media + body sit in row 1/2, time/CTA spans
   full width below; `min-width: 1024px` becomes the single row the design
   shows - media, body copy, time/CTA left to right. */
.studio-day-list {
    margin-top: var(--space-10);
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
}

.studio-day-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.studio-day-group__heading {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--space-3);
}

.studio-day-group__number {
    font-size: var(--text-h2-size);
    line-height: var(--text-h2-leading);
    color: var(--color-heading);
}

.studio-day-group__label {
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.06em; /* not tokenized - same one-off precedent as `.eyebrow--banner` */
    color: var(--color-text);
}

.studio-day-group__today {
    display: inline-flex;
    align-items: center;
    padding: var(--space-1) var(--space-4);
    border-radius: var(--radius-pill);
    background-color: var(--color-primary);
    color: var(--color-primary-contrast);
    font-size: var(--text-sm);
}

.studio-day-group__classes {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.studio-class-row {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: var(--space-4);
    row-gap: var(--space-4);
    align-items: center;
    padding: var(--space-5);
    border-radius: var(--radius-lg);
}

.studio-class-row__media {
    display: block;
    width: 8rem;
    aspect-ratio: 10 / 9;
    border-radius: var(--radius-md);
    overflow: hidden;
}

.studio-class-row__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.studio-class-row__badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill);
    background-color: var(--color-surface);
    color: var(--color-primary);
    font-size: var(--text-sm);
    font-weight: var(--font-weight-medium);
    margin: 0 0 var(--space-2);
}

.studio-class-row__badge::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background-color: var(--color-primary);
}

.studio-class-row__title {
    margin: 0;
}

.studio-class-row__teacher {
    margin: var(--space-1) 0 0;
    font-family: var(--font-heading);
    font-style: italic;
    color: var(--color-primary);
}

/* No description on June 15's "Live Now" class (`with Shravani`) - Figma's
   own frame has no description text node for that one card, unlike every
   other class row; left blank here rather than inventing copy the design
   doesn't provide, same "faithful to what Figma actually contains"
   precedent as `$courseListing`'s category-label comment in
   courses.blade.php. */
.studio-class-row__description {
    width: 70%;
    margin: var(--space-2) 0 0;
    color: var(--color-text);
}

.studio-class-row__time {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.studio-class-row__hour {
    margin: 0;
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
    color: var(--color-heading);
}

.studio-class-row__tz {
    margin: var(--space-1) 0 0;
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

.studio-class-row__cta {
    flex-shrink: 0;
}

@media (min-width: 1024px) {
    .studio-class-row {
        grid-template-columns: auto 1fr auto;
        column-gap: var(--space-6);
        align-items: center;
    }

    .studio-class-row__time {
        grid-column: 3;
        flex-direction: column;
        align-items: center;
    }

    .studio-class-row__hour,
    .studio-class-row__tz {
        text-align: right;
    }
}

/* -- Live Studio featured class (highlighted "Integrative Practice" card) --------

   Figma repeats June 20's own class content a second time here, styled as
   a highlighted, self-contained card (it carries its own date/weekday
   heading line instead of sitting in a `.studio-day-group`, unlike every
   row above) - not a different class, a deliberate design duplicate,
   preserved as-is rather than "fixed" into a distinct seventh entry (same
   call as `$courseListing`'s repeated-course comment in courses.blade.php).
   Same structure/grid mechanics as `.studio-day-group`/`.studio-class-row`,
   dark/inverted palette instead, just collapsed into one card. */
.studio-featured-class {
    margin-top: var(--space-4);
    padding: var(--space-6);
    background-color: var(--color-primary);
    border-radius: var(--radius-lg);
    scroll-margin-top: var(--space-6); /* breathing room above the card when `.studio-schedule__special-events` scrolls it into view */
}

.studio-featured-class__heading {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
}

.studio-featured-class__number {
    font-size: var(--text-h2-size);
    line-height: var(--text-h2-leading);
    color: var(--color-primary-contrast);
}

.studio-featured-class__label {
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-text-inverse);
}

.studio-featured-class__row {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: var(--space-4);
    row-gap: var(--space-4);
    align-items: center;
    margin-top: var(--space-4);
}

.studio-featured-class__media {
    display: block;
    width: 8rem;
    aspect-ratio: 10 / 9;
    border-radius: var(--radius-md);
    overflow: hidden;
    border: 2px solid var(--color-white);
}

.studio-featured-class__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.studio-featured-class__title {
    margin: 0;
    color: var(--color-primary-contrast);
}

.studio-featured-class__teacher {
    margin: var(--space-1) 0 0;
    font-family: var(--font-heading);
    font-style: italic;
    color: var(--color-text-inverse);
}

.studio-featured-class__description {
    width: 70%;
    margin: var(--space-2) 0 0;
    color: var(--color-text-muted-inverse);
}

.studio-featured-class__time {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.studio-featured-class__hour {
    margin: 0;
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
    color: var(--color-primary-contrast);
}

.studio-featured-class__tz {
    margin: var(--space-1) 0 0;
    font-size: var(--text-sm);
    color: var(--color-text-muted-inverse);
}

.studio-featured-class__cta {
    flex-shrink: 0;
    background-color: transparent;
    border-color: var(--color-white);
    color: var(--color-text-inverse);
}

.studio-featured-class__cta:hover,
.studio-featured-class__cta:focus-visible {
    background-color: var(--color-text-inverse);
    border-color: var(--color-text-inverse);
    color: var(--color-primary);
}

@media (min-width: 1024px) {
    .studio-featured-class__row {
        grid-template-columns: auto 1fr auto;
        column-gap: var(--space-6);
        align-items: center;
    }

    .studio-featured-class__time {
        grid-column: 3;
        flex-direction: column;
        align-items: center;
    }

    .studio-featured-class__hour,
    .studio-featured-class__tz {
        text-align: right;
    }
}

/* Small addition to the existing `.cta-banner` component (see "CTA banner"
   above) - only this page's instance renders a `.cta-banner__note`, other
   pages reusing `.cta-banner` simply don't include the markup, so this is
   additive and doesn't change their appearance. */
.cta-banner__note {
    margin-top: var(--space-4);
    font-style: italic;
    font-size: var(--text-sm);
    color: var(--color-text-muted-inverse);
}

/* -- Course detail (courses/show.blade.php - "Living a Life of Purpose") -----

   Matches the Figma "Course inner" frame (node 438:753). A long,
   content-heavy page built from several one-off sections below, plus two
   already-shipped shared sections reused verbatim: `.quote-section` (see
   about.blade.php) for the pull-quote band, and `.testimonials-section`
   (see welcome.blade.php) for the marquee. `.playlist-list` (see that
   same file) is reused for every bullet list here too - same 6px dot
   marker Figma shows for all of them, no need for a second bullet style.
   Mobile-first per CSS coding standard #8. */

/* -- Course hero (extends .hero-banner with rating/price/meta/CTA) ----------- */

.course-hero__rating {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: var(--space-4) 0 0;
    color: var(--color-text);
}

.course-hero__rating i {
    color: var(--color-gold);
}

.course-hero__rating strong {
    color: var(--color-heading);
}

.course-hero__description {
    max-width: 33rem;
    margin: var(--space-4) 0 0;
    color: var(--color-text);
}

.course-hero__meta {
    margin: var(--space-6) 0 0;
    color: var(--color-primary);
    font-weight: var(--font-weight-bold);
}

.course-hero__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4);
    margin-top: var(--space-6);
}

.course-hero__price {
    font-family: var(--font-heading);
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
    color: var(--color-heading);
}

/* -- The invitation (image + pull-quote, two column) ------------------------- */

.invitation-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .invitation-section {
        padding: var(--space-16) 0;
    }
}

.invitation-section__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
    align-items: center;
}

@media (min-width: 1024px) {
    .invitation-section__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-12);
    }
}

.invitation-section__media {
    display: block;
    aspect-ratio: 4 / 3;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.invitation-section__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.invitation-section__content {
    padding: var(--space-4) 0;
}

/* Overrides the default UA `<p>` margin (1em top/bottom) - every other
   `.eyebrow` on the site relies on that same default margin for its own
   spacing, but this section's tighter Figma spacing needs it zeroed out. */
.invitation-section__eyebrow {
    margin: 0;
}

.invitation-section__quote {
    margin: var(--space-4) 0 0;
}

.invitation-section__text {
    margin: var(--space-6) 0 0;
    color: var(--color-text);
}

/* -- Course benefits (bullets + plain image, no overlay per Figma) ----------- */

.benefits-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .benefits-section {
        padding: var(--space-16) 0;
    }
}

.benefits-section__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
}

@media (min-width: 1024px) {
    .benefits-section__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-12);
    }
}

.benefits-section__content {
    width: 80%;
}

.benefits-section__description {
    margin: var(--space-4) 0 0;
    color: var(--color-text);
}

/* Same height-matching technique as `.why-section__media` (see that
   class's own comment for the full mechanics) - mobile gets its own
   `aspect-ratio` since there's no sibling row to borrow a height from;
   desktop drops it and stretches to the content column's height instead,
   so the image doesn't tower over a short bullet list. */
.benefits-section__media {
    display: block;
    position: relative;
    aspect-ratio: 9 / 10;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.benefits-section__media img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

@media (min-width: 1024px) {
    .benefits-section__media {
        aspect-ratio: auto;
        height: 100%;
    }
}

/* -- Wisdom Series Overview (chapter accordion, each holding video/PDF files) ---

   Same native `<details>`/`<summary>` mutual-exclusivity technique as
   `.faq-item` (see that class's own comment for the full rationale) -
   `name="chapters"` in the Blade markup groups these the same way
   `name="faq"` does there. Only the chevron/number/description layout is
   new; the accordion mechanics are copied verbatim. Each panel holds a
   variable number of `.video-card`s (placeholder content for now - no
   course video data model exists yet, see CLAUDE.md "PENDING DECISION:
   database swap"); a PDF row (if a chapter ever has one) would sit in the
   same panel, styled separately once a real PDF appears in the data.

   White background spans the full viewport width, not just the site
   grid - same `full-bleed` + `background-color: var(--color-surface)`
   technique as `.quote-section` above, with a nested `.container` (in the
   Blade markup) re-aligning the actual content back to the grid. */
.chapter-accordion-section {
    background-color: var(--color-surface);
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .chapter-accordion-section {
        padding: var(--space-16) 0;
    }
}

.chapter-accordion__header {
    max-width: 40rem;
    margin: 0 auto;
    padding: var(--space-8) var(--space-6);
    text-align: center;
    border-bottom: 1px solid var(--color-border);
}

.chapter-accordion__header h2 {
    margin: var(--space-2) 0 0;
}

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

.chapter-item:last-child {
    border-bottom: none;
}

.chapter-item__summary {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
    padding: var(--space-6);
    list-style: none;
    cursor: pointer;
}

.chapter-item__summary::-webkit-details-marker {
    display: none;
}

.chapter-item__number {
    flex-shrink: 0;
    width: 1.5rem;
    margin-top: var(--space-1);
    color: var(--color-text-muted);
}

.chapter-item__body {
    flex: 1;
}

.chapter-item__title {
    margin: 0;
}

.chapter-item__description {
    margin: var(--space-1) 0 0;
    color: var(--color-text-muted);
}

.chapter-item__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--space-10);
    height: var(--space-10);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    color: var(--color-heading);
    font-size: var(--text-sm);
    transition: transform var(--transition-fast);
}

.chapter-item[open] .chapter-item__icon {
    transform: rotate(180deg);
}

/* `padding-left` matches the summary row's own number-column + gap offset
   (`.chapter-item__number`'s fixed `width` + `.chapter-item__summary`'s
   `gap`), on top of the shared `--space-6` edge padding - so the video
   row starts flush under the chapter title, not under the number. */
.chapter-item__content {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-6);
    padding: var(--space-4) var(--space-6) var(--space-10) calc(var(--space-6) + 1.5rem + var(--space-4));
}

@media (min-width: 640px) {
    .chapter-item__content {
        grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    }
}

/* -- Who this course is for (image + long bullet list) ----------------------- */

.who-for-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .who-for-section {
        padding: var(--space-16) 0;
    }
}

.who-for-section__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
}

@media (min-width: 1024px) {
    .who-for-section__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-12);
        align-items: start;
    }
}

.who-for-section__media {
    display: block;
    aspect-ratio: 4 / 5;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.who-for-section__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Opt-in per course, not the base rule above - a future course's own photo
   won't already be framed subject-on-the-right the way this one is, so
   flipping shouldn't be this template's default. Source photo's subject
   sits toward the right of the frame with the crop already matching the
   container's own `aspect-ratio` almost exactly (see the exported PNG's
   own 717×900 dimensions) - there's no spare width for `object-position`
   to shift the crop and bring her more central, so a horizontal flip is
   what actually repositions her to the left instead. */
.who-for-section__media--flip img {
    transform: scaleX(-1);
}

/* -- How it works (numbered 6-tile grid) -------------------------------------- */

.how-it-works-section {
    padding: var(--space-12) 0;
    background-color: var(--color-surface);
}

@media (min-width: 768px) {
    .how-it-works-section {
        padding: var(--space-16) 0;
    }
}

.how-it-works-section__header {
    max-width: 48rem;
    margin: 0 auto;
    text-align: center;
}

.how-it-works-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-12);
    margin-top: var(--space-10);
}

@media (min-width: 768px) {
    .how-it-works-grid {
        grid-template-columns: repeat(3, 1fr);
        gap: var(--space-12) var(--space-12);
    }
}

.how-it-works-tile__number {
    display: block;
    font-family: var(--font-heading);
    font-size: var(--text-h3-size);
    line-height: 1;
    color: var(--color-number-accent);
}

@media (min-width: 768px) {
    .how-it-works-tile__number {
        font-size: var(--text-h2-size);
    }
}

.how-it-works-tile h3 {
    margin-top: var(--space-4);
}

.how-it-works-tile p {
    margin: var(--space-2) 0 0;
    color: var(--color-text);
}

/* -- What you'll take away + Own this course --------------------------------- */

.take-away-section {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .take-away-section {
        padding: var(--space-16) 0;
    }
}

.take-away-section__header {
    max-width: 48rem;
    margin: 0 auto;
    text-align: center;
}

.take-away-section__header h2 {
    margin: var(--space-2) 0 0;
}

.take-away-section__grid {
    margin-top: var(--space-10);
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10);
}

@media (min-width: 1024px) {
    .take-away-section__grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-12);
    }
}

.take-away-section__text {
    margin: var(--space-4) 0 0;
    color: var(--color-text);
}

.own-course-card {
    display: flex;
    flex-direction: column;
    padding: var(--space-8);
    border-radius: var(--radius-lg);
    background-color: var(--color-card-surface);
}

.own-course-card__heading {
    margin: 0;
}

.own-course-card__divider {
    margin: var(--space-6) 0;
    border: none;
    border-top: 1px solid var(--color-border);
}

.own-course-card__title {
    margin: 0;
}

.own-course-card__description {
    margin: var(--space-4) 0 0;
    color: var(--color-text);
}

.own-course-card__price {
    margin: var(--space-6) 0 0;
    font-family: var(--font-heading);
    font-size: var(--text-h3-size);
    line-height: var(--text-h3-leading);
    color: var(--color-heading);
}

.own-course-card__meta {
    margin: var(--space-2) 0 0;
    color: var(--color-text-muted);
}

.own-course-card__cta {
    margin-top: var(--space-6);
    align-self: flex-start;
}

/* -- Meet your teacher --------------------------------------------------------- */

.teacher-section {
    padding: var(--space-12) 0;
    background-color: var(--color-surface);
}

@media (min-width: 768px) {
    .teacher-section {
        padding: var(--space-16) 0;
    }
}

.teacher-section__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
    align-items: center;
}

@media (min-width: 1024px) {
    .teacher-section__grid {
        grid-template-columns: minmax(0, 22rem) 1fr;
        gap: var(--space-12);
    }
}

/* `610 / 624` - Figma's own dimension annotation on this frame, not a
   guess. */
.teacher-section__media {
    display: block;
    aspect-ratio: 610 / 624;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.teacher-section__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.teacher-section__content p {
    margin: var(--space-4) 0 0;
    color: var(--color-text);
}

/* Multi-teacher carousel - one `.teacher-section__grid` per slide, same
   grid/media/content rules above apply unchanged inside each. Swiper's
   `autoHeight: true` (set in the JS init) resizes the container to match
   each slide's own height instead of equalizing to the tallest bio, so no
   `.swiper-slide { height: ... }` override is needed here the way
   `.courses-swiper .swiper-slide { height: auto }` was for the fixed-card
   courses carousel. */
.teacher-swiper__controls {
    display: flex;
    justify-content: center;
    gap: var(--space-3);
    margin-top: var(--space-8);
}

.teacher-swiper__controls .swiper-button-prev,
.teacher-swiper__controls .swiper-button-next {
    position: static;
    top: auto;
    margin: 0;
    width: var(--space-8);
    height: var(--space-8);
    border-radius: var(--radius-pill);
    border: 0;
}

.teacher-swiper__controls .swiper-button-prev {
    background-color: var(--color-primary);
    color: var(--color-white);
}

.teacher-swiper__controls .swiper-button-next {
    background-color: var(--color-control-surface);
    color: var(--color-primary);
}

/* Same double-icon fix as `.courses-swiper__controls` - see that class's
   own comment for why both the `::after` glyph and the injected
   `<svg class="swiper-navigation-icon">` need suppressing. */
.teacher-swiper__controls .swiper-button-next::after,
.teacher-swiper__controls .swiper-button-prev::after {
    content: none;
}

.teacher-swiper__controls .swiper-navigation-icon {
    display: none;
}

.teacher-swiper__controls i {
    font-size: var(--space-4);
}

/* -- Final quote CTA (dark photo + Anand Ji quote) ---------------------------- */

.final-quote-cta {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: var(--space-16) 0;
    text-align: center;
}

.final-quote-cta__media {
    position: absolute;
    inset: 0;
    z-index: -1;
}

.final-quote-cta__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: brightness(0.4) blur(1px);
}

.final-quote-cta__inner {
    max-width: 54rem;
}

.final-quote-cta__quote {
    margin: 0;
    font-size: var(--text-h2-size);
    line-height: var(--text-h2-leading);
    font-family: var(--font-heading);
    color: var(--color-text-inverse);
}

.final-quote-cta .btn {
    margin-top: var(--space-8);
}

/* -- Gain lifetime access CTA -------------------------------------------------- */

.access-cta {
    padding: var(--space-12) 0;
    background-color: var(--color-surface);
}

/* Nested .container aligns the inner content with the site grid while
   .full-bleed (on the <section>) lets the white background span the
   viewport edge to edge - see .full-bleed's own comment. */

@media (min-width: 768px) {
    .access-cta {
        padding: var(--space-16) 0;
    }
}

.access-cta__inner {
    max-width: 48rem;
    margin: 0 auto;
    text-align: center;
}

.access-cta__heading {
    margin: 0;
}

.access-cta__price {
    margin: var(--space-2) 0 0;
    font-family: var(--font-heading);
    font-size: var(--text-h2-size);
    line-height: var(--text-h2-leading);
    color: var(--color-heading);
}

.access-cta .btn {
    margin-top: var(--space-8);
}

/* -- Blog listing (blog.blade.php - heading + search + filter tabs) ------------ */

/* Same mobile-first top/bottom rhythm as `.course-listing`/`.explore-listing` -
   see those rules' comments. */
.blog-listing {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .blog-listing {
        padding: var(--space-16) 0;
    }
}

.blog-listing__header {
    max-width: 46rem;
    margin: 0 auto;
    text-align: center;
}

.blog-search {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    max-width: 26rem;
    margin: var(--space-6) auto 0;
    padding: var(--space-2) var(--space-5);
    border: 1px solid var(--color-primary);
    border-radius: var(--radius-pill);
    color: var(--color-text-muted);
}

.blog-search input {
    flex: 1;
    min-width: 0;
    border: none;
    background: none;
    padding: 0;
    font: inherit;
    font-size: var(--text-sm);
    color: var(--color-text);
}

.blog-search input::placeholder {
    color: var(--color-text-muted);
}

.blog-search input:focus-visible {
    outline: none;
}

.blog-filters-bar {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    margin-top: var(--space-10);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-border);
}

@media (min-width: 1024px) {
    .blog-filters-bar {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
    }
}

/* Horizontal scroll instead of wrapping - same reasoning as `.course-filters`:
   six tabs don't fit a phone viewport, and wrapping would push the grid down
   by a variable amount depending on how many rows the tabs wrap to. */
.blog-filters {
    display: flex;
    gap: var(--space-6);
    overflow-x: auto;
}

.blog-filters__tab {
    flex-shrink: 0;
    padding: 0 0 var(--space-2);
    border: none;
    border-bottom: 2px solid transparent;
    background: none;
    color: var(--color-text);
    font-family: var(--font-heading);
    font-size: var(--text-h4-size);
    cursor: pointer;
    transition: color var(--transition-fast), border-color var(--transition-fast);
}

.blog-filters__tab:hover,
.blog-filters__tab:focus-visible {
    color: var(--color-primary);
}

.blog-filters__tab--active {
    border-color: var(--color-primary);
    color: var(--color-primary);
    font-weight: var(--font-weight-bold);
}

/* A real `<select>` wrapped so the chevron icon can sit on top of it
   (native selects render their own arrow, hidden below via `appearance:
   none` in favor of the same Font Awesome chevron the rest of the site
   uses) - genuinely functional (opens, picks a year), not just a styled
   label like `.course-sort`. */
.blog-sort {
    position: relative;
    display: inline-flex;
    align-self: flex-start;
    white-space: nowrap;
}

.blog-sort__select {
    appearance: none;
    border: none;
    border-bottom: 1px solid var(--color-border);
    background: none;
    padding: 0 var(--space-6) var(--space-2) 0;
    font: inherit;
    font-size: var(--text-h4-size);
    color: var(--color-text);
    cursor: pointer;
}

.blog-sort__select:hover,
.blog-sort__select:focus-visible {
    color: var(--color-primary);
    outline: none;
}

.blog-sort__icon {
    position: absolute;
    right: 0;
    bottom: var(--space-2);
    font-size: var(--text-sm);
    color: inherit;
    pointer-events: none;
}

/* Same treatment as `.course-list__empty` - shown only once the filter
   script (see blog.blade.php) hides every `.article-card`. */
.blog-articles__empty {
    margin-top: var(--space-8);
    text-align: center;
    color: var(--color-text-muted);
}

/* -- Blog inner page (blog/show.blade.php - article body + sidebar) ---------- */

/* Same mobile-first top/bottom rhythm as `.course-listing`/`.blog-listing` -
   see those rules' comments. */
.article-detail {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .article-detail {
        padding: var(--space-16) 0;
    }
}

/* First 2fr/1fr (wide main + narrow sidebar) split on the site - every
   other multi-column grid here is an even 1fr/1fr, but a blog post's body
   next to a narrow categories/archive sidebar genuinely needs the
   asymmetry the design shows. */
.article-detail__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10);
}

@media (min-width: 1024px) {
    .article-detail__grid {
        grid-template-columns: 2fr 1fr;
        align-items: start;
    }
}

.article-detail__media {
    display: block;
    aspect-ratio: 848 / 558;
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.article-detail__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.article-detail__byline {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-6);
}

.article-detail__avatar {
    display: block;
    flex-shrink: 0;
    width: var(--space-8);
    height: var(--space-8);
    border-radius: var(--radius-pill);
    overflow: hidden;
}

.article-detail__avatar img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.article-detail__author {
    font-weight: var(--font-weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-heading);
}

.article-detail__meta {
    color: var(--color-text-muted);
    font-size: var(--text-sm);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.article-detail__body {
    margin-top: var(--space-8);
}

.article-detail__body h3 {
    margin: var(--space-8) 0 var(--space-2);
}

.article-detail__body p {
    margin: 0 0 var(--space-4);
    color: var(--color-text);
}

.article-detail__gallery {
    display: block;
    aspect-ratio: 848 / 193;
    border-radius: var(--radius-lg);
    overflow: hidden;
    margin: var(--space-6) 0;
}

.article-detail__gallery img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.article-detail__share {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-10);
    color: var(--color-text-muted);
}

.article-sidebar {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
}

.article-sidebar__heading {
    margin: 0 0 var(--space-4);
}

.article-sidebar__list {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Same chevron-bullet link treatment as `.site-footer__links`, reused here
   for the same "plain list of links in a box" shape. */
.article-sidebar__list a {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--color-text);
}

.article-sidebar__list a:hover,
.article-sidebar__list a:focus-visible {
    color: var(--color-primary);
}

.article-sidebar__list i {
    font-size: var(--text-sm);
    color: var(--color-primary);
}

/* Boxed dropdown (border on all sides, not `.blog-sort`'s underline-only
   treatment) - the sidebar card gives it room to read as a real form
   control rather than the listing page's inline text-style filter. */
.article-sidebar__select {
    position: relative;
}

.article-sidebar__select-input {
    appearance: none;
    width: 100%;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background-color: var(--color-surface);
    padding: var(--space-2) var(--space-8) var(--space-2) var(--space-3);
    font: inherit;
    color: var(--color-text);
    cursor: pointer;
}

.article-sidebar__select-input:hover,
.article-sidebar__select-input:focus-visible {
    border-color: var(--color-primary);
    outline: none;
}

.article-sidebar__select-icon {
    position: absolute;
    right: var(--space-3);
    top: 50%;
    transform: translateY(-50%);
    font-size: var(--text-sm);
    color: var(--color-text-muted);
    pointer-events: none;
}

/* -- FAQ page (faq.blade.php - category tabs + +/- accordion) ---------------- */

/* Same mobile-first top/bottom rhythm as `.course-listing`/`.blog-listing` -
   see those rules' comments. */
.faq-page {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .faq-page {
        padding: var(--space-16) 0;
    }
}

.faq-page__heading {
    margin: 0 0 var(--space-6);
    text-align: center;
}

.faq-tabs-bar {
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-border);
}

/* Horizontal scroll instead of wrapping - same reasoning as
   `.course-filters`/`.blog-filters`: four tabs don't reliably fit a phone
   viewport, and wrapping would push the accordion down by a variable
   amount depending on how many rows the tabs wrap to. */
.faq-tabs {
    display: flex;
    gap: var(--space-6);
    overflow-x: auto;
}

/* Byte-identical to `.blog-filters__tab` (font/color/underline treatment) -
   per instruction, these tabs read as the same component as the blog
   listing page's category tabs. */
.faq-tabs__tab {
    flex-shrink: 0;
    padding: 0 0 var(--space-2);
    border: none;
    border-bottom: 2px solid transparent;
    background: none;
    color: var(--color-text);
    font-family: var(--font-heading);
    font-size: var(--text-h4-size);
    cursor: pointer;
    transition: color var(--transition-fast), border-color var(--transition-fast);
}

.faq-tabs__tab:hover,
.faq-tabs__tab:focus-visible {
    color: var(--color-primary);
}

.faq-tabs__tab--active {
    border-color: var(--color-primary);
    color: var(--color-primary);
    font-weight: var(--font-weight-bold);
}

/* `--color-text-muted`, not `--color-border` - pixel-sampled from the
   design, this divider reads as a visible mid-tone line, not the usual
   near-invisible hairline `--color-border` gives elsewhere. */
.faq-accordion-item {
    border-bottom: 1px solid var(--color-text-muted);
    padding: var(--space-5) var(--space-6);
}

.faq-accordion-item:last-child {
    border-bottom: none;
}

/* A little breathing room below the tabs-bar divider before the first
   question - later items don't need it, their own `padding`/border
   already separate them from the previous row. */
.faq-accordion-item:first-child {
    margin-top: var(--space-4);
}

/* Tints the open item so it reads as the expanded panel - pixel-sampled
   from the design at `--color-card-surface` against the page's own
   `--color-tertiary`, same layering `.own-course-card` uses elsewhere.
   No horizontal padding added: the tint fills the row's own full-bleed
   box exactly like a collapsed row, just a different shade. */
.faq-accordion-item[open] {
    background-color: var(--color-card-surface);
}

/* `list-style: none` removes Firefox's native disclosure marker;
   `::-webkit-details-marker` is the separate, older mechanism Chrome/Safari
   still use for the same glyph - both needed, same as `.faq-item__summary`. */
.faq-accordion-item__summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    list-style: none;
    cursor: pointer;
}

.faq-accordion-item__summary::-webkit-details-marker {
    display: none;
}

.faq-accordion-item__question {
    margin: 0;
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    color: var(--color-heading);
}

/* Pixel-sampled from the design: the closed circle's outline and "+" are
   both `--color-primary` (not `--color-border`/`--color-heading`), same
   color the open state fills solid with - not two different greys. */
.faq-accordion-item__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--space-8);
    height: var(--space-8);
    border: 1px solid var(--color-primary);
    border-radius: var(--radius-pill);
    color: var(--color-primary);
    font-size: var(--text-sm);
    transition: background-color var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}

.faq-accordion-item[open] .faq-accordion-item__icon {
    background-color: var(--color-primary);
    border-color: var(--color-primary);
    color: var(--color-primary-contrast);
}

/* Qualified with the `.faq-accordion-item__icon` parent (not just the
   bare `.faq-accordion-item__icon-minus` class) so this reliably beats
   Font Awesome's own same-specificity `display` rule for `.fa-solid` -
   Font Awesome's stylesheet loads after app.css in the layout, so an
   equal-specificity tie there would go to Font Awesome, not this rule. */
.faq-accordion-item__icon .faq-accordion-item__icon-minus {
    display: none;
}

.faq-accordion-item[open] .faq-accordion-item__icon .faq-accordion-item__icon-plus {
    display: none;
}

.faq-accordion-item[open] .faq-accordion-item__icon .faq-accordion-item__icon-minus {
    display: block;
}

.faq-accordion-item__answer {
    margin-top: var(--space-3);
}

/* Full body size, not `--text-sm` - matches `.faq-item__answer`'s own
   (inherited, unset) size elsewhere on the site; explicit here since
   every other element in this component sets its own font-size too. */
/* An answer is an ordered sequence of `.faq-accordion-item__text`/`__steps`
   blocks (see the Blade comment above this markup) - `margin-bottom`
   spaces consecutive blocks apart, zeroed on the last one so the answer
   doesn't add trailing space of its own beneath `.faq-accordion-item`'s
   own padding. */
.faq-accordion-item__steps {
    margin: 0 0 var(--space-3);
    padding-left: var(--space-5);
    color: var(--color-text);
    font-size: var(--text-body-size);
    line-height: var(--text-body-leading);
}

.faq-accordion-item__text {
    margin: 0 0 var(--space-3);
    color: var(--color-text);
    font-size: var(--text-body-size);
    line-height: var(--text-body-leading);
}

.faq-accordion-item__steps:last-child,
.faq-accordion-item__text:last-child {
    margin-bottom: 0;
}

.faq-page__empty {
    padding: var(--space-6) 0;
    text-align: center;
    color: var(--color-text-muted);
}

.faq-page__support {
    margin-top: var(--space-6);
    text-align: center;
    color: var(--color-text-muted);
}

/* -- Split form page (layouts/split-form.blade.php - photo + form pages) -----
   Reusable shell for support.blade.php and future "same shape, different
   photo/copy" pages per instruction - not `.container`/`.full-bleed`
   (this layout has no site header/nav/footer at all, just the logo), so
   it defines its own full-bleed two-column grid from scratch.

   Deliberately pinned to exactly one screen's height (`100vh`, with a
   `100dvh` upgrade for mobile browsers whose chrome resizes the viewport)
   rather than `min-height` - the whole point is a single, contained
   "app screen" that never scrolls, not a page whose length happens to
   reach the viewport. That means the copy in each section extending this
   layout has to actually fit; trim it there; don't reach for scroll here
   as an escape hatch. `.split-form-page__panel`'s own `overflow-y: auto`
   is a safety net for an unexpectedly tall viewport-width combination,
   not a license to add more content.

   The decorative photo is dropped below 1024px rather than stacked above
   or below the form - splitting one screen's height between a real task
   (the form) and a decorative photo leaves neither enough room to be
   readable/attractive on a phone, and the form is what actually matters
   there. At 1024px+ it returns as the 50/50 side-by-side split the design
   shows, same breakpoint `.article-detail__grid` uses for its own
   wide/narrow split. */
/* `min-height`, not a hard `height` + `overflow: hidden` - per instruction,
   this page is now allowed to scroll if its content needs more than one
   viewport, it just shouldn't ever be *shorter* than one. */
.split-form-page {
    min-height: 100vh;
    min-height: 100dvh;
}

.split-form-page__grid {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100dvh;
}

@media (min-width: 1024px) {
    .split-form-page__grid {
        flex-direction: row;
    }
}

/* `center`, not `flex-start` - `.split-form-page__grid` only sets a
   `min-height` now (see its own comment), not a fixed `height`, so a
   panel whose content is shorter than one viewport no longer leaves that
   leftover space stranded below the content (the "white space at the
   bottom" bug); it's centered in the space instead. This is safe now in
   a way it wasn't when this was `flex-start`: back when the grid had a
   *fixed* `height: 100%` + `overflow-y: auto`, centering an overflowing
   child inside a fixed-height box could push its top edge above the
   scrollable area with no way to scroll back up to it. With no fixed
   height anywhere in this chain any more, a panel taller than one
   viewport just grows the whole grid/page to match instead of
   overflowing a constrained box, so that failure mode can't recur. No
   `height: 100%`/`overflow-y: auto` here either, for the same reason -
   the panel is free to grow taller than one viewport, its height is just
   whatever its content needs. */
.split-form-page__panel {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: var(--space-5) var(--space-6);
}

@media (min-width: 768px) {
    .split-form-page__panel {
        padding: var(--space-8) var(--space-12);
    }
}

@media (min-width: 1024px) {
    .split-form-page__panel {
        width: 50%;
    }
}

.split-form-page__brand {
    flex-shrink: 0;
    margin-bottom: var(--space-6);
}

/* Smaller than the sitewide header logo (`.brand__logo`, 56-94px) - this
   context is a compact form panel, not a full nav bar, so the full header
   size reads oversized here. Scoped to `.split-form-page__brand` (both
   support.blade.php and start-trial.blade.php use it), not `.brand__logo`
   itself, which would shrink the header logo everywhere else on the
   site. */
.split-form-page__brand .brand__logo {
    height: 44px;
}

@media (min-width: 768px) {
    .split-form-page__brand .brand__logo {
        height: 72px;
    }
}

.split-form-page__content {
    width: 100%;
}

/* This page's one real `<h1>` (see design_system.md's "exactly one real
   h1" rule), but the sitewide `h1` selector assumes hero-banner context
   and sizes for a full-bleed marketing headline - there's no hero banner
   here, so it's resized down to h2 to match this compact form layout,
   same "smaller heading, correct nesting" call as `.article-card__title`
   and others. */
.split-form-page__content h1 {
    font-size: var(--text-h2-size);
    line-height: var(--text-h2-leading);
    margin-bottom: var(--space-6);
}

/* `--space-10`, not the shared `--space-6` above - same reason
   `.start-trial-form__fields` uses `--space-10` for its own gap: the
   first field's floated label lifts up into whatever space precedes it,
   and at `--space-6` that left only ~1px of clearance below the heading
   when that field is focused, reading as the label touching it. Scoped
   via `body-class` (see layouts/split-form.blade.php) since support.blade.php
   shares this same `h1` rule and has no floating labels to protect
   against. */
.start-trial-page .split-form-page__content h1 {
    margin-bottom: var(--space-10);
}

.split-form-page__media {
    display: none;
}

/* `position: sticky` + its own full-viewport height, not `height: 100%`
   (which just matched whatever height the row/flex-stretch happened to
   be) - per instruction, the photo should stay fixed in the viewport
   while the page scrolls, not scroll away with the (often taller) form
   content next to it. Sticky rather than `position: fixed` so it still
   participates in the grid's normal flex layout (width/column position)
   instead of needing to be pulled out of flow and repositioned by hand. */
@media (min-width: 1024px) {
    .split-form-page__media {
        display: block;
        width: 50%;
        position: sticky;
        top: 0;
        height: 100vh;
        height: 100dvh;
    }
}

.split-form-page__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.split-form-page__actions {
    margin-top: var(--space-4);
    text-align: center;
}

/* 16px, not `.btn`'s sitewide `--text-sm` (14px) - scoped to this one
   button via the descendant selector rather than changing `.btn` itself,
   which would resize every button on the site. */
.split-form-page__actions .btn {
    font-size: var(--text-caption-size);
}

/* No `width: 100%` here on purpose - `.btn`/`.btn--primary` are
   content-width pill buttons everywhere else on the site (newsletter
   "Subscribe", CTA banner "Start Free Trial", etc.); stretching this one
   full-width would make it the one button on the site that doesn't match. */

.split-form-page__footnote {
    margin-top: var(--space-3);
    text-align: center;
    color: var(--color-text);
}

/* -- Start Trial form (start-trial.blade.php) -----------------------------------

   Own class family, not `.contact-form` - the design screenshot uses a
   genuinely different field style (a real `<label>` sitting above the
   input, not a `.sr-only` label + placeholder standing in for one) plus
   controls `.contact-form` doesn't have at all (checkbox, full-width
   submit, social buttons) - reusing `.contact-form__field` here would mean
   fighting its placeholder-as-label assumption for every field. */
/* One column, `gap: var(--space-4)` - spacing between fields comes from
   this grid gap now, not a margin on each `.start-trial-form__field`
   (removed below), so the floated label's own overlap into that space
   (see that class's comment) is the only thing determining how close it
   reads to the field above it. */
/* `--space-10`, not `--space-8` - the floated label lifts `1.5em` (27px)
   above its resting position (see `.start-trial-form__field label`
   below), which eats into this gap from below; at `--space-8` (32px)
   that left only ~9px of clearance above the *previous* field's
   underline, reading as the label touching it. `--space-10` restores a
   comfortable ~17px of daylight there without changing the label's own
   lift/scale. */
.start-trial-form__fields {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-10);
}

/* `position: relative` anchors the floating label below. */
.start-trial-form__field {
    position: relative;
}

.start-trial-form__field input {
    display: block;
    width: 100%;
    border: none;
    border-bottom: 1px solid var(--color-border);
    background: none;
    padding: var(--space-1) 0 var(--space-1);
    font: inherit;
    line-height: 1.3;
    color: var(--color-heading);
}

.start-trial-form__field input:focus-visible {
    outline: none;
    border-bottom-color: var(--color-primary);
}

/* Room for the show/hide icon button (see below) so the dots/typed
   password never runs underneath it. */
.start-trial-form__field--password input {
    padding-right: var(--space-6);
}

/* Sits in the password field's own top-right corner, vertically centered
   on the *input* row - `top`/`bottom: 0` matches the field's own height,
   which is exactly the input's height since the label is `position:
   absolute` (out of flow) and the hint sits below the field, not inside
   this box. */
.start-trial-form__password-toggle {
     position: absolute;
    top: 5px;
    bottom: 0;
    right: 5px;
    display: flex;
    align-items: center;
    padding: 0;
    border: none;
    background: none;
    color: var(--color-text-muted);
    cursor: pointer;
    height: max-content;
}

.start-trial-form__password-toggle:hover,
.start-trial-form__password-toggle:focus-visible {
    color: var(--color-primary);
}

.start-trial-form__password-toggle i {
    font-size: var(--text-sm);
}

/* Floating label: rests over the input like a placeholder, then floats up
   into a small 14px (`--text-sm`) label the moment the field is focused
   or already has a value. `label` has to follow `input` in the DOM (see
   Blade) for the `+`/`:not(:placeholder-shown)` selectors below to reach
   it - there's no CSS way to select "the label for this input" the other
   way around - and each input needs `placeholder=" "` (a single space,
   not empty) for `:placeholder-shown` to work at all. `for`/`id` keeps
   the label correctly associated with its input for assistive tech
   regardless of this visual/DOM ordering. Scales via `transform`, not an
   animated `font-size`, per CSS coding standard #6 (animate only
   transform/opacity). */
.start-trial-form__field label {
    position: absolute;
    left: 0;
    top: var(--space-1);
    transform-origin: left top;
    color: var(--color-text-muted);
    line-height: 1.3;
    pointer-events: none;
    transition: transform var(--transition-fast) var(--ease-smooth), color var(--transition-fast) var(--ease-smooth);
}

/* `:not(:placeholder-shown)` first, `:focus` second - equal specificity,
   so source order decides when both apply at once (actively typing into
   a non-empty field); listing `:focus` last means the more prominent
   focused look wins over the plainer filled-but-blurred one in that
   case, rather than the reverse. */
.start-trial-form__field input:not(:placeholder-shown) + label {
    transform: translateY(-1.1em) scale(0.7);
    color: var(--color-text);
}

.start-trial-form__field input:focus + label {
    transform: translateY(-1.5em) scale(0.7);
    color: var(--color-heading);
}

/* Same color as the label text, not `--color-danger` - the screenshot's
   asterisk reads as part of the label, not a validation-error cue. */
.start-trial-form__required {
    color: inherit;
}

.start-trial-form__hint {
    margin: var(--space-1) 0 0;
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

/* Custom `appearance: none` box instead of the native checkbox - the
   screenshot's checkbox is a plain rounded square that doesn't resemble
   any browser's default rendering closely enough to leave as-is. Checked
   state (primary-color fill + a `::after` tick) isn't in the screenshot
   (it only shows unchecked) but is a reasonable, low-risk default rather
   than shipping a checkbox with no visible checked state at all. */
.start-trial-form__checkbox {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    margin: var(--space-5) 0 var(--space-5);
    line-height: 1.3;
    cursor: pointer;
    color: var(--color-body);
}

.start-trial-form__checkbox input[type='checkbox'] {
    appearance: none;
    position: relative;
    flex-shrink: 0;
    width: var(--space-5);
    height: var(--space-5);
    margin: 0.15em 0 0;
    border: 1px solid var(--color-heading);
    border-radius: var(--radius-sm);
    background-color: transparent;
    cursor: pointer;
}

.start-trial-form__checkbox input[type='checkbox']:checked {
    border-color: var(--color-primary);
    background-color: var(--color-primary);
}

.start-trial-form__checkbox input[type='checkbox']:checked::after {
    content: '';
    position: absolute;
    top: 0.15em;
    left: 0.35em;
    width: 0.3em;
    height: 0.6em;
    border: solid var(--color-primary-contrast);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/* Underlined and inheriting the surrounding text color, not the sitewide
   green `a` default - the screenshot's Terms/Privacy links read as plain
   underlined text, not styled as a distinct call-to-action. */
.start-trial-form__checkbox a,
.start-trial-form__disclaimer a {
    color: inherit;
    text-decoration: underline;
}

.start-trial-form__actions {
    margin: 0 0 var(--space-5);
    text-align: center;
}

/* Content-width, matching `.split-form-page__actions .btn` and every
   other `.btn` on the site - not stretched full-width per instruction,
   even though the design screenshot's own Continue button spans the full
   form width. */
.start-trial-form__actions .btn {
    font-size: var(--text-caption-size);
}

.start-trial-form__login {
    margin: 0 0 var(--space-5);
    text-align: center;
    color: var(--color-heading);
}

/* Plain, uncolored, no underline - matches the screenshot's "Login" link,
   distinguished from the surrounding sentence only by being clickable. */
.start-trial-form__login a {
    color: inherit;
    text-decoration: none;
}

.start-trial-form__social {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin-bottom: var(--space-5);
}

@media (min-width: 480px) {
    .start-trial-form__social {
        flex-direction: row;
    }
}

.start-trial-form__social-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    flex: 1;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    background-color: transparent;
    color: var(--color-heading);
    font: inherit;
    line-height: 1.3;
    cursor: pointer;
    transition: border-color var(--transition-fast);
}

.start-trial-form__social-btn:hover,
.start-trial-form__social-btn:focus-visible {
    border-color: var(--color-primary);
}

.start-trial-form__social-btn svg,
.start-trial-form__social-btn i {
    flex-shrink: 0;
    width: 1.125em;
    height: 1.125em;
    font-size: 1.125em;
}

.start-trial-form__disclaimer {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--text-sm);
    line-height: 1.4;
}

/* -- Checkout (checkout.blade.php - placeholder payment step) ------------------

   Deliberate, scoped exception to "components consume tokens, never
   hardcode a color/size" (see CLAUDE.md) - every other page on this site
   uses this project's own design tokens because it's *our* brand; this
   page is a placeholder standing in for Stripe's own Checkout/Payment
   Element UI, so it intentionally borrows Stripe's own publicly-documented
   checkout colors (`#635bff` accent, `#30313d` text, `#6b7280` muted,
   `#0a2540` price) and a plain system-font stack instead of this site's
   serif brand font - the whole point is that it reads as Stripe's
   interface, not this site's. Scoped entirely to `.checkout-*` classes;
   no other page is affected and this site's own token rule still applies
   everywhere else. Not a real Stripe integration - no Stripe.js, no card
   tokenization, nothing submitted anywhere (see the Blade comment). */
.checkout-page {
    margin: 0;
    min-height: 100vh;
    background-color: #ffffff;
    color: #30313d;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
}

.checkout-page__grid {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

@media (min-width: 1024px) {
    .checkout-page__grid {
        flex-direction: row;
    }
}

.checkout-page__summary {
    padding: var(--space-8) var(--space-6);
    background-color: var(--color-tertiary);
}

@media (min-width: 1024px) {
    .checkout-page__summary {
        width: 42%;
        padding: var(--space-12) var(--space-10);
    }
}

.checkout-page__brand {
    display: inline-flex;
    margin-bottom: var(--space-10);
}

/* Smaller than the sitewide header logo, same reasoning as the split-form
   pages' own logo downsize. */
.checkout-page__brand .brand__logo {
    height: 44px;
}

.checkout-page__eyebrow {
    margin: 0 0 var(--space-2);
    font-size: 15px;
    color: #6b7280;
}

.checkout-page__price {
    margin: 0 0 var(--space-8);
    font-size: 32px;
    font-weight: 600;
    color: #0a2540;
}

.checkout-page__price span {
    font-size: 15px;
    font-weight: 400;
    color: #6b7280;
}

.checkout-page__lines {
    margin: 0 0 var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid #e6e6e6;
}

.checkout-page__line {
    display: flex;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-2) 0;
    font-size: 14px;
}

.checkout-page__line dt,
.checkout-page__line dd {
    margin: 0;
    font-weight: 400;
}

.checkout-page__line--muted {
    color: #6b7280;
}

.checkout-page__line--total {
    padding-top: var(--space-4);
    border-top: 1px solid #e6e6e6;
    font-weight: 600;
    font-size: 15px;
}

.checkout-page__note {
    margin: var(--space-6) 0 0;
    font-size: 13px;
    line-height: 1.5;
    color: #6b7280;
}

/* `align-items: center` matters once this column is much wider than the
   capped-width form inside it (any desktop wider than ~1400px) - without
   it the form stays pinned to the left edge, leaving a large dead strip
   of white on the right that reads as broken, not as a deliberately
   centered payment column the way Stripe's own Checkout renders it. */
.checkout-page__payment {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: var(--space-8) var(--space-6);
}

@media (min-width: 1024px) {
    .checkout-page__payment {
        padding: var(--space-12) var(--space-10);
    }
}

.checkout-form {
    max-width: 26rem;
    width: 100%;
}

.checkout-form__field {
    margin-bottom: var(--space-4);
}

.checkout-form__field label,
.checkout-form__group-label {
    display: block;
    margin-bottom: var(--space-1);
    font-size: 13px;
    font-weight: 500;
    color: #30313d;
}

.checkout-form__field input,
.checkout-form__field select {
    display: block;
    width: 100%;
    padding: 10px 12px;
    border: 1px solid #e0e0e0;
    border-radius: 6px;
    background-color: #fff;
    font: inherit;
    font-size: 15px;
    color: #30313d;
}

.checkout-form__field input:focus,
.checkout-form__field select:focus {
    outline: none;
    border-color: #635bff;
    box-shadow: 0 0 0 1px #635bff;
}

/* The email field is a read-only carry-over from the previous step, not a
   real editable field yet - greyed out like Stripe's own "known customer"
   checkout state. */
.checkout-form__field input[readonly] {
    background-color: #f6f9fc;
    color: #6b7280;
}

.checkout-form__group {
    margin-bottom: var(--space-4);
}

/* One bordered container with internal hairline dividers between the
   card number/expiry/CVC sub-fields, not three separate boxes - matches
   Stripe Elements' own grouped-card-field look. */
.checkout-form__card {
    border: 1px solid #e0e0e0;
    border-radius: 6px;
    background-color: #fff;
}

.checkout-form__card:focus-within {
    border-color: #635bff;
    box-shadow: 0 0 0 1px #635bff;
}

.checkout-form__card-row {
    display: flex;
    align-items: center;
    border-bottom: 1px solid #e0e0e0;
}

.checkout-form__card-row:last-child {
    border-bottom: none;
}

.checkout-form__card-row input {
    flex: 1;
    min-width: 0;
    border: none;
    padding: 10px 12px;
    font: inherit;
    font-size: 15px;
    color: #30313d;
}

.checkout-form__card-row input:focus {
    outline: none;
}

.checkout-form__card-row--split input {
    border-right: 1px solid #e0e0e0;
}

.checkout-form__card-row--split input:last-child {
    border-right: none;
}

.checkout-form__card-icons {
    display: flex;
    gap: var(--space-2);
    padding-right: var(--space-3);
    color: #a0a0a0;
    font-size: 22px;
}

.checkout-form__row {
    display: flex;
    gap: var(--space-3);
}

.checkout-form__row .checkout-form__field {
    flex: 1;
    min-width: 0;
}

.checkout-form__submit {
    display: block;
    width: 100%;
    margin-top: var(--space-2);
    padding: var(--space-3);
    border: none;
    border-radius: 6px;
    background-color: #635bff;
    color: #ffffff;
    font: inherit;
    font-size: 16px;
    font-weight: 600;
    cursor: pointer;
    transition: background-color var(--transition-fast);
}

.checkout-form__submit:hover,
.checkout-form__submit:focus-visible {
    background-color: #544ee0;
}

.checkout-form__disclaimer {
    margin: var(--space-4) 0 0;
    font-size: 12px;
    line-height: 1.5;
    color: #6b7280;
}

.checkout-page__footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    max-width: 26rem;
    margin-top: var(--space-8);
}

.checkout-page__powered-by {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: 12px;
    color: #8898aa;
}

.checkout-page__powered-by strong {
    font-weight: 600;
    color: #6b7280;
}

.checkout-page__legal-links {
    display: flex;
    gap: var(--space-3);
    margin: 0;
    font-size: 12px;
}

.checkout-page__legal-links a {
    color: #8898aa;
    text-decoration: none;
}

.checkout-page__legal-links a:hover {
    text-decoration: underline;
}

/* -- About You (about-you.blade.php - profile onboarding step) -----------------

   Own class family, not `.start-trial-form` - the design screenshot's
   labels sit permanently above their inputs with no floating-label
   animation, so this reuses that field's visual language (label above an
   underlined input, small hint text below, same `--space-*` rhythm) without
   importing the floating-label mechanic that doesn't apply here. */
.about-you-form__intro {
    margin: 0 0 var(--space-8);
    color: var(--color-text);
}

.about-you-form__field {
    margin-bottom: var(--space-6);
}

.about-you-form__field label,
.about-you-form__field legend {
    display: block;
    width: 100%;
    margin-bottom: var(--space-2);
    padding: 0;
    border: none;
    color: var(--color-heading);
}

.about-you-form__required {
    color: inherit;
}

.about-you-form__field input,
.about-you-form__field select,
.about-you-form__field textarea {
    display: block;
    width: 100%;
    border: none;
    border-bottom: 1px solid var(--color-border);
    background: none;
    padding: var(--space-2) 0;
    font: inherit;
    color: var(--color-heading);
}

.about-you-form__field input:focus-visible,
.about-you-form__field select:focus-visible,
.about-you-form__field textarea:focus-visible {
    outline: none;
    border-bottom-color: var(--color-primary);
}

/* `resize: vertical` only - matches every other field here reading as a
   single underlined line by default (no horizontal resize handle
   distorting that), while still letting a longer gift message grow. */
.about-you-form__field textarea {
    resize: vertical;
    min-height: 3.25em;
}

/* Muted text + no pointer cursor - same "locked, carried-over field"
   treatment as `.checkout-form__field input[readonly]`, signals this
   isn't editable before a user even tries clicking into it. */
.about-you-form__field input[readonly] {
    color: var(--color-text-muted);
    cursor: default;
}

/* Italic, matching the design screenshot's own hint text treatment -
   distinguishes it from the (upright) label above it at a glance. */
.about-you-form__hint {
    margin: var(--space-2) 0 0;
    font-size: var(--text-sm);
    font-style: italic;
    color: var(--color-text-muted);
}

.about-you-form__select-wrap {
    position: relative;
}

/* Same "appearance: none + Font Awesome chevron" swap as
   `.contact-form__field select`. */
.about-you-form__select-wrap select {
    appearance: none;
    padding-right: var(--space-6);
    cursor: pointer;
}

.about-you-form__select-icon {
    position: absolute;
    right: 0;
    bottom: var(--space-3);
    color: var(--color-text-muted);
    font-size: var(--text-sm);
    pointer-events: none;
}

.about-you-form__field--options {
    margin: 0 0 var(--space-6);
    padding: 0;
    border: none;
}

.about-you-form__options {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3);
}

@media (max-width: 480px) {
    .about-you-form__options {
        grid-template-columns: 1fr;
    }
}

/* Qualified with the `.about-you-form__options` ancestor, not just
   `.about-you-form__option` alone - this is itself a `<label>` nested
   inside `.about-you-form__field--options` (which also carries the base
   `.about-you-form__field` class for its own spacing), so the shared
   `.about-you-form__field label` reset above (`display: block`, higher
   specificity via its extra class+element) would otherwise win and
   collapse this back to a plain stacked label, no pill/flex row. */
.about-you-form__options .about-you-form__option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    cursor: pointer;
}

/* Custom circular radio, same `appearance: none` + hand-drawn indicator
   technique as `.start-trial-form__checkbox input[type='checkbox']` -
   just round instead of square. The checked dot is a smaller filled
   circle inset within the ring (via `box-shadow: inset`, punched out in
   the page's own background color) rather than a separate `::after`
   pseudo-element, since a plain filled circle needs no positioning math
   the way the checkbox's tick mark did. */
.about-you-form__option input[type='radio'] {
    appearance: none;
    flex-shrink: 0;
    width: var(--space-4);
    height: var(--space-4);
    margin: 0;
    border: 1px solid var(--color-heading);
    border-radius: 50%;
    background-color: transparent;
    cursor: pointer;
}

.about-you-form__option input[type='radio']:checked {
    border-color: var(--color-primary);
    background-color: var(--color-primary);
    box-shadow: inset 0 0 0 3px var(--color-tertiary);
}

.about-you-form__actions {
    margin-top: var(--space-8);
    text-align: center;
}

/* -- Review Your Details (review.blade.php - final flow step + Stay Connected) -

   Fields reuse `.contact-form__field` as-is (placeholder-as-label, real
   value pre-filled) - see that page's own comment for why, and
   `.start-trial-form__checkbox`/`.start-trial-form__password-toggle` for
   the two checkboxes and the password show/hide button. Only the page
   shell and heading/intro text below are new. */
.review-page__top {
    padding: var(--space-10) var(--space-6);
}

@media (min-width: 768px) {
    .review-page__top {
        padding: var(--space-16) var(--space-6);
    }
}

/* No `max-width` of its own any more - it carries the sitewide `.container`
   class instead (see the Blade markup), so the header/heading follow the
   same 1312px content width as every other page. The form itself still
   caps to a comfortable reading width via `.review-form` below, centered
   within that wider container. */
.review-form {
    max-width: 34rem;
    margin: 0 auto;
}

.review-form .contact-form__field label {
    display: block;
    margin-bottom: 0;
    color: var(--color-body);
}

.review-page__brand {
    display: inline-flex;
    margin-bottom: var(--space-8);
}

.review-page__brand .brand__logo {
    height: 56px;
}

.review-page__heading {
    text-align: center;
    margin-bottom: var(--space-2);
}

.review-page__intro {
    margin: 0 0 var(--space-10);
    text-align: center;
    color: var(--color-text-muted);
    font-size: var(--text-sm);
}

/* Room for the show/hide icon button, same reasoning as
   `.start-trial-form__field--password input`. */
.review-form__password-field input {
    padding-right: var(--space-6);
}

.review-form__actions {
    margin-top: var(--space-6);
    text-align: center;
}

/* -- Gift Review (gift-review.blade.php - buyer + recipient side by side) -----

   Same `.review-page__top`/`__brand`/`__heading`/`__intro` shell and
   `.contact-form__field`/`.start-trial-form__checkbox`/`.review-form__actions`
   components as review.blade.php above, reused as-is - only this wider
   inner wrapper and the two-column grid are new, since this page reviews
   a buyer *and* a recipient side by side instead of one person. */
.gift-review-page__inner {
    max-width: 54rem;
    margin: 0 auto;
}

/* Logo bar sits in the sitewide `.container` (same max-width as
   `.newsletter-section`/`.site-footer` further down this page), not
   `.gift-review-page__inner` - it needs its own top spacing since
   `.review-page__brand`'s usual `margin-bottom` (see review.blade.php,
   where the logo sits inline above the heading in the same block) would
   double up with `.review-page__top`'s own top padding otherwise. */
.gift-review-page__header {
    padding-top: var(--space-6);
}

@media (min-width: 768px) {
    .gift-review-page__header {
        padding-top: var(--space-8);
    }
}

.gift-review-page__header .review-page__brand {
    margin-bottom: 0;
}

/* Mobile-first: one column (Buyer's Details, then Recipient's Details
   stacked below with a divider borrowed from the top since there's no
   "side" left to put it on yet), two columns with a vertical divider once
   there's room for both side by side. */
.gift-review-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
    margin-bottom: var(--space-8);
}

@media (min-width: 768px) {
    .gift-review-grid {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-10);
    }
}

.gift-review-column--recipient {
    border-top: 1px solid var(--color-border);
    padding-top: var(--space-8);
}

@media (min-width: 768px) {
    .gift-review-column--recipient {
        border-top: none;
        border-left: 1px solid var(--color-border);
        padding-top: 0;
        padding-left: var(--space-10);
    }
}

/* h4 scale, not the default h2/h3 - this is a sub-heading inside an
   already-compact review card, not a section title competing with
   `.review-page__heading` above it. */
.gift-review-column__heading {
    margin: 0 0 var(--space-6);
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
}

/* What's being gifted (plan or course), shown once more above the
   buyer/recipient details - a plain bordered strip, not `.contact-card`'s
   heavier surface fill, since it's a reminder banner rather than its own
   content section. */
.gift-review-summary {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    margin-bottom: var(--space-8);
    padding: var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
}

.gift-review-summary__media {
    flex-shrink: 0;
    width: 64px;
    height: 64px;
    border-radius: var(--radius-md);
    overflow: hidden;
}

.gift-review-summary__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.gift-review-summary__label {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--color-text-muted);
}

.gift-review-summary__title {
    margin: var(--space-1) 0 0;
    font-family: var(--font-heading);
    font-size: var(--text-h4-size);
    line-height: var(--text-h4-leading);
    color: var(--color-heading);
}

.gift-review-summary__price {
    margin: var(--space-1) 0 0;
    color: var(--color-text);
}

/* -- Legal page (terms.blade.php/privacy.blade.php - TOC + long-form copy) ---
   Shared by both pages per instruction ("same design, different text") -
   a sticky table-of-contents sidebar (anchor links into `.legal-page__section`
   ids) next to the actual long-form content. Mobile-first: TOC stacks above
   the content and loses its sticky behavior (a long anchor list pinned
   above endless text would just occupy the screen); the 3fr/5fr side-by-side
   split (pixel-matched to the design) and sticky TOC both start at 1024px,
   same breakpoint `.article-detail__grid`/`.split-form-page__grid` use for
   their own wide/narrow splits. */
.legal-page {
    padding: var(--space-12) 0;
}

@media (min-width: 768px) {
    .legal-page {
        padding: var(--space-16) 0;
    }
}

.legal-page__heading {
    text-align: center;
    margin-bottom: var(--space-10);
}

.legal-page__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
}

/* Fixed, narrower sidebar (was `3fr 5fr`, ~38% of the row) - a TOC of
   short link labels doesn't need to scale up with the row the way the
   long-form content column does. */
@media (min-width: 1024px) {
    .legal-page__grid {
        grid-template-columns: 260px minmax(0, 1fr);
        gap: var(--space-12);
        align-items: start;
    }
}

.legal-page__toc-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* `display: block` so the border-bottom below spans the full item width,
   not just the underline-width of the text - a fixed transparent border
   in the base state (rather than adding it only on `.is-active`) keeps
   the row height constant when the border color turns on, same
   "reserve the space either way" reasoning as `.contact-form__field`'s
   fixed-width underline. */
.legal-page__toc-list a {
    display: block;
    padding-bottom: var(--space-2);
    border-bottom: 1px solid transparent;
    color: var(--color-text);
}

.legal-page__toc-list a:hover,
.legal-page__toc-list a:focus-visible {
    color: var(--color-primary);
}

/* Toggled by the scroll-spy in app.js as each `.legal-page__section`
   scrolls through view. */
.legal-page__toc-list a.is-active {
    border-bottom-color: var(--color-primary);
    color: var(--color-primary);
    font-weight: var(--font-weight-bold);
}

@media (min-width: 1024px) {
    .legal-page__toc {
        position: sticky;
        top: var(--space-6);
        max-height: calc(100vh - var(--space-12));
        overflow-y: auto;
        padding-right: var(--space-6);
        border-right: 1px solid var(--color-border);
    }
}

.legal-page__section {
    /* Anchor-jump target - without this, `#section-id` lands the heading
       flush against the viewport's top edge with no breathing room. */
    scroll-margin-top: var(--space-6);
    padding-bottom: var(--space-8);
    margin-bottom: var(--space-8);
    border-bottom: 1px solid var(--color-border);
}

.legal-page__section:last-child {
    padding-bottom: 0;
    margin-bottom: 0;
    border-bottom: none;
}

.legal-page__section h3 {
    margin: 0 0 var(--space-4);
}

/* Lettered subsections (privacy.blade.php's "A. Information We Process."
   etc.) within a section - the default h4 size already reads as a step
   down from h3, only the extra top margin (separating it from the
   previous block) is needed here. */
.legal-page__section h4 {
    margin: var(--space-6) 0 var(--space-3);
}

.legal-page__section h4:first-child {
    margin-top: 0;
}

.legal-page__section p,
.legal-page__section ul {
    margin: 0 0 var(--space-4);
    color: var(--color-text);
}

.legal-page__section ul {
    padding-left: var(--space-6);
}

.legal-page__section p:last-child,
.legal-page__section ul:last-child {
    margin-bottom: 0;
}
