/* =============================================================================================
   UbuGive - site stylesheet

   This file is the PRESENTATION layer: colour, type, components. All layout GEOMETRY lives in the
   generated layout.css, which tools/generate-layout.mjs writes from the recovered page model and
   which carries nothing but grid coordinates.

   HOW THE SITE IS BUILT, AND WHY IT LOOKS THIS WAY
   The original ubugive.co.za is a Hostinger/Zyro build. Every section is a "block": a full-bleed
   background with a fixed-height CSS grid centred on it, and every heading, paragraph, photograph
   and button placed at an exact coordinate inside that grid. Reproducing it faithfully means
   reproducing the grid, not approximating the composition with cards and flex rows - the first
   rebuild did the latter, and the result shared the content but none of the design.

   Every colour and every type size below was read out of the original's own page model
   (TipMe_Legacy/TipMeWebsite/data/*.json, key `styles`), not eyedropped from a screenshot.

   PALETTE, as the original declares it
     #0093AF  teal            section headings, primary buttons, links
     #006881  teal dark       button hover, the footer ground
     #35B7C5  teal light      decorative
     #FF6A28  orange          the call-to-action fill and the sub-heading ink
     #FCC046  yellow          decorative
     #F2F2F2  light grey      alternating section ground

   ONE DELIBERATE DEVIATION, and it is the only one: the original clamps its mobile layout to a
   360px column all the way up to a 920px viewport, so a tablet renders a phone-width strip with
   200px of dead margin either side. Here that column may grow to 560px and the grid's spacing
   scales with it, in `cqw`. The proportions of the 360px design are preserved exactly.

   A NOTE ON CONTRAST. White on #FF6A28 measures 2.86:1, under the 4.5:1 WCAG AA asks of a 16-17px
   button label, and #FF6A28 sub-headings on white measure the same. Those are the brand's own
   values and are reproduced as the brand's own values. Everything that is NOT part of that brand
   decision - focus rings, form field borders, validation text - is held above AA here, because
   none of it is visible brand design and all of it is where a low-contrast failure actually locks
   somebody out.
   ============================================================================================= */

:root {
    /* Brand, exactly as the original's page model declares them */
    --c-teal: #0093AF;
    --c-teal-dark: #006881;
    --c-teal-light: #35B7C5;
    --c-orange: #FF6A28;
    --c-yellow: #FCC046;

    /* Neutrals */
    --c-white: #FFFFFF;
    --c-black: #000000;
    /* The original's --color-dark, and the colour text takes when an element sets none. Measured
       on the live site: an unstyled paragraph renders rgb(29, 30, 32), not black. */
    --c-ink: #1D1E20;
    --c-ink-soft: #56585E;
    --c-grey-100: #F2F2F2;
    --c-grey-200: #E0E0E0;
    /* The text-input border, and the only thing telling a low-vision visitor a field is there at
       all. The original's #B8C0CC measures 1.83:1 against the white field, well under the 3:1 that
       WCAG 1.4.11 asks of a control's boundary. This is the nearest value to it that clears the
       bar - 3.09:1 - rather than the much darker #6B7280 an earlier pass used, which cleared it by
       a wide margin and no longer looked like the original's field. */
    --c-border-control: #8A93A0;
    --c-panel: #ECEFF3;
    /* Error ink. Not --c-orange: error text is small and sits on tinted panels, where the brand
       orange measures under 3:1 and stops being readable rather than merely being off-brand. */
    --c-error: #BC3E00;

    /* Type, from the model's `font` and per-tag entries */
    --f-display: 'Poppins', 'Segoe UI', system-ui, -apple-system, sans-serif;
    --f-body: 'Rubik', 'Segoe UI', system-ui, -apple-system, sans-serif;

    --t-h1: 64px;   --t-h1-m: 40px;
    --t-h2: 56px;   --t-h2-m: 36px;
    --t-h3: 48px;   --t-h3-m: 32px;
    --t-h4: 40px;   --t-h4-m: 24px;
    --t-h5: 32px;   --t-h5-m: 24px;
    --t-h6: 24px;   --t-h6-m: 20px;
    --t-body: 16px;
    --t-body-large: 18px;
    --t-body-small: 14px;
    --lh-heading: 1.3em;
    --lh-body: 1.5em;

    /* The design canvases. 1224 is the desktop content width; 360 is the mobile one, and the
       mobile grid's cqw units are proportions of it. */
    --canvas: 1224px;
    --canvas-mobile-max: 560px;

    --header-h: 114px;
    --header-h-m: 69px;

    --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.08);
    --radius: 12px;
}

