/* ==========================================================================
   Persistent UI that floats over every section: boot loader,
   top bar, mobile nav sheet, scroll-progress hairline, section readout.
   ========================================================================== */

/* ---------- cursor ----------
   REMOVED. This carried a two-part custom pointer (a 6px dot plus a ring
   trailing it by 0.5s) and, to show it, set cursor:none on body, a, button,
   summary, select and [data-cursor].

   The reason it went is affordance, not taste. Measured on the live page,
   cursor:none was resolving on the body text, every link, every button, the
   FAQ summaries and the select -- so nothing on the site showed the pointing
   hand that tells a visitor "this is clickable", and body copy showed no
   I-beam to say "this is selectable". The native cursor is also the one
   surface the OS accessibility settings reach: size, colour and high-contrast
   pointer schemes all apply to it and none of them apply to a div. A ring
   that lags the real pointer by half a second reads as the page being slow.

   Same judgement as the typography: a custom cursor is a design-studio
   signal, and the people buying infrastructure are not shopping for one.
   ========================================================================== */

/* ---------- boot loader ---------- */
#loader{ position:fixed; inset:0; z-index:100; background:var(--bg); display:flex; align-items:flex-end; justify-content:space-between; gap:30px; padding:6vw; }
#loader .bl-log{ font-size:var(--fs-sm); line-height:2.1; letter-spacing:.06em; color:var(--muted); text-transform:uppercase; }
#loader .bl-log b{ color:var(--accent); font-weight:500; }
#loader .bl-count{ font-family:var(--display); font-weight:800; font-size:clamp(4rem,18vw,15rem); line-height:.8; letter-spacing:-.04em; }
#loader .bl-count i{ font-family:var(--mono); font-style:normal; font-size:14px; color:var(--accent); vertical-align:super; }

/* ---------- boot wordmark ----------
   Sits above #loader (100) so it survives the loader's wipe and can carry on
   into the header, and below .cursor (300) so the pointer still reads.

   The mark is printed by a scan bar rather than faded in: this is a console,
   the hero terminal types, and a wordmark that simply appears would be the
   one thing on the boot screen not behaving like a machine.

   The three parts have three jobs and three timings — the word is the brand,
   the rule under it is the same hairline the section heads use, and the line
   beneath is what C2C stands for, which the rest of the page now says
   everywhere. Only the word travels; the other two are gone before it moves. */
#bootmark{
  position:fixed; inset:0; z-index:101; pointer-events:none;
  display:grid; place-items:center; padding:5vw;
}
.bm-inner{ display:inline-flex; flex-direction:column; align-items:center; }
.bm-line{ position:relative; display:inline-block; }

/* PLAIN --ink, NOT the .topbar's mix-blend-mode:difference — copying the
   header's blend here does not work and is worth explaining, because the
   obvious fix is to copy it again.

   mix-blend-mode blends an element with the backdrop of its PARENT stacking
   context. .topbar's parent is <body>, the root context, so it blends against
   the whole page and that is why it works there. #bootmark is
   position:fixed with z-index:101, which makes it a stacking context of its
   own, and it is transparent — so a blended child has nothing to blend with
   and paints its raw colour. That raw colour is --blend-ink, a FIXED light
   tone that does not follow the theme: near-white on the light theme's
   near-white loader, i.e. an invisible wordmark. It only looked correct in
   the dark theme by accident.

   --ink is the theme's own text colour on the theme's own background, so it
   is legible by construction: 17.6:1 in dark, 14.5:1 in light. It also lands
   within a handful of RGB units of what the blended header brand resolves to
   (#e5ece7 vs #e9f3f2 dark, #060c15 vs #0b1512 light), so the handover at the
   end of the flight stays invisible. */
