:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.145 0 0);
  --primary: oklch(0 0 0);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.45 0.045 250);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --accent: oklch(0.965 0.008 250);
  --accent-foreground: var(--primary);
  --destructive: oklch(0.577 0.245 27.325);
  --border: oklch(0.922 0 0);
  --input: oklch(0.922 0 0);
  --ring: oklch(0.5 0.11 261);
  --sidebar: oklch(0.97 0 0);
  --sidebar-foreground: oklch(0.145 0 0);
  --sidebar-accent: oklch(0.965 0.008 250);
  --sidebar-accent-foreground: var(--primary);
  --shadow-pop: 0 8px 24px oklch(0 0 0 / 8%);
  --radius: 2px;
  --radius-md: var(--radius);
  --spacing: 0.25rem;
  --gutter: clamp(24px, 14vw, 220px);
  --container-sm: 28rem;
  --container-md: 48rem;
  --container-lg: 72rem;
  --container-xl: 90rem;
  --font-sans: 'FK Grotesk', ui-sans-serif, system-ui, sans-serif;
  --font-mono: 'FK Grotesk Mono', ui-monospace, SFMono-Regular, monospace;
  --text-2xs: 0.6875rem;
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5rem;
  --text-hero: clamp(1.25rem, 4.5vw, 3rem);
  --text-xs--line-height: calc(1 / 0.75);
  --text-sm--line-height: calc(1.25 / 0.875);
  --leading-tight: 1.05;
  --leading-snug: 1.3;
  --leading-normal: 1.5;
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --tracking-tight: -0.03em;
  --tracking-snug: -0.01em;
  --default-transition-duration: 150ms;
  --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
html.dark {
  color-scheme: dark;
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.205 0 0);
  --card-foreground: oklch(0.985 0 0);
  --primary: oklch(0.985 0 0);
  --primary-foreground: oklch(0.145 0 0);
  --secondary: oklch(0.42 0.04 250);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.78 0 0);
  --accent: oklch(0.3 0.015 250);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --border: oklch(1 0 0 / 12%);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.63 0.1 261);
  --sidebar: oklch(0.185 0 0);
  --sidebar-foreground: oklch(0.985 0 0);
  --sidebar-accent: oklch(0.3 0.015 250);
  --sidebar-accent-foreground: oklch(0.985 0 0);
  --shadow-pop: 0 8px 24px oklch(0 0 0 / 45%);
}

:root{
    color-scheme: light;
}

html{
    line-height: 1.5;
    -webkit-text-size-adjust: 100%;
    -moz-tab-size: 4;
    tab-size: 4;
    -webkit-tap-highlight-color: transparent;
}
[data-slot="button"]{
    box-sizing: border-box;
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    margin: 0;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--radius);
    background-color: transparent;
    background-clip: padding-box;
    -moz-appearance: button;
    -o-appearance: button;
    -webkit-appearance: button;
    -ms-appearance: button;
    appearance: button;
    color: inherit;
    font: inherit;
    font-size: var(--text-sm);
    line-height: var(--text-sm--line-height);
    font-weight: var(--font-weight-medium);
    letter-spacing: inherit;
    white-space: nowrap;
    text-decoration: none;
    -moz-transition: all var(--default-transition-duration) var(--default-transition-timing-function);
    -o-transition: all var(--default-transition-duration) var(--default-transition-timing-function);
    -webkit-transition: all var(--default-transition-duration) var(--default-transition-timing-function);
    -ms-transition: all var(--default-transition-duration) var(--default-transition-timing-function);
    transition: all var(--default-transition-duration) var(--default-transition-timing-function);
    outline-style: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -o-user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

[data-slot="button"]:focus-visible{
    border-color: var(--ring);
    -moz-box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 50%, transparent);
    -o-box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 50%, transparent);
    -webkit-box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 50%, transparent);
    -ms-box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 50%, transparent);
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 50%, transparent);
}

[data-slot="button"]:active:not([aria-haspopup]){
    translate: 0 1px;
}

[data-slot="button"]:disabled{
    pointer-events: none;
    opacity: 50%;
}

[data-slot="button"][aria-invalid="true"]{
    border-color: var(--destructive);
    -moz-box-shadow: 0 0 0 3px color-mix(in oklab, var(--destructive) 20%, transparent);
    -o-box-shadow: 0 0 0 3px color-mix(in oklab, var(--destructive) 20%, transparent);
    -webkit-box-shadow: 0 0 0 3px color-mix(in oklab, var(--destructive) 20%, transparent);
    -ms-box-shadow: 0 0 0 3px color-mix(in oklab, var(--destructive) 20%, transparent);
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--destructive) 20%, transparent);
}

[data-slot="button"] svg{
    pointer-events: none;
    flex-shrink: 0;
    width: calc(var(--spacing) * 4);
    height: calc(var(--spacing) * 4);
}

[data-slot="button"][data-variant="default"]{
    background-color: var(--primary);
    color: var(--primary-foreground);
}

[data-slot="button"][data-variant="outline"]{
    border-color: var(--border);
    background-color: var(--background);
}

[data-slot="button"][data-variant="outline"][aria-expanded="true"]{
    background-color: var(--muted);
    color: var(--foreground);
}

[data-slot="button"][data-variant="secondary"]{
    background-color: var(--secondary);
    color: var(--secondary-foreground);
}

[data-slot="button"][data-variant="ghost"][aria-expanded="true"]{
    background-color: var(--muted);
    color: var(--foreground);
}

[data-slot="button"][data-variant="destructive"]{
    background-color: color-mix(in oklab, var(--destructive) 10%, transparent);
    color: var(--destructive);
}

[data-slot="button"][data-variant="destructive"]:focus-visible{
    border-color: color-mix(in oklab, var(--destructive) 40%, transparent);
    -moz-box-shadow: 0 0 0 3px color-mix(in oklab, var(--destructive) 20%, transparent);
    -o-box-shadow: 0 0 0 3px color-mix(in oklab, var(--destructive) 20%, transparent);
    -webkit-box-shadow: 0 0 0 3px color-mix(in oklab, var(--destructive) 20%, transparent);
    -ms-box-shadow: 0 0 0 3px color-mix(in oklab, var(--destructive) 20%, transparent);
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--destructive) 20%, transparent);
}

[data-slot="button"][data-variant="link"]{
    color: var(--primary);
    text-underline-offset: 4px;
}

@media (hover: hover){
    [data-slot="button"][data-variant="default"]:hover{
        background-color: color-mix(in oklab, var(--primary) 80%, transparent);
    }
    [data-slot="button"][data-variant="outline"]:hover{
        background-color: var(--muted);
        color: var(--foreground);
    }
    [data-slot="button"][data-variant="secondary"]:hover{
        background-color: color-mix(in oklch, var(--secondary), var(--foreground) 5%);
    }
    [data-slot="button"][data-variant="ghost"]:hover{
        background-color: var(--muted);
        color: var(--foreground);
    }
    [data-slot="button"][data-variant="destructive"]:hover{
        background-color: color-mix(in oklab, var(--destructive) 20%, transparent);
    }
    [data-slot="button"][data-variant="link"]:hover{
        text-decoration-line: underline;
    }
}

[data-slot="button"][data-size="default"]{
    height: calc(var(--spacing) * 8);
    gap: calc(var(--spacing) * 1.5);
    padding-inline: calc(var(--spacing) * 2.5);
}

[data-slot="button"][data-size="xs"]{
    height: calc(var(--spacing) * 6);
    gap: calc(var(--spacing) * 1);
    padding-inline: calc(var(--spacing) * 2);
    font-size: var(--text-xs);
    line-height: var(--text-xs--line-height);
}

[data-slot="button"][data-size="sm"]{
    height: calc(var(--spacing) * 7);
    gap: calc(var(--spacing) * 1);
    padding-inline: calc(var(--spacing) * 2.5);
    font-size: var(--text-sm);
    line-height: inherit;
}

[data-slot="button"][data-size="lg"]{
    height: calc(var(--spacing) * 9);
    gap: calc(var(--spacing) * 1.5);
    padding-inline: calc(var(--spacing) * 2.5);
}

[data-slot="button"][data-size="icon"]{
    width: calc(var(--spacing) * 8);
    height: calc(var(--spacing) * 8);
}

[data-slot="button"][data-size="icon-xs"]{
    width: calc(var(--spacing) * 6);
    height: calc(var(--spacing) * 6);
}

[data-slot="button"][data-size="icon-sm"]{
    width: calc(var(--spacing) * 7);
    height: calc(var(--spacing) * 7);
}

[data-slot="button"][data-size="icon-lg"]{
    width: calc(var(--spacing) * 9);
    height: calc(var(--spacing) * 9);
}

[data-slot="button"][data-size="xs"] svg{
    width: calc(var(--spacing) * 3);
    height: calc(var(--spacing) * 3);
}

