/*
Theme Name: Launch MSP
Theme URI: https://www.launchmsp.com
Author: Launch MSP
Description: Block theme for the Launch MSP marketing site. Ported from the
  Astro build. The colour system is surface-split by necessity - see the note
  below and docs/THEME-SPEC.md.
Version: 0.5.0
Requires at least: 6.6
Requires PHP: 7.4
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: launch-msp
*/

/* ===================================================================
   SEMANTIC ROLES - components reference these, NEVER brand hex.

   No single colour reaches WCAG AA against both white and near-black:
   the required luminance windows are <=0.1833 and >=0.1880 and do not
   overlap. That is arithmetic, not preference. So the palette is
   surface-split, and .on-dark remaps the roles below. Drop .on-dark on
   any dark Group and everything inside becomes compliant without
   knowing where it is.

   All ratios in comments are measured. See docs/THEME-SPEC.md.
   =================================================================== */
:root {
  --surface: #ffffff;
  --surface-alt: #eef2f7;
  --surface-tint: #f7f4fb;

  --text-heading: #17131f;      /* 18.27:1 on white */
  --text-body: #475467;         /*  7.69:1 */
  --text-muted: #667085;

  --accent-eyebrow: #c22f75;    /*  5.31:1 */
  --accent-link: #6d3db4;       /*  7.05:1 */
  --accent-icon: #8d5fd3;       /*  4.46:1 - LARGE / DECORATIVE ONLY */
  --status-ok: #137774;         /*  5.36:1 */

  --border: #dde3ec;
  --focus-ring: #6d3db4;

  --radius-card: 12px;
  --radius-panel: 20px;
  --radius-button: 8px;
  --measure: 68ch;

  --shadow-card: 0 1px 2px rgb(16 24 40 / 6%), 0 1px 3px rgb(16 24 40 / 10%);
  --shadow-card-hover: 0 4px 8px rgb(16 24 40 / 8%), 0 12px 24px rgb(16 24 40 / 10%);
}

.on-dark {
  --surface: #0b0718;
  --surface-alt: #27153f;

  --text-heading: #ffffff;      /* 19.85:1 on void */
  --text-body: #f7f4fb;         /* 18.90:1 */
  --text-muted: #b9b2c9;

  --accent-eyebrow: #eb639a;    /*  6.41:1 */
  --accent-icon: #8d5fd3;       /*  4.45:1 - LARGE / DECORATIVE ONLY */
  --status-ok: #1fb6b2;         /*  7.93:1 */

  --border: #2e2245;
  --focus-ring: #eb639a;

  background: var(--surface);
  color: var(--text-body);
}

.on-dark :is(h1, h2, h3, h4) { color: var(--text-heading); }
.on-dark a:not(.wp-block-button__link) { color: var(--accent-eyebrow); }

/* ------------------------------------------------------------ layout

   Sections are align:full so their background spans the viewport. theme.json
   sets useRootPaddingAwareAlignments with root padding, so a normal-width
   group has that padding applied to itself and its background stops short of
   the window edge - which renders as white gutters down both sides of every
   dark section. The inner `layout: constrained` still holds content to
   contentSize, so only the BACKGROUND goes full width. */
/* HALF the intended gap, top and bottom.
   The gap between two sections is clamp(4rem, 9vw, 8rem). Each section
   contributes half, so any two adjacent sections sum to exactly one gap.
   Applying the full value on both sides - the obvious reading - DOUBLES every
   gap on the site. tokens.css in the original build carries this same warning
   because the same mistake shipped there first. */
.section { padding-block: clamp(2rem, 4.5vw, 4rem); }

/* Sections own their rhythm through padding alone. WordPress also inserts
   --wp--style--block-gap (24px by default) between every pair of sibling
   blocks, which stacks on top and made each boundary ~25px wider than the
   design - enough to read as drift once it repeats down the page.
   Scoped to the top level so block gap still spaces paragraphs INSIDE a
   section, where it is doing useful work. */