.bm-word{
  display:inline-block; white-space:nowrap;
  font-family:var(--display); font-weight:800; line-height:1;
  /* Solved, not guessed. The lockup is a fixed shape, so its width is a
     constant multiple of its font-size — measured at 14.68x, with the symbol,
     the gap and the word all in. #bootmark pads 5vw a side, leaving
     90% of the viewport, so the mark fits exactly while

         font-size <= 0.9 * 100vw / ratio

     The ratio is re-measured whenever the lockup changes shape, because it is
     the only thing this depends on. It has been 11.43 (the word alone), then
     14.68 (the word plus the first, redrawn symbol), and is now 12.34: the
     traced mark is a narrower box and the /OS suffix is gone. That allows
     7.29vw; 6.8vw takes it with air. */
  font-size:clamp(1.3rem,6.8vw,6rem); letter-spacing:.01em;
  color:var(--ink);
  will-change:transform, clip-path;
}
/* The printing head. Sits at the left edge of the word and travels its full
   width while the word's clip follows it. */
.bm-scan{
  position:absolute; top:-4%; left:0; width:2px; height:108%;
  background:var(--accent); opacity:0;
  box-shadow:0 0 18px 1px color-mix(in oklab, var(--accent) 60%, transparent);
}
.bm-rule{
  display:block; width:100%; height:1px; margin-top:clamp(14px,2.2vh,26px);
  background:var(--accent); opacity:.55; transform-origin:50% 50%;
}
.bm-sub{
  margin-top:clamp(12px,1.8vh,20px);
  font-family:var(--mono); font-size:var(--fs-sm); letter-spacing:.28em;
  text-transform:uppercase; color:var(--faint); white-space:nowrap;
}

/* Start state, set by the .booting class the head script adds before first
   paint. GSAP animates away from exactly these values. If scripting never
   runs the mark stays hidden, which is the same outcome the loader already
   had — it never lifts either. */
/* The vertical insets are NEGATIVE, and that is the fix for a real bug rather
   than a stylistic choice.

   This clip is a horizontal wipe — the printing head travels left to right and
   the word appears behind it. It was written as inset(0 100% 0 0), i.e. zero
   inset top and bottom, which sounds like "do not clip vertically" and is not:
   it clips to exactly the border box.

   That was harmless while the lockup was only text. It stopped being harmless
   when the mark joined it, because .lg-mark carries a translateY(.06em) optical
   nudge — and a transform does not affect layout, so the flex container's
   height is measured BEFORE the shift and the mark then slides that far out of
   the bottom of its own box. Measured at 1280px: the box is 132.3px tall and
   the mark's bottom sits 5.22px below it, so the clip sliced the flat bottom
   off the cable and the globe's lower arc.

   It only ever showed on the boot screen. The header has no clip-path, and at
   19px the same nudge is 1.1px. Here the type runs at 87px and the nudge scales
   with it. -0.12em gives the wipe room to spare at any size while leaving the
   horizontal edge — the only one doing work — exact. */
.booting .bm-word{ clip-path:inset(-0.12em 100% -0.12em 0); }
.booting .bm-rule{ transform:scaleX(0); }
.booting .bm-sub{ opacity:0; }
/* The header brand is hidden for the whole boot and revealed at the instant
   the flying mark lands on it. Two copies of the same wordmark on screen for
   even one frame reads as a glitch. */
.booting .topbar .brand{ opacity:0; }

@media (max-width:640px){
  /* The subtitle at .28em tracking is wider than the mark itself on a narrow
     phone, which inverts the hierarchy. */
  .bm-sub{ font-size:var(--fs-2xs); letter-spacing:.2em; }
}

/* Reduced motion gets the wordmark, standing still. Nothing prints, nothing
   flies; intro.js hides this layer with the loader. The rules have to assert
   the finished state because .booting above parks every part of it. */
@media (prefers-reduced-motion:reduce){
  .booting .bm-word{ clip-path:none; }
  .booting .bm-rule{ transform:none; }
  .booting .bm-sub{ opacity:1; }
  .bm-scan{ display:none; }
}

