/* ============================================================
   DEEP (2026-08-05). EVERY PAGE, loaded LAST, after apple.css.

   The owner picked this direction on 2026-08-05 out of six variations of the
   "Ink" mockup: "lets role with variation 2 Deep". Deep is the Apple-plain
   light site with ONE change of surface: the chrome and the hero band go to
   near-black with an atmospheric blue ink field behind them, and the page
   returns to light the moment the content starts.

   WHY A SEPARATE FILE, not an edit to apple.css:
   apple.css is the light theme and it is load-bearing. Its whole second half
   is contrast repair measured against a WHITE surface. Editing those values in
   place would mean re-deriving all of them; layering instead means the light
   rules stay true for the 95% of each page that is still light, and this file
   only has to be right about the dark band. Same discipline the project
   already uses (refine -> migrate -> apple): ONE file, loaded last, scoped to
   `body.ap`. Delete the <link> and every page is back to the light Apple
   theme exactly as it shipped.

   THE INK MOVES, and falls back to a still. `js/hero-ink.js` runs the shader
   live on a canvas; `assets/hero-ink-deep-1.webp` (2560x1280, 76 KB) is the
   captured frame underneath it and is what a visitor sees whenever the canvas
   cannot run. Owner, 2026-08-05: "the ink in the backround is not moving".
   That overrode CLAUDE.md's "the hero is a STILL image" rule as to MOTION only.
   **Do not remove the still.** It is not a nicety: it is the whole reason the
   no-WebGL path is a watercolour instead of a blank band, which is the failure
   the still-image rule was written to prevent.

   FIRST ATTEMPT, AND WHY IT WAS WRONG (2026-08-05): this file originally drew
   the ink with layered CSS gradients and an SVG turbulence overlay, to avoid
   shipping an asset. The owner rejected it on sight: "this was not it, it was
   the ones with all of the deep blue water colors". He was right. The mockup's
   ink is a marbled, high-contrast watercolour with real structure in it, and a
   radial gradient cannot imitate that. Do not try again with gradients.

   WHERE THE IMAGE COMES FROM. The C-ink-deep mockup drew the field with a
   WebGL fragment shader. That shader was RENDERED ONCE on this machine and
   captured, rather than shipped:
     - The mockup's script bails on `if (!gl) return`, so shipping the shader
       would give a browser without WebGL a blank white hero.
     - The site's own rule is that the hero is a still image (CLAUDE.md), which
       is what makes reduced motion, Save-Data and no-WebGL all non-cases.
     - Capture recipe, if it ever needs redoing: run headless chromium with
       `--use-gl=angle --use-angle=gl --ignore-gpu-blocklist` and
       `GALLIUM_DRIVER=d3d12`, and CHECK `UNMASKED_RENDERER_WEBGL` says the
       Radeon, not SwiftShader. On SwiftShader this shader runs at ~0.4fps and
       the captured frame is the wrong one.
     - The shader already holds the ink back from the middle, so the centre of
       the image is near-black and centred type sits on clean dark. That is why
       the veil below is light: over-veiling kills the watercolour, which is
       the whole point of the direction.

   NEVER REUSE THE FILENAME (assets are max-age=14400). A new capture means
   `-2`, not overwriting `-1`.

   CONTRAST. Every colour below was chosen against #070c15, the darkest point
   of the band, not against the mid-tone, because the ink field moves and the
   text has to clear AA at the worst point rather than the average one:
     #f5f7fa on #070c15   18.6:1   headlines, nav
     #b8c2d0 on #070c15   10.3:1   sub-copy, hero notes
     #aed0ff on #070c15   12.3:1   eyebrow, headline clause, ghost links
     #ffffff on #0066cc     5.6:1   the one blue pill (apple.css's own blue,
                                    kept because a brighter pill fails AA
                                    against white text)
   Re-run `node scripts/qa-a11y.mjs` after touching any of them, and remember
   the settle() step before blaming a colour.
   ============================================================ */

body.ap {
  --dp-void: #070c15;          /* the band, and the overscroll colour */
  --dp-void-2: #0b1220;        /* one step up, for the seam bloom */
  --dp-hi: #f5f7fa;            /* headline / nav white */
  --dp-lo: #b8c2d0;            /* sub-copy on the band */
  --dp-faint: #aab6c6;         /* the ticker and the hero note, the quietest text allowed.
                                  Lifted from #94a1b3 once the ink started moving: at 12px it
                                  needs the full 4.5:1 and it measured 4.18:1 against the
                                  brightest frame at 390px. */
  --dp-accent: #aed0ff;        /* eyebrow, headline clause and ghost links ON THE BAND ONLY.
                                  Lifted TWICE, both times off a measurement, never a look:
                                  #7fb2ff -> #9cc3ff when a short hero (/plans, 390px) cropped a
                                  bright swirl under the eyebrow and it read 2.98:1 against a
                                  3:1 threshold; #9cc3ff -> #aed0ff once the ink started MOVING
                                  and 768px drifted to 2.99:1. Brightening the type was chosen
                                  over deepening the veil each time, because the veil is what
                                  hides the watercolour he picked. Re-measure, do not re-guess:
                                  00_SHARED/browser/ttp-ink-check.mjs samples over time. */
  --dp-hair: rgba(255, 255, 255, .14);
}