/* ---------------------------------------------------------------------------------------------
   Reset
   --------------------------------------------------------------------------------------------- */

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
    scroll-padding-top: calc(var(--header-h) + 16px);
}

body {
    margin: 0;
    font-family: var(--f-body);
    font-size: var(--t-body);
    font-weight: 300;
    line-height: var(--lh-body);
    color: var(--c-ink);
    background: var(--c-white);
    -webkit-font-smoothing: antialiased;
    /* The blocks are full-bleed; nothing should ever scroll sideways. */
    overflow-x: hidden;
}

img,
svg,
video {
    display: block;
    max-width: 100%;
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--f-display);
    font-weight: 700;
    line-height: var(--lh-heading);
    margin: 0;
    color: inherit;
    overflow-wrap: break-word;
}

p {
    margin: 0;
    overflow-wrap: break-word;
}

ul, ol {
    margin: 0;
    padding-left: 1.35rem;
}

a {
    color: var(--c-teal-dark);
    text-underline-offset: 0.18em;
}

a:hover {
    color: var(--c-teal);
}

/* A visible focus ring everywhere. Never remove this without replacing it. */
:focus-visible {
    outline: 3px solid var(--c-teal);
    outline-offset: 3px;
    border-radius: 4px;
}

/* outline-offset puts the ring on the BACKDROP, so its contrast is measured against whatever the
   control sits on. #0093AF on the #006881 footer is 1.76:1 - a keyboard user could not see where
   focus was, on every page. White on #006881 is 6.37:1. */
.site-footer :focus-visible,
.blk--on-teal :focus-visible,
.blk--on-orange :focus-visible {
    outline-color: var(--c-white);
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }

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

/* ---------------------------------------------------------------------------------------------
   Block layout engine

   The counterpart to the generated layout.css. Every custom property consumed here is declared
   there, per block and per element:

     --cols / --rows        the desktop grid, on a 1224px canvas
     --sd-rows              the same rows in vw, for the 920-1224px range where the canvas is fluid
     --m-cols / --m-rows    the mobile grid, on a 360px canvas, in cqw
     --min-h / --t-min-h    the block's own height, desktop and mobile
     --gr / --gc            one element's desktop placement
     --m-gr / --m-gc        the same element's mobile placement
     --el-h / --m-el-h      that element's intended height, which images and buttons need
   --------------------------------------------------------------------------------------------- */

.blk {
    position: relative;
    display: grid;
    grid-template-rows: 100%;
    grid-template-columns: 100%;
    width: 100%;
}

/* The background sits behind everything and never receives pointer events. */
.blk__bg {
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    background: var(--blk-bg, transparent);
    pointer-events: none;
}

.blk__bg img {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    /* 101% and not 100%: a fractional layout height leaves a hairline of the ground colour along
       the bottom edge of a cover image at some zoom levels. The original does the same. */
    height: 101%;
    object-fit: cover;
    object-position: center;
}

/* The overlay is BLACK at the block's own opacity, which is how the original darkens a
   photograph. Most blocks declare 0.00 and therefore get nothing: the first rebuild's white
   gradient over every hero was an invention, and it is what made the photographs look washed out.
   Do not reintroduce it. */
.blk__overlay {
    position: absolute;
    inset: 0;
    background-color: #1D1E20;
    opacity: var(--blk-overlay, 0);
}

/* attachment: fixed in the model - the photograph stays put while the block scrolls over it. */
.blk--fixed .blk__bg {
    clip-path: inset(0);
}

