/* ==========================================================================
   Reusable UI components — nav, buttons, cards, glass panels.
   ========================================================================== */

/* Site header / nav — a soft hairline + diffuse shadow reads as a subtle
   "floating glass" edge rather than a hard ruled line, and both fade in
   together (is-scrolled below) once there's actually content behind the
   header to separate from; at the very top of the page (nothing
   scrolled behind it yet) the flatter, barely-there edge looks cleaner. */
.site-header {
    position: sticky;
    top: 0;
    z-index: 50;
    background: rgba(251, 243, 232, 0.82);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    box-shadow: 0 1px 0 var(--color-border);
    transition: background-color var(--transition-base), box-shadow var(--transition-base);
}

/* Toggled by initHeaderScrollState() in main.js once the page has
   scrolled a few pixels — a touch more opaque plus a real soft shadow,
   so the header gains depth against whatever's now scrolled underneath
   it instead of relying on the hairline alone. */
.site-header.is-scrolled {
    box-shadow: 0 1px 0 var(--color-border), var(--shadow-sm);
}

/* Homepage only — lets the hero's background video show through behind
   the header instead of the solid cream wash every other page uses (see
   .hero-intro--video in layout.css, which bleeds the video up behind
   this same sticky header rather than pulling the header out of flow —
   .site-header's position/behaviour is otherwise completely unchanged,
   so every other page is unaffected). Fully transparent with no blur at
   the top of the page — any tint or backdrop-blur read as a visible
   "stripe" sitting on top of the video, which is exactly what this is
   meant to avoid; the nav/CTA/wordmark all already carry their own
   shadows and solid pill backgrounds, so they stay legible without the
   header itself adding a wash. is-scrolled still gets a real background
   (below) once the video has scrolled out from behind the header and
   there's real page content behind it instead — unlike the video, plain
   page content doesn't already guarantee contrast on its own. */
body.page-home .site-header {
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    box-shadow: 0 1px 0 transparent;
}

body.page-home .site-header.is-scrolled {
    background: rgba(251, 243, 232, 0.82);
    box-shadow: 0 1px 0 var(--color-border), var(--shadow-sm);
}

.nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-block: var(--space-md);
}

/* The wordmark — sized and weighted up from a generic nav label into a
   confident brand mark, with a touch of negative tracking (a classic
   "considered" signal on a display serif) and the same pointer-tilt
   interactivity every other clickable element on the site already has
   (see initTilt() in main.js) — previously the one interactive header
   element that didn't participate in that, which read as unfinished. */
.nav__brand {
    position: relative;
    display: inline-block;
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: var(--fs-xl);
    letter-spacing: -0.01em;
    color: var(--color-text);
    transform: perspective(var(--perspective))
        rotateX(var(--tilt-x)) rotateY(var(--tilt-y))
        translateZ(var(--tilt-z));
    transition: transform 120ms ease-out, color var(--transition-fast);
}

.nav__brand:hover {
    color: var(--color-accent-text);
    --tilt-z: 6px;
}

/* Wordmark flourish (see the Ali Abdaal-style reference brand mark this
   was modelled on) — a hand-drawn squiggle that draws itself in under
   the name on load, then keeps a slow, gentle wave going so the mark
   always reads as a touch more alive than a plain text logo. Applied
   site-wide (same markup, shared via includes/nav.php) so the header
   looks identical on every page rather than only on the homepage. */
.nav__brand-swoosh {
    display: block;
    position: absolute;
    left: -2px;
    bottom: -11px;
    width: 105%;
    height: 16px;
    pointer-events: none;
}

.nav__brand-swoosh path {
    fill: none;
    stroke: var(--color-accent);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-dasharray: 260;
    stroke-dashoffset: 260;
    animation: brand-swoosh-draw 1.3s ease-out 0.4s 1 forwards,
        brand-swoosh-wave 3.6s ease-in-out 1.8s infinite;
    transform-origin: center;
}

.nav__brand:hover .nav__brand-swoosh path {
    stroke: var(--color-accent-2);
}

@keyframes brand-swoosh-draw {
    from {
        stroke-dashoffset: 260;
    }

    to {
        stroke-dashoffset: 0;
    }
}

@keyframes brand-swoosh-wave {
    0%, 100% {
        transform: translateY(0) scaleX(1);
    }

    50% {
        transform: translateY(-2px) scaleX(1.03);
    }
}

/* Mobile menu toggle — circular badge, morphs into an X purely off the
   aria-expanded state JS already keeps in sync. display:none on desktop
   (below) means every property here is mobile-only by construction —
   nothing to leak, nothing to reset. */
.nav__toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 54px;
    height: 54px;
    border-radius: 50%;
    background: var(--color-bg-alt);
    border: 1px solid var(--color-border);
    box-shadow: var(--shadow-sm);
    transform: perspective(var(--perspective))
        rotateX(var(--tilt-x)) rotateY(var(--tilt-y))
        translateZ(var(--tilt-z)) scale(var(--tilt-scale));
    transition: transform 120ms ease-out, border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.nav__toggle:hover {
    border-color: var(--color-accent);
    box-shadow: var(--shadow-3d);
    --tilt-z: 8px;
}

.nav__toggle[aria-expanded="true"] {
    background: var(--gradient-accent);
    border-color: transparent;
}

.nav__toggle-bar,
.nav__toggle-bar::before,
.nav__toggle-bar::after {
    content: '';
    display: block;
    width: 20px;
    height: 2px;
    border-radius: 2px;
    background: var(--color-text);
    position: relative;
    transition: transform 250ms cubic-bezier(0.16, 1, 0.3, 1), opacity 150ms ease, background-color 150ms ease, top 250ms cubic-bezier(0.16, 1, 0.3, 1);
}

.nav__toggle-bar::before {
    position: absolute;
    top: -6px;
}

.nav__toggle-bar::after {
    position: absolute;
    top: 6px;
}

.nav__toggle[aria-expanded="true"] .nav__toggle-bar {
    background: transparent;
}

.nav__toggle[aria-expanded="true"] .nav__toggle-bar::before,
.nav__toggle[aria-expanded="true"] .nav__toggle-bar::after {
    top: 0;
    background: var(--color-text);
}

.nav__toggle[aria-expanded="true"] .nav__toggle-bar::before {
    transform: rotate(45deg);
}

.nav__toggle[aria-expanded="true"] .nav__toggle-bar::after {
    transform: rotate(-45deg);
}

/* Dimmed backdrop behind the drawer — click it to close (see main.js).
   display:none on desktop below, so this is mobile-only by construction. */
.nav__scrim {
    position: fixed;
    inset: 0;
    z-index: 40;
    background: rgba(25, 26, 46, 0.45);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--transition-base);
}

body.nav-open .nav__scrim {
    opacity: 1;
    pointer-events: auto;
}