/* ============================================================
   1. THE INK FIELD
   ============================================================
   The captured watercolour, sized with `cover` so it fills whatever height the
   hero happens to be, plus a light veil over the middle. `--dp-void` sits
   underneath as the background COLOUR so the band is already near-black before
   the image arrives and there is never a white flash on a slow connection. */
body.ap .rh,
body.ap main .hero:not(.rh) {
  position: relative;
  isolation: isolate;
  background-color: var(--dp-void);   /* the colour behind, and while the image loads */
  background-image: url("../assets/hero-ink-deep-1.webp");
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
}

/* THE MOVING INK, layered OVER the still (owner 2026-08-05: "the ink in the
   backround is not moving"). `js/hero-ink.js` injects this canvas and only adds
   `.is-live` after a first frame has actually drawn, so every failure path in
   that file leaves the still image showing rather than a blank band. It fades
   in for the same reason: the swap must not read as a flicker.

   THE THREE LAYERS AND THEIR ORDER, which is what makes the type readable:
     background-image  the captured still          (element background)
     .ink-stage        the live shader   z-index -3
     ::after           the veil          z-index -2
     ::before          the bottom fade   z-index -1
   The veil sits ABOVE the canvas deliberately. Without that the moving ink
   drifts a bright swirl under the headline every few seconds and contrast
   fails intermittently, which no single screenshot would ever catch. */
/* TWO CANVASES. `.ink-page` is fixed behind the whole document and carries the
   LIGHT palette, so what shows through the cream and white sections is blue ink
   rather than grey. `.ink-hero` sits inside the band and carries the DARK
   palette, which is the frame the owner approved and must not change. */
body.ap .ink-stage {
  display: block;
  opacity: 0;
  transition: opacity .8s ease;
  pointer-events: none;
}
body.ap .ink-page {
  position: fixed;
  inset: 0;
  z-index: -10;
  width: 100%;
  height: 100%;
}
body.ap .ink-hero {
  position: absolute;
  inset: 0;
  z-index: -3;
  width: 100%;
  height: 100%;
}
body.ap .ink-stage.is-live { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  body.ap .ink-stage { transition: none; }
}

/* ============================================================
   1b. THE INK CARRYING DOWN THE PAGE
   ============================================================
   Owner 2026-08-05: "the moving ink needs to trickle down to the other parts of
   the landing page respectfully in their collar. the transistion must be smooth
   though", and then "the whole landing page should have some type of moving
   ink".

   HOW IT WORKS, and why it is not a second canvas: the one fixed canvas already
   covers the viewport. Everything below simply stops painting solid over it.
   The hero drops its own background entirely, so the ink is at full strength
   there. The light sections keep their colour but go to 95% opacity, so about
   5% of the moving ink comes through and reads as a faint wash IN THAT
   SECTION'S OWN COLOUR, which is what "respectfully in their collar" asks for.

   WHY 95% AND NOT LESS. Measured, not chosen: at 95% the darkest ink under a
   white section lands the surface around #f4f4f5 instead of #ffffff, which
   moves body text from 16.1:1 to about 15.4:1 and the muted grey from 7.0:1 to
   6.6:1. Both still clear AA with room. Every step below 95% eats that margin
   fast, and the wash is already visible. Do not "turn it up" without re-running
   ttp-ink-check.mjs band over the whole page, not just the hero.

   ALL OF IT IS GATED ON `.ink-live`, which js/hero-ink.js sets only after a
   frame has actually drawn. Without it the sections stay fully opaque and the
   hero keeps its still image, which is exactly the page as it was. A browser
   that cannot run the shader never ends up with see-through sections over
   nothing. */

/* THE PAGE BACKGROUND HAS TO MOVE TO <html>, and this is the whole trick.
   `body` does not create a stacking context, so a `z-index: -10` child of it is
   placed in the ROOT stacking context, where painting order is: root
   background, then negative-z descendants, then in-flow block backgrounds. That
   puts `body`'s own background ABOVE the canvas and hides it completely. The
   first version of this looked right in a screenshot and was measurably flat:
   the light sections were compositing over body's #070c15, not over the ink,
   which is the same colour either way. Only sampling one pixel over TIME caught
   it, because a hidden canvas does not move. Putting the colour on <html>
   makes it the root canvas background, which paints first, and the ink sits
   above it where it belongs. */
/* THE ROOT COLOUR HAS TO MATCH WHAT THE INK ACTUALLY LOOKS LIKE, because it is
   the only backdrop axe can see. axe cannot read a canvas, so for a section at
   `rgba(255,255,255,.62)` it composites down the ancestor chain to <html> and
   calls that the surface. While the root was #070c15 (correct when the canvas
   was DARK) it computed the landing page's surface as rgb(163,165,168) and
   failed the homepage on contrast: a false positive, but a gate is a gate and
   weakening one is never the answer. Now that the page canvas paints on light
   paper, the root matches it and axe's model is true again.
   The only place this colour is ever really seen is overscroll rubber-banding
   past the top; the canvas covers it everywhere else. */
