/* Vertorbis — vertorbis.com
   Palette and its rules: Vertorbis-Brand/COLOUR-CATALOGUE.md
   Light and dark are exact channel opposites; each pair sums to 255.
   Surface tints are derived neutral (R=G=B), which the catalogue sanctions.
   ACCENT RULE: Signal #95F8F0 measures 1.06:1 on Paper — invisible. It is therefore
   declared as --signal and used ONLY inside .contact, where it is 15.15:1. On the light
   page the accent role falls to Deep. Accent is used ONLY for rules,
   borders, bullets and underlines - never as a text colour. No darkened variant of it
   was invented either; that would be a third brand colour. Text is always --ink.

   BORDERS USE A BRAND COLOUR, not a derived grey. --line is Graphite Light Inverse
   (--line #D3CECB) on light and (#3E4345) on dark - both derived from Deep and Paper, that
   --ink-soft uses, with the roles swapped. In light theme the border is the pale half and
   the text the dark half; in dark theme they exchange. No separate border grey is needed,
   and it reads slightly stronger than the one it replaced (1.57 vs 1.37 on white).

   DERIVED UI GREYS - exactly two, and no more. surface and muted are NOT brand colours;
   the catalogue permits derived greys provided they stay neutral (R=G=B). Two further
   rules apply on top:
     1. each is paired at 255 - v across themes, like every brand colour  f6/09, 5a/a5
     2. a grey must earn its place. Anything a brand colour can do, a brand colour does.
        A second surface tint and a border grey were both deleted on that test.
   They are catalogued in Vertorbis-Brand/COLOUR-CATALOGUE.md under "Derived UI greys". */

:root {
  --ink:        #0B1317;   /* Deep */
  --ink-soft:   #515456;   /* Deep 30% toward Paper — 6.54:1, still AA */
  --accent:     #95F8F0;   /* Signal. 1.06:1 on Paper — almost no LUMINANCE contrast, but a
                              155° hue difference, so it reads as colour rather than as
                              contrast. That is fine for ornament: rules, bullets, underlines,
                              left borders. It is NOT fine for anything that must be noticed,
                              which is why --focus exists. NEVER text. */
  --focus:      #0B1317;   /* Deep. Focus and hover states only. WCAG 2.4.11 wants a focus
                              indicator at 3:1 minimum; Signal cannot do that on Paper, so
                              the three focus rules below take this instead of --accent. */
  --bg:         #F4ECE8;   /* Paper — the page */

  --surface:    #EAE2DF;   /* Deep 95.5% toward Paper — tiles, 1.10:1 */
  --line:       #D3CECB;   /* Deep 86% toward Paper — borders, 1.34:1 */
  --muted:      #515456;   /* same value as --ink-soft; both names are in use */
  --shadow:     0 1px 2px rgba(11,19,23,.05), 0 8px 24px rgba(11,19,23,.06);
  --f-brand: "Montserrat", "Segoe UI", system-ui, sans-serif;
  --f-body:  ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --wrap: 68rem;

  /* ONE token for vertical rhythm. Every block contributes this on its top and bottom
     edges, so every boundary on the page is exactly twice it — sections, the hero, the
     footer alike. Change this and the whole page re-spaces consistently.
     Do not set a one-off padding on a single block: two adjacent paddings ADD UP, which
     is how the page came to have a 160px gap in one place and 116px in another. */
  --space-block: clamp(1.75rem,3.9vw,3.5rem);   /* 56px at 1470 — between blocks */
  --space-tight: clamp(1.25rem,2.75vw,2.5rem);  /* 40px at 1470 — inside a block */
}

/* Montserrat, SIL Open Font License 1.1 — licence at assets/Montserrat-OFL.txt, which the
   OFL requires to travel with the font. Self-hosted: no third-party request and no GDPR
   question about an EU-hosted site leaking visitor IPs. Subset to Latin + punctuation.
   Chosen because its V has ZERO curve segments — the logo is that V with a Y removed, and
   a rounded terminal cannot produce a sharp tick. */
@font-face { font-family:"Montserrat"; src:url("assets/montserrat-500.woff") format("woff");
             font-weight:500; font-display:swap; }
@font-face { font-family:"Montserrat"; src:url("assets/montserrat-600.woff") format("woff");
             font-weight:600 700; font-display:swap; }

*,*::before,*::after { box-sizing:border-box; }
html { scroll-behavior:smooth; -webkit-text-size-adjust:100%;
       /* the header is sticky, so anchored jumps must stop clear of it */
       scroll-padding-top:5rem; }
@media (prefers-reduced-motion:reduce){ html{scroll-behavior:auto;} *{transition:none!important;animation:none!important;} }
body {
  margin:0; background:var(--bg); color:var(--ink);
  font-family:var(--f-body); font-size:clamp(1rem,.96rem + .2vw,1.125rem);
  line-height:1.65; -webkit-font-smoothing:antialiased;
}
h1,h2,h3 { font-family:var(--f-brand); font-weight:500; line-height:1.15; letter-spacing:-.01em; margin:0; }
h1 { font-size:clamp(2.1rem,1.4rem + 3.4vw,4rem); letter-spacing:-.02em; }
h2 { font-size:clamp(1.6rem,1.2rem + 1.8vw,2.5rem); }
h3 { font-size:1.15rem; font-weight:500; }
p  { margin:0 0 1rem; }
/* Links carry ink for legibility and the accent only as the underline. This is the
   catalogue's own rule: electric is 1.89:1 on white and must never colour text. */
a  { color:var(--ink); text-decoration-color:var(--ink);
     text-underline-offset:.2em; text-decoration-thickness:.12em; }
a:hover { text-decoration-thickness:.2em; }
/* Focus ring uses ink, not accent: the accent is 1.89:1 on white, which would give
   keyboard users a focus indicator that fails WCAG 2.4.11. Ink is 17:1 / 16:1. */
:focus-visible { outline:3px solid var(--ink); outline-offset:3px; border-radius:3px; }

.wrap { width:min(100% - 2.5rem, var(--wrap)); margin-inline:auto; }
.skip { position:absolute; left:-9999px; }
.skip:focus { --ink:#F4ECE8; --ink-soft:#F4ECE8; --muted:#F4ECE8; --bg:#0B1317; --surface:#0B1317; --line:#3E4345; --focus:#F4ECE8;
              left:1rem; top:1rem; z-index:99; background:var(--bg); color:var(--ink);
              padding:.7rem 1rem; border:2px solid var(--ink); border-radius:6px; }

/* ---------- header ---------- */
/* THE HEADER IS DEEP. It takes the dark token set like any other Deep container, so the
   translucent color-mix resolves over Deep rather than Paper, and Signal works in it.
   color:var(--ink) is declared for the same reason it is on .card — descendants that
   inherit rather than declare would otherwise keep the page's Deep text. */
header { --ink:#F4ECE8; --ink-soft:#F4ECE8; --muted:#F4ECE8; --bg:#0B1317; --surface:#0B1317; --line:#3E4345; --focus:#F4ECE8;
         position:sticky; top:0; z-index:20;
         background:var(--bg);
         backdrop-filter:blur(10px); border-bottom:1px solid var(--line);
         color:var(--ink); transition:background-color .2s ease; }
/* At rest the header is SOLID Deep, so it is the same #0B1317 as every tile and as the
   logo disc that sits on it - at the top of the page there is nothing behind it worth
   showing through anyway. The frosted state is switched on only once content is actually
   passing underneath, which is the only moment the blur means anything.
   88% Deep over Paper paints #272D30, so the bar visibly lightens on scroll; that is the
   intended signal that the header is now floating over content, not a colour change.
   backdrop-filter stays on in BOTH states on purpose: it is inert behind an opaque
   background, and toggling it would pop the blur in rather than let it fade.
   [data-stuck] is set by the observer in the page script - see "Sticky header state". */
header[data-stuck] { background:color-mix(in srgb,var(--bg) 88%,transparent); }
@media (prefers-reduced-motion:reduce){ header { transition:none; } }
/* No backdrop-filter means no frosting to earn the translucency, so stay solid. */
@supports not (backdrop-filter:blur(1px)) { header, header[data-stuck] { background:var(--bg); } }
.bar { display:flex; align-items:center; gap:1rem; padding:.85rem 0; }
.brand { display:flex; align-items:center; gap:.6rem; text-decoration:none; color:var(--ink); margin-right:auto; }
.brand svg { display:block; height:1.9rem; width:auto; }
.brand .name { font-family:var(--f-brand); font-weight:500; font-size:1.2rem; letter-spacing:.01em; }
/* Same rule the nav links follow: on Deep, Signal is 15.15:1, so a hover can simply BE the
   accent. Set on the ANCHOR, not on .name: the disc's ring is stroke="currentColor" and the
   wordmark has no colour of its own, so one declaration moves both together. The ring AND
   the bolt are currentColor; only the disc fill stays literal Deep, so the mark keeps its
   shape and ground while everything drawn on it moves as one.
   :focus-visible is here for the same reason it is on nav a: this is the first tab stop on
   the page after the skip link, and it must show where focus is. */
.brand:hover, .brand:focus-visible { color:var(--accent); }
nav { display:flex; gap:1.4rem; align-items:center; }
/* The header is Deep, so Signal is 15.15:1 here and the label can simply turn accent on
   hover. The Deep hover chip this used to need is gone — it would now be the same colour
   as the header behind it. The padding stays for the hit target. */
nav a  { color:var(--ink-soft); text-decoration:none; font-size:.95rem;
         padding:.34rem .6rem; margin:-.34rem -.6rem; border-radius:6px; }
