/* ===========================================================================
   Reset — what Bootstrap was still doing on these pages
   ===========================================================================

   The redesign templates do not load Bootstrap. Measured against a rendered
   page before removing it, exactly thirty of its rules matched anything at all,
   and twenty-four of those were Reboot: box-sizing, the form-control font
   inheritance, heading and paragraph margins, list padding. The other six were
   `.card`, `.card-body`, `.btn` — all of which redesign.css already defines
   itself — and three utility classes used by chrome shared with the old design.

   So this is not "a reset" in the aspirational sense. It is the measured set,
   reimplemented, and nothing else. Dropping Bootstrap takes 152 KB off every
   redesign page; shipping the whole framework to get `box-sizing` and
   `.visually-hidden` was the thing worth fixing.

   Values are Bootstrap 5's, deliberately, so that removing the file changes no
   computed style. Where that looks arbitrary — `font-weight: bolder` rather
   than `bold`, `padding-left: 2rem` on lists — it is arbitrary, and it is what
   was there.
   =========================================================================== */

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

body { margin: 0; }

h1, h2, h3, h4, h5, h6 {
  margin-top: 0;
  margin-bottom: .5rem;
  line-height: 1.2;
}

p {
  margin-top: 0;
  margin-bottom: 1rem;
}

ol, ul {
  padding-left: 2rem;
  margin-top: 0;
  margin-bottom: 1rem;
}

/* Lists nested in lists do not get the outer list's bottom margin. */
ol ol, ol ul, ul ol, ul ul { margin-bottom: 0; }

b, strong { font-weight: bolder; }

/* Stops the 4px descender gap under an inline image or icon. */
img, svg { vertical-align: middle; }

label { display: inline-block; }

/* Without the font inheritance a styled <button> falls back to the UA's
   13px Arial, which is what the hamburger and the language toggle were
   silently relying on Bootstrap to prevent. */
button, input, optgroup, select, textarea {
  margin: 0;
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
}

button { border-radius: 0; }
button, select { text-transform: none; }

[type="button"],
[type="reset"],
[type="submit"],
button { -webkit-appearance: button; appearance: button; }

[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled),
button:not(:disabled) { cursor: pointer; }

textarea { resize: vertical; }

iframe { border: 0; }

[hidden] { display: none !important; }

/* --- The four utilities that shared chrome still uses --------------------- */
/* `.visually-hidden` carries the skip link; `.justify-content-center` and
   `.me-2` come from cms/includes/footer.html and the contact form fields, which
   both designs include. They are copied rather than rewritten because those
   includes are shared — changing the markup there would change the old pages
   too. */

.visually-hidden,
.visually-hidden-focusable:not(:focus):not(:focus-within) {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

/* The utility set is exactly what the six rendered redesign pages contain --
   counted from the HTML, not from the templates, because most of the Bootstrap
   names in the templates (card, btn, row, col) are the redesign's own classes
   that happen to collide. */
.d-block { display: block !important; }
.d-flex  { display: flex !important; }

.justify-content-center { justify-content: center !important; }

.text-center { text-align: center !important; }

.me-2 { margin-right: .5rem !important; }
.mb-4 { margin-bottom: 1.5rem !important; }

.container {
  width: 100%;
  padding-right: .75rem;
  padding-left: .75rem;
  margin-right: auto;
  margin-left: auto;
}

/* ========================================================================= */

/* ===========================================================================
   Redesign stylesheet -- cms/redesign/*
   ===========================================================================
   Extracted from static/demo/redesign.html, which stays as the reference
   page. Brand tokens are NOT redeclared: css/tokens.css is linked ahead of
   this file, and the honeycomb comes from css/hex-lattice.css.

   Loaded only by cms/redesign/base.html, so a page on the old templates is
   untouched by anything here and the two can be compared side by side.
   =========================================================================== */

/* ══════════════════════════════════════════════════════════════════════════
   1. BRAND TOKENS — verbatim from brand-kit/website/beelogic-tokens.css.
      Nothing here is invented. Any hex below this block is a bug.
   ══════════════════════════════════════════════════════════════════════════ */
:root{
  --bl-teal:#04828E; --bl-cyan:#00AAD3; --bl-ink:#01232B; --bl-night:#06161C;
  --bl-lift:#2AD4FF; --bl-muted:#4F6A73; --bl-wash:#F3F9FA; --bl-line:#DCE8EC;

  --bg:#FFFFFF; --bg-alt:var(--bl-wash); --surface:#FFFFFF;
  --text:var(--bl-ink); --text-muted:var(--bl-muted); --border:var(--bl-line);
  --accent:var(--bl-teal); --accent-strong:#036B75;
  --btn-bg:var(--bl-ink); --btn-text:#FFFFFF; --btn2-text:var(--bl-teal);
  --focus:var(--bl-cyan);
  --mark-comb:var(--bl-teal); --mark-circuit:var(--bl-cyan);
  --mark-die:var(--bl-cyan); --mark-word:var(--bl-ink);
  --signal:#FFFFFF; --signal-glow:var(--bl-cyan); --cell-flash:var(--bl-cyan);
  --pattern:var(--bl-teal); --pattern-opacity:.10;

  --font-display:"Alexandria","Readex Pro","Segoe UI",Tahoma,system-ui,sans-serif;
  --font-body:"Readex Pro","Segoe UI",Tahoma,system-ui,sans-serif;
  --fw-regular:400; --fw-medium:500; --fw-semibold:600;
  --fs-display:clamp(2.1rem,1.3rem + 3.2vw,3.6rem);
  --fs-h2:clamp(1.5rem,1.1rem + 1.5vw,2.1rem);
  --fs-h3:1.25rem; --fs-body:1rem; --fs-small:.875rem;
  --lh-body:1.6; --lh-body-ar:1.8;

  --radius-sm:8px; --radius:12px; --radius-lg:16px;
  --shadow:0 18px 44px -28px rgba(1,35,43,.45);
  --space-section:clamp(48px,8vw,104px);
  --container:1200px;
  --signal-duration:2.4s;

  /* prototype-only: the rail gutter */
  --rail-w:88px;

  /* ⚠ NOT FROM THE BRAND KIT. Brand kit v2 defines no error colour, but a
     contact form needs one for invalid fields and the failure banner. This is
     a placeholder chosen to clear 4.5:1 on both --bg values, awaiting a ruling
     from whoever owns the brand. Replace, don't ship on my say-so. */
  --danger:#C0392B;
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --bg:var(--bl-night); --bg-alt:#0A2028; --surface:#0C252D;
    --text:#E6F3F6; --text-muted:#8FB0B9; --border:#17363F;
    --accent:var(--bl-lift); --accent-strong:#7FE6FF;
    --btn-bg:var(--bl-lift); --btn-text:#04222B; --btn2-text:var(--bl-lift);
    --mark-comb:var(--bl-lift); --mark-circuit:#FFFFFF;
    --mark-die:#FFFFFF; --mark-word:#E6F3F6;
    --signal:#FFFFFF; --signal-glow:var(--bl-lift); --cell-flash:#FFFFFF;
    --pattern:#1BB8E0; --pattern-opacity:.30;
    --danger:#FF8A7A;   /* ⚠ not from the kit — see the note in :root */
    color-scheme:dark;
  }
}
:root[data-theme="dark"]{
  --bg:var(--bl-night); --bg-alt:#0A2028; --surface:#0C252D;
  --text:#E6F3F6; --text-muted:#8FB0B9; --border:#17363F;
  --accent:var(--bl-lift); --accent-strong:#7FE6FF;
  --btn-bg:var(--bl-lift); --btn-text:#04222B; --btn2-text:var(--bl-lift);
  --mark-comb:var(--bl-lift); --mark-circuit:#FFFFFF;
  --mark-die:#FFFFFF; --mark-word:#E6F3F6;
  --signal:#FFFFFF; --signal-glow:var(--bl-lift); --cell-flash:#FFFFFF;
  --pattern:#1BB8E0; --pattern-opacity:.30;
    --danger:#FF8A7A;   /* ⚠ not from the kit — see the note in :root */
  color-scheme:dark;
}

/* ══════════════════════════════════════════════════════════════════════════
   2. BASE
   ══════════════════════════════════════════════════════════════════════════ */
