/* ===========================================================================
   NGDA — site.css: the one design system (design/001 Requirement 8, plans/001)
   ---------------------------------------------------------------------------
   Sections, in this order:
     (a) tokens                — :root custom properties, and the Bootstrap bridge
     (b) reduced motion        — zeroes the durations, cancels the reveal
     (c) base                  — html, body, headings, links, .mono, focus
     (d) components            — the site's own classes

   Bootstrap 5.3.3 stays for the grid, the collapse, the dropdown and the forms.
   Where Bootstrap exposes a custom property (--bs-*), this file sets it from a
   token rather than restating Bootstrap's declaration, so no Bootstrap rule is
   overridden in place. No declaration in this file is forced with an override flag.
   =========================================================================== */


/* ---------------------------------------------------------------------------
   (a) tokens
   --------------------------------------------------------------------------- */

:root {
    /* colour — the palette of design/001 Requirement 8 */
    --bg: #080D14;
    --bg-2: #0B1726;
    --surface: #142438;
    --accent: #1677FF;
    --accent-2: #00C8F8;
    --accent-3: #6366F1;
    --text: #F3F7FC;
    --text-2: #A4B4C7;
    --ok: #17C784;
    --human: #F5B942;
    /* the two remaining lifecycle states (design/001 Requirement 2) */
    --blocked: #E5484D;
    --queued: #5C6B82;

    /* type */
    --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, "JetBrains Mono", "Fira Mono", Menlo, Consolas, monospace;
    --fs-1: 0.75rem;
    --fs-2: 0.875rem;
    --fs-3: 1rem;
    --fs-4: 1.125rem;
    --fs-5: clamp(1.25rem, 1.1rem + 0.6vw, 1.5rem);
    --fs-6: clamp(1.625rem, 1.3rem + 1.6vw, 2.25rem);
    --fs-7: clamp(2rem, 1.4rem + 3.2vw, 3.25rem);

    /* space */
    --sp-1: 0.25rem;
    --sp-2: 0.5rem;
    --sp-3: 0.75rem;
    --sp-4: 1rem;
    --sp-5: 1.5rem;
    --sp-6: 2rem;
    --sp-7: 3rem;
    --sp-8: 4.5rem;

    /* surface treatment and motion */
    --radius: 14px;
    --border: 1px solid rgba(243, 247, 252, 0.10);
    --border-strong: 1px solid rgba(243, 247, 252, 0.18);
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.45), 0 14px 36px rgba(0, 0, 0, 0.35);
    /* The lit treatment of plans/006 step 1: a hairline of --accent inside the box and a soft
       bloom outside it, so a surface reads as illuminated rather than merely raised. `--glow` is
       the resting state of a primary button and the hover state of a card or a panel;
       `--glow-strong` is the hero console's. Both are box-shadow lists, so they compose with
       `--shadow` rather than replacing it. */
    --glow: 0 0 0 1px rgba(22, 119, 255, .35), 0 0 24px rgba(22, 119, 255, .22);
    --glow-strong: 0 0 0 1px rgba(0, 200, 248, .45), 0 0 44px rgba(22, 119, 255, .34);
    /* `--edge` is the 1 px border image the masked `::before` of a card or a panel paints: a line
       that starts at --accent-2 along the top-left corner and fades out before the opposite one. */
    --edge: linear-gradient(130deg, var(--accent-2), rgba(0, 200, 248, 0) 62%);
    --dur-fast: 120ms;
    --dur: 420ms;
}

/* The Bootstrap bridge: Bootstrap's own variables, given this site's tokens.
   The selector is more specific than Bootstrap's own [data-bs-theme=dark]. */
:root[data-theme="dark"] {
    --bs-body-bg: var(--bg);
    --bs-body-color: var(--text);
    --bs-body-font-family: var(--font-sans);
    --bs-body-font-size: var(--fs-3);
    --bs-body-line-height: 1.65;
    --bs-heading-color: var(--text);
    --bs-emphasis-color: var(--text);
    --bs-secondary-color: var(--text-2);
    --bs-tertiary-color: var(--text-2);
    --bs-secondary-bg: var(--bg-2);
    --bs-tertiary-bg: var(--surface);
    --bs-border-color: rgba(243, 247, 252, 0.12);
    --bs-border-radius: var(--radius);
    --bs-link-color: var(--accent-2);
    --bs-link-color-rgb: 0, 200, 248;
    --bs-link-hover-color: var(--text);
    --bs-link-hover-color-rgb: 243, 247, 252;
    --bs-code-color: var(--accent-2);
    --bs-focus-ring-color: rgba(0, 200, 248, 0.45);
}


/* ---------------------------------------------------------------------------
   (b) reduced motion
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    :root {
        --dur-fast: 0ms;
        --dur: 0ms;
    }

    /* More specific than the hiding rule in (d), so order cannot reinstate it:
       a reader who prefers reduced motion sees every section, no transform. */
    :root.reveal-ready [data-reveal] {
        opacity: 1;
        transform: none;
        transition: none;
    }
}


/* ---------------------------------------------------------------------------
   (c) base
   --------------------------------------------------------------------------- */

html {
    background-color: var(--bg);
    color-scheme: dark;
}

