/*
 * The light fallback palette: surfaces, lines, ink, accent, status, avatar and code
 * colours, the focus ring, the state layers, and the elevation ladder and scrim drawn
 * in the theme's shadow colour. The engine writes the colour half on every switch.
 */
:root {
  color-scheme: light dark;

  /* ---- Surfaces -------------------------------------------------- */
  /*
   * The window frame behind the sheet.
   *
   * One step deeper than the canvas — toward black in a dark theme, toward the ink
   * in a light one — fitted by the engine so the sheet's edge reads against it.
   */
  --jelly-frame: #f3f3f4;
  /* The canvas the conversation is drawn on. */
  --jelly-bg: #ffffff;
  /* A surface that sits on the canvas: the cards, the panels, the popup sidebar. */
  --jelly-surface: #ffffff;
  /* A surface that is pushed back: the composer field and the quiet panels. */
  --jelly-surface-sunken: #f1f1f5;
  /* The list column, one shade away from the canvas. */
  --jelly-list-bg: #f4f3f8;
  /* What stands between the user and the window behind it. */
  --jelly-overlay: rgba(0, 0, 0, 0.42);

  /* ---- Lines ----------------------------------------------------- */
  --jelly-border: #e7e6ee;
  --jelly-border-strong: #d5d5d6;

  /* ---- Text ------------------------------------------------------ */
  --jelly-text: #15161c;
  /* The ink of the text a person has to read. */
  --jelly-text-muted: #5f6069;
  /*
   * The faintest ink.
   *
   * It carries the chrome that repeats what is already said — a timestamp, a
   * placeholder, an icon, a preview line — and never a sentence the reader needs.
   */
  --jelly-text-subtle: #8e8f9a;
  /* The ink on an identity ring; the avatar gradients carry it. */
  --jelly-text-inverse: #ffffff;

  /* ---- Inverse --------------------------------------------------- */
  /*
   * The ink and the canvas, swapped.
   *
   * The primary button, the person's own bubble and a checked switch: the loudest
   * neutral the palette has, so the accent can stay rare and keep its meaning.
   */
  --jelly-inverse: #15161c;
  --jelly-inverse-text: #ffffff;

  /* ---- Accent ---------------------------------------------------- */
  --jelly-accent: #5b4be0;
  --jelly-accent-hover: #4b3eb8;
  --jelly-accent-soft: #efedfc;
  /* The ink on the accent. */
  --jelly-accent-contrast: #ffffff;
  /*
   * A link in running text.
   *
   * It is a link in a sentence, so it has to be read: it clears 4.5:1 on every
   * light surface, including the sunken one a reply's bubble is drawn on.
   */
  --jelly-link: #2764bd;

  /* ---- Attention ------------------------------------------------- */
  /*
   * "Needs you": an approval, a question, a turn that is the person's.
   *
   * One warm colour, kept at least 40° of hue from the accent so it never reads as
   * "selected". It clears 3:1 on the roster as a mark and 4.5:1 on its own tint as
   * a word, and it is never the only carrier: a word always goes with it.
   */
  --jelly-attention: #8a5a00;
  --jelly-attention-soft: #ece5d6;

  /* ---- Status ---------------------------------------------------- */
  /*
   * The status colours are seeds; their tints are derived from them and the surface
   * they sit on, so a tint is always the colour it belongs to.
   */
  --jelly-success: #1c7748;
  --jelly-success-soft: #dfece5;
  --jelly-warning: #8a5a00;
  --jelly-warning-soft: #ece5d6;
  --jelly-danger: #c43129;
  --jelly-danger-soft: #f8e6e5;
  --jelly-info: #1f6c8c;
  --jelly-info-soft: #e4edf1;

  /* ---- Jelly avatar accents -------------------------------------- */
  /*
   * The identity ring. The slot names are historical, not descriptions: the
   * engine keeps every slot 30° of hue from the accent and 25° from the others,
   * rotating a palette's own colour when it has to.
   */
  --jelly-avatar-amber: #ab6600;
  --jelly-avatar-violet: #8f2cba;
  --jelly-avatar-teal: #0f6f6a;
  --jelly-avatar-rose: #b02a5b;
  --jelly-avatar-lime: #4c7a12;

  /* ---- Code ------------------------------------------------------ */
  /* The code surface is the ink at a whisper over the surface it sits on. */
  --jelly-code-text: #15161c;
  /*
   * The code surface of a reply.
   *
   * A fence of code in a message sits on the surface of the bubble, so it takes the
   * ink over the sunken surface rather than over the canvas: the frame reads as a
   * frame and not as a hole in the page.
   */
  --jelly-code-surface: #dfdfe4;
  --jelly-syntax-keyword: #5a4adc;
  --jelly-syntax-string: #1c7145;
  --jelly-syntax-comment: #60616a;
  --jelly-syntax-number: #885901;
  --jelly-syntax-function: #2661b7;
  --jelly-syntax-type: #1c7145;
  --jelly-syntax-property: #2661b7;
  --jelly-syntax-tag: #5a4adc;
  --jelly-syntax-attribute: #5f6069;
  --jelly-syntax-punctuation: #5f6069;
  --jelly-syntax-inserted: #1c7145;
  --jelly-syntax-deleted: #b62f28;

  /* ---- Quote ----------------------------------------------------- */
  /* The rule beside a quoted passage. */
  --jelly-quote-border: #bcbcc1;

  /* ---- Elevation: colour ----------------------------------------- */
  /* The one shadow colour a theme publishes; the ladder below is drawn in it. */
  --jelly-shadow-color: rgba(0, 0, 0, 0.16);

  /* ---- Focus ----------------------------------------------------- */
  /*
   * The ring is the accent at forty-five per cent, so a second focus colour
   * cannot disagree with the theme's accent.
   */
  --jelly-focus-ring-shadow: 0 0 0 var(--jelly-focus-ring-width)
    color-mix(in srgb, var(--jelly-accent) 45%, transparent);
  /*
   * The ring as drawn: nothing until the person travels by key, when base.css
   * switches it to the shadow above. Every ring draws this one, so a click that
   * moves the focus by script never rings.
   */
  --jelly-focus-ring: 0 0 0 0 transparent;

  /*
   * ---- Derived colour ---------------------------------------------
   *
   * Everything in this group is mixed from the engine's colour tokens and never
   * written by a theme, so every palette — built in, imported, or added next
   * month — gets tinted chrome without a second edit. They are declared on :root
   * beside the tokens they read, which is also where the engine writes those, so
   * they resolve against the theme that is painted.
   *
   * Fills and strokes are the ink with about a third of the accent in it, laid
   * over whatever surface is underneath (adapted from Hermes Agent, MIT). They
   * are translucent on purpose: one hover tint reads on the canvas, the roster
   * and a card alike.
   */
  /* Hover. */
  --jelly-fill-1: color-mix(
    in srgb,
    color-mix(in srgb, var(--jelly-accent) 33%, var(--jelly-text)) 3%,
    transparent
  );
  /* Pressed. */
  --jelly-fill-2: color-mix(
    in srgb,
    color-mix(in srgb, var(--jelly-accent) 33%, var(--jelly-text)) 5%,
    transparent
  );
  /* Selected; the person's own bubble. */
  --jelly-fill-3: color-mix(
    in srgb,
    color-mix(in srgb, var(--jelly-accent) 33%, var(--jelly-text)) 8%,
    transparent
  );
  /* A raised control on a filled surface. */
  --jelly-fill-4: color-mix(
    in srgb,
    color-mix(in srgb, var(--jelly-accent) 33%, var(--jelly-text)) 11%,
    transparent
  );
  /* The faintest hairline: the ring of a floating surface. */
  --jelly-stroke-1: color-mix(
    in srgb,
    color-mix(in srgb, var(--jelly-accent) 33%, var(--jelly-text)) 6%,
    transparent
  );
  /* A divider; the sheet's edge. */
  --jelly-stroke-2: color-mix(
    in srgb,
    color-mix(in srgb, var(--jelly-accent) 33%, var(--jelly-text)) 10%,
    transparent
  );
  /* A control's edge. */
  --jelly-stroke-3: color-mix(
    in srgb,
    color-mix(in srgb, var(--jelly-accent) 33%, var(--jelly-text)) 16%,
    transparent
  );
  /* A row under the pointer: painted at once, eased only on the way out. */
  --jelly-row-hover: var(--jelly-fill-2);
  /* The selected row; a leading bar carries the selection too, never colour alone. */
  --jelly-row-active: var(--jelly-fill-3);
  /*
   * The scaffolding of a turn: the receipt, the step list, the thinking header.
   *
   * The ink at sixty-four per cent over the canvas (Hermes Agent, MIT), so prose
   * reads before the machinery around it. It is chrome that repeats what can be
   * opened, so it is held to the faint floor (2.5:1), not the body floor; a
   * sentence the reader needs uses `--jelly-text-muted`.
   */
  --jelly-text-scaffold: color-mix(in srgb, var(--jelly-text) 64%, var(--jelly-bg));
  /* What stands behind a dialog: the overlay the theme publishes. */
  --jelly-scrim: var(--jelly-overlay);

  /*
   * ---- Elevation --------------------------------------------------
   *
   * Three levels, each a hairline ring plus a shadow in the theme's shadow colour.
   * In-page cards stay flat; only a thing that floats over the page is raised,
   * so a raised thing always means "this is above the page".
   */
  /* Menus, tooltips, popovers: the ring, a contact shadow and a short soft drop. */
  --jelly-elevation-1:
    0 0 0 var(--jelly-border-width) var(--jelly-stroke-1),
    0 1px 2px color-mix(in srgb, var(--jelly-shadow-color) 50%, transparent),
    0 4px 12px -2px color-mix(in srgb, var(--jelly-shadow-color) 70%, transparent);
  /*
   * The palette, toasts, the composer's stack: four layers from one light above,
   * each tucked in by its own spread so the shadow pools below the surface instead
   * of bleeding out every side (geometry adapted from Hermes Agent, MIT).
   */
  --jelly-elevation-2:
    0 0 0 var(--jelly-border-width) var(--jelly-stroke-1),
    0 2px 4px -2px color-mix(in srgb, var(--jelly-shadow-color) 45%, transparent),
    0 8px 12px -6px color-mix(in srgb, var(--jelly-shadow-color) 40%, transparent),
    0 20px 28px -14px color-mix(in srgb, var(--jelly-shadow-color) 40%, transparent),
    0 36px 48px -28px color-mix(in srgb, var(--jelly-shadow-color) 25%, transparent);
  /* Dialogs and the settings sheet: the same stack, deeper, over the scrim. */
  --jelly-elevation-3:
    0 0 0 var(--jelly-border-width) var(--jelly-stroke-1),
    0 2px 4px -2px color-mix(in srgb, var(--jelly-shadow-color) 50%, transparent),
    0 12px 20px -8px color-mix(in srgb, var(--jelly-shadow-color) 60%, transparent),
    0 28px 44px -16px color-mix(in srgb, var(--jelly-shadow-color) 70%, transparent),
    0 48px 80px -24px color-mix(in srgb, var(--jelly-shadow-color) 60%, transparent);
  /* How far the scrim softens what is behind a dialog. */
  --jelly-scrim-blur: 10px;
}