*,*::before,*::after{box-sizing:border-box}
html{background:var(--bg);color:var(--text);font-family:var(--font-body);
  line-height:var(--lh-body);scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{margin:0;overflow-x:hidden;font-size:var(--fs-body);
  background:var(--bg);color:var(--text)}
h1,h2,h3,.display{font-family:var(--font-display);font-weight:var(--fw-semibold);
  text-wrap:balance;margin:0}
p{margin:0}
/* height:auto is what makes width/height attributes behave as an aspect ratio:
   with max-width clamping the width and the height attribute still supplying a
   used value, neither dimension is auto, so the browser never applies the
   intrinsic ratio -- it just squeezes. The product band's shot was rendering
   759x778 into a 482px column at full height, which is what Lighthouse's
   image-aspect-ratio audit caught. A dozen rules below were each setting
   height:auto locally to work around its absence here.

   img only. 37 inline <svg> in the templates carry no width/height attributes
   and are sized by their own classes; svg takes its ratio from viewBox and
   wants no help. */
img{max-width:100%;height:auto}
svg{max-width:100%}
a{color:var(--accent);text-underline-offset:3px}
:focus-visible{outline:2px solid var(--focus);outline-offset:3px;border-radius:2px}
:lang(ar){line-height:var(--lh-body-ar)}

/* The skip link is styled further down as .skip-link:focus. The prototype's
   own `.skip` rules used to sit here and matched nothing: cms/base.html emits
   class="visually-hidden skip-link", which is why that rule has to out-weigh
   .visually-hidden's !important declarations rather than simply position the
   link itself. */

/* The inline-start padding reserves the rail's gutter, and that is not
   decoration: .rail is position:fixed at the viewport's left edge, 88px wide,
   and reserves no space of its own. Content has to step around it.

   This used to be a separate `.shell` class, straight from the prototype,
   where every container was `class="wrap shell"`. The templates that ship
   renamed .wrap to .shell-wrap and dropped `shell` on the way, so the rule
   matched nothing -- and between roughly 1200px and 1376px of viewport, where
   the centred 1200px container starts left of 88px, the rail's line and its
   nodes were drawn straight through the text. Merged into .shell-wrap so the
   two cannot come apart again.

   max() rather than the prototype's override: that set padding-inline-start to
   var(--rail-w) outright, which below 900px -- where --rail-w is 0 -- left the
   content with no inline-start gutter at all. This keeps whichever is larger,
   so the rail wins on desktop and the 16-40px gutter wins on a phone. */
.shell-wrap{max-width:var(--container);margin-inline:auto;
  padding-inline:clamp(16px,4vw,40px);
  padding-inline-start:max(clamp(16px,4vw,40px),var(--rail-w))}
@media (max-width:900px){:root{--rail-w:0px}}

/* ══════════════════════════════════════════════════════════════════════════
   3. THE RAIL — the logo's signal, scaled to page height.
      .rail-fill scales with scroll progress; .rail-pulse rides it.
      Nodes light as their section becomes active.
   ══════════════════════════════════════════════════════════════════════════ */
.rail{position:fixed;inset-block:0;inset-inline-start:0;width:var(--rail-w);
  z-index:40;pointer-events:none}
.rail-track{position:absolute;inset-block:0;inset-inline-start:calc(var(--rail-w) / 2);
  width:1px;background:var(--border);transform:translateX(-.5px)}
.rail-fill{position:absolute;inset-block:0;inset-inline-start:calc(var(--rail-w) / 2);
  width:1px;background:var(--accent);transform:translateX(-.5px) scaleY(var(--p,0));
  transform-origin:top}
.rail-pulse{position:absolute;inset-inline-start:calc(var(--rail-w) / 2);
  top:calc(var(--p,0) * 100%);width:7px;height:7px;border-radius:50%;
  background:var(--bl-cyan);margin-inline-start:-3.5px;margin-top:-3.5px;
  filter:drop-shadow(0 0 4px var(--signal-glow)) drop-shadow(0 0 12px var(--signal-glow))}
.rail-nodes{position:absolute;inset-block:0;inset-inline-start:0;width:100%;
  list-style:none;margin:0;padding:0;pointer-events:auto}
.rail-nodes li{position:absolute;inset-inline-start:50%;transform:translateX(-50%)}
.rail-nodes a{display:grid;place-items:center;width:26px;height:26px;
  text-decoration:none;border-radius:50%}
.rail-dot{width:9px;height:9px;border:1px solid var(--border);border-radius:50%;
  background:var(--bg);transition:background .25s,border-color .25s,transform .25s}
.rail-nodes a:hover .rail-dot{border-color:var(--accent)}
.rail-nodes a[aria-current="true"] .rail-dot{background:var(--accent);
  border-color:var(--accent);transform:scale(1.25)}
.rail-nodes a[aria-current="true"] .rail-dot::after{content:"";position:absolute;
  width:9px;height:9px;border-radius:50%;background:var(--cell-flash);
  animation:flash var(--signal-duration) ease-out}
@keyframes flash{0%{opacity:0;transform:scale(1)}8%{opacity:.9}
  35%,100%{opacity:0;transform:scale(2.6)}}
.rail-label{position:absolute;inset-inline-start:34px;top:50%;
  transform:translateY(-50%);white-space:nowrap;font-size:.75rem;
  color:var(--text-muted);opacity:0;transition:opacity .2s;
  background:var(--bg);padding-inline:6px}
.rail-nodes a:hover .rail-label,.rail-nodes a:focus-visible .rail-label{opacity:1}
@media (max-width:900px){
  .rail{width:3px}
  .rail-track,.rail-fill{inset-inline-start:0;width:3px;transform:none}
  .rail-fill{transform:scaleY(var(--p,0));transform-origin:top}
  .rail-pulse,.rail-nodes{display:none}
}

/* ══════════════════════════════════════════════════════════════════════════
   4. HEADER
   ══════════════════════════════════════════════════════════════════════════ */
.site-head{position:sticky;top:0;z-index:30;background:color-mix(in srgb,var(--bg) 86%,transparent);
  backdrop-filter:blur(10px);border-bottom:1px solid var(--border)}
.site-head .shell-wrap{display:flex;align-items:center;gap:24px;min-height:68px}
/* The approved EN lockup, inlined once and driven by the --mark-* tokens.
   The brief names two files (…-color.svg / …-color-dark.svg) swapped by
   theme. Inlining reaches the same result with one asset AND keeps working
   with the data-theme toggle, which a <picture media> swap cannot do — a
   media query does not see an attribute. Flagged for your call. */
.brand{display:flex;align-items:center;text-decoration:none;flex:none}
.bl-logo{height:34px;width:auto;display:block}
@media (max-width:760px){.bl-logo{height:28px}}
.site-nav{margin-inline-start:auto;display:flex;align-items:center;gap:clamp(14px,2.2vw,30px)}
.site-nav a{color:var(--text-muted);text-decoration:none;font-size:.9375rem;
  font-weight:var(--fw-regular)}
.site-nav a:hover{color:var(--accent)}

/* Mobile navigation. The first version of this prototype simply hid .nav-link
   below 760px and offered nothing in its place — and the rail, the only other
   way to move around, is itself off below 900px. That left a phone with no
   navigation at all. */
/* --border is a 1.25:1 hairline, which is right for a decorative rule and
   wrong for the edge of a control: WCAG 1.4.11 asks 3:1 of that, and below
   760px this button is the only navigation there is. Measured against the
   header at 1.44:1 before, 3.77:1 after -- the same idiom the contact inputs,
   the language toggle and the theme toggle use. */
.hamburger{display:none;background:transparent;
  border:1px solid color-mix(in srgb,var(--text-muted) 58%,var(--surface));
  border-radius:var(--radius-sm);width:38px;height:38px;padding:0;flex:none;
  cursor:pointer;color:var(--text);place-items:center}
.hamburger:hover{border-color:var(--accent);color:var(--accent)}
.hamburger svg{width:18px;height:18px}
.hamburger .ico-close{display:none}
.hamburger[aria-expanded="true"] .ico-open{display:none}
.hamburger[aria-expanded="true"] .ico-close{display:block}

/* 1023px, not 760px. The full navigation -- six links, the theme toggle, the
   language chooser and the CTA -- needs 988px of room, measured. The hamburger
   used to take over only below 760px, so from 761px to about 1008px the header
   was laid out as a desktop row that did not fit: at 900px the nav ran to
   x=936 in an 885px viewport and "Start a project" was clipped off the right
   edge. It read as fine because html{overflow-x:clip} hid the overflow instead
   of scrolling it.
   1024 is the first width the row fits with room to spare (988 of 1009). */
@media (max-width:1023px){
  /* The nav is out of flow below this width, so the auto margin that pushes
     it right has to move onto the button. */
  .hamburger{display:grid;margin-inline-start:auto}
  .site-nav{position:absolute;inset-inline:0;top:100%;
    flex-direction:column;align-items:stretch;gap:0;
    background:var(--bg);border-bottom:1px solid var(--border);
    box-shadow:var(--shadow);padding:8px clamp(16px,4vw,40px) 18px;
    display:none}
  .site-nav[data-open]{display:flex}
  /* Two rules used to sit here, `.site-nav .nav-link` and
     `.site-nav .theme-btn`, and both matched nothing: they were written for
     the prototype, where the links and the toggle are direct children of
     .site-nav, and what ships nests them in ul.nav-links. Their replacements
     are in the ported nav block further down, against the list that is
     actually there. The CTA rule below still matches, because .btn.primary is
     a descendant either way. */
  .site-nav .btn.primary{margin-top:12px;justify-content:center}
}
@media (min-width:1024px){.site-nav[data-open]{display:flex}}

.btn{display:inline-flex;align-items:center;justify-content:center;
  padding:11px 20px;border-radius:var(--radius-sm);text-decoration:none;
  font-family:var(--font-body);font-weight:var(--fw-medium);font-size:.9375rem;
  border:1px solid transparent;cursor:pointer;transition:background .2s,border-color .2s}
.btn.primary{background:var(--btn-bg);color:var(--btn-text)}
.btn.primary:hover{background:var(--accent-strong)}
.btn.ghost{background:transparent;color:var(--btn2-text);border-color:var(--border)}
.btn.ghost:hover{border-color:var(--accent)}

/* The theme toggle. Hidden until the inline script in cms/redesign/base.html
   sets data-js on <html>: without JavaScript the button does nothing, and the
   tokens already follow the visitor's system setting on their own, so nothing
   is lost by not showing it. The border follows the contact inputs at 3:1
   rather than the 1.25:1 hairline, because WCAG 1.4.11 asks that of the
   boundary of a control. */
.theme-btn{display:none;background:transparent;
  border:1px solid color-mix(in srgb,var(--text-muted) 58%,var(--surface));
  border-radius:var(--radius-sm);width:36px;height:36px;
  place-items:center;cursor:pointer;color:var(--text-muted);flex:none;
  transition:border-color .18s,color .18s}
:root[data-js] .theme-btn{display:grid}
.theme-btn:hover{border-color:var(--accent);color:var(--accent)}
.theme-btn svg{width:18px;height:18px}
/* The icon shows the theme you would get, not the one you are in: in light
   mode it offers the moon. aria-pressed carries the real state for anyone not
   looking at it. */
.theme-btn .ico-sun{display:none}
.theme-btn[aria-pressed="true"] .ico-sun{display:block}
.theme-btn[aria-pressed="true"] .ico-moon{display:none}

/* ══════════════════════════════════════════════════════════════════════════
   5. HERO — three parallax depths. Text is NOT one of them.
   ══════════════════════════════════════════════════════════════════════════ */
.hero{position:relative;overflow:hidden;padding-block:clamp(56px,9vw,104px) var(--space-section)}
.hero .shell-wrap{display:grid;gap:clamp(32px,5vw,56px);
  grid-template-columns:minmax(0,1fr) minmax(0,.82fr);align-items:center}
@media (max-width:860px){
  .hero .shell-wrap{grid-template-columns:1fr;gap:28px}
  /* The brief has the mark stack above the text on mobile. It stays an
     emblem, not a cover image: at 254px it pushed the h1 to 540px down a
     780px viewport and both CTAs below the fold. */
  .hero-visual{order:-1;min-height:0}
  .hero-mark{width:min(150px,42%)}
  .hero-pattern{inset:-30% -14% -30% -14%}
}
/* A dateline, not an eyebrow: sentence case, body size, muted, no rule and no
   tracking. The content earns its place — it says what the company is and where
   it is in four words — but the tracked-out uppercase treatment it arrived with
   is decoration on top of information that was already doing the work. */
.dateline{color:var(--text-muted);font-size:var(--fs-small);margin-bottom:14px}
/* Emphasis on the word that carries the proposition.
   Taken from the demo, with the colour roles corrected: there both the word
   and its wash were --accent. The guidelines give teal and cyan different
   jobs — teal carries anything near text, cyan is the signal and never sits
   under body-size text as a foreground. So the word is teal and the wash is
   cyan: each colour in the role the brand defines for it.
   The wash is a separate ::after rather than a background, so it can sit
   behind the baseline without affecting line-height or wrapping. */
/* Emphasis on the word that carries the proposition.
   Taken from the demo, with the colour roles corrected: there both the word
   and its wash were --accent. The guidelines give teal and cyan different
   jobs -- teal carries anything near text, cyan is the signal and never sits
   under body-size text as a foreground. So the word is teal and the wash is
   cyan: each colour in the role the brand defines for it.
   The wash is a separate ::after rather than a background, so it can sit
   behind the baseline without affecting line-height or wrapping.

   This briefly had to cover `.animate-hex` as well, because the shipping
   default wrote <span class="animate-hex"> where the prototype wrote <em> --
   so the treatment had never once rendered on the live page. The default
   writes <em> now (BACKLOG.md 46), and .animate-hex belongs to cms/home.html,
   which still has GSAP to animate it. */
.hero h1 em{font-style:normal;color:var(--accent);position:relative;
  white-space:nowrap}
.hero h1 em::after{content:"";position:absolute;inset-inline:-.06em;
  bottom:.1em;height:.3em;border-radius:3px;z-index:-1;
  background:color-mix(in srgb,var(--bl-cyan) 26%,transparent)}
.hero h1{font-size:var(--fs-display);letter-spacing:-.02em;line-height:1.08;
  max-width:17ch}
.hero-intro{margin-top:20px;max-width:52ch;color:var(--text-muted);
  font-size:1.0625rem}
.hero-cta{margin-top:30px;display:flex;flex-wrap:wrap;gap:12px}
.hero-note{margin-top:22px;font-size:var(--fs-small);color:var(--text-muted)}

.hero-visual{position:relative;display:grid;place-items:center;min-height:300px}
/* depth 1 — the hex lattice, from the project's own hex-lattice.css.
   Replaces the honeycomb <svg><pattern> I hand-rolled here: that was a second
   implementation of a motif the repo already solves properly, as a mask rather
   than a picture, with RTL mirroring and density as properties. */
/* Opacity comes from the brand token, not a number picked here: the brief
   sets it at 10% light / 30% dark, and tokens.css already switches it. */
.hero .hex-field{--hex-opacity:var(--pattern-opacity);
  transform:translate3d(0,calc(var(--sy,0) * .16px),0);will-change:transform}

/* depth 2 — the mark drifts the other way, which is what creates separation */
.hero-mark{position:relative;width:min(340px,76%);
  transform:translate3d(0,calc(var(--sy,0) * -.07px),0);will-change:transform}

/* The approved animated mark. Signal runs the centre loop every 2.4s. */
.bl-mark .comb{fill:var(--mark-comb)}
.bl-mark .circ{fill:var(--mark-circuit)}
.bl-mark .die{fill:var(--mark-die)}
.bl-mark .signal{fill:none;stroke:var(--signal);stroke-width:4.6;
  stroke-linecap:round;stroke-linejoin:round;
  filter:drop-shadow(0 0 3px var(--signal-glow)) drop-shadow(0 0 10px var(--signal-glow));
  stroke-dasharray:36 120;animation:bl-run var(--signal-duration) linear infinite}
@keyframes bl-run{to{stroke-dashoffset:-156}}

/* The loader's extra half: each outer cell lights as the signal passes it.
   The six delays that time them to the signal are inline on the paths in the
   kit's own component, so they are inline in cms/partials/beelogic_loader.html
   too -- they are part of the drawing, not of this stylesheet. */
.bl-mark .flash{fill:var(--cell-flash);opacity:0;
  animation:bl-flash var(--signal-duration) ease-out infinite}
@keyframes bl-flash{0%{opacity:0}8%{opacity:.9}35%,100%{opacity:0}}

/* ── The loader in use ─────────────────────────────────────────────────────
   Brief task 7. The kit puts this component at 32-96px and sends anything
   below 24px to a plain ring spinner instead, because the cell detail closes
   up. In a 46px-tall button 28px is the most it can have and still clear that
   floor, so that is what it gets -- and it is why the mark is used here at all
   rather than a ring.

   The button keeps its label while it works: "Send Now!" with a loader beside
   it still says what is happening, where a lone spinner says only "wait".

   24px exactly -- the kit's own floor -- and a min-height on the buttons,
   because the loader must not change the button's height when it appears. At
   28px it did: 46px to 52px, a visible jump at the moment the visitor is
   watching that button. 11px of padding top and bottom, a 1px border and a
   24px line box come to 48px, so that is what the buttons are at rest now.
   They were 46px; the extra 2px is also a slightly better touch target. */
.bl-loader{display:inline-flex;align-items:center;flex:none}
.bl-loader[hidden]{display:none}
.bl-mark--loader{height:24px;width:auto;display:block;
  /* Nothing in this mark is a solid block, so at 28px the glow is most of what
     reads as motion. It is scaled down with the drawing. */
  overflow:visible}
.bl-mark--loader .signal{stroke-width:5.6;
  filter:drop-shadow(0 0 2px var(--signal-glow)) drop-shadow(0 0 6px var(--signal-glow))}

/* aria-busy is set by the one submit handler in
   templates/django_recaptcha/includes/js_v3.html, which owns the whole
   sequence: CSRF, then a fresh captcha token, then the fetch. The loader has
   to cover all three, not just the fetch -- the captcha round trip is usually
   the slowest part, and it happens before forms.js is reached.

   aria-disabled rather than disabled: a disabled button drops out of the
   accessibility tree and takes focus with it, so a keyboard user who pressed
   Enter lands back on <body>. The handler's own `running` flag is what
   actually blocks a second submit; this only needs to say so and stop the
   pointer. */
form[aria-busy="true"] .primary-btn,
form[aria-busy="true"] .secondary-btn{pointer-events:none;opacity:.85}
form[aria-busy="true"] input,
form[aria-busy="true"] textarea{opacity:.7}

/* ══════════════════════════════════════════════════════════════════════════
   6. SECTIONS
   ══════════════════════════════════════════════════════════════════════════ */
.section{padding-block:var(--space-section);border-top:1px solid var(--border)}
/* A section whose content ends in a full-bleed strip rather than a block of
   copy. The clients rail is 84px of drifting texture, so a full
   --space-section beneath it met the next section's full --space-section above
   it: 213px of empty page between the logos and the next heading, on a section
   whose own content is only 262px tall. More padding than content reads as a
   hole, not as rhythm.

   Bottom only. The space above the heading is what keeps the sections looking
   evenly spaced down the page, so it is left alone. */
.section--strip{padding-bottom:calc(var(--space-section) * .5)}
.section--wash{background:var(--bg-alt)}
.section-head{max-width:60ch;margin-bottom:clamp(28px,4vw,48px)}
/* max-width with no auto margins left the block hard against the left gutter.
   The h2 inside carries justify-content-center, so it centred its hexes and
   words WITHIN that block -- and the block sat 229px left of the content
   centre, which is why the heading looked centred and misplaced at the same
   time while the paragraph under it ran left. Five heads on the home page and
   the same pattern on the other templates.

   Keyed on the h2's own class so that the heads which are deliberately left
   aligned -- the contact band's, which has no such class -- are untouched. */
.section-head:has(h2.justify-content-center){
  margin-inline:auto; text-align:center;
}
.section-head h2{font-size:var(--fs-h2);letter-spacing:-.015em;line-height:1.18}
.section-head p{margin-top:14px;color:var(--text-muted)}

/* Headings that open with an icon: "Our Mission", "Our Vision". The icon is a
   plain inline <img> in the heading's markup with nothing between it and the
   first letter, so the two touched.

   .title is excluded: it is display:flex with gap:10px for the hex pair that
   flanks the section headings, and would otherwise get the spacing twice.
   margin-inline-end rather than margin-right, so Arabic mirrors it. */
:is(h1,h2,h3,h4,h5):not(.title) > img:first-child{
  margin-inline-end:10px; vertical-align:-.18em;
}

.grid{display:grid;gap:clamp(16px,2vw,22px)}
.grid--4{grid-template-columns:repeat(auto-fit,minmax(215px,1fr))}
.grid--2{grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}

/* Cards: 1px border + surface. The shadow is reserved for ONE panel per
   section, per the brand guidelines — see .case-card--featured. */
.card{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius-lg);padding:24px;
  transition:border-color .22s}
