/* The 42pixels site system (owner, 1 Nov 2026: "learn very deep from ref website and create for us a design system
   skill very details for each elements animation ... so then for any page we know how and what to do"). Every value
   was measured on the reference at 1440 and 390 px, motion frame by frame; the rules, the reasons and what each part is
   for are in .claude/skills/42pixels-site-design. Ours: the name, the orange, the words, the pictures, this code.
   A page uses it with <body class="sys"> after header.css and styles.css. Geist and Geist Mono: SIL Open Font
   License (fonts/Geist-OFL.txt); the variable files give the reading weight (500) and bold (700). */
@font-face { font-family: "Geist"; src: url(/fonts/Geist-Variable.woff2) format("woff2"); font-weight: 500; font-display: swap; }
@font-face { font-family: "Geist"; src: url(/fonts/Geist-Variable.woff2) format("woff2"); font-weight: 700; font-display: swap; }
@font-face { font-family: "Geist Mono"; src: url(/fonts/GeistMono-Variable.woff2) format("woff2"); font-weight: 100 900; font-display: swap; }
@property --d { syntax: "*"; inherits: false; }        /* a cell's columns on wide screens, never passed to its children */
@property --m { syntax: "*"; inherits: false; }        /* ... and on phones */

:root {
  --ink: #e6e6e6;          /* titles, links, values: the brightest text */
  --ink-2: #c4c4c4;        /* body copy, big grey titles */
  --ink-3: #757575;        /* labels, counts, dates, captions, closed rows, a field's edge: 4.6:1 on black, the darkest text */
  --hint: #8f8f8f;         /* a placeholder on --field (5.4:1; --ink-3 there is only 3.8:1) */
  --fill: #2e2e2e;         /* tags, code frames, an image before it loads */
  --field: #1a1a1a;        /* inputs */
  --field-2: #212121;      /* an input pointed at */
  --line: rgba(230, 230, 230, .2);    /* hairlines between rows and under labels */
  --line-2: rgba(230, 230, 230, .3);  /* the stronger hairline: a section's top edge (.edge) */
  --orange: #ff5c26;       /* ours: where you are, a call to action pointed at, the live dot, selected text */
  --ease: cubic-bezier(.4, 0, .2, 1);
  --bar: 36px;             /* the top bar */
}
html { scroll-padding-top: calc(var(--bar) + 16px); }  /* a jump to #part, or Tab, never lands under the bar */

