/* ── self-hosted variable faces ──────────────────────────────────────────────
   Was the Google Fonts CSS, loaded async. That fixed LCP but cost CLS 0.168 as
   the page reflowed on swap. Same-origin + preload keeps both metrics green,
   and drops a third-party origin from the critical path. Both fonts are OFL. */
@font-face{font-family:'Archivo';font-style:normal;font-weight:400 700;
  font-display:swap;src:url('/assets/fonts/archivo-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
  U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Martian Mono';font-style:normal;font-weight:100 800;
  font-display:swap;src:url('/assets/fonts/martianmono-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
  U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
:root{
  --ground-0:#0F1516;  /* page ground, start */
  --ground-1:#141D1D;  /* page ground, 42% */
  --ground-2:#101819;  /* page ground, end — also text on the mint band */
  --field-0:#191919;  /* dark field start — also topbar + footer */
  --field-1:#13201F;  /* dark field 62% */
  --field-2:#0E1A1B;  /* dark field end */
  --field-3:#151E1E;  /* managed-IT field start */
  --field-4:#0E1718;  /* managed-IT field end */
  --field-5:#131C1C;  /* reviews field start */
  --field-6:#0E1516;  /* reviews field end */
  --field-7:#16201F;  /* raised dark panel */
  --teal-0:#237675;  /* teal field start. Was #2C8A85, which put mint at 3.57:1 and
                        white at 4.13:1 — both under AA. #237675 gives 4.63 / 5.36 and
                        sits a hair off the brand teal #237374, so the gradient reads
                        the same. The other two stops already passed (6.76 / 10.05). */
  --teal-1:#1B5B58;  /* teal field 55% */
  --teal-2:#123F3D;  /* teal field end */
  --teal-3:#134744;  /* teal field, deep variant */
  --teal-brand:#237374;  /* the Wix brand teal */
  --teal-deep:#174D4D;  /* labels on the mint band */
  --mint:#A5FFEE;  /* electric mint — one band per page, primary buttons */
  --aqua:#00D2AA;  /* eyebrows, labels, links on dark */
  --aqua-bright:#47C6C8;  /* gradient partner for aqua */
  --txt-head:#FFFFFF;  /* headings */
  --txt-body:#C3D6D2;  /* body */
  --txt-body-2:#A8CFC7;  /* body, alt */
  --txt-body-3:#B4CBC7;  /* body, alt */
  --txt-dim:#93B8B0;  /* footer + topbar body */
  --txt-muted:#8CA6A2;  /* muted / mono labels */
  --txt-bright:#EAF2F2;  /* topbar + footer links */
  --txt-soft:#DCE8E8;  /* soft light text */
  --txt-soft-2:#E2F0EE;  /* soft light text, alt */
  --txt-soft-3:#DCF3EF;  /* soft light text, alt */
  --txt-faint:#7E9491;  /* faintest text */
  --on-mint-body:#28403D;  /* body text on mint */
  --warn:#FF9C8A;  /* warning / stop-now copy */
  --amber:#FFD166;  /* caution */
  --grey-1:#3E3E3E;  /* hairline on dark grey */
  --grey-2:#5C5C5C;  /* secondary grey */

  /* composites straight from the handoff table */
  --grad-ground: linear-gradient(180deg, var(--ground-0), var(--ground-1) 42%, var(--ground-2));
  --grad-field:  linear-gradient(165deg, var(--field-0), var(--field-1) 62%, var(--field-2));
  --grad-teal:   linear-gradient(150deg, var(--teal-0), var(--teal-1) 55%, var(--teal-2));
  --grad-accent: linear-gradient(90deg, var(--aqua-bright), var(--aqua));

  /* glass surface */
  --glass-bg:     linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));
  --glass-border: 1px solid rgba(255,255,255,.11);
  --glass-inset:  inset 0 1px 0 rgba(255,255,255,.1);
  --glass-blur:   blur(18px) saturate(1.2);

  /* Raised surfaces on a SATURATED ground (the teal bands, the mint band).
     These darken rather than lighten, and that is the whole point: a white
     wash over --teal-0 lifts the composite enough to drop cream text to
     4.02:1, which is how the routing band shipped an AA failure that a
     non-compositing contrast check reported as clean. Darkening deepens the
     card and clears AA. On the near-black page ground the usual white-alpha
     washes still apply — this pair is only for coloured grounds. */
  --shade-1: rgba(0,0,0,.14);  /* raised card on a teal band */
  --shade-2: rgba(0,0,0,.20);  /* the highlighted card in a set */

  /* hairlines, light -> heavy */
  --line-1: rgba(255,255,255,.09);
  --line-2: rgba(255,255,255,.11);
  --line-3: rgba(255,255,255,.18);
  /* Form-control boundary. Deliberately heavier than the hairlines above:
     WCAG 2.2 SC 1.4.11 wants 3:1 for anything that identifies a control, and
     --line-2 measured 1.39:1 against the ground (--line-3 only reaches 1.77).
     At .35 the composite is rgb(102,108,108) over --ground-1 = 3.21:1.
     Controls must announce their own extent; dividers do not, so this token is
     scoped to fields and must not be used for decorative rules. */
  --line-ctl: rgba(255,255,255,.35);

  /* type — the single source of truth. 227 inline literals were collapsed into
     these two tokens, so changing a face is a one-line edit here, not a sweep
     across 69 pages.

     Archivo replaced 'Helvetica Neue'. The old stack was not a choice, it was a
     bug: Helvetica Neue ships on macOS and iOS, Helvetica ships on neither
     Windows nor Android, so the entire site rendered in ARIAL for essentially
     every visitor — and this is an Edmonton computer-repair shop whose audience
     is overwhelmingly on Windows. The site was paying to download a webfont for
     its 12px labels while its headlines fell back to a system default.

     Both faces are variable and loaded from Google Fonts on all 69 pages with
     display=swap. Fallbacks stay in place for the swap period. */
  --font-sans: Archivo, 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-mono: 'Martian Mono', ui-monospace, 'Cascadia Mono', Consolas, monospace;

  /* radii */
  --r-btn: 7px;
  --r-card: 14px;
  --r-card-lg: 16px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   rightek.ca — dark-first redesign.
   Every colour is a token in :root above; no hex is repeated across the 69 pages.
   ═══════════════════════════════════════════════════════════════════════════ */
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{
  background:var(--ground-0);
  background-image:var(--grad-ground);
  background-attachment:fixed;
  color:var(--txt-body);
  font-family:var(--font-sans);
  -webkit-font-smoothing:antialiased;
  /* Chrome on Android "font boosting" rescales text inside some blocks on its
     own, which is why a layout can look correct in a resized desktop window and
     wrong on the actual phone. Opting out is not a zoom restriction: pinch zoom
     and the browser's own text-size setting still work. */
  -webkit-text-size-adjust:100%;
  text-size-adjust:100%;
  line-height:1.6;
  /* 100vh on mobile is the LARGE viewport -- the height the page would have if
     the URL bar were hidden -- so it is taller than what you can actually see,
     and the page grows and shrinks as the toolbar slides away. dvh tracks the
     real visible height. vh stays first as the fallback for old browsers. */
  min-height:100vh;
  min-height:100dvh;
}

/* `background-attachment:fixed` forces the browser to repaint the gradient on
   every scroll frame. On desktop that is fine. On iOS Safari it is a known
   jank source -- the fixed layer is composited against a viewport that is
   itself resizing as the toolbar hides, so the background visibly steps or
   tears while scrolling. Touch devices get the same gradient, painted once. */
@media (hover:none){
  body{background-attachment:scroll}
}
img{max-width:100%;display:block}
a{color:var(--aqua);text-decoration:none}
@media (hover:hover){a:hover{color:var(--mint)}}
h1,h2,h3,h4{line-height:1.06;letter-spacing:-.025em;font-weight:600;text-wrap:balance;color:var(--txt-head)}
p{text-wrap:pretty}
::selection{background:var(--mint);color:var(--field-0)}
/* Two-tone focus ring. A single aqua outline measured 1.68:1 against the mint
   sections; WCAG 2.2 SC 1.4.11 wants 3:1. Dark inner + mint outer means one of
   the two always clears it, whatever the ring lands on. */
:focus-visible{outline:2px solid var(--ground-0);outline-offset:2px;
  box-shadow:0 0 0 5px var(--mint)}

/* Skip link: off-screen until focused, then the first thing on the page.
   :focus, not :focus-visible -- it is only ever reached by keyboard. */
.skip-link{position:absolute;left:-9999px;top:0;z-index:200;
  background:var(--mint);color:var(--ground-0);
  font:600 15px/1 var(--font-sans);padding:15px 24px;
  border-radius:0 0 var(--r-btn) 0;text-decoration:none}
.skip-link:focus{left:0}
main:focus{outline:none}

/* ── entrance motion — deliberately fail-safe ────────────────────────────────
   Pure CSS, transform-only, `both` fill. No opacity anywhere in it and no JS
   behind it: a block that never animates is still at its final visible state.
   An earlier build hid content with opacity:0 and revealed it from JS, and any
   time that JS didn't run (background tab, prerender, missed scroll) the page
   rendered blank. Do not reintroduce either. */
@keyframes rkRise{from{transform:translateY(18px)}to{transform:none}}
@keyframes rkFade{from{transform:translateY(14px)}to{transform:none}}
@keyframes rkDrift{to{background-position:88px 88px,88px 88px}}
/* ONE authored moment: the h1, once, where somebody is actually looking.
   This used to be `[data-rv]{animation:rkRise .62s both}` with six staggered
   nth-child delays, applied to 1,770 elements across 70 pages. The craft floor
   asks for "one authored moment, not scattered effects and not one identical
   entrance on every section", and that was the latter at scale.

   It was also largely wasted: the animation is pure CSS with no scroll trigger,
   so every one of those 1,770 fired on load. The below-fold ones - which is
   most of them - finished before the reader had scrolled anywhere near, so the
   work was done and never seen. The hero, the one place an entrance would land,
   had none at all.

   Transform-only from an already-visible default, as before: if it never runs,
   the heading is still exactly where it belongs. */
main h1{animation:rkRise .62s cubic-bezier(.22,.61,.36,1) both}

[data-r="glass"]{backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur)}

/* ── section texture ─────────────────────────────────────────────────────────
   The faint overlay that sits between the section gradient and its content, on
   11 elements across 6 pages. It used to be declared inline at every one of
   them, so changing it meant editing 11 places and changing it back meant
   editing 11 more.

   It is now ONE token. Swap --texture below and every instance follows; set it
   to `none` and the texture is gone while the gradient underneath is untouched.
   The alternatives are kept here rather than deleted, because the choice is
   genuinely reversible and the next person should be able to see what was
   already considered. */
:root{
  /* — in use — */
  --texture:
    repeating-linear-gradient(to right, rgba(0,210,170,.07) 0 1px, transparent 1px 88px),
    repeating-linear-gradient(to bottom, rgba(0,210,170,.07) 0 1px, transparent 1px 88px);

  /* — alternatives, swap into --texture above —
     DOTS   : plotting paper. Marks the same 88px rhythm at the intersections
              only, so the field reads as measured without being ruled.
       radial-gradient(circle at 1px 1px, rgba(0,210,170,.16) 1px, transparent 0) 0 0/88px 88px;

     HATCH  : a single 45° rule. Directional rather than static, and it does not
              fight the horizontals of the ledger rows the way a grid does.
       repeating-linear-gradient(45deg, rgba(0,210,170,.045) 0 1px, transparent 1px 16px);

     GRAIN  : no structure at all — two fine gradients at odd angles give the
              field tooth without drawing a system on top of the content.
       repeating-linear-gradient(37deg, rgba(0,210,170,.05) 0 1px, transparent 1px 3px),
       repeating-linear-gradient(-52deg, rgba(255,255,255,.02) 0 1px, transparent 1px 4px);

     RULES  : horizontals only. Reads as lined stock and lets the drift animate
              along one axis instead of two.
       repeating-linear-gradient(to bottom, rgba(0,210,170,.06) 0 1px, transparent 1px 26px);

     NONE   : none;
  */
}
[data-r="grid"]{
  position:absolute;inset:0;pointer-events:none;
  background-image:var(--texture);
  animation:rkDrift 30s linear infinite;
}

/* position:sticky is bounded by its containing block. js/site.js injects the nav
   into #site-nav, and a normal block div there becomes that containing block —
   so the header would scroll away as soon as the nav's own box ended. Both the
   host and the component root must be display:contents. HANDOFF.md predicted
   this stopped mattering in the static build; it does not, because of the host. */
#site-nav{display:contents}

/* ── nav ─────────────────────────────────────────────────────────────────── */
[data-r="nav-link"]{position:relative}
[data-r="nav-link"]::after{
  content:"";position:absolute;left:0;right:0;bottom:-7px;height:2px;border-radius:2px;
  background:var(--grad-accent);transform:scaleX(0);transform-origin:left;
  transition:transform .24s cubic-bezier(.22,.61,.36,1);
}
[data-r="nav-link"][data-on="1"]::after{transform:scaleX(1)}
@media (hover:hover){[data-r="nav-link"]:hover::after{transform:scaleX(1)}}
[data-r="nav-link"][data-on="1"]{color:var(--mint)}
[data-r="mobile"]{display:none}
[data-r="mobile"].is-open{display:block}
@media(max-width:1180px){
  [data-r="nav"]{display:none!important}
  /* The burger must push itself right. On desktop the gap is held open by the
     nav and the phone CTA, both of which carry `margin-left:auto` -- and both
     of which are hidden here. With them gone nothing was pushing, so the
     burger sat immediately beside the logo behind a 36px gap with the rest of
     the bar empty to its right, which reads as an unfinished header rather
     than a menu control. The auto margin has to live on whatever is actually
     last in the bar, so at this width that is the burger. */
  [data-r="burger"]{display:inline-flex!important;margin-left:auto}
  [data-r="cta"]{display:none!important}
}
@media(min-width:1181px){
  [data-r="mobile"]{display:none!important}
}
body.nav-open{overflow:hidden}

/* ── footer ──────────────────────────────────────────────────────────────── */
[data-r="fl"]{transition:color .16s ease}

/* ── star ratings ────────────────────────────────────────────────────────────
   Drawn, not typed. These were the ★ character (U+2605) set in var(--font-mono),
   and Martian Mono has no glyph for it — measured: a digit is 14px in Martian
   and 10px in a deliberately-absent face, so the font IS applied, while the star
   measures 16.66px in BOTH, which only happens when it falls back. So the rating
   rendered in Segoe UI Symbol on Windows, Apple Symbols on macOS and Noto on
   Android: three different shapes and weights on the single most trust-bearing
   element on the site.

   The SVG scales with font-size, so the existing inline sizing still drives it,
   and colour still comes from --amber via currentColor. */
.rk-stars{display:inline-flex;align-items:center;line-height:1;color:var(--amber)}
.rk-stars svg{height:.92em;width:auto;display:block;fill:currentColor}

/* ── video facade ────────────────────────────────────────────────────────────
   A YouTube iframe pulls well over a megabyte before anyone presses play, and
   SEO_BUILD_SPEC caps the homepage at 800KB total. So this ships a self-hosted
   thumbnail and swaps in the iframe on click.

   The facade is a real <a> to the watch page, not a <div>. With no JS it is an
   ordinary link that opens YouTube; with JS it upgrades to inline playback.
   Same fail-safe rule as the entrance motion: nothing important may depend on a
   script having run. */
[data-r="video"]{
  position:relative;display:block;aspect-ratio:16/9;
  border:1px solid var(--line-2);border-radius:var(--r-card);overflow:hidden;
  box-shadow:var(--glass-inset),0 24px 60px rgba(0,0,0,.4);
  background:var(--field-7);
}
[data-r="video"] img{width:100%;height:100%;object-fit:cover;display:block}
[data-r="video"] .rk-play{
  position:absolute;left:50%;top:50%;width:76px;height:76px;margin:-38px 0 0 -38px;
  border-radius:100px;background:var(--mint);
  box-shadow:0 14px 34px rgba(0,0,0,.45);
  transition:transform .24s cubic-bezier(.22,.61,.36,1);
}
/* The triangle is drawn, not a glyph — same reason the star ratings are. */
[data-r="video"] .rk-play::after{
  content:"";position:absolute;left:30px;top:24px;
  border-style:solid;border-width:14px 0 14px 22px;
  border-color:transparent transparent transparent var(--ground-0);
}
@media (hover:hover){[data-r="video"]:hover .rk-play{transform:scale(1.06)}}
[data-r="video"]:focus-visible{outline:2px solid var(--ground-0);outline-offset:2px;
  box-shadow:0 0 0 5px var(--mint)}
@media (prefers-reduced-motion: reduce){
  [data-r="video"] .rk-play{transition:none}
  @media (hover:hover){[data-r="video"]:hover .rk-play{transform:none}}
}

/* ── the mint band inverts the global ink ────────────────────────────────────
   `h1,h2,h3,h4{color:var(--txt-head)}` and `a{color:var(--aqua)}` are correct
   on the near-black ground and near-invisible on mint. Measured on /contact
   before this rule: an h2 at **1.16:1** (white on mint) and an inline link at
   **1.68:1**. Both inherited the global colour because their inline styles set
   size but not colour.

   Six pages carry a mint band, so this is scoped once rather than patched per
   element — the same reasoning as the raised-surface shades. Anything setting
   its own colour inline still wins, so deliberate exceptions are unaffected. */
/* The base ink first, so anything that INHERITS gets a mint-appropriate colour
   rather than the dark-ground body token. /pricing's `$85/hr` set a font and
   no colour, inherited --txt-body from <body>, and measured 1.38:1 on the
   band. Scoping the headings and links alone would not have caught it — the
   failing element was an unstyled span. */
section[style*="background:var(--mint)"]{color:var(--on-mint-body)}
section[style*="background:var(--mint)"] h1,
section[style*="background:var(--mint)"] h2,
section[style*="background:var(--mint)"] h3,
section[style*="background:var(--mint)"] h4{color:var(--ground-2)}
section[style*="background:var(--mint)"] a{color:var(--teal-2)}

/* ── tap targets in the shared chrome ────────────────────────────────────────
   WCAG 2.2 SC 2.5.8 is Level AA and wants 24x24 CSS px. Measured before this
   rule: footer links 21-22px and breadcrumb links 12px — roughly half the
   minimum, on every one of the 69 pages, because they live in the injected
   chrome and in the page furniture.

   The spec's "inline" exception does not rescue them: it covers a link sitting
   in a sentence, not a column of navigation. Padding is used rather than a
   min-height so the hit area grows without the text moving. */
footer a.rkh5{padding:7px 0}
/* .rkh4 is the 12px mono arrow-link ("ALL SERVICES →", "ARRANGE A PICK-UP →").
   At 12px/1 it measured 12px tall — half the AA minimum. */
a.rkh4{padding:7px 0}
[data-r="crumbs"] a,nav[aria-label="Breadcrumb"] a{display:inline-block;padding:7px 0}

/* ── shared page furniture ───────────────────────────────────────────────── */
.rk-mono{font-family:var(--font-mono)}
.scroller{overflow-x:auto;-webkit-overflow-scrolling:touch}

/* ── reduced motion, deliberately not a blanket kill ─────────────────────────
   This block used to be `*,*::before,*::after{transition-duration:.01ms
   !important}` — the copy-paste reset that turns every transition off. It is
   the wrong tool, and the impeccable audit is right to flag it.

   `prefers-reduced-motion` is a request about MOVEMENT: vestibular disorders
   are triggered by things travelling across the screen, scaling, parallaxing
   or looping. WCAG 2.3.3 is about motion, not colour. A blanket kill also
   removes every colour and opacity transition, which is not what was asked
   for and takes the feedback with it: hovers, focus states and form validation
   all snap, and the interface reads as broken rather than calm.

   So movement goes and feedback stays. */
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto}

  /* 1. Travel. The entrance slides and the expanding route bodies. */
  [data-rv],[data-r="route-body"]{animation:none!important;transform:none!important}

  /* 2. The infinite one, which matters most. `rkDrift` loops a background
     across 30s forever on 30 pages; continuous motion is the single worst
     case for anyone who set this preference. Declared inline in the markup,
     hence the attribute selector as well as the class. */
  [style*="rkDrift"],[data-r="grid"]{animation:none!important}

  /* 3. Hover nudges: pure decoration. */
  [data-r="video"] .rk-play{transition:none}
  @media (hover:hover){[data-r="video"]:hover .rk-play{transform:none!important}}
  #ts .ts__opt:active{transform:none}