.wp-block-post-content > * { margin-block: 0; }

/* Same treatment one level up. WordPress puts block gap between the top-level
   site blocks too - header, main, footer - which rendered as a ~24px WHITE
   STRIPE between the dark header and the dark hero, where the two should meet
   flush. Header and footer carry their own padding, so they lose nothing. */
.wp-site-blocks > * + * { margin-block-start: 0; }

/* THE STATS BAR BELONGS TO THE OPENING.
   A full-bleed dark band's bottom padding sits INSIDE its own background, so
   it contributes no visible white space. The half-and-half model breaks
   there: the section after the hero originally got 64px above it and 128px
   below - a 1:2 mismatch that read as the panel being nudged upward.

   Equalised DOWN rather than up. At 128px either side the stats panel floats
   as a standalone island; at 64px it reads as proof points attached to the
   pitch above, which is what it is. Everything further down the page keeps
   the full 128px section rhythm.

   Only the BOTTOM is overridden. The top already gets 64px from .section, and
   nothing above contributes because the hero's padding is inside the dark. The
   bottom goes to zero so the gap below is supplied entirely by the NEXT
   section's own 64px - set it to anything non-zero and the two stack, which is
   how this ended up at 128px in the first place.

   To make it standalone again, delete this rule and give the start
   clamp(4rem, 9vw, 8rem). */
.section.on-dark.alignfull + .section {
  padding-block-end: 0;
}

/* Belt and braces. A full-bleed section must never be capped by an ancestor's
   content width - that is what happened when templates/page.html wrapped
   post-content in a SECOND constrained group and every dark band rendered
   exactly 1400px wide with white gutters. The template is fixed; this stops a
   future template edit reintroducing it silently. */
.wp-block-group.alignfull.section { max-width: none; }

/* Measure caps are in REM, never `ch`.
   --measure is 68ch, and ch scales with font size: 68ch at an H2's 2.75rem
   overflows the container (heading renders flush left) while 68ch at 1rem is
   ~700px (paragraph renders centred and indented). One rule, opposite results,
   and it read as a bug because it was one. */
/* LEFT-ALIGNED CONTENT INSIDE A CONSTRAINED LAYOUT — USE A WRAPPER.

   WordPress's constrained layout gives EVERY direct child:

     :where(.is-layout-constrained) > :where(:not(.alignleft):not(.alignright):not(.alignfull)) {
       max-width: var(--wp--style--global--content-size);
       margin-left: auto !important;
       margin-right: auto !important;
     }

   Those auto margins are not centring a narrow child — they are what BUILDS
   the content column, for each child independently. Two consequences bit this
   theme in turn:

     1. A child with a SMALLER max-width centres itself inside the section,
        while its uncapped siblings fill the column. They visibly disagree.
     2. Forcing `margin-inline: 0 auto !important` to fix that removes the
        element from the column altogether and pins it to the section's
        padding edge — the viewport edge on a full-bleed section. Worse.

   The answer is neither: wrap the content in a DEFAULT-layout group.
   .hero-copy and .panel are constrained children of .section, so they get the
   content column; inside them WordPress sets no margins at all, so a
   max-width left-aligns exactly the way it would anywhere else.

   .head is the deliberate exception — it stays constrained and centres. */
.hero-copy > p,
.panel > p:not(.eyebrow) {
  max-width: 46rem;
  line-height: 1.6;
}

/* The hero summary is a lead paragraph; panel copy steps down, because a panel
   is a card rather than a page section. */
.hero-copy > p,
.hero-summary { font-size: 1.125rem; line-height: 1.6; }

/* Mobile: a long hero summary at 1.125rem/1.6 runs to a dozen lines on a
   390px screen and reads as a wall of text. Stepping the size and leading
   down recovers several lines without touching the copy.
   This only works because the paragraph carries a CLASS rather than a
   fontSize preset - a preset would beat it with !important. */