body.sys { background: #000; color: var(--ink); font: 600 12px/16px "Geist", system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
.sys ::selection { background: var(--orange); color: #000; }
/* The resets weigh nothing (:where), so every class below wins over them. */
:where(.sys) :where(h1, h2, h3, h4, p, ul, ol, dl, dd, figure, blockquote, pre) { margin: 0; }
:where(.sys) :where(h1, h2, h3, h4) { font: inherit; }
:where(.sys) :where(ul, ol) { padding: 0; list-style: none; }
:where(.sys) :where(a) { color: inherit; text-decoration: none; }
:where(.sys) :where(img, video) { display: block; max-width: 100%; height: auto; }
:where(.sys) :where(button) { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
:where(.sys) :where(input, textarea, select) { font: inherit; color: inherit; }   /* so a type class on a field wins */
:where(.sys) :where(p) { text-wrap: pretty; }
.sys :focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }

/* ---------------------------------------------------------------- the grid: 4 columns on phones, 12 from 1024 px, 12
   apart, 16 from the edge, at most 1920 wide. A cell says where it sits with --m (phones) and --d (wide). Rows are 12
   apart; a cell's .mt-* adds to that. */
.grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: 12px; row-gap: var(--rows, 12px);
  width: 100%; max-width: 1920px; margin: 0 auto; padding: 0 16px; }
.grid .grid { padding: 0; }
.grid.subgrid { grid-template-columns: subgrid; }      /* a cell's own list on the page's column lines (an app's screens on /projects) */
.grid > :not(.hatch) { grid-column: var(--m, 1 / -1); min-width: 0; }
.page { padding-top: 16px; }                           /* the first row, just under the bar */
.section { margin-top: 72px; }                         /* between a page's sections */
.edge { padding-top: 12px; border-top: 1px solid var(--line-2); }
.mt-1 { margin-top: 8px; } .mt-2 { margin-top: 12px; } .mt-3 { margin-top: 24px; } .mt-4 { margin-top: 48px; }
.mt-5 { margin-top: 64px; }                          /* 96 on wide screens: a post's title above its text, the blog's categories */
.mt-m1 { margin-top: 8px; } .mt-m2 { margin-top: 12px; } .mt-m3 { margin-top: 24px; }   /* phones only */
.sticky { position: sticky; top: calc(var(--bar) + 16px); align-self: start; }

/* ---------------------------------------------------------------- split: a side column (sticky on wide screens) and a
   main column on the page's own grid lines (subgrid), for case studies and articles. Inside .main: .w-read (6 columns,
   about 65 characters of reading text), .w-wide (7), .w-full (all 9). Phones: one column, the side first. */
.split > .main, .sub { display: grid; grid-template-columns: subgrid; row-gap: var(--rows, 12px); align-content: start; }
.sub { grid-column: 1 / -1; }                          /* a chapter or a part inside .main, on the same lines */
:is(.main, .sub) > * { grid-column: 1 / -1; min-width: 0; }
.chapter { margin-top: 72px; } .part { margin-top: 48px; } .chapter > .ruled + .part { margin-top: 0; }   /* a chapter's first part follows its label */
.toc a { color: var(--ink-2); }
.toc .n { display: inline-block; flex: none; width: var(--n, 1.75em); font-variant-numeric: tabular-nums; }
.toc .go { align-items: baseline; }                     /* a long name wraps under itself, not under its number */
.toc.end-n .n { text-align: right; padding-right: .3em; }   /* numbers of different widths (1. to 19A.) end on one line, each title right after its own */   /* the number in a box of its own, so every name starts on the same line */
.wide-only { display: none; }                          /* e.g. the chapter list: on phones the page itself is the list */
.toc a[aria-current] { color: var(--orange); }
.side .toc { max-height: calc(100vh - var(--bar) - 120px); overflow-y: auto; padding: 4px; margin-inline: -4px; }   /* a long list (a privacy policy's 25 parts) scrolls in itself; 4 px for the focus ring */

/* ---------------------------------------------------------------- type: one weight (600), the size and the grey make the
   order. Phones first; the wide sizes from 1024 px (the two biggest grow with the window up to 1440 so a word never
   outgrows its columns). Long reading is the one exception (500, 16/24). */
.t-0 { font-size: 46px; line-height: 39px; letter-spacing: -.04em; font-weight: 600; text-wrap: balance; }
.t-1 { font-size: 35px; line-height: 36px; letter-spacing: -.03em; font-weight: 600; text-wrap: balance; }
.t-2 { font-size: 24px; line-height: 24px; letter-spacing: -.03em; font-weight: 600; text-wrap: balance; }
.t-3 { font-size: 20px; line-height: 20px; letter-spacing: -.03em; font-weight: 600; }
.t-4 { font-size: 15px; line-height: 16px; letter-spacing: -.02em; font-weight: 600; }
.t-p { font-size: 12px; line-height: 16px; letter-spacing: 0; font-weight: 600; }
.t-read { font-size: 16px; line-height: 24px; font-weight: 500; letter-spacing: 0; }
.t-read strong, .t-read b { font-weight: 700; }
.t-mono { font-family: "Geist Mono", ui-monospace, monospace; font-size: 12px; line-height: 16px; font-weight: 600; }
.count { margin-left: 2px; font-size: 9px; line-height: 0; color: var(--ink-3); vertical-align: 6px; font-variant-numeric: tabular-nums; }
.ink { color: var(--ink); } .ink-2 { color: var(--ink-2); } .ink-3 { color: var(--ink-3); } .orange { color: var(--orange); }
.label { color: var(--ink-3); }
.ruled { padding-bottom: 8px; border-bottom: 1px solid var(--line); }
.nums { font-variant-numeric: tabular-nums; }
.wrap-any { overflow-wrap: anywhere; }
.whole { display: inline-block; max-width: 100%; }       /* a sentence kept on one line while it fits; it breaks inside only when it can't */                 /* an email or address that must break on a phone */

/* ---------------------------------------------------------------- links. Every link answers the pointer and the keyboard
   the same way: it dims, then blinks back three times (60 ms in, then a change every 60 ms, steady at 330 ms). .link
   adds a hairline underline and a soft glow; .go (calls to action, back, next, the chapter list) turns orange and has
   neither; .bare has the glow alone. Single-line parts only; a link inside reading text is underlined (.t-read a). */
.link, .go, .bare { position: relative; display: inline-flex; align-items: center; gap: 4px; }
.link::after { content: ""; position: absolute; left: 0; right: 0; bottom: calc(-1 * max(.06em, 1px)); height: max(.06em, 1px);
  background: currentColor; opacity: 0; transition: opacity 60ms linear; pointer-events: none; }
.t-read a { color: var(--ink); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
@keyframes blink {
  0% { opacity: 1; animation-timing-function: var(--ease); }
  16.667% { opacity: .5; animation-timing-function: step-end; }       /* 60 ms */
  25% { opacity: 1; animation-timing-function: step-end; }            /* 90 */
  41.667% { opacity: .5; animation-timing-function: step-end; }       /* 150 */
  58.333% { opacity: 1; animation-timing-function: step-end; }        /* 210 */
  75% { opacity: .5; animation-timing-function: step-end; }           /* 270 */
  91.667%, 100% { opacity: 1; }                                       /* 330: steady */
}
@media (hover: hover) {
  .link:hover, .go:hover, .bare:hover, .t-read a:hover { animation: blink 360ms forwards; }
  .link:hover::after { opacity: 1; }
  .link:hover, .bare:hover { text-shadow: 0 0 30px color-mix(in srgb, currentColor 50%, transparent); }
  .go:hover { color: var(--orange); }
}
.link:focus-visible, .go:focus-visible, .bare:focus-visible, .t-read a:focus-visible { animation: blink 360ms forwards; }
.link:focus-visible::after { opacity: 1; }
.link:focus-visible, .bare:focus-visible { text-shadow: 0 0 30px color-mix(in srgb, currentColor 50%, transparent); }
.go:focus-visible, .go[aria-current] { color: var(--orange); }   /* the page you are on, the chosen category */
.link:active, .go:active, .bare:active { animation: none; }

/* ---------------------------------------------------------------- small parts */
.tag { display: inline-block; padding: 0 4px; background: var(--fill); color: var(--ink-2); }
.tags { display: flex; flex-wrap: wrap; gap: 4px; }
.dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--orange); animation: pulse 2s linear infinite; }
@keyframes pulse { 50% { opacity: .5; } }
.live { display: inline-flex; align-items: center; gap: 6px; }
.frame { position: relative; }                         /* a picture's four corners marked by a single dot */
.frame::before, .frame::after { content: ""; position: absolute; left: 0; right: 0; height: 1px; z-index: 2; pointer-events: none;
  background: linear-gradient(to right, var(--ink) 0 1px, transparent 1px calc(100% - 1px), var(--ink) calc(100% - 1px)); }
