/* ==========================================================================
   شركة النورس للأكياس البلاستيكية — static build
   --------------------------------------------------------------------------
   Hand-written CSS, no framework. Organised in the order a page is built:

     1.  Fonts
     2.  Design tokens
     3.  Reset and base
     4.  Layout helpers
     5.  Buttons and shared surfaces
     6.  Navbar (pill, two glass tints, progress, mobile menu)
     7.  Hero
     8.  Section dividers
     9.  About (features, stats)
     10. Products (filters, slider, cards, modal)
     11. Clients
     12. Contact
     13. Footer
     14. 404
     15. Scroll reveal
     16. Reduced motion / reduced transparency / high contrast

   RTL: the document is dir="rtl", so everything here uses logical properties
   (inline-start / inline-end, margin-inline, padding-inline). Physical left and
   right appear only where a value is genuinely physical — a transform origin, a
   gradient direction.
   ========================================================================== */

/* 1. Fonts ---------------------------------------------------------------- */
/* Cairo, self-hosted. One file per unicode subset; the files are variable, so a
   single face covers every weight the site uses (400/600/700/800) and no
   separate file per weight is downloaded. */

@font-face {
  font-family: "Cairo";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/cairo-arabic.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1,
    U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF,
    U+FE70-FE74, U+FE76-FEFC;
}

@font-face {
  font-family: "Cairo";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/cairo-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Cairo";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/cairo-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* 2. Design tokens -------------------------------------------------------- */
/* Two ramps and nothing else: one accent (brand) and one neutral (ink). Both
   hold a single hue end to end. Light theme only. */

:root {
  /*
     Brand red. #D71920 is the approved colour and is locked; every other step
     is now DERIVED from it at the same hue, 357.8.

     They were not before. The tints drifted warm as they lightened — brand-50
     sat at 8.6 degrees against brand-500's 357.8, an 11-degree swing — so the
     pale steps read as salmon rather than as the brand red made lighter, which
     is what made red-tinted surfaces look slightly off next to a red button.
     Only the hue moved; every step's lightness is unchanged, so the contrast
     figures quoted throughout this file still hold.
  */
  --brand-50: #fff1f2;
  --brand-100: #ffe2e3;
  --brand-200: #ffc7c9;
  --brand-300: #fc9fa2;
  --brand-400: #ef666b;
  --brand-500: #d71920;
  --brand-600: #bc1016;
  --brand-700: #9d1015;
  --brand-800: #811014;
  --brand-900: #671013;

  /*
     Cool neutral, hue held at 220 — the comment here used to say 265, which no
     step had ever matched.

     The saturation is now a gentle taper (13 at the dark end, 10 through the
     middle, 14 at the light end) instead of the spike it had: ink-50 measured
     27.3% against ink-400's 8.4%, so the palest greys carried three times the
     chroma of the mid greys and the sunken background read visibly bluer than
     the borders drawn on it. Lightness is unchanged at every step.
  */
  --ink-900: #16181d;
  --ink-700: #323740;
  --ink-500: #545b68;
  --ink-400: #656d7c;
  --ink-300: #848c9c;
  --ink-200: #ced2d9;
  --ink-100: #e6e8eb;
  --ink-50: #f2f3f6;

  /* Semantic roles. Components reference these, never a raw ramp value. */
  --surface: #ffffff;
  --surface-sunken: var(--ink-50);
  --border: var(--ink-200);
  --text: var(--ink-900);
  --text-body: var(--ink-700);
  --text-secondary: var(--ink-500);
  --text-muted: var(--ink-400);
  --accent: var(--brand-500);
  --accent-hover: var(--brand-600);
  --accent-text: var(--brand-600);
  --accent-surface: var(--brand-50);

  /* Radius. Nested surfaces step down one level, so a box inside a card never
     looks rounder than the card holding it. */
  --radius-xs: 0.75rem;
  --radius-sm: 0.75rem;
  --radius-md: 1rem;
  --radius-card: 1.25rem;
  --radius-lg: 1.5rem;
  --radius-xl: 1.75rem;
  /* The decorative pane behind the About photo, the only step above `xl`. */
  --radius-2xl: 2.25rem;

  /*
     ELEVATION — one scale, four steps, and a brand-tinted pair.

     Every step is the same two-shadow recipe: a tight contact shadow that
     anchors the surface to the page, and a wide soft one that gives it height.
     Only the distance and the opacity change between steps, which is what
     makes a card, a menu and a dialog read as three heights of ONE material
     rather than three unrelated boxes.

     They were twenty-one hand-rolled values before, no two built the same way,
     and the difference between them was not height — it was whoever wrote them.

     1 rests on the page (cards, chips, the counter pill)
     2 lifts off it (a hovered card, an arrow button)
     3 floats above it (dropdowns, the mobile menu, the focused product)
     4 is detached from it (the modal)

     The brand pair is for surfaces that are themselves red: a red shadow under
     a grey card reads as a colour cast, but under a red button it reads as the
     button's own light.
  */
  --elev-1: 0 1px 2px rgb(22 24 29 / 0.05), 0 6px 16px -8px rgb(22 24 29 / 0.1);
  --elev-2: 0 2px 6px -2px rgb(22 24 29 / 0.09), 0 12px 28px -12px rgb(22 24 29 / 0.22);
  --elev-3: 0 4px 10px -3px rgb(22 24 29 / 0.11), 0 22px 44px -18px rgb(22 24 29 / 0.3);
  --elev-4: 0 8px 18px -6px rgb(22 24 29 / 0.13), 0 34px 64px -26px rgb(22 24 29 / 0.4);
  --elev-brand-1: 0 6px 16px -8px rgb(215 25 32 / 0.6);
  --elev-brand-2: 0 2px 6px -2px rgb(215 25 32 / 0.35), 0 12px 26px -10px rgb(215 25 32 / 0.7);

  /* The three names the components already use, now pointing at the scale. */
  --shadow-card: var(--elev-1);
  --shadow-card-hover: var(--elev-2), 0 12px 28px -18px rgb(215 25 32 / 0.28);
  --shadow-btn: var(--elev-brand-1);

  /* Layout */
  --container: 80rem;
  --nav-max: 72rem;

  /*
     Motion. One easing curve and four durations for the whole site, so nothing
     has to guess a number and every surface decelerates the same way.

     --ease-out settles and never overshoots: it is the right curve for
     something that merely appeared or moved into place. --ease-inout is for a
     value that leaves and returns along the same path — a rotation, a
     cross-fade. Both are cheap; what makes motion feel expensive is that every
     element shares them.

     Durations follow the size of the change: a colour or a 2px marker takes
     --dur-fast, a card lift or a fade takes --dur-base, a panel that changes
     the shape of the page takes --dur-slow or --dur-panel.
  */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-inout: cubic-bezier(0.4, 0, 0.2, 1);
  --dur-instant: 0.12s;
  --dur-fast: 0.16s;
  --dur-base: 0.22s;
  --dur-slow: 0.3s;
  --dur-panel: 0.36s;
  /* Longer than any UI response, because these are things ARRIVING rather than
     answering a click. A reveal that ran at UI speed would read as a flicker. */
  --dur-reveal: 0.5s;
  --dur-draw: 0.75s;
  /* The out-beat of a category swap. `SWAP_OUT_MS` in script.js waits exactly
     this long before committing the new set — CHANGE BOTH OR NEITHER. */
  --dur-swap: 0.14s;
  /* One stagger step, shared by every group that reveals as a set. Written
     into the markup as `data-reveal-delay`, in multiples of this. */
  --stagger: 70ms;
}

/* 3. Reset and base ------------------------------------------------------- */

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

/*
   Anchor scrolling. `scroll-padding-top` is what stops a jumped-to section
   ending up underneath the fixed pill, and it has to track the pill's real
   height at each breakpoint or the offset is either short (the heading hides
   behind the bar) or so long the section starts halfway down the screen.

   Measured, scrolled: 0.75rem of inset + 4rem of pill on a phone, and 1rem of
   inset + 4.5rem of pill from `sm` up. The extra rem is breathing room so the
   heading does not sit flush against the bar.
*/
html {
  scroll-behavior: smooth;
  scroll-padding-top: 5.75rem;
  -webkit-text-size-adjust: 100%;
}

@media (min-width: 640px) {
  html {
    scroll-padding-top: 6.5rem;
  }
}

@media (min-width: 768px) {
  html {
    scroll-padding-top: 7rem;
  }
}

/* `clip` rather than `hidden`: it does not create a scroll container, so the
   fixed navbar and sticky behaviour keep working. Scroll-reveal elements start
   life translated sideways and must never produce a horizontal scrollbar. */
html,
body {
  overflow-x: clip;
}

body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: var(--surface);
  color: var(--text-body);
  font-family: "Cairo", "Tajawal", system-ui, -apple-system, sans-serif;
  font-size: 1rem;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
}

main {
  flex: 1;
}

/* Headings are dark by default. NOTE: a heading placed on a coloured surface
   has to set its own colour — this rule beats an inherited white. */
h1,
h2,
h3,
h4 {
  color: var(--text);
  line-height: 1.25;
  font-weight: 800;
}

/* Arabic is a joined script: never add letter-spacing to it, it pulls the
   connected letterforms apart. Hierarchy comes from size, weight and leading. */
h1,
h2,
h3,
h4,
p,
li,
span,
a {
  letter-spacing: normal;
}

a {
  color: inherit;
  text-decoration: none;
}

img {
  display: block;
  max-width: 100%;
}

/*
   Inline icons carry no width/height attributes, so they must be sized here. An
   unsized <svg> inside a flex button stretches to the button — which is exactly
   what a full-width mobile CTA did to its arrow. One default, overridden only
   where a context genuinely needs a different size.
*/
svg {
  display: block;
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
}

/* Icons that fill a sized wrapper. */
.chip__icon svg {
  width: 100%;
  height: 100%;
}

.product__more svg,
.site-footer__contact svg,
.breadcrumb svg {
  width: 1rem;
  height: 1rem;
}

.order-cta__icon svg {
  width: 1.75rem;
  height: 1.75rem;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
  /* Removes the legacy 300ms tap delay on touch devices. */
  touch-action: manipulation;
}

ul,
ol {
  list-style: none;
}

/*
   The focus ring is the brand red with a 3px gap, and the gap is the point: it
   separates the ring from the element's own border so the two do not merge
   into one thick edge on the many pill-shaped controls here.

   It must not set a radius. `outline` already follows the element's own
   corners, and forcing 4px squared off every pill the moment it took focus.
*/
:focus-visible {
  outline: 2.5px solid var(--brand-500);
  outline-offset: 3px;
}

/* On a dark ground the red ring is hard to find; a white one is not. */
.site-header[data-state="dark"] :focus-visible,
.hero :focus-visible,
.site-footer :focus-visible,
.contact-panel :focus-visible,
.about-cta :focus-visible,
.page-header :focus-visible {
  outline-color: #fff;
}

/* Tabular figures, so columns of numbers line up. */
.numeric {
  font-variant-numeric: tabular-nums;
}

/* Latin-only values that must read left-to-right — phone numbers, e-mail.
   NEVER put this on a string that mixes Arabic and digits: bidi already orders
   those correctly and forcing LTR scrambles dates like "7 مايو 2001". */
.ltr-value {
  direction: ltr;
  unicode-bidi: isolate;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  inset-inline-start: 1rem;
  top: -100px;
  z-index: 100;
  padding: 0.75rem 1.25rem;
  border-radius: var(--radius-xs);
  background: var(--brand-600);
  color: #fff;
  font-weight: 700;
  transition: top var(--dur-base) var(--ease-out);
}

.skip-link:focus {
  top: 1rem;
}

/* 4. Layout helpers ------------------------------------------------------- */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: 1.25rem;
}

@media (min-width: 768px) {
  .container {
    padding-inline: 2rem;
  }
}

/* Section rhythm: one value everywhere, so the page reads as one system. */
.section {
  padding-block: 4.5rem;
}

@media (min-width: 768px) {
  .section {
    padding-block: 6.5rem;
  }
}

.section--sunken {
  background-color: var(--surface-sunken);
}

.section--white {
  background-color: var(--surface);
}

/* Shared section heading: eyebrow pill, title, optional lead paragraph. */
.heading {
  text-align: center;
  max-width: 48rem;
  margin-inline: auto;
}

.heading--start {
  text-align: start;
  margin-inline: 0;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  border-radius: 9999px;
  background-color: var(--accent-surface);
  color: var(--brand-700);
  padding: 0.375rem 0.9rem;
  font-size: 0.8125rem;
  font-weight: 700;
}

.eyebrow::before {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 9999px;
  background: var(--brand-500);
}

.heading__title {
  margin-top: 1rem;
  font-size: clamp(1.65rem, 4vw, 2.5rem);
  font-weight: 800;
  line-height: 1.25;
  text-wrap: balance;
}

.heading__lead {
  margin-top: 1rem;
  color: var(--text-secondary);
  line-height: 1.9;
  text-wrap: balance;
}

/* 5. Buttons and shared surfaces ------------------------------------------ */
/* Feedback lands on pointer-DOWN, never on release. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border-radius: 9999px;
  padding: 0.875rem 1.75rem;
  font-weight: 700;
  font-size: 1rem;
  line-height: 1;
  white-space: nowrap;
  transition: background-color var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out),
    translate var(--dur-base) var(--ease-out), transform var(--dur-instant) var(--ease-out);
}

.btn:active {
  transform: scale(0.97);
}

/*
   The lift is `translate`, not `transform`, so it composes with the
   `scale(0.97)` press above instead of replacing it — press a button mid-hover
   and it sinks from the lifted position rather than snapping back to the
   baseline first. Both are compositor properties; nothing here reflows.
*/
.btn--primary {
  background-color: var(--accent);
  color: #fff;
  box-shadow: var(--shadow-btn);
}

.btn--primary:focus-visible {
  background-color: var(--accent-hover);
}

.btn--outline {
  background-color: transparent;
  color: var(--accent-text);
  border: 1.5px solid var(--brand-200);
}

.btn--outline:focus-visible {
  background-color: var(--accent-surface);
  border-color: var(--brand-400);
}

.btn--ghost {
  background-color: transparent;
  color: #fff;
  border: 1.5px solid rgb(255 255 255 / 0.65);
}

.btn--ghost:focus-visible {
  background-color: rgb(255 255 255 / 0.14);
  border-color: #fff;
}

@media (hover: hover) {
  .btn:hover {
    translate: 0 -2px;
  }

  /* The shadow grows with the lift; a button that rises without its shadow
     following reads as a sticker sliding, not as a surface leaving the page. */
  .btn--primary:hover {
    background-color: var(--accent-hover);
    box-shadow: var(--elev-brand-2);
  }

  .btn--outline:hover {
    background-color: var(--accent-surface);
    border-color: var(--brand-400);
    box-shadow: var(--elev-brand-1);
  }

  /* Over the hero the lift is carried by the glass getting brighter rather
     than by a coloured shadow, which has nothing to fall onto there. */
  .btn--ghost:hover {
    background-color: rgb(255 255 255 / 0.18);
    border-color: #fff;
    box-shadow: 0 10px 26px -12px rgb(0 0 0 / 0.55);
  }
}

.btn--block {
  width: 100%;
}

@media (min-width: 640px) {
  .btn--auto-sm {
    width: auto;
  }
}

/* A card, and the press feedback shared by anything that behaves like one. */
.card {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}

.pressable {
  transition: transform var(--dur-instant) var(--ease-out);
}

.pressable:active {
  transform: scale(0.98);
}

/* Soft red dotted field behind product photos. */
.dot-field {
  background-color: var(--brand-50);
  background-image: radial-gradient(circle, rgb(215 25 32 / 0.13) 1.5px, transparent 1.6px);
  background-size: 14px 14px;
}

/* 6. Navbar --------------------------------------------------------------- */
/*
   A floating pill. The <header> still spans the window and still starts at y=0
   — that is what keeps the hero running to the top of the screen and anchor
   offsets honest — and only the capsule inside it is inset.

   Two glass tints are stacked and BOTH stay mounted at all times: the dark one
   over the hero, the light one past the scroll threshold. Only their opacity
   cross-fades. Mounting and unmounting a backdrop-filter creates and destroys a
   compositor layer, and that handover is what makes a scroll-triggered glass bar
   snap; keeping both alive makes the transition pure opacity.
*/

.site-header {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: 50;
}

.site-header__inset {
  padding: 0.75rem 0.75rem 0;
}