/* Dropdown panel — full width, appears directly below the sticky header
   via top:100% (this is the standard "position:sticky/relative parent +
   position:absolute child at top:100%" pattern, so it auto-adjusts to
   whatever the header's real rendered height is — no hardcoded pixel
   guess). .site-header is the nearest positioned ancestor (position:
   sticky also establishes a containing block), and neither .container
   nor .nav set their own `position`, so this positions against the full
   header width, not just the inner padded container. Fades + slides down
   rather than sliding in from the side. Contains the nav links, social
   icons, and a CTA — all mobile-menu-only, hidden entirely on desktop
   below (the original desktop header never had socials/CTA in it). */
.nav__panel {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 45;
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
    max-height: 85vh;
    overflow-y: auto;
    padding: var(--space-lg) var(--space-md) var(--space-xl);
    background: var(--color-bg-alt);
    background-image: radial-gradient(circle at 100% 0%, rgba(255, 107, 74, 0.1), transparent 55%),
        radial-gradient(circle at 0% 100%, rgba(62, 198, 240, 0.1), transparent 50%);
    border-top: 1px solid var(--color-border);
    box-shadow: 0 28px 48px -16px rgba(25, 26, 46, 0.3);
    opacity: 0;
    transform: translateY(-12px);
    pointer-events: none;
    transition: opacity var(--transition-base), transform var(--transition-base);
}

.nav__panel.is-open {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}

.nav__list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    perspective: var(--perspective);
}

/* Each row: icon + label, generous padding for a real touch target
   (>=48px tall including padding, well above the ~44px minimum). */
.nav__link {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding-block: var(--space-sm);
    font-size: var(--fs-md);
    font-weight: 600;
    color: var(--color-text);
    border-bottom: 1px solid var(--color-border);
    transform: perspective(var(--perspective))
        rotateX(var(--tilt-x)) rotateY(var(--tilt-y))
        translateZ(var(--tilt-z));
    transition: transform 120ms ease-out, color var(--transition-fast), padding-left var(--transition-fast);
}

.nav__link-icon {
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    color: var(--color-accent-text);
}

.nav__link:hover,
.nav__link[aria-current="page"] {
    color: var(--color-accent-text);
    padding-left: var(--space-xs);
    --tilt-z: 6px;
}

.nav__panel-socials {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-xs);
    padding-top: var(--space-md);
    border-top: 1px solid var(--color-border);
}

/* The global .social-icon size (56px, set further down this file) is
   tuned for the footer's roomier layout. At 5 icons wide, that size needs
   ~328px of unbroken row width — more than fits on common ~320-375px
   phones once the panel's own padding is subtracted, so the row wraps
   unevenly onto a second line. Sized down specifically for this narrower
   context (still a comfortable 44px touch target) so all 5 sit on one
   row down to a 320px-wide viewport with room to spare. */
.nav__panel-socials .social-icon {
    width: 44px;
    height: 44px;
}

.nav__panel-socials .social-icon svg {
    width: 22px;
    height: 22px;
}

/* The single "Book a Consultation" button (see includes/nav.php) — full
   width at the bottom of the mobile dropdown by default; repositioned
   into a compact inline pill by the desktop override below. The arrow
   is generated content rather than a real element, so it needs no
   markup change and still participates in the button's own flex/gap
   layout like a normal inline-flex child; it nudges right on hover as a
   small "go" cue instead of the button just sitting static. */
.nav__cta {
    width: 100%;
}

.nav__cta::after {
    content: '\2192';
    display: inline-block;
    transition: transform var(--transition-fast);
}

.nav__cta:hover::after {
    transform: translateX(3px);
}

@media (min-width: 900px) {
    .nav__toggle {
        display: none;
    }

    .nav__scrim {
        display: none;
    }

    .nav__cta {
        width: auto;
        display: inline-flex;
        margin-left: var(--space-lg);
        padding: 0.95em 2em;
        font-size: var(--fs-sm);
    }

    /* .nav uses justify-content: space-between for the 2-item mobile case
       (brand / toggle). On desktop there are 3 flex children in the row
       (brand, panel, cta) — space-between would spread all 3 apart with
       an ugly gap before the CTA, so margin-left: auto on .nav__panel
       does the "push everything from here on right" job instead, and
       .nav__cta's own margin-left above gives it a small gap from the
       links next to it. */
    .nav__panel {
        position: static;
        display: flex;
        align-items: center;
        margin-left: auto;
        max-height: none;
        overflow: visible;
        padding: 0;
        background: none;
        border: none;
        box-shadow: none;
        opacity: 1;
        transform: none;
        pointer-events: auto;
    }

    .nav__list {
        flex-direction: row;
        align-items: center;
        gap: var(--space-xl);
    }

    .nav__panel-socials {
        display: none;
    }

    .nav__link-icon {
        display: none;
    }

    .nav__link {
        display: inline-flex;
        padding-block: var(--space-xs) 0;
        gap: 0;
        border-bottom: none;
        font-size: var(--fs-md);
        font-weight: 600;
    }

    .nav__link:hover {
        padding-left: 0;
    }

    /* Desktop-only animated underline — grows from the centre on hover
       and stays fully drawn for the current page, instead of the plain
       colour swap the mobile dropdown uses (which already has its own
       border-bottom row divider and doesn't need this too). transform:
       scaleX (not width) so it's a cheap compositor-only animation. */
    .nav__link::after {
        content: '';
        position: absolute;
        left: 0;
        right: 0;
        bottom: -4px;
        height: 2px;
        border-radius: 2px;
        background: var(--gradient-accent);
        transform: scaleX(0);
        transform-origin: center;
        transition: transform var(--transition-base);
    }

    .nav__link:hover::after,
    .nav__link[aria-current="page"]::after {
        transform: scaleX(1);
    }
}

/* Buttons — smaller pointer-driven tilt + a press-down 3D effect on click */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    padding: 0.85em 1.75em;
    border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
    font-weight: 600;
    perspective: var(--perspective);
    transform: perspective(var(--perspective))
        translateY(var(--reveal-y)) rotateX(var(--reveal-rx))
        rotateX(var(--tilt-x)) rotateY(var(--tilt-y))
        translateZ(var(--tilt-z)) scale(var(--tilt-scale));
    transition: transform 120ms ease-out, box-shadow var(--transition-fast), opacity var(--transition-fast), border-color var(--transition-fast);
}

.btn:active {
    --tilt-scale: 0.96;
    --tilt-z: -6px;
}

.btn--primary {
    background: var(--gradient-accent);
    color: var(--color-text);
    box-shadow: var(--shadow-sm);
}

.btn--primary:hover {
    box-shadow: var(--shadow-glow);
    --tilt-z: 10px;
}

.btn--secondary {
    background: var(--color-bg-alt);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    box-shadow: var(--shadow-sm);
}