.card:hover,.card:focus-within{border-color:var(--accent)}
.card h3{font-size:var(--fs-h3);margin-bottom:8px;letter-spacing:-.01em}
.card p{color:var(--text-muted);font-size:.9375rem}
.svc-mark{width:34px;height:38px;margin-bottom:16px;display:block}
.svc-mark .o{fill:none;stroke:var(--mark-comb);stroke-width:2}
.svc-mark .d{fill:var(--mark-circuit)}

/* ── Case studies. Fixes the two defects raised on the live site:
      the stretched hexagons (source art is 854×1000 vs 1024×784) and the
      unaligned "Read more". aspect-ratio normalises the art; the 3-row grid
      puts every button on a shared baseline regardless of title length. ── */
.case-card{position:relative;display:flex;flex-direction:column;
  overflow:hidden;padding:0}
/* The brand allows a shadow on at most one panel per section. In dark mode an
   ink shadow is invisible, so "featured" is carried by the accent border too. */
.case-card--featured{box-shadow:var(--shadow);border-color:var(--accent)}
.case-art{aspect-ratio:16/10;width:100%;height:auto;object-fit:cover;
  object-position:center;display:block;background:var(--bg-alt)}
.case-tag{position:absolute;top:14px;inset-inline-start:14px;
  background:color-mix(in srgb,var(--bl-ink) 82%,transparent);color:#FFFFFF;
  font-size:.75rem;font-weight:var(--fw-medium);padding:5px 11px;
  border-radius:100px;backdrop-filter:blur(4px)}
/* flex column + margin-top:auto puts every "Read the case study" on a shared
   baseline regardless of how long the title or summary runs. */
.case-body{padding:22px 24px 24px;display:flex;flex-direction:column;
  gap:10px;flex:1}
.case-link{align-self:start;margin-top:auto;padding-top:6px;color:var(--accent);
  font-weight:var(--fw-medium);font-size:.9375rem;text-decoration:none;
  border-bottom:1px solid currentColor;padding-bottom:2px}

/* ── Mark-N band: the dark-mode tokens, used as a deliberate inversion in
      light mode. Shows the token system carrying a mid-page colour shift. ── */
.band{--bg:var(--bl-night);--surface:#0C252D;--text:#E6F3F6;
  --text-muted:#8FB0B9;--border:#17363F;--accent:var(--bl-lift);
  --btn-bg:var(--bl-lift);--btn-text:#04222B;--btn2-text:var(--bl-lift);
  --mark-comb:var(--bl-lift);--mark-circuit:#FFFFFF;--mark-die:#FFFFFF;
  --signal-glow:var(--bl-lift);
  background:var(--bg);color:var(--text);border-top:0}
.band .shell-wrap{display:grid;gap:clamp(30px,4vw,56px);
  grid-template-columns:minmax(0,1fr) minmax(0,.9fr);align-items:center}
@media (max-width:860px){.band .shell-wrap{grid-template-columns:1fr}}
.band h2{font-size:var(--fs-h2);letter-spacing:-.015em}
.band .lede{color:var(--text-muted);margin-top:14px;max-width:46ch}
.feature-list{list-style:none;margin:26px 0 0;padding:0;display:grid;gap:13px}
.feature-list li{display:grid;grid-template-columns:16px 1fr;gap:12px;
  align-items:start;font-size:.9375rem;color:var(--text-muted)}
.feature-list svg{width:16px;height:18px;margin-top:3px}
.feature-list .o{fill:none;stroke:var(--accent);stroke-width:2.4}
.band-visual{display:grid;place-items:center}
/* depth 3 — a gentler rate than the hero, so the page doesn't feel busy */
.band-shot{width:min(320px,80%);height:auto;border-radius:var(--radius-lg);
  transform:translate3d(0,calc(var(--sy,0) * -.012px),0);will-change:transform}
@media (max-width:860px){.band-shot{transform:none;width:min(240px,62%)}}

/* ── "Our edge": four parallel claims, NOT a sequence — so no 01/02/03. ── */
.edge-list{list-style:none;margin:0;padding:0;display:grid;gap:22px;
  grid-template-columns:repeat(auto-fit,minmax(238px,1fr))}
.edge-list li{display:grid;grid-template-columns:22px 1fr;gap:14px;align-items:start}
.edge-list svg{width:22px;height:25px;margin-top:2px}
.edge-list .o{fill:none;stroke:var(--mark-comb);stroke-width:2.2}
.edge-list .d{fill:var(--mark-circuit)}
.edge-list p{font-size:.9375rem;color:var(--text-muted)}
.edge-list strong{display:block;color:var(--text);font-weight:var(--fw-medium);
  margin-bottom:4px;font-family:var(--font-display)}

.split{display:grid;gap:clamp(26px,4vw,52px);
  grid-template-columns:repeat(auto-fit,minmax(272px,1fr))}
.statement{font-family:var(--font-display);font-weight:var(--fw-medium);
  font-size:clamp(1.25rem,.9rem + 1.1vw,1.6rem);line-height:1.34;
  letter-spacing:-.015em;max-width:24ch}

/* ── Industries. Six hex tiles; the icon is the client's own artwork. ── */
.industries{list-style:none;margin:0;padding:0;display:grid;
  gap:clamp(12px,1.6vw,18px);
  grid-template-columns:repeat(auto-fit,minmax(168px,1fr))}
.industries li{display:flex;align-items:center;gap:13px;padding:16px 18px;
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius);font-size:.9375rem;font-weight:var(--fw-medium);
  transition:border-color .22s}
.industries li:hover{border-color:var(--accent)}
.industries img{width:28px;height:28px;object-fit:contain;flex:none}
:root[data-theme="dark"] .industries img{filter:brightness(0) invert(1) opacity(.82)}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .industries img{filter:brightness(0) invert(1) opacity(.82)}
}

/* ── The project collage, carried over from the current site and given the
      new treatment. One sheet image, ten pointy-top hexagon windows onto it.
      Each COLUMN drifts at its own rate off --r (section-relative scroll
      position, -1..1), so the cluster separates as it passes. ── */
.collage{position:relative;display:block;width:100%;max-width:440px;
  aspect-ratio:522/791;margin-inline:auto}
.collage .cell{position:absolute;left:var(--x);top:var(--y);
  width:32.7586%;height:24.9052%;
  background-image:var(--sheet);background-size:305.2632% 401.5228%;
  background-position:var(--bgx) var(--bgy);background-repeat:no-repeat;
  clip-path:polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%);
  backface-visibility:hidden;
  transform:translate3d(0,calc(var(--r,0) * var(--d,0) * 1px),0)}
@media (max-width:860px){.collage{max-width:330px}.collage .cell{transform:none}}

/* ══ CONTACT FORM ════════════════════════════════════════════════════════
   Field set, ids, error slots and outcome banners mirror the real
   cms/includes/contact_form_fields.html exactly, so this is reviewable as
   the thing that ships rather than an impression of it.
   ════════════════════════════════════════════════════════════════════════ */
.contact-grid{display:grid;gap:clamp(30px,4vw,56px);
  grid-template-columns:minmax(0,.8fr) minmax(0,1.1fr)}
@media (max-width:860px){.contact-grid{grid-template-columns:1fr}}

.contact-details{list-style:none;margin:28px 0 0;padding:0;display:grid;gap:15px}
.contact-details li{display:grid;grid-template-columns:18px 1fr;gap:13px;
  align-items:start;font-size:.9375rem;color:var(--text-muted)}
.contact-details svg{width:18px;height:18px;margin-top:3px;flex:none;
  fill:none;stroke:var(--accent);stroke-width:1.7}
.contact-details a{color:inherit;text-decoration:none}
.contact-details a:hover{color:var(--accent)}

.contact-form{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius-lg);padding:clamp(20px,3vw,30px)}
.form-row{display:grid;gap:16px;grid-template-columns:1fr 1fr;margin-bottom:16px}
.form-row--single{grid-template-columns:1fr}
@media (max-width:560px){.form-row{grid-template-columns:1fr}}
.contact-form label{display:block;font-size:.875rem;font-weight:var(--fw-medium);
  margin-bottom:6px;color:var(--text)}
