/* ============================================================
   Webify.com — site-local components
   ------------------------------------------------------------
   Blocks the shared component library does not (yet) provide:
     .steps    — numbered "how we work" process
     .tier     — product / package card with a starting price
     .case     — Work grid card, homepage surface (challenge/solution/result)
     .compare  — products-at-a-glance table
     .split-choice — the "what do you sell?" two-door band
   Loaded AFTER components.css, BEFORE rtl.css.

   Everything uses CSS logical properties so LTR/RTL mirror from
   one codebase. The few direction-neutral fixes live at the
   bottom of this file under [dir="rtl"].
   ============================================================ */

/* ------------------------------------------------------------
   Section rhythm — unified, seamless
   ------------------------------------------------------------
   One token drives every section's vertical padding, so the gap
   between any two sections is identical down the page. Tighter
   than the library default (which ran up to 6.5rem a side, i.e.
   13rem between sections) without becoming cramped.

   Boundaries between light sections are ONE value off white --
   --ground-tint, l95. They used to be imperceptible (l99), which is
   why six pages invented their own stronger tint instead. See the
   ground ladder below.
   ------------------------------------------------------------ */
/* --section-pad and --section-pad-lg now live in tokens.css, alongside
   the ground ladder, so a client theme can retune the rhythm with the
   colours. This file no longer declares them. */

.section { padding-block: var(--section-pad); }

/* A section that IS a content unit rather than a band between units —
   the homepage storefront, a work entry, "what happens next". One step
   more air, never a third clamp. */
.section--feature { padding-block: var(--section-pad-lg); }

/* --------------------------------------------------------------
   The ground ladder, applied
   --------------------------------------------------------------
   --muted and --tint were two different near-white values (#fbfbfd at
   l99 and #f0f0fa at l96) doing one job: the first was so close to
   white it never read as a band at all, the second was mixed into
   white and landed on a lavender that fought the page's own h210
   furniture. Both now resolve to the single --ground-tint.

   They are kept as separate CLASS NAMES because the markup on nine
   pages uses both and half of it is generated; they are aliases, not
   two values.
   -------------------------------------------------------------- */
.section--muted,
.section--tint { background: var(--ground-tint); }

.section--tint { border-block: 1px solid var(--ground-rule); }

/* Muted text on a tint fails AA at gray-500 — see --muted-on-tint. */
.section--muted .section__subtitle,
.section--tint .section__subtitle,
.section--muted .lede,
.section--tint .lede { color: var(--muted-on-tint); }

/* ADJACENCY GUARD.
   Now that the tint is actually visible (l95 rather than l99), two
   tinted bands in a row merge into one undifferentiated block — the
   exact failure the Contact colour pass hit and recorded. Any tinted
   band immediately following another falls back to white, so the
   ladder alternates no matter what order the markup puts sections in.
   This is why the product pages' sections 6 and 7, and the homepage's
   sections 8 and 9, need no per-page markup edit. */
:is(.section--muted, .section--tint) + :is(.section--muted, .section--tint) {
  background: var(--ground);
  border-block-color: transparent;
}
:is(.section--muted, .section--tint) + :is(.section--muted, .section--tint) :is(.section__subtitle, .lede) {
  color: var(--text-muted);
}

/* ------------------------------------------------------------
   The About landing band
   ------------------------------------------------------------
   Every other page opens on a lit ground: .showcase (home + the three
   product pages), .wkhero (Work), .cthero (Contact). About opened on a
   bare .section — flat #ffffff, the only landing band on the site with
   no ground at all, which is exactly what reads as "the wrong colour".

   The three prototype layers that carry those heroes are per-page
   sheets About does not load, so the recipe is repeated here rather
   than shared. It is Contact's, not the homepage's, and deliberately:
   About is a text page whose landing band is followed by a tinted
   .section--muted, the same shape as Contact, and the Contact pass
   already established that a bronze radial directly above a cool tint
   band fights it. A vertical fade landing on EXACTLY --ground-tint at
   the band's own base means the band below continues the value instead
   of starting a new one — no seam, and no RTL mirroring needed.

   Only the radial is a physical percentage, so only it flips for AR.

   Selected structurally instead of by a new class: build.py generates
   this page, and css/ is hand-maintained, so a markup hook would mean
   a regeneration. `> .container > .page-intro` is unique to About in
   both languages — 404.html is the other bare first .section and has
   no intro, and the case study's opening band is .section--inverse
   wrapping .cs-hero. */
main > section.section:first-child:has(> .container > .page-intro) {
  position: relative;
  background: var(--ground);
  overflow-x: clip;
  --wash-a: 82%;
  background-image:
    radial-gradient(58vw 40vw at var(--wash-a) 9%,
      color-mix(in srgb, var(--brand-primary) 6%, transparent), transparent 66%),
    linear-gradient(to bottom, var(--ground) 55%, var(--ground-tint) 100%);
}
html[dir="rtl"] main > section.section:first-child:has(> .container > .page-intro) {
  --wash-a: 18%;
}

/* Headline block sat 3rem off its content; a touch tighter reads as one
   unit with the section it introduces. */
.section__head { margin-block-end: clamp(1.75rem, 3.5vw, 2.5rem); }

/* On the dark bands, .section__subtitle and .lede keep their light-theme
   greys (--text-muted / --text-body) and all but disappear against navy.
   Lift them to a readable tint. */
.section--inverse .section__subtitle,
.section--inverse .lede,
.section--inverse > .container > p {
  color: color-mix(in srgb, var(--color-white) 80%, transparent);
}

/* ------------------------------------------------------------
   Hero — light treatment
   ------------------------------------------------------------
   The library ships a saturated blue full-bleed hero. Webify's own
   site runs it light instead: white ground like every other
   section, navy type, and the summary card as a deep-navy block
   so the brand colour anchors the composition rather than
   flooding it. Applies to the home, product and 404 heroes.
   ------------------------------------------------------------ */
.hero {
  background: var(--surface);
  color: var(--text-body);
  /* Top padding is tighter than the library default: the sticky header
     already supplies separation, so the library's clamp(3.5rem,10vw,7rem)
     left a visible dead band under it. Bottom keeps the section rhythm. */
  padding-block: var(--hero-pad-top) var(--section-pad);
}
/* rtl.css re-declares .hero's gradient and loads after this file,
   so the RTL override needs the extra element specificity to win. */
html[dir="rtl"] .hero { background: var(--surface); }

.hero__eyebrow { color: var(--brand-primary); }
.hero__title { color: var(--text-strong); }

/* The emphasis line was a gold gradient clipped to the text. Gold on
   white only reaches ~3:1, so it becomes solid royal blue here. */
.hero__title .accent {
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  color: var(--brand-primary);
  -webkit-text-fill-color: var(--brand-primary);
}

.hero__lead { color: var(--text-body); }
.hero__stat-value { color: var(--text-strong); }
.hero__stat-label { color: var(--text-muted); }

/* The card is now the dark element on the page. */
.hero__card {
  background: var(--surface-inverse);
  border-color: transparent;
  color: var(--color-gray-200);
  box-shadow: var(--shadow-lg);
}
.hero__card h2,
.hero__card h3 { color: var(--color-white); }
/* Product pages put the starting price inside the card — keep it white. */
.hero__card .hero__stat-value { color: var(--color-white); }
.hero__card .tier__price-from { color: color-mix(in srgb, var(--color-white) 70%, transparent); }
/* The meta row defaults to muted grey, which is too low-contrast on navy. */
.hero__card .tier__meta-item { color: color-mix(in srgb, var(--color-white) 82%, transparent); }
.hero__card .tier__meta-item svg { color: var(--brand-accent-light); }