@media (width < 48rem) {
  .hero-copy > p,
  .hero-summary { font-size: 1rem; line-height: 1.5; }

  /* Long uppercase eyebrows wrap to two lines at 1rem + 0.12em tracking. */
  .eyebrow { font-size: 0.875rem; letter-spacing: 0.1em; }
}
.panel > p:not(.eyebrow) { font-size: 1rem; }

.hero-copy > h1 { max-width: 20ch; }

/* Centred head: eyebrow + H2 + intro as ONE unit, matching the Astro original.
   As three loose siblings they each centre against their own width and drift
   out of alignment - see the note above. */
.head {
  max-width: 48rem;
  margin-inline: auto;
  margin-block-end: clamp(2.5rem, 5vw, 3.5rem);
  text-align: center;
}

.head > p:not(.eyebrow) {
  max-width: none;
  margin-block-start: 1rem;
  font-size: 1.125rem;
}

/* PANELS ARE INSET, NOT BANDS.
   statsBar, featurePanel and finalCta are not full-bleed dark sections in the
   original. Each is a light .wrap at content width holding a rounded panel
   that carries the surface, the radius and its own padding. Rendering them as
   edge-to-edge bands was a deviation. Their section keeps content width; only
   the hero is a true full-bleed dark band.

   .panel-wrap needs no padding override - it takes the standard half-gap
   like every other section. */

/* The one exception: extra breathing room before the footer, matching
   FinalCta.astro's `padding-block: var(--section-pad) var(--section-space)`. */
.section.cta-wrap { padding-block: clamp(2rem, 4.5vw, 4rem) clamp(4rem, 9vw, 8rem); }

.panel {
  padding: clamp(2rem, 4vw, 3.25rem);
  border-radius: var(--radius-panel);
}
/* .on-dark supplies the dark background by remapping --surface. */

/* Type inside a panel steps down from section scale - it is a card, not a
   page section. FeaturePanel.astro sets .copy h2 to --text-2xl. */
.panel > h2 {
  max-width: 46rem;
  font-size: clamp(1.5rem, 1vw + 1.25rem, 2rem);
}

.panel > p:not(.eyebrow) {
  max-width: 46rem;
  font-size: 1rem;
  line-height: 1.65;
}

/* Stats: a mist-grey panel with a hairline, items divided by vertical rules
   and centred - no card chrome on the items themselves. */
.stats-panel {
  padding: clamp(1.75rem, 3vw, 2.5rem);
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius-panel);
}

.stats-panel .grid { gap: clamp(1.5rem, 3vw, 2rem); }

.stats-panel .card {
  padding: 0 1rem;
  text-align: center;
  background: none;
  border: 0;
  border-inline-start: 1px solid var(--border);
  border-radius: 0;
  box-shadow: none;
}

.stats-panel .card:first-child { border-inline-start: 0; }
.stats-panel .card:hover { transform: none; box-shadow: none; }
.stats-panel .icon { width: 1.6rem; height: 1.6rem; margin-inline: auto; }

/* The stat VALUE, not a card title. */
.stats-panel .card h3 {
  font-size: clamp(1.875rem, 2vw + 1.4rem, 2.75rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--text-heading);
}

.stats-panel .card p { margin-block-start: 0.35rem; font-size: 0.875rem; }

/* Vertical rules only read correctly while the row is intact. */
@media (width < 48rem) {
  .stats-panel .card { border-inline-start: 0; }
}

/* Consecutive dark sections should read as one continuous field rather than
   stacked bands. */
.on-dark + .on-dark { margin-block-start: 0; }

/* Kicker above a heading. It is meant to be smaller than body copy, but at
   0.875rem with only 0.08em tracking it read as shrunken body text rather
   than a deliberate label. Size up, and restore the 0.12em tracking the
   original used - wide tracking is what makes small uppercase look intended.

   The block markup must NOT set a fontSize attribute on these paragraphs.
   WordPress generates its preset utility classes with !important, so
   .has-small-font-size beats anything written here. */