@media (min-width: 640px) {
  .site-header__inset {
    padding: 1rem 1.25rem 0;
  }
}

.navbar {
  position: relative;
  width: 100%;
  max-width: var(--nav-max);
  margin-inline: auto;
}

.navbar__pill {
  position: relative;
  z-index: 50;
}

.navbar__pane {
  position: absolute;
  inset: 0;
  border-radius: 9999px;
  transition: opacity var(--dur-slow) var(--ease-out),
    background-color var(--dur-slow) var(--ease-out);
  pointer-events: none;
  /* Both panes carry the filter, always. */
  backdrop-filter: blur(20px) saturate(180%);
}

/* Over the dark hero: the same glass, tinted dark rather than light, so the
   photo still reads through it instead of being covered by it. */
.navbar__pane--dark {
  background-color: rgb(11 12 15 / 0.24);
  background-image: linear-gradient(
    to bottom,
    rgb(11 12 15 / 0.32),
    rgb(11 12 15 / 0.08) 65%,
    rgb(11 12 15 / 0)
  );
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.16), inset 0 0 0 1px rgb(255 255 255 / 0.13),
    0 14px 34px -18px rgb(0 0 0 / 0.55);
  opacity: 0;
}

/*
   Scrolled: a light pane. 0.78 is a FLOOR, not a taste call — its worst case is
   the bar over the darkest part of the hero photo, where the fill is all that
   holds the ground up under the dark type. At 0.68 the links measure Lc 53 and
   at 0.72 Lc 58, both under the APCA 60 this project holds 15px text to. What
   makes it read as glass is the sheen, the lit edge and the shadow — not a
   thinner fill.
*/
.navbar__pane--light {
  background-color: rgb(255 255 255 / 0.78);
  background-image: linear-gradient(
    to bottom,
    rgb(255 255 255 / 0.16),
    rgb(255 255 255 / 0) 60%,
    rgb(22 24 29 / 0.015)
  );
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.75), inset 0 0 0 1px rgb(22 24 29 / 0.06),
    0 1px 2px rgb(22 24 29 / 0.05), 0 14px 34px -14px rgb(22 24 29 / 0.28);
  opacity: 0;
}

/* State classes, set from script.js. */
.site-header[data-state="dark"] .navbar__pane--dark {
  opacity: 1;
}

.site-header[data-state="glass"] .navbar__pane--light {
  opacity: 1;
}

/* While the menu is open the pill goes opaque: the panel hanging off it is
   opaque, and a translucent pill above it reads as two unrelated surfaces. */
.site-header[data-menu="open"] .navbar__pane--light {
  background-color: #fff;
}

.navbar__inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding-inline: 0.75rem;
  height: 4.5rem;
  transition: height var(--dur-slow) var(--ease-out);
}

@media (min-width: 640px) {
  .navbar__inner {
    gap: 0.75rem;
    padding-inline: 1.25rem;
  }
}

@media (min-width: 768px) {
  .navbar__inner {
    height: 5rem;
  }
}

@media (min-width: 1024px) {
  .navbar__inner {
    padding-inline: 1.5rem;
  }
}

/* The pill condenses once the page moves. */
.site-header[data-scrolled="true"] .navbar__inner {
  height: 4rem;
}

@media (min-width: 768px) {
  .site-header[data-scrolled="true"] .navbar__inner {
    height: 4.5rem;
  }
}

/* Brand */
.navbar__brand {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  flex-shrink: 0;
}

@media (min-width: 640px) {
  .navbar__brand {
    gap: 0.75rem;
  }
}

/* The mark shrinks with the pill by SCALING, not by animating its width and
   height. The two looked the same and cost differently: width/height ran a
   layout pass on the bar on every frame of the condense, a transform does not
   touch layout at all. The box stays 2.5rem, so nothing beside it moves. */
.navbar__logo {
  width: 2.5rem;
  height: 2.5rem;
  object-fit: contain;
  transform-origin: center;
  transition: transform var(--dur-slow) var(--ease-out);
}

@media (min-width: 768px) {
  .navbar__logo {
    width: 2.75rem;
    height: 2.75rem;
  }
}

/* 2.25 / 2.5 and 2.5 / 2.75 — the same two sizes the old width pair produced. */
.site-header[data-scrolled="true"] .navbar__logo {
  transform: scale(0.9);
}

@media (min-width: 768px) {
  .site-header[data-scrolled="true"] .navbar__logo {
    transform: scale(0.909);
  }
}

.navbar__wordmark {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}

.navbar__name {
  font-size: 0.9375rem;
  font-weight: 800;
  color: var(--ink-900);
  transition: color var(--dur-base) var(--ease-out);
}

@media (min-width: 640px) {
  .navbar__name {
    font-size: 1rem;
  }
}

@media (min-width: 768px) {
  .navbar__name {
    font-size: 1.125rem;
  }
}

.navbar__tag {
  font-size: 0.625rem;
  font-weight: 600;
  color: var(--ink-400);
  transition: color var(--dur-base) var(--ease-out);
}

@media (min-width: 640px) {
  .navbar__tag {
    font-size: 0.6875rem;
  }
}

@media (min-width: 768px) {
  .navbar__tag {
    font-size: 0.75rem;
  }
}

/* Light type over the dark hero. */
.site-header[data-state="dark"] .navbar__name {
  color: #fff;
}

.site-header[data-state="dark"] .navbar__tag {
  color: rgb(255 255 255 / 0.75);
}

/* Desktop links — from `md`, not `lg`: measured at 768 the row is 654px inside
   688px of room, and without it the pill is a wide bar with a logo at one end,
   two controls at the other and nothing between them. */
.navbar__links {
  display: none;
  align-items: center;
  gap: 0.125rem;
}

@media (min-width: 768px) {
  .navbar__links {
    display: flex;
  }
}

/*
   The link row has three sizes, not two, because 768px is where it is tightest:
   the full five links arrive at exactly the width that can least afford them.
   Measured at 768 with the old single size, only 12px separated the wordmark
   from the first link and the last link from the actions — legible, but visibly
   crowded, and the first thing to break if a label ever grew.

   Trimming the padding to 0.5rem and the type to 14px across the five links
   returns about 40px to the row, which buys a comfortable gap at the one width
   that needed it. From 900px there is room for the desktop size again.
*/
.navbar__link {
  position: relative;
  border-radius: 9999px;
  padding: 0.5rem 0.5rem;
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--ink-700);
  transition: color var(--dur-base) var(--ease-out),
    background-color var(--dur-base) var(--ease-out);
}

@media (min-width: 1024px) {
  .navbar__link {
    padding-inline: 0.875rem;
    font-size: 0.9375rem;
  }
}

.navbar__link:focus-visible {
  color: var(--accent-text);
}

.site-header[data-state="dark"] .navbar__link {
  color: rgb(255 255 255 / 0.85);
}

.site-header[data-state="dark"] .navbar__link:focus-visible {
  color: #fff;
}

/* Hover is guarded so a tap on a phone does not leave the link stuck in its
   hover state until something else is touched. */
@media (hover: hover) {
  .navbar__link:hover {
    color: var(--accent-text);
    background-color: rgb(215 25 32 / 0.06);
  }

  .site-header[data-state="dark"] .navbar__link:hover {
    color: #fff;
    background-color: rgb(255 255 255 / 0.1);
  }
}

/*
   The marker under a link — the active one, and the one an inactive link grows
   on hover. It is red in both navbar states rather than white over the hero:
   the brand mark is the point of it, and brand-400 is the step that still
   reads against the photo.

   It SLIDES rather than fading or growing from the middle. Two origins do
   that with one property: it grows from the inline-start edge on the way in,
   and collapses toward the inline-end edge on the way out, so the movement
   always follows the reading direction. In this document that is right to
   left; the [dir="ltr"] pair keeps it correct if an English side is ever
   added, which is why the origins are written as a matched pair instead of a
   single physical value.

   scaleX only — no width, no layout, and it stays on the compositor.
*/
.navbar__link::after {
  content: "";
  position: absolute;
  inset-inline: 0.5rem;
  bottom: 0.1875rem;
  height: 2px;
  border-radius: 9999px;
  background: var(--brand-500);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-base) var(--ease-out),
    background-color var(--dur-base) var(--ease-out);
}

/* Matches the link's own inline padding at every breakpoint below. */

[dir="ltr"] .navbar__link::after {
  transform-origin: right center;
}

@media (min-width: 1024px) {
  .navbar__link::after {
    inset-inline: 0.875rem;
  }
}

.navbar__link:hover::after,
.navbar__link:focus-visible::after,
.navbar__link[aria-current="true"]::after {
  transform: scaleX(1);
  transform-origin: right center;
}

[dir="ltr"] .navbar__link:hover::after,
[dir="ltr"] .navbar__link:focus-visible::after,
[dir="ltr"] .navbar__link[aria-current="true"]::after {
  transform-origin: left center;
}

/* The active link keeps the full-strength brand red; a link merely being
   hovered gets the lighter step, so the two never look identical. */
.navbar__link:hover::after,
.navbar__link:focus-visible::after {
  background: var(--brand-400);
}

.navbar__link[aria-current="true"]::after {
  background: var(--brand-500);
}

.site-header[data-state="dark"] .navbar__link::after {
  background: var(--brand-400);
}

.site-header[data-state="dark"] .navbar__link[aria-current="true"]::after {
  background: var(--brand-500);
}

.site-header[data-state="dark"] .navbar__link[aria-current="true"] {
  color: #fff;
}

.navbar__link[aria-current="true"] {
  color: var(--accent-text);
}

/* Actions cluster. DOM order is language, phone, hamburger, and RTL lays that
   out right to left — so the capsule sits beside the phone button on a laptop
   and beside the hamburger on a phone without either of them moving. */
.navbar__actions {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  flex-shrink: 0;
}

@media (min-width: 640px) {
  .navbar__actions {
    gap: 0.5rem;
  }
}

/*
   The number appears as soon as the row can hold it, which measured out at
   820px — an iPad Air in portrait. It cannot appear at 768: swapping the 44px
   call circle for the labelled button costs about 100px even in its compact
   form, and at 768 the row has roughly 40px to give once the links have been
   tightened above. So 768 keeps the icon and 820 gets the number.

   Between 820 and 1024 the button is deliberately smaller than its desktop
   self — 13px type, tighter padding, a 1rem icon — because at those widths it
   shares the bar with all five links. It returns to full size at 1024.
*/
.navbar__phone {
  display: none;
  padding: 0.5rem 0.6875rem;
  font-size: 0.8125rem;
  gap: 0.25rem;
}

.navbar__phone svg {
  width: 1rem;
  height: 1rem;
}

@media (min-width: 820px) {
  .navbar__phone {
    display: inline-flex;
  }
}

@media (min-width: 1024px) {
  .navbar__phone {
    padding: 0.625rem 1rem;
    font-size: 0.9375rem;
    gap: 0.5rem;
  }

  .navbar__phone svg {
    width: 1.25rem;
    height: 1.25rem;
  }
}

/*
   The call icon covers ONE band: 768 to 819. Below 768 it is hidden, and above
   819 the labelled button carries the number instead.

   It does not appear on a phone. It used to, and the phone bar is better
   without it: the mobile menu already carries the number as a full-width
   button with its `tel:` link, so the icon was a second route to the same
   place, competing with the hamburger for the same corner. What is left below
   768 is the mark, the language capsule and the hamburger — three controls,
   each doing one thing.

   Where it does appear it mirrors the hamburger's geometry exactly (2.75rem,
   the same circle) so the two read as a pair, and it is brand red because it
   is the primary action in the bar.

   The visible label is the icon; the number itself rides on aria-label, so a
   screen reader announces what will be dialled.
*/
.navbar__call {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  flex-shrink: 0;
  border-radius: 9999px;
  background: var(--accent);
  color: #fff;
  box-shadow: var(--shadow-btn);
  transition: background-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out), translate var(--dur-base) var(--ease-out),
    transform var(--dur-instant) var(--ease-out);
}

.navbar__call svg {
  width: 1.125rem;
  height: 1.125rem;
}

.navbar__call:active {
  transform: scale(0.94);
}

@media (hover: hover) {
  .navbar__call:hover {
    background: var(--accent-hover);
    translate: 0 -2px;
    box-shadow: var(--elev-brand-2);
  }
}

/* On from 768, where the mobile menu that carried the number is gone... */
@media (min-width: 768px) {
  .navbar__call {
    display: inline-flex;
  }
}

/* ...and off again at 820, where the labelled button says the same thing
   properly. The two are never on screen together. */
@media (min-width: 820px) {
  .navbar__call {
    display: none;
  }
}

/* Language capsule — presentational only. */
.lang {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  border-radius: 9999px;
  border: 1px solid var(--ink-200);
  background: rgb(241 243 247 / 0.8);
  padding: 0.125rem;
  transition: background-color var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out);
}

.site-header[data-state="dark"] .lang {
  border-color: rgb(255 255 255 / 0.25);
  background: rgb(255 255 255 / 0.1);
}

.lang__option {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.75rem;
  min-width: 2rem;
  padding-inline: 0.5rem;
  border-radius: 9999px;
  font-size: 0.6875rem;
  font-weight: 800;
  line-height: 1;
  color: var(--ink-500);
  transition: color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out);
}

@media (min-width: 640px) {
  .lang__option {
    padding-inline: 0.625rem;
    font-size: 0.75rem;
  }
}

.lang__option:hover {
  color: var(--ink-900);
}

.site-header[data-state="dark"] .lang__option {
  color: rgb(255 255 255 / 0.8);
}

.site-header[data-state="dark"] .lang__option:hover {
  color: #fff;
}

.lang__option[aria-pressed="true"],
.site-header[data-state="dark"] .lang__option[aria-pressed="true"] {
  background: var(--accent);
  color: #fff;
}

/*
   THE TABLET BAR'S BUDGET.

   Between 768 and 1023 the pill carries everything the desktop does — mark,
   five links, language capsule, phone — in as little as 748px of usable width.
   Every value below was cut to buy back the gap between the three groups. The
   alternative was dropping a control, or letting them sit 13px apart, which is
   what they measured at 820 the moment the labelled phone button appeared.

   Trimmed: the bar's own inline padding, the actions and brand gaps, the
   language capsule's option padding, the mark, and the wordmark's two type
   sizes — about 30px in total, which is the difference between a 13px gap and
   a 28px one at the tightest width in the band.

   THIS BLOCK MUST STAY BELOW the rules it overrides. It was first written
   above `.navbar__phone`, where the `min-width: 640px` rule for
   `.lang__option` still won on source order and the capsule never shrank.
*/
@media (min-width: 768px) and (max-width: 1023px) {
  .navbar__inner {
    padding-inline: 1rem;
  }

  .navbar__actions {
    gap: 0.375rem;
  }

  .navbar__brand {
    gap: 0.625rem;
  }

  .navbar__logo {
    width: 2.5rem;
    height: 2.5rem;
  }

  .navbar__name {
    font-size: 1rem;
  }

  .navbar__tag {
    font-size: 0.6875rem;
  }

  .lang__option {
    padding-inline: 0.375rem;
    min-width: 1.75rem;
  }
}

/* Hamburger */
.navbar__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 9999px;
  border: 1px solid var(--ink-200);
  background: rgb(255 255 255 / 0.6);
  color: var(--ink-900);
  transition: background-color var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}

@media (min-width: 768px) {
  .navbar__toggle {
    display: none;
  }
}

.navbar__toggle:hover {
  background: var(--ink-50);
}

.site-header[data-state="dark"] .navbar__toggle {
  border-color: rgb(255 255 255 / 0.3);
  background: rgb(255 255 255 / 0.1);
  color: #fff;
}

/*
   Hamburger geometry, so the morph lands exactly rather than approximately.
   The box is 24px and the bars are 18px wide, inset 3px each side — that is
   what centres them. Vertically they sit at 5 / 11 / 17, so the stack spans
   5–19 and is centred on 12. Opening moves the outer two by ±6 onto 11, the
   middle bar's line, so the strokes cross exactly at the centre of the box.
*/
.burger {
  position: relative;
  display: block;
  width: 24px;
  height: 24px;
}

.burger span {
  position: absolute;
  left: 3px;
  width: 18px;
  height: 2px;
  border-radius: 9999px;
  background: currentColor;
  transition: transform var(--dur-slow) var(--ease-out),
    opacity var(--dur-fast) var(--ease-out);
}