.blk--fixed .blk__bg img {
    position: fixed;
    height: 100lvh;
}

/* The container context the mobile grid's cqw units are measured against. It has to be a separate
   element from the grid: container query units inside a container's OWN declarations resolve
   against its nearest ancestor container, not against itself. */
.blk__wrap {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: var(--blk-w, var(--canvas));
    margin-inline: auto;
    container-type: inline-size;
}

.blk__grid {
    display: grid;
    grid-template-columns: var(--cols);
    grid-template-rows: var(--rows);
    min-height: var(--min-h, auto);
    width: 100%;
}

/* 920-1224px: the desktop composition, scaled. Columns are already percentages so they scale on
   their own; the rows switch to the vw set so the vertical rhythm scales with them. */
@media (min-width: 920.02px) and (max-width: 1224px) {
    .blk__grid {
        grid-template-rows: var(--sd-rows);
        min-height: auto;
    }
}

/* Up to 920px: the mobile composition. The canvas may reach 560px rather than the original's
   360px, and every gap is a proportion of that canvas, so the design scales instead of sitting in
   a phone-width strip with dead margins either side. */
@media (max-width: 920px) {
    .blk__wrap {
        max-width: min(100%, var(--canvas-mobile-max));
    }

    .blk__grid {
        grid-template-columns: var(--m-cols);
        grid-template-rows: var(--m-rows);
        /* The original's own mobile height, as a FLOOR rather than a scaled value. It is what
           reserves the space around a composition when the content alone does not fill it; scaling
           it with the canvas would add dead space on a tablet, where the content is already
           shorter because the column is wider. */
        min-height: var(--t-min-h, auto);
        /* 16px of 360 - the original's own mobile gutter, expressed so it scales with the canvas. */
        padding-inline: 4.44cqw;
    }
}

.el {
    position: relative;
    display: grid;
    grid-template-columns: 100%;
    grid-row: var(--gr);
    grid-column: var(--gc);
    z-index: var(--z, 1);
    text-align: var(--txt, left);
    min-width: 0;
}

@media (max-width: 920px) {
    .el {
        grid-row: var(--m-gr);
        grid-column: var(--m-gc);
        text-align: var(--m-txt, var(--txt, left));
    }
}

/* Text starts at the top of its cell; images and buttons stretch to fill it. */
.el > * {
    grid-area: 1 / 1 / -1 / -1;
    align-self: start;
    min-width: 0;
}

/* Non-text content placed at a block coordinate - the contact form is the only one today. It
   stretches, because a form that starts at the top of a 479px cell and stops where its fields
   happen to end leaves the panel behind it half-height. */
.el > .el-content {
    align-self: stretch;
}

/* ---------------------------------------------------------------------------------------------
   Text boxes

   The original's type is set by TAG inside a text box, with per-element overrides for size,
   colour, weight and family. Those overrides were inline style attributes; this site's CSP has no
   'unsafe-inline', so they are the utility classes below - one class per value the original
   actually uses, and no more.
   --------------------------------------------------------------------------------------------- */

/* EVERY BASE RULE HERE IS WRAPPED IN :where(), AND THAT IS THE WHOLE DESIGN.

   `.tb h3` is (0,1,1) and `.fd-24` is (0,1,0), so without this the tag rule outranks the override
   on every element that has one - and the original overrides a size, a colour, a weight or a
   family on most of its text. Measured before the fix: 217 of 301 text runs on the home page
   rendered at the wrong size, all from this one tie. `:where(.tb) h3` is (0,0,1), so any utility
   class wins, which is what a utility is for. */
:where(.tb) h1 { font-size: var(--t-h1); margin-bottom: 48px; }
:where(.tb) h2 { font-size: var(--t-h2); margin-bottom: 32px; }
:where(.tb) h3 { font-size: var(--t-h3); margin-bottom: 32px; }
:where(.tb) h4 { font-size: var(--t-h4); margin-bottom: 32px; }
:where(.tb) h5 { font-size: var(--t-h5); margin-bottom: 16px; }
:where(.tb) h6 { font-size: var(--t-h6); font-weight: 400; margin-bottom: 8px; }