/* The library centres split columns; when the two sides differ in height
   that leaves the headings misaligned. Product pages want them level. */
.split--top { align-items: start; }

/* ------------------------------------------------------------
   Rail-step — the shared numbered-step primitive
   ------------------------------------------------------------
   A step sits under a hairline whose own segment fills from the
   reading edge on hover/focus-within. Two components were carrying
   byte-identical copies of this mechanism: `.step` here and
   `.ctstep` in prototype-contact.css. Same position/inset/::before/
   inline-size 0->100% transition, same logical properties, written
   twice.

   The MECHANISM is shared; the LOOK is not. Three custom properties
   carry everything the two components disagreed about, so Contact's
   thinner rail and gold-on-navy palette are unchanged -- they are now
   arguments to one implementation rather than a second copy of it.
   ------------------------------------------------------------ */
.rail-step,
.step,
.ctstep {
  position: relative;
  padding-block-start: var(--rail-pad, var(--space-lg));
  border-block-start: var(--rail-size, 3px) solid var(--rail-track, var(--border));
}
.rail-step::before,
.step::before,
.ctstep::before {
  content: "";
  position: absolute;
  inset-block-start: calc(-1 * var(--rail-size, 3px));
  inset-inline-start: 0;
  inline-size: 0;
  block-size: var(--rail-size, 3px);
  background: var(--rail-fill, var(--brand-accent));
  transition: inline-size var(--transition-slow);
}
.rail-step:hover::before,
.rail-step:focus-within::before,
.step:hover::before,
.step:focus-within::before,
.ctstep:hover::before,
.ctstep:focus-within::before { inline-size: 100%; }

@media (prefers-reduced-motion: reduce) {
  :is(.rail-step, .step, .ctstep)::before { transition: none; }
}

/* ------------------------------------------------------------
   Steps — how we work
   ------------------------------------------------------------ */
.steps {
  display: grid;
  gap: var(--space-lg);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  counter-reset: step;
}

/* The rail mechanism comes from .rail-step; .step only names its size
   and colours. `.step` composes it via @extend-by-selector below rather
   than a second class in the markup, because this markup is generated. */
.step {
  --rail-size: 3px;
  --rail-track: var(--border);
  --rail-fill: var(--brand-accent);
  --rail-pad: var(--space-lg);
}

.step__num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  border-radius: var(--radius-full);
  background: var(--brand-wash);
  color: var(--brand-primary);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--fs-sm);
  margin-block-end: var(--space-sm);
}
.step__title {
  font-size: var(--fs-lg);
  margin-block-end: var(--space-2xs);
}
.step__text {
  font-size: var(--fs-sm);
  line-height: var(--leading-body);
  color: var(--text-muted);
}
.step__meta {
  display: inline-block;
  margin-block-start: var(--space-sm);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brand-accent-dark);
}

/* On the inverse band the muted greys need lifting */
.section--inverse .step { border-block-start-color: color-mix(in srgb, var(--color-white) 22%, transparent); }
.section--inverse .step__num {
  background: color-mix(in srgb, var(--color-white) 14%, transparent);
  color: var(--color-white);
}
.section--inverse .step__text { color: var(--color-gray-300); }
.section--inverse .step__meta { color: var(--brand-accent-light); }

/* ------------------------------------------------------------
   Tier — product / package card
   ------------------------------------------------------------ */
.tier {
  position: relative;   /* anchors .tier__flag on the top edge */
  display: flex;
  flex-direction: column;
  padding: var(--space-xl);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
  transition: transform var(--transition-base), box-shadow var(--transition-base), border-color var(--transition-base);
}
.tier:hover {
  transform: translateY(var(--lift-hover-y));
  box-shadow: var(--lift-hover-shadow);
  border-color: color-mix(in srgb, var(--brand-primary) 30%, var(--border));
}

/* Sits ON the card's top edge, out of the flow — otherwise it pushes the
   featured card's heading, price and feature list down and they no longer
   line up with the two cards beside it.
   `inset-inline: 0` + `margin-inline: auto` centres it in both LTR and RTL;
   a translateX(-50%) would drift off-centre when the direction flips. */
.tier__flag {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  inline-size: fit-content;
  margin-inline: auto;
  transform: translateY(-50%);
  z-index: 1;
  box-shadow: 0 2px 10px -2px color-mix(in srgb, var(--brand-primary) 55%, transparent);
  padding: 0.28em 0.8em;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: var(--radius-full);
  background: var(--brand-primary);
  color: var(--brand-on-primary);
}

.tier__name {
  font-size: var(--fs-xl);
  margin-block-end: var(--space-2xs);
}
.tier__for {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  margin-block-end: var(--space-lg);
  line-height: 1.5;
}

/* Once the cards sit side by side, reserve three lines here so the price
   block starts at the same height in all three — otherwise a longer
   audience line in one card drags its price, meta row and feature list
   out of step with its neighbours. Below this width the cards stack and
   there is nothing to align against. */
@media (min-width: 60rem) {
  .tier__for { min-block-size: calc(3 * 1.5em); }
}