.burger span:nth-child(1) {
  top: 5px;
}

.burger span:nth-child(2) {
  top: 11px;
}

.burger span:nth-child(3) {
  top: 17px;
}

.navbar__toggle[aria-expanded="true"] .burger span:nth-child(1) {
  transform: translateY(6px) rotate(45deg);
}

.navbar__toggle[aria-expanded="true"] .burger span:nth-child(2) {
  opacity: 0;
  transform: scaleX(0.4);
}

.navbar__toggle[aria-expanded="true"] .burger span:nth-child(3) {
  transform: translateY(-6px) rotate(-45deg);
}

/*
   Reading progress, clipped to the capsule. The clip is what adapts it to the
   shape: it trims each end exactly where the pill curves away, and stays correct
   at every pill height. Its own clipping layer, not `overflow: hidden` on the
   pill, which would also cut off the call button's shadow and any focus ring.
*/
.progress {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: 9999px;
  pointer-events: none;
}

.progress__track {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 2px;
  background: rgb(22 24 29 / 0.1);
}

.progress__fill {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 2px;
  background: var(--brand-500);
  /* Fills from the right — the start edge in RTL — so it grows in the direction
     the page is read. scaleX keeps it on the compositor. */
  transform-origin: right;
  transform: scaleX(0);
  will-change: transform;
}

/* Over the hero the fill keeps the brand red and is washed with white on top of
   it: #D71920 alone reads at about 3.2:1 on the dark tint, and 5.3:1 washed. */
.progress__fill::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
}

.site-header[data-state="dark"] .progress__fill::after {
  opacity: 0.3;
}

/*
   The mobile menu drops out of the pill rather than sliding in from the screen
   edge, so it stays attached to the control that opened it. Absolute, not fixed:
   it inherits the pill's inset and max-width for free, and animating its height
   costs layout only inside this panel because an out-of-flow box has nothing
   below it to push.
*/
.menu-panel {
  position: absolute;
  inset-inline: 0;
  top: 100%;
  z-index: 40;
  margin-top: 0.5rem;
  overflow: hidden;
  border-radius: var(--radius-lg);
  border: 1px solid var(--ink-200);
  background: #fff;
  box-shadow: var(--elev-3);
  height: 0;
  opacity: 0;
  /* One duration for both. They ran at 0.32s and 0.14s before, so the panel
     was fully opaque while it was still less than half open, which is what
     made it look like it snapped and then grew. */
  transition: height var(--dur-panel) var(--ease-out), opacity var(--dur-panel) var(--ease-out);
}

@media (min-width: 768px) {
  .menu-panel {
    display: none;
  }
}

.menu-panel[data-open="true"] {
  opacity: 1;
}

/* `overscroll-behavior: contain` stops a scroll that reaches the end of the
   panel from chaining to the page behind it. The body is locked as well, so
   this is belt and braces — but the belt is what iOS honours most reliably. */
.menu-panel__scroll {
  max-height: calc(100dvh - 8rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.menu-panel__list {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.75rem;
}

.menu-panel__link {
  display: block;
  border-radius: var(--radius-md);
  padding: 0.75rem 1rem;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink-700);
  transition: background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
  /* Links stagger in; the delay is set per item from script.js. */
  opacity: 0;
  transform: translateY(-8px);
}

.menu-panel[data-open="true"] .menu-panel__link {
  opacity: 1;
  transform: translateY(0);
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.menu-panel__link:hover,
.menu-panel__link:focus-visible {
  background: var(--brand-50);
  color: var(--brand-700);
}

.menu-panel__foot {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  border-top: 1px solid var(--ink-100);
  padding: 0.75rem;
}

.menu-panel__hours {
  text-align: center;
  font-size: 0.75rem;
  color: var(--ink-400);
}

/* The dimmed overlay is a sibling of <header>, never a child: the panes inside
   it use backdrop-filter, and a filtered ancestor becomes the containing block
   for position:fixed children, which would stop this covering the viewport. */
/*
   z-index 45 puts the overlay above every floating control on the page — the
   back-to-top button sits at 40 — while staying under the header's 50, so the
   pill and the panel that drops out of it remain on top. At 30 the back-to-top
   button was ABOVE the overlay and stayed tappable behind the open menu, which
   is the bug this fixes.
*/
.menu-overlay {
  position: fixed;
  inset: 0;
  z-index: 45;
  background: rgb(22 24 29 / 0.45);
  backdrop-filter: blur(2px);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-base) var(--ease-out), visibility var(--dur-base) linear;
}

@media (min-width: 768px) {
  .menu-overlay {
    display: none;
  }
}

.menu-overlay[data-open="true"] {
  opacity: 1;
  visibility: visible;
}

/* Back to top — the other half of the hero's scroll cue. Exactly one of the two
   is on screen at any time. It sits on the start side, the right in RTL. */
.back-to-top {
  position: fixed;
  bottom: 1.25rem;
  inset-inline-start: 1.25rem;
  z-index: 40;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border-radius: 9999px;
  background: var(--brand-500);
  color: #fff;
  box-shadow: var(--shadow-btn);
  opacity: 0;
  visibility: hidden;
  transform: translateY(16px) scale(0.9);
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out),
    visibility var(--dur-slow) linear, background-color var(--dur-base) var(--ease-out);
}

@media (min-width: 768px) {
  .back-to-top {
    bottom: 1.75rem;
    inset-inline-start: 1.75rem;
  }
}

.back-to-top[data-visible="true"] {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
}

/*
   Gone while the menu is open, whatever its own visibility state says. It is a
   sibling of the header in the DOM, so a general sibling combinator reaches it
   without any script. `pointer-events: none` is belt and braces next to
   `visibility: hidden`, which already blocks hit-testing.
*/
.site-header[data-menu="open"] ~ .back-to-top {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(16px) scale(0.9);
}

.back-to-top:hover,
.back-to-top:focus-visible {
  background: var(--brand-600);
}

/* 7. Hero ----------------------------------------------------------------- */
/*
   Fills the viewport exactly. `dvh` tracks the visible viewport on phones as the
   URL bar hides and shows. Do NOT put a fractional viewport unit here — at 92svh
   the white About section showed through underneath as a strip on the first
   screen. The hero also paints its own dark ground, so there is no moment during
   load when the page's white can show through it.
*/

.hero {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  min-height: 100dvh;
  background-color: var(--ink-900);
  overflow: hidden;
}

@media (min-width: 768px) {
  .hero {
    min-height: 100vh;
  }
}

/* The photo drifts a little slower than the page as it scrolls away. It sits on
   an over-sized wrapper so drifting never exposes an edge. */
.hero__media {
  position: absolute;
  inset: -8% 0;
  z-index: -3;
  will-change: transform;
}

.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/*
   Three dark scrims, never a white one: a base wash, a soft pool behind the
   headline so the type has a ground without flattening the whole photo, and a
   vignette that settles the frame.

   THE VALUES WERE MEASURED, not chosen. The photograph was drawn to a canvas
   and sampled: behind the headline it averages Y 0.29 but peaks at Y 0.996 —
   there is near-white sky directly behind the type. Compositing the three
   scrims over a WORST-CASE white pixel gives the real floor.

   That measurement also located the actual problem. Under the headline the old
   scrims already landed on rgb(20,21,24) — white type at 18.3:1, never in
   danger. The washed-out feeling came from the OPEN side of the frame, where
   the photo shows through: rgb(84,84,87) at the far left and rgb(103,103,106)
   at the bottom left, where the leftmost highlight card and the scroll cue sit.
   Those measured 7.6:1 and 5.6:1.

   So the darkening is weighted toward the open side rather than applied
   evenly. Now: far left rgb(57) at 11.4:1, bottom left rgb(72) at 9.0:1, and
   the headline barely moves at 18.7:1 — no point crushing what was already
   fine. The photograph keeps about 60% of its tonal range on the open side, so
   it still reads as a building rather than as a dark panel; going further (the
   next step tested took it to 45%) flattened it.
*/
.hero__scrim-base {
  position: absolute;
  inset: 0;
  z-index: -2;
  background: linear-gradient(
    to left,
    rgb(22 24 29 / 0.96) 10%,
    rgb(22 24 29 / 0.9),
    rgb(22 24 29 / 0.78)
  );
}

@media (min-width: 768px) {
  .hero__scrim-base {
    background: linear-gradient(
      to left,
      rgb(22 24 29 / 0.93) 30%,
      rgb(22 24 29 / 0.82),
      rgb(22 24 29 / 0.55)
    );
  }
}

.hero__scrim-pool {
  position: absolute;
  inset: 0;
  z-index: -2;
  background: radial-gradient(
    110% 60% at 60% 45%,
    rgb(11 12 15 / 0.78) 0%,
    rgb(11 12 15 / 0.42) 50%,
    transparent 85%
  );
}

@media (min-width: 768px) {
  .hero__scrim-pool {
    background: radial-gradient(
      105% 62% at 72% 44%,
      rgb(11 12 15 / 0.88) 0%,
      rgb(11 12 15 / 0.6) 55%,
      transparent 90%
    );
  }
}

.hero__vignette {
  position: absolute;
  inset: 0;
  z-index: -2;
  background: radial-gradient(120% 95% at 65% 40%, transparent 40%, rgb(11 12 15 / 0.68) 100%);
}

/* The top padding has to clear the floating pill, which ends about 76px down
   on a phone — 5rem left only 4px between them. */
.hero__inner {
  position: relative;
  padding-block: 7rem 7rem;
}

@media (min-width: 640px) {
  .hero__inner {
    padding-block: 7.5rem 8rem;
  }
}

.hero__content {
  max-width: 48rem;
  will-change: transform;
}

/* The eyebrow and the three info boxes are ONE material: same tint, same
   hairline, same lit top edge, same blur. They were close but not equal
   before — 0.10 fill against 0.08, 12px of blur against 16 — which is exactly
   the kind of difference that registers as untidiness without being
   identifiable. The shared values live in the two custom properties below. */
.hero {
  --glass-fill: rgb(255 255 255 / 0.1);
  --glass-edge: rgb(255 255 255 / 0.22);
}

.hero__badge {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  border-radius: 9999px;
  border: 1px solid var(--glass-edge);
  background: var(--glass-fill);
  padding: 0.4375rem 1.125rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: normal;
  color: rgb(255 255 255 / 0.92);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.16);
  backdrop-filter: blur(16px);
}

@media (min-width: 640px) {
  .hero__badge {
    font-size: 0.75rem;
  }
}

.hero__badge-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 9999px;
  background: var(--brand-400);
  animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

@keyframes pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.4;
  }
}

/* Display type is fluid, so the hero scales with the viewport instead of
   jumping at breakpoints, and the leading tightens as the size grows. */
.hero__title {
  margin-top: 1.5rem;
  font-size: clamp(2.5rem, 7.5vw, 5rem);
  line-height: 1.06;
  font-weight: 800;
  color: #fff;
  text-wrap: balance;
  /* Two shadows: a tight one that separates the letterform from whatever is
     immediately behind it, and a wide soft one that pools under the whole
     block. One large blur alone leaves the edges of thin strokes unsupported. */
  text-shadow: 0 1px 2px rgb(0 0 0 / 0.5), 0 2px 32px rgb(0 0 0 / 0.55);
}

@media (min-width: 640px) {
  .hero__title {
    margin-top: 1.75rem;
  }
}

/* brand-400 measures Lc 43–45 and about 5:1 against the real pixels behind it:
   clears WCAG AA for large text and sits just under APCA's simplified Lc 45
   floor at this size and weight. A known, accepted trade-off — brand-300 passes
   everything but reads as washed-out salmon. */
.hero__title span {
  display: block;
  margin-top: 0.25rem;
  color: var(--brand-400);
}

.hero__lead {
  margin-top: 1rem;
  max-width: 36rem;
  font-size: clamp(1.0625rem, 2.4vw, 1.5rem);
  line-height: 1.7;
  color: rgb(255 255 255 / 0.88);
  text-wrap: balance;
  text-shadow: 0 1px 16px rgb(0 0 0 / 0.45);
}

@media (min-width: 640px) {
  .hero__lead {
    margin-top: 1.25rem;
  }
}

.hero__actions {
  margin-top: 1.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

@media (min-width: 640px) {
  .hero__actions {
    margin-top: 2rem;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 1rem;
  }
}

@media (min-width: 640px) {
  .hero__actions .btn {
    font-size: 1.125rem;
  }
}

.hero__highlights {
  margin-top: 2rem;
  display: grid;
  gap: 0.625rem;
  max-width: 42rem;
}

@media (min-width: 640px) {
  .hero__highlights {
    margin-top: 2.5rem;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.75rem;
  }
}

/* `align-items: stretch` on the grid plus `height: 100%` here keeps the three
   boxes identical even when one value wraps to a second line, which the
   longest of them does between roughly 700px and 820px. */
.hero__highlight {
  display: flex;
  height: 100%;
  align-items: center;
  gap: 0.75rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--glass-edge);
  background: var(--glass-fill);
  padding: 0.75rem 1rem;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.16);
  backdrop-filter: blur(16px);
  transition: background-color var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out), translate var(--dur-base) var(--ease-out);
}

@media (min-width: 640px) {
  .hero__highlight {
    padding-block: 0.875rem;
  }
}

@media (hover: hover) {
  .hero__highlight:hover {
    translate: 0 -2px;
    background: rgb(255 255 255 / 0.15);
    border-color: rgb(255 255 255 / 0.32);
  }
}

.hero__highlight-icon {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  flex-shrink: 0;
  border-radius: 9999px;
  background: rgb(255 255 255 / 0.16);
  color: #fff;
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.26);
  transition: background-color var(--dur-base) var(--ease-out);
}

@media (hover: hover) {
  .hero__highlight:hover .hero__highlight-icon {
    background: var(--brand-500);
    box-shadow: inset 0 0 0 1px var(--brand-400);
  }
}

.hero__highlight-text {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 0.125rem;
  line-height: 1.3;
}

.hero__highlight-label {
  font-size: 0.6875rem;
  line-height: 1.4;
  color: rgb(255 255 255 / 0.82);
}

.hero__highlight-value {
  font-size: 0.8125rem;
  font-weight: 700;
  color: #fff;
}

/*
   The scroll cue: a track with a segment falling down it, deliberately
   device-neutral. A mouse with a scroll wheel is a control half the site's
   visitors do not have; a falling segment means the same thing on a phone as on
   a laptop. `w-11` is the tap target, not the artwork — the track itself is 3px
   wide, and a 3px-wide link is not something anyone can hit.
*/
.scroll-cue {
  position: absolute;
  inset-inline: 0;
  bottom: 2.5rem;
  z-index: 20;
  margin-inline: auto;
  display: flex;
  width: 2.75rem;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  padding-block: 0.375rem;
  opacity: 1;
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out),
    visibility var(--dur-slow) linear;
}

@media (min-width: 640px) {
  .scroll-cue {
    padding-block: 0.5rem;
  }
}

@media (min-width: 768px) {
  .scroll-cue {
    bottom: 3rem;
  }
}

.scroll-cue[data-visible="false"] {
  opacity: 0;
  visibility: hidden;
  transform: translateY(14px);
}

.scroll-cue__track {
  position: relative;
  width: 3px;
  height: 2.5rem;
  overflow: hidden;
  border-radius: 9999px;
  background: rgb(255 255 255 / 0.3);
  transition: background-color var(--dur-base) var(--ease-out);
}

@media (min-width: 640px) {
  .scroll-cue__track {
    height: 3rem;
  }
}

.scroll-cue:hover .scroll-cue__track,
.scroll-cue:focus-visible .scroll-cue__track {
  background: rgb(255 255 255 / 0.45);
}

/* The segment is 30% of the track, so the travel is expressed in percentages of
   its own height and the track's height can change without a second number. */
.scroll-cue__dot {
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 30%;
  border-radius: 9999px;
  background: var(--brand-500);
  box-shadow: 0 0 10px 2px rgb(215 25 32 / 0.65);
  animation: cue-fall 2.2s ease-in-out infinite;
}

@keyframes cue-fall {
  0% {
    transform: translateY(-110%);
    opacity: 0;
  }
  22% {
    transform: translateY(0%);
    opacity: 1;
  }
  72% {
    transform: translateY(233%);
    opacity: 1;
  }
  100% {
    transform: translateY(340%);
    opacity: 0;
  }
}

