/* ==========================================================================
   SEO service pages — /local-authority-visibility-seo/ and /roseville-seo/

   One stylesheet for two routes on purpose. The live WordPress pages use the
   same layout; only the copy differs. The hub carries the full service
   explanation, the Roseville page carries the local market. Giving each its own
   sheet would be a copy of this file with the namespace renamed.

   Every value resolves to a token from tokens.css. The only literals are
   color-mix() overlays built on existing tokens, because a scrim has no token.

   Namespace: .seo-*   Page classes: .lav (hub), .rvs (Roseville)

   Surface: dark hero, light body, dark close — matching live. tokens.css
   carries the light half of the palette (--paper / --ink), and /medspa-os/
   established that only `main` goes light; the Reviews marquee and Footer that
   PageShell appends stay dark so the page ends by returning to the site.
   ========================================================================== */

.lav main,
.rvs main {
  background: var(--paper);
  color: var(--ink);

  --seo-shell: 1180px;
  --seo-band: clamp(4.5rem, 9vw, 8rem);
  --seo-dark-card: color-mix(in srgb, var(--warm-white) 4%, var(--black));
  --seo-dark-line: color-mix(in srgb, var(--warm-white) 10%, transparent);
}

.seo-shell {
  width: min(100% - (var(--gutter) * 2), var(--seo-shell));
  margin-inline: auto;
}

/* --- Bands --------------------------------------------------------------- */
.seo-band {
  padding-block: var(--seo-band);
  position: relative;
}

.seo-band--light {
  background: var(--paper);
  color: var(--ink);
  --seo-fg: var(--ink);
  --seo-fg-muted: var(--ink-muted);
  --seo-rule: var(--line);
  --seo-card: var(--white);
  --seo-accent: var(--purple);
}

.seo-band--dark {
  background: var(--black);
  color: var(--warm-white);
  --seo-fg: var(--warm-white);
  --seo-fg-muted: var(--muted);
  --seo-rule: var(--seo-dark-line);
  --seo-card: var(--seo-dark-card);
  /* #6119AC is ~2:1 on near-black; the accent has to lighten on dark bands or
     the eyebrows fail contrast. */
  --seo-accent: var(--purple-strong);
}

/* --- Type ---------------------------------------------------------------- */
.seo-eyebrow {
  font-family: var(--font-display);
  font-weight: 600;
  font-stretch: var(--wdth-display);
  font-size: var(--step--1);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--seo-accent);
  margin: 0 0 var(--sp-3);
}

.seo-h1 {
  font-family: var(--font-display);
  font-weight: 900;
  font-stretch: var(--wdth-display);
  font-size: var(--h1-hero);
  line-height: 0.98;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  margin: 0 0 var(--sp-3);
  color: var(--warm-white);
}

.seo-h2 {
  font-family: var(--font-display);
  font-weight: 900;
  font-stretch: var(--wdth-display);
  font-size: var(--step-5);
  line-height: 1.05;
  letter-spacing: -0.015em;
  margin: 0 0 var(--sp-3);
  color: var(--seo-fg);
  text-wrap: balance;
}

/* The live pages accent one or two words of each heading in purple. */
.seo-h2 em {
  font-style: normal;
  color: var(--seo-accent);
}

.seo-h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-stretch: var(--wdth-display);
  font-size: var(--step-1);
  line-height: 1.2;
  margin: 0 0 var(--sp-2);
  color: var(--seo-fg);
}

.seo-p {
  font-size: var(--step-0);
  line-height: 1.65;
  color: var(--seo-fg-muted);
  margin: 0 0 var(--sp-3);
  max-width: var(--measure);
}

.seo-center {
  text-align: center;
}
.seo-center .seo-p {
  margin-inline: auto;
}