body {
    background-color: var(--bg);
    /* Three layers, front to back: the radial wash that lifts the top of the page, and the two
       hairline gradients that draw the 64 px interface grid of the concepts' backgrounds
       (plans/006 step 7). The grid is at 4 % of --text, so it reads as texture and never as
       content; it repeats, the wash does not. */
    background-image:
        radial-gradient(1100px 620px at 50% -240px, var(--bg-2), transparent 72%),
        linear-gradient(rgba(243, 247, 252, 0.04) 1px, transparent 1px),
        linear-gradient(90deg, rgba(243, 247, 252, 0.04) 1px, transparent 1px);
    background-size: auto, 64px 64px, 64px 64px;
    background-repeat: no-repeat, repeat, repeat;
    /* The full-bleed page header is 100vw wide, which on a window with a classic
       scrollbar is a few pixels wider than the space beside it; `clip` trims
       those without making the body a scroll container, so the sticky
       navigation keeps sticking and nothing scrolls sideways. A browser that
       does not know `clip` drops the declaration and is no worse off. */
    overflow-x: clip;
    color: var(--text);
    font-family: var(--font-sans);
    font-size: var(--fs-3);
    line-height: 1.65;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
    color: var(--text);
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: -0.01em;
    margin-bottom: var(--sp-4);
}

/* The one headline of a page carries the weight and the tracking of the concepts' display type
   (plans/006 step 2); every other heading keeps the scale above. */
h1 {
    font-size: var(--fs-7);
    font-weight: 700;
    letter-spacing: -0.025em;
}
h2 { font-size: var(--fs-6); }
h3 { font-size: var(--fs-5); }
h4 { font-size: var(--fs-4); }
h5, h6 { font-size: var(--fs-3); }

p, li {
    color: var(--text-2);
}

p {
    margin-bottom: var(--sp-4);
}

strong, b {
    color: var(--text);
    font-weight: 600;
}

a {
    color: var(--accent-2);
    text-decoration: none;
    transition: color var(--dur-fast) ease;
}

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

ul, ol {
    padding-left: 1.25rem;
}

li {
    margin-bottom: var(--sp-2);
}

hr {
    border-color: rgba(243, 247, 252, 0.12);
    opacity: 1;
}

img {
    max-width: 100%;
    height: auto;
}

.mono {
    font-family: var(--font-mono);
    font-size: 0.9em;
    letter-spacing: 0.02em;
}

/* One visible ring on every interactive element, keyboard or not. */
:focus-visible {
    outline: 2px solid var(--accent-2);
    outline-offset: 2px;
}


/* ---------------------------------------------------------------------------
   (d) components
   --------------------------------------------------------------------------- */

.skip-link {
    position: absolute;
    left: var(--sp-4);
    top: var(--sp-2);
    z-index: 1100;
    padding: var(--sp-2) var(--sp-4);
    background: var(--surface);
    border: var(--border-strong);
    border-radius: var(--radius);
    color: var(--text);
    transform: translateY(-200%);
    transition: transform var(--dur-fast) ease;
}

.skip-link:focus {
    transform: translateY(0);
}

/* the preview banner (app.py PREVIEW_LABEL) */
.preview-banner {
    background: var(--human);
    color: #10161F;
    font-size: var(--fs-2);
    font-weight: 600;
    letter-spacing: 0.02em;
    text-align: center;
    padding: var(--sp-2) var(--sp-4);
}

/* the navigation */
.site-nav {
    position: sticky;
    top: 0;
    z-index: 1000;
    background-color: rgba(8, 13, 20, 0.88);
    backdrop-filter: blur(10px);
    border-bottom: var(--border);
    padding-block: var(--sp-2);
}

.site-nav__inner {
    gap: var(--sp-4);
}

.site-nav__brand {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
}

/* The navigation draws the short logo, chart and NGDA in one wide image (design/001 Decision
   18); the box is its shape, 273 x 80 at half size. */
.site-nav__logo {
    width: 137px;
    height: 40px;
    object-fit: contain;
}

.site-nav .nav-link {
    --bs-nav-link-color: var(--text-2);
    --bs-nav-link-hover-color: var(--text);
    font-size: var(--fs-2);
    font-weight: 500;
    letter-spacing: 0.01em;
    padding-inline: var(--sp-3);
}

.site-nav .dropdown-menu {
    --bs-dropdown-bg: var(--surface);
    --bs-dropdown-border-color: rgba(243, 247, 252, 0.12);
    --bs-dropdown-border-radius: var(--radius);
    --bs-dropdown-link-color: var(--text-2);
    --bs-dropdown-link-hover-bg: rgba(22, 119, 255, 0.16);
    --bs-dropdown-link-hover-color: var(--text);
    --bs-dropdown-link-active-bg: var(--accent);
    box-shadow: var(--shadow);
}

.site-nav__toggler {
    --bs-navbar-toggler-border-color: rgba(243, 247, 252, 0.18);
    --bs-navbar-toggler-border-radius: 10px;
    padding: var(--sp-2) var(--sp-3);
}

.site-nav__cta {
    white-space: nowrap;
}

@media (max-width: 991.98px) {
    .site-nav__drawer {
        padding-top: var(--sp-3);
    }

    .site-nav__cta {
        display: block;
        margin-top: var(--sp-3);
        text-align: center;
    }
}

/* the one content column */
.site-main {
    padding-block: var(--sp-7) var(--sp-8);
}

/* the footer */
.site-footer {
    border-top: var(--border);
    background-color: var(--bg-2);
    padding-block: var(--sp-6);
}

.site-footer__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-5);
    width: 100%;
    max-width: 1320px;
    margin-inline: auto;
    padding-inline: var(--sp-5);
}

/* The lockup and the social links are 436 px side by side, past a 390 px window, so the row
   wraps and the lockup takes the line above (backlog/007). */
.site-footer__brand {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-5);
    row-gap: var(--sp-3);
}

/* The footer draws the operator's full logo, so the box is its own 539 × 160 shape at half
   size (plans/004; design/001 Decision 18). */
.site-footer__logo {
    width: 270px;
    height: 80px;
    object-fit: contain;
}

.site-footer__social {
    display: flex;
    gap: var(--sp-3);
}