/* --border is a 1.25:1 hairline — right for decorative rules, but WCAG 1.4.11
   wants 3:1 for the boundary of an interactive control. Inputs therefore use
   a stronger edge than the cards around them. */
.contact-form input,.contact-form textarea{width:100%;font:inherit;
  font-size:.9375rem;color:var(--text);background:var(--bg);
  border:1px solid color-mix(in srgb,var(--text-muted) 58%,var(--surface));
  border-radius:var(--radius-sm);
  padding:11px 13px;transition:border-color .18s}
/* The box grows with what is typed, instead of handing the visitor a drag
   handle. A manual handle is the wrong control here: it can be dragged wider
   than the form and over the layout, it is invisible to touch (there is no
   corner to grab on a phone), and it asks the visitor to manage a box rather
   than write a message.

   field-sizing does it with no script at all, and min/max-height keep it
   between a readable opening size and a height that cannot push the submit
   button off screen. Past the maximum the textarea scrolls.

   resize:none is safe BECAUSE of the auto-growth: taking the handle away on a
   fixed box would be a real loss, taking it away from a box that is always the
   size of its content is not. js/redesign.js grows it the same way where
   field-sizing is missing, so no browser is left with a fixed 120px box. */
.contact-form textarea{
  resize:none; min-height:120px; max-height:min(420px,52vh); overflow-y:auto;
  field-sizing:content;
}
/* No transition on height: it would animate on every keystroke. */
@supports not (field-sizing:content){
  .contact-form textarea{min-height:120px}
}
.contact-form input::placeholder,.contact-form textarea::placeholder{
  color:var(--text-muted);opacity:.72}
.contact-form input:hover,.contact-form textarea:hover{border-color:var(--accent)}
.form-required-note{font-size:var(--fs-small);color:var(--text-muted);
  margin:4px 0 18px}

/* Error slots ship EMPTY and HIDDEN. js/forms.js writes JSON field errors into
   them in one browser's DOM — never server-rendered, because this markup is
   shared through the placeholder cache. */
.field-error{margin-top:6px;font-size:var(--fs-small);color:var(--danger)}
.field-error[hidden]{display:none}
.contact-form input[aria-invalid="true"],
.contact-form textarea[aria-invalid="true"]{border-color:var(--danger)}

.form-messages{display:grid;gap:10px;margin-bottom:20px}
.form-messages:empty{display:none}
.form-message{border-inline-start:3px solid;padding:12px 16px;
  border-radius:var(--radius-sm);font-size:.9375rem;margin:0}
.form-message[hidden]{display:none}
/* Teal carries anything near text; cyan stays decorative, per the guidelines. */
.form-message--success{background:color-mix(in srgb,var(--accent) 12%,var(--bg));
  border-color:var(--accent);color:var(--text)}
.form-message--error{background:color-mix(in srgb,var(--danger) 12%,var(--bg));
  border-color:var(--danger);color:var(--text)}

.captcha-note{font-size:.75rem;color:var(--text-muted);margin:0 0 16px}

/* ══ FOOTER ══════════════════════════════════════════════════════════════ */
.site-foot{border-top:1px solid var(--border);padding-block:clamp(40px,5vw,64px) 0;
  background:var(--bg-alt)}
.foot-grid{display:grid;gap:clamp(26px,4vw,48px);
  grid-template-columns:minmax(220px,1.3fr) repeat(3,minmax(130px,1fr));
  padding-bottom:clamp(30px,4vw,48px)}
@media (max-width:860px){
  .foot-grid{grid-template-columns:repeat(auto-fit,minmax(145px,1fr))}
  .foot-brand{grid-column:1/-1}
}
.foot-brand .bl-logo{height:30px;margin-bottom:14px}
.foot-brand p{font-size:.875rem;color:var(--text-muted);max-width:30ch}
.foot-col h3{font-family:var(--font-display);font-size:.875rem;
  font-weight:var(--fw-semibold);margin-bottom:14px;color:var(--text)}
.foot-col ul{list-style:none;margin:0;padding:0;display:grid;gap:9px}
.foot-col a{color:var(--text-muted);text-decoration:none;font-size:.875rem}
.foot-col a:hover{color:var(--accent)}

.foot-bar{border-top:1px solid var(--border);padding-block:20px;
  display:flex;flex-wrap:wrap;gap:14px 22px;align-items:center;
  justify-content:space-between;font-size:var(--fs-small);
  color:var(--text-muted)}
.foot-bar nav{display:flex;flex-wrap:wrap;gap:18px}
.foot-bar a{color:inherit;text-decoration:none}
.foot-bar a:hover{color:var(--accent)}
.lang-switch{display:flex;gap:2px;border:1px solid var(--border);
  border-radius:var(--radius-sm);overflow:hidden}
.lang-switch a{padding:5px 12px;font-size:.8125rem}
.lang-switch a[aria-current="page"]{background:var(--accent);color:var(--btn-text)}

/* ══ FLOATING SOCIAL SPEED-DIAL ══════════════════════════════════════════
   Restyled onto the tokens, contract unchanged. The hexagon is kept — it was
   already the house shape in the original, and it is now also the brand mark's
   geometry, so this is one of the few components the rebrand makes *more*
   correct rather than less.
   ════════════════════════════════════════════════════════════════════════ */
/* align-items:flex-end, not center. The menu below wraps into columns on a
   short viewport, which stretches this container to the width of the widest
   row -- and a centred toggle then floats in the middle of that box instead of
   staying in the corner. Measured at 780x390: the container is 234px wide and
   the toggle sat 123px from the edge, on its own, with nothing around it.
   flex-end is cross-axis in a column flex container, so it follows the writing
   direction and mirrors under RTL without a second rule. */
/* THE CORNER STACK. Two fixed widgets share this corner now, and each used to
   hardcode the other's geometry: the assistant's offset had to know the social
   toggle's height and the social widget's had to know the reCAPTCHA badge's.
   One set of names instead, so moving either one is an edit in one place.

   --corner-badge is the clearance for Google's reCAPTCHA badge, which is pinned
   to this corner on every page because the footer form loads it site-wide.
   Covering it hides a notice Google's terms require to stay visible -- it is a
   compliance number, not a spacing taste. --corner-item is the social toggle's
   height, which is what the assistant has to clear to sit above it. */
:root{--corner-inline:16px;--corner-bottom:16px;--corner-badge:68px;
  --corner-item:56px;--corner-gap:12px}
@media (max-width:560px){
  :root{--corner-inline:12px;--corner-bottom:12px;--corner-item:52px}
}
.sfloat{position:fixed;inset-inline-end:var(--corner-inline);
  bottom:var(--corner-bottom);z-index:60;
  display:flex;flex-direction:column;align-items:flex-end;gap:9px}
.sfloat__toggle,.sfloat__item{position:relative;display:grid;place-items:center;
  border:0;padding:0;background:transparent;text-decoration:none}
/* Painted by ::before so the control itself stays unclipped and its focus ring
   is still visible — a clip-path on the element would cut the ring off. */
.sfloat__toggle::before,.sfloat__item::before{content:"";position:absolute;
  inset:0;clip-path:polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%);
  transition:background .2s}
.sfloat__toggle{width:50px;height:56px;cursor:pointer;color:var(--btn-text)}
.sfloat__toggle::before{background:var(--btn-bg)}
.sfloat__toggle:hover::before{background:var(--accent-strong)}
.sfloat__toggle svg,.sfloat__item svg{position:relative}
.sfloat__ico--close{display:none}
.sfloat.is-open .sfloat__ico--open{display:none}
.sfloat.is-open .sfloat__ico--close{display:block}

/* column-REVERSE, so source order runs outward from the toggle. With plain
   `column` the first network sits furthest away and is the first to clip — at
   780x390 that put WhatsApp, LinkedIn, Facebook and Instagram off-screen while
   GitHub and TikTok stayed reachable. Priority now degrades from the bottom of
   the list, not the top. The --i stagger then also plays outward from the
   toggle, which is the natural direction for a speed-dial. */
.sfloat__menu{list-style:none;margin:0;padding:0;display:flex;
  flex-direction:column-reverse;align-items:center;gap:7px;order:-1}

/* Nine hexagons stacked vertically is 553px. That fits a portrait phone (just:
   66% of a 780px viewport) and fails a landscape one outright. Below 620px of
   viewport height the menu wraps into columns instead, growing sideways where
   there is room rather than upward where there is not. */
@media (max-height:620px){
  .sfloat__menu{flex-wrap:wrap-reverse;max-height:min(58vh,300px);
    align-content:flex-end;gap:6px}
  .sfloat__item{width:38px;height:44px}
}
.sfloat__li{opacity:0;transform:translateY(10px) scale(.88);pointer-events:none;
  transition:opacity .18s ease,transform .18s ease}
.sfloat.is-open .sfloat__li{opacity:1;transform:none;pointer-events:auto;
  transition-delay:calc(var(--i,0) * 26ms)}
.sfloat__item{width:42px;height:48px;color:#FFFFFF}
.sfloat__item::before{background:var(--accent)}
.sfloat__item:hover::before{background:var(--bl-cyan)}
@media (max-width:560px){
  .sfloat__toggle{width:46px;height:52px}
  .sfloat__item{width:38px;height:44px}
}

/* ══════════════════════════════════════════════════════════════════════════
   REVEAL — content ships VISIBLE. CSS only hides it once JS has proven it can
   bring it back (html.js-ready), and a 2s failsafe in js/redesign.js clears
   that class unconditionally. Worst case is "no animation", never "no
   content" -- the structural fix for the 3,622px section the live site
   strands behind an inline opacity:0 it never clears.

   This block was in the prototype and was lost when the stylesheet was
   extracted from it: only the second half of its section 7, the reduce
   block below, came across. Without it .reveal and .is-in matched no rule
   at all, so every section the templates mark up for the reveal -- and the
   observer in js/redesign.js that adds .is-in to them -- did nothing on any
   of the twelve rendered pages. Restored verbatim.
   ══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion:no-preference){
  html.js-ready .reveal{opacity:0;transform:translateY(14px);
    transition:opacity .5s cubic-bezier(.215,.61,.355,1),
               transform .5s cubic-bezier(.215,.61,.355,1)}
  html.js-ready .reveal.is-in{opacity:1;transform:none}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms !important;
    animation-iteration-count:1 !important;transition-duration:.001ms !important;
    scroll-behavior:auto !important}
  .bl-mark .signal{animation:none;opacity:0}
  /* The loader goes still rather than disappearing: the region still announces
     through its live text, so the state is never carried by motion alone. */
  .bl-mark .flash{animation:none}
  .bl-mark--loader .signal{animation:none;opacity:1}
  .rail-pulse{display:none}
  .hero-pattern,.hero-mark,.band-mark{transform:none !important}
}

/* ===========================================================================
   Containing editor markup
   ===========================================================================
   Found against the restored production database, not in the prototype: the
   client_logos slot holds eleven plugins whose own markup carries Bootstrap
   layout classes from the old design (`d-flex justify-content-between mb-3`).
   Those fight the grid this template puts around them, and one logo escaped
   53px past the inline start of the viewport.

   A placeholder renders whatever an editor put in it, so the template cannot
   assume the markup shape. It can only make sure the slot is a container that
   content cannot break out of.
*/
.clients { overflow: hidden; }
.clients > * { min-width: 0; max-width: 100%; }
.clients img { max-width: 100%; height: auto; }

/*
   overflow-x on <body> alone is not enough here. The viewport takes its overflow
   from <html>, and only falls back to <body> when html is `visible` -- a rule
   that behaves inconsistently once a fixed-position descendant is in play, and
   reCAPTCHA's badge is fixed at right:-186px on every page with a form.

   `clip` rather than `hidden`: hidden makes the element a scroll container,
   which breaks `position: sticky` on the header. clip does not.
*/
html { overflow-x: clip; }