/* --- Buttons ------------------------------------------------------------- */
.seo-btn {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 700;
  font-stretch: var(--wdth-display);
  font-size: var(--step-0);
  padding: 1rem 2.25rem;
  border-radius: var(--radius);
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

.seo-btn--ghost {
  color: var(--white);
  border: 1px solid var(--white);
  background: transparent;
}
.seo-btn--ghost:hover {
  background: var(--white);
  color: var(--black);
}

.seo-btn--outline {
  color: var(--purple);
  border: 1px solid var(--purple);
  background: transparent;
}
.seo-btn--outline:hover {
  background: var(--purple);
  color: var(--white);
}

.seo-btn--solid {
  color: var(--white);
  background: var(--purple);
  border: 1px solid var(--purple);
}
.seo-btn--solid:hover {
  background: var(--purple-glow);
  border-color: var(--purple-glow);
}

.seo-cta-row {
  margin-top: var(--sp-5);
}
.seo-center .seo-cta-row {
  text-align: center;
}

/* --- Hero ---------------------------------------------------------------- */
/* The hero is a dark surface but not a .seo-band, so it has to declare the same
   locals the band modifiers do. Without this, `color: var(--seo-accent)` on the
   eyebrow is an invalid computed value and inherits --ink from `main` instead —
   near-black type on a dark hero, measured at 1.27:1. */
.seo-hero {
  --seo-fg: var(--warm-white);
  --seo-fg-muted: var(--muted);
  --seo-rule: var(--seo-dark-line);
  --seo-card: var(--seo-dark-card);
  --seo-accent: var(--purple-strong);

  position: relative;
  min-height: clamp(560px, 80vh, 820px);
  display: grid;
  place-items: center;
  padding-block: calc(var(--nav-h) + var(--sp-6)) var(--sp-7);
  overflow: hidden;
  background: var(--black);
  isolation: isolate;
  text-align: center;
}

.seo-hero__media,
.seo-hero__media video,
.seo-hero__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.seo-hero__media {
  z-index: -2;
}

.seo-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--black) 84%, transparent),
    color-mix(in srgb, var(--black) 62%, transparent) 45%,
    color-mix(in srgb, var(--black) 90%, transparent)
  );
}

/* The photo hero (/roseville-seo/) needs a heavier scrim than the video one.
   The Roseville backdrop is bright daylight — open sky and pale pavement —
   where the tech loop it replaced was almost black to begin with. Reusing the
   video scrim would leave white type sitting on a light sky.

   Balanced deliberately: dark enough that the headline clears WCAG AA against
   the lightest part of the frame, light enough that the place is still
   recognisable, which is the only reason the photo is there. */
.seo-hero--photo::after {
  background:
    linear-gradient(
      to bottom,
      color-mix(in srgb, var(--black) 88%, transparent),
      color-mix(in srgb, var(--black) 72%, transparent) 45%,
      color-mix(in srgb, var(--black) 92%, transparent)
    ),
    color-mix(in srgb, var(--black) 30%, transparent);
}

.seo-hero__inner {
  position: relative;
  max-width: 62rem;
}

.seo-hero__lede {
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--warm-white);
  max-width: 46rem;
  margin: 0 auto var(--sp-5);
}

/* --- Split --------------------------------------------------------------- */
.seo-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
}

.seo-split--reverse .seo-split__media {
  order: -1;
}

.seo-split__copy .seo-p {
  max-width: 34rem;
}

.seo-split__media img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius-lg);
  border: 1px solid var(--seo-rule);
}

.seo-stack {
  display: grid;
  gap: var(--sp-2);
}

/* --- Callout ------------------------------------------------------------
   The live page floats a dark card over the section beneath it. Kept as a
   plain dark panel: the overlap is decorative, and at narrow widths it makes
   the two blocks collide rather than layer. */
.seo-callout {
  background: var(--black);
  color: var(--warm-white);
  border-radius: var(--radius-lg);
  padding: clamp(1.75rem, 4vw, 3rem);
  --seo-fg: var(--warm-white);
  --seo-fg-muted: var(--muted);
  --seo-accent: var(--purple-strong);
}

.seo-callout .seo-p:last-child {
  margin-bottom: 0;
}

/* --- Process track ------------------------------------------------------
   Four stages down a single spine: an oversized ghost numeral in the left
   rail, the stage copy in the right column, and each month's Search Console
   figures as a KPI row with a meter beneath it.

   The numbers replaced four full-width screenshots. Form follows the data's
   job — a handful of headline figures is a KPI row of stat tiles, not a chart,
   and one month's impressions against the four-month peak is a meter, not a
   four-bar chart.

   Purple appears only on the spine nodes and the meter fill. Values and labels
   wear ink tokens: text carries the reading, colour carries the magnitude. */
.seo-track {
  list-style: none;
  padding: 0;
  margin: var(--sp-7) 0 0;
  position: relative;
}