@media (hover:hover){#ts .ts__opt:hover{transform:none}}

  /* 4. The dropdown appears instead of sliding up. `translateX(-50%)` is
     LAYOUT — it is what centres the panel under its trigger — so it has to
     survive; only the 6px rise is removed. A blanket `transform:none` here
     would have shunted every menu half its width off-centre. */
  .rk-menu{transform:translateX(-50%) translateY(0)!important;
    transition:opacity .14s ease,visibility .14s ease}

  /* 5. The scroll hairline tracks the scroll position rather than easing
     toward it, so it never trails the thing it is reporting. */
  [data-r="progress"]{transition:none!important}

  /* 6. KEPT: colour, background, border and opacity. These are how an element
     tells you it registered a hover, a focus or an error, they are not
     vestibular triggers, and removing them is what made the old rule harmful.
     Shortened rather than deleted. The small state indicators that survive —
     the nav underline, the caret rotation, the route +/− — are a few pixels
     each and carry meaning; they are the "intentional alternative that
     preserves state change" rather than an absence of one. */
  a,button,summary,input,select,textarea,.rkc,.rk-menu a,[data-r="route"] summary{
    transition-duration:.14s!important}
}

/* ── hover states lifted out of the components ──────────────────────────────
   Read this before touching a .rkh* rule.

   Every element carrying one of these classes declares `background` and/or
   `color` INLINE. An inline declaration outranks any author stylesheet rule
   regardless of selector specificity, so a rule written here parses, matches,
   and then does nothing. That is not hypothetical — it is how the site shipped:

     .rkh1:hover{background:#fff}   never fired on the mint primary buttons
       (inline `background:var(--mint)` wins) and fired ONLY on the topbar
       links, which carry no inline background — putting --txt-bright #EAF2F2
       on #FFFFFF at 1.14:1. The one place it worked was the one place it hurt.
     .rkh3:hover{border-color:...}  never fired either; ledger rows declare
       `border-bottom` inline, which wins.
     .rkh4 through .rkh12           had no rule at all: ~15 controls across 9
       pages, including the contact form's submit button, with no hover state.

   Two levers reach these elements without !important:
     `filter`           — never set inline; works on fills, gradients and text.
     `background-color` — lands only where no inline `background` exists
                          (ghost buttons, ledger rows), which is exactly where
                          a wash is the right treatment anyway.
   Anything that must override an inline declaration needs !important, and the
   two below are deliberate. */