/* ---- Tablet ------------------------------------------------------------
   BOTH blocks below sit at the END of the hero section on purpose. Written
   higher up — next to `.hero__inner`, where they logically belong — the base
   `.hero__title` and `.hero__highlight` rules that follow them won on source
   order, and the headline kept its 7.5vw desktop ramp on a landscape iPad
   while only the padding actually changed. Keep them last.
*/

/*
   Portrait tablet. The headline's 7.5vw ramp is tuned for the jump from phone
   to desktop and overshoots in the middle: at 768 it lands on 57.6px, which is
   most of a 704px line for a two-word name, and the 18px lead underneath it
   reads as an afterthought rather than as a second voice. Holding the headline
   near 3.25rem and lifting the lead closes that gap.
*/
@media (min-width: 768px) and (max-width: 1023px) {
  .hero__title {
    font-size: clamp(3rem, 6.4vw, 3.75rem);
  }

  .hero__lead {
    max-width: 34rem;
    font-size: clamp(1.125rem, 2.1vw, 1.3125rem);
  }

  .hero__highlights {
    max-width: none;
    gap: 0.625rem;
  }

  .hero__highlight {
    padding: 0.75rem 0.875rem;
    gap: 0.625rem;
  }

  .hero__highlight-icon {
    width: 2rem;
    height: 2rem;
  }

  .hero__highlight-icon svg {
    width: 1.0625rem;
    height: 1.0625rem;
  }
}

/*
   A tablet in LANDSCAPE is the short viewport this hero has to survive: an
   iPad Mini or Air turned sideways is 1024x768, and at that width the headline
   is already at its 1024 size. Measured there, the badge, headline, lead,
   buttons and info boxes came to about 518px against the 520px the fixed
   padding left them — overflowing on the next character of any label.

   The padding is what gives, not the type: 248px of it on a 768px-tall screen
   is a third of the viewport spent on air. Keyed to height and orientation
   rather than to width, because the same 1024px is roomy in portrait.
*/
@media (orientation: landscape) and (max-height: 820px) {
  .hero__inner {
    padding-block: 6rem 5rem;
  }

  .hero__highlights {
    margin-top: 1.5rem;
  }

  .hero__title {
    font-size: clamp(2.25rem, 5.2vw, 3.75rem);
  }

  .hero__lead {
    margin-top: 0.75rem;
    font-size: clamp(1rem, 1.7vw, 1.25rem);
  }

  .hero__actions {
    margin-top: 1.5rem;
  }

  .scroll-cue {
    bottom: 1.25rem;
  }
}

/* 8. Section dividers ----------------------------------------------------- */
/*
   A hairline with the company's mark on it, sitting ON the seam between two
   sections. It adds NO height: the negative margin cancels its own height, so
   the two sections stay flush and it straddles the boundary it marks. That is
   the whole spacing mechanism — the two `.section` paddings already put the seam
   at the exact midpoint between their content, so every divider is centred by
   construction. Give it real vertical padding instead and it stops being centred.

   `z-index` is required: the sections are statically positioned, so without it
   the later one paints its background over the chip.
*/

.divider {
  position: relative;
  z-index: 10;
  margin-block: -1.25rem;
}

@media (min-width: 768px) {
  .divider {
    margin-block: -1.5rem;
  }
}

/*
   The hero seam is the one place the divider must NOT straddle its boundary.

   The hero is exactly one viewport tall, so a divider centred on the seam puts
   half its medallion above the fold, peeking over the bottom edge of the first
   screen. That is why this divider was left out of the build entirely at one
   point.

   It is pushed fully BELOW the seam instead: no negative margin on top, the
   whole of its own height taken off the bottom. It still adds no height, the
   About section's content does not move, and the medallion's TOP EDGE lands
   exactly on the boundary rather than crossing it — measured 0px of intrusion
   into the hero at 375, 390, 768 and 1440.

   The scroll reveal is a second guarantee, not the only one, and the order
   matters. A straddling divider held at `scaleX(0)` would also be invisible on
   the first screen, but only until the observer fired — and on a phone the
   hero's `100dvh` GROWS when the URL bar retracts, which moves the fold under
   a divider that has already drawn itself. Geometry that cannot bleed is worth
   more than an animation that has to be timed correctly.
*/
.divider--seam {
  margin-block: 0 -2.5rem;
}

@media (min-width: 768px) {
  .divider--seam {
    margin-block: 0 -3rem;
  }
}

.divider__inner {
  position: relative;
  display: flex;
  height: 2.5rem;
  align-items: center;
  justify-content: center;
}

@media (min-width: 768px) {
  .divider__inner {
    height: 3rem;
  }
}

/*
   THE REVEAL — the line draws out from the middle, then the mark lands on it.
   --------------------------------------------------------------------------
   Both halves are one `scaleX` and one `scale`, so the whole thing runs on the
   compositor. The mark is held back 0.22s: it should arrive ON a line that is
   already most of the way drawn, not race it. Nothing here fires until
   `is-drawn` is added by the observer in script.js, so a divider below the fold
   stays at scaleX(0) — invisible, not merely transparent.

   The line is only 1px tall, so scaling it horizontally cannot distort a
   radius or a shadow the way scaling a rounded box would.
*/
.divider__line {
  position: absolute;
  inset-inline: 0;
  height: 1px;
  transform: scaleX(0);
  transform-origin: center;
  background: linear-gradient(to right, transparent, var(--brand-300), transparent);
  transition: transform var(--dur-draw) var(--ease-out);
}

.divider.is-drawn .divider__line {
  transform: scaleX(1);
}

/* Opaque, so the line reads as passing behind the mark. */
.divider__mark {
  position: relative;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 9999px;
  border: 1px solid var(--ink-200);
  background: #fff;
  box-shadow: var(--shadow-card);
  opacity: 0;
  transform: scale(0.55);
  transition: opacity var(--dur-reveal) var(--ease-out) 0.22s,
    transform var(--dur-reveal) var(--ease-out) 0.22s;
}

.divider.is-drawn .divider__mark {
  opacity: 1;
  transform: scale(1);
}

@media (min-width: 768px) {
  .divider__mark {
    width: 3rem;
    height: 3rem;
  }
}

.divider__mark img {
  width: 1.25rem;
  height: 1.25rem;
  object-fit: contain;
}

@media (min-width: 768px) {
  .divider__mark img {
    width: 1.5rem;
    height: 1.5rem;
  }
}

/* 9. About ---------------------------------------------------------------- */

.about__grid {
  margin-top: 3rem;
  display: grid;
  align-items: start;
  gap: 2.5rem;
}

@media (min-width: 768px) {
  .about__grid {
    margin-top: 4rem;
  }
}

/*
   Two columns from 768, not 1024. On a portrait tablet the single-column
   version was the phone layout stretched: a 704px-wide photograph followed by
   704px-wide feature rows whose text ran out after a third of the line. The
   pair sits comfortably at 768 — roughly 330px a side — and the photo's 4/3
   frame stops being the whole screen.
*/
@media (min-width: 768px) {
  .about__grid {
    grid-template-columns: 1.05fr 0.95fr;
    gap: 2.25rem;
  }
}

@media (min-width: 1024px) {
  .about__grid {
    gap: 3.5rem;
  }
}

/* On a phone the photo comes first and the content follows. */
.about__media {
  order: 1;
}

.about__content {
  order: 2;
}

@media (min-width: 768px) {
  .about__media {
    order: 2;
  }
  .about__content {
    order: 1;
  }
}

.about__frame {
  position: relative;
  /* A negative z-index child needs a stacking context to sit inside, or it
     falls behind the section's own background instead. */
  isolation: isolate;
}

/* A soft red pane behind the photo gives it depth without putting a dark block
   into a light section. */
.about__pane {
  position: absolute;
  inset: -0.75rem;
  z-index: -1;
  border-radius: var(--radius-2xl);
  background: linear-gradient(to bottom left, var(--brand-100), var(--brand-50), transparent);
}

@media (min-width: 768px) {
  .about__pane {
    inset: -1.25rem;
  }
}

.about__photo {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--radius-lg);
  border: 1px solid #fff;
  background: var(--ink-100);
  box-shadow: var(--elev-3);
}

.about__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A whisper of a scrim keeps the badge readable over the sky. */
.about__photo-scrim {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 8rem;
  background: linear-gradient(to top, rgb(22 24 29 / 0.35), rgb(22 24 29 / 0.1), transparent);
}

.about__badge {
  position: absolute;
  bottom: 1rem;
  inset-inline-start: 1rem;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  border-radius: 9999px;
  border: 1px solid var(--ink-100);
  background: rgb(255 255 255 / 0.95);
  padding: 0.5rem 1.25rem 0.5rem 0.5rem;
  padding-inline: 0.5rem 1.25rem;
  box-shadow: var(--elev-2);
  backdrop-filter: blur(4px);
}

/* The badge overhangs the photo's bottom edge only from `lg`. In the narrower
   768 column it stays inside the frame, where there is no neighbouring content
   for it to collide with. */
@media (min-width: 1024px) {
  .about__badge {
    bottom: -1.25rem;
    inset-inline-start: 1.5rem;
  }
}

.about__badge-number {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  flex-shrink: 0;
  border-radius: 9999px;
  background: var(--brand-500);
  color: #fff;
  font-size: 0.9375rem;
  font-weight: 800;
  box-shadow: var(--elev-brand-1);
}

@media (min-width: 1024px) {
  .about__badge-number {
    width: 3rem;
    height: 3rem;
    font-size: 1rem;
  }
}

.about__badge-text {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}

.about__badge-title {
  font-size: 0.8125rem;
  font-weight: 800;
  color: var(--ink-900);
}

@media (min-width: 1024px) {
  .about__badge-title {
    font-size: 0.875rem;
  }
}

.about__badge-since {
  margin-top: 0.125rem;
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--accent-text);
}

/* Feature cards: nothing collapsed. These four lines are the section's
   substance and the list is shorter than the photo beside it. */
.features {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* --radius-lg, matching the stat cards in the same section: these are both
   top-level surfaces and two radii on one screen read as an accident. */
.feature {
  display: flex;
  align-items: flex-start;
  gap: 0.875rem;
  border-radius: var(--radius-lg);
  border: 1px solid var(--ink-200);
  background: #fff;
  padding: 1rem;
  box-shadow: var(--shadow-card);
  /* `translate` is its own property — naming only `transform` here would leave
     the lift snapping instantly while the shadow eased. */
  transition: translate var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) var(--ease-out),
    border-color var(--dur-slow) var(--ease-out),
    transform var(--dur-instant) var(--ease-out);
}

@media (min-width: 640px) {
  .feature {
    gap: 1rem;
    padding: 1.25rem;
  }
}

/*
   Hover effects are behind `@media (hover: hover)` from here on. On a touch
   screen there is no pointer to leave an element, so a tap latches the hover
   state and the card stays lifted and tinted until something else is touched —
   which reads as a bug, not as feedback. Keyboard focus keeps its own rules
   outside the query so nothing is lost to someone tabbing through.
*/
@media (hover: hover) {
  .feature:hover {
    translate: 0 -2px;
    border-color: var(--brand-200);
    box-shadow: var(--shadow-card-hover);
  }
}

.feature__icon {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  flex-shrink: 0;
  border-radius: 9999px;
  background: var(--brand-50);
  color: var(--accent-text);
  box-shadow: inset 0 0 0 1px var(--brand-100);
  transition: background-color var(--dur-slow) var(--ease-out),
    color var(--dur-slow) var(--ease-out), box-shadow var(--dur-slow) var(--ease-out);
}

@media (hover: hover) {
  .feature:hover .feature__icon {
    background: var(--brand-500);
    color: #fff;
    box-shadow: inset 0 0 0 1px var(--brand-500);
  }
}

.feature__body {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 0.25rem;
}

.feature__title {
  font-size: 0.9375rem;
  font-weight: 800;
  line-height: 1.4;
  color: var(--ink-900);
  transition: color var(--dur-base) var(--ease-out);
}

@media (min-width: 640px) {
  .feature__title {
    font-size: 1rem;
  }
}

/* The tablet column is the narrowest place these four cards ever live, so the
   padding comes back in rather than the text wrapping to three lines. */
@media (min-width: 768px) and (max-width: 1023px) {
  .feature {
    gap: 0.875rem;
    padding: 1rem;
  }

  .feature__title {
    font-size: 0.9375rem;
  }
}

@media (hover: hover) {
  .feature:hover .feature__title {
    color: var(--brand-700);
  }
}

.feature__text {
  font-size: 0.8125rem;
  line-height: 1.8;
  color: var(--text-secondary);
}

@media (min-width: 640px) {
  .feature__text {
    font-size: 0.875rem;
  }
}

.about__actions {
  margin-top: 2rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

@media (min-width: 640px) {
  .about__actions {
    flex-direction: row;
    gap: 1rem;
  }
}

/* Stat cards. Top-aligned, NOT centred: with the stack centred, a hint that
   wraps to two lines shifts that card's whole stack and knocks its number and
   label off the line the other three sit on. */
.stats {
  margin-top: 4rem;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
  /* Keeps all four the same height, not just the two in a row. */
  grid-auto-rows: 1fr;
}

@media (min-width: 640px) {
  .stats {
    gap: 1.25rem;
  }
}

@media (min-width: 768px) {
  .stats {
    margin-top: 5rem;
    gap: 1.5rem;
  }
}

@media (min-width: 1024px) {
  .stats {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.stat {
  display: flex;
  height: 100%;
  flex-direction: column;
  justify-content: flex-start;
  align-items: center;
  text-align: center;
  padding: 1.25rem 1rem;
  transition: translate var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) var(--ease-out),
    border-color var(--dur-slow) var(--ease-out),
    transform var(--dur-instant) var(--ease-out);
}

@media (min-width: 768px) {
  .stat {
    padding: 1.5rem 1.25rem;
  }
}

@media (hover: hover) {
  .stat:hover {
    translate: 0 -2px;
    border-color: var(--brand-200);
    box-shadow: var(--shadow-card-hover);
  }
}

/* The figure gets a fixed-height box in REM — an `em` value here would resolve
   against the card's font size, not the figure's. */
.stat__value {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 3.25rem;
  font-size: clamp(1.35rem, 4vw, 1.85rem);
  font-weight: 800;
  line-height: 1.15;
  color: var(--accent-text);
}

.stat__label {
  margin-top: 0.5rem;
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--ink-900);
}

.stat__hint {
  margin-top: 0.375rem;
  font-size: 0.75rem;
  color: var(--text-muted);
}

.about__note {
  margin-top: 1.5rem;
  text-align: center;
  font-size: 0.8125rem;
  line-height: 1.8;
  color: var(--text-muted);
}

/* 11. Clients ------------------------------------------------------------- */

/*
   Six logos, so the column count is stated rather than left to `auto-fit`.
   The old `auto-fit` line claimed it never stranded a logo, and measured it
   did exactly that: five columns at 1024 left one logo alone on a second row,
   and at 1280 it opened a SEVENTH track that no logo ever filled, so the row
   sat visibly off-centre against an empty column.

   2 / 3 / 6 are the only counts that divide six evenly, which is the whole
   reason to name them.
*/
.clients__grid {
  margin-top: 3rem;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.25rem;
}

@media (min-width: 561px) {
  .clients__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 768px) {
  .clients__grid {
    margin-top: 4rem;
    gap: 1.5rem;
  }
}

@media (min-width: 1024px) {
  .clients__grid {
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 1rem;
  }
}

@media (min-width: 1180px) {
  .clients__grid {
    gap: 1.5rem;
  }
}

.client {
  display: grid;
  place-items: center;
  padding: 1rem;
  min-height: 7rem;
  transition: translate var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) var(--ease-out),
    border-color var(--dur-slow) var(--ease-out),
    transform var(--dur-instant) var(--ease-out);
}

@media (hover: hover) {
  .client:hover {
    translate: 0 -2px;
    border-color: var(--brand-200);
    box-shadow: var(--shadow-card-hover);
  }
}

@media (min-width: 1180px) {
  .client {
    padding: 1.25rem;
  }
}

.client img {
  max-height: 4.5rem;
  max-width: 100%;
  width: auto;
  object-fit: contain;
  filter: grayscale(1);
  opacity: 0.75;
  transition: filter var(--dur-slow) var(--ease-out), opacity var(--dur-slow) var(--ease-out);
}

@media (hover: hover) {
  .client:hover img {
    filter: grayscale(0);
    opacity: 1;
  }
}