.tier__price {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding-block-end: var(--space-lg);
  margin-block-end: var(--space-lg);
  border-block-end: 1px solid var(--border);
}
.tier__price-from {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.tier__price-value {
  font-family: var(--font-heading);
  font-size: var(--fs-2xl);
  font-weight: 700;
  color: var(--text-strong);
  letter-spacing: var(--tracking-tight);
  line-height: 1.1;
}
.tier__price-note {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* Takes the price block's slot on the homepage, where the tiers lead with the
   outcome instead of a figure. Same rhythm and rule, so the cards still line
   up against the package pages, which keep their prices. */
.tier__outcome {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding-block-end: var(--space-lg);
  margin-block-end: var(--space-lg);
  border-block-end: 1px solid var(--border);
}
.tier__outcome-label {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.tier__outcome-value {
  font-family: var(--font-heading);
  font-size: var(--fs-xl);
  font-weight: 700;
  color: var(--text-strong);
  letter-spacing: var(--tracking-tight);
  line-height: 1.2;
}
.tier__outcome-note {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
/* The three notes differ in length, unlike the identical price note they
   replaced, so reserve two lines side by side to keep the meta rows and
   feature lists in step. */
@media (min-width: 60rem) {
  .tier__outcome-note { min-block-size: calc(2 * 1.5em); }
}

.tier__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-md);
  margin-block-end: var(--space-lg);
  font-size: var(--fs-sm);
}
.tier__meta-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  color: var(--text-muted);
}
.tier__meta-item svg {
  inline-size: 1rem;
  block-size: 1rem;
  color: var(--brand-accent);
  flex: none;
}

.tier__list {
  display: grid;
  gap: var(--space-sm);
  margin-block-end: var(--space-xl);
  font-size: var(--fs-sm);
  line-height: var(--leading-snug);
}
.tier__list li {
  display: grid;
  grid-template-columns: 1.15rem 1fr;
  gap: var(--space-xs);
  align-items: start;
}
.tier__list svg {
  inline-size: 1.15rem;
  block-size: 1.15rem;
  color: var(--brand-accent);
  margin-block-start: 0.1rem;
}
.tier__cta { margin-block-start: auto; }

/* "What's NOT included" — deliberately plain, never alarming */
.tier__excl {
  display: grid;
  gap: var(--space-xs);
  padding: var(--space-lg);
  background: var(--surface-muted);
  border-radius: var(--radius-lg);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.tier__excl li {
  display: grid;
  grid-template-columns: 1.15rem 1fr;
  gap: var(--space-xs);
  align-items: start;
}
.tier__excl svg {
  inline-size: 1.15rem;
  block-size: 1.15rem;
  color: var(--color-gray-400);
  margin-block-start: 0.1rem;
}

/* ------------------------------------------------------------
   Compare — products at a glance
   ------------------------------------------------------------ */
.compare-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.compare {
  inline-size: 100%;
  /* Wide enough that the three equal columns still fit an unwrapped
     "Digital Operations" header; below this the wrapper scrolls. */
  min-inline-size: 52rem;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  text-align: start;
  table-layout: fixed;
}
.compare th,
.compare td {
  padding: var(--space-md) var(--space-lg);
  border-block-end: 1px solid var(--border);
  text-align: start;
  vertical-align: middle;
}
/* Fixed layout takes its columns from the first row, so the widths are
   declared once on the head. The label column takes what its longest label
   needs; the three package columns split the rest evenly, so no package
   gets more room than its neighbours and no cell wraps to fit. */
.compare thead th:first-child { inline-size: 13rem; }
.compare thead th:not(:first-child) { inline-size: calc((100% - 13rem) / 3); }
.compare thead th {
  background: var(--surface-muted);
  font-family: var(--font-heading);
  font-size: var(--fs-base);
  color: var(--text-strong);
  white-space: nowrap;
}
.compare tbody th {
  font-weight: 600;
  color: var(--text-strong);
  white-space: nowrap;
}
.compare tbody tr:last-child th,
.compare tbody tr:last-child td { border-block-end: 0; }
.compare tbody tr:hover { background: color-mix(in srgb, var(--brand-primary) 4%, transparent); }

/* ------------------------------------------------------------
   Case — Work grid card
   ------------------------------------------------------------ */
.case {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  overflow: hidden;
  transition: transform var(--transition-base), box-shadow var(--transition-base);
}
.case:hover { transform: translateY(var(--lift-hover-y)); box-shadow: var(--lift-hover-shadow); }

.case__media {
  position: relative;
  aspect-ratio: 16 / 10;
  background: var(--surface-muted);
  overflow: hidden;
}
.case__media img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* "Concept redesign" vs "Client work" */
.case__tag {
  position: absolute;
  inset-block-start: var(--space-sm);
  inset-inline-start: var(--space-sm);
  padding: 0.28em 0.8em;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--text-strong);
  box-shadow: var(--shadow-sm);
}
.case__tag--client {
  background: var(--brand-primary);
  color: var(--brand-on-primary);
}

.case--demo .case__media img { object-fit: contain; }
.case--demo .case__tag {
  inset-block-start: auto;
  inset-block-end: var(--space-sm);
}

.case__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: var(--space-xl);
}
.case__industry {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--brand-accent-dark);
  margin-block-end: var(--space-2xs);
}
.case__title {
  font-size: var(--fs-lg);
  margin-block-end: var(--space-lg);
}

/* Challenge → Solution → Result */
.case__csr {
  display: grid;
  gap: var(--space-md);
  font-size: var(--fs-sm);
  line-height: var(--leading-body);
}
.case__csr > div {
  padding-inline-start: var(--space-md);
  border-inline-start: 2px solid var(--border);
}
.case__csr dt {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-block-end: 0.15rem;
}
.case__csr dd { color: var(--text-body); }
.case__csr > div:last-child { border-inline-start-color: var(--brand-accent); }

.case--placeholder .case__csr dd { color: var(--text-muted); font-style: italic; }

/* ------------------------------------------------------------
   Split choice — "what do you sell?"
   ------------------------------------------------------------ */
.split-choice {
  display: grid;
  gap: var(--space-lg);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
}
.choice {
  display: flex;
  flex-direction: column;
  padding: var(--space-2xl);
  border-radius: var(--radius-xl);
  background: var(--surface);
  border: 1px solid var(--border);
  transition: transform var(--transition-base), box-shadow var(--transition-base);
}
.choice:hover { transform: translateY(var(--lift-hover-y)); box-shadow: var(--lift-hover-shadow); }
.choice__icon {
  inline-size: 2.75rem;
  block-size: 2.75rem;
  color: var(--brand-primary);
  margin-block-end: var(--space-md);
}
.choice__title { font-size: var(--fs-xl); margin-block-end: var(--space-xs); }
.choice__text {
  font-size: var(--fs-sm);
  line-height: var(--leading-body);
  color: var(--text-muted);
  margin-block-end: var(--space-lg);
}
.choice__examples {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-block-start: auto;
}

/* ------------------------------------------------------------
   RTL mobile overflow fix  (library bug — see README)
   ------------------------------------------------------------
   The library's off-canvas mobile drawer parks itself outside the
   viewport with `inset-inline-end: 0` + `translateX(±100%)`. In
   LTR it lands to the RIGHT of the viewport, which browsers do not
   treat as scrollable overflow. In RTL it lands to the LEFT, which
   IS the forward scroll direction — so every Arabic page picked up
   ~320px of phantom horizontal scroll and rendered shifted/clipped.

   `overflow-x: clip` (not `hidden`) contains it without creating a
   scroll container, so the sticky header keeps working.
   ------------------------------------------------------------ */
html { overflow-x: clip; }
body { overflow-x: clip; }

/* ------------------------------------------------------------
   Header fit
   ------------------------------------------------------------
   Webify's nav carries a CTA and a language toggle alongside the
   links. Left to the library defaults the row wraps onto two lines
   at common laptop widths, so tighten the desktop nav specifically.
   These blocks are the other half of the drawer breakpoint set in
   components.css — move one and you must move the other.
   ------------------------------------------------------------ */
@media (min-width: 861px) {
  .site-header__inner { gap: var(--space-md); }
  .nav__list { gap: var(--space-md); }
  .nav__link,
  .nav__trigger { white-space: nowrap; font-size: var(--fs-sm); }
  .lang-toggle { white-space: nowrap; }
}
@media (min-width: 861px) and (max-width: 1200px) {
  .nav__list { gap: var(--space-sm); }
  .header__actions .btn { padding-inline: 1.1em; }
}

/* ------------------------------------------------------------
   Header: logo and CTA must not deform under pressure
   ------------------------------------------------------------
   Both are flex items, so by default they shrink before anything
   else gives. The logo has a fixed height and auto width, so
   shrinking squashed it horizontally — measured down to 0px wide
   at 900px, i.e. no logo at all — and the CTA wrapped onto two
   lines (three in Arabic). Neither may absorb overflow: the
   drawer breakpoint above is what makes room.
   ------------------------------------------------------------ */
.brand,
.brand__logo { flex: none; }
.header__actions .btn { white-space: nowrap; }

/* Whenever the drawer is in play the nav is position:fixed, so it leaves the
   flex row and the header packs brand + actions to the start — stranding the
   menu button ~31px short of the edge. Push the actions group to the far
   inline edge so the button sits where a thumb reaches for it: hard right in
   English, hard left in Arabic. */