:where(.tb) :where(p, .body) {
    font-family: var(--f-body);
    font-size: var(--t-body);
    font-weight: 300;
    line-height: var(--lh-body);
    margin-bottom: 32px;
}

:where(.tb) :where(.body-large) {
    font-size: var(--t-body-large);
    font-weight: 400;
    margin-bottom: 32px;
}

:where(.tb) :where(.body-small) {
    font-size: var(--t-body-small);
    font-weight: 300;
    margin-bottom: 24px;
}

:where(.tb) :where(ul, ol) {
    margin-bottom: 32px;
}

:where(.tb) li {
    font-family: var(--f-body);
    font-size: var(--t-body);
    font-weight: 300;
    line-height: var(--lh-body);
}

:where(.tb) > :where(*:last-child),
:where(.tb) :where(li:last-child) {
    margin-bottom: 0;
}

@media (max-width: 920px) {
    :where(.tb) h1 { font-size: var(--t-h1-m); }
    :where(.tb) h2 { font-size: var(--t-h2-m); }
    :where(.tb) h3 { font-size: var(--t-h3-m); }
    :where(.tb) h4 { font-size: var(--t-h4-m); }
    :where(.tb) h5 { font-size: var(--t-h5-m); }
    :where(.tb) h6 { font-size: var(--t-h6-m); }
}

/* Size overrides. `fd-` is the desktop size the original set on that element and `fm-` the mobile
   one; a page uses the pair its layout spec quotes for that element, and nothing else.

   Each set lives inside its own media query rather than being reset by the other. Declared
   unscoped, `.fd-58` would still be winning at 360px - the tag rules are (0,0,1) by design, so a
   class beats them at every width - and the hero headline would render at 58px on a phone. */
@media (min-width: 920.02px) {
    .fd-18 { font-size: 18px; }
    .fd-20 { font-size: 20px; }
    .fd-24 { font-size: 24px; }
    .fd-32 { font-size: 32px; }
    /* No element in the recovered model sets 40px explicitly - it is the h4 tag's own size. It is
       here so that demoting an h4 for heading order does not mean losing its size, which is the
       one case on /about-us where a tag had to be kept for want of a utility. */
    .fd-40 { font-size: 40px; }
    .fd-48 { font-size: 48px; }
    .fd-56 { font-size: 56px; }
    .fd-58 { font-size: 58px; }
    .fd-64 { font-size: 64px; }
}

@media (max-width: 920px) {
    .fm-16 { font-size: 16px; }
    .fm-18 { font-size: 18px; }
    .fm-20 { font-size: 20px; }
    .fm-24 { font-size: 24px; }
    .fm-32 { font-size: 32px; }
    .fm-40 { font-size: 40px; }
}

/* Colour overrides. The brand colours get names; the handful of one-off inks the original set on
   individual elements are named by their own hex, because inventing a semantic name for
   "#2B2C32, used six times, meaning nothing in particular" would be a lie about the design. */