.frame::before { top: 0; } .frame::after { bottom: 0; }
.media { position: relative; overflow: hidden; background: var(--fill); }
.media > img, .media > video { width: 100%; height: 100%; object-fit: cover; }
.cap { margin-top: 8px; color: var(--ink-3); }
.icon-app { border-radius: 22.5%; box-shadow: 0 0 0 1px rgba(255, 255, 255, .12); }   /* an app's icon keeps its own shape, edged on black */
.icon-app.own { border-radius: 0; box-shadow: none; }     /* an icon file with its own shape and rim (transparent corners) */
.pause { position: absolute; z-index: 3; left: 8px; bottom: 8px; padding: 0 4px; background: #000; color: var(--ink); }   /* system.js adds it to every looping video */

/* ---------------------------------------------------------------- the hatch: fine diagonal lines (white at 10%, one every
   8 px) drifting right at 64 px every 6 s, shown on what the pointer or the keyboard is on. Our own tile. It drifts only
   while shown; on the contact band and in placeholders it stands still. */
.hatch { position: absolute; inset: 0; overflow: hidden; pointer-events: none; opacity: 0; transition: opacity 300ms var(--ease); z-index: 1; }
.hatch::after { content: ""; position: absolute; inset: 0 0 0 -64px; animation: drift 6s linear infinite paused;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M-1 1 1-1M0 8 8 0M7 9 9 7' stroke='%23fff' stroke-opacity='.1'/%3E%3C/svg%3E") 0 0 / 8px 8px; }
@keyframes drift { to { transform: translateX(64px); } }
@media (hover: hover) {
  :hover > .hatch, .tile:hover .hatch { opacity: 1; }
  :hover > .hatch::after, .tile:hover .hatch::after { animation-play-state: running; }
}
:focus-visible > .hatch, .tile:focus-visible .hatch, .hatch.on { opacity: 1; }
:focus-visible > .hatch::after, .tile:focus-visible .hatch::after, .hatch.on::after { animation-play-state: running; }
.spot .hatch { transition-duration: 200ms; }

/* ---------------------------------------------------------------- rows: a list read across, a hairline above each row.
   A row that opens something brightens (300 ms) and shows the hatch. */
.rows > * { position: relative; border-top: 1px solid var(--line); }
.rows > :last-child { border-bottom: 1px solid var(--line); }
.row { position: relative; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: 12px; padding: 8px 0; align-items: baseline; }
.row > .mid { align-self: center; }                   /* a picture in a row (an icon) sits in the middle; the words share one baseline */
/* A thumbnail in a row (ref blog): 16:9, at the top of the row, and first on phones, where the row stacks; the
   words keep their order for a screen reader. A cell marked .tall spans two lines of the row on wide screens (the
   blog's latest post: its picture and title beside the excerpt, then the tag and the date). */
.row > .media { align-self: start; aspect-ratio: 16 / 9; order: -1; margin-bottom: 8px; }   /* 8 above the words under it on phones */
.m-end { text-align: right; }                         /* at the end on phones only (a date under a picture) */
.rows.loose > * { padding: 24px 0; }                 /* big items between the lines (the projects: an app and its screens); 32 wide */
.row > :not(.hatch) { grid-column: var(--m, 1 / -1); min-width: 0; }
a.row { color: var(--ink-2); transition: color 300ms var(--ease); }
@media (hover: hover) { a.row:hover { color: var(--ink); } }
a.row:focus-visible { color: var(--ink); outline-offset: 0; }
.end { text-align: right; }
.off, .off * { color: var(--ink-3); }                  /* closed or not available, still readable, and it says so in words */
.off .ph, .off .ph * { color: var(--ink-2); }          /* a placeholder's lifted ground needs the lighter grey to stay readable */

/* ---------------------------------------------------------------- a value as a thin bar (research counts): its share of
   the row in --v, --ink-2, square; the number beside it says it in words too */
.bar { display: block; height: 6px; margin-top: 5px; width: var(--v, 0%); background: var(--ink-2); }

/* ---------------------------------------------------------------- label and value: facts, credits, a case's numbers */
.facts > div { display: grid; grid-template-columns: minmax(80px, 1fr) minmax(0, 3fr); column-gap: 8px; padding: 4px 0; border-top: 1px solid var(--line); }
.facts > div:last-child { border-bottom: 1px solid var(--line); }
.facts dt { color: var(--ink-3); }
.facts dd { grid-column: 2 / -1; color: var(--ink); }

/* ---------------------------------------------------------------- questions: each open, a hairline above */
.qa > li { padding: 24px 0; border-top: 1px solid var(--line); }
.qa > li:last-child { border-bottom: 1px solid var(--line); }

/* ---------------------------------------------------------------- spotlight: in a set, the one pointed at stays, the rest
   step back to half (200 ms). Tiles breathe while pointed at (1 s), and their "View" shows (200 ms). */
@media (hover: hover) and (pointer: fine) {
  .spot > * { transition: opacity 200ms ease-in-out; }
  .spot:has(> :hover) > :not(:hover) { opacity: .5; }
  .tile:hover .media > img, .tile:hover .media > video { animation: breathe 1s ease-in-out infinite; }
}
@keyframes breathe { 50% { opacity: .75; } }
.tile { position: relative; display: block; }
.tile .view { position: absolute; z-index: 3; right: 8px; bottom: 8px; padding: 0 4px; background: #000; color: var(--ink); opacity: 0; transition: opacity 200ms ease-out; }
@media (hover: hover) { .tile:hover .view { opacity: 1; } }
.tile:focus-visible .view { opacity: 1; }

/* ---------------------------------------------------------------- forms: a small field, and the big borderless one; both
   keep a visible label and an edge of --ink-3 (3:1 and up for the field's outline) */
.input { height: 36px; padding: 0 8px; border: 0; border-radius: 0; background: var(--field); color: var(--ink); box-shadow: inset 0 -1px 0 var(--ink-3); }
.input::placeholder { color: var(--hint); } .field::placeholder { color: var(--ink-3); }
@media (hover: hover) { .input:hover { background: var(--field-2); } }
.input:focus::placeholder, .field:focus::placeholder { color: var(--ink-2); }
.field { display: block; width: 100%; padding: 8px 0 12px; border: 0; border-bottom: 1px solid var(--ink-3); border-radius: 0; background: none; color: var(--ink); resize: vertical; }
.field:focus { border-bottom-color: var(--ink); }

/* ---------------------------------------------------------------- reading: an article's or a case study's text. Inside
   it the spacing is its own (the .mt-* of its children don't apply); captions sit in their figure. */
.prose > * + * { margin-top: 16px; }
.prose > :first-child { margin-top: 0; }               /* a text that opens with a heading starts at its top */
.prose > h2 { margin-top: 64px; } .prose > h2 + * { margin-top: 24px; }
.prose > h3 { margin-top: 40px; } .prose > h3 + * { margin-top: 16px; }
.prose > figure { margin-top: 32px; } .prose > figure + * { margin-top: 32px; }
.prose ul, .prose ol { padding-left: 1.2em; } .prose ul { list-style: disc; } .prose ol { list-style: decimal; }
.prose li + li { margin-top: 8px; } .prose li::marker { color: var(--ink-3); }
.prose > blockquote { margin-top: 32px; padding-left: 16px; border-left: 1px solid var(--line-2); } .prose > blockquote + * { margin-top: 32px; }
/* A table in reading text (a privacy policy's partners): small type, hairlines between rows, no fills. On phones each
   row stacks: its first cell as the title, every other value under its column's name. */
.table table { width: 100%; border-collapse: collapse; font: 600 13px/16px "Geist", system-ui, sans-serif; }
.table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.table tr { display: block; padding: 8px 0; border-top: 1px solid var(--line); }
.table tbody tr:last-child { border-bottom: 1px solid var(--line); }
.table td { display: block; padding: 2px 0; color: var(--ink-2); }
.table td:first-child { color: var(--ink); }
.table td:not(:first-child)::before { content: attr(data-label); display: block; margin-top: 6px; color: var(--ink-3); }

/* ---------------------------------------------------------------- code: <pre class="code t-mono"><code><span>…</span>
   one span per line, numbered by CSS */
.code { border: 1px solid var(--fill); padding: 12px 16px; overflow-x: auto; color: var(--ink-2); }
.code code { counter-reset: n; } .code code > span { display: block; white-space: pre; }
.code code > span::before { counter-increment: n; content: counter(n); display: inline-block; width: 28px; color: var(--ink-3); }
.t-read code, .t-p code { font-family: "Geist Mono", ui-monospace, monospace; font-size: 13px; padding: 1px 4px; border-radius: 4px; background: var(--field); }

/* ---------------------------------------------------------------- blocks: the contact band (a still hatch behind); a
   placeholder for a picture still to come (what goes there, in words, over the still hatch) */
.band { position: relative; padding-top: 48px; padding-bottom: 64px; }
.band > .hatch { opacity: 1; }
.ph { position: relative; display: grid; place-items: center; aspect-ratio: var(--ar, 16 / 9); padding: 16px; background: #0b0b0b; color: var(--ink-2); text-align: center; }
.ph > .hatch { opacity: 1; } .ph > .hatch::after { animation: none; }
.ph > :not(.hatch) { position: relative; z-index: 2; max-width: 48ch; }

/* ---------------------------------------------------------------- the foot on a system page: on the grid's edge, in the
   system's type */
body.sys > footer .wrap { max-width: 1920px; padding: 0 16px; }
body.sys > footer .copy { font-size: 13px; line-height: 16px; color: var(--ink); }
body.sys > footer .marks { margin-top: 8px; font-size: 13px; line-height: 16px; color: var(--ink-3); max-width: 80ch; }

/* ---------------------------------------------------------------- wide screens */
@media (min-width: 1024px) {
  body.sys { font-size: 13px; }
  .grid { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .grid > :not(.hatch) { grid-column: var(--d, var(--m, 1 / -1)); }
  .row { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .row > :not(.hatch) { grid-column: var(--d, var(--m, 1 / -1)); }
  .split > .side { grid-column: 1 / 4; position: sticky; top: calc(var(--bar) + 16px); align-self: start; }
  .split > .main { grid-column: 4 / 13; }
  .wide-only { display: block; }
  .mt-5 { margin-top: 96px; }
  .m-end { text-align: left; }
  .row > .tall { grid-row: span 2; }
  .row > .media { margin-bottom: 0; }
  .rows.loose > * { padding: 32px 0; }
  .table thead { position: static; width: auto; height: auto; overflow: visible; clip-path: none; }
  .table tr { display: table-row; padding: 0; border: 0; }
  .table th, .table td { display: table-cell; padding: 8px 12px 8px 0; text-align: left; vertical-align: top; border-bottom: 1px solid var(--line); }
  .table th { color: var(--ink-3); border-bottom-color: var(--line-2); }
  .table td:not(:first-child)::before { content: none; }
  :is(.main, .sub) > .w-left { grid-column: 1 / 5; } :is(.main, .sub) > .w-right { grid-column: 5 / 9; }
  :is(.main, .sub) > .w-read { grid-column: 1 / 7; } :is(.main, .sub) > .w-wide { grid-column: 1 / 8; } :is(.main, .sub) > .w-full { grid-column: 1 / -1; }
  .chapter { margin-top: 128px; } .part { margin-top: 64px; }
  .section { margin-top: 192px; }
  .mt-m1, .mt-m2, .mt-m3 { margin-top: 0; }
  .t-0 { font-size: clamp(46px, 6.8vw, 98px); line-height: .9; }
  .t-1 { font-size: clamp(35px, 5.28vw, 76px); line-height: .895; letter-spacing: -.04em; }
  .t-2 { font-size: 38px; line-height: 36px; letter-spacing: -.04em; }
  .t-3 { font-size: 24px; line-height: 24px; }
  .t-4 { font-size: 20px; line-height: 20px; }
  .t-p { font-size: 13px; }
  .t-mono { font-size: 13px; }
  .band { padding-top: 64px; padding-bottom: 128px; }
}
/* touch: every standalone link and field is at least 44 px tall; rows get the same */
@media (pointer: coarse) {
  .input { height: 44px; }
  .row { padding: 14px 0; }
  .go, .link, .bare { min-height: 44px; }
}
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ---------------------------------------------------------------- still: no blink, no drift, no breathing, no pulse */
@media (prefers-reduced-motion: reduce) {
  .link, .go, .bare, .t-read a, .link:hover, .go:hover, .bare:hover, .t-read a:hover,
  .link:focus-visible, .go:focus-visible, .bare:focus-visible, .t-read a:focus-visible { animation: none !important; }
  .hatch::after, .dot, .tile .media > img, .tile .media > video { animation: none !important; }
  .spot > *, .hatch, .link::after, a.row, .tile .view { transition: none !important; }
}

/* ---------------------------------------------------------------- chat: Pixi's page (/ask; components.md 23), rebuilt
   for phones first (owner, 2 Nov 2026: "the composer ... like ChatGPT style, but adapted to our website ... open the
   keyboard on the phone, type, click send, then scroll, then open keyboard ... all this should be perfect").
   The page is one shell the size of what is visible (.chat-app; ask.html sets --vvh and --vvt from visualViewport), so on
   every phone the bar stays on top, the composer sits right on the keyboard and only the thread scrolls. The bar's
   position: fixed is measured from the shell (its transform makes it the bar's containing block).
   Thread: the visitor's words in a filled rectangle on the right, Pixi's as reading text under a small label; the newest
   question rises to the top and its answer is written below it in the space kept for it. New words fade in; nothing
   already shown moves. Composer: a rectangle on --field that grows to seven lines, the round send button inside it. */
html.chat-page, html.chat-page body { height: 100%; overflow: hidden; overscroll-behavior: none; }
.chat-app { position: fixed; left: 0; top: 0; width: 100%; height: var(--vvh, 100dvh); transform: translateY(var(--vvt, 0px));
  display: flex; flex-direction: column; padding-top: var(--bar); background: #000; }
.chat { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.chat [hidden] { display: none !important; }            /* over .go: Start over only after a question */
.chat-scroll { position: relative; flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: var(--fill) transparent; }
.chat-scroll { -webkit-mask-image: linear-gradient(transparent, #000 16px, #000 calc(100% - 16px), transparent); mask-image: linear-gradient(transparent, #000 16px, #000 calc(100% - 16px), transparent); }   /* words leave softly under the bar and above the composer */
.chat-thread { max-width: 720px; margin: 0 auto; padding: 24px 16px 16px; }
.chat-thread.empty { min-height: 100%; display: flex; flex-direction: column; justify-content: center; padding-bottom: 40px; }
.chat-pair { padding-top: 32px; }
.chat-pair .chat-pixi { margin-top: 24px; }
.chat-hello { color: var(--ink); }
.chat-hi .chat-say { margin-top: 12px; color: var(--ink-2); }
.chat-starts { display: grid; gap: 8px; margin-top: 24px; }   /* three ways to start, under the greeting (ask.html STARTS) */
.chat-start { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; min-height: 44px; padding: 10px 16px;
  border: 1px solid var(--line); border-radius: 12px; color: var(--ink-2); text-align: left; font: 500 15px/20px "Geist", system-ui, sans-serif;
  transition: color 200ms var(--ease), border-color 200ms var(--ease), background-color 200ms var(--ease); }
.chat-start span { flex: none; color: var(--ink-3); }
@media (hover: hover) { .chat-start:hover { color: var(--ink); border-color: var(--line-2); background: var(--field); } }
.chat-start:focus-visible { color: var(--ink); border-color: var(--ink-3); outline: 0; }
.chat-start:active { background: var(--field); }
.chat-you { display: flex; justify-content: flex-end; }
.chat-bubble { max-width: min(85%, 560px); padding: 10px 16px; background: var(--field-2); border-radius: 16px; color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
.chat-who { margin-bottom: 8px; color: var(--ink-3); }
.chat-who b { font-weight: 600; color: var(--ink); }
.chat-say { color: var(--ink); overflow-wrap: anywhere; }
.chat-say > * + * { margin-top: 12px; }               /* paragraphs and lists, as reading text breathes */
.chat-say ul, .chat-say ol { padding-left: 1.2em; } .chat-say ul { list-style: disc; } .chat-say ol { list-style: decimal; }
.chat-say li + li { margin-top: 8px; } .chat-say li::marker { color: var(--ink-3); }
.chat-say li > ul, .chat-say li > ol { margin-top: 8px; }
.chat-say pre { padding: 12px 16px; border: 1px solid var(--fill); overflow-x: auto; color: var(--ink-2); font: 600 13px/16px "Geist Mono", ui-monospace, monospace; }
.chat-say .dot { margin-left: 6px; vertical-align: 1px; }
.chat-say .w { animation: chat-word 400ms var(--ease) both; }   /* a new word fades in (ask.html FADE) */
@keyframes chat-word { from { opacity: 0; } }
.chat-think { display: flex; align-items: center; gap: 10px; color: var(--ink-3); }
.chat-think .dot { margin: 0; }
.chat-think span:last-child { background: linear-gradient(90deg, var(--ink-3) 0 38%, var(--ink) 50%, var(--ink-3) 62% 100%) 0 0 / 300% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent; animation: chat-shine 1.8s linear infinite; }
@keyframes chat-shine { from { background-position: 100% 0; } to { background-position: 0 0; } }
.chat-pixi.notice .chat-say { color: var(--ink-2); }
.chat-note { margin-top: 12px; color: var(--ink-3); }
.chat-note .go { color: var(--ink-2); }
.chat-dock { position: relative; flex: none; padding: 8px 16px calc(12px + env(safe-area-inset-bottom)); background: #000; }
.chat-app.kb .chat-dock { padding-bottom: 8px; }      /* the keyboard covers the home bar's safe area */
.chat-form { max-width: 720px; margin: 0 auto; }
.chat-box { display: flex; align-items: flex-end; gap: 8px; padding: 4px 4px 4px 16px; background: var(--field); border: 1px solid var(--line); border-radius: 16px; transition: border-color 200ms var(--ease); }
.chat-box:focus-within { border-color: var(--line-2); }
.chat-field { flex: 1; min-width: 0; max-height: 172px; margin: 0; padding: 10px 0; border: 0; border-radius: 0; background: none; resize: none;
  color: var(--ink); font: 500 16px/24px "Geist", system-ui, sans-serif; overflow-y: auto; }   /* 16 px: iPhone never zooms the page */
.chat-field::placeholder { color: var(--hint); }
.chat-field:focus-visible { outline: 0; }               /* the box's edge brightens instead */
.chat-send { flex: none; display: grid; place-items: center; width: 36px; height: 36px; margin: 4px 0; border-radius: 50%; background: var(--ink); color: #000; transition: background-color 150ms var(--ease), color 150ms var(--ease); }
.chat-send svg { width: 18px; height: 18px; }
.chat-send[aria-disabled="true"] { background: var(--fill); color: var(--ink-3); cursor: default; }
.chat-send:focus-visible { outline: 1px solid var(--ink); outline-offset: 2px; }
@media (hover: hover) { .chat-send:not([aria-disabled="true"]):hover { background: var(--orange); } }
.chat-foot { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; margin-top: 8px; padding: 0 4px; color: var(--ink-3); }
.chat-foot .go { color: var(--ink-2); }
.chat-app.kb .chat-foot { display: none; }            /* typing on a phone: every line goes to the thread */
.chat-down { position: absolute; left: 50%; top: -52px; z-index: 2; display: grid; place-items: center; width: 40px; height: 40px; margin-left: -20px;
  border-radius: 50%; background: var(--field-2); border: 1px solid var(--line-2); color: var(--ink); opacity: 0; pointer-events: none; transition: opacity 200ms var(--ease); }
.chat-down.on { opacity: 1; pointer-events: auto; }
.chat-down svg { width: 18px; height: 18px; }
@media (pointer: coarse) { .chat-send { width: 40px; height: 40px; margin: 2px 0; } .chat-foot .go { padding: 12px 0 12px 12px; margin: -12px 0 -12px; } }
@media (min-width: 1024px) { .chat-thread { padding-top: 48px; } .chat-dock { padding-bottom: 24px; } }
@media (prefers-reduced-motion: reduce) { .chat-say .w, .chat-think span:last-child { animation: none !important; } .chat-think span:last-child { background: none; color: var(--ink-3); }
  .chat-box, .chat-send, .chat-down { transition: none !important; } }