/* The theme-independent scale: line and focus-ring widths, spacing, radii and type. */
:root {
  /* ---- Lines: width ---------------------------------------------- */
  --jelly-border-width: 1px;

  /* ---- Focus: geometry ------------------------------------------- */
  --jelly-focus-ring-width: 2px;
  --jelly-focus-ring-offset: 2px;

  /* ---- Spacing scale --------------------------------------------- */
  --jelly-space-1: 2px;
  --jelly-space-2: 4px;
  --jelly-space-3: 6px;
  --jelly-space-4: 8px;
  --jelly-space-5: 12px;
  --jelly-space-6: 16px;
  --jelly-space-7: 20px;
  --jelly-space-8: 24px;
  --jelly-space-9: 32px;
  --jelly-space-10: 40px;
  --jelly-space-11: 48px;
  --jelly-space-12: 64px;

  /* ---- Radii ----------------------------------------------------- */
  /*
   * Nested surfaces are concentric: an inner radius is the outer radius less the
   * gap between them, and never below four pixels. The ladder below is built that
   * way — a card seated two pixels into the sheet, a control four pixels into a
   * card, a menu row four pixels into a menu — and the design test holds it.
   */
  --jelly-radius-xs: 3px;
  --jelly-radius-sm: 6px;
  --jelly-radius-md: 10px;
  --jelly-radius-pill: 999px;
  /* Buttons, fields and chips; a pill chip stays a pill. */
  --jelly-radius-control: 8px;
  /* Menus and popovers. */
  --jelly-radius-menu: 10px;
  --jelly-radius-tooltip: 6px;
  /* Cards, approval cards and settings groups. */
  --jelly-radius-card: 12px;
  /* The sheet the conversation sits on, dialogs and the palette. */
  --jelly-radius-sheet: 14px;
  /* The person's own bubble; the corner nearest the edge it speaks from is the tail. */
  --jelly-radius-bubble: 18px;
  --jelly-radius-bubble-tail: 6px;
  /* The chat bar is a pill: a fully rounded field on the canvas. */
  --jelly-radius-composer-bar: 28px;

  /* ---- Typography ------------------------------------------------ */
  --jelly-font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
  --jelly-font-mono:
    ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  /*
   * The display face, for three moments only: the empty conversation, the creator
   * and the first run. It falls back to the text face, so a missing font file is a
   * plainer heading and never a broken one.
   */
  --jelly-font-display: "Jelly Display", var(--jelly-font-sans);
  /* The overline and key caps; nothing a person reads as a sentence. */
  --jelly-font-size-xs: 11px;
  /* The floor for chrome a person reads: the caption. */
  --jelly-font-size-sm: 12px;
  /* The chrome body. */
  --jelly-font-size-md: 13px;
  /* What a thing is called: a row title. */
  --jelly-font-size-row-title: 14px;
  /* A page or a dialog. */
  --jelly-font-size-heading: 20px;
  --jelly-font-size-display: 40px;
  --jelly-font-size-display-sm: 32px;
  /*
   * The conversation's reading size.
   *
   * The transcript and the composer are read rather than scanned, so they sit one
   * pixel above the chrome body and take a looser leading.
   */
  --jelly-font-size-reading: 14px;
  /*
   * The conversation's reading scale.
   *
   * The neutral stop is the reading size. A person may choose a different stop in
   * Settings → Chat; the base below is the one font-size the transcript and the
   * composer set, and every text size inside them is a fraction of it. The default
   * is here so the base has a value before the column renders.
   */
  --jelly-reading-scale: 1;
  --jelly-reading-base: calc(var(--jelly-font-size-reading) * var(--jelly-reading-scale));
  --jelly-line-height-tight: 1.2;
  /* The leading of the chrome: the header, the roster, the lists. */
  --jelly-line-height-compact: 1.35;
  /* The leading of a heading in the prose, and of the prose's own blocks. */
  --jelly-line-height-normal: 1.5;
  --jelly-line-height-relaxed: 1.7;
  /* 22 / 14: the prose's leading, unitless so it grows with the reading scale. */
  --jelly-line-height-reading: 1.5714;
  /* The five chrome roles' line heights, named so no rule composes them. */
  --jelly-line-height-overline: 14px;
  --jelly-line-height-caption: 16px;
  --jelly-line-height-body: 18px;
  --jelly-line-height-row-title: 20px;
  --jelly-line-height-heading: 28px;
  --jelly-font-weight-regular: 400;
  --jelly-font-weight-medium: 500;
  --jelly-font-weight-semibold: 600;
  --jelly-font-weight-display: 650;
  --jelly-font-weight-bold: 700;
  --jelly-letter-spacing-tight: -0.01em;
  --jelly-letter-spacing-display: -0.02em;
  --jelly-letter-spacing-wide: 0.04em;
}