.btn--secondary:hover {
    border-color: var(--color-accent);
    color: var(--color-accent-text);
    box-shadow: var(--shadow-md);
    --tilt-z: 10px;
}

/* Glass panel / card — pointer-driven 3D tilt, set via --tilt-x/--tilt-y/--tilt-z in main.js */
.card {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-md);
    padding: var(--space-lg);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    perspective: var(--perspective);
    transform-style: preserve-3d;
    transform: perspective(var(--perspective))
        translateY(var(--reveal-y)) rotateX(var(--reveal-rx))
        rotateX(var(--tilt-x)) rotateY(var(--tilt-y))
        translateZ(var(--tilt-z)) scale(var(--tilt-scale));
    transition: transform 120ms ease-out, border-color var(--transition-base), box-shadow var(--transition-base);
    isolation: isolate;
    overflow: hidden;
}

/* Grid rows already stretch every .card in that row to equal height (CSS
   Grid's default align-items: stretch) — but without this, a trailing
   CTA link just sits wherever the card's own text happens to end, so
   three cards side by side with slightly different copy length show
   their buttons at three different heights. margin-top: auto pushes the
   link to the bottom of whatever height the row settled on, so every
   button in a row lines up on the same baseline regardless of how much
   text sits above it. */
.card > .btn {
    margin-top: auto;
}

.card:hover {
    border-color: var(--color-accent);
    box-shadow: var(--shadow-3d);
}

/* Warm sheen that tracks the pointer, layered above the card content */
.card::after {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(circle at var(--glare-x) var(--glare-y), rgba(255, 107, 74, 0.14), transparent 55%);
    opacity: 0;
    transition: opacity var(--transition-fast);
    pointer-events: none;
    z-index: 1;
}

.card:hover::after {
    opacity: 1;
}

/* Small floating icon badge used to give each homepage section a
   distinct, lively visual next to its heading — a mini logo-style
   lockup rather than a wall of plain headings back to back. Purely
   decorative (icons are inline SVGs marked aria-hidden in the markup),
   animated with a gentle bob so the page doesn't read as static;
   prefers-reduced-motion already zeroes every animation's duration
   globally (see reset.css), so this respects that automatically without
   its own override. */
.section-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 64px;
    height: 64px;
    border-radius: var(--radius-md);
    background: var(--gradient-accent);
    box-shadow: var(--shadow-md);
    color: var(--color-bg-alt);
    animation: section-icon-float 5s ease-in-out infinite;
}

.section-icon svg {
    width: 32px;
    height: 32px;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
}

@keyframes section-icon-float {
    0%, 100% {
        transform: translateY(0) rotate(-2deg);
    }

    50% {
        transform: translateY(-8px) rotate(2deg);
    }
}

/* Icon + heading lockup — the icon sits beside the eyebrow/heading/lede
   stack rather than above it, so it reads as part of the heading rather
   than a separate decorative element competing for attention. */
.section-heading--icon {
    display: flex;
    align-items: flex-start;
    gap: var(--space-md);
}

.section-heading--icon .section-icon {
    margin-top: var(--space-2xs);
}

/* Punchier, larger intro line used for a section's single descriptive
   sentence — bigger and airier than default body copy, matching a more
   confident "creator brand" tone than dense small paragraph text. */
.lede {
    font-size: var(--fs-md);
    line-height: 1.5;
}

@media (min-width: 768px) {
    .lede {
        font-size: var(--fs-lg);
    }
}

.card > * {
    position: relative;
    z-index: 2;
}

/* Card titles are <h2> for correct document heading order (no page should
   skip from <h1> straight to <h3>) but should still read as a card
   sub-heading, not a full section heading — same size h3 got before. */
.card h2 {
    font-size: var(--fs-lg);
}

.card__icon {
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-sm);
    background: var(--gradient-accent);
    margin-bottom: var(--space-md);
}

.card__icon svg {
    width: 24px;
    height: 24px;
    stroke: var(--color-bg-alt);
    fill: none;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Real favicon variant (Tools page) — a light, neutral tile instead of
   the gradient badge, since a fetched favicon already carries its own
   brand colour and would clash sitting on top of another colour. White
   background covers the common case of a favicon with a transparent
   background so it doesn't pick up the card's own background through it. */
.card__icon--favicon {
    background: var(--color-bg-alt);
    border: 1px solid var(--color-border);
}

.card__icon--favicon img {
    width: 28px;
    height: 28px;
    object-fit: contain;
    border-radius: 4px;
}

/* Icon-tile card variant — square icon on top, centered underlined
   title, short centered blurb (per the "Superfocus"-style reference
   card grid). Used for the Services Preview and Featured Projects
   grids instead of the default left-aligned card layout. */
.card--icon {
    align-items: center;
    text-align: center;
}

.card--icon h3 {
    text-decoration: underline;
    text-underline-offset: 4px;
    text-decoration-color: var(--color-accent);
    text-decoration-thickness: 2px;
}

/* Light content section (per design direction: dark bg, light content blocks) */
.section--light {
    background: var(--color-surface-light);
    color: var(--color-text-on-light);
    border-radius: var(--radius-lg);
    padding: var(--space-xl) var(--space-lg);
}

@media (min-width: 768px) {
    .section--light {
        padding: var(--space-2xl);
    }
}

.section--light p {
    color: var(--color-text-on-light-muted);
}

/* Compact CTA variant of .section--light — a one-line heading plus a
   single button, as opposed to the multi-paragraph variant elsewhere on
   the homepage. Without this, that short amount of content just stacks
   top-left inside the panel's normal padding, leaving most of a
   1200px-wide box empty. Laying text and button out as a row instead
   uses that width properly, and doubles as a clear visual "next action"
   moment rather than another paragraph block. Wraps to a stacked layout
   below 640px, where there's no spare width to lay out as a row anyway. */
.section--light--cta {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-md);
}

.section--light--cta h3 {
    margin-bottom: var(--space-2xs);
}

@media (min-width: 640px) {
    .section--light--cta {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: var(--space-lg);
    }

    .section--light--cta > .btn {
        flex: 0 0 auto;
    }
}

/* Scroll-reveal animation hook — toggled via IntersectionObserver in main.js.
   Tilts up out of the page on entry rather than a flat fade/slide.
   Sets --reveal-y/--reveal-rx rather than `transform` directly, so .card
   and .btn (which read those vars into their own transform) can layer
   the reveal motion together with their pointer-tilt effect instead of
   one clobbering the other. Plain reveal elements (not a card/button)
   get their own transform below.

   Progressive enhancement: a bare .reveal element is fully visible, full
   stop — it's just a hook JS can opt into. Only main.js adds .js-armed
   (right before it starts observing), which is what actually applies the
   hidden starting state. That way content never depends on JS running at
   all: if the script fails to load, errors out, or is blocked, everything
   marked .reveal simply renders normally instead of staying invisible
   forever. Reduced-motion is handled the same way, one level up — main.js
   checks prefers-reduced-motion before ever adding .js-armed, so nothing
   here needs its own reduced-motion override. */