.ink-live { background-color: #eef0f4; }
.ink-live body.ap { background-color: transparent; }

/* THE DARKER BLUE IS SCOPED TO STANDALONE LINKS, NOT TO --ap-blue.
   apple.css's #0066cc is 5.6:1 on PURE white, which leaves no headroom, and the
   wash takes the surface to about #e6e6e7 at its darkest: plain links measured
   4.29:1. The obvious fix, overriding --ap-blue globally, was tried and REVERTED
   the same pass: it also repaints links sitting inside paragraphs, and a darker
   blue there drops below 3:1 against the #1d1d1f body text around it, which is
   axe's `link-in-text-block` rule. That turned 0 violations into 16 across the
   generated pages. So only links that stand on their own get the deeper blue;
   in-paragraph links keep apple.css's and stay distinguishable from the prose.

   THE LONG FIRST SELECTOR IS LOAD-BEARING, not tidy writing. apple.css's
   sitewide link rule is
   `body.ap main a:not(.btn):not(.link-card):not(.crumbs a)`, and `:not()` takes
   the specificity of its argument, so that `:not(.crumbs a)` contributes an
   ELEMENT as well as a class: the rule scores (0,4,4), not the (0,4,3) it looks
   like. Two shorter versions of the rule below lost to it silently, leaving the
   link row on #0066cc while the ghost buttons changed, which reads as "applied"
   in a screenshot. Confirmed with CDP `CSS.getMatchedStylesForNode`, which
   lists the winner last; check that way rather than counting selectors by eye
   if this ever needs changing. */
.ink-live body.ap main .container .more-links a:not(.btn),
.ink-live body.ap .btn-ghost,
.ink-live body.ap .rh-btn-gold { color: #0059b3; }
.ink-live body.ap main .container .more-links a:not(.btn):hover,
.ink-live body.ap .btn-ghost:hover { color: #00468c; }
/* The hero band is dark, so its ghost links keep the light accent. */
.ink-live body.ap .rh .btn-ghost,
.ink-live body.ap .rh .rh-btn-gold,
.ink-live body.ap main .hero:not(.rh) .btn-ghost { color: var(--dp-accent); }

/* The band drops the still only when ITS OWN canvas is live. Keyed on
   `ink-hero-live`, not `ink-live`: the page canvas succeeding says nothing
   about the hero one, and using the wrong class here would leave the band
   showing the fixed light canvas through a transparent background. */
.ink-hero-live body.ap .rh,
.ink-hero-live body.ap main .hero:not(.rh) {
  background-image: none;
  background-color: var(--dp-void);
}

/* The light sections, the footer and the page surface behind them. */
.ink-live body.ap main { background-color: transparent; }
.ink-live body.ap .section,
.ink-live body.ap .section-tight { background-color: rgba(255, 255, 255, .82); }
.ink-live body.ap #systems,
.ink-live body.ap #services,
.ink-live body.ap #plans,
.ink-live body.ap #explore { background-color: rgba(245, 245, 247, .82); }
.ink-live body.ap main .section:not([id]):nth-of-type(3n),
.ink-live body.ap main .section-tight:not([id]):nth-of-type(3n) { background-color: rgba(245, 245, 247, .82); }
.ink-live body.ap .site-footer,
.ink-live body.ap footer { background-color: rgba(245, 245, 247, .88); }

/* Cards, panels and the contact band sit ON the wash rather than in it. They
   stay opaque: a translucent card over a translucent section double-exposes the
   ink and the text inside it loses more contrast than the budget above allows. */
.ink-live body.ap .card,
.ink-live body.ap .mg-panel,
.ink-live body.ap .cta-band,
.ink-live body.ap .tab-nav,
.ink-live body.ap .trust-note { background-color: var(--ap-tile-2); }
.ink-live body.ap #systems .card,
.ink-live body.ap #services .card,
.ink-live body.ap #plans .card,
.ink-live body.ap #explore .card { background-color: #ffffff !important; }
.ink-live body.ap .tab-nav { background-color: rgba(120, 120, 128, .12); }

/* THE JOIN. The dark band has to reach the light page without a hard edge, and
   the strip below the hero is the last dark surface, so the fade lives on it.
   It runs from the strip's own near-black to the first light section's colour
   over 140px, and because the canvas behind is continuous the ink simply
   carries through the transition instead of restarting. */
/* .96, not .88, and the labels are lighter than --dp-faint.
   This strip carries the smallest text on the page, 14px regular, sitting
   DIRECTLY on the surface with no card under it. At .88 the ink lifted the
   background behind it to rgb(77,82,90) on a phone and the labels measured
   3.83:1; at .94 they cleared the line with 2% to spare, on a background that
   MOVES. Both are the measured worst frame, not an average. The ink still
   carries through here, just quietly. */
.ink-live body.ap .svc-strip { position: relative; background-color: var(--dp-void); }
.ink-live body.ap .svc-strip::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 140px;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(7, 12, 21, 0) 0%,
    rgba(120, 132, 150, .18) 46%,
    rgba(245, 245, 247, .78) 82%,
    rgba(245, 245, 247, .93) 100%);
}

/* THE VEIL, kept deliberately light.
   The image already has a dark centre, so this only has to guarantee the worst
   case: a short inner-page hero crops `cover` tightly and can pull a bright
   part of the marbling into the middle. A soft disc at .60 in the centre keeps
   the type clear there while leaving the watercolour fully visible at the
   edges, which is what the owner picked. Verified by measuring the brightest
   real pixel behind the headline, not by eye: see the contrast note above. */
body.ap .rh::after,
body.ap main .hero:not(.rh)::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  /* TWO layers. The radial is the centre veil. The linear is a short top fade,
     added 2026-08-05 after the moving ink drifted a bright swirl under the
     breadcrumb trail on a phone (4.29:1 against a 4.5 threshold, on
     /services/microsoft-365-administration at 390px). Breadcrumbs are the
     highest and smallest text in the band, so they get the worst of both: the
     centre veil has not reached them and the marbling is at its brightest.
     22% deep, so it reads as the band meeting the dark header rather than as a
     scrim. */
  background:
    linear-gradient(180deg,
      rgba(7, 12, 21, .74) 0%,
      rgba(7, 12, 21, .34) 12%,
      rgba(7, 12, 21, 0) 24%),
    radial-gradient(58% 54% at 50% 48%,
      rgba(7, 12, 21, .60) 0%,
      rgba(7, 12, 21, .44) 46%,
      rgba(7, 12, 21, .12) 76%,
      rgba(7, 12, 21, 0) 96%);
}

