/* Shared type, spacing and motion tokens. */
:root {
  --ink: #161513;
  --body: #444;
  --muted: #666;
  --line: #b8b6b2;
  --hair: #ececec;
  --surface-rest: #fbfaf8;
  --paper: #f4f2ed;
  --surface-hover: #eceae3;
  --green: #8aa03d;
  --amber: #deb624;
  --hover: #222;
  --serif: "Source Serif 4", Georgia, serif;
  --sans: "Source Sans 3", "Source Sans Pro", sans-serif;
  --mono: "JetBrains Mono", monospace;
  --number: var(--serif);
  --arrow-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M1 6h10M6 1l5 5-5 5' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E");
  --ease: cubic-bezier(.22, 1, .36, 1);
  --ease-exit: cubic-bezier(.4, 0, 1, 1);
  --enter: 480ms;
  --exit: 320ms;
  --feedback: 180ms;
  --service-enter: 420ms;
  --service-exit: 300ms;
  --figure-size: 36px;
  --radius: 16px;
  --gutter: 6vw;
  --grid-gap: 3vw;
}
* { box-sizing: border-box; }
/* The white belongs to the root, never to the body: the cursor ground sits under in-flow content at
   z-index -1, and a body background would paint over it everywhere but the collapsed top margin. */
html { background: #fff; }
html, body { margin: 0; padding: 0; }
html { scrollbar-color: var(--green) #fff; scrollbar-width: thin; }
body { font: 400 17px/1.6 var(--sans); color: var(--body); -webkit-font-smoothing: antialiased; overflow-x: clip; }
a { color: inherit; text-decoration: none; transition: color var(--feedback); }
a:not(.logo):not(.btn):not(.contact), a.contact .contact-v { text-decoration: underline; text-decoration-color: var(--green); text-decoration-thickness: 1px; text-underline-offset: 4px; transition: color var(--feedback), text-decoration-color var(--feedback); }
a:not(.logo):not(.btn):is(:hover, :focus-visible), a.contact:is(:hover, :focus-visible) .contact-v { color: var(--green); text-decoration-color: var(--green); }
button, input, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
button, a, input, textarea, [tabindex="0"] { -webkit-tap-highlight-color: transparent; }
:focus-visible { outline: 2px solid var(--green); outline-offset: 6px; }
p { margin: 0; }
h1, h2, h3 { color: var(--ink); margin: 0; font-family: var(--serif); font-weight: 600; letter-spacing: -.02em; }
[data-h]:focus { outline: none; }
::selection { background: var(--muted); color: #fff; }
[data-spacer] { width: 100%; }

@keyframes chrome-enter {
  from { opacity: 0; transform: translateY(-12px); }
  to { opacity: 1; transform: none; }
}
.chrome { position: fixed; top: 24px; z-index: 20; }
.logo { color: var(--ink); left: var(--gutter); display: flex; align-items: center; gap: 18px; animation: chrome-enter 650ms var(--ease) both; }
.logo-box { position: relative; overflow: hidden; width: 50px; height: 40px; }
.logo-box img { position: absolute; display: block; width: 68px; height: 68px; left: -9px; top: -13px; }
.logo-name { font: 600 20px/1 var(--serif); letter-spacing: -.01em; white-space: nowrap; }
.blog { right: var(--gutter); display: flex; flex-direction: column; align-items: flex-end; gap: 28px; animation: chrome-enter 650ms var(--ease) 120ms both; }
.hint { position: fixed; right: var(--gutter); bottom: 6vh; z-index: 20; display: flex; align-items: center; gap: 12px; }
.hint i { display: block; width: 40px; height: 1px; background: var(--ink); transform-origin: right; animation: chrome-enter 600ms var(--ease) 500ms both; }

.btn { --fill-color: var(--hover); --arrow-color: #fff; display: inline-flex; align-items: center; justify-content: center; gap: 12px; border-radius: 999px; padding: 11px 20px 10px; font-size: 12px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; white-space: nowrap; border: 1px solid var(--green); background: var(--green); color: #fff; transition: background var(--feedback), border-color var(--feedback), color var(--feedback), transform 160ms var(--ease); }
.btn:is(:hover, :focus-visible) { border-color: var(--hover); color: #fff; }
/* Press feedback is its own budget, shorter than the colour it travels with. */
.btn:active { transform: scale(.97); }
/* The outline pill stands on white, never on nothing (Jan, 2026-09-07): the contour ground is a fixed
   canvas at z-index -1, so a transparent fill let the lines run straight through the button and it read
   as see-through. Written as the literal the root ground uses rather than as a surface token, because
   matching that ground is the whole point and a token that moves would reopen the leak. */
.btn.outline { --arrow-color: var(--muted); background: #fff; color: var(--ink); border-color: var(--ink); }
.btn.neutral { --arrow-color: var(--muted); background: var(--line); border-color: var(--line); color: var(--ink); }
/* The black fill takes the whole foreground with it, arrow included. */
.btn:is(.outline, .neutral):is(:hover, :focus-visible) { --arrow-color: #fff; border-color: var(--hover); color: #fff; }
/* Every arrow uses the same stroke; external destinations rotate it upward. */
.ar { --arrow-angle: 0deg; vertical-align: -1px; position: relative; display: inline-block; width: 12px; height: 12px; flex-shrink: 0; overflow: hidden; font-size: 0; font-style: normal; line-height: 1; letter-spacing: 0; color: var(--arrow-color, var(--muted)); transition: color var(--feedback), transform 240ms var(--ease); }
.ar::before, .ar::after { content: ''; position: absolute; inset: 0; background: currentColor; mask: var(--arrow-mask) center / contain no-repeat; transform: rotate(var(--arrow-angle)); }
.ar::after { opacity: 0; }
.ar-external { --arrow-angle: -45deg; --flight-y: -14px; }
.ar-up { --arrow-angle: -90deg; }
@keyframes arrow-out { to { translate: var(--flight-x, 14px) var(--flight-y, 0px); opacity: 0; } }
@keyframes arrow-in { from { translate: calc(-1 * var(--flight-x, 14px)) calc(-1 * var(--flight-y, 0px)); opacity: 0; } to { translate: 0 0; opacity: 1; } }
:is(.btn, a, .nav-btn:enabled):is(:hover, :focus-visible) .ar::before { animation: arrow-out 220ms var(--ease) forwards; }
:is(.btn, a, .nav-btn:enabled):is(:hover, :focus-visible) .ar::after { animation: arrow-in 300ms var(--ease) 160ms both; }
/* A tap on a touch screen fires a hover the finger never asked for, and it sticks there until
   something else is tapped, so the flight plays and the button is left mid-gesture. Withdraw it from
   pointers that cannot hover; keyboard focus is a real intent and keeps it. */
@media (hover: none), (pointer: coarse) {
  :is(.btn, a, .nav-btn:enabled):hover:not(:focus-visible) .ar::before,
  :is(.btn, a, .nav-btn:enabled):hover:not(:focus-visible) .ar::after { animation: none; }
}
/* One fill treatment for cards, panels, countdown tiles and buttons. */
.soft-fill { position: relative; isolation: isolate; }
.soft-fill::before { content: ''; position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none; background: var(--fill-color, var(--surface-hover)); clip-path: circle(0 at var(--pointer-x, 50%) var(--pointer-y, 50%)); transition: clip-path 520ms var(--ease); }
/* A region answers the pointer crossing its own edge, and only that. The slow fill follows the
   pointer into the form and stays for as long as it rests there, whether or not a field inside is
   being typed in; something coming alive inside never changes the ground on its own, so tabbing in
   from the keyboard leaves it exactly where it was (Jan, 2026-09-07). */
.soft-fill:is(:hover, :focus-visible, .is-pointer-fill)::before, .hl:focus-visible .soft-fill::before { clip-path: circle(145% at var(--pointer-x, 50%) var(--pointer-y, 50%)); }
.soft-fill.is-pointer-leaving::before { transition: none; }

.label { font: 12px/1.5 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.badges { display: flex; justify-content: flex-end; gap: 56px; }
/* Both badge groups share one slot below the button; only one is ever on screen. */
.badge-window { display: grid; }
.badge-window > * { grid-area: 1 / 1; }
.badge { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.badge .label { text-align: right; }
/* Badge values and timeline years share type, with separate size and colour. */
.badge-v, .year { font-family: var(--number); font-size: var(--figure-size); font-weight: 600; font-optical-sizing: none; font-variation-settings: "opsz" 40; font-variant-numeric: lining-nums tabular-nums; line-height: 1; letter-spacing: -.02em; }
.badge-v { color: var(--ink); font-size: clamp(44px, 3.6vw, 56px); }
.year { color: var(--line); }
.year.today { color: var(--ink); }
.badge-v [data-count] { display: inline-block; min-width: 2ch; text-align: right; }

.h { position: fixed; left: var(--gutter); top: 0; z-index: 8; opacity: 0; font-size: clamp(44px, 6.4vw, 96px); line-height: 1; white-space: nowrap; max-width: 88vw; }
/* The scrolling build re-transforms these on every frame, so say so once rather than letting the
   browser promote and demote each of them as the scroll starts and stops. Reading flow and the
   no-script fallback leave them where they stand and pay nothing for the hint. */
.story :is(.h, .c, .subhead, [data-badges]) { will-change: transform; }
.story .row { will-change: translate; }
/* `.subsection-title` is the comparison page's heading role; the homepage uses `.subhead`. */
.subhead, .subsection-title { color: var(--line); font: 400 clamp(20px, 1.8vw, 26px)/1.15 var(--sans); letter-spacing: .04em; text-transform: uppercase; }
.subhead { position: fixed; left: var(--gutter); top: 0; z-index: 8; overflow: clip; padding-bottom: .15em; }
/* Waiting out of sight is a state of its own, not a side effect of the clip. A subheading that has
   not been asked for yet is unpaintable in any engine, the way `.h` is by its own `opacity: 0` —
   relying on `overflow: clip` alone put the opening statement on screen before the heading had
   started moving (Jan, 2026-09-12). Visibility flips at the far end of the roll-up, so the exit is
   still watched all the way out, the way `.cf-shell` holds its own. */
.subhead > span { display: block; transform: translateY(-120%); visibility: hidden; transition: transform var(--exit) var(--ease-exit), visibility 0s var(--exit); }
.subhead.on > span { transform: none; visibility: visible; transition: transform var(--enter) var(--ease), visibility 0s; }
.c { position: fixed; left: var(--gutter); top: 0; z-index: 9; width: 88vw; opacity: 0; }
.grid { display: grid; gap: var(--grid-gap); align-items: start; }
.cols-4 { grid-template-columns: 1fr 1.6fr 1fr 1fr; }
.cols-4-even { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.stack { display: flex; flex-direction: column; }
.slot { display: grid; align-items: start; }
.slot > * { grid-area: 1 / 1; min-width: 0; }
.gap-lg { gap: 28px; }
.intro-content { gap: 56px; }
/* Row two keeps the row-one column width and starts at the middle of its second column. */
.work-content { gap: 52px; --work-col: calc((100% - 3 * var(--grid-gap)) / 4); }
.work-item { gap: 16px; }
.work-offset { grid-template-columns: calc(var(--work-col) * 1.5) repeat(2, minmax(0, var(--work-col))); }
.work-offset > :first-child { grid-column: 2; }
.intro-outside { gap: 28px; align-items: flex-start; }
.intro-cta { display: flex; }
/* Repository pills sit inside the Open source reveal; a long name wraps rather than leaving the column. */
.work-cta { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px; }
.work-cta .btn { max-width: 100%; white-space: normal; text-align: center; }
.contact-content { gap: 6vw; }

/* Keep offscreen items inert. Only visible outgoing items earn a transition. */
.r { --stagger: 0ms; opacity: 0; transform: translateX(60vw); pointer-events: none; transition: transform var(--enter) var(--ease) var(--stagger), opacity var(--enter) var(--ease) var(--stagger); }
.r.post { opacity: 0; transform: translateX(-60vw); transition-duration: var(--exit); transition-timing-function: var(--ease-exit); }
.r.from-left { transform: translateX(-60vw); }
.r.from-left.post { transform: translateX(60vw); }
.r.from-bottom { transform: translateY(60vh); }
.r.from-bottom.post { transform: translateY(-60vh); }
.r.from-right-back.post { transform: translateX(60vw); }
.r.lr, .r.lr.post { transform: translateX(-60vw); }
.r.on { opacity: 1; transform: none; pointer-events: auto; }
.r:not(.on) { transition-duration: var(--exit); transition-timing-function: var(--ease-exit); }
.r.is-skipped { transition: none; visibility: hidden; }
/* Arriving from below must not replay an entrance written for arriving from above: the scene is
   already deep in its horizontal travel, so it fades in where it stands instead of sliding there. */
.c.snap .r, .c.snap .pt, .c.snap .timeline-aside-body, .subhead.snap > span { transition-property: none; }
/* Taking over a layout is a rebuild, not a move. Reading flow and the no-script fallback both leave
   a subheading standing in its line, so arriving in the story layout from either one animated all
   four of them at once: three rolling up the top edge of the screen and the opening statement
   rolling up its own place, a travel none of them had ever made (Jan, 2026-09-12). The switch owns
   this one the way `.snap` owns arriving mid-scene, and it covers the reveals for the same reason. */
html.is-switching :is(.h, .c, .subhead > span, .r, .pt, .timeline-aside-body, [data-badges]) { transition-property: none; }
/* Arriving at the page is the same story told about a stylesheet rather than a class: WebKit resolves
   style before the sheet is in force, so the subheading is briefly a heading standing in its line, and
   the sheet landing transitions out of that (Jan, 2026-09-12, Safari). Only the roll needs holding —
   a reveal waiting its turn already carries `is-skipped`, and a heading and its content block have no
   transition to start — so the boot holds that alone rather than the whole switch's reach. */
html.is-booting .subhead > span { transition-property: none; }
.slot > .post { position: relative; z-index: 2; }

.photo { width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: contain; display: block; border-radius: var(--radius); background: var(--surface-hover); filter: grayscale(1) contrast(1.02); }
[data-panel].r { opacity: 1; visibility: hidden; transform: translateX(calc(100% + 24px)); transition: none; }
/* The offer shows one square card at a time on the left (mini summary, big name, lead) with the chosen
   service's copy beside it in narrow columns. A prev and next pair under the card names the neighbouring
   service beside a plain arrow; both are always present and the edge one is disabled. */
.offer-content { display: grid; grid-template-columns: 340px minmax(0, 1fr); gap: 72px; align-items: start; }
.service-menu { width: min(340px, 100%); min-width: 0; }
.service-tabs { display: grid; }
.service-tabs > .hl { grid-area: 1 / 1; }
.hl { border: 0; padding: 1px 0 0; background: none; text-align: left; min-width: 0; }
.hl.active { position: relative; z-index: 1; }
.hl:not(.active) { pointer-events: none; }
/* Card and copy trade places in opposite directions: the card comes in from the left and leaves the
   same way, clearing the viewport edge rather than nudging, while the copy arrives from the right.
   A replacement waits out the card it replaces, the way the detail stage already does; only the
   first card of the scene has nothing to wait for, so the wait is latched on the first swap. */
.card { --card-wait: 0s; background: var(--surface-rest); border-radius: var(--radius); padding: 28px; aspect-ratio: 1 / 1; display: flex; flex-direction: column; gap: 10px; transition: background var(--feedback), transform var(--service-exit) var(--ease-exit), visibility 0s var(--service-exit); }
.service-tabs.is-swapping .card { --card-wait: var(--service-exit); }
.hl.active .card { background: var(--paper); transition: background var(--feedback), transform var(--service-enter) var(--ease) var(--card-wait), visibility 0s var(--card-wait); }
.hl:not(.active) .card { transform: translateX(calc(-100% - var(--gutter))); visibility: hidden; }
.card .service-name { font-size: clamp(36px, 3vw, 44px); }
/* The mini summary hangs off the card's right edge, always over two lines. */
.card-note { text-align: right; }
.card-lead { margin-top: auto; }
.service-nav { display: flex; justify-content: space-between; align-items: center; gap: 16px; margin-top: 14px; }
.nav-btn { border: 0; border-radius: 0; background: none; padding: 7px 0 6px; display: inline-flex; align-items: center; gap: 10px; font: 12px/1.5 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--ink); cursor: pointer; transition: color var(--feedback), opacity var(--feedback); }
.nav-btn:disabled { color: var(--muted); opacity: .4; cursor: default; }
.ar-back { --arrow-angle: 180deg; --flight-x: -14px; }
.service-details { min-width: 0; overflow: clip; }
.service-copy { display: grid; grid-template-columns: repeat(2, minmax(0, 300px)); gap: 40px; }
.service-copy > .grid { display: contents; }
/* A service may carry a third column level with the other two; Design uses it for how Jan designs. Below 1100 px the third drops under the two. */
.service-copy.three { grid-template-columns: repeat(3, minmax(0, 240px)); gap: 32px; }
.service-copy .label { display: block; margin-bottom: 12px; }
.h3 { color: var(--ink); font-family: var(--serif); font-weight: 500; font-size: 26px; line-height: 1.15; letter-spacing: -.01em; }

.timeline-content { width: 94vw; }
/* A hundred pixels to the year, measured from 2000, so a column is only as wide as the run to its
   next same-side point. The markup carries the years and nothing else: `motion.js` derives every
   pixel of the axis from them and writes --point-x here, along with the span and the width below.
   The declared values are what the rail measures today, so it still draws if the script never runs. */
.row { --rail-y: 190px; --rail-span: 2700px; --rail-width: 2880px; position: relative; width: var(--rail-width); height: 380px; }
.row-line { position: absolute; left: 0; top: var(--rail-y); height: 1px; width: var(--rail-span); background: var(--line); }
/* A point enters from beyond the right edge, the distance every other reveal on the page travels, and
   settles on the rail over 720 ms with a short fade; leaving is the page's exit, fast and back the way it
   came (Jan, 2026-09-08: with an 80 px nudge the points stood on the rail as one static block; B in
   timeline-options.html). */
.pt { position: absolute; left: var(--point-x, 0px); top: 0; height: 100%; width: 280px; opacity: 0; transform: translateX(60vw); transition: transform 720ms var(--ease) var(--stagger, 0ms), opacity 300ms var(--ease) var(--stagger, 0ms); }
.pt.on { opacity: 1; transform: none; }
.pt:not(.on) { transition-duration: var(--exit); transition-timing-function: var(--ease-exit); transition-delay: 0ms; }
.dot { position: absolute; left: 0; top: var(--rail-y); width: 12px; height: 12px; border-radius: 50%; background: var(--ink); box-shadow: 0 0 0 2px #fff; transform: translate(-50%, -50%); }
.dot.today { width: 16px; height: 16px; background: var(--green); }
/* Lining figures carry a left side bearing the dot does not; the whole block moves onto it. */
.pt-text { position: absolute; left: -6px; width: 100%; }
.pt-text.up { top: var(--rail-y); transform: translateY(calc(-100% - 24px)); }
.pt-text.down { top: calc(var(--rail-y) + 24px); }
.pt p { margin-top: 10px; }
/* The closing invitation waits above its own clipped window until Today clears the rail's right side.
   It opens on the content's two-thirds line and stays a fifth of the screen wide, so it stops short
   of the content edge; the row it sits in is a gutter wider than the content, so that line is measured
   from the viewport rather than from the row. */
.timeline-aside { position: absolute; left: calc((100vw - 2 * var(--gutter)) * 2 / 3); top: 0; width: 20vw; overflow: clip; padding-bottom: 6px; }
.timeline-aside-body { display: flex; flex-direction: column; align-items: flex-start; gap: 18px; transform: translateY(calc(-100% - 10px)); transition: transform var(--exit) var(--ease-exit); }
.timeline-aside.on .timeline-aside-body { transform: none; transition-duration: var(--enter); transition-timing-function: var(--ease); }

.contact-form { margin: 0; min-width: 0; }
.form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22px; background: var(--paper); border-radius: var(--radius); padding: 32px; color: var(--body); }
.field { display: grid; gap: 6px; }
.field-wide { grid-column: 1 / -1; }
.field-label { color: var(--body); font-weight: 500; }
.required-mark { margin-left: 3px; }
.field input, .field textarea { width: 100%; min-width: 0; border: 0; border-bottom: 1px solid var(--line); background: transparent; padding: 8px 0; border-radius: 0; resize: vertical; color: var(--ink); transition: border-color var(--feedback); }
.field input:hover, .field textarea:hover { border-bottom-color: var(--body); }
.field input:focus, .field textarea:focus { outline: none; border-bottom: 2px solid var(--body); padding-bottom: 7px; }
.field-error { --arrow-color: var(--amber); display: flex; align-items: center; gap: 10px; color: var(--amber); font-size: 16px; line-height: 1.4; }
.field-error[hidden] { display: none; }
.field-error .ar { width: 14px; height: 14px; }
.form-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 20px; padding-top: 20px; }
.form-actions .btn { flex-shrink: 0; min-width: 112px; }
.form-note { flex: 1 1 220px; margin: 0; color: var(--body); font-weight: 500; }
.form-note:not(.visible) { display: none; }
.form-actions .btn .ar[hidden] { display: none; }
.contact { display: grid; gap: 6px; }
.contact-v { font-size: 22px; font-weight: 500; line-height: 1.3; overflow-wrap: anywhere; }
.footer { grid-column: 1 / -1; margin-top: 64px; display: grid; grid-template-columns: 1fr 1fr 1.4fr; gap: 4vw; padding: 20px 0 0; border-top: 1px solid var(--hair); font-size: 15px; line-height: 1.5; }
.footer .stack { gap: 6px; }
.footer a:not(.logo):not(.btn) { text-decoration: none; }
.footer a:not(.logo):not(.btn):is(:hover, :focus-visible) { text-decoration: underline; }
.footer .ar { margin-left: 4px; vertical-align: -1px; }
.footer .label { margin-bottom: 4px; }
.footer .muted { color: var(--muted); }
/* The footer stands on the bottom of the screen at every height (Jan, 2026-09-08: on a tall screen it
   hung under the form with a band of white beneath). The contact scene is at least as tall as the room
   under its heading less the 24 px the page ends on, and its first row takes the spare height, so the
   footer is carried down rather than left where the form stops. `--content-top` is the scene's own
   measured top, written by `motion.js` at layout; a form taller than the screen keeps its natural height. */
.story .contact-content { min-height: calc(100vh - var(--content-top, 0px) - 24px); grid-template-rows: 1fr auto; }

/* Natural reading flow on small screens, without a long virtual scroll runway. */
.flow .chrome { position: absolute; }
.flow .logo-box { width: 50px !important; height: 40px !important; }
.flow .logo-box img { width: 68px !important; height: 68px !important; left: -9px !important; top: -13px !important; }
/* Its full-width empty area must not intercept the logo in desktop reading flow. */
.flow .blog { position: relative; inset: auto; margin: 30px var(--gutter) 0; pointer-events: none; }
.flow .blog > * { pointer-events: auto; }
/* Reading flow keeps the personal figures under the button; the blog counts stay chrome-only. */
.flow .blog [data-badges], .flow .hint, .flow [data-spacer] { display: none; }
.flow .badge-window { display: block; }
.flow section { padding: 76px var(--gutter); scroll-margin-top: 24px; }
.flow section:first-of-type { padding-top: 64px; }
/* The page ends under the footer, never on a band of white: reading flow closes on the same small
   space the scrolling build leaves below it, rather than on a full section rhythm. */
.flow #contact { padding-bottom: 24px; }
.flow .h, .flow .subhead, .flow .c { position: relative; inset: auto; width: 100%; max-width: none; opacity: 1; visibility: visible; transform: none; clip-path: none; }
.flow .subhead { margin-bottom: 28px; }
.flow .subhead { transform: translateY(-1rem); }
.flow .subhead > span { transform: none; visibility: visible; transition: none; }
.flow #hello [data-scene] + [data-scene] { margin-top: 76px; }
.flow .h { white-space: normal; margin-bottom: 40px; }
.flow .r, .flow .pt { opacity: 1; transform: none; pointer-events: auto; transition: none; }
.flow .slot { display: block; }
.flow #offer .slot { display: grid; }
.flow [data-card][data-state="pre"] { display: none; }
.flow .row { width: 100%; height: auto; padding-left: 20px; }
.flow .row-line { top: 0; left: 0; width: 1px; height: 100%; }
.flow .pt { position: relative; left: auto; width: 100%; height: auto; margin-bottom: 32px; }
.flow .dot { left: -20px; top: 20px; }
.flow .pt-text { position: static; transform: none; }
.flow .timeline-aside { position: static; width: auto; overflow: visible; margin-top: 40px; }
.flow .timeline-aside-body { transform: none; transition: none; }
.flow [data-card] { max-height: none !important; }

@media (max-width: 1100px) and (min-width: 901px) {
  .offer-content { grid-template-columns: 300px minmax(0, 1fr); gap: 40px; }
  .service-menu { width: 300px; }
  .badges { gap: 28px; }
}
@media (max-width: 1100px) {
  .service-copy.three { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .service-copy.three p:last-child { grid-column: 1 / -1; }
}
@media (max-width: 900px) {
  :root { --gutter: 6vw; }
  .cols-4, .cols-2 { grid-template-columns: minmax(0, 1fr); gap: 28px; }
  .cols-4-even { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 36px; }
  .badges { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px; }
  .badge { align-items: flex-start; }
  .badge .label { text-align: left; }
  .work-offset { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 36px; }
  .work-offset > :first-child { grid-column: auto; }
  .offer-content { grid-template-columns: 1fr; gap: 28px; }
  .service-copy { gap: 28px; }
  .photo { max-width: 520px; }
  .intro-content, .work-content { gap: 40px; }
  .footer { grid-template-columns: 1fr 1fr; margin-top: 20px; }
  .footer > :last-child { grid-column: 1 / -1; }
}
@media (max-width: 560px) {
  .logo { gap: 10px; }
  .logo-name { font-size: 18px; }
  .flow .blog { margin-top: 92px; align-items: flex-start; }
  .flow section:first-of-type { padding-top: 48px; }
  .flow section { padding-top: 48px; padding-bottom: 48px; }
  .flow .h { font-size: 44px; margin-bottom: 28px; }
  .cols-4-even, .work-offset { grid-template-columns: 1fr; }
  .card, .form { padding: 24px; }
  .card .service-name { font-size: 36px; }
  .service-copy, .service-copy.three { grid-template-columns: 1fr; gap: 24px; }
  .form-actions { gap: 12px; }
  .form { grid-template-columns: 1fr; }
  .footer { grid-template-columns: 1fr; gap: 28px; }
  .footer > :last-child { grid-column: auto; }
}
/* Reduced motion means gentler, not silent. Removing every transition also removed the colour and
   opacity that tell you a control answered — a link changing under the pointer, a button taking its
   fill, a field's focus line, the form's reply — and those carry meaning rather than movement, so
   they stay and everything spatial goes. The whole declaration is replaced rather than only its
   property list: a shorter list of durations is cycled against a longer list of properties, so
   restricting the properties alone leaves each element's own timings paired with whatever now sits
   at that index — measured here as a 240 ms colour fade on an arrow and a 520 ms one on a button.
   One duration for the lot is both predictable and the only thing left worth timing. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: opacity var(--feedback), color var(--feedback), background-color var(--feedback), border-color var(--feedback), box-shadow var(--feedback), text-decoration-color var(--feedback) !important;
    scroll-behavior: auto !important;
  }
  .btn:hover, .btn:active, .btn:hover .ar { transform: none; }
}
/* `?curve` only, and nothing renders without it: a switch for judging the exit curve on the real
   page, because which one is right is a question about how a departure feels rather than one the
   code can answer for itself. */
.curve-switch { position: fixed; right: 12px; bottom: 12px; z-index: 99; padding: 9px 14px 8px; border: 1px solid var(--ink); border-radius: 999px; background: #fff; color: var(--ink); font: 500 11px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
/* The content remains readable if JavaScript cannot load. */
html:not(.story):not(.flow) .chrome { position: relative; inset: auto; margin: 24px var(--gutter); }
html:not(.story):not(.flow) section { margin: 64px var(--gutter); }
html:not(.story):not(.flow) #contact { margin-bottom: 24px; }
html:not(.story):not(.flow) .h, html:not(.story):not(.flow) .subhead, html:not(.story):not(.flow) .c { position: relative; inset: auto; width: auto; opacity: 1; visibility: visible; }
html:not(.story):not(.flow) .r { opacity: 1; transform: none; pointer-events: auto; }
html:not(.story):not(.flow) [data-panel] { visibility: visible; }
html:not(.story):not(.flow) .slot { display: block; }
html:not(.story):not(.flow) .slot > * { margin-bottom: 28px; }

html:not(.story):not(.flow) .subhead > span { transform: none; visibility: visible; }
html:not(.story):not(.flow) .timeline-aside { position: static; width: auto; overflow: visible; margin-top: 40px; }
html:not(.story):not(.flow) .timeline-aside-body { transform: none; }
/* The rail is placed by the script that also scrolls it, so without one the points have no axis to
   stand on. They read as the same vertical list reading flow uses, rather than stacking on the
   left edge of a row three screens wide. */
html:not(.story):not(.flow) .row { width: 100%; height: auto; padding-left: 20px; }
html:not(.story):not(.flow) .row-line { top: 0; left: 0; width: 1px; height: 100%; }
html:not(.story):not(.flow) .pt { position: relative; left: auto; width: 100%; height: auto; margin-bottom: 32px; }
html:not(.story):not(.flow) .dot { left: -20px; top: 20px; }
html:not(.story):not(.flow) .pt-text { position: static; transform: none; }

/* Contact delivery and keyboard access share the existing typography. */
.skip-link { position: fixed; top: 12px; left: 12px; z-index: 1000; padding: 12px 18px; background: #fff; color: var(--ink); transform: translateY(-180%); }
.skip-link:focus { transform: none; }
.contact-trap { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.form-note.is-error { color: #a1382e; }
.btn:disabled { cursor: wait; opacity: .7; }
.legal-page { max-width: 780px; margin: 0 auto; padding: 40px 24px 80px; }
.legal-page h1 { font-size: clamp(36px, 6vw, 60px); margin: 36px 0 24px; }
.legal-page h2 { font-size: 26px; margin: 32px 0 12px; }
.legal-page p { margin: 12px 0; }
html:not(.story):not(.flow) [data-panel].r { transform: none; }
html:not(.story):not(.flow) .hl .card { transform: none; visibility: visible; }
html:not(.story):not(.flow) .service-tabs { display: grid; }
html:not(.story):not(.flow) .service-tabs > .hl { grid-area: auto; }
html:not(.story):not(.flow) .pt { opacity: 1; transform: none; }
html:not(.story):not(.flow) .h { white-space: normal; }
html:not(.story):not(.flow) .badge-window { display: block; }
html:not(.story):not(.flow) .service-nav { display: none; }
@media (max-width: 900px) {
  .offer-content { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 740px) {
  html:not(.story):not(.flow) .grid { grid-template-columns: 1fr; }
  html:not(.story):not(.flow) .footer { grid-template-columns: 1fr; }
}
@media print {
  .chrome, .hint, .cf-shell, .skip-link, [data-spacer], .contact-form, .service-nav { display: none !important; }
  html, body { height: auto !important; overflow: visible !important; }
  [data-sec], [data-scene], [data-h], [data-subh], [data-subh] > span, [data-c], [data-r], [data-panel], .pt, .pt-text, .row, .timeline-aside, .timeline-aside-body { position: static !important; height: auto !important; width: auto !important; transform: none !important; opacity: 1 !important; visibility: visible !important; clip-path: none !important; }
  [data-sec] { margin: 24px 0 !important; }
  .row { display: block !important; }
  .offer-content, .slot { display: block !important; height: auto !important; overflow: visible !important; }
  [data-panel] { display: grid !important; }
  .pt, [data-panel] { margin-bottom: 16px; break-inside: avoid; }
  .row-line, .dot, .service-menu { display: none !important; }
}