.reveal.js-armed {
    opacity: 0;
    --reveal-y: 32px;
    --reveal-rx: 10deg;
    transition: opacity var(--transition-slow), transform var(--transition-slow);
}

.reveal.js-armed:not(.card):not(.btn) {
    transform: perspective(var(--perspective)) translateY(var(--reveal-y)) rotateX(var(--reveal-rx));
    transform-origin: bottom center;
}

.reveal.js-armed.is-visible {
    opacity: 1;
    --reveal-y: 0px;
    --reveal-rx: 0deg;
}

@media (prefers-reduced-motion: reduce) {
    .card,
    .btn,
    .nav__link,
    .social-icon,
    .nav__toggle,
    .nav__panel,
    .nav__scrim {
        transition: none;
    }
}

/* Forms */

/* The contact form reuses .card purely for its glass-panel look, but
   .card's align-items: flex-start (added so card-grid CTA buttons pin to
   a shared baseline — see .card > .btn below) also shrinks a flex-column
   form's own <div class="form-group"> children down to their content
   width instead of letting them fill the card, since flex-start sizes
   each item to its own content by default. That's what made the inputs,
   select and textarea render narrow instead of full-width. Restretching
   here fixes the form without touching .card's grid behaviour anywhere
   else on the site. The submit button also loses .card > .btn's
   margin-top: auto (a grid-row-height trick that has no meaningful job
   inside a single-column form) so it sits in normal flow instead of
   floating detached from the field above it. */
form.card {
    align-items: stretch;
}

form.card > .btn {
    margin-top: 0;
    align-self: flex-start;
}

.form-group {
    width: 100%;
    margin-bottom: var(--space-md);
}

.form-group label {
    display: block;
    font-size: var(--fs-sm);
    font-weight: 600;
    margin-bottom: var(--space-2xs);
}

.form-group input,
.form-group select,
.form-group textarea {
    width: 100%;
    padding: 0.75em 1em;
    background: var(--color-bg-alt);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--color-text);
    transition: border-color var(--transition-fast);
}

.form-group select {
    cursor: pointer;
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
    border-color: var(--color-accent);
}

.form-group--honeypot {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.form-note {
    margin-top: var(--space-md);
    font-size: var(--fs-xs);
    color: var(--color-text-muted);
}

.form-alert {
    padding: var(--space-md);
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-lg);
    font-size: var(--fs-sm);
}

.form-alert--success {
    background: rgba(52, 199, 89, 0.12);
    border: 1px solid rgba(52, 199, 89, 0.35);
    color: var(--color-text);
}

.form-alert--error {
    background: rgba(255, 69, 58, 0.1);
    border: 1px solid rgba(255, 69, 58, 0.35);
    color: var(--color-text);
}

.form-alert--error ul {
    list-style: disc;
    padding-left: var(--space-lg);
    margin-top: var(--space-xs);
}

/* Site footer */
.site-footer {
    position: relative;
    background: var(--color-surface-light);
    border-top: 1px solid var(--color-border);
    padding-block: var(--space-2xl) var(--space-lg);
    color: var(--color-text-muted);
    font-size: var(--fs-sm);
    overflow: hidden;
}

/* A thin gradient accent bar tops the footer instead of a plain hairline,
   giving the page a clear, colourful "end point" rather than fading out
   on the same flat border used between every other section. */
.site-footer::before {
    content: '';
    position: absolute;
    top: -1px;
    left: 0;
    right: 0;
    height: 3px;
    background: var(--gradient-accent);
}

.site-footer__inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-2xs);
}

.site-footer__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-xl);
    padding-bottom: var(--space-xl);
    margin-bottom: var(--space-lg);
    border-bottom: 1px solid var(--color-border);
}

@media (min-width: 640px) {
    .site-footer__grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 900px) {
    .site-footer__grid {
        /* Brand column + Explore (Quick Links + Resources merged) +
           Services + a dedicated CTA column — 4 balanced tracks so each
           column's content fills a similar visual height instead of
           leaving short columns stranded next to a tall one. */
        grid-template-columns: 1.3fr 0.9fr 1fr 0.9fr;
        gap: var(--space-lg);
    }
}

.site-footer__col {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-sm);
}

/* Each footer link gets a small arrow that slides in on hover — a subtle
   "this goes somewhere" cue, matching the nudge-right treatment already
   used on the nav's mobile CTA arrow, instead of a plain colour swap. */
.site-footer__col a {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    color: var(--color-text-muted);
    line-height: 1.3;
    transition: color var(--transition-fast), padding-left var(--transition-fast);
}

.site-footer__col a::before {
    content: '\2192';
    display: inline-block;
    width: 0;
    overflow: hidden;
    opacity: 0;
    color: var(--color-accent-text);
    transition: width var(--transition-fast), opacity var(--transition-fast);
}

.site-footer__col a:hover {
    color: var(--color-accent-text);
}

.site-footer__col a:hover::before {
    width: 1em;
    opacity: 1;
}

.site-footer__brand {
    gap: var(--space-md);
    max-width: 320px;
}

.site-footer__brand-header {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}

/* Same background-removed cutout as the heroes, just small — no crop mask
   here either, for consistency with the hero treatment. A soft gradient
   ring (via padding + background on the wrapping element) replaces the
   plain drop-shadow so the portrait reads as a small badge rather than a
   loose floating thumbnail. */
.site-footer__portrait-ring {
    display: inline-flex;
    flex-shrink: 0;
    padding: 3px;
    border-radius: 50%;
    background: var(--gradient-accent);
    box-shadow: var(--shadow-sm);
}

.site-footer__portrait {
    display: block;
    width: 48px;
    height: 48px;
    object-fit: cover;
    border-radius: 50%;
    border: 2px solid var(--color-surface-light);
}

.site-footer__brand-name {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: var(--fs-lg);
    letter-spacing: -0.01em;
    color: var(--color-text);
}

.site-footer__tagline {
    margin: 0;
}

/* Small colour tick before each column heading — a quick visual anchor
   so the four columns read as distinct, labelled groups at a glance
   rather than plain uppercase text blending into the muted link list
   beneath it. */
.site-footer__heading {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    font-size: var(--fs-md);
    font-weight: 800;
    color: var(--color-text);
    text-transform: uppercase;
    letter-spacing: 0.07em;
    margin-bottom: var(--space-xs);
}

.site-footer__heading::before {
    content: '';
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--gradient-accent);
    box-shadow: 0 0 0 4px var(--color-accent-glow);
}