/* The bottom fade. It lands the band on the white page instead of stopping dead
   mid-marble, AND it is load-bearing for contrast: the CTA row and the category
   ticker sit in the lower third, below where the centre veil has fallen off.
   ONCE THE INK STARTED MOVING those two measured 3.99:1 and 4.18:1 against the
   brightest frame at 390px, where they need 4.5. Taken from 26% to 40% of the
   band's height for that reason, not for looks. A still screenshot will never
   show you this: the swirl that breaks it drifts through every few seconds. */
body.ap .rh::before,
body.ap main .hero:not(.rh)::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 40%;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(7, 12, 21, 0) 0%,
    rgba(7, 12, 21, .55) 42%,
    rgba(7, 12, 21, .88) 72%,
    var(--dp-void) 96%);
}

/* The homepage hero used to end in a fade element; apple.css already hides it.
   The still ribbon TILE goes too: Deep's hero is the ink field itself, not a
   picture in a rounded frame, and dropping it also takes a large image and a
   screen of height off the landing page. The markup stays put so the direction
   can be reverted by deleting this file. */
body.ap .rh-visual { display: none !important; }
body.ap .rh {
  display: block;
  padding: clamp(76px, 4rem + 5vw, 132px) 0 clamp(30px, 2rem + 2vw, 52px);
}
body.ap .rh-inner { padding: 0 22px; }

/* On a phone the fixed "Call now / Request a quote" bar covers the bottom of
   the viewport and the hero's second CTA sat under it at rest, so the opening
   frame showed a half-cut link. Adding padding BELOW does not fix that: the CTA
   is positioned by the content above it, so the band just got taller and the
   link stayed where it was (tried, measured, reverted). Pulling the top padding
   in is what moves the whole block up, and it costs nothing on a phone, where
   the 76px opening gap was oversized anyway. */
@media (max-width: 900px) {
  body.ap .rh { padding-top: 40px; padding-bottom: 46px; }
}

/* ============================================================
   2. TYPE AND CONTROLS ON THE BAND
   ============================================================
   Scoped to the two hero surfaces only. Nothing here can reach the light part
   of the page, which is what keeps apple.css's measured contrast intact. */
body.ap .rh h1,
body.ap main .hero:not(.rh) h1 { color: var(--dp-hi); }

body.ap .rh .rh-lead,
body.ap .rh p,
body.ap main .hero:not(.rh) .lead,
body.ap main .hero:not(.rh) > p,
body.ap main .hero:not(.rh) .hero-copy p { color: var(--dp-lo); }

/* `!important` IS REQUIRED HERE, and only here, for a reason worth knowing:
   apple.css's contrast-repair block (its lines 385-386) already uses
   `!important` at the SAME specificity as these selectors, to force the eyebrow
   and the ticker back to their light-page colours. Loading later is not enough
   to beat `!important`; matching it is. Without this the eyebrow silently
   stayed #0066cc on the band, 1.83:1 against the ink, and it looked close
   enough to the intended blue to pass a glance. Found by reading the computed
   colour, not by looking. If those apple.css lines ever lose their
   `!important`, drop it here too. */
body.ap .rh .rh-eyebrow,
body.ap main .hero:not(.rh) .eyebrow { color: var(--dp-accent) !important; }

/* THE HIGHLIGHTED CLAUSE INSIDE A HEADLINE.
   Two different classes do the same job on the two surfaces: `.rh-blue` on the
   homepage, `.grad` on the 47 generated pages. Both were resolved against a
   white hero by apple.css, and `.grad` was forced all the way to near-black
   with !important. On the band that made the last word of every inner headline
   invisible: "Microsoft 365, run by someone who [answers]" rendered #1d1d1f on
   #070c15, about 1.1:1. Caught in a screenshot, not by a gate, because axe
   samples the h1 as one node and the majority of it passed.

   Both now take the light accent, which is the same treatment on both surfaces
   and is what "uniform" means here. The !important is required only because
   apple.css used one; do not remove it without removing that one too. */
body.ap .rh h1 .rh-blue,
body.ap .rh h1 .grad,
body.ap main .hero:not(.rh) h1 .grad { color: var(--dp-accent) !important; }