/* 10. Products ------------------------------------------------------------ */

.filters {
  margin-top: 3rem;
}

@media (min-width: 768px) {
  .filters {
    margin-top: 4rem;
  }
}

/* Two pickers, one state: the listbox below 561px, the chips above it. Both
   read and write the same value, so whichever is on screen shows the current
   category. */
.filters__chips {
  display: none;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

@media (min-width: 561px) {
  .filters__chips {
    display: flex;
  }
  .filters__select {
    display: none;
  }
}

@media (min-width: 768px) {
  .filters__chips {
    gap: 0.625rem;
  }
}

.chip {
  position: relative;
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 0.5rem;
  border-radius: 9999px;
  border: 1px solid var(--ink-200);
  background: #fff;
  padding: 0.625rem 1rem;
  font-size: 0.875rem;
  font-weight: 700;
  white-space: nowrap;
  color: var(--ink-700);
  transition: background-color var(--dur-base) var(--ease-out),
    color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out), transform var(--dur-instant) var(--ease-out);
}

@media (min-width: 768px) {
  .chip {
    padding-inline: 1.25rem;
  }
}

.chip:active {
  transform: scale(0.98);
}

@media (hover: hover) {
  .chip:hover {
    border-color: var(--brand-200);
    background: rgb(255 243 241 / 0.6);
    color: var(--brand-700);
  }
}

.chip[aria-pressed="true"] {
  border-color: var(--brand-500);
  background: var(--brand-500);
  color: #fff;
  box-shadow: var(--elev-brand-1);
}

.chip__icon {
  width: 1.05rem;
  height: 1.05rem;
  flex-shrink: 0;
  color: var(--brand-500);
  transition: color var(--dur-base) var(--ease-out);
}

.chip[aria-pressed="true"] .chip__icon {
  color: #fff;
}

.chip__count {
  display: grid;
  place-items: center;
  height: 1.25rem;
  min-width: 1.25rem;
  padding-inline: 0.375rem;
  border-radius: 9999px;
  background: var(--ink-100);
  color: var(--ink-500);
  font-size: 0.6875rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  transition: background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

@media (hover: hover) {
  .chip:hover .chip__count {
    background: var(--brand-100);
    color: var(--brand-700);
  }
}

.chip[aria-pressed="true"] .chip__count {
  background: rgb(255 255 255 / 0.2);
  color: #fff;
}

/*
   Five chips, one row. At the default tablet size the fifth wrapped onto a
   line of its own and sat centred under the other four, which reads as a
   mistake rather than as a second row. Trimming the padding and dropping the
   label a step returns about 70px across the group — enough for all five to
   share the 704px the container gives them at 768.
*/
@media (min-width: 768px) and (max-width: 1023px) {
  .filters__chips {
    gap: 0.5rem;
  }

  .chip {
    padding: 0.5rem 0.75rem;
    font-size: 0.8125rem;
    gap: 0.375rem;
  }

  .chip__count {
    height: 1.125rem;
    min-width: 1.125rem;
    padding-inline: 0.3125rem;
    font-size: 0.625rem;
  }
}

/* The phone picker: a real listbox, not a native <select>, so it can carry the
   brand's shape and colour while keeping the keyboard contract a select has. */
.filters__select {
  position: relative;
  margin-inline: auto;
  width: 100%;
  max-width: 24rem;
}

.select__label {
  display: block;
  margin-bottom: 0.5rem;
  text-align: center;
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--text-secondary);
}

.select__trigger {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  border-radius: 9999px;
  border: 2px solid var(--brand-200);
  background: #fff;
  padding: 0.875rem 1.25rem 0.875rem 1rem;
  padding-inline: 1.25rem 1rem;
  font-size: 1rem;
  font-weight: 800;
  color: var(--ink-900);
  box-shadow: var(--shadow-card);
  transition: border-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}

.select__trigger[aria-expanded="true"] {
  border-color: var(--brand-500);
}

.select__trigger:hover {
  border-color: var(--brand-400);
}

.select__value {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: 0.625rem;
}

.select__value svg {
  flex-shrink: 0;
  color: var(--brand-500);
}

.select__chevron {
  color: var(--brand-500);
  transition: transform var(--dur-slow) var(--ease-out);
}

.select__trigger[aria-expanded="true"] .select__chevron {
  transform: rotate(180deg);
}

.select__list {
  position: absolute;
  inset-inline: 0;
  top: 100%;
  z-index: 30;
  margin-top: 0.5rem;
  overflow: hidden;
  border-radius: var(--radius-card);
  border: 1px solid var(--ink-200);
  background: #fff;
  padding: 0.375rem;
  box-shadow: var(--elev-3);
  transform-origin: top center;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px) scale(0.98);
  transition: opacity var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out), visibility var(--dur-base) linear;
}

.select__list[data-open="true"] {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
}

.select__option {
  display: flex;
  cursor: pointer;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  border-radius: var(--radius-sm);
  padding: 0.75rem 1rem;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink-700);
  transition: background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.select__option-label {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: 0.625rem;
}

.select__option-label svg {
  flex-shrink: 0;
  color: var(--brand-500);
}

.select__option[data-active="true"] {
  background: var(--brand-50);
  color: var(--brand-700);
}

.select__option[aria-selected="true"] {
  background: var(--brand-500);
  color: #fff;
}

.select__option[aria-selected="true"] svg {
  color: #fff;
}

/* The tick is markup on every option, so it is hidden unless that option is the
   selected one — otherwise every category appears to be chosen at once. */
.select__option > svg {
  display: none;
}

.select__option[aria-selected="true"] > svg {
  display: block;
}

/* The slider ------------------------------------------------------------- */
.slider {
  position: relative;
  margin-top: 2rem;
}

/* The viewport is inset so the cards never sit flush against the screen edge
   and, from `sm` up, the arrows live in that gutter instead of on top of them.

   It is also the arrows' positioning context. They used to be positioned
   against `.slider`, which includes the dots row underneath, and — because
   they set `transform: translateY(-50%)` but never set `top` — they resolved
   to their STATIC position and sat at the very bottom of the block. Measured
   at 1280: the card row's centre was at y=2720 and the arrows' at y=2972,
   252px below it, level with the last few pixels of the cards. */
.slider__viewport {
  position: relative;
  padding-inline: 0.75rem;
}

@media (min-width: 640px) {
  .slider__viewport {
    padding-inline: 3rem;
  }
}

@media (min-width: 1024px) {
  .slider__viewport {
    padding-inline: 4rem;
  }
}

/*
   Native scroll snapping rather than a transform-driven track. It gives touch
   swipe, momentum, keyboard scrolling and correct RTL behaviour for free — and
   in RTL that matters, because scrollLeft's sign and origin differ between
   engines, while snapping and relative `scrollBy` do not.
*/
/*
   THE STAGE — a coverflow, not a scroller.
   --------------------------------------------------------------------------
   The focused card sits in the middle at full size; its neighbours are scaled
   down, pushed to the sides, turned away on the Y axis and faded, so they read
   as a stack receding behind it.

   This replaced a `scroll-snap` container, and the replacement is what made
   the effect possible: a scroller can only put cards side by side, because
   scroll position is the only thing it can vary. Here every card's position is
   computed from its distance to the focused index, which is what lets scale,
   depth, rotation and opacity all move together.

   The change also removed the RTL hazard the scroller carried. `scrollLeft`'s
   sign and origin in a right-to-left container differ between engines, so
   every movement had to be expressed as a relative delta and never read back.
   The stage never touches `scrollLeft` at all — it places cards from a signed
   offset it owns, and direction is one multiplier set once from `dir`.

   HEIGHT IS THE STABILITY GUARANTEE. Every slide is a grid item in the SAME
   cell, so the track's height is the tallest card's natural height and nothing
   a transform does can change it — transforms do not affect layout. That is
   why sliding and filtering cannot make the block jump, collapse or reflow the
   page beneath it.
*/
.slider__stage {
  position: relative;
  overflow: hidden;
  /* Room inside the clip for the focused card's shadow, so `overflow: hidden`
     trims the receding cards without shearing the shadow off the hero. */
  padding-block: 1.5rem;
  perspective: 1400px;
  perspective-origin: 50% 50%;
  /* Vertical panning still belongs to the page; horizontal is the carousel's.
     Without this the browser claims the whole gesture and a swipe scrolls the
     document instead of moving the cards. */
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
}

.slider__track {
  display: grid;
  justify-items: center;
  align-items: start;
  transform-style: preserve-3d;
}

/*
   Every slide in one cell. `grid-area: 1 / 1` is the whole stacking mechanism
   — no absolute positioning, so the cell still reports a height, and no height
   measured in script that could disagree with the content.
*/
/*
   The hero card is deliberately NARROWER than the stage, most of all on a
   phone. A card as wide as the stage hides its neighbours completely behind
   itself — there is nowhere for a peek to appear — so 15.5rem at 375px leaves
   about 30px of the card behind showing at each side, which is what makes the
   stack read as a stack rather than as one card.
*/
.slider__slide {
  grid-area: 1 / 1;
  width: 100%;
  max-width: 15.5rem;
  transform-style: preserve-3d;
  backface-visibility: hidden;
  /* The transform, opacity and z-index are written per card by script.js from
     its distance to the focus; only the easing lives here. */
  transition: transform var(--dur-panel) var(--ease-out),
    opacity var(--dur-panel) var(--ease-out);
  will-change: transform, opacity;
}

@media (min-width: 560px) {
  .slider__slide {
    max-width: 20rem;
  }
}

@media (min-width: 1024px) {
  .slider__slide {
    max-width: 22rem;
  }
}

/* Hidden by the category filter. `display: none` and not `visibility`, because
   a filtered-out card must not hold the grid cell open at its own height. */
.slider__slide[hidden] {
  display: none;
}

/* Follow the pointer exactly while a drag is in progress; easing here would
   put the cards a frame behind the finger. */
.slider[data-dragging="true"] .slider__slide {
  transition: none;
}

/*
   Native image dragging, off.

   A press-and-move on a product photo used to hand the gesture to the
   browser's own drag-and-drop: it lifted a translucent ghost of the image and
   carried it away, and the carousel never moved because the pointer stream was
   cancelled the moment the drag started.

   Three locks, because one is not enough on its own. The attribute
   `draggable="false"` is the standard switch and is set in the markup;
   `-webkit-user-drag: none` covers WebKit, which honours the property rather
   than the attribute; and `script.js` cancels `dragstart` as the backstop for
   anything either of them misses, such as a selection dragged out of a label.
*/
.slider__stage img,
.modal__media img {
  -webkit-user-drag: none;
  -webkit-user-select: none;
  user-select: none;
}

/*
   The grab affordance is for fine pointers only. On a touch screen there is no
   cursor to show it to, and a `grab` cursor on a phone is a rule that renders
   nowhere and confuses the next person to read this file.
*/
@media (hover: hover) and (pointer: fine) {
  .slider__stage {
    cursor: grab;
  }

  .slider[data-dragging="true"] .slider__stage {
    cursor: grabbing;
  }
}

.slider__slide-inner {
  height: 100%;
}

/*
   Circular, white, red glyph, and centred on the CARD ROW — not on the slider
   block, which is taller by the dots row beneath it.

   A plain `50%` is exact now: the stage's vertical padding is symmetric, so
   the middle of the viewport is the middle of the card. It used to need a 4px
   correction for the asymmetric bottom padding the old scroll container
   carried.
*/
.slider__arrow {
  position: absolute;
  top: 50%;
  z-index: 10;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 9999px;
  border: 1px solid var(--ink-200);
  background: #fff;
  color: var(--accent-text);
  box-shadow: var(--elev-2);
  transform: translateY(-50%);
  transition: background-color var(--dur-base) var(--ease-out),
    color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out), transform var(--dur-instant) var(--ease-out);
}

@media (min-width: 640px) {
  .slider__arrow {
    width: 3rem;
    height: 3rem;
  }
}

.slider__arrow:focus-visible {
  background: var(--brand-500);
  color: #fff;
  border-color: var(--brand-500);
  box-shadow: var(--elev-brand-2);
}

@media (hover: hover) {
  .slider__arrow:hover {
    background: var(--brand-500);
    color: #fff;
    border-color: var(--brand-500);
    box-shadow: var(--elev-brand-2);
  }
}

.slider__arrow:active {
  transform: translateY(-50%) scale(0.94);
}

/*
   The arrows are on at every width now, phones included.

   They were hidden below `sm` when the cards sat side by side in a scroller
   and an edge-pinned button had nowhere to go but on top of one. In the
   coverflow the neighbours are scaled down and tucked BEHIND the focused card,
   so the outer thirds of the stage are the receding stack rather than
   readable content, and a button there covers nothing anyone is trying to
   read.
*/
@media (max-width: 559px) {
  .slider__arrow {
    width: 2.5rem;
    height: 2.5rem;
  }
}

/* In RTL the earlier slides live on the right, so the right-hand button — the
   one pointing right — steps backwards. */
.slider__arrow--prev {
  right: 0;
}

.slider__arrow--next {
  left: 0;
}

@media (min-width: 640px) {
  .slider__arrow--prev {
    right: 0.25rem;
  }
  .slider__arrow--next {
    left: 0.25rem;
  }
}

@media (min-width: 1024px) {
  .slider__arrow--prev {
    right: 0.5rem;
  }
  .slider__arrow--next {
    left: 0.5rem;
  }
}

/* 0.375rem of gap on a 1rem box is the 1.375rem pitch the pip geometry above
   is built around. Changing it changes how far the active pip overhangs. */
/* The arrows live in the viewport's gutter from `sm` up; at tablet widths the
   gutter is 3rem and the arrow 3rem, so it exactly fills it without reaching
   over a card. */
@media (min-width: 768px) and (max-width: 1023px) {
  .slider__viewport {
    padding-inline: 3.25rem;
  }

  .slider__arrow--prev {
    right: 0;
  }

  .slider__arrow--next {
    left: 0;
  }
}

/*
   THE POSITION READOUT — "3 / 13" over a thin progress line.
   --------------------------------------------------------------------------
   Thirteen dots were a poor fit for thirteen products: at 375px they were 8px
   apart, too small to aim at and too many to count. A number says exactly
   where you are, in one glance, at any width.

   `min-width` on the two figures plus tabular numerals keeps the pill from
   changing width as the count goes from 1 to 13 and back — a readout that
   twitches on every move is worse than no readout.
*/
.slider__meter {
  margin-top: 1.75rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.625rem;
}

/*
   Forced left-to-right. The three inline boxes — current, separator, total —
   lay out right-to-left in this document like everything else, so "7 / 13"
   rendered as "13 / 7" and the reader could not tell which number was which.

   `.ltr-value`'s warning does not apply here: it says never to isolate a string
   that MIXES Arabic and digits, because bidi already orders those correctly.
   This string is digits and a slash and nothing else, which is exactly the case
   the utility exists for.
*/
.slider__count {
  direction: ltr;
  unicode-bidi: isolate;
  display: inline-flex;
  align-items: baseline;
  gap: 0.375rem;
  border-radius: 9999px;
  border: 1px solid var(--border);
  background: var(--surface);
  padding: 0.3125rem 0.9375rem;
  font-size: 0.8125rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  box-shadow: var(--shadow-card);
}

.slider__current {
  min-width: 1.25rem;
  text-align: center;
  color: var(--accent-text);
}

.slider__total {
  min-width: 1.25rem;
  text-align: center;
}

.slider__count-sep {
  color: var(--ink-200);
}

/* The line is clipped to its own track and filled with a scaleX, so the
   indicator moves on the compositor and never reflows the row. */
.slider__progress {
  position: relative;
  display: block;
  width: min(12rem, 60%);
  height: 3px;
  overflow: hidden;
  border-radius: 9999px;
  background: var(--ink-100);
}

.slider__progress-fill {
  position: absolute;
  inset: 0;
  border-radius: 9999px;
  background: var(--brand-500);
  transform-origin: right center;
  transform: scaleX(0);
  transition: transform var(--dur-slow) var(--ease-out);
}

/* Fills from the reading edge — the right in this document, the left in an
   English one — so it grows the way the page is read. */
[dir="ltr"] .slider__progress-fill {
  transform-origin: left center;
}

/*
   A category change is two beats: the stack leaves, the set is swapped while
   nothing is on screen, the new stack arrives. Both beats are opacity and a
   small translate, so they stay on the compositor, and the direction follows
   the reading direction.
*/
.slider__track {
  transition: opacity var(--dur-swap) var(--ease-out),
    transform var(--dur-swap) var(--ease-out);
}