.site-footer__col--cta {
    padding: var(--space-md);
    border-radius: var(--radius-md);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
}

.site-footer__col--cta p {
    margin: 0;
}

.site-footer__cta {
    margin-top: var(--space-2xs);
}

.placeholder-note {
    font-size: var(--fs-xs);
    color: var(--color-text-muted);
    font-style: italic;
}

.site-footer__socials {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
    perspective: var(--perspective);
    margin-top: var(--space-2xs);
}

/* Smaller than the default .social-icon size (56px) so all 5 icons sit on
   one row inside the footer's narrower brand column without wrapping. */
.site-footer__socials .social-icon {
    width: 42px;
    height: 42px;
}

.site-footer__socials .social-icon svg {
    width: 20px;
    height: 20px;
}

.site-footer__meta {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2xs);
}

.site-footer__legal {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    font-size: var(--fs-xs);
}

.site-footer__legal a {
    color: var(--color-text-muted);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.site-footer__legal a:hover {
    color: var(--color-accent-text);
}

.social-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--color-bg-alt);
    border: 1px solid var(--color-border);
    box-shadow: var(--shadow-sm);
    transform: perspective(var(--perspective))
        rotateX(var(--tilt-x)) rotateY(var(--tilt-y))
        translateZ(var(--tilt-z)) scale(var(--tilt-scale));
    transition: transform 120ms ease-out, border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.social-icon svg {
    width: 28px;
    height: 28px;
}

.social-icon:hover {
    border-color: var(--color-accent);
    box-shadow: var(--shadow-3d);
    --tilt-z: 12px;
}

/* Stat callouts (e.g. About page track-record row) */
.stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2xs);
    text-align: center;
}

.stat__value {
    font-family: var(--font-heading);
    font-size: var(--fs-2xl);
    font-weight: 700;
    color: var(--color-accent-text);
}

.stat__label {
    font-size: var(--fs-xs);
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

/* Legal pages (Privacy/Cookie Policy) */
.legal-content h2 {
    margin-top: var(--space-xl);
    margin-bottom: var(--space-sm);
    font-size: var(--fs-lg);
}

.legal-content h2:first-child {
    margin-top: 0;
}

.legal-content p {
    margin-bottom: var(--space-md);
}

.legal-content a {
    color: var(--color-text);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.legal-content a:hover {
    color: var(--color-accent-text);
}

.legal-note {
    margin-top: var(--space-lg);
    padding-top: var(--space-lg);
    border-top: 1px solid var(--color-border-light);
    font-size: var(--fs-xs);
    font-style: italic;
}

.table-scroll {
    overflow-x: auto;
    margin-bottom: var(--space-md);
}

.legal-table {
    width: 100%;
    min-width: 480px;
    border-collapse: collapse;
    font-size: var(--fs-sm);
}

.legal-table th,
.legal-table td {
    text-align: left;
    vertical-align: top;
    padding: var(--space-sm);
    border: 1px solid var(--color-border-light);
}

.legal-table th {
    font-family: var(--font-heading);
    font-weight: 700;
}

.legal-table code {
    font-family: monospace;
    background: var(--color-bg);
    padding: 0.1em 0.4em;
    border-radius: 4px;
}

/* Simple two-column skills list (About page) */
.skills-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.skills-list li {
    padding-left: var(--space-md);
    position: relative;
}

.skills-list li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0.6em;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--gradient-accent);
}

/* Two-column variant for a skills-list used as the sole content of a
   full-width section (rather than sitting beside other copy in a grid
   column) — without this, a short list of short phrases just left-aligns
   in a single narrow column and leaves the entire right half of a
   1200px-wide section empty. Still a single column on narrow viewports,
   where there's no spare width to fill in the first place. */
@media (min-width: 640px) {
    .skills-list--grid {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        column-gap: var(--space-xl);
        row-gap: var(--space-sm);
    }

    .skills-list--grid:not(:first-child) {
        margin-top: var(--space-sm);
    }
}

/* Credibility strip (homepage, just under the hero) — short wrapping
   row of pill labels, separated by a middot rather than boxed, so it
   reads as one calm line rather than a row of buttons. Centered so that
   when the items wrap (five short phrases rarely divide evenly across a
   row), the leftover item on its own line sits centered under the row
   above rather than stranded alone at the left edge. */
.credibility-strip {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-sm) var(--space-lg);
    font-size: var(--fs-sm);
    color: var(--color-text-muted);
    font-weight: 600;
}

.credibility-strip li {
    position: relative;
    padding-left: var(--space-lg);
}

.credibility-strip li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-accent);
}

.credibility-strip li:first-child {
    padding-left: 0;
}

.credibility-strip li:first-child::before {
    content: none;
}

/* Small eyebrow-style label above a project card's title */
.project-card__category {
    font-size: var(--fs-xs);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-accent-text);
    margin-bottom: var(--space-xs);
}

/* Blog listing card thumbnail — bleeds edge-to-edge across the top of the
   card via negative margins matching .card's own padding, rather than
   sitting inset inside it like the rest of the card's content, so it
   reads as a proper cover photo rather than a small inline picture. */
.blog-card__thumb {
    display: block;
    margin: calc(-1 * var(--space-lg)) calc(-1 * var(--space-lg)) var(--space-md);
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    overflow: hidden;
}

.blog-card__thumb img {
    display: block;
    width: 100%;
    height: 180px;
    object-fit: cover;
}

/* Full-width cover image at the top of a blog post — same rounded/shadow
   treatment as other large images on the site (e.g. .page-hero__image).
   height: auto (not a fixed/cropped height) shows the whole image at its
   own aspect ratio instead of centre-cropping it — a banner-style upload
   with a headline or important detail near an edge was getting its
   corners/text cut off under the old fixed-height object-fit: cover
   treatment. max-height is only a safety cap for an unusually tall
   (portrait) upload, and object-fit: contain there just letterboxes
   instead of cropping, so nothing is ever cut off. */
.blog-post__cover {
    margin-bottom: var(--space-lg);
    border-radius: var(--radius-lg);
    overflow: hidden;
    box-shadow: var(--shadow-md);
    background: var(--color-surface-light);
}

.blog-post__cover img {
    display: block;
    width: 100%;
    height: auto;
    max-height: 640px;
    object-fit: contain;
    margin-inline: auto;
}

.blog-post__cover figcaption {
    margin-top: var(--space-sm);
    text-align: center;
    color: var(--color-text-muted);
    font-size: var(--fs-sm);
}

/* Post title block — overrides the shared .section-heading's 640px cap
   and homepage-hero-scale h1 (which together produced an awkwardly
   narrow, 4-line-wrapped title with a huge slab of empty space beside it
   for any post whose title runs longer than a short marketing headline).
   A wider column and a more moderate, article-appropriate size wraps a
   typical post title to 1–2 balanced lines instead. */