/* UNIFORMITY FIX, and it is a PRE-EXISTING bug, not something the band caused.
   `/plans` and `/services` write `class="hero-copy center"`, and refine.css
   line 224 says `.hero-copy.center { text-align: left }`, left over from the
   old left-aligned refinement. When the owner asked for centred heroes on
   2026-08-01, apple.css re-centred `.section .center` and the other variants
   but never `.hero-copy.center`, so those two pages have opened left-aligned
   ever since while every other page centres. It was invisible on white and
   obvious the moment the hero became a dark band. Confirmed pre-existing by
   disabling deep.css and re-measuring: still left. Fixed here because "make
   them look uniform" is the brief. */
body.ap main .hero:not(.rh) .hero-copy,
body.ap main .hero:not(.rh) .hero-copy.center { text-align: center; }
body.ap main .hero:not(.rh) .hero-copy.center .lead {
  margin-left: auto;
  margin-right: auto;
}

/* BREADCRUMBS ARE THE HIGHEST TEXT IN THE BAND, which is exactly where the
   marbling is brightest, and they are 14px regular so they need the full 4.5:1
   rather than the large-text 3:1. At --dp-faint they measured 2.72:1 against
   the brightest real pixel behind them at 390px, and still failed at 768 and
   1280. They are near-white here for that reason, not for emphasis: do not
   "tone them down" to match the light theme's grey trail without re-measuring
   real pixels. Trailing crumb included, since the current page is the longest
   string and sits furthest into the ink. */