/* Three columns: the numeral rail, the stage copy, and the Search Console
   capture the figures were read from. The capture sits beside the numbers
   rather than under them — at full width each one ran about a screen and a
   half, which is what the redesign was fixing, but at this size it fills the
   right half of the row that was otherwise empty and puts the receipt next to
   the claim. */
.seo-track__step {
  display: grid;
  grid-template-columns: clamp(84px, 11vw, 148px) minmax(0, 1fr) clamp(190px, 20vw, 264px);
  gap: clamp(1.5rem, 3.5vw, 3rem);
  align-items: start;
  padding-block: clamp(2.25rem, 4vw, 3.5rem);
  border-top: 1px solid var(--line);
}

.seo-track__step:last-child {
  border-bottom: 1px solid var(--line);
}

/* The rail carries the spine and the numeral. The spine is drawn as a
   pseudo-element rather than a border so it can run edge to edge through the
   row padding and read as one continuous line down all four stages. */
.seo-track__rail {
  position: relative;
  align-self: stretch;
  display: flex;
  justify-content: flex-end;
  padding-right: clamp(1rem, 2vw, 2rem);
}

.seo-track__rail::before {
  content: "";
  position: absolute;
  right: 0;
  top: calc(clamp(2.25rem, 4vw, 3.5rem) * -1);
  bottom: calc(clamp(2.25rem, 4vw, 3.5rem) * -1);
  width: 1px;
  background: var(--line);
}

/* The node. Sits on the spine at the numeral's optical centre. */
.seo-track__rail::after {
  content: "";
  position: absolute;
  right: 0;
  top: 0.9em;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--purple);
  transform: translateX(50%);
  box-shadow: 0 0 0 5px var(--paper);
}

/* Oversized, weight 900, and deliberately low contrast: the numerals are
   texture and rhythm, not information — the stage titles carry that. Kept as
   a light ink tint rather than a purple wash so the accent stays scarce. */
.seo-track__num {
  font-family: var(--font-display);
  font-weight: 900;
  font-stretch: var(--wdth-display);
  font-size: clamp(2.75rem, 6vw, 4.5rem);
  line-height: 1;
  letter-spacing: -0.05em;
  color: color-mix(in srgb, var(--ink) 14%, var(--paper));
  user-select: none;
}

.seo-track__month {
  font-family: var(--font-display);
  font-weight: 600;
  font-stretch: var(--wdth-display);
  font-size: var(--step--2);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--purple);
  margin: 0 0 var(--sp-1);
}

.seo-track__title {
  font-family: var(--font-display);
  font-weight: 900;
  font-stretch: var(--wdth-display);
  font-size: var(--step-3);
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 0 0 var(--sp-2);
}

.seo-track__body .seo-p {
  margin-bottom: var(--sp-4);
  max-width: 46ch;
}

/* --- KPI row ------------------------------------------------------------- */
/* dt/dd sit directly in the <dl> so all three values share grid row 1 and all
   three labels share row 2. The obvious alternative — a wrapper per pair with
   column-reverse — breaks the moment one label wraps to two lines: that cell
   gets taller and its figure floats above the other two. Sharing the rows keeps
   the figures on one baseline whatever the labels do. */
.seo-kpi {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: auto auto;
  column-gap: var(--sp-3);
  row-gap: 0.2rem;
  margin: 0 0 var(--sp-3);
  max-width: 34rem;
}

.seo-kpi dt {
  grid-row: 2;
  font-size: var(--step--2);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin: 0;
  align-self: start;
}

/* Proportional figures, not tabular: these are standalone display values, and
   tabular-nums gives every digit the width of a zero, which reads loose at
   this size. Tabular is for columns that must align. */
.seo-kpi dd {
  grid-row: 1;
  font-family: var(--font-display);
  font-weight: 900;
  font-stretch: var(--wdth-display);
  font-size: var(--step-3);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--ink);
  margin: 0;
  align-self: end;
}

/* --- Meter ---------------------------------------------------------------
   One ratio against a limit: this month's impressions as a share of the
   four-month peak. Track is a lighter step of the same hue as the fill, so the
   bar reads as one object. Square at the baseline, 4px rounded at the data
   end. Decorative — the figure above it is the real reading — so it is hidden
   from assistive tech. */
.seo-meter {
  height: 6px;
  border-radius: 3px;
  background: var(--purple-soft);
  overflow: hidden;
  max-width: 34rem;
}

