:root {
  --ink: #000000;
  --bg: #ffffff;
  --ink-soft: rgba(0, 0, 0, 0.65);
  --bg-soft: rgba(255, 255, 255, 0.7);
  --accent: #000000;
  --border: rgba(0, 0, 0, 0.12);
  --hero-bg: #000000;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  color: var(--ink);
  background: var(--bg);
  line-height: 1.6;
}

/* The native cursor only hides once JS has actually built the custom one
   (see .has-custom-cursor in interactions.js). If the script fails to run,
   the native cursor stays put instead of leaving the user with none at all. */
body.has-custom-cursor { cursor: none; }

.container {
  max-width: 960px;
  margin: 0 auto;
  padding: 0 24px;
}

.container.narrow { max-width: 780px; }

/* Screen-reader-only text: visually hidden but still exposed to assistive
   tech, unlike aria-hidden content. Standard clip-rect technique. Used to
   give an accessible-text equivalent for content that sits next to a
   decorative aria-hidden="true" graphic (see .diagram usage in index.html). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

h1, h2, h3 { line-height: 1.1; margin: 0 0 16px; font-weight: 700; }
h1 { font-size: clamp(2.25rem, 5vw, 3.5rem); letter-spacing: -0.02em; }
h2 { font-size: clamp(1.75rem, 3vw, 2.25rem); margin-bottom: 32px; letter-spacing: -0.01em; }
h3 { font-size: 1.15rem; margin-bottom: 8px; }
p { margin: 0 0 16px; color: var(--ink-soft); }

section { padding: 120px 0; position: relative; scroll-margin-top: 100px; }

/* ---------- cursor ----------
   Both layers are white with mix-blend-mode: difference, so they invert
   against whatever's underneath (white on black, black on white) and stay
   visible on any section. A solid fill alone would go black-on-black. */
.cursor-dot, .cursor-ring {
  position: fixed;
  top: 0; left: 0;
  border-radius: 50%;
  background: var(--bg);
  pointer-events: none;
  z-index: 9999;
  mix-blend-mode: difference;
}
.cursor-dot {
  width: 8px; height: 8px;
  transform: translate(-50%, -50%);
  transition: opacity 0.25s var(--ease);
}
.cursor-ring {
  width: 34px; height: 34px;
  background: transparent;
  border: 1.5px solid var(--bg);
  transform: translate(-50%, -50%);
  transition: width 0.3s var(--ease), height 0.3s var(--ease), opacity 0.25s var(--ease);
}
.cursor-ring.-hover {
  width: 56px; height: 56px;
}
.cursor-dot.-hover { opacity: 0; }
@media (hover: none), (pointer: coarse) {
  .cursor-dot, .cursor-ring { display: none; }
}

/* ---------- fixed "Menu" nav ----------
   The nav is a floating black pill reading "Menu" that expands into an
   oversized-type panel on tap/click. The .nav element itself is just the
   fixed positioning wrapper (no background); .nav-toggle is the visible pill
   and .nav-menu is the panel. Visibility (scrolled past the hero) and open
   state are separate concerns: `-visible` shows the pill, `-open` expands the
   panel, and interactions.js keeps tabIndex in lockstep with both so keyboard
   users can never tab into something that isn't on screen. */
.nav {
  position: fixed;
  top: 20px;
  left: 50%;
  transform: translate(-50%, -8px);
  z-index: 500;
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.nav.-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* .nav itself never receives pointer events, even when -visible: it's a
   flex column wrapping .nav-toggle and .nav-menu, and .nav-menu keeps its
   layout space (visibility: hidden preserves box size, unlike display:
   none) even while closed. That reserved dead space used to sit inside
   .nav's own pointer-events:auto box, silently swallowing clicks on any
   page content underneath it. .hero.-compact packs its button close enough
   to the top that this ate the button entirely on assessment.html and
   services.html. Each interactive piece now opts back in individually:
   .nav-toggle here, .nav-menu via its own `.nav.-open .nav-menu` rule. */
.nav-toggle {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  pointer-events: auto;
  background: var(--ink);
  color: var(--bg);
  border: 0;
  border-radius: 999px;
  padding: 13px 24px;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: transform 0.3s var(--ease);
}
.nav-toggle:hover { transform: translateY(-2px); }

/* two bars that cross into an X when the menu is open */
.nav-toggle-icon {
  position: relative;
  width: 15px;
  height: 9px;
  display: inline-block;
}
.nav-toggle-icon span {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1.5px;
  background: var(--bg);
  transition: transform 0.3s var(--ease);
}
.nav-toggle-icon span:first-child { top: 0; }
.nav-toggle-icon span:last-child { bottom: 0; }
.nav.-open .nav-toggle-icon span:first-child { transform: translateY(3.75px) rotate(45deg); }
.nav.-open .nav-toggle-icon span:last-child { transform: translateY(-3.75px) rotate(-45deg); }

.nav-menu {
  margin-top: 10px;
  min-width: 264px;
  background: var(--ink);
  /* The panel is black and often opens over a black section (hero, statement,
     footer), so it needs its own edge or it reads as floating text with the
     page bleeding through. Hairline + shadow, still strictly monochrome. */
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5);
  border-radius: 24px;
  padding: 16px;
  display: flex;
  flex-direction: column;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(-10px) scale(0.97);
  transform-origin: top center;
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease), visibility 0s linear 0.35s;
}
.nav.-open .nav-menu {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateY(0) scale(1);
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease), visibility 0s;
}