[data-slot="button"][data-size="icon-xs"] svg{
    width: calc(var(--spacing) * 3);
    height: calc(var(--spacing) * 3);
}

[data-slot="button"][data-size="sm"] svg{
    width: calc(var(--spacing) * 3.5);
    height: calc(var(--spacing) * 3.5);
}
[data-slot="container"]{
    box-sizing: border-box;
    width: 100%;
    min-width: 0;
    margin-inline: auto;
}

[data-slot="container"][data-size="sm"]{
    max-width: var(--container-sm);
}

[data-slot="container"][data-size="md"]{
    max-width: var(--container-md);
}

[data-slot="container"][data-size="lg"]{
    max-width: var(--container-lg);
}

[data-slot="container"][data-size="full"]{
    max-width: none;
}
[data-slot="stack"]{
    display: flex;
    flex-direction: column;
    min-width: 0;
}

[data-slot="stack"][data-gap="none"]{
    gap: 0;
}

[data-slot="stack"][data-gap="xs"]{
    gap: calc(var(--spacing) * 1);
}

[data-slot="stack"][data-gap="sm"]{
    gap: calc(var(--spacing) * 2);
}

[data-slot="stack"][data-gap="md"]{
    gap: calc(var(--spacing) * 4);
}

[data-slot="stack"][data-gap="lg"]{
    gap: calc(var(--spacing) * 8);
}

[data-slot="stack"][data-align="stretch"]{
    align-items: stretch;
}

[data-slot="stack"][data-align="start"]{
    align-items: flex-start;
}

[data-slot="stack"][data-align="center"]{
    align-items: center;
}

[data-slot="stack"][data-align="end"]{
    align-items: flex-end;
}
[data-slot="card"]{
    background: var(--card);
    color: var(--card-foreground);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
}

[data-slot="card"][data-pad="sm"]{
    padding: calc(var(--spacing) * 4);
}

[data-slot="card"][data-pad="md"]{
    padding: calc(var(--spacing) * 6);
}
[data-slot="table-container"]{
    position: relative;
    width: 100%;
    overflow-x: auto;
}

[data-slot="table"]{
    width: 100%;
    caption-side: bottom;
    font-size: var(--text-sm);
    line-height: var(--text-sm--line-height);
    border-collapse: collapse;
}

[data-slot="table-header"] tr{
    border-bottom: 1px solid var(--border);
}

[data-slot="table-body"] tr:last-child{
    border: 0;
}

[data-slot="table-footer"]{
    border-top: 1px solid var(--border);
    background-color: color-mix(in oklab, var(--muted) 50%, transparent);
    font-weight: var(--font-weight-medium);
}

[data-slot="table-footer"] > tr:last-child{
    border-bottom-width: 0;
}

[data-slot="table-row"]{
    border-bottom: 1px solid var(--border);
    -moz-transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke;
    -o-transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke;
    -webkit-transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke;
    -ms-transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke;
    transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke;
    -moz-transition-duration: var(--default-transition-duration);
    -o-transition-duration: var(--default-transition-duration);
    -webkit-transition-duration: var(--default-transition-duration);
    -ms-transition-duration: var(--default-transition-duration);
    transition-duration: var(--default-transition-duration);
    -moz-transition-timing-function: var(--default-transition-timing-function);
    -o-transition-timing-function: var(--default-transition-timing-function);
    -webkit-transition-timing-function: var(--default-transition-timing-function);
    -ms-transition-timing-function: var(--default-transition-timing-function);
    transition-timing-function: var(--default-transition-timing-function);
}

[data-slot="table-row"]:has([aria-expanded="true"]){
    background-color: color-mix(in oklab, var(--muted) 50%, transparent);
}

[data-slot="table-row"][data-state="selected"]{
    background-color: var(--muted);
}

@media (hover: hover){
    [data-slot="table-row"]:hover{
        background-color: color-mix(in oklab, var(--muted) 50%, transparent);
    }
}

[data-slot="table-head"]{
    height: calc(var(--spacing) * 10);
    padding-inline: calc(var(--spacing) * 2);
    text-align: left;
    vertical-align: middle;
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
    color: var(--foreground);
}

[data-slot="table-head"]:has([role="checkbox"]){
    padding-right: 0;
}

[data-slot="table-cell"]{
    padding: calc(var(--spacing) * 2);
    vertical-align: middle;
    white-space: nowrap;
}

[data-slot="table-cell"]:has([role="checkbox"]){
    padding-right: 0;
}

[data-slot="table-caption"]{
    margin-top: calc(var(--spacing) * 4);
    font-size: var(--text-sm);
    line-height: var(--text-sm--line-height);
    color: var(--muted-foreground);
}
[data-slot="text-input"]{
    -moz-appearance: none;
    -o-appearance: none;
    -webkit-appearance: none;
    -ms-appearance: none;
    appearance: none;
    -webkit-appearance: none;
    font: inherit;
    font-size: var(--text-sm);
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
    color: var(--foreground);
    background: var(--background);
    border: 1px solid var(--input);
    border-radius: var(--radius);
}

[data-slot="text-input"]::-webkit-search-decoration{
    -webkit-appearance: none;
}

[data-slot="text-input"]::-webkit-search-cancel-button{
    -webkit-appearance: none;
}

[data-slot="text-input"]::placeholder{
    color: var(--muted-foreground);
}