.site-footer__social a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border: var(--border);
    border-radius: 50%;
    color: var(--text-2);
    font-size: var(--fs-4);
    transition: color var(--dur-fast) ease, border-color var(--dur-fast) ease, background-color var(--dur-fast) ease;
}

.site-footer__social a:hover {
    color: var(--text);
    border-color: var(--accent);
    background-color: rgba(22, 119, 255, 0.14);
    text-decoration: none;
}

.site-footer__copy {
    color: var(--text-2);
    font-size: var(--fs-2);
    margin: 0;
}

/* the button pair: one filled, one outlined, configured through Bootstrap's
   own --bs-btn-* properties */
.btn-primary {
    /* Resting is #0B5ED7, not --accent: white on --accent is 3.68:1 and white on this is 5.4:1,
       which carries the label at any size (design/001 "colour contrast at least 4.5:1").
       --accent keeps every other job: borders, glows, links and the lifecycle pills. */
    --bs-btn-color: #FFFFFF;
    --bs-btn-bg: #0B5ED7;
    --bs-btn-border-color: #0B5ED7;
    --bs-btn-hover-color: #FFFFFF;
    --bs-btn-hover-bg: #0A53BE;
    --bs-btn-hover-border-color: #0A53BE;
    --bs-btn-active-color: #FFFFFF;
    --bs-btn-active-bg: #094AA8;
    --bs-btn-active-border-color: #094AA8;
    --bs-btn-disabled-color: #FFFFFF;
    --bs-btn-disabled-bg: var(--queued);
    --bs-btn-disabled-border-color: var(--queued);
    --bs-btn-focus-shadow-rgb: 0, 200, 248;
    font-weight: 600;
    /* lit at rest, brighter under the pointer (plans/006 step 1) */
    box-shadow: var(--glow);
    /* The trailing `fa-arrow-right` of plans/006 step 1 sits on one line with the label, with
       one gap between them, as `.card__link`'s arrow does (backlog/008). The label stays
       centred, so the buttons with no glyph — the navigation's and the contact form's submit,
       which is an `<input>` and can hold none — are drawn exactly as before. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
}

.btn-primary:hover,
.btn-primary:focus-visible {
    box-shadow: var(--glow-strong);
}

/* The outlined half of the pair answers to a second name as well: the contact form's reset
   button is `main`'s markup byte for byte (plans/003 step 6), and it names
   .btn-outline-secondary. One declaration block, so the two cannot drift apart. */
.btn-secondary,
.btn-outline-secondary {
    --bs-btn-color: var(--text);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: rgba(243, 247, 252, 0.22);
    --bs-btn-hover-color: var(--text);
    --bs-btn-hover-bg: rgba(22, 119, 255, 0.16);
    --bs-btn-hover-border-color: var(--accent);
    --bs-btn-active-color: var(--text);
    --bs-btn-active-bg: rgba(22, 119, 255, 0.24);
    --bs-btn-active-border-color: var(--accent);
    --bs-btn-disabled-color: var(--queued);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--queued);
    --bs-btn-focus-shadow-rgb: 0, 200, 248;
    font-weight: 600;
}

/* The outline stays an outline; what the pointer adds is the light around it, so the pair reads
   as one filled and one lit rather than as two filled buttons (plans/006 step 1). */
.btn-secondary:hover,
.btn-secondary:focus-visible,
.btn-outline-secondary:hover,
.btn-outline-secondary:focus-visible {
    box-shadow: var(--glow);
}

/* the one card */
.card {
    --bs-card-bg: var(--surface);
    --bs-card-border-color: rgba(243, 247, 252, 0.10);
    --bs-card-border-radius: var(--radius);
    --bs-card-color: var(--text);
    --bs-card-spacer-x: var(--sp-5);
    --bs-card-spacer-y: var(--sp-5);
    box-shadow: var(--shadow);
}

/* --- the lit edge (plans/006 step 1) --------------------------------------
   A card and a panel are the site's two surfaces, and both take the same treatment: a 1 px
   `--edge` line around the box, and the box lifting to `--glow` when the pointer is on it or
   the keyboard is inside it. The line is a masked pseudo-element rather than `border-image`,
   because `border-image` drops the radius and these boxes are rounded. It is decoration: it
   takes no pointer and says nothing, so nothing is lost where the mask is unsupported. */
.card,
.panel {
    transition: box-shadow var(--dur-fast) ease;
}

.card::before,
.panel::before {
    content: "";
    position: absolute;
    inset: 0;
    /* above the card's own media strip, so the edge stays lit along the top of a card that
       opens on a scene; it is one hairline at the border, so it covers no text */
    z-index: 1;
    padding: 1px;
    border-radius: inherit;
    background-image: var(--edge);
    pointer-events: none;
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
}

.card:hover,
.card:focus-within,
.panel:hover,
.panel:focus-within {
    box-shadow: var(--shadow), var(--glow);
}

/* the one section rhythm */
.section {
    padding-block: var(--sp-7);
}

/* A section that opens on a full-bleed header (agentic_workflow.html #overview, which keeps its
   id and carries one paragraph below the header): the header band brings its own
   top padding, so the section adds none above it. */
.section--header {
    padding-top: 0;
}

.section-label {
    display: inline-block;
    font-size: var(--fs-1);
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--accent-2);
    margin-bottom: var(--sp-3);
}

/* A service page's header draws that service's own icon beside the label
   (components/service.html); no other label on the site carries one. */
.section-label i {
    margin-right: var(--sp-2);
}

/* one badge per lifecycle state (design/001 Requirement 2) */
.badge-state {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    font-family: var(--font-mono);
    font-size: var(--fs-1);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding: 0.3rem var(--sp-3);
    border-radius: 999px;
    border: 1px solid currentColor;
    color: var(--text-2);
    background-color: rgba(92, 107, 130, 0.14);
}