@media (hover:hover){.rkh1:hover,.rkh4:hover,.rkh5:hover,.rkh6:hover,.rkh7:hover,.rkh8:hover,.rkh9:hover,.rkh10:hover,.rkh11:hover,.rkh12:hover{filter:brightness(1.12)}}
/* Lands on ghost buttons and ledger rows; ignored where a fill is inline. */
@media (hover:hover){.rkh2:hover,.rkh3:hover,.rkh5:hover,.rkh6:hover,.rkh9:hover,.rkh10:hover,.rkh12:hover{background-color:rgba(255,255,255,.08)}}
/* Inline `border-bottom` on the row and inline `color` on the topbar link both
   need overriding; these are the only two !important rules in the sheet. */
@media (hover:hover){.rkh3:hover{border-bottom-color:var(--aqua)!important}}
@media (hover:hover){.rkh1:hover{background:#fff;color:var(--ground-0)!important}}
[class*="rkh"]{transition:filter .16s ease,background-color .16s ease}

/* ── dropdown navigation ─────────────────────────────────────────────────────
   The approved Nav component is six flat links, which left ~30 destinations
   two clicks away and made the site feel like pages were missing. Dropdowns
   restored in the redesign's own language. */
/* closed state lives here, not inline: an inline opacity:0 would outrank the
   .is-open class and the panel could never be shown. */
.rk-menu{opacity:0;visibility:hidden}
@media (hover:hover){.rk-droptoggle:hover,.rk-drop:hover .rk-droptoggle{color:var(--mint)}}
.rk-drop:focus-within .rk-menu,.rk-menu.is-open{
  opacity:1;visibility:visible;transform:translateX(-50%) translateY(0)
}
@media (hover:hover){.rk-drop:hover .rk-menu{
  opacity:1;visibility:visible;transform:translateX(-50%) translateY(0)
}}
.rk-droptoggle[aria-expanded="true"] .rk-caret{transform:rotate(180deg)}
@media (hover:hover){.rk-drop:hover .rk-caret{transform:rotate(180deg)}}
@media (hover:hover){.rk-menu a:hover{background:rgba(0,210,170,.12);color:var(--mint)}}
@media (hover:hover){.rk-menu a:hover small{color:var(--aqua)}}
.rk-droptoggle{position:relative}
.rk-droptoggle::after{
  content:"";position:absolute;left:0;right:14px;bottom:-7px;height:2px;border-radius:2px;
  background:var(--grad-accent);transform:scaleX(0);transform-origin:left;
  transition:transform .24s cubic-bezier(.22,.61,.36,1)
}
.rk-droptoggle[aria-expanded="true"]::after{transform:scaleX(1)}
@media (hover:hover){.rk-droptoggle:hover::after{transform:scaleX(1)}}

/* mobile accordions — in the flow, never absolutely positioned, so a panel can
   never end up off the left edge of the phone */
.rk-mpanel.is-open{display:block !important}
.rk-mtoggle[aria-expanded="true"] .rk-caret{transform:rotate(180deg)}
@media(max-width:1180px){
  .rk-drop{display:none !important}
}

/* ── contact form fields ─────────────────────────────────────────────────────
   These came out of the design component with a data-r="field" hook that
   nothing targeted, so they rendered as default white browser inputs on a dark
   page. Treatment matches the cards: faint white fill, --line-2 border. Focus
   is left to the global two-tone :focus-visible ring. */
/* `background-color`, never the `background` shorthand. The shorthand resets
   background-image to none, and [data-r="field"]:hover (0,2,0) outranks
   select[data-r="field"] (0,1,1) where the caret SVG is declared below — so a
   shorthand here erased the arrow on hover, and appearance:none had already
   removed the native one. A hovered select became indistinguishable from a
   text input. */
[data-r="field"]{width:100%;box-sizing:border-box;
  font:400 16.5px/1.5 var(--font-sans);color:var(--txt-soft);
  background-color:rgba(255,255,255,.04);border:1px solid var(--line-ctl);
  border-radius:var(--r-btn);padding:14px 15px;
  -webkit-appearance:none;appearance:none;
  transition:border-color .16s ease,background-color .16s ease}
[data-r="field"]::placeholder{color:var(--txt-muted);opacity:1}
@media (hover:hover){[data-r="field"]:hover{border-color:var(--txt-faint);background-color:rgba(255,255,255,.06)}}
/* native select arrow is gone with appearance:none — draw our own */
select[data-r="field"]{cursor:pointer;padding-right:42px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%238CA6A2' stroke-width='1.8' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");background-repeat:no-repeat;
  background-position:right 15px center}
/* the dropdown list itself is OS-rendered; without this it inherits white */
/* Option lists are drawn by the OS and inherit the select's colour. On a dark
   select that is white text on the system's white menu -- an invisible list.
   Reported live on the managed IT calculator, whose select carries
   data-r="level" (its JS hook) and so never matched the rule below. Style
   every option rather than relying on one attribute being present. */
select option{background:var(--field-0);color:var(--txt-soft)}
select[data-r="field"] option{background:var(--field-0);color:var(--txt-soft)}

/* ── the triage instrument ───────────────────────────────────────────────────
   The homepage's first viewport. Three routes a visitor can be on — one broken
   machine, files they cannot lose, a business that cannot go down — each
   showing its real price at rest and expanding in place to its proof and its
   services.

   Built on <details>/<summary> deliberately: it works with no JS at all, it is
   keyboard operable for free, and it cannot end up in the state this codebase
   has been bitten by before, where content is hidden by CSS and only revealed
   by a script that might never run. Closed here means "collapsed", never
   "invisible with the text still in the DOM waiting on JS".

   The routes ARE ledger rows — the site's signature pattern promoted from a
   sidebar decoration to the page's structure. */
[data-r="triage"]{border-top:1px solid var(--line-2)}
[data-r="route"]{border-bottom:1px solid var(--line-2)}
[data-r="route"] summary{
  list-style:none;cursor:pointer;display:grid;
  grid-template-columns:minmax(0,1fr) auto auto;
  gap:18px;align-items:baseline;padding:26px 0;
  transition:background-color .16s ease}
/* Global, not scoped to the routes. The FAQ rows are <details> too and shipped
   with an inline list-style:none and no indicator of any kind — six rows that
   gave no sign they open, to an audience PRODUCT.md describes as stressed,
   older or non-technical. Suppressing the native marker is only safe if
   something is drawn in its place; see details[data-rv] below. */
summary::-webkit-details-marker{display:none}
@media (hover:hover){[data-r="route"] summary:hover{background-color:rgba(255,255,255,.04)}}
/* The +/- is drawn, not a glyph, so it cannot fall back to a missing font. */
[data-r="route"] .rk-pm{position:relative;width:16px;height:16px;flex:none;align-self:center}
[data-r="route"] .rk-pm::before,[data-r="route"] .rk-pm::after{
  content:"";position:absolute;background:var(--aqua);
  transition:transform .24s cubic-bezier(.22,.61,.36,1),opacity .24s ease}
[data-r="route"] .rk-pm::before{left:0;right:0;top:7px;height:2px}
[data-r="route"] .rk-pm::after{top:0;bottom:0;left:7px;width:2px}
[data-r="route"][open] .rk-pm::after{transform:scaleY(0);opacity:0}
[data-r="route"][open] summary{background-color:rgba(255,255,255,.03)}
/* The expanding panel is the one place the fixed grid is safe: it is content,
   not layout, and the collapse block below still catches it. */
/* The 34px right pad is the summary's +/- column (16px) plus its gap (18px).
   Without it the expanded ledger's prices overshoot the summary price above
   them by exactly that much, and the column stops reading as one ledger. */
[data-r="route-body"]{padding:4px 34px 30px 0;animation:rkFade .38s cubic-bezier(.22,.61,.36,1) both}
[data-r="route"] summary:focus-visible{outline:2px solid var(--ground-0);outline-offset:-2px;
  box-shadow:inset 0 0 0 5px var(--mint)}
@media(max-width:1180px){
  /* Phone: the price drops under the label rather than chasing it across a
     narrow row, and the +/- keeps its own column spanning both rows instead of
     being pushed onto a third line of its own. */
  [data-r="route"] summary{grid-template-columns:minmax(0,1fr) auto;
    gap:7px 14px;padding:20px 0;align-items:start}
  [data-r="route"] summary .rk-label{grid-column:1;grid-row:1}
  [data-r="route"] summary .rk-price{grid-column:1;grid-row:2;text-align:left}
  [data-r="route"] summary .rk-pm{grid-column:2;grid-row:1 / span 2;align-self:center}
  /* No +/- column to align to once the price wraps under the label. */
  [data-r="route-body"]{padding-right:0}
}

/* FAQ disclosure indicator. Drawn from two gradient bars for the same reason
   the instrument's +/- is drawn: a text glyph can fall back to a missing font,
   and this is the only cue that six rows are openable at all. Collapses to a
   minus when open, matching the routes above it. */
details[data-rv] > summary{display:flex;align-items:center;gap:16px}
details[data-rv] > summary::after{
  content:"";flex:none;margin-left:auto;width:14px;height:14px;
  background:linear-gradient(var(--aqua),var(--aqua)) center/14px 2px no-repeat,
             linear-gradient(var(--aqua),var(--aqua)) center/2px 14px no-repeat;
  transition:opacity .24s ease}
details[data-rv][open] > summary::after{
  background:linear-gradient(var(--aqua),var(--aqua)) center/14px 2px no-repeat}

/* ── responsive collapse for the inline page grids ──────────────────────────
   Every multi-column grid on this site is declared inline, per-page, and until
   now nothing unwound any of them at any width: `grid-template-columns` did
   not appear in this stylesheet at all. /contact declares `1fr 380px`, so on a
   375px phone the fixed 380px aside track alone exceeded the 311px content box
   and the page scrolled 234px sideways with the form column crushed to 133px.
   `data-r="layout"` is on 3 pages, `split` on 6, `row` on 6 — same defect.

   !important is required and not negotiable: an inline declaration outranks
   any stylesheet rule, so without it these do nothing.

   1180px, not a new intermediate tier — see The One Breakpoint Rule in
   DESIGN.md. Grids using fr units or auto-fit already reflow correctly and are
   deliberately left alone; only tracks with a fixed px column are listed. */
@media(max-width:1180px){
  [data-r="layout"],[data-r="split"],[data-r="frow"],[data-r="body-grid"],
  [data-r="row"],[data-r="prow"],[data-r="svc-row"],.rkh5,.rkh12{
    grid-template-columns:minmax(0,1fr)!important;gap:20px!important}
  /* A sticky aside is a two-column affordance; stacked, it would pin a card
     over the content it was beside. */
  [data-r="aside"]{position:static!important}
  /* Ledger rows stack, so the price no longer needs its own reserved track. */
  [data-r="row"],.rkh5,.rkh12{align-items:start!important}
}

/* ── the troubleshooter instrument ───────────────────────────────────────────
   js/troubleshooter.js renders a 60-node diagnostic into #ts using the class
   names below — and not one of them existed in this stylesheet. The whole tool
   was shipping as raw user-agent buttons: 13.3px system text on #F0F0F0, 21px
   tall, on the near-black ground. Its answer buttons are the primary
   interaction of the page and they were the least designed thing on the site.

   check_hooks.py is supposed to catch markup asking for absent styling, but it
   reads .html only, so JS-injected class names were invisible to it. Extended
   there too.

   Everything the script renders is a child of #ts, so every rule is scoped to
   it. `.btn` is a name generic enough to collide with anything added later;
   scoped, it cannot. */
#ts{display:block}

/* progress. A thin bar rather than "step 3 of 7" — the tree branches, so a
   step count would be a number the tool cannot honestly know. */
#ts .ts__progress{height:3px;border-radius:2px;background:rgba(255,255,255,.08);overflow:hidden}
/* scaleX, not width — animating width thrashes layout, and the header's own
   scroll progress bar (`data-r="progress"`) already establishes this pattern. */
#ts .ts__bar{height:100%;width:100%;transform-origin:left;
  background:var(--grad-accent);
  transition:transform .42s cubic-bezier(.22,.61,.36,1)}

/* the answers so far, so the reader can see the reasoning that got them here */
#ts .ts__path{display:flex;flex-wrap:wrap;gap:7px;margin-top:16px}
/* --txt-faint measured 4.42:1 against the chip fill — under AA for 11px. */
#ts .ts__path span{font:500 11px/1 var(--font-mono);letter-spacing:.06em;
  color:var(--txt-muted);background:rgba(255,255,255,.05);
  border:1px solid var(--line-2);border-radius:100px;padding:6px 10px}
