/* ==========================================================================
   E3RD Website — Global Styles & Design System
   ==========================================================================

   Contents
   --------
   1. Design tokens (colors, typography, layout, spacing, radii, shadows,
      motion) — every reusable value lives in one place.
   2. Reset — a consistent baseline across browsers.
   3. Base typography — headings, paragraphs, links, images.
   4. Global layout — content container, sections (light, dark, deep navy),
      blueprint grid overlays, drawing-sheet corner brackets, and the
      animated binary-digit background layers.
   5. Components — buttons, section labels, service cards, form fields.
   6. Site chrome — sticky header, navigation, footer.
   7. Page sections — hero, about, projects, process, contact.
   8. Portfolio — project cards, filter bar, and project detail dialogs.
   9. Accessibility helpers — focus states, reduced motion, visually hidden.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. DESIGN TOKENS
   --------------------------------------------------------------------------
   Change a value here once and it updates everywhere on the site.
   -------------------------------------------------------------------------- */
:root {
    /* --- Brand colors --- */
    --color-black: #111111;
    --color-blue: #2563EB;
    --color-cyan: #06B6D4;
    --color-white: #FFFFFF;
    --color-gray: #64748B;
    --color-light-gray: #F8FAFC;

    /* --- Accessibility variants ---
       Pure cyan (#06B6D4) is too light for small text on white backgrounds
       (contrast is about 2.4:1; WCAG AA requires 4.5:1), so small cyan text
       on light backgrounds uses this darker shade instead. On the dark
       background pure cyan is fine (about 7.8:1 contrast). */
    --color-cyan-text: #0E7490;
    /* --color-gray is slightly too dark for "muted" text on the dark
       background (about 4:1), so dark sections use this lighter shade. */
    --color-gray-on-dark: #94A3B8;
    /* Hairline color for borders on light surfaces. */
    --color-border: #E2E8F0;
    /* A step stronger: used for form inputs so fields read as fields. */
    --color-border-strong: #CBD5E1;
    /* --color-gray fails contrast on the tinted service-card and panel
       backgrounds (about 4.4:1), so text on those tints uses this darker
       gray instead (about 6.9:1 — comfortably AA). */
    --color-gray-strong: #475569;
    /* Status colors for form feedback (AA contrast on light backgrounds). */
    --color-danger: #DC2626;
    --color-success: #15803D;

    /* --- Blueprint palette (engineering-drawing aesthetic) ---
       Deep navy anchors the dark portfolio surfaces, and the light
       blue-grey replaces plain white in the services section so the
       page reads as tinted "drawing paper" rather than plain web. */
    --color-navy: #0A192F;
    --color-navy-raised: #112240; /* cards/panels sitting ON navy */
    --color-tint-blue: #E8EEF7;   /* light blue-grey section wash */

    /* Hairline colours for the technical "graph paper" grid overlays.
       Slate lines on dark surfaces; blue ink on light ones. The major
       lines are now strong enough to read as deliberate drafting lines
       at normal zoom, and a junction dot marks each major crossing —
       exactly like the peg points on an engineer's drawing sheet. */
    --grid-line-dark: rgba(148, 163, 184, 0.11);
    --grid-line-dark-major: rgba(148, 163, 184, 0.24);
    --grid-line-light: rgba(37, 99, 235, 0.09);
    --grid-line-light-major: rgba(37, 99, 235, 0.18);
    --grid-dot-dark: rgba(148, 163, 184, 0.34);
    --grid-dot-light: rgba(37, 99, 235, 0.3);

    /* --- Typography ---
       clamp() sets a size that grows smoothly with the screen width but
       never goes below the first value or above the last. This keeps type
       scalable without writing separate sizes for every breakpoint. */
    --font-sans: "Inter", "Segoe UI", "Helvetica Neue", Arial, sans-serif;

    --text-xs: 0.75rem;
    --text-sm: 0.875rem;
    --text-base: 1rem;
    --text-lg: 1.125rem;
    --text-xl: clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem);
    --text-2xl: clamp(1.5rem, 1.3rem + 1vw, 2rem);
    --text-3xl: clamp(2rem, 1.55rem + 2.2vw, 3rem);
    --text-4xl: clamp(2.375rem, 1.7rem + 3.4vw, 4.25rem);

    --weight-regular: 400;
    --weight-medium: 500;
    --weight-semibold: 600;
    --weight-bold: 700;

    --leading-tight: 1.15;
    --leading-normal: 1.6;

    /* Letter-spacing for UPPERCASE labels. */
    --tracking-caps: 0.1em;

    /* --- Layout --- */
    --container-max: 72rem;      /* main content width: 1152px */
    --container-narrow: 46rem;   /* reading-optimized width */
    --container-padding: clamp(1.25rem, 4vw, 2.5rem);

    /* --- Spacing scale (t-shirt sizes) --- */
    --space-2xs: 0.25rem;
    --space-xs: 0.5rem;
    --space-sm: 1rem;
    --space-md: 1.5rem;
    --space-lg: 2rem;
    --space-xl: 3rem;
    --space-2xl: 4rem;
    --space-3xl: 6rem;

    /* Vertical breathing room for page sections; grows on large screens. */
    --section-padding: clamp(4rem, 9vw, 6.5rem);

    /* --- Border radii --- */
    --radius-sm: 0.375rem;
    --radius-md: 0.625rem;
    --radius-lg: 1rem;
    --radius-full: 999px;

    /* --- Shadows (subtle on purpose — premium, not flashy) --- */
    --shadow-sm: 0 1px 2px rgba(17, 17, 17, 0.06);
    --shadow-md: 0 6px 16px rgba(17, 17, 17, 0.08);
    --shadow-lg: 0 16px 40px rgba(17, 17, 17, 0.14);

    /* --- Motion --- */
    --transition-fast: 150ms ease;
    --transition-base: 250ms ease;
}

/* --------------------------------------------------------------------------
   2. RESET
   --------------------------------------------------------------------------
   Makes every browser start from the same baseline.
   "border-box" means an element's width includes its padding and border,
   which keeps layout sizes predictable.
   -------------------------------------------------------------------------- */
*,
*::before,
*::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

html {
    scroll-behavior: smooth;
    -webkit-text-size-adjust: 100%;
}

/* --------------------------------------------------------------------------
   3. BASE TYPOGRAPHY
   -------------------------------------------------------------------------- */