nav a:hover, nav a:focus-visible { color:var(--accent); text-decoration:none; }
/* The section the visitor is currently in. Same accent as hover, deliberately: the header
   is Deep, Signal is 15.15:1 on it, and inventing a third nav colour to distinguish
   "current" from "hovered" would say less, not more - you can only hover one link at a
   time and the pointer is on it. aria-current is set by the scroll observer in the page
   script; it is an attribute rather than a class so the state is announced, not just
   painted. */
nav a[aria-current] { color:var(--accent); }
@media (max-width:44rem){ nav .hide-sm{display:none;} }

/* ---------- hero ---------- */
/* Generous headroom above the wordmark: the mark is the first thing seen and it
   needs air between it and the sticky header, not a tight join. */
/* Hero bottom is intentionally small. The section below contributes its own top padding,
   and the two ADD UP - so a generous value here reads as a hole rather than as breathing
   room. Keep the pair in mind whenever either is changed. */
.hero { padding:var(--space-tight) 0 var(--space-block); }
.hero .mark-lg { width:min(72%,26rem); height:auto; display:block; margin:0 0 2.2rem; }
.eyebrow { font-family:var(--f-brand); font-size:.82rem; letter-spacing:.14em; text-transform:uppercase;
           color:var(--muted); margin:0 0 1rem; display:flex; align-items:center; gap:.6rem; }
.eyebrow::before { content:""; width:1.6rem; height:3px; background:var(--ink); border-radius:2px; }
/* On a phone the eyebrow cannot hold one line - "MICROSOFT 365 · ROBOTIC PROCESS AUTOMATION"
   is about 374px of tracked uppercase in roughly 350px of usable width - so the question is
   only how it breaks. Left alone it orphaned the last word on a line of its own, and because
   the rule is a flex row with align-items:center, the accent dash floated against the middle
   of the two-line block instead of sitting on the first line, reading as detached from it.
     display:block puts the dash inline at the head of line one, where it belongs, and lets
     the text wrap underneath it normally.
     text-wrap:balance evens the two lines rather than leaving one word stranded; browsers
     without it simply wrap as before, so it costs nothing.
     The size and tracking come down a little, which is what stops it running to three lines
     on the narrowest phones. */
@media (max-width:44rem){
  .eyebrow { display:block; font-size:.74rem; letter-spacing:.1em; text-wrap:balance; }
  /* Break BETWEEN the segments, never inside one. Balanced wrapping alone still split
     "ROBOTIC PROCESS AUTOMATION" across two lines, which reads worse than an uneven pair of
     lines does: the eyebrow is a list of things separated by "·", and the only sensible
     break is at a separator. Each segment is nowrap, and the separator is carried at the END
     of the segment it follows (tied to it with &nbsp;) so a wrap lands AFTER the dot rather
     than orphaning it onto the start of the next line. */
  .eyebrow .eb { white-space:nowrap; }
  .eyebrow::before { display:inline-block; vertical-align:middle;
                     margin:0 .6rem .2rem 0; width:1.4rem; }
}
.lede { font-size:clamp(1.08rem,1rem + .5vw,1.3rem); color:var(--ink-soft); max-width:46ch; margin-top:1.4rem; }
.cta-row { display:flex; flex-wrap:wrap; gap:.9rem; margin-top:2.2rem; align-items:center; }
.btn { display:inline-block; font-family:var(--f-brand); font-weight:500; font-size:1.02rem;
       padding:.85rem 1.6rem; border-radius:8px; text-decoration:none; border:2px solid transparent; }
/* THE 10% LANE. 60-30-10 reserves the accent lane for what must be noticed, and the
   primary call to action is the canonical example. This button was Ground Soft: 1.13:1
   against the page and visually identical to the outline button beside it, which meant the
   page had a primary action nobody could pick out. Solid ink is 17:1. */
.btn-primary { background:var(--ink); color:var(--bg); border-color:var(--ink); }
/* Hover darkens to the next value DOWN THE PALETTE, not to an invented tint.
   #D3CECB is Deep mixed 86% toward Paper - derived, not invented. */

/* In-card button. The card already supplies the dark token set, so this only needs the
   shape - and a smaller one than the page-level buttons, which would crowd a tile. */
.btn-card { font-size:.94rem; padding:.6rem 1.1rem; border-radius:8px;
            background:transparent; border-color:var(--line); color:var(--ink); }
/* FILLS with the accent, like .btn-accent and like the catalogue's action tile - the site
   has one hover treatment for a call to action and this button was the exception, tinting
   its text and edge while everything else lit up.
   color:var(--bg), NOT var(--ink). --ink on a .card is Paper, and Paper on Signal is 1.06:1
   - the label would vanish at the moment you hovered it. --bg is the card's Deep, and Deep
   on Signal is 15.15:1, the same pair inverted. The border tracks the fill because this
   button sits ON the Deep card, where the accent edge is already 15.15:1 against its
   surroundings; the catalogue tile has to do the opposite only because it sits on Paper. */
.btn-card:hover, .btn-card:focus-visible {
  background:var(--accent); color:var(--bg); border-color:var(--accent); }

.btn-ghost { --ink:#F4ECE8; --ink-soft:#F4ECE8; --muted:#F4ECE8; --bg:#0B1317; --surface:#0B1317; --line:#3E4345; --focus:#F4ECE8;
            background:var(--surface); border-color:var(--line); color:var(--ink); }

/* SIGNAL RULE: #95F8F0 is 1.06:1 on Paper. Every element that carries it therefore sits
   in its own Deep container, so the accent is always on the ground it was made for.
   Components that cannot have a container — underlines, bullets, the eyebrow rule — use
   Deep instead. There is no third case. */
.assure { --ink:#F4ECE8; --ink-soft:#F4ECE8; --muted:#F4ECE8; --bg:#0B1317; --surface:#0B1317; --line:#3E4345; --focus:#F4ECE8;
          margin-top:1.6rem; font-size:.95rem; background:var(--bg); color:var(--muted);
          border-left:3px solid var(--accent); border-radius:0 6px 6px 0;
          padding:.85rem 1rem .85rem .9rem; max-width:44ch; }

/* ---------- sections ---------- */
/* One ground throughout: every band is Ground Light. Sections are separated by a neutral
   hairline rather than by a change of colour.
   NOTE FOR ANYONE EDITING .card: with the page Paper and the card Paper, an odd card is
   white-on-white and its BORDER is the only thing that defines it. Do not remove it. The
   every-second card is #000000 and does not depend on a border. */
section { padding:var(--space-block) 0; border-top:1px solid var(--line); }
.sec-head { max-width:52ch; margin-bottom:2.6rem; }
.sec-head p { color:var(--ink-soft); margin-top:1rem; }
.grid { display:grid; gap:1.4rem; }
@media (min-width:48rem){ .grid.two{grid-template-columns:1fr 1fr;} .grid.three{grid-template-columns:repeat(3,1fr);} }
/* TILES ARE DEEP. A tile is a container in its own right, so it takes the dark token set
   and Signal works inside it — bullets included. Text inside a tile is Paper.

   THE color:var(--ink) BELOW IS LOAD-BEARING. Overriding --ink here does NOT change text
   that merely INHERITS its colour: body already resolved color:var(--ink) to Deep, and a
   custom-property override does not re-run that computation for descendants. Without an
   explicit colour on the container, every h3, summary and untouched <p> inside a tile
   inherits Deep and renders invisible on a Deep ground. The kicker and the bullets stayed
   visible only because they declare var(--muted) and var(--ink-soft) themselves. */
.card a { text-decoration:none; }
.card h3 a { color:var(--ink); }
.card { --ink:#F4ECE8; --ink-soft:#F4ECE8; --muted:#F4ECE8; --bg:#0B1317; --surface:#0B1317; --line:#3E4345; --focus:#F4ECE8;
        background:var(--surface); color:var(--ink);
        border:1px solid var(--line); border-radius:12px;
        padding:1.7rem 1.6rem; box-shadow:var(--shadow); }
/* Card headings take the accent. Safe wherever .card goes, because .card supplies its own
   Deep token block above - Signal is 15.15:1 on Deep, AAA - which is exactly the condition
   the rail marker on Paper could not meet. If a light .card variant is ever added, this
   needs revisiting: Signal on Paper is 1.06:1.
   The body copy deliberately does NOT follow. A whole tile in the accent reads as a
   highlighted block rather than as a heading doing its job. */
.card h3 { margin-bottom:.3rem; color:var(--accent); }
/* NOT scoped to .card. The same label appears in .card tiles and in the #which .opt tiles,
   and it was written twice - once here and once under a different class name for an
   identical element - with the two copies drifting to different margins (.7rem vs .55rem).
   One class, one rule. #catalogue overrides it with a smaller variant, deliberately. */
.kicker { font-family:var(--f-brand); font-size:.78rem; letter-spacing:.12em; text-transform:uppercase;
          color:var(--muted); display:block; margin-bottom:.7rem; }
.card ul { list-style:none; padding:0; margin:1rem 0 0; }
.card li { position:relative; padding-left:1.4rem; margin-bottom:.6rem; font-size:.98rem; color:var(--ink-soft); }
.card li::before { content:""; position:absolute; left:0; top:.62em; width:.5rem; height:.5rem;
                   border-radius:50%; background:var(--accent); }
.card li b { color:var(--ink); font-weight:600; }
/* The rule inside a tile is the accent, not a border colour: it sits on Deep, where
   Signal is 15.15:1, and it is the one separator on the page that has a dark ground to
   sit on. --line stays the border colour for the page's own rules (header, section,
   footer), which are on Paper and cannot use Signal. */
.note { margin-top:1.2rem; padding-top:1.1rem; border-top:1px solid var(--accent);
        font-size:.92rem; color:var(--muted); }