.nav-menu a {
  color: var(--bg-soft);
  text-decoration: none;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  padding: 9px 12px;
  border-radius: 14px;
  white-space: nowrap;
  transform: translateY(-6px);
  transition: transform 0.25s var(--ease), color 0.2s var(--ease);
}
/* The cascade animates transform only — links are never hidden by opacity.
   The panel itself already handles show/hide, so if these transitions never
   run (blocked JS, odd renderer) the links are still fully legible rather
   than invisible. Same hide-by-default rule the .wipe/.reveal gate follows.
   Stagger lives entirely in this one rule so the shorthand can't reset its
   own delay (see the .reveal-stagger note in the design kit). */
.nav.-open .nav-menu a {
  transform: translateY(0);
  transition: transform 0.3s var(--ease) calc(var(--i, 0) * 55ms),
              color 0.2s var(--ease);
}
.nav-menu a:hover, .nav-menu a.-active { color: var(--bg); }

.nav-menu .nav-cta {
  margin-top: 12px;
  color: var(--ink);
  background: var(--bg);
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0;
  text-align: center;
  padding: 14px 20px;
  border-radius: 999px;
}
.nav-menu .nav-cta:hover { color: var(--ink); }

/* ---------- hero ---------- */
.hero {
  min-height: 100svh;
  display: flex;
  align-items: center;
  background: var(--hero-bg);
  color: var(--bg);
  text-align: center;
  padding: 72px 0;
}
.hero-logo { height: clamp(90px, 12vw, 140px); width: auto; margin-bottom: 32px; }
.hero h1 { color: var(--bg); max-width: 820px; margin-left: auto; margin-right: auto; }
.hero .subhead {
  max-width: 620px;
  margin: 0 auto 36px;
  font-size: 1.1rem;
  color: var(--bg-soft);
}

/* ---------- button ---------- */
.button {
  display: inline-block;
  background: var(--bg);
  color: var(--ink);
  padding: 16px 32px;
  border-radius: 999px;
  text-decoration: none;
  font-weight: 700;
  font-size: 0.95rem;
  transition: transform 0.25s var(--ease), background 0.25s var(--ease), color 0.25s var(--ease);
}
.button:hover { transform: translateY(-2px); }
.hero .button { background: var(--bg); color: var(--ink); }
section:not(.hero) .button { background: var(--accent); color: var(--bg); }

/* ---------- curtain wipe wrapper ----------
   Default state (no JS, or interactions.js failed/unsupported) is fully
   open/visible — the covering animation only exists once JS confirms it can
   run it, via the js-motion class on <html>. This is a progressive-
   enhancement gate, not a decoration: without it, content would be
   permanently hidden behind a black overlay if the script never runs. */
.wipe {
  position: relative;
  overflow: hidden;
}
.wipe::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--ink);
  transform-origin: top;
  transform: scaleY(0);
  z-index: 5;
  pointer-events: none;
}
html.js-motion .wipe::before {
  transform: scaleY(1);
}
/* Forward-only wipe: the transition lives only on the open (-revealed)
   state, never the base covered state. CSS starts a transition from the
   destination style, so wiping OPEN (scrolling down into a section)
   animates, while resetting back to covered (scrolling up / leaving view)
   has no transition and snaps instantly — no reverse animation. Scrolling
   back down re-adds -revealed and replays the forward wipe. */
html.js-motion .wipe.-revealed::before {
  transform: scaleY(0);
  transition: transform 0.9s var(--ease);
}