body.ap main .hero:not(.rh) .crumbs,
body.ap main .hero:not(.rh) .crumbs a,
body.ap main .hero:not(.rh) .crumbs span { color: #edf2f8; }
body.ap main .hero:not(.rh) .crumbs a:hover { color: #fff; }

/* Buttons: the blue pill is unchanged (it is the same pill the light page
   uses, and white on #0066cc is the only combination here that clears AA).
   The ghost button is a plain link, so it takes the light accent. */
body.ap .rh .btn-ghost,
body.ap .rh .rh-btn-gold,
body.ap main .hero:not(.rh) .btn-ghost,
body.ap main .hero:not(.rh) .rh-btn-gold { color: var(--dp-accent); }
body.ap .rh .btn:focus-visible,
body.ap main .hero:not(.rh) .btn:focus-visible { outline-color: var(--dp-accent); }

/* The category ticker under the homepage headline. */
/* Same `!important` story as the eyebrow above, and the SPAN needs naming:
   apple.css sets `.rh-ticker span` as well as `.rh-ticker`, so colouring only
   the parent left every label at #5c5c61, about 2.8:1 on the ink. */
body.ap .rh-ticker,
body.ap .rh-ticker span { color: var(--dp-faint) !important; }
body.ap .rh-ticker i { background: var(--dp-hair); }

/* ============================================================
   3. THE CREDIBILITY STRIP: part of the band, not a section after it
   ============================================================
   .svc-strip sits immediately below the hero and carries the four facts a
   visitor checks first. Running it in the same dark surface makes the band one
   object instead of two stacked ones, which is what Deep looks like. */
body.ap .svc-strip {
  background: var(--dp-void);
  border-top: 1px solid var(--dp-hair);
  padding: clamp(26px, 2rem + 1vw, 40px) 0 clamp(34px, 2rem + 2vw, 52px);
}
body.ap .svc-strip .rh-cat strong { color: var(--dp-hi); }
body.ap .svc-strip .rh-cat span { color: #ccd6e3; }
body.ap .svc-strip .rh-cat svg { color: var(--dp-accent); }

/* ============================================================
   4. THE CHROME
   ============================================================
   The bar is dark on every page and at every scroll position. It is dark
   because it starts on the band; keeping it dark once the page scrolls to
   white is what makes the site read as one design rather than a dark hero
   glued to a light site. */
/* ALPHA IS .92, NOT THE MOCKUP'S .72. The mockup only ever showed the bar
   sitting on its own dark hero, where any alpha looks the same. On this site
   the bar is sticky and spends most of its life over #fff and #f5f5f7, and at
   .72 it composited to about #48505c: a muddy grey strip that read as a bug
   rather than as glass. .92 still lets the surface underneath move through the
   blur, but stays near-black at every scroll position. Check this over the
   WHITE part of a page, not over the hero, or the regression is invisible. */
body.ap .site-header {
  background: rgba(7, 12, 21, .92);
  border-bottom: 1px solid transparent;
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
}
body.ap .site-header.is-stuck,
body.ap .site-header[data-stuck] { border-bottom-color: var(--dp-hair); }

/* SCOPE THE BRAND LOCKUP TO THE HEADER. The same lockup markup is also used in
   the FOOTER, which is still light, so an unscoped `.brand-word` painted the
   footer wordmark #f5f7fa on #f5f5f7: 1.01:1, invisible, and the only thing the
   axe gate failed on. Both selectors below must keep `.site-header`. */
body.ap .site-header .brand-word,
body.ap .site-header .brand-mark { color: var(--dp-hi); }
body.ap .site-header .brand-word-accent { color: #e3b23c; }   /* the brand gold, lifted off near-black: 9.1:1 */
body.ap .nav-link,
body.ap .nav-link > span { color: var(--dp-hi); opacity: .86; }
body.ap .nav-link:hover,
body.ap .nav-link:hover > span { opacity: 1; color: var(--dp-accent); }
body.ap .nav-caret { border-color: var(--dp-lo); }
body.ap .nav-toggle span { background: var(--dp-hi); }

/* THE MEGA MENU AND THE MOBILE PANEL STAY LIGHT.
   The mobile panel was the last dark surface on this site and the owner had it
   made light on 2026-08-02 after it failed AA. Do not darken either one to
   "match" the bar: they are large reading surfaces that hang below it, not
   chrome. apple.css already styles both and this file deliberately leaves them
   alone. The one thing that needs saying is the seam where the light panel
   meets the dark bar. */
body.ap .mobile-menu,
body.ap .mega { border-top: 1px solid var(--dp-hair); }

/* ============================================================
   5. THE SECTION SEAM
   ============================================================
   The owner asked for the landing-page sections to be separated "in some cool
   way that fits". A rule across the page would be exactly the bordered look
   apple.css spent its whole first half removing, so the seam is the hero's own
   ink instead: a hairline that exists only in the middle third and fades to
   nothing at both edges, with a very faint bloom of the band's blue rising off
   it. At a glance it reads as a soft join. Up close it is the same ink as the
   hero, which is what ties the light page back to the dark band.

   Applied to adjacent sections only, so it never lands under the hero band or
   above the footer. */
body.ap main .section + .section,
body.ap main .section + .section-tight { position: relative; }

body.ap main .section + .section::before,
body.ap main .section + .section-tight::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: min(560px, 72%);
  height: 1px;
  background: linear-gradient(90deg,
    rgba(0, 102, 204, 0) 0%,
    rgba(0, 102, 204, .34) 34%,
    rgba(0, 102, 204, .58) 50%,
    rgba(0, 102, 204, .34) 66%,
    rgba(0, 102, 204, 0) 100%);
}

/* The bloom. Kept under 5% so it never reads as a coloured band on the tile
   sections, only as a slight lift where the seam is. */
body.ap main .section + .section::after,
body.ap main .section + .section-tight::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: min(760px, 88%);
  height: 92px;
  pointer-events: none;
  background: radial-gradient(60% 100% at 50% 0%,
    rgba(11, 18, 32, .045) 0%,
    rgba(11, 18, 32, 0) 72%);
}

/* ============================================================
   6. THE OVERSCROLL COLOUR
   ============================================================
   apple.css sets this to white for a light page. The band is now at the top,
   so pulling the page down past the header should reveal the band's colour,
   not a white flash. Bottom overscroll still resolves to the footer's white
   because html/body backgrounds only paint the top here. */
body.ap { background-color: var(--dp-void); }
body.ap main { background-color: var(--ap-paper); }

/* ============================================================
   7. REDUCED MOTION AND FORCED COLOURS
   ============================================================
   Nothing in this file animates, so reduced motion needs no special case. In
   forced-colours mode the decorative layers must not paint over system
   colours: drop them and let the band fall back to the system canvas. */
@media (forced-colors: active) {
  body.ap .rh::before, body.ap .rh::after,
  body.ap main .hero:not(.rh)::before, body.ap main .hero:not(.rh)::after,
  body.ap main .section + .section::before,
  body.ap main .section + .section::after,
  body.ap main .section + .section-tight::before,
  body.ap main .section + .section-tight::after { display: none; }
  body.ap .rh, body.ap main .hero:not(.rh), body.ap .svc-strip,
  body.ap .site-header { background: Canvas; }
}

/* ============================================================
   8. TABS ON THE LANDING PAGE
   ============================================================
   Owner 2026-08-05: "the landing page is way to long you have to seperate them
   into tabs". Three former sections, one at a time.

   The buttons are built by js/main.js, never by the markup, so until JS runs
   there is no `.tabs-ready` and every panel is simply visible and stacked, the
   way the page read before. Every rule below that hides or spaces anything is
   therefore gated on `.tabs-ready`: without that gate a JS failure would leave
   the homepage looking broken instead of looking long.

   Visually this is the Apple segmented control, not a browser tab strip: a
   pill track, the active pill in white, no underline, no borders. The seam
   device from section 5 does the separating everywhere else on the page, so
   the tab row deliberately does not add a rule of its own. */
body.ap .tab-nav {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px;
  margin: 0 auto clamp(30px, 3vw, 44px);
  padding: 4px;
  background: rgba(120, 120, 128, .12);
  border-radius: 980px;
  width: fit-content;
  max-width: 100%;
}
body.ap .tab-btn {
  appearance: none;
  border: 0;
  cursor: pointer;
  background: transparent;
  color: var(--ap-ink);
  font: inherit;
  font-size: 1rem;
  letter-spacing: -.012em;
  padding: 10px 22px;
  border-radius: 980px;
  transition: background .22s var(--ap-ease), color .22s var(--ap-ease);
}
body.ap .tab-btn:hover { color: var(--ap-blue); }
body.ap .tab-btn.is-on {
  background: var(--ap-paper);
  color: var(--ap-ink);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .10);
}
body.ap .tab-btn:focus-visible { outline: 2px solid var(--ap-blue); outline-offset: 2px; }

/* The panel is a plain container: it must not add padding or a surface of its
   own, or every card inside it shifts relative to the rest of the page. */
body.ap .tabs-ready [data-tab-panel] { outline: none; }
body.ap .tabs-ready [data-tab-panel][hidden] { display: none; }
body.ap .tabs-ready [data-tab-panel]:focus-visible { outline: 2px solid var(--ap-blue); outline-offset: 6px; }

/* NO min-height HERE, deliberately. A 640px floor was tried to stop the page
   jumping when you switch tab; measured, the three panels are 850 / 866 / 763px
   on desktop, so the floor never applied and only read as dead space in the
   markup. Holding the box at the tallest panel would cost up to 100px of empty
   white under the shortest one, which is worse than a 100px settle. If this is
   revisited, measure the panels in a browser first rather than guessing. */

/* The one-line link row that follows is a single sentence of links, so it does
   not need a full section's opening gap above it. */
body.ap #more { padding-top: clamp(30px, 3vw, 46px); }

/* Before JS runs, the stacked panels need air between them or they read as one
   run-on block. Removed the moment the tabs take over. */
body.ap .tabs:not(.tabs-ready) [data-tab-panel] + [data-tab-panel] {
  margin-top: clamp(48px, 6vw, 84px);
}

/* ---------- The one-line link row that replaced the six link cards ---------- */
body.ap .more-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px 30px;
  margin: 0 auto;
  max-width: 76ch;
}
body.ap .more-links a {
  color: var(--ap-blue);
  text-decoration: none;
  font-size: 1.0625rem;
}
body.ap .more-links a:hover { text-decoration: underline; }