.badge-state::before {
    content: "";
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background-color: currentColor;
}

.badge-state--verified { color: var(--ok); background-color: rgba(23, 199, 132, 0.14); }
.badge-state--executing { color: var(--accent-2); background-color: rgba(0, 200, 248, 0.14); }
.badge-state--human { color: var(--human); background-color: rgba(245, 185, 66, 0.14); }
.badge-state--queued { color: var(--queued); background-color: rgba(92, 107, 130, 0.18); }
.badge-state--blocked { color: var(--blocked); background-color: rgba(229, 72, 77, 0.14); }

/* forms on dark: the fill, the text and the radius come from the bridge above;
   Bootstrap hard-codes the focus colours, so the focus state is set here */
.form-control {
    padding: var(--sp-3) var(--sp-4);
}

.form-control::placeholder {
    color: rgba(164, 180, 199, 0.65);
}

.form-control:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 0.2rem rgba(0, 200, 248, 0.25);
}

.form-floating > label {
    color: var(--text-2);
}

/* the contact form's message field (contact.html, the fixed form markup) */
.message-textarea {
    min-height: 10rem;
}

/* the reveal. Only `.reveal-ready` hides anything, and only site.js sets it, so
   without JavaScript — or with a reduced-motion preference — every section reads
   as it stands. */
.reveal-ready [data-reveal] {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity var(--dur) ease, transform var(--dur) ease;
}

.reveal-ready [data-reveal].is-visible {
    opacity: 1;
    transform: none;
}


/* --- the lifecycle and delivery flow -------------------------------------
   A row of nodes joined by connectors, a column below 768 px. Used by
   templates/components/lifecycle.html (the agent lifecycle, with a state pill
   per node) and by index.html #process (the five delivery steps, no pill).
   The connector is a pseudo-element, so no node carries decorative markup.

   `--flow-gap` is the one place the space between two nodes is written: the row
   takes it as its `gap` and the connector spans it, so a variant that wants a
   different rhythm sets the one property and the connector follows. */
.flow {
    --flow-gap: var(--sp-4);
    display: flex;
    align-items: stretch;
    gap: var(--flow-gap);
    list-style: none;
    padding-left: 0;
    margin-bottom: 0;
}

.flow__node {
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--sp-2);
    margin-bottom: 0;
}

/* drawn back from a node to its predecessor's mark, across the gap; decorative,
   behind the mark */
.flow__node + .flow__node::before {
    content: "";
    position: absolute;
    top: 1.1875rem;
    right: 50%;
    left: calc(-50% - var(--flow-gap));
    height: 2px;
    background-color: rgba(243, 247, 252, 0.14);
}

.flow__mark {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    flex: 0 0 auto;
    border-radius: 50%;
    background-color: var(--bg-2);
    border: 1px solid rgba(243, 247, 252, 0.18);
    color: var(--text-2);
    font-size: var(--fs-2);
}

/* one mark per lifecycle state, the same five as .badge-state--* above */
.flow__mark--verified { color: var(--ok); border-color: rgba(23, 199, 132, 0.45); }
.flow__mark--executing { color: var(--accent-2); border-color: rgba(0, 200, 248, 0.45); }
.flow__mark--human { color: var(--human); border-color: rgba(245, 185, 66, 0.45); }
.flow__mark--queued { color: var(--queued); border-color: rgba(92, 107, 130, 0.60); }
.flow__mark--blocked { color: var(--blocked); border-color: rgba(229, 72, 77, 0.45); }

.flow__label {
    color: var(--text);
    font-weight: 600;
    font-size: var(--fs-2);
}

.flow__caption {
    color: var(--text-2);
    font-size: var(--fs-1);
    line-height: 1.5;
}

/* A state pill inside a node belongs to that node's column, below the label.
   `margin-top: auto` puts every pill of a row on one line whatever its caption
   wrapped to, and the width ceiling is the node plus one gap, so a centred pill
   reaches at most half way across the gap and never over its neighbour's
   (backlog/006: the pills floated at different heights and overlapped). The dot
   and the tracking of the standalone badge go: the mark above the label already
   carries this state's colour and icon, and the word has a column to fit. */
.flow__node > .badge-state {
    margin-top: auto;
    max-width: calc(100% + var(--flow-gap));
    padding-inline: var(--sp-2);
    letter-spacing: 0;
    overflow-wrap: anywhere;
}

.flow__node > .badge-state::before {
    display: none;
}

/* the governance boundary: two nodes, side by side rather than stretched */
.flow--pair {
    --flow-gap: var(--sp-6);
    justify-content: center;
}

.flow--pair .flow__node {
    flex: 0 1 20rem;
}

/* --- the delivery step's thumbnail (plans/007 step 5) --------------------
   One of the operator's own card-size images above each step's mark, in the
   `flow--steps` variant of the row (index.html #process). The thumbnails share
   one height, so the marks below them stay on one line and the connector meets
   the centre of each; `width: 100%` of a node is about 215 px on a 1366 px
   desktop, under every one of these files' own 217-380 px, so none of them is
   enlarged. The radius and the lit edge are the card strip's. */
.flow--steps {
    --flow-thumb-h: 7rem;
}

.flow--steps .flow__thumb {
    display: block;
    width: 100%;
    height: var(--flow-thumb-h);
    object-fit: cover;
    border: var(--border-strong);
    border-radius: calc(var(--radius) - 6px);
}

/* the connector clears the thumbnails: a mark's centre is now the thumbnail,
   the node's own gap and half a mark below the top edge of the node */