.eyebrow {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--accent-eyebrow);
}

/* Large display text, DARK BACKGROUNDS ONLY. Both stops clear the 3:1
   large-text threshold on the void. Never use at body size. */
.gradient-text {
  background: linear-gradient(100deg, #8d5fd3 0%, #eb639a 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ------------------------------------------------------------- cards */
.grid { display: grid; gap: 1.25rem; align-items: stretch; }

@media (width < 48rem) {
  .grid { grid-template-columns: 1fr !important; }
}

.card {
  padding: 1.75rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  transition: box-shadow 140ms ease, transform 140ms ease;
}

.card:hover { transform: translateY(-2px); box-shadow: var(--shadow-card-hover); }
.on-dark .card { background: var(--surface-alt); border-color: var(--border); box-shadow: none; }
.card h3 { font-size: 1.125rem; margin-block-end: 0.6rem; }

/* 0.9375rem (15px), not 0.875. The Astro original used 14px, but its cards
   sat on a wider grid; at these widths 14px read as fine print. */
.card p { font-size: 0.9375rem; line-height: 1.55; color: var(--text-body); }
.card a { text-decoration: none; }
.card a:hover { text-decoration: underline; }

/* Emitted by the [icon name="..."] shortcode - see functions.php. The
   wrapping paragraph carries .icon-wrap so the icon sits flush above the
   card heading instead of inheriting paragraph spacing. */
.icon-wrap { margin: 0 0 1rem; line-height: 0; }

.icon {
  display: block;
  width: 2.25rem;
  height: 2.25rem;
  color: var(--accent-icon);
}
.icon svg { width: 100%; height: 100%; display: block; }

/* ----------------------------------------------------------- buttons */
.wp-block-button__link { border-radius: var(--radius-button); font-weight: 600; }

/* Purple-deep -> magenta so a white label clears 4.5:1 along the ENTIRE
   length (7.05 -> 5.31). The original mockups ran purple -> Logo Pink,
   where white lands at 3.10:1 and fails. Do not "restore" that. */
.is-style-fill .wp-block-button__link {
  background: linear-gradient(100deg, #6d3db4 0%, #c22f75 100%);
  color: #ffffff;
}

.is-style-outline .wp-block-button__link {
  background: transparent;
  border: 1px solid currentColor;
  color: var(--text-body);
}
.on-dark .is-style-outline .wp-block-button__link { color: var(--text-heading); }

/* --------------------------------------------------------------- a11y
   WCAG 2.4.7. An invalid selector once made the browser discard this
   rule entirely, removing the focus ring from the homepage cards - the
   site's primary navigation. Nothing warned. Verify by tabbing. */
:is(a, button, input, summary):focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .card, .card:hover { transform: none; transition: none; }
}

/* --------------------------------------------------------------- forms

   A FORM NEEDS ITS OWN MEASURE. Left at content width the inputs stretch to
   ~1400px, which reads as a wall of empty boxes and makes label-to-field
   tracking hard. .form-wrap is the same wrapper trick used by .hero-copy and
   .panel - a DEFAULT-layout group, so WordPress sets no margins on its
   children and a max-width simply applies. Do not try to narrow the form by
   putting a max-width on the shortcode's own group: inside a constrained
   layout those auto margins BUILD the column and would centre nothing.
   See the note above .hero-copy. */
.form-wrap {
  max-width: 40rem;
  margin-inline: auto;
}

/* The reminder under the form. Small, secondary, and deliberately not a
   warning box - it is guidance, not an error. */
.form-note {
  max-width: 40rem;
  margin-inline: auto;
  margin-block-start: 1.5rem;
  font-size: 0.9375rem;
  color: var(--text-muted);
}

/* PLUGIN-AGNOSTIC ON PURPOSE. These target elements inside the content area
   rather than any plugin's class names, so the form looks right with Fluent
   Forms, CF7 or anything else, and survives swapping one for another. No
   plugin stylesheet has to be fought or disabled.

   Everything references semantic roles, so a form dropped into a dark panel
   inherits .on-dark and stays compliant without knowing where it is. */
.wp-block-post-content :is(
  input[type="text"], input[type="email"], input[type="tel"],
  input[type="url"], input[type="number"], select, textarea
) {
  width: 100%;
  padding: 0.7rem 0.85rem;
  font: inherit;
  font-size: 1rem;                 /* 16px minimum - iOS zooms the page in on
                                      focus for anything smaller */
  color: var(--text-heading);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-button);
  transition: border-color 140ms ease, box-shadow 140ms ease;
}

.on-dark :is(input[type="text"], input[type="email"], input[type="tel"],
             input[type="url"], select, textarea) {
  background: var(--surface-alt);
  border-color: var(--border);
  color: var(--text-heading);
}

.wp-block-post-content :is(input, select, textarea):focus-visible {
  border-color: var(--accent-link);
  box-shadow: 0 0 0 3px rgb(109 61 180 / 18%);
  outline: none;                   /* replaced by the ring above, not removed */
}

.on-dark :is(input, select, textarea):focus-visible {
  border-color: var(--accent-eyebrow);
  box-shadow: 0 0 0 3px rgb(235 99 154 / 22%);
}

.wp-block-post-content textarea { min-height: 8rem; resize: vertical; }

.wp-block-post-content label {
  display: block;
  margin-block-end: 0.35rem;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--text-heading);
}

