/* ==========================================================================
   Metafori B1 — shared site layer (landing + product page)
   Sits on top of wireframe.css and overrides its palette and chrome.

   Type — six steps, two families, no italics, nothing lighter than 400
   and nothing lighter than 500 in navigation or on buttons:
     Tartuffo       display xl (h1) · display lg (h2)
     General Sans   lede · title (every h3) · body · small/label
   Tartuffo never appears below section-headline level, except the brand
   lockup in the footer.

   Colour — white, neutral greys and a "night" for the footer and menu.
   Two brand hues from the live site, both kept spare:
     purple (#4B00CD)  what you press, text links, the hero rotor, one word per headline
     coral  (#FF877A)  the warm accent (never text), kept for later use
   ========================================================================== */

:root {
  /* Neutral whites and space greys — the photography and the type carry
     the warmth; purple is kept for the few things you press */
  --paper: #ffffff;
  --paper-2: #f3f3f4;
  --paper-3: #e9e9ec;
  --ink: #17181c;
  --ink-60: #585b63;
  --ink-30: #8d9099;
  --line: #dadbdf;
  --line-soft: #e9eaed;
  --box: #eeeef1;

  --brand: #4b00cd;
  --brand-deep: #3a009f;
  --brand-tint: #efe8fc;
  /* The tint again, half strength — the surface the stage card sits on */
  --brand-wash: color-mix(in srgb, var(--brand-tint) 60%, var(--paper));
  --wash: #f3f3f4;
  /* Faint purple for tinted bands, a touch warmer than --brand-tint */
  --lavender: #f6eff8;
  --night: #18191d;
  --lilac: #cdb8ff;
  /* Text-safe purple for links and accents */
  --accent: var(--brand);

  /* Coral family (live-site peach) — decorative only, never text */
  --coral: #ff877a;
  --coral-soft: #fdb8b1;
  /* Faint coral, for surfaces */
  --coral-tint: #ffeeeb;
  --night-purple: #260056;
  /* Text that sits on coral, lilac or peach */
  --on-light: #17181c;
  --radius-device: 10px;

  /* The header's own height. Everything that hangs off the header — the
     phone menu, the sticky filter bar, scroll offsets — measures from this
     rather than repeating the number. */
  --hdr-h: 68px;

  /* Fine print — one step under the type floor, for legal asides only (the
     data-use line under the send button). Nothing anyone has to read to use
     the page may use it. */
  --text-fine: 0.875rem;

  /* Headlines lead: section titles large, intros a quiet step below */
  --size-lg: clamp(2.5rem, 5.2vw, 6rem);
  --size-md: clamp(1.7rem, 2.8vw, 2.8rem);
}

::selection {
  background: var(--brand);
  color: #fff;
}
.band--brand ::selection,
.band--night ::selection,
.foot ::selection {
  background: #fff;
  color: var(--brand);
}
:focus-visible {
  outline-color: var(--brand);
}

/* --------------------------------------------------------------------------
   Type
   -------------------------------------------------------------------------- */

/* A word of a headline in purple — the colour the hero's rotating phrase
   uses. Takes the place of the old hand-drawn underlines. */
.em {
  font-style: normal;
  color: var(--accent);
}
.lede {
  font-size: clamp(1.1rem, 1.35vw, 1.4rem);
  line-height: 1.45;
}
/* Tartuffo is tall and tight; give every headline a little more air */
.display--xl {
  line-height: 1.02;
}
.display--lg {
  line-height: 1.07;
}
.display--md {
  line-height: 1.1;
}
.display--sm {
  line-height: 1.16;
}
.label {
  color: var(--ink-60);
}
/* Credit or source line under an image — small and quiet */
.caption {
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.4;
  color: var(--ink-60);
}
.title {
  font-family: var(--sans);
  font-weight: 500;
  font-size: clamp(1.3rem, 1.6vw, 1.7rem);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin: 0;
  text-wrap: balance;
}

.ico {
  width: 28px;
  height: 28px;
  flex: none;
  fill: currentColor;
}
.ico--sm {
  width: 1em;
  height: 1em;
}

/* --------------------------------------------------------------------------
   Actions
   -------------------------------------------------------------------------- */

.btn {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}
.btn:hover {
  background: var(--brand-deep);
  border-color: var(--brand-deep);
  opacity: 1;
}
.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--ink);
}
.btn--ghost:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}
.link {
  gap: 6px;
  color: var(--accent);
}
/* Colour only on hover — the arrow holds still (neutralises the base
   wireframe.css rule that widened the gap) */
.link:hover {
  color: var(--brand-deep);
  gap: 6px;
}
/* Actions that fill their column once they can't sit side by side: the row
   measures itself (container query), so it stacks exactly where two
   typical actions (~530px together) would wrap — on phones, and in narrow
   columns on mid-size screens */
.btn-row--fill {
  container-type: inline-size;
}
@container (max-width: 540px) {
  .btn-row--fill .btn {
    flex: 1 1 100%;
    justify-content: center;
  }
}
.chip {
  background: var(--paper);
}
.chip:hover {
  border-color: var(--ink);
}
.chip[aria-pressed='true'] {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}

/* Small typed labels, not pills — for project types and product features.
   One line each: a label that wraps reads as a sentence, not a label. */
.tag {
  display: inline-block;
  white-space: nowrap;
  padding: 2px 8px;
  border: 0;
  border-radius: 4px;
  background: var(--paper-2);
  color: var(--ink-60);
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.5;
}