@media (max-width: 860px) {
  .header__actions { margin-inline-start: auto; }

  /* backdrop-filter makes .site-header the containing block for its own
     position:fixed descendants, which sizes the drawer against the 73px
     header instead of the viewport — it was being clipped, with its links
     scrolling inside a sliver. Dropping the blur here restores viewport
     sizing; the header goes fully opaque so nothing shows through without
     it. Desktop keeps the blur. */
  .site-header { backdrop-filter: none; background: var(--surface); }

  /* The drawer's ceiling is the header's floor. components.css runs the
     panel from inset-block: 0 and pads its content down past the header
     row, which put the header ON the panel — one white surface, with the
     logo and the language toggle apparently floating inside the menu.
     Starting the panel at the header's height instead leaves the header
     as its own band above it. Both are fixed to the viewport, so the two
     stay locked together whatever the page does underneath. */
  .nav {
    inset-block-start: var(--header-h);
    padding-block-start: var(--space-lg);
  }
  .brand,
  .header__actions { position: relative; z-index: 1; }

  /* An inline box takes its layout height from the line box, so the
     drawer's 12px of link padding never occupied any space and the rows
     overlapped each other by ~7px. Solutions was the only row that
     measured its own padding — .nav__trigger is a button — which is why
     it alone looked like it had been given room. As blocks, every row is
     the same 55px and the 8px list gap is the only thing between them. */
  .nav__link { display: block; }
}

/* Phones: the quote CTA moves out of the header and into the drawer, where it
   gets a full-width button instead of squeezing the logo. That frees enough
   room for the full lockup, so the header reads Webify rather than just the
   mark. The floating WhatsApp button still covers one-tap contact. */
@media (max-width: 540px) {
  .header__actions > .btn { display: none; }
}
.nav__cta { display: none; }
@media (max-width: 540px) {
  .nav__cta { display: block; margin-block-start: var(--space-lg); }
}

/* The current-page underline is pinned to the link's padding box, which in
   the drawer is now a full-width row — the gold rule ran the whole width of
   the panel and read as a divider. Underlining the text itself hugs the word
   at any length, in either language, and needs no offsets to maintain.
   Hover is dropped with it: a tap leaves :hover stuck on, so on a phone the
   rule would sit under whatever was last touched. Same trade .nav__trigger
   makes two blocks down — colour carries state in the drawer. */
@media (max-width: 860px) {
  .nav__link::after { content: none; }
  .nav__link[aria-current="page"] {
    text-decoration: underline;
    text-decoration-color: var(--brand-accent);
    text-decoration-thickness: 2px;
    text-underline-offset: 0.32em;
  }
}

/* The drawer opened onto a band of empty space above the first link. A small
   eyebrow label fills it and tells you what the panel is. Drawer only — the
   desktop nav is a horizontal row with no room for it. */
.nav__heading { display: none; }
@media (max-width: 860px) {
  .nav__heading {
    display: block;
    margin-block: 0 var(--space-md);
    /* Links carry padding-inline, so match it or the label hangs left. */
    padding-inline: var(--space-xs);
    font-family: var(--font-heading);
    font-size: var(--fs-xs);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
  }
  /* Arabic is cursive — letter-spacing pulls the joins apart. */
  [dir="rtl"] .nav__heading { letter-spacing: 0; }
}

/* ------------------------------------------------------------
   Solutions: dropdown on desktop, accordion in the drawer
   ------------------------------------------------------------
   The three product pages sit behind one nav entry. There is a
   single markup tree — a <button class="nav__trigger"> followed
   by a <ul class="nav__menu"> — and the two behaviours are
   purely a matter of which block below is live:

     above the breakpoint  the menu is an absolutely-positioned
                           dropdown hanging off the trigger;
     at or below it        the menu is an indented accordion
                           panel inside the drawer.

   site.js never repeats the breakpoint: it reads the mode back
   off the menu button's computed display, so this file stays the
   only place the number is written.

   Everything here is logical-property based, so Arabic mirrors
   without a single [dir="rtl"] override — the dropdown hangs from
   the trigger's leading edge (left in English, right in Arabic)
   and the accordion indents toward the reading direction.
   ------------------------------------------------------------ */
.nav__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  position: relative;
  /* A <button> keeps none of the surrounding type or colour. The
     `font` shorthand can't do this job: it resets font-size too, and
     sitting later in this file it beat the desktop `font-size:
     var(--fs-sm)` set on .nav__link/.nav__trigger above, so "Solutions"
     rendered a step larger than every other header link. Longhands
     only, with the size stated outright — the drawer block below
     raises it to --fs-lg for the accordion row. */
  padding: 0.25rem 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 500;
  line-height: inherit;
  color: var(--text-body);
  cursor: pointer;
}
/* Same gold underline .nav__link gets, on the same terms — hover,
   and "you are on a page inside this section". */
.nav__trigger::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: -2px;
  height: 2px;
  background: var(--brand-accent);
  transform: scaleX(0);
  transform-origin: inline-start;
  transition: transform var(--transition-base);
}
.nav__trigger:hover,
.nav__trigger[data-nav-current] { color: var(--text-strong); }
.nav__trigger:hover::after,
.nav__trigger[data-nav-current]::after { transform: scaleX(1); }
.nav__chevron {
  width: 1em;
  height: 1em;
  transition: transform var(--transition-base);
}
.nav__trigger[aria-expanded="true"] .nav__chevron { transform: rotate(180deg); }
.nav__menu { margin: 0; padding: 0; }
/* Inside the panel the row highlight does the work; the underline
   would sit under a padded row and read as a stray rule. */
.nav__sublink::after { content: none; }

/* ---- Desktop: dropdown ---- */
@media (min-width: 861px) {
  .nav__item--menu { position: relative; }
  /* Both underlines are ::after pinned to their element's border box, so
     the two boxes have to be the same shape or the lines land on different
     rows. .nav__link is an inline box — its border box is the font's
     content area (17px at --fs-sm) plus padding — while .nav__trigger is a
     <button>, an inline-flex block container sized by the full 1.7
     line-height (25.5px). That 8.5px of leading is split above and below,
     which sat the Solutions underline ~3px lower than every other one.
     Matching the display type makes both boxes 33.5px, no magic offset.
     Top-level links only: .nav__sublink is display:block in the panel and
     carries no underline at all. */
  .nav__list > li > .nav__link { display: inline-flex; align-items: center; }
  /* The tag is in the selector on purpose: reset.css carries
     `ul[role="list"] { padding: 0 }`, which outranks a bare class and
     would flatten the panel's padding against its border. */
  ul.nav__menu {
    position: absolute;
    inset-block-start: calc(100% + 0.85rem);
    inset-inline-start: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    min-width: max-content;
    padding: var(--space-xs);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-0.4rem);
    transition: opacity var(--transition-fast),
                transform var(--transition-fast),
                visibility var(--transition-fast);
  }
  /* The 0.85rem of air between trigger and panel is dead ground —
     a pointer travelling down to the first item would leave the
     item and drop the hover. Bridge it with a transparent strip so
     the trip from trigger to link never breaks contact. */
  .nav__menu::before {
    content: "";
    position: absolute;
    inset-inline: 0;
    inset-block-start: -0.85rem;
    height: 0.85rem;
  }
  .nav__trigger[aria-expanded="true"] + .nav__menu {
    opacity: 1;
    visibility: visible;
    transform: none;
  }
  /* Without JS the trigger can never be pressed, which would strand
     three real pages behind a dead button. site.js marks the document
     when it wires the menu up, so these two rules are the unscripted
     fallback only — with JS on, aria-expanded stays the single source
     of truth and cannot disagree with what is on screen. */
  html:not(.js-nav) .nav__item--menu:hover .nav__menu,
  html:not(.js-nav) .nav__item--menu:focus-within .nav__menu {
    opacity: 1;
    visibility: visible;
    transform: none;
  }
  .nav__sublink {
    display: block;
    padding: var(--space-2xs) var(--space-sm);
    border-radius: var(--radius-md);
    white-space: nowrap;
  }
  .nav__sublink:hover,
  .nav__sublink:focus-visible,
  .nav__sublink[aria-current="page"] {
    background: var(--surface-muted);
    color: var(--text-strong);
  }
}