/* The layout measures, the control and row sizes, and the layer ladder. */
:root {
  /* ---- Layout ---------------------------------------------------- */
  --jelly-layout-roster-width: 292px;
  /*
   * The roster's header row is the window's top row: the sheet's top inset plus the
   * conversation header, so the roster's title, the traffic lights and the
   * conversation's title share one centre line. Media queries and the shell's
   * `trafficLightPosition` cannot read a custom property, so the sum is written out
   * and a test holds it to its parts.
   */
  --jelly-layout-roster-header-height: 48px;
  /*
   * The conversation header, the sheet's top row.
   *
   * Forty-four, so that the inset above it, the header and the transcript's first
   * twelve pixels stay inside the sixty the contract allows before the first message.
   */
  --jelly-layout-header-height: 44px;
  /* Eight lines of the composer, then it scrolls. */
  --jelly-layout-composer-max-height: 156px;
  /*
   * How much of the composer's stack stands over the transcript. Zero here; the
   * composer writes the measured height on the conversation at run time, and the
   * transcript keeps that much room at its end so the stack never covers the
   * last line (lane E).
   */
  --jelly-layout-composer-stack: 0px;
  --jelly-layout-inspector-width: 360px;
  --jelly-layout-inspector-min-width: 300px;
  --jelly-layout-inspector-max-width: 480px;
  --jelly-layout-inspector-drawer-width: 420px;
  --jelly-size-work-row: 44px;
  --jelly-size-work-row-compact: var(--jelly-size-control-md);
  --jelly-size-work-thumb: 72px;
  /*
   * How far the sheet stands in from the window's right and bottom edges.
   *
   * The frame shows in the gap, so the sheet reads as a sheet; on the roster side
   * the gap is zero and the two meet.
   */
  --jelly-layout-sheet-inset: 6px;
  /*
   * The sheet's top inset is smaller: the sheet starts just under the window's edge,
   * so the frame reads as a frame without spending the rows before the first message.
   */
  --jelly-layout-sheet-inset-top: 4px;
  /*
   * The clear space the macOS traffic lights need, in window points.
   *
   * The shell hides the title bar, so the lights float over the top-left corner of the
   * window. Nothing that matters may sit under them. Eighty-four is the lights' 8-point
   * inset, the macOS 26 cluster's 60 points and 16 points of air before the title
   * (`TRAFFIC_LIGHT_*` in the shell's `material.ts`; a test holds the sum). The lights
   * do not scale with the page, so the stylesheet divides this by the page's zoom.
   */
  --jelly-layout-traffic-light-space: 84px;
  /*
   * The narrowest rail that holds the lights, in window points: their inset, the
   * cluster and the same eight points of air on the far side.
   */
  --jelly-layout-traffic-light-rail: 76px;
  /*
   * The page zoom the shell has applied, as a plain factor. One in a browser, which
   * has no lights; the shell writes the real factor on the root before the first paint
   * and again whenever it changes, so the lights' clear space can be divided by it.
   */
  --jelly-layout-shell-zoom: 1;
  /* The measure of the transcript and of the composer, so the two share an edge. */
  --jelly-layout-transcript-max-width: 720px;
  /*
   * How tall a picture in the transcript may be.
   *
   * Half the measure: a 16:9 screenshot lands at about 640 by 360, which keeps
   * its detail readable without letting one image take the conversation.
   */
  --jelly-layout-attachment-max-height: 360px;
  --jelly-layout-modal-max-height: 80vh;
  /*
   * How tall a question may grow inside the composer's stack before it scrolls:
   * enough for four cards with their reasons, never the whole conversation.
   */
  --jelly-layout-question-max-height: 56vh;
  --jelly-layout-shortcut-help-width: 520px;
  /*
   * Where the settings modal stops being a rail and a pane.
   *
   * A container query cannot read a custom property either, so the stylesheet
   * repeats the two numbers; the token test keeps them in step. 720 is where the
   * 220-pixel navigation stops leaving a readable pane beside it; 420 is where a
   * label and its control can no longer share a line.
   */
  --jelly-layout-settings-narrow-width: 720px;
  --jelly-layout-settings-row-width: 420px;
  /*
   * The width below which the inspector becomes a drawer.
   * A media query cannot read a custom property, so the stylesheet repeats the number;
   * the token test keeps the two in step.
   */
  --jelly-layout-narrow-width: 1279px;
  --jelly-layout-roster-rail-width: 899px;
  --jelly-layout-compact-width: 880px;

  /* ---- Sizes ----------------------------------------------------- */
  /* The three control heights: a compact button, the default, and a roomy one. */
  --jelly-size-control-sm: 28px;
  --jelly-size-control-md: 32px;
  --jelly-size-control-lg: 36px;
  /* A settings row: one label and its control, with air above and below. */
  --jelly-size-settings-row: 44px;
  /* An agent beside a line of chrome: a toast, a palette row, a chip. */
  --jelly-size-avatar-small: 20px;
  --jelly-size-avatar-roster: 36px;
  --jelly-size-avatar-header: 36px;
  --jelly-size-avatar-message: 36px;
  --jelly-size-avatar-hero: 96px;
  --jelly-size-avatar-portrait: 144px;
  /*
   * The chat bar and the round send button inside it.
   *
   * The bar is a minimum, so a message that wraps grows the same pill. The button
   * sits six pixels in from the bar's ends, which is what keeps the two concentric:
   * 28 − 6 is the button's own radius of 22.
   */
  --jelly-size-composer-bar: 56px;
  --jelly-size-composer-send: 44px;
  /*
   * The rail down the side of a running row.
   *
   * The state of a row is a colour on its edge, and the edge is thick enough to be
   * seen from across the room without being a stripe.
   */
  --jelly-size-state-rail: 3px;

  /* ---- Layers ---------------------------------------------------- */
  /*
   * One ladder, with gaps of ten so a one-off can slot between two rungs without
   * renumbering the rest (the sparse ladder is adapted from Hermes Agent, MIT).
   * Everything that floats over the page is at least an overlay. A menu or
   * popover sits over a modal, because a control inside the modal can open one;
   * a toast reports over both; a tooltip is over everything, because it
   * describes whatever it sits on.
   */
  --jelly-z-raised: 10;
  --jelly-z-sticky: 20;
  --jelly-z-rail: 30;
  --jelly-z-drawer: 100;
  --jelly-z-overlay: 200;
  --jelly-z-modal: 210;
  --jelly-z-popover: 220;
  --jelly-z-toast: 230;
  --jelly-z-tooltip: 250;
}