/* --------------------------------------------------------------------------
   Dark bands — available as brand purple or night; the live pages only
   use night, in the footer and the mobile menu
   -------------------------------------------------------------------------- */

.band--brand {
  background: var(--brand);
  color: #fff;
}
.band--night,
.band--ink {
  background: var(--night);
  color: #fff;
}
:is(.band--brand, .band--night, .band--ink) :is(.lede, .body, .label, .small) {
  color: rgba(255, 255, 255, 0.78);
}
:is(.band--brand, .band--night, .band--ink) .title {
  color: #fff;
}
:is(.band--brand, .band--night, .band--ink) .btn {
  background: #fff;
  border-color: #fff;
  color: var(--brand);
}
:is(.band--brand, .band--night, .band--ink) .btn:hover {
  background: var(--lilac);
  border-color: var(--lilac);
  color: var(--brand-deep);
}
:is(.band--brand, .band--night, .band--ink) .btn--ghost {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.6);
  color: #fff;
}
:is(.band--brand, .band--night, .band--ink) .btn--ghost:hover {
  background: #fff;
  border-color: #fff;
  color: var(--brand);
}
:is(.band--brand, .band--night, .band--ink) .link:hover {
  color: var(--lilac);
}
:is(.band--brand, .band--night, .band--ink) .frame {
  border-color: rgba(255, 255, 255, 0.14);
}
.ph--dark {
  background: #231546;
  border-color: #33235e;
}
.ph--dark .ph__label {
  background: var(--night);
  border-color: #33235e;
}

/* --------------------------------------------------------------------------
   Header — solid. The logotype left, the claim centred in the same cut and
   at the same scale (both SVGs share units, so one height token sizes
   both), navigation or a quiet menu button right.
   -------------------------------------------------------------------------- */

.hdr {
  --logo-h: 22px;
  background: var(--paper);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  /* No divider at the top of the page; site.js adds .is-scrolled */
  border-bottom: 1px solid transparent;
  transition: border-color 0.3s, background-color 0.3s;
}
.hdr.is-scrolled {
  border-bottom-color: var(--line-soft);
}
.hdr__inner {
  height: var(--hdr-h);
}
/* Logotype and claim are one link on the left; navigation takes the rest of
   the row and sits at its end */
.hdr__inner {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas: 'logo end';
  column-gap: clamp(10px, 1.1vw, 18px);
}
.hdr__end {
  grid-area: end;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: clamp(8px, 1.4vw, 20px);
}

@media (prefers-reduced-motion: reduce) {
  .hdr {
    transition: none;
  }
}
/* clip, not hidden: with hidden, <body> becomes the scroller as soon as the
   menu locks <html>, and the sticky header scrolls away with it */
