/* Tuminds — "Blue & Air"
   Single stylesheet, no preprocessor, no build step.

   Brand: deep blue (#0C698C) with a bright blue accent (#14A4DA) and a teal
   third, carried over from tuminds.com — they are Rene's colours, not the old
   site's design. Everything else is deliberately new.

   The old site is round: pill buttons, curved dividers, an asymmetric photo
   mosaic. This is the opposite by intent — flat bands, a strict grid and a lot
   of air — with one exception Rene asked for by name: the buttons are pills,
   as his are. The curved dividers and the mosaic are still gone, which is
   where the difference actually lived. Chris built the current site and shares Rene's
   building; the brief is the same message in a form he will not recognise as
   his. Outfit does the rest: one geometric family across the whole range,
   separated by weight and tracking rather than by mixing families. */

/* ─── Outfit, self-hosted ───────────────────────────────────
   One variable file per subset, 100-900, 47KB for the pair. Self-hosted
   rather than fetched from Google because this site sets no third-party
   cookies and makes no third-party request it does not have to — that is the
   whole privacy story we are selling, and a font CDN would undercut it. */

@font-face {
  font-family: "Outfit";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/outfit-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Outfit";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/outfit-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ─── Tokens ─────────────────────────────────────────────────
   Studio → Design writes an inline :root{} block after this file, so anything
   a client can change must be a custom property here and must equal what
   ships. "Reset to default" is implemented as deleting the option, which only
   works while these two agree. */

:root {
  /* base — near-white with a breath of blue so it sits under the brand.
     These thirteen values are GENERATED by tum_theme_derive() in
     inc/studio-theme.php from four inputs, and pasted here. They have to
     match: the Theme panel emits nothing at all while the settings equal
     their defaults, so if these drifted, the site would visibly shift the
     first time anyone changed a single colour. */
  --c-paper:       #FBFDFE;
  --c-paper-deep:  #E3EEF3;
  --c-ink:         #03171F;   /* deep blue-black, warmer than neutral */
  --c-muted:       #616E74;

  /* brand blue */
  --c-brand:        #0C698C;
  --c-brand-deep:   #0C5976;  /* weighty bands, footer */
  --c-brand-dark:   #083A4D;  /* deepest, gradient anchor */
  --c-brand-bright: #14A4DA;  /* accent, hover, links */
  --c-brand-tint:   #D1EDF8;  /* icon fields, quiet fills */

  /* Rene's blue, sampled from the brand swatch he supplied — #4CA1D5, not a
     shade of it. It is the colour of his own footer and it is the one thing
     about this palette that is not ours to reinterpret.

     Taken from the CSS of his own site, not sampled from a screenshot of it.
     His footer sets background-color:#14a4da and his nav rgba(20,164,218,1),
     which is the same colour fully opaque. Screenshots of it read about eight
     percent lighter and less saturated once the display profile is applied —
     I had #4CA1D5 from one, and the announcement bar shot gave #17A3DA, and
     the favicon #00A5DC. All three are the same blue photographed. Sample the
     stylesheet, never the picture.

     It is also, as it turns out, --c-brand-bright: the palette has had Rene's
     blue in it from the first commit and was simply using the dark end of the
     ramp where he uses the bright end. Darkening #14A4DA by 18% lands on
     #0C6586, which is --c-brand to within a shade.

     Note for anyone tempted to "fix" the contrast by nudging it: white on this
     blue measures 2.86:1, which is under AA for body text and under the 3:1
     for large text too. Dark ink on it is 6.36:1. The band below is built the
     way Rene's own footer is built, white on blue, because that is what was
     asked for and shown; --c-rene-ink is the one-line switch to the compliant
     version, and it changes the text, never the blue. */
  --c-rene:        #14A4DA;   /* the brand blue, exactly */
  --c-rene-deep:   #1188B5;   /* one step down, for edges and hovers */
  /* The circles are a SOLID lighter blue, not translucent white — measured
     off Rene's own footer, where there is exactly one tint level across the
     whole panel and overlaps do not build up. Drawn with alpha they compound
     wherever two meet, and the pale patches that produces are the giveaway
     that it is not the same treatment. #28ACDD is 8.4% white over the panel,
     which is the relationship his footer holds. */
  --c-rene-circle: #28ACDD;
  /* The pale tint his headings use for emphasised words, from
     .featureblock h2 strong{color:#cbf1ff} in his own stylesheet.

     It measures 2.39:1 on his blue, against 2.86:1 for the white beside it —
     so the emphasis is LESS legible than the words around it, not more. That
     cannot be fixed by choosing a better tint: white is the lightest colour
     there is and it only reaches 2.86 on #14A4DA, so nothing lighter clears
     the 3:1 that large text wants. The blue is the ceiling, and the blue is
     not ours to change.

     It is survivable only because of what he tints: "with", a comma and an
     ampersand. Function words. Tint a word that carries meaning and this
     becomes a real problem rather than a documented one. */
  --c-rene-pale:   #CBF1FF;
  --c-rene-ink:    #FFFFFF;   /* switch to var(--c-ink) for 6.43:1 */

  /* teal third */
  --c-teal:        #30AFA8;
  --c-teal-bright: #49B9B2;

  /* Rene's green, and the same rule as Rene's blue: sampled from his own
     stylesheet, not from a screenshot of it. tuminds.com/wp-content/themes/
     tuminds/css/newatf.min.css sets .newslettersignup{background-color:#92cb71}
     — the mailing-list band on his site — and #84B567 is what his .green-btn
     darkens to on hover.

     It earns its place because it is the one colour on his site that is not
     blue: a green band between two blue ones is the only real break in the
     page, which is exactly why he pointed at it.

     WHITE DOES NOT GO ON THIS. His band puts white type on it and measures
     1.91:1, well under any threshold there is; --c-ink measures 9.60:1 and
     --c-brand-dark 6.39:1. Anything placed on green comes from those two. */
  --c-green:      #92CB71;
  --c-green-deep: #84B567;

  --c-line:        rgba(3, 23, 31, 0.10);
  --c-line-strong: rgba(3, 23, 31, 0.18);

  /* type — one family, differentiated by weight and tracking */
  --font-sans: "Outfit", -apple-system, BlinkMacSystemFont, "Segoe UI",
    system-ui, sans-serif;

  --t-display: clamp(42px, 6.2vw, 82px);
  --t-h1:      clamp(32px, 4.4vw, 54px);
  --t-h2:      clamp(26px, 3.2vw, 40px);
  --t-h3:      22px;
  --t-body:    18px;
  --t-small:   15px;
  --t-eyebrow: 12px;

  --lh-tight: 1.06;
  --lh-body:  1.7;
  --ls-tight: -0.03em;   /* Outfit is geometric and wide; display sizes need pulling in */
  --ls-wide:  0.14em;    /* eyebrows only */

  /* space — a step larger than the donor's at the top end. The air is the
     design; take it out and this reads as an ordinary services site. */
  --s-1: 8px;  --s-2: 16px; --s-3: 28px; --s-4: 48px;
  --s-5: 80px; --s-6: 120px; --s-7: 168px;
  --s-flow: 30px;

  /* radius — tight for panels, PILLS for controls.
     This started out tight everywhere, on the argument that the old site is
     pills and curves throughout and this one should not be. Rene asked for the
     buttons back, which is his call to make: the pill is the one piece of the
     old site he recognises as Tuminds rather than as Chris's, and the flat
     bands and strict grid carry the difference on their own. Panels stay
     square-ish; anything you press is a pill. */
  --r-sm: 4px; --r-md: 8px; --r-lg: 14px; --r-pill: 999px;

  /* shadow — blue-tinted and shallow. Flat, not floating. */
  --sh-card: 0 1px 2px rgba(7, 59, 80, 0.05), 0 12px 28px rgba(7, 59, 80, 0.08);
  --sh-lift: 0 2px 6px rgba(7, 59, 80, 0.09), 0 20px 44px rgba(7, 59, 80, 0.16);

  --ease-out:  cubic-bezier(0.2, 0.7, 0.2, 1);
  --ease-wipe: cubic-bezier(0.4, 0, 0.2, 1);

  --container: 1280px;
  --gutter:    40px;

  /* How wide body copy is allowed to run. `none` means the full container, and
     that is a decision, not a default: a prose row holds a page of his — two
     column media blocks, tables, embeds — and those were built to span, so
     capping them is what made /about-us/our-story/ look half-finished beside
     /about-us/ next door.

     The cost is that a plain paragraph at 1200px runs to about 150 characters,
     which is long. If it reads that way on the day, a `ch` value here is the
     whole revert: both rules below are centred already and do nothing with it
     until this stops being `none`. */
  --measure: none;

  /* Header logo height. Studio → Design overrides --logo-h; the header grows
     with it, and 48 + 34 = 82 is below the 92px floor, so the geometry is
     unchanged until someone enlarges the logo past that. */
  --logo-h:   48px;
  --header-h: max(92px, calc(var(--logo-h, 48px) + 34px));

  /* The announcement bar's height, set by JS to its real measured height when
     one is showing and 0 when it is not. Everything that positions against the
     top of the page reads --chrome-h, never --header-h, so adding or removing
     the bar shifts nothing else. Getting this wrong later means re-checking
     every page, which is why it is here before the bar exists. */
  --announce-h: 0px;
  --chrome-h:   calc(var(--header-h) + var(--announce-h));

  /* Stacking order, declared once. Three different things want the bottom
     right corner — the consent card, the back-to-top button and the chat
     launcher — and two more cover the whole screen. Left to per-component
     guesses this becomes a bug nobody can reproduce. */
  --z-totop:   80;
  --z-chat:    85;
  --z-header:  100;
  --z-menu:    120;
  --z-search:  130;
  --z-consent: 200;

  /* Chatling keeps the corner and the back-to-top button moves up, because the
     launcher is 55px at z-index 2147483647 and covers the 46px button
     completely — measured, not guessed. The other way round would mean fighting
     a third-party widget's inline styles for a position visitors expect a chat
     button to be in. Applied only under .has-chat, which consent.js sets when it
     actually promotes the Chatling script, so the button stays in the corner on
     a site where Rene has switched the chatbot off.

     --chat-inset is how far off the bottom edge that whole corner sits, and it
     is 40px rather than the launcher's own 20px for a reason found by looking
     at the home page: the hero's progress bar is 12px tall along the very
     bottom, and the arrows clear it by 28px — until .has-chat pulls them DOWN
     to 20px to line up with the launcher, leaving 8px. Adrian saw the crowding
     before anyone measured it. 40px is not a new number; it is `12px +
     var(--s-3)`, the clearance the hero already has when the chatbot is off,
     and now the two states agree instead of disagreeing by 20px.

     Moving the launcher with them is a nudge inside the corner, not the
     displacement rejected above: that argument was about taking the corner off
     it entirely so a back-to-top button could have it. 20px to 40px leaves it
     exactly where a visitor looks. */
  --chat-inset:  40px;
  --chat-size:   55px;
  --chat-bottom: calc(var(--chat-inset) + var(--chat-size) + 21px);
}

/* Breakpoints used literally, since custom properties cannot go in a media
   query condition:  1100 · 820 · 600 · 420 */

/* ─── Base ───────────────────────────────────────────────── */

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

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

body {
  margin: 0;
  /* --header-h, not --chrome-h. The announcement bar overlays the top of the
     page rather than pushing it down — which is already how it looks over the
     hero — so its arrival and departure must not reflow anything. Reserving
     its height here meant every inner page lifted 46px the moment it
     auto-faded, eight seconds after load, for no reason the reader could see. */
  padding-top: var(--header-h);
  background: var(--c-paper);
  color: var(--c-ink);
  font-family: var(--font-sans);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

a { color: var(--c-brand); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--c-brand-bright); }

h1, h2, h3, h4 {
  margin: 0 0 var(--s-2);
  font-weight: 600;
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  text-wrap: balance;
}
h1 { font-size: var(--t-h1); }
h2 { font-size: var(--t-h2); }
h3 { font-size: var(--t-h3); letter-spacing: -0.015em; }
p  { margin: 0 0 var(--s-flow); }
p:last-child { margin-bottom: 0; }

:focus-visible {
  outline: 3px solid var(--c-brand-bright);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

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

.skip-link {
  position: absolute; left: var(--s-2); top: var(--s-2);
  z-index: calc(var(--z-search) + 1);
  transform: translateY(-200%);
  background: var(--c-ink); color: #fff; padding: 12px 24px;
  border-radius: var(--r-pill); text-decoration: none; font-weight: 600;
}
.skip-link:focus { transform: none; color: #fff; }

/* ─── Layout ─────────────────────────────────────────────── */

.wrap {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* A band. Rows render into these; the modifier picks the colour, and there are
   only three because a client choosing from three cannot make a page that
   disagrees with itself. */
/* Plain .section IS the paper band — dhd_row_section_class() emits no
   --paper modifier, only the two that differ from the default. */
.section { padding-block: var(--s-6); background: var(--c-paper); }
.section--paper-deep { background: var(--c-paper-deep); }
.section--deep {
  background: linear-gradient(160deg, var(--c-brand) 0%, var(--c-brand-deep) 55%, var(--c-brand-dark) 100%);
  color: #fff;
}
.section--deep h1, .section--deep h2, .section--deep h3 { color: #fff; }
.section--deep a { color: #fff; }
.section--deep .eyebrow { color: var(--c-teal-bright); }

/* Consecutive bands of the same colour should read as one, not two. */
.section + .section:not(.section--paper-deep):not(.section--deep),
.section--paper-deep + .section--paper-deep { padding-top: 0; }
/* The two that put padding back exclude flush bands by name. Specificity is
   why: these selectors carry two classes and two :not()s apiece, which the
   doubled .section--flush below cannot reach — it was written believing they
   were one :not() lighter than they are, and lost silently to them. The band
   that would have shown it up did not exist yet. */
.section:not(.section--paper-deep):not(.section--deep):not(.section--flush) + .section--paper-deep,
.section--paper-deep + .section:not(.section--paper-deep):not(.section--deep):not(.section--flush) { padding-top: var(--s-6); }

/* A band whose own content reaches the section's edges: the mailing list on a
   photograph, the statement band, the news ticker. The two rules above that
   would otherwise put the padding back now exclude it by name; the doubled
   class is what beats plain .section. */
.section--flush.section--flush { padding-block: 0; }
/* And the paper section after a flush band keeps its top padding: the band's
   own colour reaches the edge, so the "one band, not two" merge above has
   nothing to merge — without this the specialist areas sat on the statement
   band's bottom edge (Adrian, 6 Sep). Same weight as the merge rule, later. */
.section--flush + .section:not(.section--paper-deep):not(.section--deep):not(.section--flush) { padding-top: var(--s-6); }

.eyebrow {
  margin: 0 0 var(--s-2);
  font-size: var(--t-eyebrow);
  font-weight: 600;
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-brand);
}

.lede { font-size: clamp(19px, 2.1vw, 22px); font-weight: 300; color: var(--c-muted); }
.section--deep .lede { color: rgba(255, 255, 255, 0.86); }

/* The brand mark: the full stop that ends a heading, in accent blue. Carried
   from the wordmark, and the one flourish this design allows itself. */
.cms-brand-dot, .dot { color: var(--c-brand-bright); }

/* ─── Buttons ────────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 15px 32px;
  border: 1px solid transparent;
  /* His, measured: .wp-block-button a{border-radius:30px} on a button about
     54px tall, which is a full pill and not a rounded rectangle. The side
     padding goes up with it — the 28px that looked generous either side of a
     square button looks pinched once the ends are round. */
  border-radius: var(--r-pill);
  background: var(--c-brand);
  color: #fff;
  font: inherit; font-weight: 600; font-size: 16px; line-height: 1;
  text-decoration: none; cursor: pointer;
  transition: background 0.18s var(--ease-out), transform 0.18s var(--ease-out);
}
.btn:hover { background: var(--c-brand-bright); color: #fff; transform: translateY(-1px); }
.btn--ghost { background: transparent; border-color: var(--c-line-strong); color: var(--c-ink); }
.btn--ghost:hover { background: var(--c-brand); border-color: var(--c-brand); color: #fff; }
.section--deep .btn { background: #fff; color: var(--c-brand-deep); }
.section--deep .btn:hover { background: var(--c-teal-bright); color: var(--c-brand-dark); }
.section--deep .btn--ghost { background: transparent; border-color: rgba(255,255,255,0.45); color: #fff; }
/* The renderers pick a button variant from the band a row sits on, so both of
   these arrive from copied code and must exist. --primary is the default
   treatment under another name; --on-dark is the inverse, for deep bands. */
.btn--primary { background: var(--c-brand); color: #fff; }
.btn--primary:hover { background: var(--c-brand-bright); color: #fff; }
.btn--on-dark { background: #fff; color: var(--c-brand-deep); }
.btn--on-dark:hover { background: var(--c-teal-bright); color: var(--c-brand-dark); }
.btn__arrow { transition: transform 0.18s var(--ease-out); }
.btn:hover .btn__arrow { transform: translateX(3px); }

/* ─── Announcement bar ───────────────────────────────────────
   Sits above the header, inside the fixed chrome, so the page below never
   moves when it is dismissed — only --announce-h changes. */

.announce {
  position: relative; overflow: hidden; isolation: isolate;
  display: flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: 46px;
  padding: 10px calc(var(--gutter) + 34px) 10px var(--gutter);
  background: var(--c-brand-bright);
  color: #fff;
  font-size: var(--t-small); font-weight: 600; text-align: center;
  transition: opacity 0.5s var(--ease-out);
}
.announce > a, .announce > span, .announce__close { position: relative; z-index: 1; }
.announce a { color: #fff; text-underline-offset: 4px; }

/* ── The circles, at band scale ────────────────────────────────
   The bar is 46px tall, so the panel-height percentages the footer and the
   menu use would draw circles a few pixels across. These are multiples of the
   bar's height instead — every one of them far taller than the strip, so only
   an arc crosses it and the band reads as a slice cut from the same pattern
   rather than a row of dots.

   The bar is --c-brand-bright, which is the same #14A4DA, so the tint needs no
   adjustment. */
/* .circles.circles--band, not .circles--band: on its own the modifier has the
   same specificity as the generic .circles span:nth-child() rules, which sit
   further down this file and were therefore winning — the bar drew the
   footer's sizes and came out as a row of small dots. Doubling the class makes
   the override win on specificity rather than on where it happens to sit. */
.circles.circles--band span:nth-child(1) { height: 420%; top: -160%; left: 3%; }
.circles.circles--band span:nth-child(2) { height: 300%; top: -90%;  left: 26%; }
.circles.circles--band span:nth-child(3) { height: 520%; top: -230%; left: 52%; }
.circles.circles--band span:nth-child(4) { height: 340%; top: -120%; right: 20%; }
.circles.circles--band span:nth-child(5) { height: 460%; top: -190%; right: -2%; }

@media (max-width: 700px) {
  .circles.circles--band span:nth-child(2),
  .circles.circles--band span:nth-child(4) { display: none; }
}
.announce[hidden] { display: none; }

/* No-JS baseline: reserve the bar's height whenever one is on the page, so the
   fixed chrome does not overlap the content for someone without JavaScript.
   theme.js corrects this to the measured height once it runs, which matters
   when the text wraps to two lines on a phone. */
:root:has(.announce) { --announce-h: 46px; }
.announce-off .announce { display: none; }
.announce-off { --announce-h: 0px; }
.announce--fading { opacity: 0; }
.announce__close {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  display: grid; place-items: center;
  width: 34px; height: 34px; padding: 0;
  border: 0; border-radius: var(--r-sm);
  background: transparent; color: #fff; cursor: pointer;
}
.announce__close:hover { background: rgba(255, 255, 255, 0.18); }

/* ─── Header ─────────────────────────────────────────────── */

.site-header {
  position: fixed; inset: 0 0 auto; z-index: var(--z-header);
  background: var(--c-paper);
  border-bottom: 1px solid var(--c-line);
  transition: background 0.25s var(--ease-out), border-color 0.25s var(--ease-out);
}

/* ── Over the hero ─────────────────────────────────────────────
   On a page that opens with the hero the header sits on the photograph
   rather than above it, and turns solid once you have scrolled past it.
   The hero is pulled up by exactly the chrome height and given it back as
   padding, so nothing else on the page has to know this is happening and
   every other template keeps the body offset it already had. */
.has-hero:not(.is-scrolled) .site-header { background: transparent; border-bottom-color: transparent; }
.has-hero:not(.is-scrolled) .site-header__logo img { filter: brightness(0) invert(1); }
.has-hero:not(.is-scrolled) .icon-btn,
.has-hero:not(.is-scrolled) .social-row--head a { color: #fff; border-color: rgba(255, 255, 255, 0.5); }
.has-hero:not(.is-scrolled) .icon-btn:hover,
.has-hero:not(.is-scrolled) .social-row--head a:hover { background: rgba(255, 255, 255, 0.18); border-color: #fff; }
.has-hero:not(.is-scrolled) .nav-toggle { color: #fff; }

/* ── The tools cluster ─────────────────────────────────────── */
.site-header__tools { display: flex; align-items: center; gap: 10px; }

/* Doubled class throughout: on its own the modifier ties with .social-row and
   .social-row a further down this file, so the header was quietly rendering
   the footer's 40px icons at its 14px spacing rather than the size and spacing
   meant for a header. */
/* Ringed, and the same ring as .icon-btn beside them — same 38px circle, same
   border, same hover — because four icons in a row where one is circled and
   three are not reads as an unfinished job rather than as a distinction. 38px
   with a 16px glyph is not a new size either: it is what .social-row--menu
   already uses in the menu panel, so the three places icons appear now agree.

   The border is the whole change. Two of the three header states had ALREADY
   named a border-colour for these links — the transparent-over-hero rule above
   and, from today, the menu-open rule below — against a border-width that did
   not exist, so the declarations sat there colouring nothing.

   Spacing goes 4px to 10px to match .site-header__tools. Four circles evenly
   spaced read as a set; 4px was right when the three were bare glyphs that
   needed grouping, and with rings on them it leaves them almost touching while
   the search button sits 10px off. */
.social-row.social-row--head { gap: 10px; }
.social-row.social-row--head a {
  width: 38px; height: 38px;
  border: 1px solid var(--c-line-strong);
  color: var(--c-ink);
}
.social-row.social-row--head a:hover { background: var(--c-brand-tint); border-color: var(--c-brand); color: var(--c-brand); }
.social-row--head svg { width: 16px; height: 16px; }

.icon-btn {
  display: grid; place-items: center;
  width: 38px; height: 38px; padding: 0;
  border: 1px solid var(--c-line-strong); border-radius: 50%;
  background: transparent; color: var(--c-ink); cursor: pointer;
  transition: background 0.18s var(--ease-out), border-color 0.18s var(--ease-out), color 0.18s var(--ease-out);
}
/* The tint comes from the social hover next door rather than the other way
   round: over a hero both already fill with rgba(255,255,255,0.18), so the
   white-header state was the only one where the four circles disagreed. */
.icon-btn:hover { background: var(--c-brand-tint); border-color: var(--c-brand); color: var(--c-brand); }

.btn--sm { padding: 10px 18px; font-size: var(--t-small); }

/* Rene's blue, like the logo and the burger beside it. White on this blue is
   2.85:1, so the label is set at 600 and the hover darkens rather than
   lightens; --c-rene-ink in the palette is the switch to dark ink if the
   contrast matters more than the match. */
.site-header__cta { background: var(--c-rene); border-color: var(--c-rene); color: #fff; }
.site-header__cta:hover { background: var(--c-rene-deep); border-color: var(--c-rene-deep); color: #fff; }

/* Below this the cluster would crowd the logo: the burger keeps the rest. */
@media (max-width: 900px) {
  /* .social-row.social-row--head, not .social-row--head: the plain modifier
     ties with .social-row { display: flex } further down this file, and the
     later rule wins — so the three icons never hid and wrapped over the logo
     on a phone. Third time this stylesheet has caught me that way. */
  .social-row.social-row--head, .site-header__cta { display: none; }
}
.site-header__bar {
  display: flex; align-items: center; gap: var(--s-3);
  height: var(--header-h);
}
.site-header__logo { display: block; margin-right: auto; }
.site-header__logo img, .site-header__logo svg { height: var(--logo-h); width: auto; }

/* The burger is always visible: there is one navigation at every width, an
   overlay, rather than a desktop bar plus a separate mobile drawer that drift
   apart. */
/* Three lines, no box. Big and bold in Rene's blue, because on a page this
   quiet at the top the burger is the only way in and it has to catch the eye
   — a thin glyph in a bordered square did not. The bars are 4px on a 30px
   span, which is heavy enough to read at a glance and still fold cleanly into
   an X. */
.nav-toggle {
  display: grid; place-items: center;
  width: 52px; height: 52px; padding: 0;
  border: 0; border-radius: var(--r-md);
  background: transparent; color: var(--c-rene); cursor: pointer;
}
.nav-toggle:hover { color: var(--c-rene-deep); }
.nav-toggle:focus-visible { outline: 3px solid var(--c-rene); outline-offset: 2px; }
/* The mark, not the button.

   The button has always been the biggest thing in the header — 52px against
   38px for the search and the three social icons — and still read as the
   smallest, because size in a row of controls is the size of what is DRAWN.
   Each icon is a filled 38px ring; the burger was 30x22 of line inside its
   own empty box, so the one control that opens everything looked like the
   least important thing up there.

   40x29 with 5px bars: wider than the 38px icons, and heavy enough to hold
   the corner. The button stays 52px, so nothing in the header moves and the
   tap target is unchanged — only the padding around the mark shrinks. */
.nav-toggle__box { position: relative; width: 40px; height: 29px; }
.nav-toggle__box span {
  position: absolute; left: 0; width: 100%; height: 5px; border-radius: 3px;
  background: currentColor;
  transition: transform 0.28s var(--ease-wipe), opacity 0.2s linear;
}
.nav-toggle__box span:nth-child(1) { top: 0; }
.nav-toggle__box span:nth-child(2) { top: 12px; }
.nav-toggle__box span:nth-child(3) { top: 24px; }
/* The header has to outrank the menu while the menu is open.
   The burger animates into an X below, and the X is the only way to close the
   overlay by pointing at it — there is no close button inside the menu. At the
   resting z-index the header sits under the overlay, so on a phone, which has
   no Escape key, a visitor who opened the menu could only get out of it by
   choosing a page. Verified in Chrome on 3 Sep: header 100, menu 120. */
body.nav-open .site-header { z-index: calc(var(--z-menu) + 1); background: transparent; border-bottom-color: transparent; }
body.nav-open .site-header .site-header__logo { filter: brightness(0) invert(1); }
/* The bars draw in currentColor, which is ink — invisible against the overlay. */
/* The header outranks the menu while it is open, which means everything in
   it is sitting on Rene's blue and none of it can keep its on-paper colours.
   The burger already turned white; the icons, the search button and the call
   to action were still dark on blue. */
body.nav-open .nav-toggle { color: #fff; }
/* The three header icons step aside while the menu is open: the menu lists
   every profile under the address, so on Rene's blue they were the same links
   twice on one screen. Doubled class, for the same reason as the 900px rule. */
body.nav-open .social-row.social-row--head { display: none; }
body.nav-open .icon-btn { color: #fff; border-color: rgba(255, 255, 255, 0.5); }
body.nav-open .icon-btn:hover { background: rgba(255, 255, 255, 0.18); border-color: #fff; color: #fff; }
body.nav-open .site-header__cta { background: #fff; border-color: #fff; color: var(--c-brand); }
body.nav-open .site-header__cta:hover { background: rgba(255, 255, 255, 0.88); border-color: transparent; color: var(--c-brand); }
body.nav-open .nav-toggle:hover { color: #fff; }

/* The X. Both bars cross on the box's centre line, so these two offsets are
   the middle bar's `top` and nothing else: 29px tall, 5px bars, centre 14.5,
   and a bar sitting at 0 or 24 is 12px away from it either way. Change the
   box above without changing these and the X comes out lopsided. */
body.nav-open .nav-toggle__box span:nth-child(1) { transform: translateY(12px) rotate(45deg); }
body.nav-open .nav-toggle__box span:nth-child(2) { opacity: 0; }
body.nav-open .nav-toggle__box span:nth-child(3) { transform: translateY(-12px) rotate(-45deg); }

body.nav-open { overflow: hidden; }

/* ─── Nav overlay ─────────────────────────────────────────────
   One navigation at every width, revealed as a full panel rather than a
   drawer. The old site drills three levels deep inside a phone-width menu;
   this is flat, and if it ever cannot be flat the answer is fewer top-level
   items, not an accordion.

   It could not stay flat: /our-services/ has nine children and /about-us/ has
   four, and none of them was reachable from the navigation at all. The promise
   above is kept anyway, because nothing here drills. A parent's children are
   shown BESIDE it, in the half of the overlay that was empty, and on a phone
   they are simply on the page under their parent. There is no toggle to find,
   no state to get stuck in, and no second tap between a visitor and a page. */

.site-menu {
  position: fixed; inset: 0; z-index: var(--z-menu);
  display: grid; align-items: center;
  /* Rene's blue, and his circles behind it. Same furniture as the footer, so
     the two panels a visitor looks at longest agree with each other. */
  position: fixed; overflow: hidden; isolation: isolate;
  /* Below the chrome, not behind it. The header stays visible over the overlay
     and outranks it on z-index, so content laid out from the viewport top ends
     up underneath the logo — which is exactly what happened at 390px, where the
     announcement bar wraps to two lines and the chrome is 163px tall rather
     than the 60px the inner padding assumed. Padding the fixed panel keeps
     align-items: center honest: the menu centres in what is left. */
  padding-top: var(--chrome-h);
  background: var(--c-rene);
  color: var(--c-rene-ink);
  opacity: 0; visibility: hidden;
  transition: opacity 0.32s var(--ease-out), visibility 0s linear 0.32s;
}
body.nav-open .site-menu { opacity: 1; visibility: visible; transition-delay: 0s; }

.site-menu__inner {
  display: grid; gap: var(--s-5);
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  align-items: center;
  padding-block: var(--s-5);
  /* The inner scrolls, not the panel. .site-menu keeps its overflow: hidden so
     the circles behind stay a fixed backdrop rather than scrolling away with
     the text — and without this the overlay simply cut off: stacked at 390px it
     is 1057px of content in an 844px viewport, and everything below the fold,
     the whole Follow us row included, was unreachable. */
  max-height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.site-menu__inner > * { position: relative; z-index: 1; }

/* ── The list ──────────────────────────────────────────────────
   Numbered. An ordered list is what it is — seven ways into the site, in a
   deliberate order — and the numbers give the eye somewhere to start. Drawn
   from a counter rather than typed in, so adding a nav item cannot leave the
   numbering wrong. */
.site-menu__list { margin: 0; padding: 0; list-style: none; counter-reset: navitem; }
/* A grid, not a flex row, and only so that the second level lines up.
   Stacked under its parent the pill row started at the counter — two
   characters and a gutter to the left of the word it belongs to, which read as
   a third column rather than as part of the item. Two named columns put the
   counter in one and everything else in the other, at any type size, without
   guessing how wide "01" is. */
.site-menu__list > li {
  counter-increment: navitem;
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  align-items: baseline; column-gap: 18px; row-gap: 6px;
}
.site-menu__list > li::before {
  content: counter(navitem, decimal-leading-zero);
  flex: none;
  font-size: var(--t-small); font-weight: 600;
  color: rgba(255, 255, 255, 0.65);
  font-variant-numeric: tabular-nums;
}
.site-menu__list > li > a {
  display: inline-block; justify-self: start;
  padding-block: 6px;
  color: #fff; text-decoration: none;
  font-size: clamp(28px, 4.4vw, 52px); font-weight: 500;
  letter-spacing: var(--ls-tight); line-height: 1.18;
  opacity: 0; transform: translateY(14px);
  transition: color 0.18s var(--ease-out);
}
body.nav-open .site-menu__list > li > a {
  animation: menu-in 0.42s var(--ease-out) forwards;
  animation-delay: calc(60ms + var(--i, 0) * 45ms);
}
.site-menu__list > li > a:hover { color: #fff; text-decoration: underline; text-underline-offset: 6px; text-decoration-thickness: 3px; }
@keyframes menu-in { to { opacity: 1; transform: none; } }

/* ── The second level ──────────────────────────────────────────
   Two shapes for one piece of markup, and no script for either.

   Phone and tablet, which is also what any browser gets before the media query
   below applies: the children sit under their parent as a wrapped row of
   pills, always visible. They borrow the search button's treatment — the same
   0.08 white on Rene's blue, the same 999px radius — so the overlay does not
   grow a second visual language, and a pill is a 34px touch target where a
   line of small text is not.

   Desktop: the same list is lifted into the right-hand half of the nav column,
   which was 700px of empty blue on a 1440 screen. One panel shows at a time and
   only while its parent is pointed at or focused; the overlay opens with none
   of them showing. */

.site-menu__sub {
  grid-column: 2;
  margin: 0 0 var(--s-2);
}
.site-menu__sublabel { display: none; }
.site-menu__sublist {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin: 0; padding: 0; list-style: none;
}
.site-menu__sublist a {
  display: inline-block;
  padding: 7px 14px;
  border: 1px solid rgba(255, 255, 255, 0.4); border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  color: #fff; text-decoration: none;
  font-size: var(--t-small); line-height: 1.25;
  transition: background 0.18s var(--ease-out), border-color 0.18s var(--ease-out);
}
.site-menu__sublist a:hover { background: rgba(255, 255, 255, 0.2); border-color: #fff; }

/* The parent says it has more. Only ever decoration — the label is still a
   link to the parent page and still goes there when clicked. */
.site-menu__list > li.has-sub > a::after {
  content: '';
  display: inline-block; vertical-align: middle;
  width: 0.36em; height: 0.36em;
  margin-left: 0.34em;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  /* vertical-align: middle and nothing else. It puts the box's midpoint on the
     baseline plus half the parent's x-height — which is the exact middle of the
     lowercase letters beside it — and rotating a square about its own centre
     does not move that centre, so the chevron's ink is centred there too.

     It used to carry a translateY(-0.12em) on top, which lifted it to the
     middle of the CAP height. That is the textbook place for a mark beside
     mixed-case type, and it is wrong here: both words that have one end in
     lowercase, "About us" and "Services", so the eye compares the chevron with
     the "us" and the "ces" and reads it as floating. No nudge, no magic
     number, and it stays exact at any font size the clamp lands on. */
  transform: rotate(-45deg);
  opacity: 0.55;
  transition: opacity 0.18s var(--ease-out), transform 0.18s var(--ease-out);
}
.site-menu__list > li.has-sub:hover > a::after,
.site-menu__list > li.has-sub:focus-within > a::after {
  opacity: 1; transform: translateX(3px) rotate(-45deg);
}

/* ── The second level, as a drop-down ──────────────────────────
   The other shape, chosen on the Studio's Design screen and carried on the
   overlay as .site-menu--accordion. Below the desktop breakpoint a parent's
   children are folded away; tapping the heading drops them as a list, with
   the parent page itself as the first row (the sublabel, rendered as a link
   in this mode — header.php). Above 1150px this block does nothing and the
   panel beside the list answers to hover, exactly as in the pills mode.

   The fold is a grid row going from 0fr to 1fr, which is the one way to
   animate to an unknown height without measuring it; the inner wrapper is
   what makes that work, since a fr track can only shrink a child that is
   allowed to be shorter than its content. visibility is switched with it so
   nothing folded away can take focus. */
@media (max-width: 1149px) {
  .site-menu--accordion .site-menu__list > li { row-gap: 0; }
  .site-menu--accordion .site-menu__sub {
    display: grid; grid-template-rows: 0fr;
    margin: 0; visibility: hidden;
    transition: grid-template-rows 0.3s var(--ease-out), margin 0.3s var(--ease-out),
                visibility 0s linear 0.3s;
  }
  .site-menu--accordion .site-menu__subinner { min-height: 0; overflow: hidden; }
  .site-menu--accordion .site-menu__list > li.is-open > .site-menu__sub {
    grid-template-rows: 1fr; margin: 2px 0 var(--s-3); visibility: visible;
    transition-delay: 0s;
  }
  .site-menu--accordion .site-menu__sublist { display: block; }
  /* Rows, not pills: the point of this mode is that the list is quiet. The
     parent page leads it in the same dress, a shade bolder. */
  .site-menu--accordion .site-menu__sublabel,
  .site-menu--accordion .site-menu__sublist a {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    margin: 0; padding: 10px 0;
    border: 0; border-bottom: 1px solid rgba(255, 255, 255, 0.22); border-radius: 0;
    background: none;
    color: #fff; text-decoration: none;
    font-size: var(--t-body); font-weight: 400; line-height: 1.3; letter-spacing: 0; text-transform: none;
    transition: none;
  }
  .site-menu--accordion .site-menu__sublabel { font-weight: 600; }
  .site-menu--accordion .site-menu__sublist li:last-child a { border-bottom: 0; }
  .site-menu--accordion .site-menu__sublabel::after,
  .site-menu--accordion .site-menu__sublist a::after { content: '\2192'; flex: none; opacity: 0.55; }
  .site-menu--accordion .site-menu__sublist a:hover { background: none; border-color: rgba(255, 255, 255, 0.22); }
  .site-menu--accordion .site-menu__sublabel:hover span,
  .site-menu--accordion .site-menu__sublist a:hover span { text-decoration: underline; text-underline-offset: 4px; }
  /* The chevron is the control now, so it is bolder, points down while the
     list is folded and up while it is open, and no longer slides right on
     hover — a touch screen's hover sticks, and a mark that had moved for
     the tap would read as a second state. */
  .site-menu--accordion .site-menu__list > li.has-sub > a::after,
  .site-menu--accordion .site-menu__list > li.has-sub:hover > a::after,
  .site-menu--accordion .site-menu__list > li.has-sub:focus-within > a::after {
    opacity: 0.85; transform: translateY(-0.12em) rotate(45deg);
  }
  .site-menu--accordion .site-menu__list > li.has-sub.is-open > a::after {
    transform: translateY(0.1em) rotate(-135deg);
  }
}

/* 1150px, not the 820px the overlay stacks at, and the arithmetic is the
   reason. The nav column has to hold "Latest news" at 52px — about 315px with
   its counter — AND a panel wide enough that a service called "The Power of AI
   and Automation for your business" does not wrap three times. Below 1150 it
   cannot hold both, and the pill row is the better answer there anyway. */
@media (min-width: 1150px) {
  /* 2.2fr, up from 1.6. The aside is an email address, a phone number and four
     lines of postcode; it was holding 437px of a 1440 overlay while the nav
     column had to fit two columns of its own into 699. It gives some back. */
  .site-menu__inner { grid-template-columns: minmax(0, 2.2fr) minmax(0, 1fr); }

  .site-menu__nav { position: relative; }
  /* Reserved, so the largest word in the list can never run under the panel.
     The panel's own width plus its gutter, written once as a variable so the
     two cannot drift apart. */
  .site-menu__list { --subw: min(46%, 400px); padding-right: calc(var(--subw) + var(--s-4)); }

  .site-menu__sub {
    position: absolute; top: 0; right: 0;
    width: var(--subw);
    margin: 0; padding-left: var(--s-4);
    border-left: 1px solid rgba(255, 255, 255, 0.32);
    opacity: 0; visibility: hidden; transform: translateX(12px);
    /* Out fast, in slow, and the asymmetry is the point. A transition is read
       off the state being moved TO, so these durations are the ones a closing
       panel uses and the shown rule below carries the ones an opening panel
       uses. With both at 0.3s, dragging the pointer down the list drew nine
       service names and four About us names over each other for a third of a
       second — two lists of white text on one patch of blue, which looks like a
       fault rather than a transition. The old panel is gone in 120ms; the new
       one still arrives at its own pace. */
    transition: opacity 0.12s var(--ease-out), transform 0.12s var(--ease-out),
                visibility 0s linear 0.12s;
  }
  .site-menu__list > li:hover .site-menu__sub,
  .site-menu__list > li:focus-within .site-menu__sub {
    opacity: 1; visibility: visible; transform: none;
    transition: opacity 0.28s var(--ease-out), transform 0.28s var(--ease-out),
                visibility 0s;
  }

  .site-menu__sublabel {
    display: block; margin: 0 0 var(--s-2);
    font-size: var(--t-eyebrow); font-weight: 600;
    letter-spacing: var(--ls-wide); text-transform: uppercase;
    color: rgba(255, 255, 255, 0.72);
  }
  /* In the drop-down mode the label is a link to the parent page; here it
     wears the same eyebrow dress and merely gains a hover. */
  a.site-menu__sublabel { text-decoration: none; }
  a.site-menu__sublabel:hover { color: #fff; }
  .site-menu__sublist { display: block; }
  /* 16px and a tight row, because nine of these have to end level with six
     items of 52px type. The panel is out of flow — .site-menu keeps
     overflow: hidden — so a panel taller than the list is not a long panel,
     it is a ninth service nobody can reach. */
  .site-menu__sublist a {
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    padding: 11px 0;
    border: 0; border-bottom: 1px solid rgba(255, 255, 255, 0.2); border-radius: 0;
    background: none;
    font-size: 16px; line-height: 1.3;
  }
  .site-menu__sublist li:last-child a { border-bottom: 0; }
  .site-menu__sublist a::after {
    content: '\2192';
    flex: none;
    opacity: 0; transform: translateX(-8px);
    transition: opacity 0.18s var(--ease-out), transform 0.18s var(--ease-out);
  }
  .site-menu__sublist a:hover { background: none; border-color: rgba(255, 255, 255, 0.55); }
  .site-menu__sublist a:hover span { text-decoration: underline; text-underline-offset: 4px; }
  .site-menu__sublist a:hover::after { opacity: 1; transform: none; }
}

/* Short laptops, where the panel and the list are competing for the same
   600-odd pixels. A 1366x768 screen leaves about 620px between the chrome and
   the bottom, and nine rows plus six items of 52px type do not both fit. The
   overlay's inner has scrolled since the 390px layout was built and it would
   scroll here for the same reason — but a row two pixels tighter is worth more
   than a scroll, so the panel gives up what it can before asking for one. */
@media (min-width: 1150px) and (max-height: 880px) {
  .site-menu__sublist a { padding: 8px 0; font-size: 15px; }
  .site-menu__sublabel { margin-bottom: 6px; }
}

.site-menu__aside { border-left: 1px solid rgba(255,255,255,0.32); padding-left: var(--s-4); }
/* One "Book a free call" on screen at a time. Above 900px the header keeps its
   own button while the menu is open, so the panel's copy is the same button
   twice; at 900 and below the header hides its button — the bar is too narrow
   for it — and this is the only one there is, so it stays. */
@media (min-width: 901px) {
  .site-menu__aside > .btn { display: none; }
}
.site-menu__block { margin-top: var(--s-3); }
.site-menu__block p { margin: 0; }
/* .site-menu__block .site-menu__label, not the bare class: .site-menu__block p
   above carries a type selector and so out-specifies it, which meant the 8px
   written here had never once applied and every label in the overlay sat hard
   against the thing it labels. */
.site-menu__block .site-menu__label {
  margin: 0 0 var(--s-2);
  font-size: var(--t-eyebrow); font-weight: 600;
  letter-spacing: var(--ls-wide); text-transform: uppercase;
  color: rgba(255, 255, 255, 0.72);
}
/* The email and the phone number are what this panel is for: someone who opens
   the menu to make contact should meet them before the address, so they lead at
   22px semibold and the address follows a size down. Both were 18px, which gave
   a postcode the same voice as the phone number. */
.site-menu__block--hello p { font-size: 22px; font-weight: 600; }
.site-menu__block--find p { font-size: 19px; }

/* :not(.btn) matters — this selector out-specifies .btn--on-dark, and without
   it the call to action is white text on its own white background. */
.site-menu__aside a:not(.btn) { color: var(--c-rene-ink); text-decoration: none; }
.site-menu__aside a:not(.btn):hover { text-decoration: underline; text-underline-offset: 3px; }
.social-row--menu a { width: 38px; height: 38px; }
.social-row--menu svg { width: 16px; height: 16px; }

@media (max-width: 820px) {
  /* safe center, because the inner is a scroll container: plain centring puts
     the overflow on both edges, and the half above scrollTop 0 cannot be
     scrolled back to. At 390px that hid the search button behind the header
     with no way to reach it. safe falls back to start the moment it overflows.
     Declared twice so a browser without safe alignment keeps the old value. */
  .site-menu__inner { grid-template-columns: 1fr; gap: var(--s-4); align-content: center; }
  .site-menu__inner { align-content: safe center; }
  .site-menu__aside { border-left: 0; border-top: 1px solid rgba(255,255,255,0.32); padding-left: 0; padding-top: var(--s-3); }
}

/* ─── Footer ─────────────────────────────────────────────── */

.site-footer {
  position: relative; isolation: isolate; overflow: hidden;
  background: var(--c-rene);
  color: var(--c-rene-ink);
  padding-block: var(--s-6) var(--s-4);
  /* Body size, not the small step. Set at 15px in pure white on a saturated
     mid-blue it still read as washed out — white on colour loses apparent
     weight, so it wants a little more size and a little more weight than the
     same text would need on paper. */
  font-size: var(--t-body);
}
.site-footer > .wrap { position: relative; z-index: 1; }

/* ── The circles ───────────────────────────────────────────────
   Rene's own footer is a field of big overlapping circles in a lighter tint of
   the same blue, and reusing them here is the point: it is the one piece of
   his existing furniture worth carrying over, and it is what ties this site to
   Digital Help Desk without either of them looking borrowed.

   Flat fills and hard edges, deliberately. The donor does this with blurred
   radial gradients, which reads as a lava lamp; these are circles. Purely
   decorative, so they are emitted with aria-hidden and sit behind everything
   on their own stacking context. */
.circles { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.circles span {
  position: absolute; display: block;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--c-circle, var(--c-rene-circle));
}
/* Sizes and positions in percentages so the arrangement survives any width
   without a media query, and each one is allowed off the edge — a circle that
   is fully visible looks placed, one that is cut off looks like a pattern. */
/* Sized off the panel's own height, not the viewport. In vw they came out
   wider than the footer is tall and merged into one near-solid field — the
   arcs have to stay legible as circles or the pattern is just a lighter panel.
   aspect-ratio keeps them round while height drives the size.

   Most of them run off an edge on purpose. On Rene's footer the tint covers
   close to 40% of the panel and no circle is fully visible, which is what
   makes it read as a pattern the panel is cut from rather than decorations
   placed on it. */
.circles span:nth-child(1) { height: 74%; top: -30%;   left: 1%; }
.circles span:nth-child(2) { height: 44%; top: 34%;    left: 20%; }
.circles span:nth-child(3) { height: 58%; bottom: -34%; left: 9%; }
.circles span:nth-child(4) { height: 36%; top: -14%;   right: 31%; }
.circles span:nth-child(5) { height: 66%; bottom: -30%; right: -3%; }
.circles span:nth-child(6) { height: 40%; top: 28%;    right: 7%; }
.circles span:nth-child(7) { height: 30%; bottom: 12%; left: 45%; }

@media (max-width: 700px) {
  .circles span:nth-child(1) { height: 40%; }
  .circles span:nth-child(2) { height: 28%; }
  .circles span:nth-child(3) { height: 34%; }
  .circles span:nth-child(4), .circles span:nth-child(7) { display: none; }
  .circles span:nth-child(5) { height: 38%; }
  .circles span:nth-child(6) { height: 26%; }
}

.site-footer a { color: #fff; text-decoration: none; font-weight: 500; }
.site-footer a:hover { text-decoration: underline; text-underline-offset: 3px; }
.site-footer h2, .site-footer h3 { color: #fff; }

/* ── Follow us ─────────────────────────────────────────────── */
/* Layout only. The ringed-white treatment belongs to the panels that are on
   Rene's blue, not to every row of icons on the site — scoped here because an
   unscoped .social-row a rule sits later in this file than .social-row--head
   and won at equal specificity, which painted the header's icons white on
   white. */
.social-row { display: flex; flex-wrap: wrap; gap: 14px; list-style: none; margin: 0; padding: 0; }
.social-row a {
  display: grid; place-items: center;
  width: 40px; height: 40px; border-radius: 50%;
  color: currentColor;
  transition: background 0.18s var(--ease-out), border-color 0.18s var(--ease-out), transform 0.18s var(--ease-out);
}
.social-row a:hover { text-decoration: none; }
.social-row svg { width: 17px; height: 17px; }

/* Two rows of four, not a row of seven and a straggler. Eight icons wrapped
   by available width put Threads on a line of its own, which reads as a
   mistake; a fixed four-column grid makes the shape deliberate and keeps the
   four that matter most on the top row. */
.site-footer .social-row,
.social-row--menu { display: grid; grid-template-columns: repeat(4, 40px); gap: 12px; }

/* The other footer columns are text lists, whose line-height gives the label
   room on its own. A grid of bordered circles starts flush against it, so the
   footer row asks for the gap the others get for free.
   Padding, not margin: the label's own 16px bottom margin is adjacent to this
   one, and two sibling margins collapse to the larger of the pair — a margin
   here measured as no change at all. */
/* Doubled class: .social-row { padding: 0 } sits later in this file than the
   .social-row--menu rules up in the menu section, so the plain modifier ties
   and loses. Same trap as .social-row--head. */
.social-row.social-row--menu { padding-top: var(--s-2); }

/* The footer row aligns rather than merely breathes: it shares a slot with the
   address column, and both start at the same y. A line of text does not begin
   at the top of its box though — half the leading sits above the ascenders,
   and the cap top a little below those. 0.5em is that offset at a 1.7 line
   height, so the first circle's top edge meets the top of "The Green House".
   In em, not the 9px it currently resolves to, so it survives the Studio's
   type-scale slider. */
.site-footer .social-row { padding-top: 0.5em; }

.site-footer .social-row a,
.social-row--menu a { border: 1px solid rgba(255, 255, 255, 0.4); }
.site-footer .social-row a:hover,
.social-row--menu a:hover { background: rgba(255, 255, 255, 0.16); border-color: #fff; transform: translateY(-2px); }
.site-footer__cols {
  display: grid; gap: var(--s-4);
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.site-footer__label {
  margin: 0 0 var(--s-2);
  font-size: var(--t-eyebrow); font-weight: 600;
  letter-spacing: var(--ls-wide); text-transform: uppercase;
  /* Teal on the old navy band; on Rene's blue it all but disappeared. */
  /* Was 72% opacity, which is what a label wants on paper and not what it
     wants here. */
  color: #fff;
  font-weight: 700;
}
.site-footer__list { margin: 0; padding: 0; list-style: none; }
.site-footer__list li + li { margin-top: 12px; }
.site-footer__legal {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px var(--s-2);
  margin-top: var(--s-5); padding-top: var(--s-3);
  border-top: 1px solid rgba(255, 255, 255, 0.38);
  color: #fff;
  font-size: var(--t-small);
}
/* The separators belong to the item before them, not to themselves.
   As their own flex items they were free to wrap alone, and at 390px the last
   line of the legal strip began with a stranded "|" before Cookie Policy. Drawn
   as a trailing ::after they sit inside the previous item's box, so a break can
   only happen after the bar — which is where a reader expects it.
   The / '' gives the pseudo-element empty alternative text, replacing the
   aria-hidden the separator spans used to carry. */
.site-footer__legal > *:not(:last-child)::after {
  content: '|' / '';
  margin-left: var(--s-2);
  color: rgba(255, 255, 255, 0.55);
}
/* A button, because it opens a panel rather than going anywhere — and it has
   to be indistinguishable from the links either side of it, including the
   inherited separator. */
.site-footer__cookies {
  padding: 0; border: 0; background: none;
  color: inherit; font: inherit; font-weight: 500;
  cursor: pointer;
}
.site-footer__cookies:hover { text-decoration: underline; text-underline-offset: 3px; }

.site-footer__badges { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; margin-top: var(--s-3); }
.site-footer__badges img { max-height: 62px; width: auto; }

/* ─── Back to top ────────────────────────────────────────────
   Deliberately below the menu overlay and clear of the consent card. Delayed
   visibility so it leaves the tab order entirely when hidden rather than
   sitting there invisible and focusable. */

.to-top {
  position: fixed; right: 24px; bottom: 24px; z-index: var(--z-totop);
  display: grid; place-items: center;
  width: 46px; height: 46px; padding: 0;
  border: 1px solid var(--c-line-strong); border-radius: var(--r-pill);
  background: var(--c-paper); color: var(--c-ink);
  cursor: pointer;
  opacity: 0; visibility: hidden;
  transition: opacity 0.2s var(--ease-out), visibility 0s linear 0.2s,
              background 0.18s var(--ease-out);
}
.has-chat .to-top { bottom: var(--chat-bottom); }

/* The launcher, lifted off the hero's progress bar with the arrows beside it.

   #chtl-chat-icon-container is Chatling's own element and its own id, carrying
   `position: fixed; z-index: 2147483647; bottom: 20px; right: 20px` inline —
   which is why this needs !important and why it is keyed on the id rather than
   on anything structural. It moves in one axis only: the corner is still the
   corner, and `right` is untouched. See --chat-inset.

   The window it opens has to come with it. Chatling positions #chtl-chat-iframe
   from the same corner at `bottom: 95px` inline, which is its own 20 + 55 + 20 —
   the button's inset, the button, and a 20px gap. Move the button and leave
   that, and the window opens sitting flat on the button instead of above it. */
#chtl-chat-icon-container { bottom: var(--chat-inset) !important; }
#chtl-chat-iframe { bottom: calc(var(--chat-inset) + var(--chat-size) + 20px) !important; }

/* And back to Chatling's own inset on a viewport too short to lift the window.
   It is `height: 80vh; max-height: 700px`, so the whole stack fits only while
   inset + 55 + 20 + 0.8h <= h — that is h >= 575px at a 40px inset, and
   h >= 475px at Chatling's 20px. Below 580px, which in practice means a phone
   held sideways, the corner goes back where the widget put it and the hero's
   arrows go with it: a chat window clipped off the top of the screen is a worse
   fault than 8px of daylight above a progress bar. */
@media (max-height: 580px) { :root { --chat-inset: 20px; } }

/* And nothing floats on top of a layer that has taken the whole screen.
   Three of ours do — the consent sheet, the menu and the search overlay — and
   Chatling's launcher is fixed to the corner at z-index 2147483647, so it sat
   over all three. On a phone that is not merely untidy: below 900px the consent
   card stops being a centred dialog and becomes a sheet across the foot of the
   screen, which puts its answers in the launcher's corner. Measured at 390px,
   the launcher covered "Accept all" by 47x9px, in both the banner and the
   preferences view. A widget obscuring the button someone has to press to
   answer a consent prompt is the one overlap here that is not cosmetic.

   This does not take the corner off it — see --chat-bottom for why that
   argument is settled. It takes the launcher out while something modal is open,
   and the class going away brings it back. You cannot reach it in that state
   anyway: consent traps focus, and the other two cover the page.

   By id, because that is the only handle the widget offers, and consent.js
   already keys on #chtl-script for the same reason — if Chatling renames it
   this stops working silently. !important because the widget writes its own
   position inline and may write display too, and an inline style beats a rule. */
body.consent-open #chtl-chat-icon-container,
body.nav-open     #chtl-chat-icon-container,
body.search-open  #chtl-chat-icon-container { display: none !important; }
.to-top:hover { background: var(--c-brand); border-color: var(--c-brand); color: #fff; }
.to-top.is-show { opacity: 1; visibility: visible; transition-delay: 0s; }

@media print { .to-top, .announce, .nav-toggle { display: none !important; } }

/* ─── Narrower ───────────────────────────────────────────── */

@media (max-width: 1100px) {
  :root { --gutter: 28px; --s-6: 96px; --s-7: 128px; }
}
@media (max-width: 820px) {
  :root { --gutter: 22px; --s-5: 60px; --s-6: 76px; --s-7: 96px; }
  .site-header__logo img, .site-header__logo svg { height: var(--logo-h-mobile, 40px); }
}
@media (max-width: 600px) {
  :root { --t-body: 17px; --gutter: 18px; --s-6: 64px; }
  .announce { text-align: left; padding-right: 52px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ═══ Rows ═══════════════════════════════════════════════════
   The nine row types the engine renders. These class names are a contract:
   rows-render.php is copied verbatim from the donor and emits exactly these,
   so the names are not ours to change — only what they look like.

   And that is the point. The donor's rows are round, purple and serif; the old
   tuminds.com's are curved and photo-heavy. Same markup, wholly different
   result, which is what "keep the brand, rebuild the layout" means in
   practice. */

/* Rows use .container where the chrome uses .wrap. Same thing. */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* The donor draws a curved wave between bands. This design does not curve —
   the band edge is the line. The renderers still call dhd_row_wave() because
   they are verbatim copies, so the markup arrives and is hidden here rather
   than by editing a file that has to stay identical. */
.wave { display: none; }

/* ── Row heads ─────────────────────────────────────────────── */

.section__head { max-width: 60ch; margin-bottom: var(--s-4); }
.section__head--center { margin-inline: auto; text-align: center; }
.section__title { margin: 0 0 var(--s-2); font-size: var(--t-h2); }
.section__intro { margin: 0; font-size: clamp(18px, 1.9vw, 21px); font-weight: 300; color: var(--c-muted); }
.section--deep .section__intro { color: rgba(255, 255, 255, 0.84); }

.rule {
  width: 56px; height: 3px; margin: var(--s-3) 0 0;
  border: 0; border-radius: 2px;
  background: var(--c-brand-bright);
}
.section__head--center .rule { margin-inline: auto; }

.link-arrow {
  display: inline-flex; align-items: center; gap: 8px;
  margin-top: var(--s-3);
  font-weight: 600; text-decoration: none;
}
.link-arrow:hover .btn__arrow { transform: translateX(3px); }

/* ── Split: words and a photo ──────────────────────────────── */

.split {
  display: grid; gap: var(--s-5);
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
}
/* Source order is always content-then-media so a phone reads the words first;
   which side the photo lands on is this, not the markup. */
.split--reverse .split__media { order: -1; }
.split--founder { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); }
.split__content { min-width: 0; }
.split__title { font-size: var(--t-h2); }
.split__body { font-size: clamp(17px, 1.7vw, 19px); }
.split__media img { width: 100%; border-radius: var(--r-lg); box-shadow: var(--sh-card); }
.split__media--portrait img { aspect-ratio: 4 / 5; object-fit: cover; }

.split__list { margin: var(--s-3) 0 0; padding: 0; list-style: none; }
.split__list li {
  position: relative;
  padding-left: 30px; margin-bottom: 12px;
  font-size: var(--t-small); font-weight: 500;
}
.split__list li::before {
  content: ""; position: absolute; left: 0; top: 7px;
  width: 16px; height: 9px;
  border-left: 2.5px solid var(--c-teal); border-bottom: 2.5px solid var(--c-teal);
  transform: rotate(-45deg);
}
.section--deep .split__list li::before { border-color: var(--c-teal-bright); }

/* ── Banner: a wide photo with words beneath ───────────────── */

.rowbanner__media img {
  width: 100%; aspect-ratio: 21 / 9; object-fit: cover;
  border-radius: var(--r-lg);
}
.rowbanner__body { max-width: 72ch; margin-top: var(--s-4); }

/* ── Prose ─────────────────────────────────────────────────── */

.prose { max-width: var(--measure); margin-inline: auto; font-size: clamp(17px, 1.7vw, 19px); }
.prose--cols { max-width: none; columns: 2; column-gap: var(--s-5); }
.prose--cols p { break-inside: avoid; }

/* ── Steps ─────────────────────────────────────────────────── */

.steps { display: grid; gap: var(--s-4); grid-template-columns: repeat(3, minmax(0, 1fr)); }
.step { padding-top: var(--s-3); border-top: 2px solid var(--c-brand-bright); }
.step__n {
  display: block; margin-bottom: var(--s-2);
  font-size: var(--t-eyebrow); font-weight: 700;
  letter-spacing: var(--ls-wide); text-transform: uppercase;
  color: var(--c-brand-bright);
}
.step__title { margin: 0 0 10px; font-size: var(--t-h3); }
.step__body { margin: 0; color: var(--c-muted); font-size: var(--t-small); }
.section--deep .step__body { color: rgba(255, 255, 255, 0.8); }

/* ── Cards (rendered as .pillar) ───────────────────────────── */

.pillars { display: grid; gap: var(--s-3); grid-template-columns: repeat(3, minmax(0, 1fr)); }
.pillar {
  padding: var(--s-4);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  background: var(--c-paper);
  transition: border-color 0.2s var(--ease-out), transform 0.2s var(--ease-out);
}
.pillar:hover { border-color: var(--c-brand-bright); transform: translateY(-2px); }
.section--deep .pillar { background: rgba(255, 255, 255, 0.06); border-color: rgba(255, 255, 255, 0.16); }
.pillar__icon {
  display: grid; place-items: center;
  width: 44px; height: 44px; margin-bottom: var(--s-3);
  border-radius: var(--r-md);
  background: var(--c-brand-tint); color: var(--c-brand);
}
.section--deep .pillar__icon { background: rgba(255, 255, 255, 0.12); color: #fff; }
.pillar__icon svg { width: 22px; height: 22px; }
.pillar__title { margin: 0 0 10px; font-size: var(--t-h3); }
.pillar__body { margin: 0; color: var(--c-muted); font-size: var(--t-small); }
.section--deep .pillar__body { color: rgba(255, 255, 255, 0.8); }

/* ── Stats ─────────────────────────────────────────────────── */

.stats { display: grid; gap: var(--s-4); grid-template-columns: repeat(3, minmax(0, 1fr)); text-align: center; }
.stat { min-width: 0; }
.stat__n {
  display: block;
  font-size: clamp(44px, 6vw, 76px); font-weight: 600;
  line-height: 1; letter-spacing: -0.04em;
  color: var(--c-brand);
}
.stats--light .stat__n, .section--deep .stat__n { color: #fff; }
.stat__n .dot, .stat__n .cms-brand-dot { color: var(--c-teal); }
.section--deep .stat__n .dot, .section--deep .stat__n .cms-brand-dot { color: var(--c-teal-bright); }
.stat__label {
  display: block; margin-top: var(--s-2);
  font-size: var(--t-small); font-weight: 500; color: var(--c-muted);
}
.section--deep .stat__label { color: rgba(255, 255, 255, 0.82); }

/* ── FAQ ───────────────────────────────────────────────────── */

.faq { max-width: 78ch; }
.faq__item { border-bottom: 1px solid var(--c-line); }
.section--deep .faq__item { border-color: rgba(255, 255, 255, 0.2); }
.faq__item summary {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-3) 0;
  cursor: pointer; list-style: none;
  font-size: var(--t-h3); font-weight: 500;
}
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary::after {
  content: ""; flex: none;
  width: 11px; height: 11px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-3px, -3px);
  transition: transform 0.2s var(--ease-out);
}
.faq__item[open] summary::after { transform: rotate(-135deg) translate(-3px, -3px); }
.faq__body { padding-bottom: var(--s-3); color: var(--c-muted); }
.section--deep .faq__body { color: rgba(255, 255, 255, 0.82); }

/* ── Call to action ────────────────────────────────────────── */

.cta-band { max-width: 62ch; margin-inline: auto; text-align: center; }
.cta-band__title { margin: 0 0 var(--s-2); font-size: var(--t-h2); }
.cta-band__intro { margin: 0 0 var(--s-4); font-size: clamp(18px, 1.9vw, 21px); font-weight: 300; color: var(--c-muted); }
/* .cta-band--light is emitted by the NOT-deep branch of dhd_row_cta() — it
   marks a light BAND, not light type — so pairing it with white text made the
   supporting line invisible on every CTA that was not on a deep band, the
   homepage's included. The deep bands are the only ones that want white. */
.section--deep .cta-band__intro { color: rgba(255, 255, 255, 0.86); }

/* ── Form row ──────────────────────────────────────────────── */

.contact { display: grid; gap: var(--s-5); grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); align-items: start; }
.contact__form { min-width: 0; }
.contact__aside {
  padding: var(--s-4);
  border-radius: var(--r-lg);
  background: var(--c-paper-deep);
}
.section--deep .contact__aside { background: rgba(255, 255, 255, 0.08); }
.contact__list { margin: var(--s-2) 0 0; padding-left: 20px; }
.contact__list li { margin-bottom: 10px; }
.form__note { margin-top: var(--s-3); font-size: var(--t-small); color: var(--c-muted); }
.section--deep .form__note { color: rgba(255, 255, 255, 0.78); }

/* An enquiry row with no side panel. The form takes the whole container and
   splits itself in two, so the long answer stands beside the short ones
   instead of a screen and a half below them. */
.contact--full { grid-template-columns: minmax(0, 1fr); }
.enquiry__intro { max-width: 62ch; color: var(--c-muted); }
.enquiry__intro p + p { margin-top: var(--s-2); }
.section--deep .enquiry__intro { color: rgba(255, 255, 255, 0.84); }
.enquiry__link { margin: var(--s-2) 0 var(--s-4); font-weight: 600; }
.enquiry__link a { color: var(--c-brand); text-decoration: underline; text-underline-offset: 3px; }
.section--deep .enquiry__link a { color: #fff; }

@media (min-width: 900px) {
  /* Placed by storage key, which is the one thing about a field that is
     guaranteed not to change — the Forms panel mints it once and freezes it.

     row-gap is 0 deliberately: the form spaces itself with margins, so the
     empty rows the long answer spans past cost nothing, and the span can be
     generous enough to survive Rene adding a question. */
  .contact--full .form--dhd {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--s-5);
    row-gap: 0;
  }
  .contact--full .form--dhd > * { grid-column: 1; }
  /* The field is not the form's child — every block is wrapped in a .form__row
     on the way out — so the row carrying it is what moves. */
  .contact--full .form--dhd > .form__row:has(> [data-dhd-key="details"]) {
    grid-column: 2; grid-row: 1 / span 40;
    align-self: stretch;
    display: flex; flex-direction: column;
  }
  .contact--full .form--dhd > .form__row:has(> [data-dhd-key="details"]) > .form__field { display: flex; flex-direction: column; flex: 1; }
  .contact--full .form--dhd > .form__row:has(> [data-dhd-key="details"]) .form__control { flex: 1; }
}

/* ── A form on a photograph ────────────────────────────────────
   The band a form row wears when it is given a photo. The same tint as
   .pagehero--image, because it is the same idea a second time: one of Rene's
   own photographs, darkened enough on the left for white type, and lighter on
   the right where the reassurance panel sits.

   Heavier than the page headers, though. A header carries three lines of big
   type; this carries labels, hints and small print, and every one of them has
   to read over whatever the picture is doing underneath. */
.photoband { position: relative; }
.photoband__media { position: absolute; inset: 0; z-index: 0; }
.photoband__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photoband::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(100deg, rgba(8, 58, 77, 0.93) 0%, rgba(8, 58, 77, 0.88) 46%, rgba(8, 58, 77, 0.78) 100%);
}
/* The deep band's own gradient would cover the photograph completely. */
.photoband > .section { position: relative; z-index: 2; background: none; }
/* The wrapper breaks the .section + .section adjacency the padding rules near
   the top of this file depend on, and the row after a photo band would open a
   gap those rules close everywhere else. */
.photoband + .section:not(.section--paper-deep):not(.section--deep) { padding-top: 0; }

@media (max-width: 700px) {
  /* No left-to-right story at this width — the form is the full column and the
     panel is under it, so the tint is even. */
  .photoband::after { background: linear-gradient(180deg, rgba(8, 58, 77, 0.90) 0%, rgba(8, 58, 77, 0.93) 100%); }
}

/* ── Scroll effects ────────────────────────────────────────────
   Opt-in per row from the Studio. Nothing moves unless the row asked for it,
   and nothing moves at all for anyone who has asked for reduced motion — the
   fx-on class is only added by JS after that check. */

.fx-on .fx-reveal { opacity: 0; transition: opacity 0.6s var(--ease-out), transform 0.6s var(--ease-out); }
.fx-on .fx--rise    { transform: translateY(28px); }
.fx-on .fx--zoom    { transform: scale(0.97); }
.fx-on .fx--stagger { transform: translateY(20px); }
.fx-on .fx-reveal.is-in { opacity: 1; transform: none; }
.fx-on .fx--stagger.is-in > .container > * { animation: fx-stagger 0.5s var(--ease-out) backwards; }
.fx-on .fx--stagger.is-in > .container > *:nth-child(2) { animation-delay: 0.08s; }
.fx-on .fx--stagger.is-in > .container > *:nth-child(3) { animation-delay: 0.16s; }
@keyframes fx-stagger { from { opacity: 0; transform: translateY(16px); } }

/* The stacking deck: a row slides up over the one above, which pins. */
.fx-on .fx-pin { position: sticky; top: var(--chrome-h); }
.fx-on .fx-cover { position: relative; z-index: 2; box-shadow: 0 -18px 40px rgba(7, 59, 80, 0.14); }

/* ── Rows, narrower ────────────────────────────────────────── */

@media (max-width: 1100px) {
  .steps, .pillars, .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 820px) {
  .split, .contact { grid-template-columns: 1fr; gap: var(--s-4); }
  .split--reverse .split__media, .split--founder .split__media { order: 0; }
  .split--founder { grid-template-columns: 1fr; }
  .prose--cols { columns: 1; }
  .rowbanner__media img { aspect-ratio: 16 / 9; }
}
@media (max-width: 600px) {
  .steps, .pillars, .stats { grid-template-columns: 1fr; }
  .stats { gap: var(--s-3); }
}

/* ═══ Tuminds' own row types ═════════════════════════════════ */

/* ── Trusted brands ────────────────────────────────────────── */

.brands__track {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: var(--s-4) var(--s-5);
}
.brand { display: block; flex: none; }
.brand__img {
  /* Sized by height times a per-logo optical factor, so a wide wordmark and a
     tall crest carry the same visual weight. The factor is worked out from the
     mark's own proportions in tum_brand_optical_k(), which explains the sums;
     the base stays here so the wall still answers to the breakpoints. */
  height: calc(var(--brand-h, 36px) * var(--brand-k, 1));
  width: auto;
  max-width: min(190px, 40vw);
  object-fit: contain;
  /* Client marks arrive in a dozen different colours and would otherwise turn
     a considered strip into a fruit salad. Greyed back by default, true colour
     on hover — and full colour for anyone who has asked for more contrast. */
  filter: grayscale(1);
  opacity: 0.62;
  transition: filter 0.2s var(--ease-out), opacity 0.2s var(--ease-out);
}
a.brand:hover .brand__img, .brand__img:hover { filter: none; opacity: 1; }
.section--deep .brand__img { filter: grayscale(1) brightness(0) invert(1); opacity: 0.8; }
@media (prefers-contrast: more) { .brand__img { filter: none; opacity: 1; } }

/* The marquee is a single track duplicated in CSS rather than in markup, so
   assistive technology reads each logo once. */
.brands--marquee { overflow: hidden; -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.brands--marquee .brands__track { flex-wrap: nowrap; justify-content: flex-start; animation: brands-scroll 38s linear infinite; }
.brands--marquee:hover .brands__track { animation-play-state: paused; }
@keyframes brands-scroll { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
  .brands--marquee .brands__track { animation: none; flex-wrap: wrap; justify-content: center; }
}

.trustpilot { margin-top: var(--s-3); text-align: center; }

/* ── News ticker ───────────────────────────────────────────── */

/* The strip under the brands. His colours are already ours: he paints it
   #0c5976 and #0c698c, which are --c-brand-deep and --c-brand to the letter,
   so this lands on his design without borrowing a value.

   The outer gradient splits at the halfway mark so that beyond the container
   the left of the window reads as the label's colour and the right as the
   strip's — his trick, and the reason the label looks like it runs off the
   edge of the screen rather than starting in the middle of nowhere. The label
   then pulls left by exactly one gutter so the two meet with no seam; his
   leaves a 20px sliver of the wrong colour there.

   White on the strip is 6.15:1 and on the label 7.76:1. The hover tint is
   --c-rene-pale at 5.14:1 — --c-teal-bright, which the rest of the dark bands
   use, is only 2.60:1 here and would have failed. */
.ticker {
  background: linear-gradient(90deg, var(--c-brand-deep) 0 50%, var(--c-brand) 50% 100%);
  color: #fff;
}
.ticker__inner {
  position: relative;
  width: 100%; max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
  background: var(--c-brand);
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  align-items: center; gap: var(--s-3);
  min-height: 56px;
}
.ticker__label {
  margin: 0 0 0 calc(var(--gutter) * -1);
  padding: 12px var(--s-3) 12px var(--gutter);
  background: var(--c-brand-deep);
  border-radius: 0 var(--r-pill) var(--r-pill) 0;
  font-weight: 600; font-size: 17px; line-height: 1.2;
  white-space: nowrap;
}

/* One headline at a time, stacked in place. The inactive ones are hidden with
   visibility rather than opacity alone, which is also what keeps four links a
   visitor cannot see out of the tab order. */
.ticker__list { position: relative; min-height: 56px; }
.ticker__item {
  position: absolute; inset: 0; margin: 0;
  display: flex; align-items: center;
  padding-right: 108px;
  visibility: hidden; opacity: 0;
  transition: opacity 0.3s var(--ease-out);
}
.ticker__item.is-on { visibility: visible; opacity: 1; }
.ticker__item a {
  color: #fff; text-decoration: none;
  font-size: 17px; line-height: 1.3;
  max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ticker__item a:hover, .ticker__item a:focus-visible {
  color: var(--c-rene-pale);
  text-decoration: underline; text-underline-offset: 3px;
}

/* The bar's duration is a custom property the script writes from its own
   interval, so the two cannot drift — the arrangement the hero slider uses. */
.ticker__bar {
  position: absolute; left: 0; bottom: 0; margin: 0;
  height: 3px; width: 0;
  background: rgba(255, 255, 255, 0.6);
  animation: ticker-bar var(--ticker-secs, 10s) linear forwards;
}
@keyframes ticker-bar { from { width: 0; } to { width: 100%; } }

.ticker__controls {
  position: absolute; right: var(--gutter); top: 50%;
  transform: translateY(-50%);
  display: flex; gap: 2px;
}
.ticker__btn {
  display: grid; place-items: center;
  width: 30px; height: 30px; padding: 0;
  background: none; border: 0; border-radius: var(--r-pill);
  color: #fff; cursor: pointer; opacity: 0.75;
  transition: opacity 0.2s var(--ease-out), background 0.2s var(--ease-out);
}
.ticker__btn:hover { opacity: 1; background: rgba(255, 255, 255, 0.16); }
.ticker__btn:focus-visible { opacity: 1; outline: 2px solid #fff; outline-offset: 2px; }
.ticker__ico--play { display: none; }
.ticker__btn--pause[aria-pressed="true"] .ticker__ico--pause { display: none; }
.ticker__btn--pause[aria-pressed="true"] .ticker__ico--play { display: block; }

/* Stacked only once the row genuinely will not fit: the label is about 200px,
   the controls 108, and a headline wants 260 before the ellipsis starts doing
   all the work — which lands at about 640. Set at 900 first, where there was
   still half a strip of empty space to the right of the headline and the row
   had gone two-storey for nothing. His own stacks at 1023, and looks it. */
@media (max-width: 640px) {
  /* The label goes above the headline rather than beside it, so a long
     headline keeps the full width instead of a third of it. */
  .ticker__inner {
    grid-template-columns: 1fr; gap: 0;
    padding-inline: 0;
  }
  .ticker__label {
    margin: 0; padding: 14px var(--gutter);
    border-radius: 0;
    background: var(--c-brand-deep);
    font-size: 15px;
  }
  .ticker__list { min-height: 52px; padding-inline: var(--gutter); }
  .ticker__item { padding-inline: var(--gutter); }
  .ticker__item a { font-size: 15px; }
  /* Up into the label row, which is otherwise a band of empty colour — and it
     gives the headline the whole width of the phone rather than two thirds. */
  .ticker__controls { right: calc(var(--gutter) - 5px); top: 3px; bottom: auto; transform: none; gap: 4px; }
  /* Bigger here than on the desktop, which is the opposite of the usual
     instinct. 30px clears WCAG's 24px minimum, but a phone has no hover: these
     three are the only way to hold or step the strip, so they are the most
     used controls on the narrowest screen. */
  .ticker__btn { width: 40px; height: 40px; }
}

/* Nothing advances on its own for anyone who asked for that, so a bar counting
   down to a change that will not come would be a lie. The controls stay —
   they are how those visitors reach the other headlines. */
@media (prefers-reduced-motion: reduce) {
  .ticker__bar { display: none; }
  .ticker__btn--pause { display: none; }
  .ticker__item { transition: none; }
}

/* ── Specialist areas ──────────────────────────────────────── */

.tiles { display: grid; gap: var(--s-3); }
.tiles--grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tiles--grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.tiles--feature-2 { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
.tiles--feature-2 .tile:first-child { grid-row: span 3; }

.tile {
  display: flex; flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  background: var(--c-paper);
  color: inherit; text-decoration: none;
  transition: border-color 0.2s var(--ease-out), transform 0.2s var(--ease-out), box-shadow 0.2s var(--ease-out);
}
a.tile:hover { border-color: var(--c-brand-bright); transform: translateY(-3px); box-shadow: var(--sh-card); }
.section--deep .tile { background: rgba(255, 255, 255, 0.06); border-color: rgba(255, 255, 255, 0.16); color: #fff; }

/* The old site overlays white type on a darkened photo. This does not: the
   photograph is a photograph and the words sit under it, with room to breathe.
   Same content, and nobody would mistake one for the other. */
.tile__media { aspect-ratio: 4 / 3; overflow: hidden; background: var(--c-paper-deep); }
.tiles--feature-2 .tile:first-child .tile__media { aspect-ratio: 4 / 3.4; }
.tile__img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s var(--ease-out); }
a.tile:hover .tile__img { transform: scale(1.03); }

.tile__body { display: flex; flex-direction: column; gap: 8px; padding: var(--s-3); }
.tile__title { margin: 0; font-size: var(--t-h3); }
.tile__text { margin: 0; color: var(--c-muted); font-size: var(--t-small); }
.section--deep .tile__text { color: rgba(255, 255, 255, 0.8); }
.tile__more {
  margin-top: auto; padding-top: var(--s-2);
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--c-brand); font-size: var(--t-small); font-weight: 600;
}
.section--deep .tile__more { color: #fff; }
.tile__arrow { color: var(--c-brand-bright); font-weight: 600; transition: transform 0.18s var(--ease-out); display: inline-block; }
.section--deep .tile__arrow { color: #fff; }
a.tile:hover .tile__arrow { transform: translateX(4px); }
a.tile:hover .tile__more { text-decoration: underline; text-underline-offset: 3px; }

.tiles__more { margin: var(--s-4) 0 0; text-align: center; }

/* A tile with no photograph should not leave a hole where one would be. */
.tile:not(:has(.tile__media)) .tile__body { padding-block: var(--s-4); }

/* ── Two doors ─────────────────────────────────────────────────
   Rene's brief (6 Sep 2026): the homepage is a choice between AI and
   Automation and digital marketing training. Two panels at heading size,
   dark so they read as the next thing after the hero rather than as two
   more tiles. Green ticks on deep blue, never white on green. */
.doors { display: grid; gap: var(--s-3); grid-template-columns: repeat(2, minmax(0, 1fr)); }
.door {
  position: relative; display: flex; flex-direction: column; overflow: hidden;
  border-radius: var(--r-lg); background: var(--c-brand-dark); color: #fff;
  text-decoration: none; box-shadow: var(--sh-card);
  transition: transform .25s var(--ease-out), box-shadow .25s var(--ease-out);
}
a.door:hover, a.door:focus-visible { transform: translateY(-4px); box-shadow: var(--sh-lift); color: #fff; }
.door__media { aspect-ratio: 16 / 10; overflow: hidden; background: var(--c-brand-deep); }
.door__img { width: 100%; height: 100%; object-fit: cover; transition: transform .7s var(--ease-out); }
a.door:hover .door__img { transform: scale(1.04); }
.door__body { display: flex; flex-direction: column; gap: 10px; flex: 1; padding: var(--s-3) var(--s-3) var(--s-4); }
.door__eyebrow {
  margin: 0; font-size: var(--t-eyebrow); font-weight: 700;
  letter-spacing: var(--ls-wide); text-transform: uppercase; color: var(--c-rene);
}
.door__title { margin: 0; font-size: var(--t-h2); line-height: var(--lh-tight); letter-spacing: var(--ls-tight); text-wrap: balance; }
.door__text { margin: 0; font-size: var(--t-body); line-height: 1.55; color: rgba(255, 255, 255, 0.84); }
.door__list { margin: var(--s-1) 0 0; padding: 0; list-style: none; }
.door__list li {
  position: relative; padding-left: 30px; margin-bottom: 10px;
  font-size: var(--t-small); font-weight: 500;
}
.door__list li::before {
  content: ""; position: absolute; left: 0; top: 7px;
  width: 16px; height: 9px;
  border-left: 2.5px solid var(--c-green); border-bottom: 2.5px solid var(--c-green);
  transform: rotate(-45deg);
}
.door__more {
  margin-top: auto; padding-top: var(--s-2); display: inline-flex; align-items: center; gap: 8px;
  color: var(--c-rene); font-size: var(--t-small); font-weight: 600;
}
.door__arrow { display: inline-block; transition: transform .18s var(--ease-out); }
a.door:hover .door__arrow { transform: translateX(4px); }
@media (max-width: 820px) {
  .doors { grid-template-columns: 1fr; }
}

/* ── Book a call ───────────────────────────────────────────────
   The frame carries data-src and no src until functional cookies are
   allowed; the fallback sits over it until then and disappears the moment
   consent.js fills the address in. Heights are fixed: without Calendly's
   own script nothing reports the calendar's real height, and it stacks
   taller on a phone. */
.booking { position: relative; }
.booking__frame {
  display: block; width: 100%; height: 720px; border: 0;
  border-radius: var(--r-lg); background: var(--c-paper-deep);
}
.booking:not(:has(.booking__frame[src])) .booking__frame { height: 300px; }
.booking:has(.booking__frame[src]) .booking__fallback { display: none; }
.booking__fallback {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: var(--s-2);
  padding: var(--s-3); text-align: center;
}
.booking__fallback .form__note { margin: 0; }
.booking__note { margin: var(--s-2) 0 0; text-align: center; }
.linklike {
  background: none; border: 0; padding: 0; font: inherit;
  color: var(--c-brand); text-decoration: underline; cursor: pointer;
}
@media (max-width: 600px) {
  .booking__frame { height: 1000px; }
}

/* ── Site map ──────────────────────────────────────────────────
   Two columns of plain links; the page tree indents, the articles sit
   under their year. Dense on purpose — it is an index, not a page. */
.sitemap { display: grid; gap: var(--s-4); grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
.sitemap__h { margin: 0 0 var(--s-2); font-size: var(--t-h3); padding-bottom: var(--s-1); border-bottom: 2px solid var(--c-brand-bright); }
.sitemap__n { font-size: var(--t-small); font-weight: 500; color: var(--c-muted); margin-left: 6px; }
.sitemap__year { margin: var(--s-3) 0 var(--s-1); font-size: var(--t-eyebrow); font-weight: 700; letter-spacing: var(--ls-wide); text-transform: uppercase; color: var(--c-brand); }
.sitemap__list { margin: 0; padding: 0; list-style: none; }
.sitemap__list li { margin: 0 0 6px; font-size: var(--t-small); line-height: 1.5; }
.sitemap__list .sitemap__list { margin: 4px 0 8px 18px; padding-left: 12px; border-left: 1px solid var(--c-line); }
.sitemap__list a { color: var(--c-ink); text-decoration: none; }
.sitemap__list a:hover { color: var(--c-brand); text-decoration: underline; }
@media (max-width: 820px) {
  .sitemap { grid-template-columns: 1fr; }
}

/* ── People ───────────────────────────────────────────────────
   His team page puts the photograph in one column and the words in the next,
   and that is the right shape for it: a face is recognisable small, and the
   line under it is a job title, not a paragraph. Kept, with our card. */

.people { display: grid; gap: var(--s-3) var(--s-4); }
.people--grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* One person, at the head of their own page: a portrait with their role and
   their profiles set against the middle of it. Aligned to the top it left a
   column of white the height of the photograph, which reads as a section that
   failed to load rather than as a heading. */
.people--profile { grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); align-items: center; gap: var(--s-4); }

.person { display: grid; grid-template-columns: minmax(0, 8.5rem) minmax(0, 1fr); gap: var(--s-3); align-items: start; }
.people--profile .person { display: contents; }

.person__photo { overflow: hidden; border-radius: var(--r-lg); background: var(--c-paper-deep); }
.person__photo a { display: block; }
.person__img { display: block; width: 100%; height: auto; aspect-ratio: 3 / 4; object-fit: cover; object-position: center top; }

.person__body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.person__name { margin: 0; font-size: var(--t-h3); }
.person__name a { color: inherit; text-decoration: none; }
.person__name a:hover { color: var(--c-brand); text-decoration: underline; text-underline-offset: 3px; }
.person__role { margin: 0; color: var(--c-muted); font-size: var(--t-small); }
.person__role--lead { margin: 0 0 var(--s-2); color: var(--c-ink); font-size: var(--t-h2); }
.section--deep .person__role { color: rgba(255, 255, 255, 0.8); }

.person__sociallabel { margin: var(--s-2) 0 0; font-weight: 600; font-size: var(--t-small); }
/* The icons come from the same row the footer uses, at the smaller of its two
   sizes: five of them beside a job title should not outweigh the name. */
.person__social { margin-top: 6px; gap: 8px; }
.person__social a { width: 32px; height: 32px; color: var(--c-ink); }
.person__social a:hover { background: var(--c-brand-tint); color: var(--c-brand); }
.person__social svg { width: 15px; height: 15px; }
.section--deep .person__social a { color: #fff; }

/* Photo gallery: CSS columns, so each photograph keeps its own shape and the
   column tops stay level. Read top-to-bottom per column, which is fine for
   photos and is why it is not a grid. */
.gallery { columns: 3; column-gap: var(--s-2); }
.gallery__item {
  display: block; break-inside: avoid; margin: 0 0 var(--s-2);
  border-radius: var(--r-md); overflow: hidden; cursor: zoom-in;
  background: rgba(0, 0, 0, 0.04);
}
.gallery__img { display: block; width: 100%; height: auto; transition: transform 0.5s var(--ease-out); }
.gallery__item:hover .gallery__img, .gallery__item:focus-visible .gallery__img { transform: scale(1.035); }
.gallery__item:focus-visible { outline: 3px solid #14A4DA; outline-offset: 3px; }

/* The lightbox: one <dialog> from theme.js. */
.has-lightbox, .has-lightbox body { overflow: hidden; }
.lightbox {
  position: fixed; inset: 0; width: 100vw; height: 100dvh; max-width: none; max-height: none;
  margin: 0; padding: 0; border: 0; background: rgba(8, 14, 20, 0.94); color: #fff;
}
.lightbox::backdrop { background: rgba(8, 14, 20, 0.6); }
.lightbox[open] { display: flex; align-items: center; justify-content: center; animation: lb-in 0.22s var(--ease-out); }
@keyframes lb-in { from { opacity: 0; } to { opacity: 1; } }
.lightbox__fig {
  margin: 0; width: 100%; height: 100%; padding: 64px 84px 72px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
}
.lightbox__img { max-width: 100%; max-height: 100%; min-height: 0; object-fit: contain; border-radius: var(--r-sm); }
.lightbox__cap { font-size: var(--t-small); text-align: center; max-width: 60ch; opacity: 0.85; }
.lightbox__cap:empty { display: none; }
.lightbox__count {
  position: absolute; top: 22px; left: 24px; margin: 0;
  font-size: var(--t-small); letter-spacing: 0.06em; opacity: 0.75;
}
.lightbox__btn {
  position: absolute; width: 52px; height: 52px; border-radius: var(--r-pill);
  border: 1px solid rgba(255, 255, 255, 0.25); background: rgba(255, 255, 255, 0.08);
  color: #fff; font-size: 30px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center; padding: 0 0 3px;
  transition: background 0.18s var(--ease-out);
}
.lightbox__btn:hover, .lightbox__btn:focus-visible { background: #14A4DA; border-color: #14A4DA; }
.lightbox__close { top: 14px; right: 16px; }
.lightbox__prev { left: 16px; top: 50%; transform: translateY(-50%); }
.lightbox__next { right: 16px; top: 50%; transform: translateY(-50%); }
.lightbox--single .lightbox__prev, .lightbox--single .lightbox__next { display: none; }
@media (prefers-reduced-motion: reduce) {
  .gallery__img { transition: none; }
  .gallery__item:hover .gallery__img { transform: none; }
  .lightbox[open] { animation: none; }
}

@media (max-width: 1100px) {
  .people--grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .gallery { columns: 2; }
}
@media (max-width: 820px) {
  .people--profile { grid-template-columns: 1fr; justify-items: start; }
  .people--profile .person__photo { max-width: 15rem; }
}
@media (max-width: 600px) {
  .people--grid { grid-template-columns: 1fr; }
  .gallery { columns: 1; }
  .lightbox__fig { padding: 60px 12px 84px; }
  .lightbox__prev, .lightbox__next { top: auto; bottom: 16px; transform: none; }
  .lightbox__prev { left: calc(50% - 64px); }
  .lightbox__next { right: calc(50% - 64px); }
}

/* Their own articles, at the foot of a team member's page. A list of headlines
   is not a list of bullet points. */
.article-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); }
.article-list li { padding-left: 0; }
.article-list li > a { font-weight: 600; }
.article-list time { margin-left: 8px; color: var(--c-muted); font-size: var(--t-small); }
.article-list p { margin: 4px 0 0; color: var(--c-muted); font-size: var(--t-small); }

/* ── What clients say ──────────────────────────────────────── */

.quotes { display: grid; gap: var(--s-3); }
.quotes--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.quotes--one { grid-template-columns: minmax(0, 76ch); justify-content: center; text-align: center; }

.quote {
  display: flex; flex-direction: column; gap: var(--s-3);
  margin: 0; padding: var(--s-4);
  border-left: 3px solid var(--c-teal);
  border-radius: 0 var(--r-lg) var(--r-lg) 0;
  background: var(--c-paper-deep);
}
.section--deep .quote { background: rgba(255, 255, 255, 0.07); border-left-color: var(--c-teal-bright); }
.quotes--one .quote { border-left: 0; border-radius: var(--r-lg); }
.quote__text {
  margin: 0;
  font-size: clamp(17px, 1.8vw, 20px); font-weight: 300;
  line-height: 1.55;
}
.quotes--one .quote__text { font-size: clamp(20px, 2.4vw, 27px); }
.quote__text::before { content: "\201C"; }
.quote__text::after { content: "\201D"; }
.quote__by { display: flex; flex-direction: column; gap: 2px; margin-top: auto; }
.quote__name { font-weight: 600; font-size: var(--t-small); }
.quote__where { font-size: var(--t-small); color: var(--c-muted); }
.section--deep .quote__where { color: rgba(255, 255, 255, 0.76); }

@media (max-width: 1100px) {
  .tiles--grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .quotes--three { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 820px) {
  .tiles--feature-2 { grid-template-columns: 1fr; }
  .tiles--feature-2 .tile:first-child { grid-row: auto; }
  .brands__track { gap: var(--s-3) var(--s-4); }
  .brand__img { --brand-h: 30px; }
  .brand__img { height: 28px; }
}
@media (max-width: 600px) {
  .tiles--grid-3, .tiles--grid-2, .quotes--three { grid-template-columns: 1fr; }
}

/* ── Statement band ────────────────────────────────────────────
   Rene's "Helping you grow with AI" row, and the one he calls the most
   important on the page. Everything below was read out of
   themes/tuminds/css/newatf.min.css rather than sampled off a screenshot:

     .featureblock{background-color:#14a4da; ...}
     .featureblock__columns{grid-template-columns:calc(40% - 40px) calc(60% - 40px);gap:80px}
     .featureblock h2{font-size:62px;font-weight:600;line-height:1.2}
     .featureblock h2 strong{color:#cbf1ff}
     .featureblock p{line-height:1.8}

   His background is a PNG of circles at 55%, top right. Ours is the same
   circles the footer and the menu already draw, from tum_circles() — the one
   piece of his furniture this site carries over, and drawing it a second way
   would be two things to keep in agreement.

   THE ONE THING THAT IS NOT HIS: the solid button's label. He sets it
   #14a4da on white, which is 2.86:1 — under AA for a control that has to be
   read. It is --c-brand here at 6.15:1, and that is not an invention either:
   #0C698C is the colour his own :hover state fills the button with, so the
   two states now share a blue instead of the label being the weak one.

   The band's own white-on-blue stays at 2.86:1, as it does in the footer and
   the menu, because that is his brand rendered the way he renders it and it
   was asked for and shown. --c-rene-ink is still the one-line switch, and it
   changes the text, never the blue. */

.intro {
  position: relative; isolation: isolate;
  background: var(--c-rene);
  color: #fff;
  padding-block: var(--s-5);
  overflow: hidden;
}
.intro__grid {
  position: relative; z-index: 1;
  width: 100%; max-width: var(--container);
  margin-inline: auto; padding-inline: var(--gutter);
  display: grid;
  grid-template-columns: minmax(0, 40fr) minmax(0, 60fr);
  gap: var(--s-5);
  align-items: start;
}

.intro__title {
  margin: 0;
  font-size: clamp(34px, 4.6vw, 62px);
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: var(--ls-tight);
  color: #fff;
}
/* Bolding a word in the Studio is what tints it — the weight is deliberately
   not inherited from <strong>, because his heading is 600 throughout and the
   emphasis is carried entirely by the colour. */
.intro__title strong, .intro__title b { font-weight: inherit; color: var(--c-rene-pale); }

.intro__body { font-size: clamp(16px, 1.6vw, 18px); line-height: 1.8; }
.intro__body p { margin: 0 0 1em; }
.intro__body p:last-of-type { margin-bottom: var(--s-3); }
.intro__body strong, .intro__body b { font-weight: 600; }
.intro__body a { color: #fff; text-underline-offset: 3px; }

.intro__actions { display: flex; flex-wrap: wrap; gap: var(--s-2); margin: 0; }

/* Both states are his, to the hex. */
.intro .btn--rene {
  background: #fff; border-color: #fff; color: var(--c-brand);
}
.intro .btn--rene:hover {
  background: var(--c-brand); border-color: var(--c-brand); color: #fff;
}
.intro .btn--rene-ghost {
  background: transparent; border-color: #fff; color: #fff;
}
.intro .btn--rene-ghost:hover {
  background: #fff; border-color: #fff; color: var(--c-brand);
}

@media (max-width: 900px) {
  .intro__grid { grid-template-columns: 1fr; gap: var(--s-3); }
}

/* ── Mailing list ──────────────────────────────────────────── */

/* The plain version: a centred column on whichever band the row sits on. What
   a signup row falls back to when nobody has given it a photograph. */
.signup { display: grid; gap: var(--s-5); align-items: center; }
.signup--band { grid-template-columns: minmax(0, 68ch); justify-content: center; }
.signup__body { margin-bottom: var(--s-3); color: var(--c-muted); }
.section--deep .signup__body { color: rgba(255, 255, 255, 0.86); }

/* The band is a single line on a wide screen: field, then button. */
.signup__form .form__field { margin-bottom: var(--s-2); }
.signup__form .form__actions { margin-top: var(--s-2); }
.signup--band .signup__form { max-width: 52ch; margin-inline: auto; }

/* ── Mailing list, on a photograph ─────────────────────────────
   Rene's own band puts a photograph behind a very large circle cut out of the
   colour. He asked for that treatment, not a copy of it, and gave the idea
   that makes it ours: make the shape point. So the photograph bleeds off the
   right of the screen, full height, and the edge that faces the words is a
   soft arrowhead with its tip level with the signup field. The picture is
   aimed at the thing we want pressed.

   It is one path, in objectBoundingBox units, and it does two jobs — it clips
   the photograph, and the same string drawn as an open stroke is the blue
   keyline along the edge. Written once so the shape and its outline cannot
   drift apart, which they would within a week if they were two strings.

   The tip is a corner, not a curve, and that is the whole difference between
   an arrow and an arc: the control points either side of it sit off the
   vertical by 0.06, so the two tangents meet at about 40 degrees. Pull that to
   zero and this becomes the circle we were asked not to copy.

   Green is Rene's #92CB71, type on it is --c-ink (9.60:1) or --c-brand-dark
   (6.39:1), and white is not an option — see the token for the measurements. */

.signup--photo {
  position: relative;
  display: grid; align-items: center;
  background: var(--c-green);
  overflow: hidden;
  min-height: clamp(440px, 38vw, 600px);
}

/* The words ride above the picture on the container grid; only the colour and
   the photograph bleed. */
.signup__grid {
  position: relative; z-index: 2;
  width: 100%; max-width: var(--container);
  margin-inline: auto; padding-inline: var(--gutter);
}
.signup--photo .signup__words { max-width: 44ch; padding-block: var(--s-5); }

/* Capped, not a straight share of the screen: a panel that keeps growing
   arrives at the photograph's own proportions on a wide monitor and object-fit
   stops cropping anything, which is how this band quietly showed the whole
   room on 4 Sep. */
.signup__art {
  position: absolute; inset: 0 0 0 auto; z-index: 1;
  /* Capped in PIXELS, not vw. The band's height is set by the words and barely
     moves, so a panel that keeps widening keeps flattening the arrow: at a 980
     cap the 30-degree slope measured 25 degrees on a 1900px screen. Held at
     806 the box stays the same shape from 1440 up and so does the angle. */
  width: clamp(380px, 56vw, 806px);
}
/* The three ids here are the three keys of tum_signup_arrows(). They are the
   only coupling between that function and this file, and an id that does not
   resolve is silent — the element renders unclipped rather than erroring — so
   check all three when either end changes. */
.signup__media { position: absolute; inset: 0; clip-path: url(#tum-signup-arrow-wide); }
.signup__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* One framing point, used twice: object-position picks what survives the
     cover crop, transform-origin is where --crop zooms in from. They agree, or
     they fight each other as the panel changes shape. Right of centre, because
     the arrowhead eats the left third of the panel and the subject has to sit
     in what is left. */
  object-position: 62% 42%;
  --crop: 1.14;
  transform: scale(var(--crop));
  transform-origin: 62% 42%;
}

/* The keyline. Open path, so it traces the arrow edge and not the three sides
   of the panel that are off-screen. non-scaling-stroke keeps it an even weight
   while preserveAspectRatio="none" stretches the geometry underneath it. */
/* width/height 100% is not belt-and-braces on top of inset:0 — it is the whole
   thing working. An inline <svg> is a REPLACED element, so absolute positioning
   with auto width and height resolves its used size from its intrinsic aspect
   ratio and then drops the over-constrained edge, rather than stretching to the
   inset box. viewBox="0 0 1 1" makes that ratio 1:1, so the keyline was being
   drawn into a 662x662 square while the clip path used the real 662x582 panel:
   same path, two different coordinate spaces, and a widening sliver of
   unclipped photograph outside the blue line. */
.signup__keyline {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: 2; pointer-events: none; overflow: visible;
}
.signup__keyline path {
  fill: none; stroke: var(--c-rene);
  stroke-width: 7; vector-effect: non-scaling-stroke;
}
.signup__keyline .is-narrow, .signup__keyline .is-stacked { display: none; }

/* Type on the green. */
.signup--photo .eyebrow { color: var(--c-brand-dark); }
.signup--photo .section__head { max-width: none; margin-bottom: var(--s-3); }
.signup--photo .section__title { font-size: var(--t-h1); color: var(--c-ink); }
.signup--photo .cms-brand-dot, .signup--photo .dot { color: var(--c-brand-dark); }
.signup--photo .signup__body {
  max-width: 42ch; margin-bottom: var(--s-4);
  font-size: clamp(17px, 1.7vw, 19px); font-weight: 300;
  color: var(--c-ink);
}
.signup--photo .signup__body a { color: var(--c-brand-dark); text-underline-offset: 3px; }

/* The form on the green.
   Every selector here carries .signup--photo, and that is load-bearing: the
   form's own stylesheet is inline and therefore printed after app.css, so an
   override at equal specificity would lose. One extra class settles it without
   depending on order. */
.signup--photo .form--dhd {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s-2);
  max-width: 42ch;
}
.signup--photo .form--dhd > * { flex: 0 0 100%; margin: 0; }
/* The gaps between the parts are the flex gap and nothing else; the form's own
   stylesheet spaces fields with a bottom margin that lands inside these
   wrappers rather than on them. */
.signup--photo .form__field, .signup--photo .form__group { margin: 0; }

/* Two controls on one line, in the order Rene's own band has them — set here
   rather than in the markup, because the submit button is the form's last
   child and the consent question sits between. */
.signup--photo .form--dhd > .form__row:has(> [data-dhd-key="email"]) { flex: 1 1 200px; }
.signup--photo .form--dhd > .form__actions { order: 1; flex: 0 0 auto; }
.signup--photo .form--dhd > .form__row:has(> [data-dhd-key="marketing_optin"]) { order: 2; }

/* Matched heights, stated rather than stretched: with align-items:stretch the
   button grows to whatever the field becomes, and the field grows by the height
   of an error message the moment anyone mistypes an address. */
.signup--photo .form__control { min-height: 58px; border-color: transparent; }
.signup--photo .form__actions .btn { min-height: 58px; padding-inline: 30px; }
.signup--photo .form__control:hover { border-color: rgba(3, 23, 31, 0.28); }
.signup--photo .form__control:focus-visible {
  border-color: var(--c-brand-dark);
  box-shadow: 0 0 0 3px rgba(3, 23, 31, 0.28);
}

/* The field's label is the placeholder's job in a band this size — but only
   visually. The <label> stays in the accessibility tree, where it is the only
   thing that names the control. */
.signup--photo .form__label {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.signup--photo .form__group--consent .form__opt__text { color: var(--c-ink); }
.signup--photo .form__group--consent .form__opt__text a { color: var(--c-brand-dark); }
.signup--photo .form__mark { border-color: rgba(3, 23, 31, 0.4); }
/* #B3261E is 3.42:1 on the green — an error nobody can read is worse than no
   error at all, so the band darkens it to 6.22:1. */
.signup--photo .form__error { color: #6E1410; }
.signup--photo .form__hint, .signup--photo .form__note { color: var(--c-brand-dark); }
.signup--photo .form-success { background: rgba(255, 255, 255, 0.92); }

/* The button has to clear the green it sits on as well as carry its own label.
   --c-brand manages 3.22:1 against the green, which is the bare minimum a
   control's edge is allowed; --c-brand-deep is 4.07:1 against it and 7.76:1
   under its own white text. The hover goes darker rather than brighter, because
   the bright-blue hover everywhere else is 2.86:1 with white on it. */
.signup--photo .btn, .signup--photo .btn--primary { background: var(--c-brand-deep); }
.signup--photo .btn:hover, .signup--photo .btn--primary:hover { background: var(--c-brand-dark); }

/* The reveal.
   The picture slides in from the right and the photograph settles out of a
   slow push-in behind it, keyline and all.

   The line does NOT draw itself on, though it should be able to: pathLength=1
   normalises any path to one unit, which is exactly what a stroke-dasharray
   draw-on wants — but vector-effect: non-scaling-stroke, which is what keeps
   the keyline an even weight under preserveAspectRatio="none", also moves dash
   lengths into screen pixels and ignores pathLength entirely. dasharray:1 then
   means a one-pixel dash, and the keyline renders as a dotted line. The two
   features cannot both be had here; an even line beats an animated one.

   All of it runs off the row-effect machinery in theme.js and is therefore
   gated on the same reduced-motion check as everything else: no .fx-on, no
   transitions, and the band is simply there. .fx-reveal's own fade is
   cancelled — a fade over the top of this reads as a page still loading. */
.fx-on .signup--photo.fx-reveal { opacity: 1; }

.fx-on .signup--photo .signup__art {
  opacity: 0; transform: translateX(5%);
  transition: opacity 0.7s var(--ease-out), transform 0.9s var(--ease-out);
}
.fx-on .signup--photo.is-in .signup__art { opacity: 1; transform: none; }

.fx-on .signup--photo .signup__img {
  transform: scale(calc(var(--crop) * 1.08));
  transition: transform 1.9s var(--ease-out);
}
.fx-on .signup--photo.is-in .signup__img { transform: scale(var(--crop)); }

.fx-on .signup--photo .signup__words {
  opacity: 0; transform: translateY(20px);
  transition: opacity 0.6s var(--ease-out) 0.1s, transform 0.6s var(--ease-out) 0.1s;
}
.fx-on .signup--photo.is-in .signup__words { opacity: 1; transform: none; }

/* 1300, not 1100: the panel is wide enough now that the arrow's tip crosses
   the container's text column before the layout stacks, and a heading over a
   photograph is a contrast problem the moment the picture is not dark. */
/* The panel gives way at middle widths, not the words. A 56vw panel puts the
   arrow's tip through the text column well before the layout stacks; narrowing
   the panel only shortens the arrow, where narrowing the column breaks the
   field and the button onto separate lines and the band stops looking like one
   thing. */
@media (max-width: 1300px) {
  .signup__art { width: clamp(320px, 44vw, 600px); }
  .signup__media { clip-path: url(#tum-signup-arrow-narrow); }
  .signup__keyline .is-wide { display: none; }
  .signup__keyline .is-narrow { display: block; }
  .signup--photo .signup__words { max-width: 40ch; }
  .signup--photo .signup__body, .signup--photo .form--dhd { max-width: none; }
}

/* Stacked, and the idea survives the rotation: the photograph goes full width
   across the top and its arrow points DOWN at the form instead of across at
   it. Same shape, quarter turn, and the keyline swaps with it. */
@media (max-width: 820px) {
  .signup--photo { display: block; min-height: 0; }
  .signup__art { position: relative; inset: auto; width: 100%; height: clamp(260px, 62vw, 400px); }
  .signup__media { clip-path: url(#tum-signup-arrow-stacked); }
  .signup__img { object-position: 62% 36%; --crop: 1.06; }
  .signup__keyline .is-wide, .signup__keyline .is-narrow { display: none; }
  .signup__keyline .is-stacked { display: block; }
  .signup__grid { padding-block: var(--s-4) var(--s-5); }
  .signup--photo .signup__words { max-width: none; padding-block: 0; }
  .signup--photo .section__title { font-size: var(--t-h2); }

  .fx-on .signup--photo .signup__art { transform: translateY(4%); }
  .fx-on .signup--photo.is-in .signup__art { transform: none; }
}

/* ── Cookie consent ────────────────────────────────────────────
   Above everything, because it has to be answerable before anything else is
   used.

   Bottom RIGHT, not left. The hero puts its headline and both calls to action
   down the left, so a card in that corner lands squarely on the two buttons we
   most want a first-time visitor to press — which is exactly what it did until
   3 Sep. The right is clear: the hero copy is capped well short of it, and the
   two things that also want this corner are not there yet when the card shows.
   The back-to-top button only appears once you have scrolled, and the page is
   inert while this is open; the chat launcher cannot load at all until this
   card has granted the functional category.

   The scrim is deliberately light. It has to say the page behind is inert —
   this is a real modal, with a focus trap and Escape — without turning an
   already-dark hero photograph to mud. */

.consent {
  position: fixed; inset: 0; z-index: var(--z-consent);
  display: grid; place-items: end end;
  padding: var(--s-3);
  background: rgba(7, 59, 80, 0.12);
  overflow-y: auto;
}
.consent[hidden] { display: none; }
body.consent-open { overflow: hidden; }

.consent__card {
  width: min(560px, 100%);
  max-height: calc(100dvh - var(--s-4));
  overflow-y: auto;
  padding: var(--s-4);
  border-radius: var(--r-lg);
  background: var(--c-paper);
  box-shadow: var(--sh-lift);
}
.consent__title { margin: 0 0 var(--s-2); font-size: 23px; }
.consent__body { margin: 0 0 var(--s-3); font-size: var(--t-small); color: var(--c-muted); }

.consent__cats { display: grid; gap: var(--s-3); margin-bottom: var(--s-3); }
.consent__cats[hidden] { display: none; }
.consent__cat { padding-top: var(--s-3); border-top: 1px solid var(--c-line); }
.consent__cat-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); }
.consent__cat-name { margin: 0; font-size: 17px; }
.consent__always { font-size: var(--t-small); font-weight: 600; color: var(--c-teal); }
.consent__cat-body { margin: 8px 0 0; font-size: 14px; color: var(--c-muted); }

/* A real checkbox, visually a switch — so it is still a checkbox to a screen
   reader and to the keyboard. */
.consent__switch { position: relative; flex: none; display: inline-flex; cursor: pointer; }
.consent__switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.consent__slider {
  width: 46px; height: 26px; border-radius: var(--r-pill);
  background: rgba(14, 36, 48, 0.22);
  transition: background 0.18s var(--ease-out);
}
.consent__slider::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 20px; height: 20px; border-radius: 50%; background: #fff;
  transition: transform 0.18s var(--ease-out);
}
.consent__switch input:checked ~ .consent__slider { background: var(--c-brand); }
.consent__switch input:checked ~ .consent__slider::after { transform: translateX(20px); }
.consent__switch input:focus-visible ~ .consent__slider { box-shadow: 0 0 0 3px rgba(20, 164, 218, 0.3); }

.consent__actions { display: flex; flex-wrap: wrap; gap: 10px; }
.consent__actions .btn { flex: 1 1 auto; justify-content: center; padding: 13px 18px; font-size: 15px; }
.consent__policy { margin: var(--s-2) 0 0; font-size: 13px; }

/* Below this the card and the hero copy would meet in the middle, so it
   becomes a sheet across the foot of the screen instead — which covers only
   the bottom strip, whatever the hero is doing above it. */
@media (max-width: 900px) {
  .consent { place-items: end center; padding: 0; }
  .consent__card { width: 100%; border-radius: var(--r-lg) var(--r-lg) 0 0; max-height: 88dvh; padding: var(--s-3); }
}

/* Keep the three answers on one line rather than stacking them.
   Stacked, the sheet took over half a phone screen and buried the hero's own
   buttons underneath it — the thing this card is not supposed to do. */
@media (max-width: 600px) {
  /* One line while they fit — which the banner's three short labels do. The
     preferences view swaps the middle one for "Save my preferences", twice the
     width of an equal third, and `flex: 1 1 0` with `min-width: 0` let a button
     shrink below its own text: at 375px that label ran out of its box and was
     clipped by Accept all. A content-width basis plus the default min-width
     means they never shrink below their label, and wrap to a second line
     instead — which only happens in the tall panel that has room for it. */
  .consent__actions { flex-wrap: wrap; gap: 8px; }
  .consent__actions .btn { flex: 1 1 auto; padding: 12px 8px; font-size: 14px; white-space: nowrap; }

  /* The preferences view stacks. Its three labels cannot share a line on a
     phone, and left to wrap they came out as two short buttons above a
     full-width Accept all — accept twice the size of reject, which is the one
     asymmetry a consent panel must not have. Stacked they are identical, and
     the panel is a tall scroller that has the room. */
  .consent__card--full .consent__actions .btn { flex: 1 1 100%; }
}

/* ── Cookie policy table ───────────────────────────────────── */

.cookietable { margin-bottom: var(--s-4); }
.cookietable__name { margin: 0 0 6px; font-size: var(--t-h3); }
.cookietable__always { font-size: var(--t-small); font-weight: 500; color: var(--c-teal); }
.cookietable__body { margin: 0 0 var(--s-2); color: var(--c-muted); font-size: var(--t-small); max-width: 70ch; }
.cookietable__scroll { overflow-x: auto; }
.cookietable table { width: 100%; border-collapse: collapse; font-size: var(--t-small); }
.cookietable th, .cookietable td { padding: 12px 14px; text-align: left; border-bottom: 1px solid var(--c-line); vertical-align: top; }
.cookietable thead th { font-size: var(--t-eyebrow); letter-spacing: var(--ls-wide); text-transform: uppercase; color: var(--c-muted); }
.cookietable tbody th { font-weight: 600; white-space: nowrap; }
.cookietable__reopen { margin-top: var(--s-3); }

/* ── Hero ──────────────────────────────────────────────────────
   Full-bleed photography with the words over it. The slides cross-fade and
   drift; the copy changes with them. Nothing moves for anyone who has asked
   for reduced motion — the animation is declared here and switched off in the
   block at the top of this file. */

.hero {
  position: relative;
  display: grid; align-items: center;
  /* Fills the fold. It was min(78vh, 760px), and the 760 cap meant that on any
     screen taller than about 975px the next band was already showing under it —
     the hero stopped short of the fold rather than owning it. min-height, not
     height, so a long headline still grows the box rather than being clipped.

     svh, not vh: on a phone vh is the height with the browser toolbars hidden,
     so a 100vh hero is taller than what you can actually see and the bottom of
     it is always cut off. */
  min-height: 100svh;
  margin-top: calc(var(--header-h) * -1);
  /* --header-h, NOT --chrome-h. This is clearance for the copy under the fixed
     header, and the announcement bar is not part of that: the bar overlays the
     top of the hero and closing it must not move anything. Tied to --chrome-h
     the padding shrank by the bar's height the moment it auto-faded, and the
     centred copy jumped up with it. */
  padding-top: var(--header-h);
  overflow: hidden;
  background: var(--c-brand-dark);
  color: #fff;
}

/* Landscape on a phone: a full-height hero leaves no room for the copy, so let
   it be as tall as it needs instead. */
@media (max-height: 520px) {
  .hero { min-height: auto; padding-block: calc(var(--header-h) + var(--s-4)) var(--s-4); }
}
.hero__stage { position: absolute; inset: 0; }
.hero__stage img,
.hero__slide img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.hero__slide { position: absolute; inset: 0; opacity: 0; transition: opacity 1.4s var(--ease-out); }
.hero__slide.is-active { opacity: 1; }

/* One photograph, moving slowly, forever — the holding page's Ken Burns
   at a walking pace. --kb-x/--kb-y/--m-focal are written inline on each
   slide by dhd_render_hero_slides(); until 6 Sep 2026 nothing read them.
   alternate, so the drift never ends on a frozen frame. */
@keyframes hero-kb {
  from { transform: scale(1) translate3d(0, 0, 0); }
  to   { transform: scale(1.08) translate3d(var(--kb-x, -2%), var(--kb-y, -1.5%), 0); }
}
.hero__slide.is-active img { animation: hero-kb 28s ease-in-out infinite alternate; will-change: transform; }
.hero__slide.is-active[data-dir="out"] img { animation-direction: alternate-reverse; }
@media (prefers-reduced-motion: reduce) {
  .hero__slide img { animation: none; }
}
/* Anything the hero's buttons jump to lands under the header, not behind it. */
.section[id] { scroll-margin-top: var(--header-h); }

/* A gradient rather than a flat scrim: the words stay readable at the left
   without flattening the photograph everywhere else. */
.hero::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, rgba(7,59,80,0.94) 0%, rgba(7,59,80,0.76) 42%, rgba(7,59,80,0.30) 100%);
}
.hero__inner { position: relative; z-index: 1; padding-block: var(--s-6); }
.hero__copy { display: none; max-width: 62ch; }
.hero__copy.is-on { display: block; }
.hero__eyebrow { color: var(--c-teal-bright); }
/* A <p>, not a heading — the page's one <h1> is the hidden line above the
   slides. So everything the h1/h2/h3/h4 rule used to lend it is spelled out
   here, and the styling now belongs to the class rather than to the tag. */
.hero__title {
  margin: 0 0 var(--s-3);
  font-size: var(--t-display);
  font-weight: 600;
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  text-wrap: balance;
}
.hero__lede {
  margin: 0 0 var(--s-4);
  font-size: clamp(18px, 2.1vw, 22px); font-weight: 300;
  color: rgba(255, 255, 255, 0.88);
}
.hero__actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 0; }
.hero__actions .btn--ghost { border-color: rgba(255,255,255,0.45); color: #fff; }
.hero__actions .btn--ghost:hover { background: rgba(255,255,255,0.12); border-color: #fff; }

@media (max-width: 820px) {
  .hero { min-height: 0; }
  .hero__inner { padding-block: var(--s-5); }
  /* The Studio's "Mobile focus" dot, honoured. */
  .hero__slide img { object-position: var(--m-focal, 50% 50%); }
  .hero::after { background: linear-gradient(160deg, rgba(7,59,80,0.92) 0%, rgba(7,59,80,0.82) 100%); }
}

/* ── Live search ───────────────────────────────────────────────
   Copied from Digital Help Desk with the markup and the JS. */
/* Copied from Digital Help Desk whole, as the rule says — but a copied panel
   carries copied colours, and this one arrived in DHD's aubergine, lilac and
   green: --c-purple, --c-wash, --c-green-dark and --font-serif, none of which
   this theme defines. The undefined ones fell back silently; the two greens
   were literals and did not. Every colour below is repointed at the tuminds
   palette. Check this on the next panel that gets copied across. */
.site-search {
  position: fixed; inset: 0; z-index: 140;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.28s ease, visibility 0.28s ease;
}
.site-search.is-open { opacity: 1; visibility: visible; pointer-events: auto; }
body.search-open { overflow: hidden; }
.site-search__backdrop {
  position: absolute; inset: 0; background: rgba(3, 23, 31, 0.62);
  -webkit-backdrop-filter: blur(8px) saturate(140%); backdrop-filter: blur(8px) saturate(140%);
}
.site-search__panel {
  position: relative; margin: 13vh auto 0; width: min(720px, 92vw);
  background: var(--c-paper, #fff); border-radius: 20px; overflow: hidden;
  box-shadow: 0 40px 100px rgba(20, 8, 26, 0.5);
  transform: translateY(-24px) scale(0.985); transition: transform 0.32s var(--ease-out);
}
.site-search.is-open .site-search__panel { transform: none; }

.site-search__form {
  display: flex; align-items: center; gap: 14px;
  padding: 20px 22px; border-bottom: 1px solid var(--c-line);
}
.site-search__icon { color: var(--c-brand); flex: 0 0 auto; }
.site-search__input {
  flex: 1 1 auto; min-width: 0; border: 0; background: none; outline: none;
  font-family: var(--font-sans); font-size: clamp(20px, 3.4vw, 27px); color: var(--c-ink);
}
.site-search__input::placeholder { color: var(--c-muted); opacity: 0.7; }
.site-search__input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.site-search__close {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%; border: 0; cursor: pointer;
  background: var(--c-brand-tint); color: var(--c-muted); font-size: 24px; line-height: 1;
  transition: background 0.2s ease, color 0.2s ease;
}
.site-search__close:hover { background: var(--c-brand); color: #fff; }

.site-search__results { max-height: min(56vh, 520px); overflow-y: auto; padding: 8px; }
.site-search__hint { padding: 26px 22px; color: var(--c-muted); font-size: 15px; text-align: center; }
.site-search__result {
  display: block; text-decoration: none; color: inherit;
  padding: 15px 16px; border-radius: 13px;
  transition: background 0.15s ease, transform 0.15s var(--ease-out);
}
.site-search__result:hover,
.site-search__result:focus-visible,
.site-search__result.is-active { background: var(--c-paper-deep); outline: none; }
.site-search__result-top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.site-search__result-title { font-family: var(--font-sans); font-size: 18px; font-weight: 600; color: var(--c-ink); }
.site-search__result:hover .site-search__result-title { color: var(--c-brand); }
.site-search__badge {
  flex: 0 0 auto; font-family: var(--font-sans); font-size: 10.5px; font-weight: 700;
  letter-spacing: 1.4px; text-transform: uppercase; color: var(--c-brand);
  background: rgba(20, 164, 218, 0.16); padding: 3px 9px; border-radius: 999px;
}
.site-search__result-excerpt { margin: 5px 0 0; font-size: 14px; line-height: 1.5; color: var(--c-muted); }
.site-search__result mark { background: rgba(20, 164, 218, 0.30); color: inherit; border-radius: 3px; padding: 0 1px; }

/* ── Hero slider controls ─────────────────────────────────────
   The bar runs along the fold and fills over the life of the slide, so a
   visitor can see how long they have rather than being surprised by a change.
   It is driven by a CSS animation whose duration is a custom property, and JS
   restarts it by reflowing the element — one source of truth for the timing,
   which is the interval itself. */
.hero__bar {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  height: 12px;
  background: rgba(255, 255, 255, 0.22);
}
.hero__bar span {
  display: block; height: 100%;
  width: 0;
  background: var(--c-brand-bright);
  animation: hero-bar var(--hero-secs, 7s) linear forwards;
}
@keyframes hero-bar { from { width: 0; } to { width: 100%; } }
/* Nothing moves on its own for anyone who asked for that, so a bar counting
   down to a change that will not happen would be a lie. */
@media (prefers-reduced-motion: reduce) { .hero__bar { display: none; } }

.hero__controls {
  position: absolute; right: var(--gutter); bottom: calc(12px + var(--s-3)); z-index: 3;
}
.hero__nav { display: flex; gap: 10px; }
.hero__arrow {
  display: grid; place-items: center;
  width: 48px; height: 48px; padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.5); border-radius: 50%;
  background: rgba(7, 59, 80, 0.28); color: #fff; cursor: pointer;
  transition: background 0.18s var(--ease-out), border-color 0.18s var(--ease-out), transform 0.18s var(--ease-out);
}
.hero__arrow:hover { background: var(--c-brand-bright); border-color: var(--c-brand-bright); transform: translateY(-2px); }
.hero__arrow:focus-visible { outline: 3px solid #fff; outline-offset: 3px; }

/* Out of the chatbot's way, and into line with it.

   Chatling's launcher is a 55px circle sitting 20px off the bottom-right
   corner — measured on the page, like the 96px above it, because the widget's
   own inline styles are the only place that number exists. The arrows were in
   the same corner 40px up, so the second one spent its life half underneath a
   blue disc. The launcher is not the thing that moves: it is third-party, at
   z-index 2147483647, and the corner is where a visitor goes looking for a chat
   button. So the arrows move, the same way .to-top already does under this
   class — and they move ALONGSIDE it rather than merely away from it, because
   three circles on one line read as a control strip where a near-miss reads as
   a mistake.

   The box is the launcher's own height at the launcher's own inset and the
   arrows are centred inside it, so the row stays level when they drop to 42px
   on a phone. One rule, no second set of numbers, and it beats the 700px
   override below on specificity rather than on order.

   Both the inset and the height are now --chat-inset and --chat-size, which the
   launcher's own override reads too, so the three circles cannot drift apart:
   there is one number for where that corner sits and one for how big the
   launcher is.

   Under .has-chat only: with the chatbot switched off there is nothing to line
   up with, and the arrows keep the corner and the page gutter. */
.has-chat .hero__controls {
  right: calc(20px + var(--chat-size) + var(--s-2));
  bottom: var(--chat-inset);
  height: var(--chat-size);
}
.has-chat .hero__nav { height: 100%; align-items: center; }

@media (max-width: 700px) {
  /* Stay in the corner rather than centring. Centred they sat directly under
     the copy column and crowded the two buttons; the right-hand side of the
     hero is empty at this width, which is the whole reason to put them there. */
  .hero__arrow { width: 42px; height: 42px; }
  .hero__controls { bottom: calc(12px + var(--s-2)); }
  /* And keep the copy clear of them. At this width the two buttons run almost
     the full column, so anything in the bottom corner meets them; the hero is
     centred, so reserving the controls' height at the foot lifts the copy off
     them rather than pushing it down. */
  /* Only while there are arrows to keep clear of — a one-slide hero has none. */
  .hero:has(.hero__controls) .hero__inner { padding-bottom: 84px; }
  /* 84px was measured against the controls at their no-chat inset, and the
     .has-chat rule above beats this block on specificity — so once that inset
     went to 40px for a 55px box, the reserve was 11px short of the thing it
     exists to reserve for. Written from the same two tokens rather than as a
     second measured number, so it cannot fall behind them again. */
  .has-chat .hero__inner {
    padding-bottom: calc(var(--chat-inset) + var(--chat-size) + var(--s-3));
  }
}

/* ── Search, on the page ───────────────────────────────────── */
.pagesearch {
  display: flex; align-items: center; gap: var(--s-2);
  max-width: 46rem; margin-inline: auto;
  padding: 6px 6px 6px var(--s-2);
  border-bottom: 2px solid var(--c-line-strong);
  color: var(--c-muted);
  transition: border-color 0.2s var(--ease-out);
}
.pagesearch:focus-within { border-color: var(--c-brand); color: var(--c-brand); }
.pagesearch__icon { flex: none; }
.pagesearch__input {
  flex: 1 1 auto; min-width: 0;
  padding: 12px 0;
  border: 0; background: transparent;
  font-family: var(--font-sans); font-size: clamp(20px, 3vw, 30px);
  color: var(--c-ink);
}
.pagesearch__input::placeholder { color: var(--c-muted); opacity: 1; }
.pagesearch__input:focus { outline: none; }
.pagesearch__input::-webkit-search-cancel-button { -webkit-appearance: none; }
.section--deep .pagesearch { border-bottom-color: rgba(255, 255, 255, 0.5); color: rgba(255, 255, 255, 0.85); }
.section--deep .pagesearch__input { color: #fff; }
.section--deep .pagesearch__input::placeholder { color: rgba(255, 255, 255, 0.75); }

/* ── Page hero ─────────────────────────────────────────────────
   The band every page opens with. Without a picture it is the flat colour it
   always was; with one the photograph sits behind the title, which is how
   every page on tuminds.com opens and the thing that stops an inner page
   looking thinner than the homepage.

   The header is transparent over it, so the copy starts below the chrome
   rather than under it. */
.pagehero {
  position: relative; isolation: isolate; overflow: hidden;
  background: var(--c-paper-deep);
  padding-block: var(--s-6);
}
.pagehero__inner { position: relative; z-index: 2; }

.pagehero--image {
  margin-top: calc(var(--header-h) * -1);
  padding-top: calc(var(--header-h) + var(--s-6));
  background: var(--c-brand-dark);
  color: #fff;
  min-height: min(46vh, 460px);
  display: grid; align-items: center;
}
.pagehero--image h1 { color: #fff; }
.pagehero--image .eyebrow { color: var(--c-brand-bright); }
.pagehero--image .lede { color: rgba(255, 255, 255, 0.88); }

.pagehero__media { position: absolute; inset: 0; z-index: 0; }
.pagehero__img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Dark enough for white type over any of Rene's photographs, and weighted to
   the left where the copy sits so the picture still reads on the right. */
.pagehero--image::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(100deg, rgba(8, 58, 77, 0.86) 0%, rgba(8, 58, 77, 0.72) 42%, rgba(8, 58, 77, 0.42) 100%);
}

/* An article with no featured image — 174 of the 270, because the attachments
   his posts point at were deleted off his own server years ago and cannot be
   recovered from anywhere. It keeps --image's geometry (full bleed under the
   transparent header, white type, the same height) and swaps the photograph
   for one of the three card tones, so an article's header and its card in the
   listing are the same colour rather than two unrelated designs.

   The gradient is off: it exists to make white type legible over an unknown
   photograph, and over a flat brand colour it only muddies it. Rene's own
   --c-rene and --c-green stay out of this for the reason the card gives —
   those two are his brand exactly, and filler behind an article he did not
   choose is not where they belong. */
.pagehero--tone { background: var(--c-brand-dark); }
.pagehero--tone::after { content: none; }
.pagehero--t1 { background: var(--c-brand-deep); }
.pagehero--t2 { background: var(--c-brand); }

/* The eyebrow is --c-brand-bright, which is Rene's blue. It reads at 4.27:1
   over the darkened left of a photograph and at 2.15:1 on --c-brand, where it
   is his blue on a shade of his blue and effectively invisible. The type
   changes, never his hex: 5.14:1 at worst, and it matches the mark. */
.pagehero--tone .eyebrow { color: var(--c-rene-pale); }

/* Bottom right, well clear of the heading and fully inside the band.
   It was hung off the edge at first, on the theory that a cropped mark reads
   as a watermark — but `bottom` resolves against the hero's height, not the
   mark's, so -12% of a 370px band cut all but the top of the dots off and what
   was left looked like three half-circles caught on the edge by accident. */
.pagehero__mark {
  position: absolute; z-index: 1; right: 4%; bottom: 9%;
  width: 24%; max-width: 220px; height: auto;
  color: var(--c-rene-pale); opacity: 0.2;
}
@media (max-width: 700px) {
  /* The copy fills the band at this width; a mark beside it is clutter. */
  .pagehero__mark { display: none; }
}

@media (max-width: 700px) {
  .pagehero--image { min-height: min(52vh, 380px); }
  /* Lighter than it was. At 0.78-0.86 the picture was there but unreadable,
     which is the worst of both — the page pays to load a photograph nobody can
     see. Clear at the top where the image is, weighted underneath the copy. */
  .pagehero--image::after {
    background: linear-gradient(180deg, rgba(8, 58, 77, 0.34) 0%, rgba(8, 58, 77, 0.52) 45%, rgba(8, 58, 77, 0.80) 100%);
  }
  /* The eyebrow is brand blue, which reads on the heavily darkened left of the
     desktop overlay and does not read at all here, where it sits over the
     lightest part of the picture. White, only at this width. */
  .pagehero--image .eyebrow { color: #fff; opacity: 0.92; }
}

/* ── Articles ──────────────────────────────────────────────── */

/* Above the first paragraph, which is where his is — and where the parity
   harness can see it, since it drops <header> as chrome on both sides. */
.article__byline {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px var(--s-2);
  margin: 0 0 var(--s-4);
  color: var(--c-muted); font-size: var(--t-small);
}
.article__avatar { display: block; flex: none; line-height: 0; }
.article__avatar-img {
  display: block; width: 44px; height: 44px;
  border-radius: var(--r-pill); object-fit: cover; object-position: center top;
}
/* His byline is two links to the same page, one round the photograph and one
   round the name. Only the name is in the tab order and only the name is read
   out — a screen reader announcing "Picture for Rene Looper" and then "Rene
   Looper" is the same destination twice. */
.article__byline a { color: var(--c-ink); font-weight: 600; text-underline-offset: 3px; }

/* "Related topics", his heading, at the foot of the piece. */
.article__topics { margin-top: var(--s-5); padding-top: var(--s-4); border-top: 1px solid var(--c-line); }
.article__topics-title { margin: 0 0 var(--s-3); font-size: var(--t-body); }
.article__tags { display: flex; flex-wrap: wrap; gap: 10px; margin: 0; padding: 0; list-style: none; }
.tagpill {
  display: inline-block; padding: 7px 16px;
  border: 1px solid var(--c-line-strong); border-radius: var(--r-pill);
  color: var(--c-ink); text-decoration: none;
  font-size: var(--t-small); line-height: 1.3;
}
.tagpill:hover { border-color: var(--c-brand); background: var(--c-brand); color: #fff; }

.article__body > * + * { margin-top: var(--s-3); }
/* An article needs its own step between heading and body. --t-h3 is 22px and
   the prose runs to 19px, so at that size a heading read as a bold paragraph
   and five of them in a row gave the piece no structure at all. */
.article__body h2 {
  margin-top: var(--s-5); margin-bottom: var(--s-2);
  font-size: clamp(25px, 2.6vw, 32px); line-height: 1.2; letter-spacing: -0.02em;
}
.article__body h3 {
  margin-top: var(--s-4); margin-bottom: var(--s-2);
  font-size: clamp(20px, 2vw, 23px); line-height: 1.3;
}
/* h4 and below have no size of their own anywhere else on the site, because
   nothing designed here goes that deep. His pages do — every team member's page
   is four h4 labels — and left at the browser's default they came out at 16px
   against 19px of prose, so the label read smaller than the list under it. */
.article__body h4 {
  margin-top: var(--s-4); margin-bottom: var(--s-2);
  font-size: clamp(18px, 1.7vw, 20px); line-height: 1.35;
}
.article__body h5, .article__body h6 {
  margin-top: var(--s-4); margin-bottom: var(--s-2);
  font-size: var(--t-body);
}
/* A heading straight after another is a title and its subtitle, not two
   sections — several of the imported pieces open that way. */
.article__body h2 + h3, .article__body h2 + h2 { margin-top: var(--s-2); }
.article__body > :first-child { margin-top: 0; }
.article__body img { max-width: 100%; height: auto; border-radius: var(--r-lg); }
.article__body ul, .article__body ol { padding-left: 1.2em; }
.article__body li + li { margin-top: 8px; }
.article__body a { color: var(--c-brand); text-underline-offset: 3px; }
/* Imported articles use bare URLs as link text — "https://www.facebook.com/…"
   with nothing to break on, 479px of it on a 390px screen. anywhere lets the
   browser break mid-token when it has to, and only when it has to, so ordinary
   prose still wraps on spaces. */
.article__body { overflow-wrap: anywhere; }
.article__body a { overflow-wrap: anywhere; }
.article__body blockquote {
  margin: var(--s-4) 0; padding-left: var(--s-3);
  border-left: 3px solid var(--c-brand-bright);
  font-size: clamp(19px, 2vw, 22px); color: var(--c-muted);
}
/* Wide things get to scroll inside themselves rather than widening the page. */
.article__body table { display: block; overflow-x: auto; max-width: 100%; }
.article__body iframe { max-width: 100%; }
.article__back { margin-top: var(--s-4); }

/* ─── Rene's own markup ──────────────────────────────────────
   His pages are ordinary Gutenberg, and .article__body above already dresses
   the parts that are just HTML — headings, lists, links, images, tables,
   iframes. What is left is the handful of block classes his editor writes, and
   they are styled here rather than left to look like nothing: a page carrying
   his words in his structure but with his buttons rendered as bare links is
   not the same page.

   Everything below is our own tokens. His blue IS --c-brand, measured from his
   stylesheet, so his call-to-action buttons land on the .btn shape the rest of
   this site uses without a single new value. */

/* Capped at 74ch and never centred, so the column pinned to the left of a
   1200px container and left ~500px of dead space down the right of 73 of the 84
   pages. That gutter is what got reported as a right-hand sidebar; there was
   never a sidebar. margin-inline is the half that was missing — max-width alone
   decides how wide a block is and says nothing about where it sits. */
.prose-row { max-width: var(--measure); margin-inline: auto; }
.prose-row--wide { max-width: none; }

/* Figures. His images carry their own <figure>, so the margin belongs here and
   not on the img — an img rule would double up inside one. */
.article__body figure { margin: var(--s-4) 0; }
.article__body figure img { display: block; }
.article__body figcaption {
  margin-top: 10px; font-size: 15px; color: var(--c-muted); text-align: center;
}
.article__body .wp-block-image.alignleft,
.article__body .wp-block-image.alignright { max-width: 42%; }
@media (min-width: 760px) {
  .article__body .wp-block-image.alignleft { float: left; margin: 6px var(--s-4) var(--s-3) 0; }
  .article__body .wp-block-image.alignright { float: right; margin: 6px 0 var(--s-3) var(--s-4); }
}
.article__body .wp-block-image.aligncenter { margin-inline: auto; }
.article__body .aligncenter, .article__body .has-text-align-center { text-align: center; }
.article__body .has-text-align-right { text-align: right; }

/* His call-to-action buttons. Same pill, same blue, same hover as every other
   button on this site — see .btn, whose radius was measured from these. */
.article__body .wp-block-buttons {
  display: flex; flex-wrap: wrap; gap: var(--s-2); margin: var(--s-4) 0;
  list-style: none; padding: 0;
}
.article__body .wp-block-buttons.is-content-justification-center { justify-content: center; }
.article__body .wp-block-button { margin: 0; }
.article__body .wp-block-button__link {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 15px 32px; border: 1px solid transparent; border-radius: var(--r-pill);
  background: var(--c-brand); color: #fff;
  font-weight: 600; font-size: 16px; line-height: 1; text-decoration: none;
  transition: background 0.18s var(--ease-out), transform 0.18s var(--ease-out);
}
.article__body .wp-block-button__link:hover {
  background: var(--c-brand-bright); color: #fff; transform: translateY(-1px);
}
.article__body .wp-block-button.is-style-outline .wp-block-button__link {
  background: transparent; border-color: var(--c-brand); color: var(--c-brand-dark);
}

/* Two columns of his, which stack on a phone in source order. */
.article__body .wp-block-media-text,
.article__body .wp-block-columns { display: grid; gap: var(--s-4); margin: var(--s-4) 0; }
@media (min-width: 760px) {
  .article__body .wp-block-media-text { grid-template-columns: 1fr 1fr; align-items: center; }
  .article__body .wp-block-media-text.has-media-on-the-right .wp-block-media-text__media { order: 2; }
  .article__body .wp-block-columns { grid-auto-flow: column; grid-auto-columns: 1fr; }
}
.article__body .wp-block-media-text__media img { width: 100%; border-radius: var(--r-lg); }

.article__body .wp-block-separator {
  margin: var(--s-5) auto; border: 0; border-top: 1px solid var(--c-line);
}
.article__body .wp-block-spacer { height: var(--s-4); }
.article__body .wp-block-embed { margin: var(--s-4) 0; }
.article__body .wp-block-embed__wrapper { position: relative; }
.article__body .wp-block-embed iframe { width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0; }
/* A video placed from the Studio's editor: the Video button writes
   <figure class="video"><iframe …></figure>. Same shape as his old embeds. */
.article__body figure.video, .prose figure.video { margin: var(--s-4) 0; }
.article__body figure.video iframe, .prose figure.video iframe { display: block; width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0; border-radius: var(--r-2, 8px); }

/* An Instagram or X embed with its script denied is a blockquote with a link in
   it. Dressed as a pull quote it reads as something Rene chose to say; dressed
   as this it reads as what it is, a card pointing at a post. */
.article__body blockquote.instagram-media,
.article__body blockquote.twitter-tweet {
  margin: var(--s-4) 0; padding: var(--s-3);
  border: 1px solid var(--c-line); border-left: 3px solid var(--c-brand);
  border-radius: var(--r-lg); font-size: 17px; color: var(--c-ink);
}

/* ── The listing ───────────────────────────────────────────── */

.postlist { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--s-4); }
/* A group of one or two cards sits in the middle at three-column width,
   rather than hugging the left with an empty slot that reads as a missing
   card (the two free webinars, 6 Sep). Below two columns the auto-fill rule
   takes over again. */
@media (min-width: 701px) {
  .postlist:has(> :nth-child(2):last-child) { grid-template-columns: repeat(2, minmax(0, calc((100% - 2 * var(--s-4)) / 3))); justify-content: center; }
  .postlist:has(> :only-child) { grid-template-columns: minmax(0, calc((100% - 2 * var(--s-4)) / 3)); justify-content: center; }
}
.postcard {
  display: flex; flex-direction: column;
  border: 1px solid var(--c-line); border-radius: var(--r-lg);
  background: #fff; overflow: hidden; text-decoration: none; color: inherit;
  transition: transform 0.18s var(--ease-out), box-shadow 0.18s var(--ease-out), border-color 0.18s var(--ease-out);
}
.postcard:hover { transform: translateY(-3px); border-color: var(--c-brand); box-shadow: var(--sh-card); }
.postcard__media { display: block; aspect-ratio: 16 / 9; overflow: hidden; }
.postcard__img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* No featured image — 251 of the 270 imported articles, because 167 of the
   attachments his posts point at have since been deleted off his server. A card
   with no media block starts its text at the top of the box while the card
   beside it starts below a photograph, so a row of three had three shapes. The
   panel keeps the 16/9 and the row keeps its rhythm.

   Three tones, off the deep end of the palette. Rene's own --c-rene and
   --c-green stay out of it: those two are his brand exactly, and a filler
   behind an article he never chose is not where they belong. */
.postcard__media--blank {
  display: grid; place-items: center;
  background: var(--c-brand-dark);
}
.postcard__media--t1 { background: var(--c-brand-deep); }
.postcard__media--t2 { background: var(--c-brand); }
/* Sized off the card, not in pixels: the grid is auto-fill, so a card is
   anywhere between 300 and 590px wide and a fixed mark is lost at one end of
   that and shouting at the other. */
.postcard__mark { width: 28%; max-width: 120px; height: auto; color: var(--c-rene-pale); opacity: 0.55; }
.postcard__body { display: flex; flex-direction: column; gap: 10px; padding: var(--s-3); flex: 1 1 auto; }
.postcard__date { font-size: var(--t-eyebrow); font-weight: 600; letter-spacing: var(--ls-wide); text-transform: uppercase; color: var(--c-muted); }
.postcard__title { font-family: var(--font-sans); font-size: var(--t-h3); font-weight: 600; line-height: 1.25; letter-spacing: -0.02em; }
.postcard__excerpt { color: var(--c-muted); font-size: var(--t-small); }
.postcard__more { margin-top: auto; padding-top: var(--s-2); display: inline-flex; align-items: center; gap: 8px; color: var(--c-brand); font-size: var(--t-small); font-weight: 600; }
.postcard:hover .postcard__more { text-decoration: underline; text-underline-offset: 3px; }

/* ── The same card, carrying a course ──────────────────────────
   A workshop card and an article card are the same object with two more facts
   on it: what level it is pitched at, and who teaches it. Built as modifiers on
   .postcard rather than as a second component, because the two sit two clicks
   apart on the same site and "everything must be consistent" was the brief. */
.postcard__level {
  align-self: start;
  padding: 4px 12px; border-radius: var(--r-pill);
  background: var(--c-brand-tint); color: var(--c-brand-dark);
  font-size: var(--t-eyebrow); font-weight: 600;
  letter-spacing: var(--ls-wide); text-transform: uppercase;
}
/* Above the "About course" line, which margin-top:auto pins to the foot, so a
   short blurb and a long one still line their footers up across a row. */
.postcard__by { display: flex; align-items: center; gap: 10px; margin-top: auto; padding-top: var(--s-2); }
.postcard__by-img { width: 36px; height: 36px; border-radius: var(--r-pill); object-fit: cover; display: block; flex: none; }
.postcard__by-text { display: flex; flex-direction: column; min-width: 0; }
.postcard__by-name { font-size: var(--t-small); font-weight: 600; color: var(--c-ink); }
.postcard__by-role { font-size: var(--t-eyebrow); color: var(--c-muted); }
/* .postcard__more also carries margin-top:auto. With a trainer above it the two
   would split the slack between them and the footer would float; this hands all
   of it to the trainer line. */
.postcard--course .postcard__more { margin-top: 0; }

/* ── The search bar under the header image ─────────────────────
   A slim band in Rene's blue holding one white pill. It sits directly beneath
   the header photograph, which is what it is for: it anchors the image and
   gives the top of every page the same shape, and its blue answers the footer
   at the other end. */
.searchbar { background: var(--c-rene); padding-block: var(--s-3); }

/* At the end of an article the bar butts straight onto the footer, which is
   the same blue — the two merged into one block and the bar lost its edge. A
   hairline is enough to say they are two things. */
.searchbar--end { border-bottom: 1px solid rgba(255, 255, 255, 0.28); }
.searchbar__pill {
  display: flex; align-items: center; gap: 8px;
  max-width: 62rem; margin-inline: auto;
  padding: 6px 6px 6px 26px;
  background: #fff; border-radius: 999px;
  box-shadow: 0 2px 10px rgba(7, 59, 80, 0.10);
}
.searchbar__input {
  flex: 1 1 auto; min-width: 0;
  padding: 12px 0; border: 0; background: transparent;
  font-family: var(--font-sans); font-size: clamp(16px, 1.7vw, 19px); color: var(--c-ink);
}
.searchbar__input::placeholder { color: var(--c-muted); opacity: 1; }
.searchbar__input:focus { outline: none; }
.searchbar__input::-webkit-search-cancel-button { -webkit-appearance: none; }
.searchbar__pill:focus-within { box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.55), 0 2px 10px rgba(7, 59, 80, 0.10); }

.searchbar__go {
  flex: none; padding: 13px 34px;
  border: 0; border-radius: 999px;
  background: var(--c-rene); color: #fff; cursor: pointer;
  font-family: var(--font-sans); font-size: var(--t-small); font-weight: 700;
  transition: background 0.18s var(--ease-out);
}
.searchbar__go:hover { background: var(--c-rene-deep); }
.searchbar__go:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }

@media (max-width: 600px) {
  .searchbar__pill { padding-left: 18px; }
  .searchbar__go { padding: 12px 20px; }
}

/* ─── Paging a long listing ───────────────────────────────────
   His blog runs to 270 articles over 23 pages of twelve. Numbers rather than an
   infinite scroll: a visitor who lands on page 14 from Google can see where they
   are, and so can a crawler.

   Twelve because the grid is auto-fill and lays out in two, three or four
   columns depending on the screen. Ten divided by none of them and left the last
   row of every page a single card on its own. */
.pagination {
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: center;
  margin-top: var(--s-5);
}
.pagination .page-numbers {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding: 0 14px;
  border: 1px solid var(--c-line); border-radius: var(--r-pill);
  color: var(--c-ink); text-decoration: none; font-weight: 600; font-size: 15px;
  transition: background 0.18s var(--ease-out), border-color 0.18s var(--ease-out);
}
.pagination .page-numbers:hover { border-color: var(--c-brand); color: var(--c-brand-dark); }
.pagination .page-numbers.current {
  background: var(--c-brand); border-color: var(--c-brand); color: #fff;
}
.pagination .page-numbers.dots { border-color: transparent; }

/* ─── His page, as one document ───────────────────────────────
   A prose row is a slice of a page of his, cut at his own headings so Rene can
   move the pieces. Between two of them a full band's padding reads as a join
   that is not in his page, so the rhythm here is the gap between sections of an
   article rather than between bands of a design. The rule above already zeroes
   the top of a paper band following a paper band; this sets what is left. */
.section--prose { padding-block: var(--s-4); }