/*
 * Motion: durations, easings and the two springs, whose curves `generate-tokens.ts`
 * writes from `src/motion/spring.ts`. `reduced-motion.css` zeroes them.
 */
:root {
  /* ---- Motion ---------------------------------------------------- */
  /*
   * Motion follows a change and never delays it. Hover paints at once and only
   * its return eases; exits are quicker than entrances; nothing is theme-driven.
   * Under reduced motion every duration below the tooltip delay is zero (see the
   * block in `reduced-motion.css`).
   */
  --jelly-duration-instant: 0ms;
  /* The way back from a hover (adapted from Hermes Agent, MIT). */
  --jelly-duration-hover-out: 100ms;
  --jelly-duration-fast: 120ms;
  /* Something leaving: a menu, a toast, a dialog. */
  --jelly-duration-exit: 150ms;
  --jelly-duration-base: 200ms;
  /* Something arriving: an overlay, a menu, a disclosure opening. */
  --jelly-duration-enter: 220ms;
  --jelly-duration-slow: 320ms;
  /* How long the pointer rests on a control before its tooltip shows. Not motion. */
  --jelly-duration-tooltip-delay: 400ms;
  --jelly-duration-work-linger: 4000ms;
  /* The slow sweep of a running row. */
  --jelly-duration-shimmer: 2200ms;
  /* One slow breath of a working agent; its tentacles and a waiting halo share it. */
  --jelly-duration-breath: 3200ms;
  --jelly-ease-standard: cubic-bezier(0.2, 0, 0.2, 1);
  /* A decisive arrival. */
  --jelly-ease-entrance: cubic-bezier(0.16, 1, 0.3, 1);
  /* Arrive and settle: disclosures and layout (Hermes Agent, MIT). */
  --jelly-ease-settle: cubic-bezier(0.22, 1, 0.36, 1);
  /* Leave gently and get out of the way (Hermes Agent, MIT). */
  --jelly-ease-exit: cubic-bezier(0.4, 0, 0.7, 0.2);
  /*
   * The agent springs, solved in `src/motion/spring.ts` and written here by
   * `scripts/generate-tokens.ts`; the token test fails if the two disagree. The
   * soft one settles without overshoot (panels); the pop overshoots by about
   * seven per cent and comes back once (a sent message, a badge).
   */
  --jelly-ease-spring-soft: linear(
    0,
    0.0619,
    0.1923,
    0.3389,
    0.4769,
    0.596,
    0.6935,
    0.7706,
    0.8303,
    0.8756,
    0.9095,
    0.9346,
    0.953,
    0.9663,
    0.976,
    0.983,
    0.9879,
    0.9915,
    0.994,
    0.9958,
    0.9971,
    0.9979,
    0.9986,
    1
  );
  --jelly-ease-spring-pop: linear(
    0,
    0.0542,
    0.1834,
    0.3469,
    0.5155,
    0.6703,
    0.8008,
    0.903,
    0.9771,
    1.026,
    1.0541,
    1.0664,
    1.0674,
    1.0613,
    1.0513,
    1.0398,
    1.0286,
    1.0187,
    1.0105,
    1.0044,
    1,
    0.9973,
    0.9959,
    0.9954,
    0.9955,
    0.9961,
    0.9968,
    0.9976,
    0.9983,
    0.999,
    0.9995,
    1
  );
}