.note > p { margin:0 0 .9rem; }
.note > .btn { margin-top:0; }

/* Align the accent rule ACROSS the two #services tiles.
   The tiles hold different amounts of copy, so each .note started wherever its own text
   happened to end and the two accent lines sat at different heights - close enough to read
   as a mistake rather than as a design.
   Neither obvious fix works. Bottom-aligning the notes (margin-top:auto) aligns their
   BOTTOMS, and the notes are one line and three lines tall, so the rules end up further
   apart, not closer. Equal heights cannot give aligned tops and aligned bottoms at once
   unless the notes are the same height, which reflowing text will not promise.
   Subgrid does it properly: the two tiles opt into the SAME three rows, so the note row has
   one height and one starting line for both. The body row takes 1fr and absorbs the
   difference in copy length, which is what the reader should not notice.
   Both tiles must keep exactly three children (h3, body, note) for span 3 to hold. Add a
   paragraph to one and the rows stop corresponding - put it inside the existing body
   paragraph's row or update the span here. It was four while the tiles carried a kicker;
   removing that meant changing BOTH numbers below, or the tiles would have kept reserving
   a row that nothing occupied and the accent rules would have drifted apart again - the
   exact fault this block exists to fix.
   @supports because a browser without subgrid must fall back to normal flow, which is
   today's behaviour: slightly misaligned rules, never a broken layout. */
@supports (grid-template-rows: subgrid) {
  @media (min-width:48rem){
    #services .grid.two { grid-template-rows:auto 1fr auto; }
    #services .grid.two > .card { display:grid; grid-row:span 3; grid-template-rows:subgrid;
                                 row-gap:0; }
    /* Margins do not collapse between grid items, so the body paragraph's own bottom margin
       would ADD to the note's top margin and open the gap the rule sits in. The note keeps
       its 1.2rem; the paragraph gives up its 1rem. */
    #services .grid.two > .card > p { margin-bottom:0; }

    /* Same treatment for the three-tile grids, with one difference: they do NOT all have
       a note. The first tile is kicker + h3 + list, the other two add a note. It still spans
       four rows - its fourth is simply empty, and the tile's background stretches over it
       because a grid item stretches by default. That is why the span is 4 and not "however
       many children this tile happens to have": the rows have to correspond ACROSS the
       tiles, not within one.
       The list row takes 1fr, so four bullets and five bullets both leave the note starting
       on the same line.
       NOT scoped to #services, unlike .grid.two above: the three-tile grids moved onto the
       service pages, which have no #services section, and the scoped selector silently
       matched nothing for as long as they lived there. */
    .grid.three { grid-template-rows:auto auto 1fr auto; }
    .grid.three > .card { display:grid; grid-row:span 4; grid-template-rows:subgrid;
                          row-gap:0; }
    /* h3's .3rem and the list's 1rem collapse to 1rem in normal flow and would ADD to 1.3rem
       as grid items. Drop the h3's so the spacing is the same in both paths. */
    .grid.three > .card > h3 { margin-bottom:0; }
  }
}
/* ---------- the process rail (#method) ----------
   A numbered rail rather than a plain list: five steps that hand back and forth between the
   client and us have to LOOK sequential, and a bulleted list does not say "then".
   The node is Deep with a Signal numeral, and that is the ONLY place the accent can appear
   in this section. The steps sit on Paper, where Signal is 1.06:1, so the accent rule that
   .note carries inside the dark tiles would be invisible here. The node gives the accent a
   dark ground to sit on, which is the rule the whole palette follows.
   The connector is ONE pseudo-element carrying two mask layers - a hairline, and a chevron
   pinned to its bottom - so the line and its arrowhead cannot drift apart at any step
   height, and both take their colour from background:var(--ink-soft) instead of from a hex
   baked into the SVG. Same masking technique as the catalogue marker, for the same reason:
   the palette will move and the artwork should follow it. */
.process { list-style:none; margin:0; padding:0; counter-reset:pstep; }
/* Collapsed, the five titles want to sit close together; open, the prose needs room.
   The node is 2.9rem and the summary is shorter than that, so without a matching min-height
   on the summary the node would overhang into the gap and collide with the next step. */
.pstep { counter-increment:pstep; position:relative;
         display:grid; grid-template-columns:2.9rem 1fr; column-gap:1.3rem;
         padding-bottom:1.9rem; }
.pstep:has(details[open]) { padding-bottom:var(--space-tight); }
.pstep:last-child { padding-bottom:0; }
.pstep > * { grid-column:2; }
/* One node, two lists. #method counts pstep, #which counts step; everything visual is
   shared so a change to the circle cannot land on one section and miss the other. */
.pstep::before, .step::before {
                 width:2.9rem; height:2.9rem; display:grid; place-items:center;
                 border-radius:50%; background:var(--ink); color:var(--accent);
                 font-family:var(--f-brand); font-weight:500; font-size:1.05rem;
                 font-variant-numeric:tabular-nums; }
.pstep::before { content:counter(pstep);
                 grid-column:1; grid-row:1 / -1; align-self:start; }
/* Each step is a disclosure: the five titles read as a scannable list, and the detail is
   there for whoever wants it. NOT an exclusive accordion like the catalogue - this is a
   sequence, and someone comparing what they do in step 3 against what happens in step 4
   should be able to hold both open. The catalogue is a menu, where exclusivity keeps the
   page still; a process is not.
   The h3 stays inside the summary so the document outline is unchanged - collapsing content
   should not cost the section its headings.
   Marker is a Deep chevron, not the accent: this sits on Paper. It rotates rather than
   swapping glyph, which is the same convention the catalogue marker uses. */
/* The chevron sits NEXT TO the title, not at the far right. grid-template-columns:1fr auto
   pinned it to the right edge of a full-width row, which on a wide screen left it marooned
   several hundred pixels from the words it belongs to and reading as page furniture rather
   than as this step's control. flex + gap puts it where the eye already is. The summary is
   still a block, so the click target remains the whole row. */
.pdet summary { list-style:none; cursor:pointer; min-height:2.9rem;
                display:flex; align-items:center; gap:.7rem; }
.pdet summary::-webkit-details-marker { display:none; }
.pdet summary h3 { margin:0; }
.pdet summary::after { content:""; width:.86em; height:.58em; background:var(--ink);
                       flex:0 0 auto; align-self:center; transition:transform .18s ease;
                       -webkit-mask:url("data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20viewBox=%270%200%2012%208%27%3E%3Cpath%20d=%27M1.6%201.8%20L6%206.2%20L10.4%201.8%27%20fill=%27none%27%20stroke=%27%23000%27%20stroke-width=%272%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27/%3E%3C/svg%3E") no-repeat center/contain;
                               mask:url("data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20viewBox=%270%200%2012%208%27%3E%3Cpath%20d=%27M1.6%201.8%20L6%206.2%20L10.4%201.8%27%20fill=%27none%27%20stroke=%27%23000%27%20stroke-width=%272%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27/%3E%3C/svg%3E") no-repeat center/contain; }
.pdet[open] summary::after { transform:rotate(180deg); }
.pbody { padding-top:.9rem; }
/* Hover inverts the numbered node - Signal fill, Deep numeral. The one hover on this section
   that can carry the accent at all, because the node is the only Deep ground here and the
   inversion is the same move the catalogue's CTA tile makes. :has() so hovering anywhere on
   the summary lights the node; without :has() support nothing happens, which is fine. */
/* .step as well as .pstep. The two share their node styling at the top of this block, and
   #which is built from the same <details> component as the process rail, so its numbers
   were the only ones on the site that stayed inert on hover - which reads as those steps
   not being interactive rather than as a deliberate difference. */
.pstep:has(summary:hover)::before,  .step:has(summary:hover)::before,
.pstep:has(summary:focus-visible)::before, .step:has(summary:focus-visible)::before
  { background:var(--accent); color:var(--ink); }
.pstep::before, .step::before { transition:background-color .16s ease, color .16s ease; }
@media (prefers-reduced-motion:reduce){ .pdet summary::after { transition:none; } }
.pstep h3 { margin-bottom:.7rem; }
.pstep p { max-width:60ch; }
@media (max-width:38rem){
  .pstep { grid-template-columns:2.3rem 1fr; column-gap:.9rem; }
  .pstep::before { width:2.3rem; height:2.3rem; font-size:.95rem; }
          .pdet summary { min-height:2.3rem; }
}

/* ---------- solutions catalogue ---------- */
/* The catalogue reuses .qa rather than .card: the automation GOAL is the summary and the
   detail sits inside, so the section collapses to seven lines instead of seven tiles.
   Same reason the FAQ uses <details> and not JavaScript - the eighteen solution names stay
   in the DOM for crawlers while collapsed. These rules only add what .qa lacks (a kicker
   and a list); everything else is inherited, so the two accordions stay visually identical. */
#catalogue .qa .kicker { display:block; margin:.85rem 0 0 1.4rem;
                         font-family:var(--f-brand); font-size:.72rem; letter-spacing:.1em;
                         text-transform:uppercase; color:var(--muted); }
#catalogue .qa ul { list-style:none; padding:0; margin:.8rem 0 0 1.4rem; }
#catalogue .qa li { position:relative; padding-left:1.15rem; margin-bottom:.45rem;
                    font-size:.94rem; color:var(--ink-soft); }
#catalogue .qa li::before { content:""; position:absolute; left:0; top:.6em;
                            width:.4rem; height:.4rem; border-radius:50%; background:var(--accent); }
#catalogue .qa li b { color:var(--ink); font-weight:600; }