/* ---- Drawer: accordion ---- */
@media (max-width: 860px) {
  .nav__item--menu { display: block; }
  .nav__trigger {
    width: 100%;
    justify-content: space-between;
    padding: var(--space-sm) var(--space-xs);
    font-size: var(--fs-lg);
  }
  /* The library insets .nav__link's underline to its text in the drawer.
     The trigger is a full-width row with the chevron pushed to the far
     end, so the same rule would stretch a gold line clear across the
     panel and read as a divider — and a tap leaves :hover stuck on, so it
     would sit there after the accordion opens. Colour carries the state
     here instead, exactly as it does for the sublinks. */
  .nav__trigger::after { content: none; }
  /* Same mechanism as .faq__panel: collapsed to height 0 here, and
     site.js animates it to the measured scrollHeight. Nothing may sit on
     the block axis or it would show through while closed. */
  .nav__menu {
    overflow: hidden;
    height: 0;
    transition: height var(--transition-base);
  }
  /* The indent goes on the link rather than the panel, so the tap target
     still spans the full width of the drawer while the text steps in
     under its parent. Top-level links carry --space-xs, so the step is a
     clear --space-md; being inline-start it mirrors in Arabic. */
  .nav__sublink {
    display: block;
    padding: var(--space-xs);
    padding-inline-start: calc(var(--space-xs) + var(--space-md));
    font-size: var(--fs-base);
    color: var(--text-muted);
  }
  .nav__sublink[aria-current="page"] { color: var(--text-strong); }
}

@media (prefers-reduced-motion: reduce) {
  .nav__menu,
  .nav__chevron { transition: none; }
}

/* Smallest phones (≤360px): the language toggle drops to its globe icon.
   The aria-label carries the accessible name either way. */
@media (max-width: 360px) {
  .lang-toggle span { display: none; }
  .lang-toggle { padding-inline: 0.6em; }
  .site-header__inner { gap: var(--space-sm); }
}

/* The three hero stats have uneven label lengths, so flex-wrap drops the
   last one onto its own row. A grid keeps them in one even band and lets
   the long labels wrap inside their own column instead. */
.hero__stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: var(--space-lg);
}

/* ------------------------------------------------------------
   Small shared helpers
   ------------------------------------------------------------ */
.lede {
  font-size: var(--fs-lg);
  line-height: var(--leading-body);
  color: var(--text-body);
  max-width: 46rem;
}
/* Page heroes share one measure across every page, so the eyebrow,
   headline and lede all start on the container's inline edge — flush
   with the logo above them — instead of the page inventing its own
   narrower, centred column. .page-body carries the same measure for
   long-form prose that follows the intro (About). */
.page-intro,
.page-body { max-width: 52rem; }
.prose > * + * { margin-block-start: var(--space-md); }
.prose h3 { margin-block-start: var(--space-xl); }
.prose ul { display: grid; gap: var(--space-xs); padding-inline-start: 1.15rem; list-style: disc; }
.anchor-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-block-start: var(--space-lg);
}

/* ------------------------------------------------------------
   Arabic headings: no negative tracking, more leading
   ------------------------------------------------------------
   Outfit's tracking is set for Latin display type. Applied to Arabic it
   pulls joined letterforms together and crowds the marks; Cairo also
   needs more leading than 0.98 for its ascenders and diacritics.

   Five separate `[dir="rtl"] ... { letter-spacing: 0 }` rules already
   existed -- .msite__h1, .showcase--product .showcase__title,
   .priceband__value, .nav__heading, .tier__outcome-value -- one per
   component, added as each was noticed. The rule was known; it was just
   never stated once. This states it once, for every heading role.

   :is() takes its most specific argument's weight, so this lands at
   (0,2,1) and outranks the single-class rules in the prototype layers
   that load after this file. .cs-hero__title sits on navy and is
   included for the same reason.
   ------------------------------------------------------------ */
html[dir="rtl"] :is(h1, h2, h3, h4,
                    .section__title, .showcase__title, .wkhero__title,
                    .cthero__title, .cs-hero__title, .wkentry__name,
                    .shopdemo__title, .wknote__title, .ctnext__title,
                    .ctclose__title, .ctform__title, .wkhero__deck) {
  letter-spacing: 0;
}
html[dir="rtl"] :is(h1, .showcase__title, .wkhero__title,
                    .cthero__title, .cs-hero__title) {
  line-height: var(--h-leading-rtl);
}

/* ------------------------------------------------------------
   RTL — direction-neutral fixes only
   ------------------------------------------------------------ */

/* The step rail grows from the reading edge, so it must start
   from the right in RTL. inset-inline-start already handles the
   origin; nothing else to flip. Table cells use text-align:start.
   Listed here so future edits keep the same discipline. */

/* Arabic numerals in the price sit better with slightly looser
   tracking at large sizes. */
[dir="rtl"] .tier__price-value,
[dir="rtl"] .tier__outcome-value { letter-spacing: 0; }

/* Latin product names inside Arabic prose stay LTR. */
[dir="rtl"] .tier__name bdi,
[dir="rtl"] .compare bdi { unicode-bidi: isolate; direction: ltr; }

/* ============================================================
   Motion system — one theme, two triggers
   ------------------------------------------------------------
   Every animated thing on the site speaks the same four-word
   vocabulary, at the same duration and easing:

     rise   fade up 14px           — the default for text and cards
     line   slide out of a mask    — page headlines and section titles
     wipe   grow from the reading edge, clip-path — primary CTAs
     panel  fade + rise + settle   — dark or elevated blocks

   Two triggers use it:

     LOAD    the header and the top-of-page block (.hero /
             .page-intro) assemble themselves once, on load, as a
             fixed stagger. CSS animations only.
     SCROLL  everything below runs on the existing .reveal /
             .is-visible system in base.css + main.js. This layer
             only retimes it to the same duration and easing and
             adds a sibling stagger, so a section arrives as a
             sequence instead of all at once.

   Four rules keep this safe:

   1. LOAD motion is CSS animation with `animation-fill-mode:
      both`, so the from-state exists at first paint. No JS is
      needed to hide anything and a JS failure cannot leave the
      hero blank. site.js only *upgrades* headlines from a fade
      to a per-line mask.
   2. SCROLL motion adds no new hidden-without-JS surface. It
      restyles `.reveal`, which already owns that trade-off, and
      never hides anything on its own.
   3. The whole block sits inside `prefers-reduced-motion:
      no-preference`. For anyone who asked for less motion there
      are no animation declarations at all, not merely disabled
      ones.
   4. Buttons animate opacity and clip-path only, never transform.
      A filled transform animation outranks `.btn:hover`'s
      translateY in the cascade and would kill the hover lift
      permanently.
   ============================================================ */
:root {
  --intro-dur: 720ms;
  --intro-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --intro-step: 80ms;   /* stagger between siblings in the load intro */
  --reveal-step: 80ms;  /* stagger between siblings on scroll */
}

