/* Appro home — brand: #2689E4, #1EDCEE, white, on graphite. */
:root {
  --blue: #2689E4;
  --cyan: #1EDCEE;
  --ink: #f4f6f8;
  --dim: #a7aeb8;
  --bg: #202225;
  --bg-2: #26282c;
  --line: #ffffff1f;
  /* The header's own height, so a page can slide artwork up underneath it.
     Measured rather than derived, because the header is a grid whose height
     comes from its tallest child; if it ever changes, the artwork starts a
     few pixels low, which is a blemish rather than a break. */
  --nav-h: 50px;
  --sans: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --mono: "SFMono-Regular", ui-monospace, "Courier New", monospace;
  color-scheme: dark;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: 6.5rem; }
body { margin: 0; min-width: 320px; background: var(--bg); color: var(--ink); font-family: var(--sans); -webkit-font-smoothing: antialiased; }
img { display: block; max-width: 100%; height: auto; }
a { color: inherit; }
:focus-visible { outline: 3px solid var(--cyan); outline-offset: 3px; border-radius: 3px; }
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.skip { position: fixed; z-index: 99; top: 1rem; left: 1rem; padding: .8rem 1.1rem; border-radius: .6rem; background: #fff; color: #111; font-weight: 700; text-decoration: none; transform: translateY(-200%); }
.skip:focus { transform: none; }
#main:focus { outline: none; }

/* particle field */
#field { position: fixed; z-index: 0; inset: 0; width: 100%; height: 100%; pointer-events: none; }
#root { position: relative; z-index: 1; }

/* type */
.eyebrow { margin: 0; font: 700 .7rem/1.4 var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--dim); }
.verify { margin: .5rem 0 0; font: 700 .62rem/1.4 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: #ffffffb8; }
/* The same caveat, worded as a sentence: uppercase mono is a tag, not prose. */
.verify.said { max-width: 46rem; margin-top: 1.4rem; font: 400 .92rem/1.6 var(--sans); letter-spacing: 0; text-transform: none; color: var(--dim); padding-left: .9rem; border-left: 2px solid color-mix(in srgb, var(--cyan) 45%, transparent); }
.kind { margin: .3rem 0 0; font: 700 .66rem/1.4 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--dim); }
h1, h2 { margin: .5rem 0 1rem; font-weight: 800; letter-spacing: -.045em; line-height: .95; }
h1 { font-size: clamp(2.9rem, 6.6vw, 6.4rem); line-height: .92; }
h2 { font-size: clamp(2.2rem, 4.6vw, 4.4rem); }
h1 em, h2 em { font-style: normal; background: linear-gradient(100deg, var(--blue), var(--cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; }
/* background-clip: text paints inside the inline box, and a glyph's right
   sidebearing sits outside it: the last coloured letter came out shaved
   (Matt, 2026-09-10). The padding gives the box the overhang back and the
   negative margin gives the layout its width back. */
h1 em, h2 em, h3 em { padding-right: .08em; margin-right: -.08em; }
p { line-height: 1.6; }
.section-head { max-width: 54rem; margin-bottom: clamp(2.5rem, 5vw, 4.5rem); transition: opacity .7s ease, transform .7s ease; }
.section-head:not(.in) { opacity: 0; transform: translateY(1.5rem); }
.section-head.in { opacity: 1; transform: none; }
.section-head > p:not(.eyebrow) { max-width: 44rem; font-size: 1.12rem; color: var(--dim); }
/* A section head that carries a mark: the words keep their measure and the
   mark goes to the far edge of the section, which is what Matt asked for on
   the festival page (2026-09-10). The text moves one level down inside it, so
   the rule above no longer reaches it and is repeated here. */
.section-head.is-marked { display: grid; grid-template-columns: minmax(0, 54rem) auto; gap: clamp(1.5rem, 4vw, 4rem); align-items: center; justify-content: space-between; max-width: none; }
.section-head.is-marked > div > p:not(.eyebrow) { max-width: 44rem; font-size: 1.12rem; color: var(--dim); }
/* The mark itself, lifted off its application tile: no rounded square, no
   ground, so it reads as the festival's identity rather than as an icon
   somebody forgot to open (Matt, 2026-09-10). */
.fest-mark { width: clamp(5.5rem, 12vw, 10.5rem); height: auto; filter: drop-shadow(0 0 2.2rem #8ea0ff40); }

.cta { display: inline-flex; align-items: center; gap: .6rem; padding: .95rem 1.5rem; border-radius: 999px; background: linear-gradient(100deg, var(--blue), var(--cyan)); color: #08131c; font-weight: 750; text-decoration: none; transition: transform .2s ease, filter .2s ease; }
.cta:hover { transform: translateY(-2px); filter: brightness(1.08); }
.cta.ghost { background: none; border: 1px solid #ffffff6b; color: var(--ink); }
.cta.ghost:hover { border-color: var(--cyan); }

/* nav */
/* 16 pixels of vertical padding in total, 8 above and 8 below, with the
   controls inside deciding the rest (Matt, 2026-09-10 — read the first time as
   16 each, which made the header taller instead of shorter). The tallest
   control is the Contact pill at 34, so the header is 50 at every width: down
   from 63 on a desktop and from 70 on a phone. The burger is sized to the
   pill rather than above it, which is what had pushed the phone to 70. */
.nav { position: sticky; z-index: 30; top: 0; display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 2rem; padding: 8px clamp(1.2rem, 4vw, 3.5rem); isolation: isolate; }
.nav > *:not(.nav-fade) { position: relative; z-index: 1; }
/* The header is glass, not a lid. It used to be 95% opaque, which meant the
   application icons scrolling up the home page were swallowed a header's height
   before the top of the screen — the grid appeared to end at the navigation
   rather than at the edge of the display. At 8% tint the icons stay visible all
   the way up, softened and darkened by the blur but never cut off.

   The tint cannot go to zero. Two things need it: the navigation text has to
   stay legible over whatever passes beneath, and a pure blur over a bright icon
   reads as a smear rather than as a surface. Eight percent is the point where
   the icons still read through and the text still holds — checked against the
   home page's icon field, which is the brightest thing that ever passes under
   the header.

   The layer spans the header plus fifty pixels and fades out across those last
   fifty, so content is never cut by a hard edge. The blur is four stacked
   backdrop layers whose masks retreat at different rates, which is what makes
   the softening gradual instead of stepped, and what makes this read as one
   piece of glass rather than as a translucent rectangle.
   The tail below the header is 18px, not the 50 it was written with. The
   header used to be 63 tall and a 50px tail read as a soft edge under it; at
   50 tall the tail was as deep as the header itself, which is the space Matt
   saw under the controls after the padding above them was already right
   (2026-09-10). Every stop below is that 50 scaled to 18. */
.nav-fade { position: absolute; z-index: 0; inset: 0 0 -18px; pointer-events: none; background: linear-gradient(to bottom, #20222514 0%, #20222514 calc(100% - 18px), #20222510 calc(100% - 12px), #20222508 calc(100% - 6px), #20222500 100%); }
/* No specular edge here, deliberately. One was tried on 2026-08-31 and read as
   a rule drawn across the page rather than as the edge of a pane: a hairline of
   light only says "glass" when the surface above it is lighter than what is
   below, and at an 8% tint it is not. The header ends by fading, and that is
   the whole of it. */
.nav-fade i { position: absolute; inset: 0; }
.nav-fade i:nth-child(1) { backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 18px), transparent calc(100% - 12px)); mask-image: linear-gradient(to bottom, #000 calc(100% - 18px), transparent calc(100% - 12px)); }
.nav-fade i:nth-child(2) { backdrop-filter: blur(9px); -webkit-backdrop-filter: blur(9px); -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 15px), transparent calc(100% - 8px)); mask-image: linear-gradient(to bottom, #000 calc(100% - 15px), transparent calc(100% - 8px)); }
.nav-fade i:nth-child(3) { backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 11px), transparent calc(100% - 3px)); mask-image: linear-gradient(to bottom, #000 calc(100% - 11px), transparent calc(100% - 3px)); }
.nav-fade i:nth-child(4) { backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 6px), transparent calc(100% - 0px)); mask-image: linear-gradient(to bottom, #000 calc(100% - 6px), transparent calc(100% - 0px)); }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav-fade { position: absolute; z-index: 0; inset: 0 0 -18px; pointer-events: none; background: linear-gradient(to bottom, #20222514 0%, #20222514 calc(100% - 18px), #20222510 calc(100% - 12px), #20222508 calc(100% - 6px), #20222500 100%); }
}
/* The navigation now sits over whatever scrolls beneath it, so it carries its
   own legibility instead of borrowing it from the header's opacity. */
.nav > *:not(.nav-fade) { text-shadow: 0 1px 12px #202225cc; }
.brand { display: inline-flex; align-items: center; gap: .6rem; font-weight: 900; letter-spacing: -.04em; text-decoration: none; }
.brand img { width: 2rem; height: 2rem; border-radius: 26%; }
.nav nav { display: flex; justify-content: center; gap: clamp(1rem, 2.6vw, 2.4rem); }
.nav nav a { padding: .5rem 0; font-size: .84rem; font-weight: 650; color: var(--dim); text-decoration: none; transition: color .2s ease; }
.nav nav a:hover, .nav nav a[aria-current] { color: var(--ink); }
/* Filled, not outlined: it is the only thing in the header anyone is meant to
   click, and an outline made it look like one more link (Matt, 2026-09-09). */
.nav-cta { padding: .55rem 1.1rem; border: 0; border-radius: 999px; background: linear-gradient(100deg, var(--blue), var(--cyan)); color: #08131c; font-size: .82rem; font-weight: 750; text-decoration: none; white-space: nowrap; transition: filter .2s ease, transform .2s ease; }
.nav-cta:hover { filter: brightness(1.08); transform: translateY(-1px); }
/* Three bars, drawn rather than fetched, becoming a cross when the panel is
   open. `display: none` by default so a page whose JavaScript never runs shows
   the links themselves instead of a control that cannot open them. */
.nav-burger { display: none; place-items: center; gap: 3px; width: 2.15rem; height: 2.15rem; padding: 0; border: 1px solid #ffffff42; border-radius: .8rem; background: #ffffff0f; cursor: pointer; }
.nav-burger i { width: 1.05rem; height: 2px; border-radius: 2px; background: var(--ink); transition: transform .22s ease, opacity .18s ease; }
.nav.is-open .nav-burger i:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.nav.is-open .nav-burger i:nth-child(2) { opacity: 0; }
.nav.is-open .nav-burger i:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }
@media (prefers-reduced-motion: reduce) { .nav-burger i { transition: none; } }

/* 1. hero lattice */
.hero { position: relative; display: grid; align-items: center; min-height: 96svh; padding: clamp(4rem, 9vw, 8rem) clamp(1.2rem, 5vw, 6rem) clamp(3rem, 6vw, 6rem); overflow: hidden; }
.lattice { position: absolute; z-index: 0; inset: 0; overflow: hidden; pointer-events: none; }
.lattice::after { content: ""; position: absolute; inset: 0; background: radial-gradient(66% 52% at 20% 44%, #202225fa 0%, #202225e0 46%, #20222500 80%), linear-gradient(#202225b3 0%, #2022258c 5%, #20222500 17%); }
/* Matt's -45deg floor: the projection is computed per cell, so the columns lean
   toward one vanishing point and the centre column stays vertical. */
.lattice-plane { position: absolute; inset: 0; }
.cell { position: absolute; left: var(--x); top: var(--y); width: var(--z); height: var(--z); }
.cell-icon { display: block; width: 100%; height: 100%; border-radius: 23%; object-fit: cover; background: #ffffff0a; opacity: calc(.10 + var(--d) * var(--d) * .90); filter: saturate(calc(.32 + var(--d) * .68)) brightness(calc(.82 + var(--d) * .18)); box-shadow: 0 .35rem 1.1rem #00000073; }
.hero-copy { position: relative; z-index: 2; max-width: 46rem; }
.hero-copy::before { content: ""; position: absolute; z-index: -1; inset: -22% -34% -44%; background: radial-gradient(58% 58% at 24% 48%, #202225fa 0%, #202225cc 46%, #20222500 78%); }
.hero-copy .lede { max-width: 34rem; font-size: clamp(1.14rem, 1.6vw, 1.36rem); line-height: 1.6; color: var(--dim); }
.hero-actions { display: flex; flex-wrap: wrap; gap: .8rem; margin-top: 2rem; }

/* 2. device */
.device-section { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2rem, 5vw, 5rem); align-items: center; padding: clamp(4rem, 8vw, 8rem) clamp(1.2rem, 5vw, 6rem); }
.device-copy > p:not(.eyebrow) { max-width: 34rem; color: var(--dim); font-size: 1.16rem; line-height: 1.6; }
.device-stage { display: grid; justify-items: center; gap: 1rem; }
.catalogue .device-section { align-items: start; }
.catalogue .device-stage { margin-top: clamp(1.5rem, 5vw, 4.5rem); }
.device { position: relative; width: min(23rem, 82vw); aspect-ratio: .485; padding: .5rem; border: .5rem solid #0d0f13; border-radius: 3.2rem; background: #0d0f13; box-shadow: 0 3rem 6rem #000000a6; }
/* Physical keys, sitting proud of the frame like the real thing. */
.key { position: absolute; width: .28rem; border-radius: .2rem; background: linear-gradient(90deg, #05070b, #2b3038 55%, #0d1117); }
.key-silent { left: -.72rem; top: 15.5%; height: 3.2%; }
.key-up { left: -.72rem; top: 22.5%; height: 7.2%; }
.key-down { left: -.72rem; top: 32%; height: 7.2%; }
.key-power { right: -.72rem; top: 26%; height: 11%; background: linear-gradient(270deg, #05070b, #2b3038 55%, #0d1117); }
.device-screen { position: relative; display: flex; flex-direction: column; height: 100%; border-radius: 2.7rem; overflow: hidden; color: #fff; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  background: linear-gradient(#0b1f47b3 0%, #0b1f4770 34%, #0b1f4700 58%), radial-gradient(60% 18% at 44% 56%, #ffffff2e 0%, transparent 70%), radial-gradient(80% 42% at 16% 82%, #f4c9a8 0%, transparent 48%), radial-gradient(115% 70% at 88% 104%, #081c47e6 0%, transparent 58%), linear-gradient(163deg, #7dc2ef 0%, #4f9ce4 32%, #2f6ecd 62%, #1c4aa8 84%, #123a8c 100%); }
/* Dynamic Island */
.os-island { position: absolute; z-index: 9; top: .55rem; left: 50%; width: 30%; height: 1.55rem; border-radius: 999px; background: #05070c; transform: translateX(-50%); }

/* Lock screen. The wallpaper is an original CSS recreation in the same family
   as Matt's reference, not the platform's own image file. */
.os-lock { position: absolute; z-index: 5; inset: 0; display: flex; flex-direction: column; align-items: center; padding: 1.3rem 1.2rem 1.6rem; overflow: hidden;
  background: radial-gradient(80% 40% at 14% 78%, #f6cdb0 0%, transparent 52%), radial-gradient(120% 66% at 92% 100%, #061a44 0%, transparent 58%), radial-gradient(70% 34% at 76% 30%, #2f8fe0 0%, transparent 62%), linear-gradient(168deg, #8ed2f7 0%, #63b3ec 26%, #2f76d4 56%, #17418f 80%, #0a2154 100%);
  transition: transform 1.5s cubic-bezier(.3, .78, .18, 1), opacity .8s ease .55s; }
.os-lock .os-status { width: 100%; padding: .2rem .3rem 0; }
.os-carrier { font-size: .72rem; font-weight: 600; text-shadow: 0 1px 2px #00000073; }
.os-lock-date { margin: 1.5rem 0 -.2rem; font-size: .82rem; font-weight: 600; text-shadow: 0 1px 3px #00000059; }
.os-lock-time { margin: 0; font-size: 3.6rem; font-weight: 300; letter-spacing: -.04em; line-height: 1.05; color: #d9f4ff; text-shadow: 0 2px 14px #00000047; }
.os-lock-actions { display: flex; justify-content: space-between; align-items: center; width: 100%; margin-top: auto; padding: 0 1.6rem .4rem; }
.os-lock-pill { flex: 0 0 auto; display: grid; place-items: center; width: 2.6rem; aspect-ratio: 1; border-radius: 50%; background: #0a1a3866; backdrop-filter: blur(8px); }
.os-lock-pill svg { width: 1.15rem; height: 1.15rem; }
.os-lock-hint { margin: .9rem 0 0; font: 700 .56rem/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: #ffffffc4; text-shadow: 0 1px 3px #00000073; }
.device.is-unlocked .os-lock { transform: translateY(-101%); opacity: 0; pointer-events: none; }
/* The locked state is an enhancement, not the default. Without JavaScript the
   clock never ticks and the unlock never fires, so an unqualified rule left the
   handset showing a dead "--:--" lock screen with the applications hidden —
   which is what a crawler and anyone with JavaScript off actually saw. The
   `js` class is set by an inline script before first paint, so nobody with
   JavaScript sees the unlocked state flash first. */
html.js .device:not(.is-unlocked) .os-home, html.js .device:not(.is-unlocked) .os-view { visibility: hidden; }
html:not(.js) .os-lock { display: none; }

.os-status { display: flex; justify-content: space-between; align-items: center; padding: 1.05rem 1.5rem .3rem; }
.os-time { font-size: .9rem; font-weight: 650; text-shadow: 0 1px 2px #000000b3; }
.os-status-icons { display: inline-flex; align-items: center; gap: .4rem; filter: drop-shadow(0 1px 2px #0006); }
.os-status-icons svg { height: .68rem; width: auto; }
.os-status-icons .batt { height: .78rem; }
.os-home { flex: 1; display: flex; flex-direction: column; padding: .9rem 0 1.4rem; min-height: 0; }
.os-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.3rem 4%; margin: 0; padding: 0 6%; list-style: none; align-content: start; }
.os-tile { display: grid; justify-items: center; gap: .35rem; width: 100%; margin: 0; padding: 0; border: 0; background: none; color: #fff; font-family: inherit; font-size: .6rem; font-weight: 500; line-height: 1.2; text-align: center; cursor: pointer; text-shadow: 0 1px 2px #000000e6, 0 0 6px #000000b3, 0 2px 8px #00000099; }
.os-tile img { width: 100%; max-width: 3.6rem; border-radius: 23%; box-shadow: 0 .3rem .8rem #00000047; }
.os-tile span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.os-tile[aria-current] img { outline: 3px solid #0b1f47; box-shadow: 0 0 0 6px #ffffffe6; }
.device-screen :focus-visible { outline: 3px solid #0b1f47; outline-offset: 2px; box-shadow: 0 0 0 6px #ffffffe6; }
/* The view no longer scrolls: the reel inside it does, so the way back stays
   where it is instead of sliding off with the screens. */
.os-view { position: absolute; inset: 0; z-index: 4; display: flex; flex-direction: column; padding: 3.2rem 0 0; background: #06080e; overflow: hidden; }
.os-view:has(.os-reel) { padding-top: 0; }
/* One screen at a time, and never half of one. `mandatory` forbids resting
   between snap points and `scroll-snap-stop: always` forbids flying past one,
   which together are what make a flick advance exactly one screen. The reel
   releases to the page at either end, so a reader scrolling past the device
   is not trapped cycling screenshots. */
.os-reel { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior-y: auto; overscroll-behavior-x: contain; scroll-snap-type: y mandatory; scrollbar-width: none; }
.os-reel::-webkit-scrollbar { display: none; }
/* Captures come from five different devices, so no single crop removes every
   stale status bar. The image is scaled a little and pulled up instead, and
   the display clips it — whatever bar survived the import sits above the
   visible area rather than fighting the Dynamic Island. */
/* `contain`, not `cover`. The captures span two eras: everything shot since
   2020 is 0.46, which is the screen's own ratio and fits edge to edge with
   nothing to letterbox, while A-note, Trolls and one Maison Smeets screen are
   0.562 from the 16:9 years. `cover` filled the frame by throwing their sides
   away, which cut the left column off A-note's board and half its title
   (Matt, 2026-09-10). `contain` shows the whole screen and pays for it with a
   band top and bottom, in the phone's own black so it reads as the device
   rather than as a mistake. */
.os-screen { display: block; width: 100%; height: 100%; object-fit: contain; background: #06080e; scroll-snap-align: start; scroll-snap-stop: always; }
.os-view[hidden], .os-home[hidden] { display: none; }
/* At the foot of the screen, not the top: an application's own back control
   lives up there, and two of them on one screen is a mockup that has stopped
   being a mockup of anything. */
.os-back { position: absolute; z-index: 6; bottom: 1rem; left: 50%; transform: translateX(-50%); display: inline-flex; align-items: center; gap: .3rem; padding: .5rem 1rem; border: 1px solid #ffffff2e; border-radius: 999px; background: #0b0d12e8; backdrop-filter: blur(10px); box-shadow: 0 .5rem 1.4rem #00000073; color: #fff; font-family: inherit; font-size: .78rem; font-weight: 650; cursor: pointer; }
.os-app { flex: 1 0 auto; display: grid; place-content: center; justify-items: center; gap: 1rem; min-height: 8rem; background: radial-gradient(60% 50% at 50% 42%, color-mix(in srgb, var(--app) 40%, transparent), transparent 70%); }
.os-app-icon { width: 8rem; border-radius: 23%; box-shadow: 0 1.5rem 3rem #000000b3; }
.os-sheet { position: relative; padding: 1.1rem 1.3rem 1.8rem; border-radius: 1.2rem 1.2rem 0 0; background: #14161cf7; }
.os-sheet .grip { position: absolute; top: .5rem; left: 50%; width: 2.2rem; height: .26rem; border-radius: 999px; background: #ffffff40; transform: translateX(-50%); }
.os-sheet h4 { margin: .4rem 0 0; font-size: 1.45rem; letter-spacing: -.035em; }
.os-sheet p:not(.kind, .verify) { font-size: .9rem; color: #c3c8d0; }
.os-sheet a { color: var(--cyan); font-weight: 650; font-size: .85rem; }
.os-bar { position: absolute; z-index: 8; bottom: .4rem; left: 50%; width: 34%; height: .28rem; border-radius: 999px; background: #ffffffd9; transform: translateX(-50%); }
/* A capture of a phone already has a status bar, an island and a home bar of
   its own. While one is open the simulated set is hidden, or the phone wears
   two of everything. */
.device:has(.os-view:not([hidden])) .os-island,
.device:has(.os-view:not([hidden])) .device-screen > .os-status,
.device:has(.os-view:not([hidden])) .os-bar { display: none; }
.device-note { max-width: 22rem; margin: 0; font: 700 .6rem/1.5 var(--mono); letter-spacing: .1em; text-transform: uppercase; text-align: center; color: #ffffff7a; }

/* 3. app universes — two per row */
.apps { padding: clamp(4rem, 8vw, 8rem) clamp(1.2rem, 5vw, 6rem); }
.app-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(.9rem, 1.6vw, 1.3rem); }
/* One rounded rectangle, icon left, words right, the whole of it a link. The
   hover does three things at once — border, background and lift — because a
   card that only changes its border reads as decorated rather than pressable. */
.app { --app-text: color-mix(in srgb, var(--app) 55%, #ffffff); --pad: clamp(1.1rem, 2vw, 1.5rem); position: relative; display: grid; grid-template-columns: auto 1fr; gap: clamp(1rem, 2vw, 1.5rem); align-items: center; padding: var(--pad); border: 1px solid color-mix(in srgb, var(--app) 30%, transparent); border-radius: 1.4rem; background: color-mix(in srgb, var(--app) 7%, var(--app-ground)); color: inherit; text-decoration: none; overflow: hidden; transition: opacity .65s ease, transform .3s ease, border-color .25s ease, background-color .25s ease, box-shadow .3s ease; }
.app:not(.in) { opacity: 0; transform: translateY(2rem); }
.app:hover, .app:focus-visible { border-color: color-mix(in srgb, var(--app) 80%, transparent); background: color-mix(in srgb, var(--app) 16%, var(--app-ground)); box-shadow: 0 1rem 2.4rem color-mix(in srgb, var(--app) 22%, transparent); transform: translateY(-3px); outline: none; }
.app:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--app) 60%, transparent); }
.app-aura { position: absolute; inset: -60% auto auto -20%; width: 60%; height: 160%; background: radial-gradient(50% 50% at 50% 50%, var(--glow), transparent 70%); opacity: .18; pointer-events: none; transition: opacity .3s ease; }
.app:hover .app-aura { opacity: .34; }
.app-icon { position: relative; width: clamp(4.6rem, 7.4vw, 6.2rem); height: auto; border-radius: 23%; box-shadow: 0 .8rem 1.8rem #00000059; }
.app-text { position: relative; min-width: 0; }
.app-text h3 { margin: .15rem 0; font-size: clamp(1.55rem, 2.4vw, 2.05rem); font-weight: 800; letter-spacing: -.03em; line-height: 1.1; }
.kicker { margin: 0; font: 700 .76rem/1.45 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--app-text); }
.app-body { margin: .55rem 0 0; color: #d8dde3; font-size: 1.02rem; line-height: 1.55; }
.app .kind { margin: .25rem 0 0; font-size: .74rem; }
.app .verify { margin: .5rem 0 0; }
/* Two applications ship artwork wide enough to stand behind their own card:
   Orak's mountains and the Trolls festival plate. It sits behind everything,
   dimmed and faded out toward the text, so the card still reads as a card and
   the words still win (Matt, 2026-09-10). */
/* `grid-area` is the whole point. Both cards are grids, and an absolutely
   positioned child of a grid container is laid out inside its own grid cell,
   not the card: without this the artwork rendered as a small square behind the
   icon in the top-left corner, which is what Matt saw. Spanning every track
   makes the containing block the card again. */
/* The card's background, and a background is what it has to be. Two things
   had to be right. An <img> could not do this job: the global
   `img { max-width: 100% }` clamps it and a replaced element never stretches
   between opposing insets, so it kept its own size instead of the card's. And
   the element must claim no grid placement: name a grid area and that area
   becomes the containing block, its height is indefinite while out-of-flow
   boxes are laid out, and the top and bottom insets collapse to a strip. With
   no placement the containing block is the card itself, so a plain `inset: 0`
   covers it edge to edge; named, it was the content cell, which is why the
   artwork first appeared as a small square behind the icon. */
.app-scene { position: absolute; z-index: 0; inset: 0; background-position: center; background-size: cover; opacity: .46; pointer-events: none;
  -webkit-mask-image: linear-gradient(100deg, #000 0%, #00000059 55%, transparent 92%); mask-image: linear-gradient(100deg, #000 0%, #00000059 55%, transparent 92%); }
.app.has-scene, .cat-card.has-scene { position: relative; overflow: hidden; }
/* The catalogue card needs what the home card gets for free. On `.app` the
   icon and the text are already positioned in their own rules, so they paint
   after the artwork by tree order; here they are ordinary in-flow content, and
   a positioned element at z-index 0 paints above that, which put the picture
   over the words and made Orak and Trolls read as disabled (Matt, 2026-09-10).
   Named child by child rather than with `*:not(.app-scene)`, because that
   shorthand is exactly what caught the home card's aura and broke its grid. */
.cat-card.has-scene > img,
.cat-card.has-scene > .cat-mark-none,
.cat-card.has-scene > .cat-copy { position: relative; z-index: 1; }
/* No rule raising the other children. The one that used to be here set
   `position: relative` on every child except the scene, which caught
   `.app-aura` too: the aura is absolutely positioned, so making it
   relative put it back in the grid as a real item, it took the first
   column at 60% of the card, and the icon was pushed out of frame. The
   raise was never needed anyway. The scene and its tint paint at z-index
   0, the icon and the text are already positioned in their own rules, and
   positioned siblings at the same level paint in tree order, so both
   land above it for free. */
.app.has-scene:hover .app-scene, .cat-card.has-scene:hover .app-scene { opacity: .6; }
/* The card's own tint sits over the picture so the words keep their
   contrast wherever the artwork happens to be bright. */
.app.has-scene::before, .cat-card.has-scene::before { content: ""; position: absolute; z-index: 0; inset: 0; background: linear-gradient(100deg, color-mix(in srgb, var(--app-ground) 74%, transparent), color-mix(in srgb, var(--app-ground) 26%, transparent) 72%); pointer-events: none; }
/* The drawn cards. Each one is the application's own screen reduced to the two
   or three marks that make it recognisable, in the application's own colours,
   sitting behind the words at an opacity that never competes with them. */
/* Faded out toward the left, where the words are. The same reason the
   artwork cards carry a mask: the decoration is allowed to be seen and is
   not allowed to be read through. */
.app-deco { position: absolute; z-index: 0; inset: 0; overflow: hidden; pointer-events: none;
  -webkit-mask-image: linear-gradient(to left, #000 0%, #0000008f 34%, transparent 70%); mask-image: linear-gradient(to left, #000 0%, #0000008f 34%, transparent 70%); }
.app-deco i { position: absolute; font-style: normal; line-height: 1; }

/* Find it!: a field of Ws with one V in it, which is the whole game. */
.app-deco.is-letters { background: radial-gradient(120% 90% at 82% 30%, color-mix(in srgb, var(--app) 16%, transparent), transparent 68%); }
.app-deco.is-letters i { font: 800 1.5rem/1 var(--sans); color: color-mix(in srgb, var(--app) 42%, transparent); }
.app-deco.is-letters i:nth-child(1) { top: 12%; right: 6%; }
.app-deco.is-letters i:nth-child(2) { top: 34%; right: 21%; transform: rotate(-8deg); }
.app-deco.is-letters i:nth-child(3) { top: 62%; right: 9%; }
.app-deco.is-letters i:nth-child(4) { top: 8%;  right: 33%; transform: rotate(6deg); }
.app-deco.is-letters i:nth-child(5) { top: 44%; right: 4%; font-size: 2.1rem; color: color-mix(in srgb, var(--app) 88%, transparent); }
.app-deco.is-letters i:nth-child(6) { top: 78%; right: 27%; transform: rotate(-4deg); }
.app-deco.is-letters i:nth-child(7) { top: 24%; right: 13%; }
.app-deco.is-letters i:nth-child(8) { top: 84%; right: 15%; font-size: 1.15rem; }
.app-deco.is-letters i:nth-child(9) { top: 55%; right: 31%; font-size: 1.15rem; }

/* KnowUs: two people, one question at a time. */
.app-deco.is-hearts i { color: color-mix(in srgb, var(--app) 46%, transparent); }
.app-deco.is-hearts i:nth-child(1) { top: 16%; right: 8%;  font-size: 2.4rem; }
.app-deco.is-hearts i:nth-child(2) { top: 52%; right: 20%; font-size: 1.5rem; transform: rotate(-12deg); }
.app-deco.is-hearts i:nth-child(3) { top: 74%; right: 6%;  font-size: 1.9rem; }
.app-deco.is-hearts i:nth-child(4) { top: 34%; right: 30%; font-size: 1.1rem; }
.app-deco.is-hearts i:nth-child(5) { top: 6%;  right: 24%; font-size: 1.2rem; transform: rotate(14deg); }
.app-deco.is-hearts i:nth-child(6) { top: 88%; right: 26%; font-size: 1rem; }

/* Up and down: the cyan blocks you dodge, the red bar you do not, the ball. */
.app-deco.is-blocks .d-b { width: 1.5rem; height: 1.5rem; border-radius: .3rem; background: #00bcd4; opacity: .62; }
.app-deco.is-blocks .d-r { width: 3.1rem; height: 1rem; border-radius: .3rem; background: var(--app); opacity: .6; }
.app-deco.is-blocks .d-o { width: 1.4rem; height: 1.4rem; border-radius: 50%; background: #fcc44b; opacity: .5; }
.app-deco.is-blocks i:nth-child(1) { top: 18%; right: 10%; }
.app-deco.is-blocks i:nth-child(2) { top: 66%; right: 26%; width: 1.05rem; height: 1.05rem; }
.app-deco.is-blocks i:nth-child(3) { top: 44%; right: 4%;  width: 1.2rem; height: 1.2rem; }
.app-deco.is-blocks i:nth-child(4) { top: 8%;  right: 27%; }
.app-deco.is-blocks i:nth-child(5) { top: 82%; right: 8%;  width: 2.2rem; height: .8rem; }
.app-deco.is-blocks i:nth-child(6) { top: 38%; right: 33%; }
.apps-more { margin: 2.5rem 0 0; max-width: 44rem; color: var(--dim); font-size: .95rem; }
@media (max-width: 760px) { .app-grid { grid-template-columns: 1fr; } }

/* `.step` outlived the section it was written for: the services list and the
   Kernel page both number their rows with it. */
.step { margin: 0; font: 700 .7rem/1 var(--mono); letter-spacing: .14em; color: var(--cyan); }

/* 5. AI */
.ai { padding: clamp(4rem, 8vw, 8rem) clamp(1.2rem, 5vw, 6rem); }
/* Equal rows, not just equal columns: one card carries a button and would
   otherwise make its own row taller than the other. */
.service-grid { display: grid; grid-template-columns: repeat(2, 1fr); grid-auto-rows: 1fr; gap: clamp(1rem, 2vw, 1.6rem); margin: 0; padding: 0; list-style: none; }
.service-grid li { display: flex; flex-direction: column; padding: clamp(1.5rem, 2.4vw, 2.4rem); border: 1px solid var(--line); border-radius: 1.4rem; background: #ffffff08; transition: opacity .6s ease, transform .6s ease; }
.service-grid li:not(.in) { opacity: 0; transform: translateY(1.6rem); }
.service-grid li:nth-child(even) { transition-delay: .08s; }
.service-grid h3 { margin: 0 0 .6rem; font-size: clamp(1.2rem, 1.9vw, 1.7rem); font-weight: 750; letter-spacing: -.03em; }
.service-grid p { margin: 0; color: var(--dim); }
.ai-cta { display: flex; flex-wrap: wrap; gap: .8rem; margin: 2.6rem 0 0; }
/* Pushed to the foot of its card so the one card that has a way in still
   lines up with the three that do not. */
.service-cta { margin: 1.4rem 0 0 !important; padding-top: .2rem; }
.service-grid li > p:not(.service-cta) { flex: 1; }

/* 6. festival + contact */
.festival { display: grid; grid-template-columns: 1fr auto; gap: clamp(2rem, 4vw, 4rem); align-items: center; margin: clamp(2rem, 5vw, 5rem) clamp(1.2rem, 5vw, 6rem); padding: clamp(2rem, 4vw, 4rem); border-radius: 1.6rem; background: linear-gradient(150deg, #2a1250, #140a26); }
.festival p:not(.eyebrow) { max-width: 34rem; color: #cfc4e8; }
/* Three devices, the way a store listing shows them: two standing behind and
   peeking out, the one in front larger and carrying the moving picture (Matt,
   2026-09-10). The two behind are the baked mockups, which already contain
   their own frame; the front one has to be drawn, because a video cannot be
   baked into a WebP. Its proportions are device-mockup.py's own, expressed as
   fractions of the body width: 50 of 1420 for the rail and glass together,
   150 of 1420 for the screen's corner, 200 of 1420 for the body's. */
.fest-trio { --w: clamp(9.5rem, 16vw, 13rem); display: flex; align-items: center; justify-content: center; }
/* The AI band, directly under the festival one (Matt, 2026-09-10). Same shape
   and same grid as its neighbour, a different ground so the two read as two
   offers rather than one repeated. Its visual is the rotating task list, which
   is why it needs a floor: a grid cell holding one phrase at a time collapses
   to the height of whichever phrase is showing, and the band would breathe. */
.ai-band { margin: clamp(2rem, 5vw, 5rem) clamp(1.2rem, 5vw, 6rem); padding: clamp(1.6rem, 3vw, 2.6rem); border-radius: 1.6rem; background: linear-gradient(150deg, #0a3550, #061523); }
.ai-band .section-head { max-width: 40rem; margin-bottom: clamp(1.1rem, 2.2vw, 1.7rem); }
.ai-band .section-head h2 { margin: .3rem 0 0; font-size: clamp(1.8rem, 3.5vw, 2.6rem); line-height: 1.04; letter-spacing: -.03em; }
/* Two lanes, and the divider is the point: they are two products bought by two
   people, so the band has to look like a fork rather than a paragraph. */
.band-split { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(1.6rem, 3.5vw, 3.2rem); }
.band-lane { display: flex; flex-direction: column; align-items: flex-start; padding: clamp(1.2rem, 2vw, 1.7rem); border: 1px solid #ffffff1f; border-radius: 1.2rem; background: #ffffff08; }
.band-lane.is-kernel { background: #ffffff05; border-color: color-mix(in srgb, var(--cyan) 24%, transparent); }
.lane-step { margin: 0 0 .5rem !important; color: var(--cyan) !important; font: 700 .68rem/1.4 var(--mono); letter-spacing: .1em; text-transform: uppercase; }
.band-lane h3 { margin: 0 0 .55rem; font-size: clamp(1.25rem, 2vw, 1.6rem); font-weight: 750; letter-spacing: -.025em; line-height: 1.15; }
.band-lane > p:not(.lane-step):not(.band-actions) { margin: 0; max-width: 34rem; color: #c2dbe8; font-size: .98rem; line-height: 1.62; }
.band-actions { display: flex; flex-wrap: wrap; gap: .8rem; margin: auto 0 0 !important; padding-top: 1.1rem; }
.band-gains { display: grid; gap: .45rem; margin: .9rem 0 0; padding: 0; list-style: none; }
.band-gains li { padding-left: .9rem; border-left: 2px solid color-mix(in srgb, var(--cyan) 45%, transparent); color: #c2dbe8; font-size: .93rem; line-height: 1.5; }
.band-tasks { margin: .9rem 0 0 !important; }
/* The rotating phrase was the tallest block in the band, and it set the whole
   card's height: wider measure, smaller type and a lower floor take about a
   fifth off it without losing a word (Matt, 2026-09-10). */
/* Scoped under .ai-band, or it loses on source order: the AI page's own
   .agent-tasks.is-rotating carries the same specificity and is written later
   in this file, so its 10rem floor was the 160px nobody could shift. */
.ai-band .band-tasks.is-rotating { min-height: clamp(3.6rem, 5.4vw, 4.6rem); place-items: center start; }
.band-tasks li { font-size: clamp(1.05rem, 1.6vw, 1.45rem) !important; }
.ai-band .band-tasks.is-rotating li { max-width: 24ch; background: linear-gradient(100deg, var(--ink), var(--cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.ai-band .band-tasks.is-static li { margin-bottom: .5rem; color: #c2dbe8; }
.fest-trio-side { flex: none; width: calc(var(--w) * .84); height: auto; filter: drop-shadow(0 1.6rem 3rem #00000080); }
.fest-trio-side:first-child { margin-right: calc(var(--w) * -.46); }
.fest-trio-side:last-child { margin-left: calc(var(--w) * -.46); }
.fest-trio-front { position: relative; z-index: 2; flex: none; width: var(--w); padding: calc(var(--w) * .0352); border-radius: calc(var(--w) * .1408); background: linear-gradient(150deg, #7A7E85, #3A3C42 22%, #3A3C42 78%, #7A7E85); box-shadow: 0 2.2rem 4rem #000000a6; }
.fest-trio-front video { display: block; width: 100%; height: auto; border-radius: calc(var(--w) * .1056); background: #05070c; }
.contact { padding: clamp(4rem, 8vw, 8rem) clamp(1.2rem, 5vw, 6rem) clamp(3rem, 6vw, 6rem); }
.contact p:not(.eyebrow, .band-book) { max-width: 40rem; color: var(--dim); font-size: 1.18rem; line-height: 1.65; }
/* The way in for a reader who would rather not compose an email. It sits
   under the address rather than replacing it, because the two are not the
   same buyer: one has a question, the other has a date in mind. */
.band-book { margin: 1.6rem 0 0; }
.mail { display: inline-block; margin-top: 1.5rem; font-size: clamp(1.3rem, 3vw, 2.6rem); font-weight: 800; letter-spacing: -.04em; text-underline-offset: .22em; }

/* footer */
.foot { padding: 2.5rem clamp(1.2rem, 5vw, 6rem) 3.5rem; border-top: 1px solid var(--line); }
.foot p { margin: 0 0 .6rem; font-size: .85rem; }
.consent { position: fixed; z-index: 90; right: clamp(.8rem, 3vw, 2rem); bottom: clamp(.8rem, 3vw, 2rem); left: clamp(.8rem, 3vw, 2rem); max-width: 34rem; margin-left: auto; padding: 1.3rem 1.4rem; border: 1px solid #ffffff2e; border-radius: 1.1rem; background: #16181cf5; backdrop-filter: blur(18px); box-shadow: 0 1.5rem 3rem #00000073; }
.consent p { margin: 0; font-size: .92rem; color: var(--dim); }
.consent b { color: var(--ink); }
.consent-actions { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1rem !important; }
/* The two answers are the same button. Making "Allow" the prettier one is the
   deceptive-design pattern the EDPB names: a choice that is nominally free and
   visually steered. Refusing comes first, because reading order is prominence
   too. */
.consent-choice { flex: 1 1 0; padding: .75rem 1.2rem; border: 1px solid #ffffff5c; border-radius: 999px; background: #ffffff12; color: var(--ink); font: 650 .88rem/1 var(--sans); cursor: pointer; transition: border-color .2s ease, background .2s ease; }
.consent-choice:hover { border-color: var(--cyan); background: #ffffff1f; }
.foot-controls { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
.consent-reset { padding: .5rem .9rem; border: 1px solid #ffffff6b; border-radius: 999px; background: none; color: inherit; font: inherit; font-size: .8rem; cursor: pointer; }
.consent-reset:hover { border-color: var(--cyan); }
/* A switch rather than a button that renames itself: the control shows its own
   state, and it sits at the right-hand end of the row where a settings switch
   is looked for. */
.motion-toggle { display: inline-flex; align-items: center; gap: .7rem; margin-left: auto; padding: .35rem .4rem .35rem .2rem; border: 0; border-radius: 999px; background: none; color: inherit; font: inherit; font-size: .8rem; cursor: pointer; }
.motion-toggle .switch { position: relative; flex: none; width: 2.6rem; height: 1.5rem; border: 1px solid #ffffff3d; border-radius: 999px; background: #ffffff1a; transition: background .2s ease, border-color .2s ease; }
.motion-toggle .switch::after { content: ""; position: absolute; top: 50%; left: .16rem; width: 1.05rem; height: 1.05rem; border-radius: 50%; background: #ffffffcc; transform: translateY(-50%); transition: transform .2s ease, background .2s ease; }
.motion-toggle[aria-checked="true"] .switch { border-color: transparent; background: linear-gradient(100deg, var(--blue), var(--cyan)); }
.motion-toggle[aria-checked="true"] .switch::after { background: #fff; transform: translate(1.12rem, -50%); }
.motion-toggle:hover .switch { border-color: var(--cyan); }
.motion-off #field { display: none; }
/* A long sentence set in uppercase mono is a wall. It stays quiet, but it stays
   readable: the mono voice is kept for the one leading word. */
/* Last line of the page, and it looks like one: quiet, spaced away from the
   draft caveat above it, and carrying no border of its own so the two do
   not read as two separate boxes. */
.foot-line { margin: 1.6rem 0 0 !important; color: #ffffff8a; font-size: .8rem; }
.foot-line b { color: #ffffffc4; }
.foot-note { max-width: 52rem; margin-top: 1rem !important; padding-top: 1rem; border-top: 1px solid var(--line); font: 400 .78rem/1.6 var(--sans); color: #ffffff8a; }
.foot-note b { font: 700 .62rem/1.6 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: #ffffffb8; }

/* application pages */
.app-page { --app-text: color-mix(in srgb, var(--app) 55%, #ffffff); }
.breadcrumb { margin: 0; padding: 3.2rem clamp(1.2rem, 5vw, 6rem) 0; font: 700 .68rem/1.4 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--dim); }
.breadcrumb a { color: var(--dim); text-underline-offset: .3em; }
/* The application's own artwork, full width, with the page carrying its ground
   colour underneath so the foot of the picture fades into the page instead of
   ending on a hard line. The mask does the fading rather than a gradient laid
   over the top, because the page below is a solid colour and the picture
   should reach it, not be covered by something the same shade. */
/* The site header is glass, which works over graphite and fails over a bright
   sky: on the festival artwork the links all but disappeared. A scrim under
   the header, part of the header treatment rather than of the header itself,
   so only a page that puts a picture up there pays for it. */
/* A page takes the application's own ground only when it asks for it: Orak,
   A-note and Trolls do, because the artwork's own colour is the point of those
   pages. Find it! keeps the site's page and only bands its sheet. */
.app-page.has-ground { background: var(--app-ground); }
/* The sheet stays its own size and stays centred; the band behind it carries
   its colour out to both edges of the screen, which is the whole request. */
.app-band { margin-top: calc(-1 * var(--nav-h)); padding: calc(var(--nav-h) + 1rem) clamp(1.2rem, 5vw, 6rem) 1.5rem; background: var(--band); }
.app-band .app-header { margin: 0 auto; }
/* The scrim has to be inside the header's own glass, not behind it: behind it
   the blur simply picks the bright sky back up. `:has()` reaches the header
   from the page that put a picture under it, so no page without one is
   touched. */
#root:has(.app-page.has-header) .nav-fade { background: linear-gradient(to bottom, #0a0c10cc 0%, #0a0c10a6 calc(100% - 18px), #0a0c1066 calc(100% - 9px), transparent 100%); }
#root:has(.app-page.has-header) .nav > *:not(.nav-fade) { text-shadow: 0 1px 14px #0a0c10, 0 0 4px #0a0c10a6; }
/* Whole, not cropped. A fixed height with object-fit: cover showed the sky and
   cut the mountains off, which is the one part worth showing. The picture
   keeps its own proportions and only the last quarter is faded, where the
   artwork is already the same navy as the page. */
.app-header { display: block; width: 100%; height: auto; max-height: 70vh; margin-top: calc(-1 * var(--nav-h)); object-fit: cover; object-position: center bottom; -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 62%, transparent 100%); mask-image: linear-gradient(to bottom, #000 0%, #000 62%, transparent 100%); }
.app-page.has-header .app-hero { padding-top: 0; }
/* An artwork that is a mark rather than a landscape: centred at its own size,
   on a page whose ground is the colour the artwork already sits on, so there is
   nothing to fade and nothing to crop. The presentation stays below it instead
   of climbing onto it, because a mark has no dark lower third to climb onto. */
.app-header.is-centred { width: auto; max-width: min(24rem, 58vw); height: auto; max-height: none; margin: 2rem auto 0; object-fit: contain; -webkit-mask-image: none; mask-image: none; }
/* A marketing sheet is wider than a mark and carries the name itself, so it is
   given room and the head above it stands down. The page keeps its h1, hidden:
   a page with no h1 has nothing to label it, to a screen reader or to a search
   result. */
.app-header.is-centred.is-title { max-width: min(58rem, 92vw); }
.app-page.has-header:has(.is-centred) .breadcrumb { margin-top: 0; padding-top: 1.6rem; }
/* The film is a phone recording, so it is shown at the width it was shot. */
.film { padding: 0 clamp(1.2rem, 5vw, 6rem) clamp(2.5rem, 5vw, 4rem); }
.film .eyebrow { margin-bottom: 1.2rem; }
.film video { display: block; width: 100%; max-width: 20rem; border-radius: 1.2rem; border: 1px solid var(--line); background: #000; box-shadow: 0 1.4rem 3rem #00000073; }
.film-note { max-width: 40rem; margin: 1rem 0 0; color: var(--dim); font-size: .95rem; }
/* Words on a picture need help a solid background gives for free: the sky
   behind them runs from blue to orange, so the breadcrumb is brightened and
   everything in the hero carries the same shadow the site header uses over
   the icon field. */
.app-page.has-header .breadcrumb, .app-page.has-header .breadcrumb a { color: #dfe4ea; }
.app-page.has-header .breadcrumb, .app-page.has-header .app-hero > *:not(.app-backdrop) { text-shadow: 0 1px 14px #032743cc; }
/* The breadcrumb sits under the picture rather than above it, so it needs its
   own room: the 3.2rem it normally takes from the top of the page is gone,
   and without this it touches the application's icon. */
/* The presentation sits on the artwork rather than under it, which is what
   the application does with its own name. The lift is in vw so it tracks the
   picture, whose height is a fixed fraction of the width, and it lands the
   text over the dark lower third where the mask is already fading the
   artwork into the page. Below 900px the picture is too short to hold
   anything, so the block goes back underneath it. */
.app-page.has-header .breadcrumb { padding-top: 0; padding-bottom: 1.2rem; margin-top: calc(-1 * clamp(9rem, 19vw, 24rem)); }
.app-hero { position: relative; padding: clamp(2rem, 5vw, 4rem) clamp(1.2rem, 5vw, 6rem) clamp(4rem, 7vw, 6rem); overflow: hidden; }
/* The glow has to reach its own transparent tail before the section clips it.
   Hung above the top edge it was cut through its bright middle, and a
   radial gradient sliced across its centre reads as a rectangle. */
.app-hero .app-aura { position: absolute; inset: 0 34% auto -12%; height: 95%; background: radial-gradient(52% 52% at 42% 42%, var(--glow), transparent 70%); opacity: .2; pointer-events: none; }
/* Three layers, and the order of them is the whole trick: the artwork at the
   back, the wash that makes text readable over it in the middle, and the words
   on top. Written without the z-indexes the first time, the wash was a
   ::after — last in tree order — and it covered the words it was meant to
   protect, which is what broke this page. */
.app-hero > * { position: relative; z-index: 2; }
.app-hero > .sr-only { position: absolute; }
.app-hero .app-backdrop { position: absolute; z-index: 0; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: .38; filter: blur(1px) saturate(.9); -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 55%, transparent 96%); mask-image: linear-gradient(to bottom, #000 0%, #000 55%, transparent 96%); }
.app-hero.has-backdrop::after { content: ""; position: absolute; z-index: 1; inset: 0; background: linear-gradient(100deg, #202225f2 0%, #202225d6 38%, #20222552 100%); pointer-events: none; }
/* One picture of the whole product, centred, because that is what it is: every
   surface at once rather than one more screen. */
.app-cover { margin: 0; padding: 0 clamp(1.2rem, 5vw, 6rem) clamp(2rem, 4vw, 3rem); }
/* The one cover on the site is a cutout of four devices with no ground of
   its own, so a border and a box-shadow would frame the empty space around
   them rather than the picture. drop-shadow follows the silhouette. */
.app-cover img { display: block; width: 100%; max-width: 56rem; margin: 0 auto; filter: drop-shadow(0 1.4rem 3rem #00000080); }
.app-mark { width: clamp(4.5rem, 9vw, 7rem); border-radius: 23%; box-shadow: 0 1.5rem 3rem #00000073; }
.app-mark-none { display: grid; place-items: center; aspect-ratio: 1; background: linear-gradient(160deg, var(--app), color-mix(in srgb, var(--app) 40%, #000)); font-style: normal; font-weight: 800; font-size: 2.6rem; color: #0E1014; }
/* Icon and name on one line, because the name belongs to the icon and reading
   it underneath makes them two separate announcements (Matt, 2026-09-09). */
.app-head { display: flex; align-items: center; gap: clamp(1rem, 2.4vw, 2rem); margin-bottom: .8rem; }
.app-head > div { min-width: 0; }
.app-hero .eyebrow { margin-top: 0; }
.app-hero h1 { margin: .2rem 0 0; font-size: clamp(2.2rem, 4.6vw, 4rem); line-height: 1.02; letter-spacing: -.03em; }
.app-tagline { margin: 0 0 1.2rem; font-size: clamp(1.1rem, 2vw, 1.6rem); font-weight: 650; color: var(--app-text); letter-spacing: -.02em; }
.app-hero .lede { max-width: 44rem; font-size: clamp(1.12rem, 1.5vw, 1.3rem); line-height: 1.6; color: var(--dim); }
.metric { display: flex; flex-wrap: wrap; align-items: baseline; gap: .7rem; margin: 1.8rem 0 0; }
.metric b { font-size: clamp(1.8rem, 3.4vw, 2.8rem); font-weight: 800; letter-spacing: -.04em; color: var(--app-text); }
.metric span { font-size: clamp(1.8rem, 3.4vw, 2.8rem); font-weight: 800; letter-spacing: -.04em; color: var(--app-text); }
.app-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .8rem; margin: 2rem 0 0; }
.store-state { margin: 0; padding: .7rem 1.1rem; border: 1px solid color-mix(in srgb, var(--app) 40%, transparent); border-radius: 999px; font: 700 .64rem/1.4 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--app-text); }
.screens { padding: 0 clamp(1.2rem, 5vw, 6rem) clamp(3rem, 6vw, 5rem); }
.screens .eyebrow { margin-bottom: 1.2rem; }
/* Bigger, on Matt's instruction of 2026-09-10, and wrapping rather than
   scrolling: at this size five captures no longer fit on one line, and a
   strip that scrolls sideways hides the last two from everybody who does
   not think to drag it. */
.screen-strip { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(.9rem, 1.8vw, 1.6rem); padding-bottom: 1rem; }
/* No panel behind a screen. A capture already has an edge, and a device brings
   its own corners and its own shadow with it; the panel was drawing a black
   rectangle around both (Matt, 2026-09-09). */
.screen-strip img { flex: 0 0 auto; height: clamp(26rem, 50vw, 42rem); width: auto; border-radius: 1.4rem; }
.screen-strip.is-wide img { height: auto; width: min(46rem, 88vw); }
/* `is-mockup`, and not `device`: that name already belongs to the simulated
   handset above, whose rule forces a .485 portrait ratio and a black body. A
   landscape MacBook wearing it came out phone-shaped, on the black rectangle
   this section had just been told to drop. */
.screen-strip img.is-mockup { border-radius: 0; }
/* A capture on a page that took its ground colour from the application
   has no edge of its own: the screenshot and the page are the same grey,
   and five of them run together into one shape. A hairline gives each
   one back its boundary. */
.screen-strip img.is-edged { border: 1px solid #000000d9; box-shadow: 0 1.2rem 2.4rem #00000059; }
/* A Mac and a phone are not two pictures of the same size. The one that leads
   is shown at the size it deserves and the other stands beside it, both sitting
   on the same floor and the pair centred. */
.screen-strip.is-lead { flex-wrap: nowrap; align-items: flex-end; }
.screen-strip.is-lead img:first-child { height: auto; width: min(46rem, 68vw); }
.screen-strip.is-lead img + img { height: clamp(13rem, 24vw, 19rem); }
.policy-page { max-width: none; }
.policy { max-width: 46rem; margin: 0 auto; padding: clamp(2rem, 5vw, 4rem) clamp(1.2rem, 5vw, 2rem) clamp(3rem, 6vw, 5rem); }
.policy h1 { font-size: clamp(2rem, 4vw, 3.2rem); }
.policy h2 { margin: 2.6rem 0 .8rem; font-size: clamp(1.2rem, 2vw, 1.6rem); font-weight: 750; letter-spacing: -.02em; color: var(--app-text); }
.policy p { color: var(--dim); font-size: .98rem; }
.policy ul { margin: 0 0 1.2rem; padding-left: 1.2rem; color: var(--dim); font-size: .98rem; }
.policy li { margin-bottom: .4rem; }
.policy .verify { margin-bottom: 2rem; padding: .9rem 1.1rem; border-left: 3px solid var(--app); border-radius: 0 .7rem .7rem 0; background: #ffffff08; text-transform: none; letter-spacing: 0; font-family: var(--sans); font-size: .88rem; font-weight: 400; line-height: 1.55; color: var(--dim); }
.app-detail { padding: 0 clamp(1.2rem, 5vw, 6rem) clamp(4rem, 7vw, 6rem); }
.app-features { display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); gap: clamp(1rem, 2vw, 1.6rem); margin: 0; padding: 0; list-style: none; }
.app-features li { padding: clamp(1.5rem, 2.4vw, 2.2rem); border: 1px solid color-mix(in srgb, var(--app) 30%, transparent); border-radius: 1.4rem; background: linear-gradient(158deg, color-mix(in srgb, var(--app) 12%, #1c1e21), var(--app-ground)); }
.app-features h3 { margin: 0 0 .6rem; font-size: clamp(1.15rem, 1.8vw, 1.5rem); font-weight: 750; letter-spacing: -.025em; }
.app-features p { margin: 0; color: #d3d8de; }
.app-pending, .app-extra, .app-note { max-width: 44rem; color: var(--dim); }
.app-note { margin-top: 2rem; padding: 1.1rem 1.3rem; border-left: 3px solid var(--app); border-radius: 0 .8rem .8rem 0; background: #ffffff08; font-size: .95rem; }
.app-note b { color: var(--ink); }
.app-legal { padding: 0 clamp(1.2rem, 5vw, 6rem) clamp(3rem, 6vw, 5rem); }
.app-legal p { max-width: 44rem; color: var(--dim); }

/* not found */
.notfound { display: grid; align-items: center; min-height: 74svh; padding: clamp(3rem, 8vw, 8rem) clamp(1.2rem, 5vw, 6rem); }
.notfound .eyebrow { color: var(--cyan); }
.notfound h1 { max-width: 14ch; font-size: clamp(2.8rem, 7vw, 6rem); }
.notfound .lede { max-width: 34rem; font-size: clamp(1.12rem, 1.5vw, 1.3rem); line-height: 1.6; color: var(--dim); }
.nf-actions { display: flex; flex-wrap: wrap; gap: .8rem; margin-top: 2.2rem; }

/* activity log */
.activity { padding: clamp(3rem, 6vw, 5rem) clamp(1.2rem, 5vw, 6rem); }
.activity-year { margin-bottom: 2.4rem; }
.activity-year h3 { margin: 0 0 .8rem; font: 700 .74rem/1.4 var(--mono); letter-spacing: .16em; color: var(--dim); }
.activity ul { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--line); }
.activity li { border-bottom: 1px solid var(--line); }
.activity li a, .activity li.is-plain { display: grid; grid-template-columns: 5.5rem 6rem 1fr; gap: 1.2rem; align-items: baseline; padding: 1.1rem .2rem; text-decoration: none; transition: background-color .2s ease; }
.activity li a:hover { background: #ffffff08; }
.activity-date { font: 700 .68rem/1.5 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--dim); }
.activity-kind { font: 700 .62rem/1.5 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--cyan); }
.activity-title { font-size: 1.02rem; font-weight: 600; }
.activity-note { grid-column: 3; font-size: .9rem; color: var(--dim); }
.activity-teaser ul { margin-bottom: 1.6rem; }
.activity-empty { border: 1px dashed #ffffff2e; border-radius: 1.4rem; margin: 0 clamp(1.2rem, 5vw, 6rem); padding: clamp(2rem, 4vw, 3rem); }
.activity-empty p { max-width: 44rem; color: var(--dim); }
.activity-empty code { padding: .15rem .4rem; border-radius: .3rem; background: #ffffff12; font-family: var(--mono); font-size: .88em; }

/* profile page */
.profile-hero { display: grid; grid-template-columns: auto 1fr; gap: clamp(2rem, 5vw, 4.5rem); align-items: center; padding: clamp(4rem, 8vw, 7rem) clamp(1.2rem, 5vw, 6rem); }
.portrait { width: clamp(9rem, 20vw, 17rem); aspect-ratio: 1; border-radius: 50%; object-fit: cover; box-shadow: 0 2rem 4rem #00000080, 0 0 0 1px #ffffff1f; }
.profile-intro h1 { font-size: clamp(2.4rem, 5vw, 4.6rem); }
.profile-intro .lede { max-width: 40rem; font-size: clamp(1.12rem, 1.5vw, 1.3rem); line-height: 1.6; color: var(--dim); }
.profile-actions { display: flex; flex-wrap: wrap; gap: .8rem; margin-top: 2rem; }
.practice { padding: clamp(2rem, 5vw, 4rem) clamp(1.2rem, 5vw, 6rem) clamp(4rem, 7vw, 6rem); }
/* Four steps, one line: three across left the fourth alone on a row of its
   own, which reads as an afterthought rather than as the last step. */
.practice-list { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(1rem, 2vw, 1.6rem); margin: 0; padding: 0; list-style: none; }
.practice-list li { padding: clamp(1.5rem, 2.4vw, 2.4rem); border: 1px solid var(--line); border-radius: 1.4rem; background: linear-gradient(160deg, #ffffff0f, #ffffff03); }
.practice-list h3 { margin: 1rem 0 .6rem; font-size: clamp(1.45rem, 2.3vw, 2.05rem); font-weight: 750; letter-spacing: -.03em; }
.practice-list p:not(.step) { margin: 0; color: var(--dim); font-size: 1.02rem; line-height: 1.6; }
.clients { padding: 0 clamp(1.2rem, 5vw, 6rem) clamp(4rem, 7vw, 6rem); }
.clients .section-head p:not(.eyebrow) { max-width: 46rem; }

/* agent-friendly page */
.agent-page { --bg: #0a0a1c; background: var(--bg); }
.agent-hero { position: relative; display: grid; place-items: center; min-height: 74svh; padding: clamp(4rem, 8vw, 8rem) clamp(1.2rem, 5vw, 6rem); text-align: center; overflow: hidden; }
.agent-hero::after { content: ""; position: absolute; inset: 0; background: radial-gradient(60% 55% at 50% 45%, #2c1a6e5c, transparent 70%); pointer-events: none; }
.agent-hero > * { position: relative; z-index: 2; }
.mark { flex: 0 0 auto; width: 4.5rem; height: 4.5rem; }
.mark-row { display: flex; gap: 1.2rem; align-items: flex-start; padding: 1.2rem; border-radius: 1rem; background: #101024; }
.mark-row b { color: var(--ink); }
.agent-hero h1 { max-width: 18ch; margin-inline: auto; }
.agent-hero .lede { max-width: 44rem; margin-inline: auto; color: #b3b6cc; font-size: clamp(1.12rem, 1.6vw, 1.34rem); line-height: 1.6; }
.agent-body { padding: 0 clamp(1.2rem, 5vw, 6rem) clamp(4rem, 7vw, 7rem); }
.agent-block { max-width: 60rem; margin: 0 auto clamp(2.5rem, 5vw, 4.5rem); }
.agent-block h2 { font-size: clamp(1.8rem, 3.4vw, 3rem); }
.agent-block p { color: #b3b6cc; font-size: 1.06rem; }
.pillars { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; margin: 2rem 0 0; padding: 0; list-style: none; }
.pillars li { padding: 1.6rem; border: 1px solid #ffffff1c; border-radius: 1.2rem; background: linear-gradient(150deg, #ffffff0d, #ffffff03); }
.pillars h3 { margin: 0 0 .5rem; font-size: 1.15rem; font-weight: 750; letter-spacing: -.02em; }
.pillars p { margin: 0; font-size: .95rem; }
.gate { max-width: 60rem; margin: 0 auto clamp(2.5rem, 5vw, 4.5rem); padding: clamp(1.6rem, 3vw, 2.6rem); border: 1px solid #ffffff2b; border-radius: 1.4rem; background: #ffffff0a; }
.gate h2 { margin-top: 0; font-size: clamp(1.5rem, 2.6vw, 2.2rem); }
.gate .state { display: inline-block; margin: 0 0 1rem; padding: .4rem .9rem; border: 1px solid #ffb02e80; border-radius: 999px; font: 700 .64rem/1.3 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: #ffc45e; }
.gate p { color: #b3b6cc; }

/* responsive */
@media (max-width: 720px) { .mark-row { flex-direction: column; } }

/* ------------------------------------------------------ core content pages */

.page-hero, .cat-hero, .svc-hero, .ai-hero, .contact-hero { position: relative; padding: clamp(3.5rem, 7vw, 6rem) clamp(1.2rem, 5vw, 6rem) clamp(1.5rem, 3vw, 2.5rem); }
.cat-hero h1, .svc-hero h1, .ai-hero h1, .contact-hero h1 { max-width: 16ch; margin: .5rem 0 1rem; font-size: clamp(2.6rem, 6vw, 5rem); line-height: .98; letter-spacing: -.03em; }
.cat-hero h1 em, .svc-hero h1 em, .ai-hero h1 em, .contact-hero h1 em { font-style: normal; background: linear-gradient(100deg, var(--blue), var(--cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; }
/* The catalogue heading takes four stops instead of two. It is the one
   word on the site that names what every card below it has in common, so
   it carries more of the palette than a heading normally would. */
/* The four-stop run, on the catalogue's "unique" and on the home page's own
   headline, which Matt asked to match it (2026-09-10). */
.catalogue .device-copy h1 em, .hero h1 em { background: linear-gradient(100deg, var(--blue) 0%, var(--cyan) 34%, #60f77a 66%, #fcae3f 100%); -webkit-background-clip: text; background-clip: text; color: transparent; }
/* The AI headline on the home page starts in violet and runs into the brand
   pair, so the offer has a colour of its own before it rejoins the site's. */
.ai .section-head h2 em { background: linear-gradient(100deg, #9a02db 0%, var(--blue) 52%, var(--cyan) 100%); -webkit-background-clip: text; background-clip: text; color: transparent; }
.cat-hero .lede, .svc-hero .lede, .ai-hero .lede, .contact-hero .lede, .page-hero .lede { max-width: 46rem; color: var(--dim); font-size: clamp(1.12rem, 1.5vw, 1.32rem); line-height: 1.6; }

/* catalogue */
.cat-group { padding: clamp(1.6rem, 3vw, 2.6rem) clamp(1.2rem, 5vw, 6rem); }
.cat-group h2 { margin: 0; font-size: clamp(1.5rem, 2.4vw, 2rem); letter-spacing: -.02em; }
.cat-group > p { max-width: 46rem; margin: .5rem 0 1.8rem; color: var(--dim); }
/* Equal rows as well as equal columns, so a two-line tagline in the first row
   does not leave the second row visibly shorter than it. */
.cat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr)); grid-auto-rows: 1fr; gap: 1rem; margin: 0; padding: 0; list-style: none; }
/* height, not just min-height: the <li> stretches to the row on its own, but
   the card inside it did not, so a two-line tagline made one card taller
   than its neighbours instead of making the whole row taller. */
.cat-card { --pad: 1.1rem; display: grid; grid-template-columns: auto 1fr; gap: 1rem; align-items: center; height: 100%; min-height: 6.6rem; padding: var(--pad); border: 1px solid var(--line); border-radius: 1.2rem; background: linear-gradient(160deg, color-mix(in srgb, var(--app) 12%, var(--bg-2)), var(--bg-2)); text-decoration: none; transition: transform .25s ease, border-color .25s ease; }
.cat-card:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--app) 55%, transparent); }
.cat-card img, .cat-mark-none { width: 66px; height: 66px; border-radius: 1rem; }
.cat-mark-none { display: grid; place-items: center; background: color-mix(in srgb, var(--app) 22%, #0000); border: 1px solid color-mix(in srgb, var(--app) 40%, transparent); color: color-mix(in srgb, var(--app) 75%, #fff); font-size: 1.7rem; font-style: normal; font-weight: 700; }
.cat-copy { display: grid; gap: .18rem; min-width: 0; }
.cat-copy b { font-size: 1.08rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-copy .kind { color: color-mix(in srgb, var(--app) 70%, #fff); font-size: .72rem; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; line-height: 1.35; }
.cat-copy span:last-child { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; color: var(--dim); font-size: .92rem; line-height: 1.45; }
.cat-note { padding: clamp(2rem, 4vw, 3rem) clamp(1.2rem, 5vw, 6rem); }
.cat-note p { max-width: 46rem; color: var(--dim); }

/* services */
.svc-list { padding: clamp(.5rem, 2vw, 1.2rem) clamp(1.2rem, 5vw, 6rem) clamp(3rem, 6vw, 5rem); }
/* Seven offers, read one after another rather than compared in a grid. Matt
   rejected the cards on 2026-09-10: boxed at a third of the width, every
   sentence had to shrink to fit, and seven small rectangles are something a
   reader scans past rather than reads. These are rows the width of the page,
   divided by a hairline, with the number in the margin and the way in on the
   right, and the type at the size the rest of the site uses for prose. */
.svc-list ol { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
.svc-list li { display: grid; grid-template-columns: 4.5rem minmax(0, 1fr) auto auto; gap: clamp(1rem, 2.4vw, 2.4rem); align-items: start; padding: clamp(2rem, 3.6vw, 3.2rem) 0; border-top: 1px solid var(--line); }
.svc-list li:last-child { border-bottom: 1px solid var(--line); }
/* Every child is placed by hand. The number and the way in span the row, so
   auto-placement never gets a chance to push the heading onto a line of its
   own, which is exactly what it did the first time. */
.svc-list .step { grid-column: 1; grid-row: 1; margin: .5rem 0 0; color: var(--cyan); font-family: var(--mono); font-size: .92rem; font-weight: 700; letter-spacing: .12em; }
.svc-list h2 { grid-column: 2; grid-row: 1; margin: 0 0 .6rem; font-size: clamp(1.7rem, 3vw, 2.5rem); font-weight: 800; letter-spacing: -.035em; line-height: 1.08; }
.svc-list li > p { grid-column: 2; grid-row: 3; margin: 0; max-width: 48rem; color: var(--dim); font-size: clamp(1.06rem, 1.35vw, 1.2rem); line-height: 1.62; }
/* Who it is for, then what it does, then how it is bought. The unit is pushed
   to the foot of the card so the six line up and can be compared at a glance,
   which is the whole point of saying it. */
/* The picture that proves an offer, in the third column. Four of the seven
   carry one and three do not, and that asymmetry is the honest part: it shows
   which offers already have shipped work behind them. Nothing generic goes
   here — no glyphs, no stock. Real icons, or a real screen.
   Matt asked for a more visual page on 2026-09-10, and the measurement said
   where: this page ran 814 words against a single image, and that image was
   the brand mark in the navigation. */
/* Enlarged on Matt's instruction of 2026-09-10: at 10.5rem the festival
   handset and the Maison Smeets device family were too small to read as
   proof of anything, which is the only job they have here. */
.svc-proof { grid-column: 3; grid-row: 1 / span 4; display: flex; align-items: center; justify-content: center; width: clamp(9rem, 18vw, 16rem); }
.svc-proof img { width: 100%; height: auto; border-radius: .9rem; box-shadow: 0 1.2rem 2.6rem #00000073; }
/* A phone is taller than it is wide, so it is sized by height or it dwarfs
   the row it sits in. */
.svc-proof.is-tall { align-items: flex-start; }
.svc-proof.is-tall img { width: auto; height: clamp(13rem, 23vw, 20rem); border-radius: 0; box-shadow: none; filter: drop-shadow(0 1.2rem 2.4rem #00000080); }
/* Four real application icons, overlapped the way they sit on a home screen.
   No new asset: these are the same files the catalogue draws. */
/* Three, not four, and the arithmetic is the reason: n icons of width w
   overlapping by o fill the column when n·w − (n−1)·o = 100%. At four they
   had to be 40% wide overlapping by half, which reads as a pile rather than
   as a set of applications. Three at 48% advance 26% each and stay
   recognisable. */
/* A landscape family of devices needs the width a phone screen does not. */
/* The device family is a cutout now, so a box-shadow would draw the very
   rectangle the white ground was removed for. drop-shadow follows the
   alpha instead, the way the tall proof already does. */
.svc-proof.is-wide { width: clamp(12rem, 26vw, 24rem); }
.svc-proof.is-wide img, .svc-proof.is-wide img:only-child { border-radius: 0; box-shadow: none; filter: drop-shadow(0 1.2rem 2.4rem #00000080); }   /* :only-child too, or the rounded corner from the icon-trio rule wins on specificity. */
/* Front to back rather than back to front: the first icon in the list is the
   one Matt names, so it is the one that must not be covered. */
.svc-proof:not(.is-tall) img + img { margin-left: -20%; }
.svc-proof:not(.is-tall) img { position: relative; width: 40%; border-radius: 23%; }
.svc-proof:not(.is-tall) img:nth-child(1) { z-index: 4; }
.svc-proof:not(.is-tall) img:nth-child(2) { z-index: 3; }
.svc-proof:not(.is-tall) img:nth-child(3) { z-index: 2; }
.svc-proof:not(.is-tall) img:nth-child(4) { z-index: 1; }
.svc-proof:not(.is-tall) img:only-child { width: 100%; border-radius: .9rem; margin-left: 0; }

.svc-who { grid-row: 2 !important; font-size: clamp(1.06rem, 1.35vw, 1.22rem) !important; font-weight: 650; color: #ffffffe0 !important; margin: 0 0 .8rem !important; }
/* An offer's last line is the way into it. Five of the seven had a line saying
   the price arrives after a conversation, which is true, is not a price, and
   gave the reader nowhere to go; Matt replaced it with the door on 2026-09-09.
   `auto` on the top margin is what keeps every card's last row on one line. */
/* A column, and with a gap: two buttons rendered inline sat against each
   other with nothing between them (Matt, 2026-09-10). */
.svc-go { grid-column: 4 !important; grid-row: 1 / span 3 !important; display: flex; flex-direction: column; align-items: flex-start; gap: .7rem; align-self: start; margin: .2rem 0 0 !important; padding-top: 0; }
.svc-unit { grid-column: 4 !important; grid-row: 1 / span 3 !important; align-self: start; margin: .8rem 0 0 !important; font-family: var(--mono); font-size: .8rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; text-align: right; color: var(--cyan) !important; }

/* faq */
.faq { padding: 0 clamp(1.2rem, 5vw, 6rem) clamp(3rem, 6vw, 5rem); }
.faq dl { display: grid; grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr)); gap: 1rem 2.4rem; margin: 0; }
.faq div { padding-bottom: 1rem; border-bottom: 1px solid var(--line); }
.faq dt { font-size: 1.02rem; font-weight: 600; }
.faq dd { margin: .45rem 0 0; color: var(--dim); line-height: 1.6; }

/* ai + contact detail */
.contact-hero .mail { margin-top: 1.6rem; }
.contact-alt { margin-top: 1.4rem; }
/* The bookable half of the contact page. Three cards rather than three buttons,
   because a reader picking between a free half-hour and a paid hour needs to
   see which is which before they click, not after. */
.contact-book { padding: clamp(1.5rem, 3vw, 2.5rem) clamp(1.2rem, 5vw, 6rem) clamp(4rem, 7vw, 6rem); }
.contact-book h2 { max-width: 20ch; margin: .5rem 0 1rem; font-size: clamp(1.9rem, 4vw, 3.2rem); line-height: 1.02; letter-spacing: -.03em; }
.contact-book h2 em { font-style: normal; background: linear-gradient(100deg, var(--blue), var(--cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.contact-book .lede { max-width: 44rem; color: var(--dim); font-size: clamp(1.02rem, 1.3vw, 1.14rem); line-height: 1.6; }
.book-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); gap: clamp(1rem, 2vw, 1.6rem); margin-top: clamp(1.8rem, 3vw, 2.6rem); }
.book-card { display: grid; align-content: start; gap: .6rem; padding: clamp(1.4rem, 2.2vw, 2rem); border: 1px solid var(--line); border-radius: 1.4rem; background: linear-gradient(158deg, #ffffff0d, #ffffff05); color: inherit; text-decoration: none; transition: border-color .25s, transform .25s, background .25s; }
.book-card:hover, .book-card:focus-visible { border-color: color-mix(in srgb, var(--cyan) 55%, transparent); background: linear-gradient(158deg, #ffffff14, #ffffff07); transform: translateY(-3px); }
.book-card h3 { margin: 0; font-size: clamp(1.15rem, 1.8vw, 1.42rem); font-weight: 750; letter-spacing: -.025em; }
.book-card p { margin: 0; color: var(--dim); font-size: .96rem; line-height: 1.6; }
.book-meta { font: 700 .68rem/1.4 var(--mono) !important; letter-spacing: .08em; text-transform: uppercase; color: var(--cyan) !important; }
@media (prefers-reduced-motion: reduce) { .book-card { transition: none; } .book-card:hover, .book-card:focus-visible { transform: none; } }

/* footer navigation */
.foot-nav { display: flex; flex-wrap: wrap; gap: .4rem 1.3rem; margin: 1.3rem 0 0; }
.foot-nav a { color: var(--dim); font-size: .9rem; text-decoration: none; }
.foot-nav a:hover { color: var(--ink); text-decoration: underline; }


/* proof and technology band */
.proof { padding: clamp(2rem, 4vw, 3.2rem) clamp(1.2rem, 5vw, 6rem) clamp(1rem, 2vw, 1.6rem); }
/* Following the profile hero, the two paddings stacked into a dead band. */
.profile-hero + .proof { padding-top: 0; }
.proof-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem 2rem; margin: 0 0 2rem; padding: 0; list-style: none; }
.proof-row li { display: grid; gap: .25rem; padding-left: 1rem; border-left: 2px solid color-mix(in srgb, var(--cyan) 55%, transparent); }
.proof-row b { font-size: clamp(2rem, 3.4vw, 3rem); line-height: 1; letter-spacing: -.03em; background: linear-gradient(100deg, var(--blue), var(--cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.proof-row span { color: var(--dim); font-size: .88rem; line-height: 1.45; }
.tech-row { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0; padding: 0; list-style: none; }
.tech-row li { padding: .42rem .85rem; border: 1px solid var(--line); border-radius: 999px; color: #ffffffcc; font: 600 .74rem/1 var(--mono); letter-spacing: .06em; }


/* legal identity block */
.legal-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 1rem 2rem; margin: 1.2rem 0 1.6rem; padding: 1.2rem 1.3rem; border: 1px solid var(--line); border-radius: 1rem; background: #ffffff08; }
.legal-facts div { display: grid; gap: .2rem; }
.legal-facts dt { color: var(--dim); font: 700 .68rem/1.4 var(--mono); letter-spacing: .1em; text-transform: uppercase; }
.legal-facts dd { margin: 0; font-size: .95rem; line-height: 1.5; }
.policy-date { color: var(--dim); font-family: var(--mono); font-size: .8rem; }


/* the card's media band: the app before the words about it */
.app-text { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 1rem; flex: 1; padding: clamp(1.4rem, 2.2vw, 2.2rem); }


/* the AI offer */
.ai-offer, .ai-price-argument { padding: 0 clamp(1.2rem, 5vw, 6rem) clamp(2.5rem, 5vw, 4rem); }
.ai-services { display: grid; grid-template-columns: repeat(auto-fit, minmax(23rem, 1fr)); gap: 1.2rem; margin: 0; padding: 0; list-style: none; }
.ai-service { display: flex; flex-direction: column; padding: 1.8rem 1.7rem 1.6rem; border: 1px solid color-mix(in srgb, var(--cyan) 26%, transparent); border-radius: 1.4rem; background: linear-gradient(160deg, #1b2a3aa8, var(--bg-2)); }
.ai-service.is-quoted { border-color: var(--line); background: var(--bg-2); }
.ai-service .step { margin: 0; color: var(--cyan); font-family: var(--mono); font-size: .8rem; letter-spacing: .16em; }
.ai-service h3 { margin: .5rem 0 .6rem; font-size: 1.45rem; letter-spacing: -.02em; }
.ai-service p { margin: 0 0 .8rem; color: var(--dim); line-height: 1.6; }
/* Who the offer is for, read before the description rather than after it: a
   buyer decides whether a paragraph is about them before deciding whether to
   read it. Set apart from the body so the eye lands on it first. */
.ai-who { font-size: .95rem; font-weight: 600; color: #ffffffd6 !important; margin-bottom: .35rem; }
.ai-detail { font-size: .9rem; color: #ffffff8f !important; }
.ai-price { display: flex; align-items: baseline; gap: .6rem; margin: auto 0 1.2rem !important; padding-top: 1.1rem; border-top: 1px solid var(--line); }
.ai-price b { font-size: clamp(2.4rem, 4vw, 3.2rem); line-height: 1; letter-spacing: -.04em; background: linear-gradient(100deg, var(--blue), var(--cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.ai-price b span { font-size: .6em; }
.ai-unit { display: grid; gap: .1rem; color: var(--dim); font-size: .92rem; }
.ai-net { color: #ffffff70; font-size: .82rem; }
.ai-price.is-quoted { min-height: 3.2rem; align-items: center; }
.ai-service .cta { align-self: flex-start; }
.ai-price-argument p { max-width: 46rem; color: var(--dim); font-size: 1.02rem; line-height: 1.65; }


/* agents: the tasks, one at a time */
.agents { padding: clamp(3rem, 6vw, 5rem) clamp(1.2rem, 5vw, 6rem) clamp(2rem, 4vw, 3rem); }
.agents .section-head p { max-width: 46rem; color: var(--dim); font-size: 1.1rem; line-height: 1.6; }
.agent-tasks { margin: clamp(2rem, 5vw, 4rem) 0; padding: 0; list-style: none; }
/* Without JavaScript, and with reduced motion, every phrase simply reads. */
.agent-tasks li { margin: 0 0 .7rem; font-size: clamp(1.5rem, 3.6vw, 2.9rem); font-weight: 750; letter-spacing: -.035em; line-height: 1.12; color: var(--ink); }
.agent-tasks.is-static li { color: var(--ink); }
/* Rotating: one phrase occupies the space, the rest are stacked under it. */
.agent-tasks.is-rotating { position: relative; display: grid; min-height: clamp(6rem, 13vw, 10rem); place-items: center start; }
.agent-tasks.is-rotating li { grid-area: 1 / 1; max-width: 20ch; margin: 0; opacity: 0; filter: blur(18px); transform: scale(.82); transition: opacity .9s ease, filter .9s ease, transform 2.6s cubic-bezier(.2,.7,.3,1); background: linear-gradient(100deg, var(--ink), var(--cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.agent-tasks.is-rotating li.is-on { opacity: 1; filter: blur(0); transform: scale(1.06); }
.agents-note { max-width: 42rem; color: var(--dim); font-size: .95rem; line-height: 1.6; }

/* kernel */
.kernel { padding: clamp(2rem, 4vw, 3rem) clamp(1.2rem, 5vw, 6rem) clamp(3rem, 6vw, 5rem); }
.kernel-body { display: grid; grid-template-columns: 1.4fr 1fr; gap: clamp(1.5rem, 4vw, 3.5rem); align-items: start; }
.kernel-body > div > p { max-width: 42rem; color: var(--dim); font-size: 1.02rem; line-height: 1.65; }
.kernel-gains { display: grid; gap: .9rem; margin: 1.6rem 0 0; padding: 0; list-style: none; }
.kernel-gains li { padding-left: 1rem; border-left: 2px solid color-mix(in srgb, var(--cyan) 45%, transparent); }
.kernel-gains h3 { margin: 0 0 .2rem; font-size: 1.05rem; }
.kernel-gains p { margin: 0; color: var(--dim); font-size: .93rem; line-height: 1.55; }
.kernel-price { padding: 1.7rem 1.6rem 1.5rem; border: 1px solid color-mix(in srgb, var(--cyan) 26%, transparent); border-radius: 1.4rem; background: linear-gradient(160deg, #1b2a3aa8, var(--bg-2)); }
.kernel-price .ai-price { margin: 0 0 1rem !important; padding-top: 0; border-top: 0; }
.kernel-upkeep { margin: 0 0 1.3rem; color: #ffffff8a; font-size: .84rem; line-height: 1.55; }
/* The caveat under the setup price. Smaller than the price it qualifies,
   because it draws a boundary rather than making an offer, and Matt asked
   for it in smaller type on 2026-09-10. */
.kernel-excludes { font-size: .86rem !important; line-height: 1.5; opacity: .8; }
.kernel-upkeep b { color: #ffffffc4; }

/* Descending order, and that is not tidiness: both of these match a phone,
   so whichever is written second wins. With 900 written first, a 375px
   screen was still reading `repeat(2, 1fr)` from the 1000 block and the
   profile page scrolled sideways by 18 pixels. */
@media (max-width: 1000px) {
  .practice-list { grid-template-columns: repeat(2, 1fr); }
  .faq dl { grid-template-columns: 1fr; }
  .device-section { grid-template-columns: 1fr; }
  .app-grid, .service-grid, .pillars { grid-template-columns: 1fr; }
  /* Centred rather than left-aligned: on one column the trio and the button
     both sat off to the left of a band that reads as a card (Matt, 2026-09-10). */
  .festival { grid-template-columns: 1fr; justify-items: center; text-align: center; }
  .festival p:not(.eyebrow) { margin-left: auto; margin-right: auto; }
  .band-split { grid-template-columns: 1fr; }
  .section-head.is-marked { grid-template-columns: 1fr; justify-items: start; }
  .fest-mark { justify-self: center; }
}
@media (max-width: 900px) {
  /* Below this width the seven links become a panel behind one button.
     They used to wrap to a second row that scrolled sideways: that cost a row
     of header height on every page, and the last entry was still off screen in
     French. Matt read the site on his own phone on 2026-09-10 and asked for a
     hamburger, and he was right.
     `.has-burger` is added by the script, so a page whose JavaScript never
     runs keeps the links visible rather than hiding them behind a control
     nothing can open. */
  .nav.has-burger { grid-template-columns: auto 1fr auto auto; gap: .7rem; }
  /* Every row named. Auto-placement never goes backwards, so with the button
     placed in column 4 first, the language and Contact block asking for column
     3 was pushed to a second row and the header grew 50 pixels for nothing. */
  .nav.has-burger .brand { grid-column: 1; grid-row: 1; }
  .nav.has-burger .nav-end { grid-column: 3; grid-row: 1; }
  .nav.has-burger .nav-burger { grid-column: 4; grid-row: 1; display: grid; }
  .nav.has-burger nav { position: absolute; z-index: 3; top: calc(100% - .3rem); right: clamp(.85rem, 4vw, 3.5rem); left: auto;
    display: grid; gap: .1rem; width: max-content; max-width: min(18rem, calc(100vw - 2rem)); padding: .5rem; border: 1px solid var(--line); border-radius: 1rem;
    background: #14161af2; box-shadow: 0 1.6rem 3rem #000000a6; backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
    opacity: 0; visibility: hidden; transform: translateY(-.4rem); transition: opacity .2s ease, transform .2s ease, visibility .2s; }
  .nav.has-burger.is-open nav { opacity: 1; visibility: visible; transform: none; }
  .nav.has-burger nav a { padding: .62rem .85rem; border-radius: .6rem; font-size: .95rem; text-align: right; }
  .nav.has-burger nav a[aria-current] { background: #ffffff12; }
  @media (prefers-reduced-motion: reduce) { .nav.has-burger nav { transition: none; } }
  /* No pull here, and the reason is arithmetic rather than taste. A header
     image is `width: 100%; height: auto`, so on a 390px screen Orak's is 156
     pixels tall — the glass header alone covers 70 of them. Pulling the
     identity up over that put the breadcrumb underneath the header, which is
     how the page came out broken (Matt, 2026-09-10).
     What the blurred strip actually needed was a taller picture, so the part
     under the glass is a smaller share of it. `cover` with a floor crops the
     sky rather than the mountains, because object-position already keeps the
     bottom. Only the plain headers: `is-title` is a logotype that must not be
     cropped at all, and `is-centred` is placed rather than filled. */
  .app-page.has-header .breadcrumb { margin-top: 0; padding-top: 1.6rem; }
  .app-header:not(.is-title):not(.is-centred) { min-height: 30vh; object-fit: cover; }
  .activity li a, .activity li.is-plain { grid-template-columns: 1fr; gap: .3rem; }
  .activity-note { grid-column: 1; }
  /* The portrait centres on one column; the words stay left, where they are
     read (Matt, 2026-09-10). */
  .profile-hero { grid-template-columns: 1fr; justify-items: start; }
  .profile-hero > img, .profile-hero > figure, .profile-hero .portrait { justify-self: center; }
  /* The actions centre under the words with the portrait, rather than
     sitting hard against the left edge on their own (Matt, 2026-09-10). */
  .profile-actions, .contact-alt { display: flex; flex-wrap: wrap; justify-content: center; text-align: center; }
  .practice-list { grid-template-columns: 1fr; }
  .practice-list h3 { font-size: clamp(1.4rem, 5vw, 1.8rem); }
}
@media (max-width: 720px) {
  /* The copy was starting halfway down the screen: 88svh centred means half
     an empty field above it before a word is read (Matt, on his phone,
     2026-09-10). Shorter, and anchored to the top of its box. */
  .hero { min-height: 62svh; align-items: start; padding-top: 1.6rem; }
  .lattice::after { background: linear-gradient(#202225cc 0%, #202225dd 20%, #202225cc 52%, #20222566 72%, #20222500 88%); }
  .hero-copy::before { inset: -18% -40% -40%; }
  .app { min-height: 0; }
  .proof-row { grid-template-columns: 1fr 1fr; }
  .cat-grid, .ai-services { grid-template-columns: 1fr; }
  .svc-list li { grid-template-columns: 3rem minmax(0, 1fr); }
  .svc-list h2, .svc-list li > p { grid-column: 2; }
  /* The proof goes under the words on a narrow screen rather than squeezing
     the sentence it is there to support. */
  /* Full width and centred under the words. The proof was still being sized
     as a side column here — 18vw is 67 pixels on a phone, which proves
     nothing (Matt, 2026-09-10). */
  .svc-proof { grid-column: 1 / -1 !important; grid-row: 5 !important; width: 100% !important; justify-content: center; margin-top: 1.4rem; }
  .svc-proof:not(.is-tall) img { width: 34%; }
  .svc-proof:not(.is-tall) img + img { margin-left: -17%; }
  .svc-proof:not(.is-tall) img:only-child { width: min(100%, 26rem); }
  .svc-proof.is-tall img { height: min(58vw, 20rem); }
  /* Centred with the proof above them (Matt, 2026-09-10). They were the last
     things on the page still hugging the left edge. */
  .svc-go, .svc-unit { grid-column: 1 / -1 !important; grid-row: 6 !important; margin-top: 1.2rem !important; align-items: center; text-align: center; }
  .kernel-body { grid-template-columns: 1fr; }
  .mail { word-break: break-word; }
}
@media (prefers-reduced-motion: reduce) {
  /* The rotator stops and every phrase reads at once. */
  .agent-tasks.is-rotating { display: block; min-height: 0; }
  .agent-tasks.is-rotating li { opacity: 1 !important; filter: none !important; transform: none !important; margin-bottom: .7rem; }
  html { scroll-behavior: auto; }
  .os-lock { transition: none; }
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
  .app:not(.in), .service-grid li:not(.in), .section-head:not(.in) { opacity: 1; transform: none; }
}

/* The employment history. Same shape as the practice list it sits near, so the
   page reads as one voice rather than a CV pasted into a marketing site. */
.employers { list-style: none; margin: 2rem 0 0; padding: 0; display: grid; gap: 1.4rem; }
.employers li { padding: 1.3rem 1.5rem; border: 1px solid var(--line); border-radius: 14px; background: var(--bg-2); }
.employers .step { margin: 0; color: var(--cyan); font-family: var(--mono); font-size: .8rem; letter-spacing: .12em; }
.employers h3 { margin: .45rem 0 .2rem; font-size: 1.3rem; letter-spacing: -.02em; }
.employer-role { margin: 0 0 .7rem !important; font-size: .95rem; color: #ffffffb0 !important; }
.employers li > p:last-child { margin: 0; color: var(--dim); line-height: 1.65; }
@media (min-width: 760px) { .employers { grid-template-columns: repeat(2, 1fr); } }

/* A platform on the way, marked as such. Deliberately quieter than the ones
   that exist: it is a statement about the roadmap, not about the product, and
   it must not be mistaken for the list beside it. */
.soon { display: inline-block; padding: .1rem .45rem; border: 1px solid var(--line); border-radius: 999px; font-size: .74em; font-weight: 600; color: #ffffff9c; text-transform: none; letter-spacing: 0; white-space: nowrap; }

/* The language switcher: two or three letters, quiet, next to the call to
   action rather than competing with it. The current language is marked rather
   than removed, because a reader needs to see which one they are in. */
/* The right-hand end of the header: the language choice and the call to
   action travel together, in one grid cell. Two cells made a fourth column the
   grid did not have, and the call to action wrapped onto its own row. */
.nav-end { display: inline-flex; align-items: center; gap: .4rem; justify-self: end; }
.lang { position: relative; }
.lang summary { display: inline-flex; align-items: center; gap: .35rem; padding: .5rem .7rem; border: 1px solid transparent; border-radius: 999px; font-family: var(--mono); font-size: .72rem; font-weight: 700; letter-spacing: .06em; color: var(--dim); cursor: pointer; list-style: none; transition: color .2s ease, border-color .2s ease; }
.lang summary::-webkit-details-marker { display: none; }
.lang summary:hover, .lang[open] summary { color: var(--ink); border-color: var(--line); }
/* The chevron, drawn rather than typed: a rotated corner is crisp at every
   zoom level and needs no font to be present. */
.lang summary i { width: .38rem; height: .38rem; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: translateY(-.1em) rotate(45deg); transition: transform .2s ease; }
.lang[open] summary i { transform: translateY(.06em) rotate(-135deg); }
.lang-menu { position: absolute; z-index: 40; top: calc(100% + .4rem); right: 0; display: grid; min-width: 11rem; padding: .35rem; border: 1px solid var(--line); border-radius: 14px; background: #262a2ef2; backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); box-shadow: 0 1.2rem 2.4rem #0000005c; }
.lang-menu a { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding: .5rem .7rem; border-radius: 9px; font-size: .86rem; font-weight: 600; color: var(--dim); text-decoration: none; transition: color .2s ease, background .2s ease; }
.lang-menu a span { font-family: var(--mono); font-size: .68rem; letter-spacing: .08em; opacity: .65; }
.lang-menu a:hover { color: var(--ink); background: #ffffff12; }
.lang-menu a[aria-current] { color: var(--ink); background: #ffffff1a; }
.lang-menu { max-width: min(19rem, calc(100vw - 2rem)); }
.lang-note { margin: .15rem .25rem .4rem; padding: 0 .45rem; font-size: .74rem; line-height: 1.4; color: var(--dim); opacity: .85; }
/* The suggestion sits above the header and scrolls away with the page: it is
   an offer, not a bar the reader has to dismiss to see the site. */
.lang-hint { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: .4rem 1rem; padding: .55rem clamp(1.2rem, 4vw, 3.5rem); border-bottom: 1px solid var(--line); background: #262a2e; font-size: .86rem; }
.lang-hint a { color: var(--ink); font-weight: 600; text-decoration: underline; text-underline-offset: .18em; text-decoration-color: #ffffff5c; }
.lang-hint a:hover { text-decoration-color: currentColor; }
.lang-hint button { padding: .2rem .5rem; border: 0; border-radius: 999px; background: none; color: var(--dim); font: inherit; font-size: .8rem; cursor: pointer; }
.lang-hint button:hover { color: var(--ink); background: #ffffff12; }
@media (max-width: 700px) { .lang summary { padding: .5rem .4rem; } .lang-menu { min-width: 9rem; } }

/* A button carrying a brand mark. The glyph sits on the text baseline rather
   than centred on the button, because a logo floating above the cap height is
   the thing that makes a button look assembled instead of designed. */
.cta.has-mark { display: inline-flex; align-items: center; gap: .55rem; }
/* The size is set here and not left to the SVG's own attributes: the button is
   a flex container, and a flex item with no fixed size stretches to the line
   box — which turned a 16px mark into a 72px one. */
.cta .mark { flex: none; width: 1.05rem; height: 1.05rem; opacity: .85; transition: opacity .2s ease; }
.cta.has-mark:hover .mark { opacity: 1; }


/* ------------------------------------------------------------------- news */

/* Cards, because a page of rows gives the eye nowhere to land. Each one is a
   headline in a sentence, the thing itself named underneath, and a picture
   where there is one. */
.news-list { padding: 0 clamp(1.2rem, 5vw, 6rem) clamp(4rem, 8vw, 7rem); }
.news-list ul { display: grid; gap: clamp(1rem, 2vw, 1.6rem); margin: 0; padding: 0; list-style: none; }
.news-card > a, .news-card > .news-inner { display: grid; gap: clamp(1rem, 2.4vw, 2rem); padding: clamp(1.4rem, 2.6vw, 2.2rem); border: 1px solid var(--line); border-radius: 1.4rem; background: var(--bg-2); text-decoration: none; transition: transform .25s ease, border-color .25s ease; }
.news-card > a:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--cyan) 45%, transparent); }
.news-card.has-shot > a, .news-card.has-shot > .news-inner { grid-template-columns: minmax(0, 22rem) 1fr; align-items: center; }
.news-shot { width: 100%; border-radius: 1rem; border: 1px solid var(--line); }
.news-body { display: block; min-width: 0; }
.news-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin-bottom: .8rem; }
.news-kind { padding: .28rem .6rem; border-radius: 999px; background: #ffffff14; font: 700 .62rem/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--ink); }
.news-date { font: 600 .78rem/1 var(--sans); color: var(--dim); }
.news-title { display: block; font-size: clamp(1.4rem, 2.4vw, 2rem); font-weight: 750; line-height: 1.14; letter-spacing: -.025em; color: var(--ink); }
.news-subtitle { display: block; margin-top: .5rem; font-size: clamp(1rem, 1.5vw, 1.15rem); font-weight: 650; line-height: 1.35; background: linear-gradient(100deg, var(--blue), var(--cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.news-note { display: block; margin-top: .8rem; max-width: 46rem; color: var(--dim); font-size: 1.04rem; line-height: 1.62; }

/* The occasion. One entry at a time wears this, or it stops meaning anything. */
.news-card.is-featured > a, .news-card.is-featured > .news-inner { border-color: transparent; background:
  linear-gradient(var(--bg-2), var(--bg-2)) padding-box,
  linear-gradient(100deg, var(--blue), var(--cyan)) border-box;
  border: 1.5px solid transparent; box-shadow: 0 1.4rem 3.4rem #2689e42e; }
.news-card.is-featured .news-kind { background: linear-gradient(100deg, var(--blue), var(--cyan)); color: #08131c; }
.news-card.is-featured .news-title { font-size: clamp(1.8rem, 3.4vw, 2.9rem); background: linear-gradient(100deg, var(--ink), var(--cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.news-card.is-featured .news-subtitle { background: none; -webkit-background-clip: border-box; background-clip: border-box; color: var(--dim); font-weight: 600; }

@media (max-width: 760px) {
  .news-card.has-shot > a { grid-template-columns: 1fr; }
}


/* ------------------------------------------------------------- festivals */
/* The vertical's own page. It is a sales page, so it is allowed to be louder
   than the rest of the site: a film behind the first screen, then five real
   captures at a size worth reading, then the whole capability list because the
   length of that list is the argument. */
.fest-page { display: block; }
.fest-hero { position: relative; display: grid; align-items: end; min-height: min(90vh, 46rem); margin-top: calc(-1 * var(--nav-h)); padding: calc(var(--nav-h) + 6rem) clamp(1.2rem, 5vw, 6rem) clamp(3rem, 6vw, 5rem); overflow: hidden; isolation: isolate; }
/* The film is darkened in the element rather than only under a scrim. Its
   brightest frame is a white sky, and white copy on a white sky is not a
   design choice a reader forgives. */
.fest-film { position: absolute; z-index: 0; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: brightness(.58) saturate(1.08); }
/* Two scrims rather than one: a vertical wash so the copy sits on ink at the
   bottom, and a flat veil so the crowd never competes with the words. */
.fest-hero::after { content: ""; position: absolute; z-index: 1; inset: 0; background: linear-gradient(180deg, #05070ce6 0%, #05070c8c 26%, #05070cd9 62%, #05070cfa 88%, var(--ground) 100%); }
.fest-hero-copy { position: relative; z-index: 2; max-width: 46rem; }
.fest-hero-copy .lede { max-width: 38rem; color: #e6eaf0; font-size: clamp(1.05rem, 1.7vw, 1.3rem); }
/* The festival page runs purple, so its own two buttons start there rather
   than in the site blue: the solid one opens on mauve before it reaches the
   brand pair, and the ghost beside it takes the same mauve on its edge when
   the pointer arrives (Matt, 2026-09-10). */
.fest-actions .cta:not(.ghost) { background: linear-gradient(100deg, #9a02db, #1b6fc4); color: #f2f6f8; }
/* It stops at a deeper blue than the brand pair rather than running on to
   cyan: the label crosses the whole button, and light text on cyan is 3.4:1.
   Mauve to #1b6fc4 keeps it above 4.5 at both ends. */
.fest-actions .cta.ghost:hover, .fest-actions .cta.ghost:focus-visible { border-color: #aa49e3; }
.fest-actions { display: flex; flex-wrap: wrap; gap: .8rem; margin: 2rem 0 0; }

.fest-claims { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; margin: clamp(2.5rem, 6vw, 5rem) clamp(1.2rem, 5vw, 6rem) 0; padding: 0; list-style: none; border: 1px solid var(--line); border-radius: 1.2rem; overflow: hidden; background: var(--line); }
.fest-claims li { display: grid; align-content: center; gap: .35rem; padding: clamp(1.2rem, 2.2vw, 1.8rem); background: var(--ground); }
.fest-claims b { font-size: clamp(2rem, 3.4vw, 3rem); font-weight: 800; letter-spacing: -.04em; line-height: 1; background: linear-gradient(100deg, var(--blue), var(--cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.fest-claims span { color: var(--dim); font-size: .95rem; line-height: 1.45; }
/* The euro inside the figure, not under it: without this it inherits the
   caption rule above and renders dim and small, which reads as a footnote
   mark rather than as part of the price. */
.fest-claims b span { color: inherit; font-size: .6em; }

.fest-screens { padding: clamp(4rem, 8vw, 7rem) clamp(1.2rem, 5vw, 6rem) clamp(2rem, 4vw, 3rem); }
.fest-shots { display: grid; gap: clamp(3.5rem, 7vw, 6.5rem); }
.fest-shot { display: grid; grid-template-columns: minmax(0, 20rem) minmax(0, 1fr); gap: clamp(2rem, 5vw, 5rem); align-items: center; margin: 0; }
/* The eye walks down the page in a zigzag rather than a column, which keeps
   five near-identical rectangles from reading as one list. The sides are
   swapped by placing each child in a column, not by `order`: order alone
   leaves the phone in the wide track, where it grows to a thousand pixels and
   the row becomes three times as tall as the one above it. */
/* The row is stated as well as the column. Auto-placement never goes backwards:
   on a flipped row the phone takes column 2 first and the caption, wanting
   column 1, is pushed onto a second row instead. */
.fest-shot > img, .fest-shot > .fest-loop { grid-column: 1; grid-row: 1; }
.fest-shot > figcaption { grid-column: 2; grid-row: 1; }
.fest-shot:nth-child(even) { grid-template-columns: minmax(0, 1fr) minmax(0, 20rem); }
.fest-shot:nth-child(even) > img, .fest-shot:nth-child(even) > .fest-loop { grid-column: 2; grid-row: 1; }
.fest-shot:nth-child(even) > figcaption { grid-column: 1; grid-row: 1; }
.fest-shot img, .fest-loop { width: 100%; max-width: 20rem; height: auto; filter: drop-shadow(0 2.5rem 4rem #000000a6); }
/* A screen recording, so it takes the corner radius a phone screen has. */
.fest-loop { display: block; border-radius: 2rem; background: #05070c; }
.fest-shot figcaption { max-width: 38rem; }
.fest-shot h3 { margin: 0 0 .7rem; font-size: clamp(1.5rem, 2.8vw, 2.3rem); font-weight: 800; letter-spacing: -.035em; line-height: 1.1; }
.fest-shot p { margin: 0; color: var(--dim); font-size: clamp(1rem, 1.4vw, 1.14rem); line-height: 1.6; }

.fest-features { padding: clamp(3rem, 6vw, 5rem) clamp(1.2rem, 5vw, 6rem); }
.fest-groups { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(1rem, 2vw, 1.6rem); }
.fest-group { padding: clamp(1.4rem, 2.4vw, 2rem); border: 1px solid var(--line); border-radius: 1.4rem; background: #ffffff05; }
.fest-group h3 { margin: 0 0 1.2rem; padding-bottom: .8rem; border-bottom: 1px solid var(--line); font-size: 1.32rem; font-weight: 800; letter-spacing: -.02em; }
.fest-group ul { display: grid; gap: .7rem; margin: 0; padding: 0; list-style: none; }
.fest-group li { position: relative; padding-left: 1.5rem; color: #cdd3db; font-size: .96rem; line-height: 1.5; }
.fest-group li::before { content: ""; position: absolute; top: .55rem; left: 0; width: .5rem; height: .5rem; border-radius: 50%; background: linear-gradient(100deg, var(--blue), var(--cyan)); }

/* The list above is the floor, and this says so. Set wide and quiet rather
   than as another boxed panel: three more cards would have read as a fourth
   group of features, which is the opposite of the point. */
/* The floor, set apart from the list it prices. A hairline above it and the
   figure at the size the AI page uses for its own, so the two pages quote
   money the same way. */
.fest-price { max-width: 52rem; margin: clamp(2.5rem, 5vw, 4rem) auto 0; padding-top: clamp(1.6rem, 3vw, 2.4rem); border-top: 1px solid var(--line); text-align: center; }
.fest-price .ai-price { justify-content: center; margin: .6rem 0 1rem !important; padding-top: 0; border-top: 0; }
.fest-price .ai-unit { text-align: left; }
/* The number is a floor, not a quote. Saying so beside it, in the same
   line the eye lands on, is the only place a reader will take it. */
.price-from { align-self: baseline; color: var(--dim); font-size: 1rem; font-weight: 650; letter-spacing: -.01em; }
.fest-terms { margin: 0 0 .9rem !important; color: #ffffff9e !important; font-size: .92rem !important; line-height: 1.55; }
.fest-price p:last-child { margin: 0; color: var(--dim); font-size: clamp(1rem, 1.35vw, 1.12rem); line-height: 1.62; }
.fest-open { max-width: 52rem; margin: clamp(2rem, 4vw, 3rem) auto 0; text-align: center; }
.fest-open h3 { margin: 0 0 .8rem; font-size: clamp(1.5rem, 2.8vw, 2.2rem); font-weight: 800; letter-spacing: -.035em; line-height: 1.12; }
.fest-open h3 em { font-style: normal; background: linear-gradient(100deg, var(--blue), var(--cyan)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.fest-open p { margin: 0; color: var(--dim); font-size: clamp(1.02rem, 1.4vw, 1.18rem); line-height: 1.65; }

.fest-white { margin: clamp(2rem, 5vw, 4rem) clamp(1.2rem, 5vw, 6rem); padding: clamp(2.5rem, 5vw, 4.5rem); border-radius: 1.6rem; background: linear-gradient(150deg, #10233f, #0a1120); }
.fest-white p { max-width: 48rem; margin: 0; color: #cfd8e6; font-size: clamp(1.02rem, 1.5vw, 1.2rem); }
/* The one piece of proof on this page that is not a demonstration. It sits
   last, after the argument has been made, because that is where a reader
   who is nearly convinced asks whether anyone has done it before. */
.fest-track { margin: 0 clamp(1.2rem, 5vw, 6rem) clamp(2.5rem, 5vw, 4rem); padding: clamp(2rem, 4vw, 3rem); border: 1px solid var(--line); border-radius: 1.6rem; }
.fest-track p:not(.eyebrow) { max-width: 46rem; color: var(--dim); font-size: clamp(1.02rem, 1.4vw, 1.16rem); }
.fest-demo { max-width: 46rem; margin: 0 clamp(1.2rem, 5vw, 6rem) clamp(2rem, 4vw, 3rem); padding-left: 1.1rem; border-left: 2px solid color-mix(in srgb, var(--cyan) 45%, transparent); }
.fest-demo h2 { margin: 0 0 .5rem; font-size: 1.05rem; font-weight: 750; letter-spacing: 0; }
.fest-demo p { margin: 0; color: var(--dim); font-size: .95rem; }

/* The film is decoration. When motion is off it is paused in script, and the
   page must not still be animating anything else behind the copy. */
html.motion-off .fest-film { opacity: .55; }

@media (max-width: 1000px) {
  .fest-claims { grid-template-columns: repeat(2, 1fr); }
  .fest-groups { grid-template-columns: 1fr; }
}
@media (max-width: 760px) {
  .fest-hero { min-height: min(86svh, 40rem); padding-top: calc(var(--nav-h) + 3rem); }
  .fest-hero-copy .lede { font-size: 1.02rem; }
  .fest-shot, .fest-shot:nth-child(even) { grid-template-columns: 1fr; justify-items: center; }
  .fest-shot > img, .fest-shot > .fest-loop, .fest-shot:nth-child(even) > img, .fest-shot:nth-child(even) > .fest-loop { grid-column: 1; grid-row: 1; }
  /* grid-row too. The wide layout puts both children in row 1 side by side;
     moving only the column stacked them into the same cell, so every mockup
     sat on top of its own paragraph (Matt, 2026-09-10). */
  .fest-shot > img, .fest-shot > .fest-loop, .fest-shot:nth-child(even) > img, .fest-shot:nth-child(even) > .fest-loop { grid-row: 1; }
  .fest-shot > figcaption, .fest-shot:nth-child(even) > figcaption { grid-column: 1; grid-row: 2; }
  .fest-shot img, .fest-loop { max-width: 15rem; }
  .fest-shot figcaption { text-align: left; }
  .fest-claims { grid-template-columns: 1fr; }
}


/* --------------------------------------------------- a-note title band */
/* One line at the top of the page: the film hard left, the picture holding the
   centre, the icon and name level with it (Matt, 2026-09-10). */
.app-row { display: grid; grid-template-columns: 1fr auto 1fr; gap: clamp(1.2rem, 3vw, 3rem); align-items: center; margin-top: calc(-1 * var(--nav-h)); padding: calc(var(--nav-h) + 1.5rem) clamp(1.2rem, 5vw, 6rem) 1rem; }
.app-row > .app-header.is-centred { margin: 0; }
.app-row-film { justify-self: end; width: min(15rem, 30vw); border-radius: 1rem; border: 1px solid var(--line); background: #000; box-shadow: 0 1.4rem 3rem #00000073; }
.app-row-id { justify-self: end; display: grid; gap: .35rem; text-align: right; justify-items: end; }
.app-row-id .app-mark { width: clamp(3.4rem, 6vw, 5rem); height: auto; border-radius: 23%; }
.app-row-name { margin: 0; font-size: clamp(1.6rem, 2.8vw, 2.6rem); font-weight: 800; letter-spacing: -.04em; line-height: 1.05; }

/* --------------------------------------------------------- up and down */
/* The game is one round shape passing between blue squares. A few of those
   squares drift through its page, in the game's own colour. */
.cube-field { position: fixed; z-index: 0; inset: 0; overflow: hidden; pointer-events: none; }
.cube-field i { position: absolute; width: var(--size); height: var(--size); border-radius: .18em; background: #00B8D9; opacity: 0; animation: cube-up 10s linear forwards; }
@keyframes cube-up {
  0%   { transform: translate3d(var(--x), 30vh, 0) rotate(0deg); opacity: 0; }
  12%  { opacity: .5; }
  85%  { opacity: .5; }
  100% { transform: translate3d(var(--x), -120vh, 0) rotate(var(--turn)); opacity: 0; }
}
@keyframes cube-down {
  0%   { transform: translate3d(var(--x), -30vh, 0) rotate(0deg); opacity: 0; }
  12%  { opacity: .5; }
  85%  { opacity: .5; }
  100% { transform: translate3d(var(--x), 120vh, 0) rotate(var(--turn)); opacity: 0; }
}
html.motion-off .cube-field { display: none; }
@media (prefers-reduced-motion: reduce) { .cube-field { display: none; } }

@media (max-width: 900px) {
  .app-row { grid-template-columns: 1fr; justify-items: center; gap: 1.2rem; }
  .app-row-film, .app-row-id { justify-self: center; }
  .app-row-id { text-align: center; justify-items: center; }
  .app-row-id { justify-items: center; text-align: center; }
  .app-row-film { width: min(18rem, 62vw); }
}