/* ===========================================================================
   Client logo wall
   ===========================================================================
   Seven supplied marks, background removed by edge flood-fill and optically
   normalised to roughly equal rendered area -- they arrived between 447x447 and
   215x74, and normalising on one dimension lets the wide ones dominate a row.
   Lossless WebP with alpha, 25 KB for the set.

   NOT recoloured, and not inverted in dark mode. These are other companies'
   trademarks: inverting Beko's blue or flattening EIC's red misrepresents a
   mark its owner controls, which is a different thing from restyling our own
   assets. The earlier prototype did `filter: invert(1)` here and that was
   wrong.

   Instead each sits on a light chip. In light mode the chip is invisible; on
   Night it is the surface that lets a full-colour logo stay itself. It also
   solves the enclosed-counter problem -- the white inside beko's b, e and o is
   genuinely part of those letterforms, so it stays opaque, and on a dark ground
   without a chip it would read as three white blobs.
*/
/* It drifts, on its own line, instead of sitting in a grid beside the heading.
   A grid is a closed set -- it has to be re-balanced every time a logo is added,
   and past about a dozen it either shrinks the marks or leaves a ragged last
   row. A strip absorbs any number of them and reads as "and more".

   The loop is two identical copies of the row translated by exactly half the
   track, so the clone lands where the original started. Hence margin on each
   chip rather than `gap`: with gap the half-way point falls half a gap short of
   the seam and the row jumps once per cycle.

   --clients-count comes from the template and is the number of chips in one
   copy, so the strip keeps one speed as the list grows rather than spinning
   faster. ~4.6s per logo is about 38px/s -- readable, and slow enough not to
   pull the eye away from the text above it. */