/* ---------- contact ---------- */
/* Padded with --space-tight, the same as the footer: both are blocks of WORDING rather
   than gaps between things, so they get equal space above and below their text. The
   separating work is done by the band's own colour change and by the footer's border.
   Sections use --space-block for separation; content blocks use --space-tight inside.

   The contact block is a SECTION, not a tile. Tiles are the things that carry a frame,
   rounded corners and a hover state; a section is a band of the page. So this takes the
   same neutral hairline the section breaks use, square corners, and no accent - the accent
   is reserved for things that respond to the pointer. */
/* THE 30% LANE. 60-30-10 wants a secondary field carrying roughly a third of the surface.
   This page had none: white page, white-ish tiles, graphite present only as thin text -
   closer to 95-4-1 than 60-30-10. One dark band at the conversion moment supplies it.
   The band is Deep #0B1317 - the palette's dark ground, and the only place Signal works.
   13:1 against the page, so it is unmistakably separate while carrying the least weight of
   Paper on Deep is 16.08:1.
   It also gives the accent the only surface where it performs: Signal is 1.06:1 on Paper
   but 6.87:1 here, so the email address and the rules finally carry weight.
   NOTE --line is Graphite Light Inverse inside this band, not Graphite Light: the band IS
   Graphite Light, so a border of the same value would be invisible.
   Ground Soft was considered and rejected - 1.13:1 against the page is a seam, not a field,
   and tiles sitting on it would vanish. The accent was rejected too: a full-width band is
   many times the 10% the accent lane allows, cyan is only 1.89:1 against white so it makes
   a weak field anyway, and the accent would then sit at 1.00:1 against itself.
   The tokens below are the palette's dark half - light and dark are exact channel
   opposites, so this is in-brand by construction, not a second theme. */
.contact {
  --ink:     #F4ECE8;   /* Paper */
  --ink-soft:#B7B4B2;   /* Paper 26% toward Deep — 9.10:1 */
  --muted:   #B7B4B2;
  --bg:      #0B1317;   /* Deep */
  --surface: #0B1317;
  --line:    #3E4345;   /* Deep 22% toward Paper — 1.87:1 */
  --accent:  #95F8F0;   /* Signal. 15.15:1 on Deep — here it is a real accent, not an ornament. */
  --focus:   #F4ECE8;   /* Paper. On a dark ground the focus ring inverts. 16.08:1. */
  background:var(--bg); color:var(--ink);
  border:0; border-radius:0;
  padding:var(--space-tight) clamp(2rem,4vw,3.2rem);
}
/* NOTE: #contact and .contact are the SAME element - <section id="contact" class="contact">.
   An earlier #contact{padding:0} rule lived here to stop a supposed triple-stack. There was
   no triple-stack: with one element there is only one padding, and the ID rule (1-0-0) was
   simply beating the class rule (0-1-0) and zeroing the band. Do not reintroduce it.
   The padding for this band is declared once, in .contact above. */
#contact > .wrap { width:100%; }
/* Copy on the left, form on the right. The section was a tall stack: a heading, a
   paragraph, then a form running the full width beneath them. Side by side, the reading
   and the doing sit together and the band is roughly half as tall.
   Collapses to a stack below 62rem, where two columns would leave both too narrow. */
.contact > .contact-grid { width:min(100% - 2.5rem, var(--wrap)); margin-inline:auto; }
.contact-grid { display:grid; grid-template-columns:minmax(0,.85fr) minmax(0,1fr);
                gap:1.25rem 3rem; align-items:start; }
@media (max-width:62rem){ .contact-grid { grid-template-columns:1fr; gap:1.75rem; } }
.contact-copy { align-self:start; }
.contact h2 { max-width:15ch; margin-bottom:.7rem; font-size:clamp(1.5rem,1.1rem + 1.4vw,2.1rem); }
.contact p { color:var(--ink-soft); max-width:42ch; }

/* ---------- enquiry form ----------
   Lives inside the dark contact band, so controls take the band's own tokens. Inputs sit on
   Ground Light: a filled field on a dark ground is the clearest possible affordance, and it
   puts the typed text at 17:1 rather than asking someone to read what they typed at low
   contrast. Focus is the ink ring the rest of the page uses, offset so it clears the fill.
   Errors are NEVER colour alone - each carries a written message (WCAG 1.4.1). */
/* Two columns, so the form is a block rather than a column of boxes running down the page.
   Short answers pair off; the one long answer and the submit row span the full width.
   Collapses to a single column below 44rem, where two columns would make each field too
   narrow to type in comfortably. */
.form { margin-top:0; display:grid; grid-template-columns:1fr 1fr;
        gap:.75rem 1rem; max-width:none; align-items:start; }
.field.wide, .form > .btn, .form-status { grid-column:1 / -1; justify-self:start; }
@media (max-width:44rem){ .form { grid-template-columns:1fr; } }
.field { display:grid; gap:.25rem; align-content:start; }
/* The estimate checkbox. accent-color paints the checked box in Electric; browsers pick a
   dark tick against a light accent, so the mark lands around 9:1 rather than the 1.89 a
   white tick on cyan would give. The focus ring stays ink for the same reason it does
   everywhere else on a light control. */
.field.check { grid-template-columns:auto 1fr; column-gap:.6rem; align-items:center; row-gap:.15rem; }
.field.check input { width:1.15rem; height:1.15rem; margin:0; accent-color:var(--accent);
                     cursor:pointer; justify-self:start; }
.field.check input:focus-visible { outline:3px solid var(--ink); outline-offset:2px; }
.field.check label { cursor:pointer; margin:0; }
.field.check .hint { grid-column:2; }