/* ---------- chrome ---------- */
/* padding-right reserves room for .nav-cta and .topbar-toggles, both fixed
   siblings positioned independently below — .topbar has no idea they exist,
   it just leaves the same gap it always would have for its own last child
   (nav, ending in the self-sized .nav-cta-space) to stop short of. */
/* THE HEADER NO LONGER BLENDS, and that is what lets the brand be the brand.

   It used to be mix-blend-mode:difference with a fixed light ink — a genuinely
   good trick: it inverts against whatever is behind it, so one header stays
   legible over both themes, over panels, and over photographs, with no
   backdrop at all. The reason it had to go is narrow and was measured rather
   than assumed.

   Difference blending is only safe for colours at the extremes. Near-white ink
   inverts to near-black over light ground and stays near-white over dark, which
   is why --blend-ink worked. A mid-tone brand colour does not survive it: the
   mint differenced against the light theme's paper renders a muddy olive-grey,
   and against the dark theme's headline text it flips to magenta. Screenshotted
   at eleven scroll positions in both themes before touching this.

   What replaces it is a scrim: the page's own background, fading out down the
   bar. rgba(var(--duo-rgb)) is that background as a triple, already defined in
   tokens.css for exactly this kind of use, so the scrim follows the theme with
   nothing to keep in step. Content still slides under the header — the bar has
   no hard edge and the page keeps its floating chrome — but it slides under
   enough tint for fixed colours to hold. Verified over the white section
   headlines and the case photograph, which are the two places the old blend
   was really earning its keep. */
.topbar{
  position:fixed; top:0; left:0; right:0; z-index:60;
  display:flex; align-items:center; justify-content:space-between;
  padding:22px calc(5vw + 44px + 26px) 22px 5vw;
  color:var(--ink);
  background:linear-gradient(180deg,
    rgba(var(--duo-rgb),.94) 0%,
    rgba(var(--duo-rgb),.86) 52%,
    rgba(var(--duo-rgb),0) 100%);
}
.topbar .brand{ display:inline-flex; align-items:center; min-height:44px; font-family:var(--display); font-weight:800; font-size:19px; letter-spacing:.01em; white-space:nowrap; }
.topbar nav{ display:flex; gap:22px; font-size:var(--fs-sm); letter-spacing:.22em; text-transform:uppercase; }
  .topbar nav a{ display:inline-flex; align-items:center; min-height:44px; }
.topbar nav a{ opacity:.75; transition:opacity .4s var(--ease-out); }
.topbar nav a:hover{ opacity:1; }
/* Same box model as .nav-cta below, invisible: reserves the width and gap the
   fixed button needs so nav-group does not collapse into its place. */
.nav-cta-space{
  visibility:hidden; margin-left:4px;
  display:inline-flex; align-items:center; min-height:44px;
  font-size:var(--fs-sm); letter-spacing:.22em; text-transform:uppercase;
  padding:0 15px; border:1px solid transparent;
}

/* .nav-cta is a SIBLING of .topbar in the markup, not a descendant, and that
   is load-bearing, not stylistic. Two things were each confirmed by direct
   browser test before landing on this:
   - a descendant cannot opt out of an ancestor's mix-blend-mode by setting
     its own to normal — the ancestor's blend applies to the whole flattened
     subtree as one unit, so a nested CTA could never show a real colour.
   - mix-blend-mode on a position:fixed element blends correctly against the
     page; the identical blend-mode on a normal child nested inside an
     *unblended* fixed wrapper washes out to near-invisible — measured, not
     theoretical: a bold black label over a white card rendered pale grey
     once the blend moved from the fixed parent onto a nested child, with no
     other change. This ruled out every restructure that kept .nav-cta
     somewhere inside .topbar, blended-group boundaries or not.
   So .topbar keeps its blend exactly as it always has (nothing above this
   comment changed), and .nav-cta is its own independent position:fixed
   element, positioned to land in the same spot the flex layout used to put
   it: the 44px theme-toggle is the only thing to its right at any width
   where this is visible (.nav-toggle stays display:none until nav-cta itself
   hides at 1023px), so the offset is a fixed sum, not a guess. */