.blog-post__heading {
    max-width: 820px;
}

.blog-post__heading h1 {
    font-size: var(--fs-2xl);
    line-height: 1.18;
    margin-top: var(--space-2xs);
}

@media (min-width: 640px) {
    .blog-post__heading h1 {
        font-size: var(--fs-3xl);
    }
}

/* Visible breadcrumb trail (Home > Blog > Topic > Post) on the blog post
   page — mirrors the BreadcrumbList schema emitted alongside it so search
   engines and AI answer systems see the same hierarchy a visitor does. */
.breadcrumbs {
    margin-bottom: var(--space-md);
}

.breadcrumbs ol {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2xs);
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: var(--fs-sm);
    color: var(--color-text-muted);
}

.breadcrumbs li {
    display: flex;
    align-items: center;
    gap: var(--space-2xs);
}

.breadcrumbs li:not(:last-child)::after {
    content: '/';
    color: var(--color-border-light);
}

.breadcrumbs a {
    color: var(--color-text-muted);
    text-decoration: none;
}

.breadcrumbs a:hover {
    color: var(--color-accent-text);
    text-decoration: underline;
}

.breadcrumbs li[aria-current="page"] {
    color: var(--color-text);
    font-weight: 600;
}

/* Automatic "In this guide" table of contents, generated from a post's
   own <h2> headings (see blog_add_heading_ids() in includes/functions.php)
   — no admin input required, so it's safe to show for any post that
   happens to have enough headings to be worth summarising. */
.blog-post__toc {
    margin-bottom: var(--space-xl);
    padding: var(--space-lg);
    background: var(--color-bg-alt);
    border: 1px solid var(--color-border);
    border-left: 4px solid var(--color-accent);
    border-radius: var(--radius-md);
}

.blog-post__toc h2 {
    font-family: var(--font-heading);
    font-size: var(--fs-lg);
    font-weight: 700;
    margin-bottom: var(--space-sm);
}

.blog-post__toc ol {
    margin: 0;
    padding-left: 0;
    list-style: none;
    counter-reset: toc-item;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.blog-post__toc li {
    counter-increment: toc-item;
    position: relative;
    padding-left: 2.1em;
}

.blog-post__toc li::before {
    content: counter(toc-item);
    position: absolute;
    left: 0;
    top: 0.05em;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6em;
    height: 1.6em;
    border-radius: 50%;
    background: var(--color-surface-light);
    color: var(--color-accent-text);
    font-size: var(--fs-sm);
    font-weight: 700;
}

.blog-post__toc a {
    color: var(--color-text);
    font-weight: 700;
    text-decoration: none;
}

.blog-post__toc a:hover {
    color: var(--color-accent-text);
    text-decoration: underline;
}

/* Author bio box at the end of every post — a single site-wide component
   (not a per-post field) since every post on this site has the same
   author; matches the "clear authorship" signal both classic SEO and
   GEO/AI-citation guidance recommend. */
.blog-post__author {
    display: flex;
    gap: var(--space-md);
    align-items: flex-start;
    margin-top: var(--space-2xl);
    padding: var(--space-lg);
    background: var(--color-surface-light);
    border-radius: var(--radius-lg);
}

.blog-post__author-label {
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-size: 0.75rem;
    color: var(--color-text-muted);
    margin-bottom: var(--space-2xs);
}

.blog-post__author h2 {
    font-size: var(--fs-md);
    margin-bottom: var(--space-xs);
}

/* Key Takeaways callout — a short, literal-language summary box near the
   top of a post, deliberately plain/scannable rather than styled like a
   testimonial or CTA, since its whole purpose is to be the easiest thing
   on the page for an AI answer engine or featured-snippet crawler to
   quote directly (see admin/blog-edit.php's "Key Takeaways" field). */
.blog-post__takeaways {
    margin-bottom: var(--space-xl);
    padding: var(--space-lg);
    background: var(--color-surface-light);
    border-left: 4px solid var(--color-accent);
    border-radius: var(--radius-md);
}

.blog-post__takeaways-title {
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: var(--fs-md);
    margin-bottom: var(--space-sm);
}

.blog-post__takeaways ul {
    margin: 0;
    padding-left: 1.25em;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}

/* Reading-width column for the whole post body — the shared .container
   is 1200px, which is a fine width for a hero or a card grid but is far
   too wide for a single column of running text (best-practice measure is
   roughly 60–75 characters per line). Every direct child of the article
   (breadcrumbs, cover image, heading, TOC, body copy, FAQ, author box)
   sits inside this narrower, centered column instead. */
.blog-post__container {
    max-width: 760px;
    margin-inline: auto;
}

/* Article body typography — the rendered post body (see blog_format_body()
   in includes/functions.php) is plain semantic HTML with no classes of its
   own, so all of its visual hierarchy comes from these descendant rules.
   Deliberately distinct from the site's homepage/marketing h2 scale, which
   is tuned for short punchy headlines, not long-form reading. */
.blog-post__content {
    font-size: var(--fs-md);
    line-height: 1.75;
    color: var(--color-text);
}

.blog-post__content > * + * {
    margin-top: var(--space-lg);
}

.blog-post__content p {
    margin: 0;
}

.blog-post__content h2 {
    font-family: var(--font-heading);
    font-size: var(--fs-xl);
    line-height: 1.25;
    font-weight: 700;
    margin-top: var(--space-2xl);
    margin-bottom: 0;
    padding-top: var(--space-sm);
    border-top: 1px solid var(--color-border);
    scroll-margin-top: var(--space-lg);
}

.blog-post__content > h2:first-child {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
}

.blog-post__content h3 {
    font-family: var(--font-heading);
    font-size: var(--fs-lg);
    line-height: 1.3;
    font-weight: 700;
    margin-top: var(--space-xl);
    margin-bottom: 0;
}

.blog-post__content ul,
.blog-post__content ol {
    margin: 0;
    padding-left: 1.4em;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.blog-post__content ul {
    list-style: none;
    padding-left: 0.2em;
}

.blog-post__content ul > li {
    position: relative;
    padding-left: 1.3em;
}

.blog-post__content ul > li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0.6em;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--color-accent);
}

.blog-post__content ol {
    list-style: none;
    counter-reset: blog-ol;
    padding-left: 0.2em;
}

.blog-post__content ol > li {
    position: relative;
    padding-left: 2em;
    counter-increment: blog-ol;
}

.blog-post__content ol > li::before {
    content: counter(blog-ol);
    position: absolute;
    left: 0;
    top: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5em;
    height: 1.5em;
    border-radius: 50%;
    background: var(--color-surface-light);
    color: var(--color-accent-text);
    font-size: var(--fs-sm);
    font-weight: 700;
}

.blog-post__content li > ul,
.blog-post__content li > ol {
    margin-top: var(--space-xs);
}