body {
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: var(--leading-normal);
    color: var(--color-black);
    background-color: var(--color-white);
    -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3,
h4 {
    font-weight: var(--weight-bold);
    line-height: var(--leading-tight);
    letter-spacing: -0.02em;
    /* Balances lines of text so headings don't leave one lonely word
       on the last line (ignored by older browsers — safe to use). */
    text-wrap: balance;
    margin-bottom: var(--space-sm);
}

h1 {
    font-size: var(--text-4xl);
}

h2 {
    font-size: var(--text-3xl);
}

h3 {
    font-size: var(--text-xl);
    font-weight: var(--weight-semibold);
}

h4 {
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
}

/* Intro paragraphs: slightly larger, muted color, limited line length
   so sentences stay easy to read. */
.text-lede {
    font-size: var(--text-lg);
    color: var(--color-gray);
    max-width: 60ch;
    margin-bottom: var(--space-lg);
}

/* Secondary/muted text color on light backgrounds. */
.text-muted {
    color: var(--color-gray);
}

/* Consecutive paragraphs need breathing room (the reset above removed
   the browser's default paragraph margins). */
p + p {
    margin-top: var(--space-sm);
}

/* Images should never overflow their container. */
img {
    max-width: 100%;
    height: auto;
}

/* --------------------------------------------------------------------------
   4. GLOBAL LAYOUT
   -------------------------------------------------------------------------- */

/* Centered content column with responsive side margins. */
.container {
    width: 100%;
    max-width: var(--container-max);
    margin-inline: auto;
    padding-inline: var(--container-padding);
}

/* Narrower column for long-form text. */
.container--narrow {
    max-width: var(--container-narrow);
}

/* Consistent vertical rhythm for every page section. position: relative
   lets each section anchor its own decorative layers (such as the
   animated binary fields) so they never leak into neighbouring sections. */
.section {
    position: relative;
    padding-block: var(--section-padding);
}

/* Light gray section (subtle contrast against white). */
.section--tint {
    background-color: var(--color-light-gray);
}

/* Dark section: flips text and muted-text colors automatically. */
.section--dark {
    background-color: var(--color-black);
    color: var(--color-white);
}

/* Deep navy section: same automatic text flips as the dark section, but
   a cooler, slightly raised surface — used for the portfolio so its
   navy cards/panels read as layers instead of one flat black sheet. */
.section--navy {
    background-color: var(--color-navy);
    color: var(--color-white);
}

/* Light blue-grey section: a cooler alternative to --tint for the
   services and contact areas (tinted "drawing paper" instead of white). */
.section--tint-blue {
    background-color: var(--color-tint-blue);
}

.section--dark p,
.section--navy p {
    color: var(--color-gray-on-dark);
}

/* Text links on dark sections are cyan. The :not(.btn) keeps buttons in
   charge of their own colors (a plain "a" rule would overpower them). */
.section--dark a:not(.btn),
.section--navy a:not(.btn) {
    color: var(--color-cyan);
}

/* ---- Blueprint utilities -------------------------------------------------
   Two small building blocks used across the page for the engineering-
   drawing look. Both are pure CSS: no image files, no scripts. */

/* Graph-paper grid overlay: fine cells with a stronger line every fifth
   cell, like engineering drawing paper, plus a small junction dot at
   each major crossing (the "peg point" marks of a drafting sheet). The
   line and dot colours come from custom properties so each section
   type can tune them; dark surfaces override the defaults below (see
   the .section--dark/.section--navy pairing). Add the class alongside
   the section's other classes:
   <section class="section section--navy section--grid"> */
.section--grid {
    --grid-minor: var(--grid-line-light);
    --grid-major: var(--grid-line-light-major);
    --grid-dot: var(--grid-dot-light);
    background-image:
        radial-gradient(circle, var(--grid-dot) 1.5px, transparent 2px),
        linear-gradient(var(--grid-major) 1px, transparent 1px),
        linear-gradient(90deg, var(--grid-major) 1px, transparent 1px),
        linear-gradient(var(--grid-minor) 1px, transparent 1px),
        linear-gradient(90deg, var(--grid-minor) 1px, transparent 1px);
    background-size:
        7.5rem 7.5rem,
        7.5rem 7.5rem,
        7.5rem 7.5rem,
        1.5rem 1.5rem,
        1.5rem 1.5rem;
    /* The dot tile is shifted half a cell so each dot lands exactly on a
       major-line intersection (with an offset the dot sits fully inside
       its tile, so no quarter-dot clipping at tile seams). */
    background-position:
        3.75rem 3.75rem,
        0 0,
        0 0,
        0 0,
        0 0;
}

/* On dark/navy surfaces the grid switches to faint slate lines. */
.section--dark.section--grid,
.section--navy.section--grid {
    --grid-minor: var(--grid-line-dark);
    --grid-major: var(--grid-line-dark-major);
    --grid-dot: var(--grid-dot-dark);
}

/* Corner brackets: two L-shaped marks (top-left, bottom-right) drawn
   with borders, like the registration marks on a drawing sheet, or the
   crop marks around a print. Purely decorative — never intercepts the
   mouse. Apply to any positioned box to "frame" it:
   <div class="about-panel framed"> */
.framed {
    position: relative;
}

.framed::before,
.framed::after {
    content: "";
    position: absolute;
    width: 1.1rem;
    height: 1.1rem;
    pointer-events: none;
}

.framed::before {
    top: -1px;
    left: -1px;
    border-top: 2px solid var(--color-blue);
    border-left: 2px solid var(--color-blue);
}

.framed::after {
    bottom: -1px;
    right: -1px;
    border-bottom: 2px solid var(--color-blue);
    border-right: 2px solid var(--color-blue);
}

/* Corner drafting arcs: a piece of an engineer's "detail view" pinned to
   a section corner — an outer dashed quarter arc, a fine inner arc, and
   a radial leader crossing both, with a solid cyan junction dot, a
   hollow companion dot, and a small crosshair. The markup is one inline
   SVG inside a positioned box (see index.html); the dashed arc reuses
   the .art-dash--arc marcher so it flows slowly with the rest of the
   sheet. Purely decorative — aria-hidden in the markup and
   pointer-events: none here — and the .container that follows the box
   is lifted above it. On phones the box shrinks so the arcs stay a
   corner detail instead of drifting behind the text. */
.blueprint-decor {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 21rem;
    height: 21rem;
    z-index: 0;
    pointer-events: none;
}

/* The --tr modifier mirrors the same drawing for a top-right corner. */
.blueprint-decor--tr {
    top: 0;
    bottom: auto;
    transform: scaleY(-1);
}

.blueprint-decor svg {
    display: block;
    width: 100%;
    height: 100%;
}

/* Keep the section's content above the arcs. */
.blueprint-decor + .container {
    position: relative;
    z-index: 1;
}

@media (max-width: 47.99em) {
    .blueprint-decor {
        width: 13rem;
        height: 13rem;
    }
}

/* ---- Animated binary fields ----------------------------------------------
   A section-sized, transparent layer that JavaScript fills with drifting
   0s and 1s (see initBinaryFields() in script.js). The digits are a real
   design feature: three size tiers and three restrained tones (cyan,
   blue-grey, slate — all from the existing palette), each holding its
   peak brightness through the middle of its cycle, so the field reads
   clearly the moment a section comes into view. It sits BEHIND the
   section's content: the field itself is z-index 0, and the .container
   that follows it is lifted to z-index 1. Purely decorative — aria-hidden
   in the HTML and pointer-events: none here, so it never blocks clicks or
   reaches assistive technology. Without JavaScript the field simply
   stays empty. */
.binary-field {
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    pointer-events: none;
}

/* Keep the section's content above the digits. */
.binary-field + .container {
    position: relative;
    z-index: 1;
}

/* One digit. Position, size, tone, peak opacity, speed and delay are set
   per-digit by JavaScript; only the movement and tones live here.
   Animating opacity and transform only keeps this cheap to paint on any
   device. */
.binary-digit {
    position: absolute;
    line-height: 1;
    font-family: ui-monospace, "Cascadia Mono", Consolas, "Courier New", monospace;
    font-weight: var(--weight-medium);
    user-select: none;
    opacity: 0;
    animation-name: binary-drift-a;
    animation-duration: var(--binary-duration, 24s);
    animation-delay: var(--binary-delay, 0s);
    animation-timing-function: linear;
    animation-iteration-count: infinite;
}

/* Three restrained tones, all from the existing palette. Cyan carries a
   very soft glow so it stays crisp against the dark backgrounds; the
   blue-grey and darker slate tones give the field depth behind it. */
.binary-digit--cyan {
    color: var(--color-cyan);
    text-shadow: 0 0 0.4rem rgba(6, 182, 212, 0.35);
}

.binary-digit--bluegrey { color: var(--color-gray-on-dark); }
.binary-digit--slate { color: var(--color-gray); }

/* Three drift shapes, assigned in turn, so neighbouring digits never
   move in lockstep. Each rises to its own peak opacity (--binary-peak),
   HOLDS that brightness through the middle of the cycle, then fades out
   — so at any moment a good share of the field is clearly visible
   instead of only flickering briefly. */
.binary-digit--b { animation-name: binary-drift-b; }
.binary-digit--c { animation-name: binary-drift-c; }

@keyframes binary-drift-a {
    0% { opacity: 0; transform: translate3d(0, 0, 0); }
    16% { opacity: var(--binary-peak, 0.35); }
    58% { opacity: var(--binary-peak, 0.35); }
    100% { opacity: 0; transform: translate3d(0.6rem, -1.5rem, 0); }
}

@keyframes binary-drift-b {
    0% { opacity: 0; transform: translate3d(0, 0, 0); }
    18% { opacity: var(--binary-peak, 0.35); }
    55% { opacity: var(--binary-peak, 0.35); }
    100% { opacity: 0; transform: translate3d(-0.9rem, -0.9rem, 0); }
}

@keyframes binary-drift-c {
    0% { opacity: 0; transform: translate3d(0, 0, 0); }
    15% { opacity: var(--binary-peak, 0.35); }
    60% { opacity: var(--binary-peak, 0.35); }
    100% { opacity: 0; transform: translate3d(0.5rem, 1.2rem, 0); }
}

/* --------------------------------------------------------------------------
   5. COMPONENTS
   -------------------------------------------------------------------------- */

/* ---- Buttons ------------------------------------------------------------ */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    padding: 0.9rem 1.9rem;
    border: 2px solid transparent;
    border-radius: var(--radius-full);
    font-family: inherit;
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition:
        background-color var(--transition-fast),
        color var(--transition-fast),
        border-color var(--transition-fast),
        box-shadow var(--transition-fast),
        transform var(--transition-fast);
}

/* Buttons are often <a> elements; keep the global link underline off. */
.btn:hover,
.btn:focus {
    text-decoration: none;
}

.btn--primary {
    background-color: var(--color-blue);
    color: var(--color-white);
}

.btn--primary:hover {
    background-color: #1D4ED8; /* one step darker than --color-blue */
    transform: translateY(-1px);
    box-shadow: var(--shadow-md);
}

.btn--primary:active {
    transform: translateY(0);
    box-shadow: var(--shadow-sm);
}

.btn--secondary {
    background-color: transparent;
    color: var(--color-black);
    border-color: var(--color-black);
}

.btn--secondary:hover {
    background-color: var(--color-black);
    color: var(--color-white);
}