.nav-cta{
  position:fixed; top:22px; right:calc(5vw + 44px + 26px); z-index:61;
  display:inline-flex; align-items:center; min-height:44px;
  font-size:var(--fs-sm); letter-spacing:.22em; text-transform:uppercase;
  padding:0 15px; color:var(--accent); border:1px solid var(--accent);
  transition:background .35s var(--ease-out), color .35s var(--ease-out);
}
@media (hover:hover){
  .nav-cta:hover{ background:var(--accent); color:var(--on-accent); }
}
.nav-cta:focus-visible{ background:var(--accent); color:var(--on-accent); }
@media (max-width:1023px){ .nav-cta{ display:none; } }

/* Its own independent position:fixed + mix-blend-mode group (see the comment
   on .nav-cta above) — flush to the true right edge, unaffected by anything
   .topbar or .nav-cta reserve room for. */
/* Sits inside the .topbar scrim's band, so it takes plain --ink for the same
   reason the bar does. It keeps its own fixed positioning — see the note
   above about why these are siblings rather than children. */
.topbar-toggles{ position:fixed; top:22px; right:5vw; z-index:60; display:flex; align-items:center; gap:26px; color:var(--ink); }

.theme-toggle{ background:none; border:none; padding:0; width:44px; height:44px; display:inline-flex; align-items:center; justify-content:center; cursor:pointer; -webkit-tap-highlight-color:transparent; transition:transform .5s var(--ease-out); }
.theme-toggle:hover{ transform:rotate(18deg); }
.theme-toggle svg{ width:18px; height:18px; display:block; }
.theme-toggle .i-moon{ display:none; }
[data-theme="light"] .theme-toggle .i-sun{ display:none; }
[data-theme="light"] .theme-toggle .i-moon{ display:block; }
@media (pointer:coarse){ .theme-toggle{ cursor:pointer; width:44px; height:44px; } }

.nav-toggle{ display:none; }
.mnav{ display:none; }
@media (max-width:1023px){
  .topbar nav{ display:none; }
  .nav-toggle{ display:inline-flex; flex-direction:column; justify-content:center; gap:6px; width:44px; height:44px; padding:0 10px; background:none; border:none; cursor:pointer; }
  .nav-toggle span{ display:block; height:1.5px; width:100%; background:currentColor; transition:transform .4s var(--ease-out), opacity .3s var(--ease-out); }
  .nav-toggle.is-open span:first-child{ transform:translateY(3.75px) rotate(45deg); }
  .nav-toggle.is-open span:last-child{ transform:translateY(-3.75px) rotate(-45deg); }
  /* safe center keeps the list centred when it fits and falls back to the top
     edge when it does not, so nothing is clipped on a landscape phone. */
  .mnav{ position:fixed; inset:0; z-index:55; display:flex; flex-direction:column; align-items:flex-start; justify-content:center; justify-content:safe center; gap:clamp(10px,1.9vh,22px); padding:96px 9vw 40px; background:var(--bg); overflow-y:auto; overscroll-behavior:contain; opacity:0; visibility:hidden; transform:translateY(-10px); transition:opacity .45s var(--ease-out), transform .45s var(--ease-out), visibility .45s; }
  .mnav.open{ opacity:1; visibility:visible; transform:none; }
  .mnav a{ display:flex; align-items:center; min-height:44px; font-family:var(--display); font-weight:700; text-transform:uppercase; font-size:clamp(1.35rem,6.2vw,2.3rem); line-height:1.05; letter-spacing:-.01em; color:var(--ink); flex:0 0 auto; }
  .mnav a .mi{ font-family:var(--mono); font-weight:400; font-size:var(--fs-sm); letter-spacing:.14em; color:var(--accent); margin-right:14px; vertical-align:middle; }
  .mnav a.mnav-cta{ margin-top:2vh; font-family:var(--mono); font-weight:500; text-transform:uppercase; font-size:var(--fs-base); letter-spacing:.18em; color:var(--accent); }
}