.field label { font-family:var(--f-brand); font-weight:500; font-size:.88rem; color:var(--ink); }
.req { color:var(--accent); }
.field input, .field textarea {
  font:inherit; font-size:1rem; color:#0B1317; background:#F4ECE8;
  border:1px solid #F4ECE8; border-radius:5px; padding:.5rem .7rem; width:100%;
}
.field textarea { resize:vertical; min-height:4.5rem; line-height:1.5; }
.field input::placeholder, .field textarea::placeholder { color:#515456; opacity:.75; }
/* NOT var(--ink-soft): inside .contact that token is #B7B4B2, made for the dark band —
   but the field itself is Paper, so a light placeholder would vanish on it. This is the
   one place in the stylesheet where a light-theme value is correct inside the dark band. */
/* Focus ring is INK, not accent. The fields are white and the accent is 1.89:1 on white,
   which fails WCAG 2.4.11 for a focus indicator. Ink on white is 17:1. The accent still
   marks the field via the border, where a low ratio is acceptable. */
.field input:focus-visible, .field textarea:focus-visible {
  outline:3px solid #0B1317; outline-offset:2px; border-color:var(--ink);
}
.hint { font-size:.8rem; color:var(--ink-soft); }
.err  { font-size:.86rem; color:var(--ink); font-weight:600; }
.err::before { content:"\2715  "; color:var(--accent); }
.field.invalid input, .field.invalid textarea { border-color:var(--ink); border-width:2px; }
/* THE ONE ELECTRIC BUTTON ON THE PAGE. 60-30-10 reserves the accent for what must be
   noticed, and this is the page's single most important action. It only works here: on the
   dark band the fill reads 6.87 against the ground with a 9.00 label, whereas on the white
   page the same fill is 1.89 and the button would dissolve into the page. Same brand, and
   the correct answer differs by surface - which is why the hero button stays graphite. */
.form .btn-primary { background:var(--accent); color:#0B1317; border-color:var(--accent);
                     cursor:pointer; white-space:nowrap; padding:.6rem 1.35rem; font-size:.98rem; }
.form .btn-primary:hover, .form .btn-primary:focus-visible {
                     background:#F4ECE8; border-color:#F4ECE8; color:#0B1317; }
/* Smaller than body copy, and sized to its text rather than the column: a one-line
   confirmation stretched across the full grid width read as a banner announcing a problem.
   PAPER is the base, which covers "Sending…" and the error state; only SUCCESS takes the
   accent. Making every state accent would paint a failure in the same celebratory colour as
   a success, and this palette has no red to fall back on - the states are told apart by
   background and glyph, so both have to keep working. */
/* p.form-status, not .form-status. `.contact p { color:var(--ink-soft) }` above is (0,1,1)
   and outranks a bare class, so a plain .form-status inherited Paper-grey body colour and the
   message came out barely legible on its own Paper background. The ok state happened to look
   correct only because its [data-state] selector is (0,2,0) and won by accident - the base and
   error states did not. Adding the element raises this to (0,1,1) and, being later in the
   file, it wins the tie. Also why max-width is reset: .contact p caps at 42ch. */
p.form-status { font-family:var(--f-brand); font-weight:500; font-size:.92rem; max-width:46ch;
               padding:.6rem .9rem; border-radius:6px; margin:.9rem 0 0;
               background:#F4ECE8; color:#0B1317; }
/* color stays Deep on the accent. Paper on Signal is 1.06:1 - the message would vanish at
   the moment it had something to say. Deep on Signal is 15.15:1. */
p.form-status[data-state="ok"] { background:var(--accent); color:#0B1317; }
.form-status[data-state="error"]::before { content:"\2715  "; }
.form-status[data-state="ok"]::before { content:"\2713  "; }
/* honeypot: off-screen rather than display:none, which some bots detect */
.hp { position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }

/* ---------- footer ---------- */
/* The footer is a BLOCK OF WORDING, padded symmetrically like the contact band - but at a
   fraction of the tight token. It carries the least important content on the page and only
   two short lines of it, so the same 40px the contact band uses reads as a large empty
   panel rather than as breathing room. Its border-top does the separating. */
footer { border-top:1px solid var(--line);
         padding:calc(var(--space-tight) * .6) 0;
         color:var(--muted); font-size:.9rem; }
.foot { display:flex; flex-wrap:wrap; gap:1rem 2rem; align-items:center; justify-content:space-between; }
.foot svg { height:1.5rem; width:auto; display:block; }
.etym { font-style:italic; }

/* FAQ accordion. <details> is used rather than JavaScript so the answers are present in
   the DOM for crawlers and for anyone without JS — an FAQ hidden behind a script is an
   FAQ Google may never read, which defeats the point of marking it up as FAQPage. */
.qa { --ink:#F4ECE8; --ink-soft:#F4ECE8; --muted:#F4ECE8; --bg:#0B1317; --surface:#0B1317; --line:#3E4345; --focus:#F4ECE8;
     border:1px solid var(--line); border-radius:12px;
     background:var(--surface); color:var(--ink);
      padding:1.1rem 1.3rem; margin-bottom:.9rem; }
.qa summary { font-family:var(--f-brand); font-weight:500; font-size:1.05rem; cursor:pointer;
              list-style:none; display:flex; align-items:flex-start; gap:.7rem; }
.qa summary::-webkit-details-marker { display:none; }
/* The +/- marker is an affordance, so it needs real contrast. Under the old palette the
   tile was light and the accent was 1.89:1 on it — too weak for a glyph, so this took
   --ink. The tile is Deep now and Signal is 15.15:1 on it, so the constraint is gone and
   the marker can carry the accent. Re-check this if the tile ever goes light again. */
/* NO line-height override. It used to declare 1.35 while the summary text sits on body's
   inherited 1.65, so the glyph's box was shorter than the text's line box and, with
   align-items:flex-start, the + rode high above the question. Inheriting the same 1.65 gives
   both flex items the same line box and lands the glyph on the text's own baseline. */
.qa summary::before { content:"+"; color:var(--accent); font-weight:600; }
.qa[open] summary::before { content:"\2212"; }
.qa p { color:var(--ink-soft); margin-top:.9rem; padding-left:1.4rem; }

/* ---------- interactive frames ----------
   Tiles and buttons sit in a NEUTRAL frame and take the accent only on hover or focus.
   Two reasons this is better than an always-on accent frame:
     it makes the accent mean something - "this responds to you" - instead of decorating
       nineteen static boxes at once;
     it leaves almost no permanent cyan on the page, so the accent reads as a response
       rather than as decoration.
   FOCUS IS INCLUDED DELIBERATELY. A hover-only affordance is invisible to anyone using a
   keyboard, so :focus-within carries the same frame. The separate focus RING stays --ink,
   because the accent is 1.89:1 on white and a focus indicator must be seen. */
.card, .qa { transition:border-color .16s ease; }
.card:hover, .card:focus-within,
.qa:hover, .qa:focus-within { border-color:var(--focus); }
/* Hovering a .qa turns its TITLE accent as well as its border - catalogue and FAQ alike.
   Unscoped on purpose: they are the same component doing the same job, and the accent means
   "this responds to you" everywhere else on the page, so a tile that only moved its border
   was the odd one out. Scoping it to #catalogue would have made two behaviours out of one.
   The title only, never the body copy. Signal is 15.15:1 on Deep and perfectly legible, but
   a whole answer set in it reads as a highlighted block rather than as a hover, and the
   bullets and kickers already carry their own colours for contrast reasons.
   :focus-within, not :focus - .qa is a <details>, so focus lands on the <summary> inside and
   never on the tile itself; without it keyboard users get the border and nothing else.
   The +/- and the Power Automate marker do NOT follow this: .qa summary::before declares
   color:var(--accent) itself, so both are already accent at rest and simply match on hover. */
.qa summary { transition:color .16s ease; }
.qa:hover summary,
.qa:focus-within summary { color:var(--accent); }
.qa[open] { --ink:#F4ECE8; --ink-soft:#F4ECE8; --muted:#F4ECE8; --bg:#0B1317; --surface:#0B1317; --line:#3E4345; --focus:#F4ECE8;
            background:var(--bg); color:var(--ink); }
.btn { transition:color .16s ease, border-color .16s ease, background .16s ease; }
nav a { transition:color .16s ease, background .16s ease; }
/* on .brand, so the wordmark and the disc's currentColor ring fade together */
.brand { transition:color .16s ease; }
/* BOTH BUTTONS HOVER THE SAME WAY. They were diverging: the primary lightened its
   background to --ink-soft while the ghost flipped its border to Paper, so the two sat
   side by side doing different things. And .btn-primary:hover was declared twice — the
   second declaration set border-color to --focus, which resolved to the Deep it already
   had, so it was inert.
   Both buttons rest on Deep, so Signal is 15.15:1 on either. Hover and focus now move the
   TEXT and the border to the accent and leave the background alone. */
/* The tile's treatment in button form: Deep at rest with accent text and an accent edge,
   filling with the accent on hover. Excluded from the shared hover rule below, which sends
   colour to the accent - on this button that is the RESTING colour, so it would do nothing
   at rest and vanish once the fill turns accent. The border tracks the fill for the reason
   given on the tile: accent on Paper is 1.06:1 and cannot carry an edge by itself. */
.btn-accent { --ink:#0B1317; --focus:#0B1317;
              background:var(--ink); color:var(--accent); border-color:var(--accent); }
.btn-accent:hover, .btn-accent:focus-visible {
              background:var(--accent); color:var(--ink); border-color:var(--ink); }

.btn-primary:hover, .btn-primary:focus-visible,
.btn-ghost:hover,   .btn-ghost:focus-visible {
  color:var(--accent);
}

/* Page is Paper #F4ECE8; tiles and buttons are #EAE2DF, Deep mixed 95.5% toward Paper.
   GROUND SOFT is the one value here that is not a brand colour. It is not picked by eye -
   it is the exact midpoint of Ground Light 255 and Graphite Deep Inverse 227, so
   (255 + 227) / 2 = 241. Neutral (R = G = B), and pairs at 255 - v like everything else,
   The dark band inverts the pair: ground Deep, ink Paper.
   It is named for what it IS, a softened white, not for the role it happens to hold - the
   page and the tile have swapped twice already and the name survived both.
   Tiles are defined by their FILL, 1.13:1 against the page, so the border is emphasis and
   the hover frame reads as a response rather than as structure.
   Text does not invert: Deep is 16.08:1 on the page and 14.6:1 on a tile, both AAA.
   Catalogued in Vertorbis-Brand/COLOUR-CATALOGUE.md. A NINTH value is not. */


/* ---------- the funnel ----------
   Three steps, each a fork. The recommended branch is a Deep tile (so Signal can mark it);
   the alternative is a plain Paper block with a --line border. That contrast IS the
   argument: the route we recommend is the solid one, the fallback is the outline.
   Deep tiles need color:var(--ink) declared — see the note on .card for why. */
/* The two questions use the SAME numbered rail as #method. The page had two visual
   languages for "numbered sequence" - a small inline numeral here, Deep circles on a rail
   there - which made one page look like two. The node styling is shared with .pstep::before
   rather than copied, so they cannot drift apart.
   Only two items are numbered now: the third block was the ANSWER, not a question, and it
   has moved out of the list entirely. */
.funnel { list-style:none; margin:0; padding:0; display:grid; gap:var(--space-tight); counter-reset:step; }
.step { counter-increment:step; position:relative;
        display:grid; grid-template-columns:2.9rem 1fr; column-gap:1.3rem; }
.step > * { grid-column:2; }
.step::before { content:counter(step); grid-column:1; grid-row:1 / -1; align-self:start; }
/* the connector has to cross the list's own gap to reach the next node */

.fork { display:grid; grid-template-columns:1fr 1fr; gap:1rem; align-items:stretch; }
/* On a phone the two branches stop being side by side, and side-by-side was the entire
   signal that they are ALTERNATIVES. Stacked, one answer simply follows the other and the
   section reads as continuous prose - a reader gets to the bottom of "Yes" and starts
   reading "Not all of it" as though it were the next paragraph rather than the other half
   of a fork.
   Three things put the either/or back, none of which needs JavaScript or new markup:
     * an OR label at the head of the second branch, so the relationship is stated;
     * the branches are pulled tight together, so they read as one control rather than as
       two sections that happen to be adjacent;
     * tighter padding, because 1.7rem of inset on a 390px screen turns two short answers
       into two screenfuls of scrolling. */
@media (max-width:48rem){
  .fork { grid-template-columns:1fr; gap:.5rem; }
  .opt { padding:1.15rem 1.15rem; }
  /* Inside the tile, not floating between the two: .opt redefines --ink and --muted to
     Paper for its own dark interior, so a label sitting outside on the Paper page would
     resolve to Paper on Paper and vanish. Keeping it within the tile keeps it token-driven
     instead of hardcoding a hex here. */
  .fork > .opt + .opt::before {
      content:"or"; display:block; margin:0 0 .75rem;
      font-family:var(--f-brand); font-size:.7rem; letter-spacing:.16em;
      text-transform:uppercase; color:var(--muted); opacity:.65; }
}

/* DEEP, like every other tile on the site. This is the same token block .card and .qa
   declare, and it is declared rather than inherited for the same reason they do it: the
   page around it is Paper, so a tile that wants light text has to redefine the tokens on
   itself or every child that names --ink resolves it back to Deep.
   It was the light Ground Soft surface until now - a leftover from when this section had a
   recommended branch on Deep and an alternative branch behind it, where the contrast WAS
   the argument. That pairing is gone: both branches are now equal answers to a question, so
   two matching tiles is the honest presentation, and being the site's only light tile just
   read as an inconsistency.
   Padding matches .qa (1.1rem) rather than .card (1.7rem), because these sit inside an
   opened panel and not on the page. No box-shadow and no hover frame: .qa has neither, and
   these tiles are read, not clicked. */
.opt { --ink:#F4ECE8; --ink-soft:#F4ECE8; --muted:#F4ECE8; --bg:#0B1317; --surface:#0B1317;
       --line:#3E4345; --focus:#F4ECE8;
       background:var(--surface); color:var(--ink);
       border:1px solid var(--line); border-radius:12px; padding:1.7rem 1.6rem; }
/* Padding matches .card, not .qa. These are the #services tiles' sibling - same Deep
   surface, same accent heading, same body type - so they get the same inset. At 1.1rem the
   type was identical but the text sat tighter to the edge, which is enough on its own to
   read as a different component. */
/* The kicker is the HEADER here. A .card tile carries a muted label above an accent <h3>,
   so its label is deliberately quiet; an .opt tile has no heading at all, which left it the
   only tile on the site with nothing in the accent and made it read as a different
   component. Accent on Deep is 15.15:1, the same pair .card h3 uses. */
/* The .opt label is that tile's HEADING, so it is set like one - the same treatment the
   #services tiles use for their <h3>: brand face, 1.15rem, weight 500, sentence case,
   accent. It was being styled as a .kicker, which is the small uppercase letter-spaced
   LABEL that sits ABOVE a heading in a .card. That is the wrong role: these tiles have no
   heading under it, so the label was carrying a heading's job in a label's type, and read
   as a different component from every other tile.
   text-transform and letter-spacing are reset explicitly, because .kicker sets both. */
.opt .kicker { font-size:1.15rem; font-weight:500; line-height:1.15; letter-spacing:-.01em;
               text-transform:none; color:var(--accent); margin-bottom:.3rem; }
.opt p { margin:0; color:var(--ink); }          /* body size, as .card's paragraphs are */


.funnel-note { margin-top:var(--space-tight); padding-top:1.1rem;
               border-top:1px solid var(--line); color:var(--ink-soft);
               max-width:62ch; font-size:.97rem; }


/* ---------- the catalogue grid ----------
   Seven groups at full page width left each row a single line of text in a very wide box.
   Scoped to #catalogue so the FAQ, which shares .qa, keeps its full-width list — a long
   question needs the width; a four-word catalogue title does not.
   align-items:start, NOT stretch. Stretch was here to keep every card in a row the same
   height so one- and two-line titles did not look ragged, but it makes each card as tall
   as the tallest in its row — so opening one group inflated its two neighbours into tall
   empty boxes. Card tops stay aligned either way (grid items start at the row edge); all
   that stretch bought was matching BOTTOMS on closed cards, which is worth far less than
   not having two empty boxes next to every open one. */
#catalogue .qa-grid { display:grid; gap:1rem; align-items:start; }
@media (min-width:48rem){ #catalogue .qa-grid { grid-template-columns:1fr 1fr; } }
@media (min-width:72rem){ #catalogue .qa-grid { grid-template-columns:1fr 1fr 1fr; } }
#catalogue .qa { margin-bottom:0; }
#catalogue .qa summary { font-size:1rem; }
/* Catalogue disclosure marker: the Power Automate silhouette in place of +/-.
   Painted as a MASK rather than a background image, so it is filled with currentColor and
   therefore inherits the accent from the rule above and follows any hover colour change --
   a background-image would freeze one colour into the file and drift the day the palette
   moves. Scoped to .page-pa, not #catalogue. It WAS catalogue-only, because the FAQ shared .qa and
   the mark would have claimed those answers were about Power Automate when most were not -
   true while all fifteen questions sat together on the homepage. Splitting the FAQ per page
   ended that: this page's five questions are all Power Automate, so the mark is accurate on
   them and the catalogue no longer needs to be fenced off from its own page's FAQ.
   The homepage and the Python page are unaffected - they keep +/- and the plus.
   Rotating 90deg on open reuses the caret convention, so the affordance still reads as
   "this expands" to someone who does not recognise the silhouette. */
.page-pa .qa summary::before,
#catalogue .qa-cta .cta-title::before {
  content:""; flex:0 0 auto; align-self:flex-start;
  width:.80em; height:.636em;                     /* 97x76 -- height drives, width follows */
  /* Vertical alignment is NOT (line-height - height)/2. That centres the mark on the line
     box, which puts it on the CAP band, and these titles are sentence case -- their visual
     mass sits in the x-height band about 1.5px lower, so a solid mark centred on caps reads
     high. Derived from the shipped Montserrat 500 metrics (asc .968, desc .251, cap .700,
     ALIGNMENT IS FIRST-LINE-BOX CENTRING, NOT A METRIC DERIVATION.
     This was twice computed from Montserrat's cap height and x-height, to sit the mark on
     the midpoint of the cap and x bands: .726em read too high, and re-deriving it for the
     inherited line-height of 1.65 gave .876em, which read too low. The derivation is not
     trustworthy here, because a browser builds the line box from the font's OWN ascent and
     descent (hhea, line gap included), not from the cap and x-height figures, so the band
     it computes is not the band that gets painted.
     Centring the mark on the line box needs no font metrics at all:
       margin-top = (line-height - mark height) / 2 = (1.65 - h) / 2
     1.65 is BODY's line-height, which .qa summary inherits - it declares none.
     margin-top rather than align-self:center on purpose: a summary that wraps to two lines
     must keep its mark on the FIRST line, and centring would drift it to the middle of the
     whole block. Recompute if the line-height, the mark height or the font-size change. */
  margin-top:.507em;                                /* (1.65 - .636) / 2 */
  background:currentColor;
  -webkit-mask:url("data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20viewBox=%270%200%2097%2076%27%3E%3Cpath%20d=%27M%200%204%20A%204%204%200%200%201%204%200%20L%2061.2116%200%20A%204%204%200%200%201%2064.1925%201.3328%20L%2094.6136%2035.3328%20A%204%204%200%200%201%2094.6136%2040.6672%20L%2064.1925%2074.6672%20A%204%204%200%200%201%2061.2116%2076%20L%204%2076%20A%204%204%200%200%201%200%2072%20A%204%204%200%200%201%201.019%2069.3328%20L%2029.0537%2038%20L%201.019%206.6672%20A%204%204%200%200%201%200%204%20Z%27/%3E%3C/svg%3E") no-repeat center/contain;
          mask:url("data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20viewBox=%270%200%2097%2076%27%3E%3Cpath%20d=%27M%200%204%20A%204%204%200%200%201%204%200%20L%2061.2116%200%20A%204%204%200%200%201%2064.1925%201.3328%20L%2094.6136%2035.3328%20A%204%204%200%200%201%2094.6136%2040.6672%20L%2064.1925%2074.6672%20A%204%204%200%200%201%2061.2116%2076%20L%204%2076%20A%204%204%200%200%201%200%2072%20A%204%204%200%200%201%201.019%2069.3328%20L%2029.0537%2038%20L%201.019%206.6672%20A%204%204%200%200%201%200%204%20Z%27/%3E%3C/svg%3E") no-repeat center/contain;
  transition:transform .18s ease; }
.page-pa .qa[open] summary::before { content:""; transform:rotate(90deg); }

/* The last tile is an ACTION, not a disclosure: it links to the brief instead of opening.
   It borrows .qa for the tile chrome and the same silhouette so it belongs to the set, but
   the mark does NOT rotate - nothing expands - and the accent border marks out the one tile
   that goes somewhere. Built as an <a>, not a <details>, so it is reachable by keyboard and
   announced as a link rather than as a collapsed group that never opens. */
/* Filled with the accent rather than Deep: this is the one tile that ACTS, and on Paper an
   accent fill is the loudest thing the palette can say without introducing a new colour.
   Deep on Signal is 15.15:1 - the same pair as Signal on Deep, inverted - so both the title
   and the silhouette (painted with currentColor) stay well clear of AAA. --focus flips to
   Deep for the same reason the dark tiles flip it to Paper: a hover frame has to be visible
   against the ground it sits on, and Paper on Signal is 1.06:1 - invisible.
   THE BORDER IS DEEP, NOT THE FILL COLOUR. Signal on Paper is 1.06:1, and the token's own
   note says that is fine for ornament but NOT for anything that must be noticed - which is
   exactly what a call to action is. Border-coloured to match the fill, this tile would have
   had no discernible edge against the page and failed WCAG 1.4.11 (3:1 for a component
   boundary). Deep on Paper carries the edge at 15.6:1; the accent still does the shouting. */
#catalogue .qa-cta { --ink:#0B1317; --ink-soft:#0B1317; --focus:#0B1317;
                     display:block; text-decoration:none;
                     background:var(--ink); color:var(--accent); border-color:var(--accent);
                     min-height:calc(2.2rem + 3.3rem + 2px); }
/* TWO LINES' WORTH OF HEIGHT, not stretch. Every catalogue title wraps to two lines at the
   three-column width, so a one-line CTA sat visibly short. align-items:stretch would fix it
   and reintroduce exactly what the grid comment above rules out - an open accordion dragging
   its row-mates into tall empty boxes. A floor on this ONE tile is deterministic and cannot
   be affected by what any neighbour does.
     2.2rem  padding      (1.1 top + 1.1 bottom, from .qa)
     3.3rem  two lines    (2 x 1.65 x 1rem - 1.65 is BODY's line-height, inherited, not 1.35)
     2px     borders      (box-sizing is border-box, so they count)
   Recompute if .qa's padding, the title font-size or its line-height change. The text stays
   top-aligned, so its first line still lines up with the neighbours' first line. */
/* color:inherit, NOT var(--ink). The tile inverts on hover by setting color on the <a>,
   but a child that DECLARES its own colour does not follow an ancestor's change - it just
   re-resolves the same token. With color:var(--ink) here the title stayed Deep while the
   fill went Deep underneath it, so hovering blanked the tile to a plain black rectangle.
   Same failure as the tiles that came up empty: overriding a token on the container does
   nothing for text that already declared that token itself. Inheriting makes both states
   fall out of the one rule on the <a>, and the silhouette follows automatically because it
   is painted with currentColor. */
/* NO line-height. .qa summary declares none, so it inherits body's 1.65; declaring 1.35 put
   this title on a different rhythm from its seven neighbours AND shifted the shared
   silhouette, whose margin-top is expressed in em. Matching means declaring exactly what
   summary declares and nothing more. */
#catalogue .qa-cta .cta-title { display:flex; align-items:flex-start; gap:.7rem;
                                font-family:var(--f-brand); font-weight:500; font-size:1rem;
                                color:inherit; }
/* Matches a COLLAPSED tile exactly: .qa supplies the padding, and these are the same three
   values .qa summary carries, so a one-line CTA is the same height as a one-line summary. */
/* Rests as a Deep tile with an accent rule and accent text, and LIGHTS UP to a full accent
   fill on hover. Both states are the same two colours swapped, so each is 15.15:1 whichever
   way round it is, and the silhouette follows because it is painted with currentColor.
   THE BORDER CHANGES WITH THEM. At rest the Deep fill carries the edge against Paper
   (16.08:1) and the accent border is ornament on top; on hover the fill IS the accent, which
   is 1.06:1 on Paper, so the border has to go Deep or the tile loses its boundary entirely
   (WCAG 1.4.11 wants 3:1 for a component edge). Leaving it accent was the tempting mistake. */
#catalogue .qa-cta:hover, #catalogue .qa-cta:focus-visible {
    background:var(--accent); color:var(--ink); border-color:var(--ink); }
@media (prefers-reduced-motion:reduce){ .page-pa .qa summary::before { transition:none; } }
#catalogue .qa .kicker { display:block; margin:.75rem 0 0; padding-left:1.4rem; }
#catalogue .qa ul { list-style:none; padding:0 0 0 1.4rem; margin:.7rem 0 0; }
#catalogue .qa li { font-size:.92rem; color:var(--ink-soft); margin-bottom:.45rem; }


/* ---------- Power Automate page: the process rail's disclosure marker ----------
   The Power Automate silhouette in place of the chevron, matching the catalogue on the
   same page, and rotating 90deg on open for the same reason the catalogue does: it reuses
   the caret convention, so it still reads as "this expands" to someone who does not
   recognise the mark. Scoped to .page-pa - the Python page's rail keeps the plain chevron,
   because the Power Automate mark there would claim that work is Power Automate's.

   IT IS INK, NOT ACCENT, AND THAT IS NOT A CHOICE. The catalogue's marks are accent
   because .qa tiles set their own Deep surface, where Signal is 15.15:1. This rail sits
   directly on Paper, and Signal on Paper is 1.06:1 - the token's own note calls that
   acceptable for ornament and not for anything that must be noticed. A disclosure marker
   is the definition of something that must be noticed, and WCAG 1.4.11 asks 3:1 of a
   component boundary. Ink on Paper is 16.08:1. Do not "fix" this to cyan: it would not
   read as a colour change, it would read as the marker having been deleted.

   Aspect is the mark's own 97x76: height drives at .58em (the chevron's height, so the
   summary row does not change height), width follows at .58 x 97/76 = .74em. */
.page-pa .pdet summary::after {
  width:.74em; height:.58em;
  /* The mark points RIGHT in the file, so rest is rotate(90deg) - pointing DOWN, the
     caret convention for "this expands". Open goes to 270deg, pointing UP, which is the
     180deg flip the plain chevron does and keeps the rotation direction consistent. */
  transform:rotate(90deg);
  /* transform only: the mark stays ink in every state. It sits on Paper, where the
     accent is 1.06:1 and would read as the marker vanishing rather than responding. */
  transition:transform .18s ease;
  -webkit-mask:url("data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20viewBox=%270%200%2097%2076%27%3E%3Cpath%20d=%27M%200%204%20A%204%204%200%200%201%204%200%20L%2061.2116%200%20A%204%204%200%200%201%2064.1925%201.3328%20L%2094.6136%2035.3328%20A%204%204%200%200%201%2094.6136%2040.6672%20L%2064.1925%2074.6672%20A%204%204%200%200%201%2061.2116%2076%20L%204%2076%20A%204%204%200%200%201%200%2072%20A%204%204%200%200%201%201.019%2069.3328%20L%2029.0537%2038%20L%201.019%206.6672%20A%204%204%200%200%201%200%204%20Z%27/%3E%3C/svg%3E") no-repeat center/contain;
          mask:url("data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20viewBox=%270%200%2097%2076%27%3E%3Cpath%20d=%27M%200%204%20A%204%204%200%200%201%204%200%20L%2061.2116%200%20A%204%204%200%200%201%2064.1925%201.3328%20L%2094.6136%2035.3328%20A%204%204%200%200%201%2094.6136%2040.6672%20L%2064.1925%2074.6672%20A%204%204%200%200%201%2061.2116%2076%20L%204%2076%20A%204%204%200%200%201%200%2072%20A%204%204%200%200%201%201.019%2069.3328%20L%2029.0537%2038%20L%201.019%206.6672%20A%204%204%200%200%201%200%204%20Z%27/%3E%3C/svg%3E") no-repeat center/contain; }
.page-pa .pdet[open] summary::after { transform:rotate(270deg); }


/* ---------- Python page disclosure marker ----------
   The rounded plus from the brand set, replacing the typographic +/- on THIS PAGE'S FAQ
   ONLY. Scoped to .page-python deliberately: the homepage and the Power Automate page keep
   +/-, and the catalogue keeps the Power Automate silhouette. One page, one mark.
   Painted as a mask over currentColor so it keeps the accent the base rule sets and follows
   the hover colour change; a background-image would freeze one cyan into the data URI.

   GEOMETRY, so it can be regenerated rather than re-traced: a plus on a 100 unit box, arm
   half-thickness 18, half-extent 37, arm tips HALF-ELLIPSES of rx 18 by ry 9 (swapped on
   the horizontal arms), inner corners SHARP.
   THE ELLIPTICAL CAPS ARE THE LIKENESS, and four attempts at a circular radius failed
   before this. The reference is two things at once that no circle can be: the curve spans
   the FULL arm width, so no flat run across the tip (a circle does that only at radius =
   half the arm, giving a deep dome), and it is SHALLOW, rising 9 of a possible 18 (a circle
   does that only by leaving a flat section either side). An ellipse is both, and meets the
   straight sides tangentially so the join has no visible corner. rx MUST equal the arm
   half-thickness or the flat run returns; ry is the only free number and carries the
   character. Filleting the armpits at any radius makes a quatrefoil - they stay square.

   ALIGNMENT is the catalogue mark's derivation re-run for a square: centring on the line
   box puts a solid mark on the CAP band, but these titles are sentence case and their mass
   sits in the x-height band. Both metric derivations of that midpoint were wrong in
   practice - .726em read high, .876em read low - because the browser builds the line box
   from the font's own ascent and descent, not from cap height and x-height. So the mark is
   simply centred on the FIRST LINE BOX, which needs no font metrics:
     margin-top = (line-height - height) / 2 = (1.65 - .86) / 2 = .395em
   margin-top, not align-self:center, so a two-line question keeps its mark on line one.

   Rotating 45deg on open turns the plus into a cross, which is what +/- does in type. */
.page-python .qa summary::before {
  content:""; flex:0 0 auto; align-self:flex-start;
  width:.86em; height:.86em; margin-top:.395em;
  background:currentColor;
  -webkit-mask:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%20100%20100%27%3E%3Cpath%20d%3D%27M%2032%2032%20L%2032%2022%20A%2018%209%200%200%201%2068%2022%20L%2068%2032%20L%2078%2032%20A%209%2018%200%200%201%2078%2068%20L%2068%2068%20L%2068%2078%20A%2018%209%200%200%201%2032%2078%20L%2032%2068%20L%2022%2068%20A%209%2018%200%200%201%2022%2032%20Z%27%2F%3E%3C%2Fsvg%3E") no-repeat center/contain;
          mask:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%20100%20100%27%3E%3Cpath%20d%3D%27M%2032%2032%20L%2032%2022%20A%2018%209%200%200%201%2068%2022%20L%2068%2032%20L%2078%2032%20A%209%2018%200%200%201%2078%2068%20L%2068%2068%20L%2068%2078%20A%2018%209%200%200%201%2032%2078%20L%2032%2068%20L%2022%2068%20A%209%2018%200%200%201%2022%2032%20Z%27%2F%3E%3C%2Fsvg%3E") no-repeat center/contain;
  transition:transform .18s ease; }
.page-python .qa[open] summary::before { content:""; transform:rotate(45deg); }


/* ---------- Python page: the process rail's disclosure marker ----------
   The same plus the FAQ uses, so this page carries ONE mark rather than a mark and a
   variant of it. Ink, not accent: this rail sits on Paper, where Signal is 1.06:1 and a
   marker in it reads as deleted rather than coloured (the FAQ can afford the accent
   because .qa tiles supply their own Deep surface).

   IT MUST ROTATE 45deg ON OPEN, NOT THE BASE RULE'S 180deg. The plus has four-fold
   symmetry, so turning it 180deg produces a pixel-identical mark and the accordion would
   have no open state at all - the transition would run and nothing would appear to happen.
   45deg turns it into a cross, which is what the FAQ does and what +/- does in type. */
.page-python .pdet summary::after {
  /* .86em, the same as the FAQ's. At .68 the plus collapses to a dot: its arms are
     short relative to its bulk, so it needs more size than a chevron did to read as
     a plus at all. The summary's min-height absorbs the extra, so the row is unchanged. */
  width:.86em; height:.86em;
  -webkit-mask:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%20100%20100%27%3E%3Cpath%20d%3D%27M%2032%2032%20L%2032%2022%20A%2018%209%200%200%201%2068%2022%20L%2068%2032%20L%2078%2032%20A%209%2018%200%200%201%2078%2068%20L%2068%2068%20L%2068%2078%20A%2018%209%200%200%201%2032%2078%20L%2032%2068%20L%2022%2068%20A%209%2018%200%200%201%2022%2032%20Z%27%2F%3E%3C%2Fsvg%3E") no-repeat center/contain;
          mask:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%20100%20100%27%3E%3Cpath%20d%3D%27M%2032%2032%20L%2032%2022%20A%2018%209%200%200%201%2068%2022%20L%2068%2032%20L%2078%2032%20A%209%2018%200%200%201%2078%2068%20L%2068%2068%20L%2068%2078%20A%2018%209%200%200%201%2032%2078%20L%2032%2068%20L%2022%2068%20A%209%2018%200%200%201%2022%2032%20Z%27%2F%3E%3C%2Fsvg%3E") no-repeat center/contain; }
.page-python .pdet[open] summary::after { transform:rotate(45deg); }


/* ---------- mobile navigation ----------
   The nav was a five-item flex row that did not wrap. Below about 44rem it was simply wider
   than the screen, so the PAGE scrolled sideways and the header's dark band ran out before
   the content did - the "gap" you get when you swipe right. A drawer fixes the cause; the
   overflow guard on body is the seatbelt, so no future wide element can do it again.

   EVERYTHING HERE IS SCOPED TO .js, which site.js puts on <html>. Without JavaScript the
   button would be inert and the nav unreachable, so with no .js class the nav simply stays
   a normal wrapping row - visible, usable, slightly taller. Progressive enhancement rather
   than a hamburger that does nothing. */
.vh { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden;
      clip-path:inset(50%); white-space:nowrap; border:0; }

.menu-btn { display:none; }
.nav-scrim { display:none; }

@media (max-width:44rem){
  /* No JS: let the row wrap instead of overflowing. */
  nav { flex-wrap:wrap; row-gap:.4rem; }

  /* Phone layout of the bar: menu button hard left, brand centred.
     The button is taken OUT OF FLOW rather than reordered, because with it in the flex row
     the brand would centre in the space LEFT OVER beside it and sit visibly off-centre. Out
     of flow, the brand is the only item left and justify-content:center puts it on the true
     middle of the bar. .brand's margin-right:auto exists to push the desktop nav right and
     has to go, or it drags the brand back to the left edge.
     The button keeps its position when the drawer opens, so the X ends up over the drawer's
     top-left corner - which is where a close control belongs anyway. It stays tappable
     because the header is raised above the drawer while open (see the z-index rule above). */
  .js .bar { position:relative; justify-content:center; }
  .js .brand { margin-right:0; }
  /* z-index ABOVE the drawer, and this is not the same fix as raising the header.
     <nav> is a DESCENDANT of <header>, so the header's z-index moves the pair together
     against the scrim but does nothing between them: inside the header's own stacking
     context the positioned nav (30) paints over the unpositioned brand and button. That did
     not show while the button sat on the right, clear of a 17rem drawer. Moved to the left
     it lands underneath, and the control that closes the drawer vanishes the moment the
     drawer opens - leaving no way out on a touch device. */
  .js .menu-btn { position:absolute; left:0; top:50%; transform:translateY(-50%); z-index:32; }

  .js .menu-btn { display:inline-flex; align-items:center; justify-content:center;
                  width:2.75rem; height:2.75rem;      /* 44px: the minimum comfortable tap target */
                  background:transparent; border:1px solid var(--line); border-radius:8px;
                  color:var(--ink); cursor:pointer; padding:0;
                  transition:border-color .16s ease, color .16s ease; }
  .js .menu-btn:hover, .js .menu-btn:focus-visible { border-color:var(--accent); color:var(--accent); }

  /* Three bars drawn from one element: the middle is the box, the outer two are its
     pseudo-elements. On open they rotate into an X, which is the same "the mark tells you
     the state" convention the accordions use. */
  .menu-bars, .menu-bars::before, .menu-bars::after {
      content:""; display:block; width:1.15rem; height:2px; background:currentColor;
      border-radius:2px; transition:transform .18s ease, opacity .12s ease; }
  .menu-bars { position:relative; }
  .menu-bars::before { position:absolute; top:-.38rem; }
  .menu-bars::after  { position:absolute; top:.38rem; }
  .js .menu-btn[aria-expanded="true"] .menu-bars { background:transparent; }
  .js .menu-btn[aria-expanded="true"] .menu-bars::before { transform:translateY(.38rem) rotate(45deg); }
  .js .menu-btn[aria-expanded="true"] .menu-bars::after  { transform:translateY(-.38rem) rotate(-45deg); }

  /* The drawer. Fixed to the left edge, full height, sliding in from off-screen.
     visibility is animated alongside transform so the panel is not focusable while closed -
     transform alone leaves it in the tab order, parked off-screen. */
  .js nav { position:fixed; top:0; left:0; bottom:0; z-index:30;
            width:min(80vw, 17rem);
            flex-direction:column; align-items:flex-start; gap:0;
            padding:5.5rem 1.5rem 1.5rem;
            background:var(--bg); border-right:1px solid var(--line);
            transform:translateX(-100%); visibility:hidden;
            transition:transform .22s ease, visibility 0s linear .22s;
            overflow-y:auto; overscroll-behavior:contain; }
  .js nav[data-open] { transform:none; visibility:visible; transition:transform .22s ease; }
  /* border-radius and the negative margins come from the DESKTOP rule, where they draw a
     small hover chip around each link. Inherited here they curved both ends of every
     separator and pulled the rules wider than the panel - the border was being drawn round
     a rounded box, so it bent to follow the corners. Both are reset.
     The separators take the ACCENT. Note this is a deliberate exception to the rule stated
     further up the file - that the accent marks "this responds to you" and should leave
     almost no permanent cyan on the page. Five standing cyan rules is the most permanent
     accent anywhere on the site. It is confined to the drawer, which is a small surface a
     visitor opens on purpose, and is at 45% so it reads as brand-tinted structure rather
     than as five things all claiming to be interactive. */
  .js nav a { display:block; width:100%; padding:.85rem 0; margin:0; font-size:1.05rem;
              border-radius:0;
              border-bottom:1px solid color-mix(in srgb, var(--accent) 45%, transparent); }
  .js nav a:last-child { border-bottom:0; }
  /* FAQs is hidden on small screens only because the ROW ran out of space. In a drawer
     there is room, so it comes back - hiding a whole section from phone users was a
     workaround for a layout problem that no longer exists. */
  .js nav .hide-sm { display:block; }

  /* THE SCRIM MUST NOT SWALLOW TAPS WHEN IT IS CLOSED, and getting this wrong froze the
     whole page. `display:block` here beats the `hidden` attribute, because [hidden] is
     applied by the UA stylesheet at the lowest possible specificity and any author rule
     outranks it. So the scrim was permanently laid over the page - invisible at opacity 0,
     but still the top hit target at z-index 29, above the header. Every tap landed on it
     instead of the button, its click handler called btn.focus(), and the page looked frozen
     with the menu button mysteriously focus-ringed. Two independent guards now:
       * the [hidden] rule below is scoped tighter so the attribute always wins;
       * pointer-events:none means that even if it is painted, it cannot take a tap. */
  .js .nav-scrim { display:block; position:fixed; inset:0; z-index:29;
                   background:color-mix(in srgb, #0B1317 55%, transparent);
                   opacity:0; pointer-events:none; transition:opacity .22s ease; }
  .js .nav-scrim[hidden] { display:none; }
  .js .nav-scrim[data-open] { opacity:1; pointer-events:auto; }
  .js body.nav-open { overflow:hidden; }

  /* The header has to sit ABOVE the scrim, or the close button it contains is covered by
     the very overlay the button is meant to dismiss - open the drawer and there is no way
     back out except Escape, which a phone does not have. Above the drawer too, so the
     drawer's own content scrolls under the bar rather than over it. */
  .js body.nav-open header { z-index:31; }

  /* THE DRAWER CANNOT BE position:fixed WHILE THE HEADER FILTERS ITS BACKDROP.
     An element with backdrop-filter (like transform and filter) becomes the containing
     block for its fixed-position descendants. <nav> lives inside <header>, so top/left/
     bottom resolved against the HEADER's ~60px box instead of the viewport: the drawer
     came out as a short wide panel pinned under the bar, with its links still in a row.
     The frosted bar is worth less on a phone than a working menu, so the filter is off
     below this breakpoint and the header stays solid - which is exactly what the
     @supports fallback already does for browsers without backdrop-filter.
     Do not "restore" it here without moving <nav> out of <header> first.
     Worth knowing: QuickLook does not implement backdrop-filter, so a static render of
     this page shows the drawer working correctly. It only breaks in a real browser. */
  .js header, .js header[data-stuck] { backdrop-filter:none; background:var(--bg); }
}

/* Nothing may push the page wider than the viewport. clip rather than hidden: hidden on
   body creates a scroll container that breaks position:sticky on the header in some
   browsers, and clip does not. */
body { overflow-x:clip; }