/* On dark sections the secondary button inverts to white. */
.section--dark .btn--secondary,
.section--navy .btn--secondary {
    color: var(--color-white);
    border-color: var(--color-white);
}

.section--dark .btn--secondary:hover,
.section--navy .btn--secondary:hover {
    background-color: var(--color-white);
    color: var(--color-black);
}

/* Smaller button used inside project cards, where a full-size button
   would overwhelm the card. */
.btn--sm {
    padding: 0.6rem 1.25rem;
    font-size: var(--text-sm);
}

/* Row of buttons that wraps gracefully on small screens. */
.btn-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
}

/* Small inline icon inside a link or button — used for the WhatsApp chat
   mark. It inherits the surrounding text colour, so the existing hover and
   focus rules need no additions, and "flex: none" stops it being squashed
   when the label beside it wraps on a narrow screen. */
.link-icon {
    flex: none;
    width: 1.25em;
    height: 1.25em;
}

/* ---- Section label (small uppercase eyebrow) ---------------------------- */
.section-label {
    display: inline-block;
    margin-bottom: var(--space-sm);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--color-cyan-text); /* darker cyan: readable on light */
}

.section--dark .section-label,
.section--navy .section-label {
    color: var(--color-cyan); /* pure cyan: readable on dark */
}

/* ---- Service cards ------------------------------------------------------ */

/* Responsive card grid: 1 column on phones, 2 on tablets, 3 on desktop.
   Breakpoints use em units so they respect the user's browser font size. */
.card-grid {
    display: grid;
    gap: var(--space-md);
    grid-template-columns: 1fr;
}

@media (min-width: 40em) {
    .card-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 64em) {
    .card-grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

.card {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    padding: var(--space-lg);
    background-color: var(--color-white);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    transition:
        border-color var(--transition-base),
        box-shadow var(--transition-base),
        transform var(--transition-base);
}

.card:hover {
    border-color: var(--color-blue);
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
}

.card h3,
.card p {
    margin-bottom: 0; /* the card's own gap handles spacing */
}

.card p {
    color: var(--color-gray);
}

/* Small numbered marker (placeholder icon until real icons are added). */
.card__badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: var(--radius-md);
    background-color: rgba(37, 99, 235, 0.08); /* 8% blue tint */
    color: var(--color-blue);
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    letter-spacing: 0.05em;
}

/* ---- Interactive service cards + detail panels ----------------------------
   Each service has its own identity, expressed as four custom properties:
     --card-accent     the service's signature colour (badge, strip, border)
     --card-accent-ink a readable variant of it for small text on the tints
     --card-on-accent  text colour that sits ON the accent colour
     --card-tint       the light background wash of the card and its panel
   Colour pairs were picked for WCAG AA contrast and verified: white text
   sits on the blue/gray/dark accents, but on cyan the text is near-black
   (white on cyan would be 2.4:1 — far too low). ------------------------- */

/* 1) Software Development — Electric Blue on pale blue */
.service-card--software,
.service-detail--software {
    --card-accent: #2563EB;      /* Electric Blue */
    --card-accent-ink: #2563EB;
    --card-on-accent: var(--color-white);
    --card-tint: #EFF6FF;
}

/* 2) AI & Automation — Cyan (with dark text) on pale cyan */
.service-card--ai,
.service-detail--ai {
    --card-accent: #06B6D4;      /* Cyan */
    --card-accent-ink: #0E7490;  /* darker cyan: readable as small text */
    --card-on-accent: var(--color-black);
    --card-tint: #ECFEFF;
}

/* 3) Data & Business Intelligence — deep blue on pale blue */
.service-card--data,
.service-detail--data {
    --card-accent: #1E40AF;
    --card-accent-ink: #1E40AF;
    --card-on-accent: var(--color-white);
    --card-tint: #EFF6FF;
}

/* 4) Business & Digital Solutions — Cool Gray on light gray */
.service-card--business,
.service-detail--business {
    --card-accent: var(--color-gray);
    --card-accent-ink: var(--color-gray);
    --card-on-accent: var(--color-white);
    --card-tint: #F8FAFC;
}

/* 5) Projects & Contracting — brand black on slate */
.service-card--projects,
.service-detail--projects {
    --card-accent: var(--color-black);
    --card-accent-ink: var(--color-black);
    --card-on-accent: var(--color-white);
    --card-tint: #F1F5F9;
}

/* 6) Brand & Creative Design — the one card carrying both brand colours:
   a blue→cyan gradient strip and cyan accents, with Electric Blue as the
   secondary colour (kept in --card-accent-2 for the gradient below). */
.service-card--brand,
.service-detail--brand {
    --card-accent: var(--color-cyan);
    --card-accent-ink: #0E7490;   /* cyan's readable text step */
    --card-on-accent: var(--color-black);
    --card-tint: #F0FDFA;
    --card-accent-2: var(--color-blue);
}

/* The card becomes a tinted panel with the accent as a top strip.
   cursor: pointer signals the whole card is clickable — the real control
   is the "View details" button inside (kept for keyboard + screen-reader
   users; JavaScript forwards clicks on the rest of the card to it). */
.service-card {
    position: relative;
    overflow: hidden;
    background-color: var(--card-tint);
    cursor: pointer;
    box-shadow: var(--shadow-sm);
}

/* The 4px colour strip along every card's top edge. */
.service-card::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background-color: var(--card-accent);
}

/* Card 6's strip is the brand blue→cyan gradient instead of one colour. */
.service-card--brand::before {
    background-image: linear-gradient(90deg, var(--color-blue), var(--color-cyan));
}

/* The number badge adopts the service colour. Card 6's badge is drawn
   with the checkered blue→cyan gradient so both brand colours appear. */
.service-card .card__badge {
    background-color: var(--card-accent);
    color: var(--card-on-accent);
}

.service-card--brand .card__badge {
    background-image: linear-gradient(135deg, var(--color-blue), var(--color-cyan));
    background-color: var(--color-cyan); /* fallback if gradients fail */
}

/* Descriptions on the tints use the stronger gray for extra contrast. */
.service-card p {
    color: var(--color-gray-strong);
}

/* Hover hints at the accent colour; the ACTIVE card (class "is-active",
   set by JavaScript) is the strong, unmistakable state. */
.service-card:hover {
    border-color: var(--card-accent);
}

.service-card.is-active {
    border-color: var(--card-accent);
    box-shadow:
        0 0 0 2px var(--card-accent),
        var(--shadow-md);
}

/* "View details" — a real <button>, so keyboard and screen-reader users
   can open every panel. Styled as a quiet text action; the whole card
   around it is clickable too. */
.service-card__toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    margin-top: auto; /* pins this row to the bottom so all six align */
    padding: 0.35rem 0;
    background: none;
    border: 0;
    color: var(--card-accent-ink);
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    cursor: pointer;
}

.service-card__toggle:hover .service-card__toggle-label {
    text-decoration: underline;
}

/* The little "+" — a bordered circle that rotates 45° into a "×" while
   its panel is open. */
.service-card__toggle-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.4rem;
    height: 1.4rem;
    border: 1.5px solid currentColor;
    border-radius: var(--radius-full);
    font-size: var(--text-sm);
    line-height: 1;
    transition: transform var(--transition-base);
}

.service-card.is-active .service-card__toggle-icon {
    transform: rotate(45deg);
}

/* ---- The detail panels ------------------------------------------------- */

/* One panel per service, shown below the card grid. JavaScript flips the
   hidden attribute of exactly one at a time, so content is never
   duplicated — all six panels are always in the page. */
.service-detail {
    position: relative;
    overflow: hidden;
    display: grid;
    gap: var(--space-lg);
    margin-top: var(--space-lg);
    padding: var(--space-lg);
    background-color: var(--color-white);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    animation: service-detail-in var(--transition-base) both;
}

/* This must beat the display rules above when the hidden attribute is
   set by JavaScript. */
.service-detail[hidden] {
    display: none;
}

/* Same 4px accent strip as the matching card. */
.service-detail::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background-color: var(--card-accent);
}

/* Card 6's panel gets the brand gradient strip, matching its card. */
.service-detail--brand::before {
    background-image: linear-gradient(90deg, var(--color-blue), var(--color-cyan));
}

/* Side-by-side intro + details from the desktop breakpoint up. */
@media (min-width: 64em) {
    .service-detail {
        grid-template-columns: 1.05fr 0.95fr;
        gap: var(--space-xl);
        padding: var(--space-xl);
    }
}