[data-slot="text-input"]:focus-visible{
    outline: none;
    border-color: var(--ring);
    -moz-box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
    -o-box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
    -webkit-box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
    -ms-box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
    box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

[data-slot="text-input"][data-size="sm"]{
    height: 1.75rem;
    padding: 0 .375rem;
}

[data-slot="text-input"][data-size="md"]{
    height: 2.25rem;
    padding: 0 .5rem;
}
@layer lims {
/* The application's own stylesheet.
 *
 * A .css file rather than a string inside a .lisp file: editing CSS in a
 * Lisp string literal means no highlighting, no brace matching, no
 * linter, and a stray double quote silently ends the string and breaks
 * the build. Read at compile time by src/html/style.lisp, exactly as the
 * component sheets under ui/ are.
 *
 * The design tokens are NOT here — they are Lisp data in
 * src/html/tokens.lisp, emitted once by the PAGE shell, because they
 * used to be declared in both this sheet and ui/tokens.css and the
 * duplicate silently won.
 */
/* ---- FK Grotesk, vendored (same faces the Next.js root layout declares) */
@font-face { font-family: 'FK Grotesk'; src: url('/static/fonts/FKGroteskTrial-Regular.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'FK Grotesk'; src: url('/static/fonts/FKGroteskTrial-Italic.woff2') format('woff2'); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: 'FK Grotesk'; src: url('/static/fonts/FKGroteskTrial-Medium.woff2') format('woff2'); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: 'FK Grotesk'; src: url('/static/fonts/FKGroteskTrial-MediumItalic.woff2') format('woff2'); font-weight: 500; font-style: italic; font-display: swap; }
@font-face { font-family: 'FK Grotesk'; src: url('/static/fonts/FKGroteskTrial-Bold.woff2') format('woff2'); font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: 'FK Grotesk Mono'; src: url('/static/fonts/FKGroteskMonoTrial-Regular.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'FK Grotesk Mono'; src: url('/static/fonts/FKGroteskMonoTrial-Medium.woff2') format('woff2'); font-weight: 500; font-style: normal; font-display: swap; }

/* The dark palette is not here. It used to be — a .dark block ported
   verbatim from the Next.js app and never applied — and it could not
   have worked where it sat: this sheet is served inside @layer lims and
   the tokens are emitted unlayered, so :root beat .dark whatever the
   markup said. It lives beside the light palette in tokens.lisp now,
   and the topbar's toggle puts .dark on <html>. */

/* ---- Base ---- */
* { box-sizing: border-box; border-color: var(--border); }
html { font-family: var(--font-sans); touch-action: manipulation; }
body {
  margin: 0; background: var(--background); color: var(--foreground);
  display: flex; flex-direction: column; min-height: 100svh;
  font-size: var(--text-base); line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none; }
code, .mono { font-family: var(--font-mono); }

/* Scrollbars are hidden everywhere — the page scrollbar and every inner
   pane. Only the bar is gone: wheel, trackpad, touch, keyboard, and
   scroll-into-view all behave exactly as before, and the scroll
   containers keep their overflow.

   The three declarations are three engines, none of which honours the
   others: scrollbar-width is the standard (Firefox), ::-webkit-scrollbar
   is Chrome and Safari, -ms-overflow-style is legacy Edge.

   The cost is that a long page no longer shows how long it is or where
   you are in it, so anything that scrolls needs some other cue that
   there is more below it. */
* { scrollbar-width: none; -ms-overflow-style: none; }
*::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* ---- The landing hero ----
   A component, not a page treatment: the photograph is the background of
   this section alone, so the sections below it sit on the ordinary white
   plane and inherit the ordinary tokens. The dark palette is scoped here
   too — every .btn, nav link, and rule inside reads its colour from a
   variable, so the hero turns over as a unit and nothing below it is
   touched.

   Full bleed, so the gutter padding lives inside rather than on a
   wrapper: an ancestor with side padding would letterbox the image. */
.landing-hero {
  color-scheme: dark;
  --background: transparent;        /* the photograph is the background */
  --foreground: oklch(0.985 0 0);
  --muted: oklch(1 0 0 / 12%);      /* ghost/outline button hover */
  --muted-foreground: oklch(0.82 0 0);
  --border: oklch(1 0 0 / 25%);
  /* Inverted, so a primary button reads white on the dark plate rather
     than black-on-black, which is unreadable. Written literally and not
     as var(--primary): --primary is redefined two lines up, so the
     reference would resolve to white and put white type on a white
     plate. */
  --primary: oklch(0.985 0 0);
  --primary-foreground: oklch(0 0 0);
  --ring: oklch(0.85 0 0);
  display: flex; flex-direction: column;
  min-height: 100svh;
  padding: 0 var(--gutter);
  color: var(--foreground);
  background-color: #000;           /* letterboxing, and the colour before the image loads */
  /* The scrim darkens the top and bottom bands, where the small nav text
     sits, and stays out of the middle so the earth keeps its contrast. */
  background-image:
    linear-gradient(to bottom,
                    oklch(0 0 0 / 55%) 0%,
                    oklch(0 0 0 / 10%) 40%,
                    oklch(0 0 0 / 15%) 60%,
                    oklch(0 0 0 / 70%) 100%),
    url('/static/landing.png');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
/* The hero in flow order: the hook high on the left, then the buttons
   and the wordmark stacked in the bottom-right corner. HERO-COPY takes
   the leftover height, which is what pushes the other two to the
   bottom without absolute positioning — and holding the hook at the
   top of that space is what lifts it off centre. */
.hero-copy { flex: 1; display: flex; flex-direction: column;
  justify-content: flex-start; padding-top: clamp(3rem, 10vh, 7rem); }
/* Normal weight on purpose: bold at this size would shout over a
   photograph already doing the work.

   Two lines, broken where the markup says. NOWRAP forbids any *other*
   break — a <br> still splits the line — so the shape holds at every
   width, and the viewport-relative size shrinks the type on a narrow
   window rather than reflowing it. */
.hero-line {
  margin: 0;
  white-space: nowrap;
  font-size: var(--text-hero);
  font-weight: var(--font-weight-normal);
  line-height: var(--leading-tight); letter-spacing: var(--tracking-tight);
  color: var(--foreground);
}
/* Two lines under the hook, broken where the markup says. White, not
   muted: at this size on a photograph, grey goes soft. */
.hero-sub {
  margin: 1.5rem 0 0;
  max-width: 62ch;
  font-size: var(--text-lg);
  font-weight: var(--font-weight-normal);
  line-height: var(--leading-snug);
  color: var(--foreground);
}

.hero-cta { margin-top: 2rem; }

/* The public bar: the wordmark left, the ways in right. It lives inside
   the section it sits on — on the landing that is .landing-hero, whose
   tokens are inverted — so it needs no colours of its own to read white
   on the photograph. */
/* Centre, not baseline: the nav now holds a button, and a button's box
   has no text baseline to line the wordmark up against. */
.public-bar { display: flex; align-items: center;
  justify-content: space-between; gap: 1.5rem; padding: 1.5rem 0; }
.public-bar-brand { font-size: var(--text-lg); font-weight: var(--font-weight-medium);
  letter-spacing: var(--tracking-snug); color: var(--foreground); }
.public-bar-nav { display: flex; align-items: center; gap: 1.25rem; }
/* The wordmark and the pages about the product are one group, set apart
   from the ways in on the right by the gap between them rather than by
   a rule. On a narrow screen the section links go: the ways in are what
   a phone visitor came for, and a wrapped two-row bar reads as broken. */
.public-bar-left { display: flex; align-items: center; gap: 2rem; }
.public-bar-links { display: flex; align-items: center; gap: 1.25rem; }
@media (max-width: 40rem) { .public-bar-links { display: none; } }
/* The ways in are plain text, not buttons: on a photograph a button's
   filled box competes with the picture, and text does not. */
.hero-link {
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  color: var(--foreground);
  transition: color .15s;
}
@media (hover: hover) {
  .hero-link:hover { color: var(--muted-foreground); }
}

/* The statement: what this is, alone in a band under the photograph.
   Larger than body copy and smaller than a heading, because it is
   neither — it is the answer to the question the hook asked. The space
   around it is doing as much work as the type. */
.landing-statement { padding: clamp(4rem, 10vh, 7rem) 0; }
.landing-statement p {
  margin: 0; max-width: 30ch;
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-normal);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-snug);
}

/* ---- The marketing shell ---- */
footer.site { display: flex; align-items: center; justify-content: space-between; padding: 1.5rem 0; font-size: var(--text-xs); color: var(--muted-foreground); }
footer.site nav { display: flex; gap: 1.25rem; }
footer.site a:hover { color: var(--foreground); }
/* Everything under the hero: the ordinary white plane, same gutter. */
/* The bands read as an editorial column: the page holds one measured
   width in the middle of the screen rather than running to the gutters,
   and each band splits into a heading beside its prose. Two columns give
   the eye a fixed left edge to come back to down the whole page, which a
   stack of full-width paragraphs never does. */
.landing-sections { display: flex; flex-direction: column;
  width: 100%; max-width: var(--container-xl);
  margin-inline: auto; padding: 0 var(--gutter);
  counter-reset: band; }
.landing-section {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 6fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  padding: clamp(2.5rem, 5vw, 4rem) 0;
}
.landing-section + .landing-section { border-top: 1px solid var(--border); }
/* The band number is generated, not written: it is an artefact of the
   order the sections happen to be in, so it belongs to the stylesheet
   and renumbers itself when one moves. */
.landing-section h2 {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: var(--font-weight-medium);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-snug);
  text-wrap: balance;
}
.landing-section h2::before {
  counter-increment: band;
  content: counter(band, decimal-leading-zero);
  display: block;
  margin-bottom: .75rem;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: var(--font-weight-normal);
  letter-spacing: .08em;
  color: var(--muted-foreground);
}
/* Both columns are pinned by hand. Auto-placement would send a band's
   SECOND paragraph back to column one, under the heading — the bands
   with two paragraphs would break and the one-paragraph bands would
   not, which is the kind of bug that only shows up on one section. */
.landing-section h2 { grid-column: 1; grid-row: 1; }
.landing-section p { grid-column: 2; margin: 0; max-width: 58ch;
  color: var(--muted-foreground); line-height: var(--leading-normal); }
.landing-section p + p { margin-top: .875rem; }
/* One column when there is no room for two — the heading sits above its
   prose rather than squeezing both into half a phone. */
@media (max-width: 52rem) {
  .landing-section { grid-template-columns: minmax(0, 1fr); gap: .875rem; }
  /* One column: the pins above would leave an empty first column. */
  .landing-section h2, .landing-section p { grid-column: 1; }
  .landing-section h2::before { margin-bottom: .5rem; }
}
/* ---- Plain public pages (privacy, terms, contact) ----
   The same measured column as the landing bands, without the two-column
   split: a document reads top to bottom. The shell pins the footer to
   the bottom on a short page, so a two-paragraph contact page does not
   leave the footer floating in the middle of the screen. */
.prose-shell { display: flex; flex-direction: column; flex: 1;
  width: 100%; max-width: var(--container-xl);
  margin-inline: auto; padding: 0 var(--gutter); }
/* No inner cap: the block occupies the same column the landing bands do,
   so both pages share a left AND a right edge. The reading measure is
   kept on the paragraphs themselves (62ch below) rather than by pulling
   the whole column in, which is what made these pages look inset. */
.prose { flex: 1;
  padding: clamp(2.5rem, 6vh, 4.5rem) 0 clamp(3rem, 8vh, 5rem); }
.prose h1 { margin: 0 0 2rem;
  font-size: var(--text-2xl); font-weight: var(--font-weight-medium);
  line-height: var(--leading-snug); letter-spacing: var(--tracking-snug); }
.prose-section + .prose-section { margin-top: 2rem; }
.prose-section h2 { margin: 0 0 .5rem;
  font-size: var(--text-base); font-weight: var(--font-weight-medium); }
.prose-section p { margin: 0; max-width: 62ch;
  color: var(--muted-foreground); line-height: var(--leading-normal); }
.prose-section p + p { margin-top: .875rem; }
.prose-lead { margin: 1rem 0 0; font-size: var(--text-lg); }
.prose-lead a { text-decoration: underline; text-underline-offset: 4px; }
/* A page that knows it is unfinished says so, in a box that cannot be
   mistaken for the document's own voice. */
.prose-note { margin: 0 0 2rem; padding: .75rem 1rem;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--muted);
  font-size: var(--text-sm); color: var(--muted-foreground); }

/* The footer carries the gutter itself rather than sharing the sections
   wrapper, so it keeps its own rule above it whatever sections exist. */
.landing-footer { padding: 0 var(--gutter); }
/* The wordmark sits in the corner beside the nav rather than centred in
   the page, so the photograph is the only thing in the middle. Baseline
   alignment, not centre: it lines the name up with the nav link by the
   text they share, whatever the size difference between them. */

/* ---- Buttons (components/ui/button.tsx) ---- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: .375rem; height: 2rem; padding: 0 .625rem;
  border: 1px solid transparent; border-radius: var(--radius);
  font: inherit; font-size: var(--text-sm); font-weight: var(--font-weight-medium); white-space: nowrap;
  cursor: pointer; user-select: none; text-decoration: none;
  transition: background-color .15s, color .15s, border-color .15s;
  background: var(--primary); color: var(--primary-foreground);
}
.btn:hover { background: color-mix(in oklch, var(--primary) 80%, transparent); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: none; border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent); }
.btn-sm { height: 1.75rem; font-size: var(--text-sm); }
.btn-outline { background: var(--background); color: var(--foreground); border-color: var(--border); }
.btn-outline:hover { background: var(--muted); }
.btn-ghost { background: transparent; color: var(--foreground); }
.btn-ghost:hover { background: var(--muted); }
.btn-destructive { background: color-mix(in oklch, var(--destructive) 10%, transparent); color: var(--destructive); }
.btn-destructive:hover { background: color-mix(in oklch, var(--destructive) 20%, transparent); }

/* ---- Cards ---- */
.card {
  background: var(--card); color: var(--card-foreground);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 1.5rem; width: 100%; max-width: 28rem;
  display: flex; flex-direction: column; gap: .75rem;
}
.card h1 { margin: 0; font-size: var(--text-base); font-weight: var(--font-weight-semibold); letter-spacing: var(--tracking-snug); }
.card p { margin: 0; font-size: var(--text-sm); color: var(--muted-foreground); }
.card .actions { display: flex; gap: .5rem; margin-top: .5rem; }
main.center { display: flex; flex: 1; align-items: center; justify-content: center; padding: 1.5rem var(--gutter); }

/* ---- The workbench shell (components/workbench/shell.tsx) ---- */
/* The shell owns the viewport and nothing inside it scrolls the page:
   the sidebar is a fixed column and the content scrolls within the
   plane. Previously the whole document scrolled, which took the
   sidebar with it — a navigation rail that leaves the screen when you
   read is not a rail. `overflow: hidden` here is what stops the body
   scrolling at all; each pane below opts back in for itself. */
.workbench { display: flex; height: 100svh; overflow: hidden;
             background: var(--sidebar);
             /* The chrome's resting ink — the rail's rows and the
                topbar's buttons. --muted-foreground alone was too faint
                for text you navigate by: these are destinations, read
                at a glance and often, not captions. Two thirds of the
                way from full ink to muted keeps them quieter than the
                plane's content without making them hard to read, and
                mixing the two tokens means the chrome follows the
                palette into dark without a second value. */
             --rail-ink: color-mix(in oklch, var(--muted-foreground) 65%,
                                   var(--sidebar-foreground)); }
/* The sidebar's metrics are Linear's, measured off its own app: a
   244px rail, 28px rows inset 12px from each edge on a 29px pitch,
   8px pill radius, 13px/500 labels. The colours are ours — the tokens
   in tokens.lisp — because this product is light and a dark rail
   beside a white plane would read as a different application. So:
   their geometry, our palette. */
.sidebar { width: 13rem; flex-shrink: 0; display: flex; flex-direction: column;
           overflow: hidden; transition: width 200ms ease; }
.sidebar-head { height: 3rem; flex-shrink: 0; display: flex; align-items: center;
                padding: 0 12px; }
.brand { display: flex; align-items: center; gap: 6px; height: 28px;
         padding: 0 6px 0 4px; border-radius: var(--radius);
         font-size: var(--text-sm); font-weight: 550; letter-spacing: -.1px;
         color: var(--sidebar-foreground); min-width: 0; }
.brand:hover { background: var(--sidebar-accent); }
.brand-full { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The chip standing in for a workspace or a team. Each one is drawn
   with a hue derived from its own name and set inline, so one rule
   colours every one of them; the declaration here is the fallback for
   a chip rendered without a name to derive from. Declared rather than
   left to var()'s second argument because a property this sheet reads
   and never declares is exactly what the UI test looks for. */
.nav-avatar, .overview-title-tile, .stack-avatar { --avatar-hue: 250; }
.nav-avatar { flex-shrink: 0; display: grid; place-items: center;
              width: 20px; height: 20px; border-radius: var(--radius);
              font-size: 10px; font-weight: var(--font-weight-medium);
              line-height: 1; letter-spacing: 0;
              color: oklch(.98 0 0);
              background: oklch(.62 .13 var(--avatar-hue, 250)); }
/* A team's chip is ink, not colour. Deriving a hue from the name gave
   every team a different bright square, which read as status — a red
   team looked like a team in trouble. Neutral says "this is a team"
   and nothing else. People keep their colours, where telling one face
   from another is the whole job. */
.nav-avatar-team { width: 16px; height: 16px; border-radius: var(--radius);
                   font-size: 8px;
                   background: var(--foreground); color: var(--background); }

/* Collapsed: an icon rail. Labels vanish, the workspace chip stands in
   for the wordmark, and the tabs keep their exact y — only width moves. */
html.sidebar-collapsed .sidebar { width: 48px; }
html.sidebar-collapsed .tab-label,
html.sidebar-collapsed .brand-full,
html.sidebar-collapsed .nav-section-head,
html.sidebar-collapsed .nav-disclosure { display: none; }
/* Collapsed, a team's children would be three unlabelled icons under
   an unlabelled icon — indistinguishable from the workspace's. Show
   the teams themselves and nothing beneath them. */
html.sidebar-collapsed .nav-team .nav-section-body { display: none; }
html.sidebar-collapsed .nav-child { display: none; }

/* The rail's own scroller — long team lists scroll here, and the head
   and foot stay put. */
.sidebar-nav { display: flex; flex-direction: column; padding: 4px 0;
               overflow-y: auto; min-height: 0; flex: 1; }
.nav-section-rows { display: flex; flex-direction: column; gap: 1px;
                    padding: 0 12px; }

/* A section: a quiet header that toggles, and a body that animates its
   own height. The 0fr/1fr grid is what makes that animation possible —
   height:auto is not interpolable, a grid row fraction is. */
.nav-section { display: flex; flex-direction: column; margin-top: 18px; }
/* The toggle and the `+` are siblings on one line: a button nested in
   a button is invalid, and clicking the inner one would collapse the
   group as a side effect of adding to it. */
.nav-section-header { display: flex; align-items: center; margin: 0 12px; }
.nav-section-head { flex: 1; min-width: 0;
                    display: flex; align-items: center; gap: 2px;
                    height: 28px; padding: 0 5px;
                    border: 0; background: none; border-radius: var(--radius);
                    font: inherit; font-size: var(--text-xs);
                    font-weight: var(--font-weight-medium);
                    color: var(--muted-foreground); cursor: pointer;
                    text-align: left; }
.nav-section-head:hover { background: var(--sidebar-accent); }
.nav-section-add { flex-shrink: 0; display: grid; place-items: center;
                   width: 24px; height: 24px; padding: 0;
                   border: 0; background: none; border-radius: var(--radius);
                   color: var(--muted-foreground); cursor: pointer;
                   opacity: 0; transition: opacity 120ms ease; }
.nav-section-add svg { width: 14px; height: 14px; }
.nav-section-add:hover { background: var(--sidebar-accent);
                         color: var(--sidebar-foreground); }
.nav-section-header:hover .nav-section-add,
.nav-section-add:focus-visible { opacity: 1; }
.nav-section-body { display: grid; grid-template-rows: 1fr; min-height: 0;
                    transition: grid-template-rows 160ms ease, opacity 160ms ease; }
.nav-section-body > * { overflow: hidden; }
[data-open="0"] > .nav-section-body { grid-template-rows: 0fr; opacity: 0; }

/* The arrow turns rather than swapping glyphs. On a section header it
   stays hidden until you hover, exactly as Linear's does — at rest the
   header reads as a label, not a control. */
.nav-chevron { display: inline-flex; width: 12px; height: 12px;
               color: var(--muted-foreground);
               transition: transform 160ms ease, opacity 120ms ease; }
.nav-chevron svg { width: 12px; height: 12px; stroke-width: 2.5; }
[data-open="1"] > .nav-section-header > .nav-section-head .nav-chevron,
[data-open="1"] > .nav-team-row .nav-disclosure .nav-chevron { transform: rotate(90deg); }
.nav-section-head .nav-chevron { opacity: 0; }
.nav-section-head:hover .nav-chevron,
.nav-section-head:focus-visible .nav-chevron { opacity: 1; }

/* A team row is one pill holding two targets: the link fills it, the
   disclosure sits at the far right. The hover highlight belongs to the
   row so both halves light the same pill. */
/* A team's chip starts exactly where "Your teams" starts — same 17px
   from the rail's edge (12px row inset + 5px control padding), so the
   group's label and its members read as one column rather than two
   slightly different indents. */
/* No margin of its own: the enclosing .nav-section-rows already insets
   by 12px, and adding another 12 here is what pushed the chip 12px
   right of the group's label. */
.nav-team-row { display: flex; align-items: center; border-radius: var(--radius); }
.nav-team-row:hover, .nav-team-row.active { background: var(--sidebar-accent); }
.nav-team-row .tab { flex: 1; min-width: 0; margin: 0;
                     padding-left: 5px; padding-right: 2px;
                     background: none; }
.nav-team-row .tab.active, .nav-team-row .tab:hover { background: none; }
.nav-disclosure { flex-shrink: 0; display: grid; place-items: center;
                  width: 20px; height: 28px; margin-right: 4px;
                  border: 0; background: none; padding: 0; cursor: pointer;
                  opacity: 0; transition: opacity 120ms ease; }
.nav-team-row:hover .nav-disclosure,
.nav-disclosure:focus-visible,
[data-open="1"] > .nav-team-row .nav-disclosure { opacity: 1; }
/* A team's children are indented enough to read as its children and no
   more: 12px on top of the enclosing group's 12px and the row's own
   5px puts their icons at 29px, a step in from the team's chip at 17px.
   They used to line up under the team's *name* at 41px, which pushed
   them so far in that the group looked like a second sidebar. */
.nav-team .nav-section-rows { padding-left: 12px; padding-right: 0; }

/* Identity chrome sits at the rail's foot, pushed there by the auto
   margin — same tabs, different neighborhood. They carry icons now, so
   the collapsed rail keeps them instead of hiding the pair. */
.sidebar-foot { display: flex; flex-direction: column; gap: 1px;
                padding: 8px 12px; margin-top: auto; }
/* 5px of leading padding, the same as a section header's, so every row
   in the rail — top-level, workspace, team, team child — starts its
   icon on one vertical line 17px from the edge. */
.tab { display: flex; gap: 8px; align-items: center; height: 28px;
       padding: 0 9px 0 5px; border-radius: var(--radius);
       font-size: var(--text-sm); font-weight: var(--font-weight-medium);
       color: var(--rail-ink);
       white-space: nowrap; overflow: hidden; }
.tab-label { overflow: hidden; text-overflow: ellipsis; }
.tab:hover { background: var(--sidebar-accent); color: var(--sidebar-foreground); }
.tab.active { background: var(--sidebar-accent);
              color: var(--sidebar-accent-foreground); }
.tab svg { width: 16px; height: 16px; flex-shrink: 0; }
/* MIN-HEIGHT 0 so the frame may be shorter than its content and hand
   the overflow to the plane; without it a flex child refuses to shrink
   below its content and the scrollbar lands back on the body. */
.frame { display: flex; flex: 1; flex-direction: column;
         min-width: 0; min-height: 0;
         /* How wide the search grows to. It no longer sizes a centred
            column, so it lives on the search's own cap. */
         --search-width: 26rem; }
/* The search sits at the bar's right end, beside the pending chip, and
   the middle is empty. It was centred once, on three columns with equal
   1fr flanks; that put it in the true centre of the bar, but the centre
   of the bar is above the middle of the page, which is where the page's
   own content is. Off to one side it stops competing with whatever is
   being read.

   Three columns still: the trigger claims the first, the search is
   pushed to the end of the wide middle one, and .right takes the last.
   The two <script> children are display:none, so they generate no boxes
   and never take a column. The order below is the markup's: trigger,
   search, .right. */
.topbar { position: sticky; top: 0; z-index: 10; height: 3rem; flex-shrink: 0;
          display: grid;
          grid-template-columns: auto minmax(0, 1fr) auto;
          align-items: center; gap: .75rem; padding: 0 1rem;
          background: color-mix(in oklch, var(--sidebar) 95%, transparent);
          backdrop-filter: blur(8px); }
.topbar .right { justify-self: end; display: flex; align-items: center; gap: .75rem; }
.sidebar-trigger { justify-self: start; margin-left: -.25rem; padding: 0 .375rem; }
/* The light/dark switch. It holds both glyphs and hides one, so the
   markup is the same in either palette and nothing has to re-render to
   change the light — the class on <html> does all of it. Each glyph
   offers the palette you are not in. */
.theme-toggle { padding: 0 .375rem; color: var(--rail-ink); }
.theme-toggle svg { width: 1rem; height: 1rem; }
.theme-glyph { display: none; }
.theme-glyph.to-dark { display: inline-flex; }
html.dark .theme-glyph.to-dark { display: none; }
html.dark .theme-glyph.to-light { display: inline-flex; }
.sidebar-trigger svg { width: 1rem; height: 1rem; }
.plane-wrap { flex: 1; display: flex; padding: 0 .5rem .5rem;
              min-width: 0; min-height: 0; }
/* The one scroller on the page. */
main.plane { flex: 1; min-width: 0; background: var(--background);
             border-radius: var(--radius); padding: 1.5rem;
             display: flex; flex-direction: column; overflow-y: auto; }
main.plane.flush { padding: 0; }

/* A plane cut in two rather than one plane with a rule down it: the
   shell showing through the gap is the divider, the same .5rem the
   plane already leaves at its own edges. The plane keeps the layout
   and gives up the surface; each pane takes the surface, and its own
   scroller — two panes that scroll together are one pane. */
main.plane.split { background: none; padding: 0; overflow: hidden;
                   flex-direction: row; gap: .5rem; }
main.plane.split > .plane-pane {
  flex: 1; min-width: 0; min-height: 0; background: var(--background);
  border-radius: var(--radius); padding: 1.5rem;
  display: flex; flex-direction: column; overflow-y: auto;
}
/* A third and two thirds, said by the pane rather than by its position:
   which side is narrow is the page's decision, and a positional rule
   made it the stylesheet's. Panes are even unless one claims the width. */
main.plane.split > .plane-pane.pane-wide { flex: 2; }
main.plane.split > .plane-pane.pane-narrow { flex: 1; }
/* Narrow enough and a third of the width is a column of single words,
   so the panes stack and the seam runs across instead of down. Stacked,
   the narrow pane goes on top: it is the one with the text box in it,
   and a composer below a full page of output is a composer nobody
   reaches. */
@media (max-width: 60rem) {
  main.plane.split { flex-direction: column; }
  main.plane.split > .plane-pane.pane-wide,
  main.plane.split > .plane-pane.pane-narrow { flex: 1; }
  main.plane.split > .plane-pane.pane-narrow { order: -1; }
}

/* ---- The overview layout (components/overview.lisp) ----
   Linear's team overview, measured off its app: a 688px main column, a
   212px rail, 60px between them, 24px between sections. Used flush —
   the crumb bar draws its own inset, so a page built from these passes
   :flush and the bar sits against the plane's top edge as Linear's
   does. */
/* The three bands of an overview page — crumb, tabs, body — are
   siblings in the plane rather than one wrapper, so they each carry the
   same cap and auto margins. That is what keeps their left edges on one
   line and puts the whole page in the middle of the plane instead of
   pinned against the sidebar. */
.crumb-bar, .pill-tabs, .overview {
  width: 100%; max-width: 62rem; margin-inline: auto;
}
.crumb-bar { display: flex; align-items: center; gap: 8px; height: 48px;
             flex-shrink: 0; padding: 0 16px;
             font-size: var(--text-sm); color: var(--foreground); }
.crumb-icon { display: inline-flex; }
.crumb-icon svg { width: 16px; height: 16px; }
.crumb-label { font-weight: var(--font-weight-medium); }
.crumb-back { font-size: var(--text-sm); color: var(--muted-foreground); }
.crumb-back::after { content: "/"; margin-left: 8px; opacity: .5; }
.crumb-actions { margin-left: auto; display: flex; align-items: center; gap: 4px; }

/* Pill tabs. Fully rounded and 28px tall, so the strip reads as
   controls rather than as a second heading row. */
/* 6px on the left, not 16: a pill carries 10px of its own padding, so
   the strip pays the difference and the first tab's *text* lands on
   16px — the same line as the crumb above it and the title tile below.
   Its highlight still bleeds the 10px left of the text, which is what
   makes a pill a pill. */
.pill-tabs { display: flex; align-items: center; gap: 4px;
             padding: 0 16px 16px 6px; flex-shrink: 0;
             overflow-x: auto; scrollbar-width: none; }
.pill-tabs::-webkit-scrollbar { display: none; }
.pill-tab { flex-shrink: 0; display: inline-flex; align-items: center;
            height: 28px; padding: 0 10px; border-radius: var(--radius);
            font-size: var(--text-xs); font-weight: var(--font-weight-medium);
            color: var(--muted-foreground); white-space: nowrap; }
.pill-tab:hover { background: var(--accent); color: var(--foreground); }
.pill-tab.active { background: var(--accent); color: var(--accent-foreground); }

/* The two columns. `align-items: start` keeps the rail at the top
   instead of stretching it the height of the main column, which is
   what makes a short rail beside a long page look deliberate. */
/* No scroller of its own — the plane above is the one that scrolls, and
   two nested scrollers means a wheel event over the rail moves the
   wrong thing. */
.overview { display: flex; align-items: start; gap: 60px;
            padding: 8px 16px 48px; }
.overview-main { flex: 1; min-width: 0; max-width: 688px;
                 display: flex; flex-direction: column; gap: 24px; }
.overview-rail { flex-shrink: 0; width: 212px;
                 display: flex; flex-direction: column; gap: 24px; }
/* Below the two-column width the rail goes under the main column
   rather than getting narrower — a squeezed rail of quick links is
   worse than one further down. */
@media (max-width: 1100px) {
  .overview { flex-direction: column; gap: 32px; }
  .overview-rail { width: 100%; }
}

.overview-title { display: flex; align-items: center; gap: 12px; }
.overview-title h1 { margin: 0; font-size: 24px;
                     font-weight: var(--font-weight-semibold);
                     letter-spacing: -.16px; }
/* Neutral, like the sidebar's team chip — the tinted version made every
   page open with a coloured square that read as a status light. */
.overview-title-tile { flex-shrink: 0; display: grid; place-items: center;
                       width: 2.25rem; height: 2.25rem;
                       border-radius: var(--radius);
                       color: var(--muted-foreground); background: var(--muted); }
.overview-title-tile svg { width: 1.25rem; height: 1.25rem; }

/* The description. Empty and filled are the same line in two moods —
   same size, same position, only the colour differs — so adding one
   does not shift the page. */
.overview-desc { margin-top: -12px; }
.overview-desc-text, .overview-desc-empty {
  display: block; margin: 0; font-size: 15px; line-height: 1.5; }
.overview-desc-text { color: var(--foreground); }
.overview-desc-empty { color: var(--muted-foreground); }
a.overview-desc-text:hover, a.overview-desc-empty:hover {
  color: var(--foreground); text-decoration: underline;
  text-underline-offset: 3px; }

/* The type-in-place description. It is the sentence, not a field: no
   border, no background, no fixed height, and no change of colour when
   you write in it — writing in a document does not tint the paper. The
   only affordance is that the pointer becomes a caret. */
.overview-desc-edit {
  cursor: text;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.overview-desc-edit:focus { outline: none; }
/* The placeholder, done the way tiptap does it (their placeholder
   extension's CSS — packages/extensions/src/placeholder, MIT):
   `.is-empty::before { content: attr(data-placeholder); float: left;
   height: 0; pointer-events: none }`.

   All three properties earn their place. FLOAT plus HEIGHT 0 take the
   pseudo-element out of flow so an empty field is exactly one line
   tall and the caret sits at its start — in normal flow it stacks
   above the caret and doubles the field's height. POINTER-EVENTS NONE
   is why clicking the placeholder puts the caret in the editor instead
   of the click landing on the pseudo-element and focusing nothing.

   :empty is no help here — an emptied contenteditable keeps a <br> —
   so the class is maintained by the script, as tiptap's is by its
   decoration plugin. */
.overview-desc-edit.is-empty::before {
  content: attr(data-placeholder);
  float: left;
  height: 0;
  pointer-events: none;
  color: var(--muted-foreground);
}
.overview-desc-status {
  display: block; margin-top: .25rem; min-height: 1rem;
  font-size: var(--text-xs); color: var(--muted-foreground);
  transition: opacity .15s;
}
.overview-desc-status[data-inline-status="bad"] { color: var(--destructive); }

.overview-section { display: flex; flex-direction: column; gap: 8px; }
.overview-section-head { display: flex; align-items: center; gap: 8px;
                         min-height: 28px; }
.overview-section-head h2 { margin: 0; font-size: 18px;
                            font-weight: var(--font-weight-medium);
                            letter-spacing: -.1px; }
.overview-section-actions { margin-left: auto; display: flex;
                            align-items: center; gap: 4px; }
.overview-section-empty { margin: 0; font-size: 15px;
                          color: var(--muted-foreground); }

/* ---- The rail ---- */
.rail-section { display: flex; flex-direction: column; gap: 12px; }
.rail-label { margin: 0; font-size: var(--text-xs);
              font-weight: var(--font-weight-medium);
              color: var(--muted-foreground); }
.rail-body { display: flex; flex-direction: column; gap: 2px; }
.rail-empty { font-size: var(--text-sm); color: var(--muted-foreground); }
.rail-link { display: flex; align-items: center; gap: 8px;
             height: 28px; margin: 0 -8px; padding: 0 8px;
             border-radius: var(--radius);
             font-size: var(--text-sm); font-weight: var(--font-weight-medium);
             color: var(--foreground); }
.rail-link:hover { background: var(--accent); }
.rail-link-icon { display: inline-flex; color: var(--muted-foreground); }
.rail-link-icon svg { width: 16px; height: 16px; }
.rail-facts { display: grid; grid-template-columns: auto 1fr;
              gap: 4px 12px; margin: 0; font-size: var(--text-sm); }
.rail-facts dt { color: var(--muted-foreground); }
.rail-facts dd { margin: 0; }

/* ---- Settings fields (components/form.lisp) ----
   A setting per row: its name on the left, its control on the right, a
   hairline between rows — the same separator a record table uses. Bare on
   the page: no panel, no surface, nothing around a group but the rule
   between its rows.

   Every control here is the .field input above wearing a different
   layout — same 1px var(--input) border, same var(--radius), same
   focus ring. There is one input style in this app and this is it. */
.settings-group { display: flex; flex-direction: column; }
.settings-row { display: flex; align-items: center; gap: 1rem;
                padding: .75rem 0;
                border-bottom: 1px solid var(--border); }
.settings-row-label { display: flex; flex-direction: column; gap: .125rem;
                      min-width: 0; }
.settings-row-label label { font-size: var(--text-sm);
                            font-weight: var(--font-weight-medium); }
.settings-row-note { font-size: var(--text-xs); color: var(--muted-foreground); }
.settings-row-control { margin-left: auto; flex-shrink: 0; }

.settings-input { width: 14rem;
                  font: inherit; font-size: var(--text-sm);
                  padding: .375rem .5rem;
                  border: 1px solid var(--input); border-radius: var(--radius);
                  background: var(--background); color: var(--foreground); }
.settings-input::placeholder { color: var(--muted-foreground); }
.settings-input:focus-visible {
  outline: none; border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}
/* A control that has to be wide — a study's title, a long picker —
   opts in rather than every control being wide by default. */
.settings-input.wide { width: 22rem; }
@media (max-width: 640px) {
  .settings-row { flex-direction: column; align-items: stretch; gap: .5rem; }
  .settings-row-control { margin-left: 0; }
  .settings-input, .settings-input.wide { width: 100%; }
}

/* A settings form is a column of groups and blocks with its Save at the
   foot — the same rhythm as the overview's sections, so a page can mix
   the two without a seam. */
.settings-form { display: flex; flex-direction: column; gap: 1.5rem; }
.settings-actions { display: flex; gap: .5rem; }

.settings-block { display: flex; flex-direction: column; gap: .25rem; }
.settings-block h3 { margin: 0; font-size: 1rem;
                     font-weight: var(--font-weight-medium); }
.settings-block-note { margin: 0 0 .5rem; font-size: var(--text-sm);
                       color: var(--muted-foreground); }
.settings-textarea { width: 100%; min-height: 7rem;
                     font: inherit; font-size: var(--text-sm);
                     padding: .5rem;
                     border: 1px solid var(--input); border-radius: var(--radius);
                     background: var(--background); color: var(--foreground);
                     line-height: var(--leading-normal); resize: vertical; }
.settings-textarea::placeholder { color: var(--muted-foreground); }
.settings-textarea:focus-visible {
  outline: none; border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}

/* The member stack. The ring is the page background, so the discs read
   as separate even where they overlap. */
.avatar-stack { display: flex; align-items: center; }
.avatar-stack-link { display: flex; }
.stack-avatar { display: grid; place-items: center;
                width: 18px; height: 18px; border-radius: var(--radius);
                margin-right: -5px;
                font-size: 8px; font-weight: var(--font-weight-medium);
                line-height: 1; color: oklch(.98 0 0);
                background: oklch(.62 .13 var(--avatar-hue, 250));
                box-shadow: 0 0 0 2px var(--background); }
.stack-more { color: var(--muted-foreground); background: var(--accent); }
.avatar-stack-link:hover .stack-avatar { box-shadow: 0 0 0 2px var(--accent); }

/* The attention chips — render only when non-zero; an empty top bar is
   the good state. */
.chip { display: inline-flex; align-items: center; gap: .375rem;
        border-radius: 9999px; padding: .125rem .625rem; font-size: var(--text-xs);
        border: 1px solid; }
/* One amber, and the chip mixed from it against whatever the page is
   sitting on. The three literals this replaces were a light-mode
   palette written out by hand — a cream plate with brown type, which on
   a dark page is a lit rectangle that reads as an error rather than a
   count. Mixed against --background and --foreground it is the same
   warning in either. */
.chip-amber { --amber: oklch(0.75 0.15 80);
              border-color: color-mix(in oklch, var(--amber) 45%, transparent);
              background: color-mix(in oklch, var(--amber) 12%, var(--background));
              color: color-mix(in oklch, var(--amber) 70%, var(--foreground)); }
.chip svg { width: .75rem; height: .75rem; }

/* The caller's organization, quiet in the topbar's right corner. */
.topbar-org { font-size: var(--text-sm); color: var(--muted-foreground);
              white-space: nowrap; }

/* ---- Topbar search ---- */
/* The dropdown shows only while the form holds focus and has content:
   :empty covers the blank query (the fragment renders nothing), and
   :focus-within is why hits swallow mousedown — a click must not blur
   the input before it lands. */
/* Pushed to the end of the bar's wide middle column, so it ends where
   the pending chip begins and never grows past its cap. */
.topbar-search { position: relative;
  justify-self: end; width: 100%; max-width: var(--search-width);
  display: flex; align-items: center; gap: .5rem; height: 2rem; padding: 0 .625rem;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--background); }
.topbar-search:focus-within { border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent); }
.topbar-search svg { width: .875rem; height: .875rem; flex-shrink: 0;
  color: var(--muted-foreground); }
.topbar-search input { flex: 1; min-width: 0; border: 0; outline: none;
  background: transparent; font: inherit; font-size: var(--text-sm);
  color: var(--foreground); }
.search-key { font-family: var(--font-mono); font-size: var(--text-2xs);
  color: var(--muted-foreground); border: 1px solid var(--border);
  border-radius: calc(var(--radius) / 2); padding: 0 .25rem; }
.topbar-search:focus-within .search-key { display: none; }
.search-pop { position: absolute; top: 2.375rem; left: 0; right: 0; z-index: 30;
  display: flex; flex-direction: column; padding: .375rem;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-pop);
  max-height: 60svh; overflow-y: auto; }
.search-pop:empty, .topbar-search:not(:focus-within) .search-pop { display: none; }
.search-group-title { padding: .375rem .5rem .125rem; font-size: var(--text-2xs);
  text-transform: uppercase; letter-spacing: .05em; color: var(--muted-foreground); }
.search-hit { display: flex; align-items: baseline; gap: .5rem;
  padding: .375rem .5rem; border-radius: var(--radius); font-size: var(--text-sm); }
.search-hit:hover { background: var(--muted); }
.search-hit .hit-title { flex-shrink: 0; font-weight: var(--font-weight-medium); }
.search-hit .hit-desc { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: var(--text-xs); color: var(--muted-foreground); }
.search-empty { margin: 0; padding: .5rem; font-size: var(--text-sm);
  color: var(--muted-foreground); }
/* The same hits, on the /search page rather than in the dropdown. */
.search-results { display: flex; flex-direction: column; }

/* ---- Workbench content ---- */
/* 64rem, not 48: a record table is five or six columns wide and the old
   cap cut the last one off inside its scroll container. Prose still
   reads at a sane measure because the paragraphs that live here — the
   page blurb, an empty-state line — are short by construction. */
.content-narrow { width: 100%; max-width: 64rem; margin: 0 auto;
                  display: flex; flex-direction: column; gap: 1rem; }
.content-head { display: flex; align-items: center; justify-content: space-between; }
.content-head h1 { margin: 0; font-size: var(--text-sm); font-weight: var(--font-weight-semibold); }
.content-head p { margin: 0; font-size: var(--text-xs); color: var(--muted-foreground); }
/* Record tables (components/records.lisp). The look is the shadcn port
   in ui/table.lisp; these are the three things a *record* table adds.

   The row link: one anchor, in the first cell, stretched over the whole
   row by an absolutely-positioned ::after. That is what keeps a row one
   click target now that it is cells rather than a single <a> — and why
   the row is the positioning context. A button inside a later cell sits
   above the sheet and stays clickable. */
tr:has(.row-link) { position: relative; }
.row-link { color: inherit; text-decoration: none;
            font-weight: var(--font-weight-medium); }
.row-link::after { content: ""; position: absolute; inset: 0; }
tr:has(.row-link) [data-slot="button"],
tr:has(.row-link) a:not(.row-link) { position: relative; z-index: 1; }

/* The first column is the record's name: it holds the link, so it never
   wraps and never gets clipped. */
td.row-name, th.row-name { white-space: nowrap; }

/* Whichever column says so takes the width the names, badges and
   timestamps do not. It ellipsises past a cap rather than pushing the
   table into its scroll container — a title is worth reading, but not
   worth a horizontal scrollbar on every row. The cap is a real length
   on purpose: in an auto-layout table max-width:0 collapses the column
   to its minimum instead of letting it grow. */
td.row-wide, th.row-wide { width: 100%; max-width: 22rem;
                           overflow: hidden; text-overflow: ellipsis; }

/* Timestamps: mono, quiet, hard against the right edge. */
td.row-time, th.row-time { text-align: right; font-family: var(--font-mono);
                           font-size: var(--text-2xs);
                           color: var(--muted-foreground); }

/* Dialog: <details> standing in for the shadcn dialog. */
details.dialog { position: relative; }
details.dialog > summary { list-style: none; cursor: pointer; }
details.dialog > summary::-webkit-details-marker { display: none; }
details.dialog[open] > .dialog-panel {
  position: absolute; right: 0; top: 2.25rem; z-index: 30; width: 24rem;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 1.25rem;
  display: flex; flex-direction: column; gap: .75rem;
  box-shadow: var(--shadow-pop);
}
/* A section heading — one rule instead of the same inline style on
   every page. */
.section-title { margin: 0; font-size: 1rem; font-weight: var(--font-weight-medium); }
.field { display: flex; flex-direction: column; gap: .375rem; }
.field label { font-size: var(--text-sm); font-weight: var(--font-weight-medium); }
.field input, .field textarea, .field select {
  font: inherit; font-size: var(--text-sm); padding: .375rem .5rem;
  border: 1px solid var(--input); border-radius: var(--radius);
  background: var(--background); color: var(--foreground);
}
.field input:focus-visible, .field textarea:focus-visible, .field select:focus-visible {
  outline: none; border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
}
/* Form layout. Every form on the site is built from these four shapes,
   so they are classes rather than the same inline flex repeated on
   forty lines — and a row of fields shares its width by the rule below
   instead of each field declaring flex:1 for itself. */
.form-stack { display: flex; flex-direction: column; gap: .5rem; }
.form-stack-wide { display: flex; flex-direction: column; gap: 1rem; }
.form-row { display: flex; gap: .5rem; }
/* min-width:0 so a long option label in a select cannot push the row
   wider than its container. */
.form-row > .field, .form-row-baseline > .field { flex: 1; min-width: 0; }
.form-row > .field-wide, .form-row-baseline > .field-wide { flex: 2; }
.form-row-baseline { display: flex; align-items: flex-end; gap: .5rem; }
.form-actions { display: flex; justify-content: flex-end; gap: .5rem; }
/* An inline element nudged off the one before it — a badge after a
   title, a note after a name. */
.trailing { margin-left: .5rem; }
.trailing-tight { margin-left: .35rem; }
/* A row title's second half — the human name after the code. */
.row-subtitle { margin-left: .5rem; font-weight: 400; }

/* ---- Detail views ---- */
.facts { display: grid; grid-template-columns: auto 1fr; column-gap: 1rem;
  row-gap: .25rem; margin: 0; font-size: var(--text-xs); }
.facts dt { color: var(--muted-foreground); }
.facts dd { margin: 0; min-width: 0; }
/* ---- The scannable code on a record's page ----
   The white plate is not decoration and does not follow the theme: a
   scanner needs dark bars on a light ground, and a barcode that
   inverted itself in dark mode would simply stop reading. So the mark
   keeps its own white while the frame around it stays themed. */
.barcode { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  padding: .75rem; margin-bottom: 1rem;
  border: 1px solid var(--border); border-radius: var(--radius); }
.barcode-mark { background: #fff; padding: .375rem; border-radius: 2px;
  line-height: 0; }
/* Never scaled down: a bar narrower than it was drawn is a bar a
   reader misjudges. It may grow on a wide plane and no further. */
.barcode-mark svg { display: block; height: 3.5rem; width: auto; }
.barcode-meta { display: flex; flex-direction: column; gap: .125rem;
  font-size: var(--text-xs); }
.barcode-code { font-size: var(--text-sm); letter-spacing: .04em; }
.barcode-caption { color: var(--muted-foreground); }
.barcode-print { color: var(--muted-foreground); text-decoration: underline; }
.barcode-print:hover { color: var(--foreground); }

/* The fixed height matches .btn-sm so a picker and its button sit as
   one row — but the body's line-height (1.5) is taller than the box
   leaves room for, which pushed the selected option's text low and
   clipped its descenders. The control carries its own line height
   and centers its content instead of inheriting the page's. */
.select-sm { font: inherit; font-size: var(--text-sm); line-height: 1.25;
  height: 1.75rem; box-sizing: border-box; padding: 0 .375rem;
  align-content: center;
  border: 1px solid var(--input); border-radius: var(--radius);
  background: var(--background); color: var(--foreground); }
details.edit > summary { list-style: none; cursor: pointer; }
details.edit > summary::-webkit-details-marker { display: none; }

/* ---- Modal dialog (shadcn Dialog) ---- */
dialog.modal { border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--card); color: var(--card-foreground);
  padding: 1.5rem; width: 26rem; max-width: calc(100vw - 2rem); }
dialog.modal::backdrop { background: oklch(0 0 0 / 40%); }

/* ---- Tables ---- */
table { border-collapse: collapse; width: 100%; font-size: var(--text-sm); }
th { text-align: left; font-weight: var(--font-weight-medium); color: var(--muted-foreground);
     padding: .5rem .75rem; border-bottom: 1px solid var(--border); }
td { padding: .5rem .75rem; border-bottom: 1px solid var(--border); }
tr.revoked td { color: var(--muted-foreground); text-decoration: line-through; }
tr.revoked td:last-child { text-decoration: none; }
.empty { color: var(--muted-foreground); }

/* ---- Small bits ---- */
.row { display: flex; gap: 1rem; align-items: center; }
.muted { color: var(--muted-foreground); font-size: var(--text-sm); }
.badge { display: inline-flex; align-items: center; height: 1.25rem;
         padding: 0 .5rem; border-radius: var(--radius);
         background: var(--secondary); color: var(--secondary-foreground);
         font-size: var(--text-xs); font-weight: var(--font-weight-medium); }
/* A judgement that failed has to look different from one that passed.
   An out-of-spec number rendered like every other number is the exact
   failure this feature exists to prevent, so the badge carries the
   destructive token rather than the neutral one. */
.badge-destructive {
  background: color-mix(in oklch, var(--destructive) 12%, transparent);
  color: var(--destructive); }
.badge.muted { background: transparent; color: var(--muted-foreground);
               box-shadow: inset 0 0 0 1px var(--border); }
.filter-row { display: flex; gap: .5rem; flex-wrap: wrap; }

.spread { margin-left: auto; }

/* ---- The control workspace (pages/control.lisp) ----
   Two panes with different jobs. The wide one on the left is where the
   work appears — what the assistant is doing, what it wants permission
   for, and what came back. The narrow one on the right is the
   conversation, which is an input, not the record.

   These were inline styles carrying their own fallbacks
   (var(--muted, #f4f4f5)) — which meant the fallback, not the token,
   was what the dark palette got. Here they take the tokens like
   everything else. */

/* The strip that says what is happening right now. Present only when
   something is: an idle assistant has nothing to announce. */
.control-status {
  display: flex; align-items: center; gap: .5rem;
  padding: .6rem .75rem; margin-bottom: 1rem;
  border-radius: var(--radius); font-size: var(--text-sm);
  background: var(--muted); color: var(--foreground);
}
.control-status.is-waiting {
  background: color-mix(in oklch, var(--primary) 10%, transparent);
  color: var(--foreground);
  box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--primary) 35%, transparent);
}
.control-status.is-failed {
  background: color-mix(in oklch, var(--destructive) 10%, transparent);
  color: var(--destructive);
}
/* The one moving thing on the page, so it reads as live rather than
   stuck. Reduced-motion callers get a static dot from the rule at the
   foot of this sheet. */