@media (prefers-reduced-motion: no-preference) {

  /* ---------- The vocabulary ---------- */
  @keyframes intro-rise {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
  }
  @keyframes intro-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
  @keyframes intro-panel {
    from { opacity: 0; transform: translateY(18px) scale(0.985); }
    to   { opacity: 1; transform: none; }
  }
  /* Inside a masked line wrapper. 120% clears the line box plus the
     descender padding the mask adds below it. */
  @keyframes intro-line {
    from { transform: translateY(120%); }
    to   { transform: translateY(0); }
  }
  /* A CTA grows from its leading edge like a filling bar. The end
     state insets outward so the button's accent glow is never
     clipped by the retained clip-path. */
  @keyframes intro-wipe {
    from { clip-path: inset(0 100% 0 0); }
    to   { clip-path: inset(-40%); }
  }
  /* clip-path has no logical-property form, so RTL needs its own. */
  @keyframes intro-wipe-rtl {
    from { clip-path: inset(0 0 0 100%); }
    to   { clip-path: inset(-40%); }
  }

  /* ---------- Masked line reveal ----------
     Built by site.js. The mask carries padding below the baseline so
     descenders and Arabic marks are not shaved off by overflow:hidden;
     the negative margin takes that space back out of the layout. */
  .line-mask {
    display: block;
    overflow: hidden;
    padding-block-end: 0.16em;
    margin-block-end: -0.16em;
  }
  .line-mask__inner { display: block; }

  /* ============================================================
     LOAD — header, hero, page intro
     ============================================================ */

  /* .site-header and .site-header__inner are deliberately left alone —
     a transform on either would become the containing block for the
     position:fixed mobile nav drawer. */
  .site-header .brand           { animation: intro-rise var(--intro-dur) var(--intro-ease) 40ms both; }
  .site-header .nav__list       { animation: intro-fade var(--intro-dur) var(--intro-ease) 100ms both; }
  .site-header .header__actions { animation: intro-rise var(--intro-dur) var(--intro-ease) 160ms both; }

  /* The navy card lands first, empty. */
  .hero__card { animation: intro-panel calc(var(--intro-dur) + 160ms) var(--intro-ease) both; }

  .hero__eyebrow,
  .page-intro > .section__eyebrow {
    animation: intro-rise var(--intro-dur) var(--intro-ease) 60ms both;
  }

  /* Headline fallback: a plain fade, used as-is when JS has not split
     it. It animates opacity only, so when site.js does split the title
     the per-line transform layers on top of an in-flight fade instead
     of fighting it. */
  .hero__title,
  .page-intro > h1 {
    animation: intro-fade var(--intro-dur) var(--intro-ease) 120ms both;
  }
  .hero .hero__title .line-mask__inner,
  .page-intro > h1 .line-mask__inner {
    animation: intro-line 820ms var(--intro-ease)
               calc(120ms + var(--intro-i, 0) * 90ms) both;
  }

  .hero__lead,
  .page-intro > .lede {
    animation: intro-rise var(--intro-dur) var(--intro-ease) 320ms both;
  }

  /* ---------- The hero CTA wipe ----------
     This was scoped to `.hero .hero__actions`, and MATCHED NOTHING on
     any page. The redesign replaced every hero: the homepage and the
     three product pages use .showcase__actions, Work uses
     Contact uses .cthero__actions, and 404 has a bare .hero__actions
     with no .reveal. Only the case study still had
     a .hero, and its .hero holds no CTA. So the site's single most
     important button -- the primary call to action above the fold --
     never wiped on any of the nine pages, while the closing band at
     the foot of the page did, via the scroll rule further down.

     Work and the case study are NOT in this list and that is correct:
     .wkhero__caption is a <figcaption> with no buttons, and neither
     page has an above-fold CTA at all. Their primaries are below the
     fold and already wipe through the scroll rule.

     The fix is a list of the real hero-action containers rather than
     one structural guess. Each sets --cta-wipe-delay to land after its
     own page's intro has placed the button; the default suits a hero
     whose copy runs on the standard stagger.

     .hero__actions:not(.reveal) is 404: the closing-band markup on the
     other eight pages always carries .reveal and belongs to the scroll
     system, so :not() cleanly separates the two without new markup --
     which matters because 404 is a GENERATED page and a class added
     here by hand would be gone on the next build. */
  .hero .hero__actions,
  .showcase__actions,
  .cthero__actions,
  .hero__actions:not(.reveal) { --cta-wipe-delay: 420ms; }

  .showcase__actions  { --cta-wipe-delay: 460ms; }  /* container rises at 440 */
  .cthero__actions    { --cta-wipe-delay: 300ms; }  /* actions rise at 270 */

  :is(.hero .hero__actions,
      .showcase__actions,
      .cthero__actions,
      .hero__actions:not(.reveal)) .btn--primary {
    animation: intro-wipe 1050ms var(--intro-ease) var(--cta-wipe-delay, 420ms) both;
  }
  html[dir="rtl"] :is(.hero .hero__actions,
                      .showcase__actions,
                      .cthero__actions,
                      .hero__actions:not(.reveal)) .btn--primary {
    animation-name: intro-wipe-rtl;
  }

  /* The ghost sibling fades in just behind its primary. */
  :is(.hero .hero__actions,
      .showcase__actions,
      .cthero__actions,
      .hero__actions:not(.reveal)) :is(.btn--ghost, .btn--secondary) {
    animation: intro-fade var(--intro-dur) var(--intro-ease)
               calc(var(--cta-wipe-delay, 420ms) + 140ms) both;
  }

  .hero__stats > * {
    animation: intro-rise var(--intro-dur) var(--intro-ease)
               calc(640ms + var(--intro-i, 0) * var(--intro-step)) both;
  }
  .hero__stats > :nth-child(2) { --intro-i: 1; }
  .hero__stats > :nth-child(3) { --intro-i: 2; }

  /* The card fills in last. The list is skipped here and staggered per
     item below, so items are not double-animated inside a fading parent. */
  .hero__card > :not(.tier__list) {
    animation: intro-rise var(--intro-dur) var(--intro-ease)
               calc(520ms + var(--intro-i, 0) * var(--intro-step)) both;
  }
  .hero__card > :nth-child(2) { --intro-i: 1; }
  .hero__card > :nth-child(3) { --intro-i: 2; }
  .hero__card > :nth-child(4) { --intro-i: 3; }
  /* The card CTA is a .btn — opacity only, per rule 4. */
  .hero__card > .btn {
    animation-name: intro-fade;
    animation-delay: 940ms;
  }
  .hero__card .tier__list li {
    animation: intro-rise var(--intro-dur) var(--intro-ease)
               calc(600ms + var(--intro-i, 0) * 70ms) both;
  }
  .hero__card .tier__list li:nth-child(2) { --intro-i: 1; }
  .hero__card .tier__list li:nth-child(3) { --intro-i: 2; }
  .hero__card .tier__list li:nth-child(4) { --intro-i: 3; }
  .hero__card .tier__list li:nth-child(5) { --intro-i: 4; }

  /* ============================================================
     SCROLL — every section below the fold
     ============================================================ */

  /* base.css owns the from-state (opacity 0, translateY 24px) and the
     .is-visible switch. This retimes it to the load intro's curve and
     shortens the travel so a stagger of eight reads as a sequence
     rather than a wave. */
  .reveal {
    transform: translateY(18px);
    transition-duration: var(--intro-dur);
    transition-timing-function: var(--intro-ease);
    transition-delay: calc(var(--intro-i, 0) * var(--reveal-step));
  }

  /* Sibling stagger. nth-child is deliberate: every container that
     holds .reveal children — .section__head, .grid, .steps, .faq,
     .split-choice — holds nothing else, so position is index. */
  .reveal:nth-child(2) { --intro-i: 1; }
  .reveal:nth-child(3) { --intro-i: 2; }
  .reveal:nth-child(4) { --intro-i: 3; }
  .reveal:nth-child(5) { --intro-i: 4; }
  .reveal:nth-child(6) { --intro-i: 5; }
  .reveal:nth-child(7) { --intro-i: 6; }
  .reveal:nth-child(8) { --intro-i: 7; }

  /* Eight FAQ rows at the full step would trail for 560ms. */
  .faq .reveal { --reveal-step: 45ms; }

  /* Section titles get the same masked line reveal as the page
     headline, gated on .is-visible so it fires with the fade. */
  .section__title.is-visible .line-mask__inner {
    animation: intro-line 820ms var(--intro-ease)
               calc(var(--intro-i, 0) * 90ms) both;
  }
  /* The container fade would otherwise clip the mask's overflow at the
     wrong moment; the title only needs to fade, the lines carry the
     movement. */
  .section__title.reveal { transform: none; }

  /* Primary CTAs wipe wherever they appear, matching the hero.
     Driven by animation rather than transition on purpose: .btn owns a
     transition list for its hover lift, and adding clip-path to it
     would have retimed the hover to 720ms and dropped the background
     and shadow transitions. */
  .reveal .btn--primary { clip-path: inset(0 100% 0 0); }
  .reveal.is-visible .btn--primary {
    animation: intro-wipe 1050ms var(--intro-ease)
               calc(var(--intro-i, 0) * var(--reveal-step) + 160ms) both;
  }
  html[dir="rtl"] .reveal .btn--primary { clip-path: inset(0 0 0 100%); }
  html[dir="rtl"] .reveal.is-visible .btn--primary { animation-name: intro-wipe-rtl; }
}