/* The dark fallback: follows the operating system unless the boot script pinned light. */
@media (prefers-color-scheme: dark) {
  :root:not([data-appearance="light"]) {
    color-scheme: dark;

    /* ---- Surfaces ------------------------------------------------ */
    --jelly-frame: #0f0f13;
    --jelly-bg: #1a1a20;
    --jelly-surface: #22222a;
    --jelly-surface-sunken: #17171c;
    --jelly-list-bg: #131318;
    --jelly-overlay: rgba(0, 0, 0, 0.62);

    /* ---- Lines --------------------------------------------------- */
    --jelly-border: #33333d;
    --jelly-border-strong: #48484e;

    /* ---- Text ---------------------------------------------------- */
    --jelly-text: #ededf2;
    --jelly-text-muted: #a3a3af;
    --jelly-text-subtle: #787884;
    --jelly-text-inverse: #000000;

    /* ---- Inverse ------------------------------------------------- */
    --jelly-inverse: #ededf2;
    --jelly-inverse-text: #1a1a20;

    /* ---- Accent -------------------------------------------------- */
    --jelly-accent: #9b8cff;
    --jelly-accent-hover: #ada1ff;
    --jelly-accent-soft: #312f44;
    --jelly-accent-contrast: #000000;
    --jelly-link: #7fb0ff;

    /* ---- Attention ----------------------------------------------- */
    --jelly-attention: #e0b24a;
    --jelly-attention-soft: #40392f;

    /* ---- Status -------------------------------------------------- */
    --jelly-success: #5bc98c;
    --jelly-success-soft: #2a3938;
    --jelly-warning: #e0b24a;
    --jelly-warning-soft: #40392f;
    --jelly-danger: #f08c86;
    --jelly-danger-soft: #3b2f35;
    --jelly-info: #6fc0dc;
    --jelly-info-soft: #2b353f;

    /* ---- Jelly avatar accents ------------------------------------ */
    --jelly-avatar-amber: #e0a33c;
    --jelly-avatar-violet: #cf7de3;
    --jelly-avatar-teal: #4fd0c4;
    --jelly-avatar-rose: #ff8fb4;
    --jelly-avatar-lime: #9ed45f;

    /* ---- Code ---------------------------------------------------- */
    --jelly-code-text: #ededf2;
    --jelly-code-surface: #28282d;
    --jelly-syntax-keyword: #9b8cff;
    --jelly-syntax-string: #5bc98c;
    --jelly-syntax-comment: #8f8f9a;
    --jelly-syntax-number: #e0b24a;
    --jelly-syntax-function: #7fb0ff;
    --jelly-syntax-type: #5bc98c;
    --jelly-syntax-property: #7fb0ff;
    --jelly-syntax-tag: #9b8cff;
    --jelly-syntax-attribute: #a3a3af;
    --jelly-syntax-punctuation: #a3a3af;
    --jelly-syntax-inserted: #5bc98c;
    --jelly-syntax-deleted: #f08c86;

    /* ---- Quote --------------------------------------------------- */
    --jelly-quote-border: #4a4a4f;

    /* ---- Elevation ----------------------------------------------- */
    --jelly-shadow-color: rgba(0, 0, 0, 0.55);

    /* ---- Focus --------------------------------------------------- */
    --jelly-focus-ring-shadow: 0 0 0 var(--jelly-focus-ring-width)
      color-mix(in srgb, var(--jelly-accent) 45%, transparent);
  }
}