/* Gentle entrance; the global reduced-motion rule switches it off. */
@keyframes service-detail-in {
    from {
        opacity: 0;
        transform: translateY(8px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* Inside the panel, spacing comes from the grid gap and the rules below. */
.service-detail h3,
.service-detail h4,
.service-detail p {
    margin-bottom: 0;
}

.service-detail__intro {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

/* The headline sentence, in the service's readable ink colour. */
.service-detail__headline {
    color: var(--card-accent-ink);
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
}

.service-detail__text {
    color: var(--color-gray-strong);
}

.service-detail__cta {
    align-self: flex-start;
    margin-top: var(--space-xs);
}

.service-detail__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

/* "Services offered" / "Practical example" mini-headings. */
.service-detail__body h4 {
    font-size: var(--text-xs);
    font-weight: var(--weight-bold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--color-gray-strong);
}

/* Tick list, mirroring the checklist style used in the About panel. */
.service-detail__list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    color: var(--color-gray-strong);
}

.service-detail__list li {
    position: relative;
    padding-left: 1.6rem;
}

.service-detail__list li::before {
    content: "✓";
    position: absolute;
    left: 0;
    font-weight: var(--weight-bold);
    color: var(--card-accent-ink);
}

/* The practical example sits in a tinted note box with an accent edge. */
.service-detail__example {
    padding: var(--space-sm) var(--space-md);
    background-color: var(--card-tint);
    border-left: 4px solid var(--card-accent);
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
    color: var(--color-black);
}

/* ---- Portfolio pills and honesty note ------------------------------------- */

/* Small pill that labels project cards ("Concept" / "Client work" /
   "E3RD project"). It is a <span> inside the card's meta line. The base
   (light) style below is kept for the white project dialogs; on the navy
   cards the pills flip to their dark-surface variants further down. */
.tag {
    align-self: flex-start;
    padding: 0.3em 0.75em;
    border-radius: 999px;
    background-color: rgba(37, 99, 235, 0.08); /* 8% blue tint */
    color: var(--color-blue);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
}

/* Client-work variant: solid blue so it reads more strongly than
   "Concept", making the honest distinction impossible to miss. */
.tag--client {
    background-color: var(--color-blue);
    color: var(--color-white);
}

/* E3RD's own projects (WIPAM) get a third label: cyan rather than blue,
   so "this is ours" never reads as "this was a client's brief". */
.tag--e3rd {
    background-color: var(--color-cyan);
    color: var(--color-black);
}

/* On the deep-navy cards the pills flip to softly tinted variants:
   slate for "Concept", translucent blue with light-blue text for the
   stronger "Client work". Scoped to .project-card so the dialogs
   (still white, like a sheet of paper on the navy) keep the base style. */
.project-card .tag {
    background-color: rgba(148, 163, 184, 0.14);
    color: #CBD5E1;
}

.project-card .tag--client {
    background-color: rgba(37, 99, 235, 0.25);
    color: #93C5FD;
}

.project-card .tag--e3rd {
    background-color: rgba(6, 182, 212, 0.22);
    color: #67E8F9;
}

/* Honesty note under the homepage project grid (sits on navy). */
.portfolio-note {
    max-width: var(--container-narrow);
    margin-bottom: var(--space-lg);
    color: var(--color-gray-on-dark);
}

/* --------------------------------------------------------------------------
   6. SITE CHROME (sticky header, navigation, footer)
   -------------------------------------------------------------------------- */

/* ---- Header ----------------------------------------------------------------
   position: sticky keeps the bar pinned to the top of the window while
   scrolling, so navigation is always within reach. */
.site-header {
    position: sticky;
    top: 0;
    z-index: 50; /* stays above section content when the menu is open */
    background-color: var(--color-black);
    color: var(--color-white);
    padding-block: var(--space-sm);
}

.site-header__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-xs) var(--space-md);
}

/* Logo block on the left of the header. */
.site-header__brand {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}

/* Official logo — the "reverse" variant: white artwork on a brand-black
   plate. The plate colour (#111111) is exactly this header's background,
   so the image blends in invisibly and only the white mark shows.
   The export wraps the artwork in a large empty plate — measured from the
   file, the mark occupies a 263x100 area inside the 524x230 canvas, sitting
   61px from the left, 67px from the top, 63px from the bottom and 200px
   from the right. Rendering the image at its natural box would therefore
   show a small mark lost in dead space, so the image is rendered LARGE and
   the empty plate is reclaimed with negative margins on all four sides.
   Only the invisible plate is trimmed — the artwork keeps its exact
   proportions and is never stretched, cropped or redrawn — and because the
   trimmed overflow is the same colour as the header, nothing shows.
   Result: the visible E3RD mark renders at about 133x50px (previously
   about 73x28px), flush with the container edge, so the brand reads
   immediately on first paint. pointer-events:none keeps the reclaimed
   (invisible) plate from stealing clicks that belong to the navigation;
   the wrapping link's own box still covers the visible mark.
   The width is stated explicitly rather than left to `auto`: inside this
   shrink-to-fit flex item, `width: auto` combined with the negative
   horizontal margins resolves to the margin-box width and squashes the
   artwork to half its correct width. calc() derives the width from the
   SVG's own 524:230 ratio, so proportions stay exact at any size.
   max-width:none is required too — the global `img { max-width: 100% }`
   reset would otherwise clamp the image to the link's (much narrower)
   shrink-to-fit box and squash it horizontally.
   The clip-path is the last part of the trick. Negative margins reclaim
   layout space, but the image still PAINTS its full plate — and because
   the plate is taller than the header bar, the leftover strip used to
   paint a faint #111111 rectangle over the top of the hero. Clipping the
   image to the artwork's own bounding box stops that. The insets are
   measured from the SVG file (artwork ink runs x 61..323, y 67..166
   inside the 524x230 canvas) and then padded outward by about 6 SVG
   pixels on every side, so the clip window always sits OUTSIDE the
   artwork: not one pixel of the approved logo is cropped, stretched or
   redrawn, and the few pixels of plate left inside the window are the
   same colour as the bar and therefore invisible. Browsers without
   clip-path support simply show the old behaviour — nothing breaks. */
.site-header__logo {
    --logo-height: 7.25rem;
    display: block;
    height: var(--logo-height);
    width: calc(var(--logo-height) * 524 / 230);
    max-width: none;
    margin-top: -2rem;     /* reclaim empty plate above the mark */
    margin-bottom: -2rem;  /* reclaim empty plate below the mark */
    margin-left: -1.9rem;  /* reclaim empty plate left of the mark */
    margin-right: -6.3rem; /* reclaim the wide empty plate right of the mark */
    -webkit-clip-path: inset(26.5% 37% 24.8% 10.5%);
    clip-path: inset(26.5% 37% 24.8% 10.5%);
    pointer-events: none;
}

/* Text wordmark. The "3" gets a blue-to-cyan gradient for a subtle
   brand signature. As a logotype it is exempt from contrast rules. */
.wordmark {
    font-size: var(--text-xl);
    font-weight: var(--weight-bold);
    letter-spacing: 0.04em;
    color: var(--color-white);
    text-decoration: none;
}

.wordmark:hover {
    text-decoration: none;
}

.wordmark__accent {
    background: linear-gradient(120deg, var(--color-blue), var(--color-cyan));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent; /* needed for Safari */
}

/* ---- Navigation -------------------------------------------------------------
   Phones: the menu is a panel that opens below the sticky header (the
   .nav-open class on the header is toggled by JavaScript).
   64em and up: a normal horizontal row; the toggle button hides. */
.site-nav {
    display: none;
    position: absolute;
    top: 100%; /* flush against the bottom of the header */
    left: 0;
    right: 0;
    background-color: var(--color-black);
    border-top: 1px solid rgba(255, 255, 255, 0.12);
    box-shadow: var(--shadow-lg);
}

.nav-open .site-nav {
    display: block;
}

.site-nav__list {
    list-style: none;
    display: flex;
    flex-direction: column;
}

.site-nav__link {
    display: block;
    padding: var(--space-sm) var(--container-padding);
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    color: var(--color-white);
    font-weight: var(--weight-medium);
    text-decoration: none;
    transition:
        color var(--transition-fast),
        background-color var(--transition-fast);
}

.site-nav__link:hover {
    color: var(--color-cyan);
    background-color: rgba(255, 255, 255, 0.05);
}

/* The "Start a Project" button sits at the end of the menu list. */
.site-nav__item--cta {
    padding: var(--space-sm) var(--container-padding) var(--space-md);
}

.site-nav__cta {
    width: 100%;
    padding: 0.7rem 1.5rem; /* slightly slimmer than the section buttons */
}

/* Hamburger: three bars drawn with spans, animated into an X when open. */
.nav-toggle {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 2.75rem;
    height: 2.75rem;
    background-color: transparent;
    border: 1px solid rgba(255, 255, 255, 0.25);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.nav-toggle__bar {
    width: 1.25rem;
    height: 2px;
    margin-inline: auto;
    background-color: var(--color-white);
    border-radius: var(--radius-full);
    transition:
        transform var(--transition-base),
        opacity var(--transition-base);
}

.nav-open .nav-toggle__bar:nth-child(1) {
    transform: translateY(7px) rotate(45deg);
}

.nav-open .nav-toggle__bar:nth-child(2) {
    opacity: 0;
}

.nav-open .nav-toggle__bar:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
}

@media (min-width: 64em) {
    /* Roomier bar on wide screens so the enlarged logo never feels crammed
       against the top edge of the window. Total header height here is about
       100px (24px padding + the ~52px logo line + 24px padding); the
       [id] scroll-margin-top further down is sized to match. */
    .site-header {
        padding-block: var(--space-md);
    }

    .site-nav {
        display: block;
        position: static;
        border-top: 0;
        box-shadow: none;
    }

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

    .site-nav__link {
        padding: var(--space-2xs) 0;
        border-bottom: 0;
        font-size: var(--text-sm);
    }

    .site-nav__link:hover {
        background-color: transparent;
    }

    .site-nav__item--cta {
        padding: 0;
    }

    .site-nav__cta {
        width: auto;
    }

    .nav-toggle {
        display: none;
    }
}

/* ---- Footer ------------------------------------------------------------------ */
.site-footer {
    position: relative; /* anchor for the tick row below */
    padding-block: var(--space-xl) var(--space-lg);
    background-color: var(--color-black);
    color: var(--color-gray-on-dark);
    font-size: var(--text-sm);
}

/* A ruler-style tick row along the footer's top edge — the same
   measuring-tape detail that closes the hero. Purely decorative. */
.site-footer::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 0.5rem;
    background-image: repeating-linear-gradient(
        90deg,
        rgba(148, 163, 184, 0.25) 0 1px,
        transparent 1px 2.5rem
    );
    pointer-events: none;
}