.blog-post__content blockquote {
    margin: 0;
    padding: var(--space-md) var(--space-lg);
    background: var(--color-surface-light);
    border-left: 4px solid var(--color-accent);
    border-radius: var(--radius-md);
    font-style: italic;
    color: var(--color-text);
}

.blog-post__content blockquote p {
    margin: 0;
}

.blog-post__content blockquote p + p {
    margin-top: var(--space-sm);
}

.blog-post__content a {
    color: var(--color-accent-text);
    text-decoration: underline;
    text-decoration-color: var(--color-border);
    text-underline-offset: 0.15em;
    transition: text-decoration-color 0.15s ease;
}

.blog-post__content a:hover {
    text-decoration-color: currentColor;
}

.blog-post__content strong {
    font-weight: 700;
}

.blog-post__content img {
    max-width: 100%;
    height: auto;
    border-radius: var(--radius-md);
}

.blog-post__content .section--light {
    margin: 0;
    padding: var(--space-lg);
    background: var(--color-surface-light);
    border-radius: var(--radius-lg);
}

.blog-post__content .section--light p:first-child {
    margin-top: 0;
}

.blog-post__content .hero__actions {
    margin-top: var(--space-md);
}

.blog-post__content .section-heading {
    margin-top: var(--space-2xl);
    max-width: none;
}

/* Responsive 16:9 video embed — the padding-top percentage trick keeps
   the iframe's aspect ratio correct at any container width without
   needing the `aspect-ratio` property's browser-support caveats to
   matter, since the iframe itself can't size to its intrinsic ratio the
   way an <img> can. */
.blog-post__video {
    margin-bottom: var(--space-xl);
}

.blog-post__video-frame {
    position: relative;
    width: 100%;
    padding-top: 56.25%;
    border-radius: var(--radius-lg);
    overflow: hidden;
    box-shadow: var(--shadow-md);
    background: #000;
}

.blog-post__video-frame iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

.blog-post__video figcaption {
    margin-top: var(--space-sm);
    text-align: center;
    color: var(--color-text-muted);
    font-size: var(--fs-sm);
}

/* Journey section — slow, continuous auto-scrolling marquee instead of a
   static grid, so the 6-step story reads as a flowing timeline. The track
   holds the 6 cards twice back to back (see index.php's aria-hidden
   duplicate set); animating exactly to translateX(-50%) moves precisely
   one full set width, so the loop point is invisible and it appears to
   scroll forever. Pauses on hover/focus so a visitor can actually stop
   and read a card. mask-image fades the two edges to transparent so cards
   entering/leaving the row do so gently rather than being hard-clipped.
   prefers-reduced-motion already forces every animation-duration to
   0.01ms globally (see reset.css), which effectively freezes this into a
   static row for anyone who has that preference set. */
.journey-slider {
    position: relative;
}

/* The mask/overflow live on this inner wrapper rather than .journey-slider
   itself, so the prev/next arrow buttons (siblings of this element, see
   index.php) can sit on top of the slider without being faded out by the
   edge mask meant for the cards. */
.journey-slider__viewport {
    overflow: hidden;
    mask-image: linear-gradient(to right, transparent, black 4%, black 96%, transparent);
    -webkit-mask-image: linear-gradient(to right, transparent, black 4%, black 96%, transparent);
}

/* Once main.js's initJourneySliders() runs, it adds .js-enhanced and takes
   over scrolling via real scrollLeft (shared by the auto-scroll loop and
   the arrow buttons' scrollBy calls) instead of the CSS transform
   animation below — this switches the viewport to a real horizontal
   scroll container for that, with the native scrollbar hidden since the
   arrows are the intended control. Without JS, the viewport simply stays
   overflow: hidden and the CSS marquee animation on .journey-slider__track
   keeps working exactly as before, so this is purely progressive
   enhancement. */
.journey-slider__viewport.js-enhanced {
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.journey-slider__viewport.js-enhanced::-webkit-scrollbar {
    display: none;
}

.journey-slider__track {
    display: flex;
    gap: var(--space-lg);
    width: max-content;
    animation: journey-slide 42s linear infinite;
}

.journey-slider:hover .journey-slider__track,
.journey-slider:focus-within .journey-slider__track {
    animation-play-state: paused;
}

/* Prev/next arrow buttons — overlaid on the slider's edges, above the
   viewport's fade mask (see .journey-slider__viewport) so they stay fully
   opaque. Clicking one pauses the auto-scroll and nudges the real scroll
   position via main.js; without JS they simply don't render any handler
   but stay visually present, so hiding them behind a no-JS check isn't
   necessary — they just wouldn't do anything, same as any other button
   would without its script. */
.journey-slider__arrow {
    position: absolute;
    top: 50%;
    z-index: 3;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--color-bg-alt);
    border: 1px solid var(--color-border);
    box-shadow: var(--shadow-md);
    color: var(--color-text);
    transform: translateY(-50%);
    transition: transform var(--transition-fast), box-shadow var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}

.journey-slider__arrow--prev {
    left: -6px;
}

.journey-slider__arrow--next {
    right: -6px;
}

@media (min-width: 768px) {
    .journey-slider__arrow--prev {
        left: -20px;
    }

    .journey-slider__arrow--next {
        right: -20px;
    }
}

.journey-slider__arrow svg {
    width: 20px;
    height: 20px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2.25;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.journey-slider__arrow:hover {
    border-color: var(--color-accent);
    color: var(--color-accent-text);
    box-shadow: var(--shadow-3d);
    transform: translateY(-50%) scale(1.08);
}

.journey-slider__track .card {
    flex: 0 0 280px;
    width: 280px;
}

@media (min-width: 640px) {
    .journey-slider__track .card {
        flex-basis: 320px;
        width: 320px;
    }
}

/* Slightly faster variant — used on the Process section so two marquees
   on the same page don't scroll in visual lockstep with the Journey
   section above it. Same right-to-left direction as the default. */
.journey-slider--reverse .journey-slider__track {
    animation-name: journey-slide;
    animation-duration: 24s;
}

@keyframes journey-slide {
    from {
        transform: translateX(0);
    }

    to {
        transform: translateX(-50%);
    }
}

/* Process slider cards — icon-led rather than text-led: a big animated
   icon carries the meaning of the step, with the step name as a caption
   underneath instead of a paragraph. Centered layout, with the numbered
   badge tucked into the corner so the sequence is still readable without
   competing with the icon for attention. */
.card--process {
    align-items: center;
    text-align: center;
    position: relative;
    padding-top: var(--space-xl);
}

.step-number--corner {
    position: absolute;
    top: var(--space-sm);
    left: var(--space-sm);
    width: 26px;
    height: 26px;
    font-size: var(--fs-xs);
    margin-bottom: 0;
}

/* Big, funky motion icon — noticeably larger than the section-heading
   icon badge (132px vs the standard 64px), with a two-tone gradient
   (accent + accent-2 blended via a radial overlay, rather than the flat
   single-gradient badge used elsewhere) and a stacked glow+3D shadow so
   it reads as the most prominent element on the card. The bounce-wiggle
   animation swings through translateY + rotate + scale together for a
   livelier, more playful motion than the gentle float used on section
   headings. Each card uses a different animation-delay so the six icons
   don't all bounce in lockstep as the slider scrolls past, and hovering
   a card pauses its icon mid-bounce at an enlarged "popped" scale as a
   direct, tactile response to the pointer. */
.process-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 132px;
    height: 132px;
    margin: 0 auto var(--space-md);
    border-radius: var(--radius-lg);
    background: radial-gradient(circle at 30% 25%, var(--color-accent-2) 0%, transparent 62%), var(--gradient-accent);
    box-shadow: var(--shadow-glow), var(--shadow-3d);
    color: var(--color-bg-alt);
    animation: process-icon-wiggle 2.6s ease-in-out infinite;
    transition: transform var(--transition-fast);
}