#ts .ts__path span + span::before{content:"";display:inline-block}

#ts .ts__card{margin-top:22px}
/* The Title step from the type ramp — this is a card heading. It was a bespoke
   clamp(20px,2.3vw,26px), which is exactly the kind of off-ramp step the ramp
   exists to prevent. */
#ts .ts__q{font-size:clamp(22px,2.6vw,32px);line-height:1.25;letter-spacing:-.025em;
  color:var(--txt-head);font-weight:600}
/* p.ts__hint also appears inside .ts__result, where `#ts .ts__result p` would
   otherwise out-specify this and render the closing note as body copy. */
#ts .ts__hint,#ts .ts__result p.ts__hint{font-size:15px;line-height:1.6;
  color:var(--txt-muted);margin-top:9px}

/* the answer buttons. --line-ctl, not --line-2: these are controls, and SC
   1.4.11 wants a 3:1 boundary. --line-2 measures 1.39:1. */
#ts .ts__opts{display:grid;gap:10px;margin-top:22px}
#ts .ts__opt{display:block;width:100%;text-align:left;
  font:400 16.5px/1.45 var(--font-sans);color:var(--txt-soft);
  background:rgba(255,255,255,.045);border:1px solid var(--line-ctl);
  border-radius:var(--r-card);padding:16px 18px;cursor:pointer;
  transition:background .18s ease,border-color .18s ease,transform .18s cubic-bezier(.22,.61,.36,1)}
