/* =========================================================================
   Bookmypet marketplace homepage — foundation stylesheet.

   Hand-written and served directly from public/. There is NO build step in
   production: never add this file to vite.config.js. Bump
   \App\Services\Marketplace\MarketplaceAssets::VERSION after editing.

   Every numeric token below was read out of Figma file kyudcw9zAgmniIXWNveugA
   with get_design_context; the Figma node id is cited beside each one. The
   file's PUBLISHED type variables are the mobile app's ramp (they stop at
   H1 = 32px) and are deliberately NOT used here — this page's hero is 82px.

   Layout uses logical properties throughout (margin-inline, padding-inline,
   inset-inline-*) so the Arabic RTL page needs no second stylesheet.
   ========================================================================= */

/* ─ Tokens ─────────────────────────────────────────────────────────────── */
:root {
  /* Colour — from the file's published variables (exact, do not alter) */
  --c-primary:      #1a3a7f;
  --c-accent:       #2ca5e8;
  --c-surface-tint: #eefaff;
  --c-success:      #23bba6;
  --c-ink:          #212428;
  --c-ink-muted:    #4f4f4f;
  --c-bg:           #fafbfc;
  --c-white:        #ffffff;

  /* Colour — literals observed in the design that are NOT published variables.
     Recorded here so later sections stop re-typing raw hex. */
  /* The accent, dark enough to carry white type. Figma fills every solid CTA
     and the compare header with --c-accent #2ca5e8 and sets the label white;
     that pair is 2.74:1, which fails even the 3:1 large-text floor, so no text
     size rescues it and there is no background behind a FILL to treat except
     the fill itself. This is #2ca5e8 held at its own hue (201.4deg) and
     saturation (80%) with lightness taken from 54% to 38% — the lightest
     member of the accent family on which white reaches 4.51:1; 38% is used
     for margin at 4.91:1. Used ONLY where the design draws white on accent:
     .mp-adv-card__cta, .mp-adv-talk__cta, .mp-adv-table thead th. */
  --c-accent-deep:  #1377ae;

  --c-pill-stroke:  #c6d0e5;                 /* mobile hero CTA hairline         971:3160 */
  --c-stat-pill:    #d4f2ff;                 /* provider Rating/Bookings pill    3546:105147 */
  --c-micro:        #525252;                 /* "Rating" / "Bookings" caption    3546:105149 */
  --c-scrim:        rgba(79, 79, 79, 0.75);  /* insight category chip            3546:104736 */
  --c-stat-value:   #000000;                 /* Task 9: "4.9" / "250+" figure — literal "black" in Figma, not --c-ink 3546:105150 */

  /* Type */
  --font-display: "Gotham", "Montserrat", system-ui, sans-serif;
  --font-arabic:  "Tajawal", system-ui, sans-serif;

  /* Display ramp — desktop (1440). Each size is a real measured value. */
  --fs-stat:    128px;  /* "4+" / "100+" numerals                3546:105225, 3546:105230 */
  --fs-stat-sm: 100px;  /* "Offers" (same row, smaller)          3546:105235 */
  --fs-hero:     82px;  /* hero H1 "One marketplace."            3546:104650 */
  --fs-hero-light: 96px;/* services H1 tail, Gotham Light — BIGGER than the bold lead-in  3472:66756 */
  --fs-display:  64px;  /* tile H2 "From trusted care…"          3546:104627 */
  --fs-h1:       60px;  /* "Download the App" / "Partner Platform"  3546:104890, 3546:104878 */
  --fs-h2-lg:    52px;  /* "Pet care, made simpler" + pull quote 3546:104610, 3546:105114 */
  --fs-h2:       48px;  /* standard section heading              3546:105119 */
  --fs-h3:       42px;  /* footer tagline                        3534:80307 */
  --fs-h4:       40px;  /* tile hover-reveal title               3434:4796 */
  --fs-name:     32px;  /* active testimonial name               3546:105035 */
  --fs-lead:     28px;  /* lead paragraphs + stat titles         3546:104611, 3546:105227 */
  --fs-card:     24px;  /* card title / testimonial body         3546:105144, 3546:104750 */
  --fs-nav:      20px;  /* nav links, footer column headings     3530:73516, 3533:80140 */
  --fs-body:     18px;  /* body copy, category chip labels       3546:105228, 3546:104659 */
  --fs-body-sm:  16px;  /* footer links, insight excerpt         3534:80330, 3546:104751 */
  --fs-label:    14px;  /* meta, address, "Verified" badge       3546:105145, 3546:105141 */
  --fs-micro:    12px;  /* stat-pill caption                     3546:105149 */

  /* Line height. Figma reports "normal" on every heading — Gotham resolves
     that to ~1.16; body copy carries explicit multipliers. */
  --lh-heading: 1.16;
  --lh-body:    1.6;   /* 18px body                             3546:105228 */
  --lh-tight:   1.2;   /* 14/16px meta + footer links           3546:104746, 3534:80330 */
  --lh-badge:   1.4;   /* "Verified" pill                       3546:105141 */

  /* Font weights actually present on this page (Gotham style names) */
  --fw-light:  300;  /* Gotham Light  — insight excerpt          3546:104751 */
  --fw-book:   400;  /* Gotham Book   — default body */
  --fw-medium: 500;  /* Gotham Medium — buttons, links           3530:73522 */
  --fw-bold:   700;  /* Gotham Bold   — every heading            3546:104650 */

  /* Space. Content band runs x=60→1380 on the 1440 frame: 1320 of content
     inside a 60px gutter (confirmed by the px-[60px] on the stats row
     3546:105223 and the footer rows 3534:80305 / 3534:80310). */
  --container: 1640px;
  --gutter:      60px;

  /* Vertical rhythm. Measured frame-to-frame gaps on the desktop node were
     197px (providers→app 3546:105116→3546:104882) and 193px (app→insights
     3546:104882→3546:104699) between the auto-layout sections whose bounding
     boxes are tight to content — i.e. ~96px of padding on each side. Bands
     that read as one composite unit sit closer (stats→categories 68px,
     categories→providers 120px); those use --space-section-sm. */
  --space-section:    96px;
  --space-section-sm: 60px;

  /* Repeating intra-section measurements every carousel section shares. */
  --space-heading: 32px;  /* heading row → card grid   3546:105117 h58 → grid y90 */
  --gap-grid:      62px;  /* card gutter, also chip gutter  3546:105132 x60 → 3546:105155 x490 */
  --card-width:   368px;  /* provider + insight card        3546:105132 */
  --arrow-size:    52px;  /* carousel arrow button          3546:105120 */
  --arrow-gap:     20px;  /* between the two arrows         3546:104704 → 3546:104709 */

  /* Radius. 108px is the signature large radius: hero bottom corners
     (3546:104634), the tile grid's outer corners (3546:104626) and the
     footer's top corners (3534:80469) all use it. */
  --radius-lg:   108px;
  --radius-nav:   32px;  /* nav bar bottom corners          3530:73524 */
  --radius-md:    12px;  /* active testimonial card         3546:105029 */
  --radius-sm:    10px;  /* card image + stat pill          3546:105137, 3546:105147 */
  --radius-pill: 100px;  /* buttons; chips use 74px, badges 28px — all fully round  3530:73520 */

  /* Shadow */
  --shadow-card:        0 0 19.2px 0 rgba(0, 0, 0, 0.25);  /* card image      3546:105137 */
  --shadow-nav:         0 4px 6.45px rgba(0, 0, 0, 0.13);  /* nav bar         3530:73524 */
  --shadow-testimonial: 0 0 12.2px 0 rgba(0, 0, 0, 0.77);  /* active review   3546:105029 */

  /* Gradients. The design paints most large headings with clipped gradient
     text, so these belong to the token layer rather than to one section. */
  --grad-heading:     linear-gradient(109.58deg, #1a3a7f 1.76%, #2ca5e8 53.96%);   /* 3546:104610 */
  --grad-heading-alt: linear-gradient(90deg, #1a3a7f 0%, #2ca5e8 100%);            /* 3546:104890 */
  --grad-stat:        linear-gradient(270deg, #2ca5e8 49.82%, #1a3a7f 100%);       /* 3546:105225 */
  --grad-testimonial: linear-gradient(228.94deg, #1a3a7f 5.71%, #2ca5e8 98.07%);   /* 3546:105029 */
  --grad-hero-scrim:  linear-gradient(107.1deg, rgba(31, 45, 51, 0.39) 15.12%,
                                                rgba(212, 242, 255, 0.09) 47.87%,
                                                rgba(26, 58, 127, 0.56) 86.69%);   /* 3546:104641 */
  /* Task 7: the tile hover scrim. Identical value confirmed on all three photo
     tiles (only the rounded corner differs per tile position, and that's
     handled by the grid parent's own clip, not per-tile) — 3434:5481 (cashback),
     3434:5485 (trusted), 3434:4794 (discover).

     Stop positions are 100-minus-raw, NOT the raw get_design_context export
     (which reads 80.706% / 40.054%, in that order). Fed straight into CSS,
     out-of-order stops get clamped to a hard-edged wedge (verified by
     rasterising the raw value — 80% flat near-transparent light, then a
     sharp cut to dark in the last ~19%) instead of the smooth diagonal
     light-to-dark blend the Figma screenshot of 3434:5480 actually shows
     (light top corner → dark where the white heading/body sits). Inverting
     both positions while keeping the original colour order reproduces that
     smooth blend (confirmed the same way, by rasterising this exact value)
     — this is a known Figma-export quirk, not a design change; do not "fix"
     it back to the raw percentages. */
  --grad-tile-scrim:  linear-gradient(226.02deg, rgba(212, 242, 255, 0.09) 19.294%,
                                                 rgba(34, 34, 34, 0.83) 59.946%);

  /* Motion — one rhythm for the whole page (spec §11.4) */
  --dur: 220ms;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* Layered z-index scale; never arbitrary values */
  --z-sticky: 100;
  --z-nav:    200;
  --z-modal:  300;
}

/* Tablet / small-laptop band. INTERPOLATED, NOT MEASURED — the Figma file only
   contains a 1440 frame and a 390 frame, so nothing between them is designed.
   Without this band the 82px hero and the 128px stat numerals overflow every
   viewport from 769px to 1439px, and thirteen sections would each invent their
   own fix. Sizes here are the desktop ramp scaled ~0.72; the gutter steps down
   with them. Re-measure nothing against this block — it is a guard rail. */
@media (max-width: 1200px) {
  :root {
    --gutter: 40px;

    --fs-stat:    92px;
    --fs-stat-sm: 72px;
    --fs-hero:    58px;
    --fs-hero-light: 68px;    /* keeps the services H1's 96:82 ratio */
    --fs-display: 46px;
    --fs-h1:      44px;
    --fs-h2-lg:   38px;
    --fs-h2:      36px;
    --fs-h3:      32px;
    --fs-h4:      30px;
    --fs-name:    26px;
    --fs-lead:    22px;
    --fs-card:    20px;

    --space-section:    64px;
    --space-section-sm: 40px;
    --gap-grid:         32px;

    --radius-lg: 72px;
  }
}

/* Mobile ramp — read from the 390px node 3417:1306. Only the tokens that
   actually change are redeclared; --container stays put so the container
   simply fills the viewport minus the smaller gutter. */
@media (max-width: 768px) {
  :root {
    --gutter: 24px;             /* content x=24, width 342        3417:1982 */

    /* Both re-measured against 3417:1306 for Task 8 (were 80px, interpolated):
       3417:2811/2816/2821 all read text-[86px] — including "Offers", which is
       SMALLER than the numerals on desktop (--fs-stat-sm 100 vs --fs-stat 128)
       but identical to them on mobile. There is only one mobile figure size. */
    --fs-stat:    86px;
    --fs-stat-sm: 86px;
    --fs-hero:    32px;         /* 3417:1986 */
    --fs-hero-light: 37px;      /* keeps the services H1's 96:82 ratio  3550:119467 */
    --fs-display: 24px;
    --fs-h1:      24px;
    --fs-h2-lg:   24px;
    --fs-h2:      24px;         /* 3417:2224, 3417:2944 */
    --fs-h3:      22px;
    --fs-h4:      20px;
    --fs-name:    20px;
    --fs-lead:    18px;
    --fs-card:    20px;         /* 3417:2969 */
    --fs-nav:     16px;
    --fs-body:    14px;         /* 3417:2225 */
    --fs-body-sm: 14px;
    --fs-label:   12px;
    --fs-micro:   11px;

    /* Mobile section gaps are a uniform 63px between every band on
       3417:3269 (categories→stats→providers→app→insights→quote→reviews). */
    --space-section:    32px;
    --space-section-sm: 32px;

    --space-heading: 16px;
    --gap-grid:      16px;      /* 3417:2957 x24 → 3417:3049 x326 minus 240 card + peek */
    --card-width:   240px;      /* 3417:2957 */

    --radius-lg: 40px;
  }
}

[dir="rtl"] { --font-display: var(--font-arabic); }

/* ─ Fonts ──────────────────────────────────────────────────────────────── */
/* Self-hosted .otf, matching website_tenant. Deliberately NOT converted to
   WOFF2 (spec §4.3) — consistency with the tenant site was chosen over the
   ~60% transfer saving. Only the four weights the design actually uses ship.

   These `src` URLs carry NO `?v=` query string, and must not gain one. The
   layout preloads Gotham-Bold.otf with `<link rel="preload" as="font" ...>`
   using this exact same unversioned URL. Browser preload-cache matching is
   keyed on the full request URL, so if a query string were added to only
   one side, the preloaded fetch and this @font-face fetch would be treated
   as two different resources — the browser would download the ~167KB file
   twice instead of reusing the preload, which is worse than not preloading
   at all. Cache-busting is reserved for site.css/site.js (hand-edited text,
   versioned via `?v={{ MarketplaceAssets::VERSION }}` in the layout); fonts
   and images are immutable binaries identified by filename and are never
   versioned. Do not "fix" a future cache issue here by adding `?v=1` — that
   reintroduces this exact bug the next time VERSION bumps and this file
   isn't hand-edited to match. See layout.blade.php's preload comments. */
@font-face {
  font-family: "Gotham";
  src: url("/marketplace/fonts/Gotham-Light.otf") format("opentype");
  font-weight: 300; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Gotham";
  src: url("/marketplace/fonts/Gotham-Book.otf") format("opentype");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Gotham";
  src: url("/marketplace/fonts/Gotham-Medium.otf") format("opentype");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Gotham";
  src: url("/marketplace/fonts/Gotham-Bold.otf") format("opentype");
  font-weight: 700; font-style: normal; font-display: swap;
}

/* ─ Reset ──────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--c-bg);
  color: var(--c-ink);
  font-family: var(--font-display);
  font-size: var(--fs-body);
  font-weight: var(--fw-book);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  /* The design has decorative ellipses that bleed past the viewport
     (3546:104517, 3546:104630). `clip` contains them WITHOUT making <body> a
     scroll container — `overflow-x: hidden` would, and that silently breaks
     `position: sticky` on the nav Task 6 has to build. */
  overflow-x: clip;
}
img, svg { max-width: 100%; display: block; }
h1, h2, h3, h4 { text-wrap: balance; margin: 0; font-weight: var(--fw-bold); line-height: var(--lh-heading); }
p { text-wrap: pretty; margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
a { color: inherit;   /* No underline anywhere by default. The reset previously set only
     `color: inherit`, so every anchor lacking an explicit rule inherited the
     UA underline — which is why nav items rendered underlined. Underlines are
     now opt-in per component rather than opt-out. */
  text-decoration: none;
}
button { font: inherit; color: inherit; }

/* ─ Primitives ─────────────────────────────────────────────────────────── */
/* border-box means the gutter eats into max-width, so the cap has to include
   both gutters or every section would render 2 x 60px too narrow. */
.mp-container {
  max-width: calc(var(--container) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.mp-section { padding-block: var(--space-section); }
.mp-section--tight { padding-block: var(--space-section-sm); }

/* Two-tone section headings: "Explore <em>Trusted Providers</em>" */
.mp-h2 { font-size: var(--fs-h2); font-weight: var(--fw-bold); color: var(--c-primary); }
/* Task 13 contrast fix — --c-accent (#2ca5e8) measured 2.65:1 on --c-bg and
   2.74:1 on white, both under the 3:1 large-text floor (this heading is
   36-48px bold at every breakpoint — 24px is the smallest, still clear of
   the 18.66px-bold "large text" threshold — across all four sections that
   use this class: providers, insights, testimonials, clients, all
   confirmed <em> usages, none downgraded to small text by their own
   selectors). Task 13 fixed it to #236fb3 (the 50% midpoint of
   --grad-heading-alt), which measures 5.07:1 / 5.26:1 — technically fine,
   but over-shot: that clears the STRICTER 4.5:1 body-text floor this large
   text never needed, and reads as a visibly duller blue than Figma's own
   #2ca5e8 (confirmed live via get_design_context on 3546:105119, which
   returns the "Trusted Providers" span literally styled text-[#2ca5e8] —
   the raw design value itself is what fails, not a transcription error).

   Fidelity pass (fidelity-testimonials-report.md) — re-picked the value
   CLOSEST to Figma's #2ca5e8 that still clears 3:1 with a small safety
   margin, instead of the nearest-round-token shortcut Task 13 took.
   Interpolated linearly from #2ca5e8 toward --c-primary (#1a3a7f, the
   gradient's own dark stop) in 1% steps, computing real WCAG contrast at
   each step (sRGB relative-luminance script, same method Task 11's own
   focus-ring fix used) until crossing 3:1 with margin:
     #2ca5e8 (Figma, t=0%):  2.744:1 white / 2.648:1 --c-bg  (fails)
     t=15%  #2995d8:         3.289:1 white / 3.175:1 --c-bg
     t=19%  #1f92d4:         3.431:1 white / 3.312:1 --c-bg  <- chosen
     t=26%  #236fb3 (old):   5.255:1 white / 5.072:1 --c-bg  (over-shot)
   #1f92d4 clears 3:1 on both backgrounds this class ever paints over
   (page --c-bg and white) with an ~11-14% margin — enough to stay solid
   under rounding/anti-aliasing without the old fix's 75-99% overshoot —
   and it is visibly the same cyan family as Figma's own value, not a
   distinct "safe blue". */
.mp-h2 em { font-style: normal; color: #1f92d4; }

/* Several headings are painted with clipped gradient text rather than a flat
   colour (3546:104610, 3546:104890, 3546:104878, 3546:105225). */
.mp-text-gradient {
  background-image: var(--grad-heading);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}
/* Task 10: same clip mechanics, the OTHER gradient — "Download the App"
   (3546:104890) and Task 11's "Explore the Partner Platform" (3546:104878)
   both cite --grad-heading-alt already; a modifier keeps both on the one
   clip-text utility instead of a second near-duplicate class. */
.mp-text-gradient--alt { background-image: var(--grad-heading-alt); }
/* Fidelity pass (task 14): the quote band's highlighted phrases alternate
   gradient DIRECTION (navy->blue, then blue->navy, then navy->blue, ...) —
   get_design_context on 3546:105114 confirmed literal reversed stops per
   span, not just repeats of --grad-heading-alt. Same two colours, same
   clip mechanics, so this is a third modifier on the shared utility
   rather than a new class. */
.mp-text-gradient--reverse { background-image: linear-gradient(90deg, #2ca5e8 0%, #1a3a7f 100%); }

/* Focus is never removed — only restyled. No border-radius is forced here: the
   outline already follows the element's own radius, and overriding it would
   square off the --radius-pill buttons the moment they take focus. */
:where(a, button, [tabindex]):focus-visible {
  outline: 3px solid var(--c-accent);
  outline-offset: 3px;
}

/* A link with no configured destination renders disabled, never href="#" (§8) */
/* No opacity knock-down. Product owner: "all items should be enabled even if
   doesn't have link" — a marketing site where half the chips, the social row
   and the store badges render at 45% reads as broken rather than as
   "destination pending", and most of these WILL get a URL.
   Still not interactive, and still announced as aria-disabled by the
   component, so nothing claims to be a working link that is not one; only the
   greying-out is gone. `cursor: default` keeps the pointer from promising a
   click the element cannot honour. */
.mp-link--disabled {
  pointer-events: none;
  cursor: default;
}


.mp-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* =========================================================================
   Task 6 — Navigation and hero (§5 rows 1–2)

   Figma kyudcw9zAgmniIXWNveugA. Node ids cited per rule. Two nodes in this
   section have raw get_metadata x/y that do NOT match their rendered
   position — both are children of a full-bleed background rectangle that
   uses a rotated image fill, a known Figma export quirk. Where that applies,
   the value below was measured from a get_screenshot render instead (pixel
   bounding box of the bright/opaque region), and the comment says so.

   Review fix (finding 1): the same rotation quirk also corrupted the scroll
   arrow's own VECTOR PATH, not just node position — the first pass hand-
   copied the path out of raw node/layer metadata that still carried the
   rotation transform, shipping a sideways arrow. The genuine, correctly-
   oriented path/viewBox comes from download_assets' whole-node `export`
   output (not the individual-layer `svgAssets` entry, which can carry the
   same baked rotation — confirmed true for the mobile arrow sub-layer,
   3417:1987, though not for the desktop one). See hero.blade.php's inline
   comment and the fix report for the bounding-box proof.
   ========================================================================= */

[x-cloak] { display: none !important; }

/* ─ Skip link ──────────────────────────────────────────────────────────── */
/* Reuses .mp-visually-hidden's clip technique via its own rule (needs a
   different positioning strategy: fixed + off-canvas, not the 1x1px clip). */
.mp-skip {
  position: fixed;
  inset-inline-start: -9999px;
  inset-block-start: 0;
  z-index: var(--z-modal);
  padding: 0.75rem 1.25rem;
  background: var(--c-primary);
  color: var(--c-white);
  border-end-end-radius: var(--radius-sm);
}
.mp-skip:focus-visible {
  inset-inline-start: var(--gutter);
  inset-block-start: 1rem;
}

/* ─ Nav ────────────────────────────────────────────────────────────────── */
/* Two variants of the SAME component, confirmed via get_design_context:
     Default  (3530:73524, instanced 3546:105248) — transparent, white text
               and icons, white logo. Used over the hero.
     Variant2 (instanced 3530:73602, sits on reference page 3434:4693) —
               white background, --c-primary text/icons, colour logo.
   Both variants share the identical bottom-radius-32 + drop-shadow geometry
   (--radius-nav / --shadow-nav, both extracted from this exact node by
   Task 3); on Default it's invisible only because the fill is transparent.
   This is the confirmed condensed state — see nav.blade.php's header
   comment for why it's a flush bar, not the brief's illustrative pill. */
.mp-nav {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: var(--z-nav);
}

.mp-nav__inner {
  position: relative;                     /* containing block for ::before below */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 36px;                              /* 3530:73572 */
  max-width: var(--container);
  margin-inline: auto;
  padding: 30px;                          /* 3530:73524 p-[30px], both variants */
  color: var(--c-white);
  border-end-start-radius: var(--radius-nav);
  border-end-end-radius: var(--radius-nav);
}

/* Review finding 2 fix: the white fill + drop-shadow used to live directly
   on .mp-nav__inner and transition background-color/box-shadow — neither is
   opacity or transform, violating this project's "animate opacity/transform
   only" rule (the same rule this task's own report cited to reject the
   brief's pill sketch, one line away from the violation). Fix: park the fill
   + shadow on a stacked ::before and crossfade ONLY its opacity. `color` is
   hard-swapped (no transition at all) rather than faded — the logo/icon
   <img> pairs below already hard-swap via `display`, so a fading text colour
   next to snapping icons would look inconsistent even if animating color
   were allowed. */
.mp-nav__inner::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--c-white);
  border-end-start-radius: inherit;
  border-end-end-radius: inherit;
  box-shadow: var(--shadow-nav);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}

/* Task 13 contrast fix — the global default focus ring (:where(a, button,
   [tabindex]):focus-visible, outline: var(--c-accent)) measures 2.65:1 on
   --c-bg and 2.74:1 on white, both under the 3:1 WCAG 1.4.11 floor for a
   UI-component boundary. That default is fine over the hero's dark photo
   (large contrast either way) and over --c-primary-backed contexts like the
   footer (--c-accent on --c-primary measures 3.91:1, already clears 3:1 —
   left alone), but the bar goes OPAQUE WHITE in both the condensed
   (scrolled) and open (mobile menu) states (.mp-nav__inner::before opacity:
   1, two rules above), and .mp-nav__panel is unconditionally white too — so
   the logo link, language link, and burger button all get the same
   accent-on-white failure once either state is active. --c-primary on
   white/--c-bg measures 10.7:1 / 10.4:1, so it's the fix, not a new color:
   the same token already used for this bar's own condensed text/icon
   swap two rules below. */
.mp-nav.is-condensed .mp-nav__inner :where(a, button, [tabindex]):focus-visible,
.mp-nav.is-open .mp-nav__inner :where(a, button, [tabindex]):focus-visible,
.mp-nav__panel :where(a, button, [tabindex]):focus-visible {
  outline-color: var(--c-primary);
}

/* Opening the mobile panel also solidifies the bar — an open, opaque
   dropdown hanging off a transparent bar (still showing hero content behind
   the logo/burger) read as broken, so treat "open" like "condensed" for the
   bar's own paint. Beyond the brief's literal spec, called out in the
   report. */
.mp-nav.is-condensed .mp-nav__inner,
.mp-nav.is-open .mp-nav__inner {
  color: var(--c-primary);
}
.mp-nav.is-condensed .mp-nav__inner::before,
.mp-nav.is-open .mp-nav__inner::before {
  opacity: 1;
}

.mp-nav__logo { display: inline-flex; }
.mp-nav__logo-img { display: inline-block; block-size: 32px; inline-size: auto; }
.mp-nav__logo-img--condensed { display: none; }
.mp-nav.is-condensed .mp-nav__logo-img--default,
.mp-nav.is-open .mp-nav__logo-img--default { display: none; }
.mp-nav.is-condensed .mp-nav__logo-img--condensed,
.mp-nav.is-open .mp-nav__logo-img--condensed { display: inline-block; }

.mp-nav__links {
  display: flex;
  align-items: center;
  gap: 36px;
}

.mp-nav__link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-display);
  font-size: var(--fs-nav);
  font-weight: var(--fw-book);
  white-space: nowrap;
}
.mp-nav__lang { font-weight: var(--fw-medium); }

.mp-nav__icon { display: inline-flex; align-items: center; justify-content: center; }
.mp-nav__icon-img { display: block; block-size: 18px; inline-size: auto; }
.mp-nav__icon-img--condensed { display: none; }
.mp-nav.is-condensed .mp-nav__icon-img--default,
.mp-nav.is-open .mp-nav__icon-img--default { display: none; }
.mp-nav.is-condensed .mp-nav__icon-img--condensed,
.mp-nav.is-open .mp-nav__icon-img--condensed { display: block; }

.mp-btn--pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-block-size: 44px;
  padding: 12px 20px;
  background: var(--c-accent);
  color: var(--c-white);
  border-radius: var(--radius-pill);
  font-family: var(--font-display);
  font-size: var(--fs-body);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  transition: transform var(--dur) var(--ease);
}
.mp-btn--pill:not(.mp-link--disabled):hover { transform: translateY(-2px); }

/* Burger — hidden until the nav collapses at 768px. Bars are drawn with
   `currentColor` so they inherit the same white/--c-primary swap as the
   text; the open state morphs them into an X with transform only. */
.mp-nav__burger {
  display: none;
  align-items: center;
  justify-content: center;
  min-inline-size: 44px;
  min-block-size: 44px;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  color: inherit;
  cursor: pointer;
}
.mp-nav__burger-bars { position: relative; inline-size: 22px; block-size: 16px; }
.mp-nav__burger-bars span {
  position: absolute;
  inset-inline: 0;
  block-size: 2px;
  background: currentColor;
  border-radius: 1px;
  transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.mp-nav__burger-bars span:nth-child(1) { inset-block-start: 0; }
.mp-nav__burger-bars span:nth-child(2) { inset-block-start: 7px; }
.mp-nav__burger-bars span:nth-child(3) { inset-block-start: 14px; }
.mp-nav.is-open .mp-nav__burger-bars span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.mp-nav.is-open .mp-nav__burger-bars span:nth-child(2) { opacity: 0; }
.mp-nav.is-open .mp-nav__burger-bars span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.mp-nav__panel { display: none; }

@media (max-width: 768px) {
  .mp-nav__links { display: none; }
  .mp-nav__burger { display: inline-flex; }
  .mp-nav__inner { padding-block: 16px; padding-inline: var(--gutter); }  /* 3417:1917 px-[24px] py-[16px] */

  .mp-nav__panel {
    display: flex;
    flex-direction: column;
    gap: 4px;
    position: absolute;
    inset-inline: 0;
    inset-block-start: 100%;
    margin-inline: var(--gutter);
    margin-block-start: 8px;
    padding: 12px;
    background: var(--c-white);
    color: var(--c-primary);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-nav);
  }
  .mp-nav__panel-link {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 8px;
    min-block-size: 44px;
    font-family: var(--font-display);
    font-size: var(--fs-nav);
  }
  .mp-nav__panel-cta { justify-content: center; margin-block-start: 4px; }

  .mp-nav__panel--enter,
  .mp-nav__panel--leave { transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease); }
  .mp-nav__panel--enter-start,
  .mp-nav__panel--leave-end { opacity: 0; transform: translateY(-6px); }
  .mp-nav__panel--enter-end,
  .mp-nav__panel--leave-start { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .mp-nav__inner::before,
  .mp-nav__burger-bars span,
  .mp-btn--pill { transition: none; }
}

/* ─ Hero ───────────────────────────────────────────────────────────────── */
/* Backdrop 3546:104634 (1440x1056 — get_screenshot's rendered size; its own
   get_metadata reports a stale 991). download_assets on this SAME node is
   what Task 3 exported as hero.jpg, so the scrim gradient (3546:104641,
   already --grad-hero-scrim) and the desktop scroll circle+arrow
   (3546:104642) are already painted INTO the photo — confirmed by sampling
   hero.jpg's own pixels (bottom corners are opaque (255,255,255), i.e. the
   image's own baked "outside the rounded rect" fill, and a bright circular
   blob sits exactly where 3546:104642 says it should). Painting either of
   those again in CSS would double them. This rule therefore only clips the
   photo's corners and lays real text + a real (transparent) focus target on
   top — it does not redraw content the photo already has. */
.mp-hero {
  position: relative;
  overflow: clip;
  /* Contain the grain layer's mix-blend-mode to this section. Without an
     explicit stacking context it would blend against whatever is painted behind
     the hero, which is not what "grain over the hero" means. */
  isolation: isolate;
  /* NOT aspect-ratio: 1440 / 1056.
     That ties hero HEIGHT to viewport WIDTH, so the hero grew taller the wider
     the screen got — 1408px at 1920, 1877px at 2560 — regardless of how short
     the window actually was. The result was a screen-and-a-half of empty
     gradient before any copy appeared. Figma's 1440x1056 is a canvas size, not
     a rule that the hero must outgrow the viewport.
     Fill the viewport instead, capped at the design's own height so it never
     exceeds what was drawn, with a floor for very short windows. svh (not vh)
     so mobile browser chrome doesn't push the bottom out of reach. */
  block-size: min(100svh, 1056px);
  min-block-size: 480px;
  background-color: var(--c-primary);
  /* --hero-img is defined inline in the layout so the URL can carry the asset
     VERSION. A static stylesheet cannot interpolate it, and without a version
     the hero image is permanently uncacheable-bustable — editing hero.jpg would
     never reach anyone holding a cached copy. Going through one custom property
     also guarantees this URL and the <link rel="preload"> stay byte-identical,
     which is the actual requirement (a mismatch double-fetches). */
  /* hero.jpg is now node 3546:104635 ONLY — the base gradient/grain. It used to
     be the parent 3546:104634, which also contains 3546:104642: the scroll
     circle. That is why the control appeared twice (once painted into the photo,
     once as the real element) and why removing it by retouching the JPEG kept
     leaving a ghost ring on the gradient.
     The scrim that used to come baked in with it (3546:104641) is layered back
     on here as the gradient token Task 3 had already extracted. */
  /* hero.jpg stays as the still fallback: it is what shows before the video
     starts, with JS off, and under reduced motion. The scrim moved to ::after
     so it can paint OVER the video too — as a background layer it would sit
     under the <video> element and do nothing. */
  background-image: var(--hero-img);
  background-size: cover;
  background-position: center;
  border-end-start-radius: var(--radius-lg);
  border-end-end-radius: var(--radius-lg);
}

/* ─ Hero background video ─────────────────────────────────────────────────
   Sits between the still fallback (the section's background-image) and the
   scrim. .mp-hero already has overflow:clip, so the bottom corner radius clips
   it without any extra work. Fades in once it actually starts playing, so a
   slow connection shows the still rather than a black rectangle. */
.mp-hero__video {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  z-index: 0;
  opacity: 0;
  transition: opacity 600ms var(--ease);
  pointer-events: none;
}

.mp-hero__video.is-playing { opacity: 1; }

/* The scrim, over the video. Was a background layer, which could only ever sit
   UNDER the <video>. */
.mp-hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--grad-hero-scrim);
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  /* Nothing is fetched at all in this case (site.js bails before assigning a
     src), so this only guarantees the element can never appear. */
  .mp-hero__video { display: none; }
}

/* ─ Film grain / TV static ────────────────────────────────────────────────
   A 96px noise tile repeated across an oversized layer that jumps position
   every frame. Moved with transform, NOT background-position, so it stays on
   the compositor and honours the project's animate-transform/opacity-only rule.

   The tile is SPARSE and has an alpha channel: ~70% of its pixels are fully
   transparent, and the rest are bright blue or orange speckles. That sparseness
   is the whole trick. Two earlier versions covered every pixel — greyscale
   first, then full-coverage chroma — and a fully-covered layer has an average
   colour, so at any usable opacity it reads as a flat blue WASH over the hero
   rather than as static. Leaving most pixels clear lets the video through
   between the speckles, which is what makes them read as individual grains.

   Blended normally, not with `overlay`. A full-coverage tile needed `overlay`
   to integrate with the backdrop; an alpha tile must composite plainly or the
   speckles get averaged back into the same wash.

   steps(1) on each keyframe segment is what makes it read as static rather than
   a drifting texture — the tile teleports instead of sliding. The layer is 200%
   and inset -50% so a jump can never expose an edge. */
.mp-hero__grain {
  position: absolute;
  /* 150%, NOT 200%. This layer is promoted to its own compositor texture by
     will-change below, and it sits at z-index 4 directly over a PLAYING VIDEO,
     re-compositing every 80ms (steps(1) across 10 keyframes / 800ms). At 200%
     square that texture is 4x the hero's area — ~8.3 megapixels on a 1920x1080
     viewport — and re-compositing it 12x a second starves the video decoder of
     GPU bandwidth. The reported symptom was the hero video visibly freezing.

     150% is the smallest size that still covers the hero at every keyframe,
     and the bound is arithmetic, not taste. translate percentages resolve
     against the ELEMENT's own size, so an element of width k*H (H = hero width)
     translating by at most f=0.15 moves 0.15*k*H, while its overhang per side
     is (k-1)/2*H. Coverage needs (k-1)/2 >= 0.15k, i.e. k >= 1.43. k=1.5 holds
     with margin: overhang 0.25H vs a worst-case excursion of 0.225H, so the
     tiled field still reaches both edges at every step.

     Area drops from 4.0x to 2.25x — 44% fewer composited pixels per tick —
     with no visual change to the effect. */
  inset: -25%;
  inline-size: 150%;
  block-size: 150%;
  /* Topmost in the hero — the grain falls over the copy and the scroll control
     too, not just the video. That is how the design reads: the headline carries
     visible grain, and the scroll disc is textured like everything around it.
     Underneath the copy it looked wrong for a subtle reason — the disc was the
     only clean shape in a grainy field, which made a 10%-white circle read as a
     solid grey plate. pointer-events:none keeps the links clickable through it. */
  z-index: 4;
  background-image: url("/marketplace/img/grain.webp");
  background-repeat: repeat;
  opacity: 0.45;
  pointer-events: none;
  will-change: transform;
  animation: mp-grain 800ms steps(1) infinite;
}

@keyframes mp-grain {
  0%   { transform: translate(0, 0); }
  10%  { transform: translate(-5%, -5%); }
  20%  { transform: translate(-10%, 5%); }
  30%  { transform: translate(5%, -10%); }
  40%  { transform: translate(-5%, 15%); }
  50%  { transform: translate(-10%, 5%); }
  60%  { transform: translate(15%, 0); }
  70%  { transform: translate(0, 10%); }
  80%  { transform: translate(-15%, 0); }
  90%  { transform: translate(10%, 5%); }
  100% { transform: translate(5%, 0); }
}

@media (prefers-reduced-motion: reduce) {
  /* Keep the texture — it is part of the hero's look — just stop it moving.
     A flickering full-bleed layer is exactly what this preference is for. */
  .mp-hero__grain { animation: none; }
}

.mp-hero__inner {
  position: relative;
  z-index: 3;                     /* above video (0), scrim (1), grain (2) */
  block-size: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  /* Shared by the copy block's own padding and the scroll target's inset so
     the two stay baseline-aligned, matching the ~2px-apart measurement of
     3546:104646 (copy, ends y=881 of 1056) vs the screenshot-measured circle
     (ends y=879 of 1056) → ~12% of the hero's own inline size. */
  /* Figma puts the copy block's bottom 175px above the base of a 1056-tall hero
     — 16.6% of the hero's HEIGHT. It was expressed as 12%, which is the same
     175px at 1440 but is a percentage of WIDTH (padding percentages always
     resolve against inline size), so on a 1920 screen it became 230px and shoved
     the copy well up the hero. svh tracks height instead; the cap keeps it at
     the drawn 175px once the hero stops growing at 1056. */
  --hero-pad-end: clamp(70px, 16.6svh, 175px);
  padding-block-end: var(--hero-pad-end);
}

.mp-hero__eyebrow {
  max-inline-size: 784px;                 /* 3546:104647 */
  margin-block-end: 18px;                 /* gap-[18px], 3546:104648 */
  color: var(--c-white);
  font-family: var(--font-display);
  font-weight: var(--fw-book);
  font-size: var(--fs-card);              /* 24px, 3546:104649 */
  line-height: var(--lh-heading);
}

.mp-hero__title {
  /* Deliberately NOT position:relative — .mp-hero__scroll is absolutely
     positioned and must resolve against .mp-hero__inner's full-width box
     (the container), not this narrower (851px) title box. */
  max-inline-size: 851px;                 /* 3546:104650 */
  color: var(--c-white);
  font-size: var(--fs-hero);
  line-height: var(--lh-heading);
}

/* The visible circle + arrow are the photo. This is a real, transparent,
   keyboard-focusable hit target sitting exactly on top of it — the arrow
   icon IS drawn here too (safe: identical position/colour, so it only
   reinforces the photo's own arrow) so the affordance still reads correctly
   if the image fails to load. Only the translucent circle FILL is left to
   the photo, so opacity never compounds. */
.mp-hero__scroll {
  position: absolute;
  inset-inline-end: 0;
  /* Centred on the H1, as in the design. The title's last line sits on the
     padding line, so aligning the disc's BOTTOM there leaves it low by exactly
     half the difference between the two boxes: the 2-line title measures 190px
     and the disc is 163px, so lift it by (190 - 163) / 2. */
  inset-block-end: calc(var(--hero-pad-end) + 13.5px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 163px;
  block-size: 163px;
  border-radius: 50%;
  color: var(--c-white);
  transition: transform var(--dur) var(--ease);
}
.mp-hero__scroll:hover,
.mp-hero__scroll:focus-visible { transform: translateY(4px); }
/* 75x82, NOT square — re-measured after finding 1 (review): the shipped
   34x34 was sized off the same rotated/transposed metadata that produced the
   sideways path, so it was both the wrong shape AND far too small. The
   node's own genuine SVG export is already 75x82 at this exact 163px-circle
   scale (no percentage guess needed), independently confirmed by sampling
   the rendered circle's pixels: bright (arrow-colour) bounding box measures
   75x81 of the 163px circle — 46.0% / 49.7% of its diameter, matching the
   export almost to the pixel. See the fix report for the full pixel dump. */
.mp-hero__scroll-icon { inline-size: 75px; block-size: 82px; opacity: 0.78; }  /* 3546:104644 */

/* The scroll control is a REAL element, not part of hero.jpg.
   An earlier fix here hid the icon on desktop on the assumption that the photo
   had the circle baked in. It does not — verified by scanning hero.jpg for a
   bright disc (upper-band peak luma 65 against a 49 mean, and on the left side:
   gradient variation, no disc). The anchor paints no background of its own, so
   that rule left the control completely invisible above 769px.
   It is Figma's Ellipse 950 (3546:104643) — a 163x163 disc of white at ~10%
   over the hero, measured from the node render: disc rgb(49,78,140) over a
   background of rgb(26,58,127) solves to alpha 0.10 on all three channels. */
/* Acrylic / frosted glass, not a flat tint. The disc BLURS what is behind it
   (the video and the scrim) rather than just veiling it — which is why every
   attempt at tuning the alpha alone still read as a grey plate. The grain sits
   at z4, above this, so it is not caught by the blur and keeps falling over the
   disc like everything else.
   `saturate` compensates for the desaturation a plain blur causes; without it
   frosted glass over colour looks washed out. */
.mp-hero__scroll {
  /* Graded, not flat. Real glass catches more light along one edge, so a single
     uniform tint always reads as a smudge no matter how the alpha is tuned. */
  background: linear-gradient(155deg,
              rgba(255, 255, 255, 0.16) 0%,
              rgba(255, 255, 255, 0.06) 55%,
              rgba(255, 255, 255, 0.10) 100%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
          backdrop-filter: blur(18px) saturate(140%);
  /* The rim is what actually sells it. Blur alone gives a soft patch; the thin
     lit edge is what the eye reads as a pane with a thickness. */
  border: 1px solid rgba(255, 255, 255, 0.22);
  box-shadow:
    inset 0 1px 1px rgba(255, 255, 255, 0.30),   /* top edge catching light */
    inset 0 -1px 1px rgba(255, 255, 255, 0.08),  /* faint bounce underneath */
    0 10px 30px rgba(0, 0, 0, 0.18);             /* lifts it off the video */
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  /* No blur support: lean harder on the tint and rim so it is still a legible
     control rather than an invisible hit area. */
  .mp-hero__scroll {
    background: rgba(255, 255, 255, 0.16);
    border-color: rgba(255, 255, 255, 0.30);
  }
}

@media (max-width: 1200px) {
  /* Interpolated, not measured — no data exists between the 1440 and 390
     frames (site.css §"Tablet / small-laptop band" applies the same caveat
     to every other size in this range). Scaled down with the same ~0.71
     ratio the other desktop sizes use in this band. */
  .mp-hero__scroll { inline-size: 115px; block-size: 115px; }
  .mp-hero__scroll-icon { inline-size: 53px; block-size: 58px; }  /* 75x82 * (115/163) */
}

@media (max-width: 768px) {
  .mp-hero {
    /* Same trap as the desktop rule, and it bit harder here because this
       breakpoint spans 320-768px: aspect-ratio 390/857 is right AT 390 (857px,
       roughly one phone screen) but produces a 1687px-tall hero at 768. Size to
       the viewport with the design's own height as the cap. */
    block-size: min(100svh, 857px);        /* 3417:1991 */
    min-block-size: 420px;
    /* A genuinely different, lighter/bluer gradient (3417:1991) than
       --grad-hero-scrim — not a scaled copy, so not the shared token. cover
       crops hero.jpg to a centred vertical slice at this aspect ratio, which
       measures clear of both the desktop scroll circle and the baked white
       corners (their x-ranges sit outside the visible centred slice), so
       this overlay paints onto plain gradient/grain, not onto that content. */
    background-image:
      linear-gradient(207.67deg, rgba(44, 165, 232, 0.6) 24.576%, rgba(41, 150, 217, 0.551) 33.294%, rgba(26, 58, 127, 0.25) 86.925%),
      var(--hero-img);
    /* Asymmetric — measured from 3417:1991 directly. NOT the interpolated
       --radius-lg guess (40px) that the tablet/mobile guard-rail block uses
       for sections nobody has re-measured yet (see that block's comment). */
    border-end-start-radius: 50px;
    border-end-end-radius: 32px;
  }

  .mp-hero__inner { --hero-pad-end: 14%; }  /* 55px / 390, 3417:1982 vs 3417:1991 */

  .mp-hero__eyebrow {
    max-inline-size: 271px;                /* 3417:1985 — wraps to 3 lines at this width */
    margin-block-end: 8px;                 /* 3417:1985→1986 measured gap */
    font-size: 16px;                       /* 3417:1985; not yet a shared token */
  }

  .mp-hero__title { max-inline-size: 307px; }  /* 3417:1986 */

  /* One element, two placements (§10): same markup, now flowed inline at
     the end of the H1 instead of absolutely positioned. This crop of
     hero.jpg has no baked circle at this size, so it's drawn for real here. */
  .mp-hero__scroll {
    position: static;
    inset-block-end: auto;
    display: inline-flex;
    vertical-align: middle;
    margin-inline-start: 8px;
    inline-size: 27px;                     /* 3417:1987 */
    block-size: 27px;
    background: rgba(255, 255, 255, 0.1);  /* 3546:104642 fill-opacity, reused — no mobile-specific value exists */
  }
  /* 3417:1987's own arrow sub-layer clip rect is 12.5391x13.7094 (get_design_context
     on that node) — same 46.0%/50.3%-of-circle-diameter ratio as desktop,
     confirming this scales consistently rather than needing its own guess.
     Rounded to whole px like every other size in this file. */
  .mp-hero__scroll-icon { inline-size: 13px; block-size: 14px; }
}

.mp-nav__sentinel { block-size: 1px; }

@media (prefers-reduced-motion: reduce) {
  .mp-hero__scroll { transition: none; }
}

/* =========================================================================
   Task 7 — "Pet care, made simpler" + the tile grid (§5 rows 3-4)

   Figma kyudcw9zAgmniIXWNveugA. Node ids cited per rule; see the section
   headers in simpler.blade.php / tiles.blade.php for the full mapping
   evidence (in particular: how the three photo tiles were matched to
   discover/cashback/trusted, confirmed two independent ways).
   ========================================================================= */

/* ─ "Pet care, made simpler" ──────────────────────────────────────────── */
.mp-simpler__grid {
  display: grid;
  /* Measured column ratio, not a guess: collage box 528 / copy box 632
     locally on 3546:104521. Fluid (fr), not px, so it holds at any width. */
  grid-template-columns: minmax(0, 528fr) minmax(0, 632fr);
  align-items: center;
  gap: var(--gutter);
}

.mp-simpler__collage img {
  display: block;
  inline-size: 100%;
  block-size: auto;
}

.mp-simpler__copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;                       /* 3546:104608 gap-[16px] (heading block → CTA) */
}

.mp-simpler__heading {
  display: flex;
  flex-direction: column;
  gap: 10px;                       /* 3546:104609 gap-[10px] (title → body) */
}

/* Painted as ONE clipped gradient across the whole string (3546:104610) —
   not the solid+accent two-tone .mp-h2 other sections use. Reuses the
   existing .mp-text-gradient utility; title_a/title_b are still two
   translation keys (file convention) but share one gradient-clipped element. */
.mp-simpler__title { font-size: var(--fs-h2-lg); }

.mp-simpler__body {
  font-size: var(--fs-lead);       /* 3546:104611 */
  color: var(--c-ink);
}

.mp-simpler__cta-desktop {
  display: inline-block;
  font-family: var(--font-display);
  font-size: var(--fs-lead);       /* 3546:104612, Medium */
  font-weight: var(--fw-medium);
  color: var(--c-primary);
  text-underline-offset: 4px;
}

/* Real content difference (§10), not a restyle — see simpler.blade.php.
   Both CTAs always render; only visibility is viewport-conditional, and both
   remain real anchors (never href="#") even while hidden. */
.mp-simpler__cta-mobile { display: none; }

@media (max-width: 768px) {
  .mp-simpler__grid { grid-template-columns: 1fr; }
  .mp-simpler__cta-desktop { display: none; }
  .mp-simpler__cta-mobile { display: flex; inline-size: 100%; }
}

/* ─ Tile grid ──────────────────────────────────────────────────────────── */
.mp-tiles__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* Clipping the whole 2x2 union to one radius is what gives each tile its
     own single rounded OUTER corner (panel: top-start; discover: top-end;
     cashback: bottom-start; trusted: bottom-end) while the inner corners
     where tiles meet stay square — matching the design without needing a
     border-radius on every individual tile. */
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.mp-tile { position: relative; }

/* Tile 1: the section heading panel. Not interactive, no hover state, and
   (no tabindex in the markup) not in the tab order. */
.mp-tile--panel {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 8px;                        /* ~2px measured (3546:104627→104628); small buffer for legibility */
  padding: var(--gutter);
  /* The visible panel is the GRADIENT layer 3546:104621, not the flat base
     rectangle 3546:104613 underneath it. tile-intro was originally exported from
     104613 and was therefore a single solid #1a3a7f across all 1440x1324 px
     (12KB for that size gave it away) — the rich blue gradient and its grain
     live entirely on 104621. Same trap as the hero, whose export came from the
     parent frame and swallowed the scroll circle: check WHICH layer carries the
     paint before exporting.
     The flat colour stays as background-color so the panel is never white while
     the image loads. */
  background-color: var(--c-primary);
  background-image: url("/marketplace/img/tile-intro.webp");
  background-size: cover;
  background-position: center;
  color: var(--c-white);
}

.mp-tile__eyebrow {
  font-size: var(--fs-lead);       /* 3546:104628 */
  font-weight: var(--fw-book);
}

.mp-tile__title { font-size: var(--fs-display); }  /* 3546:104627 */

/* Tiles 2-4: photo + copy. MOBILE-FIRST — this block is the DEFAULT state:
   copy visible, in normal flow, stacked under the photo. This is the actual
   design at 390px (3417:1306), not a fallback for when JS/hover is
   unavailable. The desktop reveal further down is a progressive enhancement
   layered on top of this default — see tiles.blade.php's header comment. */
.mp-tile__img {
  display: block;
  inline-size: 100%;
  block-size: auto;
}

.mp-tile__scrim {
  display: flex;
  flex-direction: column;
  gap: 8px;                        /* measured, mobile  3417:2654 */
  padding: var(--gutter);
  color: var(--c-primary);
  background: var(--c-bg);
}

/* Measured directly off the mobile frame (3417:2656/2655) — NOT a scaled-down
   continuation of the desktop --fs-h4/32px pair below. Dark text on the
   page's own background, not white-on-photo; that treatment is desktop-only. */
.mp-tile__heading { font-size: 24px; }
.mp-tile__body { font-size: 16px; color: var(--c-ink); }

@media (min-width: 769px) {
  .mp-tile--reveal,
  /* aspect-ratio alone converts viewport WIDTH into height, so on a full-bleed
     grid the tiles grew with the screen: 953x874 at a 1920 viewport against a
     designed 720x661 — a third taller than drawn, and the single biggest reason
     the page felt oversized. Same trap the hero had.
     Keep the ratio so the tiles shrink correctly on narrow screens, but never
     let them exceed the height they were drawn at. */
  .mp-tile--panel {
    /* inline-size must be stated explicitly. With only aspect-ratio +
       max-block-size, the browser honours the cap by SHRINKING THE WIDTH — the
       tiles dropped to 720px inside ~952px grid cells and left white gaps down
       the full-bleed row. Pinning the width to the cell makes the cap apply to
       height alone, which is what "don't grow taller than drawn" means. */
    inline-size: 100%;
    aspect-ratio: 720 / 661;                     /* measured tile size  3546:104613 */
    max-block-size: 661px;
  }

  .mp-tile__img {
    position: absolute;
    inset: 0;
    block-size: 100%;
    object-fit: cover;
  }

  .mp-tile__scrim {
    position: absolute;
    inset: 0;
    justify-content: flex-end;
    gap: 16px;                     /* measured  3434:5482 */
    color: var(--c-white);
    /* Two-layer `background` shorthand — a plain colour is only valid as the
       LAST (bottommost) layer, so the diagonal gradient stays visually on
       top and the flat colour paints underneath it, above the <img>.
       Two jobs, both load-bearing:
       1. (orig. bug 1) `background` shorthand, not `background-image` — the
          mobile-default rule above sets `background: var(--c-bg)`, i.e.
          `background-color: #fafbfc`. This shorthand replaces that opaque
          colour outright (rather than leaving it to show through beneath a
          translucent gradient — off-white panel over the photo, white text
          on white).
       2. (contrast fix) `rgba(0,0,0,.7)` is a uniform, direction-agnostic
          dark floor. The diagonal `--grad-tile-scrim` alone only reaches
          its near-opaque plateau toward the bottom-left of the tile; at
          realistic wrapped-copy widths (esp. the heading's own line, which
          can sit well above the bottom edge once the body wraps to 4-5
          lines) the right/upper portion of the box thinned to as low as
          ~1.1:1 white-on-photo contrast — verified by rasterising this
          exact gradient in Python/PIL and alpha-compositing it over the
          real shipped tile-photo-*.png files at real wrapped text rows
          (see task-7-report.md §"Critical fix"). The flat base guarantees
          a contrast floor at EVERY position in the box, independent of the
          diagonal's own shape, so a future photo swap can't reintroduce
          this failure. 0.7 was chosen empirically: it's the alpha at which
          the worst sampled pixel (whole-box scan, all three shipped
          photos) clears 4.5:1 against white with a comfortable margin
          (worst case ~4.65:1 at 0.65; ~5.27:1 at 0.7). */
    background: var(--grad-tile-scrim), rgba(0, 0, 0, 0.7);

    opacity: 0;
    transform: translateY(12px);
    transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  }

  .mp-tile--reveal:hover .mp-tile__scrim,
  .mp-tile--reveal:focus-within .mp-tile__scrim {
    opacity: 1;
    transform: none;
  }

  .mp-tile__heading { font-size: var(--fs-h4); }               /* 3434:4796/5483/5487 */
  .mp-tile__body { font-size: var(--fs-name); color: var(--c-white); }  /* 32px — reuses --fs-name's identical value  3434:4797/5484/5488 */
}

@media (max-width: 768px) {
  .mp-tiles__grid { grid-template-columns: 1fr; }
}

/* Reduced motion: crossfade only, never withhold the copy (the copy is
   already always in the DOM regardless — this only concerns the desktop
   opacity/transform reveal). transition-duration is already forced to
   0.01ms by the shared rule near the top of this file; transform is zeroed
   here explicitly so the scrim never carries the translateY offset. */
@media (prefers-reduced-motion: reduce) {
  .mp-tile__scrim { transform: none; transition: opacity 0.01ms; }
}

/* =========================================================================
   Task 8 — Stats strip and category chip marquee (§5 rows 5-6)

   Figma kyudcw9zAgmniIXWNveugA.
     Stats:      3546:105223 (desktop) / 3417:2809 (mobile 390)
     Categories: 3546:104655 (desktop, 2 rows) / 3417:2715 (mobile, 3 rows)
   See categories.blade.php's header comment for why the mobile row split is
   a real content-grouping difference (confirmed with a screenshot, not just
   guessed) and why two full row-sets are rendered rather than one row
   reflowed by CSS — a marquee track's "-50%" loop only stays seamless if it
   never wraps.
   ========================================================================= */

/* ─ Stats strip ────────────────────────────────────────────────────────── */
.mp-stats__grid {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--gutter);
}

.mp-stat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.mp-stat__value {
  font-size: var(--fs-stat);
  font-weight: var(--fw-bold);
  line-height: var(--lh-heading);
  background-image: var(--grad-stat);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}
/* "Offers" (3546:105235) is Bold + gradient-clipped exactly like the two
   numeral tiles — just smaller (--fs-stat-sm), not a whole second treatment. */
.mp-stat--sm .mp-stat__value { font-size: var(--fs-stat-sm); }

.mp-stat__copy {
  display: flex;
  flex-direction: column;
  gap: 2px;                 /* 3546:105226 gap-[2px] */
}

/* Measured LITERAL values, deliberately NOT var(--fs-lead) / var(--fs-body):
   both stay identical between the 1440 and 390 frames — 3546:105227 vs
   3417:2813 are both 28px, 3546:105228 vs 3417:2814 are both 18px — unlike
   the shared tokens of a similar size, which DO shrink for other sections at
   the 768px breakpoint (e.g. --fs-lead: 28px -> 18px). Using the shared
   token here would wrongly shrink this copy on mobile; see the tiles
   section's own analogous literal overrides (Task 7) for the same reasoning. */
.mp-stat__title { font-size: 28px; font-weight: var(--fw-bold); color: var(--c-primary); }
.mp-stat__body  { font-size: 18px; line-height: var(--lh-body); color: var(--c-ink); max-inline-size: 320px; }

@media (max-width: 768px) {
  .mp-stats__grid { flex-direction: column; align-items: center; gap: 62px; }  /* 3417:2809 gap-[62px] */
  .mp-stat, .mp-stat__copy { align-items: center; text-align: center; }
  .mp-stat__body { max-inline-size: none; }
}

/* ─ Category chip marquee ──────────────────────────────────────────────── */
/* Full-bleed by design ("Two marquee rows of pill chips ... overflowing the
   viewport" — categories.blade.php stub, kept true): no .mp-container here,
   no inset gutter on the track. The row is exactly viewport-width and clips
   the track, which is wider than it. */
.mp-cats__rows { display: flex; flex-direction: column; gap: 32px; }  /* 3546:104655 gap-[32px] */
.mp-cats__rows--mobile { display: none; }

.mp-cats__row {
  display: flex;
  overflow: hidden;
  /* Same token as the intra-track chip gap (below) — the seam between the
     real track and its aria-hidden duplicate must read identically to every
     other chip-to-chip gap, or the loop shows a visible pinch right at the
     wrap point. The brief's illustrative snippet used a flat 1rem here;
     that would be narrower than the 62px desktop chip gap and produce
     exactly that seam, so it was not carried over. */
  gap: var(--gap-grid);
}

.mp-cats__track {
  display: flex;
  align-items: center;
  gap: var(--gap-grid);      /* chip-to-chip gutter, same token the card grid uses  3546:104656 */
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 0 0 auto;
  animation: mp-marquee 40s linear infinite;
}
/* Alternate scroll direction per row, by index parity — works for both the
   2-row desktop set and the 3-row mobile set without a second rule. */
.mp-cats__row[data-row="1"] .mp-cats__track { animation-direction: reverse; }

/* RTL needs a MIRRORED KEYFRAME, not `animation-direction: reverse`.
   `transform` is physical (translateX(-x) moves left under any `dir`) but
   flex layout is logical: in RTL the row's main-axis start is the RIGHT
   edge, so the copies stack LEFTWARDS and all the spare content sits to
   the left of the viewport. Content must therefore travel RIGHT to be fed
   from that reservoir.

   `reverse` does NOT do that. It replays the same leftward keyframe
   backwards — -P → 0 — so every cycle BEGINS with the track shifted left
   by a full period. Nothing exists to the right of copy 0, so the
   rightmost P pixels are bare: the gap doesn't close, it just moves from
   the left edge to the right one. That was the first fix attempt here and
   it is what the RTL screenshot showed.

   mp-marquee-rtl runs 0 → +P instead. At +P copy 1 lands exactly where
   copy 0 began (same period, so the seam stays invisible — see the
   periodicity proof on mp-marquee below) and copies 2..3 still cover
   [R-3P, R-P], which comfortably spans any viewport up to 3P.

   Row 1 keeps `reverse` so the two rows still travel opposite ways; on the
   mirrored keyframe its extreme is +P, which copies 1..3 backfill — the
   4-copy count from cats-row-set.blade.php is what makes that safe. */
[dir="rtl"] .mp-cats__track {
  animation-name: mp-marquee-rtl;
  animation-direction: normal;
}
[dir="rtl"] .mp-cats__row[data-row="1"] .mp-cats__track { animation-direction: reverse; }

/* transform + opacity only, per the motion rule. The end state MUST be a
   full period, not "-50%": the real track and its aria-hidden duplicate are
   equal-width (W) flex siblings, G apart (.mp-cats__row's own gap, same
   var(--gap-grid) token as below), so the pattern repeats every (W + G) —
   the distance from the real track's start to the duplicate's start.
   Translating by exactly that much makes the duplicate (pixel-identical
   content) land precisely where the real track started, so the viewport's
   visible window is byte-identical before and after the loop resets — a
   proof, not a plausibility argument. -50% of the track's own width is
   NOT that distance (it's short by roughly half a track plus the gap) and
   was the actual first-pass bug here: fine while the strip drifts smoothly,
   but at the instant the animation loops back to 0% it exposes a blank
   gap-width notch mid-viewport that never appears at rest — a real seam,
   caught by the advisor, not by eyeballing the CSS. */
@keyframes mp-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-100% - var(--gap-grid))); }
}

/* The RTL twin — same period, opposite sign. Kept as its own keyframe
   rather than reused via animation-direction for the reason spelled out on
   the [dir="rtl"] rules above: reverse changes WHERE the cycle starts, not
   which way the copies are stacked, and in RTL they stack the other way. */
@keyframes mp-marquee-rtl {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(100% + var(--gap-grid))); }
}

/* Pause on hover AND focus-within (§ constraints) — a keyboard user reaching
   a chip must not have it slide out from under them. Every chip in this
   section is currently disabled (no category pages exist yet — see
   categories.blade.php), so tabindex="-1" means none can actually receive
   focus today; this rule is still correct and ready for the moment real
   hrefs land, exactly mirroring nav.blade.php's own disabled-link links. */
.mp-cats__row:hover .mp-cats__track,
.mp-cats__row:focus-within .mp-cats__track { animation-play-state: paused; }

/* (The RTL mirroring rules used to be duplicated here, verbatim. Because
   they sat LATER in the file at identical specificity they silently won,
   so editing the copy above had no effect — removed rather than kept in
   sync. The single source of truth is the [dir="rtl"] block above.) */

.mp-chip {
  display: inline-flex;
  align-items: center;
  gap: 12px;                        /* 3546:104656 gap-[12px] */
  min-block-size: 44px;
  padding: 12px 16px;                /* 3546:104657 px-[16px] py-[12px] */
  background: var(--c-surface-tint);
  border-radius: 74px;               /* fully round; deliberately not tokenised — see the token table's radius note */
  color: var(--c-primary);
  font-family: var(--font-display);
  font-size: var(--fs-body);         /* 18px desktop / 14px mobile — 3546:104659; one of the few sizes here where the shared token's own mobile value already matches Figma exactly (contrast the stats title/body above, which don't) */
  font-weight: var(--fw-bold);
  white-space: nowrap;
}
.mp-chip__icon { display: block; block-size: 42px; inline-size: auto; }  /* 3546:104658 etc — each icon keeps its own aspect ratio, Figma shows a different width per icon at this same row height */

@media (max-width: 768px) {
  .mp-cats__rows--desktop { display: none; }
  .mp-cats__rows--mobile  { display: flex; }
  .mp-cats__rows { gap: 16px; }       /* 3417:2715 gap-[16px] */
  /* .mp-cats__row's gap is still var(--gap-grid) here — no redeclaration
     needed, the token itself is already 16px at this breakpoint (root
     override above), which happens to match the measured mobile chip gap
     too, so the seam stays invisible without a mobile-specific rule. */
  .mp-chip { gap: 8px; padding: 12px; }      /* 3417:2717 gap-[8px] p-[12px] */
  .mp-chip__icon { block-size: 26px; }       /* 3417:2718 h-[26px] */
}

@media (prefers-reduced-motion: reduce) {
  /* `animation: none` (not just letting the universal duration/iteration
     override above apply) is required here specifically: that universal
     rule forces a 0.01ms single-iteration play-through, which for THIS
     animation lands the track on its FINAL keyframe (translateX(calc(-100% - var(--gap-grid)))) and
     leaves it there — frozen off-screen, exactly the anti-pattern the
     task brief calls out. Cancelling the animation outright instead resets
     transform to its unset value (none), i.e. the track's true rest
     position, and overflow-x:auto makes the now-static row reachable by
     scroll instead of by the animation that no longer runs. */
  .mp-cats__track { animation: none; }
  /* Specificity guard, NOT redundancy. The RTL block above names its
     animation on `[dir="rtl"] .mp-cats__track` (0,2,0), which outranks the
     plain `.mp-cats__track` (0,1,0) above no matter that this rule comes
     later — so without this line the marquee would keep running for Arabic
     readers who asked the OS for reduced motion. Matching the selector
     restores the cascade. */
  [dir="rtl"] .mp-cats__track { animation: none; }
  .mp-cats__row { overflow-x: auto; }
}

/* =========================================================================
   Task 7b — product-owner amendment to Task 7's "Pet care, made simpler"
   collage. Verbatim request: "should be carousel slider from top to bottom
   the right one and the left on from bottom to top and should start from
   section and inner with other section" — i.e. a dual-direction vertical
   marquee that bleeds past the section's own top/bottom edge into the
   hero above and the tile grid below. Full Figma export trail, every
   asset filename + size, and the self-review arithmetic live in
   task-7b-marquee-report.md; simpler.blade.php's own header comment has
   the per-asset notes. This section only touches .mp-simpler__collage's
   CONTENTS (a new .mp-simpler__marquee replaces the old flattened <img>);
   .mp-simpler__copy, both CTAs, and the gradient title are untouched.

   Mirrors Task 8's category-chip marquee technique (.mp-cats__track /
   @keyframes mp-marquee above), rotated to the block axis: a real <ul>
   track plus an aria-hidden duplicate, translated by exactly one content
   period via the SAME gap token used for both the intra-track gap and the
   row's own gap between the two tracks — see @keyframes mp-marquee-y's
   own comment for the periodicity proof, and the task report for the
   worked arithmetic.
   ========================================================================= */

.mp-simpler {
  /* Vertical gap between stacked phone cards AND the gap between the real
     track and its aria-hidden duplicate — MUST be the same token in both
     places (exactly Task 8's own rule for --gap-grid) or the loop shows a
     seam at every wrap. */
  --simpler-phone-gap: 20px;
  /* How far each desktop column bleeds past .mp-simpler__collage's own
     top/bottom edge. Kept well under the hero's own text clearance
     (--hero-pad-end: 12% of viewport width — 172.8px at 1440px,
     ~92.3px at the narrowest width this value still applies, 769px) so
     the hero eyebrow/title never gets covered; the tile-grid panel text
     below has a much larger margin (its heading sits ~600px down a
     top-left tile that's wider than this column), so the same value is
     safe in both directions. See the task report's clearance arithmetic. */
  /* Exactly the section's own vertical padding, so the marquee's top and bottom
     land FLUSH with the section's edges rather than at an arbitrary offset.
     .mp-simpler__marquee is positioned against .mp-simpler__collage, and the
     collage is inset from the section by precisely this padding — so
     inset-block: -(that padding) cancels it and the columns span the section
     exactly. A hardcoded 110px was fighting a 96px padding, leaving a 14px
     stub hanging past the section at each end.
     Using the token also keeps it correct per breakpoint for free
     (96 / 64 / 32px), which the old fixed pair of values did not. */
  --simpler-bleed: var(--space-section);
}

/* New rule, not a rewrite of Task 7's .mp-simpler__collage block above:
   CSS lets the same selector gain more properties in a later rule. Reserves
   the same footprint the old flattened image did (1056x1626) so the grid
   column's proportions are unchanged, and becomes the containing block the
   bleeding .mp-simpler__marquee positions itself against. */
.mp-simpler__collage {
  position: relative;
  /* Same cap as the tiles: the ratio governs narrow screens, the max stops the
     column inflating past its drawn height (528x813 in Figma) on wide ones. */
  inline-size: 100%;                /* see the tile note: without this the cap
                                       shrinks the width instead of the height */
  aspect-ratio: 1056 / 1626;
  max-block-size: 813px;
}

/* ─ Desktop: two bleeding columns, opposite directions ────────────────── */
.mp-simpler__marquee {
  position: absolute;
  inset-inline: 0;
  inset-block: calc(-1 * var(--simpler-bleed));
  /* Existing scale, reused rather than an invented value: elevates the
     bleed above the plain in-flow hero/tiles content it overlaps (which
     would otherwise paint on top of it, DOM-order-after for the tile
     grid), while staying well under --z-nav (the fixed bar) and
     --z-modal. --z-sticky is otherwise unused in this file today, so
     there's no other element it needs to out-rank. */
  z-index: var(--z-sticky);
  display: flex;
  gap: 16px;
}

.mp-simpler__marquee--mobile { display: none; }

.mp-simpler__col {
  flex: 1 1 0;
  min-inline-size: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: var(--simpler-phone-gap);
  /* Fade the two ends instead of guillotining them. overflow:hidden alone cuts
     a phone dead across the middle at the column edge, which reads as content
     scrolling inside a box; fading makes it read as a continuous river passing
     through the section. Masked rather than overlaid with a gradient so it works
     over whatever the neighbouring sections happen to be. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 7%, #000 93%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 7%, #000 93%, transparent 100%);
}

.mp-simpler__track {
  display: flex;
  flex-direction: column;
  gap: var(--simpler-phone-gap);
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 0 0 auto;
  animation: mp-marquee-y 36s linear infinite;
}

/* The phone bezel is drawn HERE, not baked into the export.
   The assets are now the raw 1179x2556 app screenshots (uniform, full, and
   untruncated) rather than Figma's wrapper frames, which came out clipped AND
   with an opaque background that showed as white square corners behind the
   rounded phone. Framing in CSS fixes both: the corners outside this radius are
   genuinely transparent, so the page background shows through. */
.mp-simpler__track li {
  background: #16181d;
  padding: 7px;
  border-radius: 38px;
  line-height: 0;                 /* no inline-gap under the img */
  /* Clip to the bezel's own radius. Without this the <img>'s rounded corner is
     antialiased against whatever is behind the li rather than against the bezel,
     which leaves a pale fringe tracing the curve — visible as a smudge in the
     corners. No box-shadow either: a 19px 25%-black halo on a rounded rect reads
     as exactly that same corner smudge against this near-white page. */
  overflow: hidden;
}

.mp-simpler__track img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  border-radius: 31px;            /* bezel radius minus the padding */
}

/* col--a = home+rooms (LTR-left column): bottom -> top.
   col--b = hotel+shop  (LTR-right column): top -> bottom.
   This is "the right one top to bottom, the left one bottom to top" from
   the product owner's brief, read as PHYSICAL sides — which is why the
   [dir="rtl"] block below swaps them: see its own comment. */
/* Direction mapping, since it is easy to get backwards: the keyframe runs
   translateY(0) -> translateY(-100% - gap), i.e. UPWARD. So `normal` scrolls
   the track up (items travel bottom -> top) and `reverse` scrolls it down
   (items travel top -> bottom). Left column = normal, right = reverse. */
.mp-simpler__col--a .mp-simpler__track { animation-direction: normal; }
.mp-simpler__col--b .mp-simpler__track { animation-direction: reverse; }

/* transform + opacity only (per the motion rule). The end state MUST be a
   full period, not "-50%": the real track and its aria-hidden duplicate
   are equal-height (H) flex siblings, G apart (.mp-simpler__col's own
   gap — the SAME var(--simpler-phone-gap) token the track uses for its
   own card-to-card gap below), so the pattern repeats every (H + G) — the
   distance from the real track's own start to the duplicate's start.
   Translating exactly that far puts the duplicate (pixel-identical
   content) where the real track began, so the visible window is
   byte-identical before and after the loop resets. Exactly Task 8's own
   proof for @keyframes mp-marquee above, rotated to translateY; see the
   task report for the worked numbers. */
@keyframes mp-marquee-y {
  from { transform: translateY(0); }
  to   { transform: translateY(calc(-100% - var(--simpler-phone-gap))); }
}

.mp-simpler__col:hover .mp-simpler__track,
.mp-simpler__col:focus-within .mp-simpler__track { animation-play-state: paused; }

/* RTL: translateY is a block-axis transform, so dir="rtl" never flips it —
   up stays up, down stays down (unlike Task 8's translateX, whose own
   comment explains why THAT one needs a swap). What RTL DOES change here
   is which physical side each column renders on: .mp-simpler__marquee is
   a plain flex row, so under dir="rtl" its first DOM child (col--a) moves
   to the visual RIGHT and its second (col--b) to the visual LEFT — the
   same auto-mirroring .mp-simpler__grid already relies on for the
   collage/copy swap. Left unhandled, the LTR-assigned directions would
   stay attached to source order, i.e. physical-right would scroll
   bottom-to-top and physical-left top-to-bottom in Arabic — backwards
   from the brief. Swapping which column gets which direction (Task 8's
   own fix, same technique, just for translateY's column pairing instead
   of translateX's row pairing) keeps "physical-right always goes down,
   physical-left always goes up" true in both directions. */
[dir="rtl"] .mp-simpler__col--a .mp-simpler__track { animation-direction: reverse; }
[dir="rtl"] .mp-simpler__col--b .mp-simpler__track { animation-direction: normal; }

/* ─ Tablet (<=1200px): same interpolated-band caveat as the guard-rail
   block near the top of this file — scale the bleed down with the rest of
   the ramp rather than let it eat into the smaller hero text clearance at
   this width (~144px at 1200px, ~92px at 769px, the narrowest point this
   band still applies). A new query, not an edit of the existing guard-rail
   block, per this section's own append-only rule. ───────────────────── */
@media (max-width: 1200px) {
  /* No --simpler-bleed override needed here any more: the base rule now derives
     it from --space-section, which this breakpoint already redefines. */
}

/* ─ Mobile (<=768px): one contained column, no bleed ──────────────────────
   Two independently-animating ~150px-wide columns are too narrow to read
   at this width, and this layout stacks the collage directly ABOVE the
   copy block in the SAME section (.mp-simpler__grid collapses to 1fr) —
   bleeding down here would cover that copy's own text/CTA, not a
   neighbouring section's. So mobile drops both the second column and the
   bleed: one column, all four phones, contained, top-to-bottom, at a
   slower rate — still alive, not a static image, but not fighting the
   copy directly beneath it for either room or attention. See the task
   report's §Mobile decision for the full reasoning. */
@media (max-width: 768px) {
  .mp-simpler__collage { aspect-ratio: unset; block-size: 460px; }

  .mp-simpler__marquee { display: none; }
  .mp-simpler__marquee--mobile {
    display: flex;
    justify-content: center;
    block-size: 100%;
    inline-size: 100%;
  }
  .mp-simpler__marquee--mobile .mp-simpler__col {
    flex: 0 0 240px;
  }
  /* More content per loop (4 phones vs. 2) AND a deliberately gentler read
     on a small screen — see the task report's §Mobile decision. */
  .mp-simpler__marquee--mobile .mp-simpler__track { animation-duration: 55s; }
}

@media (prefers-reduced-motion: reduce) {
  /* `animation: none`, not just the universal duration/iteration override
     near the top of this file — that override still plays ONE 0.01ms
     iteration, which for THIS animation lands the track on its FINAL
     keyframe (translateY off by a full period) and freezes it there,
     half off-screen — exactly Task 8's own documented anti-pattern for
     @keyframes mp-marquee, reproduced here because the same universal
     rule applies to this one too. Cancelling the animation outright
     instead resets transform to its unset rest position, and
     overflow-y:auto (the vertical counterpart of Task 8's own
     overflow-x:auto fix) makes the now-static column reachable by scroll
     if its content exceeds the (bleed-expanded, on desktop) window. */
  .mp-simpler__track { animation: none; }
  .mp-simpler__col { overflow-y: auto; }
}

/* =========================================================================
   Task 9 — Featured providers carousel (§5 row 7)

   Figma kyudcw9zAgmniIXWNveugA:
     desktop  3546:105116 (heading + Arrow-Slider 3546:105120 + card row
              3546:105131), card 3546:105132
     mobile   3417:2940, card 3417:2957 (arrows sit off the 390 frame at
              x=577 in the source file — Figma itself doesn't show them on
              mobile, confirmed by metadata, not assumed)

   First DB-driven, carousel section. .mp-carousel / .mp-carousel__track is
   the shared contract Task 10's insights carousel reuses verbatim.
   ========================================================================= */

/* Only 2 providers are featured right now. .mp-carousel__track is a flex
   row of fixed-width children (below) — never a grid or flex-grow layout —
   so a short list simply renders at its real width and the row ends. There
   is nothing here that stretches cards to fill remaining space. */
.mp-providers__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-block-end: var(--space-heading);   /* heading row -> card grid  3546:105117 h58 -> grid y90 */
}

/* Straddles Task 3's two spacing bands: the measured gap ABOVE this section
   (categories -> providers, 120px = --space-section-sm on both sides) is
   half of the gap BELOW it (providers -> app, 197px ~= --space-section on
   both sides) — the two neighbouring bands this section sits between were
   never one uniform rhythm, so a single mp-section / mp-section--tight
   class can't reproduce both at once. Asymmetric padding-block does. */
.mp-providers { padding-block: var(--space-section-sm) var(--space-section); }

.mp-carousel__controls { display: flex; align-items: center; gap: var(--arrow-gap); }

.mp-carousel__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--arrow-size);   /* 52px — already >= the 44px touch-target floor */
  block-size: var(--arrow-size);
  border-radius: var(--radius-pill);
  border: 1px solid var(--c-primary);
  background: transparent;
  color: var(--c-primary);
  cursor: pointer;
  transition: transform var(--dur) var(--ease);
}
.mp-carousel__btn:hover { transform: translateY(-2px); }
.mp-carousel__btn--primary { background: var(--c-primary); color: var(--c-white); }

/* Task 13 contrast fix — same --c-accent-on-light failure as the nav override
   above (2.65:1 / 2.74:1, under the 3:1 WCAG 1.4.11 floor). Both the plain
   (outlined) and --primary (filled) variants sit in the providers/insights/
   testimonials section heads, all on the page's default --c-bg — the ring's
   outline-offset:3px draws it OUTSIDE the button either way, onto that same
   --c-bg, regardless of which variant's own fill it surrounds. --c-primary
   already IS this button's own border/text (or fill) colour, so reusing it
   for the ring, rather than introducing a third colour, matches the button's
   own palette and measures the same verified 10.4:1 on --c-bg. */
.mp-carousel__btn:focus-visible { outline-color: var(--c-primary); }

.mp-carousel__icon { display: block; }
/* Same glyph, both buttons (3546:105120's "Vector" is identical geometry on
   both the outline-circle and filled-circle instances, only recoloured) —
   "previous" is the "next" arrow mirrored, matching how Figma itself built
   the instance rather than authoring a second path. */
.mp-carousel__icon--prev { transform: scaleX(-1); }

/* RTL: the carousel's logical "next" (deeper into the track) is now
   visually LEFTWARD, and "previous" is visually RIGHTWARD — the opposite of
   LTR. Swap which icon is mirrored rather than reordering the buttons; flex
   row order already reverses for free under dir="rtl". */
[dir="rtl"] .mp-carousel__icon--prev { transform: none; }
[dir="rtl"] .mp-carousel__icon--next { transform: scaleX(-1); }

.mp-carousel {
  /* Shared by the track's padding-inline and this scroller's scroll-padding
     below, so a card's snapped rest position and its visual alignment with
     the heading above can never drift apart. Matches .mp-container's own
     effective offset: max-width there is (container + gutter*2), so once
     the viewport exceeds that, .mp-container's content edge sits at
     (100% - container) / 2 from the viewport edge, not at a bare --gutter —
     .mp-carousel isn't wrapped in .mp-container (it needs to bleed past it
     for the scrollable peek), so without this it would misalign with the
     heading above on any viewport wider than ~1440px. */
  --carousel-edge: max(var(--gutter), calc((100% - var(--container)) / 2));
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;             /* the universal reduced-motion rule near the top of this file forces this to auto */
  scroll-padding-inline-start: var(--carousel-edge);
  scrollbar-width: none;
}
.mp-carousel::-webkit-scrollbar { display: none; }

.mp-carousel__track {
  display: flex;
  gap: var(--gap-grid);                /* 62px desktop / 32px tablet / 16px mobile — 3546:105132 x60 -> 3546:105155 x490 */
  padding-inline: var(--carousel-edge);
}
/* Shared with Task 10's insight cards — both use --card-width. */
.mp-carousel__track > * {
  flex: 0 0 auto;
  scroll-snap-align: start;
  inline-size: var(--card-width);
}

@media (max-width: 768px) {
  /* Confirmed against the Figma source, not just following the brief's
     illustrative CSS: the mobile Arrow-Slider instance (3417:2945) sits at
     x=577 inside a 390-wide frame — off-canvas in the design itself. The
     peek + swipe is the only affordance there, same as every other
     carousel/marquee already shipped on this page (Task 7b/8). */
  .mp-carousel__controls { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .mp-carousel__btn { transition: none; }
}

/* ─ Provider card (3546:105132) ────────────────────────────────────────── */
.mp-provider { display: flex; flex-direction: column; }

.mp-provider__media {
  position: relative;
  inline-size: 100%;
  /* Safe use of aspect-ratio: this box's inline-size is the fixed
     --card-width token, never the viewport or an fr track — the documented
     trap (aspect-ratio turning WIDTH into HEIGHT on a full-bleed/fr
     element) does not apply to a card whose width is already pinned. */
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-card);
}
.mp-provider__media img { inline-size: 100%; block-size: 100%; object-fit: cover; }

.mp-provider__verified {
  position: absolute;
  inset-block-start: 13px;
  inset-inline-start: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  block-size: 30px;
  padding: 4px 6px;
  background: var(--c-success);
  color: var(--c-white);
  border-radius: 28px;      /* badge radius — literal, see --radius-pill's own comment: chips/badges are deliberately not tokenised the same as buttons */
  font-size: var(--fs-label);
  font-weight: var(--fw-bold);
  line-height: var(--lh-badge);
  white-space: nowrap;
}

.mp-provider__name {
  margin-block-start: 16px;   /* image -> [name,area] group   3546:105135 gap-[16px] */
  font-size: var(--fs-card);
  color: var(--c-primary);
}
.mp-provider__area {
  margin-block-start: 8px;    /* name -> area   3546:105142 gap-[8px] */
  font-size: var(--fs-label);
  font-weight: var(--fw-book);
  color: var(--c-ink);
  letter-spacing: 0.14px;     /* 3546:105145 tracking-[0.14px] */
}

.mp-provider__stats {
  margin-block-start: 12px;   /* [name,area] group -> stats row   3546:105134 gap-[12px] */
  display: flex;
  gap: 10px;                  /* 3546:105146 gap-[10px] */
  block-size: 57px;           /* identical on both frames — 3546:105146 AND 3417:2971 both h-[57px], so this is a literal, not a token that would (wrongly) shrink at 768px */
}
.mp-provider__stats > div {
  flex: 1 0 0;
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  block-size: 100%;
  padding: 8px 7px;
  gap: 2px;                     /* 3546:105148 gap-[2px] between the caption and figure */
  background: var(--c-stat-pill);
  border-radius: var(--radius-sm);
}
.mp-provider__stats dt {
  margin: 0;
  font-size: var(--fs-micro);   /* this exact caption (3546:105149) is what --fs-micro was measured from, incl. its 11px mobile value */
  font-weight: var(--fw-book);
  color: var(--c-micro);
  letter-spacing: 0.12px;       /* 3546:105149 tracking-[0.12px] */
}
.mp-provider__stats dd {
  /* Literal, not var(--fs-nav): the two share the same 20px desktop value
     by coincidence, but --fs-nav shrinks to 16px at 768px while this figure
     stays 20px on both frames (3546:105150 vs the mobile card's "4.9" text
     box height, 24px, implying an unchanged ~20px font) — reusing the nav
     token would wrongly shrink it, exactly the mistake Task 8's own
     stats-title override was written to avoid. */
  margin: 0;
  font-size: 20px;
  font-weight: var(--fw-book);
  color: var(--c-stat-value);
  line-height: normal;
}

/* =========================================================================
   Task 10 — "Download the App" band + insights carousel (§5 rows 8-9)

   Figma kyudcw9zAgmniIXWNveugA:
     app:      desktop 3546:104882 (copy 3546:104883 + collage 3546:104895)
               mobile  3417:3268 (phones 3417:3267, copy 3417:3251)
     insights: desktop 3546:104699 (heading 3546:104700 + card row
               3546:104714), card 3546:104715
               mobile  3417:3273, card 3417:3290

   The carousel itself (.mp-carousel / .mp-carousel__track / mpCarousel) is
   Task 9's contract, reused verbatim below — nothing in this section
   redefines scrolling, snapping, or the arrow icon. Only the two sections'
   own layout and the insight card are new.
   ========================================================================= */

/* ─ "Download the App" ────────────────────────────────────────────────── */
/* Mobile-first: single column, natural DOM order (mockups then copy —
   matches 3417:3268's actual stacking). Desktop below turns this into a
   two-column row and uses `order` to swap which side renders first,
   because the DOM must stay mockups-then-copy (mobile source order, §10)
   while desktop shows copy on the LEFT. */
.mp-app__grid { display: grid; gap: 24px; }  /* mobile: 3417:3267 ends y=327, 3417:3251 starts y=351 → 24px */

.mp-app__mockups img { display: block; inline-size: 100%; block-size: auto; }

.mp-app__copy {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 18px;                       /* 3546:104883 gap-[18px] */
}

.mp-app__heading { display: flex; flex-direction: column; gap: 4px; }  /* 3546:104884 gap-[4px] */

.mp-app__available {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 18px;                       /* 3546:104885 gap-[18px] */
  font-size: var(--fs-lead);       /* 3546:104886 text-[28px] */
  font-weight: var(--fw-medium);
  color: var(--c-stat-value);      /* literal black in Figma (text-black), same token the provider stat figure uses — not --c-ink */
}

.mp-app__glyphs { display: inline-flex; align-items: center; gap: 12px; }  /* 3546:104887 gap-[12px] */
.mp-app__glyph { inline-size: 32px; block-size: 32px; }                    /* 3546:104888/104889 size-[32px] */

/* One clipped gradient across the whole string (3546:104890) — see the
   .mp-text-gradient--alt modifier above. */
.mp-app__title { font-size: var(--fs-h1); }

.mp-app__body {
  font-size: var(--fs-lead);       /* 3546:104891 */
  color: var(--c-ink);
}

/* .mp-btn--pill already supplies bg/colour/radius/44px floor; only the
   larger Figma type size (24px vs the shared pill's 18px) is section-owned.
   .mp-btn--pill itself has no justify-content (never needed it — every
   other pill on this page is content-sized), but 3546:104892 is explicitly
   justify-center and this pill goes full-width at mobile (below), where an
   un-centered icon+text pair would stick to the start edge of the row. */
.mp-app__cta { font-size: var(--fs-card); justify-content: center; }  /* 3546:104894 */

@media (max-width: 768px) {
  .mp-app__glyph { inline-size: 18px; block-size: 18px; }  /* 3417:3256/3257, measured exactly */
  .mp-app__cta { inline-size: 100%; }                       /* "full-width pill" per brief §10 */
}

@media (min-width: 769px) {
  .mp-app__grid {
    /* Measured column split on 3546:104882: copy 674 / gap 33 / remaining
       613 for the collage (1320 content − 674 − 33). Fluid fr ratio, not
       px, so it holds at any width — same technique as .mp-simpler__grid. */
    grid-template-columns: minmax(0, 674fr) minmax(0, 613fr);
    align-items: center;
    gap: 33px;
  }
  /* DOM stays mockups-then-copy (mobile order); only the VISUAL order flips,
     so copy lands in the grid's first (674fr, left) column despite being
     the second element in the markup. */
  .mp-app__copy { order: -1; align-items: flex-start; text-align: start; }
  .mp-app__available { justify-content: flex-start; }
}

/* ─ Insights heading + carousel wrapper ───────────────────────────────── */
.mp-insights__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-block-end: var(--space-heading);   /* mirrors .mp-providers__head — 3546:104700 → 3546:104714 */
}

/* New "View all articles" link to the full /blog list, paired with the
   existing arrow buttons rather than replacing anything about them. */
.mp-insights__actions { display: flex; align-items: center; gap: var(--arrow-gap); }

.mp-insights__all {
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  color: var(--c-primary);
  text-decoration: none;             /* the site reset is `a { color: inherit }` only */
  text-underline-offset: 3px;
}
.mp-insights__all:hover { text-decoration: none; }

/* No mobile override needed here: .mp-carousel__controls goes display:none
   at <=768px (below), which drops it from the flex formatting context
   entirely -- flexbox `gap` never reserves space for an out-of-flow sibling,
   so .mp-insights__actions naturally collapses to just the link with no
   leftover gap. */

/* Top boundary (app → insights, 193px measured) is within 1px of two
   adjacent .mp-section defaults (96+96=192) and needs no override. Bottom
   boundary (insights → quote, 261px) is a genuine outlier — Task 3's own
   report flags it as "should override locally rather than bend the token."
   This section owns the extra 165px (261 − the next section's assumed
   default 96px top) the same way Task 9's .mp-providers owns its own
   outsized neighbour gap. */
.mp-insights { padding-block: var(--space-section) 165px; }

@media (max-width: 768px) {
  /* The 261px outlier is desktop-only. Task 3's report is explicit that
     mobile is uniform: every band on 3417:3269, including insights→quote,
     is 63px apart — so the literal 165px must NOT survive to mobile the
     way it does on desktop; fall back to the default token pair instead. */
  .mp-insights { padding-block-end: var(--space-section); }
}

/* ─ Insight card (3546:104715) — mirrors .mp-provider's shape ─────────── */
.mp-insight { display: flex; flex-direction: column; gap: 8px; }
/* Whole-card click target for the homepage Insight cards (Task: link blogs to
   blog details). The anchor lives on the TITLE — so its accessible name is the
   headline, not "Read more" eight times — and this pseudo-element stretches
   that one anchor across the card. One link, one tab stop, full-card hit area.

   The card needs its own stacking context for the stretch to be bounded by it;
   .mp-insight is a plain flex container, so position:relative is added here. */
.mp-insight { position: relative; }

.mp-insight__link { color: inherit; text-decoration: none; }
.mp-insight__link:hover { text-decoration: underline; }

.mp-insight__link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* The "Read more" affordance is now an inert <span> (aria-hidden, no href), so
   it needs the anchor styling it used to inherit. */
.mp-insight__more { pointer-events: none; }

/* Keep the carousel's own controls clickable above the stretched link. */
.mp-insight__media { z-index: 0; }
  /* 3546:104720 "Explanation" gap-[8px] */

.mp-insight__media {
  position: relative;
  inline-size: 100%;
  /* Safe: this box's inline-size is the fixed --card-width token via
     .mp-carousel__track > *, never the viewport or an fr track — same
     reasoning as .mp-provider__media. */
  /* Square, as Figma draws it — the card's proportions stay as designed.
     What changed is how the PHOTO sits inside it (see the img rule below):
     the banners are wide artwork with baked-in text (live sources measure
     1200x600 and 1223x473), so cropping them to fill a square cut their own
     headlines in half. They are now contained, not cropped. */
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-card);              /* 3546:104723 */
  /* Only visible for the split second before the blurred backdrop paints. */
  background: var(--c-surface-tint);
}
/* Both layers fill the square completely, so the media box is edge to edge
   with no dead space and every card is identical in size. */
.mp-insight__media-bg,
.mp-insight__media-img {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
}

/* Backdrop: the same file, cropped to fill. It only ever reads as colour and
   soft shape, so cropping it costs nothing. scale(1.06) hides the transparent
   fringe a blur pulls in from the element's edges. */
.mp-insight__media-bg {
  object-fit: cover;
  filter: blur(20px) saturate(1.15);
  transform: scale(1.06);
}

/* Foreground: the real banner, WHOLE. `contain` because these are designed
   artwork with text in them, not incidental photography — a square crop was
   cutting the artwork's own headline in half ("Your Dog Wants a Coffee Date
   Too" rendering as "Dog Wants / ffee Date Too"). */
.mp-insight__media-img { object-fit: contain; }

/* Same geometry as .mp-provider__verified (30px tall, 8/13 inset, 28px
   radius, 14px bold badge type) — only the background token differs
   (--c-scrim vs --c-success). Not extracted into a shared class: this
   codebase's sections each own their complete rule set (see .mp-tile /
   .mp-stat / .mp-provider), so this mirrors rather than imports. */
.mp-insight__tag {
  position: absolute;
  inset-block-start: 13px;
  inset-inline-start: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  block-size: 30px;
  padding: 4px 6px;
  background: var(--c-scrim);      /* 3546:104736 */
  color: var(--c-white);
  border-radius: 28px;
  font-size: var(--fs-label);
  font-weight: var(--fw-bold);
  line-height: var(--lh-badge);
  white-space: nowrap;
}

/* Figma's author name/pipe are Roboto (104744/104747) — Task 3's report
   flags this as a leftover default, inconsistent with the Gotham brand
   face, and says Task 10 should render both in Gotham. Done here: no
   font-family override, so both inherit --font-display from <body>. */
.mp-insight__author {
  display: flex;
  align-items: center;
  gap: 8px;                        /* 3546:104739 gap-[8px] */
  font-size: var(--fs-body-sm);    /* 16px, matches the node's own text-[16px] */
  font-weight: var(--fw-medium);
  color: var(--c-ink);
}
.mp-insight__author img { inline-size: 24px; block-size: 24px; border-radius: 50%; object-fit: cover; }

.mp-insight__meta {
  display: flex;
  align-items: center;
  gap: 12px;                       /* 3546:104745 gap-[12px] */
  font-size: var(--fs-label);
  color: var(--c-ink-muted);       /* 3546:104745 text-[color:var(--text-grey,#4f4f4f)] */
}

.mp-insight__body { display: flex; flex-direction: column; gap: 2px; }  /* 3546:104749 gap-[2px] */

/* Figma truncates every sample title to one line (whitespace-nowrap +
   text-ellipsis on 3546:104750/104793/104826/104869) rather than letting
   card height vary with title length. */
.mp-insight__title {
  font-size: var(--fs-card);
  color: var(--c-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Figma clips this to 2 lines with ellipsis on every sample card
   (104751/104794/104827/104870) — same "don't let one field's length break
   card rhythm" intent as the title's single-line truncation above. Most
   live descriptions are the ~55-char SEO line the brief describes and never
   reach 2 lines, but at least one live row (id 92) is a multi-paragraph
   outlier — without this clamp that one card visibly grows taller than its
   seven siblings in the carousel. No physical properties: -webkit-box-orient
   is a keyword property, not a margin/padding/inset direction. */
.mp-insight__excerpt {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: var(--fs-body-sm);    /* 3546:104751, the one node --fw-light was measured from */
  font-weight: var(--fw-light);
  color: var(--c-ink);
  line-height: var(--lh-tight);
}

.mp-insight__more {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  inline-size: 100%;
  min-block-size: 44px;            /* touch-target floor — Figma draws 32px (3546:104752), same override .mp-btn--pill already applies to its own 38px Figma value */
  padding-inline: 12px;
  background: var(--c-surface-tint);
  border-radius: 41px;             /* literal "Read More" bar radius, see --radius-pill's own comment */
  color: var(--c-primary);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  letter-spacing: 0.56px;          /* 3546:104754 tracking-[0.56px] */
  transition: transform var(--dur) var(--ease);
}
.mp-insight__more:hover { transform: translateY(-2px); }

@media (prefers-reduced-motion: reduce) {
  .mp-insight__more { transition: none; }
}

/* =========================================================================
   Task 11 — Pull-quote band + testimonials accordion (§5 rows 10-11)

   Figma kyudcw9zAgmniIXWNveugA:
     quote:        desktop — loose canvas children, NO wrapping frame
                   (verified via get_metadata): mark 3546:105111 (467x467,
                   exported quote-mark.svg — Task 3 already pulled it from
                   svgAssets rather than the contaminated node `export`),
                   body 3546:105114 (--fs-h2-lg, Book weight), attribution
                   3546:105115 (32px Book, literal)
                   mobile 3417:3430 (mark 3417:3431 72x72 ABOVE the text
                   per brief §10, body 3417:3434, attribution 3417:3435)
     testimonials: desktop 3546:104959 (heading 3546:104963 + Arrow-Slider
                   3546:104964 + spine row 3546:104975), active card
                   3546:105029, name 3546:105035 (--fs-name), quote
                   3546:105044 (--fs-card)
                   mobile 3417:3441 (heading 3417:3445, Arrow-Slider
                   off-canvas 3417:3446), spine row 3530:62802

   CONFIRMED ACCORDION, not a Task 9/10 carousel — get_design_context on
   3546:104959 and an isolated get_screenshot of 3417:3430/3441 both show
   narrow "spine" cards (avatar + vertical name + stars) with ONE card
   expanded, exactly the task-11 brief's Step 2/3 shape (full trail in
   task-11-report.md). The .mp-carousel__controls/__btn/__icon arrow-button
   contract is reused verbatim below (same Figma component instance,
   re-skinned) bound to mpAccordion's own next()/prev() — never mpCarousel's
   scrollBy. The scroll-snap/track half of that contract is NOT reused
   (this widget is click/tap/arrow-key state, never native scroll-snap);
   only the --carousel-edge bleed technique is, redeclared below since
   custom properties don't cross unrelated selectors.
   ========================================================================= */

/* ─ Pull-quote band ────────────────────────────────────────────────────── */
/* Overrides only the END of .mp-section's own padding-block shorthand —
   the START stays the unmodified 96px default, which is exactly what the
   measured 261px insights→quote gap already assumes (165px is insights'
   OWN bottom override, Task 10). 60px (--space-section-sm) here plus
   testimonials' own unmodified 96px top default = 156px, within the same
   1px tolerance Task 10's own boundary math already accepted, against a
   measured 155px. */
.mp-quote { padding-block-end: var(--space-section-sm); }

.mp-quote__inner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 16px;
}

/* Mobile (default): a small, readable, in-flow mark ABOVE the text (brief
   §10) — not a background watermark. */
.mp-quote__mark {
  display: block;
  inline-size: 72px;   /* 3417:3431 */
  block-size: 72px;
  margin-block-end: 8px;
}

.mp-quote__body {
  max-inline-size: 1193px;         /* 3546:105114's own text box width */
  font-size: var(--fs-h2-lg);
  font-weight: var(--fw-book);
  line-height: var(--lh-heading);  /* Figma "leading: normal" — same convention Task 3 already applies to every heading */
  color: var(--c-ink);
  margin: 0;
  /* Per-PHRASE (not per-word) gradient highlight — see quote.blade.php's
     header comment for the full re-derivation and the marker-parsing
     approach. .mp-quote__highlight/.mp-text-gradient(--alt|--reverse)
     below style the 5 highlighted spans the @php block emits; this rule
     only owns the plain-text colour/weight surrounding them. */
}

.mp-quote__highlight { font-weight: var(--fw-medium); }  /* 3546:105114 — highlighted spans inherit the <p>'s own Medium weight; only the PLAIN text is explicitly downgraded to Book/Light in Figma */

@media (max-width: 768px) {
  .mp-quote__body { font-size: 22px; }  /* 3417:3434 literal — 2px under the shared --fs-h2-lg mobile value (24px, also used by .mp-simpler__title); scoped here rather than changed globally so that other consumers of the token are untouched */
}

.mp-quote__attribution {
  font-size: 32px;   /* 3546:105115, literal — coincides with --fs-name's value by accident, NOT that token */
  font-weight: var(--fw-book);
  line-height: 1.6;
  color: var(--c-ink);
  margin: 0;
}

@media (max-width: 768px) {
  .mp-quote__attribution { font-size: 18px; }  /* 3417:3435 h29 at lh 1.6 ⇒ ~18px */
}

@media (min-width: 769px) {
  /* Desktop: an oversized watermark BEHIND the centred text (brief §10),
     not the small in-flow mark mobile uses above it.

     RE-DERIVED this pass with pixel evidence, not a screenshot judgement
     call: get_screenshot on the full page (3546:104515) at native
     resolution (1440x9236), cropped to the quote band and scanned with
     PIL for the mark's own non-background pixels, isolated from the text
     by sampling rows/columns the text never reaches. Measured ink bbox:
     x 60-520, y 6350-6742 (page-local coords). The raw metadata for this
     loose node reports x=527 y=6780 — which is EXACTLY the measured
     top-left (60, 6312 — back-solved from the 467x467 native canvas and
     the measured ink) plus (467, 467), i.e. the metadata is reporting the
     diagonally-opposite corner of a 180deg-rotated square. This closes out
     the "rotation artefact" flagged in task-11/13: the node genuinely
     renders left-aligned with the container's own gutter, top edge 104px
     ABOVE the quote text's own top line — not centred, and not at a
     screenshot-eyeballed -64px/400px.

     inset-inline-start uses --gutter (not 0): insets on an absolutely
     positioned element resolve against .mp-quote__inner's PADDING box,
     and .mp-quote__inner (= .mp-container) carries padding-inline:
     var(--gutter) — so --gutter here lands the mark at the same content
     edge as the text, matching the measured x=60, at every width
     (including >1760px, where .mp-container's own padding no longer
     equals a flat 60px gutter but the formula still holds). Logical
     inset-inline-start (not a physical left + translateX) so this mirrors
     correctly to the end/right side on /ar, same reasoning the previous
     pass already established. */
  .mp-quote__mark {
    position: absolute;
    inset-block-start: -104px;
    inset-inline-start: var(--gutter);
    inline-size: 467px;   /* native asset size — was scaled to 400px on a judgement call; the measured ink extent (460x392) matches the native canvas almost exactly, i.e. Figma renders it unscaled */
    block-size: 467px;
    z-index: 0;
  }
  .mp-quote__body,
  .mp-quote__attribution {
    position: relative;
    z-index: 1;
  }
}

/* ─ Testimonials heading + rail ────────────────────────────────────────── */
.mp-reviews__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-block-end: var(--space-heading);
}

/* Scroller / track split for the same reason as .mp-carousel /
   .mp-carousel__track (Task 9's own comment): trailing padding-inline on
   the SCROLLING element itself is dropped at scroll-end in Firefox, so the
   edge-alignment padding lives on an inner, non-scrolling track instead.
   --carousel-edge is Task 9's own formula, redeclared here (custom
   properties don't cross unrelated selectors) so this rail's peek aligns
   with the heading above it exactly the way every other carousel section
   on this page already does. */
.mp-reviews__rail {
  --carousel-edge: max(var(--gutter), calc((100% - var(--container)) / 2));
  overflow-x: auto;
  scroll-padding-inline-start: var(--carousel-edge);
  scrollbar-width: none;
  padding-block: 16px;  /* clears --shadow-testimonial's 12.2px blur so the open card's own shadow never clips against the scroller */
  /* Fix round 1, Important #2 — formally bounds the row's own layout so
     nothing inside it (the flex-basis change below) can ever force a
     layout recalculation of anything OUTSIDE this rail. Paired with
     .mp-reviews__track's fixed block-size just below, which is what
     actually keeps this rail's own intrinsic height constant in the first
     place — contain:layout alone doesn't stop an intrinsically-sized
     ancestor from changing size when its content does; the fixed height is
     the half that does that work. */
  contain: layout;
}
.mp-reviews__rail::-webkit-scrollbar { display: none; }

.mp-reviews__track {
  display: flex;
  align-items: center;   /* centres each closed 364px (or reduced-motion-instant 422px) spine within the fixed row below */
  /* 2px, not 8px. Measured off a Figma render by scanning a horizontal line
     across the rail and counting background-coloured runs between cards:
     Figma's spine cards are separated by a 2px hairline, ours were at 8px.
     That is what made the collapsed cards read as separate little chips with
     their avatars floating in space, instead of Figma's near-continuous band
     of shapes. The card width (122px, 3546:104977) and the avatar box (68px)
     were both already correct — the spacing between them was the whole
     discrepancy. */
  gap: 2px;
  padding-inline: var(--carousel-edge);
  /* Fixed to the OPEN card's own height (422px, 3546:105029) rather than
     left intrinsic. Before this fix, toggling a card open/closed changed
     .mp-review's own block-size (364 -> 422), and since this track was
     otherwise sized by its tallest child, that grew/shrank the WHOLE
     ROW's height on every single toggle — visibly shoving .mp-quote above
     and whatever sits below out of place, a page-level reflow the
     original "confined to a single row" reasoning didn't account for
     (that reasoning only ever addressed the inline-axis flex-basis
     change). Fixing the row's own cross-size is what makes "confined to a
     single row" actually true. */
  block-size: 422px;
}

.mp-review {
  position: relative;
  flex: 0 0 122px;               /* 3546:104977 w122.5, rounded */
  block-size: 364px;             /* 3546:104977 h364 */
  border-radius: var(--radius-md);
  overflow: hidden;
  contain: layout;                /* Fix round 1, Important #2 — see .mp-reviews__rail's own contain:layout for the paired reasoning */
  /* Fallback only — pre-hydration, and for any state Alpine hasn't yet
     classified as --lighter/--darker below. Once mpAccordion mounts, every
     non-active card always gets one of those two classes, which override
     this. See the ramp rules right after .mp-review.is-open for the real
     per-card colour (Figma's six distinct spine hexes, reverse-engineered
     into a formula instead of hardcoded — fidelity-testimonials-report.md
     has the full derivation). */
  background: var(--c-primary);
  /* Fix round 1, Important #2 — flex-basis is a layout property, not
     opacity/transform, which the project's Global Constraints require;
     the coordinator ruled Global Constraints govern over the brief's own
     "transition: flex-basis" snippet, and asked for either a
     compositor-only rewrite or a justified, documented, mitigated
     exception. KEEPING flex-basis (documented exception), because a
     compositor-only rewrite would distort real content, not just cost
     more:

     - The collapsed spine (122x364) and open card (634x422) are
       different ASPECT RATIOS, not just different sizes — a transform:
       scale() from one to the other needs different X/Y factors, which
       stretches everything inside non-uniformly: avatar photos, the
       quote paragraph's glyphs, the rounded corners.
     - The two states don't just resize, they RESTRUCTURE: .mp-review__name
       flips writing-mode (vertical-rl -> horizontal-tb) and
       .mp-review__trigger switches display (flex column -> grid). Neither
       of those can be animated via transform/opacity at all, compositor
       or not — they're discrete, instant swaps in ANY implementation, so
       a transform-based rewrite would still need an opacity-crossfade
       content swap at the midpoint; only the OUTER container's size/
       position could become pure transform, via a position:absolute
       overlay + counter-scaled children technique. That's a materially
       larger rewrite carrying real regression risk (this exact page's own
       ledger records seven prior scroll/animation bugs that were only
       ever caught by opening a real browser — none of which curl or
       static analysis could see), and this task's verification is
       explicitly curl/static-analysis only, no browser. Attempting it
       blind was judged the worse risk than a documented, mitigated
       exception.

     Mitigations applied instead (per the coordinator's ruling for the
     "justified exception" path):
     - BOUNDED: HomepageData::testimonials() caps this list at
       ->limit(8) — never more than 8 flex items animating, never
       unbounded.
     - CONTAINED: contain:layout here and on .mp-reviews__rail, plus
       .mp-reviews__track's now-fixed 422px block-size (see that rule's
       own comment) — the reflow this transition causes cannot reach
       outside a single fixed-size row.
     - REDUCED MOTION IS GENUINELY INSTANT: transition:none (below) removes
       the transition entirely rather than shortening its duration — the
       flex-basis (and block-size, image sizes, writing-mode, display)
       change all apply in the SAME synchronous style/layout pass as the
       .is-open class toggle, with no intermediate animated frames to
       reason about. This isn't reasoned-only: it's how CSS `transition:
       none` works by definition, not a claim that needs a browser to
       confirm. */
  transition: flex-basis var(--dur) var(--ease);
}
.mp-review.is-open {
  flex-basis: 634px;   /* 3546:105029 */
  block-size: 422px;   /* 3546:105029 */
  background: var(--grad-testimonial);
  box-shadow: var(--shadow-testimonial);
  /* Fix round 2 — trigger (header) + panel now stack as flex children so
     the panel can be given an actual height CEILING (below) rather than
     just growing to fit its content. See .mp-review__panel's own
     is-open rule for why this matters. */
  display: flex;
  flex-direction: column;
}

/* Fidelity pass — collapsed spine colour ramp (fidelity-testimonials-
   report.md has the full derivation, script, and evidence). Figma gives
   each of the 6 non-active spines its own literal hex (3546:104977…
   105080); Task 11 correctly refused to hardcode six positional hexes for
   a dynamic 1-8 length list, but then dropped the RULE too, leaving one
   flat --c-primary. Re-extracted the actual hexes via get_design_context
   and decomposed each to HSL:

     dist   hex        H       S      L
     -3     #1571b1   204.6°  78.8%  38.8%
     -2     #2789cd   204.6°  68.0%  47.8%
     -1     #2b9ee1   202.1°  75.2%  52.5%
      0     [active card — var(--grad-testimonial)]
     +1     #1a3a7f   221.0°  66.0%  30.0%   <- literally var(--c-primary)
     +2     #112b61   220.5°  70.2%  22.4%
     +3     #03112f   220.9°  88.0%   9.8%

   HUE IS LOCKED PER SIDE: ~202-205° left (== --c-accent's own H 201.4°/S
   80.3%), ~220-221° right (== --c-primary's own H 221.0°/S 66.0% almost
   exactly — the dist+1 card IS --c-primary, unmodified). Only LIGHTNESS
   steps with distance. That's a two-hue, distance-scaled lightness ramp —
   exactly what a designer gets by duplicating one swatch and dragging only
   its Lightness slider per step — not six independent picks, which is why
   this generalises: reproduced below with hsl()+calc()+clamp(), driven by
   --review-dist (each card's own |index-active|, set inline by
   mpAccordion — see review-card.blade.php) so it degrades correctly for
   any list length from 1 to HomepageData's own ->limit(8), not just
   Figma's fixed 7-card reference layout.

   LEFT is capped at 38% lightness, not Figma's own 52.5% at dist=1:
   reproducing that literal value would drop the spine's 16px Medium name
   label (small text — needs 4.5:1 per WCAG 1.4.3, this card's real
   customer name, not decorative) to ~2.9:1 against the white
   .mp-review__trigger text colour — an accessibility regression this pass
   won't introduce chasing pixel fidelity (computed with the same sRGB
   luminance method Task 11's own focus-ring fix used). 38% keeps dist=1 at
   4.90:1 and every step further left only gets MORE contrast (darker),
   floored at --c-primary's own 30%/7.03:1 so the left side never goes
   darker than the original flat treatment, only ever lighter approaching
   the active card. RIGHT has no such ceiling — darkening only increases
   white-text contrast — so it runs to a 6% floor (never pure black, which
   would erase the card's own edge against the page). */
.mp-review--lighter {
  background: hsl(201deg 80% clamp(30%, calc(38% - (var(--review-dist, 1) - 1) * 4%), 38%));
}
.mp-review--darker {
  background: hsl(221deg 66% clamp(6%, calc(30% - (var(--review-dist, 1) - 1) * 8%), 30%));
}

@media (prefers-reduced-motion: reduce) {
  .mp-review { transition: none; }
}

.mp-review__trigger {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  inline-size: 100%;
  block-size: 100%;
  padding-block: 32px;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--c-white);
  /* Fix round 1's re-review nit: the trigger was a sharp rectangle inside
     .mp-review's 12px-rounded, overflow:hidden card, so the focus ring's
     four corner tips (below) got a few px clipped by the card's own
     rounded-corner clip region — the straight edges (most of the ring)
     were already fully visible, so this was a nit, not a defect, but it's
     a one-line fix: matching the trigger's own radius to the card's means
     the ring's corners follow the same curve instead of poking past it. */
  border-radius: inherit;
}

/* Fix round 1, Critical #1 — the global :where(a, button, [tabindex]):focus-visible
   ring (outline: 3px solid var(--c-accent); outline-offset: 3px) draws
   OUTSIDE the button's border edge. This button fills .mp-review's box
   exactly (100%/100%, zero margin, box-sizing: border-box from the global
   reset), and .mp-review clips with overflow:hidden (load-bearing — its
   own rounded corners, and it's what contains the ::after quote-glyph
   flourish that deliberately protrudes past the card below) — so the ring
   was being painted straight into, and clipped away by, that boundary.
   Zero visible focus indicator on every trigger, collapsed OR open.
   outline-offset:-3px draws the ring INWARD instead, which keeps it inside
   .mp-review's own clip region regardless of how narrow the box gets —
   including the 44px mobile collapsed floor (44 - 2*3 = 38px ring,
   comfortably visible).

   Colour is overridden here too, not inherited from the global accent
   ring. The OPEN card's background is --grad-testimonial, a gradient
   between --c-primary (relative luminance 0.048) and --c-accent (0.333) —
   two luminances close enough together that NO single solid colour can
   reach 3:1 against the gradient's full range (solving for the luminance
   that maximises the WORSE of the two contrast ratios gives ~1.98:1 as the
   theoretical best any colour could do end-to-end). The default accent
   ring computes to ~2.8:1 / ~1.4:1 at the two points the trigger's own
   bounding box actually spans on the real gradient geometry (the trigger
   sits in the card's upper region — see task-11-report.md "Fix round 1"
   for the full projection working) — both under 3:1. White clears 3:1 at
   both of those real points (3.78:1 / 7.77:1 desktop, wider margins at
   narrow mobile widths where the trigger sits further still from the
   gradient's accent-heavy corner) AND against the collapsed spine's flat
   --c-primary background (10.74:1) — chosen because it's the one colour
   that actually measures out, not merely "looks fine". */
.mp-review__trigger:focus-visible {
  outline-color: var(--c-white);
  outline-offset: -3px;
}

.mp-review.is-open .mp-review__trigger {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  column-gap: 16px;
  row-gap: 6px;
  align-items: center;
  justify-items: start;
  inline-size: auto;
  block-size: auto;
  padding: 32px 32px 0;
  /* Fix round 2 — the header is now a flex child of .mp-review.is-open
     (column stack); flex-shrink:0 keeps it at its natural content size
     (avatar + name/stars) always, so it's the PANEL below — not the
     avatar or the readable name — that gives up space first when content
     needs more room than the card's fixed 422px. */
  flex-shrink: 0;
}

.mp-review__avatar {
  inline-size: 68px;    /* spine size — matches Figma's own 68x68 mask box exactly, unchanged */
  block-size: 68px;
  /* Fidelity pass — was a plain circle (border-radius:50%), which Figma
     never uses here. Downloaded and inspected 3 of the real exported
     avatar masks (fidelity-testimonials-report.md): 2 of 3 collapsed-spine
     slots are a plain rounded square (measured radius 16px on a 68px box);
     the 3rd (one specific stock placeholder) is a one-off decorative
     scalloped/cloud mask, and the open card's own mask (confirmed via
     get_screenshot, not just the path data) is a taller "badge" shape —
     rounded-rect body + a fully-rounded pill lobe on top. All of this is
     mask geometry applied to arbitrary uploaded review photos, not a
     circle in any sampled case. No real photo exists for any of the 8 live
     reviews (verified data fact) — avatar() always falls back to
     avatar-placeholder.svg, which was itself hand-authored for Task 11's
     circle and has been redrawn for this squircle (see that file's own
     comment) so the frame reads as deliberate, not a broken image.
     Reproducing the exact scalloped/badge SVG paths would need a shipped
     mask asset for a placeholder nobody will ever see a real photo behind
     — used the DOMINANT, shared pattern (plain rounded square, ~16px
     radius on 68px, i.e. ~24%) instead, via plain border-radius, so it
     costs zero new asset bytes and needs no clip-path. */
  /* CORRECTED — measured off the user's own tight crop of the Figma avatar
     row, which is the first render clean enough to read the silhouette from.
     For each spine avatar I dumped the mask width row by row against the
     card's sampled background colour:

       Aya Khalil   body y34-y101 -> 68w x 68h, SQUARE
       Lana Ghanim  same, 69w
       Rashed Moh   same, 68w

     The corners are NOT uniform. The top reaches full width within ~2 rows
     (inset 1.5px at the first row), while the bottom tapers over ~10 rows
     from full width down to 44px — an inset of 12px per side at the last
     row. Fitting a circular corner to Rashed Moh's taper (inset 2px at 9
     rows up, 12px at the last row) gives r ≈ 20px, and both other avatars
     agree.

     So: a nearly-square top and a strongly rounded bottom — the "shape, not
     square" the user was pointing at. The previous uniform 16px was too
     round on top and not round enough underneath.

     The open card's own 129px mask was measured from the same crop and is
     near-symmetric (13px inset top, 10px bottom) — its existing
     19/19/15/15 is within 2px, so it is left alone. */
  /* MASK, not clip-path, and not border-radius.

     border-radius could never express these shapes at all — several have
     concave joins between lobes (the scalloped top, the double-lobe "B").
     SVG clip-path DID express them, but Chrome does not antialias
     `clip-path: url(#…)`, so every curve shipped visibly jagged.
     mask-image is antialiased, so the same vectors come out smooth.

     Each mask is the real Figma path as an inline SVG data URI carrying its
     own viewBox with preserveAspectRatio='none'; mask-size:100% 100% then
     stretches it to whatever box it lands in — 68px spine, 129px open card,
     91px mobile. That is what keeps ONE definition valid at every size.

     border-radius cleared so it cannot fight the mask on the initials
     fallback tile, which shares this box. */
  border-radius: 0;
  -webkit-mask-image: var(--avatar-mask, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='272' height='272' viewBox='0 0 68 68' preserveAspectRatio='none'%3E%3Cpath d='M52 0C60.8366 0 68 7.16344 68 16V52C68 60.8366 60.8366 68 52 68H16C7.16344 68 3.2215e-08 60.8366 0 52V16C0 7.16344 7.16344 3.22129e-08 16 0H52Z' fill='%23fff'/%3E%3C/svg%3E"));
          mask-image: var(--avatar-mask, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='272' height='272' viewBox='0 0 68 68' preserveAspectRatio='none'%3E%3Cpath d='M52 0C60.8366 0 68 7.16344 68 16V52C68 60.8366 60.8366 68 52 68H16C7.16344 68 3.2215e-08 60.8366 0 52V16C0 7.16344 7.16344 3.22129e-08 16 0H52Z' fill='%23fff'/%3E%3C/svg%3E"));
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  object-fit: cover;
  flex-shrink: 0;
}
/* Photo-less fallback. Shares .mp-review__avatar's box and radius exactly, so
   the mask geometry measured off Figma stays the single source of truth and
   this variant only supplies the fill.

   Translucent white over the card's own gradient rather than an opaque tile:
   the previous stock placeholder was a near-white #f0efeb block with a dark
   grey glyph, which punched a hole in the card and read as a failed image
   load. Sitting IN the card's colour is what makes it look chosen. */
.mp-review__avatar--initials {
  display: flex;
  align-items: center;
  justify-content: center;
  /* No border. The avatar carries an SVG mask, and a 1px semi-transparent
     border sitting under it gets cut by the mask edge — which is exactly
     where stair-stepping shows up worst. The background tint alone reads the
     shape cleanly. */
  background: rgba(255, 255, 255, 0.18);
  color: var(--c-white);
  font-weight: var(--fw-bold);
  font-size: 22px;
  line-height: 1;
  letter-spacing: 0.02em;
  /* Latin initials are cropped tight; Arabic ones sit lower and would clip
     against a flex-centred baseline at this size. */
  padding-block-start: 2px;
  user-select: none;
}

.mp-review.is-open .mp-review__avatar--initials { font-size: 40px; }

/* Every collapsed card carries its OWN avatar mask — this is a deliberate
   decorative rotation in the design, not one shared shape.
   Measured from the user's crop of the Figma avatar row; the inset at a mask's
   extreme row is its corner radius:

     Lana Ghanim    TL 8  TR 8  BL  2  BR  2     round top, near-square bottom
     Rashed Moh     TL11  TR11  BL 12  BR 11     uniform
     Marah Jamaah   TL23  TR23  BL  2  BR  1     heavy top
     Jack Smith     TL11  TR12  BL 10  BR 11     uniform
     Sarah Rob      TL 7  TR 7  BL  2  BR  1     round top, near-square bottom
     Aya Khalil     TL 1  TR 2  BL 23  BR 23     heavy BOTTOM — Marah mirrored

   Four distinct shapes, so they cycle every 4th card. nth-child is safe here:
   the rail's children are the .mp-review articles in a stable DOM order, and
   mpAccordion only ever adds/removes classes — it never reorders them.

   The count is dynamic (the rail is admin-curated with no cap), so a cycle is
   the only thing that scales; hard-coding six shapes would leave card seven
   unstyled. */
/* Figma's own sequence across the six collapsed cards, in canvas order:

     1 Lana Ghanim   scalloped top (three lobes)        -> a
     2 Rashed Moh    plain squircle, r16                -> b
     3 Marah Jamaah  domed top, square base             -> c
     4 Jack Smith    double-lobe "B"                    -> d
     5 Sarah Rob     scalloped top  (shares Lana's)     -> a
     6 Aya Khalil    square top, domed base             -> e

   Five distinct shapes over a six-card period, so the cycle is 6n — not 5n,
   because Sarah repeats Lana's shape in the middle rather than at the end.
   A cycle rather than six fixed rules because the rail is admin-curated with
   no cap; card seven has to inherit something. */
.mp-reviews__track > .mp-review:nth-child(6n + 1) { --avatar-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='272' height='272' viewBox='0 0 68 68' preserveAspectRatio='none'%3E%3Cpath d='M56.5 0C62.8511 4.53003e-05 67.9998 5.1489 68 11.5V64C68 66.2091 66.2091 68 64 68H49C46.7909 68 45 66.2091 45 64C45 66.2091 43.2091 68 41 68H27C24.7909 68 23 66.2091 23 64C23 66.2091 21.2091 68 19 68H4C1.79086 68 -5.81628e-08 66.2091 0 64V11.5C0.000173856 5.14887 5.14883 -4.60914e-07 11.5 0C17.8511 4.32387e-05 22.9998 5.1489 23 11.5V11C23.0002 4.92502 27.925 -4.40874e-07 34 0C40.075 4.32185e-05 44.9998 4.92504 45 11V11.5C45.0002 5.14887 50.1488 -4.60914e-07 56.5 0Z' fill='%23fff'/%3E%3C/svg%3E"); }
.mp-reviews__track > .mp-review:nth-child(6n + 2) { --avatar-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='272' height='272' viewBox='0 0 68 68' preserveAspectRatio='none'%3E%3Cpath d='M52 0C60.8366 0 68 7.16344 68 16V52C68 60.8366 60.8366 68 52 68H16C7.16344 68 3.2215e-08 60.8366 0 52V16C0 7.16344 7.16344 3.22129e-08 16 0H52Z' fill='%23fff'/%3E%3C/svg%3E"); }
.mp-reviews__track > .mp-review:nth-child(6n + 3) { --avatar-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='272' height='272' viewBox='0 0 68 68' preserveAspectRatio='none'%3E%3Cpath d='M34 0C52.7777 0 68 15.2223 68 34V64C68 66.2091 66.2091 68 64 68H4C1.79086 68 8.05333e-09 66.2091 0 64V34C0 15.2223 15.2223 0 34 0Z' fill='%23fff'/%3E%3C/svg%3E"); }
.mp-reviews__track > .mp-review:nth-child(6n + 4) { --avatar-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='272' height='272' viewBox='0 0 68 68' preserveAspectRatio='none'%3E%3Cpath d='M53 34C61.2843 34 68 40.7157 68 49V53C68 61.2843 61.2843 68 53 68H15C6.71573 68 6.44299e-08 61.2843 0 53V49C6.443e-08 40.7157 6.71573 34 15 34H17C7.61116 34 0 26.3888 0 17C0 7.61116 7.61116 0 17 0H51C60.3888 0 68 7.61116 68 17C68 26.3888 60.3888 34 51 34H53Z' fill='%23fff'/%3E%3C/svg%3E"); }
.mp-reviews__track > .mp-review:nth-child(6n + 5) { --avatar-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='272' height='272' viewBox='0 0 68 68' preserveAspectRatio='none'%3E%3Cpath d='M56.5 0C62.8511 4.53003e-05 67.9998 5.1489 68 11.5V64C68 66.2091 66.2091 68 64 68H49C46.7909 68 45 66.2091 45 64C45 66.2091 43.2091 68 41 68H27C24.7909 68 23 66.2091 23 64C23 66.2091 21.2091 68 19 68H4C1.79086 68 -5.81628e-08 66.2091 0 64V11.5C0.000173856 5.14887 5.14883 -4.60914e-07 11.5 0C17.8511 4.32387e-05 22.9998 5.1489 23 11.5V11C23.0002 4.92502 27.925 -4.40874e-07 34 0C40.075 4.32185e-05 44.9998 4.92504 45 11V11.5C45.0002 5.14887 50.1488 -4.60914e-07 56.5 0Z' fill='%23fff'/%3E%3C/svg%3E"); }
.mp-reviews__track > .mp-review:nth-child(6n + 6) { --avatar-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='272' height='272' viewBox='0 0 68 68' preserveAspectRatio='none'%3E%3Cpath d='M64 0C66.2091 0 68 1.79086 68 4V34C68 52.7777 52.7777 68 34 68C15.2223 68 0 52.7777 0 34V4C0 1.79086 1.79086 8.0532e-09 4 0H64Z' fill='%23fff'/%3E%3C/svg%3E"); }

.mp-review.is-open .mp-review__avatar {
  grid-row: 1 / 3;
  inline-size: 129px;   /* 3546:105034 — already matched Figma's own 129x129 mask box exactly, unchanged */
  block-size: 129px;
  /* The open card's own mask (imgImage339, downloaded and decoded from its
     raw SVG path) is asymmetric — top corners ~19px radius (the pill
     lobe), bottom corners ~15px (the body) — confirmed visually via
     get_screenshot on 3546:105029 too, not just the path maths. Plain
     border-radius can't reproduce the pill's concave "waist" flare where
     it meets the body, but a taller-reading top-heavy squircle is the
     legible, no-new-asset approximation of it. */
  /* The active card has its own mask in Figma (3546:105031) — a full-width
     pill lobe on top joined to a rounded-rect body, i.e. the "B". Overrides
     the rotation above so the open card looks the same wherever it sits. */
  border-radius: 0;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='516' height='516' viewBox='0 0 129 129' preserveAspectRatio='none'%3E%3Cpath d='M114 37.9414C122.284 37.9414 129 44.6571 129 52.9414V114C129 122.284 122.284 129 114 129H15C6.71575 129 3.30529e-05 122.284 0 114V52.9414C0 44.6571 6.71573 37.9414 15 37.9414H18.9707C8.49358 37.9414 6.18524e-05 29.4478 0 18.9707C0 8.49354 8.49354 0 18.9707 0H110.029C120.506 0 129 8.49354 129 18.9707C129 29.4478 120.506 37.9414 110.029 37.9414H114Z' fill='%23fff'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='516' height='516' viewBox='0 0 129 129' preserveAspectRatio='none'%3E%3Cpath d='M114 37.9414C122.284 37.9414 129 44.6571 129 52.9414V114C129 122.284 122.284 129 114 129H15C6.71575 129 3.30529e-05 122.284 0 114V52.9414C0 44.6571 6.71573 37.9414 15 37.9414H18.9707C8.49358 37.9414 6.18524e-05 29.4478 0 18.9707C0 8.49354 8.49354 0 18.9707 0H110.029C120.506 0 129 8.49354 129 18.9707C129 29.4478 120.506 37.9414 110.029 37.9414H114Z' fill='%23fff'/%3E%3C/svg%3E");
}
@media (max-width: 768px) {
  .mp-review.is-open .mp-review__avatar { border-radius: 14px 14px 11px 11px; }  /* same ratio, scaled to the 91px mobile size (3530:62877) */
}

.mp-review__name {
  writing-mode: vertical-rl;
  white-space: nowrap;
  font-size: 16px;      /* 3546:104987, literal */
  font-weight: var(--fw-medium);
}
.mp-review.is-open .mp-review__name {
  writing-mode: horizontal-tb;
  font-size: var(--fs-name);
  font-weight: var(--fw-bold);
}

/* Fidelity pass — was a single flat-coloured run of str_repeat('★',
   stars()), so a non-5-star review rendered FEWER glyphs total instead of
   "N filled + (5-N) empty" out of 5 (a real gap, not a style nit — see
   ReviewCard::emptyStars()). Downloaded and sampled Figma's own star
   assets (imgStar1 = filled, imgStar4/imgStar5 = empty, used together on
   the one sampled non-5-star card, 3546:105027/105028): filled is a solid
   #ffc107, empty is a pale near-white #fff7e4/#fff9e9 — same star SHAPE,
   different fill, not a colour-only dimming of the filled one. Still a
   Unicode glyph per Task 11's own documented reasoning (no icon asset for
   one small repeating glyph), just split into two runs so both tones can
   be shown. Sizes bumped too: Figma's own star icons measure 14x14.7 on
   the spine (3546:104990) and 26x26.7 on the open card (3546:105037) — a
   ~1.86x ratio — but a TEXT ★ glyph's visible ink doesn't fill its em-box
   the way an icon does, so matching Figma's px measurements literally
   would still look smaller; sizes below are scaled up from the previous
   14px/20px to read as "large and present" at roughly the same ratio. */
.mp-review__stars {
  writing-mode: vertical-rl;
  font-size: 18px;
  letter-spacing: 3px;
}
.mp-review__stars-filled { color: #ffc107; }
.mp-review__stars-empty { color: #fff7e4; }
.mp-review.is-open .mp-review__stars {
  writing-mode: horizontal-tb;
  font-size: 32px;
  letter-spacing: 6px;
}
@media (max-width: 768px) {
  /* Figma's mobile open-card stars (3530:62880) measure ~16.5x14.1 vs
     desktop's 26x26.7 — a ~0.63x ratio — applied the same way here. */
  .mp-review.is-open .mp-review__stars { font-size: 20px; letter-spacing: 3px; }
}

.mp-review__panel { padding: 12px 32px 32px; }
.mp-review:not(.is-open) .mp-review__panel { display: none; }

/* Fix round 2 — WCAG 1.4.4/1.4.10/1.4.12: the card's own block-size:422px
   (above) plus .mp-review's overflow:hidden had NO scroll fallback, so once
   --fs-card grew far enough that the header (~161px, avatar-dominated,
   effectively fixed) plus the panel's own padding+clamped quote exceeded
   422px, the excess was silently clipped and permanently unreachable — a
   real failure mode via an ordinary browser "larger minimum font size"
   setting, not just a theoretical one (see task-11-report.md, "Fix round
   2", for the exact threshold — unchanged from before this fix — and why
   -webkit-line-clamp's own LINE-count cap doesn't prevent it: 5 lines just
   take proportionally more PIXELS as the font grows).

   A bare overflow-y:auto here would be inert: without a height CEILING to
   measure scrollHeight against, the panel simply grows to fit its content
   (same as before) and .mp-review's outer overflow:hidden clips it exactly
   as it did previously — scrollHeight never exceeds clientHeight, so no
   scrollbar ever appears. flex:1 is what supplies that ceiling: inside
   .mp-review.is-open's now-fixed-height flex column, the panel is only
   ever GIVEN whatever space remains after the header's own flex-shrink:0
   size — and min-block-size:0 is required for a flex child to actually
   shrink to that allotment instead of overflowing it (flex items default
   to min-size:auto, i.e. "never smaller than my content," which would
   defeat this entirely). Only once the panel is genuinely bounded does
   overflow-y:auto have anything to engage against. overflow-x is pinned to
   hidden alongside it — content here only ever needs to scroll vertically,
   and leaving overflow-x unset would compute to auto too (CSS Overflow: an
   explicit non-visible axis forces the other, unset axis from its
   `visible` initial value to `auto`), risking a stray horizontal
   scrollbar for no reason. */
.mp-review.is-open .mp-review__panel {
  flex: 1;
  min-block-size: 0;
  overflow: hidden auto;
}

.mp-review__quote {
  margin: 0;
  color: var(--c-white);
  font-size: var(--fs-card);
  font-weight: var(--fw-book);
  line-height: var(--lh-body);
  /* testimonials.description has no length cap in the schema (Task 5's own
     minor note); clamped the same way Task 10 clamps a variable-length
     insight excerpt, so one unusually long review can never grow this
     card past its neighbours' fixed block-size. Physical-sounding
     properties, not physical directions — Task 10's own note applies
     here too: -webkit-box-orient is a keyword, not a margin/inset axis. */
  display: -webkit-box;
  -webkit-line-clamp: 5;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Fidelity pass — Figma wraps the quote body in curly typographic
     quotes (3546:105044's own text is literally "…" in the design). The
     marks live here, in CSS, via the `quotes` property + generated
     content — never concatenated onto $review->description or baked into
     a lang string, so they can't get duplicated by a future edit and stay
     locale-correct automatically. Default (LTR/English) pair is the
     standard curly double quotes; [dir="rtl"] below swaps them per
     Unicode CLDR's `ar` locale convention (quotationStart=U+201D,
     quotationEnd=U+201C — reversed from English, not the same pair
     reused, because RTL text still uses these two glyphs but at swapped
     positions so the curl still points at the quoted text). */
  quotes: "\201C" "\201D";
}
.mp-review__quote::before { content: open-quote; }
.mp-review__quote::after { content: close-quote; }
[dir="rtl"] .mp-review__quote { quotes: "\201D" "\201C"; }

/* Decorative closing-quote flourish on the open card only (mirrors
   3546:105043's Gotham:Ultra glyph — Task 3's own report says not to ship
   a whole font for one punctuation mark: "render that glyph from an
   exported SVG or --grad-testimonial-filled ::before art, not by loading
   Gotham Ultra"). A literal character avoids a second asset for one
   corner mark; .mp-review's own overflow:hidden plus its border-radius
   already contain it regardless of card size.

   Fidelity pass — pixel-sampled the real rendered mark on 3546:105029
   (get_screenshot, PIL grid scan): its fill is (26,58,127) at FULL
   opacity — exactly var(--c-primary), zero alpha blending with the
   gradient behind it — not the low-key rgba(255,255,255,0.16) previously
   shipped. It reads as "subtle" purely because a navy mark on a
   navy-to-cyan gradient is close in hue/lightness to begin with, not
   because it's translucent. Figma's own glyph is also ~200px (vs the
   110px shipped), rotated 180° in Gotham:Ultra; kept the existing
   available weight/orientation (Task 3's no-extra-font ruling still
   applies) and matched the size directly, repositioning from the
   Figma-metadata inset box (3546:105042, top 45.33%/right 8.01% of the
   634x422 card) so it sits low and to the end, mostly inside the card
   like the reference render, rather than mostly clipped by the corner. */
.mp-review.is-open::after {
  content: "\201D";
  position: absolute;
  inset-block-end: 4px;
  inset-inline-end: 44px;
  font-size: 168px;
  font-weight: var(--fw-bold);
  line-height: 1;
  color: var(--c-primary);
  pointer-events: none;
}

@media (max-width: 768px) {
  /* Figma's own mobile spines sit genuinely off-canvas (x=-89.5 / x=359 in
     the 390 frame — verified via metadata, not eyeballed) — only the
     rail's own scroll+peek reproduces that. The CLOSED width itself is NOT
     taken from that raw measurement (~33px visible): that is under the
     44px touch-target floor (project constraints), so it's bumped to
     44px — a documented, deliberate deviation, not a missed number. */
  .mp-review { flex-basis: 44px; }
  .mp-review.is-open { flex-basis: 88vw; }   /* 3417 spine row: open card 340 / 390 frame ≈ 87%, rounded */
  .mp-review.is-open .mp-review__avatar { inline-size: 91px; block-size: 91px; }  /* 3530:62873, mobile-specific */

  /* The open card is SHORTER on mobile than on desktop — 372, not 422. Both
     numbers are real and distinct Figma values, not one token reused:
     desktop open card 3546:105029 is h422, mobile open card 3530:62872 is
     h372. The collapsed spines stay 364 at both sizes (3546:104977 /
     3530:62855), so only the open state and the row that has to contain it
     change here.

     The track's height must move WITH the open card: it was fixed to 422 in
     Task 11's fix round precisely so toggling a card cannot resize the row,
     and leaving it at the desktop value would reintroduce a 50px dead band
     under every mobile card. */
  .mp-reviews__track { block-size: 372px; }
  .mp-review.is-open { block-size: 372px; }

  /* No manual scrolling on mobile — the band is an auto-advancing carousel
     here, not a drag rail. `hidden` rather than removing the overflow: the
     element MUST stay a scroll container because center() positions the open
     card by setting scrollLeft, and an overflow:hidden box is still
     scrollable programmatically — just not by the user.

     Desktop keeps overflow-x:auto: it has visible arrows, a mouse, and
     enough width for the drag to be useful. */
  .mp-reviews__rail {
    overflow-x: hidden;
    touch-action: pan-y;          /* vertical page scroll still passes through */
  }

  /* 8 lines, not 4. Figma's mobile quote box (3530:62887) is 281x154 — at
     this size that is ~8 lines, and the design shows Ahmed Qadri's full
     quote reaching "recommended!". Clamping to 4 truncated it halfway
     through a sentence, which is the opposite of what the band is for.
     Anything longer than the box still scrolls, per the Task 11 round-2
     panel fix, so nothing becomes unreachable. */
  .mp-review__quote { -webkit-line-clamp: 8; }
}

/* =========================================================================
   Task 12 — Clients, partner platform, footer, and the SEO partial
   (§5 rows 12-13 + global chrome)

   Figma kyudcw9zAgmniIXWNveugA. Node ids cited per rule; see each
   section's own .blade.php header comment for the full node trail and for
   the judgement calls this block only cross-references (client-logo mobile
   grid vs Figma's unfinished 390 canvas; the partner mockup's asset/node
   size mismatch; the footer's dropped background art).
   ========================================================================= */

/* ─ Clients (3546:105097) ──────────────────────────────────────────────── */
.mp-clients__heading { text-align: center; margin-block-end: var(--space-heading); }

.mp-clients__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 48px 62px;                  /* 3546:105102 gap-[62px]; row-gap added only for the wrap case */
}

/* Fix round 1 (Finding 1): 310px is the measured Figma value
   (3546:105102's own 4 children), but a FIXED 310px item never fits a
   single row within this page's own content band — natural row width is
   4x310 + 3x62 = 1426px, while content width (viewport minus 2x--gutter,
   capped by .mp-container's own max-width) is only 1320px at the 1440
   reference frame itself (1440 - 2x60), let alone narrower desktop/tablet
   widths. The old fixed width wrapped to an uneven 3-then-1 split at
   essentially every real desktop viewport, not just a narrow edge case.

   Fix: each item's flex-basis is the SMALLER of 310px and an even 4-way
   share of whatever space is actually available (100% minus the 3 real
   column gaps) — so below ~1546px the row shrinks to fill the content
   band edge-to-edge with all 4 logos on one line, and at/above ~1546px it
   holds at the literal 310px Figma size (flex-grow:0, so logos never
   stretch past their designed size — the extra space centers around the
   row via .mp-clients__row's own justify-content instead). Verified at
   1280/1440/1714px — see task-12-report.md "Fix round 1" for the
   per-width row-width arithmetic.

   min-inline-size:0 overrides the flex item's automatic min-width:auto,
   which otherwise floors shrinking at the <img>'s own content size and
   would silently defeat the shrink above. flex-wrap stays on the row as
   a defensive safety net only — the fluid basis math means it should
   never actually trigger above 768px. */
.mp-clients__item {
  flex: 0 1 min(310px, calc((100% - 3 * 62px) / 4));
  min-inline-size: 0;
}
.mp-clients__item img { display: block; inline-size: 100%; block-size: auto; }

@media (max-width: 768px) {
  /* Real 2x2 grid, not a cropped copy of the desktop row — see the
     .blade.php header for why this diverges from Figma's raw mobile
     canvas. */
  .mp-clients__row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px 16px;
    justify-items: center;
  }
  .mp-clients__item { inline-size: 100%; max-inline-size: 150px; }
}

/* ─ Partner platform (copy 3546:104876, mockup 3546:105107) ────────────── */
/* Mobile-first: mockup then copy (matches 3531:79767's real stacking, and
   Task 10's own .mp-app__grid convention for the identical "mockup vs
   copy, order flips on desktop" shape). */
.mp-partner__grid { display: grid; gap: 32px; }

.mp-partner__mockups img { display: block; inline-size: 100%; block-size: auto; }

.mp-partner__copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
}

.mp-partner__heading { display: flex; flex-direction: column; gap: 12px; }

.mp-partner__title { font-size: var(--fs-h1); }          /* 60px, 3546:104878 */
.mp-partner__body { font-size: var(--fs-lead); color: var(--c-ink); }  /* 28px, 3546:104879 */

/* .mp-btn--pill supplies bg/colour/radius/44px floor; only the larger
   Figma type size is section-owned — same reuse .mp-app__cta documents. */
.mp-partner__cta { font-size: var(--fs-card); justify-content: center; }

/* Real content difference (mobile CTA carries a leading search icon,
   desktop's does not) — both always render, CSS decides which shows,
   exactly like .mp-simpler__cta-desktop / --mobile. */
.mp-partner__cta--mobile { display: none; }

@media (max-width: 768px) {
  .mp-partner__title { font-size: 22px; }                          /* 3443:44409, literal */
  .mp-partner__body { font-size: var(--fs-body-sm); font-weight: var(--fw-light); }  /* 3443:44410 */
  .mp-partner__cta--desktop { display: none; }
  .mp-partner__cta--mobile { display: flex; inline-size: 100%; }   /* "full-width pill" per brief §10 */
}

@media (min-width: 769px) {
  /* Copy (586) and mockup (1168) sit on independent canvas nodes with no
     shared parent — the .blade.php header explains why this uses its own
     fluid split rather than that literal ratio. */
  .mp-partner__grid {
    grid-template-columns: minmax(0, 586fr) minmax(0, 620fr);
    align-items: center;
    gap: 64px;
  }
  .mp-partner__copy { order: -1; }

  /* The device mockups run UNDER the footer — Figma cuts the phone off at the
     footer's rounded top edge rather than parking it in clear space above.

     Measured off a Figma render against ours: Figma's phone bezel reaches
     y=501 with the footer starting at y=502 (a 1px clip), while ours stopped
     125px short, leaving a band of empty white. That 125px is accounted for:
     96px of this section's own --space-section bottom padding, plus ~24px of
     rendered white below the bezel inside the artwork itself (47px in a
     1953px-tall source, drawn at ~0.52 scale).

     So the padding goes, and the negative margin then pulls the artwork far
     enough down for the footer to actually clip it. No transparency is needed
     for the overlapped part: .mp-footer is positioned and later in the DOM, so
     it paints over the artwork completely — whatever the image's own
     background is below that line is never seen.

     Desktop-only. Below 769px the grid reorders so the CTA button, not the
     mockup, is the last thing before the footer — a negative margin there
     would pull the artwork into the copy instead. */
  .mp-partner { padding-block-end: 0; }
  .mp-partner__mockups { margin-block-end: -48px; }
}

@media (min-width: 1200px) {
  /* --space-section is 64px in the 769-1199 band but 96px here, and the
     artwork is drawn larger, so the same visual overlap needs a bigger pull. */
  .mp-partner__mockups { margin-block-end: -88px; }
}

/* ─ Footer (desktop 3546:105249/3534:80334, mobile 3537:103119) ────────── */
.mp-footer {
  /* Fidelity pass (task 14): Figma layers a full-bleed blurred wave
     (Group 289483) plus a translucent white backdrop-blur panel behind a
     flat --c-primary rect (3533:79986/79991), which the original pass
     dropped entirely — the shipped panel was pure flat colour. Reproducing
     the exact irregular blob shape as an asset risks the "wrapper frame
     adds opaque corners" trap this project has hit six times, so this is a
     CSS gradient approximation instead, not a re-export.

     Stops sampled directly off a full-resolution Figma render
     (get_screenshot, node 3546:104515, footer region y 8698-9234): centre
     top ~rgb(66,147,212), mid ~rgb(38,134,211), lower ~rgb(53,112,181) —
     all sit between --c-accent (#2ca5e8) and --c-primary (#1a3a7f).

     CORRECTED after the user compared this side-by-side with Figma and
     rejected it as "not matched". The previous revision capped the PEAK at
     #236fb3 (5.26:1) and then let the gradient fall all the way to
     --c-primary #1a3a7f (10.74:1) across ~60% of the panel. Capping the peak
     was never the real problem — the FALL-OFF was. Figma's own darkest
     sampled stop is ~rgb(53,112,181) (5.08:1); ours bottomed out more than
     twice as dark as that, which is what made the panel read as flat navy
     instead of vivid blue.

     The rule applied here is a floor on CONTRAST, not on brightness: every
     stop must clear 4.5:1 against white, because small text (the body
     paragraph and the column links) sits over most of this panel and the
     large-text 3:1 allowance does not cover it.

     Revised again after sampling a Figma render pixel-by-pixel to find what
     the design actually puts BEHIND ITS OWN TEXT:

       body paragraph   bg #2485D1  ->  3.92:1   (small text needs 4.5) FAILS
       'Saudi Arabia'   bg #5489BF  ->  3.68:1   (small text needs 4.5) FAILS
       tagline          bg #4096D4  ->  3.22:1   (large text needs 3.0) passes

     So the Figma footer genuinely fails WCAG AA for its small copy —
     "match the design" and "pass AA" are not both satisfiable here, and no
     single flat cap resolves it.

     What IS satisfiable: the peak is Figma's exact sampled #4096D4, because
     only the logo and the 42px tagline sit in it and large text needs just
     3:1. The falloff then reaches >=4.5:1 before it gets anywhere near the
     small text — the body paragraph below the tagline, and the link columns
     to the right. Geometry is what buys the fidelity, not a compromise hex:

       peak (22% 30%), extent 70% x 55%
       tagline  ~0.35 radial -> ~3.4:1  (large, needs 3.0)   OK
       body     ~0.71 radial -> 6.16:1  (small, needs 4.5)   OK
       columns  ~0.91 radial -> 6.16:1  (small, needs 4.5)   OK

     - end #2A62A8 (6.16:1) replaces the original #1a3a7f (10.74:1). This is
       the change that does most of the visual work: the dominant area is
       2.5x brighter in luminance than the first attempt, which capped the
       PEAK and then let the gradient fall to near-navy across ~60% of the
       panel. The peak was never the problem; the fall-off was.

     The decorative bottom-right circle is NOT painted here — see
     .mp-footer__foot::before. It has a hard edge and is anchored to the top
     of the legal bar, neither of which a background layer on this element
     can express. */
  position: relative;
  isolation: isolate;
  background: radial-gradient(70% 55% at 22% 30%,
              #4096D4 0%, #377BB2 45%, #2A62A8 75%);
  color: var(--c-white);
  /* The signature 108px radius, same token the hero and tile grid already
     cite (3534:80469 is the footer's own instance of it). */
  border-start-start-radius: var(--radius-lg);
  border-start-end-radius: var(--radius-lg);
}

/* Film grain over the footer gradient — Figma's footer is visibly textured,
   and the previous revision dropped it entirely, which is part of why the
   panel read as flat.

   Reuses the hero's existing grain.webp rather than exporting a second
   asset. Two deliberate differences from .mp-hero__grain:

   - STATIC. The hero animates its grain with steps(1) to get a TV-static
     shimmer. A footer that crawls would be a distraction at the bottom of
     the page and would repaint on every frame for no benefit.
   - UNDER the content, not over it. The hero deliberately lets grain fall
     across its copy; here it must not touch the small link text, so
     z-index keeps it behind .mp-footer__inner.

   inherit on the radius stops the texture squaring off the 108px corners. */
.mp-footer::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  border-start-start-radius: inherit;
  border-start-end-radius: inherit;
  background-image: url("/marketplace/img/grain.webp");
  background-repeat: repeat;
  opacity: 0.28;
}

.mp-footer__inner {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 24px;                       /* 3537:102994 gap-[24px] */
  padding-block: 32px 0;           /* 3537:102992 top-[32px]; bottom rows own their own padding below */
  grid-template-areas:
    "brand"
    "rule1"
    "intro"
    "badges"
    "rule2"
    "columns"
    "foot";
}

.mp-footer__brand { grid-area: brand; }
.mp-footer__rule--1 { grid-area: rule1; }
.mp-footer__rule--2 { grid-area: rule2; }
.mp-footer__intro { grid-area: intro; }
.mp-footer__badges { grid-area: badges; }
.mp-footer__columns { grid-area: columns; }
.mp-footer__foot { grid-area: foot; }

.mp-footer__brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;                       /* 3537:102991 gap-[16px] */
}

.mp-footer__logo img { display: block; inline-size: 168px; block-size: auto; }

/* 22px boxes with a 44px hit target: padding the box (rather than
   stretching the glyph) means adjacent boxes can sit gap:0 and still land
   the icons themselves ~21px apart — visually the same rhythm as Figma's
   literal 22px gap between the smaller glyphs, but every link keeps a real
   44x44 target (project constraint; the raw 23px icons alone do not). */
.mp-footer__social { display: flex; align-items: center; }
.mp-footer__social-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 44px;
  block-size: 44px;
}
.mp-footer__social-link img { display: block; }

.mp-footer__rule {
  display: block;                  /* mobile-only element — hidden again at 769px below */
  inline-size: 100%;
  block-size: 1px;
  border: 0;
  /* Figma's own value is a near-invisible 0.25px solid white hairline;
     rendered at a real 1px with reduced opacity instead, since a
     sub-pixel border silently disappears on most non-Retina displays. */
  background: rgba(255, 255, 255, 0.2);
  margin: 0;
}

.mp-footer__intro { display: flex; flex-direction: column; gap: 8px; text-align: center; }
/* line-height is stated, not inherited: without it the tagline picked up
   body's --lh-body 1.6, which opened a visible gap between its two lines
   where the design sets them tight. */
.mp-footer__tagline {
  font-size: 24px;
  font-weight: var(--fw-bold);
  line-height: var(--lh-heading);
  margin: 0;
}    /* 3537:102996, mobile literal */
.mp-footer__body { font-size: var(--fs-body-sm); font-weight: var(--fw-light); line-height: var(--lh-tight); margin: 0; }  /* 3537:102997 */

.mp-footer__badges { display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap; }
/* Figma's badges run 41-42px tall; padded to the 44px floor the same way
   the social icons are, without resizing the badge art itself. */
.mp-footer__badge { display: inline-flex; align-items: center; min-block-size: 44px; }
/* Fidelity pass: mobile Figma badges are LARGER than desktop, not smaller
   — app-store 157x51 (3537:103000), google-play 157x47 (3537:103035),
   vs desktop's 124x41 (3534:80256) / 141x42 (3534:80291). Both mobile
   sizes fall out of a single uniform width scale-up (157px) applied via
   the SVG's own aspect ratio — 157/124=1.27x and 157/141x2=1.11x land
   within 1px of the two measured mobile heights (51.9->51, 46.8->47), so
   this is one asset at two widths, not two different exports. */
.mp-footer__badge img { display: block; inline-size: 157px; block-size: auto; }

.mp-footer__columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px 16px;
}
.mp-footer__available { grid-column: 1 / -1; }         /* "Available In" spans full width on mobile (§10) */

.mp-footer__col-heading {
  margin: 0 0 16px;
  font-size: var(--fs-nav);
  font-weight: var(--fw-medium);
  text-transform: uppercase;       /* copy is title-case in lang files; Figma's caps are styling, not content (§10) */
}
.mp-footer__col-list { display: flex; flex-direction: column; gap: 12px; }  /* 3537:103051 gap-[12px], mobile */
/* text-decoration is load-bearing here. These were ALL inert <span>s until
   the marketplace/FAQs/partner links were given real destinations — the
   moment they became anchors they picked up the browser's default underline,
   because this stylesheet's reset only sets `a { color: inherit }`. Underline
   on hover instead, which is what the rest of the site does. */
.mp-footer__link {
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-book);
  text-decoration: none;
}
.mp-footer__link:hover { text-decoration: underline; text-underline-offset: 3px; }

.mp-footer__country {
  display: flex;
  align-items: center;
  gap: 5px;                        /* 3537:103070 gap-[5px] */
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-book);
}
.mp-footer__country img { display: block; flex-shrink: 0; }

/* Full-bleed wrapper for the bottom band — see the .blade.php header for
   why this exists (Figma's own bottom row is edge-to-edge on both
   breakpoints, not inset inside .mp-container's gutter, and carries a
   real #204688 fill rather than flat --c-primary). Breaks out of the
   container by exactly its own padding-inline, then re-adds that same
   inset as its own padding so children still line up with the gutter-
   aligned content above.

   Fix round 1 (Finding 5): past .mp-container's own max-width cap
   (1760px = --container + --gutter*2), this breakout only reached
   .mp-container's own edge, not the true viewport edge — leaving a
   visible seam of flat --c-primary outside the #204688 band (confirmed
   by simulating the two adjacent fills at a 1920px viewport: visibly two
   different blues meeting at a hard edge, not a graceful fade). Scoped
   the fix to ONLY that >1760px range (media query below) rather than
   swapping the base formula everywhere: below 1200px this element is
   still a grid item inside .mp-footer__inner's single AUTO-sized mobile
   column (no grid-template-columns declared there), and a definite
   `inline-size: 100vw` on a grid item participates in that column's
   intrinsic track-sizing pass — engines have historically disagreed on
   whether a `calc(50% - 50vw)` margin resolves to ~0 or to the full
   100vw during that pass, and getting it wrong would inflate the mobile
   column and clip the whole footer, a strictly worse failure than the
   seam this is fixing. Above 1760px only the desktop grid exists
   (`.mp-footer__inner`'s `min-width:769px` columns are `minmax(0, 1fr)`
   fr tracks, which take no intrinsic contribution from a spanning item),
   so the track-sizing hazard doesn't exist there and the viewport-
   centring algebra below applies unconditionally. Below 1761px this
   rule is byte-identical to the original, already-reviewed formula. */
.mp-footer__foot {
  display: flex;
  flex-direction: column;
  margin-inline: calc(-1 * var(--gutter));
  inline-size: calc(100% + var(--gutter) * 2);
  /* Figma reads this strip as a clearly DARKER band than the panel above it.
     It was #204688 against a panel that bottomed out at #1a3a7f — barely a
     step, so the division was almost invisible. Now that the panel ends at
     #2A62A8 the same separation needs a darker bar: #1C4585 (9.38:1). */
  background: #1C4585;
  /* Positioning context for the decorative corner circle below. */
  position: relative;
  /* Rounds the WRAPPER, not either child: the legal child paints no
     background of its own (the wrapper's fill shows straight through it),
     so the corner has to live where the fill actually is. 3537:103076's
     own literal value. */
  border-start-start-radius: 24px;
  border-start-end-radius: 24px;
}

/* The decorative circle in the footer's bottom-right corner (Figma's Union
   shape, 3533:80119).

   Two properties of it defeated the earlier attempt, which painted a soft
   radial-gradient onto .mp-footer and was effectively invisible:

   1. It has a HARD edge — a real circle with a visible arc, not a fade. The
      double colour stop at the same position (`0 100%, transparent 100%`)
      is what gives the boundary; a percentage fade cannot.
   2. Its flat side sits exactly on TOP OF THE LEGAL BAR, not at the panel's
      bottom. A circle anchored to the panel corner cannot match both the
      arc's height above the bar and its width at the bar line — the two
      constrain each other and Figma's values only solve with the centre on
      the bar's top edge.

   `inset-block-end: 100%` on a child of the bar puts this box's bottom edge
   exactly at the bar's top edge whatever the bar's height turns out to be —
   which matters because that height differs between breakpoints (24px
   padding + one 16px line on desktop; stacked and taller on mobile). No
   magic number to keep in sync.

   Colour and radius are MEASURED off a Figma render, not estimated. Sampling
   the pixels settled two things a first pass got wrong:

   - The circle is DARKER than the panel, not a lighter lift. Three probes
     inside it all read exactly rgb(31,71,135) = #1F4787, against #2B69AF /
     #3578BC around it. An earlier revision used translucent white, which
     lightened the corner — the wrong direction entirely.
   - It is FLAT and OPAQUE. Pixels outside the circle vary high-frequency
     (that is the grain texture); inside, they are perfectly constant. So the
     grain must not fall over it — which is already true here, since this
     sits inside .mp-footer__inner (z-index 1) while the grain is on
     .mp-footer::before (z-index 0).

   Radius 160px: the arc measured 157px tall above the bar and ~152-165px
   half-wide at the bar line. Those agreeing is itself the proof that the
   centre sits on the bar's top edge — a circle anchored anywhere else would
   give two different numbers. No text sits here, so no contrast constraint. */
.mp-footer__foot::before {
  content: "";
  position: absolute;
  inset-block-end: 100%;
  inset-inline-end: 0;
  inline-size: 320px;
  block-size: 160px;
  pointer-events: none;
  background: radial-gradient(circle 160px at 100% 100%,
              #1F4787 0 100%, transparent 100%);
}

/* Desktop only. On the mobile frame (3555:120294) the panel has no corner
   circle — at 393 wide a 320px arc spans four fifths of the footer and reads
   as a mistake rather than as decoration, and it collides with the
   "Available in" country rows. */
@media (max-width: 768px) {
  .mp-footer__foot::before { content: none; }
}

.mp-footer__copyright {
  order: 2;                        /* mobile: legal band first, copyright bar below it (§10) */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  margin: 0;
  padding: 8px 16px;
  background: var(--c-white);      /* opaque — paints over the wrapper's #204688 for its own box only */
  color: var(--c-primary);
  font-size: var(--fs-label);
  font-weight: var(--fw-light);
}

.mp-footer__legal {
  order: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px 16px;
  padding: 20px 16px;
  font-size: var(--fs-label);
  font-weight: var(--fw-book);
}
.mp-footer__legal-link { font-size: var(--fs-label); text-decoration: none; }
.mp-footer__legal-link:hover { text-decoration: underline; text-underline-offset: 3px; }

@media (min-width: 769px) {
  .mp-footer__inner {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 30px 40px;                /* 3534:80311 gap-[30px] vertical; horizontal is this file's own judgement call */
    padding-block: 60px 0;         /* 3534:80311 top-[60px] */
    grid-template-areas:
      "brand  brand"
      "intro  columns"
      "badges badges"
      "foot   foot";
  }

  /* Fidelity pass: was flex row + justify-content:space-between, which
     hugs the social icons to the ROW's own far-right edge. Measured
     against a full-resolution Figma render (footer-desktop crop, pixel
     scan): social icons actually sit at x 753-909 (of 1440), while the
     Explore/Grow/Available columns row directly below starts at x~760 —
     i.e. the social-icon group is column-aligned with the SAME "second
     column" start as the row below it, not edge-anchored. Mirroring
     .mp-footer__inner's own 2-column split here (rather than a flex
     space-between) reproduces that alignment at every viewport width,
     not just the 1440 reference frame. items-end per 3534:80305's own
     Figma layout (not items-center — the two groups' natural heights
     differ, and Figma bottom-aligns them). */
  .mp-footer__brand {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 40px;
    align-items: end;
  }
  .mp-footer__logo { justify-self: start; }
  .mp-footer__logo img { inline-size: 220px; }
  .mp-footer__social { justify-self: start; gap: 0; }

  .mp-footer__rule { display: none; }   /* redundant with the base rule but explicit: never shows past 768px */

  /* gap: 15px is a fidelity fix — 3534:80306 (desktop tagline->body) is
     15px, not the 8px .mp-footer__intro's base (mobile, 3537:102995)
     rule ships; both are real, distinct Figma values, not one token
     reused across breakpoints. */
  .mp-footer__intro { align-items: flex-start; text-align: start; max-inline-size: 580px; gap: 15px; }
  .mp-footer__tagline { font-size: var(--fs-h3); }                          /* 42px, 3534:80307 */
  .mp-footer__body { font-size: var(--fs-nav); font-weight: var(--fw-book); line-height: var(--lh-heading); }  /* 20px, 3534:80308 */

  .mp-footer__badges { justify-content: flex-start; }
  /* Desktop badges use their own native HTML width/height attrs (124x41 /
     141x42, exact Figma match) — only mobile needs the 157px CSS override
     above. */
  .mp-footer__badge img { inline-size: auto; block-size: auto; }

  /* Fidelity pass: justify-self:end. Figma's own 1440 render already has
     this group landing at x 760-1371 — 9px of slack before the true
     right edge (1380), i.e. functionally flush-right, not left-aligned
     with slack trailing after it. Left-aligned (the prior default) reads
     nearly the same AT exactly 1440px, but .mp-container's fluid 1fr/1fr
     split means the column TRACK itself grows on wider viewports while
     this group's own content (fixed by its text) does not — so a
     left-aligned column produces a growing gap AFTER the columns as the
     viewport widens past 1440px, which is the "large empty gap" the user
     flagged. justify-self:end keeps the group pinned to the column's own
     end edge at every width, matching Figma's edge-anchored behaviour
     rather than just its one-frame pixel snapshot. */
  .mp-footer__columns {
    display: flex;
    align-items: flex-start;
    /* START, not end. In Figma the social row and the EXPLORE column share
       one inline-start edge — both begin at the start of the layout's second
       half. `justify-self: end` pushed this block to the panel's right edge
       instead, so the two rows visibly disagreed: the social icons sat at the
       half-split while EXPLORE began well to their right.

       A Fix-round-1 finding read the trailing space after AVAILABLE IN as a
       gap that "grew on wide viewports" and closed it by end-anchoring. That
       was the wrong diagnosis — the trailing space is in the design, and it
       does not grow without bound because .mp-container caps at --container.
       Anchoring to start is what actually reproduces Figma, and it keeps the
       two rows aligned at every width rather than only at 1440. */
    justify-self: start;
    gap: 52px;                     /* 3533:80149 gap-[52px] */
  }
  .mp-footer__col-list { gap: 16px; }  /* 3534:80328 gap-[16px], desktop */

  /* Desktop: one row, order flips back to source order (copyright start,
     legal end — 3534:80305's own layout), no rounding (flush against the
     panel's own already-square bottom edge), fill stays #204688 on the
     WRAPPER exactly as mobile — this is the "Union shape gives the bottom
     row a real, distinct luminance" case the .blade.php header calls out,
     not flat --c-primary continuing straight through. */
  .mp-footer__foot {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    padding-inline: var(--gutter);
    padding-block: 24px;
    border-radius: 0;
  }

  .mp-footer__copyright {
    order: 0;
    padding: 0;
    background: none;              /* the wrapper's own #204688 shows through instead of a separate white bar */
    color: var(--c-white);
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-book);
  }

  .mp-footer__legal {
    order: 0;
    padding: 0;
    font-size: var(--fs-body-sm);
  }
  .mp-footer__legal-link { font-size: var(--fs-body-sm); }
}

/* Fix round 1 (Finding 5), continued: only above .mp-container's own
   max-width cap (1760px = --container + --gutter*2) does the base rule's
   container-relative breakout fall short of the true viewport edge.
   Scoped here rather than swapped in globally — see the long comment on
   the base .mp-footer__foot rule above for why this specific range is
   the only one where a definite `100vw` is safe on this grid item (the
   desktop `minmax(0, 1fr)` columns active here take no intrinsic
   contribution from a spanning item, so there's no track-inflation
   hazard, unlike the mobile auto-sized column below 769px). The +1px
   (1761 vs 1760) means this activates a hair after .mp-container itself
   stops growing, so the two formulas hand off with no visible jump —
   confirmed by the algebra in the base rule's comment, not a screenshot. */
@media (min-width: 1761px) {
  .mp-footer__foot {
    margin-inline: calc(50% - 50vw);
    inline-size: 100vw;
  }
}


/* =========================================================================
   FAQs page (Figma 3509:880 desktop / 3548:105869 mobile)

   Only this page uses these rules; they are appended rather than woven into
   the homepage blocks above so the two pages can diverge without either
   breaking the other.
   ========================================================================= */

/* The nav is `position: fixed` (see .mp-nav) and therefore out of flow, so a
   page whose content starts at the top slides straight under it — which is
   what buried this page's H1 behind the bar. The homepage never showed this
   because its hero is a full-height section designed to sit UNDER the
   transparent nav on purpose.

   Offset = the nav's own height (108px, Figma 3530:73633 / 3550:108052) plus
   the section's normal top space, so the title clears the bar at every width
   rather than only where the nav happens to be one line tall. */
.mp-faq { padding-block: calc(108px + var(--space-section)) var(--space-section); }

/* ─ Hero (3509:1937) ───────────────────────────────────────────────────── */
.mp-faq__head { text-align: center; margin-block-end: 40px; }

/* 82px in a 1154-wide box (3509:1399). "Frequently Asked" is Light and
   "Questions" Bold in the SAME line, which is why this is one heading with an
   <em> rather than two elements — a line break between them would be a
   different shape at every width. */
.mp-faq__title {
  font-size: var(--fs-h1);
  font-weight: var(--fw-light);
  color: var(--c-primary);
  line-height: var(--lh-heading);
  margin: 0;
  text-wrap: balance;
}
.mp-faq__title em { font-style: normal; font-weight: var(--fw-bold); }

.mp-faq__lede {
  margin-block-start: 21px;                 /* 3509:1407 y103 - title 82 */
  margin-inline: auto;
  max-inline-size: 900px;
  font-size: var(--fs-lead);
  font-weight: var(--fw-book);
  color: var(--c-primary);
  line-height: var(--lh-heading);
  text-wrap: pretty;
}

/* ─ Photo strip (3509:1423) ────────────────────────────────────────────── */
/* 1322 wide overall: 198 + 56 + 252 + 56 + 198 + 56 + 252 + 56 + 198.
   The gaps are the 56px the node positions imply (254-198, 816-562-198). */
.mp-faq__strip {
  display: grid;
  grid-template-columns: 198fr 252fr 198fr 252fr 198fr;
  gap: 56px;
  align-items: start;
  margin-block-end: 52px;                   /* 3509:2596 y579 - strip end 527 */
}

.mp-faq__stack { display: grid; gap: 22px; }   /* 163 - 141 */

.mp-faq__shot { margin: 0; }
.mp-faq__shot img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  object-fit: cover;
}

/* The arch masks, straight from Figma's own mask rectangles. Same technique
   the testimonial avatars use and for the same two reasons: border-radius
   cannot express these (the dome is a semicircle wider than any corner), and
   clip-path renders them jagged in Chrome. Intrinsic size is 4x the viewBox
   so the raster downsamples smoothly on HiDPI. */
.mp-faq__shot--tall img { aspect-ratio: 198 / 304; }
.mp-faq__shot--wide img { aspect-ratio: 252 / 141; }

.mp-faq__shot img {
  -webkit-mask-size: 100% 100%;  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
.mp-faq__shot--arch-tl img    { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='792' height='1216' viewBox='0 0 198 304' preserveAspectRatio='none'%3E%3Cpath d='M0 120C0 53.7258 53.7258 0 120 0H186C192.627 0 198 5.37258 198 12V292C198 298.627 192.627 304 186 304H12C5.37258 304 0 298.627 0 292V120Z' fill='%23fff'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='792' height='1216' viewBox='0 0 198 304' preserveAspectRatio='none'%3E%3Cpath d='M0 120C0 53.7258 53.7258 0 120 0H186C192.627 0 198 5.37258 198 12V292C198 298.627 192.627 304 186 304H12C5.37258 304 0 298.627 0 292V120Z' fill='%23fff'/%3E%3C/svg%3E"); }
.mp-faq__shot--dome img       { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='792' height='1216' viewBox='0 0 198 304' preserveAspectRatio='none'%3E%3Cpath d='M0 99C0 44.3238 44.3238 0 99 0V0C153.676 0 198 44.3238 198 99V292C198 298.627 192.627 304 186 304H12C5.37258 304 0 298.627 0 292V99Z' fill='%23fff'/%3E%3C/svg%3E");   mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='792' height='1216' viewBox='0 0 198 304' preserveAspectRatio='none'%3E%3Cpath d='M0 99C0 44.3238 44.3238 0 99 0V0C153.676 0 198 44.3238 198 99V292C198 298.627 192.627 304 186 304H12C5.37258 304 0 298.627 0 292V99Z' fill='%23fff'/%3E%3C/svg%3E"); }
.mp-faq__shot--arch-tr img    { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='792' height='1216' viewBox='0 0 198 304' preserveAspectRatio='none'%3E%3Cpath d='M0 12C0 5.37259 5.37258 0 12 0H78C144.274 0 198 53.7258 198 120V292C198 298.627 192.627 304 186 304H12C5.37258 304 0 298.627 0 292V12Z' fill='%23fff'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='792' height='1216' viewBox='0 0 198 304' preserveAspectRatio='none'%3E%3Cpath d='M0 12C0 5.37259 5.37258 0 12 0H78C144.274 0 198 53.7258 198 120V292C198 298.627 192.627 304 186 304H12C5.37258 304 0 298.627 0 292V12Z' fill='%23fff'/%3E%3C/svg%3E"); }
.mp-faq__shot--wide-bl img    { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1008' height='564' viewBox='0 0 252 141' preserveAspectRatio='none'%3E%3Cpath d='M0 8C0 3.58172 3.58172 0 8 0H244C248.418 0 252 3.58172 252 8V133C252 137.418 248.418 141 244 141H100C44.7715 141 0 96.2285 0 41V8Z' fill='%23fff'/%3E%3C/svg%3E");    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1008' height='564' viewBox='0 0 252 141' preserveAspectRatio='none'%3E%3Cpath d='M0 8C0 3.58172 3.58172 0 8 0H244C248.418 0 252 3.58172 252 8V133C252 137.418 248.418 141 244 141H100C44.7715 141 0 96.2285 0 41V8Z' fill='%23fff'/%3E%3C/svg%3E"); }
.mp-faq__shot--wide-r img     { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1008' height='564' viewBox='0 0 252 141' preserveAspectRatio='none'%3E%3Cpath d='M0 8C0 3.58172 3.58172 0 8 0H152C207.228 0 252 44.7715 252 100V133C252 137.418 248.418 141 244 141H8C3.58172 141 0 137.418 0 133V8Z' fill='%23fff'/%3E%3C/svg%3E");     mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1008' height='564' viewBox='0 0 252 141' preserveAspectRatio='none'%3E%3Cpath d='M0 8C0 3.58172 3.58172 0 8 0H152C207.228 0 252 44.7715 252 100V133C252 137.418 248.418 141 244 141H8C3.58172 141 0 137.418 0 133V8Z' fill='%23fff'/%3E%3C/svg%3E"); }
.mp-faq__shot--wide-plain img { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1008' height='564' viewBox='0 0 252 141' preserveAspectRatio='none'%3E%3Cpath d='M0 12C0 5.37258 5.37258 0 12 0H240C246.627 0 252 5.37258 252 12V129C252 135.627 246.627 141 240 141H12C5.37258 141 0 135.627 0 129V12Z' fill='%23fff'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1008' height='564' viewBox='0 0 252 141' preserveAspectRatio='none'%3E%3Cpath d='M0 12C0 5.37258 5.37258 0 12 0H240C246.627 0 252 5.37258 252 12V129C252 135.627 246.627 141 240 141H12C5.37258 141 0 135.627 0 129V12Z' fill='%23fff'/%3E%3C/svg%3E"); }

/* ─ Tab pills (3509:2596) ──────────────────────────────────────────────── */
/* A white 70px-tall bar holding four 38px pills, 16px inset all round. */
.mp-faq__tabs {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 24px;
  padding: 16px;
  background: var(--c-white);
  border-radius: 999px;
  box-shadow: var(--shadow-card);
  margin-block-end: 52px;
}

.mp-faq__tab {
  min-block-size: 44px;                     /* touch floor; Figma's 38 is under it */
  padding-inline: 20px;
  border: 0;
  border-radius: 999px;
  background: var(--c-surface-tint);
  color: var(--c-primary);
  font-family: inherit;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-book);
  cursor: pointer;
  transition: background-color 160ms ease, color 160ms ease;
}
.mp-faq__tab:hover { background: color-mix(in srgb, var(--c-accent) 18%, var(--c-white)); }
.mp-faq__tab.is-active {
  background: var(--c-accent);
  color: var(--c-white);
  font-weight: var(--fw-bold);
}

/* ─ Tab intro (3509:2654) ──────────────────────────────────────────────── */
.mp-faq__intro { margin-block-end: 32px; }
.mp-faq__intro-body {
  margin-block-start: 18px;
  max-inline-size: 1322px;
  font-size: var(--fs-lead);
  font-weight: var(--fw-book);
  color: var(--c-ink);
  line-height: var(--lh-heading);
  text-wrap: pretty;
}

/* ─ Group cards (3509:3945) ────────────────────────────────────────────── */
.mp-faq__group {
  background: var(--c-white);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: 32px 24px;
  margin-block-end: 32px;
}

.mp-faq__group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  cursor: pointer;
  list-style: none;                         /* kill the default disclosure marker */
  padding-block-end: 8px;
  border-block-end: 2px solid var(--c-surface-tint);
}
.mp-faq__group-head::-webkit-details-marker { display: none; }

.mp-faq__group-title {
  margin: 0;
  font-size: var(--fs-h3);
  font-weight: var(--fw-bold);
  color: var(--c-accent);
  line-height: 1.2;
}

.mp-faq__caret {
  flex-shrink: 0;
  color: var(--c-primary);
  transition: transform 200ms ease;
}
.mp-faq__group[open] > .mp-faq__group-head .mp-faq__caret { transform: rotate(180deg); }

.mp-faq__items { padding-block-start: 24px; }

.mp-faq__item + .mp-faq__item { border-block-start: 1px solid var(--c-surface-tint); }

.mp-faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  cursor: pointer;
  list-style: none;
  padding-block: 16px;
  font-size: var(--fs-card);
  font-weight: var(--fw-bold);
  color: var(--c-primary);
}
.mp-faq__q::-webkit-details-marker { display: none; }

/* ONE plus glyph at two angles. Figma draws "+" on the open row and "x" on
   every closed one (3509:3971 vs 3509:3976) — the same mark rotated 45deg,
   not two icons. Rotating rather than swapping keeps the transition honest
   and needs no second asset. */
.mp-faq__sign {
  flex-shrink: 0;
  color: var(--c-primary);
  transform: rotate(45deg);
  transition: transform 200ms ease;
}
.mp-faq__item[open] > .mp-faq__q .mp-faq__sign { transform: rotate(0deg); }

.mp-faq__a {
  padding-block-end: 20px;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-book);
  color: var(--c-ink);
  line-height: var(--lh-body);
  text-wrap: pretty;
}

.mp-faq__empty {
  padding: 40px 24px;
  text-align: center;
  color: var(--c-ink-muted);
  font-size: var(--fs-body-sm);
}

@media (prefers-reduced-motion: reduce) {
  .mp-faq__caret, .mp-faq__sign, .mp-faq__tab { transition: none; }
}

/* ─ FAQs, tablet and below ─────────────────────────────────────────────── */
@media (max-width: 1024px) {
  .mp-faq__strip { gap: 32px; }
  .mp-faq__tabs { gap: 12px; }
}

/* ─ FAQs, mobile (3548:105869, 390 frame) ──────────────────────────────── */
@media (max-width: 768px) {
  /* Mobile nav is 108px too (3550:108052), but the section's own top space
     shrinks — so the clearance is stated separately rather than scaled. */
  .mp-faq { padding-block: calc(108px + 24px) 40px; }

  /* 342 wide inside a 390 frame (3548:107454) — the container's own gutter
     already gives that, so only the type scale changes here. */
  .mp-faq__head { margin-block-end: 28px; }
  .mp-faq__title { font-size: 30px; }              /* 342x76 across two lines */
  .mp-faq__lede { font-size: var(--fs-body-sm); margin-block-start: 8px; }

  /* Three photos, not seven: Figma's mobile strip (3548:107472) keeps only
     the three TALL arches — owner, setter, car — at 108x166 with 17px gaps,
     and drops both stacked columns entirely. Hiding the stacks rather than
     re-exporting means the same seven files serve both layouts. */
  .mp-faq__strip {
    grid-template-columns: repeat(3, 1fr);
    gap: 17px;
    margin-block-end: 32px;
  }
  .mp-faq__stack { display: none; }

  /* 96px tall rather than 70 (3550:107877) because the four pills wrap to two
     rows at this width. */
  .mp-faq__tabs {
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
    padding: 12px;
    border-radius: var(--radius-md);
    margin-block-end: 28px;
  }
  .mp-faq__tab { padding-inline: 12px; font-size: var(--fs-label); }

  .mp-faq__intro { margin-block-end: 20px; }
  .mp-faq__intro-body { font-size: var(--fs-body-sm); margin-block-start: 10px; }

  /* 342-wide cards (3549:107751); the first is 746 tall and open, the rest
     60-80 and shut — which is exactly what `open` on the first group gives. */
  .mp-faq__group { padding: 20px 16px; margin-block-end: 16px; border-radius: var(--radius-sm); }
  .mp-faq__group-title { font-size: var(--fs-card); }
  .mp-faq__caret { inline-size: 22px; block-size: 22px; }

  .mp-faq__items { padding-block-start: 14px; }
  .mp-faq__q { font-size: var(--fs-body-sm); gap: 14px; padding-block: 13px; }
  .mp-faq__sign { inline-size: 22px; block-size: 22px; }
  .mp-faq__a { font-size: var(--fs-label); padding-block-end: 14px; }
}

/* =========================================================================
   SERVICES PAGE — measured fidelity pass
   Figma kyudcw9zAgmniIXWNveugA · desktop 3472:66518 (1440x5710)
                                · mobile  3550:117739 (393x6477)

   Every number here was read out of Figma, not estimated. Two of them were
   read the hard way and are worth knowing about before editing:

   1. THE HERO COLLAGE IS LAID OUT IN A ROTATED AUTO-LAYOUT. get_metadata
      reports its children in the PRE-rotation frame, so the offsets look
      nonsensical (a 386-wide child at "y=386" inside a 623-wide parent) and
      two of the five mask exports come back transposed. The geometry below
      was therefore measured off a 1:1 render of the collage node, scanned
      against the brand blue Figma bakes in behind it. Do not "correct" these
      numbers against get_metadata — they disagree by design.

   2. THE HERO TITLE MIXES TWO TYPE SIZES, not just two weights: "Pet
      Services for " is Gotham Bold 82px and "Every Kind of Care" is Gotham
      Light 96px (3472:66756). An earlier pass read it as one 82px string
      with a weight change, which is why the headline sat too small.

   Section rhythm, measured frame-to-frame on the desktop node:
     hero      0 → 1056     statement text 1279 → 1589
     grid   1824 → 3030     flow band      3180 → 3567
     providers 3717 → 4351  app  4520 → 5088   footer 5172
   ========================================================================= */

/* ─ Shapes ──────────────────────────────────────────────────────────────
   Generated from Figma's own vector exports by gen_masks.py — inline
   data-URI SVGs at 4x intrinsic size with preserveAspectRatio="none",
   driven by mask-size: 100% 100%. Same technique and the same two reasons
   as the FAQ photo strip: border-radius cannot express a dome, and
   clip-path renders jagged in Chrome. DO NOT hand-edit the path data. */
/* This page's vertical rhythm is its own — measured band-to-band, it runs
   roughly 2.3x the site's --space-section, and forcing the shared token on
   it would collapse the design's deliberately airy statement band:
     hero 1056 -> statement text 1279   = 223   (--svc-gap-lg)
     statement 1589 -> grid heading 1824 = 235  (--svc-gap-xl)
     grid 3030 -> flow 3180              = 150  (--svc-gap-md)
     flow 3567 -> providers 3717         = 150
   Providers/app bring their own --space-section, so the flow band's bottom
   subtracts it rather than double-counting. */
:root {
  --svc-gap-lg: 223px;
  --svc-gap-xl: 235px;
  --svc-gap-md: 150px;
}

.mp-mask {
  -webkit-mask-size: 100% 100%;   mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
/* generated by gen_masks.py — do not hand-edit the path data */
.mp-mask--collage-triple { /* 386x204, path 578 chars */
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1544'%20height='816'%20viewBox='0%200%20386%20204'%20preserveAspectRatio='none'%3E%3Cpath%20d='M66.667%200C100.909%200.000155412%20128.667%2027.7584%20128.667%2062L128.672%2061.1982C129.101%2027.3261%20156.693%20-1.30423e-06%20190.667%200H195.333C229.575%201.3145e-06%20257.333%2027.7583%20257.333%2062L257.339%2061.1982C257.768%2027.3262%20285.359%200.000184449%20319.333%200H324C358.242%201.3145e-06%20386%2027.7583%20386%2062V182C386%20194.15%20376.15%20204%20364%20204H279.333C267.183%20204%20257.333%20194.15%20257.333%20182C257.333%20194.15%20247.483%20204%20235.333%20204H150.667C138.517%20204%20128.667%20194.15%20128.667%20182C128.667%20194.15%20118.817%20204%20106.667%20204H22C9.84974%20204%202.17764e-06%20194.15%200%20182V62C1.70426e-06%2027.7584%2027.7584%201.2703e-05%2062%200H66.667Z'%20fill='%23fff'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1544'%20height='816'%20viewBox='0%200%20386%20204'%20preserveAspectRatio='none'%3E%3Cpath%20d='M66.667%200C100.909%200.000155412%20128.667%2027.7584%20128.667%2062L128.672%2061.1982C129.101%2027.3261%20156.693%20-1.30423e-06%20190.667%200H195.333C229.575%201.3145e-06%20257.333%2027.7583%20257.333%2062L257.339%2061.1982C257.768%2027.3262%20285.359%200.000184449%20319.333%200H324C358.242%201.3145e-06%20386%2027.7583%20386%2062V182C386%20194.15%20376.15%20204%20364%20204H279.333C267.183%20204%20257.333%20194.15%20257.333%20182C257.333%20194.15%20247.483%20204%20235.333%20204H150.667C138.517%20204%20128.667%20194.15%20128.667%20182C128.667%20194.15%20118.817%20204%20106.667%20204H22C9.84974%20204%202.17764e-06%20194.15%200%20182V62C1.70426e-06%2027.7584%2027.7584%201.2703e-05%2062%200H66.667Z'%20fill='%23fff'/%3E%3C/svg%3E");
}
.mp-mask--collage-taxi { /* 263x218, path 298 chars */
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1052'%20height='872'%20viewBox='0%200%20263%20218'%20preserveAspectRatio='none'%3E%3Cpath%20d='M217%20116C242.405%20116%20263%20136.595%20263%20162V172C263%20197.405%20242.405%20218%20217%20218H46C20.5949%20218%201.61086e-07%20197.405%200%20172V162C0%20136.595%2020.5949%20116%2046%20116H217ZM217%200C242.405%200%20263%2020.5949%20263%2046V60C263%2085.4051%20242.405%20106%20217%20106H46C20.5949%20106%200%2085.4051%200%2060V46C1.22425e-06%2020.5949%2020.5949%200%2046%200H217Z'%20fill='%23fff'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1052'%20height='872'%20viewBox='0%200%20263%20218'%20preserveAspectRatio='none'%3E%3Cpath%20d='M217%20116C242.405%20116%20263%20136.595%20263%20162V172C263%20197.405%20242.405%20218%20217%20218H46C20.5949%20218%201.61086e-07%20197.405%200%20172V162C0%20136.595%2020.5949%20116%2046%20116H217ZM217%200C242.405%200%20263%2020.5949%20263%2046V60C263%2085.4051%20242.405%20106%20217%20106H46C20.5949%20106%200%2085.4051%200%2060V46C1.22425e-06%2020.5949%2020.5949%200%2046%200H217Z'%20fill='%23fff'/%3E%3C/svg%3E");
}
.mp-mask--collage-car { /* 393x204, path 367 chars */
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1572'%20height='816'%20viewBox='0%200%20393%20204'%20preserveAspectRatio='none'%3E%3Cpath%20d='M54.7549%200C80.1347%200.000232566%20100.713%2020.5544%20100.754%2045.9248C100.794%2020.5543%20121.374%200%20146.754%200H346.754C372.159%200%20392.754%2020.5949%20392.754%2046V158C392.754%20183.405%20372.159%20204%20346.754%20204H146.754C121.374%20204%20100.794%20183.445%20100.754%20158.074C100.714%20183.445%2080.1351%20204%2054.7549%20204H46C20.5949%20204%201.61086e-07%20183.405%200%20158V46C1.80416e-06%2020.5949%2020.5949%200%2046%200H54.7549Z'%20fill='%23fff'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1572'%20height='816'%20viewBox='0%200%20393%20204'%20preserveAspectRatio='none'%3E%3Cpath%20d='M54.7549%200C80.1347%200.000232566%20100.713%2020.5544%20100.754%2045.9248C100.794%2020.5543%20121.374%200%20146.754%200H346.754C372.159%200%20392.754%2020.5949%20392.754%2046V158C392.754%20183.405%20372.159%20204%20346.754%20204H146.754C121.374%20204%20100.794%20183.445%20100.754%20158.074C100.714%20183.445%2080.1351%20204%2054.7549%20204H46C20.5949%20204%201.61086e-07%20183.405%200%20158V46C1.80416e-06%2020.5949%2020.5949%200%2046%200H54.7549Z'%20fill='%23fff'/%3E%3C/svg%3E");
}
.mp-mask--card-stacked { /* 368x302, path 286 chars */
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1472'%20height='1208'%20viewBox='0%200%20368%20302'%20preserveAspectRatio='none'%3E%3Cpath%20d='M322%20155C347.405%20155%20368%20175.595%20368%20201V256C368%20281.405%20347.405%20302%20322%20302H46C20.5949%20302%200%20281.405%200%20256V201C0%20175.595%2020.5949%20155%2046%20155C20.5949%20155%201.25647e-06%20134.405%200%20109V46C1.25647e-06%2020.5949%2020.5949%200%2046%200H322C347.405%200%20368%2020.5949%20368%2046V109C368%20134.405%20347.405%20155%20322%20155Z'%20fill='%23fff'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1472'%20height='1208'%20viewBox='0%200%20368%20302'%20preserveAspectRatio='none'%3E%3Cpath%20d='M322%20155C347.405%20155%20368%20175.595%20368%20201V256C368%20281.405%20347.405%20302%20322%20302H46C20.5949%20302%200%20281.405%200%20256V201C0%20175.595%2020.5949%20155%2046%20155C20.5949%20155%201.25647e-06%20134.405%200%20109V46C1.25647e-06%2020.5949%2020.5949%200%2046%200H322C347.405%200%20368%2020.5949%20368%2046V109C368%20134.405%20347.405%20155%20322%20155Z'%20fill='%23fff'/%3E%3C/svg%3E");
}
.mp-mask--card-split { /* 368x302, path 409 chars */
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1472'%20height='1208'%20viewBox='0%200%20368%20302'%20preserveAspectRatio='none'%3E%3Cpath%20d='M48.4043%200C73.8094%201.14816e-05%2094.4043%2020.5949%2094.4043%2046L94.4082%2045.4053C94.7266%2020.2743%20115.198%209.01341e-07%20140.404%200H322.001C347.406%200.000209776%20368.001%2020.595%20368.001%2046V256C368.001%20281.405%20347.406%20302%20322.001%20302H140.404C115.198%20302%2094.7266%20281.726%2094.4082%20256.595L94.4043%20256C94.4043%20281.405%2073.8094%20302%2048.4043%20302H46C20.5949%20302%201.28869e-07%20281.405%200%20256V46C1.28869e-07%2020.5949%2020.5949%200%2046%200H48.4043Z'%20fill='%23fff'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1472'%20height='1208'%20viewBox='0%200%20368%20302'%20preserveAspectRatio='none'%3E%3Cpath%20d='M48.4043%200C73.8094%201.14816e-05%2094.4043%2020.5949%2094.4043%2046L94.4082%2045.4053C94.7266%2020.2743%20115.198%209.01341e-07%20140.404%200H322.001C347.406%200.000209776%20368.001%2020.595%20368.001%2046V256C368.001%20281.405%20347.406%20302%20322.001%20302H140.404C115.198%20302%2094.7266%20281.726%2094.4082%20256.595L94.4043%20256C94.4043%20281.405%2073.8094%20302%2048.4043%20302H46C20.5949%20302%201.28869e-07%20281.405%200%20256V46C1.28869e-07%2020.5949%2020.5949%200%2046%200H48.4043Z'%20fill='%23fff'/%3E%3C/svg%3E");
}
.mp-mask--card-triple { /* 368x302, path 510 chars */
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1472'%20height='1208'%20viewBox='0%200%20368%20302'%20preserveAspectRatio='none'%3E%3Cpath%20d='M306.5%200C340.465%201.9223e-06%20368%2027.5346%20368%2061.5V280C368%20292.15%20358.15%20302%20346%20302H267C254.85%20302%20245%20292.15%20245%20280C245%20292.15%20235.15%20302%20223%20302H144C133.454%20302%20124.644%20294.58%20122.5%20284.676C120.356%20294.58%20111.546%20302%20101%20302H22C9.84974%20302%20-4.10195e-07%20292.15%200%20280V61.5C0.000126751%2027.5346%2027.5346%201.39593e-07%2061.5%200C92.7966%201.77126e-06%20118.633%2023.3777%20122.5%2053.623C126.367%2023.3777%20152.203%201.28625e-07%20183.5%200C217.465%201.9223e-06%20245%2027.5346%20245%2061.5L245.005%2060.7051C245.431%2027.106%20272.8%201.38503e-07%20306.5%200Z'%20fill='%23fff'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='1472'%20height='1208'%20viewBox='0%200%20368%20302'%20preserveAspectRatio='none'%3E%3Cpath%20d='M306.5%200C340.465%201.9223e-06%20368%2027.5346%20368%2061.5V280C368%20292.15%20358.15%20302%20346%20302H267C254.85%20302%20245%20292.15%20245%20280C245%20292.15%20235.15%20302%20223%20302H144C133.454%20302%20124.644%20294.58%20122.5%20284.676C120.356%20294.58%20111.546%20302%20101%20302H22C9.84974%20302%20-4.10195e-07%20292.15%200%20280V61.5C0.000126751%2027.5346%2027.5346%201.39593e-07%2061.5%200C92.7966%201.77126e-06%20118.633%2023.3777%20122.5%2053.623C126.367%2023.3777%20152.203%201.28625e-07%20183.5%200C217.465%201.9223e-06%20245%2027.5346%20245%2061.5L245.005%2060.7051C245.431%2027.106%20272.8%201.38503e-07%20306.5%200Z'%20fill='%23fff'/%3E%3C/svg%3E");
}
.mp-mask--collage-arch-top { /* 193x204, path 74 chars */
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='772'%20height='816'%20viewBox='0%200%20193%20204'%20preserveAspectRatio='none'%3E%3Cpath%20d='M0%20204V96.5C0%2043.2045%2043.2045%200%2096.5%200C149.795%200%20193%2043.2045%20193%2096.5V204Z'%20fill='%23fff'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='772'%20height='816'%20viewBox='0%200%20193%20204'%20preserveAspectRatio='none'%3E%3Cpath%20d='M0%20204V96.5C0%2043.2045%2043.2045%200%2096.5%200C149.795%200%20193%2043.2045%20193%2096.5V204Z'%20fill='%23fff'/%3E%3C/svg%3E");
}
.mp-mask--collage-arch-bottom { /* 193x204, path 80 chars */
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='772'%20height='816'%20viewBox='0%200%20193%20204'%20preserveAspectRatio='none'%3E%3Cpath%20d='M0%200H193V107.5C193%20160.795%20149.795%20204%2096.5%20204C43.2045%20204%200%20160.795%200%20107.5V0Z'%20fill='%23fff'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='772'%20height='816'%20viewBox='0%200%20193%20204'%20preserveAspectRatio='none'%3E%3Cpath%20d='M0%200H193V107.5C193%20160.795%20149.795%20204%2096.5%20204C43.2045%20204%200%20160.795%200%20107.5V0Z'%20fill='%23fff'/%3E%3C/svg%3E");
}

/* ─ Hero (3472:67464, 1440x1056) ────────────────────────────────────────
   Copy block and collage are BOTTOM-aligned in the design: both end at
   y=881, 175px above the hero's base — the same 175px the homepage hero
   uses. Top padding is 165px, which is where the collage starts, i.e. 57px
   of clear air below the 108px fixed nav. The nav sits transparent over
   this hero exactly as it does on the homepage, so no nav clearance is
   added on top of that (unlike FAQs, which opens on a light background).

   The background is Figma's own gradient-and-grain layer ("Group 289485",
   3472:67465) rather than a CSS approximation of it — the design's texture
   is a real raster and a radial-gradient guess reads visibly flatter. The
   URL arrives as --hero-img from the layout, built ONCE there so that the
   preload and this rule are byte-identical (see layout.blade.php). */
.mp-svc-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;                       /* clip the texture to the 108px radius */
  background-color: var(--c-primary);     /* never white while the texture loads */
  background-image: var(--hero-img);
  background-size: cover;
  background-position: center;
  color: var(--c-white);
  border-end-start-radius: var(--radius-lg);
  border-end-end-radius: var(--radius-lg);
  padding-block: 165px 175px;
}

.mp-svc-hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 640fr) minmax(0, 623fr);
  align-items: end;
  column-gap: 50px;
}

/* ─ Hero copy (3477:67557) ─ 21px between all three children. */
.mp-svc-hero__copy { display: flex; flex-direction: column; align-items: flex-start; gap: 21px; }

.mp-svc-hero__title {
  margin: 0;
  max-inline-size: 720px;
  font-size: var(--fs-hero);              /* 82px — the Bold lead-in */
  font-weight: var(--fw-bold);
  /* leading-none on both spans (3472:66756), NOT the --lh-heading 1.16 the
     rest of the site uses. The three lines are driven by the largest font on
     each — 82 + 96 + 96 = 274, which is the drawn 276. At 1.16 the block
     came out 318 and pushed the bottom-aligned copy 40px up the hero. */
  line-height: 1;
}
/* "Every Kind of Care" is Gotham Light at 96px — BIGGER than the bold
   lead-in, not merely lighter (3472:66756). */
.mp-svc-hero__title em {
  font-style: normal;
  font-weight: var(--fw-light);
  font-size: var(--fs-hero-light);
}

.mp-svc-hero__body {
  margin: 0;
  max-inline-size: 592px;
  font-size: var(--fs-lead);              /* 28px  3477:67556 */
  font-weight: var(--fw-book);
  line-height: 1.2;
}

/* Solid accent pill, NOT a translucent white one (3477:67558). */
.mp-svc-hero__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-block-size: 52px;
  inline-size: 330px;
  max-inline-size: 100%;
  padding-inline: 20px;
  border-radius: var(--radius-pill);
  background: var(--c-accent);
  color: var(--c-white);
  font-size: var(--fs-card);              /* 24px Medium */
  font-weight: var(--fw-medium);
  /* The stylesheet's reset sets `a { color: inherit }` and nothing else, so
     an anchor that is not .mp-btn keeps the browser's default underline —
     which is exactly how this pill first shipped. */
  text-decoration: none;
}
.mp-svc-hero__cta:hover { background: color-mix(in srgb, var(--c-accent) 86%, #000); }
.mp-svc-hero__cta img { inline-size: 20px; block-size: 20px; flex: 0 0 auto; }
/* The magnifier is drawn pointing down-right; mirror it on the RTL page so
   it leans into the text the same way it does in LTR. */
[dir="rtl"] .mp-svc-hero__cta img { transform: scaleX(-1); }

/* ─ Hero collage (3477:67591 desktop / 3550:119434 mobile) ──────────────
   ONE flat list of six tiles, in one order, at both breakpoints. The
   mobile frame is not a different composition — it is the same six tiles
   reflowed three-per-row instead of two, which is why this needs no
   duplicate markup:

     desktop 623x716        mobile 612x309 (overflows the 345 gutter)
       salon 193 | park 386   salon 124.46 | park 248.92 | groom 207
       groom 343 | taxi 263   taxi  158.71 | cat  124.46 | car  253.27
       cat   193 | car  393
     row gap 45 on both. Every tile's ASPECT RATIO is identical across the
     two frames (0.946 / 1.892 / 1.573 / 1.206 / 0.946 / 1.926) — only the
     widths change.

   flex-wrap does the grouping for free: 193+386+343 = 922 > 623 forces the
   break after two on desktop, and 124.46+248.92+207+158.71 = 739 > 612
   forces it after three on mobile. justify-content: space-between then
   produces the drawn gaps as leftover space — 44 / 17 / 37 on desktop and
   16 / 37.8 on mobile — without any of them being written down.

   Capped at the drawn 623px so the hero cannot grow TALLER as the viewport
   grows WIDER. That is the single most repeated bug on this page (hero,
   tile panel, simpler collage all shipped it). */
.mp-svc-hero__collage {
  container-type: inline-size;
  inline-size: 100%;
  max-inline-size: 623px;
  justify-self: end;
}
.mp-svc-hero__tiles {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  row-gap: calc(45 / 623 * 100cqw);
}

.mp-svc-hero__tile { position: relative; margin: 0; }
.mp-svc-hero__tile > * {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}
.mp-svc-hero__tile--salon { inline-size: calc(193 / 623 * 100%); aspect-ratio: 193 / 204; }
.mp-svc-hero__tile--park  { inline-size: calc(386 / 623 * 100%); aspect-ratio: 386 / 204; }
.mp-svc-hero__tile--groom { inline-size: calc(343 / 623 * 100%); aspect-ratio: 343 / 218; }
.mp-svc-hero__tile--taxi  { inline-size: calc(263 / 623 * 100%); aspect-ratio: 263 / 218; }
.mp-svc-hero__tile--cat   { inline-size: calc(193 / 623 * 100%); aspect-ratio: 193 / 204; }
.mp-svc-hero__tile--car   { inline-size: calc(393 / 623 * 100%); aspect-ratio: 393 / 204; }

/* The grooming tile is the one plain rounded rectangle in the collage —
   46px, no mask (3473:67490). */
.mp-svc-hero__tile--groom > * { border-radius: 46px; }

/* Video tiles: the poster still sits underneath as a real image, so the
   shape is never an empty hole before the first frame decodes, and it is
   what a reduced-motion or no-JS visitor keeps. */
.mp-svc-hero__video {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 320ms ease;
}
.mp-svc-hero__video.is-playing { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .mp-svc-hero__video { display: none; }
}

/* ─ The pale panel behind statement + grid (3472:66523) ─────────────────
   Figma paints a semi-transparent gradient with backdrop-blur(80px) over
   two large blurred blobs (Group 7 3472:66634 — #2CA5E8 into #EF88CC at
   stdDeviation 60 — and Group 6 3472:66758). Reproduced as pre-softened
   radial gradients plus the panel gradient itself: blurring an already
   soft shape through backdrop-filter across a ~2000px band is a real
   compositing cost for no visible difference. */
.mp-svc-band {
  position: relative;
  isolation: isolate;
  background:
    /* Top layer: dissolve the panel into the page over its last 8%. In
       Figma the lower blob EXTENDS PAST the panel (Group 7 runs to y=3296
       against a panel ending at 3010), so there is no boundary there. Here
       the blobs are painted BY the panel and are therefore clipped to it,
       which left a visible horizontal seam where the tint stopped dead
       above the flow band. */
    linear-gradient(180deg, transparent 92%, var(--c-bg) 100%),
    radial-gradient(54% 30% at 28% 96%,  rgba(44, 165, 232, 0.13), transparent 72%),
    radial-gradient(46% 34% at 101% 64%, rgba(239, 136, 204, 0.10), transparent 70%),
    linear-gradient(180deg, rgba(255, 255, 255, 0) 0%,
                            rgba(44, 165, 232, 0.14) 31.458%,
                            rgba(255, 255, 255, 0.24) 100%),
    var(--c-bg);
}

/* ─ Statement band (3477:67595) ─────────────────────────────────────────
   52px Book, centred, --c-ink, with three Bold phrases painted in a
   clipped gradient that ALTERNATES direction (navy→blue, blue→navy,
   navy→blue). That is the homepage pull-quote's exact mechanism, so this
   reuses .mp-text-gradient--alt/--reverse and the same **marker** parser
   rather than inventing a second one. Measured 1193x310 = 5 lines at 62px,
   i.e. 62/52 = 1.19. */
.mp-svc-statement { padding-block: var(--svc-gap-lg) calc(var(--svc-gap-xl) / 2); }
.mp-svc-statement__inner { position: relative; }

/* Measured against the design rather than eyeballed, because the two
   breakpoints are genuinely different marks, not one mark scaled:

     desktop  467px box, ink 1215..1605 (starts 64px above the text and runs
              BEHIND it), left-anchored at the gutter, ~0.11 alpha
     mobile    72px box, ink 890..953 (20px clear above the text), CENTRED,
              ~0.35 alpha — three times the presence, because at 72px a
              desktop-strength wash does not read at all

   The asset is quote-mark-SOLID.svg, a copy of the homepage's quote-mark.svg
   with its baked opacity="0.06" removed. A 0.06 asset can never reach 0.11 or
   0.35 through CSS opacity, and stripping it from the shared file would have
   changed the homepage pull quote.

   inset-block-start accounts for the glyph's own padding: its ink occupies
   y 32..429 of a 467 box, so the BOX sits ~6.85% higher than the ink does. */
.mp-svc-statement__mark {
  position: absolute;
  inset-block-start: -95px;
  /* The gutter is part of the offset, NOT free space to ignore: the
     containing block here is .mp-container's PADDING box, which starts at
     x=0, so a bare -6px put the glyph 66px left of where it is drawn and
     clipped its first lobe against the viewport edge.
     +76 on top of that lands the lobe tops at x=238 on a 1440 frame, which
     is where they are drawn — measured off the ONE row of the glyph that is
     above the statement text (y=1223) and therefore uncontaminated by it. */
  inset-inline-start: calc(var(--gutter) + 76px);
  inline-size: 467px;
  max-inline-size: 40%;
  block-size: auto;          /* the 467x467 attributes would otherwise pin the
                                height and squash the glyph — see
                                .mp-svc-card__media img */
  opacity: 0.11;
  pointer-events: none;
  z-index: -1;
}

.mp-svc-statement__text {
  margin: 0;
  margin-inline: auto;
  max-inline-size: 1193px;
  text-align: center;
  font-size: var(--fs-h2-lg);             /* 52px  3477:67595 */
  font-weight: var(--fw-book);
  color: var(--c-ink);
  line-height: 1.19;
  text-wrap: pretty;
}
.mp-svc-statement__highlight { font-weight: var(--fw-bold); }

/* ─ Service grid (3477:67597) ───────────────────────────────────────────
   Cards are 368 wide at the drawn 1320 container with 107px between them
   (justify-between across 1318) and 62px between rows. Expressed as a
   3-track grid so the row stays aligned at every width — the design's own
   second row is inset 65px and centred with a 42px gap, which does not
   line up with its first row; that is a slip in the file, not a pattern to
   reproduce. */
/* Bottom padding is 0: the design's pale panel ends at y=3010 and the grid's
   last CTA at 3030, i.e. flush. The 150px to the flow band is that band's
   own top padding. */
.mp-svc-grid {
  padding-block: calc(var(--svc-gap-xl) / 2) 0;
  /* The hero's primary CTA jumps here, and .mp-nav is position: fixed, so
     without this the heading lands under the bar. The section's own top
     padding already supplies most of the clearance and it differs per
     breakpoint, so subtract it rather than pick one number: 132 - 117.5 =
     14.5 desktop, 132 - 31 = 101 mobile, both landing the heading just
     below the 108px bar. */
  scroll-margin-block-start: calc(132px - var(--svc-gap-xl) / 2);
}

.mp-svc-grid__list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 107px;
  row-gap: var(--gap-grid);               /* 62px */
  margin-block-start: var(--space-heading);
}

.mp-svc-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
  position: relative;   /* containing block for the CTA's stretched hit area */
}

.mp-svc-card__media { margin: 0; inline-size: 100%; }
.mp-svc-card__media img {
  display: block;
  inline-size: 100%;
  /* block-size: auto is LOAD-BEARING. The width/height attributes on the
     <img> map to presentational hints, and aspect-ratio only fills in a
     dimension that computes to `auto` — so with height still pinned at the
     attribute's 604px the ratio was ignored and every card's photo rendered
     at double its drawn height. This stylesheet's reset deliberately does
     not carry a blanket `img { height: auto }`, so each sized image states
     it. (The collage tiles escape this because they set block-size: 100%.) */
  block-size: auto;
  aspect-ratio: 368 / 302;
  object-fit: cover;
}
/* Card 2 is the one card with no mask — a plain 46px rectangle
   (3480:68490). The other five carry Figma's own union shapes. */
.mp-svc-card__media--plain img { border-radius: 46px; }

.mp-svc-card__copy { display: flex; flex-direction: column; gap: 8px; }

.mp-svc-card__title {
  margin: 0;
  font-size: var(--fs-card);              /* 24px Bold  3480:68477 */
  font-weight: var(--fw-bold);
  color: var(--c-primary);
  line-height: var(--lh-heading);
}
.mp-svc-card__lede,
.mp-svc-card__body {
  margin: 0;
  font-size: var(--fs-body-sm);           /* 16px Book, 1.2  3480:68478 */
  font-weight: var(--fw-book);
  color: var(--c-ink);
  line-height: 1.2;
}
/* Figma sets the two paragraphs as one text node separated by an empty
   line, so the gap between them is exactly one 16px line. */
.mp-svc-card__body { margin-block-start: calc(var(--fs-body-sm) * 1.2); }

.mp-svc-card__cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  inline-size: 100%;
  margin-block-start: auto;
  min-block-size: 42px;
  padding: 12px;
  border-radius: 41px;
  background: var(--c-stat-pill);         /* #d4f2ff  3480:68479 */
  color: var(--c-primary);
  font-size: var(--fs-label);             /* 14px Medium, +0.56 tracking */
  font-weight: var(--fw-medium);
  letter-spacing: 0.56px;
  line-height: 1.2;                       /* 12 + 17 + 12 = 41 ≈ the drawn 42 */
  text-decoration: none;                  /* see .mp-svc-hero__cta */
}
/* Stretch the CTA's hit area over the whole card, which is what Figma makes
   clickable — without wrapping the card in an <a> whose accessible name would
   be the title plus both paragraphs. Only when it is a real link: the disabled
   branch renders a <span>, and a span with no destination must not swallow the
   card's clicks. z-index keeps it above the media but the ::after is
   transparent, so nothing changes visually. */
.mp-svc-card a.mp-svc-card__cta::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}
.mp-svc-card:hover a.mp-svc-card__cta {
  background: color-mix(in srgb, var(--c-stat-pill) 72%, var(--c-accent));
}
.mp-svc-card__cta:hover { background: color-mix(in srgb, var(--c-stat-pill) 72%, var(--c-accent)); }
.mp-svc-card__cta img { inline-size: 16px; block-size: 16px; flex: 0 0 auto; }
[dir="rtl"] .mp-svc-card__cta img { transform: scaleX(-1); }

/* ─ "How it works" band (3550:117737 desktop / 3555:119962 mobile) ──────
   A single full-bleed raster in the design — there is no structured
   content behind it to render, so it ships as one image. It is the only
   text on this page that cannot be translated or read by a screen reader,
   which is why it carries a real alt string. */
/* Full-bleed at EVERY width, NOT inside .mp-container: the node is x=0
   w=1440 on a 1440 frame and its connector line runs off both edges, so the
   composition only reads correctly when it spans the viewport.
   Deliberately NO max-inline-size — capping it at the drawn 1440 centred the
   band on wider screens and started the line a few hundred px in from the
   left, which is what it looked like before. The artwork is shipped at 2880
   so it stays at or above 1x out to a 2880 viewport.
   ⚠️ The consequence is that this band's HEIGHT grows with viewport WIDTH
   (516px at 1920, 688px at 2560). That is intended here — it is a single
   full-bleed illustration whose aspect ratio must hold — and is NOT the
   aspect-ratio-on-a-section trap documented above, which applies to bands
   that contain their own laid-out content. */
.mp-svc-flow { padding-block: var(--svc-gap-md) calc(var(--svc-gap-md) - var(--space-section)); }
.mp-svc-flow img {
  display: block;
  inline-size: 100%;
  block-size: auto;                       /* see .mp-svc-card__media img */
  aspect-ratio: 1440 / 387;
  object-fit: contain;
}

/* ─ Tablet ──────────────────────────────────────────────────────────────
   Below 1100 the 640:623 split stops working — the collage would squeeze
   the 96px headline into three or four very short lines — so the hero
   stacks and the collage becomes a full-width band. */
@media (max-width: 1100px) {
  :root { --svc-gap-lg: 160px; --svc-gap-xl: 170px; --svc-gap-md: 108px; }

  .mp-svc-hero { padding-block: calc(108px + 56px) 96px; }
  .mp-svc-hero__inner { grid-template-columns: minmax(0, 1fr); row-gap: 48px; }
  .mp-svc-hero__collage { max-inline-size: none; justify-self: stretch; }
  .mp-svc-hero__body { font-size: var(--fs-body); }

  /* -61px keeps the desktop's -95:467 box-to-offset ratio at this size. */
  .mp-svc-statement__mark { inline-size: 300px; inset-block-start: -61px; }

  .mp-svc-grid__list { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 48px; }
}

/* ─ Mobile (3550:117739, 393x6477) ──────────────────────────────────────
   The mobile frame keeps ALL SIX collage tiles but re-flows them into two
   rows of three, 612 wide against a 393 frame — i.e. the collage
   deliberately overflows the viewport and is clipped by the hero. Hero
   copy is 345 wide at a 24px gutter, the CTA is full-width, and the six
   service cards stack one per row at 345 with a 200px-tall media box. */
@media (max-width: 768px) {
  /* Measured on 3550:117739 the same way as desktop: hero base 852 →
     statement text 968 (116), text 1150 → grid heading 1212 (62), grid 3678
     → flow 3740 (62), flow 4524 → providers 4586 (62). */
  :root { --svc-gap-lg: 116px; --svc-gap-xl: 62px; --svc-gap-md: 62px; }

  /* Re-measured off a 1:1 render of 3550:117739, because get_metadata put the
     collage frame at y=393 — flush against the CTA — while the render puts its
     first tile row at 433. The CTA ends at 395, so there is a real 38px gap
     there, not the zero the metadata implied. Third time on this page that the
     mobile metadata disagreed with the pixels; trust the render.
       181 top | copy 214 | gap 38 | collage 309 | 110 bottom = 852 */
  .mp-svc-hero {
    padding-block: 181px 110px;
    /* 786x1704 portrait cut. The 768px breakpoint here MUST stay in step
       with the media-scoped preload in layout.blade.php. */
    background-image: var(--hero-img-mobile, var(--hero-img));
  }
  .mp-svc-hero__inner { row-gap: 38px; }
  .mp-svc-hero__copy { gap: 8px; }
  .mp-svc-hero__body { line-height: 1.2; }
  .mp-svc-hero__cta {
    inline-size: 100%;                    /* 345 full-width  3550:119468 */
    min-block-size: 38px;
    margin-block-start: 8px;
    font-size: var(--fs-body-sm);
  }
  .mp-svc-hero__cta img { inline-size: 14px; block-size: 14px; }

  /* 612 wide against a 345 gutter — the collage deliberately runs past
     BOTH edges and is clipped by the hero's own overflow, exactly as
     drawn. Equal bleed each side keeps it centred the way Figma has it. */
  .mp-svc-hero__collage {
    max-inline-size: none;
    inline-size: calc(612 / 345 * 100%);
    margin-inline: calc(-1 * (612 - 345) / 345 * 50%);
  }
  .mp-svc-hero__tiles { row-gap: calc(45 / 612 * 100cqw); }
  .mp-svc-hero__tile--salon { inline-size: calc(124.46 / 612 * 100%); }
  .mp-svc-hero__tile--park  { inline-size: calc(248.92 / 612 * 100%); }
  .mp-svc-hero__tile--groom { inline-size: calc(207 / 612 * 100%); }
  .mp-svc-hero__tile--taxi  { inline-size: calc(158.71 / 612 * 100%); }
  .mp-svc-hero__tile--cat   { inline-size: calc(124.46 / 612 * 100%); }
  .mp-svc-hero__tile--car   { inline-size: calc(253.27 / 612 * 100%); }

  /* 22px, not the shared --fs-h2-lg's 24 (3550:119482), and the SAME 1.19
     leading as desktop rather than a looser mobile one: the drawn block is
     345x182 over seven lines = a 26px pitch, and 26/22 = 1.18. At 24/1.3 the
     paragraph ran a line longer and broke "different / needs". */
  .mp-svc-statement__text { font-size: 22px; }
  /* Centred, not left-anchored — and inset-inline: 0 + margin-inline: auto
     rather than a 50% + translate, so it centres the same way in RTL. */
  .mp-svc-statement__mark {
    inline-size: 72px;
    max-inline-size: 72px;
    inset-inline: 0;
    margin-inline: auto;
    inset-block-start: -88px;
    opacity: 0.35;
  }

  .mp-svc-grid__list { grid-template-columns: minmax(0, 1fr); row-gap: 36px; }
  .mp-svc-card__media img { aspect-ratio: 345 / 200; }
  .mp-svc-card__cta { min-block-size: 38px; padding: 8px; }

  .mp-svc-flow img { aspect-ratio: 393 / 784; max-inline-size: none; }
}

/* =========================================================================
   SERVICE DETAIL PAGES — /services/{slug}
   Figma 3508:172 in-store · 3511:1068 mobile grooming · 3511:1681 daycare
       · 3511:2315 boarding · 3513:2967 veterinary · 3514:476 pet taxi
       · 3555:120427 mobile

   All six are ONE template; everything that differs is content. Unlike the
   Services index this page opens on a LIGHT background, so the nav is
   condensed from the top and the hero carries real clearance beneath it —
   the same treatment the FAQs page needed.
   ========================================================================= */

.mp-svcd-hero {
  padding-block: calc(108px + 64px) var(--space-section);
  background:
    radial-gradient(60% 50% at 12% 96%, rgba(44, 165, 232, 0.10), transparent 70%),
    var(--c-white);
}
.mp-svcd-hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 560fr) minmax(0, 700fr);
  align-items: center;
  column-gap: 60px;
}
.mp-svcd-hero__copy { display: flex; flex-direction: column; align-items: flex-start; }

.mp-svcd-hero__eyebrow {
  margin: 0 0 12px;
  font-size: var(--fs-nav);
  font-weight: var(--fw-medium);
  color: var(--c-accent);
}
/* "**Fresh cuts. Trusted salons.** Happy pets." — marked span Bold in
   --c-primary, tail Light. One string with markers so Arabic can put its own
   emphasis where its grammar does. */
.mp-svcd-hero__title {
  margin: 0;
  font-size: var(--fs-h1);
  font-weight: var(--fw-light);
  line-height: 1.14;
  color: var(--c-primary);
}
.mp-svcd-hero__title strong { font-weight: var(--fw-bold); }

.mp-svcd-hero__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-block: 24px 0;
}
.mp-svcd-hero__chip {
  padding: 8px 18px;
  border-radius: var(--radius-pill);
  background: var(--c-bg);
  border: 1px solid rgba(26, 58, 127, 0.10);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--c-primary);
}

.mp-svcd-hero__lede {
  margin-block: 24px 0;
  max-inline-size: 520px;
  font-size: var(--fs-body);
  font-weight: var(--fw-book);
  line-height: var(--lh-body);
  color: var(--c-primary);
}

.mp-svcd-hero__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-block-start: 28px;
  min-block-size: 44px;
  padding-inline: 24px;
  border-radius: var(--radius-pill);
  background: var(--c-accent);
  color: var(--c-white);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  text-decoration: none;
}
.mp-svcd-hero__cta:hover { background: color-mix(in srgb, var(--c-accent) 86%, #000); }

.mp-svcd-hero__media { margin: 0; }
.mp-svcd-hero__media img {
  display: block;
  inline-size: 100%;
  block-size: auto;                 /* the width/height attributes would pin it */
  aspect-ratio: 580 / 476;
  object-fit: cover;
}

/* ─ Band 2: heading + blue photo-card carousel ─────────────────────────── */
.mp-svcd-features__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 40px;
}
.mp-svcd-features__body {
  margin-block-start: 12px;
  max-inline-size: 900px;
  font-size: var(--fs-body);
  font-weight: var(--fw-book);
  line-height: var(--lh-body);
  color: var(--c-ink);
}
.mp-svcd-features__body strong { font-weight: var(--fw-bold); }

.mp-svcd-features__rail { margin-block-start: var(--space-heading); }
/* 76px between panels: cards sit at x 60 / 516.5 / 973 on the 1443 row
   (3511:2480) with a 380-wide panel, so 516.5 - 60 - 380 = 76.5. */
.mp-svcd-features__track { gap: 76px; }

/* The card is a blue panel with the photo overlapping its top-left, which is
   why the photo is pulled UP and the panel carries the padding rather than
   the two sitting in a column. */
.mp-svcd-feature {
  flex: 0 0 auto;
  /* 380, the drawn panel width (3511:2482) — not the 308 first guessed. At
     308 three cards totalled 1048 in a 1320 container, so the rail never
     overflowed and the arrows had nothing to scroll. At 380 a 3-card page
     fills the row exactly (380*3 + 76*2 = 1292) and only the 4-card
     veterinary page scrolls, which is what the design does too. */
  inline-size: 380px;
  display: flex;
  flex-direction: column;
  padding: 0 20px 24px;
  border-radius: 24px;
  background: var(--c-accent);
  color: var(--c-white);
}
/* The photo overhangs the panel: 36px above it and 30px past its right edge
   (photo 377 at x=33 in a 410 frame; panel 380 at y=36). */
.mp-svcd-feature__media { margin: -36px -30px 18px 0; }
.mp-svcd-feature__media img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: 377 / 249;
  object-fit: cover;
  border-start-start-radius: 24px;
  border-end-end-radius: 56px;
}
.mp-svcd-feature__title {
  margin: 0 0 8px;
  font-size: var(--fs-nav);
  font-weight: var(--fw-bold);
  line-height: var(--lh-heading);
}
.mp-svcd-feature__body {
  margin: 0;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-book);
  line-height: 1.35;
}

/* ─ App band (service-detail variant) ────────────────────────────────────
   Figma 3531:74492 (panel) + 3531:74582 (phone + button), measured:
     panel  710 x 990 at x=0, full-bleed LEFT, radius on the RIGHT only
     words  ~166px, three lines on a 226px pitch, overflowing the panel
     phone  368 wide at x=889, screen 337 x 744
     button 490 wide, 66 tall, below the phone
   The panel is 710 of a 1440 frame, so it is expressed as a fraction of the
   container rather than a fixed px width — at 1640 it stays the same half of
   the composition instead of leaving a hole beside the phone. */
.mp-svcd-app { overflow: clip; padding-block: var(--space-section); }

.mp-svcd-app__inner {
  display: grid;
  grid-template-columns: minmax(0, 710fr) minmax(0, 490fr);
  align-items: center;
  gap: 118px;                             /* 1318 - 710 - 490 */
}

.mp-svcd-app__panel {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 33px;                              /* 226 pitch - 193 line box */
  /* inline-size MUST be stated: with aspect-ratio + max-block-size alone the
     browser honours the cap by SHRINKING THE WIDTH, which is exactly the bug
     Task 7g/6h hit on the tile grid. */
  inline-size: 100%;
  aspect-ratio: 710 / 990;
  max-block-size: 990px;
  padding-inline-start: var(--gutter);
  /* Bleed to the VIEWPORT's left edge, not the container's. `100%` inside a
     grid item is the ITEM's width, so the obvious (100vw - 100%)/2 measures
     the wrong box and overshot by 300px. This walks out the container's own
     centring instead: gutter + however much auto-margin the container has. */
  margin-inline-start: calc(-1 * (var(--gutter)
      + max(0px, (100vw - (var(--container) + var(--gutter) * 2)) / 2)));
  padding-inline-end: 0;
  border-start-end-radius: var(--radius-lg);
  border-end-end-radius: var(--radius-lg);
  background: var(--grad-heading-alt);
  color: var(--c-white);
  overflow: hidden;                       /* the marquee runs past both ends */
}

/* One row per word. Two tracks: the real one and an aria-hidden duplicate,
   translated by exactly one content period so the loop has no seam — the same
   contract as the homepage phone marquee. Rows run at different speeds so the
   three lines never lock into a moving block. */
.mp-svcd-app__row {
  display: flex;
  flex: 0 0 auto;
  min-inline-size: 0;
  --speed: calc(26s + var(--row) * 5s);
}
.mp-svcd-app__track {
  display: flex;
  flex: 0 0 auto;
  /* A full em between repeats. At 0.4em the copies read as one garbled word
     ("FINDFIN") instead of a ticker; the eye needs a clear space to parse each
     repeat as a separate item. */
  gap: 1em;
  padding-inline-end: 1em;
  animation: mp-svcd-marquee var(--speed) linear infinite;
}

@keyframes mp-svcd-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-100%); }
}
[dir="rtl"] .mp-svcd-app__track { animation-direction: reverse; }

/* Reduced motion keeps the words — they ARE the band — and drops only the
   movement. `animation: none` rather than the universal 0.01ms override,
   which would freeze a track mid-travel (the trap Task 8 documented). */
@media (prefers-reduced-motion: reduce) {
  .mp-svcd-app__track { animation: none; }
}

.mp-svcd-app__word {
  font-size: clamp(64px, 11.5vw, 166px);  /* 166 at the drawn 1440 */
  font-weight: var(--fw-bold);
  line-height: 1.16;                      /* 193 box on a 166 face */
  letter-spacing: -0.01em;
  white-space: nowrap;
  color: transparent;
  -webkit-text-stroke: 1.5px rgba(255, 255, 255, 0.9);
}
.mp-svcd-app__row--solid .mp-svcd-app__word {
  color: var(--c-white);
  -webkit-text-stroke: 0;
}

.mp-svcd-app__aside { display: flex; flex-direction: column; align-items: center; gap: 41px; }

/* Bezel drawn in CSS, not shipped as a mockup PNG — same decision as the
   homepage phone marquee, and it keeps the corners genuinely transparent
   instead of antialiasing white against whatever is behind. */
.mp-svcd-app__phone {
  position: relative;
  inline-size: 100%;
  max-inline-size: 368px;
  aspect-ratio: 368 / 780;
  padding: 8px;
  border-radius: 44px;
  background: #16181d;
  box-shadow: 0 18px 44px rgba(26, 58, 127, 0.22);
  overflow: hidden;                       /* clips the screen to the bezel */
}
.mp-svcd-app__screen {
  position: absolute;
  inset: 8px;
  inline-size: calc(100% - 16px);
  block-size: calc(100% - 16px);
  /* contain, NOT cover: the recording is 416x848 (0.491) against a 0.461
     screen slot, so cover shaved ~11px off each side and took the "B" off the
     Bookmypet logo with it. The leftover band top and bottom is painted the
     bezel's own colour, so it reads as bezel rather than as letterboxing. */
  object-fit: contain;
  background: #16181d;
  border-radius: 36px;
}
.mp-svcd-app__video { opacity: 0; transition: opacity 320ms ease; }
.mp-svcd-app__video.is-playing { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .mp-svcd-app__video { display: none; }
}

/* Desktop has no store block — it is drawn on the mobile frame only. */
.mp-svcd-app__store { display: none; }

.mp-svcd-app__cta {
  inline-size: 100%;
  max-inline-size: 490px;
  min-block-size: 66px;      /* 3531:74628 — taller than the shared pill's 44 */
  justify-content: center;
}

/* ─ FAQ accordion ──────────────────────────────────────────────────────── */
.mp-svcd-faqs__title { margin-block-end: var(--space-heading); }
.mp-svcd-faq { border-block-end: 1px solid rgba(26, 58, 127, 0.12); }
.mp-svcd-faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding-block: 20px;
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-nav);
  font-weight: var(--fw-bold);
  color: var(--c-primary);
}
.mp-svcd-faq__q::-webkit-details-marker { display: none; }
.mp-svcd-faq__sign { flex: 0 0 auto; color: var(--c-primary); }
/* One glyph, two states: the vertical bar is hidden while open, turning the
   plus into a minus, so the icon can never disagree with the real state. */
.mp-svcd-faq[open] .mp-svcd-faq__bar-v { display: none; }
.mp-svcd-faq__a {
  margin: 0;
  padding-block-end: 22px;
  max-inline-size: 1100px;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-book);
  line-height: var(--lh-body);
  color: var(--c-ink);
}

@media (max-width: 1100px) {
  .mp-svcd-hero__inner { grid-template-columns: minmax(0, 1fr); row-gap: 40px; }
  .mp-svcd-features__head { flex-direction: column; }
  /* Stacked: the panel becomes a full-bleed banner above the phone, so it
     drops the aspect-ratio (which would make it enormously tall at one
     column) and sizes to its own three rows instead. */
  .mp-svcd-app__inner { grid-template-columns: minmax(0, 1fr); gap: 48px; }
  .mp-svcd-app__panel {
    aspect-ratio: auto;
    max-block-size: none;
    padding-block: 56px;
    margin-inline-end: calc(-1 * max(var(--gutter), (100vw - 100%) / 2));
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }
}

/* ─ Mobile (3555:120427) ───────────────────────────────────────────────────
   The photo comes FIRST and the copy is centred. Reordered with grid rows
   rather than a second DOM, so the H1 still precedes the image in the
   document for a screen reader. Chips and lede are omitted here, as drawn. */
@media (max-width: 768px) {
  .mp-svcd-hero { padding-block: calc(108px + 32px) var(--space-section); }
  .mp-svcd-hero__inner { grid-template-columns: minmax(0, 1fr); row-gap: 28px; }
  .mp-svcd-hero__copy { order: 2; align-items: center; text-align: center; }
  .mp-svcd-hero__media { order: 1; }
  .mp-svcd-hero__media img { aspect-ratio: 345 / 300; }
  /* 28px and BOLD THROUGHOUT. The mobile frame does not carry desktop's
     Bold-then-Light split — both lines are drawn at one weight — and at
     --fs-h1's 24px the headline sat visibly smaller than the drawn one. */
  .mp-svcd-hero__title { font-size: 28px; font-weight: var(--fw-bold); }
  .mp-svcd-hero__title strong { font-weight: var(--fw-bold); }
  .mp-svcd-hero__chips, .mp-svcd-hero__lede { display: none; }
  .mp-svcd-hero__cta { inline-size: 100%; margin-block-start: 20px; }

  .mp-svcd-features__body { font-size: var(--fs-body-sm); }
  .mp-svcd-feature { inline-size: 250px; border-radius: 20px; }
  .mp-svcd-features__track { gap: var(--gap-grid); }
  /* No arrows on mobile — the mobile frame positions them off-canvas, same as
     every other rail on this site (providers, insights, categories). The peek
     plus swipe is the affordance. */
  .mp-svcd-features .mp-carousel__controls { display: none; }

  .mp-svcd-app__panel { padding-block: 36px; gap: 10px; border-radius: 32px 32px 0 0; }
  .mp-svcd-app__word { font-size: clamp(38px, 15vw, 64px); }
  .mp-svcd-app__aside { gap: 24px; }
  /* ~150 on the mobile frame, not the 240 first guessed. */
  .mp-svcd-app__phone { max-inline-size: 150px; border-radius: 22px; padding: 5px; }
  .mp-svcd-app__store { display: block; text-align: center; }
  .mp-svcd-app__available {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    margin: 0;
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-book);
    color: var(--c-ink);
  }
  .mp-svcd-app__glyphs { display: inline-flex; gap: 10px; }
  .mp-svcd-app__glyphs img { inline-size: 22px; block-size: 22px; }
  .mp-svcd-app__store-title {
    margin: 6px 0 0;
    font-size: var(--fs-card);
    font-weight: var(--fw-bold);
    color: var(--c-primary);
  }
  .mp-svcd-app__screen { inset: 5px; inline-size: calc(100% - 10px); block-size: calc(100% - 10px); border-radius: 18px; }

  .mp-svcd-faq__q { font-size: var(--fs-body-sm); padding-block: 14px; gap: 14px; }
  .mp-svcd-faq__a { font-size: var(--fs-label); padding-block-end: 14px; }
}

/* Feature-card tag pills. Only the Veterinary page draws them (3513:2967);
   the card is otherwise identical, which is why they are an optional row
   here rather than a second card component to keep in sync. */
.mp-svcd-feature__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-block-start: 14px;
}
.mp-svcd-feature__tag {
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.22);
  font-size: var(--fs-micro);
  font-weight: var(--fw-medium);
  line-height: 1.4;
}

/* =========================================================================
   BECOME A PARTNER — /partner
   Figma 2557:170315 (1440x6779) desktop · 2571:2185 (393x5187) mobile

   Static page: all copy lives in the lang files (see PartnerController for
   why this one does NOT get a table the way /services does).
   ========================================================================= */

/* ─ Hero ────────────────────────────────────────────────────────────────
   Same gradient-and-grain background as the services hero — it is the same
   Figma layer, so it reuses --hero-img rather than shipping a second copy
   of identical pixels. The nav sits transparent over it. */
.mp-ptn-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background-color: var(--c-primary);
  background-image: var(--hero-img);
  background-size: cover;
  background-position: center;
  color: var(--c-white);
  border-end-start-radius: var(--radius-lg);
  border-end-end-radius: var(--radius-lg);
  /* Measured off the 1:1 render: H1 top y=335, hero base y=984. Auto-detecting
     the base by "blue at the centre column" kept catching the pale tints
     further down the page, so these came from a crop read by eye. */
  padding-block: calc(108px + 145px) 178px;
}
.mp-ptn-hero__inner {
  display: grid;
  /* 640:620, not 520:740. At the narrower split the H1 wrapped to FOUR lines
     ("The Operating / System for / Modern Pet / Businesses") where the design
     has three — its copy block measures ~609 wide against the 440 the old
     ratio allowed. */
  grid-template-columns: minmax(0, 640fr) minmax(0, 620fr);
  align-items: center;
  gap: 60px;
}
.mp-ptn-hero__title {
  margin: 0;
  font-size: var(--fs-h1);
  font-weight: var(--fw-bold);
  line-height: 1.16;
}
.mp-ptn-hero__lede {
  margin-block: 20px 0;
  max-inline-size: 460px;
  font-size: var(--fs-lead);
  font-weight: var(--fw-book);
  line-height: var(--lh-body);
}
.mp-ptn-hero__actions { display: flex; flex-wrap: wrap; gap: 16px; margin-block-start: 32px; }

.mp-ptn-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 52px;
  padding-inline: 32px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-bold);
  text-decoration: none;
}
.mp-ptn-btn--light  { background: var(--c-white);  color: var(--c-primary); }
.mp-ptn-btn--accent { background: var(--c-accent); color: var(--c-white); }
.mp-ptn-btn--light:hover  { background: color-mix(in srgb, var(--c-white) 88%, var(--c-accent)); }
.mp-ptn-btn--accent:hover { background: color-mix(in srgb, var(--c-accent) 86%, #000); }

.mp-ptn-hero__media { margin: 0; }
.mp-ptn-hero__media img { display: block; inline-size: 100%; block-size: auto; }

/* ─ Stats ───────────────────────────────────────────────────────────────── */
.mp-ptn-stats { padding-block-start: 136px; }   /* heading lands at the drawn y=1120 */
/* 72px, not the shared --fs-h2's 48. Measured: the drawn heading's first line
   is ~980 wide against the 648 that 48px produced — the largest type on the
   page after the hero. */
.mp-ptn-stats__title {
  text-align: center;
  margin-inline: auto;
  max-inline-size: 1100px;
  font-size: clamp(40px, 5vw, 72px);
}
.mp-ptn-stats__list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 48px;
  margin-block-start: 56px;
}
.mp-ptn-stat__value {
  margin: 0 0 6px;
  font-size: var(--fs-h2);
  font-weight: var(--fw-bold);
  color: var(--c-primary);
  line-height: 1.1;
}
.mp-ptn-stat__title {
  margin: 0 0 8px;
  font-size: var(--fs-card);        /* 24, not 20 — measured against the drawn label */
  font-weight: var(--fw-bold);
  color: var(--c-primary);
}
.mp-ptn-stat__body {
  margin: 0;
  font-size: var(--fs-body);        /* 18, not 16 */
  font-weight: var(--fw-book);
  color: var(--c-ink);
  line-height: var(--lh-body);
}

/* ─ AI banner ───────────────────────────────────────────────────────────
   Full-bleed split. The artwork carries the big rounded corner on its inner
   edge; the accent panel runs to the viewport edge. */
.mp-ptn-banner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: stretch;
  margin-block: var(--space-section);
}
.mp-ptn-banner__media { margin: 0; position: relative; overflow: hidden; }
.mp-ptn-banner__media img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* Inner TOP corner (2557:170315) — not the bottom one. */
  border-start-end-radius: var(--radius-lg);
}
.mp-ptn-banner__text {
  margin: 0;
  display: flex;
  align-items: center;
  padding: 72px clamp(32px, 6vw, 96px);
  background: var(--c-accent);
  color: var(--c-white);
  font-size: var(--fs-h2);
  font-weight: var(--fw-bold);
  line-height: 1.25;
}

/* ─ Feature rows ────────────────────────────────────────────────────────
   Alternation is :nth-child(even), not a per-row flag, so adding or removing
   a row can never leave two images stacked on the same side. */
.mp-ptn-features { padding-block: var(--space-section); }
.mp-ptn-feature {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: 72px;
  padding-block: 56px;
}
/* ODD rows put the image first. The DOM order is copy-then-media (so the
   heading precedes its screenshot for a screen reader), and the design opens
   with the image on the start side — so it is rows 1 and 3 that swap, not
   2 and 4. Shipped inverted first time. */
.mp-ptn-feature:nth-child(odd) .mp-ptn-feature__copy  { order: 1; }
.mp-ptn-feature:nth-child(odd) .mp-ptn-feature__media { order: 0; }

.mp-ptn-feature__title {
  margin: 0 0 16px;
  font-size: var(--fs-h2);
  font-weight: var(--fw-bold);
  color: var(--c-primary);
  line-height: var(--lh-heading);
}
.mp-ptn-feature__body {
  margin: 0 0 24px;
  font-size: var(--fs-body);
  font-weight: var(--fw-book);
  color: var(--c-ink);
  line-height: var(--lh-body);
}
.mp-ptn-feature__media { margin: 0; }
.mp-ptn-feature__media img { display: block; inline-size: 100%; block-size: auto; }

.mp-ptn-points { display: flex; flex-direction: column; gap: 12px; }
.mp-ptn-point {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-book);
  color: var(--c-ink);
  line-height: var(--lh-body);
}
.mp-ptn-point__tick { flex: 0 0 auto; margin-block-start: 2px; color: var(--c-accent); }

/* ─ AI tools band ───────────────────────────────────────────────────────── */
.mp-ptn-ai {
  display: grid;
  grid-template-columns: minmax(0, 480fr) minmax(0, 960fr);
  align-items: center;
  gap: 0;
  margin-block: var(--space-section);
  background: var(--c-surface-tint);
}
.mp-ptn-ai__media { margin: 0; align-self: stretch; overflow: hidden; }
.mp-ptn-ai__media img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  border-start-end-radius: var(--radius-lg);   /* inner TOP corner */
}
.mp-ptn-ai__panel { padding: 72px clamp(32px, 5vw, 72px); min-inline-size: 0; }
.mp-ptn-ai__title {
  margin: 0 0 32px;
  font-size: var(--fs-h2);
  font-weight: var(--fw-bold);
  color: var(--c-primary);
  line-height: var(--lh-heading);
}
.mp-ptn-ai__rail { --carousel-edge: 0px; }
.mp-ptn-ai__track {
  gap: 24px;
}

.mp-ptn-tool {
  inline-size: 280px;
  display: flex;
  flex-direction: column;
  padding: 28px 24px;
  border-radius: var(--radius-md);
  background: var(--c-white);
  box-shadow: 0 6px 24px rgba(26, 58, 127, 0.10);
}
.mp-ptn-tool__title {
  margin: 0 0 10px;
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  color: var(--c-primary);
  line-height: var(--lh-heading);
}
.mp-ptn-tool__body {
  margin: 0 0 20px;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-book);
  color: var(--c-ink);
  line-height: 1.35;
}
.mp-ptn-tool__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-block-start: auto;
  min-block-size: 38px;
  padding-inline: 18px;
  border-radius: var(--radius-pill);
  background: var(--c-accent);
  color: var(--c-white);
  font-size: var(--fs-label);
  font-weight: var(--fw-bold);
  text-decoration: none;
}
.mp-ptn-ai__controls { margin-block-start: 28px; }

/* ─ Pricing ─────────────────────────────────────────────────────────────── */
.mp-ptn-price__inner { text-align: center; }
.mp-ptn-price__title {
  margin: 0;
  margin-inline: auto;
  max-inline-size: 900px;
  font-size: var(--fs-h1);
  font-weight: var(--fw-bold);
  color: var(--c-primary);
  line-height: 1.2;
}
.mp-ptn-price__title em { font-style: normal; color: var(--c-accent); }
.mp-ptn-price__body {
  margin: 20px auto 0;
  max-inline-size: 820px;
  font-size: var(--fs-body);
  font-weight: var(--fw-book);
  color: var(--c-primary);
  line-height: var(--lh-body);
}
.mp-ptn-price__value {
  margin: 40px 0 0;
  font-size: clamp(64px, 7vw, 110px);
  font-weight: var(--fw-bold);
  color: var(--c-accent);
  line-height: 1;
}
.mp-ptn-price__note {
  margin: 12px 0 0;
  font-size: var(--fs-lead);
  font-weight: var(--fw-book);
  color: var(--c-primary);
}
.mp-ptn-price__cta {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 100%;
  max-inline-size: 750px;
  margin: 40px auto 0;
  min-block-size: 56px;
  padding-inline: 32px;
  border-radius: var(--radius-pill);
  /* Blue-to-navy, left to right — the design's own direction. */
  background: linear-gradient(90deg, #2ca5e8 0%, #1a3a7f 100%);
  color: var(--c-white);
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  text-decoration: none;
}

@media (max-width: 1100px) {
  .mp-ptn-hero__inner { grid-template-columns: minmax(0, 1fr); row-gap: 40px; }
  .mp-ptn-stats__list { grid-template-columns: minmax(0, 1fr); gap: 32px; }
  .mp-ptn-banner { grid-template-columns: minmax(0, 1fr); }
  .mp-ptn-banner__text { padding: 48px var(--gutter); font-size: var(--fs-h3); }
  .mp-ptn-feature { grid-template-columns: minmax(0, 1fr); gap: 32px; padding-block: 40px; }
  /* Stacked: the image goes ABOVE the copy on every row, so the alternation
     must be switched off or half the rows would put it below. */
  .mp-ptn-feature .mp-ptn-feature__copy  { order: 1; }
  .mp-ptn-feature .mp-ptn-feature__media { order: 0; }
  .mp-ptn-ai { grid-template-columns: minmax(0, 1fr); }
  .mp-ptn-ai__panel { padding: 48px var(--gutter); }
}

@media (max-width: 768px) {
  .mp-ptn-hero { padding-block: calc(108px + 48px) 72px; }
  .mp-ptn-hero__lede { font-size: var(--fs-body-sm); }
  .mp-ptn-hero__actions { inline-size: 100%; }
  .mp-ptn-btn { flex: 1 1 auto; padding-inline: 20px; min-block-size: 44px; }
  .mp-ptn-banner__media img { border-start-end-radius: 0; }
  .mp-ptn-ai__media img { border-start-end-radius: 0; }
  .mp-ptn-tool { inline-size: 240px; }
  .mp-ptn-price__value { margin-block-start: 28px; }
  .mp-ptn-price__cta { min-block-size: 48px; }
}

/* =========================================================================
   ADMIN-AUTHORED CONTENT PAGES — legal documents + blog (Task 6)

   No Figma reference exists for these: they come from a CMS spec, not a
   design file, so there are no node ids to cite. Every colour/radius/type
   token below is one already declared at :root, or -- where flagged -- a
   literal already used elsewhere in this file for the exact same purpose.
   Container, gutter and vertical rhythm are the SAME ones .mp-container /
   .mp-svc-band / .mp-faq already use: --container, --gutter,
   --space-section, --space-section-sm, --space-heading, --gap-grid,
   --radius-pill. No new width or spacing token is introduced. The blog
   grid's 3/2/1-column breakpoints reuse 1100px and 768px -- the exact pair
   .mp-svc-grid__list already reflows on -- rather than inventing new ones.

   Logical properties throughout; [dir="rtl"] already swaps --font-display
   for --font-arabic globally (see line 222), so nothing here needs an RTL
   override block.
   ========================================================================= */

/* ─ Prose: the ONE place admin-authored HTML renders (legal document
   bodies + blog article bodies), both normalised on write by
   App\Support\RichHtml -- one stylesheet block for both, because there is
   no second schema to drift out of sync with this one. h1 is absent on
   purpose: the schema demotes it, because the page template owns the
   document's single <h1>.

   Token substitution for the brief's placeholder names: --c-brand is
   --c-primary, the one brand/link colour every other interactive/heading
   element on this site already uses (.mp-h2, .mp-svc-card__title, every
   CTA). --c-line is --c-surface-tint -- the site's one existing divider
   token, already used as a border exactly this way by
   .mp-faq__item + .mp-faq__item. --c-ink / --c-ink-muted are already real
   custom properties, used verbatim. Font sizes / line-heights / spacing
   multipliers below are the brief's own considered values, not
   placeholders, so they are kept as given. */

/* ── Defensive rules for LEGACY admin HTML ────────────────────────────────
   Bodies written before App\Support\RichHtml existed were never normalised,
   so they still carry markup the sanitiser would now remove. One real post
   ("your-dog-wants-a-coffee-date-too") ships 11 <div>s and 30 AI-paste
   Tailwind classes, and one of those divs measured 6975px tall while
   containing no text and no media — 82% of the article's prose height as
   dead space. Re-saving through the sanitiser removes it (verified: 11 divs
   and 30 classes out, ZERO visible-text change), but that rewrites rows the
   mobile app reads, so it is a data decision, not a styling one.

   These rules neutralise the layout damage without touching stored content,
   and become harmless no-ops once a backfill runs.

   display:contents makes a stray wrapper vanish from layout while its
   children stay in flow exactly where they should be — the correct tool for
   "unwrap this box" without deleting anything. */
.mp-prose div { display: contents; }

/* Classes from an AI/word-processor paste (font-*, break-words, whitespace-*)
   match nothing in this stylesheet, but any height/margin they carried in
   their origin is gone — so re-assert the prose rhythm on every block. */
.mp-prose > div > p,
.mp-prose > div > ul,
.mp-prose > div > ol { margin-block: 0; }

.mp-prose { color: var(--c-ink); font-size: 16px; line-height: 1.75; }

.mp-prose > * + * { margin-block-start: 1.1em; }

.mp-prose h2 { font-size: 28px; line-height: 1.3; margin-block-start: 2em; font-weight: var(--fw-bold); color: var(--c-primary); }
.mp-prose h3 { font-size: 22px; line-height: 1.35; margin-block-start: 1.6em; font-weight: var(--fw-bold); color: var(--c-primary); }
.mp-prose h4 { font-size: 18px; line-height: 1.4; margin-block-start: 1.4em; font-weight: var(--fw-medium); }

.mp-prose ul, .mp-prose ol { padding-inline-start: 1.4em; }
.mp-prose li + li { margin-block-start: 0.4em; }
.mp-prose ul { list-style: disc; }
.mp-prose ol { list-style: decimal; }

/* The site reset is only `a { color: inherit }` -- every anchor needs its
   own decoration stated, or it renders as plain undecorated text. Missing
   this has regressed four times already; see the file's own header. */
/* Underline removed at the product owner's request. Colour alone is not a
   sufficient affordance (WCAG 1.4.1 Use of Color), so body-copy links keep a
   heavier weight as the second, non-colour cue; the hover underline below
   remains as the interaction affordance. */
.mp-prose a { color: var(--c-primary); text-decoration: none; font-weight: 600; }
.mp-prose a:hover { text-decoration: none; }

.mp-prose blockquote {
  border-inline-start: 3px solid var(--c-primary);
  padding-inline-start: 16px;
  color: var(--c-ink-muted);
}

.mp-prose img { max-inline-size: 100%; block-size: auto; border-radius: var(--radius-md); }

/* Wide tables scroll inside their own container; the page body must never
   scroll horizontally (a standing rule on this stylesheet). */
.mp-prose table { inline-size: 100%; border-collapse: collapse; display: block; overflow-x: auto; }
.mp-prose th, .mp-prose td { border: 1px solid var(--c-surface-tint); padding: 8px 12px; text-align: start; }

.mp-prose hr { border: 0; border-block-start: 1px solid var(--c-surface-tint); margin-block: 2em; }

@media (max-width: 767px) {
  .mp-prose { font-size: 15px; }
  .mp-prose h2 { font-size: 24px; }
  .mp-prose h3 { font-size: 20px; }
}

/* ─ Legal documents — /privacy-policy, /terms, etc. Head mirrors the FAQ
   page's plain-text-header treatment (same 108px-fixed-nav clearance
   convention as .mp-svcd-hero / .mp-faq); body just needs the site's
   normal container width and closing rhythm. ─────────────────────────── */
.mp-legal__inner,
.mp-blog__inner,
.mp-article__inner {
  max-width: calc(var(--container) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.mp-legal__head { padding-block: calc(108px + 64px) var(--space-section-sm); }
.mp-legal__title { font-size: var(--fs-h1); color: var(--c-primary); }

.mp-legal { padding-block: 0 var(--space-section); }

@media (max-width: 768px) {
  .mp-legal__head { padding-block: calc(108px + 24px) 40px; }
}

/* ─ Blog index — /blog, /blog/category/<slug>, /blog/tag/<slug>.

   Redesign pass. What was here before laid every post out as the same square
   homepage-carousel card in a fixed 3-column grid: identical visual weight on
   all ten items, so the page opened with no first fixation point, and the one
   featured post occupied column 1 of 3 with two empty cells beside it. The
   page now reads as an editorial index — one full-width lead, then a
   breakpoint-free auto-fit grid of cards.

   Every colour, size and space below is an existing :root token. The card
   grid restyles .mp-insight ONLY through descendant selectors under
   .mp-blog__grid, so the homepage carousel (.mp-carousel__track > .mp-insight)
   inherits nothing from this section — verified by diffing the rendered
   homepage HTML before and after, which changes only in the ?v= asset stamp.

   Logical properties throughout; [dir="rtl"] already swaps --font-display for
   --font-arabic globally, so /ar/blog needs no override block. ─────────── */
.mp-blog__head { padding-block: calc(108px + 64px) var(--space-section-sm); text-align: center; }
.mp-blog__title { font-size: var(--fs-h1); color: var(--c-primary); }
.mp-blog__lede {
  margin-block-start: 16px;
  margin-inline: auto;
  max-inline-size: 900px;               /* same measure .mp-faq__lede already uses */
  font-size: var(--fs-lead);
  font-weight: var(--fw-book);
  color: var(--c-ink-muted);            /* 7.91:1 on --c-bg */
  line-height: var(--lh-heading);
}

/* Only the content wrapper (filters + lead + grid + pagination), not the copy
   returned by .mp-blog__head using the same class inside its own header. */
.mp-blog__head + .mp-blog__inner { padding-block: 0 var(--space-section); }

/* ── Category filters ──────────────────────────────────────────────────────
   Real pills, in the language .mp-chip established on the homepage
   (surface-tint ground, --c-primary label, fully round) — echoed rather than
   re-invented, at the smaller label size a filter row wants.

   The selected state was solid --c-accent with white text: 2.74:1, a failure
   at 16px, and the file already documents that exact measurement for --c-accent
   next to .mp-h2. Selected is now solid --c-primary / white — 10.74:1 — which
   is also the darkest, heaviest pill in the row, so "which one is on" is
   carried by weight as well as by hue. aria-current carries it for AT. */
.mp-blog__filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-block-end: var(--space-heading);
}
.mp-blog__filter {
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;                 /* touch-target floor */
  padding-inline: 18px;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: var(--c-surface-tint);
  color: var(--c-primary);              /* 10.10:1 on --c-surface-tint */
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  text-decoration: none;                /* explicit: the reset is only `a { color: inherit }` */
  white-space: nowrap;
  transition: background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.mp-blog__filter:hover {
  background: color-mix(in srgb, var(--c-accent) 18%, var(--c-white));
  border-color: color-mix(in srgb, var(--c-primary) 22%, transparent);
  text-decoration: none;
}
.mp-blog__filter.is-active {
  background: var(--c-primary);
  border-color: var(--c-primary);
  color: var(--c-white);                /* 10.74:1 */
}
.mp-blog__filter.is-active:hover { background: var(--c-primary); }
/* The global ring is --c-accent (2.65:1 on this background — the file measures
   it next to .mp-h2). Recoloured to --c-primary here, exactly as the nav
   already does for its own light surface. */
.mp-blog__filter:focus-visible { outline-color: var(--c-primary); }

@media (max-width: 768px) {
  /* One scrollable row instead of four wrapped ones. The negative inline
     margin lets it run edge to edge while its OWN overflow-x holds the
     scroll — the page body never widens (and <body> is overflow-x: clip
     anyway, which would silently hide the overflow rather than scroll it). */
  .mp-blog__filters {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    padding-block-end: 4px;             /* room for the focus ring's outline-offset */
  }
  .mp-blog__filters::-webkit-scrollbar { display: none; }
  .mp-blog__filter { scroll-snap-align: start; flex: 0 0 auto; }
}

/* ── Editorial lead ────────────────────────────────────────────────────────
   The featured post is not "a bigger card": it is a different object. Image
   beside copy at the full container width, headline at --fs-h2 (48px, twice
   the grid card's --fs-card), one hairline closing the band. Below 768px —
   the same breakpoint the rest of this file reflows on — it stacks to image
   over copy. Rendered on page 1 only (see the view). */
.mp-lead {
  position: relative;                   /* bounds .mp-lead__link::after */
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  align-items: center;
  column-gap: var(--gap-grid);
  row-gap: 24px;
  margin-block-end: var(--space-section-sm);
  padding-block-end: var(--space-section-sm);
  border-block-end: 1px solid var(--c-surface-tint);   /* same hairline .mp-faq__item uses */
}
@media (max-width: 768px) { .mp-lead { grid-template-columns: minmax(0, 1fr); } }

/* Two layers, one request — identical treatment to .mp-insight__media and for
   the identical reason: these banners are wide artwork with baked-in headlines
   (live sources measure 1200x600), so a crop cuts their own text in half.
   Both layers are absolutely positioned with an explicit block-size, so the
   <img width/height> attributes cannot beat the box and double its height. */
.mp-lead__media {
  position: relative;
  inline-size: 100%;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: 20px;
  background: var(--c-surface-tint);
}
.mp-lead__media-bg,
.mp-lead__media-img {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
}
.mp-lead__media-bg { object-fit: cover; filter: blur(24px) saturate(1.15); transform: scale(1.08); }
.mp-lead__media-img { object-fit: contain; transition: transform var(--dur) var(--ease); }
.mp-lead:hover .mp-lead__media-img { transform: scale(1.02); }

.mp-lead__body { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; }

.mp-lead__cat {
  display: inline-flex;
  align-items: center;
  min-block-size: 30px;
  padding-inline: 14px;
  border-radius: var(--radius-pill);
  background: var(--c-surface-tint);
  color: var(--c-primary);              /* 10.10:1 */
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
}

.mp-lead__title { font-size: var(--fs-h2); color: var(--c-primary); }
.mp-lead__link { color: inherit; text-decoration: none; }
.mp-lead__link:hover { text-decoration: underline; }
.mp-lead__link:focus-visible { outline-color: var(--c-primary); }
.mp-lead__link::after { content: ""; position: absolute; inset: 0; z-index: 1; }

.mp-lead__excerpt {
  max-inline-size: 62ch;
  font-size: var(--fs-body);
  color: var(--c-ink-muted);            /* 7.91:1 on --c-bg */
  line-height: var(--lh-body);
}

/* Meta sits a full step below the headline in size and two in weight, so it
   reads as attribution rather than competing with the title. */
.mp-lead__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-label);
  color: var(--c-ink-muted);            /* 7.91:1 */
}
.mp-lead__author { font-weight: var(--fw-medium); color: var(--c-ink); }
/* Decorative separator (aria-hidden in the markup): lightened so it groups the
   three meta values without reading as a fourth one. */
.mp-lead__dot { color: color-mix(in srgb, var(--c-ink-muted) 60%, var(--c-bg)); }

.mp-lead__more {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-block-size: 44px;
  margin-block-start: 2px;
  padding-inline: 20px;
  border-radius: var(--radius-pill);
  background: var(--c-surface-tint);
  color: var(--c-primary);              /* 10.10:1 */
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  letter-spacing: 0.56px;
  pointer-events: none;                 /* the stretched title link owns the click */
  transition: background-color var(--dur) var(--ease);
}
.mp-lead:hover .mp-lead__more { background: color-mix(in srgb, var(--c-accent) 18%, var(--c-white)); }

/* ── Card grid ─────────────────────────────────────────────────────────────
   No hand-written column counts: auto-fit + minmax means the track count
   follows the container, which is also what keeps 1 leftover card on the last
   row from needing its own rule. */
.mp-blog__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--gap-grid);
  align-items: stretch;
}

/* Everything below is scoped INSIDE .mp-blog__grid. .mp-insight itself is the
   homepage carousel's card and is not touched — none of these selectors can
   match there. The card component's markup is likewise untouched; the
   author/meta merge below is done with grid areas, not with a new wrapper. */
.mp-blog__grid .mp-insight {
  --pad: 18px;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto 1fr auto;
  grid-template-areas:
    "media  media"
    "author meta"
    "body   body"
    "more   more";
  column-gap: 0;
  row-gap: 12px;
  padding-block: 0 var(--pad);
  padding-inline: var(--pad);
  background: var(--c-white);
  border: 1px solid color-mix(in srgb, var(--c-primary) 10%, transparent);
  border-radius: 20px;
  transition: transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.mp-blog__grid .mp-insight:hover {
  transform: translateY(-4px);
  border-color: color-mix(in srgb, var(--c-primary) 22%, transparent);
  box-shadow: 0 14px 30px color-mix(in srgb, var(--c-primary) 14%, transparent);
}
.mp-blog__grid .mp-insight:focus-within {
  border-color: var(--c-primary);
  box-shadow: 0 14px 30px color-mix(in srgb, var(--c-primary) 14%, transparent);
}

.mp-blog__grid .mp-insight__media  { grid-area: media; }
.mp-blog__grid .mp-insight__author { grid-area: author; }
.mp-blog__grid .mp-insight__meta   { grid-area: meta; }
.mp-blog__grid .mp-insight__body   { grid-area: body; }
.mp-blog__grid .mp-insight__more   { grid-area: more; }

/* Wider than the carousel's square (a 1/1 photo plus body copy made a
   ~700px-tall card, and nine of them a wall) and bled to the card's own
   edges: inline-size drops to auto so the stretched grid item can grow by the
   card's padding on both sides. Radius is stated on the two START corners
   logically, so RTL mirrors it and the card needs no overflow: hidden — which
   in turn is what keeps focus rings from being clipped. */
.mp-blog__grid .mp-insight__media {
  inline-size: auto;
  margin-inline: calc(var(--pad) * -1);
  aspect-ratio: 16 / 10;
  border-radius: 0;
  border-start-start-radius: 19px;      /* 20px card radius minus its 1px border */
  border-start-end-radius: 19px;
  box-shadow: none;                     /* one object, not a card inside a card */
}
.mp-blog__grid .mp-insight__media-img { transition: transform var(--dur) var(--ease); }
.mp-blog__grid .mp-insight:hover .mp-insight__media-img { transform: scale(1.03); }

/* White on --c-scrim composites to ~4.23:1 over a light banner — under the
   4.5:1 floor for this 14px label. Solid --c-primary instead: 10.74:1, and it
   reads as the brand rather than as a grey wash. */
.mp-blog__grid .mp-insight__tag {
  inset-block-start: 12px;
  inset-inline-start: 12px;
  background: var(--c-primary);
}

/* Byline and meta share one row: author is an auto column that collapses to
   zero when the row has no author_name (57 of 68 live rows), and column-gap is
   0, so the date starts flush at the inline start either way.

   white-space: nowrap on both, with the meta flex allowed to wrap, is what
   keeps a long byline tidy in a ~265px content box: each run ("BookMyPet
   team", "December 19, 2025", "1 Min Read") is its own flex item and moves to
   the next line WHOLE, instead of the date breaking mid-phrase. */
.mp-blog__grid .mp-insight__author {
  align-self: center;
  gap: 6px;
  margin-inline-end: 10px;
  font-size: var(--fs-micro);
  font-weight: var(--fw-medium);
  color: var(--c-ink);                  /* 15.58:1 on --c-white */
  white-space: nowrap;
}
.mp-blog__grid .mp-insight__author img { inline-size: 18px; block-size: 18px; }
.mp-blog__grid .mp-insight__meta {
  align-self: center;
  flex-wrap: wrap;
  gap: 4px 6px;
  font-size: var(--fs-micro);
  color: var(--c-ink-muted);            /* 8.19:1 on --c-white */
  white-space: nowrap;
}
.mp-blog__grid .mp-insight__meta span { color: color-mix(in srgb, var(--c-ink-muted) 45%, var(--c-white)); }

.mp-blog__grid .mp-insight__body { gap: 8px; }
/* The carousel truncates every title to one line to keep eight slides the same
   height. On a listing that hides the one thing a reader scans by, so titles
   wrap here; the 1fr body row absorbs the difference and keeps every Read More
   bar on one baseline across the row. */
.mp-blog__grid .mp-insight__title {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  /* One step down from the carousel's --fs-card: these cards are ~300px wide,
     not the carousel's 368px, and a wrapping 24px headline ran to five lines.
     20px at every width -- --fs-nav holds 20px down to 769px, and the mobile
     rule below picks up --fs-card, which is 20px on the mobile ramp. */
  font-size: var(--fs-nav);
  line-height: var(--lh-heading);
}
.mp-blog__grid .mp-insight__link:focus-visible { outline-color: var(--c-primary); }
.mp-blog__grid .mp-insight__excerpt {
  -webkit-line-clamp: 3;
  font-weight: var(--fw-book);          /* Gotham Light at 16px is too thin to be the body copy of a listing */
  color: var(--c-ink-muted);            /* 8.19:1 on --c-white */
  line-height: var(--lh-body);
}
.mp-blog__grid .mp-insight__more { align-self: end; }
.mp-blog__grid .mp-insight:hover .mp-insight__more {
  background: color-mix(in srgb, var(--c-accent) 18%, var(--c-white));
}

/* ── Empty state ───────────────────────────────────────────────────────────
   Six of the eleven categories have zero published posts, so this is a normal
   page state and gets a designed one: a tinted panel, the shipped article
   placeholder, and a way back to the full index instead of one grey line. */
.mp-blog__empty {
  display: grid;
  justify-items: center;
  gap: 18px;
  padding-block: 64px;
  padding-inline: 24px;
  border-radius: 24px;
  background: var(--c-surface-tint);
  text-align: center;
}
/* block-size: auto is REQUIRED -- the width/height attributes on the <img> are
   presentational hints that otherwise win over any ratio the box declares. */
.mp-blog__empty img { inline-size: 160px; block-size: auto; }
.mp-blog__empty-text {
  max-inline-size: 44ch;
  font-size: var(--fs-lead);
  font-weight: var(--fw-medium);
  color: var(--c-ink);                  /* 14.66:1 on --c-surface-tint */
  line-height: var(--lh-heading);
}
.mp-blog__empty-cta {
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  padding-inline: 24px;
  border-radius: var(--radius-pill);
  background: var(--c-primary);
  color: var(--c-white);                /* 10.74:1 */
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  text-decoration: none;                /* explicit — see the reset */
  transition: background-color var(--dur) var(--ease);
}
.mp-blog__empty-cta:hover { background: color-mix(in srgb, var(--c-primary) 88%, var(--c-accent)); }
.mp-blog__empty-cta:focus-visible { outline-color: var(--c-primary); }

.mp-blog__pagination { margin-block-start: var(--space-section-sm); }

@media (max-width: 768px) {
  .mp-blog__head { padding-block: calc(108px + 24px) 40px; }
  .mp-blog__empty { padding-block: 40px; }
  .mp-blog__empty img { inline-size: 112px; }
  /* --fs-nav drops to 16px on the mobile ramp, which is body size; the card is
     full-width here, so the title holds its 20px (--fs-card's mobile value). */
  .mp-blog__grid .mp-insight__title { font-size: var(--fs-card); }
}

/* Motion here is hover/focus only and every animated element is fully visible
   at rest — nothing on this page is revealed BY a transition, so a renderer
   that never fires one still paints the finished page. */
@media (prefers-reduced-motion: reduce) {
  .mp-blog__filter,
  .mp-lead__media-img,
  .mp-lead__more,
  .mp-blog__grid .mp-insight,
  .mp-blog__grid .mp-insight__media-img,
  .mp-blog__empty-cta { transition: none; }
  .mp-lead:hover .mp-lead__media-img,
  .mp-blog__grid .mp-insight:hover,
  .mp-blog__grid .mp-insight:hover .mp-insight__media-img { transform: none; }
}

/* ─ Blog card — now ONLY the "related posts" grid on the article page
   (blog-article.blade.php:99 is its single call site: the listing page's own
   grid and lead were redesigned above and render .mp-insight / .mp-lead).
   Left as-is rather than pruned. Mirrors .mp-svc-card's own mechanism: flex
   column, block-size: auto + aspect-ratio on the image so every row in the
   grid stays aligned regardless of each photo's native dimensions. There is
   no Figma reference for this new page to measure a ratio from; 3:2 is a
   conventional editorial-thumbnail ratio, not a guess at a design value
   that doesn't exist. */
.mp-blog-card { display: flex; flex-direction: column; }
.mp-blog-card__link { text-decoration: none; display: block; color: inherit; }
.mp-blog-card__img {
  inline-size: 100%;
  block-size: auto;                      /* REQUIRED -- see .mp-svc-card__media img */
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: 16px;
}
.mp-blog-card__body { display: flex; flex-direction: column; gap: 8px; padding-block-start: 16px; }
/* --c-accent fails WCAG at this size (see the .mp-h2 contrast-fix comment
   above, ~2.7:1 -- fine for large bold text, not for a 14px label): use
   --c-primary here instead, same swap that comment already documents. */
.mp-blog-card__cat { font-size: var(--fs-label); font-weight: var(--fw-medium); color: var(--c-primary); text-transform: uppercase; letter-spacing: 0.04em; }
.mp-blog-card__title { font-size: var(--fs-card); font-weight: var(--fw-bold); color: var(--c-primary); line-height: var(--lh-heading); }
.mp-blog-card__excerpt { font-size: var(--fs-body-sm); font-weight: var(--fw-book); color: var(--c-ink-muted); line-height: var(--lh-body); }
.mp-blog-card:hover .mp-blog-card__title { text-decoration: underline; }

/* ─ Paginator. Logical properties only — in RTL the row reverses on its own
   and "previous" lands on the right, correctly.

   Numbered now (the index runs to 8 pages), and styled as controls rather
   than as links: 44px touch targets, a hairline resting state on the page
   ground, one solid --c-primary chip for the current page. The old disabled
   style was opacity: 0.5, which took the label to ~4:1 and made "Previous" on
   page 1 the least legible text on the page; it is a real colour now. Below
   768px the numbers give way to the "Page x of y" status so nothing wraps. */
.mp-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.mp-pager__link,
.mp-pager__num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 44px;
  min-inline-size: 44px;
  padding-inline: 18px;
  border: 1px solid color-mix(in srgb, var(--c-primary) 14%, transparent);
  border-radius: var(--radius-pill);
  background: var(--c-white);
  color: var(--c-primary);              /* 10.74:1 on --c-white */
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  text-decoration: none;                /* explicit — the reset is only `a { color: inherit }` */
  transition: background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.mp-pager__num { padding-inline: 10px; }
.mp-pager__link:hover,
.mp-pager__num:hover {
  background: var(--c-surface-tint);
  border-color: color-mix(in srgb, var(--c-primary) 28%, transparent);
  text-decoration: none;
}
.mp-pager__link:focus-visible,
.mp-pager__num:focus-visible { outline-color: var(--c-primary); }

.mp-pager__link.is-disabled {
  pointer-events: none;
  background: var(--c-bg);
  border-color: color-mix(in srgb, var(--c-ink-muted) 16%, transparent);
  color: var(--c-ink-muted);            /* 7.91:1 on --c-bg — legible, still clearly inert */
}
.mp-pager__num.is-current {
  background: var(--c-primary);
  border-color: var(--c-primary);
  color: var(--c-white);                /* 10.74:1 */
}

.mp-pager__nums { display: flex; align-items: center; gap: 8px; }
.mp-pager__gap { color: var(--c-ink-muted); padding-inline: 2px; }
.mp-pager__status { display: none; font-size: var(--fs-body-sm); color: var(--c-ink-muted); }

@media (max-width: 768px) {
  .mp-pager__nums { display: none; }
  .mp-pager__status { display: inline; }
}

@media (prefers-reduced-motion: reduce) {
  .mp-pager__link,
  .mp-pager__num { transition: none; }
}

/* ─ Blog article — /blog/{slug}. Three .mp-article__inner wrappers share
   the one container-width rule above (head copy, banner, body); vertical
   rhythm is carried by the elements themselves -- the head's own bottom
   padding, the banner's own margin-block-end, .mp-article's own
   padding-block-end -- so it holds together whether or not the post has a
   banner image or related posts. ─────────────────────────────────────── */
.mp-article { padding-block-end: var(--space-section-sm); }

.mp-article__head { padding-block: calc(108px + 64px) 40px; text-align: center; }
.mp-article__cat {
  display: inline-block;
  margin-block-end: 12px;
  padding: 6px 16px;
  border-radius: var(--radius-pill);
  background: var(--c-surface-tint);
  color: var(--c-primary);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
}
.mp-article__title { font-size: var(--fs-h1); color: var(--c-primary); }
.mp-article__meta {
  margin-block-start: 16px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  font-size: var(--fs-body-sm);
  color: var(--c-ink-muted);
}

.mp-article__banner {
  inline-size: 100%;
  block-size: auto;                      /* REQUIRED -- see file header */
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 16px;
  margin-block: 32px 0;
}

.mp-article__tags { display: flex; flex-wrap: wrap; gap: 10px; margin-block-start: var(--space-section-sm); }
.mp-article__tags a {
  display: inline-flex;
  align-items: center;
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  background: var(--c-bg);
  border: 1px solid rgba(26, 58, 127, 0.10);  /* same literal .mp-svcd-hero__chip already uses */
  color: var(--c-primary);
  font-size: var(--fs-label);
  text-decoration: none;
}
.mp-article__tags a:hover { text-decoration: underline; }

.mp-article__share {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-block-start: var(--space-section-sm);
  padding-block-start: 24px;
  border-block-start: 1px solid var(--c-surface-tint);
}
.mp-article__share > span { font-size: var(--fs-body-sm); font-weight: var(--fw-medium); color: var(--c-ink); }
/* One class on both <a> and <button> (the copy-link control) -- border/
   font-family/cursor normalise the button to look identical to the links. */
.mp-article__share-link {
  display: inline-flex;
  align-items: center;
  min-block-size: 38px;
  padding-inline: 16px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--c-surface-tint);
  color: var(--c-primary);
  font-family: inherit;
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  text-decoration: none;
  cursor: pointer;
  transition: background-color 160ms ease, color 160ms ease;
}
.mp-article__share-link:hover { background: color-mix(in srgb, var(--c-accent) 18%, var(--c-white)); }

.mp-article__related { padding-block-start: var(--space-section); }
.mp-article__related h2 { font-size: var(--fs-h2); color: var(--c-primary); margin-block-end: var(--space-heading); }

@media (max-width: 768px) {
  .mp-article__head { padding-block: calc(108px + 24px) 32px; }
  .mp-article__banner { margin-block-start: 24px; }
}

/* =========================================================================
   ADVERTISE WITH US — /advertise
   Figma kyudcw9zAgmniIXWNveugA: 961:78481 + 966:79765 desktop,
   967:1472 + 971:3026 mobile, 992:5536 the media-kit dialog.

   FIDELITY PASS (2026-08-17). The first build repainted three of the design's
   WHITE-on-colour pairs blue because white failed contrast on them. That read
   as "not the Figma design" and has been reversed: the ink is Figma's again
   and the BACKGROUND is what was treated, which is the only edit that keeps
   both the drawing and WCAG.

     · hero display line  white kept; the photograph carries a scrim gradient
                          so the composited ground lands at the luminance where
                          white AND the design's own --c-primary lines beside it
                          both clear the 3:1 large-text floor (measured below).
     · statement band     white kept; the accent panel carries a 9% black tint,
                          which is a ~4/6/21-per-channel shift off #2ca5e8 and
                          still reads as the accent.
     · solid accent CTAs  white kept; the FILL drops to --c-accent-deep, the
                          same hue and saturation as --c-accent with lightness
                          38% instead of 54%. White on it is 4.91:1. A fill IS
                          its own background, so darkening it is the same move
                          the band gets, not a different one.

   MOBILE PASS (2026-08-17, second). Everything above describes the DESKTOP
   frames and is unchanged. The ≤768 block at the foot of this section was
   re-measured against 971:3026 / 967:1472 and now differs from the desktop
   reading in three ways worth knowing before editing either:
     · the mobile frames DROP the hero lede, the hero's media-kit button and
       the whole Compare Features table, and re-cut the banner so the photo
       OVERLAPS the accent panel from above;
     · the hero's scrim is LOCALISED there, not a full-width ramp: mobile line
       1 is Gotham Book 18, i.e. WCAG small text owed 4.5:1, which the raw
       artwork already gives it, so only the white display line's own rows are
       darkened. Its stops are measured from the band's BASE in px — see the
       rule for why percentages break it;
     · sizes come from the frames, not the shared mobile ramp: --fs-label is
       12 and --fs-body-sm 14 at this width, while the cards want 14 and 16.

   Gotham ships in 300/400/500/700 only; the design's "Gotham Black" display
   line renders at 700 rather than pulling in a fifth face.

   Horizontal composition is scaled x1.1 wherever the design's own measurement
   is a share of ITS content band: this frame is drawn on a 1200 band (120px
   gutters) while every other page on this site — nav, footer and all — sits on
   1320 (60px gutters, --gutter). Type sizes are Figma's absolute values; only
   the widths and gutters between repeated objects scale, so the rhythm of the
   composition survives the wider band instead of the gaps absorbing all 120px.
   ========================================================================= */

/* ─ Shared to this page ─────────────────────────────────────────────────── */

/* Section eyebrow (961:78589 / 967:2185). Figma sets the mobile variant in
   --c-accent (2.74:1 on both grounds this page uses — the same measurement
   the .mp-h2 comment records), so both breakpoints take the desktop's
   --c-primary: 10.4:1 on --c-bg, 10.1:1 on --c-surface-tint. */
.mp-adv-eyebrow {
  margin: 0 0 8px;
  color: var(--c-primary);
  font-size: var(--fs-card);              /* 24px  961:78589 */
  font-weight: var(--fw-medium);
  /* 961:78589 is 29px tall on a 24px face — 1.2, not the inherited --lh-body
     1.6, which was adding 9.6px of dead space above every section heading. */
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.01em;
}

/* 40px in the design, not the shared --fs-h2's 48 (961:78590, 967:2186). */
.mp-adv-stats__title,
.mp-adv-packages__title,
.mp-adv-compare__title {
  margin: 0;
  font-size: clamp(26px, 3.2vw, 40px);
}

/* Buttons. --primary and --light are Figma's own hero pair (961:78604 /
   961:78608) and already pass: white on --c-primary is 10.74:1, --c-primary
   on white is the same. --outline is the dialog's second answer (992:6816). */
.mp-adv-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 60px;                   /* 961:78604 h=60 */
  padding-inline: 30px;                   /* 961:78604 px-30 */
  border: 1px solid transparent;
  border-radius: var(--radius-pill);      /* 50px on a 60px box = fully round */
  font-family: inherit;
  font-size: var(--fs-nav);               /* 20px  961:78605 */
  font-weight: var(--fw-bold);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;                  /* the reset is only `a { color: inherit }` */
  cursor: pointer;
  transition: transform var(--dur) var(--ease);
}
.mp-adv-btn--primary { background: var(--c-primary); color: var(--c-white); }
.mp-adv-btn--light   { background: var(--c-white);   color: var(--c-primary); }
.mp-adv-btn--outline {
  background: var(--c-white);
  color: var(--c-primary);
  border-color: var(--c-primary);
}
.mp-adv-btn:not(.mp-link--disabled):hover { transform: translateY(-2px); }

/* ─ Hero (961:78591 / 971:3028) ─────────────────────────────────────────── */
/* The photograph lives on a ::before rather than on the section itself so the
   Arabic page can mirror it. The dog occupies the START third and the copy the
   END two-thirds; under dir="rtl" the copy moves to the left, so the artwork
   has to follow or the two collide. Mirroring the layer is the only way to do
   that with a raster — background-position cannot move a subject that is
   baked into the picture. */
.mp-adv-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background-color: var(--c-accent);      /* never white while the photo loads */
  /* 227px to the first line of copy from the page top, 136px from the buttons
     to the base of the band (961:78597 y=227, band base y=704). The nav is
     position: fixed and 108px tall, so its clearance is inside that 227. */
  padding-block: 227px 136px;
}
/* The photograph, plus the scrim that lets the design's own ink survive on it.
   Figma paints the display line WHITE (961:78599) and the two lines around it
   --c-primary. On the bare artwork white is 1.7–2.2:1, so the first build
   repainted the white line blue — the single change the page was rejected for.

   Both floors can be met at once, but only in a narrow window, because the two
   inks pull opposite ways: white needs the ground DARK, --c-primary needs it
   LIGHT. Every string in this block is WCAG large text (32px Bold, 72px, and
   the lede restored to its drawn 32px), so both floors are 3:1, and the ground
   luminances that satisfy them overlap between L=0.243 and L=0.300. The two
   ratios are equal at L=0.270, where each reads 3.28:1 — that is what this
   gradient aims the composited ground at, and where it actually lands is
   measured on the shipped render (see the report).

   It is a gradient, not a flat wash, because the dog occupies the start third
   and carries no type: leaving him at full brightness keeps the band reading
   as Figma's bright hero rather than a dimmed one. The scrim rides on the same
   layer as the photo so [dir="rtl"]'s scaleX(-1) mirrors BOTH — an RTL page
   moves the copy to the left, and an unmirrored scrim would darken the empty
   side and leave the type on the bright one. */
.mp-adv-hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  /* CALIBRATED, not eyeballed. The stops were solved against the exported
     artwork's own luminance field (adv-hero-bg.webp, measured pixel by pixel
     over the copy box on a 1440 render): the photograph is NOT flat there — it
     runs L=0.385 at x≈940 up to L=0.561 at x=1380 — and the feasible window is
     only L=0.243..0.300 wide, so a single flat wash cannot hold both inks. The
     alpha dips in the middle and climbs at both ends precisely to cancel that,
     landing the whole box in L=0.243..0.286.

     Shipped worst cases, measured per text band on the FINAL render (every
     pixel of each band's box, worst not mean — white against the lightest
     ground under it, --c-primary against the darkest):
       display line, white   3.12:1     line 1, --c-primary   3.18:1
       lede, --c-primary     3.19:1     white pill edge       3.18:1
     Re-run the solver if adv-hero-bg.webp is ever re-exported — these numbers
     belong to THAT file, not to the rule. */
  background-image:
    linear-gradient(to right,
      rgba(26, 58, 127, 0)     34%,
      rgba(26, 58, 127, 0.365) 48.6%,
      rgba(26, 58, 127, 0.325) 55%,
      rgba(26, 58, 127, 0.275) 65%,
      rgba(26, 58, 127, 0.295) 70%,
      rgba(26, 58, 127, 0.343) 80%,
      rgba(26, 58, 127, 0.390) 90%,
      rgba(26, 58, 127, 0.400) 100%),
    var(--hero-img);
  background-size: 100% 100%, cover;
  background-position: center, center;
  background-repeat: no-repeat, no-repeat;
}
[dir="rtl"] .mp-adv-hero::before { transform: scaleX(-1); }

.mp-adv-hero__inner { display: grid; justify-items: end; }
.mp-adv-hero__copy {
  /* 681, not 679: 961:78601 is 680.39 wide (two 329.697 buttons + a 21 gap)
     inside a 679 frame — the design's own row overflows its column by 1.4px.
     Rounded to 330 the pair needs 681 or the second button wraps and the band
     grows 80px taller than the 704 it is drawn at.

     The 51.6% arm is LOAD-BEARING, not a nicety. 681 is 51.6% of the 1320
     content band, so at 1440 this resolves to exactly 681 and nothing moves.
     Below 1440 a fixed 681 would keep its width while the band shrank around
     it, sliding the copy's START edge from 48.6% of the viewport down to 34.5%
     at 1101 — straight out of the scrim, which is aimed at the viewport. That
     measured 2.15:1 for the white display line at 1200. Held as a share of the
     band instead, the start edge stays at 48.5-48.6% at every width from 1101
     to 1440 and the one ramp covers all of it. */
  max-inline-size: min(681px, 51.6%);     /* 679  961:78597 */
  text-align: start;
}

.mp-adv-hero__title {
  margin: 0;
  color: var(--c-primary);                /* 961:78599 line 1 is #1a3a7f */
  font-size: clamp(18px, 2.25vw, 32px);   /* 32  961:78599 line 1 */
  font-weight: var(--fw-bold);
  line-height: 1.06;                      /* 961:78599 is 110px over 32+72 */
}
/* The 72px display line. `display: block` because Figma breaks it onto its own
   line; as an <em> inside the sentence it still lets Arabic place the phrase
   wherever that language's grammar wants it. 700 is the heaviest Gotham this
   site ships — the design's Black is not one of the four faces.

   WHITE, as drawn (961:78599). It is legible because .mp-adv-hero::before
   carries the scrim, not because the ink was changed. */
.mp-adv-hero__title em {
  display: block;
  color: var(--c-white);
  font-style: normal;
  font-size: clamp(28px, 5vw, 72px);
  font-weight: var(--fw-bold);
  line-height: 0.977;
}
/* 32px, not 24 (961:78600 — Gotham Book 32/37.67). The first build shipped
   23px, which both undersized the lede by a third and dropped it below the
   24px WCAG large-text threshold the hero's whole contrast budget rests on. */
.mp-adv-hero__lede {
  margin-block: 16px 0;
  color: var(--c-primary);
  font-size: clamp(16px, 2.3vw, 32px);
  font-weight: var(--fw-book);
  line-height: 1.18;
}
.mp-adv-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 21px;                              /* 961:78601 */
  margin-block-start: 42px;
}
/* Two equal halves of the row rather than two 330px minimums: 961:78604 is
   329.697 wide in a 679 column, i.e. exactly half the row less the 21 gap, and
   stating it that way keeps the pair side by side once the copy narrows below
   1440 instead of wrapping the second button onto its own line. At 1440 this
   computes to 330. */
.mp-adv-hero__actions > * { flex: 1 1 0; min-inline-size: 0; }  /* 329.7  961:78604 */

/* ─ Stats (961:78610) ───────────────────────────────────────────────────── */
/* 82px, measured: the hero band bases at y=704 and 961:78588 (the eyebrow)
   tops at y=786. --space-section's 96 was 14px too much. */
.mp-adv-stats { padding-block: 82px var(--space-section-sm); }

/* Desktop is a plain row, so the shared .mp-carousel scroller is neutralised
   here and only switched back on under 768 (below). Overriding it rather than
   branching the markup keeps ONE list in the Blade. */
.mp-adv-stats__carousel { overflow: visible; }
.mp-adv-stats__list {
  /* 33.33 between 275px circles on the design's 1200 band (961:78611 x=0,
     961:78617 x=308.33). Scaled x1.1 onto this site's 1320 band that is 36.67
     between 302.5px circles; the first build kept 275 and let space-between
     stretch the gaps to 73.3, which pulled the row apart. */
  gap: 37px;
  /* Kept, NOT zeroed: this rail sits outside .mp-container, so the shared
     --carousel-edge is what aligns the first circle with the heading above
     at every width (the same value .mp-container's own inset resolves to). */
  padding-inline: var(--carousel-edge);
  margin: 40px 0 0;
  list-style: none;
  justify-content: space-between;
}
.mp-adv-stat {
  display: flex;
  flex: 1 1 0;                            /* 275 circle x1.1 = 302.5  961:78611 */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  aspect-ratio: 1;
  inline-size: auto;                      /* beats .mp-carousel__track > * */
  max-inline-size: 303px;
  padding: 24px;
  border-radius: var(--radius-pill);
  text-align: center;
}
/* Alternating grounds. #eaf6fd in Figma has no token; --c-surface-tint
   (#eefaff) is the nearest published one and is the tint the rest of this
   site already uses for exactly this treatment. */
.mp-adv-stat:nth-child(odd)  { background: var(--c-bg); }
.mp-adv-stat:nth-child(even) { background: var(--c-surface-tint); }
/* Each glyph is drawn at its own size (71x66, 66x66, 63x63, 56x56 —
   961:78612, 961:78618, 961:78624, 961:78629), so `auto` on both axes lets the
   width/height attributes the Blade already emits per icon govern. Pinning
   every one to a single block-size, as the first build did, made the two
   social marks 4px too tall and the newsletter mark 6px too short. */
.mp-adv-stat__icon {
  display: block;
  inline-size: auto;
  block-size: auto;
  max-inline-size: 100%;
}
.mp-adv-stat__value {
  margin: 0;
  color: var(--c-primary);                /* 10.4:1 / 10.1:1 */
  font-size: clamp(24px, 2.3vw, 32px);
  font-weight: var(--fw-bold);
  line-height: 1.2;                       /* 961:78615 is 38px on 32  */
}
.mp-adv-stat__label {
  margin: 0;
  color: var(--c-primary);
  font-size: clamp(15px, 1.7vw, 24px);    /* 24  961:78616 (58px over 2 lines) */
  font-weight: var(--fw-book);
  line-height: 1.2;
}

/* ─ Banner (961:78633 + 961:78635 / 971:3192) ───────────────────────────── */
/* Two overlapping rectangles rather than a split: the photograph is taller
   than the panel and slides 84px over it (panel ends x=804, photo starts
   x=720), which is what gives the band its offset look. */
.mp-adv-banner {
  /* Same expression .mp-carousel uses for --carousel-edge, and for the same
     reason: past --container the content edge is (100% - container) / 2, not
     a bare --gutter, so a hardcoded gutter would drift from every other
     section's start edge on a wide screen. */
  --adv-edge: max(var(--gutter), calc((100% - var(--container)) / 2));
  display: grid;
  grid-template-columns: minmax(0, 804fr) minmax(0, 636fr);
  align-items: center;
  /* The photograph HANGS INTO the packages band: 961:78635 bases at y=1812
     and 961:78483 (the tint) tops at y=1732, an 80px overlap, with only 2px
     between the accent panel's base and that same edge. The first build left a
     60px gap instead, which lost the overlap the band is composed around.
     50px above matches 961:78610's base (y=1191) to the photo's top (y=1301). */
  margin-block: 50px -80px;
  position: relative;
  z-index: 1;                             /* so the photo paints ON the tint */
}
.mp-adv-banner__text {
  grid-column: 1;
  grid-row: 1;
  margin: 0;
  /* The start inset matches .mp-container's own content edge — resolved from
     --adv-edge on the full-width parent, because 100% inside this grid item
     is the COLUMN's width, not the viewport's.
     LOGICAL, not a four-value shorthand: the grid column flips under
     dir="rtl" but a physical `padding: … left` would not, putting the
     container inset on the wrong side of the Arabic panel. */
  padding-block: 79px;
  /* 180 on the end side puts the measure at 564px on the 1440 frame — the
     width 961:78634 is actually set on (557) inside its 804px panel — so the
     sentence breaks on Figma's own three lines instead of a wider set. */
  padding-inline: var(--adv-edge) 180px;
  min-block-size: 346px;
  display: flex;
  align-items: center;
  /* Figma's ink here is WHITE on --c-accent (961:78634) — 2.74:1, under even
     the 3:1 large-text floor. The first build kept the fill and turned the
     sentence blue. Reversed: the sentence is white as drawn and the PANEL is
     treated, with 9% black over the accent. That composites to #2896d3, a
     4/6/21-per-channel shift that still reads as the accent, and white on it
     is 3.29:1 — clear of the 3:1 floor this 48px Bold line sits under. */
  background-color: var(--c-accent);
  background-image: linear-gradient(rgba(0, 0, 0, 0.09), rgba(0, 0, 0, 0.09));
  color: var(--c-white);
  font-size: clamp(20px, 3.4vw, 48px);
  font-weight: var(--fw-bold);
  line-height: 1.208;                     /* 961:78634 is 174px over 3 lines */
  border-start-start-radius: 6px;
  border-end-start-radius: 6px;
  box-shadow: 0 4px 7.35px rgba(0, 0, 0, 0.25);
}
.mp-adv-banner__media {
  grid-column: 2;
  grid-row: 1;
  z-index: 1;                             /* the photo sits ON the panel */
  /* Logical, so /ar mirrors the overlap instead of needing an RTL override:
     the panel ends at x=804 and the photo starts at x=720 (961:78635). */
  margin: 0;
  margin-inline: -84px 0;
  border-start-start-radius: 173px;
  border-end-start-radius: 6px;
  overflow: hidden;
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.25);
}
.mp-adv-banner__media img {
  display: block;
  inline-size: 100%;
  block-size: auto;                       /* the 1849x1310 attrs would pin it */
  object-fit: cover;
}

/* ─ Packages (967:2187 …) ───────────────────────────────────────────────── */
/* 200px of lead-in, not --space-section: the tint tops at y=1732 and the
   heading at y=1932 (961:78483, 961:78637), because the banner photograph
   hangs 80px into this band and the heading has to clear it. 88px out, from
   the last card's base (y=2627) to the tint's (y=2715). Top corners square,
   bottom pair 42 — measured off the 1:1 render, not assumed. */
.mp-adv-packages {
  padding-block: 200px 88px;
  background: var(--c-surface-tint);      /* Figma #eaf6fc — nearest token */
  border-end-start-radius: 42px;
  border-end-end-radius: 42px;
}

.mp-adv-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-block: 32px 42px;
}
.mp-adv-tab {
  min-inline-size: 228px;                 /* 967:2194 */
  min-block-size: 44px;                   /* touch-target floor */
  padding: 16px 24px;
  border: 0;
  border-block-end: 3px solid transparent;
  background: transparent;
  color: var(--c-primary);                /* 10.1:1 on the tint */
  font-family: inherit;
  font-size: clamp(16px, 1.8vw, 24px);
  font-weight: var(--fw-bold);
  line-height: 1.2;                       /* 967:2195 is 29 on 24; the strip is 61 tall */
  cursor: pointer;
  transition: border-color var(--dur) var(--ease);
}
/* The underline is the whole selected affordance in the design (967:2192 is a
   3px rule). Keyed off .is-active — Alpine's :class binding, the same carrier
   faqs.blade.php uses — and server-rendered for the default tab so the strip
   is right before Alpine boots. aria-selected carries the same state for
   assistive tech but is deliberately NOT a second style hook: two selectors
   for one state can disagree, and one of them would then paint a second
   underline. */
.mp-adv-tab.is-active { border-block-end-color: var(--c-primary); }
.mp-adv-tab:focus-visible { outline-color: var(--c-primary); }

.mp-adv-tiers,
.mp-adv-biz__track {
  list-style: none;
  margin: 0;
  padding: 0;
}
/* 102px between 332px cards on the design's 1200 band (967:2199 x=0,
   967:2211 x=434, 967:2223 x=868), 42px between the two rows (967:2198 y=0,
   967:2235 y=241). Scaled x1.1 the column gutter is 112 and the cards land at
   365; the first build's 42px column gap made the cards 47px too wide and the
   gutter between them less than half what is drawn. Row gutters do not scale —
   the band is only wider horizontally. */
.mp-adv-tiers {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* STRETCH, not start. The three tiers carry 4, 5 and 7 bullets, so
     align-items:start let every card hug its own content and the row ended
     ragged — three different bottom edges. 967:2199 / 2211 / 2223 are all the
     same height with their bottoms aligned, which is also what makes the
     pricing read as one comparable set rather than three unrelated boxes.
     Grid's default is stretch, so this is a deletion of the override; the
     equal height then falls out of the row sizing for free. */
  align-items: stretch;
  gap: 42px 112px;
}

.mp-adv-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 24px 16px;
  border-radius: 22px;                    /* 967:2199 */
  background: var(--c-white);
  box-shadow: 0 6px 7.45px rgba(0, 0, 0, 0.11);
}
.mp-adv-card__title {
  margin: 0;
  color: var(--c-primary);                /* 10.74:1 on white */
  font-size: clamp(18px, 1.9vw, 22px);
  font-weight: var(--fw-bold);
  line-height: 1.16;
}
.mp-adv-card__body {
  margin: 0;
  color: var(--c-ink);                    /* 15.58:1 on white */
  font-size: var(--fs-label);             /* 14  967:2204 */
  font-weight: var(--fw-book);
  line-height: 1.2;
}
/* Figma fills this --c-accent with white type (967:2208). At 16px Bold the
   label is below the 18.66px large-text threshold, so it needs 4.5:1 and the
   accent gives 2.74. The first build turned the fill navy, which is what made
   the whole packages grid read wrong. The fill is now --c-accent-deep — the
   accent's own hue and saturation at 38% lightness — and white on it is
   4.91:1. See the token's note at the top of this file. */
.mp-adv-card__cta {
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: 44px;                   /* 36 in Figma, floored for touch */
  margin-block-start: 26px;
  padding-inline: 20px;
  border-radius: 4px;                     /* measured off the 1:1 render */
  background: var(--c-accent-deep);
  color: var(--c-white);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-bold);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  transition: transform var(--dur) var(--ease);
}
.mp-adv-card__cta:not(.mp-link--disabled):hover { transform: translateY(-2px); }
.mp-adv-card__cta:focus-visible { outline-color: var(--c-primary); }

.mp-adv-tier__includes {
  margin: 32px 0 0;
  color: var(--c-ink);
  font-size: var(--fs-label);
  font-weight: var(--fw-bold);
  line-height: 1.2;
}
.mp-adv-points {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
}
.mp-adv-point {
  display: flex;
  gap: 8px;
  color: var(--c-ink);
  font-size: var(--fs-label);
  font-weight: var(--fw-book);
  line-height: 1.2;
}
.mp-adv-point__tick {
  flex: 0 0 auto;
  inline-size: 10px;
  block-size: auto;
  margin-block-start: 2px;
}
/* The tick leans right in the export; lean it into the text on the RTL page,
   the same treatment .mp-svc-hero__cta's magnifier already gets. */
[dir="rtl"] .mp-adv-point__tick,
[dir="rtl"] .mp-adv-table__tick { transform: scaleX(-1); }

/* Pet Businesses: a 3x2 grid on desktop. The shared carousel scroller is
   switched off here and back on under 768 (below), so the six cards are one
   list in the markup, not two. */
/* --carousel-edge is zeroed, unlike the stats rail: this one lives INSIDE
   .mp-container, which already supplies the gutter, and 967:1472 shows the
   mobile cards inset rather than bleeding. */
.mp-adv-biz { --carousel-edge: 0px; overflow: visible; }
.mp-adv-biz__track {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: start;
  gap: 42px 112px;                        /* same measurement as .mp-adv-tiers */
  padding-inline: 0;
}
.mp-adv-biz__track > .mp-adv-card { inline-size: auto; }
/* Arrows are a mobile affordance on this page (they sit on-canvas in
   967:1472, unlike the other carousels whose arrows Figma parks off-frame). */
.mp-adv-biz__controls { display: none; }

/* ─ Compare features (990:1652) — Partners tab only ─────────────────────── */
/* 120px in: the tint bases at y=2715 and this eyebrow tops at y=2836. */
.mp-adv-compare { padding-block: 120px var(--space-section); }
.mp-adv-compare__title { margin-block-end: 32px; }

/* The table keeps its own scroller so the BODY never scrolls sideways at
   390px. Focusable, because a scroll region that only responds to pointer
   drag is unreachable from the keyboard. */
.mp-adv-compare__scroller {
  /* position: relative is LOAD-BEARING, not decoration. The tick/dash cells
     each carry a .mp-visually-hidden label, and that helper is
     `position: absolute`. Without a positioned ancestor inside this scroller
     its containing block is the initial one, so those spans are NOT clipped
     by the overflow here — they keep their static position deep inside the
     640px-wide table and drag the DOCUMENT's scrollWidth out with them.
     Measured at 390: html scrollWidth was 598 against a clientWidth of 390,
     i.e. real horizontal page scroll, from spans that are 1px wide and
     invisible. Making this the containing block brings them back inside. */
  position: relative;
  overflow-x: auto;
  border-radius: 4px;
  background: var(--c-white);
}
.mp-adv-compare__scroller:focus-visible { outline-color: var(--c-primary); }

.mp-adv-table {
  inline-size: 100%;
  min-inline-size: 640px;                 /* four legible columns; scrolls below that */
  border-collapse: collapse;
  text-align: start;
}
.mp-adv-table th,
.mp-adv-table td {
  padding: 10px 12px;
  font-size: clamp(14px, 1.4vw, 18px);
  font-weight: var(--fw-bold);
  line-height: 1.2;                       /* rows are 42px tall on the render */
  text-align: start;
  vertical-align: middle;
}
.mp-adv-table td,
.mp-adv-table thead th + th { text-align: center; }
/* Figma's header row is white on --c-accent (990:1653): 2.74:1 at 18px Bold,
   below the 18.66px large-text threshold and so short of the 4.5:1 it needs.
   The first build made the row navy, which is the single loudest thing on the
   Partners tab. It is --c-accent-deep now — the accent family, dark enough to
   carry white at 4.91:1. */
.mp-adv-table thead th {
  background: var(--c-accent-deep);
  color: var(--c-white);
}
.mp-adv-table tbody th,
.mp-adv-table tbody td { color: var(--c-ink); }        /* 15.58:1 / 14.7:1 */
.mp-adv-table tbody tr:nth-child(odd)  td,
.mp-adv-table tbody tr:nth-child(odd)  th { background: var(--c-white); }
.mp-adv-table tbody tr:nth-child(even) td,
.mp-adv-table tbody tr:nth-child(even) th { background: var(--c-bg); }
.mp-adv-table__tick { display: inline-block; inline-size: 10px; block-size: auto; }

/* ─ Let's Talk (961:78803 / 971:3262) ───────────────────────────────────── */
.mp-adv-talk { background: var(--c-bg); }
.mp-adv-talk__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: 60px;
}
.mp-adv-talk__media { margin: 0; }
/* The organic silhouette is baked into the WebP's alpha channel, not a
   border-radius and not a CSS mask.

   Two dead ends are recorded here so neither is retried. Figma's PNG export
   of the masked group (963:79005) comes back FLATTENED AGAINST WHITE — its
   alpha channel is entirely opaque — so shipping the export put a white
   rectangle on this band. Applying the exported mask PATH as mask-image over
   the plain photograph then made the picture vanish outright in Chromium
   (computed mask-mode resolves to match-source, and the exported path is
   filled #C4C4C4). The shape now comes from compositing that same vector's
   rasterised alpha onto the unmasked source, which needs nothing from the
   renderer at all. */
.mp-adv-talk__media img {
  display: block;
  inline-size: 100%;
  max-inline-size: 485px;                 /* 441 (963:79005) x1.1 */
  block-size: auto;                       /* the 898x896 attrs would pin it */
  margin-inline: auto;
}
.mp-adv-talk__title {
  margin: 0;
  color: var(--c-primary);
  font-size: clamp(32px, 4.6vw, 64px);
  font-weight: var(--fw-bold);
  line-height: 1.2;                       /* 961:78805 is 77 on 64 */
}
.mp-adv-talk__body {
  margin-block: 21px 0;
  color: var(--c-primary);                /* 10.4:1 on --c-bg */
  font-size: clamp(16px, 2vw, 28px);      /* 28  961:78806 */
  font-weight: var(--fw-book);
  line-height: 1.62;                      /* 961:78806 is 136 over 3 lines */
}
.mp-adv-talk__cta {
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: 60px;
  margin-block-start: 32px;
  padding-inline: 30px;
  border-radius: 10px;                    /* 961:78810 */
  /* Figma: white on --c-accent (961:78810). 20px Bold clears the large-text
     threshold so the floor is 3:1, and the accent still only gives 2.74 — the
     fill takes --c-accent-deep, the same treatment its sibling CTAs get, at
     4.91:1 rather than the first build's navy. */
  background: var(--c-accent-deep);
  color: var(--c-white);
  font-size: var(--fs-nav);
  font-weight: var(--fw-bold);
  text-align: center;
  text-decoration: none;
  transition: transform var(--dur) var(--ease);
}
.mp-adv-talk__cta:not(.mp-link--disabled):hover { transform: translateY(-2px); }
.mp-adv-talk__cta:focus-visible { outline-color: var(--c-primary); }

/* ─ Media-kit dialog (992:5536) ─────────────────────────────────────────── */
/* A native <dialog>: the scrim is ::backdrop, the focus trap and Escape are
   the element's own behaviour, so none of it is re-implemented in JS. */
.mp-adv-kit {
  inline-size: min(518px, calc(100vw - 32px));
  max-block-size: min(84vh, 720px);
  padding: 0;
  border: 0;
  border-radius: 16px;
  background: var(--c-white);
  color: var(--c-ink);
  overflow: hidden;
}
.mp-adv-kit::backdrop { background: rgba(0, 0, 0, 0.5); }   /* 992:6688 */

.mp-adv-kit__panel {
  display: flex;
  flex-direction: column;
  max-block-size: inherit;
}
.mp-adv-kit__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 24px 12px;
  box-shadow: 0 3px 1px rgba(0, 0, 0, 0.06);
}
.mp-adv-kit__title {
  margin: 0;
  color: var(--c-primary);                /* 10.74:1 on white */
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  line-height: 1.3;
}
.mp-adv-kit__close {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 44px;                      /* 24px glyph, 44px target */
  block-size: 44px;
  margin-inline-end: -10px;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--c-primary);
  cursor: pointer;
}
.mp-adv-kit__close:hover { background: var(--c-surface-tint); }
.mp-adv-kit__close:focus-visible { outline-color: var(--c-primary); }

.mp-adv-kit__body { padding: 24px; overflow-y: auto; }
.mp-adv-kit__question,
.mp-adv-kit__empty {
  margin: 0 0 20px;
  color: var(--c-ink);                    /* 15.58:1 on white */
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-book);
  line-height: 1.4;
}
.mp-adv-kit__choices { display: flex; flex-wrap: wrap; gap: 16px; }
.mp-adv-kit__choice {
  flex: 1 1 200px;
  min-block-size: 44px;
  border-radius: 8px;                     /* 992:6817 */
  font-size: var(--fs-body-sm);
}

.mp-adv-kit__files { margin: 0; padding: 0; list-style: none; }
.mp-adv-kit__file + .mp-adv-kit__file { margin-block-start: 10px; }
.mp-adv-kit__link {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 4px 16px;
  padding: 14px 16px;
  border: 1px solid color-mix(in srgb, var(--c-primary) 22%, transparent);
  border-radius: 10px;
  color: var(--c-primary);
  text-decoration: none;                  /* the reset is only `a { color: inherit }` */
  transition: background-color var(--dur) var(--ease);
}
.mp-adv-kit__link:hover { background: var(--c-surface-tint); }
.mp-adv-kit__link:focus-visible { outline-color: var(--c-primary); }
.mp-adv-kit__file-name { font-size: var(--fs-body-sm); font-weight: var(--fw-bold); }
.mp-adv-kit__file-meta {
  grid-column: 1;
  color: var(--c-ink-muted);              /* 8.19:1 on white */
  font-size: var(--fs-label);
  font-weight: var(--fw-book);
}
.mp-adv-kit__file-action {
  grid-column: 2;
  grid-row: 1 / span 2;
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  text-underline-offset: 3px;
}
.mp-adv-kit__back {
  margin-block-start: 20px;
  padding: 10px 0;
  border: 0;
  background: transparent;
  color: var(--c-primary);
  font-family: inherit;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  text-underline-offset: 3px;
  cursor: pointer;
}
.mp-adv-kit__back:focus-visible { outline-color: var(--c-primary); }

@media (prefers-reduced-motion: reduce) {
  .mp-adv-btn,
  .mp-adv-card__cta,
  .mp-adv-talk__cta { transition: none; }
}

/* ─ Tablet ──────────────────────────────────────────────────────────────── */
@media (max-width: 1100px) {
  .mp-adv-hero { padding-block: 180px 90px; }
  /* The copy stays on the END side here, it does not go full-width. The dog is
     baked into the START of the artwork at every width above 768, so a
     stretched column lays the 72px display line straight across his face — and
     that line is white now, over white fur. Holding the design's own
     two-column composition is both truer to 961:78591 and the only way the
     scrim has one side to darken. */
  .mp-adv-hero__inner { justify-items: end; }
  .mp-adv-hero__copy { max-inline-size: 62%; }
  /* Re-aimed for this band, and re-solved, not merely shifted: `cover` crops
     the SAME artwork differently once the viewport narrows, so the photograph
     trends dark toward the end side here where at 1440 it trends light. The
     alpha therefore falls where the 1440 ramp rises. Copy starts at ~38-39% of
     the viewport at every width in the band, which is why one ramp serves all
     of it. Measured worst cases, 800/900/1000/1100: white 3.18:1, --c-primary
     3.06-3.13:1, navy pill edge 3.01-3.04:1, white pill edge 3.24-3.27:1. */
  .mp-adv-hero::before {
    background-image:
      linear-gradient(to right,
        rgba(26, 58, 127, 0)     14%,
        rgba(26, 58, 127, 0.405) 37%,
        rgba(26, 58, 127, 0.363) 48%,
        rgba(26, 58, 127, 0.347) 55%,
        rgba(26, 58, 127, 0.326) 60%,
        rgba(26, 58, 127, 0.302) 66%,
        rgba(26, 58, 127, 0.312) 75%,
        rgba(26, 58, 127, 0.338) 80%,
        rgba(26, 58, 127, 0.362) 87%,
        rgba(26, 58, 127, 0.377) 95%,
        rgba(26, 58, 127, 0.385) 100%),
      var(--hero-img);
  }

  .mp-adv-tiers,
  .mp-adv-biz__track { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }

  /* Stacked (971:3192), so the desktop overlap is undone: there is nothing to
     hang over once the photo is above the panel rather than beside it. */
  .mp-adv-banner {
    grid-template-columns: minmax(0, 1fr);
    margin-block: var(--space-section-sm);
    z-index: auto;
  }
  .mp-adv-packages { padding-block: var(--space-section); }
  .mp-adv-compare { padding-block: var(--space-section); }
  .mp-adv-banner__text {
    grid-column: 1;
    grid-row: 2;
    padding: 32px var(--gutter);
    min-block-size: 0;
    border-start-start-radius: 0;
    border-end-start-radius: 0;
  }
  .mp-adv-banner__media {
    grid-column: 1;
    grid-row: 1;
    margin-inline: 0;
    border-start-start-radius: 0;
    border-start-end-radius: 76px;        /* 971:3192 turns the corner over */
    border-end-start-radius: 0;
  }

  .mp-adv-talk__inner { grid-template-columns: minmax(0, 1fr); gap: 32px; text-align: center; }
  .mp-adv-talk__media img { max-inline-size: 320px; }
}

/* ─ Mobile (971:3026 Partners / 967:1472 Pet Businesses, 393 wide) ───────── */
/* MEASURED PASS. Every number in this block came off those two frames — either
   get_design_context for type and fills or a 1:1 393x3722 render scanned pixel
   by pixel for radii and row pitch. The frames are 393 wide with 16px gutters;
   this site's container is 24 (--gutter), shared with the nav and footer this
   page must line up with, so the CONTENT WIDTH is 342 rather than 361 and every
   full-bleed measurement below is stated as a share of the frame instead of an
   absolute. That is the same trade the desktop pass made at 60-vs-120.

   The mobile frames are not the desktop frame reflowed. They drop the hero
   lede, drop the second hero button, drop the Compare Features table entirely,
   and re-cut the banner so the photograph OVERLAPS the accent panel from above
   instead of stacking on top of it. Those are composition changes, not
   measurements, and getting them wrong reads as "not the design" however good
   the numbers are. */
@media (max-width: 768px) {
  /* 786x522 landscape cut. This breakpoint MUST stay in step with the
     media-scoped preload in layout.blade.php or the preload warms the file
     the page never paints.

     The mobile crop carries NO horizontal scrim, unlike the two bands above.
     That is a deliberate reversal, and it is what lets this band ship the
     design's own type instead of a bolder stand-in. 971:3159 sets lines 1-2 in
     Gotham BOOK 18 — normal weight under 24px, i.e. WCAG small text owed
     4.5:1, which needs a ground of L>=0.390, while the white display line
     under it needs L<=0.300. The earlier build read that as unsolvable and
     shipped 19px BOLD so the navy line would qualify as large text at 3:1.
     It is only unsolvable if both inks share pixels, and they do not: the navy
     lines occupy the copy block's first 44px and the white line the next 31.
     The raw artwork under the copy column measures L~0.48, which already gives
     the navy Book line its 4.5:1 with no treatment at all, so the scrim moved
     from the whole band to JUST the display line's rows — see ::after. */
  .mp-adv-hero::before {
    background-image: var(--hero-img-mobile, var(--hero-img));
  }
  /* The localised scrim. A second layer rather than a stop on ::before's
     gradient because stacked background layers compose as a UNION down the
     stack: a horizontal ramp and a vertical one painted together darken two
     whole strips, not their intersection. This is one radial ellipse centred
     on the white display line inside the copy column, so the navy lines above
     it and the dog at the start keep the design's own brightness.
     It rides its own layer BELOW the copy (z-index -1, same as the photo) and
     mirrors under dir=rtl with the photograph.
     Solved against adv-hero-bg-mobile.webp and re-measured on the composited
     render at 390/480/640/760; the shipped worst cases are recorded in the
     mobile fidelity report beside this rule. */
  .mp-adv-hero::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    /* Vertical ramp, measured from the BAND'S BASE rather than as a share of
       its height, and that is load-bearing. Everything under the white line is
       a fixed stack — em 31, gap 13, pill 36, padding 69 — so the display line
       always sits 142-151px above the base, whatever the two navy lines above
       it do. Stated as percentages this ramp slid straight off the type the
       moment the heading fitted on one line instead of two: at 640 the band
       loses exactly one 22px line box and the white line measured 1.73:1.
         · 0 above 151 — the navy lines' last glyph row is 151 above the base
         · 0.42 from 142 (the white line's first glyph row) down through 48,
           which covers the pill's base at 69
         · out by 14, so the band's own base is untouched
       0.42 was solved, not picked: the brightest raw pixel under the display
       line is rgb(128,207,255), L=0.564, and 0.42 of --c-primary over it
       composites to L=0.262 — white reads 3.37:1 there. */
    background-image:
      linear-gradient(to top,
        rgba(26, 58, 127, 0)     14px,
        rgba(26, 58, 127, 0.42)  48px,
        rgba(26, 58, 127, 0.42) 142px,
        rgba(26, 58, 127, 0)    151px);
    /* …intersected with the copy column. A MASK, not a second background
       layer: stacked layers compose as a union down the stack, so a horizontal
       ramp painted beside this one would darken a whole vertical strip of the
       dog as well. The mask multiplies instead, so the ramp above only exists
       from 43% of the frame outward — the copy's own start edge is 45.6%. */
    -webkit-mask-image: linear-gradient(to right, transparent 22%, #000 43%);
    mask-image: linear-gradient(to right, transparent 22%, #000 43%);
  }
  [dir="rtl"] .mp-adv-hero::after { transform: scaleX(-1); }

  /* The band is 261 tall under a 94px header (971:3028 y=94..355); this site's
     mobile nav is 76 and position: fixed, so 76 + 68 of clearance = 144 puts
     the first line of copy exactly where 971:3156 draws it (y=162, i.e. 68
     below the band top). 69 out: the CTA bases at y=286, the band at 355. */
  .mp-adv-hero { padding-block: 144px 69px; }
  /* The copy has to stay OFF the dog. The mobile artwork puts him across the
     start ~200 of the 393 frame (971:3028) and 971:3156 sets its copy in the
     remaining end half, at x=177 — 45.0% of the frame. 55% of this site's 342
     content band lands that start edge at 45.6%, which is why the SHARE is
     held here rather than the 200px absolute: the ::after scrim is aimed at
     the viewport and only works if the copy sits at a known fraction of it. */
  .mp-adv-hero__inner { justify-items: end; }
  .mp-adv-hero__copy { max-inline-size: 55%; }
  /* Gotham Book 18 / 22, --c-primary — 971:3159 verbatim, no longer bolded.
     The negative end margin reproduces the frame's own overflow: 971:3159 is a
     216px text node inside a 200px column and runs to the frame's right edge.
     Pulling the title box out by exactly one gutter does the same here, so the
     two lines break where the design breaks them instead of one word early.
     Logical, so /ar overflows to ITS end edge; .mp-adv-hero is overflow:hidden
     either way. */
  .mp-adv-hero__title {
    margin-inline-end: calc(var(--gutter) * -1);
    font-size: 18px;
    font-weight: var(--fw-book);
    line-height: 22px;
  }
  /* 24 / 31 — 971:3159's second run. 22 + 22 + 31 = the 75px text block.
     The line box is a FIXED 31px and the face is capped by a vw arm, because
     this one line is what the scrim above is aimed at: everything below it is a
     fixed stack, so a one-line em always sits 142-151px above the band's base,
     and a WRAPPED one does not. Below ~388 the 24px face no longer fits the
     column ("with BookMyPet" is ~196px wide at 24) and wrapped it dropped the
     first of the two lines clean out of the scrim. 6.2vw resolves to 24 at
     every width from 388 up, so 390-768 is Figma's size to the pixel and only
     genuinely narrow phones scale. */
  .mp-adv-hero__title em { font-size: min(24px, 6.2vw); line-height: 31px; }
  /* Not in the mobile frames at all: 971:3156 / 967:3025 carry the two heading
     lines and ONE button, nothing else. The proposition it states is repeated
     almost verbatim by the Let's Talk band lower down the same page. */
  .mp-adv-hero__lede { display: none; }
  /* The mobile frames drop the media-kit button and keep only "Let's Talk"
     (971:3160 / 967:2725 — one 200x36 CTA). Scoped to the hero row so the
     media-kit DIALOG's own primary buttons are untouched; the dialog itself
     still opens from anywhere that dispatches mp-media-kit-open.
     ⚠️ This is the page's only mobile entry point to the media kit. Deleting
     this one rule restores it. */
  .mp-adv-hero__actions .mp-adv-btn--primary { display: none; }
  .mp-adv-hero__actions { gap: 10px; margin-block-start: 13px; }  /* 971:3156 gap */
  .mp-adv-hero__actions > * {
    flex: 1 1 100%;
    min-inline-size: 0;
    min-block-size: 36px;                 /* 971:3160 h=36 */
    padding-inline: 20px;                 /* 971:3160 px-20 */
    font-size: 14px;                      /* 971:3161 Gotham Medium 14/1.4 */
    font-weight: var(--fw-medium);
  }
  /* 971:3160 draws a 1px stroke on the white pill — the only thing separating
     it from the bright sky behind, now that the band is no longer scrimmed
     across its whole width. */
  .mp-adv-hero__actions .mp-adv-btn--light { border-color: var(--c-pill-stroke); }

  /* Eyebrow: 971:3154 is Gotham BOOK 16, sentence case, hard against the
     heading under it (y=0 h=19, heading at y=19 — no gap). The desktop node
     961:78589 really is uppercase Medium 24, so only the case is re-set here
     and the base rule is left alone.
     The one thing NOT taken from the mobile frame is the ink: 971:3154 is
     --c-accent, which is 2.63:1 on --c-bg, and --c-accent-deep only reaches
     4.34:1 there — still short of the 4.5 a 16px normal-weight line is owed.
     --c-primary (10.4:1) stays. */
  .mp-adv-eyebrow {
    margin-block-end: 0;
    font-size: 16px;
    font-weight: var(--fw-book);
    text-transform: none;
    letter-spacing: 0;
  }
  /* 24px Bold — 971:3155 / 971:3202, not the 26 the desktop clamp bottoms out
     at. h=29 on a 24px face is the 1.2 the base rule already carries. */
  .mp-adv-stats__title,
  .mp-adv-packages__title,
  .mp-adv-compare__title { font-size: 24px; }

  /* ── Why Advertise (971:3151 + 971:3162) ── */
  /* 42 in: the hero band bases at y=355 and the eyebrow tops at y=397.
     30 out, so the 62px from the circle row's base (y=682) to the banner
     photograph's top (y=744) is shared with the banner's own 32px lead-in. */
  .mp-adv-stats { padding-block: 42px 30px; }
  /* The stats row becomes the site's standard scroll-snap carousel: peek and
     swipe, matching 971:3162's own bleed (that frame runs x=-92..414 on a 393
     canvas, i.e. off BOTH edges) and every other carousel already on this
     site. Two divergences stay, both of them JS-contract rather than
     measurement: Figma draws a 72px progress rail under the row (971:3186) and
     scales the CENTRED circle to 211 while its neighbours sit at 172. The
     shared carousel has neither an active-item transform nor a rail, and
     inventing both for one section is a bigger cost than the divergence. */
  .mp-adv-stats__carousel { overflow-x: auto; }
  .mp-adv-stats__list {
    gap: 12px;                            /* 971:3163 x=0 w=172 → 971:3169 x=184 */
    margin-block-start: 26px;             /* heading bases y=445, row tops y=471 */
    padding-inline: var(--carousel-edge);
    justify-content: flex-start;
  }
  .mp-adv-stat {
    flex: 0 0 172px;                      /* 971:3163 */
    max-inline-size: 172px;
    /* 971:3168 is a 172-wide text node — the label runs the FULL diameter, so
       the desktop's 24px inset would break "Newsletter Subscriptions" onto
       three lines inside a circle drawn for two. */
    padding: 0;
    /* 971:3166 stacks the figure and its label 6 apart inside a group that
       sits 16 below the icon; the flat three-child column here gets the same
       rhythm from a 6px gap plus 10 on the icon. */
    gap: 6px;
  }
  .mp-adv-stat__icon { margin-block-end: 10px; }
  /* Each glyph is drawn at its own mobile size — 47x43, 52x52, 40x40, 33x33
     (971:3164, 971:3170, 971:3176, 971:3181) — and they are NOT one scale of
     the desktop set, so a single block-size cannot produce them. Width stays
     auto, so each icon's own width/height attributes keep its aspect. */
  .mp-adv-stat:nth-child(1) .mp-adv-stat__icon { block-size: 43px; }
  .mp-adv-stat:nth-child(2) .mp-adv-stat__icon { block-size: 52px; }
  .mp-adv-stat:nth-child(3) .mp-adv-stat__icon { block-size: 40px; }
  .mp-adv-stat:nth-child(4) .mp-adv-stat__icon { block-size: 33px; }
  .mp-adv-stat__value { font-size: 18px; }   /* 971:3167 Gotham Bold 18 */
  .mp-adv-stat__label { font-size: 14px; }   /* 971:3168 Gotham Book 14/1.2 */

  /* ── Banner (971:3192 + 971:3194) ── */
  /* NOT the tablet stack. The mobile frame keeps the desktop's overlap and
     turns it through 90 degrees: the photograph is 329 of 393 wide (83.7%),
     flush to the START edge, and hangs 44px ABOVE the accent panel, which then
     runs the full width underneath it with a 76px corner turned at its END
     top. Every property the ≤1100 stack sets has to be restated here or the
     two composite on each other. */
  .mp-adv-banner {
    grid-template-columns: minmax(0, 1fr);
    /* 0 out: 971:3027 (the packages tint) tops at y=1035, 33px BEHIND the
       panel's own base at 1068, and the panel is opaque — so the tint's
       visible edge IS the panel's base and a zero gap renders identically to
       the design's overlap. */
    margin-block: 32px 0;
    z-index: auto;
  }
  .mp-adv-banner__media {
    grid-column: 1;
    grid-row: 1;
    align-self: start;
    inline-size: 83.72%;                  /* 329 of 393  971:3194 */
    /* 971:3194 is 329x206 — a tighter crop than the source's own 1849x1310,
       so the box is stated and the picture fills it. */
    aspect-ratio: 329 / 206;
    margin-inline: 0;
    border-start-start-radius: 0;
    border-start-end-radius: 0;
    border-end-start-radius: 0;
    border-end-end-radius: 76px;          /* arc fitted on the 1:1 render */
  }
  .mp-adv-banner__media img { block-size: 100%; }
  .mp-adv-banner__text {
    grid-column: 1;
    grid-row: 1;
    align-self: start;
    margin-block-start: 44px;             /* photo y=744, panel y=788 */
    min-block-size: 280px;                /* 971:3192 */
    padding: 16px;                        /* 971:3192 p-16 */
    align-items: flex-end;                /* justify-end: the line sits at the base */
    font-size: 24px;                      /* 971:3193 Gotham Bold 24 (87 over 3) */
    border-start-start-radius: 0;
    border-start-end-radius: 76px;
    border-end-start-radius: 6px;
    border-end-end-radius: 0;
  }

  /* ── Packages (971:3195) ── */
  /* 42 from the banner panel's base (y=1068) to the eyebrow (y=1110); 65 from
     the last card's base to the tint's (y=2443.6 → 2509). */
  .mp-adv-packages { padding-block: 42px 65px; }
  .mp-adv-tabs { gap: 0; margin-block: 16px 24px; }   /* 971:3198 h48 → 971:3203 y64 */
  .mp-adv-tab {
    flex: 1 1 0;
    min-inline-size: 0;
    /* 16 block is 971:3205's; the 24 inline is not reproducible on a 342 band
       (971:3206 is a 135px nowrap label in a 180px tab, and Figma lets it
       overflow its own padding). 8 keeps the label on ONE line inside a 171px
       half — wrapped it made the strip 77 tall against the frame's 54 — and
       the inline value is invisible anyway: both tabs are flex: 1 1 0 halves
       with the label centred. */
    padding: 16px 8px;
    white-space: nowrap;
    border-block-end-width: 2px;          /* 971:3207 is a 2px rule, not 3 */
    font-size: 18px;                      /* 971:3206 Gotham Bold 18 */
  }

  .mp-adv-tiers { grid-template-columns: minmax(0, 1fr); gap: 23px; }  /* 971:3214→3496 */

  /* Cards are drawn tighter on mobile than on desktop: 12px corners and a flat
     16 inset all round (971:3214), against 22 and 24/16. */
  .mp-adv-card { padding: 16px; border-radius: 12px; }
  /* 971:3216 gaps: title→body 6, then 16 to the button, 16 to the "get:" line
     and 16 to the list. The card's own 6px gap carries the first, so the other
     three are 10 on top of it. */
  .mp-adv-card__body { font-size: 14px; }              /* --fs-label is 12 here */
  .mp-adv-card__cta {
    min-block-size: 36px;                 /* 971:3223 — full-width target */
    margin-block-start: 10px;
    padding-inline: 30px;
    font-size: 16px;                      /* 971:3224; --fs-body-sm is 14 here */
  }
  .mp-adv-tier__includes { margin-block-start: 10px; font-size: 14px; }
  .mp-adv-points { margin-block-start: 10px; }
  .mp-adv-point { font-size: 14px; }                   /* 971:3472 Gotham Book 14 */

  /* Pet Businesses: 967:2874 / 967:2948 / 967:2960 are full-width cards
     STACKED, the same column the three tiers use and the same 23px pitch —
     not the horizontal scroller the first build shipped. All six are rendered
     rather than paginated three at a time, which is the one divergence: the
     frame's Arrow-Slider pages a set of three, and a second pagination
     contract for one section is not worth building. With nothing to page, the
     arrows go back to being hidden. */
  .mp-adv-biz { overflow: visible; }
  .mp-adv-biz__track {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 23px;
    padding-inline: 0;
  }
  .mp-adv-biz__track > .mp-adv-card { inline-size: auto; }
  .mp-adv-biz__controls { display: none; }
  /* 967:2878 centres the business-card headings; the partner tiers keep theirs
     at the start edge. */
  .mp-adv-biz .mp-adv-card__title { text-align: center; }

  /* Neither mobile frame draws the Compare Features table: 971:3026 runs
     packages → Let's Talk with nothing between them, and the tint band closes
     at y=2509. Kept for desktop, dropped here.
     x-show writes an inline `display` only when it HIDES the panel, so this
     rule wins whenever the Partners tab is selected and there is nothing to
     un-hide when it is not. */
  .mp-adv-compare { display: none; }

  /* ── Let's Talk (971:3258 / 971:3262 / 971:3265) ── */
  /* 71 in: the tint bases at y=2509 and the blob tops at y=2580. */
  .mp-adv-talk { padding-block: 71px var(--space-section); }
  /* 361 wide — the full content band, not the tablet's 320 cap (971:3258). */
  .mp-adv-talk__media img { max-inline-size: 100%; }
  .mp-adv-talk__body { font-size: 20px; }   /* 971:3264 Gotham Book 20/1.6 */
  .mp-adv-talk__cta {
    min-block-size: 52px;                   /* 971:3268 */
    font-size: 20px;                        /* 971:3269 Gotham Bold 20/30 */
  }

  .mp-adv-kit__head { padding-inline: 16px; }
  .mp-adv-kit__body { padding: 16px; }
  .mp-adv-kit__choices > * { flex: 1 1 100%; }
}

/* ─ Download-the-App dialog + contact modal ──────────────────────────────── */
/* Both are native <dialog>s and both deliberately reuse the media-kit
   chooser's visual language (.mp-adv-kit, 992:5536): white panel, 16px
   radius, a header rule under the title, a 44px close target, and the same
   ::backdrop. They are the third and fourth modals on this site and inventing
   a second modal look for them would make the set read as three unrelated
   products.

   No new tokens and no new colours — every value below is either an existing
   :root identity token or a measurement. Logical properties throughout, so
   /ar needs no override block.

   The two CTAs that changed ELEMENT TYPE first. `.mp-adv-card__cta` (ten
   "Contact Sales" pills) and `.mp-adv-talk__cta` ("Contact Us") were inert
   <span>s and are now real <button>s, because they operate a dialog on this
   page rather than leading anywhere. A <button> brings a UA border, a UA
   background and — outside this site's reset, which only covers `font` and
   `color` — its own font-family, so those are cleared to keep both pixel-
   identical to what the design draws. `.mp-adv-btn` already declares all
   three, which is why the media-kit choice button needs nothing here. */
.mp-adv-card__cta,
.mp-adv-talk__cta {
  border: 0;
  font-family: inherit;
  cursor: pointer;
}

.mp-appdl,
.mp-contact {
  inline-size: min(518px, calc(100vw - 32px));
  max-block-size: min(88vh, 760px);
  padding: 0;
  border: 0;
  border-radius: 16px;
  background: var(--c-white);
  color: var(--c-ink);
  overflow: hidden;
}
.mp-appdl::backdrop,
.mp-contact::backdrop { background: rgba(0, 0, 0, 0.5); }

.mp-appdl__panel,
.mp-contact__panel {
  display: flex;
  flex-direction: column;
  max-block-size: inherit;
}

.mp-appdl__head,
.mp-contact__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 24px 12px;
  box-shadow: 0 3px 1px rgba(0, 0, 0, 0.06);
}
.mp-appdl__title,
.mp-contact__title {
  margin: 0;
  color: var(--c-primary);                /* #1a3a7f on #fff — 10.74:1 */
  font-size: var(--fs-body);
  font-weight: var(--fw-bold);
  line-height: 1.3;
}
/* margin-inline-end, not margin-right: under dir="rtl" the close button moves
   to the start side and the optical nudge has to move with it. */
.mp-appdl__close,
.mp-contact__close {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 44px;                      /* 24px glyph, 44px target */
  block-size: 44px;
  margin-inline-end: -10px;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--c-primary);
  cursor: pointer;
}
.mp-appdl__close:hover,
.mp-contact__close:hover { background: var(--c-surface-tint); }
.mp-appdl__close:focus-visible,
.mp-contact__close:focus-visible { outline-color: var(--c-primary); }

.mp-appdl__body { padding: 24px; overflow-y: auto; }
.mp-appdl__text {
  margin: 0 0 20px;
  color: var(--c-ink-muted);              /* #4f4f4f on #fff — 8.19:1 */
  font-size: var(--fs-body-sm);
  line-height: 1.5;
}
.mp-appdl__badges { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; }
/* The footer's own badge treatment: 44px touch floor around artwork that is
   41-42px tall, without resizing the art. */
.mp-appdl__badge { display: inline-flex; align-items: center; min-block-size: 44px; border-radius: 8px; }
.mp-appdl__badge:focus-visible { outline-color: var(--c-primary); outline-offset: 4px; }
/* block-size: auto keeps the SVG's own aspect ratio while inline-size drives
   the scale — without it the width/height attributes on the <img> would fix
   both axes and any width change would squash the artwork. */
.mp-appdl__badge img { display: block; inline-size: 157px; block-size: auto; }

/* ── Contact modal ── */
.mp-contact__body { padding: 24px; overflow-y: auto; }
.mp-contact__lede {
  margin: 0 0 20px;
  color: var(--c-ink-muted);              /* 8.19:1 */
  font-size: var(--fs-body-sm);
  line-height: 1.5;
}

/* The honeypot. Taken out of the flow entirely rather than display:none —
   some bots skip anything display:none, and this field only works if they
   fill it in. inset-inline-start so it parks off the START edge in both
   directions; a hardcoded `left` would push it off-canvas in LTR and into
   view in RTL. */
.mp-contact__hp {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  inset-inline-start: -9999px;
  overflow: hidden;
}

.mp-contact__alert {
  margin: 0 0 20px;
  padding: 12px 16px;
  border-radius: 8px;
  background: var(--c-surface-tint);
  color: var(--c-primary);                /* #1a3a7f on #eefaff — 9.60:1 */
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  line-height: 1.4;
}

.mp-contact__field { margin-block-end: 16px; }
.mp-contact__label {
  display: block;
  margin-block-end: 6px;
  color: var(--c-ink);                    /* #212428 on #fff — 15.58:1 */
  font-size: var(--fs-label);
  font-weight: var(--fw-bold);
  line-height: 1.3;
}
.mp-contact__req {
  color: var(--c-ink-muted);              /* 8.19:1 */
  font-weight: var(--fw-book);
}

.mp-contact__input {
  inline-size: 100%;
  min-block-size: 48px;
  padding: 12px 14px;
  /* --c-ink-muted (8.19:1 on white), NOT --c-pill-stroke.
     WCAG 1.4.11 asks 3:1 of any visual information needed to IDENTIFY a
     control, and on a text input the border is the entire thing that says
     "type here" — there is no fill, no glyph and no shadow doing that job.
     --c-pill-stroke (#c6d0e5) measures 1.55:1 on white and fails it. It is
     not wrong where it already lives: on the mobile hero CTA it is a
     decorative hairline around a pill whose own fill and label identify it,
     so the hairline carries no information. Here it would be the only
     boundary, so it has to be legible. */
  border: 1px solid var(--c-ink-muted);
  border-radius: 8px;
  background: var(--c-white);
  color: var(--c-ink);                    /* 15.58:1 */
  font-family: inherit;
  font-size: var(--fs-body-sm);
  line-height: 1.4;
  transition: border-color var(--dur) var(--ease);
}
/* PLACEHOLDER CONTRAST — the usual failure. A browser's default placeholder
   is roughly #757575 (4.6:1) in Chrome and considerably lighter in Safari and
   Firefox, and several UAs additionally apply their own opacity on top, which
   drops a passing colour below the floor without changing the declared value.
   Both are pinned here: --c-ink-muted at 8.19:1 on white, and opacity: 1 so
   no UA can quietly thin it. Placeholder text carries the same 4.5:1 minimum
   as body text — it is not decoration. */
.mp-contact__input::placeholder {
  color: var(--c-ink-muted);              /* #4f4f4f on #fff — 8.19:1 */
  opacity: 1;
}
.mp-contact__input:focus-visible {
  outline: 2px solid var(--c-primary);
  outline-offset: 1px;
  border-color: var(--c-primary);
}
/* The invalid state is carried by BORDER + an error message + aria-invalid,
   never by colour alone (WCAG 1.4.1): the identity palette has no red, and
   inventing one would be a new colour. The message beneath the field is what
   actually says what is wrong. */
.mp-contact__input[aria-invalid="true"] { border-color: var(--c-primary); border-width: 2px; }

.mp-contact__textarea {
  min-block-size: 110px;
  resize: vertical;
}

/* DROPDOWN. Deliberately keeps the UA's OWN arrow — no `appearance: none`
   plus a background-image chevron. Two reasons and both are rules this file
   already follows: `background-position` has no logical equivalent, so a
   hand-drawn arrow would need a physical left/right value and therefore an
   [dir="rtl"] override on /ar, which is exactly the block this stylesheet is
   written to avoid; and the native arrow is drawn by the platform at
   guaranteed contrast in both light and forced-colours modes. The border,
   height and type are shared with the text inputs via .mp-contact__input, so
   the control still reads as one of the set. */
.mp-contact__select {
  /* Overrides the UA's own font, which several browsers set to a system face
     at a smaller size than the inputs beside it. */
  font-family: inherit;
  font-size: var(--fs-body-sm);
  /* Leaves room for the arrow the UA draws in the inline-end padding box. */
  padding-inline-end: 36px;
}

/* CHECKBOX. Its question IS the label beside the box, so there is no separate
   label above it — which is why this is a flex row and not the
   label-over-control stack every other field uses. `gap` and the flex
   direction are writing-mode aware, so /ar mirrors with no override. */
.mp-contact__check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.mp-contact__checkbox {
  /* 20px, not the UA's ~13px: the box is the entire target, and a 13px one is
     below every touch-target guideline. flex-shrink stops it collapsing when
     the label wraps to two lines. */
  inline-size: 20px;
  block-size: 20px;
  margin: 2px 0 0;
  flex: 0 0 auto;
  /* Paints the ticked state in the identity blue rather than the UA's default
     accent. --c-accent-deep is the same token the submit button uses (4.91:1
     against white), so the checked box clears 3:1 as a non-text control. */
  accent-color: var(--c-accent-deep);
}
.mp-contact__checkbox:focus-visible {
  outline: 2px solid var(--c-primary);
  outline-offset: 2px;
}
.mp-contact__check-label {
  color: var(--c-ink);                    /* 15.58:1 */
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-book);
  line-height: 1.4;
  cursor: pointer;
}

/* Admin-authored help text under a field. --c-ink-muted at 8.19:1 — it is
   read, not decoration, so it carries the same 4.5:1 floor as body copy. */
.mp-contact__help {
  margin: 6px 0 0;
  color: var(--c-ink-muted);              /* 8.19:1 */
  font-size: var(--fs-label);
  line-height: 1.45;
}

.mp-contact__error {
  margin: 6px 0 0;
  color: var(--c-primary);                /* 10.74:1 */
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  line-height: 1.35;
}

.mp-contact__submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 100%;
  min-block-size: 48px;
  margin-block-start: 8px;
  padding-inline: 24px;
  border: 0;
  border-radius: 8px;
  /* White on --c-accent-deep — 4.91:1. The same substitution every solid CTA
     on /advertise already makes: --c-accent with white is 2.74:1 and fails
     even the 3:1 large-text floor, and a fill is its own background, so
     darkening the fill IS treating the background. See the token's note at
     the top of this file. */
  background: var(--c-accent-deep);
  color: var(--c-white);
  font-family: inherit;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-bold);
  line-height: 1.2;
  cursor: pointer;
  transition: transform var(--dur) var(--ease);
}
.mp-contact__submit:hover:not(:disabled) { transform: translateY(-2px); }
.mp-contact__submit:focus-visible { outline-color: var(--c-primary); outline-offset: 3px; }
.mp-contact__submit:disabled { opacity: 0.72; cursor: progress; }

/* Success. The tick is --c-success as a GLYPH only: #23bba6 on white is
   2.20:1, nowhere near a text-legible ratio, so the confirmation it
   accompanies is set in --c-ink and the colour carries no meaning on its
   own. */
.mp-contact__done { text-align: center; }
.mp-contact__done:focus-visible { outline: none; }
.mp-contact__tick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 56px;
  block-size: 56px;
  margin-block-end: 16px;
  border-radius: var(--radius-pill);
  background: var(--c-surface-tint);
  color: var(--c-success);
}
.mp-contact__done-title {
  margin: 0 0 8px;
  color: var(--c-primary);                /* 10.74:1 */
  font-size: var(--fs-card);
  font-weight: var(--fw-bold);
  line-height: 1.2;
}
.mp-contact__done-body {
  margin: 0 0 20px;
  color: var(--c-ink);                    /* 15.58:1 */
  font-size: var(--fs-body-sm);
  line-height: 1.6;
}

@media (prefers-reduced-motion: reduce) {
  .mp-contact__submit,
  .mp-contact__input { transition: none; }
}

@media (max-width: 768px) {
  .mp-appdl__head,
  .mp-contact__head { padding-inline: 16px; }
  .mp-appdl__body,
  .mp-contact__body { padding: 16px; }
  /* One badge per row on a narrow phone rather than two cramped ones. */
  .mp-appdl__badges > * { flex: 1 1 100%; }
}

/* ─ 404 — resources/views/marketplace/errors/404.blade.php ─────────────────

   From the product owner's mockups (raster, not Figma), so nothing here is a
   measured node id: every size, colour and space is an EXISTING :root token,
   which is also what makes the page track the site's own responsive ramp
   (--fs-stat steps 128 → 92 → 86 across the three bands on its own).

   Contrast, measured against the band's own ground. Its background is a wash
   from --c-surface-tint (#eefaff) to --c-bg (#fafbfc), so the tint is the
   worse of the two and every figure below is quoted on it:
     .mp-404__code      --c-primary       on tint  10.10:1   (128px, large)
     .mp-404__title     --c-primary       on tint  10.10:1   (60px,  large)
     .mp-404__title em  --c-accent-deep   on tint   4.62:1   (60px,  large)
     .mp-404__body      --c-ink-muted     on tint   7.71:1   (18px,  body)
     --solid            --c-white on --c-accent-deep 4.91:1  (18px,  body)
     --ghost            --c-primary on --c-white   10.74:1   (18px,  body)
     ghost border       --c-primary on --c-white   10.74:1   (>= 3:1 non-text)
     .mp-404__help-title  --c-primary     on tint  10.10:1   (24px,  large)
     .mp-404__help-body   --c-ink-muted   on tint   7.71:1   (18px,  body)
   --c-accent itself is NOT used for the accent word: it measures 2.65:1 on
   --c-bg, under even the 3:1 large-text floor, which is the whole reason
   --c-accent-deep exists (see its token comment).

   Logical properties throughout — /ar needs no override block. The one
   direction-aware rule is .mp-404__help-cta's arrow-free layout; nothing here
   depends on left/right.

   Nav clearance: .mp-nav is position:fixed, and this page has no hero to sit
   under it, so the band pads its own block-start by the nav's height the same
   way .mp-legal__head and .mp-blog__head do.

   ⚠️ This page must show the CONDENSED (white) nav. That is not set here — it
   comes from site.js `mpNav`, whose hero-title querySelector chain misses on
   this page and falls to its null branch, which sets condensed = true. Do not
   add .mp-hero__title / .mp-svc-hero__title / .mp-ptn-hero__title /
   .mp-adv-hero__title / .mp-hero__eyebrow / .mp-nav__sentinel to this page:
   any of them would flip the bar to its transparent white-on-dark treatment
   over a near-white background. ───────────────────────────────────────────── */
.mp-404 {
  background: linear-gradient(180deg, var(--c-surface-tint) 0%, var(--c-bg) 62%);
  padding-block: calc(108px + 64px) var(--space-section);
}

.mp-404__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: var(--gap-grid);
}

/* ── Left column ────────────────────────────────────────────────────────── */
.mp-404__code {
  margin: 0;
  font-size: var(--fs-stat);
  font-weight: var(--fw-bold);
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--c-primary);
}

.mp-404__title {
  margin: 16px 0 0;
  font-size: var(--fs-h1);
  font-weight: var(--fw-bold);
  line-height: var(--lh-heading);
  color: var(--c-primary);
}
/* <x-marketplace.marked> emits <em> for the **walk.** span; the reset leaves
   <em> italic, which this treatment is not. Same normalisation .mp-h2 em
   already does for the site's other two-tone headings. */
.mp-404__title em {
  font-style: normal;
  color: var(--c-accent-deep);
}

.mp-404__body {
  margin: 20px 0 0;
  max-inline-size: 46ch;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-ink-muted);
}

.mp-404__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-block-start: 32px;
}

/* One button shape, two skins. Not .mp-btn--pill: that one is the nav's
   accent-filled CTA at 2.74:1 white-on-#2ca5e8, a ratio this page is not
   allowed to reproduce. */
.mp-404__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-block-size: 52px;
  padding-block: 12px;
  padding-inline: 26px;
  border: 1.5px solid transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-display);
  font-size: var(--fs-body);
  font-weight: var(--fw-medium);
  line-height: 1.2;
  text-align: center;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.mp-404__cta--solid {
  background: var(--c-accent-deep);
  border-color: var(--c-accent-deep);
  color: var(--c-white);
}
.mp-404__cta--ghost {
  background: var(--c-white);
  border-color: var(--c-primary);
  color: var(--c-primary);
}
.mp-404__cta:not(.mp-link--disabled):hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-nav);
}
.mp-404__cta-icon { flex: none; display: block; }

/* ── Artwork ────────────────────────────────────────────────────────────── */
.mp-404__art { display: block; }
/* Presentational width/height on the <img> and <source> carry the aspect
   ratio; `block-size: auto` is what stops those attributes being read as a
   literal 1028px tall box once inline-size is overridden. An `aspect-ratio`
   declaration would lose to them. */
.mp-404__art-img {
  display: block;
  inline-size: 100%;
  max-inline-size: 620px;
  block-size: auto;
  margin-inline: auto;
}

/* ── "Still need help?" ─────────────────────────────────────────────────── */
.mp-404__help {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin-block-start: var(--space-section-sm);
  text-align: center;
}
.mp-404__help-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 56px;
  block-size: 56px;
  margin-block-end: 8px;
  border-radius: var(--radius-pill);
  background: var(--c-white);
  color: var(--c-accent-deep);
}
.mp-404__help-title {
  margin: 0;
  font-size: var(--fs-card);
  font-weight: var(--fw-bold);
  line-height: var(--lh-heading);
  color: var(--c-primary);
}
.mp-404__help-body {
  margin: 0;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-ink-muted);
}
.mp-404__help-cta { margin-block-start: 16px; }

@media (max-width: 900px) {
  /* Stacked and centred, artwork after the CTAs — which is already the source
     order, so the single column needs no `order` rules. */
  .mp-404__inner { grid-template-columns: minmax(0, 1fr); }
  .mp-404__copy { text-align: center; }
  .mp-404__body { margin-inline: auto; }
  .mp-404__actions { justify-content: center; }
}

@media (max-width: 768px) {
  .mp-404 { padding-block: calc(108px + 24px) var(--space-section); }
  /* Full-width, stacked, in the order the mockup shows them. */
  .mp-404__actions { flex-direction: column; align-items: stretch; }
  .mp-404__cta { inline-size: 100%; }
  /* The mockup turns the help block into a card on a phone. */
  .mp-404__help {
    padding: 24px;
    border-radius: var(--radius-md);
    background: var(--c-white);
    box-shadow: var(--shadow-nav);
  }
  .mp-404__help-icon { background: var(--c-surface-tint); }
}

@media (prefers-reduced-motion: reduce) {
  .mp-404__cta { transition: none; }
  .mp-404__cta:not(.mp-link--disabled):hover { transform: none; }
}
