/*
 * Kreativ HQ brand layer.
 *
 * Scope rule for this file: it may only set custom properties that GF Builder
 * already reads. It must never restyle a block by selecting its internals --
 * that is what forks a design system. If something here needs a new selector,
 * the right fix is usually a token in theme.json instead.
 */

/* ---------------------------------------------------------------------------
 * 0. Cascade layers.
 *
 * GF Builder's CSS is layered -- properties, theme, base, components,
 * utilities -- and this file is not. Unlayered CSS beats every layer at any
 * specificity, which is how section 45.3's white wash silently replaced the
 * Gradient band's background-image for weeks (worklist item 71).
 *
 * WHERE A RULE BELONGS. Generic grounds, washes, resets -- anything written
 * about "sections" or "headings" in general that happens to match GF markup --
 * go inside `/* The ground wash below is for WHITE bands only. It now also skips any band
   with a core Background colour (.has-background) or a photograph: over the
   brand-orange review band it faded the top 30% towards off-white, which read
   as a mistake, not a transition. */
@layer theme { ... }`, where GF's components win over them. A rule
 * that must beat GF on purpose stays unlayered and names what it overrides
 * (`.gf-section.is-style-dark.gf-decoration-grid`, not `.gf-section`).
 *
 * ALL FIVE NAMES, in this order. Repeating the statement is harmless wherever
 * this file loads; a four-name statement would be a trap if it ever loaded
 * first, because it would push Tailwind's `properties` layer to the top.
 * See the plugin's docs/cascade-and-layers.md, "Child themes".
 * ------------------------------------------------------------------------ */
@layer properties, theme, base, components, utilities;

/* ---------------------------------------------------------------------------
 * 1. The CTA button.
 *
 * GF's `secondary` button hard-codes its label to --gf-text-on-dark (white),
 * which is correct when `secondary` is a dark colour. Kreativ HQ's secondary IS
 * the brand orange (#FBB040), and white on that orange measures 2.1:1 -- it
 * fails WCAG AA at any size. Ink on the same orange measures 8.5:1.
 *
 * So the orange CTA keeps the brand colour and swaps the label to ink. This is
 * the one place the brand and the component library disagree, and the
 * accessible reading wins.
 * ------------------------------------------------------------------------ */
:is(a, button).gf-button-secondary {
  --gf-button-color: var(--wp--preset--color--primary);
  --gf-button-color-hover: var(--wp--preset--color--primary);
}

/* Same reasoning for filled chips and badges carrying the brand orange. */
.gf-chip-filled.gf-tone-secondary,
.gf-badge-filled.gf-tone-secondary {
  --gf-chip-color: var(--wp--preset--color--primary);
  --gf-badge-color: var(--wp--preset--color--primary);
}

/* ---------------------------------------------------------------------------
 * 2. Display typography.
 *
 * theme.json sets weight and tracking for all headings. Display sizes need
 * tighter tracking than heading sizes do -- at 4.5rem the same -0.02em reads
 * loose. Optical, not arithmetic.
 * ------------------------------------------------------------------------ */
.gf-heading.has-display-lg-font-size,
.gf-heading.has-display-xl-font-size {
  letter-spacing: -0.035em;
  line-height: 1.02;
}

.gf-heading.has-heading-xl-font-size {
  line-height: 1.12;
}

/* ---------------------------------------------------------------------------
 * 3. The brand rule.
 *
 * The logo lockup is a black wordmark over an orange bar. That bar is the one
 * piece of brand furniture worth carrying into the site, as a section-opening
 * accent rather than as decoration sprinkled everywhere.
 *
 * Applied by adding `khq-rule` to a heading or stack in the editor.
 * ------------------------------------------------------------------------ */
.khq-rule::before {
  content: "";
  display: block;
  width: 3.5rem;
  height: 0.3125rem;
  margin-block-end: var(--wp--preset--spacing--sm);
  background: var(--wp--preset--color--secondary);
}

.khq-rule.has-text-align-center::before {
  margin-inline: auto;
}

/* On dark sections the orange bar still reads; nothing to change. Kept as an
 * explicit no-op comment so the next person does not "fix" it. */

/* ---------------------------------------------------------------------------
 * 4. Link affordance.
 *
 * theme.json points links at `warning` (#A9640B), the only orange in the ramp
 * that clears 4.5:1 on white. Colour alone is never the affordance, so prose
 * links are underlined too.
 * ------------------------------------------------------------------------ */
.gf-text a:not(.gf-button),
.entry-content a:not(.gf-button) {
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.18em;
}

/* ---------------------------------------------------------------------------
 * 5. Scroll-reveal offset.
 *
 * motion.js reads --gf-scroll-offset and shrinks the observer's root box by it,
 * so a section does not count as "seen" while it is still behind the sticky
 * header. The value is the header height. GF exposes the hook; this just tells
 * it how tall our header is.
 * ------------------------------------------------------------------------ */
:root { --gf-scroll-offset: 76px; }

/* ---------------------------------------------------------------------------
 * 6. Mega-menu panel.
 *
 * Sets the panel's own column rhythm only. Everything visual -- surface,
 * elevation, radius -- comes from the navigation block's dropdownSurface token,
 * so the panel restyles with the rest of the site rather than drifting from it.
 * ------------------------------------------------------------------------ */
.khq-mega .gf-nav-mega__panel,
.khq-mega .gf-nav-group__panel {
  --gf-nav-mega-columns: 2;
}

@media (min-width: 62rem) {
  .khq-mega .gf-nav-mega__panel,
  .khq-mega .gf-nav-group__panel {
    --gf-nav-mega-columns: 3;
  }
}

/* ---------------------------------------------------------------------------
 * 7. Brand slot.
 *
 * The header ships wp:site-logo AND wp:site-title, because site-logo renders
 * nothing at all until a logo is set and an empty brand slot makes the header
 * impossible to judge. Once a real logo image exists, the text title hides
 * itself. The fallback removes itself; nobody has to remember to delete it.
 * ------------------------------------------------------------------------ */
.khq-brand {
  gap: var(--wp--preset--spacing--xs);
}

.khq-brand:has(img) .wp-block-site-title {
  display: none;
}

.khq-brand .wp-block-site-title a {
  color: currentColor;
  font-weight: 800;
  letter-spacing: -0.03em;
  text-decoration: none;
  text-transform: uppercase;
}

/* ---------------------------------------------------------------------------
 * 8. The CTA on a dark header.
 *
 * READ OFF 0.14.0's CSS, not off notes taken against 0.13.0 -- an earlier
 * version of this rule set --gf-nav-active-bg / --gf-nav-active-text, which are
 * the ACTIVE-nav-item variables. Every rule that reads those excludes the
 * button variant explicitly (`:not(.gf-nav-link-button)`), so the override was
 * inert. The button reads its own chain:
 *
 *     a.gf-nav-link-button {
 *       background: var(--gf-nav-button-bg);
 *       color:      var(--gf-nav-button-color);
 *     }
 *     --gf-nav-button-bg    -> --gf-button-primary-bg    -> --gf-color-primary
 *     --gf-nav-button-color -> --gf-button-primary-color -> --gf-text-on-dark
 *
 * Left alone that gives ink-on-ink: the CTA background resolves to
 * --gf-color-primary (#0B0D10), which is the same colour as the dark header it
 * sits on. Brand orange with ink text measures 8.5:1 against the bar.
 * ------------------------------------------------------------------------ */
.khq-nav-dark a.gf-nav-link-button {
  --gf-nav-button-bg: var(--wp--preset--color--secondary);
  --gf-nav-button-color: var(--wp--preset--color--primary);
  --gf-nav-button-bg-hover: var(--wp--preset--color--accent);
  --gf-nav-button-border: transparent;
  --gf-nav-button-border-hover: transparent;
}

/* The light header has the same collision in reverse -- ink CTA on a white bar
 * is legible but reads as "secondary button", not as the site's primary action.
 * Orange there too, so the CTA is the same object in both schemes. */
.khq-nav-light a.gf-nav-link-button {
  --gf-nav-button-bg: var(--wp--preset--color--secondary);
  --gf-nav-button-color: var(--wp--preset--color--primary);
  --gf-nav-button-bg-hover: var(--wp--preset--color--accent);
}

/* THE FILTER HACK IS GONE — there is a real reversed asset now.
 *
 * It was `grayscale(1) invert(1)`, which turned the black wordmark white but
 * flattened the orange to grey. That was a fair trade while the logo was a
 * placeholder; it is actively wrong now that #FBB040 is the brand. Inverting
 * the real mark would be worse still — invert() maps #FBB040 to a mid blue.
 *
 * Use kreativhq-logo-reversed.png as the site logo while the header is dark,
 * and kreativhq-logo.png if it ever goes light. */

/* ---------------------------------------------------------------------------
 * 9. Navigation link colour.
 *
 * MEASURED: every link in the header and footer navs computed to
 * rgb(11,13,16) -- ink on a black bar, invisible. The source is WordPress's
 * global styles:
 *
 *     a:where(:not(.wp-element-button)) { color: var(--wp--preset--color--primary) }
 *
 * `:where()` contributes ZERO specificity, but it is still a direct match on
 * the anchor, and GF's navigation styling sets the colour on the nav CONTAINER
 * and lets the links inherit. A direct match always beats an inherited value,
 * no matter how weak its specificity -- inheritance only applies when nothing
 * matches the element itself. So the global rule won every time.
 *
 * `color: inherit` puts the links back on the container's colour, which is
 * exactly what GF already sets per scheme:
 *
 *     .gf-navigation-scheme-dark { color: var(--gf-text-on-dark) }
 *
 * So this is scheme-agnostic by construction: white on the dark header, ink on
 * the light one, correct in the dark footer, and it needs no update if the
 * header scheme is switched again. The CTA is excluded because it sets its own
 * colour deliberately.
 * ------------------------------------------------------------------------ */
.gf-navigation a:not(.gf-nav-link-button) {
  color: inherit;
}

.gf-navigation a:not(.gf-nav-link-button):hover {
  color: inherit;
}

/* ---------------------------------------------------------------------------
 * 10. No block gap between the top-level regions.
 *
 * WordPress applies the global block gap (1.5rem here) between the children of
 * .wp-site-blocks -- so header / content / footer each sat 24px apart, showing
 * a white strip between the dark header and the dark hero that read as a
 * rendering fault rather than as spacing.
 *
 * Every top-level region on this site is a full-bleed section that owns its own
 * vertical padding, so the gap between them should be zero and the rhythm
 * should come from the sections themselves. Scoped to the direct children of
 * .wp-site-blocks, so block gap inside content is untouched.
 * ------------------------------------------------------------------------ */
.wp-site-blocks > * + * {
  margin-block-start: 0;
}

/* ---------------------------------------------------------------------------
 * 11. Admin-bar offset for the sticky header.
 *
 * GF Builder 0.14.4 sticks the template-part wrapper itself, which is what the
 * theme used to do here -- a sticky .gf-header inside a wrapper only as tall as
 * itself has no travel and scrolls away. That half is now the plugin's.
 *
 * What remains is the logged-in admin bar: it is fixed at the top, so an editor
 * sees the first row of nav underneath it. Visitors get the plugin's top: 0.
 * ------------------------------------------------------------------------ */
.admin-bar .wp-site-blocks > header.wp-block-template-part {
  top: 32px;
}

@media screen and (max-width: 782px) {
  .admin-bar .wp-site-blocks > header.wp-block-template-part {
    top: 46px;
  }
}

/* ---------------------------------------------------------------------------
 * 12. Full-height orange hero.
 * ------------------------------------------------------------------------ */
:root {
  --khq-header-h: 76px;
}

.khq-hero {
  min-height: calc(100svh - var(--khq-header-h));
  display: flex;
  align-items: center;
}

/* The container is the section's only child; it has to fill the flex line or
 * the centred content collapses to its own width. */
.khq-hero > * {
  width: 100%;
}

/* Ink on orange, not white. #FBB040 with white text is 2.1:1 and fails AA at
 * every size; with ink it is 8.5:1. Set on the section so every heading,
 * paragraph and the trailing location line inherit it together. */
.khq-hero,
.khq-hero .gf-heading,
.khq-hero .gf-text {
  color: var(--wp--preset--color--primary);
}

/* The outline button reads off currentColor on a dark band by default; on the
 * orange it needs to be explicitly ink or it inherits the on-dark white. */
.khq-hero a.gf-button-outline {
  --gf-button-border: var(--wp--preset--color--primary);
  --gf-button-color: var(--wp--preset--color--primary);
  --gf-button-bg-hover: var(--wp--preset--color--primary);
  --gf-button-color-hover: var(--wp--preset--color--light);
}

.khq-hero-media {
  max-width: 100%;
  height: auto;
}

/* On a laptop the admin bar eats 32px of the viewport; logged-out visitors get
 * the full height. */
.admin-bar .khq-hero {
  min-height: calc(100svh - var(--khq-header-h) - 32px);
}

@media screen and (max-width: 782px) {
  .admin-bar .khq-hero {
    min-height: calc(100svh - var(--khq-header-h) - 46px);
  }
}

/* ---------------------------------------------------------------------------
 * 13. The header stays solid at the top of the page.
 *
 * MEASURED: `.gf-header-scroll-compact:not(.gf-header-scrolled)` sets
 * `background: 0 0` — the compact effect is a transparent-until-scrolled
 * header. view.js only adds `.gf-header-scrolled` past 12px of scroll.
 *
 * That is a good effect over a photographic hero. Over this one it is wrong in
 * both directions: at rest the bar is transparent so the white wordmark sits
 * on #FBB040 at about 2.1:1 and the brand looks like it is fading out, and
 * then it visibly "pops" to solid black the instant you scroll — which is the
 * drop-off effect that looked like a bug.
 *
 * Keeping the padding shrink (the genuinely nice half of `compact`) and
 * dropping the transparency gives a solid dark bar against the orange from the
 * first frame.
 * ------------------------------------------------------------------------ */
.khq-nav-dark.gf-header-scroll-compact:not(.gf-header-scrolled) {
  background: var(--wp--preset--color--dark);
}

.khq-nav-light.gf-header-scroll-compact:not(.gf-header-scrolled) {
  background: var(--wp--preset--color--surface);
}

/* ---------------------------------------------------------------------------
 * 15. Pill buttons, site-wide.
 *
 * The button block has no shape attribute, but its CSS reads
 * `border-radius: var(--gf-button-radius, var(--gf-radius-md))`. Setting the
 * variable is therefore the supported route -- no selector overrides, and every
 * button everywhere follows, including ones added later in the editor.
 * ------------------------------------------------------------------------ */
:root {
  --gf-button-radius: 999px;
}

/* ---------------------------------------------------------------------------
 * 16. Hero: wider container, bigger headline, gradient ground.
 * ------------------------------------------------------------------------ */

/* The wide container caps at 86rem (~1376px). The hero is the one place that
 * should use the whole screen, because the media column is what suffers when
 * the row is narrow -- the illustration was being asked to fit half of 1376px.
 * Everything below the hero keeps the normal measure, so body copy stays
 * readable. */
/* Container-fluid: 90% of the viewport, no ceiling.
 *
 * The previous `min(1680px, 94vw)` still capped out on a wide display, and the
 * cap is what was holding the hero in -- the grid can only be as wide as the
 * container it sits in, so the media column kept getting half of a constrained
 * row rather than half of the screen.
 *
 * Root padding is zeroed here too. The theme sets a clamped inline padding on
 * every constrained block, which would eat into the 90% and quietly make it
 * ~84% at desktop widths. The 5% either side IS the gutter now.
 *
 * Hero only. Body copy at 90% of a 27" display is unreadable, so every other
 * section keeps the normal measure. */
.khq-hero .gf-container {
  max-width: none;
  width: 90%;
  margin-inline: auto;
  padding-inline: 0;
}

/* The grid inherits the container's width, but its own alignfull/wide handling
 * can re-impose a max-width; this makes sure it fills the fluid container. */
.khq-hero .gf-grid {
  max-width: none;
  width: 100%;
}

/* Modern caps display-xl at 3.75rem, which is right for interior pages and
 * undersized for the one headline on the site that has to carry the whole
 * proposition. Tighter leading and tracking as it grows -- large type needs
 * less of both. */
.khq-hero .gf-heading {
  font-size: clamp(2.75rem, 5.4vw, 4.75rem);
  line-height: 1.02;
  letter-spacing: -0.035em;
  max-width: 15ch;
}

.khq-hero .gf-text {
  max-width: 52ch;
}

/* Let the illustration use the full column rather than its intrinsic size. */
.khq-hero-media,
.khq-hero-media img {
  width: 100%;
  max-width: 100%;
  height: auto;
}

/* A warm ground rather than one flat orange. The light corner sits behind the
 * media so the illustration's white cards have something to lift off, and the
 * deeper corner sits under the text. Ink measures 11:1 on the lightest stop and
 * 6.5:1 on the darkest, so the headline is safe across the whole sweep. */
.khq-hero {
  background-color: var(--wp--preset--color--secondary);
  background-image:
    radial-gradient(120% 120% at 88% 12%, #FDD291 0%, rgb(253 210 145 / 0) 55%),
    linear-gradient(152deg, #FCC573 0%, #FBB040 52%, #E79A22 100%);
}

/* ---------------------------------------------------------------------------
 * 18. The four-band system.
 *
 * black / Kreativ orange / light grey / white. `is-style-dark` and
 * `is-style-muted` are GF's own, so only the orange band needs defining -- and
 * what it needs is the text colour, because GF has no on-orange scheme and
 * would otherwise inherit ink-on-orange for body and white-on-orange for
 * anything the dark scheme touches.
 *
 * Ink on #FBB040 is 8.5:1. White on it is 2.1:1 and fails AA, so the orange
 * bands are ink-on-orange throughout, deliberately, and the buttons invert to
 * ink-filled rather than the usual white outline.
 * ------------------------------------------------------------------------ */
.khq-band-orange,
.khq-band-orange .gf-heading,
.khq-band-orange .gf-text,
.khq-band-orange .gf-container {
  color: var(--wp--preset--color--primary);
}

.khq-band-orange a.gf-button-outline {
  --gf-button-border: var(--wp--preset--color--primary);
  --gf-button-color: var(--wp--preset--color--primary);
  --gf-button-bg-hover: var(--wp--preset--color--primary);
  --gf-button-color-hover: var(--wp--preset--color--light);
}

/* The closing band gets the same gradient ground as the hero, so the page opens
 * and closes on the same note rather than on two different oranges. */
.khq-close {
  background-image:
    radial-gradient(120% 120% at 12% 88%, #FDD291 0%, rgb(253 210 145 / 0) 55%),
    linear-gradient(152deg, #FCC573 0%, #FBB040 52%, #E79A22 100%);
}

.khq-close .gf-heading,
.khq-close .gf-text {
  max-width: 40ch;
}

/* ---------------------------------------------------------------------------
 * 19. The metric row.
 *
 * Numerals want tabular figures and tight tracking; at display size the default
 * proportional widths make a row of unrelated numbers look accidentally ragged.
 * ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------------------
 * 20. The service index.
 *
 * An editorial list rather than seven identical boxes. The rule above each
 * entry and the set-back number do the work a card border would, with less
 * furniture — which is most of the difference between "capable" and "upscale"
 * on a page like this.
 * ------------------------------------------------------------------------ */
.khq-index-num {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.12em;
  opacity: 0.55;
  margin-block-end: 0;
}

/* The 34 deliverables under each category. Deliberately quiet — it is there to
 * be scanned for a word you recognise, not read as a sentence. */
.khq-service-list {
  opacity: 0.72;
  line-height: 1.7;
}

/* ---------------------------------------------------------------------------
 * 21. Heading case — REVERSED 2026-09-11.
 *
 * `text-transform: capitalize` was here on the reasoning that casing is a
 * presentation decision. Two things killed it when the site was rebuilt as an
 * editable template:
 *
 * 1. CSS capitalize has no idea what a title is. It uppercases EVERY word, so
 *    "Everything a growing business needs, in one place" rendered as
 *    "Everything A Growing Business Needs, In One Place". Real title case
 *    lowercases articles, conjunctions and short prepositions, and no stylesheet
 *    can do that.
 *
 * 2. It broke WYSIWYG. An editor types sentence case and the canvas shows
 *    something else, which is the wrong trade for a site whose whole job is to
 *    be handed over and edited by someone who did not build it.
 *
 * Headings now render as written. Case is a copy decision, made in the copy.
 * ------------------------------------------------------------------------ */

/* Sentence-case is right where the text is a sentence, not a title. */
.khq-service-list,
.gf-text,
.gf-nav-link,
.gf-dropdown-item {
  text-transform: none;
}

/* ---------------------------------------------------------------------------
 * 22. Cards on a dark band.
 *
 * THIS IS WHY THE PROCESS SECTION LOOKED EMPTY. `.is-style-dark` sets the
 * section's colour to on-dark white, and a card with `backgroundColor: surface`
 * keeps its white background but INHERITS that white text. White on white —
 * the cards were rendering perfectly and were simply invisible.
 *
 * GF's own answer is `.gf-card.is-style-dark` (a dark card on a dark band). A
 * white card on black is the stronger look for a numbered process, so instead
 * the text colour is put back to ink for everything inside it.
 * ------------------------------------------------------------------------ */
/* Photo tiles (.gf-has-background-image) are excluded: their ground is the
   photograph under a dark mask, so they keep the band's light ink. Without the
   :not() every image card on a black band rendered black headings on a dark
   photo (SEO page, 90-day tiles, Sept 2026). */
.is-style-dark .gf-card:not(.gf-has-background-image),
.is-style-dark .gf-card:not(.gf-has-background-image) .gf-heading,
.is-style-dark .gf-card:not(.gf-has-background-image) .gf-text {
  color: var(--wp--preset--color--dark);
}

/* Same collision on the orange bands, where the roadmap panel is a white card. */
.khq-band-orange .gf-card:not(.gf-has-background-image),
.khq-band-orange .gf-card:not(.gf-has-background-image) .gf-heading,
.khq-band-orange .gf-card:not(.gf-has-background-image) .gf-text {
  color: var(--wp--preset--color--dark);
}

/* ---------------------------------------------------------------------------
 * 23. Service cards + roadmap panel.
 * ------------------------------------------------------------------------ */
.khq-service-card .gf-button {
  margin-block-start: auto;
}

.khq-roadmap-panel {
  max-width: 30rem;
  margin-inline-start: auto;
}

.khq-roadmap-panel .gf-button {
  width: 100%;
  justify-content: center;
  margin-block-start: var(--wp--preset--spacing--sm);
}

/* ---------------------------------------------------------------------------
 * 24. Footer alignment.
 *
 * The footer inherits centring from a couple of GF defaults (the nav list
 * centres itself when it has room, and the closing line sits in a constrained
 * container). A footer is a reference table, not a composition — everything
 * ranges left off one margin so the four columns scan as columns.
 *
 * Text that the author aligned in the editor (Align text → right, which core
 * prints as .has-text-align-*) is exempt: the bottom row's "Built with GF
 * Builder | Designed by KreativHQ" credit sits right, opposite the copyright,
 * in a 2-column grid. Measured 13 Nov 2026 before this exemption: the credit
 * carried has-text-align-right yet computed text-align:left, because this rule
 * (0,3,1) outranks core's :root .has-text-align-right (0,2,0).
 * Below 48rem the grid stacks to one column (gf-grid-mobile-1) and a
 * right-aligned line under a left one reads as a mistake, so it ranges left
 * again there. A per-breakpoint text alignment on gf-builder/text would delete
 * that media query (inbox new-gap 2026-11-13).
 * ------------------------------------------------------------------------ */
.wp-site-blocks > footer .gf-stack,
.wp-site-blocks > footer .gf-heading,
.wp-site-blocks > footer .gf-text:not([class*="has-text-align-"]),
.wp-site-blocks > footer .gf-navigation,
.wp-site-blocks > footer .gf-navigation__list,
.wp-site-blocks > footer .gf-nav-link {
  text-align: left;
  align-items: flex-start;
  justify-content: flex-start;
}

@media (max-width: 47.99rem) {
  .wp-site-blocks > footer .gf-text.has-text-align-right {
    text-align: left;
  }
}

.wp-site-blocks > footer .gf-navigation__list {
  flex-direction: column;
  gap: 0.35rem;
}

.wp-site-blocks > footer .gf-heading {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: var(--wp--preset--font-size--body-sm);
  opacity: 0.62;
}

/* ---------------------------------------------------------------------------
 * 25. Stat labels on a dark band.
 *
 * MEASURED: the value renders white at 52px and counts correctly; the LABEL
 * computes to rgb(11,13,16) on a black band. `gf-tone-light` sets the colour of
 * `.gf-stat__value` only — the label takes its colour from `.gf-stat`, which
 * resolves to ink. So the strip was rendering bare numerals with no captions,
 * which reads as "the numbers are broken" rather than "the labels are missing".
 * ------------------------------------------------------------------------ */
.is-style-dark .gf-stat,
.is-style-dark .gf-stat__label {
  color: var(--gf-text-on-dark, #ffffff);
}

/* The label is supporting text, not a second headline. */
.gf-stat__label {
  opacity: 0.68;
  max-width: 22ch;
  line-height: 1.4;
}

/* ---------------------------------------------------------------------------
 * 26. Unfilled cards.
 *
 * A white card on a black band is a heavy object — it stops reading as a card
 * and starts reading as a hole punched in the section. These carry no fill and
 * no border; a single hairline rule above each one does the separating, and the
 * band's own contrast does the rest.
 * ------------------------------------------------------------------------ */
.khq-card-plain {
  background: none;
  border: 0;
  box-shadow: none;
  padding-inline: 0;
  border-block-start: 1px solid rgb(255 255 255 / 0.18);
  padding-block-start: var(--wp--preset--spacing--md);
}

/* Undo the ink-on-card rule from section 22 — with no white fill beneath it,
 * the text needs to go back to the band's on-dark colour. */
.is-style-dark .khq-card-plain,
.is-style-dark .khq-card-plain .gf-heading,
.is-style-dark .khq-card-plain .gf-text {
  color: var(--gf-text-on-dark, #ffffff);
}

.is-style-dark .khq-card-plain .gf-text {
  opacity: 0.78;
}

/* ---------------------------------------------------------------------------
 * 27. The stat block paints its own surface. Take it off on dark bands.
 *
 * MEASURED, walking the background chain up from the value: `.gf-stat` itself
 * computes to `rgb(255,255,255)`. The block ships a card-like fill of its own —
 * `gf-tone-light` supplies a soft background as well as a value colour — so
 * fixing the section's cards never touched it. White label and white value on a
 * white stat surface, sitting on a black band.
 *
 * The figures carry the brand colour instead of white. Kreativ orange on black
 * measures 8.5:1, and it gives the strip the one moment of orange between the
 * hero and the closing band without adding another orange section.
 * ------------------------------------------------------------------------ */
.is-style-dark .gf-stat {
  background: none;
  border: 0;
  box-shadow: none;
  padding: 0;
}

.is-style-dark .gf-stat .gf-stat__value {
  color: var(--wp--preset--color--secondary);
}

.is-style-dark .gf-stat .gf-stat__label {
  color: var(--gf-text-on-dark, #ffffff);
  opacity: 0.66;
}

/* ---------------------------------------------------------------------------
 * 28. Service index numerals in Kreativ orange.
 *
 * They were 55%-opacity ink, which made them furniture. At full-strength brand
 * orange they become what ties the seven cards together -- and they pair with
 * the alternating orange buttons so the colour reads as deliberate rhythm
 * rather than as two unrelated uses of the same hue.
 * ------------------------------------------------------------------------ */
.khq-index-num {
  color: var(--wp--preset--color--secondary);
  opacity: 1;
  font-weight: 700;
}

/* ---------------------------------------------------------------------------
 * 30. Process steps light up on hover.
 *
 * The four steps are unfilled cards on black, separated only by a hairline. On
 * hover the rule and the step title take the brand orange and the whole card
 * lifts very slightly — enough to confirm the row is a discrete thing, not
 * enough to look like a button.
 *
 * The rule thickens rather than only changing colour, so the affordance is not
 * carried by hue alone — a colour-only hover is invisible to a viewer who
 * cannot separate orange from white.
 * ------------------------------------------------------------------------ */
.khq-card-plain {
  transition:
    border-color 180ms ease,
    border-width 180ms ease,
    transform 180ms ease,
    background-color 180ms ease;
}

.is-style-dark .khq-card-plain:hover,
.is-style-dark .khq-card-plain:focus-within {
  border-block-start-color: var(--wp--preset--color--secondary);
  background-color: rgb(251 176 64 / 0.08);
  transform: translateY(-2px);
}

.is-style-dark .khq-card-plain:hover .gf-heading,
.is-style-dark .khq-card-plain:focus-within .gf-heading {
  color: var(--wp--preset--color--secondary);
}

.is-style-dark .khq-card-plain:hover .gf-text,
.is-style-dark .khq-card-plain:focus-within .gf-text {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .khq-card-plain {
    transition: none;
  }

  .is-style-dark .khq-card-plain:hover,
  .is-style-dark .khq-card-plain:focus-within {
    transform: none;
  }
}

/* ---------------------------------------------------------------------------
 * 32. The index numeral becomes an orange chip.
 *
 * MEASURED: brand orange text on a white card is 2.28:1 — it fails AA badly and
 * looked washed out rather than branded, which is the worst of both.
 *
 * Inverting it solves both at once. Orange is now the FILL and the numeral is
 * ink on top of it: 8.5:1, full-strength brand colour, and considerably more
 * designed than a tinted numeral. The chip also gives the card a fixed visual
 * anchor at the top-left, which is what makes seven cards scan as a numbered
 * set.
 * ------------------------------------------------------------------------ */
.khq-index-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.25rem;
  padding: 0.25rem 0.6rem;
  border-radius: 999px;
  background: var(--wp--preset--color--secondary);
  color: var(--wp--preset--color--primary);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  opacity: 1;
  align-self: flex-start;
}

/* ---------------------------------------------------------------------------
 * 33. Stepper markers carry the brand.
 *
 * MEASURED: the marker ships ink-filled with white text for every state, so the
 * `current` step — the one that says "start here" — looked identical to the two
 * that come after it, and the state was doing no work.
 * ------------------------------------------------------------------------ */
.gf-stepper-step-current .gf-stepper-step__marker {
  background: var(--wp--preset--color--secondary);
  border-color: var(--wp--preset--color--secondary);
  color: var(--wp--preset--color--primary);
}

.gf-stepper-step-upcoming .gf-stepper-step__marker {
  background: transparent;
  border-color: rgb(11 13 16 / 0.25);
  color: var(--wp--preset--color--primary);
}

/* ---------------------------------------------------------------------------
 * 34. Header spans the screen: brand hard left, everything else hard right.
 *
 * MEASURED at 1920px: the header row was 1152px wide and centred, so the brand
 * started 384px in from the left edge and the nav ended 384px from the right.
 * `justify-content: space-between` was working perfectly — it was just
 * splitting a narrow centred box, which reads as "centred header" rather than
 * "split header".
 *
 * 1152px is contentSize (72rem), not wideSize — the header container resolves
 * to the content measure. Rather than nudge it to `wide`, the header gets the
 * same fluid treatment as the hero, because a header is chrome: it belongs to
 * the screen edges, not to the text column. The 4% either side is the gutter.
 *
 * Root padding is zeroed for the same reason it is on the hero — the theme's
 * clamped inline padding would eat into the 92% and quietly re-centre it.
 * ------------------------------------------------------------------------ */
.gf-header .gf-container {
  max-width: none;
  width: 92%;
  margin-inline: auto;
  padding-inline: 0;
}

.khq-header-row {
  width: 100%;
}

/* Same for the footer, so the two pieces of chrome share one margin and the
 * page reads as a single frame rather than three different widths. */
.wp-site-blocks > footer .gf-container {
  max-width: none;
  width: 92%;
  margin-inline: auto;
  padding-inline: 0;
}

/* ---------------------------------------------------------------------------
 * 35. Release the header row from the constrained layout.
 *
 * The header container is `is-layout-constrained`, and WordPress's own rule
 *
 *   .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull))
 *     { max-width: var(--wp--style--global--content-size); margin-inline: auto }
 *
 * caps every direct child at contentSize (1152px) and centres it. So widening
 * the container to 1766px changed nothing visible: the row inside it was still
 * being pinned to 1152 and centred, leaving the brand 384px from the left edge.
 *
 * Setting `width: 100%` was not enough on its own — `max-width` was doing the
 * capping, and max-width beats width. Both have to be released.
 * ------------------------------------------------------------------------ */
.gf-header .khq-header-row {
  max-width: none;
  width: 100%;
  margin-inline: 0;
}

/* Footer columns get the same release, so the two pieces of chrome line up on
 * one margin instead of the footer quietly re-centring itself. */
.wp-site-blocks > footer .gf-container > * {
  max-width: none;
  margin-inline: 0;
}

/* And the hero, for the same reason — its grid is a constrained child too. */
.khq-hero .gf-container > * {
  max-width: none;
  margin-inline: 0;
}

/* ---------------------------------------------------------------------------
 * 36. Scroll reveal.
 *
 * The hidden state is applied by reveal.js, never by this stylesheet. That is
 * deliberate: if the script fails, nothing is hidden and the page is simply
 * static. A CSS-first reveal fails to a blank page, which is not a trade worth
 * making for motion.
 *
 * Transitions rather than keyframes, because a transition is interruptible --
 * an element part-way through can be re-shown instantly without fighting an
 * animation that insists on finishing.
 * ------------------------------------------------------------------------ */
.khq-reveal {
  opacity: 0;
  transform: translate3d(var(--khq-x, 0), var(--khq-y, 18px), 0);
  transition:
    opacity 620ms cubic-bezier(0.22, 0.7, 0.3, 1) var(--khq-d, 0ms),
    transform 620ms cubic-bezier(0.22, 0.7, 0.3, 1) var(--khq-d, 0ms);
  will-change: opacity, transform;
}

.khq-reveal.khq-in {
  opacity: 1;
  transform: none;
}

/* Once it has arrived, stop promising the compositor more work. */
.khq-reveal.khq-in {
  will-change: auto;
}

/* A side-on reveal (--khq-x) starts 36px off its column. When a media-object
   stacks at tablet width the element is full width, so the waiting state pokes
   9px past the viewport and the page scrolls sideways until it arrives
   (measured 11 Nov on 10 pages at 768px, 4px on 3 at 1440). gf-builder clips the
   same case for its own animations with
   `:is(.gf-section,.gf-hero):has(.gf-animate){overflow-x:clip}` (motion.css);
   this is that rule for the theme's reveal. `clip`, not `hidden`: no scroll
   container, so sticky and scroll-pin sections are unaffected. */
:is(.gf-section, .gf-hero):has(.khq-reveal) {
  overflow-x: clip;
}

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

/* ---------------------------------------------------------------------------
 * 37. Carousel controls, on a text carousel.
 *
 * THE CAUSE: `.gf-carousel__controls` is absolutely positioned at
 * `inset-block-start: 50%; inset-inline: .75rem` — arrows floating over the
 * slide's left and right edges at its vertical centre. That is right for the
 * image carousel the block was built around, where the arrows sit over
 * photography.
 *
 * We asked for `surface: flush`, and `.gf-carousel-flush .gf-carousel-slide`
 * sets `padding: 0` — so a text slide runs the full width, straight under both
 * arrows. The block is behaving correctly; a quote is just not a photograph.
 *
 * Rather than move the arrows off the slide, the slide is inset to make room
 * for them. The arrows keep the position the block intends, and the quote gets
 * a clear column.
 * ------------------------------------------------------------------------ */
.khq-review-carousel .gf-carousel__controls {
  inset-inline: 0;
}

/* On a narrow screen there is no room for a 4rem inset either side, so the
 * arrows drop below the quote and sit beside the dots instead. */
@media (max-width: 48rem) {
  .khq-review-carousel .gf-carousel-slide {
    padding-inline: 0;
    padding-block-end: 3.25rem;
  }

  .khq-review-carousel .gf-carousel__controls {
    inset-block-start: auto;
    inset-block-end: 0.25rem;
    transform: none;
  }
}

/* The arrows are drawn with currentColor borders; on the black reviews band
 * they need to be light, and large enough to hit comfortably. */
.is-style-dark .khq-review-carousel .gf-carousel__control {
  color: var(--gf-text-on-dark, #ffffff);
  pointer-events: auto;
  padding: 0.75rem;
  opacity: 0.7;
}

.is-style-dark .khq-review-carousel .gf-carousel__control:hover,
.is-style-dark .khq-review-carousel .gf-carousel__control:focus-visible {
  opacity: 1;
  color: var(--wp--preset--color--secondary);
}

.khq-review-quote {
  max-width: 46ch;
}

.khq-review-name {
  opacity: 0.7;
}

/* ---------------------------------------------------------------------------
 * 38. Logo sizing.
 *
 * GF Builder 0.14.4 caps the header logo and exposes the ceiling as
 * `--gf-header-logo-max-height` (default 2.5rem). So this is now a setting,
 * not an override: the brand is near-square and needs more height than a
 * wordmark, so the budget is raised to 75px and the plugin does the rest.
 * ------------------------------------------------------------------------ */
.khq-nav-dark,
.khq-nav-light {
  --gf-header-logo-max-height: 75px;
}

.khq-brand .wp-block-site-logo {
  width: auto;
  height: auto;
}

.khq-brand .wp-block-site-logo {
  margin: 0;
  line-height: 0;
}

/* The compact scroll state shrinks the bar; the mark comes down with it rather
 * than forcing the header to stay tall around it. */
.gf-header-scrolled .khq-brand img.custom-logo,
.gf-header-scrolled .khq-brand .wp-block-site-logo img {
  max-height: 58px;
  transition: max-height 180ms ease;
}

/* ---------------------------------------------------------------------------
 * 39. Archive templates.
 *
 * WHAT CHANGED. This section used to be 110 lines of card styling for CORE's
 * query loop, and the old comment explained why: only a core query with
 * `inherit: true` picked up the actual archive query, so GF's Post Loop -- which
 * ran its own query -- could not be an archive's spine. The cost was that the
 * items were bare core blocks with no card styling of their own, and the theme
 * drew the card by hand.
 *
 * GF Builder 0.14.4 added `sourceType: "archive"`, which follows the query of
 * the page it is on. The archives now run on `gf-builder/post-loop` and the
 * cards arrive as real `gf-card`s -- surface, radius, border and shadow from the
 * style pack, changing with it. That is the whole point of the swap: the archive
 * matches the rest of the site by construction, rather than by a second
 * implementation of the same card kept in step by hand.
 *
 * What is left is the three things a card in a GRID needs that a card on its
 * own does not.
 * ------------------------------------------------------------------------ */

/* One. Equal heights, with the buttons on a shared baseline. Without this a
 * card with a two-line title sets its button higher than its neighbours and
 * the row reads as ragged. */
.khq-archive .gf-post-loop__item,
.khq-archive .gf-post-loop__item > .gf-stack {
  height: 100%;
}

.khq-archive .gf-post-loop__item > .gf-stack > .gf-button {
  margin-block-start: auto;
  align-self: flex-start;
}

/* Two. Section 4 gives links in prose an underline, which is right in an
 * article and wrong on a card whose whole surface is the target. */
.khq-archive .gf-post-loop__item .gf-heading a,
.khq-archive .gf-post-loop__item .gf-heading a:hover {
  text-decoration: none;
  color: inherit;
}

.khq-archive .gf-post-loop__item:hover .gf-heading a {
  color: var(--wp--preset--color--warning);
}

/* Three. The card lifts to say it is one target rather than a stack of them. */
.khq-archive .gf-post-loop__item {
  transition: border-color 180ms ease, transform 180ms ease;
}

.khq-archive .gf-post-loop__item:hover {
  border-color: var(--wp--preset--color--secondary);
  transform: translateY(-2px);
}

/* Four. The empty state is a child of the same grid as the cards, so in a
 * three-column loop it renders one column wide and centred -- a small tile
 * marooned in a wide band, which reads as a broken card rather than as a
 * message. Span the row and align it with everything else on the page.
 *
 * GF BUILDER: worth closing upstream. The empty state is not a card in the
 * collection, it stands in for the whole collection, so it should span. */
.khq-archive .gf-post-loop__empty {
  grid-column: 1 / -1;
  max-width: 58ch;
  text-align: start;
}

/* The date is metadata: present, quiet, never competing with the title. */
.khq-archive .gf-post-loop__meta {
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.6;
}

/* The post hero's category and date. These carry `khq-archive-terms` /
 * `khq-archive-date`, which used to be styled by the hand-drawn card block
 * that the GF post loop replaced -- so they were left bare when it went. They
 * are metadata in exactly the same sense as the card meta above, and they are
 * set to match it, and to match the eyebrow on the cover art. */
.khq-archive-terms,
.khq-archive-date {
  font-size: var(--wp--preset--font-size--body-sm);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.6;
  margin-block: 0;
}

.khq-archive-terms a {
  color: var(--wp--preset--color--warning);
  text-decoration: none;
}

.khq-archive-terms a:hover {
  text-decoration: underline;
}

/* Pagination reads as controls, not as body copy. GF draws the page links;
 * this only sets them apart from the grid above. */
.khq-archive .gf-post-loop + *,
.khq-archive .gf-pagination {
  margin-block-start: var(--wp--preset--spacing--xl);
  padding-block-start: var(--wp--preset--spacing--md);
  border-block-start: 1px solid var(--wp--preset--color--border);
}

/* ---------------------------------------------------------------------------
 * 40. Article and case-study bodies.
 *
 * post-content emits plain core blocks, so the measure and rhythm have to be
 * set here or long-form reads as an undifferentiated wall.
 * ------------------------------------------------------------------------ */
.khq-prose .wp-block-post-content,
.khq-case-body .wp-block-post-content {
  font-size: var(--wp--preset--font-size--body-lg);
  line-height: 1.7;
}

.khq-prose .wp-block-post-content > p,
.khq-case-body .wp-block-post-content > p {
  max-width: 68ch;
  margin-block: 0 1.25em;
}

.khq-prose .wp-block-post-content > h2,
.khq-case-body .wp-block-post-content > h2 {
  margin-block: 2em 0.6em;
  font-size: var(--wp--preset--font-size--heading-lg);
  line-height: 1.2;
}

.khq-prose .wp-block-post-content > h2::before,
.khq-case-body .wp-block-post-content > h2::before {
  content: "";
  display: block;
  width: 2.5rem;
  height: 0.25rem;
  background: var(--wp--preset--color--secondary);
  margin-block-end: 0.75rem;
}

.khq-post-thumb img,
.khq-case-thumb img {
  width: 100%;
  height: auto;
  border-radius: var(--wp--custom--gf--radius--lg, 6px);
}

/* On the dark case-study hero the terms need the on-dark treatment. */
.khq-case-hero .wp-block-post-excerpt {
  color: var(--gf-text-on-dark, #ffffff);
  opacity: 0.8;
}

/* ---------------------------------------------------------------------------
   41. The mobile drawer follows the header, not the page.
   ---------------------------------------------------------------------------
   NOT a bug patch any more. GF Builder 0.14.4 fixed the white-on-white drawer,
   and 0.14.6 fixed the toggle on a dark chrome. The plugin's answer is a light
   drawer: it pins the panel to `--gf-color-surface` and pins the text with it,

       .gf-navigation-mobile-toggle .gf-navigation-list {
         background: var(--gf-color-surface);
         --gf-nav-text: var(--gf-text-on-light);
         --gf-link-color: var(--gf-text-on-light);
       }

   which is readable, and correct as a default. It is not what this site wants:
   the bar is black, and a white sheet dropping out of it breaks the one piece
   of chrome that carries the brand on a phone.

   Because the plugin now pins the text as well as the surface, going dark means
   re-pointing all three variables, not just the background.

   THIS IS A DESIGN CHOICE. Leave it in place. */

@media (max-width: 63.9375rem) {
  .khq-nav-dark .gf-navigation-mobile-toggle .gf-navigation-list {
    background: var(--gf-surface-contrast, var(--wp--preset--color--dark, #000));
    border-color: rgba(255, 255, 255, 0.16);
    box-shadow: 0 24px 48px rgba(0, 0, 0, 0.45);
    color: var(--gf-text-on-dark, #fff);
    --gf-nav-text: var(--gf-text-on-dark, #fff);
    --gf-link-color: var(--gf-text-on-dark, #fff);
    --gf-link-hover-color: var(--wp--preset--color--secondary);
  }

  .khq-nav-dark .gf-navigation-mobile-toggle .gf-nav-link:hover,
  .khq-nav-dark .gf-navigation-mobile-toggle .gf-nav-group__trigger:hover,
  .khq-nav-dark .gf-navigation-mobile-toggle .gf-nav-group-open > .gf-nav-group__trigger {
    background: rgba(255, 255, 255, 0.1);
    color: var(--wp--preset--color--secondary);
  }

  /* Sub-panels arrive already dark from `dropdownSurface: dark`. Inside a dark
     drawer a second dark fill just muddies it, so they separate by rule. */
  .khq-nav-dark .gf-navigation-mobile-toggle .gf-nav-group__panel {
    background: transparent;
    border: 0;
    border-inline-start: 2px solid var(--wp--preset--color--secondary);
    border-radius: 0;
    padding-inline-start: 0.75rem;
    margin-inline-start: 0.4rem;
  }

  .khq-nav-dark .gf-navigation-mobile-toggle .gf-dropdown-item,
  .khq-nav-dark .gf-navigation-mobile-toggle .gf-dropdown-item:link,
  .khq-nav-dark .gf-navigation-mobile-toggle .gf-dropdown-item:visited {
    color: var(--gf-text-on-dark, #fff);
  }

  /* The plugin returns the toggle to a light button once the header goes
     scrolled -- right for a bar that turns white, wrong for one that stays
     black. Section 13 keeps this bar solid, so the button stays with it. */
  .khq-nav-dark.gf-header-scrolled .gf-navigation__toggle {
    background: rgba(255, 255, 255, 0.1);
    border-color: rgba(255, 255, 255, 0.24);
    color: var(--gf-text-on-dark, #fff);
  }
}

/* ---------------------------------------------------------------------------
   43. Mobile header row
   ---------------------------------------------------------------------------
   Theme layout, not a GF gap. The header uses a `khq-header-row` group rather
   than GF's own zones, because zones cannot do brand-left / everything-right
   with a mega menu. That means the plugin's zone-stacking media query never
   applies here and this row owns its own small-screen behaviour.

   Row one is the logo and a compact Menu button. The drawer is pulled out of
   the flex line and pinned across the full row, so it is not indented by the
   width of the logo. */

@media (max-width: 63.9375rem) {
  .khq-header-row {
    position: relative;
    flex-wrap: nowrap;
    align-items: center;
  }

  .khq-header-row > .gf-navigation {
    flex: 1 1 auto;
    min-width: 0;
    position: static;
    display: flex;
    justify-content: flex-end;
  }

  .gf-navigation-mobile-toggle .gf-navigation__toggle {
    width: auto;
    flex: 0 0 auto;
  }

  .gf-navigation-mobile-toggle .gf-navigation-list {
    position: absolute;
    inset-inline: 0;
    inset-block-start: calc(100% + 0.65rem);
    z-index: 60;
    margin-block-start: 0;
    max-height: min(70vh, 34rem);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* ===========================================================================
   44. THE ELEVATION PASS — 26 Aug 2026
   ===========================================================================
   Written against GF Builder 0.18.2 for the 11am presentation. Where it
   disagrees with an earlier section, this one wins and the earlier one is
   superseded; it is appended rather than merged so the change is reviewable
   in one place and reversible in one delete.

   WHAT WAS ACTUALLY WRONG. Measured on the live page, not guessed:

     section padding   160px top AND bottom, on every band
     section headline  36px
     lede              18px, pure black, 1254px wide
     card padding      24px
     eyebrow           a filled pill, PLUS an orange rule, PLUS the heading

   None of those is a bug. Together they are why the page reads as competent
   rather than expensive. 160px of padding around content that does not fill
   it is not generosity, it is a hole; a 36px headline in a 1376px container
   has no authority; and an 18px line of pure black running 1254px wide is
   roughly 150 characters, which is twice a readable measure.

   THE MODEL. Tailwind UI's marketing pages, which is what was asked for, are
   built on four moves and this section is those four moves:

     1. One eyebrow device, not three. Small, tracked, coloured, no chrome.
     2. A headline with real scale contrast against the lede beneath it.
     3. Every run of prose capped at a measure, and set in muted ink so the
        headline keeps the contrast.
     4. Hairlines instead of shadows, everywhere.

   All of it is token-driven, so a style pack still changes what it should.
   ======================================================================== */

/* --- 44.1 Vertical rhythm — RETIRED into theme.json (0.19.0) ------------
   This used to be two `!important` rules fighting the inline padding the
   seeder writes on every section.

   0.19.0 changed the shape of the problem: `.gf-section` now takes its padding
   from `--gf-rhythm-section`, declared inside `:where()` so it carries zero
   specificity. The rhythm is therefore a TOKEN, and the theme sets it in
   `theme.json` under `settings.custom.gf.rhythm` like any other design value.

   The pack's own rhythm is clamp(6rem … 8rem); KHQ runs tighter, at
   clamp(4.5rem … 7rem), which is a brand decision rather than a correction.

   The seeded inline padding still wins where it exists -- that is correct,
   an explicit per-block value should -- so pages re-seeded after this will
   pick the token up. Nothing here is needed any more.
   ------------------------------------------------------------------------ */

/* --- 44.2 The eyebrow ----------------------------------------------------
   The chip was a filled pill at 14px. Tailwind UI's equivalent is a tracked
   uppercase label in the accent colour with no container at all: it reads as
   a category, not as a badge. Same block, same setting, different treatment.
   Chips set in a row (a group of tags, like ad networks or blocked terms) are
   tags, not eyebrows, so they keep the plugin's pill. */
.gf-section:not(.khq-hero) .gf-chip:not(.gf-stack-row > .gf-chip) {
  background: transparent;
  border: 0;
  padding: 0;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--wp--preset--color--warning);
}

.is-style-dark:not(.khq-hero) .gf-chip:not(.gf-stack-row > .gf-chip),
.khq-band-orange:not(.khq-hero) .gf-chip:not(.gf-stack-row > .gf-chip) {
  color: inherit;
  opacity: 0.72;
}

/* The hero's pill is a deliberate object -- it was designed as one line
   carrying the positioning statement, and it is the first thing anyone sees.
   It keeps its container; only the interior bands lose theirs. */
.khq-hero .gf-chip {
  font-size: 0.8125rem;
  letter-spacing: 0.01em;
  text-transform: none;
}

/* Two eyebrows is one too many. When a chip introduces the band, the rule
   under the heading is redundant, so it stands down. */
.gf-stack:has(> .gf-chip) > .khq-rule::before {
  display: none;
}

/* --- 44.3 Headlines ------------------------------------------------------
   36px had no authority at this container width. The jump to ~48px is what
   creates the scale contrast the lede then sits beneath. */
.gf-section .gf-type-heading-xl {
  font-size: clamp(2.125rem, 3.4vw, 3rem);
  line-height: 1.04;
  letter-spacing: -0.03em;
  text-wrap: balance;
  max-width: 20ch;
}

.gf-section .gf-type-heading-lg {
  font-size: clamp(1.5rem, 2.2vw, 2rem);
  line-height: 1.1;
  letter-spacing: -0.025em;
  text-wrap: balance;
}

/* A headline introducing a full-width grid may run wider than a headline
   sitting over prose. */
.gf-section:has(.gf-grid) .gf-type-heading-xl {
  max-width: 24ch;
}/* --- 44.4 The lede -------------------------------------------------------
   Capped at a real measure and set in muted ink. Pure black at 18px competing
   with a 48px black headline is why the old hierarchy read flat. */
.gf-section > .gf-container > .gf-stack > .gf-type-body-lg {
  font-size: clamp(1.0625rem, 1.25vw, 1.1875rem);
  line-height: 1.6;
  max-width: 65ch;
  color: color-mix(in srgb, currentColor 72%, transparent);
}

/* --- 44.5 Cards ----------------------------------------------------------
   More room inside, hairline outside, and a hover that moves the border
   rather than casting a shadow. Shadows are what a 2014 theme reaches for. */
/* MY BUG, found 28 Aug 2026 against GF Builder 1.0.0.
   This used to be `padding: clamp(1.5rem, 2.2vw, 2rem)`, written when the card
   had no padding token worth respecting. It now has one -- `--gf-card-pad`,
   driven by the `padding` setting -- and this rule silently defeated it: a card
   set to `padding: none` still measured 32px, so an edge-to-edge image was
   inset 33px on both sides and could never meet the card's corners.

   The theme's opinion about card padding was right in spirit and wrong in
   mechanism. It now sets the TOKEN, so the block's own setting still wins when
   an author changes it, and `none` means none. */
.gf-section .gf-card {
  --gf-card-pad: clamp(1.5rem, 2.2vw, 2rem);
  box-shadow: none;
  transition: border-color 180ms ease, background-color 180ms ease, transform 180ms ease;
}

.gf-section .gf-card.gf-card-padding-none {
  --gf-card-pad: 0rem;
}

.gf-section .gf-card:hover {
  border-color: color-mix(in srgb, var(--wp--preset--color--secondary) 70%, var(--wp--preset--color--border));
  transform: translateY(-2px);
}

.is-style-dark .gf-card {
  border-color: rgba(255, 255, 255, 0.14);
}

.is-style-dark .gf-card:hover {
  border-color: var(--wp--preset--color--secondary);
  background: rgba(255, 255, 255, 0.03);
}

/* --- 44.6 Grids ----------------------------------------------------------
   A slightly wider gutter reads as considered; 24px reads as a default. */
.gf-section .gf-grid {
  gap: clamp(1.25rem, 2vw, 2rem);
}/* --- 44.7 Numbers --------------------------------------------------------
   Tabular figures so a row of stats sits on a common baseline grid, and a
   hairline between them instead of a card around each. */
.gf-stat__value,
.gf-section .gf-type-display-lg,
.gf-section .gf-type-display-xl {
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.04em;
}

.is-style-dark .gf-grid > .gf-stat,
.is-style-dark .gf-grid > .gf-stack {
  padding-inline-start: clamp(1rem, 2vw, 1.75rem);
  border-inline-start: 1px solid rgba(255, 255, 255, 0.14);
}

.is-style-dark .gf-grid > .gf-stat:first-child,
.is-style-dark .gf-grid > .gf-stack:first-child {
  padding-inline-start: 0;
  border-inline-start: 0;
}

/* --- 44.9 Section head block --------------------------------------------
   The eyebrow, headline and lede are one object with its own internal
   rhythm, not three siblings on a generic gap. */
.gf-section > .gf-container > .gf-stack:first-child {
  gap: 0.85rem;
}

.gf-section > .gf-container > .gf-stack:first-child > .gf-chip {
  margin-block-end: 0.15rem;
}

/* --- 44.10 Buttons -------------------------------------------------------
   Slightly more horizontal room and a firmer optical weight. */
.gf-section .gf-button {
  padding-inline: 1.35rem;
  letter-spacing: -0.006em;
}

/* --- 44.11 Reveal timing -------------------------------------------------
   620ms plus a stagger meant the last card in a row finished a full second
   after the first. On a laptop being scrolled by hand in front of a room that
   does not read as elegant, it reads as "the page has not loaded yet" -- and
   every screenshot taken during this pass caught content mid-fade, which is
   the same thing an audience sees.

   460ms with a shorter throw is still clearly a reveal and is over before
   anyone waits for it. The stagger stays: arrival order is the point. */
.khq-reveal {
  transform: translate3d(var(--khq-x, 0), var(--khq-y, 14px), 0);
  transition:
    opacity 460ms cubic-bezier(0.2, 0.65, 0.3, 1) var(--khq-d, 0ms),
    transform 460ms cubic-bezier(0.2, 0.65, 0.3, 1) var(--khq-d, 0ms);
}

/* Above the fold nothing should animate at all. The hero is the first thing
   in the room and it must be finished before it is looked at. */
.khq-hero .khq-reveal {
  opacity: 1;
  transform: none;
  transition: none;
}

/* ===========================================================================
   45. DEPTH — grounds, elevation, gradient
   ===========================================================================
   Section 44 fixed proportion: rhythm, scale, measure. What it deliberately
   did not do was add depth, and flat is where "clean" tips over into "plain".

   The rule this section follows, which is the one Tailwind UI actually keeps:
   DEPTH IS CUMULATIVE AND ALMOST INVISIBLE. Not one 24px drop shadow, but a
   1px hairline plus a 2px contact shadow plus a wide soft one, each barely
   perceptible alone. That is what makes a card look like it is resting on the
   page rather than pasted onto it.

   Everything below resolves through GF's own tokens -- `--gf-elevation-*`,
   `--gf-gradient-text-*` -- so a style pack still owns the ramp and a palette
   change still carries.
   ======================================================================== */

/* --- 45.1 Elevation ramp -------------------------------------------------
   MOVED TO theme.json, `settings.custom.gf.elevation`.

   The pack's ramp is tuned for slate: rgb(15 23 42 / a), a cool blue-grey.
   On a warm black-and-orange palette that reads dirty under every card, so
   the ink is warmed toward the brand and each rung is split into two shadows
   -- a tight contact shadow plus a wide ambient one. A single blur is what
   makes a card look pasted on rather than resting.

   It lived here as hard-coded `:root` custom properties, which had two costs:
   the editor could not show it, and it silently stopped at level-4, so
   `--gf-elevation-5` still resolved to a slate pack value while 1-4 were warm.
   custom.gf.elevation is the route the plugin documents (Design/Settings.php
   omits any leaf a theme declares), so the ramp is now a setting rather than
   a stylesheet, and level-5 is finished. Do not re-add it here. */

/* --- 45.2 Cards rest on the page ----------------------------------------
   Section 44 took the shadow off entirely, which was right at the time --
   the pack's shadow was a single grey blur and it looked cheap. This puts
   depth back as a layered, warm, barely-there thing, and gives the hover
   somewhere to travel to. */
.gf-section .gf-card {
  box-shadow: var(--gf-elevation-1);
}

.gf-section .gf-card:hover {
  box-shadow: var(--gf-elevation-3);
  transform: translateY(-3px);
}

/* On ink a drop shadow is invisible; depth has to come from the surface
   itself lifting away from the ground. */
.is-style-dark .gf-card:not(.gf-has-background-image) {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.045), rgba(255, 255, 255, 0.012));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

.is-style-dark .gf-card:hover {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.075), rgba(255, 255, 255, 0.02));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.10),
    0 18px 40px -18px rgba(0, 0, 0, 0.8);
}

/* --- 45.3 Grounds --------------------------------------------------------
   A band was one flat fill edge to edge. Three of them stacked reads as three
   slabs. Each ground now has a light source: a wide, very low-contrast bloom
   toward the top, which is what stops a large area of one colour from looking
   printed. Nothing here is strong enough to see on its own -- it is only
   visible as the absence of flatness. */

/* Warm paper: a bloom from the upper left, warmed toward the brand. */
.gf-section.is-style-muted {
  background-image:
    radial-gradient(72% 58% at 18% 0%, rgba(255, 255, 255, 0.9) 0%, transparent 62%),
    radial-gradient(55% 45% at 92% 8%, rgba(251, 176, 64, 0.055) 0%, transparent 70%);
}

/* Ink: lit from above, and never quite pure black in the body of the band --
   pure black across 700px is a hole in the page. */
.gf-section.is-style-dark {
  background-image:
    radial-gradient(85% 60% at 50% -10%, rgba(255, 255, 255, 0.055) 0%, transparent 65%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.018) 0%, transparent 38%);
}

/* White: the faintest cool-to-warm drift, so a white band under a black one
   has an edge rather than a seam.

   IN `@layer theme`, WHICH IS WHAT MAKES A GENERIC GROUND SAFE. This rule
   matches any section the brand has not given a style to, and an unlayered
   generic rule beats GF's `@layer components` however weak its selector. It
   did: this `background-image` REPLACED the plugin's black-to-orange Gradient
   variation and left only its `background-color`, so every gradient band on
   the site rendered flat black for weeks - what the client team reported as
   "gradients that sit over black".

   It used to be excluded one variation at a time with
   `:not(.is-style-gradient)`. The layer covers every variation the plugin
   ships, now and later, which is the point: the next one will not need a new
   `:not()` here. See section 0, and the plugin's docs/cascade-and-layers.md.
   Worklist item 71; the editor now warns when this happens. */
@layer theme {
  .gf-section:not(.is-style-dark):not(.is-style-muted):not(.has-background):not(.gf-has-background-image):not(.khq-hero):not(.khq-band-orange):not(.gf-backdrop *) {
    background-image:
      linear-gradient(180deg, rgba(246, 244, 241, 0.55) 0%, transparent 30%);
  }
}

/* --- 45.4 Gradient in the type ------------------------------------------
   GF ships `.gf-gradient-text` and the `--gf-gradient-text-from/to` pair, so
   this is a class on a heading in the block panel, not a bespoke treatment.

   USED ONCE PER PAGE, ON PURPOSE. A gradient headline is an accent; three of
   them is a theme. The brand default here is orange into amber, which is a
   short hop in hue -- a long gradient across unrelated colours is the thing
   that dates a page fastest. */

/* On a light ground the same pair has no contrast, so it runs deep-orange
   into brand-orange instead and stays legible. */

/* A gradient clips to the glyphs, so descenders need the line box to have
   somewhere to go or they get shaved. */
.gf-gradient-text {
  padding-block-end: 0.08em;
}

/* --- 45.5 The orange band ------------------------------------------------
   Flat brand orange over a full band is the loudest thing on the site. Giving
   it a light source turns a fill into a surface. */
.khq-band-orange,
.khq-hero {
  background-image:
    radial-gradient(70% 60% at 78% 6%, rgba(255, 255, 255, 0.30) 0%, transparent 62%),
    radial-gradient(60% 55% at 6% 96%, rgba(165, 100, 3, 0.16) 0%, transparent 68%);
}

/* --- 45.6 Dividers and rules --------------------------------------------
   A hairline that fades at both ends reads as drawn rather than as a border. */
.gf-divider {
  border: 0;
  block-size: 1px;
  background: linear-gradient(90deg, transparent, var(--wp--preset--color--border) 12%, var(--wp--preset--color--border) 88%, transparent);
}

.is-style-dark .gf-divider {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.16) 12%, rgba(255, 255, 255, 0.16) 88%, transparent);
}

/* --- 45.7 The primary button --------------------------------------------
   One gradient, one inner highlight, one contact shadow. It is the single
   most-clicked object on the page and it should look pressable. */
.gf-section .gf-button-primary,
.khq-hero .gf-button-primary {
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(0, 0, 0, 0.06));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    var(--gf-elevation-1);
}

.gf-section .gf-button-primary:hover {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    var(--gf-elevation-2);
}

/* --- 45.8 Motion ---------------------------------------------------------
   A card that only fades reads as a slide; a card that also settles the last
   half-percent of its scale reads as arriving. Small enough that nobody
   should be able to name it. */
.gf-card.khq-reveal {
  transform: translate3d(var(--khq-x, 0), var(--khq-y, 14px), 0) scale(0.995);
}

.gf-card.khq-reveal.khq-in {
  transform: none;
}

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

/* ===========================================================================
   46. LANDING PAGE, PASS THREE — 26 Aug 2026
   ===========================================================================
   Answers eleven specific notes taken while reading the page. Grouped by the
   note they answer so each one can be found and reversed on its own.
   ======================================================================== */

/* --- 46.1  HERO (note 1) -------------------------------------------------
   The hero was doing its job with one device: a big black headline on orange.
   Adding a second — gradient in the type — needs care, because gradient ink
   on a gradient ground is where pages go to look like 2013. So the ground
   gets DEEPER rather than more colourful (a diagonal from brand orange into a
   burnt edge, which is the same hue, not a new one), and only the last line
   of the headline takes the gradient. */
.khq-hero {
  background-image:
    radial-gradient(78% 62% at 76% 4%, rgba(255, 255, 255, 0.38) 0%, transparent 58%),
    radial-gradient(70% 70% at 4% 100%, rgba(120, 68, 0, 0.30) 0%, transparent 66%),
    linear-gradient(152deg, rgba(253, 208, 130, 0.55) 0%, transparent 42%);
}

/* The H1 uses GF's own `.gf-gradient-text`; this only supplies the pair. On
   orange the gradient has to stay INSIDE the dark end of the ramp -- burnt
   brown into ink -- because anything lighter loses contrast against the
   ground it is sitting on. */

/* --- 46.1b  The hero pill -----------------------------------------------
   It was rendering as a flat light-grey lozenge: `chip-filled` + `tone-primary`
   resolves to a muted surface, which on brand orange reads as a dead patch
   rather than as a badge.

   What it wants is glass: a translucent fill so the orange shows through and
   tints it, a hairline that catches the light at the top, and a soft shadow
   under it. The text then sits on something rather than in a hole.

   GF BUILDER GAP: there is no `glass` chip variant. `chipVariant` offers
   filled / outline / soft, all of which are opaque. A translucent variant that
   borrows the ground is the one missing option, and it is the one every
   hero badge on the web currently uses. */
.khq-hero .gf-chip {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0.24));
  border: 1px solid rgba(255, 255, 255, 0.55);
  color: #1A1206;
  -webkit-backdrop-filter: blur(10px) saturate(1.15);
  backdrop-filter: blur(10px) saturate(1.15);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    0 1px 2px rgba(74, 45, 0, 0.10),
    0 8px 20px -10px rgba(74, 45, 0, 0.35);
  padding: 0.4rem 0.95rem;
  font-weight: 600;
}

/* The hero buttons carry the page's only two CTAs, so they get the most
   elevation on the site — and the outline one stops being a ghost. */
.khq-hero .gf-button-primary {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 1px 2px rgba(40, 24, 0, 0.18),
    0 12px 28px -10px rgba(40, 24, 0, 0.45);
}

.khq-hero .gf-button-primary:hover {
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.26),
    0 2px 4px rgba(40, 24, 0, 0.20),
    0 18px 38px -12px rgba(40, 24, 0, 0.55);
}

.khq-hero .gf-button-outline,
.khq-hero .gf-button-secondary {
  background: rgba(255, 255, 255, 0.55);
  border-color: rgba(0, 0, 0, 0.22);
  backdrop-filter: blur(6px);
  box-shadow: 0 1px 2px rgba(40, 24, 0, 0.10);
}

.khq-hero .gf-button-outline:hover,
.khq-hero .gf-button-secondary:hover {
  background: rgba(255, 255, 255, 0.85);
  border-color: rgba(0, 0, 0, 0.4);
}

/* --- 46.2  TRUST STRIP (note 2) -----------------------------------------
   Flat black under a bright orange hero is a cliff. A gradient that starts
   near-black at the top and opens out lower down gives the transition
   somewhere to happen, and the hairline grid then has something to sit in. */
.gf-section.is-style-dark.gf-decoration-grid {
  background-color: #000;
  /* THE GRID IS THE ONLY ACCENT ON THIS BAND.
     Two orange radial blooms used to sit under it as well, so the band carried
     the grid's orange lines AND an orange wash behind the figures -- two accent
     layers doing the same job, which is what made it read heavy. The blooms are
     gone. What is left is depth only: true black for the top quarter (the
     section cut above is filled with primary, also #000000, so any lift there
     shows as a step exactly where the scroll should read as continuous), then
     a barely-there warm drift below it. No hue. */
  background-image:
    linear-gradient(168deg, #000000 0%, #000000 26%, #050403 62%, #0A0806 100%);
}

/* The grid sits over that, so it needs to be a touch stronger to stay legible
   against a lit ground rather than a flat one. */
.is-style-dark.gf-decoration-grid {
  --gf-decoration-grid-line: rgba(251, 176, 64, 0.17);
}

/* --- 50.2  Longer dividers ----------------------------------------------
   2.25rem was a tick mark. Against a 52px figure with a two-line caption
   under it the column is about 100px tall, so a 36px rule reads as a stray
   mark rather than as a separator.

   Now it runs the height of the content and fades at BOTH ends -- solid in
   the middle where the eye needs the separation, gone at the extremes so it
   never looks like a table border. Brighter in the middle too: the old one
   started at 55% and fell away immediately, so its strongest point was also
   its shortest-lived. */
.is-style-dark .gf-grid > .gf-stat::before {
  inset-block: 0.15rem 0.35rem;
  block-size: auto;
  inline-size: 1px;
  background: linear-gradient(
    180deg,
    transparent 0%,
    rgba(251, 176, 64, 0.30) 12%,
    rgba(251, 176, 64, 0.72) 46%,
    rgba(251, 176, 64, 0.26) 82%,
    transparent 100%
  );
}

/* A second hairline a pixel over, at very low opacity, gives the rule a soft
   edge instead of an aliased one -- the difference between a drawn line and a
   1px artefact on a dark ground. */
.is-style-dark .gf-grid > .gf-stat::after {
  content: "";
  position: absolute;
  inset-block: 0.15rem 0.35rem;
  inset-inline-start: 1px;
  inline-size: 1px;
  background: linear-gradient(
    180deg,
    transparent 0%,
    rgba(251, 176, 64, 0.10) 46%,
    transparent 100%
  );
  pointer-events: none;
}

.is-style-dark .gf-grid > .gf-stat:first-child::after {
  display: none;
}

@media (max-width: 30rem) {
  .is-style-dark .gf-grid > .gf-stat::after {
    display: none;
  }
}

/* ===========================================================================
   51. DARK BANDS, EMPHASIS FORMATS — 26 Aug 2026
   ===========================================================================
   Two findings, both worth reading before touching anything here.

   FINDING ONE: the reviews band went grey.
   It was not the trust-bar gradient leaking. The `decoration` setting on that
   section is `sheen`, and the Modern style pack defines the sheen token as

     linear-gradient(180deg, color-mix(in srgb, var(--gf-color-muted) 85%,
                     transparent) 0%, transparent 55%)

   -- an 85% opaque wash of #F6F4F1. That is authored for a LIGHT ground, and
   `.is-style-dark` does not set `color-scheme: dark`, so `light-dark()` cannot
   rescue it. On a black band it paints a grey fog over the top half.

   `wash` has the same problem in reverse: it is a 12% BLACK radial, invisible
   on black.

   The token names are already right. What is missing is a dark-scheme value
   for them, which is the one thing a section that declares itself dark should
   be able to assume. Three declarations here; the same three belong upstream
   in the section block's dark style, where they would fix every dark band on
   every site at once rather than this one.  [logged for Claude Code]

   FINDING TWO: 50.1's trust-bar gradient was scoped to `.is-style-dark`, so
   every dark band got it. It is now scoped to `.is-style-dark
   .gf-decoration-grid` -- the combination it was drawn for. Dark + grid is the
   trust strip; dark + sheen is the reviews band; they no longer collide, and
   the difference between them is now carried by a SETTING rather than by a
   class someone has to remember to add.
   ======================================================================== */

/* --- 51.1  Decoration tokens on a dark ground --------------------------- */
.is-style-dark {
  /* A lit top edge instead of a grey fog: the same gesture the pack intends,
     inverted for the ground it is actually sitting on. Kept low -- above 8%
     it stops reading as light and starts reading as a panel seam. */
  --gf-decoration-sheen:
    linear-gradient(180deg, rgba(255, 255, 255, 0.055) 0%, transparent 58%);

  /* Warm rather than neutral, so a wash on a dark band belongs to the brand
     rather than just lightening it. */
  --gf-decoration-wash:
    radial-gradient(60% 55% at 50% 0%,
      rgba(251, 176, 64, 0.10) 0%, transparent 100%);
}

/* --- 51.2  Emphasis is now a format, not a class ------------------------
   0.19.0 ships three rich-text formats -- Gradient text, Brand highlight and
   Inline badge -- so emphasis is applied by selecting words and clicking a
   toolbar button. That is strictly better than what was here before, which
   was a class on a whole heading:

     - it works on a PHRASE, which is the only way a gradient headline reads
       as deliberate rather than as a filter over the block
     - the colour pair comes from palette slugs (`gf-gradient-text-from-*`),
       so a palette change carries it
     - the finish -- angle, highlight alpha, underline weight -- comes from
       `custom.gf.emphasis` in the style pack, so a pack change carries it too

   The old whole-heading classes `.khq-gradient-head` and `.khq-hero-grad`
   were removed on 27 Sep 2026 in the dead-class sweep; nothing referenced
   them. Use the rich-text formats instead. */

/* A clipped background on an INLINE span can shave descenders where the line
   box is tight. Cheaper than a line-height override and scoped to the format
   rather than to the heading. */
.gf-gradient-text {
  padding-block-end: 0.06em;
}

/* ===========================================================================
   52. THE PHOTO HERO — retired 27 Aug 2026, GF Builder 0.21.3
   ===========================================================================
   Everything this section used to hold is now a block setting. Deleted rather
   than commented out, because a workaround kept "just in case" is how a theme
   stops shrinking. The old file is at khq.css.bak-pre0213 if any of it is
   needed back.

     52.1 glass pill          -> chipVariant: glass
     52.2 button elevation    -> buttonElevation: floating
     52.4 outline hover fill  -> buttonEmphasis / buttonElevation
     52.6 three-stop scrim    -> overlayFadeReach + overlayFadeMidpoint
     52.8 crossfade duration  -> backgroundTransition

   What is KEPT below is only what the plugin still does not do: the copy's own
   contrast floor on an unpredictable photograph. It is a drop-shadow rather
   than a text-shadow because the H1 contains a background-clipped gradient
   span, and text-shadow paints behind transparent glyphs.
   ======================================================================== */

.khq-hero-photo .gf-heading,
.khq-hero-photo .gf-text {
  text-shadow: none;
  filter:
    drop-shadow(0 1px 2px rgba(0, 0, 0, 0.50))
    drop-shadow(0 2px 18px rgba(0, 0, 0, 0.45));
}

/* --- 50.3  Equal height across rows, not just within one -----------------
   `equalHeight` on a grid emits `.gf-grid-equal-height > * { height: 100% }`,
   which makes every item fill ITS OWN row. CSS grid then sizes each row to its
   own tallest item, so an eight-card group wraps to 184px/184px/208px/208px --
   equal across, uneven down, which reads as a mistake rather than a grid.

   `grid-auto-rows: 1fr` makes every implicit row take the same track size, so
   a group of cards is one block whatever the copy lengths.

   CSS BECAUSE THERE IS NO SETTING FOR IT. `equalHeight` is a boolean and it
   already claims to do this. Filed on the worklist: the plugin's own rule
   should carry this, not the theme. */
.gf-grid-equal-height {
  grid-auto-rows: 1fr;
}

/* --- 50.4  Reviews: pin the attribution to the bottom of the card --------
   The card BOXES were already equal -- 388/388/388 measured. What read as
   uneven was the content inside them stopping at three different points: the
   names sat 66px, 125px and 96px up from their card's lower edge, with the
   dead space below each one different.

   The chain, measured:

     .gf-card.gf-card-equal-height   flex column, 388px
       .gf-stack                     flex: 1 1 auto  <- plugin pins this, works
         .gf-rating
         figure.gf-testimonial       display:block, flex: 0 1 auto  <- stops here
           blockquote.gf-testimonial__quote
           figcaption.gf-testimonial__person

   The plugin already ships this exact pattern one level up
   (`.gf-card-equal-height > .gf-stack:last-child { flex: auto }`). It just does
   not follow it into the testimonial, which is a leaf it never considers -- so
   the stack grows and the figure inside it does not.

   Two rules: let the figure grow, and push its caption to the bottom. The
   quote block then absorbs the slack and every attribution lines up across the
   row.

   CSS BECAUSE THERE IS NO SETTING FOR IT. `stack` exposes `align`, which is the
   cross axis; there is no main-axis distribute, and `testimonial` has no
   fill-height option. Filed on the worklist -- this belongs beside the
   plugin's own equal-height rules, not here. */
.gf-card-equal-height .gf-stack > .gf-testimonial:last-child {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
}

.gf-card-equal-height .gf-stack > .gf-testimonial:last-child > .gf-testimonial__quote {
  flex: 1 1 auto;
}

.gf-card-equal-height .gf-stack > .gf-testimonial:last-child > .gf-testimonial__person {
  margin-block-start: auto;
  padding-block-start: 1rem;
}

/* Sections 47-50 (typewriter, hover reveal, stepper tab patch, button
   nowrap) were removed on 2026-09-22: GF Builder 2.16.0 ships all four as
   settings - Heading > Typewriter, Card > Reveal on hover, the stepper fix
   in 2.15.1, and button labels that hold one line on their own. */

/* ===========================================================================
 * 47. Article measure - STYLING
 * ===========================================================================
 * `68ch` measured 767px at 18px Inter, which is about 85 characters: too wide
 * to read comfortably and it left headings and images sitting 500px wider than
 * the paragraphs. The whole prose column is now capped and centred instead, so
 * every element in an article shares one edge.
 * ------------------------------------------------------------------------ */
.khq-prose .wp-block-post-content,
.khq-case-body .wp-block-post-content {
  max-width: 42rem;
  margin-inline: auto;
}

.khq-prose .wp-block-post-content > p,
.khq-case-body .wp-block-post-content > p {
  max-width: none;
}

.khq-prose .khq-post-thumb,
.khq-prose .wp-block-post-featured-image {
  max-width: 52rem;
  margin-inline: auto;
}

/* --------------------------------------------------------------------------
 * STOPGAP -- GF Builder 2.23.1 placeholder bug (filed: docs/problems.md)
 *
 * placeholder/render.php ALWAYS emits `<span class="gf-u-sr-only">label</span>`
 * before the bars. feedback.css then shortens the ragged last line with
 *
 *     .gf-placeholder__line:last-child:not(:first-child) { width: 72% }
 *
 * The `:not(:first-child)` guard was meant to say "only when there is more
 * than one bar", but the sr-only span is the real first child, so the ONLY bar
 * of a single-line placeholder matches too and renders at 72%. Every one-line
 * placeholder on every GF site is short. The fix belongs in the plugin --
 * `.gf-placeholder__line + .gf-placeholder__line:last-child` says what was
 * meant. Delete this block when that ships.
 * ------------------------------------------------------------------------ */
.gf-placeholder .gf-u-sr-only + .gf-placeholder__line:last-child {
  width: 100%;
}

/* --------------------------------------------------------------------------
 * STOPGAP -- GF Builder 2.28.0 card background image (filed: docs/problems.md)
 *
 * A card with `backgroundImageId` renders the photograph as a real <img
 * class="gf-background-image"> at `z-index: -2`, with the overlay scrim on
 * ::before at `z-index: -1`. Neither line makes the CARD a stacking context,
 * so on a card whose own surface is transparent the whole negative layer
 * escapes behind the section background and the photograph never paints. The
 * tile renders as an empty gradient box.
 *
 * The plugin already knows this contract and applies exactly this pair for
 * the same reason on `.gf-decoration-*` and `.gf-has-section-cut`
 * (surfaces.css, "WHY z-index: -1 AND isolation: isolate TOGETHER"). The card
 * needs it too. Measured: with this rule the image paints; without it the
 * canvas pixels are the section background.
 *
 * Delete this block when the plugin ships `isolation: isolate` on
 * `.gf-card.gf-has-background-image`.
 * ------------------------------------------------------------------------ */
.gf-card.gf-has-background-image {
  position: relative;
  isolation: isolate;
}

/* ---------------------------------------------------------------------------
 * 52. Stepped contact form (Forminator) -- the parts gf-builder does not style
 *
 * gf-builder's forms.css re-points Forminator (form style "none") at the site's
 * form tokens: labels, text inputs, the submit button, errors. A PAGED form adds
 * three things it has no rules for, measured on /contact-us/ 11 Nov:
 *   - the step indicator (.forminator-pagination-steps) rendered as bare text,
 *   - Next/Back (.forminator-button-next / -back) rendered as unstyled buttons,
 *   - checkbox/radio options ran together on one line with no spacing.
 * Everything below uses only gf-builder's own form/button tokens, so it follows
 * the design system. Receipt: when gf-builder styles paged forms and choice
 * options itself (INBOX new-gap, 11 Nov), delete this section.
 * ------------------------------------------------------------------------ */

/* A form in a light card keeps light tokens even inside a dark band (the Contact
   split hero). A dark hero REDEFINES the colour tokens for everything inside it
   (measured: --gf-color-border = white 30%, help text = white 74%), and a light card
   does not reset them, so tile borders, help text and empty step bars disappeared on
   the white card (11 Nov). Re-point them at gf-builder's own on-light tokens. */
.gf-card .forminator-custom-form {
  color-scheme: light;
  color: var(--gf-text-on-light);
  --gf-color-border: var(--gf-color-border-on-light);
  --gf-text-muted: var(--gf-text-muted-on-light);
  --gf-form-field-border: var(--gf-color-border-on-light);
  --gf-form-help-text: var(--gf-text-muted-on-light);
  --gf-form-label-text: var(--gf-text-on-light);
  --gf-form-field-text: var(--gf-text-on-light);
}

/* Step indicator: three equal segments, label above a bar; done + current fill. */
.forminator-custom-form .forminator-pagination-steps {
  display: flex;
  gap: 0.5rem;
  margin: 0 0 var(--wp--preset--spacing--md, 1.5rem);
  counter-reset: khq-step;
}
.forminator-custom-form .forminator-pagination-steps .forminator-break {
  display: none;
}
.forminator-custom-form .forminator-step {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--gf-form-help-text, var(--gf-text-muted));
  font: inherit;
  font-size: var(--gf-form-label-size, 0.875rem);
  font-weight: var(--gf-form-label-weight, 600);
  text-align: start;
  cursor: default;
}
.forminator-custom-form .forminator-step-label::before {
  counter-increment: khq-step;
  content: counter(khq-step) ". ";
}
.forminator-custom-form .forminator-step-dot {
  display: block;
  order: 2;
  height: 4px;
  border-radius: var(--gf-radius-pill, 999px);
  background: var(--gf-form-field-border, var(--gf-color-border));
  transition: background-color 0.2s var(--gf-easing-standard, ease);
}
.forminator-custom-form .forminator-step.forminator-current,
.forminator-custom-form .forminator-step:has(~ .forminator-step.forminator-current) {
  color: var(--gf-form-label-text, inherit);
}
.forminator-custom-form .forminator-step.forminator-current .forminator-step-dot,
.forminator-custom-form .forminator-step:has(~ .forminator-step.forminator-current) .forminator-step-dot {
  background: var(--gf-color-primary);
}
/* Measured against the FORM's width, not the screen: in the Contact split hero the
   card is ~440px at a 1024px screen and "3. How to reach you" wrapped. */
.forminator-custom-form {
  container-type: inline-size;
}
@container (max-width: 560px) {
  /* Three labels do not fit a narrow row: keep three equal bars and print only the
     current step's label, pinned to the left above them, so it has the full width. */
  .forminator-custom-form .forminator-pagination-steps {
    position: relative;
    padding-block-start: 1.75rem;
  }
  .forminator-custom-form .forminator-step {
    min-width: 0;
  }
  .forminator-custom-form .forminator-step-label {
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 0;
    white-space: nowrap;
  }
  .forminator-custom-form .forminator-step:not(.forminator-current) .forminator-step-label {
    visibility: hidden; /* not display:none, which would stop the step counter */
  }
}

/* Choice options as tap targets: a grid of bordered tiles, filled when chosen. */
.forminator-custom-form .forminator-field-checkbox .forminator-field,
.forminator-custom-form .forminator-field-radio .forminator-field {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr));
  gap: 0.5rem;
}
.forminator-custom-form .forminator-field-checkbox .forminator-field > :not(.forminator-checkbox),
.forminator-custom-form .forminator-field-radio .forminator-field > :not(.forminator-radio) {
  grid-column: 1 / -1;
}
/* Error and hint each on their own line (they rendered run together: "start.Pick"). */
.forminator-custom-form .forminator-field-checkbox .forminator-error-message,
.forminator-custom-form .forminator-field-radio .forminator-error-message {
  display: block;
}
.forminator-custom-form .forminator-description {
  display: block;
  margin: 0 0 0.25rem;
  color: var(--gf-form-help-text, var(--gf-text-muted));
  font-size: var(--gf-form-help-size, 0.8125rem);
}
.forminator-custom-form .forminator-checkbox,
.forminator-custom-form .forminator-radio {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-height: var(--gf-form-field-height, 3rem);
  padding: var(--gf-form-field-pad-y, 0.75rem) var(--gf-form-field-pad-x, 0.875rem);
  border: var(--gf-form-field-border-width, 1px) solid var(--gf-form-field-border, var(--gf-color-border));
  border-radius: var(--gf-form-field-radius, var(--gf-radius-md));
  background: var(--gf-form-field-bg, transparent);
  color: var(--gf-form-field-text, inherit);
  font-size: var(--gf-form-field-size, 1rem);
  line-height: 1.3;
  cursor: pointer;
  transition: border-color 0.15s var(--gf-easing-standard, ease), background-color 0.15s var(--gf-easing-standard, ease);
}
.forminator-custom-form .forminator-checkbox:hover,
.forminator-custom-form .forminator-radio:hover {
  border-color: var(--gf-color-primary);
}
.forminator-custom-form .forminator-checkbox:has(input:checked),
.forminator-custom-form .forminator-radio:has(input:checked) {
  border-color: var(--gf-color-primary);
  background: color-mix(in srgb, var(--gf-color-primary) 12%, var(--gf-form-field-bg, transparent));
  box-shadow: inset 0 0 0 1px var(--gf-color-primary);
}
.forminator-custom-form .forminator-checkbox:has(input:focus-visible),
.forminator-custom-form .forminator-radio:has(input:focus-visible) {
  box-shadow: var(--gf-focus-ring-shadow);
}
.forminator-custom-form .forminator-checkbox input,
.forminator-custom-form .forminator-radio input {
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  margin: 0;
  accent-color: var(--gf-color-primary);
}
/* Decorative spans Forminator draws for its own skins; with style "none" they are empty boxes. */
.forminator-custom-form .forminator-checkbox-box,
.forminator-custom-form .forminator-radio-bullet {
  display: none;
}

/* Next and Back: Next matches gf-builder's submit (forms.css), Back is the outline form. */
.forminator-custom-form .forminator-pagination-footer {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  margin-block-start: var(--wp--preset--spacing--md, 1.5rem);
}
.forminator-custom-form .forminator-button-next,
.forminator-custom-form .forminator-button-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--gf-control-md-height, 2.75rem);
  padding-block: var(--gf-control-md-pad-block, 0.75rem);
  padding-inline: var(--gf-control-md-pad-inline, 1rem);
  border-radius: var(--gf-button-radius);
  font-family: inherit;
  font-size: var(--gf-type-body-size, 1rem);
  font-weight: var(--gf-button-font-weight);
  line-height: 1.4;
  cursor: pointer;
}
.forminator-custom-form .forminator-button-next {
  border: 1px solid var(--gf-button-primary-border, transparent);
  background: var(--gf-button-primary-bg);
  box-shadow: var(--gf-button-shadow);
  color: var(--gf-button-primary-color);
}
.forminator-custom-form .forminator-button-next:hover {
  border-color: var(--gf-button-primary-border-hover, transparent);
  background: var(--gf-button-primary-bg-hover);
  box-shadow: var(--gf-button-shadow-hover);
  color: var(--gf-button-primary-color-hover);
}
.forminator-custom-form .forminator-button-back {
  border: 1px solid var(--gf-form-field-border, var(--gf-color-border));
  background: transparent;
  color: var(--gf-form-label-text, inherit);
}
.forminator-custom-form .forminator-button-back:hover {
  border-color: var(--gf-color-primary);
}
.forminator-custom-form .forminator-button-next:focus-visible,
.forminator-custom-form .forminator-button-back:focus-visible {
  box-shadow: var(--gf-focus-ring-shadow);
  outline: none;
}
.forminator-custom-form .forminator-button.forminator-hidden {
  display: none;
}

/* 52.1 While sending: Forminator drops a "Submitting..." box at the TOP of the form,
   above the step bar, which pushed the whole form down for about a second before the
   redirect (seen 11 Nov). Keep it for screen readers (it is an aria-live alert) and
   show the state on the submit button instead, where the visitor is looking. */
.forminator-custom-form .forminator-response-message.forminator-loading {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  border: 0;
  white-space: nowrap;
}
.forminator-custom-form:has(.forminator-response-message.forminator-loading) .forminator-button-submit {
  gap: 0.5rem;
  opacity: 0.75;
  pointer-events: none;
  cursor: progress;
}
.forminator-custom-form:has(.forminator-response-message.forminator-loading) .forminator-button-submit::after {
  content: "";
  width: 1em;
  height: 1em;
  border: 2px solid currentColor;
  border-inline-end-color: transparent;
  border-radius: 50%;
  animation: khq-spin 0.7s linear infinite;
}
@keyframes khq-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .forminator-custom-form:has(.forminator-response-message.forminator-loading) .forminator-button-submit::after {
    animation: none;
  }
}

/* ===========================================================================
   60. POLISH PASS — 12 Nov 2026 (approved by Andrew from the polish report)
   ===========================================================================
   Measured on staging at 390px and 1440px across all 71 published URLs.
   Each part names the GF Builder setting that would let it be deleted.
   ------------------------------------------------------------------------ */

/* --- 60.1 Column dividers only where there is a column to the left ------
   §44.7 draws a hairline + 16px inset before every dark-band grid item but
   the first. When the grid stacks (footer on phones: 1 column; stats: 2 per
   row) the line and inset landed on items with nothing beside them, so
   "Industries & Work", "Company" and "Talk to us" sat 17px right of
   "Services". Breakpoints match GF Builder's grid (48rem / 64rem).
   Deletable when the grid block gets a "divider between columns" setting. */
@media (max-width: 47.99rem) {
  .is-style-dark .gf-grid-mobile-1 > :is(.gf-stat, .gf-stack),
  .is-style-dark .gf-grid-mobile-2 > :is(.gf-stat, .gf-stack):nth-child(odd) {
    padding-inline-start: 0;
    border-inline-start: 0;
  }
}
@media (min-width: 48rem) and (max-width: 63.99rem) {
  .is-style-dark .gf-grid-tablet-1 > :is(.gf-stat, .gf-stack),
  .is-style-dark .gf-grid-tablet-2 > :is(.gf-stat, .gf-stack):nth-child(odd) {
    padding-inline-start: 0;
    border-inline-start: 0;
  }
}

/* --- 60.2 Post category link: a tappable size ---------------------------
   The category under a post title measured 17px tall at 390px. Padding the
   link (not the text) gives a 32px target without moving anything: the
   negative margin cancels the padding visually. */
.khq-archive-terms a {
  display: inline-block;
  padding-block: 0.4375rem;
  margin-block: -0.4375rem;
}

/* --- 60.3 Cards lift on hover (team and article loops) -------------------
   2px rise and a softer, wider shadow. Pointer devices only, so a tap on a
   phone never leaves a card stuck "lifted"; off for reduced motion.
   Deletable when post-loop gets a card hover setting. */
@media (hover: hover) and (pointer: fine) {
  .gf-post-loop__item.gf-card {
    transition: transform 200ms ease, box-shadow 200ms ease;
  }
  .gf-post-loop__item.gf-card:hover,
  .gf-post-loop__item.gf-card:focus-within {
    transform: translateY(-2px);
    box-shadow: 0 10px 28px -12px rgb(0 0 0 / 0.22);
  }

  /* --- 60.4 Team photos zoom 3% on hover --------------------------------
     The media box already clips (overflow: hidden), so the zoom stays
     inside the card's frame. Team loops only: bin/seed-team.php gives each
     roster post-loop the class khq-team-loop, so nobody types it. */
  .khq-team-loop .gf-post-loop__image {
    transition: transform 320ms ease;
  }
  .khq-team-loop .gf-post-loop__item:hover .gf-post-loop__image {
    transform: scale(1.03);
  }
}
@media (prefers-reduced-motion: reduce) {
  .gf-post-loop__item.gf-card,
  .khq-team-loop .gf-post-loop__image {
    transition: none;
  }
  .gf-post-loop__item.gf-card:hover,
  .gf-post-loop__item.gf-card:focus-within,
  .khq-team-loop .gf-post-loop__item:hover .gf-post-loop__image {
    transform: none;
  }
}

/* --- 60.5 Overlay carousel: room for the quote on phones ----------------
   GF Builder fixes an overlay slide's image at 21:9 at every width, and the
   quote sits on top of it. At 390px the slide measured 101px tall, so the
   rating, quote and name overflowed and were clipped (home, Website
   Maintenance, Why Kreativ, Testimonials). Below 48rem the image takes a
   portrait-ish frame instead, cropped from the middle; photos are cropped
   so faces sit in that middle band. Deletable when carousel-slide gets an
   image aspect / height setting (and a focal point). */
@media (max-width: 47.99rem) {
  .gf-media-overlay .gf-carousel-slide__image {
    aspect-ratio: 4 / 5;
  }
}

/* ---------------------------------------------------------------------------
 * 61. Job pages (careers portal, 12 Nov 2026).
 *
 * The role description is HTML from the job's "About the role" field, printed
 * by [khq_job_body] (mu-plugins/khq-careers/jobs.php), not blocks, so it gets
 * the same reading rhythm as §40 here. Site feature, not a GF gap: nothing to
 * delete upstream. The interviewing badge on job cards comes from the
 * gf_builder_post_loop_card_meta filter (class khq-job-badge).
 * ------------------------------------------------------------------------ */
.khq-job-body {
  font-size: var(--wp--preset--font-size--body-lg);
  line-height: 1.7;
}

.khq-job-body > * {
  max-width: 68ch;
  margin-block: 0 1em;
}

.khq-job-body > p > strong:only-child {
  display: block;
  margin-block-start: 1.5em;
  font-size: var(--wp--preset--font-size--heading-sm);
  line-height: 1.3;
}

.khq-job-body > p:first-child > strong:only-child {
  margin-block-start: 0;
}

.khq-job-body ul,
.khq-job-body ol {
  padding-inline-start: 1.25em;
}

.khq-job-body ul {
  list-style: disc;
}

.khq-job-body ol {
  list-style: decimal;
}

.khq-job-body li {
  margin-block-end: 0.4em;
}

.khq-job-apply {
  margin-block-start: var(--wp--preset--spacing--xl);
  padding-block-start: var(--wp--preset--spacing--lg);
  border-block-start: 1px solid var(--wp--preset--color--border);
}

.khq-job-apply > h2 {
  margin-block: 0 0.4em;
}

.khq-job-apply > p {
  margin-block: 0 var(--wp--preset--spacing--lg);
}

.khq-job-loop .khq-job-badge {
  font-weight: 700;
  color: var(--wp--preset--color--warning);
}