@media (hover:hover){#ts .ts__opt:hover{background:rgba(255,255,255,.09);border-color:var(--aqua);transform:translateX(3px)}}
#ts .ts__opt:active{transform:translateX(1px)}

#ts .ts__nav{display:flex;gap:10px;margin-top:22px;flex-wrap:wrap}

/* buttons */
#ts .btn{font:600 14px/1 var(--font-sans);border-radius:100px;padding:15px 20px;
  cursor:pointer;border:1px solid transparent;display:inline-flex;align-items:center;
  gap:8px;transition:background .18s ease,border-color .18s ease,filter .18s ease}
#ts .btn--primary{background:var(--mint);color:var(--ground-2);border-color:var(--mint)}
@media (hover:hover){#ts .btn--primary:hover{filter:brightness(1.08)}}
#ts .btn--ghost{background:transparent;color:var(--txt-soft);border-color:var(--line-ctl)}
@media (hover:hover){#ts .btn--ghost:hover{background:rgba(255,255,255,.08);border-color:var(--aqua)}}
/* SC 1.4.3 exempts disabled controls, so this is about looking deliberate
   rather than looking broken — which is how the UA default read. */
#ts .btn:disabled{opacity:.38;cursor:not-allowed}
@media (hover:hover){#ts .btn:disabled:hover{background:transparent;border-color:var(--line-ctl)}}

/* result */
/* Same Title step as .ts__q — the question and the verdict are the same rank
   in the instrument, and they should not be set at two invented sizes. */
#ts .ts__result h2{font-size:clamp(22px,2.6vw,32px);line-height:1.25;
  letter-spacing:-.025em;margin:14px 0 12px}
#ts .ts__result p{font-size:16.5px;line-height:1.65;color:var(--txt-body);margin-bottom:12px}
#ts .ts__result p strong{color:var(--txt-head)}
/* The Label step — uppercase tracked mono is the Label role, so it takes 12px
   like every other eyebrow, not a bespoke 11. */
#ts .ts__sev{display:inline-flex;align-items:center;
  font:500 12px/1 var(--font-mono);letter-spacing:.18em;text-transform:uppercase;
  border-radius:100px;padding:7px 12px}
#ts .ts__sev--crit{color:var(--warn);background:rgba(255,156,138,.13);border:1px solid rgba(255,156,138,.34)}
#ts .ts__sev--warn{color:var(--amber);background:rgba(255,209,102,.12);border:1px solid rgba(255,209,102,.32)}
#ts .ts__sev--info{color:var(--aqua);background:rgba(0,210,170,.11);border:1px solid rgba(0,210,170,.3)}

/* Steps are sequential, but they get a drawn marker rather than numerals —
   the numbered treatment tested badly with the owner and a count adds nothing
   a reader cannot see. */
#ts .ts__steps{list-style:none;display:grid;gap:11px;margin:4px 0 6px}
#ts .ts__steps li{display:grid;grid-template-columns:16px minmax(0,1fr);gap:11px;
  font-size:15px;line-height:1.6;color:var(--txt-body)}