.site-footer a {
    color: var(--color-gray-on-dark);
    text-decoration: none;
}

.site-footer a:hover {
    color: var(--color-white);
    text-decoration: underline;
}

.site-footer__top {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--space-lg);
    padding-bottom: var(--space-lg);
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.site-footer__brand {
    max-width: 22rem;
}

/* Same reverse logo as the header — the footer is the same brand-black,
   so the plate blends in here too. Exactly the same enlarge-and-reclaim
   trick (see the .site-header__logo comment for the measured plate
   geometry, for why the width is stated explicitly, and for why the plate
   is clipped to the artwork's bounding box), sized one step down for the
   footer: rendered at 6.5rem with the empty plate trimmed on all four
   sides, so the visible mark lands at about 119x45px instead of the old
   64x24px. pointer-events:none keeps the reclaimed invisible plate from
   covering the footer navigation links beside it; the wrapping link still
   covers the visible mark. */
.site-footer__logo {
    --logo-height: 6.5rem;
    display: block;
    height: var(--logo-height);
    width: calc(var(--logo-height) * 524 / 230);
    max-width: none; /* see the header rule: the global img reset would clamp it */
    margin-top: -1.8rem;     /* reclaim empty plate above the mark */
    margin-bottom: -1.8rem;  /* reclaim empty plate below the mark */
    margin-left: -1.7rem;    /* reclaim empty plate left of the mark */
    margin-right: -5.65rem;  /* reclaim the wide empty plate right of the mark */
    -webkit-clip-path: inset(26.5% 37% 24.8% 10.5%);
    clip-path: inset(26.5% 37% 24.8% 10.5%);
    pointer-events: none;
}

/* Brand line under the footer logo — lifted to white and a step larger so
   the footer reads as a proper brand block rather than fine print. */
.site-footer__brand p {
    margin-top: var(--space-xs);
    font-size: var(--text-lg);
    font-weight: var(--weight-medium);
    color: var(--color-white);
}

.site-footer__nav-list {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm) var(--space-md);
}

.site-footer__bottom {
    display: flex;
    flex-wrap: wrap;
    align-items: center; /* copyright line and the links share a common centre line */
    justify-content: space-between;
    gap: var(--space-xs) var(--space-md);
    padding-top: var(--space-lg);
}

/* E3RD's public links, on the right of the bottom row. The row already
   wraps, so on a phone this list drops below the copyright line instead of
   squeezing it. Each link keeps a 44px-tall hit area even though the text
   is small, and the icon/text colours come from the .site-footer a rules
   above (grey at rest, white and underlined on hover, cyan focus ring). */
.site-footer__links {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-xs) var(--space-md);
}

.site-footer__link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    min-height: 2.75rem; /* 44px minimum touch target */
}

/* --------------------------------------------------------------------------
   7. PAGE SECTIONS (hero, about, projects, process, contact)
   -------------------------------------------------------------------------- */

/* Anchored sections land below the sticky header instead of under it.
   6.75rem (108px) clears the tallest header state — the desktop bar is
   about 100px now that the logo is enlarged — with a few pixels to spare.
   js/script.js measures the real header height for service detail panels,
   so those stay correct at any width. */
[id] {
    scroll-margin-top: 6.75rem;
}

/* <main> is focused programmatically by the skip link (tabindex="-1").
   Suppress the focus ring there — the whole page lighting up is noise. */
#main:focus,
#main:focus-visible {
    outline: none;
}

/* ---- Hero (dark, with a faint blue glow at the top) ---------------------- */
.hero {
    position: relative; /* anchor for the tick row below */
    background:
        radial-gradient(ellipse 80% 50% at 50% -10%, rgba(37, 99, 235, 0.22), transparent 70%),
        var(--color-black);
}

/* A ruler-style tick row along the hero's bottom edge — one subtle
   technical detail that hands the eye over to the next section. */
.hero::after {
    content: "";
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 0.5rem;
    background-image: repeating-linear-gradient(
        90deg,
        rgba(148, 163, 184, 0.25) 0 1px,
        transparent 1px 2.5rem
    );
    pointer-events: none;
}

.hero h1 {
    max-width: 24ch; /* wraps the tagline into two balanced lines */
}

/* Official brand philosophy ("the three E's"), placed directly beneath
   the headline so it reads as part of the first impression. It carries
   the same blue-to-cyan signature as the "3" in the wordmark, with a
   slightly lighter blue so the words stay easy to read on the dark
   background. width: fit-content keeps the gradient sized to the words
   instead of stretching across the full column. */
.hero .hero-philosophy {
    margin-top: var(--space-sm);
    margin-bottom: var(--space-md);
    width: fit-content;
    font-size: var(--text-xl);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.01em;
    line-height: var(--leading-tight);
    background: linear-gradient(120deg, #3B82F6, var(--color-cyan));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent; /* needed for Safari */
}

/* Two columns on desktop: message left, visual right. */
.hero__inner {
    display: grid;
    gap: var(--space-xl);
}

@media (min-width: 64em) {
    .hero__inner {
        grid-template-columns: 1.05fr 0.95fr;
        align-items: center;
    }
}

/* Decorative hero graphic, drawn entirely with CSS (no image files).
   width: 100% matters — the div's children are all absolutely
   positioned, so without an explicit width the browser would size it
   to 0 instead of stretching it to the grid column. */
.hero-visual {
    position: relative;
    width: 100%;
    max-width: 26rem;
    aspect-ratio: 1 / 1;
    margin-inline: auto;
}

/* Faint blueprint grid, fading out at the edges. */
.hero-visual__grid {
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(rgba(148, 163, 184, 0.12) 1px, transparent 1px),
        linear-gradient(90deg, rgba(148, 163, 184, 0.12) 1px, transparent 1px);
    background-size: 2rem 2rem;
    -webkit-mask-image: radial-gradient(circle at 50% 50%, black 35%, transparent 72%);
    mask-image: radial-gradient(circle at 50% 50%, black 35%, transparent 72%);
}

/* Soft blue glow behind the constellation. */
.hero-visual__glow {
    position: absolute;
    inset: 15%;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(37, 99, 235, 0.28), transparent 65%);
}

/* Inline SVG "drawing sheet" (see index.html): sheet corner marks,
   dashed construction circle and axes, a wireframe cube, and a
   dimension line. Sits above the grid and glow, below the floating
   nodes and the E3RD core badge. */
.hero-visual__art {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}

/* Dashed construction lines drift slowly along their own path —
   the strokes appear to travel around the circle, along the axes,
   out along the cyan leaders, and around the corner arcs. Each loop
   shifts by a whole number of dash periods (24 × the dasharray), so
   every pass ends exactly where it began; when the animation is
   stopped (reduced motion) the drawing simply looks unchanged. */
.art-dash {
    animation: dash-march 70s linear infinite;
}

.art-dash--circle { --dash-period: 11px; }  /* circle dasharray 4 7  */
.art-dash--axes { --dash-period: 8px; }     /* axes dasharray 2 6    */
.art-dash--leaders { --dash-period: 7px; }  /* leaders dasharray 3 4 */
.art-dash--arc { --dash-period: 12px; }     /* decor arcs dasharray 4 8 */

@keyframes dash-march {
    from { stroke-dashoffset: 0; }
    to { stroke-dashoffset: calc(var(--dash-period, 8px) * -24); }
}

.hero-visual__node {
    position: absolute;
    width: 0.75rem;
    height: 0.75rem;
    border-radius: 50%;
    background-color: var(--color-cyan);
    box-shadow: 0 0 12px rgba(6, 182, 212, 0.8);
    animation: hero-float 6s ease-in-out infinite;
}