.clients-rail{
  position:relative;
  --clients-gap:clamp(10px,1.4vw,16px);
  /* clip, not hidden: the track is `max-content` and twice the rail's width, so
     without this it bleeds past the section to the edge of the viewport. hidden
     would do it too but makes this a scroll container for no reason.

     The edges fade instead of cutting, which is what tells the eye the row
     continues rather than ending at the container. Symmetrical, so RTL needs no
     second rule; no-repeat because a mask tiles by default, which puts a second
     pair of fades over whatever hangs outside the box. */
  overflow:clip;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-image:linear-gradient(to right,transparent,#000 7%,#000 93%,transparent);
  mask-image:linear-gradient(to right,transparent,#000 7%,#000 93%,transparent);
}
.clients-rail__track{
  display:flex; width:max-content;
  animation:clientsDrift calc(var(--clients-count,7) * 4.6s) linear infinite;
}
/* Stops under the pointer so a logo can be looked at, and under keyboard focus
   in case a chip is ever made a link. */
.clients-rail:hover .clients-rail__track,
.clients-rail:focus-within .clients-rail__track{ animation-play-state:paused; }
@keyframes clientsDrift{ to{ transform:translateX(-50%); } }
/* RTL lays the track out from the right, so the same motion has to run the
   other way to keep travelling with the reading direction. */
[dir="rtl"] .clients-rail__track{ animation-name:clientsDriftRtl; }
@keyframes clientsDriftRtl{ to{ transform:translateX(50%); } }

.clients{
  list-style:none; margin:0; padding:0;
  display:flex; align-items:stretch;
}
.clients li{
  display:grid; place-items:center;
  flex:0 0 auto;
  width:clamp(136px,16vw,172px);
  margin-inline-end:var(--clients-gap);
  padding:16px 18px; min-height:84px;
  background:#FFFFFF;
  border:1px solid var(--border);
  border-radius:var(--radius);
  transition:border-color .22s, transform .22s;
}
.clients img{
  max-width:100%; max-height:46px; width:auto; height:auto;
  object-fit:contain;
  /* Quiet by default so the wall reads as texture rather than seven competing
     brands, full colour on hover. Desaturation is presentation, not a change to
     the asset -- the files keep their colour. */
  filter:grayscale(1); opacity:.62;
  transition:filter .25s, opacity .25s;
}
.clients li:hover{ border-color:var(--accent); }
.clients li:hover img{ filter:none; opacity:1; }

/* Touch has no hover, so without this the quiet state is the only state a phone
   ever shows: every mark permanently desaturated at .62, and no gesture that
   reaches the legible one. Desktop can afford restraint because the colour is a
   pointer away; here the wall has one chance to be read, and whether these
   brands are recognisable IS the content of the section.

   (hover: none) rather than a width breakpoint -- a small window on a laptop
   still has a pointer, and a large tablet still does not. */
@media (hover: none){
  .clients img{ filter:none; opacity:1; }
}

/* No motion: the strip falls back to the wall it replaced -- one wrapped,
   centred row of the real list, with the clone dropped so nothing is duplicated
   on screen, and no mask, since there is no longer an edge to imply. */
@media (prefers-reduced-motion:reduce){
  .clients-rail{ -webkit-mask-image:none; mask-image:none; }
  .clients-rail__track{ animation:none; width:auto; flex-wrap:wrap;
    justify-content:center; gap:var(--clients-gap); }
  .clients-rail__track > [aria-hidden="true"]{ display:none; }
  .clients{ flex-wrap:wrap; justify-content:center; gap:var(--clients-gap); }
  .clients li{ margin-inline-end:0; }
  .clients li, .clients img{ transition:none; }
}

/* ===========================================================================
   Task 6 — sections, against real content
   ===========================================================================
   The prototype's rules targeted classes its own hand-written SVGs carried
   (.svc-mark .o / .d) and an <img class="case-art">. The template emits bare
   <polygon>/<circle> inside a classed <svg>, and wraps the case image in a
   <div class="case-art">. So the marks fell back to the SVG default fill —
   solid black discs — and the case art was never normalised.

   Styling by element rather than by class on the child: the shape is the
   contract, and a generated mark should not have to remember two class names.
*/
.svc-mark, .ind-icon, .edge-mark, .feat-mark { display:block; flex:none; }
.svc-mark  { width:34px; height:38px; margin-bottom:16px; }
.ind-icon  { width:22px; height:25px; }
.edge-mark { width:22px; height:25px; margin-top:2px; }
.feat-mark { width:16px; height:18px; margin-top:4px; }

.svc-mark polygon, .ind-icon polygon, .edge-mark polygon, .feat-mark polygon {
  fill:none; stroke:var(--mark-comb); stroke-width:2; stroke-linejoin:round;
}
.feat-mark polygon { stroke:var(--accent); stroke-width:2.4; }
.svc-mark circle, .ind-icon circle, .edge-mark circle { fill:var(--mark-circuit); }
/* On the dark Mark-N band --mark-comb is lift and --mark-circuit is white,
   which the band already redefines, so the marks follow without a second rule. */

/* ---- Case study art ------------------------------------------------------
   The two source images are 854x1000 portrait and 1024x784 landscape -- the
   pair that produced the stretched hexagons on the live site. The placeholder
   renders an <img> inside .case-art, so the normalisation has to sit on the
   child: aspect-ratio crops both to one shape instead of distorting either. */
.case-art { display:block; width:100%; aspect-ratio:16/10; overflow:hidden;
            background:var(--bg-alt); border-radius:0; }
.case-art img { width:100%; height:100%; object-fit:cover; object-position:center;
                display:block; }

/* ---- Services -----------------------------------------------------------
   Links were sitting directly under the copy at whatever height the longest
   description ended. margin-top:auto in a flex column puts every one on the
   card's own baseline, so the row reads as a set. */
.grid--4 .card { display:flex; flex-direction:column; }
.grid--4 .card .card-body { flex:1; }
.grid--4 .card .card-link { margin-top:14px; align-self:start; }
.grid--4 .card h3 { font-size:var(--fs-h3); letter-spacing:-.01em; margin-bottom:8px; }
.grid--4 .card .card-body { color:var(--text-muted); font-size:.9375rem; }

/* ---- Mark-N band --------------------------------------------------------- */
.feature-list { list-style:none; margin:24px 0 0; padding:0; display:grid; gap:13px; }
.feature-list li { display:grid; grid-template-columns:16px 1fr; gap:12px;
                   align-items:start; font-size:.9375rem; color:var(--text-muted); }
.feat-head { margin-top:22px; font-family:var(--font-display);
             font-weight:var(--fw-medium); color:var(--text); }

/* ---- Edge and industries ------------------------------------------------- */
.edge-list { list-style:none; margin:0; padding:0; display:grid; gap:22px;
        grid-template-columns:repeat(auto-fit,minmax(238px,1fr)); }
.edge-list li { display:grid; grid-template-columns:22px 1fr; gap:14px; align-items:start; }
.edge-list li > div { font-size:.9375rem; color:var(--text-muted); }
.edge-head { margin-top:clamp(34px,5vw,56px); }

.industries { list-style:none; margin:0; padding:0; display:grid;
              gap:clamp(12px,1.6vw,18px);
              grid-template-columns:repeat(auto-fit,minmax(178px,1fr)); }
.industries li { display:flex; align-items:center; gap:13px; padding:16px 18px;
                 background:var(--surface); border:1px solid var(--border);
                 border-radius:var(--radius); font-size:.9375rem;
                 font-weight:var(--fw-medium); transition:border-color .22s; }
.industries li:hover { border-color:var(--accent); }

/* ---- Vision / mission ---------------------------------------------------- */
.vm-split { margin-top:clamp(26px,4vw,44px); }
.vm-split .card h3 { font-size:var(--fs-h3); margin-bottom:8px; }
.vm-split .card p  { color:var(--text-muted); font-size:.9375rem; }


/* ---------------------------------------------------------------------------
   Class namespacing
   ---------------------------------------------------------------------------
   The redesign extends cms/base.html, so it loads style.css -- 4,300 lines of
   the old design -- alongside this file. 22 of the 83 classes the redesign
   templates use also appear there, and seven of those old rules paint
   something.

   .hex-icon was the live one: style.css:1535 makes it a 24x24 cyan block with a
   clip-path and rotateZ(90deg), which my <svg> inherited, so every service mark
   rendered as a solid cyan hexagon instead of a teal outline with a cyan dot.
   Renamed to .svc-mark, and .edge to .edge-list for the same reason.

   The other five -- btn, primary-btn, secondary-btn, btn-bee, hex-collage --
   are deliberately left alone. Those classes arrive in placeholder DEFAULTS,
   which are old-design markup, and they should keep using the old rules.
   --------------------------------------------------------------------------- */

/* ===========================================================================
   Inner pages — services, products, jobs, case studies
   =========================================================================== */

/* Shared page hero. Narrower than the home hero: these pages are read, not
   landed on, so the headline does not need the full display size. */
/* grid-column spans the parent, because there are two grids nested here and
   only one of them was designed for this markup. `.hero .shell-wrap` is a
   two-column grid built for the home page, whose hero holds .hero-text and
   .hero-visual side by side. The inner pages put a single .page-hero in that
   same container, so it landed in column one and column two stayed empty:
   measured on /en/products/ at 1330px, .page-hero had 585px of a 1200px
   container and its own two columns were squeezed into that, which is why the
   copy ran at 287px wide and the phone mockup was tiny with half the hero
   blank beside it. Spanning 1/-1 gives it the full container back. */
.page-hero{display:grid;grid-column:1/-1;gap:clamp(28px,4vw,52px);
  grid-template-columns:minmax(0,1.05fr) minmax(0,.9fr);align-items:center}
@media (max-width:860px){
  .page-hero{grid-template-columns:1fr}
  .page-hero-visual{order:-1}
  /* Capped for the same reason the home hero caps its collage: stacked, the
     visual comes first, and at full width it pushes the headline toward the
     fold. Measured at 390x667 before this: the art ran 380px tall on products
     and 419px on a case study, putting the h1 at 533px and 572px with the
     intro at or past the fold. Capping by HEIGHT rather than width, because
     these pages carry art of several different aspect ratios -- a phone
     mockup, a wide illustration -- and it is the vertical space that is
     scarce. 34vh puts every one of them at the same place. */
  .page-hero-visual img,.page-hero-visual svg{
    max-height:min(300px,34vh);width:auto}
}
.page-hero-visual img,.page-hero-visual svg{max-width:100%;height:auto;display:block;
  margin-inline:auto}
.page-hero .hero-title h1,.page-hero .hero-title h2{font-size:var(--fs-display);
  letter-spacing:-.02em;line-height:1.08;margin:0}
.page-hero .hero-intro{margin-top:18px;max-width:54ch;color:var(--text-muted)}

/* The "Service 1..4" label. It was an unclassed <span>, so it inherited body
   copy exactly -- same 16px, same colour -- and read as a stray sentence above
   the heading rather than as a label on it. Smaller, tracked out, in the accent
   and in caps, so it does the job of an eyebrow and stops competing with the
   sentence underneath. */
.svc-eyebrow{display:block;font-size:.75rem;font-weight:var(--fw-semibold);
  letter-spacing:.14em;text-transform:uppercase;color:var(--accent);
  margin:0 0 10px}

.svc-sub{color:var(--accent);font-weight:var(--fw-medium);
  font-size:.9375rem;margin:0 0 6px}
.svc-desc{margin-top:16px;max-width:60ch;color:var(--text-muted)}
/* Same fault .page-hero above was given grid-column:1/-1 for, and the same
   fix. .hero .shell-wrap is a two-column grid built for the home page's
   headline-beside-visual. The services page puts three children in it -- the
   hidden h1, the service block and its card row -- so grid auto-placement
   dealt them into cells: the copy ran in a 281px column (about 35 characters a
   line) and the card row landed in column two, which put the cards ABOVE the
   heading they belong to. Document order said block-then-cards, the screen
   said the reverse, so the reading order was wrong as well as the measure.

   Spanning the full container gives each one its own row, in document order. */
.hero .shell-wrap > .svc-block,
.hero .shell-wrap > .svc-detail,
.hero .shell-wrap > h1{grid-column:1/-1}

/* The media column was .8fr -- roughly 450px of a 1072px block -- while what
   sits in it is a 72px decorative mark. That is where the empty right-hand side
   came from, and it pushed the copy into a column narrower than it needed.

   auto sizes the column to whatever is actually in it, so the mark takes 72px
   and an editor who later drops a real image into first_service_image still
   gets the room for it. The copy is capped at its own measure instead of
   filling whatever is left: at 1072px it ran 130 characters a line, twice a
   comfortable read. */
.svc-block{display:grid;gap:clamp(26px,4vw,52px);
  grid-template-columns:minmax(0,68ch) auto;align-items:center;
  justify-content:start}
.svc-block-head{min-width:0}
@media (max-width:860px){
  .svc-block{grid-template-columns:1fr}
}
/* Two service blocks in a row need the same air between them that separates
   the first from its cards. */
.svc-detail + .svc-block{margin-top:clamp(48px,7vw,96px)}
.svc-block-media img{max-width:100%;height:auto;display:block;margin-inline:auto}
/* The service NAME is an h2, and the templates put it in whichever of these
   two wrappers that service was built with -- .svc-block-title on some,
   .svc-sub on others, because the placeholder slots are named the other way
   round from what they hold (first_service_sub_title carries the title). The
   slot names cannot change without orphaning an editor's plugin, so the
   heading is styled for both wrappers instead.

   .svc-sub sets a 15px base for its own text, and an h2 is sized in em, so a
   heading left to inherit it came out at 22.5px in the accent colour while the
   same heading in the other wrapper was 33.6px in the text colour. Same page,
   same element, two sizes. */
.svc-block-title h2,.svc-block-title h3,
.svc-sub h2,.svc-sub h3{font-size:var(--fs-h2);color:var(--text);
  letter-spacing:-.015em;margin:0}
.svc-detail{margin-top:clamp(28px,4vw,48px)}

.grid--3{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}

/* Lists inside placeholder content: the editor's <ul> arrives unstyled. */
/* The card's leading icon sat with its box bottom exactly on the heading's top
   -- measured at 0px on every one of them -- so the artwork read as resting on
   the words. The rule that used to space these lives in style.css, which the
   redesign does not load. */
.card > div > img:only-child{display:block;margin-bottom:12px}

.card ul,.svc-desc ul,.cta-copy ul{margin:10px 0 0;padding-inline-start:1.1em;
  color:var(--text-muted);font-size:.9375rem;display:grid;gap:7px}
.card img,.cta-copy img{max-width:100%;height:auto}

/* One CTA band per page, the single place a shadow is spent. */
.cta-band{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius-lg);padding:clamp(24px,4vw,44px);
  box-shadow:var(--shadow)}
.cta-copy{color:var(--text-muted);margin-top:10px;max-width:62ch}
.cta-band .hero-cta{margin-top:22px}

/* App-store badges. Capped: the raw assets are ~500px wide and filled the hero
   column, which made the download buttons louder than the product name. */
.app-badges{margin-top:26px}
.app-badges-title{font-size:var(--fs-small);color:var(--text-muted);margin-bottom:10px}
.app-badges-row{display:flex;flex-wrap:wrap;gap:12px;align-items:center}
.app-badges-row img{height:44px;width:auto;max-width:100%;display:block}
.app-badges-row a{display:inline-flex}

/* Products: benefits, core features, who-it-is-for, stack */
.benefit-split,.target-split,.screens-split{align-items:center}
.benefit-art img,.target-art img,.screens-split img{max-width:100%;height:auto;
  display:block;margin-inline:auto}
.feature-list .tick img,.feature-list .tick svg{width:16px;height:16px;display:block}
.feature-list .tick{margin-top:3px}
.feat-art img{width:44px;height:44px;object-fit:contain;margin-bottom:12px}
.feat-copy{color:var(--text-muted);font-size:.9375rem}
.stack-logo img{height:38px;width:auto;object-fit:contain;margin:10px 0}
/* A row of marks rather than one, for the cards that name more than one
   technology. Wraps on a narrow card instead of shrinking the logos, because a
   wordmark below about 28px stops being readable and starts being texture.
   Baseline-aligned on a shared height: these files have aspect ratios from
   0.8 (Flutter) to 3.4 (Python), so matching heights is the only way the row
   reads as one set. */
.stack-logos{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:center;
  /* Column and row gaps are set apart because they do different jobs. The
     column gap separates marks standing side by side and wants to be generous;
     the row gap only appears once a card holds enough marks to wrap, and at
     the same 28px the second line looked like a separate row of logos rather
     than the rest of one set. Backend carries four, so it wraps 2x2 at every
     width -- which reads better than four marks crushed into one line. */
  column-gap:clamp(14px,2.4vw,28px); row-gap:14px; margin:10px 0;
}
.stack-logos img{height:34px;width:auto;object-fit:contain;margin:0}
@media (max-width:420px){ .stack-logos img{height:28px} }
.stack-name{color:var(--text-muted);font-size:.875rem}

/* Case study: feature cards and the technology row */
.case-feat-art img{width:46px;height:46px;object-fit:contain;margin-bottom:12px}
.case-feat-text{color:var(--text-muted);font-size:.9375rem}
.case-more{margin-top:clamp(22px,3vw,34px);color:var(--text-muted)}
.tech-art img{height:40px;width:auto;object-fit:contain;margin-bottom:10px}
/* The hosting card's mark. Same 40px as the three logos beside it, and left
   aligned like them -- the `m-auto` on those images is a Bootstrap class this
   stylesheet does not reimplement, so they sit at the card's padding edge, and
   the card's heading and text are left aligned too. */
.tech-mark{height:40px;width:auto;display:block;margin-bottom:10px}
.tech-mark polygon{fill:none;stroke:var(--mark-comb);stroke-width:2;
  stroke-linejoin:round}
.tech-mark .wire{fill:none;stroke:var(--mark-comb);stroke-width:2;
  stroke-linecap:round}
.tech-mark .node{fill:var(--mark-circuit)}
.tech-desc{color:var(--text-muted);font-size:.9375rem;margin-top:6px}

/* Jobs: the application form carries its own field styling from the contact
   rules; it only needs the surface around it. */
#apply .contact-form{max-width:760px;margin-inline:auto}

/* ===========================================================================
   Shared chrome — what style.css was still painting on these pages
   ===========================================================================
   The redesign templates no longer load css/style.css. Everything below is the
   part of it these pages actually depended on, reimplemented on the brand
   tokens. BACKLOG.md 39.

   It was selected by measurement rather than by reading: every computed
   property on every element of a settled page was snapshotted, style.css was
   disabled, the page was snapshotted again, then the sheet was re-enabled and
   snapshotted a third time. A property counts only if it changed when the
   sheet went away AND returned to the same value when it came back. That
   A-B-A gate is what rejects the animation drift which made the naive
   two-snapshot diff report 123 false regressions on /ar/products/.

   About a third of style.css's rules MATCH something here, but matching is not
   contributing: most are name collisions whose declarations lose to the rules
   above. Only what won a property is below.

   Three sources feed that list, and only the first two are worth carrying:

     1. Chrome shared with the old design through includes this template still
        uses — the language chooser, the contact form fields, the social float,
        the skip link.
     2. Old-design class names that arrive in placeholder defaults and in
        editor plugins. A placeholder renders what an editor put in it, so the
        stylesheet has to meet that markup rather than the other way round.
     3. The old design's own decoration bleeding onto the new pages — the
        hero's pseudo-element flourishes, its headline colour, the bee sprites
        behind the buttons. None of that is ported. Removing it is the point.

   Values are retokenised, not copied: style.css reaches these colours through
   eighteen compatibility aliases (--primary-color, --gray-color, the --danger-*
   set) that it declares itself and nothing in the redesign references. Copying
   the rules verbatim would have meant porting that alias block too, and with it
   the hardcoded whites that render a white menu panel on Night.
   =========================================================================== */

/* ── The skip link ─────────────────────────────────────────────────────────
   cms/base.html emits class="visually-hidden skip-link", so the focused state
   has to beat .visually-hidden's !important declarations — the !important here
   is not decoration. (.skip in section 2 is the prototype's own skip link and
   matches nothing in the real markup; kept for prototype parity.) The
   clip-path line is added for the reason style.css had it: `clip` is
   deprecated, and a skip link that is merely 1px is still focusable. */
.visually-hidden,
.visually-hidden-focusable:not(:focus):not(:focus-within){clip-path:inset(50%) !important}
.skip-link:focus{position:fixed !important;top:10px;inset-inline-start:10px;
  z-index:10000;width:auto !important;height:auto !important;
  margin:0 !important;padding:12px 20px !important;overflow:visible !important;
  clip:auto !important;clip-path:none !important;white-space:nowrap;
  background:var(--btn-bg);color:var(--btn-text);
  border-radius:var(--radius-sm);text-decoration:none}

/* ── Header navigation ─────────────────────────────────────────────────────
   .nav-links is a flex row only because style.css made it one; without this
   the header's six links, the language chooser and the CTA stack vertically
   and the header becomes 430px tall.

   NOT ported, deliberately: style.css's own mobile menu, which set
   .nav-links{display:none} from 0 to 1024px and revealed it again on .open —
   a class only main.js's #hamburger ever adds, and the redesign header has no
   #hamburger. The result was a site with no reachable navigation at all
   between 761px and 1024px, and a hamburger that opened an empty panel on
   every phone. Measured at 390px and at 800px: 0 of 13 links visible, open or
   closed. The redesign's own rules below 760px are in section 4. */
.nav-links{display:flex;align-items:center;gap:1.5rem;list-style:none;
  margin:0;padding:0}
@media (max-width:1120px){.nav-links{gap:16px}}
.nav-links > li > a{color:var(--text-muted);text-decoration:none;
  font-size:.9375rem;font-weight:var(--fw-regular);white-space:nowrap}
.nav-links > li > a:hover,
.nav-links > li > a:focus-visible{color:var(--accent)}
.nav-links > li > a.active,
.nav-links > li > a[aria-current="page"]{color:var(--accent);
  font-weight:var(--fw-medium)}
.nav-lang{position:relative}
.nav-cta{display:flex;align-items:center}
.nav-cta .btn.primary{white-space:nowrap}

/* The mobile panel. Section 4 turns .site-nav into the dropdown; the list
   inside it has to stop being a row, and the link rules there were written
   against `.nav-link`, which this markup does not have. */
@media (max-width:1023px){
  .nav-links{flex-direction:column;align-items:stretch;gap:0;width:100%}
  /* :not(.nav-cta) matters. This rule is (0,2,1) and `.btn.primary` is (0,2,0),
     so without it the row styling won here and repainted the button's label
     with --text -- which is --bl-ink, the same ink the button's own --btn-bg
     is. Dark text on a dark button: the label vanished entirely on every
     phone. It was also handed a bottom border and the row's padding, so it
     was not a button any more either. The CTA is a button in a list, not a
     navigation row, and only the rows want this. */
  .site-nav .nav-links > li:not(.nav-cta) > a{display:block;padding:13px 2px;
    font-size:1rem;color:var(--text);border-bottom:1px solid var(--border)}
  .nav-theme,.nav-lang,.nav-cta{padding-top:12px}
  .nav-cta .btn.primary{width:100%}
}

/* ── Language chooser — cms/includes/language_chooser.html ─────────────────
   Shared with the old design, and styled nowhere else. Retokenised rather than
   copied: style.css paints the panel with --white-color, which is a literal
   #FFFFFF, so on Night the menu opened as a white card with white text on it.
   The toggle's border follows the contact inputs at 3:1 rather than the 1.25:1
   hairline, because WCAG 1.4.11 asks that of the boundary of a control. */
.lang-switcher{position:relative}
.lang-switcher__toggle{appearance:none;-webkit-appearance:none;
  display:inline-flex;align-items:center;gap:6px;background:none;
  border:1px solid color-mix(in srgb,var(--text-muted) 58%,var(--surface));
  border-radius:999px;padding:4px 12px;font:inherit;font-size:.875rem;
  line-height:1.7;color:var(--text-muted);cursor:pointer;
  transition:border-color .18s,color .18s}
.lang-switcher__toggle:hover{border-color:var(--accent);color:var(--accent)}
.lang-switcher__caret{width:6px;height:6px;border-right:1px solid currentColor;
  border-bottom:1px solid currentColor;transform:rotate(45deg) translateY(-2px);
  transition:transform .2s ease}
.lang-switcher__toggle[aria-expanded="true"] .lang-switcher__caret{
  transform:rotate(-135deg) translateY(-2px)}
.lang-switcher__menu{display:none;position:absolute;top:calc(100% + 8px);
  /* Anchored to the inline-end edge so the panel opens inward on both sides. */
  inset-inline-end:0;min-width:190px;margin:0;padding:8px;list-style:none;
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius);box-shadow:var(--shadow);z-index:2500}
.lang-switcher.is-open .lang-switcher__menu{display:block}
/* The no-JavaScript fallback, and only that. Clicking the toggle focuses it,
   so :focus-within matched for as long as the button held focus -- a second
   click removed .is-open and the menu stayed open anyway, closing only when a
   click elsewhere moved focus away. That is precisely the "it will not close
   again" behaviour, and it was the fallback fighting the script rather than
   the script being wrong.

   redesign.js marks the root with .js-lang once it has bound the toggle, so
   the fallback applies exactly when there is no script to do the job. */