/* Helper text under a field - the "current setup, what is prompting the
   change" line does real work, so it needs to be legible rather than fine
   print. --text-muted is the lightest role that still clears AA. */
.wp-block-post-content .form-help {
  margin-block-start: 0.35rem;
  font-size: 0.875rem;
  color: var(--text-muted);
}

/* Submit buttons match the site's primary CTA, whatever the plugin calls
   them. The gradient runs purple-deep -> magenta so a white label clears
   4.5:1 along its whole length. */
.wp-block-post-content :is(button[type="submit"], input[type="submit"]) {
  padding: 0.8rem 1.5rem;
  font: inherit;
  font-weight: 600;
  color: #fff;
  background: linear-gradient(100deg, #6d3db4 0%, #c22f75 100%);
  border: 0;
  border-radius: var(--radius-button);
  cursor: pointer;
}

.wp-block-post-content :is(button[type="submit"],
                           input[type="submit"]):disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* A honeypot must be hidden from sight but NOT from assistive tech in a way
   that makes it focusable - if a screen-reader user tabs into it and types,
   their submission is silently discarded as spam. */
.wp-block-post-content .form-honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

@media (width < 48rem) {
  .wp-block-post-content :is(button[type="submit"], input[type="submit"]) {
    width: 100%;
  }
}

/* ---------------------------------------------------------------- art */
.hero-art img { border-radius: var(--radius-panel); height: auto; }

/* Hero art sits BESIDE the copy on desktop and stacks under it on mobile.
   The Astro original used mix-blend-mode: screen, which erases pure black
   exactly - that is what removes the pasted-rectangle look. It only works
   if the artwork is isolated on PURE black; art whose blacks sit around
   #0d0a1a lifts the whole plate. Tried twice, failed twice. */
.hero-cols .hero-art img { mix-blend-mode: screen; }

.panel-art img { border-radius: var(--radius-panel); height: auto; }

@media (width < 48rem) {
  .hero-cols, .panel-cols { flex-wrap: wrap; }
  .hero-cols > .wp-block-column,
  .panel-cols > .wp-block-column { flex-basis: 100% !important; }
}

/* ------------------------------------------------------------- brand
   Served from the theme rather than the media library - WordPress blocks
   SVG uploads by default because an SVG can carry JavaScript. See the note
   in parts/header.html. */
.brand { display: inline-flex; align-items: center; text-decoration: none; }
.brand img { display: block; width: auto; max-width: 180px; height: auto; }

.site-header { padding-block: 1.25rem; }
.header-bar { gap: 1rem; }
.header-right { gap: clamp(1rem, 2vw, 2rem); align-items: center; }

/* Nav links inherit the dark-surface roles from .site-header.on-dark. */
.site-header .wp-block-navigation { font-size: 0.9375rem; font-weight: 500; }
.site-header .wp-block-navigation a { text-decoration: none; }
.site-header .wp-block-navigation a:hover { text-decoration: underline; }

/* Smaller than a section CTA - it sits in a 1.25rem-padded bar. */
.header-cta .wp-block-button__link {
  padding: 0.6rem 1.1rem;
  font-size: 0.9375rem;
}

/* Below the nav's mobile breakpoint the button pushes the burger out of the
   top-right corner, where people reach for it.

   NEEDS THE EXTRA CLASS AND !important. WordPress adds .is-layout-flex to the
   buttons block and sets `display: flex` on it from global-styles, which is
   emitted AFTER the theme stylesheet. Equal specificity, later source order,
   so a plain `.header-cta { display: none }` silently lost.

   The mobile menu carries its own Contact entry instead - add a nav item with
   the class .nav-contact and it appears ONLY here, where the button does not.
   See NAVIGATION.md. */
@media (width >= 48rem) {
  /* Desktop already has the Contact Us button in the header bar, so a text
     link beside it would be a second route to the same page. */
  .wp-block-navigation .nav-contact { display: none; }
}

@media (width < 48rem) {
  .wp-block-buttons.header-cta { display: none !important; }

  /* EVERY button, not just the hero's.
     Buttons are sized for a wide dark band and read as oversized on a phone.
     A first pass scoped this to .hero-copy and .hero-cols, which missed the
     featurePanel and finalCta CTAs entirely - so "More About How We Work"
     stayed chunky while the hero shrank. Scope it to the element. */
  .wp-block-button__link {
    padding: 0.7rem 1.2rem;
    font-size: 0.9375rem;
  }

  /* FULL WIDTH, STACKED.
     Button labels here vary from "Contact Us" to "Request a Recovery
     Consultation", so at phone widths they sit side by side at wildly
     different widths, or wrap one under the other ragged. Stretching them
     gives a clean stack and a bigger tap target.

     Needs .is-layout-flex for specificity: WordPress sets flex-direction and
     alignment on that class from global-styles, emitted AFTER this file, so a
     bare .wp-block-buttons would lose on source order - the same thing that
     stopped the header CTA hiding. */
  .wp-block-buttons.is-layout-flex {
    flex-direction: column;
    align-items: stretch;
    gap: 0.75rem;
  }

  .wp-block-button { width: 100%; }

  .wp-block-button__link {
    display: block;
    width: 100%;
    text-align: center;
  }
}
.site-footer .wp-block-columns { gap: 2rem; }
.site-footer h3 { font-size: 1rem; margin-block: 1.5rem 0.75rem; }
.site-footer h3:first-child { margin-block-start: 0; }

.footer-links {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.9375rem;
}

.footer-links li { margin-block-end: 0.4rem; }
.footer-links a { text-decoration: none; color: var(--text-body); }
.footer-links a:hover { text-decoration: underline; }

/* Contact details sat at the inherited 1rem while every link beside them was
   0.9375rem, so the email and phone read as the largest thing in the footer -
   which they are not. Matched to the link list, and the underline dropped so
   they stop shouting. */
.footer-contact {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.9;
}

.footer-contact a { text-decoration: none; color: var(--text-body); }
.footer-contact a:hover { text-decoration: underline; }

.footer-social {
  display: inline-flex;
  gap: 0.5rem;
  align-items: center;
  margin-block-start: 1rem;
  font-size: 0.9375rem;
  text-decoration: none;
  color: var(--text-body);
}

.footer-social:hover { text-decoration: underline; }
.footer-social svg { width: 1.15rem; height: 1.15rem; flex: none; }