.hero-visual__node--1 { top: 15%; left: 22%; animation-delay: 0s; }
.hero-visual__node--2 { top: 28%; right: 11%; animation-delay: -1.5s; }
.hero-visual__node--3 { bottom: 24%; left: 10%; animation-delay: -3s; }
.hero-visual__node--4 { bottom: 11%; right: 25%; animation-delay: -4.5s; }

.hero-visual__core {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: grid;
    place-items: center;
    width: 7rem;
    height: 7rem;
    border: 1px solid rgba(37, 99, 235, 0.55);
    border-radius: 50%;
    background-color: rgba(17, 17, 17, 0.65);
    color: var(--color-white);
    font-weight: var(--weight-bold);
    letter-spacing: 0.08em;
    animation: hero-pulse 4s ease-in-out infinite;
}

@keyframes hero-float {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-0.6rem); }
}

@keyframes hero-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.35); }
    50% { box-shadow: 0 0 0 1.25rem rgba(37, 99, 235, 0); }
}

/* ---- About ---------------------------------------------------------------- */
.about__inner {
    display: grid;
    gap: var(--space-xl);
}

@media (min-width: 64em) {
    .about__inner {
        grid-template-columns: 1.1fr 0.9fr;
        gap: var(--space-2xl);
        align-items: start;
    }
}

/* Side panel summarising how E3RD works. It sits on the dark About
   section, so it is a subtly raised dark surface with a hairline
   border — with the .framed corner brackets marking it as a "detail
   drawing" on the blueprint. */
.about-panel {
    padding: var(--space-lg);
    background-color: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(148, 163, 184, 0.25);
    border-radius: var(--radius-lg);
}

.about-panel h3 {
    margin-bottom: var(--space-md);
}

.about-points {
    list-style: none;
    display: grid;
    gap: var(--space-sm);
}

.about-points li {
    display: flex;
    align-items: baseline;
    gap: var(--space-sm);
}

/* Tick marks drawn with CSS — no icon font or image needed.
   Pure cyan reads clearly on the dark panel. */
.about-points li::before {
    content: "✓";
    flex-shrink: 0;
    color: var(--color-cyan);
    font-weight: var(--weight-bold);
}

/* ---- Section heading block ------------------------------------------------ */
.section-head {
    max-width: var(--container-narrow);
    margin-bottom: var(--space-2xl);
}

.section-head p {
    color: var(--color-gray);
}

/* The blue-grey sections need a shade more contrast than the default
   gray (about 4:1 on --color-tint-blue), so the intro line switches to
   the stronger gray (about 7:1 — comfortably AA). */
.section--tint-blue .section-head p {
    color: var(--color-gray-strong);
}

/* On dark and navy surfaces the intro line flips to the on-dark gray.
   This needs to out-specify the plain default above, which would
   otherwise win on source order and fail contrast (~3.7:1 on navy). */
.section--dark .section-head p,
.section--navy .section-head p {
    color: var(--color-gray-on-dark);
}

/* ---- Dimension mark under a heading ----------------------------------------
   The kind of measurement mark found on a technical drawing: two end
   ticks and a hairline between them, capped with small arrowheads that
   point outward — the "extent" arrows of a real dimension line. Used
   as one restrained "drawn" detail beneath section headings — purely
   decorative, so it is aria-hidden in the markup. */
.heading-dim {
    position: relative;
    width: 5rem;
    height: 0.6rem;
    margin-top: -0.5rem;
    margin-bottom: var(--space-md);
    border-left: 1px solid rgba(37, 99, 235, 0.35);
    border-right: 1px solid rgba(37, 99, 235, 0.35);
    background-image: linear-gradient(rgba(37, 99, 235, 0.35), rgba(37, 99, 235, 0.35));
    background-size: 100% 1px;
    background-position: 0 50%;
    background-repeat: no-repeat;
}

/* The arrowheads: a bordered square turned 45°, so two of its edges
   show as a chevron pointing away from the line  < |—————| >  */
.heading-dim::before,
.heading-dim::after {
    content: "";
    position: absolute;
    top: 50%;
    width: 0.4rem;
    height: 0.4rem;
    border-top: 1px solid rgba(37, 99, 235, 0.5);
    border-right: 1px solid rgba(37, 99, 235, 0.5);
}

.heading-dim::before {
    left: -0.6rem;
    transform: translateY(-50%) rotate(-135deg);
}

.heading-dim::after {
    right: -0.6rem;
    transform: translateY(-50%) rotate(45deg);
}

/* On the dark About section the marks switch to slate. */
.section--dark .heading-dim,
.section--navy .heading-dim {
    border-color: rgba(148, 163, 184, 0.55);
    background-image: linear-gradient(rgba(148, 163, 184, 0.55), rgba(148, 163, 184, 0.55));
}

.section--dark .heading-dim::before,
.section--dark .heading-dim::after,
.section--navy .heading-dim::before,
.section--navy .heading-dim::after {
    border-color: rgba(148, 163, 184, 0.6);
}

/* ---- Portfolio: honest empty state -----------------------------------------
   Shown on the portfolio page when the chosen filters match no projects
   (for example the Projects & Contracting category, which has none yet).
   It never fakes results; it offers a one-click way back to all projects. */
.empty-state {
    padding: var(--space-2xl) var(--space-lg);
    text-align: center;
    background-color: rgba(255, 255, 255, 0.02);
    border: 2px dashed rgba(148, 163, 184, 0.35);
    border-radius: var(--radius-lg);
}

.empty-state p {
    max-width: 40rem;
    margin-inline: auto;
    margin-bottom: var(--space-lg);
    color: var(--color-gray-on-dark);
}

/* ---- How we work (deep navy, three connected stages) ----------------------
   Explore → Evolve → Execute as one connected process. Each stage carries
   a small wireframe-cube badge that fills in as the process advances:
   Explore is a dashed outline, Evolve a forming structure, Execute a
   complete one — the geometry itself telling the progression story.
   On wider screens a dashed timeline links the three badges; on phones
   the stages stack and small chevrons point down to the next one. */
.process-grid {
    list-style: none;
    position: relative;
    /* Creates the grid's own stacking context, so the travelling point
       added on wider screens (z-index -1) stays above the section
       background but behind the stage cards. */
    z-index: 0;
    display: grid;
    gap: var(--space-lg);
    counter-reset: process; /* starts the 01/02/03 numbering */
}

.process-step {
    position: relative;
    padding: var(--space-lg) var(--space-md) var(--space-md);
    background-color: var(--color-navy-raised);
    border: 1px solid rgba(148, 163, 184, 0.2);
    border-radius: var(--radius-md);
    counter-increment: process;
}

/* The step number is generated by CSS, so the HTML stays clean.
   It reads as a large, quiet watermark in the card's corner. */
.process-step::before {
    content: "0" counter(process);
    position: absolute;
    top: var(--space-sm);
    right: var(--space-md);
    font-size: var(--text-2xl);
    font-weight: var(--weight-bold);
    line-height: 1;
    color: rgba(148, 163, 184, 0.18);
}

/* Circular badge holding the wireframe-cube glyph. */
.process-step__badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    margin-bottom: var(--space-sm);
    background-color: var(--color-navy); /* the card's "cut out" circle */
    border: 1px solid rgba(148, 163, 184, 0.35);
    border-radius: 50%;
}

.process-step__glyph {
    width: 1.75rem;
    height: 1.75rem;
}

/* Stage names echo the hero's blue-to-cyan signature. They are set a
   step larger than the card default so each stage leads its card. */
