/* Vega Sky, theme.css. Matches the live vega-sky.com identity: light ground, Inter + Space Grotesk,
   purple → emerald gradient (#6B46C1 → #10B981), white cards with soft shadows. */
:root{
  --bg:#FFFFFF; --bg-2:#F7F5FB; --surface:#FFFFFF; --surface-2:#F4F1FA;
  --line:rgba(107,70,193,.12); --line-2:rgba(107,70,193,.25);
  --ink:#1E293B; --ink-2:#475569; --ink-3:#5B6B80;
  /* --ink-3 carries the quiet text: breadcrumbs, article bylines, captions.
     At #64748B it measured 4.76:1 on white and 4.26:1 on the --surface-2 tint,
     which is below the 4.5:1 WCAG AA asks for text this size, so every tinted
     band on the site was showing its small print under the threshold. Two
     shades darker clears it on both grounds with room to spare and is close
     enough to the old tone that nothing else in the palette had to move. */
  --accent:#6B46C1; --accent-2:#7C5CD6; --accent-3:#5B3AA8; --accent-4:#10B981;
  --grad:linear-gradient(135deg,#6B46C1,#10B981);
  --ok:#10B981; --warn:#A24A07;   /* was #B45309, exactly 4.50:1 on the tint */
  --radius:16px; --radius-s:8px;
  --shadow:0 1px 3px rgba(0,0,0,.1),0 1px 2px rgba(0,0,0,.06);
  --shadow-lg:0 10px 30px rgba(107,70,193,.12);
  --font:"Inter",system-ui,-apple-system,"Segoe UI",sans-serif;
  --font-head:"Space Grotesk","Inter",system-ui,sans-serif;
  --font-ar:"IBM Plex Sans Arabic","Inter",system-ui,sans-serif;
  --max:1180px; --measure:66ch;
  /* One vertical rhythm for the whole site. Every band's own padding is one of
     these two values, so the space a reader sees above a heading and below the
     last line of the band above it is always the same amount, whatever the band
     is made of and whatever ground it sits on. */
  --band-y:88px; --band-y-tight:56px;
}
@media (max-width:900px){:root{--band-y:64px; --band-y-tight:48px}}
*{box-sizing:border-box}
/* The raster figures are wrapped in <picture> so the browser can pick AVIF,
   WebP or the original JPEG. display:contents removes the wrapper from the box
   tree, so the <img> stays the direct child its parent's rules expect: a flex
   item inside .shot-card, a grid item where it was one before. Without this,
   every rule written as "parent > img" would quietly stop matching.

   The second line is not optional. A <source> generates no box only because
   its parent <picture> consumes it; once the picture is display:contents the
   sources escape into the parent layout, and a grid or flex container counts
   them as items. That is what pushed every side-by-side figure out of its
   column and onto a row of its own below the text. */
picture{display:contents}
picture > source{display:none}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto} *{animation:none!important;transition:none!important}}
html{background:var(--bg);
  background-image:radial-gradient(ellipse 55% 38% at 85% -5%,rgba(107,70,193,.08),transparent 60%),radial-gradient(ellipse 45% 35% at 0% 15%,rgba(16,185,129,.06),transparent 60%);
  background-attachment:fixed}
body{margin:0;background:transparent;color:var(--ink);font-family:var(--font);font-size:17px;line-height:1.7;-webkit-font-smoothing:antialiased}
html[lang="ar"] body{font-family:var(--font-ar);font-size:17.5px}
a{color:var(--accent);text-decoration:none}
a:hover{color:var(--accent-3)}
img,svg{max-width:100%;display:block}
h1,h2,h3,h4{margin:0;line-height:1.15;letter-spacing:-.015em;font-weight:700;color:var(--ink);font-family:var(--font-head);text-wrap:balance}
html[lang="ar"] h1,html[lang="ar"] h2,html[lang="ar"] h3,html[lang="ar"] h4{font-family:var(--font-ar);letter-spacing:0;line-height:1.35}
h1{font-size:clamp(2.2rem,4.8vw,3.6rem)}
h2{font-size:clamp(1.6rem,3vw,2.3rem)}
h3{font-size:1.2rem}
p{margin:0 0 1rem}
.measure{max-width:var(--measure)}
.wrap{max-width:var(--max);margin:0 auto;padding:0 24px}
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.muted{color:var(--ink-2)}
.small{font-size:.9rem}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:6px}
.skip{position:absolute;inset-inline-start:-999px;top:8px;background:var(--ink);color:#fff;padding:8px 12px;border-radius:8px;z-index:100}
.skip:focus{inset-inline-start:8px}
/* Text that exists for a screen reader and must not be drawn.
   The footer heading carried this class and the class had never been defined,
   so the company name was printed in full above the logo plate: the one place
   the name must not appear, because the logo underneath it already says it.
   clip-path rather than display:none, so it stays in the accessibility tree. */
.vh{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;border:0;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap}

/* Header */
.site-header{position:sticky;top:0;z-index:50;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);background:rgba(255,255,255,.85);border-bottom:1px solid var(--line)}
/* flex-wrap is the safety net, not the layout. Measured slack in the row at
   the desktop threshold is 46px in German with the web font and 20px with the
   fallback font, so nothing wraps in either state. It wraps only when a
   reader's own text spacing pushes the row past the container, and then the
   header grows a line instead of pushing the page sideways. Without it,
   WCAG 1.4.12 spacing scrolled the page horizontally at 1340 and 1366. */
.site-header .wrap{display:flex;flex-wrap:wrap;align-items:center;gap:18px;row-gap:8px;min-height:84px;max-width:1380px}
.logo{display:flex;flex-shrink:0;align-items:center;gap:10px;color:var(--ink);font-weight:700;letter-spacing:.02em}
.logo img{height:52px;width:auto}
@media (max-width:900px){.logo img{height:44px}}
/* The desktop row.

   `gap` is a real number rather than 0, and the CTA gets its own margin on
   top of it, because two interactive controls that touch read as one control.
   v51 set gap:0 at desktop widths and pressed the consultation button against
   the Contact link; tests/browser.js now measures the rectangles of Contact,
   the CTA and the language switcher at every tested width and fails if any
   pair has a gap below 8px or intersects at all. */
.nav{display:flex;align-items:center;gap:4px;margin-inline-start:auto}
.nav a{padding:8px 12px;border-radius:8px;color:var(--ink-2);font-weight:500;font-size:.95rem}
/* :not(.btn) is load-bearing. The header CTA is an <a class="btn btn-primary">
   inside .nav, and .nav a:hover scores (0,2,1) against .btn-primary's (0,1,0),
   so on hover this rule replaced the button's gradient with a 6% purple tint
   while a later rule held the label at white. White text on a near-white
   ground: the button did not change colour, it disappeared. The same applies
   to the current-page treatment, which would otherwise draw an underline
   across the bottom of a filled button. */
.nav a:not(.btn):hover,.nav a:not(.btn)[aria-current="page"],.nav a:not(.btn)[data-section="true"]{background:rgba(107,70,193,.06);color:var(--accent)}
.nav a:not(.btn)[aria-current="page"]{box-shadow:inset 0 -2px 0 var(--accent)}

/* ---- the Practices disclosure ----

   A button and a panel, not a link and a hover. Practices used to be a link to
   services.html, so a click navigated away before any list appeared: the five
   services were one page further than they needed to be, and on a touch screen
   there was no hover to reveal anything at all.

   The panel ships in the markup and ships open, with the button marked
   aria-expanded="true". A browser that never ran the script therefore shows the
   five links as an ordinary list rather than hiding them behind a button
   nothing can open; site.js collapses it on load and takes over from there.
   That is why there is no `display:none` in the default state here. */
.nav-group{position:relative;display:flex;align-items:center}
.nav-toggle{display:inline-flex;align-items:center;gap:6px;background:none;border:0;padding:8px 12px;border-radius:8px;
  font:inherit;font-weight:500;font-size:.95rem;color:var(--ink-2);cursor:pointer}
.nav-toggle:hover,.nav-toggle[aria-expanded="true"],.nav-toggle[data-section="true"]{background:rgba(107,70,193,.06);color:var(--accent)}
.nav-toggle[data-section="true"]{box-shadow:inset 0 -2px 0 var(--accent)}
.nav-caret{display:inline-flex;width:14px;height:14px;transition:transform .18s ease}
.nav-caret svg{width:100%;height:100%}
.nav-toggle[aria-expanded="true"] .nav-caret{transform:rotate(180deg)}

/* Enhanced state: the script adds .nav-js to the root, which is what switches
   the panel from an always-visible list to a real disclosure. Without the
   script the rules below never apply. */