.progress{ position:fixed; top:0; left:0; right:0; height:2px; z-index:80; pointer-events:none; }
.progress i{ display:block; height:100%; width:100%; transform:scaleX(0); transform-origin:left; background:linear-gradient(90deg, var(--accent), var(--amber)); }
#sysread{ position:fixed; left:5vw; bottom:calc(26px + var(--consent-h)); transition:bottom .5s var(--ease-out); z-index:50; pointer-events:none; font-size:var(--fs-xs); letter-spacing:.24em; text-transform:uppercase; color:var(--faint); }
#sysread b{ color:var(--accent); font-weight:500; }
/* Stands down over the contact panel, which is the one solid block that
   reaches this corner — see initSysRead() in nav.js. */
#sysread{ opacity:1; transition:opacity .35s var(--ease-out); }
#sysread.is-hidden{ opacity:0; }
@media (max-width:900px){ #sysread{ display:none; } }

/* ---------- floating scroll-to-top ----------
   Fixed bottom-right. The SVG ring is a read-progress meter driven from
   nav.js; the button itself only appears once there is a meaningful amount
   of page behind you, and steps aside while the case-file reel is pinned
   (the reel runs its own progress bar along the same edge). */
.totop{
  position:fixed; right:clamp(16px,5vw,52px); bottom:calc(clamp(16px,4.5vh,46px) + var(--consent-h)); z-index:70;
  width:56px; height:56px; display:grid; place-items:center;
  padding:0; border:none; border-radius:50%;
  background:color-mix(in oklab, var(--bg) 82%, transparent);
  -webkit-backdrop-filter:blur(9px); backdrop-filter:blur(9px);
  color:var(--ink); cursor:pointer;
  opacity:0; visibility:hidden; transform:translateY(14px) scale(.86);
  transition:opacity .5s var(--ease-out), transform .5s var(--ease-out),
             visibility .5s, background .4s var(--ease-out), color .4s var(--ease-out),
             bottom .5s var(--ease-out);
}
.totop.show{ opacity:1; visibility:visible; transform:none; }
.totop.stow{ opacity:0; visibility:hidden; transform:translateY(14px) scale(.86); }

.totop-ring{ position:absolute; inset:0; width:100%; height:100%; transform:rotate(-90deg); }
.totop-ring circle{ fill:none; stroke-width:1.5; }
.tt-track{ stroke:var(--line); }
.tt-bar{
  stroke:var(--accent); stroke-linecap:round;
  /* 2πr for r=26 ≈ 163.36; nav.js drives the dashoffset from scroll position */
  stroke-dasharray:163.36; stroke-dashoffset:163.36;
  transition:stroke-dashoffset .12s linear;
}
.totop-arrow{ position:relative; width:19px; height:19px; fill:none; stroke:currentColor; stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round; transition:transform .45s var(--ease-out); }

@media (hover:hover){
  .totop:hover{ background:var(--accent); color:var(--on-accent); }
  .totop:hover .totop-arrow{ transform:translateY(-3px); }
  .totop:hover .tt-track{ stroke:transparent; }
}
.totop:focus-visible{ outline:2px solid var(--accent); outline-offset:4px; }
@media (pointer:coarse){ .totop{ cursor:pointer; } }

/* The reel owns the bottom edge while it is pinned. */
.reel-pinned .totop{ opacity:0; visibility:hidden; transform:translateY(14px) scale(.86); pointer-events:none; }

@media (prefers-reduced-motion:reduce){
  .totop{ transition:opacity .2s linear, visibility .2s; transform:none; }
  .totop.show{ transform:none; }
  .totop.stow, .reel-pinned .totop{ transform:none; }
  .tt-bar{ transition:none; }
  .totop-arrow{ transition:none; }
}

/* ---------- very small handsets (≤400px) ----------
   The brand lockup plus two 44px controls does not fit 320px at full size,
   so the wordmark steps down and the control gap tightens. */
@media (max-width:400px){
  .topbar{ padding:12px 4vw; }
  .topbar .brand{ font-size:15.5px; }
    /* These were shrunk to 40px to fit the narrow bar, which put both controls
     under the 44px minimum. Dropping the gap instead buys back the width at
     the same total footprint, and the targets stay 44px and non-overlapping. */
  .topbar-toggles{ top:12px; right:4vw; gap:0; }
}

/* ---------- skip link ----------
   Off-screen until focused, then lands in the top-left as a normal button. */
.skip-link{
  position:fixed; top:0; left:0; z-index:200;
  padding:14px 20px; background:var(--accent); color:var(--on-accent);
  font-size:var(--fs-sm); letter-spacing:.16em; text-transform:uppercase;
  transform:translateY(-120%); transition:transform .25s var(--ease-out);
}
.skip-link:focus{ transform:none; outline:2px solid var(--ink); outline-offset:2px; }

/* ---------- mobile contact bar ----------
   Phone-only. Below 1023px .nav-cta is display:none, so without this there is
   no persistent way to reach us from a phone at all. Three equal targets, each
   comfortably over 44px, sitting on the safe-area inset so it clears the iOS
   home indicator. */
.mobile-bar{
  position:fixed; left:0; right:0; bottom:var(--consent-h); z-index:75;
  transition:bottom .5s var(--ease-out);
  display:none; align-items:stretch;
  background:color-mix(in oklab, var(--panel) 94%, transparent);
  -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px);
  border-top:1px solid var(--line);
  padding-bottom:env(safe-area-inset-bottom,0);
  transform:translateY(102%); transition:transform .4s var(--ease-out);
}
.mobile-bar.show{ transform:none; }
.mobile-bar .mb-item{
  flex:1 1 0; min-width:0; min-height:58px;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px;
  padding:9px 6px; color:var(--ink);
  font-size:var(--fs-2xs); letter-spacing:.11em; text-transform:uppercase;
  border-left:1px solid var(--line);
}
.mobile-bar .mb-item:first-child{ border-left:none; }
.mobile-bar .mb-item svg{ width:19px; height:19px; flex:0 0 auto; }
.mobile-bar .mb-item span{ white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }
/* The site's accent, not WhatsApp's brand green. #25d366 belonged to another
   company's palette and read as a pasted-in third-party widget; --accent keeps
   the bar one system and flips correctly between themes on its own, so the
   hand-darkened light-mode variant this used to need is gone. */
.mobile-bar .mb-whats{ color:var(--accent); }
.mobile-bar .mb-cta{ background:var(--accent); color:var(--on-accent); border-left-color:var(--accent); }
.mobile-bar .mb-item:active{ filter:brightness(1.15); }

@media (max-width:1023px){
  .mobile-bar{ display:flex; }
  /* The bar owns the bottom edge, so the round controls move up above it. */
  .totop{ bottom:calc(66px + env(safe-area-inset-bottom,0) + 12px); }
  /* The reel's own progress bar would sit underneath it otherwise. */
  .reel-progress{ bottom:calc(66px + env(safe-area-inset-bottom,0) + 12px); }
}
/* Nothing should sit on top of the form the bar points at. */
.mobile-bar.stow{ transform:translateY(102%); }

/* The floating WhatsApp bubble that used to live here is gone: the assistant
   launcher took its slot and WhatsApp is now the permanent bar along the foot
   of that panel. See the header of chat.css. The mobile bar below still
   carries WhatsApp as one of its three targets, which is why nothing needed
   replacing on phones. */

@media (prefers-reduced-motion:reduce){
  .skip-link,.mobile-bar{ transition:none; }
}