/* ---------- scroll reveal ----------
   Same progressive-enhancement gate as .wipe above: visible by default,
   only fades/slides once html.js-motion confirms the observer is running. */
.reveal {
  opacity: 1;
  transform: none;
}
html.js-motion .reveal {
  opacity: 0;
  transform: translateY(24px);
}
/* Forward-only, same principle as .wipe above: the transition lives only on
   the shown (-in) state, never the base hidden state. Revealing on scroll-down
   animates the fade/slide in; un-revealing on scroll-up has no transition and
   snaps to hidden instantly (no reverse animation). Scrolling back down re-adds
   -in and replays the forward reveal. */
html.js-motion .reveal.-in {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}
/* Stagger cascade. Scoped to the shown (-in) state and given enough
   specificity (js-motion + 3 classes) to beat the transition-delay:0 that
   the `transition` shorthand in .reveal.-in otherwise sets — without that,
   the delay lost the cascade and every child revealed at once. Gating on
   -in keeps the leaving direction instant, so forward-only still holds. */
html.js-motion .reveal-stagger > .reveal.-in { transition-delay: calc(var(--i, 0) * 90ms); }

/* ---------- sections ---------- */
.about { background: var(--bg); }

/* Intro paragraph sits above the (wide, non-narrow) .focus-grid container, so
   it's capped independently to stay readable instead of stretching to the
   grid's full 960px width. Reuses the documented narrow-container width
   (780px) rather than introducing a new one. margin-bottom reuses the grid's
   own 40px gap so the first card's icon doesn't sit tighter to the intro
   than the cards sit to each other (default <p> margin is only 16px). */
.work-intro { max-width: 780px; margin-bottom: 40px; }

.focus-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 40px;
}
.proof-item { margin-bottom: 32px; }
.proof-item:last-child { margin-bottom: 0; }

/* Founder photo + bio, two columns on desktop, stacked on mobile. The one
   exception to "no photography" on the site (see the design kit's
   Photography section) — used exactly once, here. */
.about-grid {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 40px;
  align-items: start;
}
.founder-photo-wrap { max-width: 200px; margin: 0 auto; }
.founder-photo {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  filter: grayscale(1);
  /* Same hexagon point path as .focus-icon / the logo mark, expressed as
     percentages of the image's own box. */
  clip-path: polygon(50% 8.33%, 86.25% 29.17%, 86.25% 70.83%, 50% 91.67%, 13.75% 70.83%, 13.75% 29.17%);
  border: 1px solid var(--border);
}
.about-text p:last-child { margin-bottom: 0; }

/* ---------- graphics ----------
   All site graphics are inline monochrome SVG: no raster images, no icon
   font, no external requests, and no photography (see the design kit). They
   inherit color via `currentColor` so the same markup works on light or dark
   sections. Keep any new graphic to stroke/fill in the existing tokens. */
.focus-icon {
  width: 34px;
  height: 34px;
  display: block;
  margin-bottom: 18px;
  color: var(--ink);
}

/* 3-vs-30 diagram under the "What we do" copy. Earns its place by making the
   site's central claim visual rather than decorating the page. */