/* The dark fallback: explicitly requested by the boot script or the product shell. */
:root[data-appearance="dark"] {
  color-scheme: dark;

  /* ---- Surfaces -------------------------------------------------- */
  --jelly-frame: #0f0f13;
  --jelly-bg: #1a1a20;
  --jelly-surface: #22222a;
  --jelly-surface-sunken: #17171c;
  --jelly-list-bg: #131318;
  --jelly-overlay: rgba(0, 0, 0, 0.62);

  /* ---- Lines ----------------------------------------------------- */
  --jelly-border: #33333d;
  --jelly-border-strong: #48484e;

  /* ---- Text ------------------------------------------------------ */
  --jelly-text: #ededf2;
  --jelly-text-muted: #a3a3af;
  --jelly-text-subtle: #787884;
  --jelly-text-inverse: #000000;

  /* ---- Inverse --------------------------------------------------- */
  --jelly-inverse: #ededf2;
  --jelly-inverse-text: #1a1a20;

  /* ---- Accent ---------------------------------------------------- */
  --jelly-accent: #9b8cff;
  --jelly-accent-hover: #ada1ff;
  --jelly-accent-soft: #312f44;
  --jelly-accent-contrast: #000000;
  --jelly-link: #7fb0ff;

  /* ---- Attention ------------------------------------------------- */
  --jelly-attention: #e0b24a;
  --jelly-attention-soft: #40392f;

  /* ---- Status ---------------------------------------------------- */
  --jelly-success: #5bc98c;
  --jelly-success-soft: #2a3938;
  --jelly-warning: #e0b24a;
  --jelly-warning-soft: #40392f;
  --jelly-danger: #f08c86;
  --jelly-danger-soft: #3b2f35;
  --jelly-info: #6fc0dc;
  --jelly-info-soft: #2b353f;

  /* ---- Jelly avatar accents -------------------------------------- */
  --jelly-avatar-amber: #e0a33c;
  --jelly-avatar-violet: #cf7de3;
  --jelly-avatar-teal: #4fd0c4;
  --jelly-avatar-rose: #ff8fb4;
  --jelly-avatar-lime: #9ed45f;

  /* ---- Code ------------------------------------------------------ */
  --jelly-code-text: #ededf2;
  --jelly-code-surface: #28282d;
  --jelly-syntax-keyword: #9b8cff;
  --jelly-syntax-string: #5bc98c;
  --jelly-syntax-comment: #8f8f9a;
  --jelly-syntax-number: #e0b24a;
  --jelly-syntax-function: #7fb0ff;
  --jelly-syntax-type: #5bc98c;
  --jelly-syntax-property: #7fb0ff;
  --jelly-syntax-tag: #9b8cff;
  --jelly-syntax-attribute: #a3a3af;
  --jelly-syntax-punctuation: #a3a3af;
  --jelly-syntax-inserted: #5bc98c;
  --jelly-syntax-deleted: #f08c86;

  /* ---- Quote ----------------------------------------------------- */
  --jelly-quote-border: #4a4a4f;

  /* ---- Elevation ------------------------------------------------- */
  --jelly-shadow-color: rgba(0, 0, 0, 0.55);

  /* ---- Focus ----------------------------------------------------- */
  --jelly-focus-ring-shadow: 0 0 0 var(--jelly-focus-ring-width)
    color-mix(in srgb, var(--jelly-accent) 45%, transparent);
}