.flow--steps .flow__node + .flow__node::before {
    top: calc(var(--flow-thumb-h) + var(--sp-2) + 1.1875rem);
}

@media (min-width: 992px) {
    /* The compact variant: the home hero's panel (index.html #hero) is half a row
       wide, about 390 px, so its seven nodes get roughly 50 px each. A mark and a
       short label fit that; a caption does not, and neither does a pill whose own
       word is some 80 px wide, so components/lifecycle.html renders both for
       assistive technology and leaves them undrawn (backlog/006). Seven columns
       because the lifecycle has seven stages, and this variant is the hero's alone. */
    .flow--compact {
        --flow-gap: var(--sp-2);
        display: grid;
        grid-template-columns: repeat(7, minmax(0, 1fr));
    }

    .flow--compact .flow__node {
        gap: var(--sp-1);
    }

    .flow--compact .flow__mark {
        width: 32px;
        height: 32px;
        font-size: var(--fs-1);
    }

    .flow--compact .flow__label {
        font-size: var(--fs-1);
        line-height: 1.3;
    }

    /* the connector meets the centre of the smaller mark */
    .flow--compact .flow__node + .flow__node::before {
        top: 0.9375rem;
    }
}

@media (max-width: 767.98px) {
    .flow {
        --flow-gap: var(--sp-5);
        flex-direction: column;
    }

    .flow__node {
        flex: 1 1 auto;
        width: 100%;
        align-items: flex-start;
        text-align: left;
    }

    /* a column, so a pill sits under its own caption rather than on a shared line */
    .flow__node > .badge-state {
        margin-top: 0;
        max-width: 100%;
    }

    /* the connector turns vertical, from the mark above down to this one */
    .flow__node + .flow__node::before {
        top: calc(-1 * var(--flow-gap));
        left: 1.1875rem;
        right: auto;
        width: 2px;
        height: var(--flow-gap);
    }

    /* plans/007 step 5: in the column layout the thumbnail sits beside its step's
       label, at the top right of the node, and the label and the caption get the
       room back as padding. 7rem is under every one of these files' own widths,
       and the node is full width, so nothing reaches past a 390 px window. */
    .flow--steps .flow__node {
        padding-right: calc(7rem + var(--sp-4));
    }

    .flow--steps .flow__thumb {
        position: absolute;
        top: 0;
        right: 0;
        width: 7rem;
        height: 5rem;
    }

    /* the vertical connector of this layout, not the row's cleared one above */
    .flow--steps .flow__node + .flow__node::before {
        top: calc(-1 * var(--flow-gap));
    }
}

/* --- the panel: a translucent surface with a thin illuminated border -----
   The hero's lifecycle panel (index.html #hero) and the Agentic Workflow page's. */
.panel {
    position: relative;
    padding: var(--sp-5);
    border: var(--border-strong);
    border-radius: var(--radius);
    background-color: rgba(20, 36, 56, 0.72);
    background-image: linear-gradient(160deg, rgba(22, 119, 255, 0.16), rgba(0, 200, 248, 0.05) 55%, transparent);
    box-shadow: var(--shadow);
}

.panel__heading {
    font-size: var(--fs-4);
    margin-top: var(--sp-6);
    margin-bottom: var(--sp-2);
}

/* --- the hero console (templates/components/console.html) ----------------
   plans/006 step 3: the home hero's right column, as the concepts draw it — a window with a
   title bar, a sidebar of the harness's own views and a grid of panels. Everything in it
   is text and CSS; the three status lines the panel of plans/002 carried are the Conformance,
   Human gate and Engine panels now.

   Below 992 px the window lies flat and full width and the sidebar is a single row that scrolls
   inside itself rather than wrapping; from 992 px the window is two columns with the views in a
   left sidebar, and it turns off-axis in perspective, with the stronger glow and a reflection
   beneath it. The panel grid is `auto-fit`, so the half-row column gets two panels side by side and a
   phone gets one, with no width typed per breakpoint. */
.console {
    position: relative;
    border: var(--border-strong);
    border-radius: var(--radius);
    background-color: rgba(11, 23, 38, 0.86);
    background-image: linear-gradient(160deg, rgba(22, 119, 255, 0.18), rgba(0, 200, 248, 0.06) 55%, transparent);
    box-shadow: var(--shadow), var(--glow);
}

.console__bar {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-bottom: var(--border);
    /* inside the console's own hairline, so the bar's fill keeps the window's top corners */
    border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
    background-color: rgba(8, 13, 20, 0.55);
}

.console__dots {
    display: inline-flex;
    gap: var(--sp-1);
}

.console__dots i {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background-color: rgba(243, 247, 252, 0.22);
}

/* the window's name, and the heading that names the console to assistive technology */
.console__name {
    color: var(--text);
    font-family: var(--font-mono);
    font-size: var(--fs-1);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    margin-bottom: 0;
}

.console__nav {
    display: flex;
    /* One line, always: a row that wrapped dropped "Audit trail" to a second line (backlog/008).
       What is too wide for the window scrolls inside the nav; the page itself never does. */
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: thin;
    gap: var(--sp-2);
    list-style: none;
    padding: var(--sp-3) var(--sp-4);
    margin-bottom: 0;
    border-bottom: var(--border);
}

.console__nav li {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    gap: var(--sp-2);
    margin-bottom: 0;
    padding: 0.2rem var(--sp-3);
    border: 1px solid transparent;
    border-radius: 999px;
    color: var(--text-2);
    font-size: var(--fs-1);
    white-space: nowrap;
}

/* Overview is the view the window is on; the word "active" is in the markup for a reader using
   assistive technology, so the fill is not the only thing that says so. */