.nav-js .nav-panel{display:none;position:absolute;top:calc(100% + 10px);z-index:70;min-width:268px;max-width:min(340px,calc(100vw - 32px));
  flex-direction:column;gap:2px;padding:10px;background:#fff;border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow-lg)}
.nav-js .nav-group[data-open] .nav-panel{display:flex}
/* Anchored to the start edge of the button, which is the left edge in English
   and the right edge in Arabic, and clamped so the panel cannot leave the
   viewport in either direction. The German labels are the longest and this is
   the width that holds them. */
.nav-js .nav-panel{inset-inline-start:0}
.nav-js .nav-panel a{display:block;padding:9px 12px;border-radius:8px;font-size:.92rem;line-height:1.45;color:var(--ink-2);font-weight:500}
.nav-js .nav-panel a:hover,.nav-js .nav-panel a[aria-current="page"]{background:rgba(107,70,193,.07);color:var(--accent)}
.nav-js .nav-panel a[aria-current="page"]{font-weight:650}
/* The overview link sits above the five and is separated from them, so it reads
   as "or see them all" rather than as a sixth practice. */
.nav-js .nav-panel .nav-panel-all{color:var(--ink-3);font-size:.85rem;padding-bottom:12px;margin-bottom:6px;border-bottom:1px solid var(--line)}
.nav-js .nav-panel .nav-panel-all:hover{color:var(--accent)}
/* ---------- the second level, under Managed IT Services ----------

   Two controls on one row: the overview link, and a separate button that
   expands the nine pages. They are siblings rather than one element doing both
   jobs, because a control that navigates and expands cannot be operated with a
   keyboard: there is no gesture for "open this" as distinct from "go here".

   The panel opens to the side on a desktop, and inline as an accordion in the
   compact menu, which is the section further down. */
.nav-sub{position:relative}
.nav-sub-row{display:flex;align-items:center;gap:2px}
.nav-js .nav-panel .nav-sub-row .nav-sub-link{flex:1 1 auto;min-width:0}
.nav-sub-toggle{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;padding:0;background:none;border:0;border-radius:8px;color:var(--ink-3);cursor:pointer}
.nav-sub-toggle:hover{background:rgba(107,70,193,.07);color:var(--accent)}
.nav-sub-toggle .nav-caret{transition:transform .18s ease}
.nav-sub-toggle[aria-expanded="true"]{background:rgba(107,70,193,.07);color:var(--accent)}
.nav-sub-toggle[aria-expanded="true"] .nav-caret{transform:rotate(180deg)}
.nav-js .nav-panel a.is-section{color:var(--accent)}

/* Before the script runs, the nine links are simply part of the list: a
   browser with no JavaScript gets a longer menu rather than a button that
   opens nothing. The script hides them and reveals the toggle. */
.nav-subpanel{display:block}
.nav-js .nav-subpanel{display:none}
.nav-js .nav-sub[data-open] > .nav-subpanel{display:block}

@media (min-width:1380px){
  /* A flyout to the side, on the side with room. The direction is set by the
     script from a measurement of the viewport edge, not from the writing
     direction alone, so a menu near the right edge of a wide screen opens
     leftwards instead of pushing the page sideways. */
  .nav-js .nav-subpanel{position:absolute;top:-8px;z-index:80;min-width:262px;
    max-width:min(320px,calc(100vw - 32px));background:#fff;border:1px solid var(--line);
    border-radius:12px;box-shadow:var(--shadow-lg);padding:8px;
    max-height:min(70vh,520px);overflow-y:auto;overscroll-behavior:contain}
  .nav-js .nav-sub[data-side="end"] > .nav-subpanel{inset-inline-start:calc(100% + 6px)}
  .nav-js .nav-sub[data-side="start"] > .nav-subpanel{inset-inline-end:calc(100% + 6px)}
  /* The bridge. Without it the pointer crosses six pixels of nothing on its
     way from the parent row to the flyout, the parent loses hover, and the
     panel closes under the cursor. */
  .nav-js .nav-sub[data-open]::after{content:"";position:absolute;top:0;bottom:0;width:10px;
    inset-inline-start:100%}
  .nav-js .nav-sub[data-open][data-side="start"]::after{inset-inline-start:auto;inset-inline-end:100%}
}

.lang{display:flex;gap:2px;padding:3px;border:1px solid var(--line);border-radius:10px;background:var(--surface-2)}
.lang a{padding:6px 10px;border-radius:8px;font-size:.8rem;font-weight:600;color:var(--ink-2)}
.lang a[aria-current="true"]{background:var(--accent);color:#fff}
.menu-btn{display:none;margin-inline-start:auto;background:var(--surface-2);border:1px solid var(--line);color:var(--ink);border-radius:10px;width:44px;height:44px;cursor:pointer}
/* 1099px, not 960px.

   The header is a logo, seven links, a language switcher and a button, and
   what decides whether they fit is how long the words are. In German they are
   longest: the desktop row needs about 1120px before the button and about
   1265px with it. With the switch at 960px the full row was laid out at
   1024px, where it needs 1118, so the page scrolled sideways in all three
   languages at a width every laptop and every landscape tablet uses. The
   German row then overflowed again at 1280px once the button came back.

   So the widths below come from measuring the row in the language that needs
   the most room, with a margin, rather than from a round number. The
   thresholds and the two media queries under them are checked at 320, 390,
   768, 1024, 1280, 1360 and 1440 in all three languages by
   tests/browser.js. */
@media (max-width:1379px){
  .menu-btn{display:inline-flex;align-items:center;justify-content:center}
  .nav{display:none;position:absolute;inset-inline:0;top:76px;flex-direction:column;align-items:stretch;background:#fff;border-bottom:1px solid var(--line);box-shadow:var(--shadow-lg);padding:12px 24px 20px;gap:4px}
  .nav.open{display:flex}
  .nav a{padding:12px 10px}
  /* The CTA is a full-width primary action at the foot of the open menu, not
     a hidden element. v51 hid it here and again between 1180 and 1339, which
     meant the primary action of the site was absent at every width from a
     small laptop downwards. */
  .header-cta{display:flex;justify-content:center;width:100%;margin-top:10px;padding-block:14px}
  /* Inside the mobile column the panel is a block that pushes the rest of the
     list down, not a floating card: a dropdown overlaying a vertical menu on a
     360px screen covers the items underneath it. */
  .nav-group{display:block}
  .nav-toggle{width:100%;justify-content:space-between;padding:12px 10px}
  .nav-js .nav-panel{position:static;top:auto;min-width:0;max-width:none;border:0;border-radius:0;box-shadow:none;
    padding:2px 0 8px 14px;margin:0;background:none}
  html[dir="rtl"] .nav-js .nav-panel{padding:2px 14px 8px 0}
  .nav-js .nav-panel a{padding:11px 10px}
  /* The nested accordion. Inline, indented, scrolling inside the menu rather
     than off the bottom of a short landscape viewport. */
  .nav-js .nav-subpanel{position:static;padding-inline-start:14px;margin:0 0 4px;
    border-inline-start:2px solid var(--line);box-shadow:none;border-radius:0;background:none}
  .nav-js .nav-subpanel a{padding:10px 10px;font-size:.9rem}
  .nav-sub-toggle{width:44px;height:44px}
  /* The menu itself scrolls, and stops short of the bottom inset so the last
     item is not under a phone's home indicator. The floating WhatsApp control
     is below this in the stacking order, so an open menu covers it rather than
     the other way round. */
  .nav.open{max-height:calc(100dvh - 84px - env(safe-area-inset-bottom, 0px));
    overflow-y:auto;overscroll-behavior:contain;
    padding-bottom:calc(20px + env(safe-area-inset-bottom, 0px))}
}
/* Desktop spacing, at every desktop width rather than a band of them.

   The row is a logo, eight links, a language switcher and a button, and adding
   Home took it past its budget: in German it overflowed at 1920 as well as at
   1280. Banding the tighter spacing between two breakpoints only moved the
   problem to the width above the band, so the tighter spacing is the desktop
   spacing, unconditionally.

   The threshold below is measured rather than round. With the tighter spacing
   the row's own width, logo and language switcher and button included, is:

       language   web font   fallback font
       English      1279.6px   1301.9px
       Arabic       1217.6px   1278.7px
       German       1333.6px   1359.8px

   The fallback column is the one that decides it. Those numbers are what a
   visitor sees from first paint until the web font arrives, which is a real
   state on a slow connection and not a theoretical one, and German in that
   state needs 1359.8px. A 1340px threshold therefore pushed two controls
   19.8px outside the container on every German page load at every desktop
   width, and left only 6.4px of slack once the font had loaded.

   1380px is the widest of those numbers rounded up past a 20px margin. The
   cost is that a 1366px laptop now uses the compact menu, where the CTA is a
   full-width primary action rather than a hidden one, which is the right
   trade: a squeezed row with controls hanging out of it is worse than a menu.
   tests/browser.js measures all six numbers again on every build and fails if
   the margin is gone, so a longer label cannot quietly spend it. */
@media (min-width:1380px){
  /* Wrapping belongs to the desktop row and nowhere else. Set on .nav
     unconditionally it also reached the compact menu, where .nav is a flex
     *column*: a wrapping column flexes into several content-sized columns, so
     the Practices panel was laid out 118px from the left edge at its own
     max-content width and the nested list ran off the side of a 390px screen.
     A column that wraps is not the same thing as a row that wraps. */
  .nav{gap:2px;flex-wrap:wrap;justify-content:flex-end;row-gap:6px}
  /* The button's own breathing room, desktop only. Applied unconditionally it
     also reached the compact menu, where the button is width:100%: a 12px
     start margin on a full-width element put its edge 12px past the menu's
     content box, and the open menu could be swiped sideways by exactly that
     much on every phone. */
  .nav .header-cta{margin-inline-start:12px}
  .nav a:not(.btn){padding:8px 9px;font-size:.92rem}
  .nav-toggle{padding:8px 9px;font-size:.92rem}
  .lang a{padding:6px 8px}
}

/* Buttons */
.btn{display:inline-flex;align-items:center;gap:10px;padding:13px 22px;border-radius:var(--radius-s);font-weight:600;font-size:.95rem;border:1px solid transparent;cursor:pointer;transition:transform .15s,box-shadow .15s;white-space:nowrap}
.btn-primary{background:var(--grad);color:#fff;box-shadow:0 6px 18px rgba(107,70,193,.25)}
.btn-primary:hover{transform:translateY(-1px);color:#fff;box-shadow:0 10px 24px rgba(107,70,193,.32)}
.btn-ghost{background:#fff;border-color:var(--line-2);color:var(--accent)}
.btn-ghost:hover{background:var(--surface-2);color:var(--accent-3)}
.btn .arrow,.btn svg{width:18px;height:18px;flex:0 0 auto}
html[dir="rtl"] .arrow{transform:scaleX(-1)}

/* Eyebrow / pills */
/* The dot is positioned, not a flex item, and the label is ordinary inline
   content. A build pass wraps the company name in <span class="brandname">,
   and inside a flex box that span becomes its own item: "VEGA SKY · DUBAI"
   was laid out as separate columns with the flex gap between them. Any box
   that holds running text plus a marker gets the marker out of the flow. */
.eyebrow{display:inline-block;position:relative;padding:6px 14px 6px 28px;border-radius:999px;font-size:.78rem;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--accent);background:rgba(107,70,193,.06);border:1px solid rgba(107,70,193,.22)}
[dir="rtl"] .eyebrow{padding:6px 28px 6px 14px}
html[lang="ar"] .eyebrow{letter-spacing:0;font-size:.85rem}
.eyebrow::before{content:"";position:absolute;inset-inline-start:14px;top:50%;transform:translateY(-50%);width:6px;height:6px;border-radius:50%;background:var(--accent-4)}
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:.8rem;background:var(--surface-2);border:1px solid var(--line);color:var(--ink-2)}

/* Sections */
.section{padding:var(--band-y) 0}
.section-tight{padding:var(--band-y-tight) 0}
/* Stamped by the build on a section whose ground is the same as the one above
   it: the two read as a single band, so the seam between them is one unit of
   space rather than two. Where the ground changes the class is absent and the
   section opens with its full padding, which is why no heading sits against a
   coloured edge any more. */
.seam{padding-top:0}
/* The space below a band's last line has to equal the space above its first.
   A band that ends in a paragraph or an accordion kept that element's bottom
   margin, and the section's padding sat on top of it, so a band ending in prose
   closed between 10px and 34px lower than the identical band ending in a card
   grid. Nothing inside a band needs a trailing margin; the padding is the gap. */
main > section > .wrap > :last-child,
main > section > .wrap > :last-child > :last-child{margin-bottom:0}
.section-head{display:flex;flex-direction:column;align-items:flex-start;gap:14px;margin-bottom:40px;max-width:760px}
.section-head p{color:var(--ink-2);font-size:1.08rem;margin:0}
.hero{padding:96px 0 72px;position:relative;overflow:hidden;background:linear-gradient(180deg,#FBFAFE 0%,#FFFFFF 100%)}
.hero .wrap{display:grid;grid-template-columns:1.15fr .85fr;gap:48px;align-items:center}
.hero-copy{display:flex;flex-direction:column;gap:22px;align-items:flex-start}
.hero-copy p.lead{font-size:1.18rem;color:var(--ink-2);max-width:60ch;margin:0}
.hero-actions{display:flex;gap:12px;flex-wrap:wrap}
.trust{display:flex;gap:18px;flex-wrap:wrap;color:var(--ink-2);font-size:.9rem}
.trust span{display:inline-flex;align-items:center;gap:8px}
.trust svg{width:16px;height:16px;color:var(--ok)}
.hero-art{position:relative;min-height:320px;border-radius:var(--radius);border:1px solid var(--line);background:linear-gradient(150deg,rgba(107,70,193,.08),rgba(16,185,129,.06));overflow:hidden;box-shadow:var(--shadow-lg)}
.hero-art canvas{position:absolute;inset:0;width:100%;height:100%}
.hero-art .badge{position:absolute;padding:10px 14px;border-radius:10px;background:rgba(255,255,255,.92);border:1px solid var(--line-2);font-size:.85rem;font-weight:600;color:var(--ink);box-shadow:var(--shadow)}
@media (max-width:900px){.hero .wrap{grid-template-columns:1fr}.hero-art{min-height:220px}.hero{padding:64px 0 48px}}
.page-hero{padding:72px 0 64px;background:linear-gradient(180deg,#FBFAFE 0%,#FFFFFF 100%)}
.page-hero .wrap{display:flex;flex-direction:column;gap:18px;align-items:flex-start}
.page-hero p.lead{font-size:1.15rem;color:var(--ink-2);max-width:66ch;margin:0}
.breadcrumb{display:flex;gap:8px;flex-wrap:wrap;font-size:.85rem;color:var(--ink-3);list-style:none;padding:0;margin:0}
.breadcrumb li+li::before{content:"/";margin-inline-end:8px;color:var(--ink-3)}
.breadcrumb a{color:var(--ink-2)}

/* Cards & grids */
.grid{display:grid;gap:18px}
.grid-2{grid-template-columns:repeat(2,1fr)}
.grid-3{grid-template-columns:repeat(3,1fr)}
.grid-4{grid-template-columns:repeat(4,1fr)}
@media (max-width:960px){.grid-3,.grid-4{grid-template-columns:repeat(2,1fr)}}
@media (max-width:620px){.grid-2,.grid-3,.grid-4{grid-template-columns:1fr}}
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:26px;display:flex;flex-direction:column;gap:12px;box-shadow:var(--shadow);transition:box-shadow .15s,transform .15s,border-color .15s}
a.card:hover{border-color:var(--line-2);transform:translateY(-2px);box-shadow:var(--shadow-lg);color:inherit}
.card h2,.card h3{font-size:1.1rem}
.card p{color:var(--ink-2);margin:0;font-size:.97rem}
.card .icon{width:46px;height:46px;border-radius:13px;display:grid;place-items:center;background:var(--grad);border:0;color:#fff;box-shadow:0 6px 16px rgba(107,70,193,.28)}
.card .icon svg{width:22px;height:22px}
.card .more{margin-top:auto;color:var(--accent);font-weight:600;font-size:.9rem;display:inline-flex;gap:6px;align-items:center}
.card .more svg{width:16px;height:16px}
.card-accent{background:linear-gradient(150deg,rgba(107,70,193,.06),rgba(16,185,129,.05))}
.list-check{list-style:none;padding:0;margin:0;display:grid;gap:12px}
/* Same reason as .eyebrow: the tick is positioned so the line is a plain
   block of inline text, and a brand-name span inside it cannot be pulled out
   into a column of its own. */
.list-check li{position:relative;padding-inline-start:32px;color:var(--ink-2)}
.list-check li::before{content:"";position:absolute;inset-inline-start:0;top:4px;width:20px;height:20px;border-radius:50%;background:rgba(16,185,129,.12) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2310B981' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><path d='M5 13l4 4L19 7'/></svg>") center/12px no-repeat;border:1px solid rgba(16,185,129,.35)}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:40px;align-items:start}
@media (max-width:860px){.two-col{grid-template-columns:1fr}}
.split{display:grid;grid-template-columns:360px 1fr;gap:40px;align-items:start}
@media (max-width:900px){.split{grid-template-columns:1fr}}
.sticky{position:sticky;top:96px}

/* Numbers strip */
.numbers{display:grid;grid-template-columns:repeat(4,1fr);gap:18px}
@media (max-width:760px){.numbers{grid-template-columns:repeat(2,1fr)}}
.number{padding:22px;border:1px solid var(--line);border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow);text-align:center}
.number b{display:block;font-size:2.1rem;font-weight:700;font-family:var(--font-head);background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;font-variant-numeric:tabular-nums;letter-spacing:-.02em}
.number span{color:var(--ink-2);font-size:.9rem}

/* Logo strip */
.logos{display:grid;grid-template-columns:repeat(6,1fr);gap:14px}
@media (max-width:900px){.logos{grid-template-columns:repeat(3,1fr)}}
.logos .slot{height:72px;border:1.5px dashed var(--line-2);border-radius:12px;display:grid;place-items:center;color:var(--ink-3);font-size:.8rem;background:var(--surface-2)}

/* Steps */
.steps{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;counter-reset:step}
@media (max-width:900px){.steps{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.steps{grid-template-columns:1fr}}
.step{padding:24px;border:1px solid var(--line);border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow);position:relative}
.step::before{counter-increment:step;content:counter(step,decimal-leading-zero);font-size:.8rem;font-weight:700;font-family:var(--font-head);color:var(--accent-4);letter-spacing:.1em;display:block;margin-bottom:10px}
.step h3{font-size:1.02rem;margin-bottom:8px}
.step p{color:var(--ink-2);font-size:.93rem;margin:0}

/* Testimonial */
.quote{padding:32px;border-radius:var(--radius);border:1px solid var(--line);background:linear-gradient(150deg,rgba(107,70,193,.06),rgba(16,185,129,.05))}
.quote blockquote{margin:0 0 16px;font-size:1.15rem;color:var(--ink);line-height:1.6}
.quote cite{font-style:normal;color:var(--ink-2);font-size:.9rem}

/* Placeholders */
.placeholder{border:1.5px dashed rgba(180,83,9,.5);background:rgba(251,191,36,.08);border-radius:var(--radius);padding:20px 22px;color:var(--ink-2);position:relative}
.placeholder::before{content:attr(data-label);display:inline-block;font-size:.72rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--warn);margin-bottom:8px}
html[lang="ar"] .placeholder::before{letter-spacing:0;font-size:.8rem}
.placeholder .avatar{width:96px;height:96px;border-radius:50%;background:var(--surface-2);border:1.5px dashed var(--line-2);margin-bottom:12px}
.placeholder .photo{aspect-ratio:16/9;border-radius:12px;background:var(--surface-2);border:1.5px dashed var(--line-2);display:grid;place-items:center;color:var(--ink-3);font-size:.85rem}
.placeholder-inline{display:inline-block;padding:2px 8px;border-radius:6px;border:1.5px dashed rgba(180,83,9,.5);background:rgba(251,191,36,.1);color:var(--warn);font-size:.85em;font-weight:600}

/* Tables */
.tbl{overflow-x:auto;border:1px solid var(--line);border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow)}
table{border-collapse:collapse;width:100%;font-size:.95rem}
th,td{padding:14px 16px;text-align:start;vertical-align:top;border-bottom:1px solid var(--line)}
th{font-size:.78rem;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);background:var(--surface-2)}
html[lang="ar"] th{letter-spacing:0;font-size:.85rem}
tr:last-child td{border-bottom:0}
td{color:var(--ink-2)} td strong{color:var(--ink)}

/* FAQ */
.faq details{border:1px solid var(--line);border-radius:14px;background:var(--surface);box-shadow:var(--shadow);padding:0 22px;margin-bottom:10px}
/* The question sits in its own <span> so the flex row has exactly two items,
   the question and the +. A build pass wraps the company name in
   <span class="brandname">, which turned "What exactly does Vega Sky do?" into
   three separate flex items: space-between then spread them across the whole
   width with 16px gaps, and the question read as three columns. A flex
   container must never hold bare text alongside an element. */
.faq summary{cursor:pointer;padding:18px 0;font-weight:600;color:var(--ink);list-style:none;display:flex;justify-content:space-between;gap:16px;align-items:center}
.faq summary > span{flex:1 1 auto;min-width:0}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{content:"+";font-size:1.4rem;color:var(--accent);flex:0 0 auto}
.faq details[open] summary::after{content:"–"}
.faq details p{color:var(--ink-2);padding-bottom:18px;margin:0}

/* Prose */
.prose{max-width:var(--measure)}
.prose h2{font-size:1.45rem;margin:40px 0 12px}
.prose h3{font-size:1.1rem;margin:26px 0 8px}
.prose p,.prose li{color:var(--ink-2)}
.prose ul,.prose ol{padding-inline-start:22px}
.prose li{margin-bottom:6px}
.prose strong{color:var(--ink)}
.prose hr{border:0;border-top:1px solid var(--line);margin:32px 0}
.article-meta{display:flex;gap:14px;flex-wrap:wrap;color:var(--ink-3);font-size:.88rem}

/* Forms */
.form{display:grid;gap:16px}
/* minmax(0,...) and min-width:0, for the usual reason and one specific to
   forms: an <input> has an intrinsic width of about twenty characters, so a
   grid column of 1fr never shrinks below roughly 180px however narrow its
   container is. At a 180px viewport, which is a 360px phone at 200% browser
   zoom, that floor pushed the page sideways by itself. */
.form .row{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:16px}
@media (max-width:620px){.form .row{grid-template-columns:minmax(0,1fr)}}
.field{display:flex;flex-direction:column;gap:6px;min-width:0}
.field label{font-size:.88rem;font-weight:600;color:var(--ink)}
.field input,.field select,.field textarea{width:100%;min-width:0;background:#fff;border:1px solid var(--line-2);border-radius:10px;padding:12px 14px;color:var(--ink);font:inherit;font-size:.97rem}
/* appearance:none removes the browser's own dropdown arrow AND the width it
   reserves for one, so the replacement arrow drawn as a background image sits
   on top of the option text instead of beside it. The longest country name was
   running under the chevron with nothing in the layout to stop it. The padding
   on the arrow side is what reserves that space, and because it is padding the
   browser counts it in the select's intrinsic width, so a select sized to its
   content is sized to its content plus room for the arrow. */
.field select{appearance:none;padding-inline-end:38px;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748B' stroke-width='2'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat:no-repeat;background-position:right 12px center;background-size:16px}
html[dir="rtl"] .field select{background-position:left 12px center}
.field textarea{min-height:140px;resize:vertical}
.field input:focus,.field select:focus,.field textarea:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(107,70,193,.15)}
.field .err{display:none;color:#DC2626;font-size:.82rem}
.field.invalid .err{display:block}
.field.invalid input,.field.invalid textarea,.field.invalid select{border-color:#DC2626}
.consent{display:flex;gap:10px;align-items:flex-start;font-size:.88rem;color:var(--ink-2);font-weight:400!important}
.consent input{width:auto!important;flex:0 0 auto;margin-top:5px}
.form-status{display:none;padding:14px 16px;border-radius:10px;font-weight:600}
.form-status.ok{display:block;background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.4);color:#047857}
.form-status.fail{display:block;background:rgba(220,38,38,.08);border:1px solid rgba(220,38,38,.35);color:#B91C1C}
.hp{position:absolute;left:-9999px;opacity:0;height:0;width:0}
/* justify-self:start takes the button out of the grid's stretch. width is then
   stated rather than left to the grid: max-content is what the label and the
   arrow need in whichever language they are in, and max-width keeps it inside
   the form on a narrow screen. (A scrollWidth-based overflow check reports
   this button as overflowing on every Arabic page. It is not: the rendered
   content is 120px inside a 122px content box. scrollWidth is not dependable
   on an RTL flex container, which is why the layout checks in
   tests/browser.js compare child rectangles against the content box instead.) */
.form .btn{justify-self:start;width:max-content;max-width:100%}

/* Office cards & map */
.office{display:flex;flex-direction:column;gap:10px}
.office .rows{display:grid;gap:8px;color:var(--ink-2);font-size:.95rem}
.office .rows a{color:var(--ink)}
/* An address and its label share a line, and both are unbreakable words. At a
   180px viewport, which is what a 360px phone gives at 200% browser zoom,
   "info@vega-sky.com · new inquiries" needed 209px and pushed the page 76px
   sideways. Letting the row wrap and the address break at a character costs
   nothing at any width where it already fitted. */
.office .rows span{display:flex;gap:10px;align-items:flex-start;flex-wrap:wrap;min-width:0}
.office .rows span a,.office .rows span span{overflow-wrap:anywhere;min-width:0}
.office .rows svg{width:18px;height:18px;flex:0 0 18px;margin-top:4px;color:var(--accent)}
.map{aspect-ratio:16/9;border:0;border-radius:12px;width:100%;background:var(--surface-2)}
.wa{background:#15803D!important;color:#fff!important;box-shadow:0 6px 18px rgba(31,157,85,.25)!important}

/* CTA band */
.cta-band{border-radius:24px;padding:48px;background:var(--grad);color:#fff;display:flex;justify-content:space-between;align-items:center;gap:32px;flex-wrap:wrap;box-shadow:var(--shadow-lg)}
.cta-band h2{font-size:1.7rem;margin-bottom:6px;color:#fff}
.cta-band p{color:rgba(255,255,255,.85);margin:0}
.cta-band .btn-primary{background:#fff;color:var(--accent);box-shadow:0 6px 18px rgba(0,0,0,.15)}
.cta-band .btn-primary:hover{color:var(--accent-3)}
.cta-band .btn-ghost{background:transparent;border-color:rgba(255,255,255,.6);color:#fff}
.cta-band .btn-ghost:hover{background:rgba(255,255,255,.12);color:#fff}

/* Footer, gradient like the live site */
.site-footer{padding:64px 0 28px;margin-top:64px;background:var(--grad);color:#fff}
.footer-grid{display:grid;grid-template-columns:1.4fr 1fr 1fr 1fr;gap:32px}
@media (max-width:960px){.footer-grid{grid-template-columns:1fr 1fr}}
@media (max-width:560px){.footer-grid{grid-template-columns:1fr}}
.site-footer h3,.site-footer h4{font-size:.8rem;letter-spacing:.12em;text-transform:uppercase;color:#fff;margin-bottom:14px;font-family:var(--font-head)}
html[lang="ar"] .site-footer h3,html[lang="ar"] .site-footer h4{letter-spacing:0;font-size:.9rem}
.site-footer ul{list-style:none;margin:0;padding:0;display:grid;gap:8px}
.site-footer a{color:rgba(255,255,255,.85);font-size:.93rem}
.site-footer a:hover{color:#fff}
.site-footer p{color:rgba(255,255,255,.85);font-size:.93rem}
.site-footer .logo{color:#fff}
.site-footer .placeholder-inline{border-color:rgba(255,255,255,.5);background:rgba(255,255,255,.12);color:#fff}
.social{display:flex;gap:10px;margin-top:16px}
.social a{width:38px;height:38px;border-radius:10px;border:1px solid rgba(255,255,255,.4);display:grid;place-items:center;color:#fff}
.social a:hover{border-color:#fff;background:rgba(255,255,255,.12)}
.social svg{width:18px;height:18px}
.footer-bottom{margin-top:40px;padding-top:20px;border-top:1px solid rgba(255,255,255,.25);display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;color:rgba(255,255,255,.8);font-size:.85rem}
.footer-bottom ul{display:flex;gap:18px;list-style:none;margin:0;padding:0}

/* Cookie notice */
.cookie{position:fixed;inset-inline:16px;bottom:16px;z-index:60;max-width:560px;margin-inline-start:auto;background:#fff;border:1px solid var(--line-2);border-radius:16px;padding:18px 20px;display:none;gap:14px;align-items:center;flex-wrap:wrap;box-shadow:0 20px 50px rgba(30,41,59,.2)}
.cookie.show{display:flex}
/* A button that reads as a link. It is a <button> and not an <a> because it
   performs an action on this page rather than going anywhere, and a link that
   goes nowhere is a link a keyboard user cannot tell from one that does. */
.link-btn{appearance:none;background:none;border:0;padding:0;font:inherit;color:inherit;cursor:pointer;text-align:inherit;text-decoration:none}
.link-btn:hover{text-decoration:underline}

/* ---- tables in the legal pages ----

   The privacy policy states what each form field is for, and what is kept for
   how long, as tables, because both are lists of three things and prose made
   them unreadable. On a phone a table has to be able to scroll sideways inside
   its own box: the alternative is either a page that scrolls sideways or cells
   squeezed to one word per line. */
.legal-table{width:100%;border-collapse:collapse;margin:18px 0;font-size:.94rem;display:block;overflow-x:auto}
.legal-table thead th{text-align:start;font-weight:650;color:var(--ink);background:var(--surface-2);
  border-bottom:1px solid var(--line-2);padding:10px 12px;white-space:nowrap}
.legal-table th{text-align:start;font-weight:650;color:var(--ink);vertical-align:top;padding:10px 12px}
.legal-table td{padding:10px 12px;vertical-align:top;color:var(--ink-2);line-height:1.6}
.legal-table tbody tr+tr th,.legal-table tbody tr+tr td{border-top:1px solid var(--line)}
/* The identification table in the legal notice is a two-column list of
   labelled facts rather than a grid, so its first column is the label. */
.legal-table tbody th{width:1%;white-space:nowrap;padding-inline-end:20px}
@media (max-width:620px){
  .legal-table tbody th{white-space:normal;width:auto}
  .legal-table{font-size:.9rem}
}
.legal-table code{font-size:.88em;background:var(--surface-2);padding:1px 5px;border-radius:4px}
/* Where a picture came from, said quietly but said. A schematic drawn for a
   page and a document handed to a client are different things, and a reader
   looking at one should not have to guess which. */
/* No opacity on text.

   This read opacity:.78 and axe reported it as a serious contrast failure. The
   two numbers are the whole lesson: the declared colour scores 5.52:1 against
   this background and passes, and what is actually painted scores 3.48:1 and
   fails, because opacity composites the text towards the background after the
   colour is chosen. A stylesheet reader sees the first number and a reader of
   the page sees the second.

   So the muting is a colour now, not a filter. #586273 opaque scores 5.52:1 on
   the tinted figure ground, 6.16:1 on white and 5.89:1 on the pale panel,
   which are the three grounds this element appears over, against the 4.5:1
   that normal-size text needs. contrast-audit.js computes the painted colour
   and the real WCAG threshold for each element's size and weight, so a return
   to opacity on text fails the build rather than the audit. */
.figure-provenance{display:inline-block;color:#586273;font-style:italic}
/* The label under a band of scenario figures. It has to be readable, not a
   disclaimer in grey six-point type: the whole point is that somebody who
   scrolls straight to the numbers sees what kind of numbers they are.
   #586273 on the dark band is unreadable, so the colour is set per context and
   checked by contrast-audit.js like any other text. */
/* 16px, not 12: spacing-audit.js reports any pair of block siblings closer
   than 14px, and a house caption sits at 20px under a figure. A note that the
   spacing report has to mention every run is a line a reviewer has to think
   about every run, and the band's own internal gap is 18px, so 12 read tighter
   than the thing it belongs to anyway. */
.stat-band-note{margin:16px 0 0;font-size:.88rem;color:#4A5468;font-style:italic}
.band-dark .stat-band-note{color:rgba(255,255,255,.86)}
.cookie p{margin:0;font-size:.9rem;color:var(--ink-2);flex:1 1 260px}
.cookie .btn{padding:9px 16px;font-size:.85rem}

/* Reveal

   Every rule that hides a block until it scrolls into view is scoped to
   html.reveal-ready, a class site.js sets on itself once the observer that
   un-hides them is actually running. Nothing else may hide content.

   This is not decoration. Hiding text in CSS and restoring it in JavaScript
   means every way the script can fail is a way the page can ship blank: the
   file blocked, a syntax error, an old browser, or, as happened here, an
   element that gets the class after the observer has already taken its list.
   The class arrives with the code that can remove it, so a page whose script
   never ran shows its text instead of a white rectangle. */
html.reveal-ready .reveal{opacity:0;transform:translateY(14px);transition:opacity .5s ease,transform .5s ease}
html.reveal-ready .reveal.in{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){html.reveal-ready .reveal{opacity:1;transform:none}}

/* ---- v4: stat bands, figures, use cases ---- */
.stat-band{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;margin-top:26px}
@media (max-width:900px){.stat-band{grid-template-columns:repeat(2,1fr)}}
.stat-band.stats-3{grid-template-columns:repeat(3,1fr)}
@media (max-width:700px){.stat-band.stats-3{grid-template-columns:1fr}}
.stat{position:relative;padding:22px 22px 20px;border:1px solid var(--line);border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow);overflow:hidden}
.stat::before{content:"";position:absolute;inset-inline-start:0;top:0;bottom:0;width:4px;background:var(--grad)}
.stat b{display:block;font-size:1.9rem;font-weight:700;font-family:var(--font-head);background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.stat span{display:block;margin-top:4px;color:var(--ink-2);font-size:.9rem;line-height:1.45}
.figure-img{width:100%;height:auto;border-radius:var(--radius);border:1px solid var(--line);background:var(--surface);box-shadow:var(--shadow)}
.figure-wide{display:block}
.hard-list{display:grid;gap:14px}
.hard{padding:16px 18px;border:1px solid var(--line);border-inline-start:4px solid var(--accent);border-radius:var(--radius-s);background:var(--surface);box-shadow:var(--shadow)}
.hard h4{font-family:var(--font-head);font-size:1rem;margin-bottom:4px;color:var(--ink)}
.hard p{color:var(--ink-2);font-size:.93rem}
.roles{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:26px}
@media (max-width:800px){.roles{grid-template-columns:1fr}}
.role-vega{border-top:4px solid var(--accent)}
.role-others{border-top:4px solid var(--accent-4)}
.uc-section .steps{grid-template-columns:repeat(auto-fit,minmax(210px,1fr))}
.uc-section h3{font-size:1.15rem}
.nav a{white-space:nowrap}
.site-footer .logo img{background:#fff;border-radius:10px;padding:7px 12px;box-sizing:content-box}

/* ---- v5: motion & new home sections ---- */
.card{transition:transform .35s cubic-bezier(.2,.7,.3,1),box-shadow .35s ease,border-color .35s ease}
a.card:hover{transform:translateY(-5px)}
.card .icon{transition:transform .35s cubic-bezier(.2,.7,.3,1)}
a.card:hover .icon,.card.reveal:hover .icon{transform:scale(1.08) rotate(-3deg)}
.card .more .arrow{transition:transform .3s ease}
a.card:hover .more .arrow{transform:translateX(4px)}
[dir=rtl] a.card:hover .more .arrow{transform:scaleX(-1) translateX(4px)}
.btn{transition:transform .25s ease,box-shadow .25s ease,background-color .25s ease}
.btn:hover{transform:translateY(-2px)}
.btn .arrow{transition:transform .3s ease}
.btn:hover .arrow{transform:translateX(3px)}
[dir=rtl] .btn:hover .arrow{transform:scaleX(-1) translateX(3px)}
/* The gradient in the hero headline, on the dark hero, in lighter tints.

   --grad runs #6B46C1 to #10B981, which is the brand pair and is right on a
   light page. On the dark hero it is not: the purple end scores 1.79:1
   against the darkest part of that background, and the animation walks the
   gradient across the words, so the half of the headline in gradient faded
   towards unreadable and back every seven seconds. Three at a minimum is what
   large text needs and the purple end was nowhere near it.

   These are tints of the same two hues, not different colours, so the hero
   still reads as the brand: the worst contrast anywhere along the gradient,
   against any part of the background, is 6.2:1, and it is 6.2:1 in every
   frame of the animation because both ends are light. Measured, and kept
   measured, by tests/browser.js. */
.hero-dark h1 .gradient-text{background-image:linear-gradient(135deg,#C4B5FD,#6EE7B7)}
.hero h1 .gradient-text{background-size:220% 220%;animation:vega-grad 7s ease-in-out infinite}
@keyframes vega-grad{0%,100%{background-position:0% 30%}50%{background-position:100% 70%}}
.hero-art .badge{animation:vega-float 6s ease-in-out infinite}
.hero-art .badge:nth-of-type(2){animation-delay:1.6s}
.hero-art .badge:nth-of-type(3){animation-delay:3.2s}
@keyframes vega-float{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}
.step{transition:transform .3s ease,box-shadow .3s ease}
.step:hover{transform:translateY(-3px);box-shadow:var(--shadow-lg)}
.stat{transition:transform .3s ease,box-shadow .3s ease}
.stat:hover{transform:translateY(-3px);box-shadow:var(--shadow-lg)}
.faq details p{animation:vega-fadein .35s ease}
@keyframes vega-fadein{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
.faq summary{transition:color .25s ease}
.faq details[open] summary{color:var(--accent)}
.pill{transition:transform .25s ease,border-color .25s ease}
.pill:hover{transform:translateY(-2px);border-color:var(--line-2)}
.tech-strip{display:flex;flex-wrap:wrap;gap:10px;justify-content:center}
/* minmax(0,...) rather than a bare fr on both tracks. An fr track's automatic
   minimum is its content's min-content width, which an iframe does not have
   and a block of text does: when the map became a text placeholder the right
   track grew to fit the longest line and pushed the page 260px wider than the
   viewport at 960. */
.loc-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.2fr);gap:22px;align-items:stretch}
.loc-grid>*{min-width:0}
@media (max-width:860px){.loc-grid{grid-template-columns:1fr}}
.loc-grid .map{width:100%;height:100%;min-height:300px;border:0;border-radius:var(--radius);box-shadow:var(--shadow);border:1px solid var(--line)}
/* The map cell fills its row, before the frame arrives and after.

   Through v57 the panel carried align-self:start and sized to its own content,
   on the reasoning that a box holding four lines of text should not be as tall
   as a 16:9 video. The result on the contact page was a map visibly shorter
   than the company card beside it, with a ragged gap under it, in both states:
   the placeholder was short because it hugged its text, and the loaded frame
   was short because height:100% resolves against a parent that has no height
   of its own.

   Both halves of the row now stretch, which is what align-items:stretch on the
   grid was already asking for, and the frame fills the cell it is given. The
   card decides the height, because the card is the thing with content in it.

   min-height is the floor for the case where the card is unusually short, and
   it is a floor rather than a height: a fixed height clips whichever language
   has the longest address. */
.loc-grid .map-panel{align-self:stretch;min-height:320px}
/* The panel that stands where the Google frame would be until somebody asks
   for it.

   It no longer matches the frame's height. The v50 version did, on the
   reasoning that the page should not jump when the map arrives, and the result
   was a 16:9 box holding four lines of text with a great deal of nothing
   underneath them. The jump is worth less than the space: a panel sized to its
   own content reads as a deliberate part of the page rather than as a picture
   that failed to load. min-height keeps it from collapsing to a thin strip in
   the two-column layout, where it sits beside a taller card.

   Nothing of Google's appears here: no logo, no tile, no screenshot. */
.map-panel{display:flex;align-items:center;justify-content:center;padding:26px 24px;
  background:var(--surface-2);border-radius:var(--radius);border:1px solid var(--line);
  text-align:start;min-width:0;max-width:100%;min-height:200px;position:relative}
.map-panel-inner{max-width:min(46ch,100%);display:grid;gap:10px;justify-items:start;width:100%}

/* The line a visitor came for. The pin is the site's own icon, the same one
   the office card uses, so the two read as the same place. */
.map-panel-head{margin:0;display:flex;align-items:center;gap:10px;
  font-family:var(--font-head);font-weight:600;font-size:1.05rem;color:var(--ink)}
.map-panel-pin{display:inline-flex;flex:0 0 auto;color:var(--accent)}
.map-panel-pin svg{width:20px;height:20px}
.map-panel-address{margin:0;color:var(--ink-2);font-size:.95rem;line-height:1.6;overflow-wrap:anywhere}
.map-panel-note{margin:2px 0 0;color:var(--ink-3);font-size:.86rem;line-height:1.55}

/* The two sentences that are only true in one state each. The no-JS line ships
   visible and is hidden by the script; the status line ships hidden and is
   filled in when something is happening. */
.map-panel-nojs{margin:0;color:var(--ink-2);font-size:.86rem;line-height:1.55}
.map-panel-status{margin:0;color:var(--ink-2);font-size:.88rem;line-height:1.55}
.map-panel.is-blocked .map-panel-status{color:var(--warn);font-weight:500}

/* The button is the point of the panel, so it is the primary control. Its
   label is a short phrase in three languages; it still wraps rather than
   setting the width of the column it sits in. */
.map-panel .btn{white-space:normal;max-width:100%;margin-top:4px}
.map-panel .btn[disabled]{opacity:.65;cursor:default}
/* The way out for somebody who would rather not load a frame at all. Separate
   from the button, and deliberately quieter than it. */
.map-panel-link{margin:0;font-size:.88rem}
.map-panel-link a{color:var(--accent-3)}

/* While the frame is being fetched it is in the panel but has nothing to show,
   so it is kept out of the layout rather than pushing the text around. */
.map-pending{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;border:0}

/* The line that stays under the loaded frame. A refused cross-origin frame is
   indistinguishable from a working one (see the note in site.js), so the way
   out cannot be shown conditionally: it has to be there for the visitor who is
   looking at a blank rectangle and has no other clue what happened. */
/* The loaded state is the frame and nothing else in the flow.

   grid with a single 1fr row, so the iframe's height:100% has a real height to
   resolve against, and the wrapper fills the stretched cell. */
/* The loaded frame fills the cell and contributes nothing to sizing it.

   Laid out in flow, an iframe with height:100% inside a grid item is a
   circular question: the row is being sized by its contents, and the contents
   want a percentage of the row. Browsers answer it with the element's own
   intrinsic height, and in Arabic that landed 20.6px taller than the
   placeholder had been, so pressing the button moved the whole row and the
   card with it. Taken out of flow the question does not arise: the card sizes
   the row, and the frame is stretched to whatever the row turns out to be. */
.map-panel.is-loaded{display:block;position:relative;padding:0;background:none;border:0}
.map-panel.is-loaded > .map{position:absolute;inset:0;width:100%;height:100%;min-height:0;margin:0}
/* The way out sits below the frame rather than inside it.

   It used to be a paragraph appended into the panel, which pushed the frame up
   and made the map shorter than the card by exactly the height of one line of
   text: the thing the whole section was supposed to stop. Taken out of the
   flow it costs the map nothing, and .loc-grid reserves the room for it so it
   does not land on whatever follows. Still in the document, still in the tab
   order, still read in source order by a screen reader. */
.map-panel{position:relative}
.loc-grid-after{grid-column:1/-1;margin:8px 0 0}
.map-panel-after{margin:8px 0 0;font-size:.85rem;color:var(--ink-3);line-height:1.55}
.map-panel-after a{color:var(--accent-3);white-space:nowrap}
.band-dark .map-panel-after{color:rgba(255,255,255,.82)}
.band-dark .map-panel-after a{color:#A7F3D0}

/* On a dark band, every one of these has to be restated. The tokens above are
   dark ink on a light ground, and inheriting them onto the navy gradient is
   how a panel ends up with 1.4:1 text that looks fine in a screenshot taken on
   a white page. Measured against the darkest stop of that gradient, #0C4136:
   #FFFFFF is 14.0:1, rgba(255,255,255,.86) over it is about 10.5:1, and
   rgba(255,255,255,.78) about 8.6:1. All are past the 4.5:1 that WCAG 2.2 AA
   asks for body text, with room for the lighter stops of the gradient. */
.band-dark .map-panel{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.18)}
.band-dark .map-panel-head{color:#fff}
.band-dark .map-panel-pin{color:#6EE7B7}
.band-dark .map-panel-address{color:rgba(255,255,255,.90)}
.band-dark .map-panel-note,
.band-dark .map-panel-nojs,
.band-dark .map-panel-status{color:rgba(255,255,255,.82)}
.band-dark .map-panel.is-blocked .map-panel-status{color:#FCD34D}
.band-dark .map-panel-link a{color:#A7F3D0}

.nav a.btn-primary,.nav a.btn-primary:hover{color:#fff}
.site-footer .logo img{height:44px}

/* The band that used to hide the CTA is gone, and with it the rule it
   implemented.

   v51 switched to the burger below 1180 and then hid the consultation button
   between 1180 and 1339, because the German row does not fit in that space
   with the button in it. The reasoning was sound and the result was not:
   between those two widths a visitor saw a complete desktop navigation with
   the site's primary action missing from it, and below 1180 the button was
   hidden inside the compact menu as well.

   v52 uses one threshold instead of two, at the measured width above. The
   compact menu now starts wherever the complete row, button included, stops
   fitting, and the button appears inside that menu as a full-width primary
   action. So there is no width at which the CTA is absent, and no width at
   which the navigation is squeezed to make room for it: it either all fits,
   or it is all in the menu. */

/* ---- v8: sidebar cards, hero blobs, managed banner ---- */
.article-layout{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:32px;align-items:start}
@media (max-width:980px){.article-layout{grid-template-columns:1fr}.article-layout .sticky{position:static}}
.side-stack{display:grid;gap:18px}
.side-card{border-radius:var(--radius);padding:20px 22px;background:var(--surface);border:1px solid var(--line);box-shadow:var(--shadow)}
.side-card h2,.side-card h3,.side-card h4{font-family:var(--font-head);font-size:.8rem;letter-spacing:.09em;text-transform:uppercase;margin-bottom:12px}
.side-card ul{list-style:none;margin:0;padding:0;display:grid;gap:2px}
.side-card ul a{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:8px;font-weight:500;font-size:.93rem;transition:background .2s ease,color .2s ease,transform .2s ease}
.side-card ul a svg{width:14px;height:14px;flex:0 0 auto}
.toc-card{border-inline-start:4px solid var(--accent)}
.toc-card h2,.toc-card h4{color:var(--accent)}
.toc-card ul a{color:var(--accent-3)}
.toc-card ul a:hover{background:rgba(107,70,193,.08);transform:translateX(3px)}
[dir=rtl] .toc-card ul a:hover{transform:translateX(-3px)}
.toc-card ul a.active{background:var(--grad);color:#fff}
.related-card{border-inline-start:4px solid var(--accent-4)}
.related-card h2,.related-card h3,.related-card h4{color:#047857}   /* was --accent-4 mint: 2.53:1 on white */
.related-card ul a{color:#0B7A5C}
.related-card ul a:hover{background:rgba(16,185,129,.1);transform:translateX(3px)}
[dir=rtl] .related-card ul a:hover{transform:translateX(-3px)}
.quote-card{background:linear-gradient(135deg,rgba(107,70,193,.10),rgba(16,185,129,.10));border:1px solid var(--line-2)}
.quote-card h3{font-size:1.05rem;margin-bottom:6px}
.quote-card p{color:var(--ink-2);font-size:.92rem;margin-bottom:14px}
.quote-card .side-actions{display:grid;gap:10px}
.quote-card .btn{justify-content:center}
.managed-banner{display:flex;align-items:center;gap:18px;margin-bottom:40px;background:linear-gradient(135deg,rgba(107,70,193,.08),rgba(16,185,129,.08))}
.managed-banner h3{margin-bottom:2px}
.managed-banner p{color:var(--ink-2)}
.managed-banner .more{margin-inline-start:auto;white-space:nowrap;margin-top:0}
@media (max-width:700px){.managed-banner{flex-direction:column;align-items:flex-start}.managed-banner .more{margin-inline-start:0}}
.hero{position:relative;overflow:hidden}
.hero::before,.hero::after{content:"";position:absolute;border-radius:50%;filter:blur(70px);pointer-events:none;z-index:0}
.hero::before{width:480px;height:480px;background:rgba(107,70,193,.14);top:-160px;inset-inline-end:-120px}
.hero::after{width:420px;height:420px;background:rgba(16,185,129,.12);bottom:-180px;inset-inline-start:-120px}
.hero .wrap{position:relative;z-index:1}
.prose h2{scroll-margin-top:110px}
.section-head[id]{scroll-margin-top:110px}
section[id]{scroll-margin-top:90px}
html{scroll-behavior:smooth}

/* .under-hero used to give the first section after a hero a 40px top padding,
   a third value alongside the 56 and 88 everything else used. The build's seam
   pass covers the same case now: the hero is a different ground, so the section
   below it keeps its full padding and its cards cannot touch the hero's edge. */

/* ---- v9: full-width dark bands & dark page hero ---- */
/* The dark grounds carry the same constellation the home hero draws on its
   canvas. It was on one hero and nowhere else, which is why the motif read as
   missing from the site rather than as part of it. A tile costs one small SVG
   and no script, so every dark band can have it. */
.page-hero{background:url("img/sky-tile.svg") repeat 0 0/620px 620px,linear-gradient(120deg,#251C55 0%,#1D2A5C 48%,#0C4136 100%);color:#fff;position:relative;overflow:hidden}
.page-hero::before,.page-hero::after{content:"";position:absolute;border-radius:50%;filter:blur(60px);pointer-events:none}
.page-hero::before{width:380px;height:380px;background:rgba(46,196,182,.22);top:-140px;inset-inline-end:-90px}
.page-hero::after{width:320px;height:320px;background:rgba(16,185,129,.16);bottom:-160px;inset-inline-start:-80px}
.page-hero .wrap{position:relative;z-index:1}
.page-hero h1{color:#fff}
.page-hero p.lead{color:rgba(255,255,255,.88)}
.page-hero .breadcrumb,.page-hero .breadcrumb a{color:rgba(255,255,255,.65)}
.page-hero .breadcrumb a:hover{color:#fff}
.page-hero .eyebrow{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.25);color:#fff}
.band-dark{background:url("img/sky-tile.svg") repeat 0 0/620px 620px,linear-gradient(120deg,#251C55 0%,#1D2A5C 48%,#0C4136 100%);position:relative;overflow:hidden}
.band-dark::before{content:"";position:absolute;width:420px;height:420px;border-radius:50%;filter:blur(70px);background:rgba(46,196,182,.16);top:-180px;inset-inline-end:-100px;pointer-events:none}
.band-dark .wrap{position:relative;z-index:1}
.band-dark > .wrap > .stat-band:first-child{margin-top:0}
.band-dark .stat{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.16);box-shadow:none}
.band-dark .stat b{background:linear-gradient(135deg,#9F7AEA,#34D399);-webkit-background-clip:text;background-clip:text}
.band-dark .stat span{color:rgba(255,255,255,.78)}

/* ===== v10: motion layer ===== */
.hero-dark{background:linear-gradient(120deg,#1A1440 0%,#1D2A5C 52%,#0C4136 100%);color:#fff;padding:110px 0 96px}
.hero-dark canvas.sky{position:absolute;inset:0;width:100%;height:100%;z-index:0;pointer-events:none}
.hero-dark::before{background:rgba(107,70,193,.28)}
.hero-dark::after{background:rgba(16,185,129,.20)}
.hero-dark h1{color:#fff}
.hero-dark .hero-copy p.lead{color:rgba(255,255,255,.88)}
.hero-dark .trust span{color:rgba(255,255,255,.82)}
.hero-dark .eyebrow{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.25);color:#fff}
.hero-dark .btn-ghost{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.45);color:#fff}
.hero-dark .btn-ghost:hover{background:rgba(255,255,255,.1);border-color:#fff;color:#fff}
.hero-dark .hero-art{border:0;background:none;box-shadow:none;min-height:340px}
.hero-dark .hero-art .badge{background:rgba(255,255,255,.10);border:1px solid rgba(255,255,255,.22);color:#fff;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
/* headline + copy entrance */
@keyframes vega-rise{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:none}}
.hero-dark .hero-copy>*{animation:vega-rise .7s cubic-bezier(.22,.8,.3,1) both}
.hero-dark .hero-copy>*:nth-child(2){animation-delay:.12s}
.hero-dark .hero-copy>*:nth-child(3){animation-delay:.24s}
.hero-dark .hero-copy>*:nth-child(4){animation-delay:.36s}
.hero-dark .hero-copy>*:nth-child(5){animation-delay:.48s}
/* drifting aurora blobs on dark bands */
@keyframes vega-drift1{0%,100%{transform:translate(0,0) scale(1)}50%{transform:translate(-50px,36px) scale(1.15)}}
@keyframes vega-drift2{0%,100%{transform:translate(0,0) scale(1)}50%{transform:translate(44px,-30px) scale(1.1)}}
.hero-dark::before,.page-hero::before{animation:vega-drift1 14s ease-in-out infinite}
.hero-dark::after,.page-hero::after{animation:vega-drift2 17s ease-in-out infinite}
/* tech strip marquee */
.tech-marquee{overflow:hidden;position:relative;-webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent)}
.tech-marquee .track{display:flex;gap:10px;width:max-content;animation:vega-marquee 36s linear infinite}
.tech-marquee:hover .track{animation-play-state:paused}
@keyframes vega-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
[dir="rtl"] .tech-marquee .track{animation-name:vega-marquee-rtl}
@keyframes vega-marquee-rtl{from{transform:translateX(0)}to{transform:translateX(50%)}}
/* button shimmer */
.btn-primary{position:relative;overflow:hidden}
.btn-primary::after{content:"";position:absolute;top:0;bottom:0;width:44%;left:-60%;background:linear-gradient(105deg,transparent,rgba(255,255,255,.35),transparent);transform:skewX(-18deg);transition:none}
.btn-primary:hover::after{left:130%;transition:left .6s ease}
/* card + icon micro-motion */
.card{transition:transform .35s cubic-bezier(.22,.8,.3,1),box-shadow .35s ease,border-color .35s ease}
.card:hover{transform:translateY(-6px)}
.card .icon{transition:transform .35s cubic-bezier(.22,.8,.3,1)}
.card:hover .icon{transform:translateY(-4px) scale(1.06) rotate(-3deg)}
/* stat numbers shimmer on dark band */
.band-dark .stat b{background-size:200% 200%;animation:vega-grad 6s ease-in-out infinite}
@media (max-width:900px){.hero-dark{padding:72px 0 56px}.hero-dark .hero-art{min-height:0;display:none}}
@media (prefers-reduced-motion: reduce){
  .hero-dark .hero-copy>*,.hero-dark::before,.hero-dark::after,.page-hero::before,.page-hero::after,.tech-marquee .track,.band-dark .stat b{animation:none}
  .card:hover{transform:none}
}

/* ===== v11: gallery, roadmap, pain/fix, group ===== */
.hero-dark .hero-art .orbit{position:absolute;inset:0;margin:auto;width:min(430px,92%);height:auto;opacity:.95}
.hero-dark .hero-art .badge{z-index:1}
.shots .shot-card{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;background:#fff;box-shadow:var(--shadow);transition:transform .35s cubic-bezier(.22,.8,.3,1),box-shadow .35s ease;text-decoration:none}
.shots .shot-card:hover{transform:translateY(-6px);box-shadow:var(--shadow-lg)}
.shots .shot-card img{width:100%;height:auto;display:block;background:#FBFAFE}
.shots .shot-card span{display:flex;align-items:center;gap:8px;justify-content:space-between;padding:14px 18px;font-weight:600;color:var(--ink);border-top:1px solid var(--line)}
.shots .shot-card:hover span{color:var(--accent)}
.road-band .section-head h2{color:#fff}
.road-band .section-head p{color:rgba(255,255,255,.8)}
.road{position:relative;margin-top:34px}
.road-line{display:block;width:100%;height:24px;margin-bottom:-6px}
.road-steps{display:grid;grid-template-columns:repeat(5,1fr);gap:18px}
.road-step{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.14);border-radius:14px;padding:18px}
.road-step .n{display:inline-grid;place-items:center;width:30px;height:30px;border-radius:50%;background:var(--grad);color:#fff;font-weight:700;margin-bottom:10px}
.road-step h3{color:#fff;font-size:1.02rem;margin:0 0 6px}
.road-step p{color:rgba(255,255,255,.75);font-size:.9rem;margin:0}
@media (max-width:900px){.road-steps{grid-template-columns:1fr 1fr}.road-line{display:none}}
@media (max-width:560px){.road-steps{grid-template-columns:1fr}}
.card.pain{border-top:3px solid #EF6461}
.card.pain .icon{background:linear-gradient(135deg,#EF6461,#F5C044)}
.card.fix{border-top:3px solid var(--accent-4)}
.group-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px}
.logo-card{display:grid;place-items:center;background:#fff;border:1px solid var(--line);border-radius:14px;min-height:96px;padding:14px;text-decoration:none;transition:transform .3s ease,box-shadow .3s ease}
.logo-card:hover{transform:translateY(-4px);box-shadow:var(--shadow)}
.logo-card img{max-height:76px;width:auto;max-width:100%}
.logo-card .mono{display:flex;align-items:center;gap:9px;font-weight:700;color:var(--ink);text-align:center}
.logo-card .mono b{font-size:1.25rem;background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.eu-wall{display:flex;flex-wrap:wrap;gap:9px;justify-content:center;max-width:900px;margin:0 auto}
.eu-wall .pill{direction:ltr}

/* ===== v11: full-width story bands (gallery, road, pain/fix, client wall) ===== */
/* image gallery */
.shots{gap:20px}
.shot-card{display:block;background:#fff;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;text-decoration:none;transition:transform .35s cubic-bezier(.22,.8,.3,1),box-shadow .35s ease,border-color .35s ease}
.shot-card:hover{transform:translateY(-6px);box-shadow:var(--shadow-lg);border-color:var(--accent)}
.shot-card img{display:block;width:100%;height:auto;background:var(--surface-2)}
.shot-card span{display:flex;align-items:center;gap:8px;padding:14px 18px;font-weight:650;color:var(--ink);font-size:.95rem;border-top:1px solid var(--line)}
.shot-card:hover span{color:var(--accent)}
/* road / journey */
.road{margin-top:26px}
.road-line{display:block;width:100%;height:24px;margin-bottom:10px}
.road-steps{display:grid;grid-template-columns:repeat(6,1fr);gap:18px}
.road-step{background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.14);border-radius:14px;padding:18px 16px}
.road-step .n{display:grid;place-items:center;width:32px;height:32px;border-radius:50%;background:var(--grad);color:#fff;font-weight:700;font-size:.95rem;margin-bottom:10px}
.road-step h3{color:#fff;font-size:1rem;margin:0 0 6px}
.road-step p{color:rgba(255,255,255,.76);font-size:.87rem;margin:0;line-height:1.55}
.road-band .section-head h2{color:#fff}
.road-band .section-head p{color:rgba(255,255,255,.8)}
.road-band .eyebrow{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.25);color:#fff}
/* problems vs solutions */
.card.pain{border-inline-start:4px solid #E05252}
.card.pain .icon{background:linear-gradient(135deg,#E05252,#F6A244)}
.card.fix{border-inline-start:4px solid var(--accent-4)}
.card.fix .icon{background:linear-gradient(135deg,#6B46C1,#10B981)}
/* client logo wall */
.group-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:16px}
.logo-card{display:grid;place-items:center;min-height:104px;padding:18px;background:#fff;border:1px solid var(--line);border-radius:14px;text-decoration:none;transition:transform .3s ease,box-shadow .3s ease,border-color .3s ease}
.logo-card:hover{transform:translateY(-4px);box-shadow:var(--shadow);border-color:var(--accent)}
.logo-card img{max-height:58px;max-width:100%;width:auto;object-fit:contain;opacity:.9;transition:opacity .3s ease,transform .3s ease}
.logo-card:hover img{opacity:1;transform:scale(1.04)}
.logo-card .mono{display:flex;align-items:center;gap:8px;font-family:'Space Grotesk',system-ui,sans-serif;font-weight:600;font-size:.98rem;color:var(--ink-2);text-align:center;letter-spacing:.01em}
.logo-card .mono b{font-size:1.15rem;background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.logo-card:hover .mono{color:var(--accent)}
.eu-wall{display:flex;flex-wrap:wrap;gap:9px;justify-content:center}
.eu-wall .pill{font-size:.82rem;background:#fff;border:1px solid var(--line)}
@media (max-width:1100px){.road-steps{grid-template-columns:repeat(3,1fr)}.road-line{display:none}}
@media (max-width:700px){.road-steps{grid-template-columns:1fr}.group-grid{grid-template-columns:repeat(2,1fr)}}
/* v11 fixes: gallery card polish */
.shot-card span svg{width:18px;height:18px;flex-shrink:0;margin-inline-start:auto}
.shot-card img{aspect-ratio:16/10;object-fit:contain;padding:10px;box-sizing:border-box}
.shot-card span{min-height:62px}
.road-step p{font-size:.845rem}

/* ===== v12: alternating bands, real case study, vacancies ===== */
.section-alt{background:var(--surface-2)}
.section-alt + .section-alt{background:transparent}
.why-band .section-head h2{color:#fff}
.why-band .section-head p{color:rgba(255,255,255,.8)}
.why-band .eyebrow{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.25);color:#fff}
.why-band .card{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.14)}
.why-band .card h3{color:#fff}
.why-band .card p{color:rgba(255,255,255,.76)}
.why-band .card:hover{border-color:rgba(255,255,255,.35);box-shadow:none}
/* featured case study */
.case-list{display:grid;gap:26px}
.case-feature{display:grid;grid-template-columns:1.05fr .95fr;gap:32px;align-items:center;background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:30px;box-shadow:var(--shadow)}
.case-feature h2,.case-feature h3{margin:8px 0 18px;font-size:1.45rem}
.case-rows{display:grid;gap:14px}
.case-rows>div{display:grid;grid-template-columns:104px 1fr;gap:14px;align-items:start}
.case-rows span{font-size:.74rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--accent);padding-top:3px}
.case-rows p{margin:0;color:var(--ink-2);line-height:1.65}
.case-art img{width:100%;height:auto;border-radius:14px;background:var(--surface-2)}
@media (max-width:900px){.case-feature{grid-template-columns:1fr;padding:22px}.case-rows>div{grid-template-columns:1fr;gap:4px}}
/* vacancies */
.role-card{display:flex;flex-direction:column}
.role-card .role-meta{font-size:.82rem;font-weight:650;color:var(--accent);margin:-4px 0 10px;letter-spacing:.01em}
.role-card ul{margin:14px 0 18px}
.role-card .more{margin-top:auto}
/* tech marquee: no half-erased pills at the edges */
.tech-marquee{-webkit-mask-image:linear-gradient(90deg,transparent,#000 3%,#000 97%,transparent);mask-image:linear-gradient(90deg,transparent,#000 3%,#000 97%,transparent);padding:6px 0}
.tech-marquee .track{padding-inline:12px}

/* v12: platform pills as a full wrapping cloud, nothing clipped at the edges */
.tech-cloud{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;max-width:960px;margin:0 auto}
.tech-cloud .pill{transition:transform .3s ease,border-color .3s ease,opacity .5s ease}
.tech-cloud .pill:hover{transform:translateY(-3px);border-color:var(--accent);color:var(--accent)}

/* ===== v13: .band-dark is now applied automatically across every page, so it has
   to style everything a normal section can contain, not just stat tiles. ===== */
/* A dark band is a section like any other and takes the section scale. It used
   to declare 44px here and 76px further down the file, both of which outranked
   .section-tight by source order, so every dark band on the site opened and
   closed 12px tighter than the light band above it. Only a band that carries
   no scale class of its own needs a default. */
.band-dark:not(.section):not(.section-tight){padding:var(--band-y) 0}
.band-dark .section-head h2,.band-dark .section-head h3,.band-dark h2,.band-dark h3:not(.card h3){color:#fff}
.band-dark .section-head p,.band-dark>.wrap>p{color:rgba(255,255,255,.8)}
.band-dark .muted,.band-dark .small{color:rgba(255,255,255,.7)}
.band-dark .eyebrow{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.25);color:#fff}
/* cards on a dark ground */
.band-dark .card{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.14);box-shadow:none}
.band-dark .card h3{color:#fff}
.band-dark .card p,.band-dark .card li{color:rgba(255,255,255,.78)}
.band-dark .card:hover{border-color:rgba(255,255,255,.38);transform:translateY(-6px);box-shadow:0 18px 40px rgba(0,0,0,.28)}
.band-dark .card .more,.band-dark a.more{color:#7DE3C0}
.band-dark .card-accent{background:rgba(255,255,255,.10);border-color:rgba(255,255,255,.22)}
.band-dark .eyebrow{backdrop-filter:blur(6px)}
/* numbered steps */
.band-dark .step{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.14)}
.band-dark .step h3{color:#fff}
.band-dark .step p{color:rgba(255,255,255,.76)}
/* check lists */
.band-dark .list-check li{color:rgba(255,255,255,.82)}
.band-dark .list-check li svg,.band-dark .list-check li::before{color:#34D399}
/* links, pills, buttons */
.band-dark a:not(.btn):not(.card):not(.more){color:#9FE8D2}
.band-dark a:not(.btn):not(.card):not(.more):hover{color:#fff}
.band-dark .pill{background:rgba(255,255,255,.09);border-color:rgba(255,255,255,.2);color:rgba(255,255,255,.88)}
.band-dark .btn-ghost{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.45);color:#fff}
.band-dark .btn-ghost:hover{background:rgba(255,255,255,.14);border-color:#fff;color:#fff}
/* icon tiles keep the brand gradient; give them a touch more contrast on dark */
.band-dark .card .icon{box-shadow:0 6px 18px rgba(0,0,0,.28)}
/* two dark bands should never touch */
.band-dark + .band-dark{background:none;background-color:transparent}

/* v13: managed sub-pages sit inside a sidebar layout, so their visual break is a
   dark panel rather than a full-width band, same contrast, same purpose. */
.roi-panel{background:linear-gradient(120deg,#251C55 0%,#1D2A5C 48%,#0C4136 100%);border-radius:var(--radius);padding:32px;margin:38px 0 8px;position:relative;overflow:hidden}
.roi-panel::before{content:"";position:absolute;width:280px;height:280px;border-radius:50%;filter:blur(60px);background:rgba(46,196,182,.20);top:-120px;inset-inline-end:-80px;pointer-events:none}
.roi-panel>*{position:relative;z-index:1}
.roi-panel h2{color:#fff}
.roi-panel .section-head p{color:rgba(255,255,255,.82)}
.roi-panel .stat{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.14)}
.roi-panel .stat b{background:linear-gradient(120deg,#9F7AEA,#34D399);-webkit-background-clip:text;background-clip:text;color:transparent}
.roi-panel .stat span{color:rgba(255,255,255,.78)}
@media (max-width:700px){.roi-panel{padding:22px}}
.band-dark .step::before{color:#5EEAD4}
.band-dark .step{box-shadow:none}
.roi-panel .stat b{filter:saturate(1.15) brightness(1.12)}
/* FAQ accordions on a dark band */
.band-dark .faq details{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.14)}
.band-dark .faq summary{color:#fff}
.band-dark .faq summary::after,.band-dark .faq summary::marker{color:#7DE3C0}
.band-dark .faq p{color:rgba(255,255,255,.8)}
.band-dark .faq details[open]{background:rgba(255,255,255,.09);border-color:rgba(255,255,255,.24)}
/* An open question went purple and vanished into the dark band.

   The cause is specificity, not colour: `.faq details[open] summary` is a rule
   about a STATE and scores (0,2,2); `.band-dark .faq summary` is a rule about
   a VARIANT and scores (0,2,1). The state rule wins everywhere, including on
   the one ground where its colour cannot be read. Adding the variant to the
   state rule takes it to (0,3,2), which is the only thing that settles it.

   The same trap has been hit repeatedly in this stylesheet, always the same
   way round: a :hover or [open] rule quietly outranking the band it sits in. */
.band-dark .faq details[open] summary{color:#fff}

/* v14 fix: .icon was only sized inside .card, so a standalone icon (industries
   page sections) rendered at the SVG's natural size, a huge glyph. */
.icon{width:46px;height:46px;border-radius:13px;display:grid;place-items:center;background:var(--grad);border:0;color:#fff;box-shadow:0 6px 16px rgba(107,70,193,.28);flex-shrink:0}
.icon svg{width:22px;height:22px}

/* v14: client wall, grouped by region, each tile showing the live domain */
.wall-h{text-align:center;font-size:1.02rem;letter-spacing:.04em;text-transform:uppercase;color:#586273;margin:26px 0 14px;font-weight:700}
/* The track-record tiles were 230px wide and 132px tall, which turned six
   client logos into two full rows of billboards and made the section the
   heaviest thing on the page. They are a logo strip: fixed narrow tiles,
   centred, at a size where the eye takes in the whole row at once. Flex rather
   than grid so that three logos in a region do not stretch to fill 1180px. */
.group-grid{display:flex;flex-wrap:wrap;justify-content:center;gap:12px}
.logo-card{flex:0 0 156px;align-content:center;min-height:78px;padding:12px;cursor:default}
.logo-card img{max-height:42px}
@media (max-width:560px){.logo-card{flex:0 0 132px;min-height:68px}.logo-card img{max-height:36px}}

/* ===== v15: About page, story, mission/vision, team ===== */
.story h2{font-size:2.1rem;font-weight:800;letter-spacing:-.02em;line-height:1.15;position:relative;padding-bottom:16px}
.story h2::after{content:"";position:absolute;bottom:0;inset-inline-start:0;width:64px;height:4px;border-radius:2px;background:var(--grad)}
.story .story-lead{margin-top:20px;font-size:1.14rem;line-height:1.7;color:var(--ink);font-weight:500}
.story .muted{margin-top:14px;line-height:1.72}
/* mission & vision */
.mv-grid{display:grid;grid-template-columns:1fr 1fr;gap:22px}
.mv-card{position:relative;overflow:hidden;padding:32px;border-radius:var(--radius);background:var(--surface);border:1px solid var(--line);
  transition:transform .4s cubic-bezier(.22,.8,.3,1),box-shadow .4s ease,border-color .4s ease,background .4s ease}
.mv-card::before{content:"";position:absolute;inset:0;border-radius:inherit;opacity:0;transition:opacity .4s ease;
  background:linear-gradient(135deg,#251C55 0%,#1D2A5C 52%,#0C4136 100%)}
.mv-card>*{position:relative;z-index:1}
.mv-card .mv-icon{width:52px;height:52px;border-radius:15px;display:grid;place-items:center;background:var(--grad);color:#fff;margin-bottom:16px;transition:transform .4s cubic-bezier(.22,.8,.3,1)}
.mv-card .mv-icon svg{width:24px;height:24px}
.mv-card h3{font-size:1.24rem;font-weight:750;margin-bottom:10px;transition:color .35s ease}
.mv-card p{color:var(--ink-2);line-height:1.72;margin:0;transition:color .35s ease}
.mv-card:hover{transform:translateY(-8px);border-color:transparent;
  box-shadow:0 22px 50px rgba(37,28,85,.28), 0 0 0 1px rgba(159,122,234,.55), 0 0 26px rgba(46,196,182,.35)}
.mv-card:hover::before{opacity:1}
.mv-card:hover h3{color:#fff}
.mv-card:hover p{color:rgba(255,255,255,.84)}
.mv-card:hover .mv-icon{transform:scale(1.08) rotate(-4deg)}
@media (max-width:820px){.mv-grid{grid-template-columns:1fr}}
/* the team */
.team-band{background:var(--surface-2)}
.team-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:18px}
.team-card{position:relative;padding:26px 24px 24px;border-radius:var(--radius);background:#fff;border:1px solid var(--line);
  border-top:4px solid var(--tc,var(--accent));transition:transform .35s cubic-bezier(.22,.8,.3,1),box-shadow .35s ease,border-color .35s ease}
/* --tc paints the top rule and the hover glow, which are decoration and carry
   no text. --tc-ink is the same colour taken darker for the small bold label,
   which does have to be readable: the teal and the green measured 2.2:1 and
   2.5:1 on white, well under the 4.5:1 that 12px text needs. Splitting the two
   keeps the four cards looking as they did while making the labels legible. */
.team-card.t1{--tc:#6B46C1;--tc-ink:#6B46C1}
.team-card.t2{--tc:#2EC4B6;--tc-ink:#0F6B62}
.team-card.t3{--tc:#10B981;--tc-ink:#047857}
.team-card.t4{--tc:#9F7AEA;--tc-ink:#6B46C1}
.team-card .tag{display:inline-block;font-family:var(--font-head);font-size:.76rem;font-weight:800;letter-spacing:.12em;color:var(--tc-ink,var(--tc));margin-bottom:10px}
.team-card h3{font-size:1.05rem;font-weight:700;margin-bottom:8px}
.team-card p{color:var(--ink-2);font-size:.94rem;line-height:1.62;margin:0}
.team-card:hover{transform:translateY(-6px);border-color:var(--tc);
  box-shadow:0 16px 38px rgba(31,41,55,.12), 0 0 0 1px var(--tc), 0 0 22px color-mix(in srgb, var(--tc) 40%, transparent)}
/* dark-band safety: a <strong> label must not stay ink-coloured on a dark ground */
.band-dark strong,.band-dark b,.band-dark .rel-practices{color:#fff}
.band-dark .rel-practices a{color:#7DE3C0}
.band-dark .rel-practices a:hover{color:#fff}

/* ===== v15: scroll choreography ===== */
.scroll-rail{position:fixed;top:0;inset-inline:0;height:3px;z-index:60;pointer-events:none}
.scroll-rail i{display:block;height:100%;width:100%;transform:scaleX(0);transform-origin:left center;background:var(--grad)}
[dir="rtl"] .scroll-rail i{transform-origin:right center}
.drift{transform:translateY(var(--drift,0));will-change:transform;transition:transform .12s linear}
/* The two halves of a two-column band arrive from their own side. While one
   is still 32px out, its box extends past the edge of the row, and on a
   viewport narrow enough for the row to reach the page edge that is real
   horizontal scroll: the page can be dragged sideways for the half-second the
   animation lasts.

   It was intermittent and therefore easy to dismiss. It reproduces on about
   four loads in twenty-five at 1024px, and only when the measurement lands
   inside the animation window, which is why it showed up as a flaky test
   rather than as a bug report. Measured, not guessed: documentElement
   scrollWidth 1032 against clientWidth 1024, with the offending box named as
   the .from-end half of the office row.

   overflow-x: clip rather than hidden, because hidden on one axis forces the
   other to auto and would turn every one of these rows into a scroll
   container, which breaks sticky positioning inside them. clip creates no
   scrollport. overflow-clip-margin keeps the card shadows, which are three
   pixels and would otherwise be sliced off flat at the column edge. */
html.reveal-ready .two-col{overflow-x:clip;overflow-clip-margin:8px}
html.reveal-ready .reveal.from-start{transform:translateX(-32px)}
html.reveal-ready .reveal.from-end{transform:translateX(32px)}
html.reveal-ready .reveal.from-start.in,html.reveal-ready .reveal.from-end.in{transform:none}
@media (max-width:900px){html.reveal-ready .reveal.from-start,html.reveal-ready .reveal.from-end{transform:translateY(18px)}}
@media (prefers-reduced-motion: reduce){.scroll-rail{display:none}.drift{transform:none}html.reveal-ready .reveal.from-start,html.reveal-ready .reveal.from-end{transform:none}}
/* ===== v17: editorial case study, full-bleed plate, narrow prose, phase list ===== */
/* The case masthead was written for a dark ground and only ever appeared on
   one, so its colours were hard-coded white. The build now takes the dark
   ground away from a band that would otherwise sit directly under the dark
   page hero and repeat its gradient, which is the case on the case-study
   index, so the masthead needs to read on the light ground too. Layout and
   type are shared; only the colours are split. */
.case-open{padding:var(--band-y) 0}
.case-open h2{color:var(--ink);font-size:clamp(2rem,4.2vw,3.1rem);line-height:1.1;letter-spacing:-.025em;font-weight:800;margin:18px 0 0;max-width:20ch}
.case-outcome{color:var(--ink-2);font-size:1.18rem;line-height:1.65;max-width:62ch;margin:20px 0 0}
/* The case meta was a flex row, so each pair took the width of its own text.
   Nothing lined up: the second column started at a different place on every
   row, and a value that wrapped to two lines pushed the pair below it out of
   step with its neighbour. A grid gives what the client asked for, which is the
   behaviour of a spreadsheet: fixed columns, and a row that starts below the
   tallest cell of the row above it. */
.case-meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:26px 40px;margin:34px 0 0;padding-top:24px;border-top:1px solid var(--line-2);align-items:start}
@media (max-width:620px){.case-meta{grid-template-columns:1fr;gap:20px}}
.case-meta dt{font-size:.72rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-bottom:6px}
.case-meta dd{margin:0;color:var(--ink);font-weight:600;font-size:.98rem}
.case-open.band-dark .eyebrow{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.25);color:#fff}
.case-open.band-dark h2{color:#fff}
.case-open.band-dark .case-outcome{color:rgba(255,255,255,.86)}
.case-open.band-dark .case-meta{border-top-color:rgba(255,255,255,.16)}
.case-open.band-dark .case-meta dt{color:rgba(255,255,255,.55)}
.case-open.band-dark .case-meta dd{color:#fff}
/* the design itself, breaking the column */
.case-plate{margin:0;padding:0 0 8px;background:var(--surface-2)}
.case-plate img{display:block;width:min(1340px,calc(100% - 32px));height:auto;margin:-56px auto 0;border-radius:16px;
  box-shadow:0 30px 70px rgba(23,20,60,.24);background:#fff}
.case-plate figcaption{max-width:74ch;margin:20px auto 0;padding:0 16px;text-align:center;color:#586273;font-size:.9rem;line-height:1.6}
/* narrow reading column */
.case-narrow .wrap{max-width:760px}
.case-block{margin-bottom:34px}
.case-label{display:block;font-size:.74rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--accent);margin-bottom:12px}
.case-block p{margin:0;color:var(--ink-2);line-height:1.78}
.case-block .case-lead{font-size:1.14rem;color:var(--ink);line-height:1.7}
/* delivery phases: a sticky label beside a numbered narrative */
.case-phases{display:grid;grid-template-columns:280px 1fr;gap:48px;align-items:start}
.case-phases-head{position:sticky;top:110px}
.case-phases-head h3{font-size:1.75rem;font-weight:800;letter-spacing:-.02em;line-height:1.2;padding-bottom:14px;position:relative}
.case-phases-head h3::after{content:"";position:absolute;bottom:0;inset-inline-start:0;width:56px;height:4px;border-radius:2px;background:var(--grad)}
.case-phases ol{list-style:none;margin:0;padding:0;counter-reset:ph}
.case-phases li{position:relative;padding:0 0 32px 0;padding-inline-start:66px;border-inline-start:2px solid var(--line);margin-inline-start:20px}
.case-phases li:last-child{border-color:transparent;padding-bottom:0}
.case-phases li::before{counter-increment:ph;content:counter(ph,decimal-leading-zero);position:absolute;inset-inline-start:-21px;top:-4px;
  width:40px;height:40px;border-radius:50%;display:grid;place-items:center;background:var(--grad);color:#fff;
  font-family:var(--font-head);font-weight:800;font-size:.86rem;box-shadow:0 6px 16px rgba(107,70,193,.3)}
.case-phases h4{font-size:1.12rem;font-weight:700;margin:0 0 8px}
.case-phases p{margin:0;color:var(--ink-2);line-height:1.75}
@media (max-width:900px){
  .case-phases{grid-template-columns:1fr;gap:24px}
  .case-phases-head{position:static}
  .case-plate img{margin-top:-28px;width:calc(100% - 24px)}
}

/* ===== v18: case-study slider, technology grid, outcome row ===== */
.slider{outline:none}
.slider-nav{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px;margin-bottom:26px}
.slider-tab{display:flex;flex-direction:column;align-items:flex-start;gap:6px;text-align:start;cursor:pointer;
  background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:16px 18px;font:inherit;color:var(--ink-2);
  border-top:3px solid var(--line);transition:border-color .3s ease,color .3s ease,transform .3s ease,box-shadow .3s ease}
.slider-tab .n{font-family:var(--font-head);font-weight:800;font-size:.78rem;letter-spacing:.12em;color:var(--ink-3)}
.slider-tab .t{font-weight:650;font-size:.95rem;line-height:1.35}
.slider-tab:hover{transform:translateY(-3px);box-shadow:var(--shadow)}
.slider-tab.on{border-top-color:var(--accent);border-color:var(--accent);color:var(--ink);box-shadow:var(--shadow)}
.slider-tab.on .n{background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.slider-stage{position:relative;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);
  padding:36px 40px;min-height:210px;box-shadow:var(--shadow);overflow:hidden}
.slider-stage::before{content:"";position:absolute;inset-inline-start:0;top:0;bottom:0;width:4px;background:var(--grad)}
.slide{display:none}
.slide.on{display:block;animation:slide-in .5s cubic-bezier(.22,.8,.3,1) both}
@keyframes slide-in{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
.slide-n{display:block;font-family:var(--font-head);font-weight:800;font-size:2.6rem;line-height:1;margin-bottom:12px;
  background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.slide h3,.slide h4{font-size:1.28rem;font-weight:750;margin:0 0 12px}
.slide p{margin:0;color:var(--ink-2);line-height:1.78;max-width:74ch}
.slider-foot{display:flex;align-items:center;gap:16px;justify-content:center;margin-top:22px}
.slider-arrow{width:44px;height:44px;border-radius:50%;display:grid;place-items:center;cursor:pointer;
  background:var(--surface);border:1px solid var(--line-2);color:var(--accent);transition:all .3s ease}
.slider-arrow svg{width:18px;height:18px}
.slider-arrow[data-prev] svg{transform:rotate(180deg)}
[dir="rtl"] .slider-arrow[data-prev] svg{transform:none}
[dir="rtl"] .slider-arrow[data-next] svg{transform:rotate(180deg)}
.slider-arrow:hover{background:var(--grad);border-color:transparent;color:#fff}
.slider-dots{display:flex;gap:8px}
.slider-dots .dot{width:8px;height:8px;border-radius:50%;background:var(--line-2);cursor:pointer;transition:all .3s ease}
.slider-dots .dot.on{width:26px;border-radius:4px;background:var(--grad)}
/* technology grid */
.stack-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:10px;margin-top:8px}
.stack-chip{display:flex;align-items:center;gap:10px;padding:14px 16px;border-radius:12px;font-size:.92rem;font-weight:600;
  background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.14);color:rgba(255,255,255,.9);transition:all .3s ease}
.stack-chip::before{content:"";width:7px;height:7px;border-radius:50%;background:#34D399;flex-shrink:0}
.stack-chip:hover{background:rgba(255,255,255,.13);border-color:rgba(255,255,255,.34);transform:translateY(-2px)}
/* outcome row */
.outcome-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(215px,1fr));gap:18px}
.outcome{padding:24px;border-radius:var(--radius);background:var(--surface);border:1px solid var(--line);
  transition:transform .35s cubic-bezier(.22,.8,.3,1),box-shadow .35s ease,border-color .35s ease}
.outcome .icon{margin-bottom:14px}
.outcome h3,.outcome h4{font-size:1.04rem;font-weight:700;margin:0 0 8px}
.outcome p{margin:0;color:var(--ink-2);font-size:.93rem;line-height:1.62}
.outcome:hover{transform:translateY(-6px);box-shadow:var(--shadow-lg);border-color:var(--accent)}
@media (max-width:700px){.slider-stage{padding:26px 22px}.slider-nav{grid-template-columns:1fr 1fr}}
@media (prefers-reduced-motion: reduce){.slide.on{animation:none}}
/* v18 fix: even rows for the technology grid and the outcome row */
@media (min-width:1000px){
  .stack-grid{grid-template-columns:repeat(4,1fr)}
  .outcome-row{grid-template-columns:repeat(5,1fr)}
  .outcome{padding:22px 20px}
  .outcome h3,.outcome h4{font-size:1rem}
}
@media (min-width:700px) and (max-width:999px){
  .stack-grid{grid-template-columns:repeat(3,1fr)}
  .outcome-row{grid-template-columns:repeat(2,1fr)}
}

/* ===== v21: vacancy pages ===== */
.ref-badge{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-head);font-size:.76rem;font-weight:800;
  letter-spacing:.12em;padding:7px 14px;border-radius:999px;background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.28);color:#fff}
.page-hero .ref-badge{margin-bottom:4px}
.ref-chip{display:inline-block;font-family:var(--font-head);font-size:.72rem;font-weight:800;letter-spacing:.1em;
  color:var(--accent);background:rgba(107,70,193,.09);border:1px solid rgba(107,70,193,.22);padding:5px 10px;border-radius:999px;margin-bottom:10px;align-self:flex-start}
.role-h2{font-size:1.42rem;font-weight:750;letter-spacing:-.01em;margin-bottom:16px;padding-bottom:12px;position:relative}
.role-h2::after{content:"";position:absolute;bottom:0;inset-inline-start:0;width:48px;height:3px;border-radius:2px;background:var(--grad)}
.role-about{max-width:76ch;line-height:1.8;color:var(--ink-2);font-size:1.06rem;margin:0}
.role-list{columns:2;column-gap:44px;max-width:1060px}
.role-list li{break-inside:avoid;margin-bottom:12px}
@media (max-width:900px){.role-list{columns:1}}
.apply-card{position:relative;overflow:hidden;padding:38px 40px;border-radius:var(--radius);
  background:linear-gradient(120deg,#251C55 0%,#1D2A5C 48%,#0C4136 100%);color:#fff}
.apply-card::before{content:"";position:absolute;width:300px;height:300px;border-radius:50%;filter:blur(70px);
  background:rgba(46,196,182,.22);top:-140px;inset-inline-end:-90px;pointer-events:none}
.apply-card>*{position:relative;z-index:1}
.apply-card h2{color:#fff;margin:14px 0 10px;font-size:1.5rem}
.apply-card p{color:rgba(255,255,255,.85);margin:0 0 16px;max-width:74ch}
.apply-card .list-check li{color:rgba(255,255,255,.88)}
.apply-card .hero-actions{margin-top:24px}
.apply-card .btn-ghost{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.42);color:#fff}
.apply-card .btn-ghost:hover{background:rgba(255,255,255,.16);border-color:#fff;color:#fff}
a.role-card{display:flex;flex-direction:column}
@media (max-width:700px){.apply-card{padding:26px 22px}}
/* v21: portrait brand card on the About page */
.about-card{display:block;width:100%;height:auto;border-radius:20px;box-shadow:0 26px 60px rgba(23,20,60,.26);margin-inline:auto}

/* ===== v22 =====
   1. Dark bands: every text style that could still fall through to the light
      palette, the section head centred, and no two dark bands stacked.
   2. No empty gap between a closing dark band and the footer.
   3. "Our story" gets the Mission/Vision shading and glow.
   4. The cursor constellation.                                              */

/* --- 1. dark-band legibility ------------------------------------------- */
.band-dark .office .rows{color:rgba(255,255,255,.84)}
.band-dark .office .rows a{color:#9FE8D2}
.band-dark .office .rows a:hover{color:#fff}
.band-dark .office .rows svg{color:#5EEAD4}
.band-dark .card .muted,.band-dark .card .small,.band-dark .office .muted{color:rgba(255,255,255,.72)}
.band-dark .map{background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.16)}
.band-dark .rows span,.band-dark .rows span span{color:inherit}
/* Text sitting directly on the dark ground. Deliberately NOT a blanket
   `.band-dark p`, that also repainted the paragraphs inside white cards that
   happen to sit in a dark band (the use-case pitfall cards), leaving white text
   on a white surface. Only elements on the band itself are touched. */
.band-dark > .wrap > p,
.band-dark > .wrap > ul > li,
.band-dark > .wrap > ol > li,
.band-dark > .wrap > div > p,
.band-dark > .wrap > div > h3,
.band-dark > .wrap > div > h4{color:rgba(255,255,255,.85)}
.band-dark > .wrap > div > h3,.band-dark > .wrap > div > h4{color:#fff}
.band-dark .list-check > li{color:rgba(255,255,255,.85)}
.band-dark .card h3,.band-dark .card h4{color:#fff}
.band-dark .btn-primary{color:#fff}

/* the heading of a full-width dark band reads as a statement, centre it */
.band-dark .section-head{align-items:center;text-align:center;max-width:820px;margin-inline:auto}
.band-dark .section-head h2,.band-dark .section-head h3{text-align:center}
.band-dark .section-head p{text-align:center}

/* two dark bands in a row are now prevented in the builder; this stays as a
   fallback so the second one can never end up transparent with white text */
.band-dark + .band-dark{background:url("img/sky-tile.svg") repeat 0 0/620px 620px,linear-gradient(120deg,#251C55 0%,#1D2A5C 48%,#0C4136 100%);padding-top:0}
.band-dark + .band-dark::before{display:none}

/* --- 2. no void above the footer --------------------------------------- */
.site-footer.flush{margin-top:0}

/* --- 3. "Our story" ----------------------------------------------------- */
/* The story and the brand panel are one box, not a card next to a picture.

   They used to be two cells of a .two-col: the text sat in a card and the
   panel floated beside it at its own fixed height, so the two never lined up
   and the panel looked like an afterthought pasted alongside. Now the card IS
   the grid, and the panel is a cell of it, which means it is exactly as tall
   as the text however long the text runs, in any language, at any width.

   The panel's SVG carries preserveAspectRatio="slice", so it fills the height
   it is given by cropping its margins rather than by stretching: a squashed
   logo would be worse than a cropped starfield. */
.story{position:relative;overflow:hidden;padding:0;border-radius:var(--radius);
  display:grid;grid-template-columns:minmax(0,1.08fr) minmax(0,.92fr);align-items:stretch;
  background:var(--surface);border:1px solid var(--line);
  box-shadow:0 10px 30px rgba(37,28,85,.08);
  transition:transform .4s cubic-bezier(.22,.8,.3,1),box-shadow .4s ease,border-color .4s ease}
.story::before{content:"";position:absolute;inset:0;border-radius:inherit;opacity:0;transition:opacity .4s ease;
  background:linear-gradient(135deg,#251C55 0%,#1D2A5C 52%,#0C4136 100%)}
.story::after{content:"";position:absolute;top:0;inset-inline-start:0;width:100%;height:4px;background:var(--grad);opacity:.9;z-index:2}
.story>*{position:relative;z-index:1}
.story-text{padding:38px 34px 34px}
/* The panel: the supplied artwork at the top, the words under it, and the
   ground continuing the colour the artwork ends on. --about-ground is written
   into the element by the build from a sample of the picture's own bottom row,
   so there is no line where one stops and the other starts. */
.story .about-card{display:flex;flex-direction:column;justify-content:flex-start;
  width:100%;height:100%;min-height:100%;margin:0;border-radius:0;box-shadow:none;overflow:hidden;
  background:var(--about-ground,linear-gradient(90deg,#1E2161,#0D314F));color:#fff}
.about-art{flex:none}
.about-art img{display:block;width:100%;height:auto}
.about-words{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:14px;padding:12px 26px 34px;text-align:center}
.ac-lines{margin:0;display:flex;flex-direction:column;gap:4px;
  font-family:var(--font-head);font-weight:800;letter-spacing:-.02em;line-height:1.1;
  font-size:clamp(1.5rem,2.5vw,2.05rem);
  background:linear-gradient(180deg,#FFFFFF 0%,#CFE9E2 62%,#7DE3C0 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent}
html[lang="ar"] .ac-lines{letter-spacing:0;line-height:1.5;font-size:clamp(1.35rem,2.2vw,1.8rem)}
.ac-rule{display:block;width:120px;height:3px;border-radius:3px;background:#7DE3C0;margin:2px auto}
.ac-strap{margin:0;font-size:.95rem;font-weight:600;color:rgba(255,255,255,.86)}
.ac-foot{margin:0;font-size:.85rem;color:rgba(255,255,255,.6)}
.story h2,.story .story-lead,.story .muted{transition:color .35s ease}
.story::before{z-index:0}
.story:hover{transform:translateY(-6px);border-color:transparent;
  box-shadow:0 22px 50px rgba(37,28,85,.28), 0 0 0 1px rgba(159,122,234,.55), 0 0 26px rgba(46,196,182,.35)}
.story:hover::before{opacity:1}
.story:hover h2{color:#fff}
.story:hover h2::after{background:linear-gradient(90deg,#7DE3C0,#A68AFF)}
.story:hover .story-lead{color:#fff}
.story:hover .muted{color:rgba(255,255,255,.82)}
@media (max-width:820px){
  .story{grid-template-columns:1fr}
  .story-text{padding:26px 22px 24px}
  /* stacked, the panel sizes to its own content: there is no text column
     beside it to take a height from */
  .story .about-card{height:auto;min-height:0}
  .about-words{padding:6px 20px 28px}
}

/* --- 4. the cursor constellation was removed in v40 ---------------------
   A canvas fixed over the whole page drew a rotating sphere of points that
   followed the pointer. It read as a blob chasing the mouse rather than as an
   effect, and it appeared only for visitors with animations enabled, which is
   most of them. Both the canvas and these rules are gone. */

/* ===== v22: the working tool at the foot of every Insight article ===== */
.tool-card{scroll-margin-top:110px;margin-top:44px;max-width:none;padding:30px 30px 26px;border-radius:var(--radius);
  background:linear-gradient(150deg,rgba(46,196,182,.07),rgba(124,77,255,.07));
  border:1px solid var(--line);box-shadow:0 12px 34px rgba(37,28,85,.10);position:relative;overflow:hidden}
.tool-card::after{content:"";position:absolute;top:0;inset-inline-start:0;width:100%;height:4px;background:var(--grad)}
.tool-card h2{font-size:1.5rem;font-weight:800;letter-spacing:-.015em;margin:12px 0 8px}
.tool-card .tool-intro{color:var(--ink-2);line-height:1.7;margin:0 0 22px;max-width:70ch}
.tool-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px 18px;margin-bottom:20px}
.tool-field{display:flex;flex-direction:column;gap:7px;font-size:.93rem;font-weight:600;color:var(--ink)}
.tool-field.wide{grid-column:1/-1}
.tool-input{display:flex;align-items:center;gap:8px;background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:0 12px;transition:border-color .2s ease,box-shadow .2s ease}
.tool-input:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px rgba(46,196,182,.18)}
.tool-input input{flex:1;min-width:0;border:0;background:none;padding:11px 0;font:inherit;font-weight:650;color:var(--ink);outline:none}
.tool-input em{font-style:normal;font-weight:600;font-size:.86rem;color:var(--ink-2);white-space:nowrap}
/* The explicit calculator asks for six amounts instead of three, and each one
   carries a sentence saying what belongs in it. Two columns of that is a wall,
   so this grid is one column and the help text sits under its own label. */
/* The explicit calculator is three rows of two, and each row is a real row.

   The outer grid holds pairs, not fields, so the gap between rows is one
   number rather than whatever the tallest paragraph in the previous row left
   behind. Inside a pair the two fields stretch to a common height and each
   one pushes its input to the bottom, which is what puts the two inputs on the
   same line however long the help text above either of them runs.

   One column until 900px. The previous threshold was 820, and at 820 the
   German employer-costs paragraph ran to nine lines in a 384px column, which
   is the "cramped" the instruction asks to switch before rather than at. */
/* min-width:0 on every grid item in this stack, and it is load-bearing.

   A grid item defaults to min-width:auto, which means it refuses to shrink
   below the min-content width of what is inside it. On the Arabic page at
   320px that produced a 379px pair inside a 230px column, sitting at
   left:-104 and clipped by the card's own overflow:hidden. The controls were
   partly off the card and there was no way to scroll to them.

   Nothing reported it. The device matrix looks for the document scrolling
   sideways, and an ancestor with overflow:hidden guarantees it never will:
   the clip is what hides the symptom the check was looking for. The geometry
   test added in v56 asserts containment inside the card instead, which is the
   property that was actually broken. */
.tool-fields-explicit{grid-template-columns:1fr;gap:0;min-width:0}
.tool-pair{display:grid;grid-template-columns:1fr;gap:18px;min-width:0}
.tool-pair > .tool-field{min-width:0}
.tool-pair + .tool-pair{margin-top:18px}
@media (min-width:900px){
  .tool-pair{grid-template-columns:1fr 1fr;gap:22px;align-items:stretch}
  .tool-pair + .tool-pair{margin-top:20px}
}
/* Label, help, input, error. The input carries margin-top:auto so it sits at
   the bottom of whatever height the pair settled on; the error sits under it
   and is out of flow while hidden, so it cannot shift the row when it appears
   on one side only. */
.tool-field{display:flex;flex-direction:column;gap:7px;align-self:stretch}
/* The input is the last thing in flow, and the error hangs below it.

   With the error in normal flow it took space at the bottom of its own field,
   and since the input is pushed down by margin-top:auto that lifted the input
   by exactly the height of the message: measured, 28.33px, on whichever side
   of the pair happened to be invalid. So an error on one field broke the
   alignment of the row it was in, which is the state the instruction asks to
   test for.

   Out of flow, with a uniform reserve underneath every field whether or not it
   has anything to say. Uniform is the point: a reserve that only appears when
   there is an error would move the row the moment one did. */
.tool-field{position:relative;padding-bottom:26px}
.tool-field .tool-input{margin-top:auto}
.tool-field .tool-err{position:absolute;inset-inline:0;top:100%;margin-top:-24px}
.tool-field label{display:block;cursor:pointer}
.tool-help{margin:0;font-size:.86rem;font-weight:400;line-height:1.6;color:#4A5468}
/* The error sits beside its own field rather than in a summary panel, and it
   is the element aria-describedby points at. #B42318 on the card ground is
   5.9:1, checked by contrast-audit.js like any other text. */
.tool-err{margin:0;font-size:.86rem;font-weight:600;line-height:1.55;color:#B42318}
.tool-actions{display:flex;gap:12px;flex-wrap:wrap;margin:0 0 20px}
/* The printed workings. The whole reason this calculator exists in its v55
   form is that a reader can add the column up by hand and land on the total,
   so the terms are a description list with the amounts aligned to the end. */
.tool-bd{margin-top:18px;padding:16px 18px;border:1px solid var(--line);border-radius:12px;background:var(--surface-2)}
.tool-bd > h3{margin:0 0 14px;font-size:.98rem;font-weight:750;color:var(--ink)}
.tool-bd-group + .tool-bd-group{margin-top:16px;padding-top:16px;border-top:1px solid var(--line)}
.tool-bd-group h4{margin:0 0 8px;font-size:.92rem;font-weight:700;color:var(--ink)}
.tool-bd dl{margin:0;display:grid;gap:6px}
.tool-bd dl > div{display:flex;justify-content:space-between;align-items:baseline;gap:14px}
.tool-bd dt{margin:0;font-size:.9rem;line-height:1.55;color:#4A5468}
.tool-bd dd{margin:0;font-size:.9rem;font-weight:700;color:var(--ink);white-space:nowrap}
.tool-bd-total{display:flex;justify-content:space-between;align-items:baseline;gap:14px;
  margin:10px 0 0;padding-top:9px;border-top:1px solid var(--line);font-size:.92rem}
.tool-bd-total span{color:#4A5468}
.tool-bd-total b{color:var(--ink);white-space:nowrap}
.tool-eq{margin-top:16px;padding-top:16px;border-top:1px solid var(--line)}
.tool-eq h4{margin:0 0 10px;font-size:.92rem;font-weight:700;color:var(--ink)}
.tool-eq p{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:baseline;gap:6px 14px;margin:0 0 8px}
.tool-eq p:last-child{margin-bottom:0}
.tool-eq span{font-size:.88rem;color:#4A5468}
/* The sum scrolls rather than clips.

   Measured on the Arabic page at 390px, the longest line ran to x=-6: with
   white-space:nowrap and no overflow rule, the total was outside its own box
   and unreadable, which defeats the point of printing the working. The line
   stays on one line, because a sum broken across two is harder to check than
   one you have to nudge sideways, but now it can be nudged. */
/* direction:ltr on the box as well as the override inside it, because a
   scrollable box starts scrolled to its own start edge. In an RTL page that
   edge is the right one, so an Arabic reader opened the sum already scrolled
   past its beginning: measured, the first term sat at x=-6 with the box
   starting at x=64. The Arabic currency word after the sum is its own run and
   still renders correctly inside an LTR line. */
.tool-eq code{display:block;max-width:100%;overflow-x:auto;direction:ltr;text-align:start;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.88rem;
  font-weight:650;color:var(--ink);background:var(--surface);border:1px solid var(--line);
  border-radius:8px;padding:3px 9px;white-space:nowrap}
.tool-eq code bdo{unicode-bidi:bidi-override}
.tool-eq code i{font-style:normal}
/* Below the width where a label and a sum fit side by side, they stack, and
   the sum gets the full width of the card before it has to scroll at all. */
@media (max-width:560px){
  .tool-eq p{display:block}
  .tool-eq span{display:block;margin-bottom:5px}
  /* And on a phone the sum wraps rather than scrolls.

     Measured at 390px, the longest line needs 382px inside a 260px box, so
     nowrap leaves the total off the right-hand edge behind a horizontal
     scroll. It is reachable, but a reader checking arithmetic should not have
     to drag the answer into view, and shrinking the type enough to fit would
     put it near 11px. Wrapping inside a bidi-override is safe: each line is
     laid out left to right like the whole run. */
  .tool-eq code{white-space:normal;overflow-x:visible;line-height:1.9}
}
.tool-cl-intro{margin:0 0 12px;font-size:.9rem;line-height:1.6;color:#4A5468}
.tool-checks{list-style:none;margin:0 0 20px;padding:0;display:grid;gap:9px}
.tool-checks.cols{grid-template-columns:repeat(auto-fit,minmax(250px,1fr))}
.tool-checks label{display:flex;gap:11px;align-items:flex-start;cursor:pointer;font-size:.96rem;line-height:1.55;color:var(--ink-2)}
.tool-checks input{margin-top:3px;width:17px;height:17px;flex:0 0 17px;accent-color:#5B4BC4;cursor:pointer}
.tool-checks label:hover span{color:var(--ink)}
.tool-ask{margin-bottom:18px}
.tool-chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.tool-chips .pill{cursor:pointer;border:1px solid var(--line);background:var(--surface);font:inherit;font-size:.85rem;transition:border-color .2s ease,color .2s ease}
.tool-chips .pill:hover{border-color:var(--accent);color:var(--ink)}
.tool-out{border-top:1px solid var(--line);padding-top:18px}
.tool-rows{display:grid;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:12px;overflow:hidden;margin-bottom:16px}
.tool-row{display:flex;justify-content:space-between;align-items:baseline;gap:16px;background:var(--surface);padding:12px 15px}
.tool-row span{color:var(--ink-2);font-size:.93rem;line-height:1.45}
.tool-row b{font-family:var(--font-head);font-size:1.12rem;font-weight:800;white-space:nowrap;
  background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.tool-row:last-child b{font-size:1.24rem}
.tool-verdict{margin:0;color:var(--ink-2);line-height:1.72;font-size:1rem}
/* What the reader sees instead of a result while a number cannot produce one.
   It replaces the rows and the verdict rather than sitting above them, because
   a wrong total with a warning over it is still a wrong total on the page. */
.tool-problems{padding:16px 18px;border:1px solid var(--warn-line,#f3d19b);border-radius:12px;background:var(--warn-bg,#fffaf0)}
.tool-problems h3{margin:0 0 8px;font-size:.98rem}
.tool-problems ul{margin:0;padding-inline-start:18px;display:grid;gap:6px;color:var(--ink-2);font-size:.92rem;line-height:1.6}
.tool-problems b{color:var(--ink)}
.tool-input input.tool-invalid{border-color:#c2410c;box-shadow:0 0 0 3px rgba(194,65,12,.12)}
/* .legal-status used to style a notice at the head of each legal page saying
   the page was a draft prepared for review. The pages are published documents
   now, so the notice is gone and its rules go with it: dead CSS is a trap for
   whoever next tries to work out what a class is for. release-audit.js fails
   the build if the wording it styled ever comes back. */

/* The neutral first-paint panel for the two boxes that open empty on purpose.
   Deliberately not the amber of .tool-problems: nothing is wrong yet, and
   painting an untouched calculator in a warning colour teaches the reader to
   ignore the warning colour when it means something. */
.tool-pending{padding:16px 18px;border:1px dashed var(--line);border-radius:12px;background:var(--surface-2)}
.tool-pending h3{margin:0 0 8px;font-size:.98rem}
.tool-pending p{margin:0 0 10px;color:var(--ink-2);font-size:.92rem;line-height:1.6}
.tool-pending ul{margin:0;padding-inline-start:18px;display:grid;gap:6px;color:var(--ink-2);font-size:.92rem;line-height:1.6}
.tool-verdict em{font-style:normal;font-weight:700;color:var(--accent-2)}
.tool-sub{font-family:var(--font-head);font-size:.78rem;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2);margin:18px 0 10px}
html[lang="ar"] .tool-sub{letter-spacing:0;font-size:.92rem}
.tool-list{margin:0 0 4px;padding-inline-start:20px;display:grid;gap:7px;color:var(--ink-2);line-height:1.6}
.tool-score{display:flex;align-items:baseline;gap:8px;margin-bottom:10px}
.tool-score b{font-family:var(--font-head);font-size:2.4rem;font-weight:800;line-height:1;
  background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.tool-score span{color:var(--ink-2);font-weight:600}
.tool-meter{height:8px;border-radius:99px;background:var(--surface-2);border:1px solid var(--line);overflow:hidden;margin-bottom:16px}
.tool-meter i{display:block;height:100%;background:var(--grad);transition:width .45s cubic-bezier(.22,.8,.3,1)}
.tool-band{font-size:1.16rem;font-weight:750;margin:0 0 8px}
.tool-docs{display:grid;gap:10px;margin-bottom:4px}
.tool-doc{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:13px 15px}
.tool-doc .pill{margin-bottom:8px;font-size:.78rem}
.tool-doc p{margin:0;color:var(--ink-2);font-size:.95rem;line-height:1.65}
.tool-note{margin:18px 0 0;padding-top:14px;border-top:1px dashed var(--line);line-height:1.6}
@media (max-width:640px){.tool-card{padding:24px 20px 20px}.tool-card h2{font-size:1.28rem}}

/* ===== v24: the talent-pool panel =====
   A speculative application used to be one grey line nobody read. It is now a
   panel in the brand's darkest gradient, so it separates cleanly from the white
   vacancy cards above it without introducing a colour outside the system. */
.talent-pool{position:relative;overflow:hidden;margin-top:26px;display:grid;
  grid-template-columns:auto 1fr;gap:22px;align-items:start;
  padding:30px 32px;border-radius:var(--radius);
  background:linear-gradient(125deg,#2B1E63 0%,#1D2A5C 46%,#0C4136 100%);
  box-shadow:0 0 0 1px rgba(159,122,234,.35),0 20px 46px rgba(37,28,85,.26),0 0 30px rgba(46,196,182,.16)}
.talent-pool::before{content:"";position:absolute;width:340px;height:340px;border-radius:50%;
  filter:blur(70px);background:rgba(46,196,182,.24);top:-170px;inset-inline-end:-90px;pointer-events:none}
.talent-pool>*{position:relative;z-index:1}
.tp-mark{width:54px;height:54px;border-radius:16px;display:grid;place-items:center;flex-shrink:0;
  background:linear-gradient(135deg,#2EC4B6,#6B46C1);color:#fff;box-shadow:0 10px 24px rgba(0,0,0,.28)}
.tp-mark svg{width:24px;height:24px}
.talent-pool h3{color:#fff;font-size:1.3rem;font-weight:800;letter-spacing:-.015em;margin:0 0 8px}
.talent-pool p{color:rgba(255,255,255,.84);line-height:1.72;margin:0 0 18px;max-width:72ch}
.tp-btn{background:#fff;color:#2B1E63;border:1px solid #fff;font-weight:700}
.tp-btn:hover{background:#F3F0FF;color:#2B1E63;border-color:#F3F0FF;transform:translateY(-2px);
  box-shadow:0 14px 30px rgba(0,0,0,.26)}
.tp-btn .arrow{color:#6B46C1}
@media (max-width:640px){
  .talent-pool{grid-template-columns:1fr;gap:16px;padding:24px 22px}
  .talent-pool h3{font-size:1.14rem}
}
/* an odd number of vacancies used to leave half a row empty; the last one is
   centred under the two columns instead, the same 2 + 2 + 1 rhythm as the hub */
@media (min-width:621px){
  .role-card:last-child:nth-child(odd){grid-column:1/-1;max-width:calc(50% - 9px);margin-inline:auto}
}

/* ===== v25: the "what this includes" grids are interactive everywhere =====
   Nothing is hidden, every word stays in the DOM and on screen. Hovering, or
   tapping on a touch device, lifts one card out of the grid and flips it to the
   brand's dark gradient, so a long list of equal boxes becomes something you
   read one item at a time. */
.grid-x{counter-reset:cx}
.grid-x > .card{position:relative;overflow:hidden;cursor:pointer;
  transition:transform .38s cubic-bezier(.22,.8,.3,1),box-shadow .38s ease,border-color .38s ease}
.grid-x > .card::before{content:"";position:absolute;inset:0;border-radius:inherit;opacity:0;
  transition:opacity .38s ease;background:linear-gradient(135deg,#251C55 0%,#1D2A5C 52%,#0C4136 100%)}
.grid-x > .card > *{position:relative;z-index:1}
.grid-x > .card h3{position:relative;transition:color .3s ease}
.grid-x > .card p{transition:color .3s ease}
/* a counter badge, but only on the cards that have no icon of their own */
.grid-x > .card:not(:has(.icon)) h3{padding-inline-start:40px}
.grid-x > .card:not(:has(.icon)) h3::before{counter-increment:cx;content:counter(cx,decimal-leading-zero);
  position:absolute;inset-inline-start:0;top:-1px;width:29px;height:29px;border-radius:9px;
  display:grid;place-items:center;font-family:var(--font-head);font-size:.72rem;font-weight:800;
  background:var(--surface-2);color:var(--ink-2);border:1px solid var(--line);
  transition:background .3s ease,color .3s ease,border-color .3s ease,transform .3s ease}
.grid-x > .card .icon{transition:transform .38s cubic-bezier(.22,.8,.3,1)}

.grid-x > .card:hover,.grid-x > .card.pinned{transform:translateY(-7px);border-color:transparent;
  box-shadow:0 22px 48px rgba(37,28,85,.26),0 0 0 1px rgba(159,122,234,.5),0 0 24px rgba(46,196,182,.30)}
.grid-x > .card:hover::before,.grid-x > .card.pinned::before{opacity:1}
.grid-x > .card:hover h3,.grid-x > .card.pinned h3{color:#fff}
.grid-x > .card:hover p,.grid-x > .card.pinned p{color:rgba(255,255,255,.86)}
.grid-x > .card:hover h3::before,.grid-x > .card.pinned h3::before{background:var(--grad);color:#fff;
  border-color:transparent;transform:scale(1.07)}
.grid-x > .card:hover .icon,.grid-x > .card.pinned .icon{transform:scale(1.08) rotate(-4deg)}
/* inside a dark band the flip needs to go the other way to stay visible */
.band-dark .grid-x > .card::before{background:linear-gradient(135deg,#3B57B8 0%,#2EC4B6 100%);opacity:0}
.band-dark .grid-x > .card:hover::before,.band-dark .grid-x > .card.pinned::before{opacity:.92}
@media (prefers-reduced-motion:reduce){.grid-x > .card,.grid-x > .card .icon{transition:none}}

/* ===== v26 ===== */

/* Numbered and bulleted points inside an article: one item per line, never a
   run-on paragraph. Native list numbering is kept so `start="4"` still works. */
.prose ol.steps-list{padding-inline-start:26px;display:grid;gap:13px;margin:20px 0}
.prose ol.steps-list > li{padding-inline-start:5px;line-height:1.75}
.prose ol.steps-list > li::marker{color:#5B4BC4;font-weight:800;font-family:var(--font-head);font-size:1.02em}
.prose ul.steps-list{list-style:none;padding-inline-start:0;display:grid;gap:13px;margin:20px 0}
.prose ul.steps-list > li{position:relative;padding-inline-start:28px;line-height:1.75}
.prose ul.steps-list > li::before{content:"";position:absolute;inset-inline-start:0;top:.62em;
  width:10px;height:10px;border-radius:3px;background:var(--grad)}
.prose .steps-list strong{color:var(--ink)}

/* What the on-premise column leaves out. The headline number is only half the
   answer, so the other half sits right under it instead of in a footnote. */
.tool-hidden{margin-top:18px;padding:16px 18px;border-radius:12px;
  background:rgba(245,158,11,.07);border:1px solid rgba(245,158,11,.34);
  border-inline-start:4px solid #D97706}
.tool-hidden h3,.tool-hidden h4{font-size:.95rem;font-weight:750;color:var(--ink);margin:0 0 10px}
.tool-hidden ul{list-style:none;margin:0;padding:0;display:grid;gap:7px}
.tool-hidden li{position:relative;padding-inline-start:20px;font-size:.93rem;line-height:1.6;color:var(--ink-2)}
.tool-hidden li::before{content:"";position:absolute;inset-inline-start:2px;top:.6em;
  width:7px;height:7px;border-radius:2px;background:#D97706}
.tool-hidden p{margin:12px 0 0;font-size:.9rem;line-height:1.62;color:var(--ink-2);
  padding-top:10px;border-top:1px dashed rgba(245,158,11,.4)}

/* The "where projects go wrong" cards: lit on the dark band, and alive on hover */
.hard{cursor:default;transition:transform .35s cubic-bezier(.22,.8,.3,1),box-shadow .35s ease,border-color .35s ease}
.band-dark .hard{box-shadow:0 0 0 1px rgba(255,255,255,.10),0 12px 30px rgba(0,0,0,.26),0 0 20px rgba(46,196,182,.14)}
.hard:hover{transform:translateY(-5px);border-inline-start-color:#7DE3C0;
  box-shadow:0 22px 46px rgba(0,0,0,.30),0 0 0 1px rgba(159,122,234,.55),0 0 26px rgba(46,196,182,.40)}
.hard h4{transition:color .3s ease}
.hard:hover h4{color:#4B2F9E}
@media (prefers-reduced-motion:reduce){.hard{transition:none}}
/* the quiet line under a field that explains why a value is refused */
.field .hint{display:block;margin-top:6px;font-size:.82rem;line-height:1.5;color:var(--ink-3)}
.field.invalid .hint{color:var(--ink-2)}

/* =====================================================================
   v30
   ===================================================================== */

/* ---- 1. the company name, set in the logo's own colours --------------
   `brandMark()` in build.js wraps every readable occurrence of the name.
   On the light ground it takes the logo gradient; on a dark ground a
   gradient in those same dark purples would disappear, so it switches to
   the light end of the palette instead. Inside a link it keeps the weight
   but hands the colour back, so link and hover states still read. */
.brandname{font-weight:650;background:var(--grad);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  -webkit-text-fill-color:transparent}
.band-dark .brandname,.hero-dark .brandname,.page-hero .brandname,
.road-band .brandname,.why-band .brandname,.site-footer .brandname,
.cta-band .brandname,.talent-pool .brandname,.roi-panel .brandname{
  background:linear-gradient(120deg,#D9CCFF 0%,#A8E6D0 60%,#7DE3C0 100%);
  -webkit-background-clip:text;background-clip:text}
/* a link already carries a colour and a hover state; a second one fights it */
a .brandname,.more .brandname,summary .brandname,.nav .brandname{
  background:none;color:inherit;-webkit-text-fill-color:currentColor;font-weight:700}
/* a heading that is already a gradient must not be re-painted inside itself */
.gradient-text .brandname,h1 .brandname,.hub-name .brandname{
  background:none;color:inherit;-webkit-text-fill-color:currentColor}

/* ---- 2. every box lights up under the pointer ------------------------
   The interactive `.grid-x` cards already flipped to the brand gradient.
   Everything else on the site sat inert, so the same affordance is given
   to every card, panel and tile: a lift, a brighter edge and a glow.
   On a dark ground a purple glow is invisible, so the glow inverts to
   white there rather than being dropped. */
/* .talent-pool was added after hover-audit.js was run against this site for
   the first time: it read as a panel to a visitor and did not respond to the
   pointer. The bands and stages the same audit reports (.cta-band,
   .slider-stage, the .tool-* internals) are deliberately left inert, because
   they are sections and controls rather than cards. The hub core is absent
   from this list on purpose: it has its own, larger hover further down. */
.card,a.card,.shot-card,.shots .shot-card,.side-card,.step,.hard,.mv-card,.story,
.tool-card,.role-card,.logo-card,.team-card,.case-feature,.stat,.road-step,
.faq details,.toc-card,.quote-card,.talent-pool{
  transition:transform .4s cubic-bezier(.22,.8,.3,1),box-shadow .4s ease,border-color .4s ease}

.card:hover,a.card:hover,.shot-card:hover,.shots .shot-card:hover,.side-card:hover,
.step:hover,.hard:hover,.mv-card:hover,.story:hover,.tool-card:hover,.role-card:hover,
.logo-card:hover,.team-card:hover,.case-feature:hover,.stat:hover,.road-step:hover,
.faq details:hover,.toc-card:hover,.talent-pool:hover{
  border-color:rgba(107,70,193,.40);
  box-shadow:0 0 0 1px rgba(107,70,193,.16),
             0 20px 44px rgba(107,70,193,.18),
             0 0 34px rgba(16,185,129,.14)}
.step:hover,.stat:hover,.logo-card:hover{transform:translateY(-4px)}
.case-feature:hover,.faq details:hover,.toc-card:hover{transform:translateY(-3px)}

/* the same gesture, inverted, wherever the ground is dark */
.band-dark .card:hover,.band-dark a.card:hover,.band-dark .step:hover,
.band-dark .stat:hover,.band-dark .shot-card:hover,.band-dark .faq details:hover,
.band-dark .hard:hover,.band-dark .side-card:hover,.band-dark .role-card:hover,
.hero-dark .card:hover,.page-hero .card:hover,.why-band .card:hover,
.road-band .road-step:hover,.roi-panel .card:hover{
  border-color:rgba(255,255,255,.58);
  box-shadow:0 0 0 1px rgba(255,255,255,.30),
             0 20px 46px rgba(0,0,0,.38),
             0 0 38px rgba(255,255,255,.24)}
/* a white card that happens to sit inside a dark band keeps the light glow:
   the glow answers the surface under the pointer, not the band around it */
.band-dark .hard:hover,.band-dark .tool-card:hover,.band-dark .case-feature:hover{
  border-color:rgba(107,70,193,.42);
  box-shadow:0 0 0 1px rgba(107,70,193,.20),0 20px 44px rgba(0,0,0,.34),0 0 32px rgba(16,185,129,.18)}

/* the interactive grid keeps its gradient flip and gains the glow */
.grid-x > .card:hover,.grid-x > .card.pinned{
  box-shadow:0 0 0 1px rgba(107,70,193,.24),0 22px 48px rgba(107,70,193,.30),0 0 36px rgba(16,185,129,.20)}
.band-dark .grid-x > .card:hover,.band-dark .grid-x > .card.pinned{
  box-shadow:0 0 0 1px rgba(255,255,255,.34),0 22px 50px rgba(0,0,0,.42),0 0 40px rgba(255,255,255,.26)}

@media (hover:none){
  .card:hover,a.card:hover,.shot-card:hover,.step:hover,.stat:hover,.logo-card:hover,
  .case-feature:hover,.faq details:hover,.toc-card:hover{transform:none;box-shadow:var(--shadow)}
}

/* ---- 3. the dashboards are a figure, not a billboard -----------------
   The supplied dashboards are 1400px wide screenshots. Rendered at the
   full text column they dominated the page and pushed the copy they were
   meant to support below the fold. They are capped and centred now, so
   the page reads as an argument with an illustration in it. */
/* One size for every section figure.

   The dashboards were arriving at 1400x933 and being drawn nearly full width,
   so a page turned into a gallery of screenshots with the argument between
   them. Worse, they were not even the same height as each other: the wide ones
   and the standard ones cropped differently, so scrolling the site felt like
   scrolling several sites.

   So there is now one width, and only one. Every section figure is a 1400x933
   source, so capping the width is enough to make them all the same rendered
   size; build.js checks that and warns if a new figure arrives at a different
   ratio, rather than leaving it to be noticed.

   An earlier version of this rule forced 16:10 with object-fit:cover, which
   made the block identical at the cost of slicing the bottom row off every
   dashboard. On a screenshot the cropped part is the evidence, so the crop
   went and the cap stayed. The width is deliberately well under the text
   measure: a screenshot supports the sentence above it, it is not the page.

   The hero and the case-study plate opt out: those are designed compositions
   that fill their own container. */
.figure-raster{max-width:620px;margin-inline:auto}
.figure-raster.figure-wide{max-width:620px}
.case-art .figure-raster,.hero-art .figure-raster{max-width:none}
@media (max-width:760px){
  .figure-raster,.figure-raster.figure-wide{max-width:100%}}

/* ---- 4. WhatsApp in the footer, beside the number it belongs to ------ */
.foot-wa{display:inline-flex;align-items:center;gap:8px}
.foot-wa svg{width:17px;height:17px;flex:none}
.site-footer .foot-wa:hover{color:#fff}

/* ---- 6. the page ground is plain -------------------------------------
   A fixed tile of the logo mark used to sit behind every page. It was
   removed in v42 on request: repeated across a white page it read as a
   pattern competing with the content rather than as texture, and it showed
   through wherever a panel lifted away from the ground on hover.

   The alternating band was translucent so the mark could read through it.
   With nothing behind it to read through, it is a flat colour: a rule that
   only made sense as a companion to something that no longer exists is the
   kind of thing that survives three redesigns and confuses the next person. */
.section-alt{background:#F4F1FA}
.section-alt + .section-alt{background:transparent}

/* ===== v32: two layout defects found by the mobile sweep =====

   1. The header row did not fit a 390px phone. Logo (198) plus the menu button
      plus the language switcher (131) plus the gaps and the wrap padding came to
      423px, so every page on the site had an 8px horizontal scroll. Nothing
      looked broken, which is why it survived this long.
   2. The talent-pool panel is a grid whose column was sized by its content's
      min-content width, so the body measured 413px inside a 342px panel. The
      panel clips its overflow, so on a phone the text was being cut off rather
      than wrapped. This is the standard CSS grid trap: `1fr` floors at
      min-content unless you say `minmax(0,1fr)`. */
@media (max-width:520px){
  .site-header .wrap{gap:10px;padding-inline:16px}
  .site-header .logo img{width:150px;height:auto}
  .lang{padding:2px}
  .lang a{padding:5px 7px;font-size:.74rem}
}
@media (max-width:380px){
  .site-header .logo img{width:128px}
  .lang a{padding:4px 6px;font-size:.7rem}
}
/* Below the width anything is designed for.

   320 CSS pixels is the narrowest the layout targets, and it is what WCAG's
   reflow criterion asks for. A phone at 200% browser zoom goes further than
   that: a 360px screen becomes a 180px viewport, and at 180 the header's three
   language links no longer fit beside the logo, so the page scrolled sideways
   by 95px.

   The fix is to let the header wrap onto a second line rather than push. It
   costs nothing at any normal width, because the rule only applies below the
   width at which the row stops fitting, and it turns an unusable page into a
   slightly taller one. */
@media (max-width:300px){
  .site-header .wrap{flex-wrap:wrap;justify-content:center;row-gap:8px}
  .site-header .logo{flex:0 0 auto}
  .lang{flex:0 0 auto}
}

.talent-pool{grid-template-columns:auto minmax(0,1fr)}
.tp-body{min-width:0}
.tp-body h3,.tp-body p{overflow-wrap:anywhere}
@media (max-width:640px){
  .talent-pool{grid-template-columns:minmax(0,1fr)}
  .tp-btn{width:100%;justify-content:center}
}

/* The footer's legal row was a nowrap flex list. German legal words are long
   ("Nutzungsbedingungen"), so on a phone the row ran past the right edge and
   the German pages carried a 14px horizontal scroll the other languages did
   not. It wraps now, in every language. */
.footer-bottom ul{flex-wrap:wrap;row-gap:8px}

/* The skip link was hidden by pushing it 999px off the inline start. In a
   right-to-left document "inline start" is the right edge, so on Arabic pages
   it was pushed off the RIGHT of the viewport and contributed to the page
   width. The clip technique hides it without occupying any space in either
   writing direction, and it still appears on keyboard focus. */
.skip{position:absolute;inset-inline-start:auto;left:8px;top:8px;
  width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
[dir="rtl"] .skip{left:auto;right:8px}
.skip:focus,.skip:focus-visible{width:auto;height:auto;overflow:visible;clip-path:none;
  inset-inline-start:8px;padding:8px 12px;z-index:100}
/* The hub columns enter from the sides: `.hub-b .reveal` starts 26px to the
   right. Once the hub collapses to a single column on a phone, that starting
   offset pushes the un-revealed spokes past the viewport, so the services
   pages carried a 2px horizontal scroll until the reader scrolled far enough
   to trigger the animation. Below the collapse the entrance is vertical, which
   is the only direction that has room. */
@media (max-width:900px){
  html.reveal-ready .hub-a .reveal,html.reveal-ready .hub-b .reveal{transform:translateY(14px)}
}

/* German compounds are long. "Managed-IT-Angebot anfragen" is 334px on one
   line, which ran the German managed-IT hero 16px past a phone screen. Buttons
   wrap their label on small screens rather than forcing the page wider, and
   the hyphenated compound is allowed to break at its hyphens. */
@media (max-width:520px){
  .btn{white-space:normal;text-align:center;overflow-wrap:break-word;hyphens:auto}
  .hero-actions{flex-wrap:wrap}
  .hero-actions .btn{max-width:100%}
}

/* The last of the mobile overflows, and the same CSS trap three times: a grid
   column declared `1fr` cannot get narrower than its content's min-content
   width. German sets long compound words, so the article sidebar's column
   resolved to 415px inside a 342px box and pushed the German service and
   article pages off the screen. `minmax(0,1fr)` lets the column shrink, and
   the long compounds are then allowed to break rather than to win. */
.split,.two-col,.side-stack,.article-grid{min-width:0}
@media (max-width:900px){
  .split,.two-col,.article-grid{grid-template-columns:minmax(0,1fr)}
  .side-stack{grid-template-columns:minmax(0,1fr);min-width:0}
  .side-card,.toc-card,.related-card{min-width:0}
  .side-card h4,.toc-card h4,.side-card h3,.prose h2,.prose h3,.prose p{overflow-wrap:break-word;hyphens:auto}
}

/* The process strips set their column count with an inline style, which beats
   every media query in this file, so on a phone a five-across or two-across
   strip stayed that way and the German labels pushed it off the screen. The
   count is a class now, and it collapses like everything else. */
.steps-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.steps-5{grid-template-columns:repeat(5,minmax(0,1fr))}
@media (max-width:1100px){.steps-5{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (max-width:760px){.steps-5{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:560px){.steps-2,.steps-5{grid-template-columns:minmax(0,1fr)}}
.steps > .step{min-width:0}
.steps .step h3,.steps .step p{overflow-wrap:break-word;hyphens:auto}

/* Last one: the article layout is a grid, and its single mobile column was
   also floored at min-content, which a long German compound sets to 376px. */
.article-layout{min-width:0}
@media (max-width:900px){
  .article-layout{grid-template-columns:minmax(0,1fr)}
  .article-layout > *{min-width:0}
  .prose li,.prose td,.prose th,.prose figcaption{overflow-wrap:break-word;hyphens:auto}
}

/* At 360px, the narrowest phone still in common use, two grids were still
   floored at min-content: the use-case "what we do / what your suppliers do"
   pair, and the contact page's accent cards. Same fix, applied where the
   breakpoint actually is. */
.roles,.grid,.grid-2,.grid-3,.grid-4{min-width:0}
.roles > .card,.grid > .card,.grid > *{min-width:0}
@media (max-width:640px){
  .roles{grid-template-columns:minmax(0,1fr)}
  .grid-2,.grid-3,.grid-4{grid-template-columns:minmax(0,1fr)}
  .card h3,.card p,.card li{overflow-wrap:break-word;hyphens:auto}
}

/* A `.reveal` wrapper inside the two-column contact layout was itself a grid
   with the same min-content floor. Rather than chase each container, every
   grid in the page body is given a floor of zero at phone widths: no layout
   on this site wants a column that refuses to shrink. */
@media (max-width:900px){
  main [class*="grid"],main .two-col,main .split,main .roles,main .steps,
  main .reveal,main .side-stack,main .article-layout{min-width:0}
  main .reveal{grid-template-columns:minmax(0,1fr)}
}

/* Below 340px German compounds ("Nutzungsbedingungen", "Konfigurationsstandard")
   are wider than the column no matter how the grid is declared, so at that size
   they are allowed to break mid-word. It is not beautiful, and it is better
   than a page that scrolls sideways. */
@media (max-width:340px){
  main li,main p,main h1,main h2,main h3,main h4,main td,main th,main .pill,main .btn{
    word-break:break-word;overflow-wrap:anywhere;hyphens:auto}
}

/* The case-study tabs sat in a nowrap row; two German labels are 2px wider
   than a 320px screen. They wrap now. */
@media (max-width:400px){
  .slider-tabs{flex-wrap:wrap}
  .slider-tab{white-space:normal;flex:1 1 auto;min-width:0}
}

/* ---- a link in running text is underlined -------------------------------
   WCAG 1.4.1: a link inside a paragraph cannot be told apart from the text
   around it by colour alone, and these were purple on white with no underline.
   Cards, buttons, navigation and the footer are excluded: those are whole
   surfaces or lists that read as links from their shape, not runs of prose
   with a link inside them. The offset keeps the rule clear of the Arabic
   descenders. */
main p > a:not(.btn):not(.more),
main li > a:not(.btn):not(.more),
.cookie p > a,.cookie span > a,.consent a,
.prose a:not(.btn){
  text-decoration:underline;
  text-decoration-thickness:.06em;
  text-underline-offset:.2em;
  text-decoration-color:color-mix(in srgb,currentColor 55%,transparent)}
main p > a:not(.btn):hover,
main li > a:not(.btn):hover,
.cookie p > a:hover,.cookie span > a:hover,.consent a:hover,
.prose a:not(.btn):hover{text-decoration-color:currentColor}
/* inside a card the whole surface is the link, so its inner links keep the
   card's own affordance rather than gaining a second one */
.card p > a:not(.btn),.card li > a:not(.btn){text-decoration:none}

/* ===== The capability hub =====

   The company in the middle, its capabilities around it, drawn in markup so it
   can be clicked, read aloud, translated and resized. It replaced a flat image
   of the same idea, which could do none of those and went stale against the
   site the moment a service was renamed.

   Direction carries meaning here, as it does everywhere else on this site: the
   column left of the core takes the purple end of the brand gradient, the one
   right of it takes the green end, and the odd card centred beneath takes the
   blend. In RTL the columns swap sides with the grid and the colours travel
   with them, which is correct: the gradient reads outward from the core in
   both directions.

   The rails are CSS, not one SVG overlay. An overlay has to be re-measured
   every time the grid reflows or the direction flips; a border on a pseudo
   element does not. */
.hub{
  display:grid;
  grid-template-columns:1fr minmax(210px,250px) 1fr;
  align-items:center;
  gap:26px 34px;
  max-width:1140px;
  margin:0 auto;
  padding:6px 0 12px}
.hub.has-foot{grid-template-rows:auto auto auto}
.hub-col{display:flex;flex-direction:column;gap:16px}
.hub-a{align-items:stretch}
.hub-b{align-items:stretch}
.hub-foot{grid-column:1/-1;align-items:center;margin-top:4px}
.hub-foot .hub-cap{max-width:420px}

/* ---- the heading rule ---- */
.hub-head{
  grid-column:1/-1;
  display:flex;align-items:center;gap:16px;
  margin-bottom:22px;
  font-family:var(--font-head);
  font-size:.82rem;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink-2);text-align:center}
.hub-head b{color:var(--accent);font-weight:800}
.hub-head-rule{
  flex:1;height:1px;position:relative;
  background:linear-gradient(90deg,transparent,rgba(107,70,193,.35))}
.hub-head-rule:last-child{background:linear-gradient(90deg,rgba(16,185,129,.35),transparent)}
.hub-head-rule::after{
  content:"";position:absolute;top:50%;width:7px;height:7px;border-radius:50%;
  transform:translateY(-50%);background:var(--accent)}
.hub-head-rule:first-child::after{inset-inline-start:0;background:#6B46C1}
.hub-head-rule:last-child::after{inset-inline-end:0;background:#10B981}
html[lang="ar"] .hub-head{letter-spacing:0;font-size:.95rem}

/* ---- a capability card ---- */
.hub-cap{
  position:relative;
  display:flex;align-items:center;gap:16px;
  padding:16px 20px;
  border-radius:16px;
  text-decoration:none;
  background:var(--surface);
  border:1px solid var(--line);
  box-shadow:0 1px 2px rgba(11,13,18,.05),0 10px 26px rgba(37,28,85,.07);
  transition:transform .38s cubic-bezier(.22,.8,.3,1),box-shadow .38s ease,border-color .38s ease}
/* the coloured spine down the leading edge, the card's family marker */
.hub-cap::before{
  content:"";position:absolute;inset-block:14px;inset-inline-start:0;width:4px;border-radius:4px;
  background:linear-gradient(180deg,#6B46C1,#3B57B8);
  transition:inset-block .38s ease,box-shadow .38s ease}
.hub-b .hub-cap::before{background:linear-gradient(180deg,#1D8FA8,#10B981)}
.hub-foot .hub-cap::before{background:linear-gradient(180deg,#3B57B8,#10B981)}

.hub-cap-ico{
  flex-shrink:0;width:46px;height:46px;border-radius:13px;
  display:grid;place-items:center;
  background:rgba(107,70,193,.08);
  color:#6B46C1;
  transition:background .38s ease,color .38s ease,transform .38s cubic-bezier(.22,.8,.3,1)}
.hub-b .hub-cap-ico{background:rgba(16,185,129,.10);color:#0B7A5E}
.hub-foot .hub-cap-ico{background:rgba(59,87,184,.09);color:#3B57B8}
.hub-cap-ico svg{width:24px;height:24px}

.hub-cap-txt{display:flex;flex-direction:column;gap:3px;min-width:0}
.hub-cap-txt b{
  font-family:var(--font-head);font-size:1rem;font-weight:750;line-height:1.25;
  color:#4C2E93;transition:color .38s ease}
.hub-b .hub-cap-txt b{color:#0B7A5E}
.hub-foot .hub-cap-txt b{color:#2F4796}
.hub-cap-txt em{
  font-style:normal;font-size:.82rem;font-weight:600;line-height:1.4;
  color:var(--ink-3);letter-spacing:.01em}
html[lang="ar"] .hub-cap-txt em{font-size:.88rem}

/* ---- the rail from the card toward the core ---- */
.hub-rail{
  position:absolute;top:50%;width:34px;height:1px;
  inset-inline-end:-34px;
  transform:translateY(-50%);
  pointer-events:none;
  border-top:1.5px dashed rgba(107,70,193,.34);
  display:flex;align-items:center;justify-content:center;gap:3px}
.hub-b .hub-rail{inset-inline-end:auto;inset-inline-start:-34px;border-top-color:rgba(16,185,129,.36)}
.hub-foot .hub-rail{
  top:auto;bottom:100%;inset-inline:50% auto;width:1px;height:26px;
  transform:translateX(-50%);
  border-top:0;border-inline-start:1.5px dashed rgba(59,87,184,.34)}
html[dir="rtl"] .hub-foot .hub-rail{transform:translateX(50%)}
.hub-rail i{
  width:4px;height:4px;border-radius:50%;background:rgba(107,70,193,.55);
  opacity:.5;transition:opacity .3s ease,transform .3s ease}
.hub-b .hub-rail i{background:rgba(16,185,129,.6)}
.hub-foot .hub-rail i{display:none}

/* ---- hovering a capability: it lights and it grows ---- */
.hub-cap:hover,.hub-cap:focus-visible{
  transform:translateY(-4px) scale(1.035);
  border-color:rgba(107,70,193,.42);
  box-shadow:0 0 0 1px rgba(107,70,193,.18),
             0 22px 46px rgba(37,28,85,.20),
             0 0 34px rgba(107,70,193,.20);
  z-index:5}
.hub-b .hub-cap:hover,.hub-b .hub-cap:focus-visible{
  border-color:rgba(16,185,129,.45);
  box-shadow:0 0 0 1px rgba(16,185,129,.20),
             0 22px 46px rgba(11,90,70,.18),
             0 0 34px rgba(16,185,129,.22)}
.hub-cap:hover::before,.hub-cap:focus-visible::before{
  inset-block:6px;box-shadow:0 0 14px rgba(107,70,193,.5)}
.hub-b .hub-cap:hover::before,.hub-b .hub-cap:focus-visible::before{box-shadow:0 0 14px rgba(16,185,129,.5)}
.hub-cap:hover .hub-cap-ico,.hub-cap:focus-visible .hub-cap-ico{
  background:linear-gradient(135deg,#6B46C1,#3B57B8);color:#fff;transform:scale(1.08)}
.hub-b .hub-cap:hover .hub-cap-ico,.hub-b .hub-cap:focus-visible .hub-cap-ico{
  background:linear-gradient(135deg,#1D8FA8,#10B981)}
.hub-foot .hub-cap:hover .hub-cap-ico,.hub-foot .hub-cap:focus-visible .hub-cap-ico{
  background:linear-gradient(135deg,#3B57B8,#10B981)}
.hub-cap:hover .hub-rail,.hub-cap:focus-visible .hub-rail{border-top-color:rgba(107,70,193,.75)}
.hub-b .hub-cap:hover .hub-rail,.hub-b .hub-cap:focus-visible .hub-rail{border-top-color:rgba(16,185,129,.8)}
.hub-cap:hover .hub-rail i,.hub-cap:focus-visible .hub-rail i{opacity:1;animation:hub-travel 1.1s ease-in-out infinite}
.hub-cap:hover .hub-rail i:nth-child(2),.hub-cap:focus-visible .hub-rail i:nth-child(2){animation-delay:.12s}
.hub-cap:hover .hub-rail i:nth-child(3),.hub-cap:focus-visible .hub-rail i:nth-child(3){animation-delay:.24s}
@keyframes hub-travel{0%,100%{transform:translateX(-3px);opacity:.45}50%{transform:translateX(3px);opacity:1}}
.hub-cap:focus-visible{outline:2px solid #7DE3C0;outline-offset:3px}

/* ---- the core ---- */
.hub-core{
  position:relative;isolation:isolate;
  aspect-ratio:1;border-radius:50%;
  display:grid;place-items:center;align-content:center;gap:4px;
  text-align:center;padding:26px;
  background:radial-gradient(120% 120% at 30% 20%,#FFFFFF 0%,#FBFAFF 55%,#F3F0FF 100%);
  border:10px solid rgba(255,255,255,.9);
  box-shadow:0 0 0 1px rgba(107,70,193,.16),0 0 34px rgba(107,70,193,.18),0 22px 54px rgba(37,28,85,.18);
  cursor:default;
  transition:transform .42s cubic-bezier(.22,.8,.3,1),box-shadow .42s ease}
.hub-core img{width:clamp(54px,32%,78px);height:auto;margin-bottom:4px}
.hub-core .hub-name{
  font-family:var(--font-head);font-weight:800;font-size:1.28rem;line-height:1.22;letter-spacing:-.01em;
  background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.hub-core em{
  font-style:normal;font-size:.7rem;font-weight:600;letter-spacing:.02em;line-height:1.35;
  color:var(--ink-3);max-width:22ch;margin-top:2px}
html[lang="ar"] .hub-core em{font-size:.8rem;letter-spacing:0}

/* the aura and the turning ring, both decorative and both behind the disc */
.hub-halo{
  position:absolute;inset:-42px;border-radius:50%;z-index:-2;pointer-events:none;
  background:radial-gradient(circle,rgba(107,70,193,.32) 0%,rgba(46,196,182,.20) 42%,rgba(46,196,182,0) 72%);
  filter:blur(10px);animation:hub-breathe 7s ease-in-out infinite;
  transition:opacity .42s ease,transform .42s ease}
@keyframes hub-breathe{0%,100%{opacity:.55;transform:scale(.96)}50%{opacity:1;transform:scale(1.04)}}
.hub-ring{
  position:absolute;inset:-14px;border-radius:50%;z-index:-1;pointer-events:none;
  background:conic-gradient(from 0deg,#2EC4B6,#3B57B8,#6B46C1,#10B981,#2EC4B6);
  -webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 6px),#000 calc(100% - 6px));
  mask:radial-gradient(farthest-side,transparent calc(100% - 6px),#000 calc(100% - 6px));
  opacity:.85;animation:hub-spin 18s linear infinite;
  transition:opacity .42s ease,filter .42s ease}
@keyframes hub-spin{to{transform:rotate(360deg)}}
/* the gradient-text rule on .hub-core spans would clip these two, which are spans */
.hub-halo,.hub-ring{-webkit-background-clip:border-box;background-clip:border-box}

/* ---- hovering the core: it lights and it grows ---- */
.hub-core:hover,.hub-core:focus-visible{
  transform:scale(1.06);
  box-shadow:0 0 0 1px rgba(107,70,193,.28),0 0 54px rgba(107,70,193,.34),0 28px 64px rgba(37,28,85,.24)}
.hub-core:hover .hub-ring,.hub-core:focus-visible .hub-ring{opacity:1;filter:saturate(1.25) brightness(1.08)}
.hub-core:hover .hub-halo,.hub-core:focus-visible .hub-halo{opacity:1;transform:scale(1.12)}
.hub-core:focus-visible{outline:2px solid #7DE3C0;outline-offset:6px}
/* while the core is lit, every rail lights with it: the diagram reads as one
   thing rather than as a disc with unrelated boxes around it */
.hub:has(.hub-core:hover) .hub-rail,.hub:has(.hub-core:focus-visible) .hub-rail{border-top-color:rgba(107,70,193,.6)}
.hub:has(.hub-core:hover) .hub-rail i,.hub:has(.hub-core:focus-visible) .hub-rail i{opacity:.9}

/* ---- entrance ---- */
html.reveal-ready .hub .reveal{opacity:0;transform:translateY(14px)}
html.reveal-ready .hub-a .reveal{transform:translateX(-26px)}
html.reveal-ready .hub-b .reveal{transform:translateX(26px)}
html.reveal-ready .hub .reveal.in{opacity:1;transform:none}

/* ---- collapsed ----
   Below the breakpoint the two columns become one, so the rails have nothing
   to point at and the core stops being a disc. The core moves to the top,
   because on a phone the diagram reads as a list with a heading rather than as
   a picture with a centre. */
@media (max-width:900px){
  .hub{grid-template-columns:1fr;gap:14px}
  .hub-a,.hub-b,.hub-foot{align-items:stretch}
  /* Scoped to html.reveal-ready to match the rule it overrides. The base
     sideways entrance gained that prefix when the reveal state was gated on
     JavaScript being alive; this override did not, so it lost the cascade and
     the spokes went back to starting 26px off-screen, which is a 2px
     horizontal scroll on a 390px phone. Same specificity, later in the file,
     wins: both halves have to move together. */
  html.reveal-ready .hub-a .reveal,html.reveal-ready .hub-b .reveal{transform:translateY(14px)}
  .hub-rail{display:none}
  .hub-ring{display:none}
  .hub-halo{inset:-24px}
  .hub-foot{margin-top:0}
  .hub-foot .hub-cap{max-width:none}
  /* the core moves above the cards, but the heading has to stay above the
     core: order:-1 on the core alone put the disc before its own title */
  .hub-head{order:-2}
  .hub-core{
    aspect-ratio:auto;border-radius:var(--radius);order:-1;padding:20px;
    display:flex;align-items:center;gap:14px;text-align:start}
  .hub-core img{width:46px;margin:0}
  .hub-core em{max-width:none}
  .hub-core:hover,.hub-core:focus-visible{transform:none}
  .hub-cap:hover,.hub-cap:focus-visible{transform:translateY(-2px)}
  .hub-head{margin-bottom:16px;gap:10px}
}
@media (prefers-reduced-motion:reduce){
  .hub-halo,.hub-ring,.hub-rail i{animation:none}
  .hub-core:hover,.hub-core:focus-visible,.hub-cap:hover,.hub-cap:focus-visible{transform:none}
}

/* ===== v44: the step carousel =====

   A row of four or five numbered steps was being squeezed into four or five
   columns, about 210px each, and the words broke mid-syllable. The markup
   below is a progressive enhancement: the row ships as the grid it has always
   been, and only when site.js has wired the buttons does .car-ready turn it
   into a track. A reader without JavaScript keeps every step on screen instead
   of a frozen row with two dead buttons under it. */
.carousel{position:relative}
.carousel .car-controls{display:none}
.carousel.car-ready .car-controls{display:flex;align-items:center;justify-content:center;gap:10px;margin-top:20px}

.carousel.car-ready .steps.car-track{
  display:flex;grid-template-columns:none;
  overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;scroll-behavior:smooth;
  /* the cards carry a shadow, and a scroll container clips it: the padding is
     inside the scrolling box, so it makes room rather than adding a gap */
  padding:6px 2px 14px;margin:-6px -2px -14px;
  scrollbar-width:none;-ms-overflow-style:none}
.carousel.car-ready .steps.car-track::-webkit-scrollbar{display:none}
.carousel.car-ready .steps.car-track > *{flex:0 0 calc((100% - 2*18px)/3);scroll-snap-align:start}
@media (max-width:900px){.carousel.car-ready .steps.car-track > *{flex:0 0 calc((100% - 18px)/2)}}
@media (max-width:620px){.carousel.car-ready .steps.car-track > *{flex:0 0 100%}}

.car-btn{
  flex:0 0 auto;width:40px;height:40px;border-radius:50%;
  display:grid;place-items:center;cursor:pointer;
  background:var(--surface);border:1px solid var(--line-2);color:var(--accent);
  transition:background .25s ease,border-color .25s ease,color .25s ease,transform .25s ease}
.car-btn svg{width:20px;height:20px}
.car-btn:hover:not(:disabled){background:var(--accent);border-color:var(--accent);color:#fff;transform:translateY(-2px)}
.car-btn:disabled{opacity:.38;cursor:default}
/* the arrows are drawn pointing left and right; in Arabic the start of the row
   is on the right, so the glyph is mirrored rather than swapped in the markup */
[dir="rtl"] .car-btn svg{transform:scaleX(-1)}

.car-dots{display:flex;align-items:center;gap:8px}
/* The dot a reader sees is 8px; the dot a reader can hit is 24px. The padding
   is the target and background-clip keeps the paint off it, which is what lets
   a control this small still meet the 24px minimum without looking like a
   button. The gap shrinks to match, so the row reads the same as before. */
.car-dot{
  width:8px;height:8px;padding:8px;box-sizing:content-box;border-radius:50%;cursor:pointer;
  background:var(--line-2);background-clip:content-box;border:0;
  transition:background .25s ease,width .25s ease}
.car-dot[aria-current="true"]{width:24px;border-radius:999px;background:var(--grad);background-clip:content-box}
.car-dots{gap:0}

.car-track:focus-visible{outline:2px solid var(--accent);outline-offset:4px;border-radius:10px}
.band-dark .car-track:focus-visible{outline-color:#7DE3C0}

.band-dark .car-btn{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.24);color:#fff}
.band-dark .car-btn:hover:not(:disabled){background:rgba(255,255,255,.18);border-color:#fff}
.band-dark .car-dot{background:rgba(255,255,255,.28)}
.band-dark .car-dot[aria-current="true"]{background:linear-gradient(135deg,#9F7AEA,#34D399)}

@media (prefers-reduced-motion: reduce){
  .carousel.car-ready .steps.car-track{scroll-behavior:auto}
  .car-btn:hover:not(:disabled){transform:none}
}
/* every card already fits at this width, so the controls are not a choice */
.carousel.car-static .car-controls{display:none}

/* The "In practice" panel under a row of stat tiles carried max-width:78ch, a
   reading measure. It made the panel stop well short of the last tile above it
   and it was not centred either, so it read as a box that had come loose. The
   panel now spans the row, which is what lines it up, and the measure moves to
   the paragraph inside it, where it belongs: the edges match and the prose
   still breaks at a comfortable line length. */
.example-panel{margin-top:22px}
.example-panel p{max-width:78ch}

/* The phone field is a country selector and a national number, side by side.
   The selector is narrow because a dial code is short, and the number takes the
   rest; below 420px they stack, because two controls on one line at that width
   leaves the number four characters wide. */
/* ---- the phone row ----

   The selector is sized by its own content and the number takes the rest.

   What this replaces: `max-width:44%` on the selector, inside a field that was
   itself half a row, inside a column that was half the page. Forty-four per
   cent of a half of a half is about 120px, and "United Arab Emirates (+971)"
   needs roughly 230px at this font size — so the country the visitor had
   chosen was clipped mid-word, and the number beside it had room for about
   eight digits of a ten-digit number. The fix is width, not a smaller font and
   not "UAE": `width:auto` with `flex:0 1 auto` lets the select ask for what its
   longest option needs, and min-width holds a floor for the shortest.

   The longest option across the three languages is the German
   "Vereinigte Arabische Emirate (+971)", which is what the 20rem ceiling is
   sized for; past that the select wraps to its own line rather than squeezing
   the number. */
.phone-row{display:flex;gap:10px;align-items:start;flex-wrap:wrap}
/* flex-shrink is 0 deliberately. With it at 1 the select still gave ground to
   the number and still clipped its own text, about twenty pixels short in
   German; the point of this row is that the country the visitor chose is
   readable, so the select keeps its content width and the number is what
   yields. 24rem is sized for the longest option in the three languages,
   "Vereinigte Arabische Emirate (+971)", with headroom. */
.phone-row select{flex:0 0 auto;width:auto;min-width:11rem;max-width:24rem}
/* The dial code. Not an input: it is derived from the country and there is
   nothing to type into it. Sized and coloured like the fields it sits between
   so the row reads as one control, and forced left-to-right so the Arabic page
   shows "+971" rather than the browser's bidi reordering of it. */
.dial{flex:0 0 auto;align-self:stretch;display:flex;align-items:center;
  padding-inline:12px;border:1px solid var(--line-2);border-radius:10px;
  background:var(--surface-2);color:var(--ink-2);font-size:.97rem;
  font-variant-numeric:tabular-nums;white-space:nowrap}
/* The number is always the wider of the two and never shrinks below something
   a ten-digit number fits in; when it cannot have that, the two stack. */
.phone-row input{flex:1 1 14rem;min-width:0}

/* An inline SVG inside a button has no intrinsic width of its own until it is
   given one, and in an Arabic page the flow direction made the shortfall
   visible: the arrow was being measured as wider than the space the button had
   left for it, so the button's own scrollWidth exceeded its client width on
   every Arabic page. Fixing the icon's box fixes it in all three languages. */
.btn .arrow{flex:0 0 auto;width:18px;height:18px}
/* Narrow: two full-width rows rather than two cramped columns. The breakpoint
   is on the container, not the viewport, because this row is inside a form that
   is inside a column, and the viewport says nothing useful about how wide any
   of those are. */
@container form (max-width:30rem){
  .phone-row{flex-wrap:nowrap;flex-direction:column;align-items:stretch}
  .phone-row select,.phone-row input{max-width:none;min-width:0;width:100%}
  /* Stacked, the dial code goes on the same line as the number rather than
     taking a line of its own: it is three characters. */
  .phone-row{display:grid;grid-template-columns:auto minmax(0,1fr);gap:10px}
  .phone-row select{grid-column:1 / -1}
  .dial{align-self:auto}
  /* Every pixel of the row goes to the option text once the row is a single
     full-width control: the card's own padding comes in, and the select's
     padding comes down to the minimum the arrow needs. This is the difference
     between the longest English option fitting at 360px and being cut. */
  .phone-row select{padding-inline-start:11px;padding-inline-end:30px}
}
/* The card's own padding, narrowed when the column it sits in is narrow.

   A note on what this rule is, because a review read it as a container trying
   to style itself: the named container is the plain <div> that wraps the form
   inside .contact-cols, not the form. .form.card is that div's child, so this
   is an ordinary descendant selector inside a container query and it applies.
   Measured rather than argued: at a 320px viewport the form's computed
   padding-inline is 18px, and at 1280px it is 26px. */
@container form (max-width:30rem){
  .form.card{padding-inline:18px}
}
/* One font step, at one width, in every language.

   The German option used to be "Vereinigte Arabische Emirate (+971)", 35
   characters in a closed select, which needed about 327px in a form 274px
   wide. That produced a language-specific font reduction: German only, below a
   22rem form. The reduction is gone, because the cause is gone. The dial code
   moved out of the selector and sits beside the number, so the longest option
   in any language is now a country name on its own.

   What remains is one step at 17.5rem of form width, which is a 320px screen,
   and it applies to every language equally. Measured at 320 with the dial code
   split out: English 137px of text in 191px of room, Arabic 127, German 184.
   Nothing is clipped and nothing is abbreviated. */
@container form (max-width:17.5rem){
  .phone-row select{font-size:.80rem}
}
/* Fallback for a browser with no container queries: the same stacking, keyed
   to the viewport width at which the form is narrow on every layout. */
@supports not (container-type: inline-size){
  @media (max-width:520px){
    .phone-row{flex-direction:column}
    .phone-row select,.phone-row input{max-width:none;min-width:0;width:100%}
  }
}

/* ---- the contact page's two columns ----

   Scoped to .contact-cols, so .two-col keeps its 1fr 1fr everywhere else on
   the site. The form was getting half the width and the "what happens next"
   card was getting the other half, which is how the phone row ran out of room
   in the first place: the widest thing on the page was sharing space with a
   card of four short lines. */
.contact-cols{grid-template-columns:minmax(0,72fr) minmax(0,28fr);gap:36px;container-type:inline-size;container-name:contactcols}
.contact-cols>div:first-child{container-type:inline-size;container-name:form}
/* The side card carries less padding and a smaller measure than a full card,
   because it is a narrow column now. The body text keeps its size: shrinking
   the font is how a layout problem gets hidden rather than fixed. */
.contact-cols .side-note{padding:20px 22px}
.contact-cols .side-note h2{font-size:1.05rem;margin-bottom:10px}
.contact-cols .side-note ul{gap:8px}
.contact-cols .side-note li{font-size:.92rem;line-height:1.55}
@media (max-width:1023px){
  /* One column, form first, side card under it. */
  .contact-cols{grid-template-columns:minmax(0,1fr);gap:24px}
}
.field .hint{display:block;margin-top:6px;font-size:.82rem;color:var(--ink-3);line-height:1.5}

/* .case-meta on a dark hero.

   The component was written for the case-study page, where it sits on white,
   so its label is --ink-3 and its value is --ink. The vacancy pages put the
   same component inside the dark page hero, and those two colours are dark
   grey on dark navy: the location, the employment type and the reference were
   on the page and unreadable.

   The contrast audit did not catch it because that audit asks whether text is
   effectively invisible, at a ratio of 2.2, and this measured just above it.
   It was invisible to a reader and visible to the tool, which is the gap
   between a threshold and a person. */
.page-hero .case-meta,.hero-dark .case-meta,.band-dark .case-meta{border-top-color:rgba(255,255,255,.22)}
.page-hero .case-meta dt,.hero-dark .case-meta dt,.band-dark .case-meta dt{color:rgba(255,255,255,.72)}
.page-hero .case-meta dd,.hero-dark .case-meta dd,.band-dark .case-meta dd{color:#fff}


/* ===================== the floating WhatsApp shortcut =====================

   Fixed at the physical bottom-right of the viewport on every public page, in
   every language.

   PHYSICAL right, not inset-inline-end, and this is the one control on the
   site where the logical property would be wrong. Everything else mirrors on
   the Arabic pages, which is correct for content that reads right to left.
   This is not content: it is a control the owner asked to be in the
   bottom-right corner, and a reader of an Arabic page reaches for the same
   corner a reader of an English page does, because their thumb is in the same
   place. If somebody later "fixes" this to inset-inline-end, the Arabic pages
   move it to the left and tests/browser.js fails, which is the intended
   outcome.

   STACKING ORDER, documented because a floating control is exactly the thing
   that ends up on top of something important six months later:

       10   ordinary page content and its own stacking contexts
       40   this shortcut
       50   the sticky site header
       60   the open compact navigation menu
       70   the Practices panel
       80   the Managed IT flyout

   So it sits above the page and below every navigation layer. An open menu
   covers it; it never covers an open menu.

   It makes no network request until it is clicked. A wa.me href is a plain
   link, so nothing reaches Meta while the page is merely open, and the browser
   test asserts exactly that. */
/* The dock is the landmark and holds no space: its only child is fixed, so it
   collapses to nothing in the flow and changes no layout. */
.wa-dock{display:block;height:0}
/* A circle at every width, including the desktop.

   Through v57 this was a labelled pill on anything wider than 560px and a
   circle below it. The label was the same three words on every page of the
   site, in a control that is recognisable from its mark alone, and it made a
   fixed element roughly 180px wide sit over whatever the reader was reading.
   Now there is one shape, and the accessible name is the aria-label on the
   link, which was always where it came from: hiding the text changes nothing
   a screen reader hears.

   52 by 52 rather than the 44 that WCAG asks for. 44 is the floor for a target
   somebody has to hit; a control that sits over somebody's reading deserves to
   be unmistakable rather than merely legal. */
.wa-float{
  position:fixed;
  right:calc(18px + env(safe-area-inset-right, 0px));
  bottom:calc(18px + env(safe-area-inset-bottom, 0px));
  z-index:40;
  display:inline-flex;align-items:center;justify-content:center;gap:0;
  width:52px;height:52px;min-width:52px;min-height:52px;
  padding:0;
  border-radius:50%;
  background:#25D366;color:#08331B;
  line-height:1;
  box-shadow:0 6px 20px rgba(0,0,0,.22);
  text-decoration:none;
}
/* #08331B on #25D366 is 8.2:1. White on that green is 2.1:1, which is the
   combination almost every WhatsApp button on the web uses and which fails AA
   outright. Dark ink on the brand green keeps both the recognition and the
   contrast. */
/* Out of the way of the enquiry form.

   Moved rather than hidden with display:none, so nothing reflows and the
   control comes back in place. pointer-events:none stops it intercepting a tap
   meant for a field underneath while it is on its way out, and visibility
   takes it out of the tab order in browsers that honour that before the
   script's tabIndex lands. */
.wa-float.is-tucked{
  transform:translateY(140%);
  opacity:0;
  visibility:hidden;
  pointer-events:none;
  /* Leaving is instant, arriving is animated.

     The transition is read from the state being moved INTO, so declaring none
     here makes the retreat immediate while the rule below still animates the
     return. That matters: with a 220ms ease on the way out there is a fifth of
     a second in which the control is still over the form, and a full-scroll
     scan measured 54 intersections inside exactly that window. Brief is not
     the same as absent, and a finger is faster than an animation. */
  transition:none;
}
.wa-float{transition:transform .22s ease, opacity .22s ease, visibility .22s}
@media (prefers-reduced-motion:reduce){
  .wa-float{transition:none}
}
.wa-float:hover{background:#1FBB59;color:#08331B;transform:translateY(-1px)}
.wa-float:active{transform:translateY(0)}
.wa-float:focus-visible{outline:3px solid var(--accent);outline-offset:3px}
.wa-float-icon{display:inline-flex;flex:0 0 auto;width:24px;height:24px}
.wa-float-icon svg{width:100%;height:100%;fill:currentColor}
html[dir="rtl"] .wa-float{flex-direction:row}

/* Phone widths and short landscape viewports: a round icon button, 52 by 52.

   Through v56 this happened in two steps. The label shortened to one word at
   560px, and the control became a circle only below 380px, which left a pill
   about 120px wide across the whole range of screens people actually hold a
   phone at. The Arabic full-page screenshot at 390px is what settled it: the
   shortened pill sits over the body text of an article, and no amount of
   clearance at the foot of the page helps, because the text it covers is in
   the middle of the document.

   A circle is roughly a fifth of that width, so the most it can cover is the
   corner of one line rather than a phrase. The shortcut stays where the owner
   asked for it, at the physical bottom-right, and everything else about it is
   unchanged: the accessible name is the aria-label on the link and never came
   from the visible text, the ink-on-green contrast is the same 8.2:1, the
   safe-area offsets still apply, it is still focusable with a visible ring, it
   still retreats while the enquiry form is on screen, and it still makes no
   request before it is clicked.

   52px rather than 44: 44 is the floor for a tap target, and a control that
   sits over somebody's reading deserves to be unmistakable rather than merely
   legal. */
/* Phone widths and short landscape viewports keep the tighter inset, so the
   circle sits closer to the corner where a thumb reaches it. The shape itself
   no longer changes here, because it no longer changes anywhere. */
@media (max-width:560px),(max-height:420px){
  .wa-float{
    right:calc(12px + env(safe-area-inset-right, 0px));
    bottom:calc(12px + env(safe-area-inset-bottom, 0px));
  }
}
@media (prefers-reduced-motion:reduce){
  .wa-float:hover,.wa-float:active{transform:none}
}

/* Clearance, so the shortcut never sits on top of something a visitor needs.

   A fixed control cannot be moved out of the way by the page, so the page is
   given room underneath it instead. This is the last band of the footer and
   the foot of the contact form, which are the two places it would otherwise
   overlap. */
/* The clearance is the same at every width, and the measurement is why.

   It used to be reduced to 28px from 900px upwards, on the assumption that a
   wider footer puts its bottom row clear of a corner control. It does not: the
   bottom row is a flex row with the legal links at its end, which is the same
   corner the shortcut is fixed to. Measured against the delivered pages, the
   pill covered "Legal notice" at 1440, "Terms" and "Legal notice" at 1280, and
   all three of Privacy, Terms and Legal notice at 1024 and at 900. Of every
   link on the site those are the three that must not be unreachable.

   76px was what the control needed when it was 44px tall at an 18px offset,
   with 14px to spare. v58 made it 52px at every width, so its reach from the
   bottom of the viewport is 70px and the spare fell to six. 104px restores a
   real margin: 18 of offset, 52 of button, and 34 left over, so the row above
   is clear rather than nearly clear.

   tests/browser.js hit-tests every footer link against the shortcut at
   thirteen widths in three languages, and asks the question that matters:
   whether there is any scroll position at which the link is visible and the
   point at its own centre belongs to it. */
.site-footer .footer-bottom{padding-bottom:104px}
.form.card .form-actions,.form.card [type="submit"]{scroll-margin-bottom:88px}