/* The width is the datum and never changes at runtime; the growth is a
   transform, so nothing here triggers layout. Animating `width` would be both
   janky and pointless — there is no state that would move it. */
.seo-meter__fill {
  display: block;
  height: 100%;
  background: var(--purple);
  border-radius: 0 3px 3px 0;
  transform-origin: left center;
}

/* The bars grow as each stage scrolls in, which is the one place motion earns
   its place here: the ramp from a sliver to a full bar is the argument the
   section is making.

   From-state is gated on .has-motion, which motion.js adds only when the
   visitor has not asked for less motion. Without it — no JS, or reduced motion
   — the bars are simply drawn at their final width, and motion.js adds .is-in
   to every [data-stage] immediately as belt and braces. */
.has-motion .seo-track__step[data-stage] .seo-meter__fill {
  transform: scaleX(0);
  transition: transform var(--dur-slow) var(--ease-out);
}

.has-motion .seo-track__step[data-stage].is-in .seo-meter__fill {
  transform: scaleX(1);
}

/* Impressions as a share of the 52.6K peak: 947, 14.8K, 21.3K, 52.6K. Set here
   rather than as inline style attributes so every value stays in the
   stylesheet. */
.seo-track__step:nth-child(1) .seo-meter__fill {
  width: 1.8%;
}
.seo-track__step:nth-child(2) .seo-meter__fill {
  width: 28.1%;
}
.seo-track__step:nth-child(3) .seo-meter__fill {
  width: 40.5%;
}
.seo-track__step:nth-child(4) .seo-meter__fill {
  width: 100%;
}

/* --- Stage figure --------------------------------------------------------
   The capture is decorative in the accessibility sense: every number in it is
   already in the KPI row as text and the caption names the source, so an alt
   description would only make a screen reader read the same figures twice.
   The visible caption carries the provenance.

   A 1px border rather than a shadow — the screenshots are near-white on a
   near-white band, so they need an edge, and the brand system uses clean
   borders instead of shadows. */
.seo-track__figure {
  margin: 0;
  align-self: start;
}

.seo-track__figure img {
  width: 100%;
  height: auto;
  display: block;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--white);
}

.seo-track__cap {
  margin-top: 0.6rem;
  font-size: var(--step--2);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.seo-track__source {
  margin: var(--sp-4) 0 0;
  font-size: var(--step--1);
  line-height: 1.6;
  color: var(--ink-muted);
  max-width: 62ch;
}

/* --- Bullet list --------------------------------------------------------- */
.seo-list {
  margin: 0 0 var(--sp-3);
  padding-left: 1.15rem;
  color: var(--seo-fg-muted);
  font-size: var(--step-0);
  line-height: 1.8;
  max-width: var(--measure);
}

.seo-list li::marker {
  color: var(--seo-accent);
}

/* --- Results gallery ----------------------------------------------------- */
.seo-gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-3);
  margin-top: var(--sp-6);
  list-style: none;
  padding: 0;
}

.seo-gallery--wide {
  grid-template-columns: repeat(2, 1fr);
}

.seo-gallery img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius-lg);
  border: 1px solid var(--seo-rule);
  background: var(--seo-card);
}

/* --- Cards --------------------------------------------------------------- */
.seo-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-3);
  margin-top: var(--sp-6);
  list-style: none;
  padding: 0;
}

.seo-card {
  background: var(--seo-card);
  border: 1px solid var(--seo-rule);
  border-radius: var(--radius-lg);
  padding: clamp(1.4rem, 2.8vw, 2rem);
}

.seo-card .seo-p {
  margin-bottom: 0;
  font-size: var(--step--1);
  max-width: none;
}

/* --- Proof (case studies) ------------------------------------------------ */
.seo-proof__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-3);
  margin-top: var(--sp-6);
  list-style: none;
  padding: 0;
}

.seo-proof__link {
  display: block;
  text-decoration: none;
  border: 1px solid var(--seo-rule);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--seo-card);
  height: 100%;
  transition: border-color var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}

.seo-proof__link:hover {
  border-color: var(--purple);
  transform: translateY(-3px);
}

.seo-proof__img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  display: block;
}

.seo-proof__body {
  display: block;
  padding: var(--sp-3);
}

.seo-proof__vertical {
  display: block;
  font-size: var(--step--2);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--seo-fg-muted);
  margin-bottom: var(--sp-1);
}