.slider[data-swapping="true"] .slider__track {
  opacity: 0;
  transform: translateX(-1.5rem);
  /* Nothing invisible should still be clickable on its way out. */
  pointer-events: none;
}

[dir="ltr"] .slider[data-swapping="true"] .slider__track {
  transform: translateX(1.5rem);
}

@keyframes slider-enter {
  from {
    opacity: 0;
    transform: translateX(1.5rem);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes slider-enter-ltr {
  from {
    opacity: 0;
    transform: translateX(-1.5rem);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

.slider__track--enter {
  animation: slider-enter 0.26s var(--ease-out) both;
}

[dir="ltr"] .slider__track--enter {
  animation-name: slider-enter-ltr;
}

/*
   The focused card is marked as the hero, not just placed as one. A red
   hairline and a red-tinted pool under it separate it from the neutral grey
   shadows of the cards behind, so which one is "the" product is legible even
   in a still frame — scale and opacity alone read as depth, not as selection.

   `data-focused` is written by script.js from the card's distance to the
   middle, so it survives a drag: the accent moves as the stack moves rather
   than waiting for the gesture to end.
*/
.slider__slide[data-focused="true"] .product {
  border-color: var(--brand-200);
  box-shadow: var(--elev-3), 0 0 0 1px rgb(215 25 32 / 0.16),
    0 14px 44px -18px rgb(215 25 32 / 0.3);
}

/* The cards behind keep a flat, neutral shadow — a coloured one on a card that
   is already faded reads as a smudge. */
.slider__slide[data-focused="false"] .product {
  box-shadow: var(--elev-1);
}

/* Product card ----------------------------------------------------------- */
/* Every block below the photo has a reserved height, so the name, the pills and
   the التفاصيل link sit on the same line across all thirteen cards. */

.product {
  display: flex;
  height: 100%;
  width: 100%;
  flex-direction: column;
  overflow: hidden;
  text-align: start;
  transition: translate var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) var(--ease-out),
    border-color var(--dur-slow) var(--ease-out),
    transform var(--dur-instant) var(--ease-out);
}

.product:focus-visible {
  translate: 0 -6px;
  border-color: var(--brand-200);
  box-shadow: var(--shadow-card-hover);
}

@media (hover: hover) {
  .product:hover {
    translate: 0 -6px;
    border-color: var(--brand-200);
    box-shadow: var(--shadow-card-hover);
  }
}

/* The packshots come at mixed aspect ratios and carry printed labels, so they
   are CONTAINED, never cropped — a cover crop would cut the product name off
   the packaging. */
.product__media {
  position: relative;
  aspect-ratio: 4 / 3;
  width: 100%;
  flex-shrink: 0;
  overflow: hidden;
}

.product__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 1.25rem;
  transition: transform var(--dur-reveal) var(--ease-out);
}

.product:focus-visible .product__media img {
  transform: scale(1.06);
}

@media (hover: hover) {
  .product:hover .product__media img {
    transform: scale(1.06);
  }
}

/* A hairline where the photo field meets the body, so the two read as two
   materials rather than one panel that changes colour. */
.product__media::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 1px;
  background: rgb(22 24 29 / 0.06);
}

.product__tag {
  position: absolute;
  top: 0.75rem;
  inset-inline-start: 0.75rem;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  border-radius: 9999px;
  border: 1px solid var(--brand-100);
  background: rgb(255 255 255 / 0.95);
  padding: 0.25rem 0.75rem 0.25rem 0.625rem;
  padding-inline: 0.625rem 0.75rem;
  font-size: 0.6875rem;
  font-weight: 800;
  color: var(--brand-700);
  box-shadow: var(--elev-1);
  backdrop-filter: blur(4px);
}

.product__tag::before {
  content: "";
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 9999px;
  background: var(--brand-500);
}

.product__body {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 1.25rem;
}

.product__kicker {
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--accent-text);
}

/* 3rem, not 2.75rem: at 17px and this leading two lines measure 46.8px, and the
   smaller reservation let a two-line name push the pills out of line. */
.product__name {
  margin-top: 0.375rem;
  min-height: 3rem;
  font-size: 1.0625rem;
  font-weight: 800;
  line-height: 1.4;
  color: var(--ink-900);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  transition: color var(--dur-base) var(--ease-out);
}

@media (hover: hover) {
  .product:hover .product__name {
    color: var(--brand-700);
  }
}

.product__variants {
  margin-top: 0.75rem;
  min-height: 3.75rem;
}

.variants {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.variants__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem 0.5rem;
}

.variants__label {
  flex-shrink: 0;
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--text-muted);
}

.variants__values {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}

/* Outlined, not filled: at 11px a fill-only pill reads as a coloured smear, and
   an outline gives each value its own edge. */
.pill {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  border-radius: 9999px;
  border: 1px solid var(--brand-100);
  background: rgb(255 243 241 / 0.7);
  padding: 0.25rem 0.625rem;
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--brand-700);
}

.pill--md {
  padding: 0.375rem 0.75rem;
  font-size: 0.8125rem;
}

.product__more {
  margin-top: auto;
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  border-top: 1px solid var(--ink-100);
  padding-top: 1rem;
  font-size: 0.875rem;
  font-weight: 800;
  color: var(--accent-text);
}

.product__more svg {
  transition: transform var(--dur-slow) var(--ease-out);
}

@media (hover: hover) {
  .product:hover .product__more svg {
    transform: translateX(-4px);
  }
}

/* Custom-order banner ---------------------------------------------------- */
.order-cta {
  margin-top: 4rem;
  display: flex;
  justify-content: center;
}

@media (min-width: 768px) {
  .order-cta {
    margin-top: 5rem;
  }
}

/* Both decorative layers are positioned, not negative-z children: a `-z-10`
   child would fall behind the card's own background. */
.order-cta__card {
  position: relative;
  width: 100%;
  max-width: 48rem;
  overflow: hidden;
  padding: 1.5rem;
  text-align: center;
}

@media (min-width: 768px) {
  .order-cta__card {
    padding: 2.5rem;
  }
}

.order-cta__edge {
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 3px;
  background: linear-gradient(to right, transparent, var(--brand-500), transparent);
}

.order-cta__wash {
  position: absolute;
  inset: 0;
  background: radial-gradient(80% 120% at 50% 0%, var(--brand-50) 0%, transparent 70%);
}

.order-cta__inner {
  position: relative;
}

.order-cta__icon {
  display: grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  margin-inline: auto;
  border-radius: 9999px;
  background: #fff;
  color: var(--accent-text);
  box-shadow: inset 0 0 0 1px var(--brand-100), var(--shadow-card);
}

.order-cta__title {
  margin-top: 1.25rem;
  font-size: 1.25rem;
  font-weight: 800;
  text-wrap: balance;
}

@media (min-width: 768px) {
  .order-cta__title {
    font-size: 1.5rem;
  }
}

.order-cta__text {
  margin-inline: auto;
  margin-top: 0.75rem;
  max-width: 36rem;
  line-height: 1.9;
  color: var(--text-secondary);
}

.order-cta__card .btn {
  margin-top: 1.5rem;
}

/* Product modal ---------------------------------------------------------- */
.modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-slow) var(--ease-out), visibility var(--dur-slow) linear;
}

@media (min-width: 640px) {
  .modal {
    align-items: center;
    padding: 1.5rem;
  }
}

.modal[data-open="true"] {
  opacity: 1;
  visibility: visible;
}

.modal__overlay {
  position: absolute;
  inset: 0;
  background: rgb(22 24 29 / 0.55);
  backdrop-filter: blur(3px);
}

/* A bottom sheet on phones, a centred dialog from `sm` up. */
.modal__dialog {
  position: relative;
  width: 100%;
  max-width: 44rem;
  max-height: 92dvh;
  overflow-y: auto;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: #fff;
  box-shadow: var(--elev-4);
  transform: translateY(24px);
  transition: transform var(--dur-panel) var(--ease-out), opacity var(--dur-panel) var(--ease-out);
  opacity: 0;
}

@media (min-width: 640px) {
  .modal__dialog {
    max-height: 88dvh;
    border-radius: var(--radius-lg);
    transform: translateY(0) scale(0.97);
  }
}

.modal[data-open="true"] .modal__dialog {
  transform: translateY(0) scale(1);
  opacity: 1;
}

.modal__close {
  position: absolute;
  top: 1rem;
  inset-inline-end: 1rem;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 9999px;
  border: 1px solid var(--ink-200);
  background: rgb(255 255 255 / 0.9);
  color: var(--ink-700);
  backdrop-filter: blur(4px);
  transition: background-color var(--dur-base) var(--ease-out),
    color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out),
    transform var(--dur-instant) var(--ease-out);
}

.modal__close:hover,
.modal__close:focus-visible {
  background: var(--brand-500);
  color: #fff;
  border-color: var(--brand-500);
}

.modal__media {
  position: relative;
  aspect-ratio: 16 / 10;
  width: 100%;
  overflow: hidden;
}

.modal__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 1.5rem;
}

.modal__body {
  padding: 1.5rem;
}

@media (min-width: 768px) {
  .modal__body {
    padding: 2rem;
  }
}

.modal__tag {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  border-radius: 9999px;
  background: var(--brand-50);
  padding: 0.25rem 0.75rem;
  font-size: 0.75rem;
  font-weight: 800;
  color: var(--brand-700);
}

.modal__tag::before {
  content: "";
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 9999px;
  background: var(--brand-500);
}

.modal__title {
  margin-top: 0.75rem;
  font-size: 1.375rem;
  font-weight: 800;
  line-height: 1.4;
  text-wrap: balance;
}

@media (min-width: 768px) {
  .modal__title {
    font-size: 1.625rem;
  }
}

.modal__text {
  margin-top: 0.75rem;
  line-height: 1.9;
  color: var(--text-secondary);
}

.modal__variants {
  margin-top: 1.5rem;
  border-radius: var(--radius-card);
  border: 1px solid var(--ink-200);
  background: var(--ink-50);
  padding: 1.25rem;
}

.modal__variants-title {
  font-size: 0.9375rem;
  font-weight: 800;
  color: var(--ink-900);
}

.modal__variants .variants {
  margin-top: 0.875rem;
  gap: 0.75rem;
}

.modal__cta {
  margin-top: 1.5rem;
  width: 100%;
}

@media (min-width: 640px) {
  .modal__cta {
    width: auto;
  }
}

/* 12. Contact ------------------------------------------------------------- */
/* Two pieces: the branded panel and the map, as a matched pair. The message
   form was removed on request — the section is call / WhatsApp / e-mail plus
   the map. */

/*
   The two panels are a MATCHED PAIR: same radius, same elevation, same gap on
   every side. They only read as one designed block if nothing distinguishes
   them but their contents.
*/
.contact__grid {
  margin-top: 3rem;
  display: grid;
  gap: 1.5rem;
  align-items: stretch;
}

/*
   Stacked, the map comes FIRST. On a phone the map is the thing that orients
   you — it answers "where is this" before the rows answer "how do I reach
   them" — and the details panel is the taller of the two, so leading with it
   pushed the map most of a screen down. The DOM keeps the panel first because
   that is the reading order the two-column layout wants, with the details on
   the inline-start side.
*/
@media (max-width: 767px) {
  .contact__map {
    order: -1;
  }
}

@media (min-width: 768px) {
  .contact__grid {
    margin-top: 4rem;
  }
}

/*
   Also from 768. Stacked, the red panel ran the full 704px while its longest
   row — the e-mail address — used barely a third of it, so each of the five
   rows trailed off into empty colour. Side by side the panel is about 340px,
   which is close to the measure the rows were designed at.
*/
@media (min-width: 768px) {
  .contact__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.5rem;
  }
}

@media (min-width: 1024px) {
  .contact__grid {
    gap: 2rem;
  }
}

/*
   The gradient was chosen by measurement, not by eye. White type has to clear
   its LIGHTEST stop, so the ramp runs brand-600 → brand-700 → brand-900: labels
   sit at white/85 (Lc 66 on brand-600) and values at full white (Lc 85).
   Anything lighter than brand-600 at the top fails the label colour —
   brand-500 puts white/85 at Lc 61 and 4.06:1.
*/
.contact-panel {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: linear-gradient(to bottom left, var(--brand-600), var(--brand-700), var(--brand-900));
  padding: 1.75rem;
  color: #fff;
  box-shadow: var(--elev-2);
}

@media (min-width: 768px) {
  .contact-panel {
    padding: 1.75rem;
  }
}

@media (min-width: 1024px) {
  .contact-panel {
    padding: 2.25rem;
  }
}

.contact-panel__glow {
  position: absolute;
  top: -6rem;
  inset-inline-end: -6rem;
  width: 18rem;
  height: 18rem;
  border-radius: 9999px;
  background: rgb(255 255 255 / 0.08);
  filter: blur(48px);
  pointer-events: none;
}

/* The base layer gives every h3 a dark colour and that rule beats the white
   inherited from a coloured panel — a heading inside one has to set its own. */
.contact-panel__title {
  position: relative;
  font-size: 1.25rem;
  font-weight: 800;
  color: #fff;
}

@media (min-width: 640px) {
  .contact-panel__title {
    font-size: 1.5rem;
  }
}

.contact-panel__lead {
  position: relative;
  margin-top: 0.5rem;
  font-size: 0.9375rem;
  line-height: 1.8;
  color: rgb(255 255 255 / 0.85);
}

.contact-panel__list {
  position: relative;
  margin-top: 1.75rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.contact-row {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 0.875rem;
  border-radius: var(--radius-card);
  padding: 0.75rem;
  /* `translate`, not `transform`, so the nudge composes with the reveal's own
     transform instead of cancelling it mid-entrance. */
  transition: background-color var(--dur-base) var(--ease-out),
    translate var(--dur-base) var(--ease-out);
}

/* Four of the five rows are links; the working-hours row is not, and it must
   not pretend to be. Every rule below is scoped to `a.contact-row`. */
a.contact-row:focus-visible {
  background: rgb(255 255 255 / 0.12);
}

@media (hover: hover) {
  a.contact-row:hover {
    background: rgb(255 255 255 / 0.12);
    /* Toward the reading edge — the right in this document. */
    translate: -0.25rem 0;
  }

  [dir="ltr"] a.contact-row:hover {
    translate: 0.25rem 0;
  }

  a.contact-row:hover .contact-row__icon {
    background: #fff;
    color: var(--brand-700);
    box-shadow: inset 0 0 0 1px #fff;
  }

  a.contact-row:hover .contact-row__go {
    opacity: 1;
    translate: 0 0;
  }
}

/*
   The outbound mark. It sits at rest and only arrives on hover, because five
   of them showing at once would read as decoration on a panel whose job is to
   be read, not scanned. It is `aria-hidden`: the link's own text already says
   where it goes.
*/
.contact-row__go {
  margin-inline-start: auto;
  align-self: center;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  color: rgb(255 255 255 / 0.9);
  opacity: 0;
  translate: 0.375rem 0;
  transition: opacity var(--dur-base) var(--ease-out),
    translate var(--dur-base) var(--ease-out);
}

[dir="ltr"] .contact-row__go {
  translate: -0.375rem 0;
}

.contact-row__go svg {
  width: 0.875rem;
  height: 0.875rem;
}

/* On a touch screen there is no hover to reveal it, so it is simply there. */
@media (hover: none) {
  .contact-row__go {
    opacity: 0.55;
    translate: 0 0;
  }
}

.contact-row__icon {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  border-radius: 9999px;
  background: rgb(255 255 255 / 0.15);
  color: #fff;
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.25);
  transition: background-color var(--dur-base) var(--ease-out),
    color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}

.contact-row__text {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 0.125rem;
}

.contact-row__label {
  font-size: 0.75rem;
  font-weight: 700;
  color: rgb(255 255 255 / 0.85);
}

.contact-row__value {
  font-size: 0.9375rem;
  font-weight: 700;
  line-height: 1.6;
  color: #fff;
  word-break: break-word;
}

/* The map box is a fixed height whether or not the iframe has arrived, so the
   third party can never shift the layout when it loads. */
/*
   The map box is a fixed height whether or not the iframe has arrived, so the
   third party can never shift the layout when it loads. `overflow: hidden` is
   what keeps the iframe's square corners inside the rounded ones — an iframe
   ignores a radius set on itself.
*/
.contact__map {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--ink-100);
  min-height: 22rem;
  box-shadow: var(--elev-2);
}