/* No radius: the two other drawn indicators in this stylesheet (the route +/-
   and the FAQ caret) are square-capped 2px bars, and a 1px radius is off the
   rounded scale besides. */
#ts .ts__steps li::before{content:"";width:8px;height:2px;background:var(--aqua);
  margin-top:11px}

#ts .ts__actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:24px}

/* The copyable summary. Newline-delimited plain text, so it needs pre-wrap or
   it collapses into one paragraph — which is what it did. */
#ts .ts__summary{white-space:pre-wrap;font:400 13px/1.6 var(--font-mono);
  color:var(--txt-muted);background:rgba(0,0,0,.22);border:1px solid var(--line-2);
  border-radius:var(--r-card);padding:18px;margin-top:24px;
  max-height:320px;overflow:auto;-webkit-overflow-scrolling:touch}

@media(max-width:640px){
  #ts .ts__actions .btn,#ts .ts__nav .btn{flex:1 1 100%;justify-content:center}
  #ts .ts__opt{padding:15px 15px;font-size:15px}
}

/* ── range input ─────────────────────────────────────────────────────────────
   The managed-IT calculator's slider rendered at the UA default, a 16px thumb.
   SC 2.5.8 exempts unstyled UA controls, but 16px is a genuinely awkward thing
   to hit on a phone, and the calculator is the page's only interaction. */