body {
  overflow-x: clip;
  /* viewport-fit=cover runs the page to the physical edge; the insets are 0
     everywhere but a notched phone held sideways */
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.hdr__nav a.btn {
  color: #fff;
}
.hdr__nav a[aria-current='page'] {
  color: var(--ink);
}

/* A bare Phosphor symbol beside a label — the same piece the navigation
   carried, now on the actions that ask for something (it reads as noise on a
   link, and as a cue on a button). Decorative: the label speaks. */
.cta-ico {
  display: grid;
  place-items: center;
  flex: none;
  width: clamp(24px, 2vw, 30px);
  height: clamp(24px, 2vw, 30px);
  color: inherit;
}
.cta-ico .ico {
  width: 100%;
  height: 100%;
}
/* On a button it sits a step down from the label's own weight */
.btn .cta-ico {
  width: 20px;
  height: 20px;
  margin-right: -2px;
}
.btn .cta-ico .ico {
  width: 100%;
  height: 100%;
}
@media (min-width: 861px) {
  /* Room between the links comes from their own padding, so the lit area
     reads as one control */
  .hdr__nav {
    gap: clamp(2px, 0.6vw, 10px);
  }
  .hdr__nav > a:not(.btn) {
    display: inline-flex;
    align-items: center;
    padding: 8px 14px;
    border-radius: 999px;
    white-space: nowrap;
    transition: background-color 0.15s, color 0.15s;
  }
  .hdr__nav > a:not(.btn):hover {
    background: var(--brand-tint);
    color: var(--brand);
  }
  /* Over the hero, Contact is a quiet text control — the hero's own "Contact
     us" is the filled button on that screen. It fills in once the hero has
     scrolled past the bar. */
  .hdr.has-hero:not(.is-past-hero) .hdr__nav a.btn {
    background: transparent;
    border-color: transparent;
    color: var(--ink-60);
  }
  .hdr.has-hero:not(.is-past-hero) .hdr__nav a.btn:hover {
    background: var(--brand-tint);
    border-color: var(--brand-tint);
    color: var(--brand);
  }
}


.hdr__logo {
  grid-area: logo;
  justify-self: start;
  display: flex;
  align-items: center;
  column-gap: clamp(10px, 1.1vw, 18px);
  margin: 0;
  color: var(--ink);
  transition: color 0.15s;
}
/* The whole lockup — logotype and claim — lights up as one on hover */
.hdr__logo:hover,
.hdr__logo:focus-visible {
  color: var(--brand);
}
.hdr__logo:hover .hdr__claim,
.hdr__logo:focus-visible .hdr__claim {
  fill: var(--brand);
}
.hdr__word {
  display: block;
  height: var(--logo-h);
  width: auto;
  aspect-ratio: 71 / 18;
  fill: currentColor;
}
/* Tagline box is 24 units tall to the logotype's 18 (room for descenders), so
   it is sized by cap height instead of box height — the two words then read at
   one size; the nudge puts both baselines on one line. */
.hdr__claim {
  display: none;
  height: calc(var(--logo-h) * 24 / 18.5);
  width: auto;
  aspect-ratio: 211 / 24;
  translate: 0 calc(var(--logo-h) * 0.17);
  fill: var(--ink-60);
  transition: fill 0.15s;
}
@media (min-width: 1000px), (min-width: 520px) and (max-width: 860px) {
  .hdr__claim {
    display: block;
  }
}
/* Pages with a .hero keep the claim out of the first screen; site.js adds
   .is-past-hero once the hero has scrolled away */
.hdr.has-hero .hdr__claim {
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.4s, visibility 0s 0.4s;
}
.hdr.has-hero.is-past-hero .hdr__claim {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.4s;
}
@media (prefers-reduced-motion: reduce) {
  .hdr.has-hero .hdr__claim {
    transition: none;
  }
}

/* Plain text and icon, no chrome — the logo and claim lead */
.menu-btn {
  display: none;
  align-items: center;
  height: 44px;
  margin-right: -6px;
  padding: 0 6px;
  border: 0;
  background: none;
  color: var(--ink);
  font-size: var(--text-min);
  font-weight: 500;
  cursor: pointer;
  transition: color 0.2s;
}
.menu-btn > span {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.menu-btn .ico {
  width: 22px;
  height: 22px;
}
/* Child combinators so these outrank `.menu-btn > span` above */
.menu-btn > .menu-btn__close,
.menu-btn[aria-expanded='true'] > .menu-btn__open {
  display: none;
}
.menu-btn[aria-expanded='true'] > .menu-btn__close {
  display: inline-flex;
}
.menu-btn[aria-expanded='true'] {
  color: #fff;
}

/* Extras for the phone menu only: email, socials, language */
.hdr__more {
  display: none;
}

@media (max-width: 860px) {
  .menu-btn {
    display: inline-flex;
  }
}

/* Phones: the header is chrome, not a band — a shorter row and a slightly
   smaller lockup, so the page starts higher up the screen */
@media (max-width: 640px) {
  :root {
    --hdr-h: 56px;
  }
  .hdr {
    --logo-h: 19px;
  }
  .menu-btn {
    height: 40px;
  }
  .menu-btn .ico {
    width: 20px;
    height: 20px;
  }
}

@media (max-width: 860px) {
  /* A full-screen night panel under the header: the sections, Contact as
     the one button, then email, socials and language at the foot — out of
     the header grid, so no grid alignment narrows it */
  .hdr__nav {
    grid-area: auto;
    justify-self: stretch;
    position: fixed;
    top: calc(var(--bar-h) + var(--hdr-h));
    left: 0;
    right: 0;
    height: calc(100vh - var(--bar-h) - var(--hdr-h));
    height: calc(100dvh - var(--bar-h) - var(--hdr-h));
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: clamp(16px, 4vh, 40px) var(--gutter) 28px;
    background: var(--night);
    color: #fff;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    z-index: 50;
    opacity: 0;
    visibility: hidden;
    transition:
      opacity 0.3s ease,
      visibility 0s linear 0.3s;
  }
  .hdr__nav[data-open='true'] {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.3s ease;
  }
  .hdr__nav > a,
  .hdr__nav > a:hover,
  .hdr__nav > a[aria-current='page'] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 18px 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.14);
    font-size: var(--size-md);
    font-weight: 500;
    line-height: 1.1;
    color: #fff;
  }
  .hdr__nav > a:first-child {
    border-top: 1px solid rgba(255, 255, 255, 0.14);
  }

  .hdr__nav > a:not(.btn):hover {
    color: var(--lilac);
  }
  .hdr__nav > a.btn,
  .hdr__nav > a.btn:hover {
    justify-content: center;
    height: 56px;
    margin-top: 28px;
    padding: 0 24px;
    border: 0;
    background: #fff;
    color: var(--brand);
    font-size: 1.125rem;
    font-weight: 600;
  }
  .hdr__nav > a.btn:hover {
    background: var(--lilac);
    color: var(--brand-deep);
  }
  .hdr__more {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 14px 24px;
    margin-top: auto;
    padding-top: 40px;
    font-size: var(--text-min);
    font-weight: 500;
  }
  .hdr__more a {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-height: 44px;
    padding: 0;
    border: 0;
    color: rgba(255, 255, 255, 0.85);
    text-decoration: none;
  }
  .hdr__more a:hover {
    color: #fff;
  }
  .hdr__mail {
    width: 100%;
  }
  .hdr__more .ico {
    width: 18px;
    height: 18px;
  }
  .hdr__lang {
    margin-left: auto;
    color: rgba(255, 255, 255, 0.6);
  }
  /* The links rise into place as the panel fades in */
  .hdr__nav > *,
  .hdr__more {
    transform: translateY(12px);
    transition: transform 0.45s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  .hdr__nav[data-open='true'] > * {
    transform: none;
  }
  .hdr__nav[data-open='true'] > :nth-child(2) {
    transition-delay: 0.03s;
  }
  .hdr__nav[data-open='true'] > :nth-child(3) {
    transition-delay: 0.06s;
  }
  .hdr__nav[data-open='true'] > :nth-child(4) {
    transition-delay: 0.09s;
  }
  .hdr__nav[data-open='true'] > :nth-child(5) {
    transition-delay: 0.12s;
  }
  .hdr:has(.hdr__nav[data-open='true']) {
    background: var(--night);
    border-bottom-color: rgba(255, 255, 255, 0.14);
  }
  .hdr:has(.hdr__nav[data-open='true']) .hdr__logo {
    color: #fff;
  }
  .hdr:has(.hdr__nav[data-open='true']) .hdr__claim {
    fill: #fff;
  }
}
@media (max-width: 860px) and (prefers-reduced-motion: reduce) {
  .hdr__nav,
  .hdr__nav > *,
  .hdr__more {
    transform: none;
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   Section head — eyebrow + headline left, intro right, bottoms aligned
   -------------------------------------------------------------------------- */

.sec-head {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(20px, 3vw, 64px);
  align-items: end;
  margin-bottom: clamp(40px, 5vw, 80px);
}
.sec-head .label {
  margin-bottom: clamp(12px, 1.4vw, 20px);
}
/* 7.5em is exactly 15ch in Tartuffo (its `0` is a clean half-em), but stated
   in em the box no longer resizes with whatever face is painted during the
   font swap — `ch` scaled the box and the text together, so the heading kept
   rewrapping on load however the fallback was tuned. */
.sec-head h2 {
  max-width: 7.5em;
}
.sec-head .lede {
  /* em, not ch, for the same reason as the h2 above: 1ch in General Sans is
     0.59685em, so 46ch/62ch are 27.46em/37.01em — identical with the real
     face, but no longer rewrapping while the fallback is painted. */
  max-width: clamp(27.46em, 32vw, 37.01em);
}
/* Intro under the headline — for heads whose intro doesn't answer the
   controls beside it (the work filters) */
.sec-head--stacked {
  grid-template-columns: minmax(0, 1fr);
  margin-bottom: clamp(28px, 3.5vw, 48px);
}
.sec-head--stacked .lede {
  margin-top: clamp(16px, 1.6vw, 24px);
}
@media (max-width: 900px) {
  .sec-head {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
  }
}

/* --------------------------------------------------------------------------
   Media
   -------------------------------------------------------------------------- */

.frame {
  position: relative;
  margin: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--box);
}
.frame > img,
.frame > video,
.frame > iframe {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.7s cubic-bezier(0.2, 0.7, 0.2, 1);
}

/* Browser window around a plain website screenshot: rounded corners, a bar
   with three dots and an empty address field. Give it a bare page capture
   (no browser chrome, no background) and the frame does the rest. */
.frame.frame--browser {
  --bar: clamp(18px, 1.6vw, 24px);
  padding-top: var(--bar);
  border: 1px solid var(--line);
  border-radius: var(--radius-device);
  /* Faint purple bar — stands apart from the grey band and ties the
     mockups to the brand without reading as something to press */
  background: var(--brand-tint);
}
/* Window dots only — no address bar */
.frame--browser::before {
  content: '';
  position: absolute;
  z-index: 1;
  top: 0;
  left: calc(var(--bar) * 0.4);
  width: calc(var(--bar) * 1.5);
  height: var(--bar);
  background: radial-gradient(circle, var(--dot) 0 calc(var(--bar) * 0.15), transparent calc(var(--bar) * 0.15 + 0.5px))
    0 50% / calc(var(--bar) * 0.5) 100% repeat-x;
  --dot: color-mix(in srgb, var(--brand) 30%, transparent);
}
.frame--browser > img {
  border-top: 1px solid var(--line);
  object-position: top center;
}
.frame--browser .badge {
  top: calc(var(--bar) + 10px);
}

/* A cut-out (e.g. phones on a transparent background) on a quiet panel */
.frame--panel {
  background: var(--paper-2);
}
.frame.frame--panel > img {
  object-fit: contain;
  padding: 6% 4%;
}
.frame > iframe {
  border: 0;
}

/* Play button over a video poster; site.js swaps in the embed on click */
.frame__play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  width: 100%;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.frame__play .ico {
  box-sizing: content-box;
  width: clamp(22px, 2vw, 28px);
  height: clamp(22px, 2vw, 28px);
  padding: clamp(16px, 1.6vw, 22px);
  border-radius: 50%;
  background: color-mix(in srgb, var(--paper) 94%, transparent);
  color: var(--ink);
  box-shadow: 0 8px 30px -10px rgba(0, 0, 0, 0.45);
  transition: background 0.2s, color 0.2s;
}
.frame__play:hover .ico,
.frame__play:focus-visible .ico {
  background: var(--brand);
  color: #fff;
}
.frame__play:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: -4px;
}

/* Two screenshots, one overlapping the other */
.collage {
  position: relative;
  aspect-ratio: 5 / 4;
}
.collage .frame {
  position: absolute;
  aspect-ratio: 16 / 10;
  box-shadow: 0 40px 80px -30px rgba(10, 4, 30, 0.6);
}
.collage__a {
  top: 0;
  right: 0;
  width: 84%;
}
.collage__b {
  bottom: 0;
  left: 0;
  width: 58%;
}
.collage__cap {
  position: absolute;
  left: 12px;
  bottom: 12px;
  padding: 3px 10px;
  border-radius: 4px;
  background: #fff;
  color: var(--ink);
  font-size: 0.875rem;
  font-weight: 500;
}

/* Badge on an image — Lab.SNG, Mosaic, Ark */
.badge {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 1;
  padding: 3px 10px;
  border-radius: 4px;
  background: var(--paper);
  color: var(--ink);
  font-size: 0.875rem;
  font-weight: 500;
}
/* A label with a note — Lab.SNG on a project, a badge on an image. The note
   itself is one element site.js moves around (.note-tip): a pseudo-element
   sticks open after a tap on a phone, and can't follow the pointer. */
.badge[data-note],
.tag[data-note],
.project__origin[data-note] {
  cursor: help;
}
/* One note for the whole page, parked on <body> so no card can clip it */
.note-tip {
  position: fixed;
  z-index: 60;
  top: 0;
  left: 0;
  max-width: min(28ch, calc(100vw - 2 * var(--gutter)));
  padding: 10px 14px;
  border-radius: 6px;
  background: var(--night);
  color: #fff;
  font-size: var(--text-min);
  font-weight: 500;
  line-height: 1.4;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity 0.15s,
    visibility 0s linear 0.15s;
}
.note-tip[data-open='true'] {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.15s;
}
@media (prefers-reduced-motion: reduce) {
  .note-tip {
    transition: none;
  }
}
/* The label is a run of text inside a line, so the ring hugs it */
.project__origin[data-note]:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: 2px;
}