html:not(.js-lang) .lang-switcher:focus-within .lang-switcher__menu{display:block}
.lang-switcher__menu a{display:flex;align-items:center;gap:10px;
  padding:8px 12px;border-radius:var(--radius-sm);font-size:.875rem;
  line-height:1.6;color:var(--text);text-decoration:none}
.lang-switcher__menu a:hover{background:var(--bg-alt);color:var(--accent)}
.lang-switcher__menu a.is-current{font-weight:var(--fw-semibold);
  color:var(--accent)}
.lang-switcher__code{min-width:26px;font-weight:var(--fw-semibold);opacity:.7}
@media (max-width:1023px){
  .lang-switcher__menu{position:static;box-shadow:none;min-width:0;
    border:0;padding-inline:0}
}

/* ── Buttons that arrive in placeholder defaults ───────────────────────────
   .primary-btn / .secondary-btn / .outline-btn are the old design's button
   classes, and they come through the hero CTA defaults and the contact form's
   submit, so the template cannot simply stop using them. They are given the
   redesign's own button shape rather than style.css's — same padding, radius,
   type scale and hover as .btn in section 4, so a page does not show two
   different button languages depending on whether the markup came from a
   default or from the template. style.css's min-width:220px goes with them: a
   fixed 220px is what made the contact form's submit 300px wide and the
   loudest thing in the footer. */
.primary-btn,.secondary-btn,.outline-btn{display:inline-flex;align-items:center;
  justify-content:center;gap:10px;min-height:48px;
  padding:11px 20px;border-radius:var(--radius-sm);
  font-family:var(--font-body);font-weight:var(--fw-medium);font-size:.9375rem;
  line-height:1.5;text-decoration:none;border:1px solid transparent;
  cursor:pointer;transition:background .2s,border-color .2s,color .2s}
.primary-btn,.secondary-btn{background:var(--btn-bg);color:var(--btn-text)}
.primary-btn:hover,.secondary-btn:hover{background:var(--accent-strong);
  color:var(--btn-text)}
.outline-btn{background:transparent;color:var(--btn2-text);
  border-color:var(--border)}
.outline-btn:hover{border-color:var(--accent);color:var(--accent)}

/* The bee and cell sprites the old hero painted behind its buttons. They are
   empty spans; with no background image they would collapse to nothing
   anyway. Hidden explicitly so the next person can see they were considered
   rather than missed — the hex lattice is the hero's pattern now. */
.btn-bee,.btn-cell{display:none}

/* Dropping style.css's blanket `a{text-decoration:none !important}` is right —
   it was suppressing the underline on every link on the page, including body
   copy — but it leaves the service cards' read-more links on the browser
   default. They arrive in a placeholder default as a bare <a> inside
   .card-link, so nothing in the redesign had ever styled them; the reset was
   doing it by accident. Given .case-link's treatment, so the two kinds of card
   on the home page use one link language. */
.card-link a{color:var(--accent);font-weight:var(--fw-medium);
  font-size:.9375rem;text-decoration:none;
  border-bottom:1px solid currentColor;padding-bottom:2px}
.card-link a:hover{color:var(--accent-strong)}

/* That border is an underline for a TEXT link, and some of these cards hold an
   arrow icon instead of the words. An inline box takes its height from the
   line, not from a replaced child, so the anchor around a 53x52 icon was still
   22px tall and the border landed across the middle of the artwork rather than
   under it -- a line through the button.

   inline-flex makes the box wrap the icon, which also gives the link a real
   53x52 hit area instead of a 22px strip. */
.card-link a:has(img){
  border-bottom:0; padding-bottom:0;
  display:inline-flex; align-items:center;
}

/* ── The project collage ───────────────────────────────────────────────────
   Template markup (home.html), geometry only, and the one place a measurement
   can be read straight off the artwork: 171/522 and 197/791 is one cell as a
   fraction of the sheet, and the background-size is its inverse so that one
   cell frames one photo.

   Nothing here hides a cell. The entrance is "Flip, tuned" in redesign.js,
   driven by Web Animations, which supplies its own start state and leaves no
   trace -- so a cell is visible whether or not that file ever runs. The
   earlier note here said main.js applies a per-column drift; main.js has not
   shipped on these pages since item 42. */
.hex-collage{position:relative;display:block;width:100%;max-width:522px;
  aspect-ratio:522 / 791;margin-inline-start:auto}
/* The hero stacks below 860px with the visual first, and section 5 caps that
   visual at min(150px,42%) so it stays an emblem rather than a cover image —
   but it caps `.hero-mark`, and the page that ships puts `.hex-collage` in
   that slot. Uncapped, a 522x791 collage at 390px wide is 543px tall and put
   the h1 at 696px of an 800px viewport: headline on the fold line, intro and
   both CTAs below it. That is the defect BACKLOG.md 19 closed on the old
   template, reopened by the new one. Same cap, applied to the element that is
   actually there; the collage is a ten-photo cluster rather than a single
   mark, so it gets more room than the 150px the mark is held to. */
@media (max-width:860px){
  .hex-collage{max-width:min(210px,56%);margin-inline:auto}
}
.hex-cell{position:absolute;left:var(--x);top:var(--y);
  width:32.7586%;height:24.9052%;
  background-image:var(--sheet);background-size:305.2632% 401.5228%;
  background-position:var(--bgx) var(--bgy);background-repeat:no-repeat;
  clip-path:polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%);
  backface-visibility:hidden}

/* ── Contact form fields — cms/includes/contact_form_fields.html ───────────
   The include is shared with the old design and its markup is load-bearing for
   cache safety, so it cannot be restyled by editing it (BACKLOG.md 28). Most
   of it is already covered by the CONTACT FORM section above; these are the
   parts that were still coming from style.css.

   The required-field asterisk was a hardcoded #ff9b9b — a pink that exists in
   style.css as --danger-text and reads as decoration on white. It now uses the
   same --danger the field errors use, so "required" and "this field is wrong"
   speak with one voice. */
.contact-form label span{color:var(--danger)}
.contact-form-button{margin-top:4px}
@media (max-width:560px){.contact-form-button .secondary-btn{width:100%}}

/* The subhead inside the form, and the plugin-authored contact block on the
   home page, both use .title with a hexagon bullet. .hex-icon is the old
   design's name for that bullet — unrelated to .svc-mark, which is the
   redesign's own hexagon line-work and was renamed precisely because the two
   collided. */
.title{display:flex;align-items:center;gap:10px}
.hex-icon{width:20px;height:20px;flex:none;background:var(--accent);
  clip-path:polygon(25% 6.7%,75% 6.7%,100% 50%,75% 93.3%,25% 93.3%,0% 50%);
  transform:rotateZ(90deg)}
/* p{margin:0} is the page-wide reset, which is right for copy inside a card
   but left the apply form welded to the sentence introducing it -- measured at
   0px between them. These two paragraphs introduce a form rather than sit in a
   block of prose, so they carry their own space. */
.job-form .content-wrap > p,
.contact-container > .contact-subtext{margin-bottom:clamp(20px,3vw,30px)}

.contact-subhead{text-align:start;margin-bottom:18px}
.contact-subhead h3{font-family:var(--font-display);font-size:var(--fs-h3);
  font-weight:var(--fw-semibold);color:var(--text);line-height:1.3}
.contact-subhead p{margin-top:8px;font-size:.9375rem;color:var(--text-muted)}

/* Editor-authored contact markup on the home page: the footer_contact_form
   slot holds a plugin whose HTML is the old design's, down to a width:85% and
   centred margins sized for a full-width section rather than for one cell of
   .contact-grid. Reset to fill the cell it is actually in. */
.contact-container{width:100%;margin-inline:0;text-align:start}
.contact-container h2{font-size:var(--fs-h2);letter-spacing:-.015em;
  line-height:1.18;margin-bottom:10px;color:var(--text)}
.contact-subtext{color:var(--text-muted);font-size:.9375rem;margin-bottom:22px}

/* mobile-only / desktop-only come from the shared includes, and the only thing
   they switch is the contact form's subhead. So they follow the width that
   form actually folds at -- .contact-grid goes single-column at 860px -- not
   style.css's 768px and not the navigation's breakpoint, which is a different
   question about a different element. */
.desktop-only{display:block}
.mobile-only{display:none}
@media (max-width:860px){
  .desktop-only{display:none}
  .mobile-only{display:block}
}

/* ── Floating social widget — the parts the port left behind ───────────────
   The widget was restyled onto the tokens in the FLOATING SOCIAL SPEED-DIAL
   section above, but four things stayed in style.css and would have gone with
   it. Three of them are not cosmetic:

   * the bottom offset clears Google's reCAPTCHA v3 badge, which is pinned to
     the bottom corner at 60px tall on every page because the footer form loads
     it site-wide. Covering it hides a notice Google's terms require to stay
     visible, and makes it unclickable;
   * the closed menu is invisible but still laid out, about 250px tall above
     the toggle, so without pointer-events:none it sits over the page and
     swallows every click in that column;
   * nine links stack past the top of a short or landscape viewport, so the
     stack scrolls. The padding/negative-margin pair buys room for the items'
     drop-shadows, which overflow-y would otherwise clip, without moving
     anything.

   The fourth is the breathing pulse, which is most of the reason the widget is
   noticed at all. */
.sfloat{bottom:calc(var(--corner-bottom) + var(--corner-badge));
  padding-bottom:env(safe-area-inset-bottom,0px)}
.sfloat__toggle{filter:drop-shadow(0 10px 22px rgba(1,35,43,.32));
  animation:sfloatBreathe 6s ease-in-out infinite;transition:transform .35s ease}
.sfloat__toggle:hover{transform:translateY(-3px)}
.sfloat__toggle:hover,.sfloat__toggle:focus-visible,
.sfloat.is-open .sfloat__toggle{
  /* One cue at a time: the breathing stops the moment the widget has the
     visitor's attention, and does not come back. */
  animation:none}
.sfloat__item{filter:drop-shadow(0 6px 16px rgba(1,35,43,.26))}
.sfloat__item:hover{transform:translateY(-2px) scale(1.06)}
.sfloat__menu{max-height:calc(100vh - 258px);max-height:calc(100dvh - 258px);
  overflow-y:auto;pointer-events:none;overscroll-behavior:contain;
  scrollbar-width:none;padding:14px 16px;margin:-14px -16px}
.sfloat__menu::-webkit-scrollbar{display:none}
.sfloat.is-open .sfloat__menu{pointer-events:auto}
@keyframes sfloatBreathe{
  0%,80%,100%{transform:none}
  87%{transform:scale(1.08)}
  94%{transform:scale(.97)}
}
@media (prefers-reduced-motion:reduce){
  .sfloat__toggle{animation:none}
  .sfloat,.sfloat__toggle,.sfloat__ico,.sfloat__li,.sfloat__item,
  .sfloat__toggle::before,.sfloat__item::before{transition:none}
}