/* Light palette: explicitly requested by the boot script or the product shell. */
:root[data-appearance="light"] {
  color-scheme: light;
}

/*
 * Less motion.
 *
 * The stylesheets already zero every animation and transition under this
 * preference; the tokens follow, so a script that reads a duration to wait for
 * an exit (presence, a toast) waits for nothing, and a spring is a step. The
 * tooltip delay is not motion and stays; the shimmer's own rules stop it.
 */
@media (prefers-reduced-motion: reduce) {
  :root {
    --jelly-duration-hover-out: 0ms;
    --jelly-duration-fast: 0ms;
    --jelly-duration-exit: 0ms;
    --jelly-duration-base: 0ms;
    --jelly-duration-enter: 0ms;
    --jelly-duration-work-linger: 0ms;
    --jelly-duration-slow: 0ms;
    --jelly-duration-breath: 0ms;
    --jelly-ease-spring-soft: linear;
    --jelly-ease-spring-pop: linear;
  }
}

/*
 * High contrast.
 *
 * Derived from the theme's own foreground rather than enumerated per palette, so a
 * theme added next month gets the switch without anybody remembering to add it.
 * Four tokens, and no colour of their own: the border and the muted ink are mixed
 * from `--jelly-text` and the canvas the theme already chose, which is why this works
 * in light and dark and under every accent. The derived hairlines and the scaffold
 * ink are raised with them, because a tinted stroke at six per cent is exactly the
 * kind of edge a person asking for contrast cannot see.
 *
 * The focus ring is already 2px (`--jelly-focus-ring-width`), so the switch has
 * nothing to thicken; it only makes the rest of the interface catch up with it.
 *
 * This block is last on purpose: it has the same specificity as a theme block, and a
 * reader who asked for contrast must win over the palette's own answer.
 */
[data-contrast="high"] {
  --jelly-border: color-mix(in oklab, var(--jelly-text) 30%, var(--jelly-bg));
  --jelly-border-strong: color-mix(in oklab, var(--jelly-text) 45%, var(--jelly-bg));
  --jelly-text-muted: color-mix(in oklab, var(--jelly-text) 88%, var(--jelly-bg));
  --jelly-text-subtle: color-mix(in oklab, var(--jelly-text) 78%, var(--jelly-bg));
  --jelly-stroke-1: color-mix(in oklab, var(--jelly-text) 24%, transparent);
  --jelly-stroke-2: color-mix(in oklab, var(--jelly-text) 30%, transparent);
  --jelly-stroke-3: color-mix(in oklab, var(--jelly-text) 45%, transparent);
  --jelly-text-scaffold: color-mix(in oklab, var(--jelly-text) 88%, var(--jelly-bg));
}

/*
 * The front door's own layout, on the Classic theme's tokens.
 *
 * The tokens arrive first in the same stylesheet (`assets.ts`), so every
 * colour, size, radius and duration here is the app's: light and dark follow
 * the operating system exactly as the app's first paint does. The look is the
 * connect gate's: the mark in its own light, the headline in the display face,
 * then one card.
 */

@font-face {
  font-family: "Jelly Display";
  font-style: normal;
  font-weight: 600 700;
  font-display: swap;
  src: url("/_door/display.woff2") format("woff2");
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  background: var(--jelly-bg);
}

body {
  margin: 0;
  font-family: var(--jelly-font-sans);
  font-size: var(--jelly-font-size-md);
  line-height: var(--jelly-line-height-normal);
  color: var(--jelly-text);
  -webkit-font-smoothing: antialiased;
}

.door {
  display: flex;
  min-height: 100vh;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--jelly-space-8);
  /* The mark's light, spilling over the top of the page. */
  background: radial-gradient(ellipse at 50% 0%, var(--jelly-accent-soft) 0%, transparent 60%);
}

.door__panel {
  display: flex;
  width: min(460px, 100%);
  flex-direction: column;
  align-items: stretch;
  gap: var(--jelly-space-6);
}