.console__nav .is-active {
    background-color: rgba(22, 119, 255, 0.18);
    border-color: rgba(22, 119, 255, 0.45);
    color: var(--text);
}

.console__main {
    display: grid;
    /* `min(11rem, 100%)` rather than `11rem`: with the sidebar beside it from 992 px (backlog/008)
       the panel area is narrower than that floor at the smaller desktop widths, and a track wider
       than the area it is in overflows the window. The floor still decides how many panels stand
       side by side wherever it fits, so nothing changes at the widths where it did. */
    grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
    gap: var(--sp-3);
    /* The lifecycle row above carries the window's top inset, so the top here is the grid's own
       gap and the space between the row and the first panel reads as one more gap. */
    padding: var(--sp-3) var(--sp-4) var(--sp-4);
}

.console__panel {
    padding: var(--sp-4);
    border: var(--border);
    border-radius: calc(var(--radius) - 6px);
    background-color: rgba(20, 36, 56, 0.70);
}

/* The lifecycle panel: a row of its own, outside the panel grid (backlog/009). Beside the
   sidebar of backlog/008 the main column left it about 330 px, so its seven compact nodes had
   about 47 px each and "Objective" ran into "Design"; here it has the console's whole inner
   width. The grid's padding is not around it, so it carries the window's inset itself — three
   sides, with the space below it coming from `.console__main`'s own top padding. */
.console__panel--wide {
    margin: var(--sp-4) var(--sp-4) 0;
}

.console__title {
    font-size: var(--fs-1);
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--accent-2);
    margin-bottom: var(--sp-3);
}

.console__line {
    font-size: var(--fs-1);
    line-height: 1.5;
    margin-top: var(--sp-3);
    margin-bottom: 0;
}

.console__rows,
.console__trail {
    list-style: none;
    padding-left: 0;
    margin-bottom: 0;
    display: grid;
    gap: var(--sp-2);
}

.console__rows li,
.console__trail li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin-bottom: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-1);
    color: var(--text-2);
}

/* A conformance row is a grid of two columns, label then pill (backlog/008): on a wrapping flex
   line the longest label, "acceptance criteria", pushed its own pill below itself while the
   other three sat beside theirs. The label takes the column that is left and wraps inside it;
   the pill holds its line and stays on the right. */
.console__rows li {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
}

/* The label takes what is left and wraps inside it, at a space wherever the longest word fits
   and inside the word where the panel is too narrow to hold it — never outside its own column
   and never over the pill. */
.console__rows li > span:first-child {
    overflow-wrap: break-word;
}

.console__rows li .badge-state {
    white-space: nowrap;
}

/* An audit entry's state as a dot. Decorative: the entry's own words are what it says, so the
   dot is hidden from assistive technology and nothing here is carried by colour alone. */
.console__dot {
    width: 0.5rem;
    height: 0.5rem;
    flex: 0 0 auto;
    border-radius: 50%;
    background-color: var(--queued);
}

.console__dot--verified { background-color: var(--ok); }
.console__dot--human { background-color: var(--human); }

@media (min-width: 992px) {
    /* The left sidebar plans/006 step 3 asks for, as concept 1 draws it (backlog/008): the
       window becomes two columns, the title bar across the top of both, the views stacked in a
       column of their own beside the panels. `minmax(0, 1fr)` so the panel grid resolves against
       the column it is in rather than its own contents. */
    .console {
        display: grid;
        grid-template-columns: 10rem minmax(0, 1fr);
        transform: perspective(1800px) rotateY(-10deg) rotateX(3deg);
        transform-origin: left center;
        box-shadow: var(--shadow), var(--glow-strong);
    }

    .console__bar {
        grid-column: 1 / -1;
        grid-row: 1;
    }

    /* The lifecycle panel's own row, directly under the title bar and across both columns, so
       its seven nodes share the console's whole inner width (backlog/009); the sidebar and the
       four remaining panels follow in the row below it. The rows are typed rather than left to
       auto-placement because the panel follows the sidebar in the document, where it keeps the
       single column below this width in the order it has always had. */
    .console__panel--wide {
        grid-column: 1 / -1;
        grid-row: 2;
    }

    .console__main {
        grid-column: 2;
        grid-row: 3;
    }

    .console__nav {
        grid-column: 1;
        grid-row: 3;
        flex-direction: column;
        align-items: stretch;
        gap: var(--sp-1);
        /* nothing to scroll past once the views are stacked */
        overflow: visible;
        padding: var(--sp-3);
        border-bottom: 0;
        border-right: var(--border);
    }

    /* a row of the sidebar rather than a pill of a row: icon and label, the active one lit */
    .console__nav li {
        padding: var(--sp-2) var(--sp-3);
        border-radius: calc(var(--radius) - 6px);
    }

    /* the soft reflection under the window */
    .console::after {
        content: "";
        position: absolute;
        left: 8%;
        right: 8%;
        top: 100%;
        height: var(--sp-6);
        background-image: radial-gradient(60% 100% at 50% 0%, rgba(0, 200, 248, 0.20), transparent 70%);
        pointer-events: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    /* After the query above, so it is this that wins: the turn and the reflection are the
       motion-adjacent half of the treatment, and a reader who asks for less of it gets the
       window flat and square on, keeping the static glow (plans/006 step 1). */
    .console {
        transform: none;
    }

    .console::after {
        display: none;
    }
}

/* --- the page header, with the operator's scene behind it -----------------
   The home hero draws two columns at >=992 px through Bootstrap's grid; every
   other header draws one column of copy. The scene is drawn at full strength
   and the scrim, not the image, is what keeps the copy legible (plans/004). */
.hero {
    position: relative;
    overflow: hidden;
    border: var(--border);
    border-radius: var(--radius);
    background-color: var(--bg-2);
    padding: var(--sp-7) var(--sp-5);
}

.hero__bg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 1;
}