/* Once the map is beside the panel it matches its height instead of keeping a
   fixed one, so the pair reads as one row rather than two loose boxes. */
@media (min-width: 768px) {
  .contact__map {
    min-height: 100%;
  }
}

.contact__map iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.contact__map-placeholder {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--text-muted);
  font-size: 0.875rem;
}

/* 13. Footer -------------------------------------------------------------- */

.site-footer {
  background: var(--ink-900);
  color: rgb(255 255 255 / 0.8);
}

.site-footer__inner {
  padding-block: 3.5rem;
}

@media (min-width: 768px) {
  .site-footer__inner {
    padding-block: 4rem;
  }
}

.site-footer__columns {
  display: grid;
  gap: 2.5rem;
}

@media (min-width: 768px) {
  .site-footer__columns {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 3rem;
  }
}

@media (min-width: 1024px) {
  .site-footer__columns {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/*
   On a portrait tablet the two-column footer put the brand paragraph in a
   336px column beside the link list, where it wrapped to six short lines. It
   spans the full row instead, and the two lists sit side by side underneath —
   which is the same shape the desktop footer has, just stacked.
*/
@media (min-width: 768px) {
  .site-footer__brand {
    grid-column: span 2;
  }
}

@media (min-width: 1024px) {
  .site-footer__brand {
    grid-column: span 2;
  }
}

.site-footer__logo-link {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
}

.site-footer__logo {
  display: grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: var(--radius-md);
  background: #fff;
  padding: 0.5rem;
}

.site-footer__logo img {
  width: 2.5rem;
  height: 2.5rem;
  object-fit: contain;
}

.site-footer__wordmark {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}

.site-footer__name {
  font-size: 1.125rem;
  font-weight: 800;
  color: #fff;
}

.site-footer__tag {
  font-size: 0.75rem;
  color: rgb(255 255 255 / 0.7);
}

.site-footer__about {
  margin-top: 1.25rem;
  max-width: 28rem;
  font-size: 0.875rem;
  line-height: 1.9;
  color: rgb(255 255 255 / 0.75);
}

@media (min-width: 768px) {
  .site-footer__about {
    font-size: 1rem;
  }
}

/* One heading style for all columns: white, with a short red rule under it. */
.site-footer__heading {
  font-size: 0.875rem;
  font-weight: 800;
  color: #fff;
}

.site-footer__heading::after {
  content: "";
  display: block;
  margin-top: 0.625rem;
  width: 2rem;
  height: 2px;
  border-radius: 9999px;
  background: var(--brand-500);
}

.site-footer__list {
  margin-top: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* A marker that lights up rather than a layout that shifts: the dot is always
   in the flow and only its colour moves, so the label never slides. */
.site-footer__link {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  font-size: 0.875rem;
  color: rgb(255 255 255 / 0.75);
  transition: color var(--dur-base) var(--ease-out);
}

.site-footer__link::before {
  content: "";
  width: 0.375rem;
  height: 0.375rem;
  flex-shrink: 0;
  border-radius: 9999px;
  background: rgb(255 255 255 / 0.25);
  transition: background-color var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}

.site-footer__link:focus-visible {
  color: #fff;
}

@media (hover: hover) {
  .site-footer__link:hover {
    color: #fff;
  }

  .site-footer__link:hover::before {
    transform: scale(1.35);
  }
}

.site-footer__link:hover::before,
.site-footer__link:focus-visible::before {
  background: var(--brand-500);
}

.site-footer__contact {
  margin-top: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  font-size: 0.875rem;
}

.site-footer__contact li {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
}

.site-footer__contact svg {
  margin-top: 0.125rem;
  flex-shrink: 0;
  color: var(--brand-300);
}

.site-footer__contact a,
.site-footer__contact span {
  color: rgb(255 255 255 / 0.75);
  line-height: 1.8;
  transition: color var(--dur-base) var(--ease-out);
}

.site-footer__contact a:hover,
.site-footer__contact a:focus-visible {
  color: #fff;
}

.site-footer__bottom {
  margin-top: 3rem;
  border-top: 1px solid rgb(255 255 255 / 0.1);
  padding-top: 1.75rem;
}

/*
   The copyright is centred on the page, not centred in whatever space the login
   icon leaves over. From `sm` the icon is taken out of the flow and pinned to
   the end edge, so the text block stays centred on the container however wide
   the icon is; below `sm` it sits under the text, still centred.
*/
.site-footer__bar {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.25rem;
}

.site-footer__legal {
  text-align: center;
}

.site-footer__copyright {
  font-size: 0.75rem;
  color: rgb(255 255 255 / 0.7);
}

@media (min-width: 768px) {
  .site-footer__copyright {
    font-size: 0.875rem;
  }
}

/* white/70 is a FLOOR: at 12px on this ground it measures Lc 64, and white/45
   measures Lc 33.6 against a 60 minimum. The credit reads as secondary because
   it is smaller and because the link is the only bright thing in it. */
.site-footer__credit {
  margin-top: 0.5rem;
  font-size: 0.75rem;
  color: rgb(255 255 255 / 0.7);
}

.site-footer__credit a {
  font-weight: 700;
  color: rgb(255 255 255 / 0.9);
  text-decoration: underline;
  text-decoration-color: rgb(255 255 255 / 0.3);
  text-underline-offset: 4px;
  transition: color var(--dur-base) var(--ease-out),
    text-decoration-color var(--dur-base) var(--ease-out);
}

.site-footer__credit a:hover,
.site-footer__credit a:focus-visible {
  color: var(--brand-300);
  text-decoration-color: var(--brand-300);
}

.site-footer__login {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  border-radius: 9999px;
  border: 1px solid rgb(255 255 255 / 0.15);
  background: rgb(255 255 255 / 0.05);
  color: rgb(255 255 255 / 0.7);
  transition: background-color var(--dur-base) var(--ease-out),
    color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}

@media (min-width: 640px) {
  .site-footer__login {
    position: absolute;
    inset-inline-end: 0;
    top: 50%;
    transform: translateY(-50%);
  }
}

.site-footer__login:hover,
.site-footer__login:focus-visible {
  border-color: var(--brand-500);
  background: var(--brand-500);
  color: #fff;
}

/* 14. 404 ----------------------------------------------------------------- */
/*
   A dead end, and nothing else. The page carries no navbar, no footer and no
   scroll-to-top button: the mark in the bar is the way home, the button in the
   body is the way home, and there is nothing to scroll.

   Because there is no fixed pill above it, the content is centred in the space
   the bar leaves rather than pushed down by a padding value that has to guess
   the bar's height.
*/

.page-404 {
  background-color: var(--surface-sunken);
}

/* The minimal top bar. In flow, not fixed — a page this short has nothing to
   scroll under it, and a fixed bar here would only mean reserving space for
   itself twice. */
.minibar {
  position: relative;
  z-index: 10;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.minibar__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  height: 4.5rem;
}

@media (min-width: 768px) {
  .minibar__inner {
    height: 5rem;
  }
}

.minibar__brand {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-width: 0;
}

@media (min-width: 640px) {
  .minibar__brand {
    gap: 0.75rem;
  }
}

.minibar__logo {
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  object-fit: contain;
}

@media (min-width: 768px) {
  .minibar__logo {
    width: 2.75rem;
    height: 2.75rem;
  }
}

.minibar__wordmark {
  display: flex;
  min-width: 0;
  flex-direction: column;
  line-height: 1.2;
}

.minibar__name {
  font-size: 0.9375rem;
  font-weight: 800;
  color: var(--ink-900);
}

@media (min-width: 640px) {
  .minibar__name {
    font-size: 1rem;
  }
}

@media (min-width: 768px) {
  .minibar__name {
    font-size: 1.125rem;
  }
}

.minibar__tag {
  font-size: 0.625rem;
  font-weight: 600;
  color: var(--ink-400);
}

@media (min-width: 640px) {
  .minibar__tag {
    font-size: 0.6875rem;
  }
}

@media (min-width: 768px) {
  .minibar__tag {
    font-size: 0.75rem;
  }
}

/* `main` is already the flex child that takes the leftover height, so grid
   centring here needs no viewport arithmetic and no magic padding. */
.notfound {
  display: grid;
  place-items: center;
  padding-block: 3.5rem;
}

@media (min-width: 768px) {
  .notfound {
    padding-block: 5rem;
  }
}

.notfound__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

/* The mark sits on a white disc so it reads as a piece of the brand rather
   than a picture dropped onto the page. */
.notfound__mark {
  display: grid;
  place-items: center;
  width: 5.5rem;
  height: 5.5rem;
  border-radius: 9999px;
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

.notfound__mark img {
  width: 3rem;
  height: 3rem;
  object-fit: contain;
}

/*
   The number is the largest thing on the page and the least useful, so it is
   drawn as a watermark: brand-100 rather than brand-500, sitting behind the
   sentence that actually tells the visitor what happened. The heading keeps
   the contrast.
*/
.notfound__code {
  margin-top: 1.5rem;
  font-size: clamp(4.5rem, 22vw, 7.5rem);
  font-weight: 800;
  line-height: 0.9;
  color: var(--brand-100);
}

.notfound__title {
  margin-top: 0.5rem;
  font-size: clamp(1.5rem, 5vw, 1.875rem);
  font-weight: 800;
  text-wrap: balance;
}

.notfound__text {
  margin-top: 1rem;
  max-width: 30rem;
  line-height: 1.9;
  color: var(--text-secondary);
  text-wrap: balance;
}

.notfound__home {
  margin-top: 2rem;
}

/*
   The fallback for `inert`. Everything outside the header is marked inert by
   script.js while the menu is open; engines that implement the attribute block
   pointer and keyboard access themselves, and this covers the ones that do not.
   `[inert]` never appears on the header, so the menu and the button that opened
   it stay live.
*/
[inert] {
  pointer-events: none;
  user-select: none;
  cursor: default;
}

/* 15. Scroll reveal ------------------------------------------------------- */
/*
   Elements start life offset and transparent and are released when they scroll
   into view. Critically damped easing: it settles, it never overshoots —
   overshoot on something that merely appeared reads as decoration.

   Everything here is `opacity` and `transform`, so it stays on the compositor.

   TWO THINGS HERE ARE LOAD-BEARING.

   First, this block sits at the END of the stylesheet, so its `transition`
   shorthand beats the one a component declares for itself — `.feature`,
   `.stat`, `.product` and `.client` all carry `data-reveal`. While the
   attribute is on the element, the card's hover lift runs on THIS transition:
   half a second instead of the card's own 0.3s, and offset by whatever
   `data-reveal-delay` the script wrote inline, which is up to 340ms. That is
   what made every card hover feel late and heavy. The script now strips the
   attribute once the reveal has finished, which hands the element back to its
   own transition; `.is-visible` no longer requires the attribute so the final
   state survives.

   Second, there is deliberately no `will-change` here. It used to be declared
   on every `[data-reveal]` element at once — around sixty of them on the home
   page — which pins a compositor layer for each before any of them has
   animated. Opacity and transform are promoted for the duration of a
   transition anyway, so the hint bought nothing and cost memory on phones.
*/

[data-reveal] {
  opacity: 0;
  transition: opacity var(--dur-reveal) var(--ease-out),
    transform var(--dur-reveal) var(--ease-out);
}

[data-reveal="bottom"] {
  transform: translateY(28px);
}

[data-reveal="top"] {
  transform: translateY(-28px);
}

/* "start" is the right edge in RTL and "end" the left, matching the document
   direction, so reveals always feel like they follow the text. */
[data-reveal="start"] {
  transform: translateX(28px);
}

[data-reveal="end"] {
  transform: translateX(-28px);
}

[data-reveal="none"] {
  transform: none;
}

/* Keyed on the class alone, not on `[data-reveal].is-visible`: the script
   removes the attribute once the reveal is over, and the element must keep its
   revealed state after it goes. */
.is-visible {
  opacity: 1;
  transform: none;
}

/* 16. Display preferences ------------------------------------------------- */
/*
   Reduced motion is not "no feedback" — it is a gentler, non-vestibular
   equivalent: opacity instead of movement. Transparency and contrast are
   separate settings and are honoured separately.
*/

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.12s !important;
    scroll-behavior: auto !important;
  }

  /* Reveals become a plain fade — no travel. */
  [data-reveal] {
    transform: none !important;
  }

  /* The scroll cue's segment rests part-way down the track instead of falling. */
  .scroll-cue__dot {
    animation: none;
    transform: translateY(117%);
    opacity: 1;
  }

  .hero__badge-dot {
    animation: none;
  }

  /* The slider jumps rather than sliding. */
  .slider__track {
    transition: none;
  }

  .hero__media,
  .hero__content {
    transform: none !important;
  }

  /*
     Markers and states still CHANGE — they just stop travelling. The nav
     underline appears at full width instead of sliding in, the current dot is
     already wide, and the pill's mark holds one size. Removing the feedback
     entirely would leave a keyboard user with no way to see where they are.
  */
  .navbar__link::after {
    transition-property: background-color, box-shadow;
  }

  /*
     The coverflow flattens: no perspective, no Y rotation, no depth — script.js
     drops the 3D terms and keeps only the position, the scale and the opacity,
     so the stack still reads as a stack and the carousel still works. What goes
     is the part that moves toward and away from the reader, which is the part
     that provokes.
  */
  .slider__stage {
    perspective: none;
  }

  /*
     The dividers stop drawing and simply appear. The line is already full
     width and the mark is already full size; only their opacity moves, and the
     mark's delay goes with the motion — under reduced motion a staggered
     entrance is just a slower one, not a gentler one.
  */
  .divider__line {
    transform: scaleX(1);
    opacity: 0;
    transition: opacity 0.2s linear;
  }

  .divider.is-drawn .divider__line {
    opacity: 1;
  }

  .divider__mark {
    transform: none;
    transition: opacity 0.2s linear;
  }

  .divider.is-drawn .divider__mark {
    transform: none;
  }

  .navbar__link:hover::after,
  .navbar__link:focus-visible::after,
  .navbar__link[aria-current="true"]::after {
    transform: scaleX(1);
  }

  .navbar__logo {
    transform: none !important;
  }

  /* Lifts flatten; the border, colour and shadow still answer the pointer. */
  .feature:hover,
  .stat:hover,
  .client:hover,
  .product:hover,
  .product:focus-visible,
  .info-card:hover,
  .owner:hover,
  .btn:hover,
  .navbar__call:hover,
  .hero__highlight:hover,
  a.contact-row:hover,
  [dir="ltr"] a.contact-row:hover {
    translate: none;
  }

  /* The outbound mark stops sliding in and is simply present — the colour and
     background still answer the pointer. */
  .contact-row__go,
  [dir="ltr"] .contact-row__go {
    opacity: 0.75;
    translate: none;
  }

  .map__link:hover::after,
  [dir="ltr"] .map__link:hover::after {
    translate: none;
  }

  .product:hover .product__media img,
  .product:focus-visible .product__media img,
  .product:hover .product__more svg,
  .site-footer__link:hover::before {
    transform: none;
  }
}

@media (prefers-reduced-transparency: reduce) {
  /* Solid, not see-through. The two navbar states cannot both collapse to
     white: the type over the hero is light, so that pane goes solid ink. */
  .navbar__pane {
    backdrop-filter: none;
    background-image: none;
  }

  .navbar__pane--light {
    background-color: #fff;
  }

  .navbar__pane--dark {
    background-color: var(--ink-900);
  }

  .hero__badge,
  .hero__highlight,
  .btn--ghost,
  .product__tag,
  .modal__overlay,
  .modal__close,
  .menu-overlay,
  .about__badge {
    backdrop-filter: none;
  }
}

@media (prefers-contrast: more) {
  .navbar__pane {
    backdrop-filter: none;
    background-image: none;
  }

  .navbar__pane--light {
    background-color: #fff;
    box-shadow: inset 0 0 0 1.5px var(--ink-900);
  }

  .navbar__pane--dark {
    background-color: var(--ink-900);
    box-shadow: inset 0 0 0 1.5px rgb(255 255 255 / 0.85);
  }

  .card,
  .feature,
  .product,
  .client {
    border-color: var(--ink-500);
  }

  .btn--outline {
    border-color: var(--brand-600);
  }
}

/* 17. The /about page ----------------------------------------------------- */
/* One shape for every section: `.section` padding, the container, a start-aligned
   heading, then the content. */

.page-header {
  position: relative;
  overflow: hidden;
  background: var(--ink-900);
  color: #fff;
}

.page-header__wash {
  position: absolute;
  inset: 0;
  background: linear-gradient(to left, rgb(103 17 16 / 0.45), var(--ink-900), var(--ink-900));
}

.page-header__glow {
  position: absolute;
  top: -6rem;
  inset-inline-start: -6rem;
  width: 24rem;
  height: 24rem;
  border-radius: 9999px;
  background: rgb(215 25 32 / 0.14);
  filter: blur(48px);
}

.page-header__inner {
  position: relative;
  padding-block: 7rem 5rem;
}

@media (min-width: 768px) {
  .page-header__inner {
    padding-block: 9rem 7rem;
  }
}

.breadcrumb {
  margin-bottom: 1.5rem;
}

.breadcrumb ol {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.875rem;
  color: rgb(255 255 255 / 0.7);
}

.breadcrumb a {
  transition: color var(--dur-base) var(--ease-out);
}

.breadcrumb a:hover,
.breadcrumb a:focus-visible {
  color: #fff;
}

.breadcrumb__current {
  font-weight: 700;
  color: rgb(255 255 255 / 0.9);
}

.page-header__title {
  font-size: clamp(2.25rem, 6vw, 3.75rem);
  font-weight: 800;
  line-height: 1.15;
  color: #fff;
}

.page-header__title span {
  display: block;
  color: var(--brand-400);
}

.page-header__lead {
  margin-top: 1.5rem;
  max-width: 42rem;
  font-size: 1.125rem;
  line-height: 1.9;
  color: rgb(255 255 255 / 0.8);
}

@media (min-width: 768px) {
  .page-header__lead {
    font-size: 1.25rem;
  }
}

.about-page__body {
  margin-top: 3rem;
  max-width: 52rem;
}

@media (min-width: 768px) {
  .about-page__body {
    margin-top: 4rem;
  }
}

/*
   The timeline's line is drawn PER MILESTONE, not as one rail behind the list.
   An absolute rail on the <ol> resolves against the list's padding box, so any
   padding on the list silently knocks it out of line with the markers, which
   live inside the items — that was an 8px drift.

   The geometry comes from two numbers: the marker is 24px starting 6px down, so
   the connector starts at 30px and ends 38px past the item (one 32px gap plus
   the next marker's own 6px offset). CHANGE THE LIST'S GAP AND THE `bottom`
   MUST CHANGE WITH IT.
*/
/*
   ONE track for the whole list, not a connector per milestone.

   The old version drew the line as `.timeline__marker::before` with
   `top: 24px; bottom: -38px`, and the comment above it worked the geometry out
   against the ITEM. But `.timeline__marker` is `position: absolute`, so it is
   its own pseudo-element's containing block, and those offsets resolved against
   the marker's 24px box instead — a fixed 34px stub. The real distance between
   consecutive nodes is 168 to 199px, because the cards differ in height, so the
   "line" was six stubs with five gaps of 134 to 165px between them.

   Now the track belongs to the list, spans first node centre to last node
   centre, and cannot gap by construction — there is only one of it. Its exact
   top and height are measured once by `initTimeline` and written here as custom
   properties; the fallbacks keep a sensible line if that never runs.

   `::before` is the grey track and `::after` is the red fill stacked on it. The
   fill is a scaleY from the top, driven by scroll progress, so the growth is a
   compositor transform and never a layout.
*/
.timeline {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

.timeline::before,
.timeline::after {
  content: "";
  position: absolute;
  inset-inline-start: 11px;
  top: var(--tl-top, 1.125rem);
  width: 2px;
  height: var(--tl-height, calc(100% - 2.25rem));
  border-radius: 9999px;
  pointer-events: none;
}

.timeline::before {
  background: var(--ink-100);
}

.timeline::after {
  background: linear-gradient(to bottom, var(--brand-400), var(--brand-500));
  transform-origin: top center;
  transform: scaleY(var(--tl-progress, 0));
  will-change: transform;
}

/* Item, marker and connector are defined in section 19 (timeline cards). */

/* The date renders as an `.eyebrow` pill, matching the original. */

/* Legal identity ---------------------------------------------------------- */
/*
   Five boxes in a two-column grid leaves one alone on the last row. Rather
   than pad the set or drop to one column, the LAST card takes the whole row —
   which suits it, because the odd one out is the capital figure and it is the
   number this section exists to state.

   `:last-child:nth-child(odd)` is the whole mechanism: it only fires when the
   count is odd, so the same rule can sit on a grid that sometimes holds an
   even number and do nothing there. `.info-grid` below relies on exactly that,
   because it is shared with the two-card capacity section.
*/
.legal-grid {
  display: grid;
  gap: 1rem;
}

@media (min-width: 640px) {
  .legal-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .legal-grid__item:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
}

/* Icon, then label over value — the same three-part shape the machinery cards
   and the contact rows use, so the three sections read as one family. */
.legal-grid__item {
  display: flex;
  align-items: flex-start;
  gap: 0.875rem;
  padding: 1.25rem;
  transition: border-color var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) var(--ease-out),
    translate var(--dur-slow) var(--ease-out);
}

@media (hover: hover) {
  .legal-grid__item:hover {
    translate: 0 -2px;
    border-color: var(--brand-200);
    box-shadow: var(--shadow-card-hover);
  }

  .legal-grid__item:hover .legal-grid__icon {
    background: var(--brand-500);
    color: #fff;
    box-shadow: inset 0 0 0 1px var(--brand-500);
  }
}

.legal-grid__icon {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  border-radius: 9999px;
  background: var(--brand-50);
  color: var(--accent-text);
  box-shadow: inset 0 0 0 1px var(--brand-100);
  transition: background-color var(--dur-base) var(--ease-out),
    color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}

.legal-grid__icon svg {
  width: 1.125rem;
  height: 1.125rem;
}

.legal-grid__text {
  min-width: 0;
}

.legal-grid__label {
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--text-muted);
}

.legal-grid__value {
  margin-top: 0.375rem;
  font-size: 1.0625rem;
  font-weight: 800;
  line-height: 1.5;
  color: var(--ink-900);
}

/* The full-width capital card gets the figure a size to match its span. */
@media (min-width: 640px) {
  .legal-grid__item:last-child:nth-child(odd) .legal-grid__value {
    font-size: 1.25rem;
    color: var(--accent-text);
  }
}

/* Owners ------------------------------------------------------------------ */
/*
   Three equal cards on one row, not three pills whose width follows the length
   of the name. The monogram sits above the name and everything is centred, so
   the set reads as a group of peers — which is what three owners of one company
   are. A grid rather than a wrapping flex row for the same reason: equal
   columns cannot produce a short third card stranded on its own line.
*/
.owners {
  display: grid;
  gap: 1rem;
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 560px) {
  .owners {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.owner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--surface);
  padding: 1.5rem 1.25rem;
  text-align: center;
  font-size: 1rem;
  font-weight: 800;
  color: var(--ink-900);
  box-shadow: var(--elev-1);
  transition: translate var(--dur-slow) var(--ease-out),
    border-color var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) var(--ease-out);
}