.process-icon svg {
    width: 72px;
    height: 72px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.card--process:hover .process-icon {
    animation-play-state: paused;
    transform: scale(1.22) rotate(0deg) translateY(0);
}

.card--process:nth-child(2) .process-icon {
    animation-delay: 0.25s;
}

.card--process:nth-child(3) .process-icon {
    animation-delay: 0.5s;
}

.card--process:nth-child(4) .process-icon {
    animation-delay: 0.75s;
}

.card--process:nth-child(5) .process-icon {
    animation-delay: 1s;
}

.card--process:nth-child(6) .process-icon {
    animation-delay: 1.25s;
}

@keyframes process-icon-wiggle {
    0%, 100% {
        transform: translateY(0) rotate(-8deg) scale(1);
    }

    25% {
        transform: translateY(-7px) rotate(5deg) scale(1.1);
    }

    50% {
        transform: translateY(0) rotate(8deg) scale(1.16);
    }

    75% {
        transform: translateY(-4px) rotate(-4deg) scale(1.06);
    }
}

/* Numbered badge for the homepage's Journey/Process step cards */
.step-number {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    margin-bottom: var(--space-sm);
    background: var(--gradient-accent);
    color: var(--color-text);
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: var(--fs-sm);
}

/* FAQ accordion — native <details>/<summary>, zero JS, works with the
   keyboard and screen readers out of the box. */
.faq-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.faq-item {
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-md);
    padding: var(--space-md) var(--space-lg);
}

.faq-item summary {
    cursor: pointer;
    font-family: var(--font-heading);
    font-weight: 700;
    font-size: var(--fs-md);
    list-style: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
}

.faq-item summary::-webkit-details-marker {
    display: none;
}

.faq-item summary::after {
    content: '+';
    flex-shrink: 0;
    font-size: var(--fs-lg);
    color: var(--color-accent-text);
    transition: transform var(--transition-fast);
}

.faq-item[open] summary::after {
    transform: rotate(45deg);
}

.faq-item p {
    margin-top: var(--space-sm);
}

/* No-JS fallback link for the homepage reviews widget (see index.php's
   <noscript> block) — this site's usual progressive-enhancement pattern. */
.no-js-note {
    padding: var(--space-lg);
    border-radius: var(--radius-md);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    color: var(--color-text-muted);
    font-size: var(--fs-sm);
    text-align: center;
}

/* Library-style category tabs for the /tools/ page (see pages/tools.php
   and admin/tools-categories.php, which manages what shows up here).
   Progressive enhancement: every panel and its heading render visible by
   default (this block deliberately has no `display: none` on
   .tools-tabs__panel), so a visitor never loses content if JS fails —
   only once main.js's initToolsTabs() adds .js-enhanced does the tab
   row actually start switching between panels instead of just being a
   (still-clickable-later) row of labels sitting above a fully visible,
   scrollable stack of every category. */
.tools-tabs__list {
    display: flex;
    gap: var(--space-xs);
    overflow-x: auto;
    padding-bottom: var(--space-sm);
    margin-bottom: var(--space-lg);
    border-bottom: 1px solid var(--color-border);
    scrollbar-width: thin;
}

.tools-tabs__tab {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-sm) var(--space-lg);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    background: var(--color-bg-alt);
    border: 1px solid var(--color-border);
    border-bottom: none;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: var(--fs-sm);
    color: var(--color-text-muted);
    cursor: pointer;
    transition: color var(--transition-fast), background var(--transition-fast), box-shadow var(--transition-fast);
}

.tools-tabs__tab:hover {
    color: var(--color-accent-text);
}

.tools-tabs__tab[aria-selected="true"] {
    background: var(--gradient-accent);
    color: var(--color-text);
    border-color: transparent;
    box-shadow: var(--shadow-sm);
}

.tools-tabs__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    padding: 0 6px;
    border-radius: 999px;
    background: rgba(25, 26, 46, 0.08);
    font-size: 0.7rem;
    font-weight: 700;
}

.tools-tabs__tab[aria-selected="true"] .tools-tabs__count {
    background: rgba(255, 255, 255, 0.35);
}

.tools-tabs__panel {
    margin-bottom: var(--space-2xl);
}

.tools-tabs__panel:last-child {
    margin-bottom: 0;
}

.tools-tabs__panel-heading {
    margin-bottom: var(--space-md);
}

/* Once JS is driving the tabs, only the active panel needs to be
   in the document flow — hiding the header text too keeps a screen
   reader from announcing a heading for content that's currently hidden. */
.tools-tabs.js-enhanced .tools-tabs__panel:not(.is-active) {
    display: none;
}

.tools-tabs__subheading {
    margin: var(--space-lg) 0 var(--space-sm);
    font-size: var(--fs-md);
    color: var(--color-accent-text);
}

.tools-tabs__subheading:first-child {
    margin-top: 0;
}

/* Page hero — text + brand illustration side-by-side, used on the
   opening section of Services/Projects/Blog/Tools (not the home page,
   which keeps its own video hero). Stacks to a single column on mobile
   with the image below the text so the H1 is always read first. */
.page-hero {
    display: flex;
    flex-direction: column-reverse;
    gap: var(--space-lg);
    align-items: center;
}

.page-hero__content {
    max-width: 640px;
}

.page-hero__image {
    width: 100%;
    max-width: 480px;
}

.page-hero__image img {
    width: 100%;
    height: auto;
    display: block;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    background: var(--color-surface-light);
}

@media (min-width: 900px) {
    .page-hero {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: var(--space-xl);
    }

    .page-hero__content {
        flex: 1 1 55%;
        max-width: none;
    }

    .page-hero__image {
        flex: 1 1 40%;
        max-width: 420px;
    }
}