.hero__scrim {
    position: absolute;
    inset: 0;
    background-image: linear-gradient(115deg, var(--bg) 6%, rgba(8, 13, 20, 0.90) 46%, rgba(11, 23, 38, 0.76) 100%);
}

.hero__inner {
    position: relative;
    z-index: 1;
}

/* The full-bleed variant (plans/004 step 1): the header breaks out of
   `main.container` to the width of the window, its own `.container` puts the
   copy back on the grid, and the scrim is left-heavy, so the text column sits
   on the dark side and the right half of the window shows the scene. */
.hero--bleed {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    border: 0;
    border-radius: 0;
    padding-inline: 0;
}

.hero--bleed > .container {
    position: relative;
    z-index: 1;
}

/* Two layers: the left-heavy darkening that carries the contrast, and a fade
   into the page's own background along the bottom edge.

   plans/006 step 4 lightens the first layer — 0.86 across the text column rather than 0.92, and
   0.08 at the right edge rather than 0.20 — so the operator's scene reads as brightly as the
   concepts draw it. The left stop stays the opaque `--bg`, which is what holds the headline and
   the body copy at or above 4.5:1; only the half of the band with no text over it brightens. */
.hero--bleed .hero__scrim {
    background-image:
        linear-gradient(100deg,
            var(--bg) 0%,
            rgba(8, 13, 20, 0.86) 36%,
            rgba(8, 13, 20, 0.40) 66%,
            rgba(8, 13, 20, 0.08) 100%),
        linear-gradient(to bottom, rgba(8, 13, 20, 0) 58%, var(--bg) 100%);
}

.hero--bleed .hero__inner {
    min-height: 38vh;
}

/* The home hero is the tall one (index.html #hero). */
.hero--tall .hero__inner {
    min-height: 70vh;
}

/* The home hero's inner element is also a Bootstrap row, which lays its two
   columns out itself; every other header's is one column of copy, centred in
   the band and held to the width the scrim is darkest across. */
.hero--bleed .hero__inner:not(.row) {
    display: flex;
    flex-direction: column;
    justify-content: center;
    max-width: 46rem;
}

@media (max-width: 991.98px) {
    /* The text column spans the width here, so the scene is darkened evenly
       across it rather than down one side. */
    .hero--bleed .hero__scrim {
        background-image:
            linear-gradient(rgba(8, 13, 20, 0.88), rgba(8, 13, 20, 0.88)),
            linear-gradient(to bottom, rgba(8, 13, 20, 0) 58%, var(--bg) 100%);
    }
}

/* The accent half of a page's one headline (plans/006 step 2): the last words of the `<h1>`,
   wrapped in a span that no word of the operator's copy changes for. The declared `color` is the
   fallback and is a solid --accent-2; the gradient only replaces it where the text clip is
   supported, so a browser without it reads the same words in one accent colour rather than in
   none at all. */
.headline-accent {
    color: var(--accent-2);
    background-image: linear-gradient(100deg, var(--accent-2), var(--accent));
    -webkit-background-clip: text;
    background-clip: text;
}

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
    .headline-accent {
        -webkit-text-fill-color: transparent;
    }
}

.hero__support {
    font-size: var(--fs-4);
    max-width: 60ch;
}

/* the three capability indicators under the hero's buttons */
.indicators {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-5);
    list-style: none;
    padding-left: 0;
    margin-top: var(--sp-5);
    margin-bottom: 0;
}

.indicators li {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    color: var(--text);
    font-size: var(--fs-2);
    font-weight: 600;
    margin-bottom: 0;
}

.indicators i {
    color: var(--ok);
}

/* --- shared page furniture ---------------------------------------------- */
/* the one button row */
.actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
    margin-top: var(--sp-5);
}

/* the sentence that opens a section */
.lead-copy {
    font-size: var(--fs-4);
    max-width: 72ch;
}

/* a sourcing or boundary note: smaller than body copy, never a heading */
.note {
    font-size: var(--fs-2);
    color: var(--text-2);
    max-width: 78ch;
}

/* the challenge section's six items (index.html #challenge) */
.checks {
    list-style: none;
    padding-left: 0;
    margin-bottom: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: var(--sp-3) var(--sp-5);
}

.checks li {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
}

/* The amber tone is the list's own (backlog/011 keeps it while the six glyphs stop repeating).
   One glyph per item means six different glyph widths, so the icon is drawn in a cell of one
   fixed width and the labels still start on one line. */
.checks i {
    color: var(--human);
    margin-top: 0.35rem;
    flex: 0 0 auto;
    width: 1.25em;
    text-align: center;
}

/* the same list where the items are what a client gets, not what goes wrong
   (templates/services/*.html, "What you get") */
.checks--ok i {
    color: var(--ok);
}

/* the card's heading and its one trailing link */
.card h3 {
    font-size: var(--fs-4);
}

/* One theme icon per card of a block list (design/001 Decision 13): a 44 px rounded square
   in the surface colour, the glyph in --accent-2, a thin --accent border so the badge reads
   against the card it sits on. The badge is aria-hidden in the markup: it says nothing the
   card's own heading does not. */
.card__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    margin-bottom: var(--sp-4);
    background-color: var(--surface);
    border: 1px solid var(--accent);
    border-radius: calc(var(--radius) - 6px);
    color: var(--accent-2);
    font-size: var(--fs-4);
}

.card__link {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    font-weight: 600;
    font-size: var(--fs-2);
    margin-top: auto;
}