@media (hover: hover) {
  .owner:hover {
    translate: 0 -2px;
    border-color: var(--brand-200);
    box-shadow: var(--shadow-card-hover);
  }

  .owner:hover .owner__initial {
    background: var(--brand-500);
    color: #fff;
  }
}

.owner__initial {
  display: grid;
  place-items: center;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 9999px;
  background: var(--brand-50);
  color: var(--brand-700);
  font-size: 1.25rem;
  font-weight: 800;
  line-height: 1;
  box-shadow: inset 0 0 0 1px var(--brand-100);
  transition: background-color var(--dur-slow) var(--ease-out),
    color var(--dur-slow) var(--ease-out);
}

/* Machinery + capacity share the card grid -------------------------------- */
/*
   Two columns, and an ODD last card takes the whole row. The machinery set is
   five and used to leave one stranded; the capacity set is two and must not be
   touched, which is what `:last-child:nth-child(odd)` distinguishes — it only
   matches when the count is odd, so one rule serves both grids.
*/
.info-grid {
  display: grid;
  gap: 1rem;
  align-items: stretch;
}

@media (min-width: 640px) {
  .info-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.25rem;
  }

  .info-grid > .info-card:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }
}

/* `height: 100%` plus the grid's `align-items: stretch` is what keeps a row of
   cards level when one description wraps to a third line. */
.info-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 1.5rem;
  transition: translate var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) var(--ease-out),
    border-color var(--dur-slow) var(--ease-out),
    transform var(--dur-instant) var(--ease-out);
}

@media (hover: hover) {
  .info-card:hover {
    translate: 0 -2px;
    border-color: var(--brand-200);
    box-shadow: var(--shadow-card-hover);
  }
}

.info-card__icon {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  flex-shrink: 0;
  border-radius: 9999px;
  background: var(--brand-50);
  color: var(--accent-text);
  box-shadow: inset 0 0 0 1px var(--brand-100);
  transition: background-color var(--dur-base) var(--ease-out),
    color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}

@media (hover: hover) {
  .info-card:hover .info-card__icon {
    background: var(--brand-500);
    color: #fff;
    box-shadow: inset 0 0 0 1px var(--brand-500);
  }
}

.info-card__count {
  display: inline-flex;
  margin-top: 1rem;
  border-radius: 9999px;
  background: var(--brand-500);
  padding: 0.125rem 0.75rem;
  font-size: 0.75rem;
  font-weight: 800;
  color: #fff;
}

.info-card__title {
  margin-top: 0.75rem;
  font-size: 1.125rem;
  font-weight: 800;
  color: var(--ink-900);
}

.info-card__body {
  margin-top: 0.5rem;
  line-height: 1.9;
  color: var(--text-secondary);
}

.info-card__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  border-top: 1px solid var(--ink-100);
  margin-top: 0.875rem;
  padding-top: 0.875rem;
}

.info-card__row-label {
  font-size: 0.8125rem;
  color: var(--text-muted);
}

/*
   The capacity cards exist to state two figures, so the figures carry the
   weight rather than sharing it with their labels. Tabular numerals keep the
   monthly and the yearly rows aligned under each other.
*/
.info-card__row-value {
  font-size: 1.0625rem;
  font-weight: 800;
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
  color: var(--accent-text);
  text-align: end;
}

@media (min-width: 640px) {
  .info-card__row-value {
    font-size: 1.125rem;
  }
}

/* The last row has nothing under it, so it takes the leftover height and keeps
   the card's bottom edge level with its neighbour's. */
.info-card__row:last-child {
  margin-top: auto;
}

/* The dark CTA that closes the page. */
.about-cta {
  margin-top: 4rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.5rem;
  border-radius: var(--radius-xl);
  background: var(--ink-900);
  color: #fff;
  padding: 2rem;
}

@media (min-width: 768px) {
  .about-cta {
    margin-top: 5rem;
    flex-direction: row;
    align-items: center;
    padding: 3rem;
  }
}

.about-cta__title {
  font-size: 1.5rem;
  font-weight: 800;
  color: #fff;
}

@media (min-width: 768px) {
  .about-cta__title {
    font-size: 1.875rem;
  }
}

.about-cta__text {
  margin-top: 0.75rem;
  max-width: 36rem;
  line-height: 1.9;
  color: rgb(255 255 255 / 0.8);
}

.about-cta .btn {
  flex-shrink: 0;
  font-size: 1.125rem;
}

/* 18. Map header ---------------------------------------------------------- */
/* A titled bar above the map, matching the original: the heading on the reading
   edge, the "open in Maps" link opposite it. */

.contact__map {
  display: flex;
  flex-direction: column;
}

.map__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  border-bottom: 1px solid var(--border);
  background: #fff;
  padding: 1rem 1.25rem;
}

@media (min-width: 640px) {
  .map__bar {
    padding-inline: 1.75rem;
  }
}

.map__title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--ink-900);
}

.map__title svg {
  width: 1.125rem;
  height: 1.125rem;
  color: var(--accent-text);
}

/*
   The heading sits on the reading edge and the link opposite it — first in the
   DOM is the right in this document, which is why the order is not reversed
   here for RTL.
*/
.map__link {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  border-radius: 9999px;
  padding: 0.3125rem 0.75rem;
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--accent-text);
  background: transparent;
  transition: color var(--dur-base) var(--ease-out),
    background-color var(--dur-base) var(--ease-out);
  white-space: nowrap;
}

/*
   A chevron pointing the way the link travels — left in this document, right
   in an English one — so it reads as "goes somewhere else" rather than as a
   disclosure control.

   Two borders meeting at a corner, rotated so that corner leads. In RTL
   `border-inline-end` is the LEFT border, so the corner is top-left and -45deg
   turns it to point left; in LTR it is the right border, corner top-right, and
   +45deg points right. It was -135deg, which aimed the corner DOWN and made
   the link look like a dropdown.
*/
.map__link::after {
  content: "";
  width: 0.4375rem;
  height: 0.4375rem;
  border-top: 1.5px solid currentColor;
  border-inline-end: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: translate var(--dur-base) var(--ease-out);
}

[dir="rtl"] .map__link::after {
  transform: rotate(-45deg);
}

.map__link:focus-visible {
  color: var(--brand-700);
  background: var(--brand-50);
}

@media (hover: hover) {
  .map__link:hover {
    color: var(--brand-700);
    background: var(--brand-50);
  }

  .map__link:hover::after {
    translate: -0.125rem 0;
  }

  [dir="ltr"] .map__link:hover::after {
    translate: 0.125rem 0;
  }
}

.map__holder {
  position: relative;
  flex: 1;
  min-height: 20rem;
  background: var(--ink-50);
}

.map__holder iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* 19. Timeline cards ------------------------------------------------------ */
/* Each milestone is its own card on the rail: the date and the capital sit at
   the TOP of the card, the node is a white ring with a red centre, and the
   connector runs between consecutive nodes. */

.timeline__item {
  position: relative;
  padding-inline-start: 2.5rem;
}

/*
   The node sits ON the track — same 11px centre line, `z-index` above it, and
   an opaque white fill so the track reads as passing behind rather than
   through it.

   It waits, hollow and grey, until the red fill reaches it, then takes the
   brand colour and pops a little. The state is bidirectional: scroll back above
   a node and it returns to waiting, because the class is recomputed from the
   fill position on every frame rather than latched the first time.
*/
.timeline__marker {
  position: absolute;
  inset-inline-start: 0;
  top: 0.375rem;
  z-index: 10;
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 9999px;
  border: 2px solid var(--ink-200);
  background: #fff;
  box-shadow: none;
  transition: border-color var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}

.timeline__marker::after {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 9999px;
  background: var(--ink-200);
  transition: background-color var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}

.timeline__item.is-active .timeline__marker {
  border-color: var(--brand-500);
  transform: scale(1.1);
  box-shadow: 0 0 0 4px rgb(215 25 32 / 0.12);
}

.timeline__item.is-active .timeline__marker::after {
  background: var(--brand-500);
  transform: scale(1.15);
}

/* No per-marker connector any more — see the note on `.timeline` above. The
   list owns one continuous track and the markers sit on top of it. */

.timeline__card {
  padding: 1.25rem;
  transition: border-color var(--dur-slow) var(--ease-out),
    box-shadow var(--dur-slow) var(--ease-out);
}

/* Reached by the fill: the card takes the brand's hairline and steps up one
   elevation, so the milestone the reader is level with is the one that looks
   current. */
.timeline__item.is-active .timeline__card {
  border-color: var(--brand-200);
  box-shadow: var(--elev-2);
}

@media (min-width: 768px) {
  .timeline__card {
    padding: 1.5rem;
  }
}

@media (hover: hover) {
  .timeline__card:hover {
    border-color: var(--brand-200);
    box-shadow: var(--shadow-card-hover);
  }
}

.timeline__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

.timeline__capital {
  font-size: 0.875rem;
  font-weight: 800;
  color: var(--accent-text);
}

.timeline__title {
  margin-top: 0.75rem;
  font-size: 1.125rem;
  font-weight: 800;
  color: var(--ink-900);
}

@media (min-width: 768px) {
  .timeline__title {
    font-size: 1.25rem;
  }
}

.timeline__body {
  margin-top: 0.5rem;
  font-size: 0.875rem;
  line-height: 1.9;
  color: var(--text-secondary);
}

@media (min-width: 768px) {
  .timeline__body {
    font-size: 1rem;
  }
}