.c-teal { color: var(--c-teal); }
.c-teal-dark { color: var(--c-teal-dark); }
.c-orange { color: var(--c-orange); }
.c-yellow { color: var(--c-yellow); }
.c-white { color: var(--c-white); }
.c-black { color: var(--c-black); }
.c-ink { color: var(--c-ink); }
.c-ink-soft { color: var(--c-ink-soft); }
.c-0a1015 { color: #0A1015; }
.c-2b2c32 { color: #2B2C32; }
.c-242424 { color: #242424; }
.c-0d131a { color: #0D131A; }
.c-b31a00 { color: #B31A00; }
.c-176be0 { color: #176BE0; }

.w300 { font-weight: 300; }
.w400 { font-weight: 400; }
.w500 { font-weight: 500; }
.w700 { font-weight: 700; }

.ff-display { font-family: var(--f-display); }
.ff-body { font-family: var(--f-body); }

/* Line height is set PER BREAKPOINT in the original and does not carry between them: an element
   with only a desktop value falls back to its tag's own line-height on mobile - 1.5 for body copy,
   which is noticeably airier than 1.3. Declared unscoped, `.lh-13` held 1.3 at every width and made
   every mobile paragraph on the site about 12% tighter than the original's. So each set lives in
   its own query, exactly like the font sizes above. */
@media (min-width: 920.02px) {
    .lh-101 { line-height: 1.01em; }
    .lh-13 { line-height: 1.3em; }
    .lh-15 { line-height: 1.5em; }
}

@media (max-width: 920px) {
    .lhm-122 { line-height: 1.22em; }
    .lhm-13 { line-height: 1.3em; }
    .lhm-15 { line-height: 1.5em; }
}

/* The per-element spacing overrides the original sets. */
.mb-0 { margin-bottom: 0; }
.mb-8 { margin-bottom: 8px; }
.mb-10 { margin-bottom: 10px; }
.mb-13 { margin-bottom: 13px; }
.mb-16 { margin-bottom: 16px; }
.mb-18 { margin-bottom: 18px; }
.mb-19 { margin-bottom: 19px; }
.mb-20 { margin-bottom: 20px; }
.mb-24 { margin-bottom: 24px; }

/* ---------------------------------------------------------------------------------------------
   Buttons

   Geometry from the model's `grid-button-primary`: 16px/40px padding, Poppins 400 at 16px, pill
   radius, a 0.2s ease colour transition. Each instance's own colours come from layout.css as
   --btn-bg / --btn-bg-hover / --btn-fg, because the original sets them per button: the home hero
   is orange going teal on hover, the header is teal going darker.
   --------------------------------------------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 16px 40px;
    font-family: var(--f-display);
    font-size: var(--btn-fs, 16px);
    font-weight: 400;
    line-height: normal;
    text-align: center;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    color: var(--btn-fg, var(--c-white));
    background-color: var(--btn-bg, var(--c-teal));
    border: var(--btn-border-w, 0) solid var(--btn-border, transparent);
    border-radius: var(--btn-radius, 50px);
    transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
}

.btn:hover,
.btn:focus-visible {
    color: var(--btn-fg-hover, var(--c-white));
    background-color: var(--btn-bg-hover, var(--c-teal-dark));
    border-color: var(--btn-border-hover, transparent);
}

/* Two variants for the pages the recovered design does not cover - /404, the error page, the
   contact acknowledgement. Everything inside a block gets its colours from layout.css instead. */
.btn--orange {
    --btn-bg: var(--c-orange);
    --btn-bg-hover: var(--c-teal);
}

.btn--outline {
    --btn-bg: transparent;
    --btn-fg: var(--c-teal-dark);
    --btn-border-w: 2px;
    --btn-border: var(--c-teal-dark);
    --btn-bg-hover: var(--c-teal-dark);
    --btn-fg-hover: var(--c-white);
    --btn-border-hover: var(--c-teal-dark);
}

/* A button placed in the grid takes the exact height the original drew it at, and sits at the top
   of its cell.

   NOT align-self: stretch. On desktop each element's row range is derived from its own box, so
   stretching happens to give the right answer; on mobile the rows are shared and content-sized, so
   a button in the same row band as a photograph grew to 315px tall on a tablet and 231px on a
   phone. An explicit height is right at every width. */
.el > .btn {
    align-self: start;
    justify-self: stretch;
    width: 100%;
    height: var(--el-h);
}

/* The contact form's submit button. It is a form control rather than a grid button, and the
   original keeps it at 16px on mobile where a grid button drops to 14px. */
.btn--form {
    align-self: flex-start;
}

@media (max-width: 920px) {
    .btn {
        padding: 14px 36px;
        font-size: 14px;
    }

    .btn--form {
        padding: 16px 40px;
        font-size: 16px;
    }

    .el > .btn {
        height: var(--m-el-h);
    }
}

/* ---------------------------------------------------------------------------------------------
   Images inside the grid
   --------------------------------------------------------------------------------------------- */

.el > .el-img {
    align-self: stretch;
    height: 100%;
    max-height: var(--el-h);
    border-radius: var(--el-radius, 0);
    overflow: hidden;
}

.el > .el-img img,
.el > .el-img picture {
    width: 100%;
    height: 100%;
}

.el > .el-img img {
    object-fit: cover;
}

/* On mobile most image rows are `auto`, so a fixed height would collapse the picture to nothing.
   It keeps the original's CROP instead, as an aspect ratio taken from the mobile box the original
   drew - which survives the canvas growing, where a fixed height would not. --m-el-ar is only
   emitted for images; anything without one falls back to its own proportions. */
@media (max-width: 920px) {
    .el > .el-img {
        height: auto;
        max-height: none;
        aspect-ratio: var(--m-el-ar, auto);
    }

    .el > .el-img picture {
        height: 100%;
    }

    .el > .el-img img {
        height: 100%;
    }
}

/* ---------------------------------------------------------------------------------------------
   Site header

   114px tall on desktop and 69px on mobile, exactly as the model declares: white, sticky, a 236px
   logo (143px on mobile) and Rubik 16px navigation.
   --------------------------------------------------------------------------------------------- */

.site-header {
    position: sticky;
    top: 0;
    z-index: 100;
    background: var(--c-white);
}

.site-header__inner {
    display: flex;
    align-items: center;
    gap: 34px;
    width: 100%;
    max-width: 1240px;
    min-height: var(--header-h);
    margin-inline: auto;
    padding: 25px 16px;
}

.site-header__brand {
    flex-shrink: 0;
    display: block;
    line-height: 0;
}

.site-header__brand img {
    width: 236px;
    height: auto;
}

.site-header__toggle {
    display: none;
    width: 44px;
    height: 44px;
    margin-left: auto;
    padding: 0;
    background: transparent;
    border: 0;
    cursor: pointer;
    color: var(--c-black);
}

.site-header__toggle-bars,
.site-header__toggle-bars::before,
.site-header__toggle-bars::after {
    display: block;
    width: 24px;
    height: 2px;
    margin-inline: auto;
    background: currentColor;
    border-radius: 2px;
    transition: transform 0.2s ease, opacity 0.2s ease;
}

.site-header__toggle-bars::before,
.site-header__toggle-bars::after {
    content: '';
    position: relative;
}

.site-header__toggle-bars::before { top: -7px; }
.site-header__toggle-bars::after { top: 5px; }

.site-header__toggle[aria-expanded='true'] .site-header__toggle-bars {
    background: transparent;
}

.site-header__toggle[aria-expanded='true'] .site-header__toggle-bars::before {
    transform: translateY(7px) rotate(45deg);
}

.site-header__toggle[aria-expanded='true'] .site-header__toggle-bars::after {
    transform: translateY(-5px) rotate(-45deg);
}

.site-nav {
    display: flex;
    align-items: center;
    gap: 34px;
    margin-left: auto;
}

.site-nav__list {
    display: flex;
    align-items: center;
    /* The list WRAPS, which is what the original's header grid does too. Without it the six links
       plus the logo plus the call to action are 995px of nowrap content inside a 992px content box
       at a 1024px viewport, and the whole page scrolls sideways. */
    flex-wrap: wrap;
    row-gap: 12px;
    column-gap: 39px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.site-nav__link {
    font-family: var(--f-body);
    font-size: 16px;
    font-weight: 400;
    line-height: var(--lh-body);
    color: var(--c-black);
    text-decoration: none;
    white-space: nowrap;
    padding-block: 4px;
    border-bottom: 2px solid transparent;
    transition: color 0.2s ease, border-color 0.2s ease;
}

.site-nav__link:hover,
.site-nav__link.active {
    color: var(--c-teal);
    border-bottom-color: var(--c-teal);
}

/* Between 992 and 1200 the six links, the logo and the call to action still fit, but only once
   the spacing gives way. Shrinking the gaps here is what keeps the header on one line down to
   992px instead of wrapping into two. */
@media (max-width: 1200px) {
    .site-header__inner { gap: 16px; }
    .site-nav { gap: 20px; }
    .site-nav__list { column-gap: 24px; }
}

/* The navigation needs more room than a 920px viewport has once the logo and the call to action
   are beside it, so the header collapses at 991px rather than at the grid's own breakpoint. */
@media (max-width: 991px) {
    .site-header__inner {
        flex-wrap: wrap;
        gap: 0;
        min-height: var(--header-h-m);
        padding: 15px 16px;
    }

    .site-header__brand img {
        width: 143px;
    }

    .site-header__toggle {
        display: block;
    }

    .site-nav {
        display: none;
        flex-basis: 100%;
        flex-direction: column;
        align-items: stretch;
        gap: 16px;
        margin-left: 0;
        padding-block: 16px;
        border-top: 1px solid var(--c-grey-200);
    }

    .site-nav.is-open {
        display: flex;
    }

    .site-nav__list {
        flex-direction: column;
        align-items: stretch;
        gap: 0;
    }

    .site-nav__link {
        display: block;
        padding: 12px 0;
        border-bottom: 1px solid var(--c-grey-100);
    }

    /* The desktop underline resets to the same hairline every other link gets, so below 992px the
       current page would be signalled by COLOUR ALONE - WCAG 1.4.1, a Level A failure. Weight and
       a leading rule are both non-colour cues. */
    .site-nav__link.active {
        font-weight: 700;
        border-left: 3px solid var(--c-teal);
        border-bottom-color: var(--c-grey-100);
        padding-left: 12px;
    }

    .site-nav__cta {
        align-self: flex-start;
    }
}

/* ---------------------------------------------------------------------------------------------
   Site footer

   The footer is a block like any other and takes its geometry from layout.css: on desktop the
   logo sits left, the tagline centres across the middle and the social icon sits right, with the
   registration line centred underneath. The first rebuild stacked all four centred, which is the
   single most visible difference between the two footers.
   --------------------------------------------------------------------------------------------- */

.site-footer,
.site-footer .tb,
.site-footer a {
    color: var(--c-white);
}

.site-footer__social {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    color: var(--c-white);
    transition: color 0.2s ease;
}

.site-footer__social:hover {
    color: var(--c-black);
}

.site-footer__social svg {
    width: 33px;
    height: 33px;
}

/* ---------------------------------------------------------------------------------------------
   Blocks whose ground is dark

   A block with a teal or orange background needs its text to invert. Which blocks those are is a
   property of the design, so the page marks them; the colour utilities above still win on any
   element that sets its own.
   --------------------------------------------------------------------------------------------- */

.blk--on-teal,
.blk--on-teal .tb,
.blk--on-orange,
.blk--on-orange .tb {
    color: var(--c-white);
}

/* ---------------------------------------------------------------------------------------------
   Entry animations

   The original animates every element into view: 20% down and transparent, rising over 0.65s
   after a 0.1s delay. Reproducing it is the difference between a page that arrives and a page
   that simply appears. site.js sets data-animation-state="active" as each element enters the
   viewport.
   --------------------------------------------------------------------------------------------- */

/* EVERY RULE HERE IS GATED ON .js, AND THAT IS LOAD-BEARING. The hidden state is the start of an
   animation, so it may only exist where something is going to run the animation. js-detect.js is a
   one-line render-blocking script in <head> that adds the class; if it is ever removed, or blocked,
   or fails, the whole site stays painted instead of going blank. A blank page is not an error
   state anyone reports - it just looks like the site is broken. */
.js [data-animation='slide'] {
    opacity: 0;
    transform: translateY(20%);
    transition-property: transform, opacity;
    transition-duration: 0.65s;
    transition-timing-function: ease;
    transition-delay: 0.1s;
}

.js [data-animation='slide'][data-animation-state='active'] {
    opacity: 1;
    transform: translate(0);
}

/* The state site.js falls back to: shown, with no movement. */
.js [data-animation='slide'][data-animation-state='shown'] {
    opacity: 1;
    transform: none;
    transition: none;
}

@media (prefers-reduced-motion: reduce) {
    .js [data-animation='slide'] {
        opacity: 1;
        transform: none;
    }
}

/* ---------------------------------------------------------------------------------------------
   Forms
   --------------------------------------------------------------------------------------------- */

/* Measured off the original's own contact form, not designed: a grid with a 15px gap and NO
   padding or panel of its own, 53px fields at 16px padding and a 10px radius, 14px labels, and a
   96px message box. The first rebuild put it in a 32px padded panel with 24px between fields,
   which made the section 154px taller than the original's. */
.form-panel {
    display: grid;
    gap: 15px;
    padding: 0;
    background: transparent;
}

.field {
    margin-bottom: 0;
}

.field__label {
    display: block;
    margin-bottom: 8px;
    font-family: var(--f-body);
    font-size: var(--t-body-small);
    font-weight: 400;
    color: var(--c-black);
}

.field__note {
    margin: 12px 0 0;
    font-size: var(--t-body-small);
    color: var(--c-ink-soft);
}

.field__control {
    display: block;
    width: 100%;
    padding: 16px;
    font-family: var(--f-body);
    font-size: var(--t-body);
    line-height: 1.3;
    color: var(--c-black);
    background: var(--c-white);
    border: 1px solid var(--c-border-control);
    border-radius: 10px;
}

.field__control:focus {
    border-color: var(--c-teal);
}

textarea.field__control {
    min-height: 96px;
    resize: vertical;
}

.field__control.invalid {
    border-color: var(--c-error);
}

.validation-message,
.field__error {
    display: block;
    margin-top: 8px;
    font-size: var(--t-body-small);
    color: var(--c-error);
}

.validation-errors {
    margin: 0 0 24px;
    padding: 16px 24px;
    list-style: none;
    background: #FDECE4;
    border-left: 4px solid var(--c-error);
    border-radius: var(--radius);
    color: var(--c-error);
}

.form-note {
    padding: 16px 24px;
    margin-bottom: 24px;
    border-radius: var(--radius);
    border-left: 4px solid var(--c-teal);
    background: #E6F4F7;
    color: var(--c-teal-dark);
}

.form-note--error {
    border-left-color: var(--c-error);
    background: #FDECE4;
    color: var(--c-error);
}

/* The honeypot. Off-screen rather than display:none, because some bots skip fields that are
   explicitly hidden - and a honeypot that bots skip catches nothing. */
.hp-field {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* ---------------------------------------------------------------------------------------------
   Utilities
   --------------------------------------------------------------------------------------------- */

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: absolute;
    left: 16px;
    top: -100%;
    z-index: 200;
    padding: 12px 24px;
    background: var(--c-teal-dark);
    color: var(--c-white);
    font-family: var(--f-display);
    font-weight: 600;
    border-radius: 0 0 var(--radius) var(--radius);
    text-decoration: none;
    transition: top 0.15s ease;
}

.skip-link:focus {
    top: 0;
    color: var(--c-white);
}

main:focus {
    outline: none;
}

/* ---------------------------------------------------------------------------------------------
   Status pages

   /404 and the error page are not part of the recovered design - the original had none - so they
   use the brand's own type and colour without a block grid.
   --------------------------------------------------------------------------------------------- */

.status-page {
    display: grid;
    place-items: center;
    text-align: center;
    padding: 96px 16px;
    min-height: 50vh;
}

.status-page__inner {
    max-width: 42rem;
}

.status-page__code {
    font-family: var(--f-display);
    font-size: clamp(4rem, 2rem + 9vw, 8rem);
    font-weight: 700;
    line-height: 1;
    color: var(--c-teal);
    margin: 0 0 16px;
}

.status-page h1 {
    font-size: var(--t-h3-m);
    margin-bottom: 16px;
}

.status-page p {
    margin-bottom: 32px;
    color: var(--c-ink-soft);
}

.status-page__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    justify-content: center;
}