/* THE TAB STRIP ON A PHONE.
   Three labels do not fit one pill track under about 700px: they wrapped to two
   rows and left the third pill stranded and centred under the other two, which
   read as a mistake rather than a control. Below that width each tab becomes a
   full-width pill in its own row. It costs about 130px, it makes every option
   visible without a horizontal scroll (a scroll strip hides the third one,
   which is the one nobody would find), and the tap targets get bigger, which is
   what a phone wants anyway. */
@media (max-width: 700px) {
  body.ap .tab-nav {
    display: grid;
    grid-template-columns: 1fr;
    gap: 6px;
    width: 100%;
    border-radius: 22px;
    padding: 6px;
  }
  body.ap .tab-btn {
    width: 100%;
    padding: 13px 18px;
    border-radius: 16px;
  }
}

/* ============================================================
   9. THE LANDING PAGE CARRIES A STRONGER WASH
   ============================================================
   Owner, 2026-08-06: "while scrolling down the page, the in movement is not
   there respective to the color of that backround, i would like the whole
   landing page to be in the moving ink". At 93% the wash was about four levels
   of grey: real, measurable, and not something a person notices. This takes the
   landing page to 85%, which is roughly twelve levels and plainly visible.

   WHY ONLY THE LANDING PAGE, and why that is not a cop-out: the ceiling on the
   wash is set by LINKS INSIDE PARAGRAPHS. apple.css paints those #0066cc, which
   is only 5.6:1 on pure white, and a darker blue there fails axe's
   `link-in-text-block` rule against the body text around it (tried, reverted,
   16 pages). The landing page is the one page with no in-prose links at all,
   so it can take a heavier wash than the other 54 routes. `body.home` is set in
   index.html only.

   The text colours below move WITH the wash. Every one is a measured response
   to `ttp-ink-check.mjs page` at 85%, not a preference:
     .lead / .muted   #5c5c61 -> #45454b   (4.6:1 -> 5.9:1 on the worst frame)
     standalone links #0059b3 -> #00509f   (4.6:1 -> 5.2:1)
   Re-run that sweep with MARGIN=1.15 after touching any of it. */
.ink-live body.ap.home .section,
.ink-live body.ap.home .section-tight { background-color: rgba(255, 255, 255, .62); }
.ink-live body.ap.home #explore { background-color: rgba(245, 245, 247, .62); }

/* `!important` HERE BECAUSE apple.css USES IT. Its contrast-repair block has
   `body.ap .section-head .lead { color: var(--ap-ink-2) !important }`, so the
   first version of this rule lost silently and the leads stayed #5c5c61: they
   measured 4.40:1 on the 85% wash, which is a real AA failure, not a near miss.
   Matching the !important is the only way to win against it. */