.process-step h3 {
    width: fit-content;
    margin-bottom: var(--space-sm);
    font-size: var(--text-xl);
    background: linear-gradient(120deg, #60A5FA, var(--color-cyan));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent; /* needed for Safari */
}

/* Roomier, higher-contrast text than the section default: these short
   paragraphs are the section's core message, so they read at 17px with
   a relaxed line-height and a brighter slate than the muted default. */
.process-step p {
    margin-bottom: 0;
    font-size: 1.0625rem; /* 17px */
    line-height: 1.7;
    color: #CBD5E1;
}

/* Phones: a small chevron in the gap between stacked stages. */
.process-step:not(:last-child)::after {
    content: "";
    position: absolute;
    bottom: calc(var(--space-lg) / -2);
    left: 50%;
    width: 0.6rem;
    height: 0.6rem;
    border-right: 2px solid var(--color-cyan);
    border-bottom: 2px solid var(--color-cyan);
    transform: translate(-50%, 50%) rotate(45deg);
}

/* Wider widths: three columns with a dashed "timeline" running through
   the badge centres. The line is drawn on the grid behind the cards, so
   it only shows in the gaps between them; each card's ::after dot then
   sits bright and centred on the line in the gap. */
@media (min-width: 48em) {
    .process-grid {
        grid-template-columns: repeat(3, 1fr);
        /* The travelling point below slides from the first card's left
           edge to the last card's right edge; clipping the grid keeps
           its trailing (transparent) edge from ever widening the page. */
        overflow: hidden;
    }

    .process-grid::before {
        content: "";
        position: absolute;
        top: 3.5rem; /* through the badge centres: 2rem padding + 1.5rem */
        left: 1.5rem;
        right: 1.5rem;
        border-top: 1px dashed rgba(6, 182, 212, 0.55);
    }

    /* A single faint point of light travels the dashed timeline from
       Explore to Execute, one unhurried pass every 16 seconds. It is
       painted behind the cards (z-index -1 inside the grid's stacking
       context), so it quietly emerges in the gaps between stages; the
       cards' own bright dots remain the fixed milestones. */
    .process-grid::after {
        content: "";
        position: absolute;
        top: 3.25rem; /* centres the point on the 3.5rem timeline */
        left: 1.5rem;
        width: calc(100% - 3rem);
        height: 0.5rem;
        background-image: radial-gradient(
            circle at 0.25rem 50%,
            var(--color-cyan) 0.16rem,
            rgba(6, 182, 212, 0.35) 0.2rem,
            transparent 0.32rem
        );
        z-index: -1;
        animation: process-travel 16s linear infinite;
    }

    .process-step:not(:last-child)::after {
        top: 3.5rem;
        bottom: auto;
        left: auto;
        right: calc(var(--space-lg) / -2);
        width: 0.5rem;
        height: 0.5rem;
        border: 0;
        border-radius: 50%;
        background-color: var(--color-cyan);
        transform: translate(50%, -50%);
    }
}

@keyframes process-travel {
    0% { transform: translateX(0); opacity: 0; }
    10% { opacity: 1; }
    85% { opacity: 1; }
    100% { transform: translateX(100%); opacity: 0; }
}

/* ---- Contact ---------------------------------------------------------------- */
.contact__inner {
    display: grid;
    gap: var(--space-xl);
}

@media (min-width: 64em) {
    .contact__inner {
        grid-template-columns: 0.9fr 1.1fr;
        gap: var(--space-3xl);
    }
}

/* The form sits on a white "sheet" lifted off the tinted, grid-lined
   section — like a drawing pinned to graph paper. The .framed class
   on the same element adds the blue corner brackets. */
.contact-panel {
    padding: var(--space-lg);
    background-color: var(--color-white);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
}

@media (min-width: 40em) {
    .contact-panel {
        padding: var(--space-xl);
    }
}

/* "Prefer to chat?" block under the contact copy — the WhatsApp alternative
   to the form. The dashed hairline is the same drawing-sheet detail used
   elsewhere on the site, and it reads as a divider inside the column rather
   than the start of a new section. "justify-items: start" keeps the button at
   its natural width instead of stretching it across the whole column. */
.contact__direct {
    margin-top: var(--space-lg);
    padding-top: var(--space-lg);
    border-top: 1px dashed var(--color-border-strong);
    display: grid;
    gap: var(--space-sm);
    justify-items: start;
}

.contact__direct-text {
    color: var(--color-gray-strong);
}

/* The business WhatsApp number printed as plain, readable text under the
   chat button, so a visitor can read, copy, or save it without opening a
   chat. International form first (the form wa.me links use), local form in
   brackets for callers inside Uganda. tabular-nums keeps the digits
   evenly spaced so the number reads as a number, not as prose. */
.contact__direct-number {
    color: var(--color-gray-strong);
    font-variant-numeric: tabular-nums;
}

.contact__direct-number strong {
    color: var(--color-black);
    font-weight: var(--weight-semibold);
}

.contact__direct-local {
    color: var(--color-gray);
}

/* ---- Form fields ------------------------------------------------------------ */
.form-field {
    display: grid;
    gap: var(--space-2xs);
    margin-bottom: var(--space-md);
}

.form-label {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
}

.form-optional {
    font-weight: var(--weight-regular);
    color: var(--color-gray);
}

.form-input {
    width: 100%;
    padding: 0.8rem 1rem;
    font-family: inherit;
    font-size: var(--text-base);
    color: var(--color-black);
    background-color: var(--color-white);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-md);
    transition:
        border-color var(--transition-fast),
        box-shadow var(--transition-fast);
}

.form-input:hover {
    border-color: var(--color-gray);
}

.form-input:focus {
    outline: none; /* replaced by the visible border + ring below */
    border-color: var(--color-blue);
    box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.18);
}

/* Invalid state is set by the JavaScript validation. */
.form-input[aria-invalid="true"] {
    border-color: var(--color-danger);
}

.form-input[aria-invalid="true"]:focus {
    box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.15);
}

textarea.form-input {
    min-height: 9rem;
    resize: vertical;
}

/* Validation messages: hidden until JavaScript reveals them. */
.field-error {
    display: none;
    font-size: var(--text-sm);
    color: var(--color-danger);
}

.field-error.is-visible {
    display: block;
}

/* Confirmation shown ONLY after the server confirms an enquiry was
   received (see the note above the form in index.html). */
.form-success {
    padding: var(--space-md);
    margin-bottom: var(--space-md);
    background-color: rgba(21, 128, 61, 0.08);
    border: 1px solid rgba(21, 128, 61, 0.35);
    border-radius: var(--radius-md);
    color: var(--color-success);
    font-weight: var(--weight-medium);
}

/* Honest failure message: shown when the enquiry could not be sent
   (for example, no internet connection, or the page opened from disk).
   Field values are deliberately kept so nothing the visitor typed is lost. */
.form-error {
    padding: var(--space-md);
    margin-bottom: var(--space-md);
    background-color: rgba(220, 38, 38, 0.07);
    border: 1px solid rgba(220, 38, 38, 0.35);
    border-radius: var(--radius-md);
    color: var(--color-danger);
    font-weight: var(--weight-medium);
}

/* Honeypot trap field (see index.html): humans must never see or reach
   it, but it must still exist in the form data for the spam filter.
   Clipped to invisibility (same technique as the skip link) and taken
   out of the layout with absolute positioning. */
.honeypot-field {
    clip-path: inset(50%);
    height: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
    width: 1px;
}

/* --------------------------------------------------------------------------
   8. PORTFOLIO (project cards, filter bar, detail dialogs)
   -------------------------------------------------------------------------- */

/* ---- Portfolio page hero (portfolio.html banner) --------------------------
   A shorter dark banner than the homepage hero: just the page label,
   heading, and intro line. */
.page-hero {
    padding-block: var(--space-2xl);
}

/* The lede sits at the bottom of the banner, so drop its trailing margin. */
.page-hero .text-lede {
    margin-bottom: 0;
}

/* ---- Filter bar ------------------------------------------------------------
   Two groups of chips: category and project type. The selected chip is
   marked both visually and for assistive technology through
   aria-pressed="true", which script.js keeps up to date. */
.filter-bar {
    display: grid;
    gap: var(--space-md);
    margin-bottom: var(--space-md);
}

.filter-group__label {
    margin-bottom: var(--space-xs);
    font-size: var(--text-xs);
    font-weight: var(--weight-bold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--color-gray-on-dark); /* the filter bar sits on navy */
}

.filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
}

/* Chips are outlined pills on the navy section: white text inside a
   slate hairline, turning cyan on hover. The selected chip below is
   the one filled solid with the brand blue. */
.filter-chip {
    padding: 0.5rem 1.1rem;
    background-color: transparent;
    color: var(--color-white);
    border: 1.5px solid rgba(148, 163, 184, 0.4);
    border-radius: var(--radius-full);
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    line-height: 1.2;
    cursor: pointer;
    transition:
        background-color var(--transition-fast),
        color var(--transition-fast),
        border-color var(--transition-fast);
}

.filter-chip:hover {
    border-color: var(--color-cyan);
    color: var(--color-cyan);
}

/* The selected chip stands out as a solid blue pill. */
.filter-chip[aria-pressed="true"] {
    background-color: var(--color-blue);
    border-color: var(--color-blue);
    color: var(--color-white);
}

.filter-chip[aria-pressed="true"]:hover {
    color: var(--color-white); /* keep white text on the solid blue fill */
}

/* Live result count under the filters ("Showing 3 of 8 projects."). */
.filter-status {
    margin-bottom: var(--space-lg);
    color: var(--color-gray-on-dark);
    font-size: var(--text-sm);
}

/* ---- Project grid and cards ------------------------------------------------
   The same card structure is used on the homepage (three featured
   projects) and on the portfolio page (all projects). */
.project-grid {
    display: grid;
    gap: var(--space-lg);
}