input[type="range"]{-webkit-appearance:none;appearance:none;background:transparent;height:26px}
input[type="range"]::-webkit-slider-runnable-track{height:4px;border-radius:2px;background:rgba(255,255,255,.16)}
input[type="range"]::-moz-range-track{height:4px;border-radius:2px;background:rgba(255,255,255,.16)}
input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  width:24px;height:24px;border-radius:50%;background:var(--mint);
  border:2px solid var(--ground-2);margin-top:-10px;cursor:pointer}
input[type="range"]::-moz-range-thumb{width:24px;height:24px;border-radius:50%;
  background:var(--mint);border:2px solid var(--ground-2);cursor:pointer}

/* ── related-service cards (.rkc) ────────────────────────────────────────────
   45 of these across 15 pages and the class had NO rule, so every one was a
   clickable card with no hover feedback at all. Exactly the defect that
   `.rkh4`-`.rkh12` had, and it survived because check_hooks only validated
   class names matching `.rkh<number>`; anything else in the markup was
   invisible to it. Widened there.

   Both properties need !important: the cards declare `border` and `background`
   inline, and an inline declaration outranks any stylesheet rule regardless of
   specificity. This is the Inline Origin Rule in DESIGN.md. */
.rkc{transition:border-color .2s ease,background-color .2s ease}
@media (hover:hover){.rkc:hover{border-color:var(--aqua)!important;
  background-color:rgba(255,255,255,.07)!important}}
@media (hover:hover){.rkc:hover h3{color:var(--mint)}}

/* ── reserved space for the injected chrome ──────────────────────────────────
   site.js writes the topbar and header into #site-nav after the document
   parses, so <main> renders at the top of the viewport and is then pushed
   down. Measured, that is a single layout shift of **0.0953** attributed to
   MAIN#main - against Google's 0.1 "good" CLS threshold, which it passes by
   four thousandths on a fast local connection. Core Web Vitals are a ranking
   signal and field data is worse than lab data, so this is not a margin worth
   keeping.

   #site-nav cannot hold the space itself: it is display:contents on purpose
   (a real box there would become the containing block and the sticky header
   would stick to it instead of the viewport). Hence a sibling spacer, which
   site.js removes in the same synchronous block that injects the chrome, so
   there is never an intermediate state where both or neither exist.

   The three values are measured, not guessed: 121px once the topbar sits on
   one line, 159px when it wraps to two, 171px when the header's own contents
   wrap as well. */
[data-r="navspace"]{height:121px}
@media(max-width:820px){ [data-r="navspace"]{height:159px} }
@media(max-width:430px){ [data-r="navspace"]{height:171px} }

/* ── drawn icons ─────────────────────────────────────────────────────────────
   The site used Unicode glyphs as its icon system: 551 &#8594; arrows, 74
   &#10003; checks and 6 &#9660; carets. That is a real failure mode here, not
   a matter of taste — this project has already been bitten by it once. The
   review stars were &#9733;, Martian Mono has no glyph for it, and every OS
   substituted a different fallback face; they had to be redrawn as SVG. The
   same exposure applied to every arrow and check on the site.

   Defined once as CSS masks rather than 625 inline SVGs, which would have added
   roughly 100 KB of markup. `background:currentColor` means they inherit text
   colour exactly as the glyphs did, so nothing that styled them needs to change.
   One stroke weight, one cap style, one size relative to the text. */
.ic{display:inline-block;width:1em;height:1em;background:currentColor;
  vertical-align:-.115em;flex:none;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain}