.door__head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--jelly-space-4);
  text-align: center;
}

.door__mark {
  display: block;
  width: var(--jelly-size-avatar-portrait);
  height: var(--jelly-size-avatar-portrait);
  margin-bottom: var(--jelly-space-2);
  filter: drop-shadow(0 var(--jelly-space-3) var(--jelly-space-6) var(--jelly-shadow-color));
  animation: door-drift var(--jelly-duration-breath) var(--jelly-ease-standard) infinite alternate;
}

@keyframes door-drift {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(calc(var(--jelly-space-2) * -1));
  }
}

.door__title {
  margin: 0;
  font-family: var(--jelly-font-display);
  font-size: var(--jelly-font-size-display-sm);
  font-weight: var(--jelly-font-weight-display);
  letter-spacing: var(--jelly-letter-spacing-display);
  line-height: var(--jelly-line-height-tight);
}

.door__lede {
  margin: 0;
  color: var(--jelly-text-muted);
  line-height: var(--jelly-line-height-relaxed);
}

.door__card {
  display: flex;
  flex-direction: column;
  gap: var(--jelly-space-4);
  padding: var(--jelly-space-6);
  border: var(--jelly-border-width) solid var(--jelly-stroke-1);
  border-radius: var(--jelly-radius-card);
  background: var(--jelly-fill-1);
  box-shadow: var(--jelly-elevation-1);
}

.door__card form {
  margin: 0;
}

.door__note {
  margin: 0;
  color: var(--jelly-text-subtle);
  font-size: var(--jelly-font-size-sm);
  text-align: center;
}

.door__note code {
  font-family: var(--jelly-font-mono);
  font-size: var(--jelly-font-size-sm);
}

.door__button {
  display: inline-flex;
  width: 100%;
  min-height: var(--jelly-size-control-lg);
  align-items: center;
  justify-content: center;
  padding: 0 var(--jelly-space-5);
  border: 0;
  border-radius: var(--jelly-radius-control);
  background: var(--jelly-accent);
  color: var(--jelly-accent-contrast);
  font: inherit;
  font-weight: var(--jelly-font-weight-semibold);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--jelly-duration-fast) var(--jelly-ease-standard);
}

.door__button:hover {
  background: var(--jelly-accent-hover);
}

.door__button:focus-visible,
.door__host:focus-visible,
.door__link:focus-visible {
  outline: var(--jelly-focus-ring-width) solid var(--jelly-focus-ring);
  outline-offset: var(--jelly-focus-ring-offset);
}

/* The chooser: one row per host, the whole row the link. */
.door__hosts {
  display: flex;
  flex-direction: column;
  gap: var(--jelly-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.door__host {
  display: flex;
  align-items: center;
  gap: var(--jelly-space-4);
  padding: var(--jelly-space-4) var(--jelly-space-5);
  border: var(--jelly-border-width) solid var(--jelly-stroke-1);
  border-radius: var(--jelly-radius-md);
  background: var(--jelly-surface);
  color: var(--jelly-text);
  text-decoration: none;
  transition: background var(--jelly-duration-fast) var(--jelly-ease-standard);
}

a.door__host:hover {
  background: var(--jelly-row-hover);
}

.door__host-text {
  display: flex;
  min-width: 0;
  flex: 1;
  flex-direction: column;
  gap: var(--jelly-space-1);
}

.door__host-name {
  overflow: hidden;
  font-size: var(--jelly-font-size-row-title);
  font-weight: var(--jelly-font-weight-semibold);
  line-height: var(--jelly-line-height-row-title);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.door__host-meta {
  overflow: hidden;
  color: var(--jelly-text-muted);
  font-size: var(--jelly-font-size-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A state is a word beside its dot, never the colour alone. */
.door__state {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: var(--jelly-space-2);
  color: var(--jelly-text-muted);
  font-size: var(--jelly-font-size-sm);
}

.door__state::before {
  width: var(--jelly-space-2);
  height: var(--jelly-space-2);
  border-radius: var(--jelly-radius-pill);
  background: var(--jelly-success);
  content: "";
}

.door__state--waiting::before {
  background: var(--jelly-warning);
  animation: door-pulse var(--jelly-duration-breath) var(--jelly-ease-standard) infinite alternate;
}

.door__open {
  flex: none;
  color: var(--jelly-link);
  font-weight: var(--jelly-font-weight-medium);
}

@keyframes door-pulse {
  from {
    opacity: 1;
  }
  to {
    opacity: 0.35;
  }
}

/* Setting up: the waiting line under the lede. */
.door__waiting {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--jelly-space-2);
}

.door__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--jelly-space-2) var(--jelly-space-3);
  color: var(--jelly-text-subtle);
  font-size: var(--jelly-font-size-sm);
}

.door__foot form {
  margin: 0;
}

.door__link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--jelly-link);
  font: inherit;
  text-decoration: underline;
  text-underline-offset: var(--jelly-space-1);
  cursor: pointer;
}

@media (max-width: 520px) {
  .door {
    padding: var(--jelly-space-4);
  }
}

@media (prefers-reduced-motion: reduce) {
  .door__mark,
  .door__state--waiting::before {
    animation: none;
  }
}