@media (min-width: 40em) {
    .project-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 64em) {
    .project-grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

/* Project cards sit on the navy portfolio section. They get a raised
   navy surface (lighter than the section behind them), a slate hairline
   border, and a dark hover shadow — the same "panel on a drawing sheet"
   language as the process steps. */
.project-card {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background-color: var(--color-navy-raised);
    border: 1px solid rgba(148, 163, 184, 0.22);
    border-radius: var(--radius-lg);
    transition:
        border-color var(--transition-fast),
        box-shadow var(--transition-fast),
        transform var(--transition-fast);
}

.project-card:hover {
    border-color: var(--color-cyan);
    box-shadow: 0 12px 28px rgba(2, 8, 23, 0.5);
    transform: translateY(-2px);
}

/* One shared proportion (16:10) for every thumbnail so cards line up
   neatly whatever the source image size. object-fit crops instead of
   squashing; .thumb-focus-top shifts the crop upward on tall images. */
.project-card__thumb {
    aspect-ratio: 16 / 10;
    overflow: hidden;
    background-color: rgba(255, 255, 255, 0.04);
    border-bottom: 1px solid rgba(148, 163, 184, 0.22);
}

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

.thumb-focus-top {
    object-position: 50% 12%;
}

/* Wide artwork that must not be cropped (the WIPAM banner) is fitted
   inside the same 16:10 box instead of covering it, so none of the
   design is cut away at the sides. The selector carries the element as
   well as the class so it outranks .project-card__thumb img above. */
.project-card__thumb img.thumb-fit-contain {
    object-fit: contain;
}

.project-card__body {
    display: flex;
    flex-direction: column;
    flex: 1;
    gap: var(--space-xs);
    padding: var(--space-md);
}

.project-card__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-xs);
}

.project-card__category {
    color: var(--color-gray-on-dark);
    font-size: var(--text-sm);
}

.project-card__body h3 {
    margin-bottom: 0; /* the card's gap handles spacing */
    font-size: var(--text-lg);
}

/* Scoped to out-specify the navy section's paragraph flip (which would
   otherwise dim the description to the muted on-dark gray). */
.project-card .project-card__desc {
    margin-bottom: 0;
    color: #CBD5E1;
}

/* The button always sits at the bottom edge, so cards with different
   text lengths still line up. */
.project-card__cta {
    margin-top: auto;
    align-self: flex-start;
}

/* Homepage extras: the honesty note below the grid, and the
   "Explore All Projects" button beneath it. */
.project-grid + .portfolio-note {
    margin-top: var(--space-lg);
}

.portfolio-actions {
    margin-top: var(--space-lg);
}

/* ---- Project detail dialogs ------------------------------------------------
   Native <dialog> elements opened with showModal(). The browser supplies
   the focus trap, Escape key handling, and the backdrop; these rules only
   restyle it. IMPORTANT: never set a "display" value on .project-dialog
   itself — the browser's own rules are what keep a closed dialog hidden. */
.project-dialog {
    /* The browser centres a modal <dialog> with its own "margin: auto",
       but the global reset above (* { margin: 0 }) would strip that and
       pin the panel to the top-left corner — so put it back here. */
    margin: auto;
    width: min(54rem, calc(100% - 2rem));
    max-height: 88vh;
    max-height: min(88vh, 88dvh);
    padding: 0;
    border: 0;
    border-radius: var(--radius-lg);
    background-color: var(--color-white);
    /* The dialog renders in the browser's top layer but is still a DOM
       descendant of the navy section, so it would otherwise inherit the
       section's white text — invisible on this white sheet. Reset the
       text colour to the light-surface default here. */
    color: var(--color-black);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

/* Focus normally lands on the close button (the first focusable
   element); when it does land on the dialog box itself, skip the ring. */
.project-dialog:focus {
    outline: none;
}

.project-dialog::backdrop {
    background-color: rgba(5, 12, 25, 0.68);
}

/* The inner wrapper scrolls; the dialog itself only clips the corners.
   overscroll-behavior stops the page behind from scrolling when a touch
   visitor reaches the end of the dialog content. */
.project-dialog__inner {
    position: relative;
    max-height: 88vh;
    max-height: min(88vh, 88dvh);
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Round close button that stays pinned near the top corner while the
   dialog content scrolls. The negative bottom margin pulls the content
   back up, so the button floats over it instead of pushing it down. */
.project-dialog__close {
    position: sticky;
    top: var(--space-xs);
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    margin: var(--space-xs) var(--space-xs) -3rem auto;
    background-color: var(--color-white);
    color: var(--color-black);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-full);
    box-shadow: var(--shadow-sm);
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
    transition:
        background-color var(--transition-fast),
        color var(--transition-fast),
        border-color var(--transition-fast);
}

.project-dialog__close:hover {
    background-color: var(--color-blue);
    border-color: var(--color-blue);
    color: var(--color-white);
}

.project-dialog__media {
    display: grid;
    gap: var(--space-sm);
    padding: var(--space-md);
    background-color: var(--color-light-gray);
    border-bottom: 1px solid var(--color-border);
}

.project-dialog__media figure {
    margin: 0;
}

.project-dialog__media img {
    display: block;
    width: 100%;
    height: auto;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
}

.project-dialog__media figcaption {
    margin-top: var(--space-xs);
    color: var(--color-gray);
    font-size: var(--text-sm);
}

/* Projects with two real images (the CleanRazor flyer and logo) show
   them side by side from the small-tablet width, flyer column larger. */
@media (min-width: 40em) {
    .project-dialog__media--two-up {
        grid-template-columns: 1.3fr 1fr;
        align-items: start;
    }
}

.project-dialog__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    padding: var(--space-lg);
}

.project-dialog__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-xs);
}

.project-dialog__category {
    color: var(--color-gray);
    font-size: var(--text-sm);
}

.project-dialog__body h2 {
    margin-bottom: 0;
    font-size: var(--text-2xl);
}

/* The h3s inside a dialog act as small section labels, in the same
   style as the service detail panels. */
.project-dialog__body h3 {
    margin-top: var(--space-xs); /* groups each label with what follows */
    margin-bottom: 0;
    font-size: var(--text-xs);
    font-weight: var(--weight-bold);
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--color-gray-strong);
}

.project-dialog__body p {
    margin-bottom: 0;
    color: var(--color-gray-strong);
}

/* Tick list, same checklist style as the About panel. */
.project-dialog__list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    color: var(--color-gray-strong);
}

.project-dialog__list li {
    position: relative;
    padding-left: 1.6rem;
}

.project-dialog__list li::before {
    content: "✓";
    position: absolute;
    left: 0;
    font-weight: var(--weight-bold);
    color: var(--color-blue);
}

.project-dialog__related {
    color: var(--color-gray);
    font-size: var(--text-sm);
}

.project-dialog__cta {
    align-self: flex-start;
    margin-top: var(--space-xs);
}

/* Gentle entrance when a dialog opens; the global reduced-motion rule
   turns this off for visitors who prefer less motion. */
.project-dialog[open] {
    animation: dialog-in var(--transition-base) both;
}

@keyframes dialog-in {
    from {
        opacity: 0;
        transform: translateY(10px) scale(0.99);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

/* --------------------------------------------------------------------------
   9. ACCESSIBILITY HELPERS
   -------------------------------------------------------------------------- */

/* The hidden attribute must always win, even over other display rules. */
[hidden] {
    display: none !important;
}

/* "Skip to content" link: sits above the viewport until a keyboard user
   Tabs to it, then it drops into view as the first focusable element. */
.skip-link {
    position: absolute;
    top: 0;
    left: var(--space-sm);
    z-index: 100; /* above the sticky header */
    background-color: var(--color-blue);
    color: var(--color-white);
    border-radius: 0 0 var(--radius-sm) var(--radius-sm);
    font-weight: var(--weight-semibold);
    text-decoration: none;
    white-space: nowrap;
    /* Hidden by collapsing to a clipped 1px box instead of sliding it
       offscreen with a transform. Some browsers leave elements that sit
       fully outside the viewport out of the Tab order, which would make
       the link unreachable by keyboard; a clipped box stays reachable. */
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
}

.skip-link:focus {
    /* Expand back to a normal visible link when reached by keyboard. */
    width: auto;
    height: auto;
    padding: var(--space-xs) var(--space-sm);
    overflow: visible;
    clip-path: none;
}

/* Hides an element visually while keeping it available to screen readers
   and other assistive technology (used for the menu button label). */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Strong, always-visible keyboard focus ring.
   Blue on light backgrounds; cyan on dark sections (blue is too dim there). */
:focus-visible {
    outline: 3px solid var(--color-blue);
    outline-offset: 3px;
}

.section--dark :focus-visible,
.section--navy :focus-visible,
.site-header :focus-visible,
.site-footer :focus-visible {
    outline-color: var(--color-cyan);
}

/* Respect visitors who prefer reduced motion. */
@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }

    /* Decorative motion that adds nothing when animation is off is
       removed outright: the drifting digits and the travelling process
       point. (JavaScript also skips creating the digits entirely, and a
       stopped dash-march — hero drawing and corner arcs alike — lands
       on a whole number of dash periods, so the drawings look
       unchanged.) */
    .binary-field,
    .process-grid::after {
        display: none;
    }

    *,
    *::before,
    *::after {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
    }
}