.ic-arrow{-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.75 8h10M8.75 4.25 12.5 8l-3.75 3.75'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.75 8h10M8.75 4.25 12.5 8l-3.75 3.75'/%3E%3C/svg%3E")}
.ic-back{-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13.25 8h-10M7.25 4.25 3.5 8l3.75 3.75'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13.25 8h-10M7.25 4.25 3.5 8l3.75 3.75'/%3E%3C/svg%3E")}
.ic-check{-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.6 6.4 12 13 4.6'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.6 6.4 12 13 4.6'/%3E%3C/svg%3E")}
.ic-caret{width:.7em;height:.7em;
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 5.75 8 10.25l4.5-4.5'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 5.75 8 10.25l4.5-4.5'/%3E%3C/svg%3E")}

/* ── before/after comparison ──────────────────────────────────────────────────
   The restoration pages described the work in prose and showed none of it,
   which is the wrong way round for a service whose whole value is visible.

   The source images were single side-by-side composites, so the halves are
   split apart at build time and normalised to identical pixel dimensions.
   That normalisation is not cosmetic: the wipe reveals one image over the
   other, so a few pixels of size mismatch reads as the subject jumping as the
   handle moves, which looks like a broken widget rather than a restoration.

   The control is a real <input type="range">, stretched invisibly over the
   frame. Dragging anywhere on the photograph works because the input IS the
   whole photograph, and arrow keys, Home/End, screen readers and the touch
   target all come from the platform rather than from re-implementing a
   slider on pointer events. Nothing here reimplements focus, roles or steps.

   Without JS the wipe cannot follow the input, so the input is hidden and the
   frame stays at the authored 50% -- a static side-by-side, which is exactly
   what the original composite was. A dead control is worse than no control. */
.rkba{margin:0}
.rkba-frame{position:relative;overflow:hidden;border-radius:var(--r-card);
  border:1px solid var(--line-2);background:var(--field-6);
  touch-action:pan-y}
.rkba-frame>img{display:block;width:100%;height:auto}
.rkba-before{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  clip-path:inset(0 calc(100% - var(--pos,50%)) 0 0)}
/* Mint scores 15.9:1 on this page's background, but the divider and handle are
   measured against PHOTOGRAPHS. Column-averaged across the full travel, the
   mint line drops to 2.40:1 over the restored portrait and spends about a
   third of its sweep under 3:1, which SC 1.4.11 wants for a control that
   conveys position. It fades exactly where the face is brightest.

   Same fix the global focus ring already uses, for the same reason: pair it
   with a dark edge so one of the two always clears, whatever it lands on.
   `outline` rather than a zero-offset box-shadow because this is a legibility
   hairline, not depth -- the grip's real shadow keeps its offset and blur. */
.rkba-line{position:absolute;top:0;bottom:0;left:var(--pos,50%);width:2px;
  margin-left:-1px;background:var(--mint);pointer-events:none;
  outline:1px solid rgba(0,0,0,.55)}
.rkba-grip{position:absolute;top:50%;left:var(--pos,50%);
  width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;
  background:var(--mint);color:var(--ground-0);
  display:flex;align-items:center;justify-content:center;gap:1px;
  pointer-events:none;outline:1.5px solid rgba(0,0,0,.5);
  box-shadow:0 2px 14px rgba(0,0,0,.5)}
.rkba-grip .ic{width:.82em;height:.82em;font-size:15px}
.rkba-tag{position:absolute;top:12px;padding:7px 10px;
  border-radius:var(--r-btn);background:rgba(0,0,0,.62);
  color:var(--txt-bright);pointer-events:none;
  font:600 11px/1 var(--font-sans);letter-spacing:.08em;text-transform:uppercase}
.rkba-tag-b{left:12px}
.rkba-tag-a{right:12px}
.rkba figcaption{margin-top:13px;font-size:14px;line-height:1.6;
  color:var(--txt-muted)}

/* The input carries no paint of its own; the frame it covers is the visual. */
.rkba-range{position:absolute;inset:0;width:100%;height:100%;margin:0;padding:0;
  -webkit-appearance:none;appearance:none;background:none;border:none;
  cursor:ew-resize}
.rkba-range::-webkit-slider-thumb{-webkit-appearance:none;width:44px;height:44px;
  opacity:0;cursor:ew-resize}
.rkba-range::-moz-range-thumb{width:44px;height:44px;opacity:0;border:none;
  cursor:ew-resize}
/* An invisible input cannot show the global ring, so the frame wears it. */
.rkba-range:focus-visible{outline:none}
.rkba-frame:has(.rkba-range:focus-visible){outline:2px solid var(--ground-0);
  outline-offset:2px;box-shadow:0 0 0 5px var(--mint)}

.rkba:not([data-live]) .rkba-range{display:none}
.rkba:not([data-live]) .rkba-grip{display:none}
/* Do not tell someone to drag a handle that was never drawn. */
.rkba:not([data-live]) .rkba-hint{display:none}

/* ── touch feedback ──────────────────────────────────────────────────────────
   Every :hover rule above now sits behind @media (hover:hover), because on a
   touch screen :hover LATCHES: the browser applies it on tap and leaves it
   applied until you touch something else. With them unguarded, the primary CTA
   turned white on tap and stayed white, service cards kept a highlighted
   border, and half the buttons on a page sat lit up at once. It looked like
   broken styling because, from the visitor's side, it was.

   Guarding them removes the only press feedback touch had, though, so it is
   replaced with something honest: a brief, uniform press state that ends when
   the finger lifts. :active cannot latch the way :hover does.

   `-webkit-tap-highlight-color` clears the grey box iOS and Android paint over
   a tapped link, which fights this and mis-hugs the shape of a rounded card. */
@media (hover:none){
  a,button,summary,label,[role="button"]{-webkit-tap-highlight-color:transparent}
  a[class^="rkh"]:active,button:active,summary:active,.rkc:active{
    filter:brightness(1.14);
    transition:filter .06s ease-out}
  .rkh1:active{filter:none;background:#fff;color:var(--ground-0)!important}
}

/* ── safe areas ──────────────────────────────────────────────────────────────
   Notches and home indicators only intrude once the viewport is told to fill
   the display, which `viewport-fit=cover` in the page meta does. Padding the
   two edges that can actually be occluded costs nothing on hardware without
   them, because env() resolves to 0 there. In portrait this mostly matters for
   the sticky header and the footer; in landscape it is the difference between
   readable body copy and copy running under the notch. */
@supports (padding:max(0px)){
  body{
    padding-left:env(safe-area-inset-left);
    padding-right:env(safe-area-inset-right)}
  #site-foot{padding-bottom:max(0px,env(safe-area-inset-bottom))}
}

/* ── hero CTA row on narrow screens ──────────────────────────────────────────
   The row is one flex line on desktop: price, qualifying note, a `flex:1`
   spacer, then the two buttons pushed to the right edge. That spacer is what
   breaks it on a phone. With `flex-wrap:wrap` the spacer is still a flex item,
   so it wraps onto a line and eats whatever space is left there, which shunts
   the primary button to the right and drops the secondary one below it at the
   left. The result is two buttons staggered diagonally down the page, looking
   placed by accident rather than by anyone.

   Below 760px the row becomes a stack: spacer removed from layout, both
   buttons full width and centred, so the primary reads as primary and the two
   line up. `!important` because the row carries its layout inline, and inline
   wins on origin regardless of specificity. */
@media (max-width:760px){
  [data-r="hero-cta"]{
    flex-direction:column!important;
    align-items:stretch!important;
    gap:12px!important}
  [data-r="hero-cta"] [data-r="spacer"]{display:none!important}
  [data-r="hero-cta"] a{width:100%;text-align:center}
}

/* ── monospace at phone widths ───────────────────────────────────────────────
   Martian Mono is already wide, and the tracking these labels carry (.08em to
   .16em) is tuned for a desktop line. On a 390px screen it is the difference
   between a breadcrumb that fits and one that wraps leaving a lone separator
   at the end of the first line, or a topbar that strands "APPOINTMENT" on a
   line of its own. Tightening the tracking on small screens buys back roughly
   a word per line without touching the type scale or the desktop rhythm. */
@media (max-width:620px){
  [style*="var(--font-mono)"]{letter-spacing:.04em!important}
  nav[aria-label="Breadcrumb"]{letter-spacing:.02em!important}
}

/* ── topbar alignment on narrow screens ──────────────────────────────────────
   The topbar is one flex row on desktop: tagline left, phone and email right.
   On a phone it wraps to two rows, and they do not line up. The contact links
   carry `padding:0 10px` so the tap target is wider than the text, which is
   correct, but it means their glyphs start 10px right of the container edge
   while the tagline above starts flush against it.

   Aligning the two rows on the left fixed that, and was still wrong. Once the
   row wraps, `justify-content:space-between` has nothing left to hold apart,
   so both rows collapse against the left edge and the strip reads as text that
   happened to wrap rather than as a designed band. Centring restores the
   intent: two short lines, balanced, obviously placed.

   Centring also removes the original problem rather than compensating for it.
   The 10px tap padding becomes symmetric, so there is no edge for it to fall
   out of alignment against, and the negative margin that used to cancel it
   would now pull the row visibly off centre. It is dropped, not kept. */
@media (max-width:620px){
  [data-r="topbar"]{justify-content:center!important;text-align:center}
  [data-r="topbar-contact"]{margin-left:0}
}

/* Calculator seniority picker. Styled here rather than inline: the chevron
   is a data: URI containing quotes, which cannot live in a style attribute
   without terminating it. appearance:none removes the native arrow, so one
   has to be drawn back or the control does not read as a dropdown at all. */
#rk-level{width:100%;box-sizing:border-box;color:#fff;
  font:600 16px/1.4 var(--font-mono);padding:13px 42px 13px 14px;
  border:1px solid var(--line-ctl,rgba(255,255,255,.35));
  border-radius:var(--r-btn);cursor:pointer;
  -webkit-appearance:none;appearance:none;
  background:rgba(255,255,255,.06) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23A5FFEE' stroke-width='1.8' fill='none' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat right 15px center}
#rk-level:hover{border-color:var(--line-3)}