.seo-proof__jump {
  display: block;
  font-family: var(--font-display);
  font-weight: 900;
  font-stretch: var(--wdth-display);
  font-size: var(--step-2);
  color: var(--seo-accent);
  margin-bottom: var(--sp-1);
}

.seo-proof__title {
  display: block;
  font-size: var(--step--1);
  line-height: 1.45;
  color: var(--seo-fg);
}

.seo-proof__more {
  margin-top: var(--sp-4);
  text-align: center;
}
.seo-proof__more a {
  color: var(--seo-accent);
}

/* --- FAQ ----------------------------------------------------------------- */
.seo-faq__list {
  margin-top: var(--sp-5);
  border-top: 1px solid var(--seo-rule);
}

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

.seo-faq__q {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  cursor: pointer;
  font-family: var(--font-display);
  font-weight: 700;
  font-stretch: var(--wdth-display);
  font-size: var(--step-1);
  color: var(--seo-fg);
  list-style: none;
  text-align: left;
}

.seo-faq__q::-webkit-details-marker {
  display: none;
}

.seo-faq__mark {
  flex: none;
  color: var(--seo-accent);
  transition: transform var(--dur) var(--ease-out);
}

.seo-faq__item[open] .seo-faq__mark {
  transform: rotate(45deg);
}

.seo-faq__a {
  padding: 0 0 var(--sp-4);
  font-size: var(--step-0);
  line-height: 1.65;
  color: var(--seo-fg-muted);
  max-width: var(--measure);
  text-align: left;
}

/* --- Final CTA ----------------------------------------------------------- */
.seo-final {
  text-align: center;
  position: relative;
  overflow: hidden;
}

.seo-final::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 40%;
  width: min(1100px, 120vw);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: radial-gradient(
    circle,
    color-mix(in srgb, var(--purple-glow) 42%, transparent),
    transparent 62%
  );
  pointer-events: none;
}

.seo-final__inner {
  position: relative;
}

.seo-final__logo {
  width: 76px;
  height: 76px;
  margin: 0 auto var(--sp-4);
  display: block;
}

/* --- Responsive ---------------------------------------------------------- */
@media (max-width: 900px) {
  .seo-split,
  .seo-gallery,
  .seo-gallery--wide,
  .seo-cards {
    grid-template-columns: 1fr;
  }
  .seo-proof__grid {
    grid-template-columns: repeat(2, 1fr);
  }
  /* Copy first on small screens whatever the desktop order. */
  .seo-split--reverse .seo-split__media {
    order: 0;
  }
}

/* Below this the third column is too narrow for the capture to be legible, so
   it drops under the copy at a readable size instead of being squeezed. */
@media (max-width: 1080px) {
  .seo-track__step {
    grid-template-columns: clamp(84px, 11vw, 148px) minmax(0, 1fr);
  }
  .seo-track__figure {
    grid-column: 2;
    grid-row: 2;
    margin-top: var(--sp-3);
    max-width: 300px;
  }
}

@media (max-width: 760px) {
  /* The spine and the rail cost more width than they earn on a phone. The
     numeral moves inline above the stage title and the row goes single
     column, which keeps the sequence readable without the scaffolding. */
  .seo-track__step {
    grid-template-columns: 1fr;
    gap: var(--sp-2);
  }
  .seo-track__figure {
    grid-column: 1;
    grid-row: auto;
  }
  .seo-track__rail {
    justify-content: flex-start;
    padding-right: 0;
  }
  .seo-track__rail::before,
  .seo-track__rail::after {
    display: none;
  }
  .seo-track__num {
    font-size: var(--step-3);
  }
  .seo-kpi {
    column-gap: var(--sp-2);
  }
  .seo-kpi dd {
    font-size: var(--step-2);
  }
}

@media (max-width: 620px) {
  .seo-proof__grid {
    grid-template-columns: 1fr;
  }
  .seo-hero {
    min-height: 0;
  }
  .seo-btn {
    display: block;
    text-align: center;
  }
}

/* The meter needs no rule here: its from-state lives behind .has-motion, which
   motion.js only adds when reduced motion is not requested, so the bars are
   already drawn at full width in that case. */
@media (prefers-reduced-motion: reduce) {
  .seo-hero__media video {
    display: none;
  }
}