.control-pulse {
  width: .5rem; height: .5rem; border-radius: 50%;
  background: currentColor; animation: control-pulse 1.4s ease-in-out infinite;
}
@keyframes control-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .25 } }

.control-section + .control-section { margin-top: 1.5rem; }
.control-section > h2 { font-size: var(--text-sm); font-weight: var(--font-weight-medium);
                        color: var(--muted-foreground); margin: 0 0 .5rem; }

/* A proposal, which is the one thing on this page a person must act on
   — so it is the one thing drawn as a raised card rather than a row. */
.approval {
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: .875rem; margin-bottom: .75rem; background: var(--background);
}
.approval-head { display: flex; align-items: center; gap: .5rem;
                 margin-bottom: .5rem; }
.approval-tool { font-family: var(--font-mono); font-size: var(--text-sm);
                 font-weight: var(--font-weight-medium); }
.approval-actions { display: flex; gap: .5rem; margin-top: .75rem; }

/* What has already happened. A list, not cards: it is read by scanning
   and only opened when one line raises a question. */
.feed { list-style: none; margin: 0; padding: 0; }
.feed > li { padding: .6rem 0; border-bottom: 1px solid var(--border); }
.feed > li:last-child { border-bottom: 0; }
.feed-head { display: flex; align-items: center; gap: .5rem; }
.feed-tool { font-family: var(--font-mono); font-size: var(--text-sm); }
.feed-head > time { margin-left: auto; color: var(--muted-foreground);
                    font-size: var(--text-xs); font-family: var(--font-mono); }