/* ------------------------------------------------------------
   Case study — full project page (/work/<project>/)
   ------------------------------------------------------------
   The Work grid card (.case) is the summary; this is the
   long form behind it. Built from the same tokens, so a new
   case study is markup only.
   ------------------------------------------------------------ */

/* ---------- Hero: copy beside the launch screenshot ---------- */
.cs-hero { display: grid; gap: var(--space-2xl); align-items: center; }
@media (min-width: 62rem) {
  .cs-hero { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); gap: var(--space-3xl); }
}
.cs-hero__client {
  font-family: var(--font-heading);
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--brand-accent-light);
  margin-block-end: var(--space-2xs);
}
.cs-hero__title { color: var(--color-white); font-size: var(--h-page); }
.cs-hero__lead {
  margin-block-start: var(--space-lg);
  font-size: var(--fs-lg);
  color: color-mix(in srgb, #fff 82%, transparent);
  max-width: 46ch;
}

.cs-demo__notice {
  margin-block-start: var(--space-lg);
  max-inline-size: 52ch;
  color: var(--brand-accent-light);
  font-size: var(--fs-sm);
  line-height: 1.6;
}

/* Live-site link — an outbound chip, not a CTA button */
.cs-livelink {
  display: inline-flex; align-items: center; gap: var(--space-xs);
  margin-block-start: var(--space-lg);
  padding: 0.5em 1em;
  font-size: var(--fs-sm); font-weight: 600;
  color: var(--color-white);
  border-radius: var(--radius-full);
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, #fff 32%, transparent);
  transition: background var(--transition-fast), box-shadow var(--transition-fast);
}
.cs-livelink:hover {
  background: color-mix(in srgb, #fff 10%, transparent);
  box-shadow: inset 0 0 0 1.5px var(--brand-accent);
}
.cs-livelink svg { width: 1em; height: 1em; flex: none; }

/* ---------- Fact strip under the hero copy ---------- */
.cs-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 8rem), 1fr));
  gap: var(--space-lg);
  margin-block-start: var(--space-2xl);
  padding-block-start: var(--space-lg);
  border-block-start: 1px solid color-mix(in srgb, #fff 22%, transparent);
}
.cs-facts dt {
  font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--brand-accent-light);
  margin-block-end: var(--space-2xs);
}
.cs-facts dd { color: color-mix(in srgb, #fff 88%, transparent); font-size: var(--fs-sm); }

/* ---------- Screenshot frame ----------
   A hairline browser chrome so a flat screenshot reads as a live
   site rather than a picture dropped on the page. */
.cs-shot {
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
}
.cs-shot__bar {
  display: flex; align-items: center; gap: 0.4rem;
  padding: 0.6rem 0.85rem;
  background: var(--surface-muted);
  border-block-end: 1px solid var(--border);
}
.cs-shot__dot { width: 0.55rem; height: 0.55rem; border-radius: var(--radius-full); background: var(--color-gray-300); flex: none; }
.cs-shot__url {
  margin-inline-start: var(--space-sm);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cs-shot img { display: block; width: 100%; height: auto; }
.cs-shot--plain { border-radius: var(--radius-lg); }

/* ---------- Outcome: the number the page is built around ----------
   Deliberately the largest type on the page — larger than the h1 —
   so the result is what a skimming visitor takes away. */
.cs-outcome { text-align: center; }
.cs-outcome__value {
  display: block;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: clamp(4.5rem, 18vw, 11rem);
  line-height: 0.9;
  letter-spacing: var(--tracking-tight);
  color: var(--color-white);
  background: linear-gradient(180deg, #fff 35%, var(--brand-accent-light));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.cs-outcome__unit {
  display: block;
  margin-block-start: var(--space-md);
  font-family: var(--font-heading);
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--color-white);
}
.cs-outcome__note {
  margin-block-start: var(--space-lg);
  margin-inline: auto;
  max-width: 44ch;
  color: var(--color-gray-300);
}
/* "Why it worked" — an inference, so it is visually set apart from
   the measured number above it. */
.cs-inference {
  margin-block-start: var(--space-2xl);
  margin-inline: auto;
  max-width: 52rem;
  padding: var(--space-lg) var(--space-xl);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, #fff 8%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, #fff 16%, transparent);
}
.cs-inference__label {
  display: block;
  font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--brand-accent-light);
  margin-block-end: var(--space-xs);
}
.cs-inference p { color: color-mix(in srgb, #fff 88%, transparent); }

/* ---------- Delivered features ---------- */
.cs-features {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: var(--space-md) var(--space-xl);
  margin-block-start: var(--space-xl);
}
.cs-features li {
  display: flex; gap: var(--space-sm); align-items: flex-start;
  padding-block: var(--space-sm);
  border-block-end: 1px solid var(--border);
}
.cs-features li:last-child { border-block-end: 0; }
.cs-features__check { flex: none; width: 1.3rem; height: 1.3rem; color: var(--brand-accent); margin-block-start: 0.2rem; }
.cs-features__title { display: block; font-weight: 600; color: var(--text-strong); }
.cs-features__text { font-size: var(--fs-sm); color: var(--text-muted); }

/* ---------- Booking flow: numbered screenshot sequence ---------- */
.cs-flow {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
  gap: var(--space-xl);
  margin-block-start: var(--space-2xl);
}
.cs-flow__step { display: flex; flex-direction: column; gap: var(--space-md); }
.cs-flow__head { display: flex; align-items: center; gap: var(--space-sm); }
.cs-flow__num {
  flex: none;
  display: grid; place-items: center;
  width: 2rem; height: 2rem;
  border-radius: var(--radius-full);
  background: var(--brand-primary);
  color: var(--brand-on-primary);
  font-family: var(--font-heading); font-weight: 700; font-size: var(--fs-sm);
}
.cs-flow__title { font-family: var(--font-heading); font-weight: 600; color: var(--text-strong); }
.cs-flow__text { font-size: var(--fs-sm); color: var(--text-muted); }

/* ---------- Mobile view: phone frame ---------- */
.cs-mobile { display: grid; gap: var(--space-2xl); align-items: center; }
@media (min-width: 48rem) {
  .cs-mobile { grid-template-columns: 1fr auto; gap: var(--space-3xl); }
}
.cs-device {
  justify-self: center;
  padding: 0.55rem;
  border-radius: 2.1rem;
  background: var(--color-gray-900);
  box-shadow: var(--shadow-xl);
  max-width: 17rem;
}
.cs-device img { display: block; width: 100%; height: auto; border-radius: 1.6rem; }

/* ---------- Client quote ---------- */
.cs-quote {
  max-width: 52rem;
  margin-inline: auto;
  text-align: center;
}
.cs-quote__mark {
  display: block;
  font-family: Georgia, serif;
  font-size: 4rem; line-height: 0.5;
  color: var(--brand-accent);
  margin-block-end: var(--space-lg);
}
.cs-quote__text {
  font-family: var(--font-heading);
  font-size: var(--fs-xl);
  font-weight: 400;
  line-height: var(--leading-snug);
  color: var(--text-strong);
}
.cs-quote__author { margin-block-start: var(--space-xl); }
.cs-quote__name { font-weight: 700; color: var(--text-strong); }
.cs-quote__role { font-size: var(--fs-sm); color: var(--text-muted); }

/* ---------- Back link ---------- */
.cs-back {
  display: inline-flex; align-items: center; gap: var(--space-xs);
  font-size: var(--fs-sm); font-weight: 600;
  color: var(--text-muted);
}
.cs-back:hover { color: var(--link-hover); }
.cs-back svg { width: 1em; height: 1em; }

/* The pull-quote that rides on the Work grid card, above the
   challenge/solution/result list. */
.case__pullquote {
  margin-block-end: var(--space-lg);
  padding-inline-start: var(--space-md);
  border-inline-start: 3px solid var(--brand-accent);
  font-size: var(--fs-sm);
  line-height: var(--leading-body);
  color: var(--text-body);
  font-style: italic;
}
.case__pullquote cite { display: block; margin-block-start: var(--space-xs); font-style: normal; font-size: var(--fs-xs); color: var(--text-muted); }

/* Headline metric on the grid card. The label wraps to two lines at most
   card widths, so the number is CENTRED against it, not baseline-locked:
   baseline pins the number to the label's first line and leaves the second
   line hanging below it, which reads as a number stuck to the top padding.
   The value also needs its own tight line-height — inheriting body leading
   gave its line box ~18px of half-leading and unbalanced the tinted box. */
.case__metric {
  display: flex; align-items: center; gap: var(--space-xs);
  margin-block-end: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--brand-primary) 6%, transparent);
}
.case__metric-value { font-family: var(--font-heading); font-weight: 700; font-size: var(--fs-xl); line-height: 1.1; color: var(--brand-primary); }
/* gray-600, not --text-muted: the muted grey on the 6% blue tint was
   4.27:1 at 12px (audit F16). */
.case__metric-label { font-size: var(--fs-xs); line-height: 1.4; color: var(--color-gray-600); }

.case__link { margin-block-start: auto; padding-block-start: var(--space-lg); }

/* A linked card title stays body-coloured until hover, so the grid does
   not read as a wall of blue. */
.case__title a { color: inherit; }
.case__title a:hover { color: var(--link-hover); }
.case__desc { color: var(--text-body); font-size: var(--fs-sm); margin-block-end: var(--space-lg); }

/* ---------- Case study: two-column screenshot + narrative ---------- */
.cs-split { display: grid; gap: var(--space-2xl); align-items: center; }
@media (min-width: 62rem) {
  .cs-split { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: var(--space-3xl); }
}
/* Customer side vs studio side — two halves of the same database, so
   they are colour-coded against each other rather than numbered. */
.cs-sides { display: grid; gap: var(--space-xl); }
.cs-side { padding-inline-start: var(--space-lg); border-inline-start: 3px solid var(--border); }
.cs-side--customer { border-inline-start-color: var(--brand-primary); }
.cs-side--studio { border-inline-start-color: var(--brand-accent); }
.cs-side__label {
  display: block;
  font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--text-muted);
  margin-block-end: var(--space-2xs);
}
.cs-side__title {
  font-family: var(--font-heading); font-weight: 600;
  font-size: var(--fs-lg); color: var(--text-strong);
  margin-block-end: var(--space-xs);
}

/* Links inside the dark hero fact strip need the accent, not the
   default body-link colour, to stay legible on the inverse band. */
.cs-facts a {
  color: var(--brand-accent-light);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
}
.cs-facts a:hover { color: var(--color-white); }

/* Slim centred aside that names the product this project maps to. */
.cs-note {
  max-width: 54rem;
  margin: var(--space-2xl) auto 0;
  padding-block-start: var(--space-lg);
  border-block-start: 1px solid var(--border);
  text-align: center;
  color: var(--text-muted);
}
.cs-note strong { color: var(--text-strong); font-weight: 600; }

/* ------------------------------------------------------------
   Ghost button on a dark section
   ------------------------------------------------------------
   .btn--ghost takes its colour and ring from the light-surface
   palette, so on .section--inverse it needs both replaced. This
   was previously an inline style repeated on every inverse CTA.

   The :hover rule repeats the base values on purpose. As an inline
   style it outranked .btn--ghost:hover, so these buttons have only
   ever lifted on hover, never changed colour — keeping that here
   means the class is a pure refactor. Giving them a real inverse
   hover is a deliberate design change, not a side effect of this.
   ------------------------------------------------------------ */
/* On a navy ground, .btn--ghost's gold-on-hover border and blue text
   both fail. This variant is the ghost for dark grounds.

   It previously grouped :hover WITH the base state and gave both the
   same declarations, so the button on seven closing CTA bands had no
   hover feedback at all beyond the shared 2px lift -- the only button
   on the site that did nothing on hover. The hover now brightens the
   hairline to solid white, which is the dark-ground equivalent of what
   .btn--ghost:hover does on light. */
.btn--ghost-inverse {
  color: #fff;
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .35);
}
.btn--ghost-inverse:hover,
.btn--ghost-inverse:focus-visible {
  color: #fff;
  background: rgba(255, 255, 255, .08);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .9);
}

/* The ghost for a TINTED ground. .btn--ghost's border is --border, which
   all but vanishes on --ground-tint; that gap is why the Contact close
   band reached for a filled navy .btn--secondary to pair with its
   primary, inverting the primary/secondary hierarchy every other page
   uses. Available for markup that wants the pairing back. */
.btn--ghost-tint {
  color: var(--brand-primary);
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--brand-primary) 22%, transparent);
}
.btn--ghost-tint:hover,
.btn--ghost-tint:focus-visible {
  color: var(--brand-accent-dark);
  box-shadow: inset 0 0 0 2px var(--brand-accent);
}

/* Privacy notice page: the "last updated" line under the lead (audit F5). */
.privacy__updated { font-size: var(--fs-sm); color: var(--text-body); }
/* Links inside reading pages (About, Privacy) were body-grey with no
   underline, so the privacy notice's mailto: was indistinguishable from
   text (WCAG 1.4.1). */
.page-body a { color: var(--brand-primary); text-decoration: underline; text-underline-offset: 0.2em; }