/* --- the image-led service card (plans/006 step 5) -----------------------
   The strip of scene a service card opens on: a `cover` crop with a gradient over it that ends
   in `--surface`, so the image fades into the card rather than sitting in a box of its own, and
   the icon badge below is lifted to overlap its lower edge. The `<img>` carries its own box and
   `loading="lazy"` in the markup; its `alt` is the one CREDITS.md gives that file.

   plans/007 step 1 drops the fixed 9rem strip height: the operator's images are card-size
   (380 px wide), so the strip's height follows the image's own aspect ratio and the image is
   never stretched to a shape of the stylesheet's choosing. `width: 100%` of a card is at most
   380 px on a 1366 px desktop, so none of them is enlarged either. */
.card__media {
    position: relative;
    display: block;
    border-radius: var(--radius) var(--radius) 0 0;
    overflow: hidden;
    line-height: 0;
}

.card__media img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: auto;
    object-fit: cover;
}

.card__media::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image: linear-gradient(to bottom,
        rgba(8, 13, 20, 0.10) 0%,
        rgba(8, 13, 20, 0.45) 55%,
        var(--surface) 100%);
}

/* the badge half over the strip, half on the card */
.card__media + .card-body .card__icon {
    position: relative;
    margin-top: calc(-1 * (var(--sp-5) + var(--sp-4)));
}

/* Three clauses of that service's own README row, under its sentence: a word each a reader can
   scan before deciding to open the page. */
.card__chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    list-style: none;
    padding-left: 0;
    margin-bottom: var(--sp-4);
}

.chip {
    display: inline-flex;
    align-items: center;
    margin-bottom: 0;
    padding: 0.2rem var(--sp-3);
    border: var(--border-strong);
    border-radius: 999px;
    background-color: rgba(22, 119, 255, 0.10);
    color: var(--text-2);
    font-size: var(--fs-1);
    line-height: 1.5;
}

/* A supplied image inside a card, above the heading (portfolio.html). plans/007 step 2: the
   product images are 380 px wide, so the image is drawn at its own width with the card as the
   ceiling rather than stretched across it, and its height follows its own shape. */
.card__img {
    display: block;
    width: auto;
    max-width: 100%;
    height: auto;
    border: var(--border);
    border-radius: calc(var(--radius) - 6px);
    margin-bottom: var(--sp-4);
}

/* A supplied image placed as section imagery (index.html #experience, contact.html #facts).
   plans/007 steps 3 and 6: a card-size file is drawn at its own width with the column as the
   ceiling, never enlarged to fill it. */
.media-figure {
    margin-bottom: 0;
}

.media-figure img {
    display: block;
    width: auto;
    max-width: 100%;
    height: auto;
    border-radius: var(--radius);
    border: var(--border-strong);
}

/* the company's data as a definition list (about.html #company) */
.facts {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--sp-3) var(--sp-6);
    margin-bottom: 0;
    max-width: 72ch;
}

.facts dt {
    color: var(--text-2);
    font-size: var(--fs-2);
    font-weight: 600;
    letter-spacing: 0.02em;
}

.facts dd {
    color: var(--text);
    margin-bottom: 0;
}

@media (max-width: 575.98px) {
    /* the term above its value, so a long value never squeezes the column */
    .facts {
        grid-template-columns: 1fr;
        gap: var(--sp-1);
    }

    .facts dd {
        margin-bottom: var(--sp-3);
    }
}

/* the two columns of the experience section (index.html #experience)
   backlog/010: the feature image held a column of its own beside the paragraph and both product
   cards, so the narrow column stood almost empty at desktop width. The intro — the image and the
   company's paragraph — is the narrow column; the two cards have the wide one to themselves.
   design/001 Decision 16 took the employment note out of the intro, so the ordering rule that
   drew it past the cards at the stacked width went with it.

   The document order is the intro's two, then the cards: the order the columns are read in.
   Below the breakpoint the intro draws no box of its own (`display: contents`), so its two
   children become items of this flex column beside the cards; from 992 px up it is a column of
   its own again. */
.experience {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
}

.experience__intro {
    display: contents;
}

.experience__intro > .lead-copy {
    margin-bottom: 0;
}

@media (min-width: 992px) {
    /* The columns of the row this section carried before, and its `g-lg-5` gutter: the intro in
       the narrow one, the cards in the wide one, each at the top of its own column. */
    .experience {
        display: grid;
        grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
        column-gap: var(--sp-7);
        align-items: start;
    }

    .experience__intro {
        display: flex;
        flex-direction: column;
        gap: var(--sp-5);
    }
}

/* the industry tiles (index.html #industries) */
.tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: var(--sp-4);
}

.tile {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    height: 100%;
    padding: var(--sp-5);
    background-color: var(--surface);
    border: var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

.tile__img {
    width: 100%;
    max-height: 9rem;
    object-fit: cover;
    border: var(--border);
    border-radius: calc(var(--radius) - 6px);
}

.tile h3 {
    font-size: var(--fs-4);
    margin-bottom: 0;
}

.tile p {
    font-size: var(--fs-2);
    margin-bottom: 0;
}

/* --- the closing band (index.html #cta) ---------------------------------
   The supplied globe under an overlay, with the copy centred over it. */
.band {
    position: relative;
    overflow: hidden;
    border: var(--border);
    border-radius: var(--radius);
    background-color: var(--bg-2);
    padding: var(--sp-7) var(--sp-5);
    text-align: center;
}

.band__bg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0.45;
}

.band__scrim {
    position: absolute;
    inset: 0;
    background-image: radial-gradient(76% 100% at 50% 50%, rgba(8, 13, 20, 0.62), var(--bg) 92%);
}

.band__inner {
    position: relative;
    z-index: 1;
}

.band .lead-copy {
    margin-inline: auto;
}

.band .actions {
    justify-content: center;
}