.feed-note { color: var(--muted-foreground); font-size: var(--text-sm);
             margin-top: .25rem; }
.feed-note.is-error { color: var(--destructive); }
.feed-detail > summary { list-style: none; cursor: pointer;
                         color: var(--muted-foreground); font-size: var(--text-xs);
                         margin-top: .35rem; }
.feed-detail > summary::-webkit-details-marker { display: none; }
.feed-detail > summary:hover { color: var(--foreground); }

/* Arguments and results are JSON and programs are Lisp; both are read
   as text, wrap nowhere, and scroll on their own rather than widening
   the pane. */
.control-code {
  font-family: var(--font-mono); font-size: var(--text-xs);
  line-height: 1.5; margin: .5rem 0 0; padding: .6rem .7rem;
  background: var(--muted); border-radius: var(--radius);
  overflow-x: auto; white-space: pre; max-height: 22rem; overflow-y: auto;
}
.control-code.is-truncated::after {
  content: "… truncated"; display: block; margin-top: .5rem;
  color: var(--muted-foreground); font-style: italic;
}

/* ---- The conversation pane ---- */
.chat-log { display: flex; flex-direction: column; gap: .5rem; }
.chat-turn { padding: .6rem .75rem; border-radius: var(--radius);
             font-size: var(--text-sm); }
.chat-turn > .chat-who { font-size: var(--text-xs);
                         color: var(--muted-foreground); margin-bottom: .15rem; }
/* One block per line (see %CHAT-TEXT) rather than pre-wrap, so the
   markup's own indentation stays invisible. A blank line in the
   message is an empty block, which would collapse to nothing without
   a height of its own. */
.chat-line:empty { height: .7em; }
.chat-turn.from-user { background: var(--muted); margin-left: 1.5rem; }
.chat-turn.from-assistant { border: 1px solid var(--border); margin-right: 1.5rem; }
/* The assistant reaching for a tool, in the transcript where it happened.
   The outcome is the left pane's job; this is only the gesture. */
.chat-tool { margin-right: 1.5rem; }
/* Pinned to the pane's foot however tall the transcript grows. */
.chat-composer { display: flex; gap: .5rem; margin-top: auto; padding-top: 1rem; }
.chat-composer > input { flex: 1; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

}