/* --------------------------------------------------------------------------
   Tabs — a row of labels on a rule; the chosen one is underlined in purple.
   Pair with role="tablist" / role="tab"; site.js handles [data-tabs]. On
   narrow screens the row scrolls sideways, edge to edge.
   -------------------------------------------------------------------------- */

.tabs {
  display: flex;
  gap: clamp(20px, 2.5vw, 40px);
  border-bottom: 1px solid var(--line);
}
.tabs__tab {
  flex: none;
  margin-bottom: -1px;
  padding: 14px 0 12px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--ink-60);
  font: 500 var(--text-min) / 1.2 var(--sans);
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.2s, border-color 0.2s;
}
.tabs__tab:hover {
  color: var(--ink);
}
.tabs__tab[aria-selected='true'] {
  color: var(--ink);
  border-bottom-color: var(--brand);
}
.tabs__tab:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
@media (max-width: 640px) {
  .tabs {
    overflow-x: auto;
    margin-inline: calc(-1 * var(--gutter));
    padding-inline: var(--gutter);
    scrollbar-width: none;
  }
  .tabs::-webkit-scrollbar {
    display: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .tabs__tab {
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   Cycle — stages on a ring as tabs; the chosen stage reads in the middle
   (below the ring on phones). Each stage has its own colour; the arc into
   the chosen stage and a soft disc behind the text take it on.
   With motion allowed, site.js starts advancing the stages after a while
   without interaction: a thin arc draws towards the next stage as the timer.
   Hover, a tap or keyboard focus stops it until things go quiet again.
   -------------------------------------------------------------------------- */

.cycle {
  --turn: 0;
  --dwell: 5s;
  /* One colour for every stage — the stages are steps, not categories */
  --c: var(--brand);
  --c-next: var(--brand);
  position: relative;
  width: 100%;
  /* The ring is a diagram beside the text, not the other way round: at 600px
     it dwarfed the stage it explains. Fluid from here up, so a wide screen
     gets a wide diagram; it stays the taller of the two at every stage, so
     the stage always centres against it. */
  max-width: clamp(236px, 39vw, 620px);
  margin-inline: auto;
}
.cycle__wheel {
  position: relative;
  aspect-ratio: 1;
}
/* The soft disc of colour the stage's icon sits on — the same surface as the
   card beside it, so the diagram and the stage read as one object */
.cycle__wheel::before {
  content: '';
  position: absolute;
  inset: 27%;
  border-radius: 50%;
  background: var(--brand-tint);
}
/* The ring: one bordered circle. The station pills are opaque and mask it
   where they sit, so each gets a gap its own width. */
.cycle__ring {
  position: absolute;
  inset: 12%;
  border: 2px solid var(--line);
  border-radius: 50%;
}
/* The leg being crossed, drawn as a conic sweep and masked down to the ring's
   own width. It is the timer and nothing else: it draws itself from the live
   stage towards the next one while the ring is running, and is not there at
   all when it isn't — picking a stage by hand lights the pill, not the ring. */
@property --arc {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}
.cycle__arc {
  --arc: 0deg;
  position: absolute;
  inset: 12%;
  border-radius: 50%;
  background: conic-gradient(var(--c) var(--arc), transparent 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 2px));
  rotate: calc(var(--turn) * 90deg);
  transition: rotate 0.6s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.cycle.is-playing .cycle__arc {
  animation: cycle-arc var(--dwell) linear forwards;
}
@keyframes cycle-arc {
  from {
    --arc: 0deg;
  }
  to {
    --arc: 90deg;
  }
}
/* Four heads on the ring, one between each pair of stations, pointing the
   way the loop runs. They are part of the ring, drawn in its colour, and the
   pointer never touches them — nothing here reads as something to press. */
.cycle__arrows {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.cycle__arrows path {
  fill: var(--line);
  stroke: var(--line);
  stroke-width: 2px;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
/* The head on the leg being crossed takes the arc's colour as the sweep
   reaches it — it sits at the middle of the leg, so it turns over halfway
   through the same timer. Nothing lights while the timer isn't running. */
.cycle.is-playing[data-step='0'] .cycle__arrows path:nth-child(1),
.cycle.is-playing[data-step='1'] .cycle__arrows path:nth-child(2),
.cycle.is-playing[data-step='2'] .cycle__arrows path:nth-child(3),
.cycle.is-playing[data-step='3'] .cycle__arrows path:nth-child(4) {
  animation: cycle-arrow var(--dwell) linear forwards;
}
@keyframes cycle-arrow {
  0%,
  46% {
    fill: var(--line);
    stroke: var(--line);
  }
  54%,
  100% {
    fill: var(--c);
    stroke: var(--c);
  }
}

/* Stations sit on the ring: top, right, bottom, left (ring r = 38% of the wheel) */
.cycle__station {
  --sc: var(--brand);
  --son: #fff;
  position: absolute;
  translate: -50% -50%;
  padding: clamp(5px, 0.55vw, 11px) clamp(10px, 1.1vw, 22px);
  border: 2px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  font: 500 clamp(0.875rem, 0.95vw, 1.2rem) / 1.2 var(--sans);
  white-space: nowrap;
  cursor: pointer;
  font-weight: 600;
  transition:
    background-color 0.3s,
    border-color 0.3s,
    color 0.3s,
    scale 0.3s cubic-bezier(0.3, 1.6, 0.5, 1);
}
.cycle__station:nth-child(1) { left: 50%; top: 12%; }
.cycle__station:nth-child(2) { left: 88%; top: 50%; }
.cycle__station:nth-child(3) { left: 50%; top: 88%; }
.cycle__station:nth-child(4) { left: 12%; top: 50%; }
.cycle__station:hover {
  border-color: var(--sc);
}
.cycle__station[aria-selected='true'] {
  background: var(--sc);
  border-color: var(--sc);
  color: var(--son);
  font-weight: 600;
  scale: 1.05;
}
.cycle__station:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
/* The chosen stage's icon in the centre of the wheel; the icons sit on top
   of each other and dissolve as the stage changes */
.cycle__arts {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}
.cycle__art {
  grid-area: 1 / 1;
  width: 16%;
  height: 16%;
  fill: var(--ink);
  opacity: 0;
  /* Leaving: quick, and gone before the next one starts */
  transition: opacity 0.16s ease;
}
.cycle[data-step='0'] .cycle__art:nth-child(1),
.cycle[data-step='1'] .cycle__art:nth-child(2),
.cycle[data-step='2'] .cycle__art:nth-child(3),
.cycle[data-step='3'] .cycle__art:nth-child(4) {
  opacity: 1;
  /* Arriving: after the old one has left, so the two never overlap */
  transition: opacity 0.22s ease 0.16s;
}

/* The chosen stage's text, beside the wheel: one faint purple card, the stage
   over a hairline and what we can be hired for under it. The card is the
   container, not the panel — only the live panel takes up room in it, so the
   card is as big as the stage it shows and site.js walks its height from one
   stage to the next while the text cross-dissolves in place. */
.process__panels {
  position: relative;
  border-radius: 12px;
  overflow: hidden;
  background: var(--brand-wash);
  transition: height 0.3s cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* The padding is the panel's, so the rule between the two halves stops at the
   text rather than running to the card's edges */
/* Two stages cross-fading through each other read as blurred text, so the
   swap is a handover instead: the old stage leaves in 0.16s, the new one
   arrives over the 0.22s after that, lifting the last few pixels into place. */
.process__panel {
  padding: clamp(16px, 2.2vw, 32px) clamp(16px, 1.7vw, 24px);
  transform: translateY(0);
  transition:
    opacity 0.22s ease 0.16s,
    transform 0.28s cubic-bezier(0.2, 0.7, 0.2, 1) 0.16s,
    visibility 0s;
}
.process__stage {
  padding: 0 0 clamp(18px, 1.95vw, 28px);
}
.process__panel .title {
  font-size: clamp(1.5rem, 2.5vw, 2.25rem);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.009em;
}
.process__panel[hidden] {
  display: block;
  position: absolute;
  inset: 0 0 auto;
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  transition:
    opacity 0.16s ease,
    transform 0.16s ease,
    visibility 0s linear 0.16s;
}
.process__panel .body {
  margin: 12px 0 0;
  max-width: clamp(34ch, 30.5vw, 38ch);
  font-size: clamp(0.875rem, 1.39vw, 1.25rem);
  line-height: 1.45;
  color: color-mix(in srgb, var(--ink) 80%, transparent);
}
/* What we can be hired for in this stage — under the rule */
.process__services {
  margin: 0;
  padding: clamp(18px, 2.15vw, 31px) 0 0;
  border-top: 1px solid var(--line);
}
.process__services-head {
  display: block;
  margin-bottom: clamp(8px, 1.1vw, 16px);
  font-size: clamp(0.9375rem, 1.11vw, 1rem);
  font-weight: 600;
  color: var(--ink);
}
/* On the card, the services are paper labels — the card carries the colour */
.process__services .tags {
  gap: 8px;
}
.process__services .tag {
  padding: 2px 8px;
  border: 0;
  border-radius: 8px;
  background: var(--paper);
  color: var(--brand);
  font-size: clamp(0.875rem, 1.11vw, 1.125rem);
  line-height: 1.5;
}

/* For anyone the four stages don't answer */
.process__ask {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px 24px;
  margin-top: clamp(20px, 2.2vw, 32px);
}
.process__ask .small {
  margin: 0;
  color: var(--ink-60);
}

/* One column — phones and small tablets. The ring goes on top, on the band,
   and under it one card holds the whole answer: the stage, what we can be
   hired for, and the way to ask for something else. So the surface moves out
   from the panels to the column, the button sits inside it at full width, and
   and under it the card, holding the stage and what we can be hired for. The
   ring runs a little larger here — at the two-column size it read as a token
   of itself — and the ask sits below the card, its button the card's width. */
@media (max-width: 960px) {
  .cycle {
    max-width: min(74vw, 340px);
  }
  .process__panel .body {
    max-width: 62ch;
  }
  .process__services .tags {
    gap: 4px;
  }
  .process__ask {
    margin-top: clamp(20px, 3vw, 24px);
  }
  .process__ask .btn {
    width: 100%;
    justify-content: center;
  }
}

/* Phones: the floating Contact is already on screen here, so the ask under
   the card would be a second purple button in the same view — it goes. */
@media (max-width: 860px) {
  .process__ask {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .cycle__station,
  .cycle__arc,
  .cycle__art,
  .process__panels,
  .process__panel,
  .process__panel[hidden] {
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   Stats (product page)
   -------------------------------------------------------------------------- */

.stats-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 32px);
  padding-top: clamp(18px, 2vw, 28px);
  border-top: 1px solid var(--line);
}
.stat__num {
  display: block;
  font-family: var(--display);
  font-size: clamp(2rem, 3vw, 3.4rem);
  line-height: 1;
  letter-spacing: -0.02em;
}
@media (max-width: 560px) {
  .stats-row {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --------------------------------------------------------------------------
   Project card — image, title, institution, description, then one footer
   row: type tags left, links right
   -------------------------------------------------------------------------- */

.projects {
  /* Filtering changes the grid's height; the browser's own scroll
     anchoring would fight site.js keeping the reader's place */
  overflow-anchor: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(56px, 5.5vw, 104px) clamp(24px, 3vw, 56px);
}
@media (max-width: 760px) {
  .projects {
    grid-template-columns: minmax(0, 1fr);
    gap: 56px;
  }
  /* Phones: an edge-to-edge rule between visible projects, halfway
     through the gap, so it's clear where one ends and the next begins */
  .project:not([hidden]) ~ .project:not([hidden]) {
    position: relative;
  }
  .project:not([hidden]) ~ .project:not([hidden])::before {
    content: '';
    position: absolute;
    top: -28px;
    left: calc(-1 * var(--gutter));
    right: calc(-1 * var(--gutter));
    border-top: 1px solid var(--line);
  }
  /* and one closing rule under the last visible project */
  .projects::after {
    content: '';
    grid-column: 1 / -1;
    margin-top: -28px;
    margin-inline: calc(-1 * var(--gutter));
    border-top: 1px solid var(--line);
  }
}
.project {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.project[hidden] {
  display: none;
}
.project .frame {
  aspect-ratio: 16 / 9;
}
.project:hover .frame > img {
  transform: scale(1.035);
}
.project__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding-top: clamp(20px, 2vw, 28px);
}
/* Project titles are the one place Tartuffo runs below section level —
   the work is the headline here */
.project .title {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(1.9rem, 2.3vw, 3rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
}
.project__client {
  margin: 6px 0 0;
  font-size: var(--text-min);
  color: var(--ink-60);
}
.project__desc {
  margin: 14px 0 0;
  max-width: 33.42em; /* 56ch in General Sans — see .sec-head .lede */
}
.project__foot {
  margin-top: auto;
  padding-top: clamp(20px, 2vw, 28px);
}
.project__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 24px;
}
/* Wider screens: the rule closes each project under its links */
@media (min-width: 761px) {
  .project__foot {
    padding-bottom: clamp(20px, 2vw, 28px);
    border-bottom: 1px solid var(--line);
  }
}
.tags,
.project__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.project__links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
}
.project__links .link {
  font-size: var(--text-min);
}

/* --------------------------------------------------------------------------
   Contact
   -------------------------------------------------------------------------- */

.field input,
.field textarea {
  background: var(--paper);
}
.field--check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  cursor: pointer;
}
/* .field--check sets display, so [hidden] needs saying again */
.field--check[hidden] {
  display: none;
}
.field--check input {
  width: auto;
  margin-top: 4px;
  accent-color: var(--brand);
}
.field--check span {
  display: block;
  margin: 0;
  font-weight: 400;
  color: var(--ink);
  line-height: 1.45;
}
/* Spam honeypot — off screen rather than display:none, which some bots skip */
.honeypot {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
/* The note about the data belongs with the button that sends it: beside it
   on one line where there is room, under it when there isn't */
.btn-row--note {
  gap: 12px 20px;
}
.btn-row--note .privacy {
  flex: 1 1 24ch;
  margin: 0;
  min-width: 0;
}
/* Phones: under the button, full width — beside it there is only room for
   three cramped lines */
@media (max-width: 640px) {
  .btn-row--note .privacy {
    flex-basis: 100%;
  }
}
.privacy {
  margin: 14px 0 0;
  font-size: var(--text-fine);
}

/* Form answers — the field that needs another look is outlined and told why,
   and the note by the button carries the answer for the form as a whole.
   Purple does the pointing: the palette has no error red. */
.field.is-error input,
.field.is-error textarea {
  border-color: var(--brand);
}
.field__error {
  display: block;
  margin-top: 6px;
  font-size: var(--text-fine);
  font-weight: 500;
  color: var(--brand);
}
[data-form-note].is-error {
  color: var(--brand);
  font-weight: 500;
}
/* On the night footer purple would sink into the dark; lilac carries it */
.foot .field.is-error input {
  border-color: var(--lilac);
}
.foot .field__error {
  color: var(--lilac);
}
.foot [data-form-note].is-error {
  color: #fff;
}
/* A button mid-send says so and cannot be pressed again */
.btn[disabled] {
  opacity: 0.55;
  pointer-events: none;
}
/* Sent — the button carries the confirmation instead of a line of text under
   the form: it keeps its colour, takes a tick, and waits, unpressable, until
   there is something new to send. */
.btn.is-sent[disabled] {
  opacity: 1;
}
.btn.is-sent .ico {
  animation: btn-tick 0.32s cubic-bezier(0.2, 0.8, 0.3, 1);
}
@keyframes btn-tick {
  from {
    opacity: 0;
    transform: scale(0.6);
  }
}
@media (prefers-reduced-motion: reduce) {
  .btn.is-sent .ico {
    animation: none;
  }
}
.reply {
  margin-top: clamp(20px, 2.4vw, 32px);
}
.reply .link {
  display: inline;
}

/* --------------------------------------------------------------------------
   Footer — night (charcoal). Three levels and no more: who we are, one short
   group of links, and the legal facts in a quiet base row. The newsletter is
   its own band on the page above (the product page still opens with
   .foot__news, below).
   -------------------------------------------------------------------------- */

.foot {
  background: var(--night);
  color: #fff;
  border-top: 0;
}
.foot :is(.body, .small) {
  color: rgba(255, 255, 255, 0.72);
}
.foot h3,
.foot .label {
  color: rgba(255, 255, 255, 0.78);
}
.foot li {
  color: rgba(255, 255, 255, 0.72);
}
.foot a {
  color: rgba(255, 255, 255, 0.8);
}
.foot a:hover {
  color: #fff;
}
.foot__base {
  color: rgba(255, 255, 255, 0.55);
  border-top-color: rgba(255, 255, 255, 0.14);
}
/* The legal facts: present, findable, and last in line */
.foot__base a {
  color: rgba(255, 255, 255, 0.55);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.foot__base a:hover {
  color: #fff;
}
.foot .todo {
  color: rgba(255, 255, 255, 0.55);
  border-color: rgba(255, 255, 255, 0.4);
}
.foot__logo {
  display: block;
  width: 96px;
  height: auto;
  margin-bottom: 18px;
}
/* The same 5 / 7 split the contact and newsletter bands use, so the footer
   sits on the page's grid instead of its own: who we are on the left, three
   short columns of links on the right. */
.foot__top {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(32px, 3vw, 64px);
  padding-bottom: clamp(40px, 5vw, 72px);
}
.foot__id .small {
  max-width: 40ch;
}
.foot__links {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(20px, 2.5vw, 48px);
}
.foot h3 {
  color: rgba(255, 255, 255, 0.5);
  letter-spacing: 0.02em;
}
.foot__links li {
  margin-top: 10px;
}
.foot__links a {
  display: inline-block;
  overflow-wrap: anywhere;
}
@media (max-width: 900px) {
  .foot__top {
    grid-template-columns: minmax(0, 1fr);
  }
}
@media (max-width: 560px) {
  .foot__links {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 32px 20px;
  }
}
.foot__logo path {
  fill: #fff;
}
.foot__news {
  display: grid;
  grid-template-columns: minmax(0, 6fr) minmax(0, 5fr);
  gap: clamp(24px, 4vw, 88px);
  align-items: end;
  padding-bottom: clamp(40px, 5vw, 80px);
  margin-bottom: clamp(40px, 5vw, 72px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}
.foot__news .display {
  max-width: 18ch;
}
.foot__news .small {
  margin: 14px 0 0;
  max-width: 48ch;
}
.foot .news__form .field {
  flex: 1 1 240px;
  margin: 0;
}
.foot .field span {
  color: rgba(255, 255, 255, 0.6);
}
.foot .field input {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.28);
  color: #fff;
}
.foot .btn {
  background: #fff;
  border-color: #fff;
  color: var(--ink);
}
.foot .btn:hover {
  background: var(--lilac);
  border-color: var(--lilac);
}
.foot [data-form-note] {
  flex-basis: 100%;
}
@media (max-width: 860px) {
  .foot__news {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
  }
}

.band--tint {
  background: var(--lavender);
}
.band--wash {
  background: var(--wash);
}

/* --------------------------------------------------------------------------
   Buttons — a tight pill, label only. No motion
   beyond the colour change on hover.
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 46px;
  padding: 0 20px;
  border-radius: 999px;
  font-size: var(--text-min);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  transition:
    background 0.15s,
    border-color 0.15s,
    color 0.15s;
}
.btn .ico {
  width: 18px;
  height: 18px;
  margin-right: -2px;
}
.btn--sm {
  height: 40px;
  padding: 0 16px;
}

/* Navigation never drops below medium */
.hdr__nav a {
  font-weight: 500;
}


/* Editorial product views: whole compositions remain visible at every width. */
.feature__visual {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: contain;
  margin-bottom: var(--stack-section);
}
.feature--illustrated .feature__head {
  justify-content: flex-end;
  margin-bottom: var(--stack-section);
}