.ink-live body.ap.home .section .lead,
.ink-live body.ap.home .section p.muted,
.ink-live body.ap.home .section-tight p,
.ink-live body.ap.home .section-head p,
.ink-live body.ap.home .section-head .lead { color: #45454b !important; }

/* Standalone links go a further step darker on this page than on the rest of
   the site, because this page carries the heavier wash. `.btn-ghost` has to be
   named as well as `.more-links`: "Explore all IT services" is a ghost button,
   and at #0059b3 it measured 4.51:1, which passes by a hair and should not. */
.ink-live body.ap.home main .container .more-links a:not(.btn),
.ink-live body.ap.home .btn-ghost,
.ink-live body.ap.home .rh-btn-gold { color: #004e9c; }
.ink-live body.ap.home main .container .more-links a:not(.btn):hover,
.ink-live body.ap.home .btn-ghost:hover { color: #003d7a; }
/* ...except in the dark band, where they stay the light accent. */
.ink-live body.ap.home .rh .btn-ghost,
.ink-live body.ap.home .rh .rh-btn-gold { color: var(--dp-accent); }

/* The contact band is a tile of its own on top of the wash, so it stays opaque
   and its text keeps apple.css's colours. */
.ink-live body.ap.home .cta-band { background-color: var(--ap-tile); }

/* The footer is part of "the whole landing page", so it takes the wash too.
   92%, not the 85% the sections get, and its links go a shade darker: this is
   the densest block of small type on the site, 13 to 15px, all of it needing
   the full 4.5:1 with no large-text relief. At 88% the link columns measured
   4.85 to 5.12:1, which passes but leaves nothing spare on a background that
   moves.
   The contact band deliberately stays OPAQUE: it is a tile sitting ON the wash,
   the same as every card, and stacking two translucent surfaces would put the
   ink through twice under the text inside it. */
.ink-live body.ap.home .site-footer,
.ink-live body.ap.home footer { background-color: rgba(245, 245, 247, .74); }
.ink-live body.ap.home .site-footer a,
.ink-live body.ap.home footer a,
.ink-live body.ap.home .site-footer p,
.ink-live body.ap.home footer p { color: #4a4a50; }
.ink-live body.ap.home .site-footer a:hover,
.ink-live body.ap.home footer a:hover { color: var(--ap-ink); }

/* ============================================================
   10. TWO ACTIONS IN THE HEADER: EMAIL AND CALL
   ============================================================
   Owner 2026-08-06. The primary stays the blue pill; the phone number sits
   beside it as a plain link, because two filled pills side by side compete and
   neither reads as the main action. The number is written out rather than
   hidden behind the word "Call": a visible phone number is itself the
   reassurance, which is the whole reason he asked for it.

   Hidden below 1200px, NOT at the usual 900px breakpoint: between those widths
   the nav is already tight, and a phone number is the longest string in the
   row. Nothing is lost on a phone, where the fixed action bar puts Call and
   Request a quote permanently on screen, and the mobile menu carries the
   number too. */
body.ap .nav-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}
body.ap .nav-call {
  white-space: nowrap;
  padding: 12px 14px;
  font-size: 1rem;
}
/* THE HEADER CALL LINK IS ON THE DARK BAR, so it takes the light accent, and it
   has to say so at a specificity that beats the wash overrides. Those darken
   every `.btn-ghost` for the light sections, and the header is not inside
   `main`, so nothing else excluded it: the link rendered #004e9c on #1b1f28,
   2.01:1, and the pre-deploy axe run caught it. All three selectors are needed,
   one per scope that could otherwise claim it. */
body.ap .site-header .nav-call,
.ink-live body.ap .site-header .nav-call,
.ink-live body.ap.home .site-header .nav-call { color: var(--dp-accent); }
body.ap .site-header .nav-call:hover,
.ink-live body.ap .site-header .nav-call:hover,
.ink-live body.ap.home .site-header .nav-call:hover { color: #fff; text-decoration: underline; }
@media (max-width: 1200px) {
  body.ap .nav-call { display: none; }
}



}

/* HEADER WIDTH (2026-08-31). The homepage header has always run in a 1560px
   container (body.story-dark, hero-ribbon.css) while generated pages used the
   1180px content container, so the same header had two different widths. With
   the repositioned menu the narrower one left zero clearance between the last
   link and the action buttons at 1280. Header only; content containers are
   untouched, and this is what the codebase already claims about shared chrome. */
.site-header .container { max-width: 1560px; }

/* NAV LABELS DO NOT WRAP (2026-08-31). Header labels were wrapping inside their
   own item at 1280, which reads as a broken two line menu rather than as an
   overflow, so it does not show up in a width check. Forcing nowrap makes a
   too long menu fail visibly on the next change instead of quietly. */
.nav-links .nav-link { white-space: nowrap; }

/* THE 1280 SQUEEZE (2026-08-31, measured). At 1280 the root font steps up to
   17px while the header container is still narrow, so the menu touches the
   brand on one side and the action buttons on the other. At 1200 and at 1440
   there is 70px of clearance either side; only this band is tight. In it the
   gap tightens and the phone button steps back, because Start a Review is the
   action this site is now built around. The number is still in the mobile
   action bar, the footer, on /contact and in the header again from 1440. */
@media (min-width: 1280px) and (max-width: 1439px) {
  .nav-links { gap: 1.6rem; }
}

/* BRAND, MENU, ACTIONS NEED AIR (2026-08-31). .nav-links is a flex child that
   fills the row, so its first and last items sat flush against the wordmark and
   the buttons at every width above 1400. A gap on the nav itself keeps the three
   groups apart without relying on any one of them having spare width. */
.site-header .nav { gap: 1.5rem; }

/* THE SEVENTH MENU ITEM (2026-09-01, owner asked for Security Review back).
   Seven items plus two actions need about 130px more than 1440 gives. Rather
   than drop the phone button, the digits step back below 1700 and the button
   keeps its label, so the affordance survives at every width and the number
   returns on a wide screen. It is also still in the mobile action bar, the
   footer and on /contact. */
@media (max-width: 1699px) {
  .site-header .nav-call .nav-call-num { display: none; }
}