.diagram { margin: 40px 0 0; }
.diagram svg {
  display: block;
  width: 100%;
  max-width: 420px;
  height: auto;
  color: var(--ink);
}
.diagram figcaption {
  margin-top: 14px;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

/* Nested-hexagon accent echoing the logo mark, bled off the edge of the
   statement band as texture. Deliberately near-invisible: it must never
   compete with the oversized type, which is still the section's whole point. */
.statement-accent {
  position: absolute;
  top: 50%;
  right: -70px;
  width: min(520px, 68vw);
  height: auto;
  transform: translateY(-50%);
  color: var(--bg);
  opacity: 0.15;
  pointer-events: none;
  z-index: 0;
}
.statement .container { position: relative; z-index: 1; }

/* Link out of the homepage focus grid to the services page. Focus areas are
   capabilities; services are what you buy. This line names that relationship
   so the two lists don't read as competing. */
.focus-link {
  margin-top: 44px;
  font-size: 0.95rem;
  color: var(--ink-soft);
}
.focus-link a { color: var(--ink); text-underline-offset: 3px; }

/* ---------- services page ----------
   services.html reuses style.css and interactions.js wholesale, per the
   kit's new-page rule. Only the pieces below are page-specific. */

/* Shorter hero band for interior pages: same black slab and white type as the
   homepage hero, without the full-viewport height that only the landing page
   earns. Still a .hero so the nav's scroll-past-the-hero trigger works. */
.hero.-compact {
  min-height: auto;
  padding: 150px 0 110px;
}

.service { padding: 64px 0; }
.service + .service { border-top: 1px solid var(--border); }
.service .button { margin-top: 20px; }
.service-icon {
  width: 34px;
  height: 34px;
  display: block;
  margin-bottom: 20px;
  color: var(--ink);
}
/* Eyebrow naming who the service is for. Uppercase + tracking is the one
   place small caps-style type is used; it keeps the "who" scannable without
   inventing a new heading size. */
.service-who {
  display: block;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 10px;
}
.service ul {
  margin: 20px 0 0;
  padding-left: 20px;
}
.service li {
  margin-bottom: 8px;
  color: var(--ink-soft);
  line-height: 1.6;
}
.service-format {
  margin-top: 20px;
  font-size: 0.9rem;
  color: var(--ink-soft);
}
.service-format strong { color: var(--ink); }

.availability { background: var(--bg); }

/* ---------- assessment page (assessment.html) ----------
   The self-scoring "four fundamentals read." Reuses .fundamental-def and
   the .rating/.scale pieces below only here; everything else (headings,
   .button, .proof-item for result blocks) is reused straight from the rest
   of the kit. */
.instrument, .results { background: var(--bg); }
.instruction { max-width: 620px; }

/* .results deliberately skips the site's default .wipe + .reveal-on-scroll
   entrance (see the design kit's "conventions for new sections" rule) because
   its reveal moment isn't a scroll position, it's a click. #results-output
   starts empty in the raw HTML, so this section collapses to zero padding
   by default -- true even with JS disabled, since the collapse depends only
   on DOM structure, not on any script running. The moment assessment.js
   appends result nodes, :has() picks it up and the section eases open to
   the normal section padding. Combined with #results-output's own
   .reveal-stagger (children get .reveal from assessment.js, staggered via
   the existing --i mechanism), the whole thing reads as the results panel
   opening and its content cascading in, using only CSS/JS patterns that
   already exist elsewhere on the site. */
.results {
  padding: 0;
  transition: padding 0.4s var(--ease);
}
.results:has(#results-output:not(:empty)) { padding: 120px 0; }

/* Fieldsets carry real semantics (grouped radios announced with their
   question) but none of the browser default chrome — border/padding are
   reset so a <fieldset> reads as a plain content block, same as every other
   section on the site. */
.fundamental, .rating {
  border: 0;
  padding: 0;
  margin: 0 0 40px;
}
.fundamental:last-child { margin-bottom: 0; }
.fundamental legend, .rating legend { padding: 0; }
.fundamental > legend h3 { margin-bottom: 8px; }
.fundamental-def { margin-bottom: 24px; }

/* Divider between consecutive fundamentals so the transition from one to the
   next is unambiguous — same pattern as .service + .service (border only on
   the adjacent sibling, so the first item never gets a stray rule above it),
   reusing the 40px spacing value already used elsewhere on this page instead
   of inventing a new one. */
.fundamental + .fundamental {
  border-top: 1px solid var(--border);
  padding-top: 40px;
}

/* Icon lives inside <legend>, before the h3, so <legend> stays the literal
   first child of the fieldset (required for it to be picked up as the
   fieldset's accessible name) while the icon still renders visually above
   the heading. Same 34px hexagon-vocabulary spec as .focus-icon /
   .service-icon, so the set reads as one family. */
.fundamental-icon {
  width: 34px;
  height: 34px;
  display: block;
  margin-bottom: 18px;
  color: var(--ink);
}

/* Each statement is its own nested fieldset so a screen reader announces
   the exact statement being rated, not just "radio button, 3 of 5." */
.rating {
  margin-bottom: 32px;
}
.rating:last-child { margin-bottom: 0; }
.rating legend {
  color: var(--ink);
  font-weight: 600;
  line-height: 1.5;
  margin-bottom: 16px;
}

/* 1-5 rating scale. Native radio inputs, not a custom control — keeps
   keyboard and screen reader behavior standard rather than reinvented.
   Only the two endpoints carry a text hint ("Never" / "Consistently"),
   matching the only two points the spec copy actually defines; 2-4 are
   left as plain numbers rather than inventing label text for them.
   Five equal grid columns keep the radios evenly spaced regardless of
   label width — a flex row sized each option by its own content, which
   pushed 1 and 5 (which carry the hint text) wide and bunched 2-4 in the
   middle. max-width caps it as a compact control rather than stretching
   across the text column on desktop. The hint text is pulled out of flow
   (absolute, under its option) so it never contributes to column width;
   padding-bottom on .scale reserves the vertical room it needs so it
   doesn't collide with the next question's legend.
   justify-items is stretch, not center: .scale-option needs to actually
   span its full 72px column, not shrink to the ~18px radio and sit
   centered inside it. .scale-hint's left:0/right:0 (below) resolves
   against .scale-option's own box, so a shrunk option puts those anchors
   9px from the radio instead of flush with the column's true edge —
   "Consistently" then reads as pulled toward the middle of the control
   instead of sitting under its own radio. Stretching the option to the
   full column, and centering its *contents* (radio, digit) with its own
   flex align-items, fixes the anchor without touching the even 72px radio
   spacing above. */
.scale {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  justify-items: stretch;
  max-width: 360px;
  margin: 0;
  padding-bottom: 22px;
}
.scale-option {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  font-size: 0.85rem;
  color: var(--ink-soft);
  cursor: pointer;
}
.scale-option input {
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--ink);
}
/* Centered under its own column by default. The endpoints (1 and 5) are
   anchored to the .scale box's own left/right edge instead of staying
   centered under their narrow end column, so the hint text grows inward
   rather than outward — it can never push past the control's own bounding
   box and cause horizontal overflow at narrow viewports. */
.scale-hint {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-top: 4px;
  font-size: 0.72rem;
  white-space: nowrap;
}
.scale-option:first-child .scale-hint { left: 0; transform: none; }
.scale-option:last-child .scale-hint { left: auto; right: 0; transform: none; }

.assessment-actions { margin-top: 40px; }
.assessment-actions .instruction { margin-top: 20px; }
.assessment-actions noscript p { color: var(--ink-soft); font-size: 0.9rem; }

/* Result blocks reuse .proof-item as-is (h3 + p, 32px stack spacing) rather
   than a new class, since the shape is identical. No separate closing
   block anymore -- the per-fundamental results used to end in a
   .result-closing wrapper (a "start with your lowest score" note plus its
   own Book a call button), removed at Brady's request since the site
   already has a Book a call CTA in the #book section right below. */

/* ---------- oversized type moment ---------- */
.statement {
  background: var(--ink);
  color: var(--bg);
  text-align: center;
}
.statement p {
  color: var(--bg);
  font-size: clamp(1.75rem, 4.5vw, 3.25rem);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.01em;
  max-width: 920px;
  margin: 0 auto;
}
.statement span { color: var(--bg-soft); }

/* ---------- "How this starts" numbered steps ----------
   Native <ol> markers, not a CSS counter() in ::before — generated content is
   inconsistently exposed to assistive tech, and the numerals need to be real,
   announced content per the design brief. Only the marker is restyled; the
   numbering itself stays the browser's own list semantics. */
.plan { background: var(--bg); }
.plan-steps {
  margin: 0;
  padding-left: 24px;
}
.plan-step { margin-bottom: 32px; }
.plan-step:last-child { margin-bottom: 0; }
.plan-step::marker {
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--ink);
}
.plan-step p { margin: 0; color: var(--ink-soft); }
.plan-step strong { color: var(--ink); }

.book { text-align: center; background: var(--bg); }

footer {
  padding: 40px 0;
  background: var(--hero-bg);
  color: var(--bg-soft);
  text-align: center;
}
footer p { margin: 0; font-size: 0.85rem; color: var(--bg-soft); }

@media (max-width: 600px) {
  section { padding: 72px 0; }
  .nav { top: 14px; }
  .nav-menu { min-width: min(80vw, 300px); }
  .nav-menu a { font-size: 1.35rem; }
  .about-grid { grid-template-columns: 1fr; gap: 24px; }
  .results:has(#results-output:not(:empty)) { padding: 72px 0; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .cursor-dot, .cursor-ring { display: none; }
  body.has-custom-cursor { cursor: auto; }
  html.js-motion .wipe::before,
  html.js-motion .wipe.-revealed::before { transition: none; }
  html.js-motion .reveal,
  html.js-motion .reveal.-in { transition: none; opacity: 1; transform: none; }
  .results { transition: none; }
  .nav, .nav-toggle, .nav-toggle-icon span { transition: none; }
  .nav-menu { transition: none; }
  .nav-menu a { transition: none; }
  .nav.-open .nav-menu a { transform: none; transition: none; }
}