/* ── ASSISTANT ─────────────────────────────────────────────────────────────
   The floating sales router. Markup: cms/partials/assistant.html. Behaviour:
   js/assistant.js. It sits above the social speed-dial and uses the corner
   tokens declared beside .sfloat above, so neither widget hardcodes the
   other's height.

   z-index:61 — it only has to beat .sfloat's 60. It cannot beat the reCAPTCHA
   badge's 2000000000 and must not try: see --corner-badge. */
.bl-assist{position:fixed;inset-inline-end:var(--corner-inline);
  bottom:calc(var(--corner-bottom) + var(--corner-badge));z-index:61;
  display:flex;flex-direction:column;align-items:flex-end;gap:10px;
  padding-bottom:env(safe-area-inset-bottom,0px)}

/* BESIDE the social widget, not above it.

   It was above: bottom + badge + one item + gap, which cleared the social
   TOGGLE exactly. That was measured against the wrong thing. The social menu
   opens UPWARD from its toggle (.sfloat__menu is column-reverse with order:-1),
   so the space one item above the toggle is precisely where its first link
   appears -- and with .bl-assist at z-index 61 over .sfloat's 60, this launcher
   painted on top of it. On beelogico.com, where WhatsApp is the only social
   link configured, that covered the entire menu: opening it did nothing
   visible at all.

   Sideways instead of taller, because the collision is with a stack whose
   height is not knowable here. The menu holds one link or nine depending on
   what an editor has filled in, so any vertical offset is a guess that is
   wrong for every other count. Offset by one item and a gap along the inline
   axis and the two never share space, whatever the menu grows to.

   Not solved by hiding this launcher while the menu is open, which was the
   first idea and is a trap: the menu opens on hover under (pointer: fine) and
   its open box covers this position, so a mouse travelling up to reach the
   assistant would keep the menu open and the assistant hidden the whole way.
   The control would be unreachable by mouse rather than merely obscured.

   The class is server-rendered from the same conditions social_float.html
   tests, so it is identical for every visitor and safe in a cached page. */
.bl-assist--beside{inset-inline-end:calc(var(--corner-inline)
  + var(--corner-item) + var(--corner-gap))}

/* The launcher steps aside; the panel does not have to. Pulled back to the
   corner so it still opens against the edge of the screen rather than leaving
   a gap the width of the social widget down its trailing side. */
.bl-assist--beside .bl-assist__panel{
  margin-inline-end:calc(-1 * (var(--corner-item) + var(--corner-gap)))}

/* Scoped, not global. The steps rely on the `hidden` attribute rather than a
   class, because it removes an inactive step from the accessibility tree AND
   the tab order in one move — but `display` from any later rule would beat it,
   and .bl-assist__step is a flex container. !important inside this widget
   only, so nothing else on the site inherits the hammer. */
.bl-assist [hidden]{display:none !important}

.bl-assist__toggle{position:relative;display:grid;place-items:center;
  width:50px;height:56px;border:0;padding:0;background:transparent;
  cursor:pointer;color:var(--btn-text);
  filter:drop-shadow(0 10px 22px rgba(1,35,43,.32));
  transition:transform .35s ease}
/* Painted by ::before so the control itself stays unclipped and its focus ring
   is still visible — a clip-path on the element would cut the ring off. Same
   reasoning, same polygon, as .sfloat__toggle. */
.bl-assist__toggle::before{content:"";position:absolute;inset:0;
  background:var(--accent);transition:background .2s;
  clip-path:polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%)}
.bl-assist__toggle:hover::before{background:var(--accent-strong)}
.bl-assist__toggle:hover{transform:translateY(-3px)}
.bl-assist__toggle svg{position:relative}
/* The bot face. currentColor is --btn-text, the ink that already reads on
   --accent in both themes; the eyes take --accent back so they look lit. */
.bl-assist__eye{fill:var(--accent)}
.bl-assist__bot .bl-assist__eye{transform-box:fill-box;transform-origin:center;
  animation:blAssistBlink 5s infinite}
@keyframes blAssistBlink{0%,92%,100%{transform:none}95%{transform:scaleY(.1)}}
/* A gentle bob while closed, so the button reads as someone waiting rather
   than as a static icon. Stops while open and under reduced motion. */
.bl-assist:not(.is-open) .bl-assist__toggle{animation:blAssistBob 3.5s ease-in-out infinite}
.bl-assist:not(.is-open) .bl-assist__toggle:hover{animation:none}
@keyframes blAssistBob{0%,100%{transform:none}50%{transform:translateY(-4px)}}
.bl-assist__online{position:absolute;top:5px;inset-inline-end:1px;width:12px;height:12px;
  border-radius:50%;background:#3DDC97;border:2px solid var(--bg)}

/* The countdown ring, traced round the hexagon while the panel is auto-open.
   stroke-dasharray/--len are set from getTotalLength() in assistant.js, so the
   path can change without anyone recomputing a magic number. */
/* .bl-assist__toggle in the selector to outrank `.bl-assist__toggle svg` above,
   which would otherwise put the ring back in the grid beside the face. */
.bl-assist__toggle .bl-assist__ring{position:absolute;inset:0;width:100%;height:100%;overflow:visible;
  pointer-events:none}
.bl-assist__ring path{fill:none;stroke:var(--accent);stroke-width:2.5;
  stroke-linejoin:round;opacity:0}
.bl-assist.is-peeking .bl-assist__ring path{opacity:1;
  animation:blAssistTrace var(--assist-peek,3s) linear forwards}
.bl-assist.is-peek-paused .bl-assist__ring path,
.bl-assist.is-peek-paused .bl-assist__peek-fill{animation-play-state:paused}
@keyframes blAssistTrace{from{stroke-dashoffset:0}to{stroke-dashoffset:var(--len,0)}}

@media (max-width:560px){.bl-assist__toggle{width:46px;height:52px}
  .bl-assist__bot{width:27px;height:27px}}

.bl-assist__panel{order:-1;
  width:min(360px,calc(100vw - (var(--corner-inline) * 2)));
  max-height:min(72vh,560px);overflow-y:auto;overscroll-behavior:contain;
  background:var(--surface);color:var(--text);
  border:1px solid var(--border);border-radius:var(--radius);
  box-shadow:var(--shadow);padding:16px}

/* A centred bot card: avatar, name, status. The avatar sits in the flow
   rather than riding over the panel's top edge, because the panel scrolls
   (overflow-y:auto) and would clip anything positioned outside it. */
.bl-assist__head{position:relative;display:flex;flex-direction:column;
  align-items:center;gap:2px;text-align:center;
  margin:-16px -16px 14px;padding:16px 40px 12px;
  border-radius:var(--radius) var(--radius) 0 0;
  background:linear-gradient(180deg,var(--bg-alt),var(--surface));
  border-bottom:1px solid var(--border)}
.bl-assist__avatar{display:grid;place-items:center;width:52px;height:58px;
  margin-bottom:6px;color:var(--btn-text);background:var(--accent);
  clip-path:polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%)}
.bl-assist__title{font-family:var(--font-display);font-weight:var(--fw-semibold);
  font-size:1rem}
.bl-assist__status{display:inline-flex;align-items:center;gap:6px;
  font-size:.78rem;color:var(--text-muted)}
.bl-assist__status::before{content:"";width:7px;height:7px;border-radius:50%;
  background:#3DDC97}
.bl-assist__hello{font-weight:var(--fw-medium);margin:0 0 2px}
.bl-assist__close{position:absolute;top:8px;inset-inline-end:8px;
  display:grid;place-items:center;width:28px;height:28px;
  border:0;border-radius:var(--radius-sm);background:transparent;
  color:var(--text-muted);cursor:pointer;transition:background .2s,color .2s}
.bl-assist__close:hover{background:var(--bg-alt);color:var(--text)}

.bl-assist__q{font-weight:var(--fw-medium);font-size:var(--fs-small);
  margin:0 0 10px}
.bl-assist__choices,.bl-assist__actions{display:flex;flex-direction:column;
  gap:8px}

/* One filled action and the rest quiet, rather than a column of identical
   buttons where nothing says which route is the normal one. */
.bl-assist__choice,.bl-assist__action{display:block;width:100%;
  text-align:start;font:inherit;font-size:var(--fs-small);
  padding:11px 13px;border:1px solid var(--border);border-radius:var(--radius-sm);
  background:var(--bg);color:var(--text);text-decoration:none;cursor:pointer;
  transition:border-color .2s,background .2s,transform .12s ease}
.bl-assist__choice:hover,.bl-assist__action:hover{border-color:var(--accent);
  background:var(--bg-alt)}
.bl-assist__choice:active,.bl-assist__action:active{transform:translateY(1px)}
.bl-assist__action--wa{background:var(--accent);border-color:var(--accent);
  color:var(--btn-text)}
.bl-assist__action--wa:hover{background:var(--accent-strong);
  border-color:var(--accent-strong)}
/* The "read about it" route is the one most visitors want and the one that is
   not a handoff, so it reads as a link rather than competing as a fourth
   button. */
.bl-assist__action--read{border-color:transparent;background:transparent;
  color:var(--accent);padding-inline:2px}
.bl-assist__action--read:hover{background:transparent;text-decoration:underline}

/* A word, never an arrow glyph: "←" does not mirror under RTL, so it would
   point away from where it goes on the Arabic site. */
.bl-assist__back{margin-top:12px;border:0;background:transparent;padding:4px 2px;
  font:inherit;font-size:var(--fs-small);color:var(--text-muted);
  cursor:pointer;text-decoration:underline}
.bl-assist__back:hover{color:var(--text)}
.bl-assist__aside{margin:14px 0 0;font-size:var(--fs-small);
  color:var(--text-muted)}

.bl-assist__fieldset{border:0;margin:0;padding:0;min-width:0}
.bl-assist__fieldset legend{font-weight:var(--fw-semibold);
  font-size:var(--fs-small);padding:0;margin-bottom:8px}
.bl-assist__form .form-row{margin-bottom:10px}
.bl-assist__hours,.bl-assist__legal{margin:0 0 10px;
  font-size:.78rem;line-height:1.5;color:var(--text-muted)}
.bl-assist__legal a{color:inherit}
.bl-assist__submit{margin-top:4px}
.bl-assist__submit .secondary-btn{width:100%;justify-content:center}

/* The auto-open countdown strip at the foot of the panel. */
.bl-assist__peek{display:flex;align-items:center;gap:10px;margin-top:14px;
  font-size:.78rem;color:var(--text-muted);font-variant-numeric:tabular-nums}
.bl-assist__peek-track{flex:1;height:4px;border-radius:2px;
  background:var(--border);overflow:hidden}
.bl-assist__peek-fill{display:block;height:100%;background:var(--accent);
  transform-origin:0 50%}
[dir="rtl"] .bl-assist__peek-fill{transform-origin:100% 50%}
.bl-assist.is-peeking .bl-assist__peek-fill{
  animation:blAssistDrain var(--assist-peek,3s) linear forwards}
@keyframes blAssistDrain{from{transform:scaleX(1)}to{transform:scaleX(0)}}

/* While the panel is open the social widget below is unpointable. This is what
   fixes the hover trap: social-float.js opens that menu on mouseenter under a
   fine pointer, and the launcher sits directly above its toggle, so a mouse
   travelling up to this panel used to pop nine icons open behind it. An element
   with pointer-events:none is never sent mouseenter, so no JS coordination is
   needed — and on a phone the sheet covers it anyway. */
html.assistant-open .sfloat{pointer-events:none}

/* Below 560px two stacked widgets plus a panel do not fit a 375x667 screen, so
   the panel becomes a bottom sheet and the social widget is hidden outright
   while it is open. 85dvh rather than vh: on iOS Safari vh includes the
   retracted toolbar, so the sheet's own footer would sit under it. */
@media (max-width:560px){
  html.assistant-open .sfloat{display:none}
  .bl-assist.is-open .bl-assist__panel{position:fixed;inset:auto 0 0 0;
    width:auto;max-width:none;max-height:85dvh;border-radius:var(--radius-lg)
    var(--radius-lg) 0 0;border-inline:0;border-bottom:0;margin-inline:0;
    padding-bottom:calc(16px + env(safe-area-inset-bottom,0px))}
}

@media (prefers-reduced-motion:reduce){
  .bl-assist__toggle,.bl-assist__choice,.bl-assist__action,
  .bl-assist__close,.bl-assist__toggle::before{transition:none}
  .bl-assist__toggle:hover,.bl-assist__choice:active,
  .bl-assist__action:active{transform:none}
  .bl-assist:not(.is-open) .bl-assist__toggle,
  .bl-assist__bot .bl-assist__eye{animation:none}
}
