/* THE POST CARDS SHEET at /design/post-cards (design/post_cards.js). The scaffolding only: the
   cards are the post card wearing its own classes, and what is here lays them out and draws the
   frames they sit in.

   ROOTED AT .ob-pcsheet, and every class under it starts pc-. This file is in the frontend
   bundle and loads on every page, so a bare class here would land on the product too (the
   collision okaybot.css records). */

.ob-pcsheet {
    max-width: 1480px;
    margin: 0 auto;
    padding: 32px 28px 80px;
    font-family: var(--ob-font);
    color: var(--ob-ink);
    background: var(--ob-canvas);

    /* the widths the state grid and its header share: the label, then the three presets in the
       proportions they are drawn at in the product */
    --pc-cols: 190px minmax(0, .8fr) minmax(0, .9fr) minmax(0, 1.5fr);

    code {
        font-family: var(--ob-mono);
        font-size: 11.5px;
        font-weight: 500;
        color: var(--ob-ink-2);
        background: var(--ob-card);
        border: 1px solid var(--ob-line);
        border-radius: 5px;
        padding: 0 5px;
        text-transform: none;
        letter-spacing: 0;
    }

    .pc-head { margin-bottom: 32px; }
    .pc-back { font-size: 12.5px; font-weight: 700; color: var(--ob-muted); text-decoration: none; }
    .pc-back:hover { color: var(--ob-accent-ink); }
    .pc-head .ob-title { margin: 8px 0 8px; }
    .pc-sub { margin: 0; max-width: 80ch; font-size: 13.5px; line-height: 1.55; color: var(--ob-muted); }

    .pc-sec { margin-bottom: 48px; }

    .pc-fname { display: flex; align-items: center; gap: 8px; }
    .pc-fname b { font-size: 15px; font-weight: 800; letter-spacing: -.01em; }
    .pc-where { font-size: 12px; font-weight: 700; line-height: 1.45; color: var(--ob-ink-2); }
    .pc-what { font-size: 12.5px; line-height: 1.5; color: var(--ob-muted); }
    .pc-flags { font-family: var(--ob-mono); font-size: 11px; line-height: 1.5; color: var(--ob-faint-1); }

    /* ---- the card and its switches ---- */
    .pc-one {
        display: flex;
        flex-direction: column;
        gap: 14px;
        padding: 16px;
        background: var(--ob-card);
        border: 1px solid var(--ob-line);
        border-radius: var(--ob-radius);
    }
    .pc-onehead { display: flex; flex-direction: column; gap: 8px; max-width: 80ch; }
    .pc-onectl { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 22px; }
    .pc-ctlset { display: flex; align-items: center; gap: 10px; }
    .pc-ctlname { font-size: 12px; font-weight: 700; color: var(--ob-muted); }

    /* one card per look, at the width the chosen size has in the product */
    .pc-onerow {
        display: grid;
        grid-template-columns: repeat(auto-fill, 190px);
        gap: 22px 18px;
        align-items: start;
        padding: 14px;
        border-radius: 12px;
        background: var(--ob-canvas);
    }
    .pc-onerow[data-size="sm"] { grid-template-columns: repeat(auto-fill, 210px); }
    .pc-onerow[data-size="md"] { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }

    /* ---- where each preset is drawn ---- */
    .pc-formats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; align-items: start; }

    .pc-format {
        display: flex;
        flex-direction: column;
        gap: 8px;
        min-width: 0;
        padding: 16px;
        background: var(--ob-card);
        border: 1px solid var(--ob-line);
        border-radius: var(--ob-radius);
    }

    /* the cards stand on the page's grey inside the white tile, because that is the ground they
       are drawn on in the product and the rail's cards are rimmed in it */
    .pc-samples {
        display: flex;
        flex-direction: column;
        gap: 14px;
        margin-top: 6px;
        padding: 12px;
        border-radius: 12px;
        background: var(--ob-canvas);
    }

    /* ---- every state ---- */
    .pc-cols, .pc-line { display: grid; grid-template-columns: var(--pc-cols); gap: 18px; }

    .pc-cols {
        position: sticky;
        top: 0;
        z-index: 60;
        padding: 10px 0;
        background: var(--ob-canvas);
        border-bottom: 1px solid var(--ob-line);
        font-size: 11px;
        font-weight: 700;
        letter-spacing: .08em;
        text-transform: uppercase;
        color: var(--ob-muted);

        span { display: flex; align-items: center; gap: 7px; min-width: 0; white-space: nowrap; overflow: hidden; }
    }

    .pc-group {
        display: flex;
        align-items: center;
        gap: 10px;
        margin: 30px 0 4px;
        padding-bottom: 10px;
        border-bottom: 1px solid var(--ob-line);
        font-size: 12.5px;
        color: var(--ob-muted);

        code { font-size: 12.5px; font-weight: 700; color: var(--ob-ink); }
    }

    /* the tone, as the calendar's legend draws it */
    .pc-swatch { width: 12px; height: 12px; border-radius: 4px; flex: 0 0 auto; }
    .pc-swatch.wait { background: var(--ob-butter-bg); box-shadow: inset 0 0 0 1px var(--ob-butter-edge); }
    .pc-swatch.going { background: var(--ob-card); box-shadow: inset 0 0 0 1.5px var(--ob-accent-ring); }
    .pc-swatch.held { background: var(--ob-card); box-shadow: inset 0 0 0 1.5px var(--ob-butter-ink); }
    .pc-swatch.went { background: var(--ob-mint-bg); }
    .pc-swatch.gone { background: var(--ob-card); box-shadow: inset 0 0 0 1px var(--ob-grey-edge); }
    .pc-swatch.stop { background: var(--ob-rose-bg); }

    .pc-line {
        align-items: start;
        padding: 14px 0;
        border-bottom: 1px dashed var(--ob-line);
    }

    .pc-label { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 0; }
    .pc-label b { font-size: 13.5px; font-weight: 800; }
    .pc-word { font-size: 12px; font-weight: 600; color: var(--ob-ink-2); }
    .pc-aside { font-size: 11.5px; line-height: 1.4; color: var(--ob-muted); }

    /* ---- a frame of cards ---- */
    .pc-cell { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
    .pc-note { font-size: 11px; font-weight: 600; line-height: 1.4; color: var(--ob-muted); }

    .pc-never {
        display: flex;
        align-items: center;
        min-height: 30px;
        padding: 6px 10px;
        border: 1.5px dashed var(--ob-line);
        border-radius: 10px;
        font-size: 11.5px;
        font-weight: 600;
        line-height: 1.4;
        color: var(--ob-faint-1);
    }

    /* one cell of the month: the real .ob-cal .cell, without the grid's fixed row height */
    .pc-month { border-radius: 12px; }
    .pc-month .cell { border: 0; }

    /* a stretch of the rail: the real .ob-calrail, only as tall as it is told, so a card is placed
       down it by its top exactly as on a day. The tall one draws the hour lines */
    .ob-calrail.pc-rail { position: relative; height: var(--pc-h, 30px); overflow: visible; }
    .ob-calrail.pc-rail.tall {
        border-radius: 8px;
        background: repeating-linear-gradient(to bottom, transparent 0 37px, var(--ob-line) 37px 38px);
    }
}

body:not(.ob-app) .ob-pcsheet { min-height: 100vh; }

/* the medium tier takes the places a column narrower; the grid of states keeps its columns */
@media (max-width: 1599px) {
    .ob-pcsheet .pc-formats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .ob-pcsheet { --pc-cols: 150px minmax(0, .8fr) minmax(0, .9fr) minmax(0, 1.4fr); }
}
 /* The atom sheet's own scaffolding: a canvas page that lays the atoms out in rows. Nothing
   here is a product class; the atoms carry their own.

   ITS ROOT IS .ob-atomsheet, NOT .ob-sheet (2026-09-23). This file is in the frontend bundle, so
   it loads on every page, and .ob-sheet is also the homepage's two comparison boxes
   (marketing.html, okaybot-site.css). The auto margins here shrank both boxes to their own
   widths, and the min-height below made each one a whole screen tall for anyone signed out. */

.ob-atomsheet {
    max-width: 1080px;
    margin: 0 auto;
    padding: 40px 28px 80px;
    font-family: var(--ob-font);
    color: var(--ob-ink);
    background: var(--ob-canvas);

    .ob-sheet-head { display: flex; align-items: baseline; gap: 14px; margin-bottom: 30px; }
    .ob-sheet-sec { margin-bottom: 34px; }
    .ob-sheet-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
    .ob-sheet-row.wide { gap: 18px; }
    .ob-sheet-row.ramp { gap: 26px; }
    .ob-sheet-row.ramp .amt { font-size: 15px; font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
    .ob-sheet-row.ramp .c1 { color: var(--ob-c1); font-weight: var(--ob-c1-w); }
    .ob-sheet-row.ramp .c2 { color: var(--ob-c2); font-weight: var(--ob-c2-w); }
    .ob-sheet-row.ramp .c3 { color: var(--ob-c3); font-weight: var(--ob-c3-w); }
    .ob-sheet-row.ramp .c4 { color: var(--ob-c4); font-weight: var(--ob-c4-w); }
    .ob-sheet-row.ramp .c5 { color: var(--ob-c5); font-weight: var(--ob-c5-w); }
    .ob-sheet-row.ramp .cx { color: var(--ob-cx); font-weight: var(--ob-cx-w); text-decoration: line-through; text-decoration-thickness: 1.5px; }

    /* every harness mark, at the two sizes that decide whether it works: the card and the rail */
    .glyphs { display: flex; flex-wrap: wrap; gap: 8px; }
    .glyphs .one { width: 104px; display: flex; flex-direction: column; align-items: center; gap: 7px; padding: 12px 4px 10px; border-radius: 12px; background: var(--ob-card); border: 1px solid var(--ob-line); }
    .glyphs .pair { display: flex; align-items: center; gap: 7px; }
    .glyphs .nm { font-size: 10px; line-height: 1.3; color: var(--ob-muted); text-align: center; }

    .swatches { display: flex; flex-wrap: wrap; gap: 8px; }
    .sw { width: 84px; height: 56px; border-radius: 12px; display: flex; align-items: flex-end; padding: 6px 8px; font-size: 11px; font-weight: 700; }

}

body:not(.ob-app) .ob-atomsheet { min-height: 100vh; }
 /*
 * Shift Design System - Form Controls
 * Labels
 *
 * Requires: shift-core.css (tokens)
 */

/* ============================================
   LABELS
   ============================================ */

/* Labels - Friendly */
.label {
    display: block;
    margin-bottom: var(--spacing-2);
    font-size: var(--font-sm);
    font-weight: var(--font-bold);
    color: var(--text-primary);
}
 /*
 * Shift Design System - Cards
 * Use sparingly - only for truly grouped content
 *
 * Requires: shift-core.css (tokens)
 */

/* ============================================
   CARDS - Use Sparingly!
   Only for truly grouped content
   ============================================ */

.card {
    position: relative;
    background: var(--bg-surface);
    border-radius: var(--radius-2xl);
    padding: var(--spacing-6);
    box-shadow: var(--shadow-md);
}
 /*
 * Shift Design System - Buttons
 * Friendly, pill-shaped, bouncy buttons
 *
 * Requires: shift-core.css (tokens)
 */

/* ============================================
   BUTTONS - Friendly and Bouncy
   ============================================ */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-2);
    height: var(--button-height-md);
    padding: 0 var(--spacing-6);
    border: none;
    border-radius: var(--radius-full);  /* Pill shaped! */
    font-family: inherit;
    font-size: var(--font-sm);
    font-weight: var(--font-semibold);
    line-height: 1;
    cursor: pointer;
    transition: all var(--duration-fast) var(--easing-bounce);
    transform: translateY(0);
    position: relative;
    overflow: hidden;
}

.btn:active:not(:disabled) {
    transform: translateY(-1px) scale(0.98);
}

.btn:focus-visible {
    outline: var(--focus-ring-width) solid var(--accent-primary);
    outline-offset: var(--focus-ring-offset);
}

.btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn-xs {
    height: var(--button-height-xs);
    padding: 0 var(--spacing-3);
    font-size: var(--font-xs);
}

.btn-sm {
    height: var(--button-height-sm);
    padding: 0 var(--spacing-4);
    font-size: var(--font-sm);
}

.btn-primary {
    background: var(--action-primary-bg);
    color: var(--action-primary-text);
    box-shadow: var(--shadow-sm);
}

.btn-primary:hover:not(:disabled) {
    background: var(--action-primary-bg-hover);
}

.btn-primary:active:not(:disabled) {
    background: var(--action-primary-bg-active);
    box-shadow: var(--shadow-xs);
}

.btn-secondary {
    background: var(--action-secondary-bg);
    color: var(--action-secondary-text);
    border: none;
    box-shadow: 0 0 28px var(--action-secondary-glow) inset;
}

.btn-secondary:hover:not(:disabled) {
    background: var(--action-secondary-bg-hover);
}

.btn-secondary:active:not(:disabled) {
    background: var(--action-secondary-bg-active);
}
  .brand {
    display: flex;
    align-items: center;
    gap: var(--spacing-3);
    position: relative;
    z-index: 1;
} /* ============================================================
   Legal
   Long-form prose pages (/terms, /privacy) whose body is
   markdown rendered to HTML by the Md module. The prose itself
   is .ob-prose.legal, styled by element in okaybot-site.css;
   this is only the column's width and its foot.
   ============================================================ */

.legal {
    max-width: 46rem;
    padding-bottom: var(--spacing-24);
}
 /* ============================================================
   Site Footer
   Faint legal links pinned to the bottom of the viewport.

   #Main is absolutely positioned and fills the viewport, so a
   footer in normal flow would never be visible. This one is
   fixed instead, and stays click-through except on the links
   themselves so it can never intercept a click meant for the
   page underneath.
   ============================================================ */

.site-footer {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 10;               /* above body::before (0) */

    display: flex;
    justify-content: center;
    align-items: center;
    gap: var(--spacing-2);

    padding: var(--spacing-4);
    font-size: var(--font-xs);
    color: var(--text-tertiary);

    pointer-events: none;      /* the bar itself never blocks the page */
}

.site-footer a {
    pointer-events: auto;      /* but the links are still clickable */
    color: inherit;
    text-decoration: none;
    opacity: 0.75;
    padding: var(--spacing-1) var(--spacing-1);
    border-radius: var(--radius-sm);
    transition: opacity var(--duration-fast) var(--easing-default),
                color var(--duration-fast) var(--easing-default);
}

.site-footer a:hover {
    opacity: 1;
    color: var(--text-secondary);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.site-footer a:focus-visible {
    opacity: 1;
    outline: var(--focus-ring-width) solid var(--accent-500);
    outline-offset: 2px;
}

.site-footer span {
    opacity: 0.5;
}
 /*
 * Playful Design System v2 - Core
 * "Delightfully Simple" - More playful, less busy
 *
 * Key Philosophy Changes from v1:
 * - Forms don't need cards - let them breathe on the page
 * - Use whitespace and typography for hierarchy, not borders
 * - Add personality through illustrations and micro-animations
 * - Warmer, softer colors with playful gradients
 * - More conversational, fun microcopy encouraged
 * - Subtle decorative elements (blobs, gradients, doodles)
 *
 * Component stylesheets (import alongside this file):
 *   buttons.css  - .btn, sizes xs and sm, primary and secondary
 *   forms.css    - .label
 *   cards.css    - .card
 *   brand.css    - .brand
 *
 * Pruned to what is drawn on 2026-09-24: the product has its own sheets (okaybot-*.css), and
 * what is left here is the base styles, the tokens they read, and the handful of classes the
 * /design test pages and a few product templates still use.
 */

/* ============================================
   CORE TOKENS
   ============================================ */

:root {
    /* Typography - Font Family (System fonts for best cross-platform rendering) */
    --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: 'Fira Code', JetBrains Mono, Consolas, monospace;

    /* Typography - Font Size (Generous, readable) */
    --font-xs: 0.75rem;
    --font-sm: 0.875rem;
    --font-base: 1rem;
    --font-lg: 1.125rem;
    --font-xl: 1.25rem;
    --font-2xl: 1.5rem;
    --font-3xl: clamp(1.75rem, 1.5rem + 1vw, 2rem);
    --font-4xl: clamp(2rem, 1.5rem + 1.5vw, 2.5rem);

    /* Typography - Font Weight */
    --font-regular: 400;
    --font-semibold: 600;
    --font-bold: 700;
    --font-extrabold: 800;

    /* Typography - Line Height */
    --leading-tight: 1.2;
    --leading-normal: 1.6;

    /* Typography - Letter Spacing */
    --tracking-tighter: -0.02em;
    --tracking-tight: -0.01em;

    /* Colors - Super Warm Neutrals (cream/ivory base) */
    --neutral-0: #ffffff;
    --neutral-200: #ebe7e0;
    --neutral-300: #ddd7cd;
    --neutral-400: #b5ada1;     /* Warm taupe */
    --neutral-600: #635d56;
    --neutral-700: #4a453f;
    --neutral-900: #1f1c19;

    /* Colors - Delight (Peachy coral - super friendly!) */
    --delight-50: #fff5f0;
    --delight-600: #e85a2a;

    /* Colors - Soft Status Colors (pastel-ish, friendly) */
    --info-100: #e0f2fe;
    --info-500: #0ea5e9;        /* Sky blue */

    /* Spacing */
    --spacing-1: 0.25rem;
    --spacing-2: 0.5rem;
    --spacing-3: 0.75rem;
    --spacing-4: 1rem;
    --spacing-6: 1.5rem;
    --spacing-8: 2rem;
    --spacing-24: 6rem;

    /* Radius - Super Round and Friendly */
    --radius-sm: 0.5rem;        /* 8px */
    --radius-2xl: 2rem;         /* 32px */
    --radius-full: 9999px;

    /* Shadows - Soft, diffused, friendly */
    --shadow-xs: 0 0 3px 0 rgb(31 28 25 / 0.04);
    --shadow-sm: 0 0 8px -2px rgb(31 28 25 / 0.08);
    --shadow-md: 0 0 20px -4px rgb(31 28 25 / 0.1);
    --shadow-lg: 0 0 25px -5px rgb(31 28 25 / 0.11);

    /* Motion - Bouncy and Fun */
    --duration-fast: 150ms;

    --easing-default: cubic-bezier(0.4, 0, 0.2, 1);
    --easing-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);

    /* Focus */
    --focus-ring-width: 3px;
    --focus-ring-offset: 3px;

    /* ============================================
       ACCENT
       ============================================ */

    /* Warm Violet - Vibrant & modern */
    --accent-50: #f5f3f9;
    --accent-100: #ebe5f3;
    --accent-200: #d4c8e6;
    --accent-300: #b8a4d4;
    --accent-400: #9a7ec0;
    --accent-500: #7c5dac;
    --accent-600: #6b4d9a;   /* Primary */
    --accent-700: #5a4082;
    --accent-800: #4a3469;

    /* ============================================
       SEMANTIC TOKENS
       ============================================ */

    /* Background - Warmer page background */
    --bg-surface: var(--neutral-0);

    /* Text */
    --text-primary: var(--neutral-900);
    --text-secondary: var(--neutral-600);
    --text-tertiary: var(--neutral-400);
    --text-link: var(--accent-600);
    --text-link-hover: var(--accent-700);

    /* Accent (Semantic) */
    --accent-primary: var(--accent-600);
    --accent-hover: var(--accent-700);
    --accent-active: var(--accent-800);
    --accent-contrast: var(--neutral-0);

    /* Actions (Buttons) */
    --action-primary-bg: var(--accent-primary);
    --action-primary-bg-hover: var(--accent-hover);
    --action-primary-bg-active: var(--accent-active);
    --action-primary-text: var(--accent-contrast);

    --action-secondary-bg: #e7e6ea;
    --action-secondary-bg-hover: #d8d6de;
    --action-secondary-bg-active: #cbc8d3;
    --action-secondary-glow: #d8d5e4;
    --action-secondary-text: var(--neutral-700);

    /* Component Sizing */
    --button-height-xs: 1.75rem;
    --button-height-sm: 2.25rem;
    --button-height-md: 2.75rem;

    /* ============================================
       THEME TOKENS (formerly in _default-theme.css)
       ============================================ */

    /* Canvas */
    --canvas-bg: #f7f7f7;
    --table-header-border: rgba(90, 90, 180, 0.10);

    /* Focus & Interactive */
    --interactive-focus-ring: 0 0 0 3px color-mix(in srgb, var(--accent-primary) 35%, transparent);
}

/* ============================================
   BASE STYLES
   ============================================ */

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

html {
    font-size: 100%;
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
}

body {
    margin: 0;
    font-family: var(--font-sans);
    font-size: var(--font-base);
    font-weight: var(--font-regular);
    line-height: var(--leading-normal);
    color: var(--text-primary);
    background: var(--canvas-bg);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Soft glowing gradient background */
body::before {
    content: '';
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 0;
    background:
        radial-gradient(
            ellipse 120% 60% at 20% 95%,
            rgba(230, 210, 235, 0.45) 0%,
            transparent 60%
        ),
        radial-gradient(
            ellipse 100% 50% at 80% 95%,
            rgba(255, 220, 210, 0.40) 0%,
            transparent 55%
        ),
        radial-gradient(
            ellipse 80% 40% at 50% 95%,
            rgba(255, 200, 220, 0.35) 0%,
            transparent 50%
        ),
        linear-gradient(
            to bottom,
            rgba(255, 255, 255, 0) 0%,
            rgba(250, 245, 250, 0.4) 70%,
            rgba(245, 240, 248, 0.6) 100%
        );
}

/* The page's one region, still under the 4rem the scaffold's top menu bar took. body.ob-app and
   body.ob-site each place it their own way (okaybot-page.css, okaybot-site.css), so this is
   only what a signed-out page outside the site sees - the /design pages. */
#Main {
    margin-top: 4rem;
    position: absolute;
    bottom: 0;
    top: 0;
    left: 0;
    right: 0;
}

/* ============================================
   SCROLLBAR - Always visible, custom styled
   ============================================ */

html {
    overflow-y: scroll;
    scrollbar-color: var(--neutral-300) transparent;
    scrollbar-width: thin;
}

/* Webkit browsers (Chrome, Safari, Edge) */
::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background: var(--neutral-300);
    border-radius: var(--radius-full);
    border: 2px solid transparent;
    background-clip: padding-box;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--neutral-400);
    border: 2px solid transparent;
    background-clip: padding-box;
}

::-webkit-scrollbar-corner {
    background: transparent;
}

/* ============================================
   TYPOGRAPHY
   ============================================ */

h1, h2, h3, h4, h5, h6 {
    margin: 0;
    font-weight: var(--font-bold);
    line-height: var(--leading-tight);
    letter-spacing: var(--tracking-tight);
    color: var(--text-primary);
}

h1 {
    font-size: var(--font-4xl);
    font-weight: var(--font-extrabold);
    letter-spacing: var(--tracking-tighter);
}

h2 {
    font-size: var(--font-3xl);
    font-weight: var(--font-bold);
}

h3 {
    font-size: var(--font-2xl);
}

h4 {
    font-size: var(--font-xl);
}

h5 {
    font-size: var(--font-lg);
}

h6 {
    font-size: var(--font-base);
    font-weight: var(--font-semibold);
}

p {
    margin: 0;
}

a {
    color: var(--text-link);
    text-decoration: none;
    text-underline-offset: 3px;
    transition: color var(--duration-fast) var(--easing-default);
}

a:hover {
    color: var(--text-link-hover);
    text-decoration: underline;
}

/* ============================================
   ANIMATIONS - Playful
   ============================================ */

@keyframes shake {
    0%, 100% { transform: translateX(0); }
    10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
    20%, 40%, 60%, 80% { transform: translateX(4px); }
}
.shake { animation: shake 0.4s var(--easing-default); }

/* ============================================
   UTILITY CLASSES
   ============================================ */

/* Text Colors */
.text-secondary { color: var(--text-secondary); }
.text-tertiary { color: var(--text-tertiary); }

/* Font Sizes */
.text-sm { font-size: var(--font-sm); }

/* Layout Containers */
.page { max-width: 1000px; margin: 0 auto; padding: var(--spacing-8); }

/* Flex Layouts */
.row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--spacing-4); }
.stack { display: flex; flex-direction: column; gap: var(--spacing-4); }

/* Grid Layouts */
.grid-2 { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--spacing-6); }

/* Light Label */
.label-light {
    font-size: var(--font-xs);
    font-family: var(--font-mono);
    color: var(--text-tertiary);
}

/* Cards */
.card {
    border-bottom-color: var(--table-header-border);
}

/* Focus Ring (Buttons / Interactive) */
.btn:focus-visible {
    outline: none;
    box-shadow: var(--interactive-focus-ring);
}
 /* ============================================
   OKAYBOT TOKENS
   Every value from design.md section 2, as custom properties. The scaffold's _shift-core.css
   keeps its own token set for the marketing pages; the product's screens use these and only
   these. Component files (okaybot-*.css) never carry a literal colour, size or radius.
   ============================================ */

:root {
    /* ---- surfaces ---- */
    --ob-canvas: #f3f4f7;           /* the page, the sidebar's money block, muted chips */
    --ob-canvas-2: #f5f6f9;         /* read tiles */
    --ob-card: #ffffff;             /* every card, the sidebar */
    --ob-card-head: #f9fafc;        /* an approval card's header: a shade off the card, well short of the canvas */
    --ob-line: #e8eaf0;             /* card borders, the sidebar's right edge, gridlines, the rail line, bar tracks */
    --ob-line-card: #e9ebf1;        /* feed cards */
    --ob-line-in: #eef0f4;          /* dividers inside a card */
    --ob-line-chip: #eceef3;        /* the rim chip's border and separator */

    /* ---- ink ---- */
    --ob-ink: #16181d;              /* titles, body, the loudest cost, the Approvals count */
    --ob-ink-2: #5f6472;            /* nav labels, descriptions, quoted reasons */
    --ob-muted: #9a9fad;            /* meta, section labels, times, handles, inactive icons */
    --ob-faint-1: #bcc1cc;          /* the quietest cost */
    --ob-faint-2: #c5c8d2;          /* frozen bars, struck costs */
    --ob-faint-3: #ccd0d9;          /* chevrons */

    /* ---- the one accent ---- */
    --ob-accent: #4f66f0;           /* primary buttons, the active nav icon, money bars with room, the logo */
    --ob-accent-ink: #3547c9;       /* active nav text */
    --ob-accent-bg: #e9edff;        /* active nav background, the lit rail child */
    --ob-accent-ring: #c9d1fb;      /* the border of the one card asking to be answered */
    --ob-accent-lift: #6376f3;      /* hovering something already selected in the accent: the same blue, a shade up */
    --ob-accent-halo-0: rgba(79, 102, 240, 0);      /* ...and its halo, at rest */
    --ob-accent-halo-1: rgba(79, 102, 240, .13);    /* ...and at the top of its breath */

    /* ---- a read waiting for a person (2026-09-25) ----
       THE BLUE IS FOR WRITES. Most people will ask to okay only what goes out to X, and a post
       among a column of reads should stand out by colour before a word is read - so a read
       waiting to be answered wears a muted blue: its edge, its chip and its button. A trial:
       these values are the whole of it (slate #3d4350 and grey #6b7080 were tried first). */
    --ob-read: #64739a;             /* a read's button, and its edge when it is the one breathing */
    --ob-read-lift: #56648a;        /* ...hovered */
    --ob-read-ink: #4b5a80;         /* the words on a read's chip */
    --ob-read-bg: #ebeef6;          /* the chip's ground */
    --ob-read-ring: #d2d8e8;        /* the edge of a read waiting to be answered */
    --ob-read-halo-0: rgba(100, 115, 154, 0);       /* its halo, at rest */
    --ob-read-halo-1: rgba(100, 115, 154, .12);     /* ...and at the top of its breath */

    /* ---- state tints: a state tints the whole card and its chip ---- */
    --ob-butter-bg: #fff3cf;  --ob-butter-ink: #8a5a00;    /* waiting for a person */
    --ob-rose-bg: #ffe4e6;    --ob-rose-ink: #b91c1c;      /* destructive, blocked */
    --ob-rose-halo-0: rgba(185, 28, 28, 0);         /* a destructive frame's halo, at rest */
    --ob-rose-halo-1: rgba(185, 28, 28, .11);       /* ...and at the top of its breath */
    --ob-rose-card: #fff1f2;  --ob-rose-edge: #f6d7da;  --ob-rose-line: #f4d9dc;  --ob-rose-chip: #f5d2d6;
    --ob-mint-bg: #d9f5e5;    --ob-mint-ink: #157347;      /* done, published, active, enabled */
    --ob-mint-edge: #6cc596;                               /* the dashed edge of a post a person posted themselves */
    --ob-butter-edge: #f2e2b0;                             /* ...and the edge of a butter card, on the calendar */
    --ob-accent-tint: #fbfbff;                             /* today's cell on the month grid, barely there under its ring */
    --ob-cal-out: #e9ebf1;    --ob-cal-out-line: #e1e4eb;  /* another month's days: a plainly greyer ground, so the month has an edge */
    --ob-grey-bg: #f3f4f7;    --ob-grey-ink: #5f6472;      /* frozen, the Posts count */
    --ob-grey-edge: #d9dce4;                               /* the edge of a deleted post's white card: grey-bg is the canvas, so it cannot be its ground */
    --ob-bar-red: #ef4444;                                 /* a money bar past 80 percent */
    --ob-chart-period: rgba(79, 102, 240, .055);           /* the plan's period on the Usage chart, from the moment it started */
    --ob-live: #22c55e;                                    /* the dot beside Live */

    /* ---- the cost ramp: ink and weight carry the size of the number ---- */
    --ob-c1: #bcc1cc;  --ob-c1-w: 600;
    --ob-c2: #9a9fad;  --ob-c2-w: 600;
    --ob-c3: #6f7585;  --ob-c3-w: 700;
    --ob-c4: #3d4350;  --ob-c4-w: 800;
    --ob-c5: #16181d;  --ob-c5-w: 800;
    --ob-cx: #c5c8d2;  --ob-cx-w: 600;

    /* ---- identity tints: a slot per agent or profile, stored as a number on the row.
            bg is the pale ground of a circle or an untinted square, fg a letter or a mark on
            it, chart the colour that slot wears in a chart, solid the deep ground an agent's
            square stands on ----

            THE CHART COLOURS ARE LIGHT AND DEEP (2026-09-25). Two charts on the Usage page
            carry an agent - the stacked day chart and the BY AGENT bars - and an agent keeps
            its colour on both, so neither needs a legend. They were a pastel wash until
            2026-09-12, then strong mid-tones, and the mid-tones glared: often more saturated
            than the agent's own face, and all at one lightness, so a stack told its agents
            apart by hue alone. Green (slot 3) and red (slot 4) were then the same colour to a
            red-green colour-blind reader, OKLab delta E 1.5 against a target of 8.

            So each is worked out from the slot's own hue, read off its solid, in OKLCH:
                L = 0.66 + 0.12 * cos(hue - 130deg),  C = 0.12
            Strength is a quarter down on what it was, and the lightness follows the hue: greens,
            limes and ambers sit lighter, purples, reds and magentas deeper. That lightness gap
            is what keeps the first four agents apart for a colour-blind reader (delta E 9.1,
            and 15.3 in normal vision). The black person's slot is a neutral, oklch(0.56 0.03
            267). The lab that chose it: https://claude.ai/artifact/TARmZyTwjpArfe8YZ5vtTr

            A formula on hue alone means a family's later outings land close to its first:
            slot 12's blue is near slot 5's. A workspace has to reach eleven agents to see it.

            Change a solid below and work its chart value out again from that formula. They
            stay louder than the charge colours: an agent is a who, a charge type is a what,
            and the two breakdowns sit side by side. */
    --ob-t1-bg: #16181d;  --ob-t1-fg: #ffffff;  --ob-t1-chart: #6c7487;    /* black person */
    --ob-t2-bg: #e9d5ff;  --ob-t2-fg: #6b21a8;  --ob-t2-chart: #7e5ba7;    /* purple base */
    --ob-t3-bg: #bfead1;  --ob-t3-fg: #076a45;  --ob-t3-chart: #65ca98;    /* green base */
    --ob-t4-bg: #fecdd3;  --ob-t4-fg: #9f1239;  --ob-t4-chart: #bf616d;    /* red base */
    --ob-t5-bg: #b9e5fd;  --ob-t5-fg: #036285;  --ob-t5-chart: #2396c6;    /* blue base */
    --ob-t6-bg: #eddcb4;  --ob-t6-fg: #6f5603;  --ob-t6-chart: #cdaa4b;    /* amber base */
    --ob-t7-bg: #dcd9ff;  --ob-t7-fg: #4338ca;  --ob-t7-chart: #636cba;    /* indigo base */
    --ob-t8-bg: #d6e5bc;  --ob-t8-fg: #4c6306;  --ob-t8-chart: #a7c46c;    /* lime base */
    --ob-t9-bg: #f9d0ea;  --ob-t9-fg: #912576;  --ob-t9-chart: #a05589;    /* magenta base */
    --ob-t10-bg: #b6ece2;  --ob-t10-fg: #0f766e;  --ob-t10-chart: #27bfb2;    /* teal base */
    --ob-t11-bg: #fdd4bc;  --ob-t11-fg: #8a4306;  --ob-t11-chart: #d4844f;    /* orange base */
    --ob-t12-bg: #b4ddff;  --ob-t12-fg: #034871;  --ob-t12-chart: #388bc6;    /* blue deep */
    --ob-t13-bg: #e1d9a8;  --ob-t13-fg: #4e4400;  --ob-t13-chart: #c4b452;    /* amber deep */
    --ob-t14-bg: #cfd4fe;  --ob-t14-fg: #3926a3;  --ob-t14-chart: #666ab8;    /* indigo deep */
    --ob-t15-bg: #c5e2b8;  --ob-t15-fg: #255003;  --ob-t15-chart: #94c97a;    /* lime deep */
    --ob-t16-bg: #fac7dc;  --ob-t16-fg: #7b044c;  --ob-t16-chart: #ac577f;    /* magenta deep */
    --ob-t17-bg: #a5e4ed;  --ob-t17-fg: #044d56;  --ob-t17-chart: #0eadbf;    /* teal deep */
    --ob-t18-bg: #f6cfac;  --ob-t18-fg: #623803;  --ob-t18-chart: #d5904a;    /* orange deep */
    --ob-t19-bg: #e8cbf4;  --ob-t19-fg: #680184;  --ob-t19-chart: #8a579d;    /* purple deep */
    --ob-t20-bg: #ace6d2;  --ob-t20-fg: #04503f;  --ob-t20-chart: #4bc6a5;    /* green deep */
    --ob-t21-bg: #ffc8c0;  --ob-t21-fg: #830206;  --ob-t21-chart: #c96c61;    /* red deep */
    --ob-t22-bg: #e4e8c7;  --ob-t22-fg: #727608;  --ob-t22-chart: #b7bd5e;    /* lime light */
    --ob-t23-bg: #f5dbf4;  --ob-t23-fg: #9a4f98;  --ob-t23-chart: #955593;    /* magenta light */
    --ob-t24-bg: #c5efe8;  --ob-t24-fg: #068276;  --ob-t24-chart: #2fc0b0;    /* teal light */
    --ob-t25-bg: #ffdccf;  --ob-t25-fg: #b04e2a;  --ob-t25-chart: #d07758;    /* orange light */
    --ob-t26-bg: #e4e0ff;  --ob-t26-fg: #735eb9;  --ob-t26-chart: #7262b0;    /* purple light */
    --ob-t27-bg: #d1edd4;  --ob-t27-fg: #26853e;  --ob-t27-chart: #7ecb89;    /* green light */
    --ob-t28-bg: #ffd9e1;  --ob-t28-fg: #af4867;  --ob-t28-chart: #b75c75;    /* red light */
    --ob-t29-bg: #c5ecf9;  --ob-t29-fg: #047d98;  --ob-t29-chart: #02a2c5;    /* blue light */
    --ob-t30-bg: #f6e1c4;  --ob-t30-fg: #966500;  --ob-t30-chart: #d39c48;    /* amber light */
    --ob-t31-bg: #d4e6ff;  --ob-t31-fg: #3370be;  --ob-t31-chart: #4a7ec3;    /* indigo light */
    --ob-t32-bg: #eed5c4;  --ob-t32-fg: #763e01;  --ob-t32-chart: #d58a4c;    /* orange muted */
    --ob-t33-bg: #e2d4ed;  --ob-t33-fg: #62397e;  --ob-t33-chart: #8459a3;    /* purple muted */
    --ob-t34-bg: #c5e3d5;  --ob-t34-fg: #045d43;  --ob-t34-chart: #59c89e;    /* green muted */
    --ob-t35-bg: #f2d2d0;  --ob-t35-fg: #832e30;  --ob-t35-chart: #c56766;    /* red muted */
    --ob-t36-bg: #c5dff0;  --ob-t36-fg: #05547b;  --ob-t36-chart: #308fc6;    /* blue muted */
    --ob-t37-bg: #e2dbc0;  --ob-t37-fg: #5e4d01;  --ob-t37-chart: #c9af4e;    /* amber muted */
    --ob-t38-bg: #d3d9f3;  --ob-t38-fg: #3c468e;  --ob-t38-chart: #606ebb;    /* indigo muted */
    --ob-t39-bg: #d3e0c7;  --ob-t39-fg: #395a01;  --ob-t39-chart: #9ec773;    /* lime muted */
    --ob-t40-bg: #edd1e0;  --ob-t40-fg: #792f5c;  --ob-t40-chart: #a65684;    /* magenta muted */
    --ob-t41-bg: #c0e2e5;  --ob-t41-fg: #005a5f;  --ob-t41-chart: #07b3bc;    /* teal muted */
    --ob-t42-bg: #ffd5da;  --ob-t42-fg: #ba024a;  --ob-t42-chart: #bc5f70;    /* red vivid */
    --ob-t43-bg: #b9eaff;  --ob-t43-fg: #0a6f8e;  --ob-t43-chart: #169cc6;    /* blue vivid */
    --ob-t44-bg: #f9ddab;  --ob-t44-fg: #825e03;  --ob-t44-chart: #d0a349;    /* amber vivid */
    --ob-t45-bg: #d2e2ff;  --ob-t45-fg: #1658d8;  --ob-t45-chart: #5279c1;    /* indigo vivid */
    --ob-t46-bg: #dce8b2;  --ob-t46-fg: #5f6d03;  --ob-t46-chart: #afc165;    /* lime vivid */
    --ob-t47-bg: #ffd0f5;  --ob-t47-fg: #a41c94;  --ob-t47-chart: #9b548e;    /* magenta vivid */
    --ob-t48-bg: #a9f1ec;  --ob-t48-fg: #067470;  --ob-t48-chart: #1bbcb6;    /* teal vivid */
    --ob-t49-bg: #ffd8c6;  --ob-t49-fg: #a24405;  --ob-t49-chart: #d27d54;    /* orange vivid */
    --ob-t50-bg: #e5dbff;  --ob-t50-fg: #773ac9;  --ob-t50-chart: #785eac;    /* purple vivid */

    /* TEN FAMILIES, FIVE SHADE PASSES, AND THE ORDER IS THE POINT. A slot is handed out by a
       counter - the lowest one no live agent is wearing - so this list is the order agents come
       out in, and it is arranged to cross the hue wheel every step rather than walk round it.
       Consecutive slots are 84 degrees apart at worst and 145 on average; the palette this
       replaced averaged 7, which is why its slots 9 to 22 were all brown.

       The families sit on an even 36 degree grid: purple, green, red, blue, amber, indigo, lime,
       magenta, teal, orange. The brand's own blue waits until slot 5 rather than greeting you
       first. Four of the old hand-picked colours land within 10 degrees of a grid slot and are
       kept exactly - purple is the old lilac, red is rose, indigo is lavender, teal is mint. The
       other six families are generated at those four's lightness and chroma, which is what keeps
       fifty colours looking like one set.

       A family's later outings move 12 degrees of hue and a good deal of weight, so two purples
       ten slots apart never read as a pair; each pass also starts three families along, so the
       seam between passes is as wide a jump as every other step.

       MIRRORED IN code/App/Faces.cs: change a value here and change it there in the same commit,
       because an SVG served to an img cannot read a stylesheet. */

    /* ---- and the ground an agent's face stands on ----

            An agent's face is the Okaybot face in white on the agent's own colour, so it needs a
            ground white reads against - the pale -bg above is for a letter or a chip, not for a
            face. These are the deep inks of the same fifty hues: -fg for every slot but the black
            one, which is already its own ground. */
    --ob-t1-solid: #16181d;
    --ob-t2-solid: #6b21a8;
    --ob-t3-solid: #076a45;
    --ob-t4-solid: #9f1239;
    --ob-t5-solid: #036285;
    --ob-t6-solid: #6f5603;
    --ob-t7-solid: #4338ca;
    --ob-t8-solid: #4c6306;
    --ob-t9-solid: #912576;
    --ob-t10-solid: #0f766e;
    --ob-t11-solid: #8a4306;
    --ob-t12-solid: #034871;
    --ob-t13-solid: #4e4400;
    --ob-t14-solid: #3926a3;
    --ob-t15-solid: #255003;
    --ob-t16-solid: #7b044c;
    --ob-t17-solid: #044d56;
    --ob-t18-solid: #623803;
    --ob-t19-solid: #680184;
    --ob-t20-solid: #04503f;
    --ob-t21-solid: #830206;
    --ob-t22-solid: #727608;
    --ob-t23-solid: #9a4f98;
    --ob-t24-solid: #068276;
    --ob-t25-solid: #b04e2a;
    --ob-t26-solid: #735eb9;
    --ob-t27-solid: #26853e;
    --ob-t28-solid: #af4867;
    --ob-t29-solid: #047d98;
    --ob-t30-solid: #966500;
    --ob-t31-solid: #3370be;
    --ob-t32-solid: #763e01;
    --ob-t33-solid: #62397e;
    --ob-t34-solid: #045d43;
    --ob-t35-solid: #832e30;
    --ob-t36-solid: #05547b;
    --ob-t37-solid: #5e4d01;
    --ob-t38-solid: #3c468e;
    --ob-t39-solid: #395a01;
    --ob-t40-solid: #792f5c;
    --ob-t41-solid: #005a5f;
    --ob-t42-solid: #ba024a;
    --ob-t43-solid: #0a6f8e;
    --ob-t44-solid: #825e03;
    --ob-t45-solid: #1658d8;
    --ob-t46-solid: #5f6d03;
    --ob-t47-solid: #a41c94;
    --ob-t48-solid: #067470;
    --ob-t49-solid: #a24405;
    --ob-t50-solid: #773ac9;

    /* ---- charge types: a colour means the same thing on every chart ----

            PASTEL, BECAUSE THE AGENTS ARE SOLID (2026-09-12). What it was spent on sits beside
            by agent and under the stacked day chart, and all three used the same weight of
            colour - so the eye read the indigo of a charge type and the indigo of an agent as
            the same fact. These are the same seven hues, lightened, and they now read as the
            quieter half of the pair. */
    --ob-charge-read_post: #9ec5fe;     /* reading a post */
    --ob-charge-post: #c3b5fb;          /* posts */
    --ob-charge-post_link: #b7bfd0;     /* posts with links */
    --ob-charge-read_user: #8fd9c9;     /* user lookups */
    --ob-charge-read_own: #f7cd93;      /* your own reads */
    --ob-charge-reply: #dcc6fb;         /* replies, beside posts */
    --ob-charge-manage: #cfd3dc;        /* deleting a post: an admin act, so the quietest of them */

    /* ---- type ---- */
    --ob-font: 'Plus Jakarta Sans', 'Segoe UI', system-ui, -apple-system, sans-serif;
    --ob-mono: 'IBM Plex Mono', ui-monospace, Consolas, monospace;

    /* ---- a machine payload, drawn (json/json.js, .ob-json in okaybot-atoms.css) ----
            Quiet inks, not a code editor's. The punctuation is deliberately the faintest
            thing on the tile: braces and commas are structure, and the indent already says
            the structure, so what is left to read is the names and the values. */
    --ob-json-step: 16px;           /* one level of indent */
    --ob-json-key: #3547c9;         /* a field name: the accent's ink */
    --ob-json-str: #157347;         /* a value somebody wrote */
    --ob-json-num: #9a3412;         /* a number */
    --ob-json-word: #6b21a8;        /* true, false, null */
    --ob-json-punc: #b4b9c5;        /* braces, brackets, commas, colons */

    /* ---- a block to paste, on the ink (agents/connect_block.js, .ob-connectbox .win) ----
            The connect block's dark boxes: a bar saying where it goes over what goes there */
    --ob-term-ink: #e8eaf1;         /* what is pasted */
    --ob-term-bar: #23262e;         /* the bar: Terminal, or the file's path */
    --ob-term-bar-ink: #a3a8b8;     /* the bar's words and icon */
    --ob-term-btn: #30343e;         /* Copy, in the bar */
    --ob-term-prompt: #6b7184;      /* the `$` before a terminal line, which Copy leaves out */
    --ob-term-key: #9fe3bd;         /* the key in its line, or its placeholder */

    /* ---- the page ---- */
    /* BACK TO 236 (2026-09-16). It was 262 - 236 plus 26 - for one reason: the money block read
       "$0.61 of $10 this month" at the foot of the sidebar, and that line wants 194px inside
       16px of padding either side. The block is gone and today's spend is a 56px line on the
       Usage item, so nothing in this column has to fit a sentence any more and every page body
       is 26px wider. */
    --ob-sidebar-w: 236px;
    /* ONE BODY GAP, EVERY PAGE. It used to be 32 left and 28 right, and Home alone added
       another 22 inside its left column - a gap and then another gap, so the one screen that
       felt comfortable was comfortable by accident and every other screen was cramped by
       comparison. One number now, and a page that wants its content inset further does it
       with its own card's padding, never by adding a second page gap. */
    --ob-body-pad-top: 26px;
    --ob-body-pad-right: 44px;
    --ob-body-pad-left: 44px;
    --ob-head-h: 40px;
    --ob-head-gap: 20px;
    --ob-card-gap: 14px;
    --ob-cap-w: 260px;               /* the cap block on an Agents row: fixed, so nothing shifts it */

    /* ---- the stage: the picture of what a call will do (okaybot-stage.css) ---- */
    --ob-stage-gap: 18px;            /* between two rows of the picture */
    --ob-stage-face: 44px;           /* the face column, fixed so every row starts its text in one place */
    --ob-stage-av: 44px;             /* the face itself, real or silhouette */
    --ob-stage-row-gap: 12px;
    --ob-stage-spine-gap: 16px;      /* under a spined row: the gap lives inside it so the line can cross it */
    /* THE FRAME IS THE CARD (2026-09-24): the approval card's own radius, and a top inset deep
       enough to clear the half of the chip that hangs inside it */
    --ob-frame-radius: 22px;
    --ob-frame-line: 1.5px;
    --ob-frame-pad-x: 20px;          /* ...and the inset an unframed picture copies, so it keeps one left edge */
    --ob-frame-pad-y: 26px;
    --ob-frame-pad-b: 16px;
    --ob-flabel-h: 26px;             /* the chip on the top line, half above it and half inside */
    --ob-strip-gap: 28px;            /* between the small silhouettes of a read of many */
    --ob-strip-gap-people: 24px;     /* ...and between people, who are narrower */
    /* THE GROUND OF THE THING BEING DECIDED (2026-09-22). A proposed post sits on the faintest
       blue there is - enough that the frame reads as ours and as unfinished, not enough to be a
       colour - and opening its words deepens it one step, with the edge going solid. That pair
       IS the editing state: nothing is drawn on the words themselves. */
    --ob-proposed: #fbfcff;          /* a proposed post, waiting to be answered */
    --ob-band: #f3f5fb;              /* ...and the band under its foot that offers Post it yourself (2026-10-01) */
    --ob-editing: #f5f6fe;           /* ...and the same frame with its words open */
    --ob-editing-spine: #c8cfe4;     /* ...and the line joining one open part to the next */

    --ob-skel: #e4e7ee;              /* a silhouette's name bar: scenery, and it must never compete */
    --ob-skel-2: #edeff4;            /* ...its text bars, quieter still */
    --ob-skel-face: #e8eaf1;         /* ...its face */
    --ob-skel-face-2: #e0e4ed;       /* ...a face on the read window's tinted ground */
    --ob-spine: #d5d9e3;             /* the line joining one post to the next: quiet, but it has to be seen */

    /* ---- the home page: the decisions beside the log's panel (okaybot-home.css) ---- */
    --ob-home-split: 1.5fr;          /* the decisions against the log panel's 1fr: 60/40 */
    --ob-home-panel: 1fr;            /* ...and the panel, which is the same 40 the gap used to eat into */
    /* --ob-home-gutter and --ob-home-gap went on 2026-09-18 with the panel: a white column
       flush to the right edge has no gap beside it and no gutter inside it, it has an edge */
    --ob-home-card-gap: 16px;        /* between decision cards */
    /* --ob-home-avatar-w went on 2026-09-16 with the fixed avatar: the right column's head ran
       short of that corner to clear it, and the corner is the page's own again */
    --ob-log-h: 38px;                /* a log line: shorter than a feed row, because it is not a card */
    --ob-log-sq: 22px;               /* the agent's square on a log line */
    --ob-log-gap: 11px;
    --ob-log-amt-w: 52px;            /* the money column's floor: a status word may push it wider */
    --ob-log-when-w: 34px;           /* the time column, fixed so the numbers line up down the page */

    /* ---- setup mode: the steps Home draws until the workspace is set up (okaybot-setup.css) ---- */
    --ob-setup-w: 860px;             /* the whole column, body padding included: a form's width, not a page's.
                                        860, not 720, since 2026-10-04: the connect block's harness list
                                        and its blocks side by side want a card about 680 wide */
    --ob-setup-num: 36px;            /* a step's circle */
    --ob-on-accent: #ffffff;         /* words and faces on the accent: the plan's blue box on the last step */
    --ob-glass: rgba(255, 255, 255, .15);           /* ...a chip on it */
    --ob-glass-edge: rgba(255, 255, 255, .24);      /* ...and the chip's edge */
    --ob-shadow-on-accent: 0 6px 18px rgba(22, 24, 80, .18);   /* ...the white button lifted off it */
    --ob-setup-col-gap: 18px;        /* between the circle and what is beside it */
    --ob-setup-gap: 22px;            /* between steps */
    --ob-setup-drop: 12px;           /* the current step's circle sits this far down, level with its card's title */
    --ob-setup-next-h: 64px;         /* the silhouette of the step after this one */

    /* ---- Activity: the log beside the panel (okaybot-activity.css) ---- */
    --ob-act-panel: 1fr;             /* the detail panel against the list's 1fr: 50/50 on Activity and Posts. 1.5fr (40/60) until 2026-09-23 */
    --ob-act-panel-pad: 44px;        /* the panel's right padding. 84 until 2026-09-16, when the account avatar left that corner for the sidebar's foot; the page's own gap now */
    --ob-act-panel-top: 34px;        /* more than the body's: the panel is read, not scanned */
    /* THE SIDE PANEL, one width for every page that has one beside a list or a grid (2026-09-24):
       the calendar, Profiles, Usage and one agent. It was the calendar's, sized for the picker's
       seven days; Usage had 340 of its own and Profiles took 40 percent. Padding included. */
    --ob-side-panel-w: 452px;

    /* ---- the list page: Activity's and Posts' menu and the bar of faces (okaybot-listpage.css) ---- */
    --ob-listmenu-w: 176px;          /* the status menu down the left of the list */
    --ob-listmenu-gap: 28px;         /* ...and the gap between it and the list */
    --ob-face-lg: 34px;              /* an account in the bar of faces */
    --ob-face-sm: 28px;              /* ...and an agent, a step smaller */

    /* ---- the account page: the settings beside the record (okaybot-identity.css) ----
            An even split, not Activity's 40/60: on that page the panel is the subject and the
            log is the index, and here neither half is either. The left is what this browser
            holds, the right is what has happened to the account. */
    --ob-acct-panel: 1fr;

    /* ---- the detail: one call in full (okaybot-detail.css) ---- */
    --ob-detail-side: 340px;         /* the modal's right column: who, the reason, the facts, the timeline */

    /* ---- atoms ---- */
    --ob-radius: 18px;               /* cards */
    --ob-radius-row: 22px;           /* feed rows and approval cards: half the 44px slim row, so the row is a pill */
    --ob-radius-panel: 16px;         /* the opened rim chip, the money block, tiles */
    --ob-radius-tile: 14px;
    --ob-radius-sq: 10px;            /* an agent square */
    --ob-pill-h: 38px;
    --ob-chip-h: 24px;
    --ob-seg-h: 32px;
    --ob-nav-h: 42px;
    --ob-shadow-row: 0 0 14px 0 rgba(0, 0, 0, .02);          /* a slim feed row, no border: just off the canvas */
    --ob-shadow-modal: 0 24px 80px rgba(22, 24, 29, .35);   /* the item modal over the shade */
    --ob-shadow-menu: 0 18px 44px rgba(22, 24, 29, .14);    /* a menu hung off a button, over the page: the folded bar of faces */
    --ob-shade: rgba(22, 24, 29, .55);                      /* the page behind a modal */
}

/* ---- MEDIUM: 1200 to 1599 (dev/docs/responsive.md) ----
        The same three columns and the same components, tightened. Everything here is a number
        the sheets already read, so no page needs a query of its own for this tier: the sidebar,
        the gaps and the panels shrink, and the list the person is reading down gets what they
        give up. Narrow (1199 and below) inherits all of it and then takes the side panel away,
        which IS structural and lives in each page's sheet. */
@media (max-width: 1599px) {
    :root {
        --ob-sidebar-w: 200px;
        --ob-body-pad-right: 32px;
        --ob-body-pad-left: 32px;
        --ob-cap-w: 190px;
        --ob-act-panel: .82fr;           /* Activity and Posts 55/45, in the list's favour */
        --ob-act-panel-pad: 32px;
        --ob-act-panel-top: 28px;
        --ob-side-panel-w: 380px;        /* the picker's days 45 wide, from 52 */
        --ob-listmenu-w: 148px;          /* "Waiting for you", the longest item, still fits */
        --ob-listmenu-gap: 20px;
        --ob-face-lg: 30px;
        --ob-face-sm: 24px;
        --ob-detail-side: 300px;
    }
}

/* ---- the outside pages: the homepage, pricing, docs, the legal pages (okaybot-site.css) ----
        One column down the middle, a top bar instead of the sidebar. The same palette and the
        same type as the product, because a person who clicks from the pricing page into the
        dashboard should feel they stayed in one place. */
:root {
    --ob-site-w: 1360px;             /* the column, padding included. 1120 read as narrow on a big screen */
    --ob-site-pad: 32px;             /* its side gutter; the fold narrows it (okaybot-site.css) */
    --ob-site-gap: 20px;             /* between cards on an outside page: roomier than the app's 14 */
    --ob-site-sect: 80px;            /* between sections */
    --ob-site-nav-h: 68px;           /* the top bar */
    --ob-site-nav-bg: rgba(243, 244, 247, .86);   /* the canvas, letting the page show through under a blur */
    --ob-site-prose-w: 760px;        /* a docs or legal column: a reading measure, not a page */
    --ob-site-docs-nav-w: 240px;     /* the topic list beside the docs */
    --ob-site-dark: #16181d;         /* the one dark section: the closing call, on ink */
    --ob-site-art-bg: #e0e8fd;       /* the sign-in panel's ground: the keeper picture's own flat blue */
    --ob-site-good: #f5f7ff;         /* the with-Okaybot sheet on the homepage: the accent, barely there */
}
 /* ============================================
   OKAYBOT STAGE
   The picture of what a call will do, as ONE CARD (2026-09-24): the act as a chip on the card's
   top line, the preview inside it, and a foot - the answer, the quiet links for what can be
   changed, and on Home the door into the detail. The markup is stage/stage.html, inside.html,
   foot.html and row.html; what goes in each is decided in stage/stage.js. Nothing here carries
   a literal colour or radius - the silhouette's greys are tokens like everything else.

   There used to be a card round this with a header and the agent's reason, and a frame inside
   it between grey silhouettes of the timeline around the post. The frame is the card now, and
   nothing is drawn round it.
   ============================================ */

/* THE STAGE IS A CONTAINER, so a narrow card can drop a silhouette from a read of many however
   it came to be narrow - the Home column, the modal, the Activity panel (the query at the foot). */
.ob-stage {
    container-type: inline-size;
    font-family: var(--ob-font);
}

/* the chip hangs half above the card's top line, so a card that wears one keeps the room above
   it clear - between two cards on Home, and under the switch in the detail */
.ob-stage.labelled { padding-top: calc(var(--ob-flabel-h) / 2 + 2px); }

/* A post that has gone out has no card, and sits where it would inside one: the same inset,
   so the picture keeps one left edge whichever way it is drawn. */
.ob-stage > .subject {
    padding: 0 calc(var(--ob-frame-pad-x) + var(--ob-frame-line));
}

/* ---- one row: the face column, then the body ----
   The face column is a fixed width so every row starts its text in the same place, which is
   what makes a reply and the post it answers read as one conversation. */
.ob-srow {
    display: flex;
    gap: var(--ob-stage-row-gap);
    min-width: 0;

    > .col {
        display: flex;
        flex-direction: column;
        align-items: center;
        flex: 0 0 var(--ob-stage-face);
        align-self: stretch;
    }

    .body { min-width: 0; flex: 1 1 auto; }

    .who { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
    .name { font-size: 14.5px; font-weight: 700; color: var(--ob-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .handle { font-size: 13px; font-weight: 500; color: var(--ob-muted); }
    .when { font-size: 12px; font-weight: 600; color: var(--ob-faint-1); margin-left: auto; }

    .text {
        margin-top: 3px;
        font-size: 15.5px;
        line-height: 1.5;
        color: var(--ob-ink);
        white-space: pre-wrap;
        word-break: break-word;
    }

    .desc {
        margin-top: 2px;
        font-size: 13px;
        font-weight: 500;
        line-height: 1.45;
        color: var(--ob-ink-2);
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    /* the post being replied to: present, but quieter than the reply under it */
    &.dim .name, &.dim .text { color: var(--ob-ink-2); }
    &.dim .text { font-size: 14.5px; }

    /* the line from one face into the next, which is what a reply and a thread look like */
    .spine { width: 2px; flex: 1 1 auto; min-height: 14px; margin: 8px 0 -2px; background: var(--ob-spine); border-radius: 1px; }
}

/* THE PROVIDER'S BADGE on our own profile's face: a little smaller a share of the face than on
   a row, as on the Profiles panel's big face, or at 44px it would be a second face */
.ob-srow .ob-badged > .badge { width: 40%; height: 40%; right: -2px; bottom: -2px; }

/* AN ARTICLE a read brought back: the title as X draws it, big and heavy, then its words in the
   post's own type. A long one shows its opening until Show more, which is a checkbox the sheet
   reads (row.html) */
.ob-srow .article { margin-top: 6px; }
.ob-srow .article .title { display: block; font-size: 19px; font-weight: 800; line-height: 1.3; color: var(--ob-ink); text-decoration: none; }
.ob-srow .article a.title:hover { text-decoration: underline; }
.ob-srow .article .words { margin-top: 8px; font-size: 15.5px; line-height: 1.55; color: var(--ob-ink); word-break: break-word; }
.ob-srow .article .words p { margin: 0; }
.ob-srow .article .words p + p { margin-top: 12px; }
.ob-srow .article .whole { display: none; }
.ob-srow .article:has(.more input:checked) .whole { display: block; }
.ob-srow .article:has(.more input:checked) .opening { display: none; }
.ob-srow .article .more { display: inline-block; margin-top: 6px; font-size: 13px; font-weight: 700; color: var(--ob-accent-ink); cursor: pointer; }
.ob-srow .article .more:hover { text-decoration: underline; }
.ob-srow .article .more input { display: none; }
.ob-srow .article .more .hide { display: none; }
.ob-srow .article .more:has(input:checked) .show { display: none; }
.ob-srow .article .more:has(input:checked) .hide { display: inline; }

/* a row with no name has nothing above its text, so the text lines up on the face itself */
.ob-srow.post .body > .text:first-child { margin-top: 11px; }

/* A THREAD THAT STOPPED PART WAY: under each part, how far it got (row.html, Event.MarkSent) */
.ob-srow .sent { margin-top: 6px; font-size: 12.5px; font-weight: 700; display: flex; gap: 8px; align-items: baseline; }
.ob-srow .sent.ok { color: var(--ob-mint-ink); }
.ob-srow .sent.bad { color: var(--ob-rose-ink); }
.ob-srow .sent.warn { color: var(--ob-butter-ink); }
.ob-srow .sent.off { color: var(--ob-muted); }
.ob-srow .sent a { color: var(--ob-accent-ink); font-weight: 600; text-decoration: none; }
.ob-srow .sent a:hover { text-decoration: underline; }

/* A POST NOBODY IS ATTACHED TO. A read comes back with no people in it, so the rows of a read
   that has run have a time and words and nothing else - and a face column standing empty beside
   them is 56px spent saying "we do not know". The column goes, and the words take it. */
.ob-srow.post.anon > .col { display: none; }
.ob-srow.post.anon .who .when { margin-left: 0; }

/* two rows sit a post apart */
.ob-srow + .ob-srow { margin-top: var(--ob-stage-gap); }

/* A SPINED ROW IS JOINED TO THE NEXT ONE, NOT SPACED FROM IT. The gap goes inside the row, as
   padding under its body, so the face column - which stretches to the row's full height - has
   something to run its line down through. Put the gap between the rows instead and the line
   stops short of the next face and the thread comes apart. */
.ob-srow:has(.spine) + .ob-srow { margin-top: 0; }
.ob-srow:has(.spine):not(:last-child) .body { padding-bottom: var(--ob-stage-spine-gap); }

/* ---- the silhouette ----
   Grey and wordless: what a read is about to bring back, drawn before it has. */
.ob-srow.skel {
    .face {
        width: var(--ob-stage-av);
        height: var(--ob-stage-av);
        border-radius: 50%;
        background: var(--ob-skel-face-2);
        flex: 0 0 auto;
    }

    .who { gap: 8px; padding-top: 6px; }

    i {
        display: block;
        height: 8px;
        border-radius: 4px;
        background: var(--ob-skel);
    }
    i.hd { background: var(--ob-skel-2); }
    i.ln { height: 9px; margin-top: 8px; background: var(--ob-skel); }
    i.ln:first-of-type { margin-top: 11px; }

    .spine { background: var(--ob-skel); }
}

/* a post about to be struck out of the world: the only silhouette that means something bad */
.ob-srow.skel.strike {
    .face { background: var(--ob-rose-chip); }
    i { background: var(--ob-rose-chip); }
    position: relative;
    &::after {
        content: '';
        position: absolute;
        left: 0; right: 0; top: 50%;
        height: 1.5px;
        background: var(--ob-rose-edge);
    }
}

/* a profile card that came back from a read is a shorter row - a face, a name, a line about
   them - so its face is smaller and its column narrower */
.ob-srow.person {
    > .col { flex-basis: 30px; }
    .name { font-size: 14px; }
    & + .ob-srow.person { margin-top: 14px; }
}

/* ---- a read of many: a row of small silhouettes, side by side ----
   Never a column of fifty. Each takes a fixed share of the row - a third for a post, a fifth
   for a person - so a silhouette is the same size whether the read is of two or of fifty, and
   the count is in the foot rather than in the picture. */
.ob-stage .rows.strip {
    display: flex;
    align-items: center;
    gap: var(--ob-strip-gap);

    > .ob-srow { margin-top: 0; flex: 0 0 calc((100% - 2 * var(--ob-strip-gap)) / 3); }
    > .ob-srow + .ob-srow { margin-top: 0; }

    &.user { gap: var(--ob-strip-gap-people); }
    &.user > .ob-srow { flex-basis: calc((100% - 4 * var(--ob-strip-gap-people)) / 5); }

    /* more than the row holds: it is a full row, so its silhouettes share it out between them */
    &.many > .ob-srow { flex: 1 1 0; }

    /* THE ROW FADES TO THE RIGHT, a step a silhouette: the first is the thing about to arrive and
       the rest are more of it, so the eye reads one and a direction rather than five of equal
       weight. A narrow card drops the last one and the steps before it stay where they were. */
    > .ob-srow:nth-child(2) { opacity: .74; }
    > .ob-srow:nth-child(3) { opacity: .52; }
    > .ob-srow:nth-child(4) { opacity: .38; }
    > .ob-srow:nth-child(5) { opacity: .28; }
}

/* a post's silhouette at two thirds: the same face, name and handle and three lines */
.ob-srow.mini {
    gap: 10px;
    > .col { flex-basis: 30px; }
    .face { width: 30px; height: 30px; }
    .who { gap: 6px; padding-top: 4px; overflow: hidden; }
    i { height: 7px; flex: 0 1 auto; min-width: 0; }
    i.ln { height: 8px; margin-top: 7px; }
    i.ln:first-of-type { margin-top: 10px; }
}

/* a person: a face, a name and a handle, and nothing under them */
.ob-srow.mini.person {
    align-items: center;
    > .col { flex-basis: 36px; align-self: auto; }
    .face { width: 36px; height: 36px; }
    i { height: 8px; }
    i.hd { height: 7px; margin-top: 7px; }
}

/* A NARROWER CARD DROPS A SILHOUETTE, never the count: a full row of three posts becomes two and
   one of five people becomes four. Only a full row - a read of two or three draws exactly that
   many, and taking one away would draw a different number than the foot says. */
@container (max-width: 600px) {
    .ob-stage .rows.strip.many > .ob-srow:last-child { display: none; }
}

/* ============================================================
   THE CARD
   The frame is the card: everything inside it is being decided, or was.
   ============================================================ */
/* THE GROUND SAYS IT IS OURS AND UNFINISHED (2026-09-22). A write waiting to be answered sits on
   the faintest blue there is - not a colour so much as a temperature - and opening its words
   deepens it one step (.editing, below). The two grounds are a pair, which is what lets the
   change between them be the whole of the editing state: nothing is drawn on the words. */
.ob-frame {
    /* THE GROUND IS ONE VALUE, set by each kind of card below, because the faces in the foot
       cut their rings out of it and must cut the right colour */
    --ob-ground: var(--ob-proposed);
    /* ...and the provider's badge on our own profile's face cuts its ring out of it too */
    --ob-badge-ring: var(--ob-ground);
    position: relative;
    padding: var(--ob-frame-pad-y) var(--ob-frame-pad-x) var(--ob-frame-pad-b);
    border: var(--ob-frame-line) solid var(--ob-accent-ring);
    border-radius: var(--ob-frame-radius);
    background: var(--ob-ground);
    transition: background-color .18s ease, border-color .18s ease;
}

/* THE ACT, on the top line the way a legend sits on a fieldset: half above the card's edge and
   half inside it, its own edge the card's edge, so it reads as part of the frame rather than as
   a badge set on it. A glyph for which side of the line the call is on, then the words. */
.ob-flabel {
    position: absolute;
    top: calc(-1 * var(--ob-flabel-h) / 2);
    left: 18px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--ob-flabel-h);
    padding: 0 11px 0 8px;
    border-radius: 999px;
    background: var(--ob-accent-bg);
    color: var(--ob-accent-ink);
    border: var(--ob-frame-line) solid var(--ob-accent-ring);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .01em;
    white-space: nowrap;
    max-width: calc(100% - 36px);

    svg { width: 14px; height: 14px; flex: 0 0 auto; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
    .t { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
}

/* THE SECOND CHIP: the words on this card are not the agent's, because somebody changed them.
   It sits at the other end of the top line, quiet - grey against the blue - because it is a
   footnote on what the first one says rather than a second thing to read. */
.ob-frame .ob-flabel.edited {
    left: auto;
    right: 18px;
    padding: 0 11px;
    background: var(--ob-canvas);
    color: var(--ob-ink-2);
    border-color: var(--ob-line-chip);
    font-weight: 600;
}

/* "and 36 more": the card has more behind it than it can draw */
.ob-stage > .ob-frame > .more,
.ob-stage > .subject > .more {
    margin-top: 12px;
    padding-top: 11px;
    border-top: 1px solid var(--ob-line-in);
    font-size: 12.5px;
    font-weight: 700;
    color: var(--ob-faint-1);
}

/* a card round something destructive takes the rose edge and the rose chip - and its own pale
   ground, because the blue one under every other card would be two temperatures on one card */
.ob-frame.rose {
    --ob-ground: var(--ob-rose-card);
    border-color: var(--ob-rose-chip);
    .ob-flabel { background: var(--ob-rose-bg); color: var(--ob-rose-ink); border-color: var(--ob-rose-chip); }
}

/* A READ WAITING TO BE ANSWERED (2026-09-25): its own colour (the --ob-read tokens) where a
   write is blue - the edge, the chip and the button, and nothing else. The blue is kept for
   what goes out to X, so a post in a column of reads is the thing the eye lands on. Before
   .done, which wins once it has run.
   A read is fetched, not written, so it sits on the card's plain white rather than the blue. */
.ob-frame.read {
    --ob-ground: var(--ob-card);
    border-color: var(--ob-read-ring);
    .ob-flabel { background: var(--ob-read-bg); color: var(--ob-read-ink); border-color: var(--ob-read-ring); }
    .ob-sfoot .ob-pill.primary { background: var(--ob-read); }
    .ob-sfoot .ob-pill.primary:hover { background: var(--ob-read-lift); }
}

/* ANSWERED ALREADY. The detail draws a call long after it was decided, and the blue of a thing
   waiting for a person would be the one untrue thing on it: a call that has run is drawn in
   grey, on white. */
.ob-frame.done {
    --ob-ground: var(--ob-card);
    border-color: var(--ob-line);
    .ob-flabel { background: var(--ob-card); color: var(--ob-ink-2); border-color: var(--ob-line); }
}

/* The one card the eye should land on: the oldest write still waiting (or read, when no write
   is - home/queue.js), breathing on a slow cycle. ONE on the page and never a stack of them - a column of pulsing borders is noise, and
   noise is the thing this screen exists to remove. Its chip's edge goes solid with it. */
.ob-frame.now {
    border-color: var(--ob-accent);
    animation: ob-frame-attention 2.8s ease-in-out infinite;
    .ob-flabel { border-color: var(--ob-accent); }
}
@keyframes ob-frame-attention {
    0%, 100% { box-shadow: 0 0 0 0 var(--ob-accent-halo-0); }
    50%      { box-shadow: 0 0 0 6px var(--ob-accent-halo-1); }
}

/* a destructive card breathes in its own colour: a blue ring round a rose card says the two
   halves of the same card disagree about how serious it is */
.ob-frame.rose.now {
    border-color: var(--ob-rose-edge);
    animation: ob-frame-attention-rose 2.8s ease-in-out infinite;
    .ob-flabel { border-color: var(--ob-rose-edge); }
}
@keyframes ob-frame-attention-rose {
    0%, 100% { box-shadow: 0 0 0 0 var(--ob-rose-halo-0); }
    50%      { box-shadow: 0 0 0 6px var(--ob-rose-halo-1); }
}
/* ...and a read in its own: the accent's ring round a read would be the write's colour on it */
.ob-frame.read.now {
    border-color: var(--ob-read);
    animation: ob-frame-attention-read 2.8s ease-in-out infinite;
    .ob-flabel { border-color: var(--ob-read); }
}
@keyframes ob-frame-attention-read {
    0%, 100% { box-shadow: 0 0 0 0 var(--ob-read-halo-0); }
    50%      { box-shadow: 0 0 0 6px var(--ob-read-halo-1); }
}
@media (prefers-reduced-motion: reduce) {
    .ob-frame.now, .ob-frame.rose.now, .ob-frame.read.now { animation: none; }
}

/* ============================================================
   THE FOOT
   The answer on the left; a little space; the things a person can change as quiet glyph links -
   the words, the time, how many a read takes; and at the right, on Home, the agent and the
   account stacked with the price, which is the door into the detail. stage/foot.html.
   ============================================================ */
.ob-sfoot {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 18px;

    a.ob-pill { text-decoration: none; }

    .tools {
        display: inline-flex;
        align-items: center;
        flex-wrap: wrap;
        gap: 4px 18px;
        margin-left: 14px;
    }
    /* ...unless there is no answer before them: a read that has run has only its count */
    .tools:first-child { margin-left: 0; }
    /* a pill is busy (2026-10-03): the words, the time and the count wait for its answer */
    .tools.waiting { opacity: .45; pointer-events: none; }

    .tool {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        height: 32px;
        padding: 0 2px;
        border: 0;
        background: none;
        font-family: var(--ob-font);
        font-size: 13px;
        font-weight: 700;
        color: var(--ob-ink-2);
        white-space: nowrap;

        svg { width: 14px; height: 14px; flex: 0 0 auto; fill: none; stroke: var(--ob-muted); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
    }
    /* the money's warning: its month will not cover it as things stand (Caps.Uncovered) */
    .tool.warn { color: var(--ob-butter-ink); }
    .tool.warn svg { stroke: var(--ob-butter-ink); }
    button.tool { cursor: pointer; }
    button.tool:hover { color: var(--ob-ink); }
    button.tool:hover svg { stroke: var(--ob-ink-2); }
    button.tool:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; border-radius: 6px; }

    /* WHO ASKED, AS WHOM, AND WHAT IT COSTS - small, quiet, and one door */
    .door {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        height: 32px;
        margin-left: auto;
        padding: 0 6px 0 4px;
        border: 0;
        border-radius: 999px;
        background: transparent;
        font-family: var(--ob-font);
        cursor: pointer;

        /* the two faces overlap, each ringed in the card's own ground so the edge between them
           is cut rather than drawn */
        .faces { display: flex; }
        .faces .ob-circle { box-shadow: 0 0 0 2px var(--ob-ground, var(--ob-card)); }
        .faces > * + * { margin-left: -6px; }
        .ob-amt { font-size: 12px; }
        .chev { font-size: 15px; font-weight: 700; color: var(--ob-faint-3); line-height: 1; }

        &:hover { background: var(--ob-canvas); }
        &:hover .chev { color: var(--ob-muted); }
        &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
    }

    /* the detail's corner: the account the read was made as, where Home puts its door */
    .who {
        display: inline-flex;
        margin-left: auto;
        padding: 0 4px;
    }
}

/* POST IT YOURSELF (2026-10-01, Dave): a band under the foot, on a ground of its own to the card's
   edges, with the way out as the solid button and why at the right in the card's own blue. The
   card's answer above it goes hollow while it shows (DecisionCard.bar, ItemView.bar) */
.ob-sfoot.ob-band {
    --ob-ground: var(--ob-band);
    --ob-badge-ring: var(--ob-band);
    flex-wrap: nowrap;
    gap: 12px;
    margin: 16px calc(-1 * var(--ob-frame-pad-x)) calc(-1 * var(--ob-frame-pad-b));
    padding: 10px var(--ob-frame-pad-x);
    background: var(--ob-band);
    border-top: 1px solid var(--ob-line-in);
    border-radius: 0 0 calc(var(--ob-frame-radius) - var(--ob-frame-line)) calc(var(--ob-frame-radius) - var(--ob-frame-line));

    .tools { margin-left: auto; min-width: 0; }
    .tool.reason { color: var(--ob-accent-ink); white-space: normal; text-align: right; height: auto; min-height: 32px; }
    .tool.reason svg { stroke: var(--ob-accent-ink); }
    button.tool.reason:hover { color: var(--ob-accent); text-decoration: underline; text-underline-offset: 3px; }
    button.tool.reason:hover svg { stroke: var(--ob-accent); }
}
/* ...THE QUIET BAND (2026-10-01, Dave): a long post on an account that takes long posts. The same
   ground, a plain button, and why in the meta grey with a tick, since the network should take it */
.ob-sfoot.ob-band.quiet {
    .tool.reason { color: var(--ob-muted); }
    .tool.reason svg { stroke: var(--ob-muted); }
    button.tool.reason:hover { color: var(--ob-ink-2); }
    button.tool.reason:hover svg { stroke: var(--ob-ink-2); }
}
/* ...and under a post the detail draws with no card round it, a band of its own shape */
.ob-stage > .subject .ob-sfoot.ob-band {
    margin: 16px 0 0;
    border-top: 0;
    border-radius: var(--ob-frame-radius);
}
/* ============================================================
   THE WORDS, OPEN
   Somebody pressed Edit. TWO VALUES CHANGE AND THAT IS ALL: this frame's ground and its edge.
   Nothing is drawn on the words - no box, no border, no ring, not even on focus - because a
   field inside the picture is the form this screen exists not to be, and the tinted frame
   around them is what says the post is open. The caret says where the typing goes.

   Everything a thread needs beyond that lives in the GUTTER, which is already a fixed 44px in
   both modes and empty below the face: the face is the grip, a badge on its corner removes that
   part, and a dashed circle at the foot adds one. The text column is never narrowed, so not one
   line rewraps at the moment a post opens - which is the whole trick.

   The markup is stage/stage.html and stage/row.html; the session is stage/edit.js.
   ============================================================ */
.ob-frame.editing {
    --ob-ground: var(--ob-editing);
    border-color: var(--ob-accent);

    /* the label belongs to the frame, so it takes the frame's solid edge with it */
    .ob-flabel { border-color: var(--ob-accent); }

    /* the joins between open parts, a step firmer than the ones on a card being read */
    .ob-srow .spine { background: var(--ob-editing-spine); }
}

/* the one element that is the same in both modes. `cursor: text` is the only thing it gains,
   because it is the only thing about it that is different */
.ob-srow .text.ob-ed {
    outline: none;
    cursor: text;
    min-height: 23px;           /* an empty part is still a post: it keeps a line's worth of room */
    -webkit-user-modify: read-write-plaintext-only;
}

/* WHAT IS LEFT OF THE 280, AND ONLY WHEN IT IS CLOSE. A counter that is always there is
   furniture on a screen that is mostly not about typing, so it appears at forty and turns the
   warning's colour past 280: X may refuse a longer post unless the account has Premium, so it
   is a caution rather than an error (2026-09-30). */
.ob-srow .body > .left {
    margin-top: 5px;
    font-size: 12px;
    font-weight: 700;
    color: var(--ob-faint-1);
    font-variant-numeric: tabular-nums;

    &.over { color: var(--ob-butter-ink); }
}

/* ---- the gutter's two controls ---- */

/* the face, as a button. It keeps every pixel of the circle it was: a grip that looked
   different from the face would be a second thing in a column that has room for one */
.ob-srow .ob-circle.lg.handle {
    padding: 0;
    border: 0;
    cursor: grab;

    &:active { cursor: grabbing; }
    &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
}

/* ...and the badge on its corner. Quiet until it is pointed at, and gone when there is only one
   post left, because removing the last part of a thread is refusing the whole thing - which is
   what No is for. */
.ob-srow > .col > .rm {
    position: absolute;
    top: 28px;
    left: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 19px;
    height: 19px;
    padding: 0;
    border: 1px solid var(--ob-line);
    border-radius: 50%;
    background: var(--ob-card);
    color: var(--ob-muted);
    cursor: pointer;
    box-shadow: 0 0 0 2px var(--ob-editing);
    transition: background .12s, color .12s, border-color .12s;

    svg { width: 8px; height: 8px; }
    &:hover { background: var(--ob-rose-bg); border-color: var(--ob-rose-chip); color: var(--ob-rose-ink); }
    &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
}
/* the column has to be the badge's frame of reference, and only while there is a badge in it */
.ob-srow.open > .col { position: relative; }

/* the part in the air: still in the list, still in its new place, just quieter. Lifting it out
   and following the cursor with it would mean the list was one row short of what it is about
   to be, which is the one thing a person dragging needs to see. */
.ob-srow.lifted { opacity: .4; }

/* ---- the row that adds one ----
   THE SAME GEOMETRY AS A POST, so the spine runs out of the last face and into the dashed
   circle and the foot of the frame reads as the thread carrying on. A button parked under the
   list would read as a toolbar, which is the thing this design does not have.

   The part above it is given a little more room than the parts have between themselves: the
   circle is a control rather than a post, and it should not crowd the words it follows. */
.ob-stage .ob-srow:has(+ .addpart) .body { padding-bottom: calc(var(--ob-stage-spine-gap) + 5px); }
.ob-stage .ob-srow.addpart {
    display: flex;
    align-items: center;
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    font-family: var(--ob-font);
    text-align: left;
    cursor: pointer;

    > .col {
        display: flex;
        align-items: center;
        justify-content: center;
        flex: 0 0 var(--ob-stage-face);
    }

    .ring {
        display: flex;
        align-items: center;
        justify-content: center;
        width: var(--ob-stage-av);
        height: var(--ob-stage-av);
        border: 1.5px dashed var(--ob-faint-2);
        border-radius: 50%;
        color: var(--ob-muted);
        transition: border-color .12s, color .12s;
    }
    .ring svg { width: 15px; height: 15px; }

    .lbl {
        font-size: 15.5px;
        font-weight: 600;
        color: var(--ob-muted);
        transition: color .12s;
    }

    &:hover .ring { border-color: var(--ob-accent); color: var(--ob-accent); }
    &:hover .lbl { color: var(--ob-accent-ink); }
    &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 3px; border-radius: 12px; }
}

/* NOTHING TO CARRY ON FROM YET (Dave, 2026-10-04): while the last part is empty the row is in the
   markup but hidden (stage.js, add_waits), and the part above it closes up as if it were the
   last - no line out of its face, no gap under its words. The first key shows it (edit.js, typed). */
.ob-stage .ob-srow.addpart[hidden] { display: none; }
.ob-stage .ob-srow:has(+ .addpart[hidden]) .spine { display: none; }
.ob-stage .ob-srow:has(+ .addpart[hidden]) .body { padding-bottom: 0; }

/* the one sentence this picture is ever allowed: the server would not take the edit, and why */
.ob-frame .ob-ederr {
    margin-top: 14px;
    font-size: 12.5px;
    font-weight: 700;
    color: var(--ob-rose-ink);
}

/* ---- the fold ---- */
@media (max-width: 760px) {
    .ob-frame { padding: 24px 14px 14px; }
    .ob-stage > .subject { padding: 0 calc(14px + var(--ob-frame-line)); }
    .ob-srow .text { font-size: 15px; }
    .ob-srow > .col { flex-basis: 36px; }
    .ob-srow .ob-circle.lg { width: 36px; height: 36px; font-size: 15px; }
    .ob-srow.skel .face { width: 36px; height: 36px; }
    .ob-frame .ob-sfoot.ob-band { margin: 14px -14px -14px; padding: 10px 14px; flex-wrap: wrap; }
    .ob-frame .ob-sfoot.ob-band .tool.reason { text-align: left; }
}

/* THE CAPTION ON A CARD NO SCENE DRAWS, and the link card under a post: the decision card draws
   the one, the stage's rows the other */
.ob-pcap {
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.45;
    color: var(--ob-muted);
    margin: 0 0 11px;
    b { color: var(--ob-ink-2); }
}

.ob-link {
    display: flex;
    margin-top: 12px;
    border: 1px solid var(--ob-line-in);
    border-radius: var(--ob-radius-tile);
    overflow: hidden;
    text-decoration: none;
    color: var(--ob-ink);

    .img { width: 110px; flex: 0 0 auto; background: var(--ob-canvas) center / cover no-repeat; }
    .meta { display: flex; flex-direction: column; gap: 3px; padding: 12px 14px; min-width: 0; }
    .domain { font-size: 12px; font-weight: 600; color: var(--ob-muted); }
    .title { font-size: 14px; font-weight: 700; line-height: 1.35; }
}

/* ============================================================
   IMAGES ON A POST (2026-10-08): stage/images.js, stage/row.html, stage/inside.html.

   X's arrangement for one to four, with a gap between them so each reads as its own picture
   rather than one flush grid (Dave). The round x on each takes it off, in or out of edit mode;
   a GIF is its first frame, marked. Tiles still on their way wait in the same grid. A file held
   over the frame gives it the accent edge and shows the drop area; over a thread, the part under
   the pointer lights instead, and the drop lands on that part only.
   ============================================================ */
.ob-media { margin-top: 12px; display: grid; gap: 8px; }
.ob-media .m { position: relative; min-height: 0; background: var(--ob-canvas); border-radius: 12px; overflow: hidden; box-shadow: inset 0 0 0 1px var(--ob-line-in); }
.ob-media .m img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ob-media.n1 { grid-template-columns: minmax(0, 1fr); }
.ob-media.n1 .m { border-radius: 16px; aspect-ratio: 16 / 10; }
.ob-media.n2 { grid-template-columns: repeat(2, minmax(0, 1fr)); height: 200px; }
.ob-media.n3 { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); height: 230px; }
.ob-media.n3 .m:first-child { grid-row: 1 / 3; }
.ob-media.n4 { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); height: 250px; }

/* the preview no longer kept: the thumbnail stands in at its own small size, never stretched */
.ob-media .m.small { width: 96px; height: 72px; aspect-ratio: auto; justify-self: start; align-self: start; border-radius: 10px; }

.ob-media .x {
    position: absolute; top: 8px; right: 8px; width: 26px; height: 26px; padding: 0;
    border: 0; border-radius: 50%; background: rgba(15, 17, 22, .66); color: #fff;
    display: flex; align-items: center; justify-content: center; cursor: pointer;
    svg { width: 12px; height: 12px; }
}
.ob-media .x:hover { background: var(--ob-rose-ink); }
.ob-media .x:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
.ob-media .gif { position: absolute; left: 8px; bottom: 8px; height: 20px; padding: 0 7px; border-radius: 6px; background: rgba(15, 17, 22, .72); color: #fff; font-size: 10.5px; font-weight: 800; letter-spacing: .06em; line-height: 20px; }
/* ON ITS WAY (Dave, 2026-10-08): the person's own file, drawn at once and pulsing until the server
   has it; a tile with nothing the browser can draw (HEIC) pulses on its own */
.ob-media .m.up { animation: ob-media-up 0.9s ease-in-out infinite alternate; }
@keyframes ob-media-up { from { opacity: .95; } to { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .ob-media .m.up { animation: none; opacity: .6; } }

/* A FILE HELD OVER A POST THAT TAKES IMAGES (Dave, 2026-10-08): the whole of it lights - a framed card
   takes the accent edge and the open ground, a post drawn on its own takes an accent ring and tint -
   and "Drop an image" sits where the label sits, in the label's own shape. Over a thread, the part
   under the pointer lights as well, and the drop lands on that part only. */
.ob-dropchip { display: none; }
.ob-stage.over .ob-dropchip { display: inline-flex; z-index: 2; }
.ob-stage.over .ob-flabel:not(.ob-dropchip) { visibility: hidden; }
.ob-dropchip svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.ob-stage.over > .ob-frame { border-color: var(--ob-accent); --ob-ground: var(--ob-editing); }
.ob-stage.over > .ob-frame .ob-dropchip { background: var(--ob-accent); color: #fff; border-color: var(--ob-accent); }

.ob-stage > .subject { position: relative; }
.ob-stage.over > .subject { border-radius: 16px; background: var(--ob-editing); box-shadow: 0 0 0 1.5px var(--ob-accent), 0 0 0 8px var(--ob-editing); }
.ob-stage.over > .subject .ob-dropchip { top: calc(-8px - var(--ob-flabel-h) / 2); left: 10px; background: var(--ob-accent); color: #fff; border-color: var(--ob-accent); }

.ob-stage.takes-images:focus { outline: none; }
.ob-srow.target .body { background: var(--ob-accent-bg); border-radius: 12px; margin: -6px -10px; padding: 6px 10px; }

/* THE FOOT'S WORDS (Dave, 2026-10-08): Edit and Image keep theirs while the foot fits on one line,
   and both drop to their glyphs when it would wrap (Stage.fit_feet puts .tight on). A tool with no
   word at all is the glyph alone */
.ob-sfoot.tight .tool.squeeze .w { display: none; }
.ob-sfoot.tight .tool.squeeze,
.ob-sfoot .tool.bare { padding: 0 4px; }
.ob-sfoot button.tool:disabled { opacity: .4; cursor: default; }
 /* ============================================
   OKAYBOT SITE
   The outside pages: the homepage, pricing, your-own-key, the docs and the two legal
   pages. One column down the middle of the canvas with a top bar over it, in the product's own
   palette and type, so that a person who clicks from the pricing page into the dashboard has
   stayed in one place.

   body.ob-site is the switch. The sidebar sets it whenever the router lands on a page whose
   menu is "site" (sidebar.js, select) and clears it on any other page, so it holds whether or
   not anybody is signed in: a signed-in person reading the docs sees the top bar and a
   Dashboard button, not the sidebar. It has to beat both the scaffold's chrome (an absolute
   #Main under a 4rem menu bar, a gradient on the body) and body.ob-app's (the fixed sidebar and
   the padded #Main), so its selectors carry one more level than either.
   ============================================ */

body.ob-site {
    background: var(--ob-canvas);
    font-family: var(--ob-font);
    color: var(--ob-ink);
    -webkit-font-smoothing: antialiased;
}
body.ob-site::before { display: none; }
html body.ob-site #Sidebar,
html body.ob-site .site-footer { display: none; }

html body.ob-site #Main {
    position: static;
    margin: 0;
    padding: 0;
    min-height: 100vh;
}

/* The switch and the column share a name, so the body picks up the column's own max-width and
   side padding and every outside page ends up boxed: a full-bleed section like .ob-band cannot
   reach the edge of the window, and the ceremony's "full-viewport takeover" is not one. Undo
   the column on the body - the .ob-site divs inside each page still draw it. */
html body.ob-site {
    max-width: none;
    margin: 0;
    padding: 0;
}

/* the column every outside page lives in */
.ob-site {
    max-width: var(--ob-site-w);
    margin: 0 auto;
    padding: 0 var(--ob-site-pad);
    font-family: var(--ob-font);
    color: var(--ob-ink);
}

/* ---- the top bar ---- */
/* The bar is mounted in a <div widget="nav"> exactly its own height, and sticky only moves inside
   its parent, so it scrolled away with the page. Without a box of its own the mount hands the bar
   the whole page to stick in (found in the cross-browser pass, 2026-10-06; every engine alike) */
.ob-sitewrap > [widget="nav"] { display: contents; }
.ob-sitenav {
    position: sticky;
    top: 0;
    z-index: 60;
    background: var(--ob-site-nav-bg);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--ob-line);

    .inner {
        max-width: var(--ob-site-w);
        margin: 0 auto;
        padding: 0 var(--ob-site-pad);
        height: var(--ob-site-nav-h);
        display: flex;
        align-items: center;
        gap: 26px;
    }

    .links {
        display: flex;
        align-items: center;
        gap: 2px;
    }
    .links a {
        display: inline-flex;
        align-items: center;
        height: 36px;
        padding: 0 13px;
        border-radius: 10px;
        font-size: 14px;
        font-weight: 600;
        color: var(--ob-ink-2);
        text-decoration: none;
    }
    .links a:hover { background: var(--ob-card); color: var(--ob-ink); text-decoration: none; }
    .links a.on { background: var(--ob-accent-bg); color: var(--ob-accent-ink); font-weight: 700; }

    .right {
        margin-left: auto;
        display: flex;
        align-items: center;
        gap: 14px;
    }
    /* THE DASHBOARD BUTTON CARRIES THE FACE. The person's name used to sit beside it in a
       .who; the picture says whose dashboard it is in less room and without a second thing to
       read. The circle is 30px in a 38px pill, so the ground shows as an even 4px above, below
       and to the left, and the label keeps the pill's own 18px on the right. */
    .ob-pill.face {
        padding-left: 4px;
        gap: 9px;
    }
}

/* the logo, the sidebar's own at the same size */
.ob-sitelogo {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    color: var(--ob-ink);

    /* THE MARK ALONE, NOT THE TILE, the same as the sidebar's (okaybot-page.css): the bot and
       the tick in the accent on nothing at all, so the logo sits on the page's own ground
       rather than carrying a blue square around with it. No radius, because there is no ground
       left to round.

       It is a mask, the way .ob-square.bot draws the same mark, so the path stays in the
       stylesheet and no template carries it - and a dark ground can repaint it white with one
       line, since the colour is the fill and nothing else.
       The box is the mono file's own aspect, 376 by 456, so `contain` fills the slot top to
       bottom and the wordmark sits a true 10px off the ink rather than off empty space. */
    .mark {
        display: block;
        width: 25px;
        height: 30px;
        background: var(--ob-accent);
        -webkit-mask: url(/images/logo/okaybot-mono.svg) center / contain no-repeat;
        mask: url(/images/logo/okaybot-mono.svg) center / contain no-repeat;
    }
    .name { font-size: 17px; font-weight: 800; letter-spacing: -.02em; }

    &:hover { color: var(--ob-ink); text-decoration: none; }
}

/* ---- type roles ---- */
.ob-eyebrow {
    margin: 0 0 14px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ob-accent-ink);
}

.ob-h1 {
    margin: 0 0 18px;
    font-family: var(--ob-font);
    font-size: 60px;
    font-weight: 800;
    letter-spacing: -.03em;
    line-height: 1.02;
    color: var(--ob-ink);
    text-wrap: balance;
}

.ob-h2 {
    margin: 0 0 14px;
    font-family: var(--ob-font);
    font-size: 38px;
    font-weight: 800;
    letter-spacing: -.025em;
    line-height: 1.1;
    color: var(--ob-ink);
    text-wrap: balance;
}

.ob-h3 {
    margin: 0 0 8px;
    font-family: var(--ob-font);
    font-size: 19px;
    font-weight: 800;
    letter-spacing: -.015em;
    line-height: 1.2;
    color: var(--ob-ink);
}

.ob-lead {
    margin: 0;
    font-size: 19px;
    font-weight: 500;
    line-height: 1.55;
    color: var(--ob-ink-2);
    max-width: 600px;
}

.ob-copy {
    margin: 0;
    font-size: 15px;
    font-weight: 500;
    line-height: 1.6;
    color: var(--ob-ink-2);
}
.ob-copy + .ob-copy { margin-top: 10px; }
.ob-copy b, .ob-lead b { font-weight: 700; color: var(--ob-ink); }
.ob-copy a, .ob-lead a { color: var(--ob-accent-ink); text-decoration: none; font-weight: 600; }
.ob-copy a:hover, .ob-lead a:hover { text-decoration: underline; }

/* the muted line under a section heading. Not .ob-sub, which is the page header's 13.5px line */
.ob-site-sub {
    margin: 0 0 40px;
    font-size: 17px;
    font-weight: 500;
    line-height: 1.55;
    color: var(--ob-ink-2);
    max-width: 640px;
}

/* ---- the hero ---- */
.ob-hero {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: 80px;
    align-items: center;
    padding: 104px 0 80px;
}

/* THE HERO, STACKED (the homepage, 2026-09-25): the words centred, then Home as it really is,
   the width of the column. A capture of a whole screen wants the whole column; beside the words
   it came out at half size and unreadable. Since 2026-09-29 it is Home without its log, the
   sidebar and what is waiting, at no more than its own width (--w on the figure) */
.ob-hero.stack {
    display: block;
    padding: 96px 0 24px;

    .words { max-width: 1120px; margin: 0 auto; text-align: center; }
    .ob-h1 { font-size: 72px; margin-bottom: 14px; }        /* a short line, so it can be bigger */
    .ob-subhead { margin: 0 auto; max-width: 760px; font-size: 26px; font-weight: 600; letter-spacing: -.015em; line-height: 1.35; color: var(--ob-ink-2); }
    .ob-ctas, .ob-ticks { justify-content: center; }
    .ob-shot { max-width: min(var(--w, 1120px), 1120px); margin: 64px auto 0; }     /* big, and still the height of a screen */
}

.ob-ctas {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin: 28px 0 0;
}
.ob-ctas .ob-pill { height: 46px; padding: 0 24px; font-size: 15px; }

.ob-ticks {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 22px;
    margin: 30px 0 0;
    padding: 0;
    list-style: none;
    font-size: 14px;
    font-weight: 600;
    color: var(--ob-ink-2);

    li { display: inline-flex; align-items: center; gap: 7px; }
    svg { width: 16px; height: 16px; color: var(--ob-mint-ink); flex: 0 0 auto; }
}

/* ---- a section ---- */
.ob-sect {
    padding: var(--ob-site-sect) 0;
    border-top: 1px solid var(--ob-line);
}
.ob-sect.tight { padding: 40px 0; }
.ob-sect.first { border-top: 0; }
.ob-sect .head { max-width: 740px; margin-bottom: 44px; }

/* ---- the belt: the runtimes, as names ---- */
.ob-belt {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: center;        /* under the hero, which is centred in the column */
    gap: 10px 34px;
    font-size: 15px;
    font-weight: 700;
    color: var(--ob-muted);

    .lbl { font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ob-faint-1); margin-right: 6px; }
    .name { color: var(--ob-ink-2); }
    .or { font-weight: 600; color: var(--ob-muted); }
}

/* ---- three across: the fixes, the steps, the plans ---- */
.ob-three {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--ob-site-gap);
}
.ob-two {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--ob-site-gap);
}

/* ---- the product, captured ----
   THE PICTURES ARE SCREENSHOTS (2026-09-25). Until then the product was drawn here in markup - a
   decision card, cap rows, key rows, a log - so it could not go stale on a deploy. It went stale
   anyway, quietly, as the product moved on under the drawings. They are now captures of the real
   screens in images/site/, taken at twice the pixels by dev/tools/site-shots; run it again when
   a screen changes.

   .ob-shot is the frame: the capture's own corners and a lift off the page, and nothing that
   pretends to be a browser. The image carries the radius. */
.ob-shot {
    position: relative;
    margin: 0;
    max-width: var(--w, none);     /* the capture's own width, set on the figure: never blown up past it */
    border-radius: 14px;
    background: var(--ob-card);
    box-shadow:
        0 0 0 1px rgba(22, 24, 29, .07),
        0 28px 60px -24px rgba(22, 24, 29, .26),
        0 10px 22px -12px rgba(22, 24, 29, .12);

    img { display: block; width: 100%; height: auto; border-radius: inherit; }
}

/* on ink the frame is the light itself, so the lift goes darker and the hairline goes */
.ob-shot.ink { box-shadow: 0 30px 70px -20px rgba(0, 0, 0, .6); }

/* THE THREE ASKS (Dave, 2026-09-25): a post, a read and a delete waiting, drawn by the product's
   own stage in a row under a plain title - no frame round them and no picture of them. They
   arrive as they do on Home (.ob-decide.pop, okaybot-home.css), one after another, the first
   time the row is seen; until then the row is .waiting and the cards are not drawn */
.ob-asks {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 22px;
    margin-top: 40px;

    /* one height across the row: each card stretches to the tallest and its foot sits at the
       bottom, so the three answers line up */
    > .ob-decide { display: flex; }
    > .ob-decide > .ob-stage { flex: 1 1 auto; display: flex; }
    > .ob-decide > .ob-stage > .ob-frame { flex: 1 1 auto; display: flex; flex-direction: column; }
    > .ob-decide > .ob-stage > .ob-frame > .ob-sfoot { margin-top: auto; padding-top: 18px; }     /* the stage's own 18px, kept as the least */
}
.ob-asks.waiting > .ob-decide { opacity: 0; }
/* the delete's silhouette without the line through it: on this page the rose says enough */
.ob-asks .ob-srow.skel.strike::after { display: none; }

/* A CHAPTER: the words on one side and ONE SMALL VIEW on the other - a part of one screen, never
   a whole one across the page (Dave, 2026-09-25: the full-width screens were overwhelming, and
   none of them fitted on a screen). The picture sits in the middle of its column at no more than
   its own size (--w on the figure). .flip puts the picture first; .below is a second row in the
   same band */
.ob-chapter {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    gap: 72px;
    align-items: center;

    .words .ob-copy.lg { margin-top: 4px; }
    > .ob-shot { width: 100%; justify-self: center; }
}
.ob-chapter.flip { grid-template-columns: minmax(0, 6fr) minmax(0, 5fr); }
.ob-chapter.below { margin-top: 96px; }
.ob-h3.big { font-size: 26px; letter-spacing: -.02em; margin-bottom: 10px; }

/* WHAT A CHAPTER SAYS YOU CAN DO: a tick and a few words, and no sentence under it (Dave,
   2026-09-25: the bold-line-and-sentence points were far too much text - "they'll get it").
   The hero's ticks, stacked beside a picture instead of in a row under the buttons */
.ob-checks {
    margin: 26px 0 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: 12px;

    li { display: flex; align-items: center; gap: 10px; font-size: 16px; font-weight: 700; color: var(--ob-ink); }
    svg { width: 18px; height: 18px; color: var(--ob-mint-ink); flex: 0 0 auto; }
}
.ob-band .ob-checks {
    li { color: #fff; }
    svg { color: var(--ob-accent-ring); }
}

/* THE BLOCK, drawn as an editor would show it (2026-09-25): a dark card with a bar across the
   top - what it is, and Copy - and the config under it coloured by what each part is. Its own
   colours, because the product's JSON ones (--ob-json-*) are for a light ground */
.ob-code {
    border-radius: 16px;
    background: var(--ob-site-dark);
    box-shadow: 0 30px 60px -28px rgba(22, 24, 29, .55), 0 0 0 1px rgba(22, 24, 29, .08);
    overflow: hidden;

    .bar {
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 16px 20px;
        border-bottom: 1px solid rgba(255, 255, 255, .08);
    }
    .bar .t { font-size: 13.5px; font-weight: 700; color: #fff; }
    pre {
        margin: 0;
        padding: 20px 22px 24px;
        font-family: var(--ob-mono);
        font-size: 13.5px;
        line-height: 1.75;
        color: #e8eaf1;
        white-space: pre;
        overflow-x: auto;
    }
    .k { color: #a9b6ff; }
    .s { color: #8fe3b0; }
    .p { color: #6b7180; }
}

/* ---- the plans ---- */
/* THREE PLANS, CENTRED (Dave, 2026-09-25). Agency is off the page for now (pricing.js), and the
   three that are left sit in the middle with the toggle over them at their width - and with
   nothing beside or under them: the cards that used to sit under the plans pulled the eye off
   the prices. It was four across until then, at a smaller padding and price. */
.ob-planset { max-width: 1080px; margin: 0 auto var(--ob-site-sect); }
.ob-planfoot { margin: 18px 0 0; text-align: center; font-size: 14px; font-weight: 600; line-height: 1.5; color: var(--ob-ink-2); }
.ob-plans {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 20px;
    margin-top: var(--ob-site-gap);
}
/* pricing's head, set like the homepage's: a short title on one line, a subheading under it
   (Dave, 2026-09-25) */
.ob-pagehead.mid { max-width: 1120px; margin: 0 auto; text-align: center; }
.ob-pagehead.mid .ob-lead { margin-left: auto; margin-right: auto; }
.ob-pagehead.mid .ob-subhead { margin: 0 auto; max-width: 900px; font-size: 22px; font-weight: 600; letter-spacing: -.01em; line-height: 1.4; color: var(--ob-ink-2); text-wrap: balance; }
.ob-pagehead.mid .ob-subhead a { color: var(--ob-accent-ink); text-decoration: none; }
.ob-pagehead.mid .ob-subhead a:hover { text-decoration: underline; }
/* signed in, the title is the plan you are on (2026-10-02): held, at its height, until the plan is known */
.ob-pagehead.wait { visibility: hidden; }
/* ...and so is each card's button, so a guest's Join the waitlist never shows first (billing/planset.js) */
.ob-plan .ob-pill.wait { visibility: hidden; }

/* AN OLDER PLAN'S BOX (2026-10-02): above the cards, filled like the lit card, the plan as paid on
   the left and Cancel plan or Keep on the right */
.ob-older {
    display: flex;
    align-items: center;
    gap: 20px;
    margin-bottom: 20px;
    padding: 20px 24px;
    border-radius: var(--ob-radius-card, 18px);
    background: linear-gradient(160deg, var(--ob-accent-lift), var(--ob-accent) 45%, var(--ob-accent-ink));
    color: #fff;

    .w { flex: 1 1 auto; min-width: 0; display: grid; gap: 4px; }
    .t { font-size: 17px; font-weight: 800; letter-spacing: -.01em; }
    .n { font-size: 14px; font-weight: 500; color: rgba(255, 255, 255, .85); }
    .ob-pill { flex: 0 0 auto; background: transparent; border-color: rgba(255, 255, 255, .55); color: #fff; }

    /* not the plan that bills next (a change waits, or it is set to end): white, like a card */
    &.quiet {
        background: var(--ob-card);
        border: 1px solid var(--ob-line);
        color: var(--ob-ink);
        .n { color: var(--ob-ink-2); }
        .ob-pill { border-color: var(--ob-faint-2); color: var(--ob-ink); }
    }
}

/* THE HEADLINE LIMIT. One panel per card, label and figure in the same blue. */
.ob-plan .limit {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 14px;
    padding: 13px 15px;
    border-radius: var(--ob-radius-tile);

    .lbl { display: flex; align-items: center; gap: 7px; font-size: 10.5px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase; }
    .big { font-size: 19px; font-weight: 800; letter-spacing: -.02em; line-height: 1.1; font-variant-numeric: tabular-nums; }

    &.ours { background: var(--ob-accent-bg); .lbl, .big { color: var(--ob-accent-ink); } }
}

.ob-plan {
    display: flex;
    flex-direction: column;
    padding: 28px 28px 26px;

    .tier { font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ob-muted); margin-bottom: 14px; }
    .price { display: flex; align-items: baseline; gap: 6px; }
    .price .n { font-size: 44px; font-weight: 800; letter-spacing: -.03em; color: var(--ob-ink); font-variant-numeric: tabular-nums; line-height: 1; }
    .price .per { font-size: 13px; font-weight: 600; color: var(--ob-muted); }
    .facts {
        margin: 18px 0 0;
        padding: 0;
        list-style: none;
        display: flex;
        flex-direction: column;
    }
    .facts li {
        display: flex;
        justify-content: space-between;
        gap: 12px;
        padding: 9px 0;
        border-top: 1px solid var(--ob-line-in);
        font-size: 13.5px;
        font-weight: 600;
        color: var(--ob-ink-2);
    }
    .facts li b { font-weight: 800; color: var(--ob-ink); font-variant-numeric: tabular-nums; }

    /* a heading inside the list, so "roughly, if you did only this" carries the either/or once
       instead of every row repeating it */
    .facts li.head { border-top: 0; padding-bottom: 4px; font-size: 10.5px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--ob-muted); }
    .facts.rough { margin-top: 14px; }
    .facts.rough + .facts li:first-child { border-top: 1px solid var(--ob-line-in); }

    /* THE "OR" ON THE LINE between posts read and posts published (Dave, 2026-10-02): the row's own
       hairline drawn as two lengths with the word between them, centred, in the heading's small
       capitals - so it works on the lit card's gradient as on white, where a word on a filled patch
       would show the patch */
    .facts li.or { position: relative; border-top: 0; }
    .facts li.or .orline {
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        transform: translateY(-50%);
        display: flex;
        align-items: center;
        gap: 8px;
        font-size: 10px;
        font-weight: 800;
        letter-spacing: .06em;
        line-height: 1;
        text-transform: uppercase;
        color: var(--ob-muted);
    }
    .facts li.or .orline::before,
    .facts li.or .orline::after { content: ""; flex: 1 1 0; border-top: 1px solid var(--ob-line-in); }

    /* the limit this card is NOT leading with: present, quiet, and never the big number */
    .facts li.other { color: #0f766e; b { color: #0f766e; } }

    .price .from { font-size: 17px; font-weight: 700; color: var(--ob-muted); }
    .ob-pill { margin-top: auto; }
    .facts:last-of-type { margin-bottom: 22px; }

    /* THE LIT CARD (Dave, 2026-10-02): the plan you are on, or Popular when you have none, filled
       in the accent with its chip in the corner, the way the eye is meant to land. Everything on
       it turns white; its primary button turns white with blue words, a ghost one an outline */
    position: relative;
    .chip { position: absolute; top: 22px; right: 22px; height: 26px; padding: 0 12px; border-radius: 999px; font-size: 12px; font-weight: 700; display: inline-flex; align-items: center; background: var(--ob-accent-bg); color: var(--ob-accent-ink); }
    &.lit {
        background: linear-gradient(160deg, var(--ob-accent-lift), var(--ob-accent) 45%, var(--ob-accent-ink));
        border-color: var(--ob-accent);
        box-shadow: 0 18px 40px -18px rgba(53, 71, 201, .55);
        .chip { background: rgba(255, 255, 255, .2); color: #fff; }
        .tier { color: rgba(255, 255, 255, .78); }
        .price .n { color: #fff; }
        .price .per, .price .from { color: rgba(255, 255, 255, .72); }
        .limit.ours { background: rgba(255, 255, 255, .14); .lbl, .big { color: #fff; } }
        .facts li { color: rgba(255, 255, 255, .82); border-top-color: rgba(255, 255, 255, .2); }
        .facts li b { color: #fff; }
        .facts li.head { color: rgba(255, 255, 255, .62); }
        .facts.rough + .facts li:first-child { border-top-color: rgba(255, 255, 255, .2); }
        .facts li.or .orline { color: rgba(255, 255, 255, .62); }
        .facts li.or .orline::before,
        .facts li.or .orline::after { border-top-color: rgba(255, 255, 255, .2); }
        .ob-pill.primary { background: #fff; border-color: #fff; color: var(--ob-accent-ink); }
        .ob-pill.ghost { background: transparent; border-color: rgba(255, 255, 255, .55); color: #fff; }
        .ob-pill:disabled { opacity: .7; }
    }

    /* AGENCY DOES NOT FLIP, because it is already own-key - that is what makes it Agency. Dark,
       so the row reads as three plans and a different kind of thing. */
    &.custom {
        background: var(--ob-ink);
        border-color: var(--ob-ink);
        .tier { color: #b6ece2; }
        .price .n { color: #fff; }
        .facts li { color: var(--ob-line); border-top-color: #3d4350; }
        .facts li b { color: #fff; }
        .facts li.head { color: var(--ob-muted); }
        .facts li.other, .facts li.other b { color: #b6ece2; }
    }
}

/* the lines every plan shares */
.ob-every {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--ob-site-gap);
    margin-top: var(--ob-site-gap);

    .ob-card { padding: 22px 24px; }
    .ob-card .t { font-size: 14px; font-weight: 800; color: var(--ob-ink); margin-bottom: 4px; }
    .ob-card .d { font-size: 13.5px; font-weight: 500; color: var(--ob-ink-2); line-height: 1.5; }
}

/* ---- the pricing page's questions, beside X's prices (Dave, 2026-09-25) ----
   One section where there were two: the plan-and-payment questions down the left at about 60%,
   short answers under hairlines; X's price list down the right as a card that stays in view
   while the questions scroll past it. It replaced a four-column rate table with an aside of
   paragraphs, and a separate band of questions under it */
.ob-qaprice {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    gap: 72px;
    align-items: start;

    .qa .ob-h2 { margin-bottom: 26px; }
}

/* ---- the questions, on every outside page (Dave, 2026-09-25) ----
   One column of questions under hairlines, each a <details>: the question is the row, a small
   arrow in front of it points down and turns up when it opens, and the answer drops under it,
   lined up with the question's words. In front, not at the far right, because the rows are wide
   and a short question would leave its arrow a page-width from where the eye is (a plus in a
   round chip on the right was tried first, the same day). All closed to start,
   and opening one leaves the others as they are. The browser's own element, so the keyboard,
   a screen reader and find-in-page (which opens the one it finds) all work with no script.
   It replaced every answer drawn open in a two-column grid, which asked a reader to take in
   fourteen at once to find one */
.ob-qa {
    max-width: 880px;
    border-bottom: 1px solid var(--ob-line);

    .q { border-top: 1px solid var(--ob-line); }
    summary {
        list-style: none;
        display: flex;
        align-items: flex-start;
        gap: 14px;
        padding: 20px 0;
        line-height: 1.4;
        cursor: pointer;
        user-select: none;      /* a quick second click opens it, never selects a word */
        font-size: 16.5px;
        font-weight: 800;
        letter-spacing: -.01em;
        color: var(--ob-ink);
        transition: color .15s ease;
    }
    summary::-webkit-details-marker { display: none; }
    summary:hover { color: var(--ob-accent); }
    summary:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 4px; border-radius: 6px; }

    /* the arrow: two sides of a small square, turned to point down, and up when open. Its box
       is one line of the question tall, so on a question that wraps it sits by the first line */
    .tog {
        position: relative;
        flex: 0 0 14px;
        height: 1.4em;
        color: var(--ob-muted);
        transition: color .15s ease;
    }
    .tog::before {
        content: "";
        position: absolute;
        left: 2px;
        top: 50%;
        width: 7px;
        height: 7px;
        border-right: 2px solid currentColor;
        border-bottom: 2px solid currentColor;
        transform: translateY(-70%) rotate(45deg);
        transition: transform .2s ease;
    }
    summary:hover .tog { color: var(--ob-accent); }
    .q[open] .tog { color: var(--ob-ink); }
    .q[open] .tog::before { transform: translateY(-25%) rotate(225deg); }

    .a { padding: 0 24px 22px 28px; }       /* 28: the arrow and the gap, so the words line up */
    .ob-copy { font-size: 15px; max-width: 640px; }
    ul.ob-copy { margin: 6px 0 0; padding-left: 20px; }
    ul.ob-copy li { margin: 2px 0; }
}

/* The answer slides open where the browser can size to auto (Chrome 131 on); elsewhere it just
   appears. Kept out of the nest above so a browser that does not know the pseudo drops these
   two rules and nothing else */
.ob-qa { interpolate-size: allow-keywords; }
.ob-qa .q::details-content {
    block-size: 0;
    overflow: hidden;
    transition: block-size .25s ease, content-visibility .25s allow-discrete;
}
.ob-qa .q[open]::details-content { block-size: auto; }

/* the homepage's: the title on the left, staying in view while the list scrolls past it */
.ob-asked {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    gap: 72px;
    align-items: start;

    > .ob-h2 { position: sticky; top: calc(var(--ob-site-nav-h) + 24px); margin: 0; }
}

/* X's price list as a card: a glyph, what it is and when it is charged, and the price per one.
   The post with a link is the one to watch, so its glyph and price are in the rose */
.ob-xprices {
    position: sticky;
    top: calc(var(--ob-site-nav-h) + 24px);
    padding: 26px 26px 22px;

    .h { margin: 0; font-size: 18px; font-weight: 800; letter-spacing: -.015em; color: var(--ob-ink); }
    .sub { margin: 4px 0 16px; font-size: 13.5px; font-weight: 600; color: var(--ob-muted); }
    ul { margin: 0; padding: 0; list-style: none; }
    li {
        display: flex;
        align-items: center;
        gap: 14px;
        padding: 12px 0;
        border-top: 1px solid var(--ob-line-in);
    }
    .ic {
        display: grid;
        place-items: center;
        flex: 0 0 auto;
        width: 36px;
        height: 36px;
        border-radius: 11px;
        background: var(--ob-accent-bg);
        color: var(--ob-accent-ink);
    }
    .ic svg { width: 19px; height: 19px; }
    .what { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
    .what b { font-size: 14.5px; font-weight: 800; color: var(--ob-ink); }
    .what span { font-size: 12.5px; font-weight: 500; line-height: 1.4; color: var(--ob-muted); }
    .usd { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; gap: 1px; }
    .usd b { font-size: 15px; font-weight: 800; color: var(--ob-ink); font-variant-numeric: tabular-nums; }
    .usd span { font-size: 12px; font-weight: 600; color: var(--ob-muted); }
    li.loud .ic { background: var(--ob-rose-chip); color: var(--ob-rose-ink); }
    li.loud .usd b { color: var(--ob-rose-ink); }
    .more { display: inline-block; margin-top: 14px; font-size: 13.5px; font-weight: 700; color: var(--ob-accent-ink); text-decoration: none; }
    .more:hover { text-decoration: underline; }
}

/* two columns of a table's worth of words: the table, and the sentence beside it */
.ob-beside {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    gap: 48px;
    align-items: start;
}

/* ---- the two modes, side by side ---- */
.ob-mode {
    padding: 28px;

    .head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
    .head .ob-h3 { margin: 0; }
    .ob-copy { font-size: 14px; }
    .rows { margin: 18px 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; }
    .rows li { display: flex; gap: 14px; padding: 9px 0; border-top: 1px solid var(--ob-line-in); font-size: 13.5px; font-weight: 600; color: var(--ob-ink-2); }
    .rows li .k { flex: 0 0 150px; color: var(--ob-muted); }
    .rows li .v { color: var(--ob-ink); font-weight: 700; }
}

/* ---- the closing call, on ink ---- */
.ob-close {
    margin: 32px 0 48px;
    padding: 72px 56px;
    border-radius: var(--ob-radius);
    background: var(--ob-site-dark);
    color: #fff;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 40px;
    align-items: center;

    .ob-h2 { color: #fff; margin-bottom: 10px; }
    .ob-copy { color: var(--ob-faint-1); font-size: 16px; max-width: 520px; }
    .ob-ctas { margin-top: 22px; }
    .ob-pill.primary { background: #fff; color: var(--ob-ink); }
    .ob-pill.primary:hover { background: var(--ob-canvas); }
}

/* ---- the footer ---- */
.ob-sitefoot {
    border-top: 1px solid var(--ob-line);
    margin-top: 24px;
    padding: 36px 0 48px;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px 26px;
    font-size: 13px;
    font-weight: 600;
    color: var(--ob-muted);

    .links { display: flex; flex-wrap: wrap; gap: 4px 20px; }
    .links a { color: var(--ob-ink-2); text-decoration: none; }
    .links a:hover { color: var(--ob-ink); text-decoration: none; }
    .co { margin-left: auto; font-weight: 500; font-size: 12px; color: var(--ob-muted-2); }
    .ob-sitelogo .name { font-size: 15px; }
    .ob-sitelogo .mark { width: 20px; height: 24px; }
}

/* ---- a page of prose: the docs and the legal pages ---- */
.ob-pagehead {
    padding: 64px 0 36px;
    max-width: 960px;      /* wider than the prose: a title wants to land in two lines, not three */
}
.ob-pagehead .ob-lead { margin-top: 12px; }

.ob-docs {
    display: grid;
    grid-template-columns: var(--ob-site-docs-nav-w) minmax(0, 1fr);
    gap: 72px;
    align-items: start;
    padding-bottom: 40px;
}

/* An operation's page has a third column: the summary card. The prose gives up the width,
   because a docs column at 720px is still a reading measure and the card is what a person came
   to check. Placed explicitly rather than by source order, so the card can sit before the body
   in the DOM and read first when the columns collapse on a phone. */
.ob-docs.railed {
    grid-template-columns: var(--ob-site-docs-nav-w) minmax(0, 1fr) 260px;
    gap: 40px;

    .ob-docsnav { grid-area: 1 / 1; }
    .ob-docbody { grid-area: 1 / 2; }
    .ob-docrail { grid-area: 1 / 3; }
}

.ob-docrail {
    position: sticky;
    top: calc(var(--ob-site-nav-h) + 24px);
}

/* THE CONNECT BLOCK IN THE PROSE (docs.js cuts the prose around it): Setup's block, which frames
   itself, the prose's width, with the two halves of the prose closing up to it */
.ob-docconnect {
    container-type: inline-size;
    max-width: var(--ob-site-prose-w);
    margin: 6px 0 18px;

    /* on the page's grey, not inside a card, so its edge is a card's edge */
    .ob-connectbox .split { border-color: var(--ob-line); }
}

/* wide, the list of harnesses is drawn on the canvas, which is this page's own ground, so here it
   takes a shade off the card instead and the box lifts a little: otherwise the list read as part
   of the page and only the right half as a box (Dave, 2026-10-05). The same width the block folds
   at, where it has no frame to lift */
@container (min-width: 560px) {
    .ob-docconnect .ob-connectbox .split { box-shadow: 0 1px 2px rgba(22, 24, 29, .04), 0 8px 24px rgba(22, 24, 29, .05); }
    .ob-docconnect .ob-connectbox .side { background: var(--ob-card-head); }
}

/* narrow, the block folds to its row of marks and loses its frame - the same width its own
   container query folds at - so the docs give it a card */
@container (max-width: 559px) {
    .ob-docconnect > div { padding: 14px; background: var(--ob-card); border: 1px solid var(--ob-line); border-radius: var(--ob-radius-panel); }
}
.ob-prose:has(+ .ob-docconnect) { padding-bottom: 0; }
.ob-docconnect + .ob-prose > h2:first-child { margin-top: 24px; }

.ob-doccard {
    padding: 16px 18px 18px;
    background: var(--ob-card);
    border: 1px solid var(--ob-line);
    border-radius: var(--ob-radius-panel);

    > *:first-child { margin-top: 0; }
    h3 {
        margin: 18px 0 10px;
        font-size: 11px;
        font-weight: 700;
        letter-spacing: .08em;
        text-transform: uppercase;
        color: var(--ob-muted);
    }
    p { margin: 10px 0 0; font-size: 13px; line-height: 1.5; color: var(--ob-ink-2); }

    /* the endpoints, one per line, each on the card's full width: a path like
       /2/users/{id}/timelines/reverse_chronological does not fit a table cell at this size
       and broke mid-word when it tried */
    ul { margin: 0 0 14px; padding: 0; list-style: none; }
    li + li { margin-top: 6px; }
    p:has(> code:only-child) { margin: 0 0 14px; }
    li code, p > code:only-child {
        display: block;
        padding: 7px 9px;
        background: var(--ob-line-in);
        border-radius: 8px;
        line-height: 1.5;
        word-break: break-word;
    }
    li em {
        display: block;
        margin-top: 3px;
        font-size: 11.5px;
        font-style: normal;
        font-weight: 600;
        color: var(--ob-muted);
    }

    /* the card is one two-column table, label then value, and its header row says nothing */
    table { width: 100%; border-collapse: collapse; }
    thead { display: none; }
    td {
        padding: 7px 0;
        font-size: 13px;
        line-height: 1.45;
        color: var(--ob-ink);
        vertical-align: top;
        border-bottom: 1px solid var(--ob-line-in);
    }
    tr:last-child td { border-bottom: 0; }
    td:first-child { width: 40%; padding-right: 10px; color: var(--ob-muted); font-weight: 600; }
    code {
        font-family: var(--ob-mono);
        font-size: 11.5px;
        line-height: 1.4;
        overflow-wrap: anywhere;
        color: var(--ob-ink);
    }
    a { color: var(--ob-accent-ink); text-decoration: none; font-weight: 600; }
    a:hover { text-decoration: underline; }
}

/* the note under the card (docs.js, data-note): what the call cannot do yet, on the accent's tint */
.ob-docnote {
    margin-top: 12px;
    padding: 14px 18px 16px;
    background: var(--ob-accent-bg);
    border: 1px solid var(--ob-accent-ring);
    border-radius: var(--ob-radius-panel);

    h3 {
        margin: 0 0 6px;
        font-size: 11px;
        font-weight: 700;
        letter-spacing: .08em;
        text-transform: uppercase;
        color: var(--ob-accent-ink);
    }
    p { margin: 0; font-size: 13px; line-height: 1.5; color: var(--ob-ink-2); }
    p + p { margin-top: 8px; }
}

.ob-docsnav {
    position: sticky;
    top: calc(var(--ob-site-nav-h) + 24px);
    display: flex;
    flex-direction: column;
    gap: 2px;

    .ob-lbl { padding: 0 12px; margin-bottom: 8px; }
    a {
        display: block;
        padding: 8px 12px;
        border-radius: 10px;
        font-size: 14px;
        font-weight: 600;
        color: var(--ob-ink-2);
        text-decoration: none;
    }
    a:hover { background: var(--ob-card); color: var(--ob-ink); text-decoration: none; }
    a.on { background: var(--ob-accent-bg); color: var(--ob-accent-ink); font-weight: 700; }

    /* The second level: the operations, under Operations, only while you are in them. They are
       spelled the way an agent spells them, so they are set in the mono face and hang off a
       rule that says which parent they belong to. */
    .kids {
        display: flex;
        flex-direction: column;
        gap: 2px;
        margin: 4px 0 10px 12px;
        padding-left: 10px;
        border-left: 1px solid var(--ob-line);
    }
    .kids a {
        padding: 5px 10px;
        font-family: var(--ob-mono);
        font-size: 12.5px;
        font-weight: 600;
        letter-spacing: -.01em;
    }
}

/* Everything under .ob-prose is rendered from markdown, so it is styled by element. */
.ob-prose {
    max-width: var(--ob-site-prose-w);
    font-size: 16px;
    font-weight: 500;
    line-height: 1.65;
    color: var(--ob-ink-2);
    padding-bottom: 40px;

    > *:first-child { margin-top: 0; }

    h1 { font-size: 34px; font-weight: 800; letter-spacing: -.025em; line-height: 1.1; color: var(--ob-ink); margin: 0 0 18px; }
    h2 { font-size: 23px; font-weight: 800; letter-spacing: -.02em; line-height: 1.2; color: var(--ob-ink); margin: 40px 0 12px; }
    h3 { font-size: 17px; font-weight: 800; letter-spacing: -.01em; color: var(--ob-ink); margin: 28px 0 8px; }
    p { margin: 0 0 14px; }
    ul, ol { margin: 0 0 14px; padding-left: 22px; }
    li { margin-bottom: 6px; }
    li::marker { color: var(--ob-muted); }
    strong { font-weight: 700; color: var(--ob-ink); }
    a { color: var(--ob-accent-ink); text-decoration: none; font-weight: 600; }
    a:hover { text-decoration: underline; }
    /* a link whose whole label is code - every operation in the docs index - or the code
       would take the ink colour off the rule below and the link would not look like one */
    a code { color: inherit; }
    hr { border: 0; border-top: 1px solid var(--ob-line); margin: 34px 0; }
    blockquote { margin: 0 0 14px; padding: 2px 0 2px 16px; border-left: 3px solid var(--ob-accent-ring); color: var(--ob-ink-2); }

    /* a screenshot of the product (docs.js gives it its 2x). Markdown puts it in a paragraph of
       its own; {.half} and {.tall} after it, in the markdown, draw a close-up or a tall one at a
       size that is not the whole column.
       ON A WHITE MAT (2026-10-05, Dave): most of a shot is the app's canvas, which is this page's
       own grey, so with only a rim Home's middle column ran straight into the page. The mat and
       the lift are the connect block's (.ob-docconnect), so the two read as one kind of thing */
    img {
        display: block;
        width: auto;            /* its own size (the 2x is density-corrected), never blown up */
        max-width: 100%;
        height: auto;
        margin: 8px 0 26px;
        padding: 6px;
        background: var(--ob-card);
        border-radius: 16px;
        box-shadow:
            0 0 0 1px var(--ob-line),
            0 1px 2px rgba(22, 24, 29, .04),
            0 8px 24px rgba(22, 24, 29, .05);
    }
    img.half { max-width: min(520px, 100%); }       /* never past the column: a tall one at 400 widened a phone's page */
    img.tall { max-width: min(400px, 100%); }

    /* a white chip with a rim, since the prose sits on the canvas and a grey chip vanished
       into it; a link's chip is the accent's, so an operation reads as a link at a glance */
    code {
        font-family: var(--ob-mono);
        font-size: .84em;
        font-weight: 500;
        background: var(--ob-card);
        box-shadow: inset 0 0 0 1px var(--ob-line);
        border-radius: 6px;
        padding: .12em .42em;
        color: var(--ob-ink);
    }
    a code { background: var(--ob-accent-bg); box-shadow: none; }
    pre {
        margin: 0 0 16px;
        padding: 14px 16px;
        border-radius: var(--ob-radius-tile);
        background: var(--ob-ink);
        color: #e8eaf1;
        font-family: var(--ob-mono);
        font-size: 12.5px;
        line-height: 1.55;
        overflow-x: auto;
    }
    pre code { background: none; box-shadow: none; padding: 0; color: inherit; font-size: inherit; }

    table {
        width: 100%;
        border-collapse: collapse;
        margin: 0 0 18px;
        font-size: 14px;
    }
    th {
        text-align: left;
        padding: 0 12px 8px 0;
        font-size: 11px;
        font-weight: 700;
        letter-spacing: .08em;
        text-transform: uppercase;
        color: var(--ob-muted);
        border-bottom: 1px solid var(--ob-line);
    }
    td { padding: 9px 12px 9px 0; border-bottom: 1px solid var(--ob-line-in); vertical-align: top; }
    tr:last-child td { border-bottom: 0; }
    td:first-child { color: var(--ob-ink); font-weight: 700; white-space: nowrap; }

    /* A DOCS TABLE SITS IN A BOX THAT SCROLLS SIDEWAYS (docs.js, tables). On a phone a wide one -
       the plans, an operation's statuses - is wider than the column, and on its own it widened
       the whole page, which then panned. The box takes the table's margin; the table keeps its
       full width, so nothing changes where it fits */
    .ob-tablewrap { overflow-x: auto; margin: 0 0 18px; }
    .ob-tablewrap > table { margin: 0; }

    /* A first column that holds a LABEL must not wrap - "Billing records", "Page size", "X's
       charge" - which is what the nowrap above is for, and it is right for every table in
       md/docs. The legal pages are the exception: their first column holds a sentence ("The
       full content of a call: the request, and X's response", every purpose in the lawful
       basis table), and nowrap put that sentence on one line, took the whole table's width
       for it and left the second column a word wide. So the legal prose wraps its first
       column and carries the emphasis at 600 rather than 700, because a bold sentence is a
       lot of bold. .ob-prose.legal is set in apps/frontend/legal/legal.html. */
    &.legal td:first-child { white-space: normal; font-weight: 600; }

    /* the legal pages' "Last updated" line: markdown emits it as a lone emphasised paragraph, and
       it is always the first one. Scoped to :first-child because :only-child counts elements and
       not text, so without it every sentence with one italic in it - "Press Rotate the key" -
       came out grey and a size small. */
    > p:first-child > em:only-child { color: var(--ob-muted); font-size: 13.5px; font-style: normal; font-weight: 600; }
}

.ob-prose-loading { padding: 20px 0; font-size: 14px; font-weight: 600; color: var(--ob-muted); }
.ob-prose-error { padding: 20px 0; font-size: 14px; font-weight: 600; color: var(--ob-rose-ink); }

/* ---- the fold ----
   Up: past 1800px the column grows once more and the type with it, so a big monitor is not a
   narrow strip with the same page on it. Down: the tablet, then the phone. */
@media (min-width: 1800px) {
    :root { --ob-site-w: 1520px; --ob-site-sect: 96px; --ob-site-prose-w: 840px; --ob-site-docs-nav-w: 260px; }
    .ob-h1 { font-size: 68px; }
    .ob-h2 { font-size: 42px; }
    .ob-lead { font-size: 20px; max-width: 640px; }
    .ob-hero { padding: 128px 0 96px; gap: 96px; }
}

/* Between the phone and a wide screen the three columns stop fitting, so the card goes above
   the prose instead of beside it. */
@media (max-width: 1280px) and (min-width: 961px) {
    .ob-docs.railed {
        grid-template-columns: var(--ob-site-docs-nav-w) minmax(0, 1fr);
        gap: 48px 56px;
    }
    .ob-docs.railed .ob-docrail { grid-area: 1 / 2; }
    .ob-docs.railed .ob-docbody { grid-area: 2 / 2; }
    .ob-docrail { position: static; }
}

@media (max-width: 960px) {
    :root { --ob-site-pad: 24px; --ob-site-sect: 56px; }
    .ob-hero { grid-template-columns: 1fr; gap: 36px; padding: 48px 0 40px; }
    .ob-h1 { font-size: 42px; }
    .ob-three, .ob-every { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .ob-plans { grid-template-columns: minmax(0, 1fr); max-width: 440px; margin-left: auto; margin-right: auto; }
    .ob-beside { grid-template-columns: minmax(0, 1fr); }   /* not 1fr: the config block's long lines would widen the page */
    .ob-close { grid-template-columns: 1fr; padding: 40px 28px; }
    .ob-docs, .ob-docs.railed { grid-template-columns: minmax(0, 1fr); gap: 20px; }   /* not 1fr, as .ob-beside: a long line in a pre widened the page */
    .ob-docs.railed .ob-docsnav,
    .ob-docs.railed .ob-docbody,
    .ob-docs.railed .ob-docrail { grid-area: auto; }
    .ob-docrail { position: static; }
    .ob-docsnav { position: static; flex-direction: row; flex-wrap: wrap; gap: 4px; }
    .ob-docsnav .ob-lbl { display: none; }
    .ob-docsnav .kids { flex-direction: row; flex-wrap: wrap; width: 100%; margin: 0; padding: 0; border-left: 0; }
}

@media (max-width: 640px) {
    :root { --ob-site-pad: 18px; --ob-site-gap: 14px; --ob-site-sect: 44px; }
    .ob-h1 { font-size: 36px; }
    .ob-h2 { font-size: 28px; }
    .ob-hero { padding: 36px 0 32px; }
    .ob-sect { padding: 40px 0; }
    .ob-three, .ob-two, .ob-every { grid-template-columns: 1fr; }
    .ob-sitenav .inner { gap: 12px; }
    .ob-sitenav .links a { height: 40px; padding: 0 9px; font-size: 13px; }
    /* Terms and Privacy leave the bar on a phone: with them the row ran past the screen and took
       Sign in with it. The footer carries both */
    .ob-sitenav .links a.legal { display: none; }
    .ob-sitenav .right .ob-pill { height: 40px; }
    .ob-sitenav .ob-pill.face { padding-left: 5px; }
    .ob-sitelogo .name { display: none; }
    .ob-sitefoot .co { margin-left: 0; }

    /* A THUMB'S WORTH (40px) for every link a phone taps out here, in padding rather than type:
       the footer's row and the docs' topics and operations */
    .ob-sitefoot .links { gap: 0 8px; margin: 0 -6px; }
    .ob-sitefoot .links a { display: inline-flex; align-items: center; min-height: 40px; padding: 0 6px; }
    .ob-docsnav a { display: flex; align-items: center; min-height: 40px; }
    /* the logos, the mark alone here, get the same: padding out and margin back, so the mark
       does not move */
    .ob-sitenav .ob-sitelogo { min-height: 40px; padding: 0 8px; margin: 0 -8px; }
    .ob-sitefoot .ob-sitelogo { min-height: 40px; padding: 0 10px; margin: 0 -10px; }
    .ob-mode .rows li .k { flex-basis: 110px; }
}

/* A SIGNED-IN PHONE KEEPS THE FACE AND DROPS THE WORD (2026-10-05): with "Dashboard" in it the
   bar was 408px wide and ran past every phone, the button flush to the edge at 390 and off it at
   360. The face alone is still the way back, and the link keeps its name for a screen reader */
@media (max-width: 440px) {
    .ob-sitenav .ob-pill.face > span:not(.ob-circle) { display: none; }
    .ob-sitenav .ob-pill.face { padding: 0 4px; }
}

/* ---- the waitlist: /waitlist (marketing/waitlist*.js, dev/docs/waitlist.md) ----
   The pitch on the left of the hero, the card that takes the address on the right; under it
   the three selling points on a band of ink with the product's drawings on white, then why
   there is a list beside the two keys. The card's steps reuse the ceremony's .ob-cstep
   (okaybot-identity.css): a title, a line under it, the controls, the fine print. */
.ob-wlhero { padding: 96px 0 72px; }
.ob-wlhero .ob-h1 { font-size: 56px; }

.ob-joincard {
    width: 100%;
    max-width: 460px;
    justify-self: end;
    padding: 34px 34px 30px;
    box-shadow: 0 20px 60px rgba(22, 24, 29, .10);

    .ob-cstep .title { font-size: 30px; }
    .ob-cstep .controls { margin-top: 24px; }
}

/* the action row under the questions, on the card and on the ceremony's refusal step alike */
.ob-cstep .acts { display: flex; align-items: center; gap: 8px; margin-top: 24px; }
.ob-cstep .acts .ob-pill.primary { flex: 1 1 auto; }

/* the two optional questions, one row of choices each (waitlist_questions.js). Not .ob-ask:
   that is the atoms' in-place confirmation. The choice is a hairline pill, the accent when
   chosen; a second tap on it clears it */
.ob-wlq { display: flex; flex-direction: column; gap: 8px; }
.ob-wlq + .ob-wlq { margin-top: 18px; }
.ob-wlq .q { font-size: 13.5px; font-weight: 700; color: var(--ob-ink); }
.ob-wlq .q .u { font-weight: 600; color: var(--ob-muted); margin-left: 4px; }
.ob-wlq .opts { display: flex; flex-wrap: wrap; gap: 8px; }
.ob-wlopt {
    display: inline-flex;
    align-items: center;
    height: var(--ob-seg-h);
    padding: 0 13px;
    border: 1px solid var(--ob-line);
    border-radius: 999px;
    background: var(--ob-card);
    font-family: var(--ob-font);
    font-size: 12.5px;
    font-weight: 700;
    color: var(--ob-ink);
    cursor: pointer;
    transition: background .12s, border-color .12s, color .12s;
}
.ob-wlopt:hover { background: var(--ob-canvas); }
.ob-wlopt.on { background: var(--ob-accent-bg); border-color: var(--ob-accent-ring); color: var(--ob-accent-ink); }
.ob-wlopt:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }

/* the band: the section on ink, full width, its column inside it. The drawings sit on white
   cards with no hairline - the ink is the edge - and the words go white and faint under them.
   Chosen over three white cards with grey figure areas, which vanished into the grey page */
.ob-band {
    background: var(--ob-site-dark);
    padding: 72px 0 76px;

    .head { max-width: 740px; margin-bottom: 44px; }
    .ob-eyebrow { color: var(--ob-accent-ring); }
    .ob-h2 { color: #fff; }
    .ob-h3 { color: #fff; margin: 22px 0 6px; }
    .ob-copy { color: var(--ob-faint-1); font-size: 14.5px; }
    .fig {
        background: var(--ob-card);
        border-radius: var(--ob-radius);
        padding: 24px 24px 22px;
        min-height: 204px;
        display: flex;
        flex-direction: column;
        justify-content: center;
        gap: 10px;
    }
    /* a capture in a figure (the waitlist's three): one height for all three, so the headings
       under them line up, and each picture as large as fits it */
    .fig.shot { height: 262px; min-height: 0; padding: 18px; align-items: center; background: var(--ob-canvas); }
    .fig.shot img { display: block; max-width: 100%; max-height: 100%; width: auto; height: auto; border-radius: 12px; }
}

/* why there's a list */
.ob-copy.lg { font-size: 16px; max-width: 560px; }
.ob-copy.lg.wide { max-width: 760px; }     /* one line under a heading that runs the width of the page */
@media (max-width: 960px) {
    .ob-wlhero { padding: 48px 0 40px; }
    .ob-wlhero .ob-h1 { font-size: 40px; }
    .ob-joincard { max-width: none; justify-self: stretch; padding: 26px 22px 22px; box-shadow: none; }
    .ob-band { padding: 48px 0 52px; }
    .ob-band .head { margin-bottom: 28px; }
}

/* ---- contact: /contact (marketing/contact*.js) ----
   The waitlist's hero again: the ways to reach us on the left, the card that takes a message on
   the right (.ob-joincard, its steps .ob-cstep). What it is about is a button that opens a menu
   of four, each with its icon, because a select can draw no icon. */
.ob-contacthero { padding: 96px 0 88px; align-items: start; }     /* top, so the words stay put when the card shrinks to Message sent */
.ob-contacthero .ob-h1 { font-size: 56px; }

/* the address, and the docs: two tiles under the lead */
.ob-ways {
    margin-top: 36px;
    max-width: 460px;
    display: flex;
    flex-direction: column;
    gap: 10px;

    .way {
        display: flex;
        align-items: center;
        gap: 14px;
        padding: 14px 14px 14px 16px;
        border: 1px solid var(--ob-line);
        border-radius: var(--ob-radius-tile);
        background: var(--ob-card);
        color: var(--ob-ink);
        text-decoration: none;
    }
    a.way { padding-right: 16px; transition: border-color .12s; }
    a.way:hover { border-color: var(--ob-accent-ring); text-decoration: none; }

    .ic {
        flex: 0 0 auto;
        width: 40px;
        height: 40px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 50%;
        background: var(--ob-accent-bg);
        color: var(--ob-accent-ink);
    }
    .ic.quiet { background: var(--ob-canvas); color: var(--ob-ink-2); }
    .ic svg { width: 18px; height: 18px; }

    .w { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
    .n { font-size: 12.5px; font-weight: 600; color: var(--ob-ink-2); }
    .t { font-size: 16px; font-weight: 700; color: var(--ob-ink); text-decoration: none; overflow-wrap: anywhere; }
    a.t:hover { text-decoration: underline; }
    .ob-pill { flex: 0 0 auto; height: 34px; padding: 0 14px; font-size: 13px; }
    .go { flex: 0 0 auto; width: 16px; height: 16px; color: var(--ob-faint-3); }
}

/* the card: a label over each field, the message a taller field */
.ob-contactcard {
    .ob-cstep .controls { gap: 18px; }
    .field { display: flex; flex-direction: column; gap: 8px; }
    .lbl { font-size: 13px; font-weight: 700; color: var(--ob-ink-2); }
    /* .lg as well, to outrank .ob-cstep .ob-input.lg (okaybot-identity.css), whose 0 18px
       padding and 50px height are a one-line field's */
    textarea.ob-input.lg {
        height: auto;
        min-height: 150px;
        padding: 14px 18px;
        font-weight: 600;
        line-height: 1.55;
        resize: vertical;
    }
}

/* sent: the tick over the title */
.ob-sentmark {
    width: 48px;
    height: 48px;
    margin-bottom: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--ob-mint-bg);
    color: var(--ob-mint-ink);

    svg { width: 22px; height: 22px; }
}

/* what it is about: the button, the field's height, and the menu hung under it */
.ob-topic {
    position: relative;

    .pick {
        width: 100%;
        height: 50px;
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 0 14px 0 10px;
        border: 1px solid var(--ob-line);
        border-radius: var(--ob-radius-tile);
        background: var(--ob-card);
        font-family: var(--ob-font);
        font-size: 15px;
        font-weight: 700;
        color: var(--ob-ink);
        text-align: left;
        cursor: pointer;
        outline: none;
        transition: border-color .12s, box-shadow .12s;
    }
    .pick.none { padding-left: 18px; font-weight: 600; color: var(--ob-muted); }
    .pick:focus-visible, &.open .pick { border-color: var(--ob-accent); box-shadow: 0 0 0 3px var(--ob-accent-bg); }

    .now { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 12px; }
    .chev { flex: 0 0 auto; width: 18px; height: 18px; color: var(--ob-muted); transition: transform .12s; }
    &.open .chev { transform: rotate(180deg); }

    .ic {
        flex: 0 0 auto;
        width: 30px;
        height: 30px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 50%;
        background: var(--ob-accent-bg);
        color: var(--ob-accent-ink);
    }
    .ic svg { width: 16px; height: 16px; }

    .menu {
        position: absolute;
        top: calc(100% + 6px);
        left: 0;
        right: 0;
        z-index: 20;
        display: flex;
        flex-direction: column;
        gap: 2px;
        padding: 6px;
        background: var(--ob-card);
        border-radius: var(--ob-radius-tile);
        box-shadow: 0 0 0 1px var(--ob-line), var(--ob-shadow-menu);
    }
    .menu button {
        width: 100%;
        min-height: 46px;
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 0 12px 0 8px;
        border: 0;
        border-radius: 10px;
        background: transparent;
        font-family: var(--ob-font);
        font-size: 14.5px;
        font-weight: 700;
        color: var(--ob-ink);
        text-align: left;
        cursor: pointer;
        outline: none;
    }
    .menu button:hover, .menu button:focus-visible { background: var(--ob-canvas); }
    .menu button.on { background: var(--ob-site-good); }
    .menu .w { flex: 1 1 auto; min-width: 0; }
    .menu .tick { flex: 0 0 auto; width: 16px; height: 16px; color: var(--ob-accent); }
}

@media (max-width: 960px) {
    .ob-contacthero { padding: 48px 0 40px; }
    .ob-contacthero .ob-h1 { font-size: 40px; }
    .ob-ways { max-width: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   THE HOMEPAGE, 2026-09-18, rebuilt around screenshots 2026-09-25

   ELEVEN BANDS, AND THE GROUND IS WHAT SEPARATES THEM. The page used to be one canvas with a
   hairline between every section and a white card around every thought - about twenty of them,
   which is the same as none: a card is the loudest separator there is (a ground change, a
   border, a radius and a shadow at once), so using it everywhere leaves nothing emphasised.

   The order of grounds is fixed and alternates:

     1 hero          canvas      6 every post       canvas
     2 belt          canvas      7 your costs       INK (the price line closes it)
     3 problem       WHITE       8 how it works     WHITE
     4 your posts    canvas      9 questions        canvas
     5 your calendar WHITE      10 the close        INK

   From 3 down it is a plain alternation, and that is the whole of the separating. Four of the
   bands are the product's four chapters - the posts, the calendar, every post, the costs - and
   each is words beside one small view of the real screen (.ob-chapter, .ob-shot, above) - never
   a whole screen across the page, which only the hero is. The wire and the guards went on
   2026-09-25 (Dave): the page is for the person, not their agent.

   TWO INK BANDS, NO MORE. The costs and the close; a third and none of them mean anything. The
   ink used to hold "the fix" as three drawn cards; the chapters replaced it, and the costs took
   the ink because money is the claim a reader should not miss.

   NO RULE DOES A GROUND'S JOB. The only hairlines left are inside a list (the questions, the
   night sheets, the ledger, the points' accent rule), where they separate rows of one kind
   rather than sections. The one exception is the price line's rule inside the costs band.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* the band on white: the same shape as .ob-band, the other ground */
.ob-bandw {
    background: var(--ob-card);
    padding: 76px 0 80px;
}
.ob-bandw.tight { padding: 56px 0 60px; }
.ob-bandw .head { max-width: 740px; margin-bottom: 44px; }

/* a centred chapter head. Every other head on the page is left-aligned, so centring one says
   "new chapter" without a line or a box */
.head.mid,
.ob-band .head.mid,
.ob-bandw .head.mid {
    max-width: 820px;
    margin-left: auto;
    margin-right: auto;
    text-align: center;
}
.head.mid .ob-site-sub { margin-left: auto; margin-right: auto; margin-bottom: 0; }
.ob-ctas.mid { justify-content: center; margin-top: 38px; }

/* .ob-band recolours .ob-h2 and .ob-copy for ink and always has; .ob-site-sub is the line under
   a centred head and was never in that list, so it kept its --ob-ink-2 and went near-invisible
   on the two ink bands */
.ob-band .ob-site-sub { color: var(--ob-faint-1); }

.ob-eyebrow.rose { color: var(--ob-rose-ink); }


/* ---- the harness marks on the belt ----

   THE VENDORS' OWN MARKS (2026-09-25), in images/site/harness/, each in its own colours: Claude
   Code's mascot, Cursor's cube, OpenAI's knot for Codex, OpenClaw's lobster, Hermes Agent's icon
   and Windsurf's wave. Where they came from is images/site/harness/README.md. They replaced
   monograms on tiles in our own tints, which were placeholders and said so. A mark is 20 pixels
   square and sits on the name's line; the word is still the name. */
.ob-belt .glyph {
    display: block;
    width: 20px;
    height: 20px;
    margin-right: 9px;
    flex: 0 0 auto;
    object-fit: contain;
}

/* the belt's own rows have to stop being baseline-aligned once they carry a mark */
.ob-belt { align-items: center; }
.ob-belt .name { display: inline-flex; align-items: center; }

/* ---- 3. the night: the same six requests, twice ----
   The counts and every price beside them come from the server (marketing.js, figures), so the
   story cannot state a rate that is not X's. */
.ob-night {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    gap: 22px;
    align-items: stretch;
    margin-top: 40px;
}
.ob-night .arrow { align-self: center; color: var(--ob-faint-2); }
.ob-night .arrow svg { display: block; width: 28px; height: 28px; }

.ob-side { display: flex; flex-direction: column; min-width: 0; }
.ob-side > .lbl {
    margin: 0;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
}
.ob-side > .lbl { margin-bottom: 11px; display: flex; align-items: center; justify-content: center; gap: 8px; }
/* the face in front of each side's label: 😱 without, 😁 with (Dave, 2026-09-25; tried after
   "You woke up" and moved back) */
.ob-side > .lbl .face { font-size: 20px; line-height: 1; letter-spacing: 0; }
.ob-side.bad > .lbl { color: var(--ob-rose-ink); }
.ob-side.good > .lbl { color: var(--ob-accent-ink); }

.ob-sheet {
    flex: 1 1 auto;
    border-radius: var(--ob-radius);
    padding: 2px 22px 18px;
}
.ob-side.bad .ob-sheet { background: var(--ob-rose-card); border: 1px solid var(--ob-rose-edge); }
.ob-side.good .ob-sheet { background: var(--ob-site-good); border: 2px solid var(--ob-accent); }

.ob-nrow {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 0;
    border-bottom: 1px solid var(--ob-line-in);

    .t { flex: 0 0 42px; font-family: var(--ob-mono); font-size: 12px; color: var(--ob-ink-2); }
    .w { flex: 1 1 auto; min-width: 0; font-size: 13.5px; font-weight: 600; line-height: 1.4; color: var(--ob-ink-2); }
    .w b { font-weight: 800; color: var(--ob-ink); }
    .w b.wait { color: var(--ob-butter-ink); }
    .w b.stop { color: var(--ob-rose-ink); }
    .w .sub { display: block; margin-top: 3px; font-size: 11.5px; font-weight: 600; color: var(--ob-ink-2); }
    .c { flex: 0 0 78px; text-align: right; font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ob-rose-ink); }
    .c.big { font-size: 18px; font-weight: 800; }
    .c.none { color: var(--ob-ink-2); font-weight: 600; }
    .c.spent { color: var(--ob-ink); }      /* money the good side did spend, on purpose: not damage */
}
.ob-side.bad .ob-nrow { border-bottom-color: var(--ob-rose-line); }
.ob-nrow.hd {
    padding: 12px 0 8px;

    .t, .w, .c { font-family: var(--ob-font); font-size: 10px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ob-ink-2); }
}
.ob-side.bad .ob-nrow.hd .t,
.ob-side.bad .ob-nrow.hd .w,
.ob-side.bad .ob-nrow.hd .c { color: var(--ob-rose-ink); }

/* the pivot: the same moment closes both columns */
.ob-nrow.woke {
    border-bottom: 2px solid var(--ob-ink);

    .t { color: var(--ob-ink); }
    .w { color: var(--ob-ink); font-size: 14px; }
    .c { color: var(--ob-ink); }
}

.ob-ntot {
    display: flex;
    align-items: baseline;
    gap: 12px;
    padding: 15px 0 0;

    .k { flex: 1 1 auto; font-size: 11.5px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ob-rose-ink); }
    .v { font-size: 26px; font-weight: 800; color: var(--ob-ink); font-variant-numeric: tabular-nums; }
}
.ob-side.good .ob-ntot .k { color: var(--ob-accent-ink); }
.ob-nnote { margin: 2px 0 0; text-align: right; font-size: 13.5px; font-weight: 600; color: var(--ob-rose-ink); }
.ob-nnote.good { color: var(--ob-accent-ink); }

/* the placeholder the agent never filled in. It is one token, so it wraps before itself and
   never down the middle - which it did, in the narrow card on the ink band */
.ob-ph {
    white-space: nowrap;
    font-style: normal;
    font-weight: 700;
    background: var(--ob-rose-chip);
    color: var(--ob-rose-ink);
    padding: 1px 6px;
    border-radius: 5px;
}

/* ---- 7. how it works: three ledger rows, not three cards. A third of the furniture, and the
          stack reads as a sequence in a way three cards in a row never do.
          The block is half the height of the ledger beside it, so its column centres rather
          than leaving the bottom two thirds of the page empty ---- */
.ob-beside.mid { align-items: center; }
/* THE STEPS WITHOUT RULES (Dave, 2026-09-25): the heavy ink lines above and below went. Each
   number sits in a soft round badge, and a thin line joins the badges down the left, so the
   three read as a sequence with nothing boxing them in */
.ob-ledger {
    position: relative;
    margin-top: 32px;
    display: flex;
    flex-direction: column;
    gap: 26px;

    .r { position: relative; display: flex; align-items: flex-start; gap: 18px; }

    /* the thread from each badge down to the next, so it stops at the last one however tall a
       step's words wrap */
    .r:not(:last-child)::after {
        content: '';
        position: absolute;
        left: 17px;
        top: 36px;
        bottom: -26px;
        width: 2px;
        background: var(--ob-accent-bg);
    }
    .n {
        flex: 0 0 36px;
        height: 36px;
        display: grid;
        place-items: center;
        border-radius: 50%;
        background: var(--ob-accent-bg);
        color: var(--ob-accent-ink);
        font-size: 15px;
        font-weight: 800;
        line-height: 1;
    }
    .r > div { padding-top: 6px; }
    .ob-h3 { margin: 0 0 4px; }
    .ob-copy { font-size: 14.5px; }
}

/* ---- what it costs: one centred line, and no furniture at all. The seven X rates and the four
          plans live on /pricing, one click away in the nav; a per-call rate card on our own
          homepage is the most reseller-shaped thing we could publish (dev/docs/x-risk.md). Since
          2026-09-25 it closes the costs chapter on ink (.ink) rather than being a band of its own ---- */
.ob-price1 {
    padding: var(--ob-site-sect) 0;
    text-align: center;

    .n { margin: 0 0 12px; font-size: 38px; font-weight: 800; letter-spacing: -.028em; color: var(--ob-ink); }
    .ob-copy { max-width: 560px; margin: 0 auto; }
    .ob-ctas { margin-top: 26px; }
}
.ob-price1.ink {
    padding: 72px 0 0;
    margin-top: 64px;
    border-top: 1px solid rgba(255, 255, 255, .1);

    .n { color: #fff; }
    .ob-pill.primary { background: var(--ob-accent); color: #fff; }
    .ob-pill.primary:hover { background: var(--ob-accent-lift); }
    .ob-pill.text { color: var(--ob-faint-1); }
}

/* the costs chapter on ink: the band's usual padding, a little more at the foot */
.ob-band.costs { padding: 80px 0 84px; }

/* ---- 11. the close, on ink, centred ---- */
.ob-band.closer {
    padding: 84px 0 88px;

    .ob-h2 { font-size: 44px; }
    .ob-pill.primary { background: var(--ob-accent); color: #fff; }
    .ob-pill.primary:hover { background: var(--ob-accent-lift); }
}

@media (max-width: 1280px) {
    .ob-chapter { gap: 48px; }
}

@media (max-width: 960px) {
    .ob-bandw { padding: 52px 0 56px; }
    .ob-bandw.tight { padding: 44px 0 48px; }
    .ob-band.closer { padding: 56px 0 60px; }
    .ob-band.closer .ob-h2 { font-size: 32px; }
    .ob-band.costs { padding: 52px 0 56px; }
    .ob-night { grid-template-columns: 1fr; gap: 28px; }
    .ob-night .arrow { display: none; }
    .ob-price1 .n { font-size: 30px; }
    .ob-price1.ink { padding-top: 48px; margin-top: 44px; }
    .ob-chapter, .ob-chapter.flip { grid-template-columns: minmax(0, 1fr); gap: 32px; }
    .ob-asks { grid-template-columns: minmax(0, 1fr); max-width: 560px; }
    .ob-qaprice { grid-template-columns: minmax(0, 1fr); gap: 40px; }
    .ob-asked { grid-template-columns: minmax(0, 1fr); gap: 20px; }
    .ob-asked > .ob-h2 { position: static; }
    .ob-xprices { position: static; }
    .ob-chapter.below { margin-top: 48px; }
    .ob-chapter.flip > .ob-shot { order: 2; }       /* stacked, the words come first either way */
    .ob-hero.stack { padding: 48px 0 16px; }
    .ob-hero.stack .ob-h1 { font-size: 46px; }
    .ob-hero.stack .ob-subhead { font-size: 21px; }
    .ob-hero.stack .ob-shot { margin-top: 36px; }
}

@media (max-width: 640px) {
    .ob-sheet { padding: 2px 16px 16px; }
    .ob-nrow .t { flex-basis: 38px; }
    .ob-nrow .c { flex-basis: 64px; }
    .ob-hero.stack { padding: 36px 0 12px; }
    .ob-hero.stack .ob-h1 { font-size: 38px; }
    .ob-hero.stack .ob-subhead { font-size: 19px; }
    .ob-pagehead.mid .ob-subhead { font-size: 18px; }
    .ob-shot { border-radius: 10px; }
    .ob-h3.big { font-size: 22px; }
}
 /* ============================================
   OKAYBOT SETUP MODE
   What Home draws while the workspace is not set up (setup/flow.js): numbered circles down the
   left, joined by a line, and beside each the one thing to do. The current step is the only
   card; the ones before it have folded to a tick and their answer, the one after it is a
   silhouette with a faint title. The line is what says these are steps rather than cards.

   No rail and no second column: the body's own padding, and a width a form is comfortable at.
   Colours, radii and type off the tokens; nothing literal here.
   ============================================ */

.ob-setup {
    max-width: var(--ob-setup-w);
    min-height: 100vh;
    padding: var(--ob-body-pad-top) var(--ob-body-pad-right) 60px var(--ob-body-pad-left);
    font-family: var(--ob-font);
    color: var(--ob-ink);
}

.ob-steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--ob-setup-gap);
}

/* ---- one step: the circle in a fixed column, the thing beside it ----
   THE LINE IS DRAWN BY THE STEP ABOVE. Each step draws from the centre of its own circle down
   past its foot and into the next step, and the next circle paints over the end of it. The
   current step's circle sits a little lower (--drop), level with its card's title rather than
   its top edge, and the line above it runs on into the drop so the two meet. */
.ob-step {
    position: relative;
    display: grid;
    grid-template-columns: var(--ob-setup-num) minmax(0, 1fr);
    gap: var(--ob-setup-col-gap);
    align-items: start;

    &::before {
        content: "";
        position: absolute;
        left: calc(var(--ob-setup-num) / 2 - 1px);
        top: calc(var(--ob-setup-num) / 2 + var(--drop, 0px));
        bottom: calc(-1 * (var(--ob-setup-gap) + var(--ob-setup-drop)));
        width: 2px;
        background: var(--ob-line);
    }
    &:last-child::before { display: none; }

    &.now { --drop: var(--ob-setup-drop); }

    /* a step with no card of its own (the free post's, holding Home's card): its title sits on the
       line where a card's top edge would be, so the circle is level with it, not dropped */
    &.now.flat { --drop: 0px; }

    > .n {
        position: relative;
        z-index: 1;
        display: grid;
        place-items: center;
        width: var(--ob-setup-num);
        height: var(--ob-setup-num);
        margin-top: var(--drop, 0px);
        border-radius: 50%;
        background: var(--ob-accent);
        color: #fff;
        font-size: 14.5px;
        font-weight: 800;
        font-variant-numeric: tabular-nums;

        svg { width: 18px; height: 18px; }
    }
    /* A DONE STEP'S TICK IS BLUE (2026-10-04, Dave): the accent's pale ground and its ink. It was
       mint, the only green on a screen that is otherwise all the one blue */
    &.done > .n { background: var(--ob-accent-bg); color: var(--ob-accent); }
    &.next > .n { background: var(--ob-card); border: 2px solid var(--ob-line); color: var(--ob-faint-1); }

    > .b { min-width: 0; }
}

/* an answered step: the title and the answer on one line, level with the tick */
.ob-stepline {
    display: flex;
    align-items: baseline;
    gap: 10px;
    min-height: var(--ob-setup-num);
    padding-top: 8px;

    .t { font-size: 14.5px; font-weight: 800; color: var(--ob-ink); }
    .a { font-size: 13.5px; font-weight: 600; color: var(--ob-muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

/* the step after this one: the shape of a card and its title, and nothing in it */
.ob-stepnext {
    display: flex;
    align-items: center;
    min-height: var(--ob-setup-next-h);
    padding: 0 22px;
    border: 1.5px dashed var(--ob-line);
    border-radius: var(--ob-radius);
    font-size: 15px;
    font-weight: 800;
    color: var(--ob-faint-1);
}

/* ---- the current step's card ---- */
.ob-stepcard {
    padding: 18px 22px 20px;

    h2 {
        margin: 0 0 6px;
        font-size: 17px;
        font-weight: 800;
        letter-spacing: -.01em;
        line-height: 1.3;
    }
    p {
        margin: 0 0 14px;
        font-size: 13.5px;
        line-height: 1.55;
        color: var(--ob-ink-2);
    }

    .ob-config { margin: 0; }
    .ob-note { margin-top: 12px; }
    .acts { margin-top: 14px; }
}

/* the one-line form: a field or two and Okay, on one row */
.ob-stepform {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;

    .ob-input { width: auto; flex: 1 1 160px; max-width: 280px; }

    .money, .at { position: relative; display: block; flex: 0 1 150px; }
    .money .sym, .at .sym { position: absolute; left: 13px; top: 50%; transform: translateY(-50%); font-size: 14px; font-weight: 700; color: var(--ob-muted); }
    .money .ob-input, .at .ob-input { width: 100%; max-width: none; padding-left: 27px; }
}

/* waiting on the agent: the one moving thing on the screen */
.ob-wait {
    display: flex;
    align-items: center;
    gap: 9px;
    margin-top: 16px;
    font-size: 13.5px;
    font-weight: 700;
    color: var(--ob-ink-2);

    .dot {
        width: 9px;
        height: 9px;
        border-radius: 50%;
        background: var(--ob-accent);
        flex: 0 0 auto;
        animation: ob-wait-pulse 1.6s ease-in-out infinite;
    }
}
@keyframes ob-wait-pulse {
    0%, 100% { box-shadow: 0 0 0 0 var(--ob-accent-halo-1); opacity: 1; }
    50%      { box-shadow: 0 0 0 7px var(--ob-accent-halo-0); opacity: .65; }
}
@media (prefers-reduced-motion: reduce) {
    .ob-wait .dot { animation: none; }
}

/* the example prompt on the last step: what a person types into their harness */
.ob-prompt {
    margin: 0 0 14px;
    padding: 12px 16px;
    border-radius: var(--ob-radius-tile);
    background: var(--ob-canvas);
    font-family: var(--ob-mono);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.5;
    color: var(--ob-ink);
    display: flex;
    align-items: flex-start;
    gap: 12px;
}
.ob-prompt .t { flex: 1; min-width: 0; }
.ob-prompt .copy {
    flex: none;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    margin: -3px -6px -3px 0;
    padding: 0;
    border: 0;
    border-radius: 8px;
    background: none;
    color: var(--ob-muted);
    cursor: pointer;
}
.ob-prompt .copy:hover { background: var(--ob-card); color: var(--ob-ink); }
.ob-prompt .copy svg {
    width: 16px;
    height: 16px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.ob-prompt .copy .i-done { display: none; }
.ob-prompt .copy.done .i-copy { display: none; }
.ob-prompt .copy.done .i-done { display: block; }

/* step 1: how to connect the agent, under the title (agents/connect_block.js) */
.ob-stepcard .ob-connectbox:has(.split) { margin-top: 14px; }

/* ---- the free tries (steps 3 and 4, code/Setup/FreeTry.cs) ---- */

/* a step's title: inside a card, or on the page where the tried post's card stands. The FREE chip
   beside it went on 2026-10-05 (Dave): trying is real usage before a plan, not something free */
.ob-stephead {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 6px;

    h2 { margin: 0; font-size: 17px; font-weight: 800; letter-spacing: -.01em; line-height: 1.3; }
}
.ob-step.flat .ob-stephead { min-height: var(--ob-setup-num); margin-bottom: 0; }

/* ...and its one line, on the page. Under it is Home's own card (home/queue.js), whose chip hangs
   over its top edge and brings its own room for it */
.ob-stepsub {
    margin: 0 0 6px;
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--ob-ink-2);
}

/* ---- step 5: the plan, a blue box (option C, Dave 2026-10-04) ----
   What is ready as chips on the accent, a line, and one white button to /billing. Stars twinkle
   in the right-hand side, behind the words. */
.ob-setupplan {
    position: relative;
    overflow: hidden;
    padding: 27px 32px 30px;            /* a little less over the title than under the button */
    border-radius: var(--ob-radius-row);
    background: var(--ob-accent);
    color: var(--ob-on-accent);

    .words { position: relative; z-index: 1; }

    h2 {
        margin: 0;
        color: var(--ob-on-accent);         /* the page's h2 is ink */
        font-size: 30px;
        font-weight: 800;
        letter-spacing: -.025em;
        line-height: 1.12;
    }

    p {
        margin: 0 0 22px;
        max-width: 470px;
        font-size: 15px;
        font-weight: 500;
        line-height: 1.55;
    }

    .ready { display: flex; flex-wrap: wrap; gap: 8px; margin: 21px 0 18px; }
    .ready .r {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        height: 38px;
        padding: 0 14px 0 8px;
        border-radius: 999px;
        background: var(--ob-glass);
        border: 1px solid var(--ob-glass-edge);
        font-size: 13.5px;
        font-weight: 700;
    }
    .ready .ob-circle { background: var(--ob-on-accent); }
    .ready .ok {
        display: grid;
        place-items: center;
        width: 22px;
        height: 22px;
        border-radius: 50%;
        background: var(--ob-on-accent);
        color: var(--ob-accent);

        svg { width: 13px; height: 13px; }
    }

    .ob-pill.go {
        height: 46px;
        padding: 0 22px;
        font-size: 14.5px;
        font-weight: 800;
        background: var(--ob-on-accent);
        color: var(--ob-accent-ink);
        box-shadow: var(--ob-shadow-on-accent);

        svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
    }
    .ob-pill.go:hover { background: var(--ob-accent-bg); }

    .star {
        position: absolute;
        color: var(--ob-on-accent);
        animation: ob-setup-twinkle 2.6s ease-in-out infinite;

        svg { display: block; width: 100%; height: 100%; fill: currentColor; }
    }
    .star.s1 { right: 48px;  top: 30px;  width: 34px; height: 34px; }
    .star.s2 { right: 104px; top: 92px;  width: 18px; height: 18px; animation-delay: .8s; }
    .star.s3 { right: 40px;  top: 150px; width: 22px; height: 22px; animation-delay: 1.5s; }
    .star.s4 { right: 150px; top: 36px;  width: 12px; height: 12px; animation-delay: 2s; }
    .star.s5 { right: 120px; top: 210px; width: 14px; height: 14px; animation-delay: .4s; }
}
@keyframes ob-setup-twinkle {
    0%, 100% { opacity: .15; transform: scale(.6); }
    50%      { opacity: .95; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
    .ob-setupplan .star { animation: none; opacity: .6; }
}
 /* ============================================
   OKAYBOT - POSTS
   The reading list (posts/posts.js): one list of posts, a menu on the left saying which posts
   they are, and a bar of faces over the list saying whose. The layout, the menu and the faces
   are the list page's, shared with Activity (okaybot-listpage.css); this sheet is the list of
   post cards. The cards themselves are the one card's post preset (cards/postcard_card.html,
   okaybot-postcard.css, since 2026-09-25). The calendar's own sheet is okaybot-calendar.css.

   ONE COLUMN SINCE 2026-09-23. This was three columns, each its own scroll inside a page the
   height of the viewport. One list is one scroll, the page's own, so the page is an ordinary
   page again and the menu is sticky beside it: the list can be read a long way down with the
   menu still under the hand.

   THE POST IN FULL IS BESIDE IT (2026-09-23): the page is Activity's split, .ob-act with its
   .ob-panel (okaybot-activity.css), so the detail is exactly as wide here as there.

   EVERYTHING IS ON THE GREY. The menu is the sidebar's own kind of item - no panel, no card
   around it - and only the posts are white, so the one white thing on the page is what you
   came to read.
   ============================================ */

/* ---- the list, and the day heading that rides on the first card of each day ---- */
.ob-pstack {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.ob-pday {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin: 14px 4px 0;
    font-family: var(--ob-font);

    b { font-size: 12px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase; color: var(--ob-ink-2); }
    span { font-size: 12.5px; font-weight: 600; color: var(--ob-muted); }
}
.ob-pstack .ob-slot > .ob-pc-item:first-child .ob-pday { margin-top: 0; }

.ob-posts-more { height: 1px; }

.ob-empty-col {
    font-family: var(--ob-font);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.5;
    color: var(--ob-faint-2);
    padding: 6px 4px;
}
 /* ============================================
   OKAYBOT - THE POST CARD (2026-09-25)
   cards/postcard_card.html: every post card in the product - the calendar's chip and its bigger
   card, the Held list, Posts - as one drawing and one style. It replaced four (the month's
   .ob-ev, the rail's .rchip, the tall chip and the Posts card, .ob-postrow). Where a card SITS -
   placed down the rail, carried in a drag, stepped along a stack - is the calendar's sheet;
   what a card IS is this one. /design/post-cards draws every switch in every state.

   ONE SET OF RULES, THREE SIZES. Everything a size changes - the padding, the corner, the face,
   the type, the gap under the words - is a custom property the size sets and the rules read, so
   a rule is written once and holds at every size. md is the post, sm the tall chip, xs the chip.

   TWO WAYS TO COLOUR IT, and the difference is the one real split between today's cards:
     fill    the chips: the whole card is the state's colour and so are its words
     quiet   the post: a white card with a hairline, the status word in the state's colour, and
             the card tinted only when it waits for you or was stopped
   Held takes each mode's colour as it is today - the rail's blue when filled, butter's word when
   quiet - because which colour held is has not been decided.

   THE EDGE IS A SHADOW, NOT A BORDER, so the hairline, the going ring and the hover ring are the
   same property and none of them moves what is inside the card by a pixel.
   ============================================ */

/* the day heading's ride: the wrapper draws nothing, and the button is what is laid out */
.ob-pc-item { display: contents; }

.ob-pc {
    /* md, the post. The sizes below override these */
    --pc-face: 30px;
    --pc-gap: 10px;
    --pc-pad: 14px 16px;            /* the post's 13 and 15 inside its 1px border, which is a shadow here */
    --pc-radius: var(--ob-radius);
    --pc-fs: 14px;
    --pc-lh: 21px;
    --pc-fw: 400;
    --pc-nm: 14px;
    --pc-nw: 700;
    --pc-hn: 12.5px;
    --pc-lift: -2px;            /* the first line's letters start just below the face's top edge */
    --pc-foot-gap: 9px;
    --pc-agent: 18px;
    --pc-st: 11.5px;
    --pc-tm-h: 20px;            /* the time's tab (with_time): its height, its type, and the room */
    --pc-tm-fs: 10.5px;         /* the first line of the words leaves it */
    --pc-tm-room: 36px;

    display: flex;
    align-items: flex-start;
    gap: var(--pc-gap);
    width: 100%;
    min-width: 0;
    padding: var(--pc-pad);
    border: 0;
    border-radius: var(--pc-radius);
    background: var(--ob-card);
    /* THE PROVIDER'S BADGE on the face (with_provider, the post only) cuts its ring out of the
       card's own ground, which each tone below sets beside its background */
    --ob-badge-ring: var(--ob-card);
    color: var(--ob-ink);
    font-family: var(--ob-font);
    text-align: left;
    cursor: pointer;
    transition: box-shadow .12s ease;

    .face { width: var(--pc-face); height: var(--pc-face); flex: 0 0 auto; font-size: 9px; }

    .post { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; margin-top: var(--pc-lift); }

    /* the height is set as well as the leading: the handle's smaller type on the same baseline
       otherwise stretches the line by a pixel */
    .who { display: flex; align-items: baseline; gap: 6px; min-width: 0; height: calc(var(--pc-nm) + 6px); line-height: calc(var(--pc-nm) + 6px); }
    .nm { font-size: var(--pc-nm); font-weight: var(--pc-nw); white-space: nowrap; flex: 0 0 auto; }
    .hn { font-size: var(--pc-hn); font-weight: 500; opacity: .75; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }

    /* a hair more under the name than the words' own leading, so the two read as two things */
    .who + .tx { margin-top: 2px; }

    .tx {
        font-size: var(--pc-fs);
        line-height: var(--pc-lh);
        font-weight: var(--pc-fw);
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 4;
        overflow: hidden;
        overflow-wrap: anywhere;
    }
    /* ONE LINE RUNS TO THE EDGE AND IS CUT THERE, as the chip always was. A one-line clamp breaks
       at the last whole word and leaves the rest of the line empty */
    &.l1 .tx { display: block; white-space: nowrap; text-overflow: ellipsis; }
    &.l2 .tx { -webkit-line-clamp: 2; }
    &.l3 .tx { -webkit-line-clamp: 3; }

    .foot { display: flex; align-items: center; gap: 7px; margin-top: var(--pc-foot-gap); min-width: 0; }
    .agent { width: var(--pc-agent); height: var(--pc-agent); flex: 0 0 auto; }
    .st { font-size: var(--pc-st); font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

    &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 1px; }
    &.movable { cursor: grab; }
}

/* sm, the tall chip: a line or three of the words beside a small face */
.ob-pc.sm {
    --pc-face: 20px;
    --pc-gap: 7px;
    --pc-pad: 6px 8px 7px 6px;
    --pc-radius: 9px;
    --pc-fs: 11.5px;
    --pc-lh: 16px;
    --pc-fw: 600;
    --pc-nm: 11.5px;
    --pc-nw: 800;
    --pc-hn: 11px;
    --pc-lift: 0px;
    --pc-foot-gap: 6px;
    --pc-agent: 14px;
    --pc-st: 10.5px;
    --pc-tm-h: 16px;
    --pc-tm-fs: 9.5px;
    --pc-tm-room: 36px;
}

/* xs, the chip: one line, 22px tall, the face the height of the line */
.ob-pc.xs {
    --pc-face: 14px;
    --pc-gap: 5px;
    --pc-pad: 4px 7px 4px 4px;
    --pc-radius: 7px;
    --pc-fs: 11.5px;
    --pc-lh: 14px;
    --pc-fw: 700;
    --pc-nm: 11.5px;
    --pc-nw: 800;
    --pc-hn: 11px;
    --pc-lift: 0px;
    --pc-foot-gap: 3px;
    --pc-agent: 12px;
    --pc-st: 10.5px;
    --pc-tm-fs: 9.5px;
    --pc-tm-room: 38px;

    .face { font-size: 7px; }
}

/* ---- fill: the whole card in the state's colours, as the chips are today ---- */
.ob-pc {
    &.went { background: var(--ob-mint-bg); --ob-badge-ring: var(--ob-mint-bg); color: var(--ob-mint-ink); }
    &.going, &.held { background: var(--ob-card); --ob-badge-ring: var(--ob-card); color: var(--ob-accent-ink); box-shadow: inset 0 0 0 1.5px var(--ob-accent-ring); }
    &.wait { background: var(--ob-butter-bg); --ob-badge-ring: var(--ob-butter-bg); color: var(--ob-butter-ink); }
    &.stop { background: var(--ob-rose-bg); --ob-badge-ring: var(--ob-rose-bg); color: var(--ob-rose-ink); }

    /* GONE IS A WHITE CARD WITH A GREY EDGE (2026-10-03). It was filled grey-bg, which is the
       canvas, so on the week and the list it vanished into the page; white with an edge reads on
       the canvas, on the month's white cells and on today's tint alike */
    &.gone { background: var(--ob-card); --ob-badge-ring: var(--ob-card); color: var(--ob-grey-ink); box-shadow: inset 0 0 0 1px var(--ob-grey-edge); }

    /* ONLY GONE IS STRUCK (2026-10-03): it went out and has been taken back, so its words are
       crossed out. A stopped post never went out and has nothing to cross out - red says it was
       refused, failed or expired. Both struck, red read as a second kind of deleted */
    &.gone .tx { text-decoration: line-through; }
}

/* ---- quiet: a white card, the status word coloured, as the post is today ---- */
.ob-pc.quiet {
    background: var(--ob-card);
    --ob-badge-ring: var(--ob-card);
    color: var(--ob-ink);
    box-shadow: inset 0 0 0 1px var(--ob-line-card);

    .hn { color: var(--ob-muted); opacity: 1; }
    .st { color: var(--ob-muted); }

    &.went .st { color: var(--ob-mint-ink); }
    &.going .st { color: var(--ob-accent-ink); }
    &.held .st { color: var(--ob-butter-ink); }
    &.gone .st { color: var(--ob-grey-ink); }

    &.wait { background: var(--ob-butter-bg); --ob-badge-ring: var(--ob-butter-bg); box-shadow: inset 0 0 0 1px var(--ob-butter-edge); }
    &.wait .st, &.wait .hn { color: var(--ob-butter-ink); }

    &.stop { background: var(--ob-rose-card); --ob-badge-ring: var(--ob-rose-card); box-shadow: inset 0 0 0 1px var(--ob-rose-edge); }
    &.stop .st { color: var(--ob-rose-ink); }

    &.stop .tx, &.gone .tx { color: var(--ob-muted); }
}

/* A PERSON POSTED IT THEMSELVES (2026-09-30, Dave: the hollow one): out, so mint, but not by us, so
   no ground and a dashed edge - the same dash that says "not quite" on a post its month will not
   cover. On every size, fill or quiet */
.ob-pc.went.self_published,
.ob-pc.quiet.went.self_published {
    background: var(--ob-card);
    --ob-badge-ring: var(--ob-card);
    color: var(--ob-mint-ink);
    box-shadow: none;
    outline: 1.5px dashed var(--ob-mint-edge);
    outline-offset: -1.5px;

    .st { color: var(--ob-mint-ink); }
}

/* A WARNING ON THE CARD, the server's words (a reply X may refuse): a dashed amber edge on any
   size, so a calendar chip too small for words still says it, and the words in the foot where
   there is one. Never money: the page says that once, above its posts (Caps.PlanWarning, 2026-10-02) */
.ob-pc.warned { outline: 1.5px dashed var(--ob-butter-ink); outline-offset: -2px; }
.ob-pc .foot .warn { font-size: var(--pc-st); font-weight: 700; color: var(--ob-butter-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---- THE TIME, in a tab in the top-right corner (with_time, 2026-09-29) ----
   The calendar's list mode, where a card's place in its day says the order and not the hour, so
   the card says it. The tab is tucked into the card's corner, a shade deeper than its ground: the
   card's own ink at a low strength, so every tone gets its shade without a colour of its own.
   ONLY THE FIRST LINE GIVES WAY TO IT: a float as wide as the tab stands at the head of the words
   (.tm-room), and the lines under it run the card's whole width. The chip has one line and no
   second to give back, so there the words stop short of it and the tab is the card's height. */
.ob-pc.timed { position: relative; }
.ob-pc .tm {
    position: absolute;
    top: 0;
    right: 0;
    height: var(--pc-tm-h);
    padding: 0 6px 0 7px;
    border-radius: 0 var(--pc-radius) 0 8px;
    background: color-mix(in srgb, currentColor 13%, transparent);
    font-size: var(--pc-tm-fs);
    font-weight: 800;
    line-height: var(--pc-tm-h);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    pointer-events: none;
}
.ob-pc .tm-room { float: right; width: var(--pc-tm-room); height: 1px; }
.ob-pc.l1 .tm-room { display: none; }
.ob-pc.timed.l1 .tx { padding-right: var(--pc-tm-room); }
.ob-pc.xs .tm { bottom: 0; height: auto; display: flex; align-items: center; border-radius: 0 var(--pc-radius) var(--pc-radius) 0; }

/* a quiet card's ink is the page's, so its tab is the page's grey rather than a shade of black */
.ob-pc.quiet .tm { background: var(--ob-canvas); }

/* ---- THE ONE HOVER, after every tone so none of their edges wins over it. The post's size
   takes the halo round it as well, as the post does today; at chip size a halo is most of the
   chip, so it gets the ring and a short drop ---- */
.ob-pc:hover, .ob-pc.quiet:hover {
    box-shadow: inset 0 0 0 1.5px var(--ob-accent), 0 2px 8px rgba(79, 102, 240, .22);
}
.ob-pc.md:hover, .ob-pc.md.quiet:hover {
    box-shadow: inset 0 0 0 1px var(--ob-accent), 0 0 0 3px var(--ob-accent-halo-1), 0 4px 14px rgba(79, 102, 240, .18);
}

/* the card the panel beside the list is drawing: the accent edge, held */
.ob-pc.sel, .ob-pc.quiet.sel {
    box-shadow: inset 0 0 0 1px var(--ob-accent), 0 0 0 3px var(--ob-accent-halo-1);
}

/* ITS IMAGES (2026-10-08): the first at a chip's end, a row of up to four under a taller card's
   words. The 160 px thumbnail, which is kept for the life of the post */
.ob-pc .pic { flex: 0 0 auto; align-self: center; width: 16px; height: 16px; border-radius: 4px; overflow: hidden; box-shadow: 0 0 0 1px rgba(255, 255, 255, .7); }
/* a chip with the time in its corner: the thumbnail stands just left of the tab, and the words stop
   at the thumbnail rather than leaving the tab's room as well */
.ob-pc.timed .pic { margin-right: var(--pc-tm-room); }
.ob-pc.timed.l1:has(> .pic) .tx { padding-right: 0; }
.ob-pc .pic img, .ob-pc .pics img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ob-pc .pics { display: flex; gap: 4px; margin-top: 6px; }
.ob-pc .pics span { width: 30px; height: 30px; flex: 0 0 auto; border-radius: 6px; overflow: hidden; background: var(--ob-canvas); }
.ob-pc.md .pics span { width: 44px; height: 44px; border-radius: 8px; }

/* A FILE HELD OVER THE CARD (2026-10-08): it lights in the accent and takes the drop on its first
   part; while a dropped image is on its way it breathes */
.ob-pc.over { box-shadow: inset 0 0 0 2px var(--ob-accent), 0 0 0 3px var(--ob-accent-bg); background: var(--ob-accent-bg); color: var(--ob-accent-ink); }
.ob-pc.adding { animation: ob-pc-adding 1s ease-in-out infinite alternate; }
@keyframes ob-pc-adding { from { opacity: 1; } to { opacity: .55; } }
@media (prefers-reduced-motion: reduce) { .ob-pc.adding { animation: none; opacity: .7; } }
 /* ============================================
   OKAYBOT PAGE
   The three columns: sidebar 236, body, rail 300. The header row and its arithmetic, the two
   page families (feed, cards), the row cards on Agents and Profiles, the Setup steps and the
   Spend tiles. design.md sections 3 and 4.

   The scaffold positions #Main absolutely under a top menu bar and paints a warm gradient on
   the body; the product has neither. body.ob-app (set by the sidebar when someone is signed
   in) is the switch, and its selectors outrank the scaffold's by specificity, so no scaffold
   file is edited.
   ============================================ */

body.ob-app {
    background: var(--ob-canvas);
    font-family: var(--ob-font);
    color: var(--ob-ink);
    -webkit-font-smoothing: antialiased;
}
body.ob-app::before { display: none; }
body.ob-app .site-footer { display: none; }

body.ob-app #Main {
    position: static;
    margin: 0;
    padding-left: var(--ob-sidebar-w);
    min-height: 100vh;
}

/* ---- the sidebar: white, fixed, the only navigation ---- */
body.ob-app #Sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: var(--ob-sidebar-w);
    z-index: 40;
}

.ob-sidebar {
    height: 100%;
    display: flex;
    flex-direction: column;
    padding: 22px 16px 18px;
    background: var(--ob-card);
    border-right: 1px solid var(--ob-line);
    font-family: var(--ob-font);
    overflow-y: auto;
}

.ob-logo {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 8px 22px;
    text-decoration: none;
    color: var(--ob-ink);

    /* THE MARK ALONE, NOT THE TILE. The sidebar is the one place the logo stands on the
       product's own card rather than on a ground of its own, so it drops the blue square and
       draws the bot and the tick in the accent on nothing at all - which is also why there is
       no radius here any more: there is no ground left to round.

       It is a mask, the way .ob-square.bot draws the same mark, so the file's path stays in
       the stylesheet and the template carries none of it. The box is the mono file's own
       aspect - 376 by 456 - so `contain` fills the 30px slot top to bottom and the wordmark
       sits a true 10px off the ink rather than off a column of empty space. */
    .mark {
        display: block;
        width: 25px;
        height: 30px;
        background: var(--ob-accent);
        -webkit-mask: url(/images/logo/okaybot-mono.svg) center / contain no-repeat;
        mask: url(/images/logo/okaybot-mono.svg) center / contain no-repeat;
    }
    .name { font-size: 17px; font-weight: 800; letter-spacing: -.02em; }

    &:hover { color: var(--ob-ink); text-decoration: none; }
}

/* NEW POST (2026-10-03, Dave): the composer's way in (popups/compose.js), under Docs on every
   page since 2026-10-04 (it sat under the logo before). HOLLOW - the accent's outline, never its
   fill - and small, its plus on the line of the glyphs above it: easy to find, and never louder
   than the page beside it. It is not a section, so it never lights */
.ob-newpost {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 34px;
    margin: 12px 0 0 2px;
    padding: 0 15px 0 11px;
    border: 1.5px solid var(--ob-accent);
    border-radius: 999px;
    background: var(--ob-card);
    color: var(--ob-accent-ink);
    font-family: var(--ob-font);
    font-size: 13px;
    font-weight: 700;
    white-space: nowrap;
    cursor: pointer;
    transition: background .12s;

    svg { width: 14px; height: 14px; flex: 0 0 auto; }
    &:hover { background: var(--ob-accent-bg); }
    &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
}

.ob-nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.ob-navitem {
    display: flex;
    align-items: center;
    gap: 12px;
    height: var(--ob-nav-h);
    padding: 0 12px;
    border-radius: 12px;
    font-size: 14px;
    font-weight: 600;
    color: var(--ob-ink-2);
    text-decoration: none;

    svg { width: 18px; height: 18px; color: var(--ob-muted); flex: 0 0 auto; }
    /* never on two lines: in the medium tier's 200px sidebar "Profiles & Agents" is 2px over, and
       runs into the item's own right padding rather than wrapping (2026-10-05) */
    .grow { flex: 1 1 auto; white-space: nowrap; }

    &:hover { background: var(--ob-canvas); color: var(--ob-ink-2); text-decoration: none; }
    &.on { background: var(--ob-accent-bg); color: var(--ob-accent-ink); font-weight: 700; }
    &.on svg { color: var(--ob-accent); }
}

/* A LINK THAT LEAVES. Docs is not a section of the product - it opens an outside page and the
   sidebar goes with it - so it is drawn a step back from the six that are: the label a shade
   lighter and a shade less bold, the glyph lighter again, and a gap above it that takes it out
   of the run. Lighter, not illegible: --ob-c3 still clears 4.5:1 on the canvas, which
   --ob-muted does not, and a nav label is text however quiet it is meant to look. */
.ob-navitem.quiet {
    margin-top: 10px;
    color: var(--ob-c3);
    font-weight: 500;

    svg { color: var(--ob-faint-1); }
    &:hover { color: var(--ob-ink-2); }
    &:hover svg { color: var(--ob-muted); }
}

/* THE FOOT: one line, and it is a nav item (2026-09-16). It held a bare avatar and the three
   outside links until then - four targets on one 236px row, three of them 30px wide - and the
   links went to the foot of every page's left column, and then went altogether (2026-09-19):
   Docs is a nav item in this list now and the logo above is the way back out. What is left
   is the person, drawn the way the six sections above are drawn: grey under the pointer, the
   accent when it is the page you are on. That is the whole reason it reads as navigation now -
   it IS navigation, to /account, and it used to open a menu instead.

   The money block that used to sit above this is gone (2026-09-16) - today's spend is a line on
   the Usage item now (sidebar.js). This row is what is left below the nav, which is why it
   carries the margin-top: auto that pushed the block down. */
.ob-sidefoot {
    margin-top: auto;
    padding-top: 18px;
}

/* The account's own nav item. It differs from the six above it in two numbers and nothing else:
   the row is 48 rather than 42, because a 30px face wants more than 6px of air above and below
   it, and the left padding is 8 rather than 12, which puts the face on 24px - the logo mark's
   edge at the top of the column, so the two 30px faces bookend it. The label lands 6px right of
   the section labels as a result; the face is the thing the eye lines up here, not the text. */
.ob-navitem.me {
    height: 48px;
    padding: 0 8px;
    gap: 10px;

    .grow { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

/* ---- the header: title, chip, muted line, the one control, the avatar ---- */
/* IT WRAPS RATHER THAN RUNS OVER (2026-09-24): a long title beside a wide control - the
   calendar's week title beside its faces and its four views, at the foot of medium - puts the
   control on a line of its own, still at the right, instead of pushing it off the page */
.ob-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    min-height: var(--ob-head-h);
    margin-bottom: var(--ob-head-gap);
}

.ob-title {
    margin: 0;
    font-family: var(--ob-font);
    font-size: 26px;
    font-weight: 800;
    letter-spacing: -.02em;
    line-height: 1.15;
    color: var(--ob-ink);
}

.ob-control { margin-left: auto; }

/* the title's row inside the header: the title, and on narrow the glyph and the steps around it */
.ob-headrow { display: flex; align-items: center; gap: 12px; }

/* ---- the title as the way to the panel, on narrow (PageHead `nav` and `sheet`) ----
   Two ways a page reaches the panel it has lost (dev/docs/responsive.md). NAV makes the title
   itself the door - the calendar's "September 2026" opens the picker - with the calendar's
   glyph before it and the two steps after it. SHEET is a pill on the title's row, just left of
   the control, for a panel that is not about where you are - Usage's "X's prices", the
   calendar's Held. Above narrow the panel is on the screen, so the title is only a title and
   neither the glyph, the steps nor the pill is drawn. */
.ob-titlebtn {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    letter-spacing: inherit;
    color: inherit;
    text-align: left;
    pointer-events: none;
}
.ob-titlebtn .glyph,
.ob-titlesteps,
.ob-head .ob-sheetbtn { display: none; }

@media (max-width: 1199px) {
    .ob-titlebtn { pointer-events: auto; cursor: pointer; }
    .ob-titlebtn:hover { color: var(--ob-accent-ink); }
    .ob-titlebtn:hover .glyph { color: var(--ob-accent); }
    .ob-titlebtn:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 4px; border-radius: 6px; }
    .ob-titlebtn .glyph { display: block; flex: 0 0 auto; width: 20px; height: 20px; color: var(--ob-muted); }

    .ob-titlesteps { display: inline-flex; gap: 2px; margin-left: -4px; }
    .ob-titlesteps .b {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 30px;
        height: 30px;
        padding: 0;
        border: 0;
        border-radius: 9px;
        background: transparent;
        color: var(--ob-ink-2);
        cursor: pointer;
    }
    .ob-titlesteps .b svg { width: 15px; height: 15px; }
    .ob-titlesteps .b:hover { background: var(--ob-line); color: var(--ob-ink); }
    .ob-titlesteps .b:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 1px; }

    .ob-head .ob-sheetbtn { display: inline-flex; gap: 8px; margin-left: auto; }
    .ob-head .ob-sheetbtn + .ob-control { margin-left: 0; }
}

.ob-halves {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--ob-card-gap);
}

/* TWO SUBJECTS, NOT TWO PARTS OF ONE. 14px is the distance between two cards in the same
   stack, and it is too close when each column is about something different - the account
   page's accounts beside its passkeys read as one card cut in half at that gap. This is the
   money card's column gap, the only other place two columns of unrelated things sit level. */
.ob-halves.wide { gap: 40px; }

.ob-chart-empty {
    padding: 34px 0 20px;
    font-size: 13px;
    font-weight: 600;
    color: var(--ob-muted);
}

/* ---- the object card: one per agent, one per profile, and the calls to action ----
   Collapsed it is a line you can click; open it is everything that can be done to that one
   object. The head is a real <button> so the keyboard reaches it, and it carries the padding so
   the whole line is the target rather than the words in it. */
.ob-card.oc {
    padding: 0;
    overflow: hidden;

    /* The head is a row, not a button: an object card carries a toggle at its right edge, and a
       switch nested inside the button that opens the card would be neither valid nor clickable.
       So .oc-open is the button, and the toggle is its sibling in its own column. */
    .oc-head { display: flex; align-items: center; }
    .oc-head:hover { background: var(--ob-card-head); --ob-badge-ring: var(--ob-card-head); }

    .oc-open {
        display: flex;
        align-items: center;
        gap: 11px;
        flex: 1 1 auto;
        min-width: 0;
        padding: 15px 0 15px 20px;
        border: 0;
        background: none;
        text-align: left;
        cursor: pointer;
        font-family: var(--ob-font);
        color: var(--ob-ink);
    }
    .oc-open:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: -2px; }

    .oc-open .name { font-size: 15.5px; font-weight: 800; white-space: nowrap; }
    .oc-open .handle { font-size: 13px; font-weight: 600; color: var(--ob-muted); white-space: nowrap; }
    .oc-open .grow { flex: 1 1 auto; min-width: 0; }
    .oc-open .fact { font-size: 12.5px; font-weight: 600; color: var(--ob-muted); white-space: nowrap; }
    .oc-open .fact .num { font-weight: 800; color: var(--ob-ink-2); font-variant-numeric: tabular-nums; }

    /* The caret sits against the name rather than out on its own at the far edge: it belongs to
       the words it opens, and a column of arrows down the page was more furniture than signal.
       The 1px nudge is real - flex centres the 24-box, which leaves the drawn stroke reading a
       hair high against the cap height of the name beside it. */
    .oc-open .chev {
        display: flex;
        margin-left: -3px;
        position: relative;
        top: 1px;
        color: var(--ob-faint-3);
        transition: transform .15s;
    }
    .oc-open .chev svg { width: 15px; height: 15px; }
    .oc-head:hover .chev { color: var(--ob-ink-2); }

    /* Who works here, in the middle of a profile's collapsed row: the label, then one square per
       agent granted on it, a size down from the profile's own circle so it reads as a list of
       small things belonging to the big one rather than a second row of faces. A spacer either
       side keeps it centred in whatever room the name leaves. */
    /* PINNED RIGHT, like the cap on an Agents row and for the same reason. Between two flex
       spacers these faces were centred in the leftover room, so a Needs reconnecting or a
       Disabled chip appearing made the whole group jump sideways - a toggle you press should
       change the switch, not shuffle the row. Fixed width, at the end, clear of the switch. */
    .oc-open .oc-agents {
        display: flex;
        align-items: center;
        justify-content: flex-end;
        gap: 6px;
        flex: 0 0 var(--ob-cap-w);
        width: var(--ob-cap-w);
        margin-right: 24px;
        min-width: 0;
    }
    .oc-open .oc-agents .ob-lbl { margin: 0 3px 0 0; }
    .oc-open .oc-agents .none { font-size: 12.5px; font-weight: 600; color: var(--ob-faint-2); }

    /* NO CARD CARRIES A SWITCH ANY MORE (2026-09-23): an agent's is on its page and a profile's
       in its panel. The head's last child is the row itself, so it owns the right edge, and the
       cap or the faces pinned to the end of it sit against that rather than against a gap */
    .oc-open:last-child { padding-right: 20px; }
    .oc-open:last-child .oc-cap,
    .oc-open:last-child .oc-agents { margin-right: 0; }

    .oc-body { padding: 4px 20px 20px; }
    /* an open card whose head is hidden (an empty section's call to action) owns the top padding */
    .oc-body.first { padding-top: 18px; }

    /* the open card is the one thing being looked at, so it gets the edge */
    &.open { border-color: var(--ob-faint-3); }
    &.open .oc-open .chev { transform: rotate(90deg); }
    &.open .oc-head { background: var(--ob-card-head); border-bottom: 1px solid var(--ob-line-in); }

    /* A disabled object keeps its white card - tinting the card itself made it read as a gap in
       the list rather than as a row that is switched off. What changes is the contents: the face
       loses its colour and the words fade, and the Disabled badge says it in words. A profile
       waiting on a reconnect does not fade: its chip says it (Dave, 2026-10-04). */
    &.off .ob-square,
    &.off .ob-circle { filter: grayscale(1); opacity: .55; }
    &.off .oc-open .name { color: var(--ob-muted); }
    &.off .oc-open .handle { color: var(--ob-faint-2); }
    &.off .oc-open .chev { color: var(--ob-faint-2); }
    &.off .oc-body { color: var(--ob-ink-2); }

    /* a call to action is not an object: quieter until it is opened */
    &.cta .oc-open .name { font-weight: 700; color: var(--ob-ink-2); }
    &.cta .oc-head:hover .name { color: var(--ob-ink); }

    /* A ROW THAT GOES SOMEWHERE RATHER THAN OPENING (2026-09-11). On Agents and Profiles the
       head is a link to the object's page, so it is an <a> wearing the button's class: same
       geometry, and the scaffold's underline and link colour taken off it. The caret keeps
       pointing right, because that is where the click goes. */
    a.oc-open { text-decoration: none; color: var(--ob-ink); }
    a.oc-open:hover { text-decoration: none; color: var(--ob-ink); }

    /* Today against the cap, in the middle of an agent's row: the number, "of $5 today", and
       the bar under them. It takes the room the name leaves, centred by a spacer either side
       like the profile row's agents, and holds a floor so the bar means the same width on
       every row. */
    /* PINNED RIGHT, AND IT DOES NOT MOVE. It used to sit between two flex spacers, which
       centred it in whatever room the name left over - so a long agent name pushed it right, a
       short one pulled it left, and turning an agent off slid the whole block sideways as the
       Disabled chip appeared. A fixed width at the end of the row instead: every number starts
       at the same x down the page, and nothing about the row's contents moves it. The margin is
       the air between it and the switch, which it used to run right up against. */
    .oc-open .oc-cap {
        display: flex;
        flex-direction: column;
        gap: 6px;
        flex: 0 0 var(--ob-cap-w);
        width: var(--ob-cap-w);
        margin-right: 24px;
    }
    .oc-open .oc-cap .line { display: flex; align-items: baseline; gap: 5px; font-size: 12.5px; font-weight: 600; color: var(--ob-muted); white-space: nowrap; }
    .oc-open .oc-cap .line .num { font-size: 14px; font-weight: 800; color: var(--ob-ink); font-variant-numeric: tabular-nums; }
    /* the block is a fixed width, so a longer line clips with an ellipsis rather than running out of the card */
    .oc-open .oc-cap .line { overflow: hidden; }
    .oc-open .oc-cap .line .state { margin-left: 4px; color: var(--ob-rose-ink); overflow: hidden; text-overflow: ellipsis; }
    &.off .oc-open .oc-cap .line .num { color: var(--ob-muted); }
}

/* MEDIUM AND BELOW: a Profiles row keeps its agents' squares and drops the words "Works here"
   in front of them. The cap block beside it has narrowed to 190 (--ob-cap-w), and the squares
   need no label once one row has been read (dev/docs/responsive.md, dropping a column). */
@media (max-width: 1599px) {
    .ob-card.oc .oc-open .oc-agents .ob-lbl { display: none; }
}

/* ---- PROFILES AND USAGE: a list or a chart, and a panel beside it ----
   Activity's split (.ob-act, .ob-panel - okaybot-activity.css) with the calendar's panel width
   (--ob-side-panel-w, 2026-09-24): the rows or the chart take every pixel the panel does not
   need. Profiles was 60/40 and Usage 340 wide until Dave asked for one width everywhere. Agents
   had the same split until it was folded into Profiles (2026-10-01).

   On narrow Activity's own rule takes the panel away. On Profiles a row - an account's or an
   agent's - opens it as a sheet (PanelSheet), and on Usage "X's prices" does. */
.ob-act.profiles,
.ob-act.usage { grid-template-columns: minmax(0, 1fr) var(--ob-side-panel-w); }
@media (max-width: 1199px) {
    .ob-act.profiles,
    .ob-act.usage { grid-template-columns: minmax(0, 1fr); }
}

.ob-act.profiles .left .ob-card { margin-bottom: var(--ob-card-gap); }

/* THE PLAN'S ROOM (Dave, 2026-10-03): the plan change popup's warning (.warn, okaybot-dialog.css)
   outlined rather than filled, in the title row's control slot, while there are more X accounts or
   agents than the plan holds, now or once a downgrade lands. One line beside the title, so the lists
   do not move; a narrow column puts it on a line of its own at the right, as the head does with any
   control (.ob-head). Nothing is drawn when everything fits */
.ob-roomwarn { display: flex; gap: 8px; align-items: flex-start; padding: 7px 12px; border: 1.5px solid color-mix(in srgb, var(--ob-butter-ink) 45%, transparent); border-radius: 12px; color: var(--ob-butter-ink); font-size: 13.5px; font-weight: 600; line-height: 1.4; }
.ob-roomwarn svg { width: 15px; height: 15px; flex: 0 0 auto; margin-top: 2px; }

/* THE AGENTS, UNDER THE ACCOUNTS: a second header over the second list, the title's size and the
   room under it the title's (Dave, 2026-10-01), with two of the header's gaps above it so the two
   lists read as two sections. It waits for its rows: on the first frame the slot under it is empty,
   and a heading over nothing would sit under the title and be pushed down when the accounts came */
.ob-act.profiles .ob-subhead { margin-top: calc(var(--ob-head-gap) * 2); }
.ob-act.profiles .ob-subhead:has(+ [widget="agents"]:empty) { display: none; }

/* the cards down the left space themselves, as they did on a cards page. Children only: the two
   limit tiles and the two halves are grids of cards, and a margin on those would stretch them */
.ob-act.usage .left > .ob-card,
.ob-act.usage .left > .ob-halves { margin-bottom: var(--ob-card-gap); }

/* the link to X's own page, under a hairline so it reads as where the list came from rather than
   as its last row. The small muted link the detail and the agent page use (.lnk) */
.ob-pricepanel .src {
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--ob-line-in);
    font-family: var(--ob-font);
    font-size: 12.5px;
    font-weight: 700;

    a { color: var(--ob-muted); text-decoration: none; }
    a:hover { color: var(--ob-accent-ink); }
}

/* ---- BILLING: the pricing page's cards inside the app (2026-10-02, billing/billing.js) ----
   Usage's split: the pricing page's title, cards and line (billing/planset.js) on the left, and the
   questions about plans, billing and usage (billing/questions.js) in the white panel on the right, at
   the shared side panel width (Dave). No title of its own: the title over the cards says the plan.
   The cards keep the outside site's look (okaybot-site.css, "the plans"), so a person who chose a
   plan on /pricing finds the same cards here; what is here is the frame, the pricing page's title
   brought down from the site's scale to the console's, and the questions at the panel's. */
.ob-act.billing { grid-template-columns: minmax(0, 1fr) var(--ob-side-panel-w); }
.ob-act.billing > .left {
    container: billing / inline-size;

    /* LEFT, NOT CENTRED, AND WHERE EVERY SCREEN'S TITLE IS (Dave, 2026-10-02): the title, its line
       and the cards start at the column's left edge, and "You are on Max" is drawn as PageHead draws
       Usage's or Profiles' - the same type (.ob-title) in a row of the head's height at the top of
       the column - with its line under it, smaller, then the head's gap before the cards */
    .ob-pagehead.mid { max-width: none; margin: 0; padding: 0; text-align: left; }
    .ob-pagehead.mid .ob-h1 {
        display: flex;
        align-items: center;
        min-height: var(--ob-head-h);
        margin: 0;
        font-size: 26px;
        letter-spacing: -.02em;
        line-height: 1.15;
    }
    .ob-pagehead.mid .ob-subhead { margin: 0; max-width: none; font-size: 15px; text-wrap: pretty; }
    /* THREE ACROSS DOWN TO THE CONSOLE'S FLOOR (Dave, 2026-10-03): the outside site folds the cards
       to one column at 960 (okaybot-site.css), but the console stops at 900 (okaybot-desktop.css), so
       here they stay three across all the way down and the container steps below make them fit */
    .ob-plans {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        max-width: none;
        margin: var(--ob-head-gap) 0 0;
    }

    /* Manage billing and Invoices, ending where the cards end, on the line under the title (Dave,
       2026-10-02): the title and the cards share one 1080 box, and the two buttons sit at its right,
       centred on the line's own height (15px at 1.4) under the head's row. The line keeps clear of
       them: its right padding is the two buttons and their gap */
    .ob-billwrap { position: relative; max-width: 1080px; }
    .ob-billacts {
        position: absolute;
        top: var(--ob-head-h);
        right: 0;
        display: flex;
        align-items: center;
        gap: 8px;
        height: calc(15px * 1.4);
    }
    .ob-billwrap:has(.ob-billacts) .ob-pagehead.mid .ob-subhead { padding-right: 220px; }
    .ob-planset { margin: 0; }

    /* IMPORTANT TO KNOW (Dave, 2026-10-02): the pricing page's footnote as a section of its own
       under the cards, left-aligned, its heading the panel's own (.ob-secthead) */
    .ob-notes { margin-top: 34px; }
    .ob-notes ul { margin: 0; padding-left: 18px; font-size: 14px; font-weight: 500; line-height: 1.6; color: var(--ob-ink-2); }
    .ob-notes li + li { margin-top: 4px; }
}

/* THE CARDS FOLLOW THEIR COLUMN, NOT THE WINDOW. Beside the panel, three cards share what the
   sidebar and the panel leave, so how tight they get is the column's width - a container query, named
   so nothing else answers it, as the stage's (okaybot-stage.css). Under 760 the padding and the price come down so "$69.95 a month"
   still fits on one line; under 680 the chip moves out of the corner to the top of the card, where it
   cannot sit on the plan's name, and "a month" may drop under the price */
@container billing (max-width: 760px) {
    .ob-plans { gap: 14px; }
    .ob-plan { padding: 22px 16px 20px; }
    .ob-plan .price .n { font-size: 34px; }
    .ob-plan .chip { top: 18px; right: 14px; }
}
@container billing (max-width: 680px) {
    .ob-plan .price { flex-wrap: wrap; row-gap: 2px; }
    .ob-plan .price .n { font-size: 30px; }
    .ob-plan .chip { position: static; order: -1; align-self: flex-start; margin-bottom: 12px; }
    .ob-plan .facts li { font-size: 13px; }
}

/* the questions in the panel, at the console's scale: the site's list (okaybot-site.css, "the
   questions") with its rows and words brought down to the panel's. QUIETER THAN THE CARDS (Dave,
   2026-10-02): the questions in the second ink at a lighter weight, the arrows and the rules
   fainter, so the panel is there when wanted and does not pull the eye off the plans. An open
   question comes up to the full ink, and its answer keeps the copy's own second ink to be read */
.ob-act.billing .ob-panel .ob-qa {
    border-bottom-color: var(--ob-line-in);

    .q { border-top-color: var(--ob-line-in); }
    summary { gap: 10px; padding: 13px 0; font-size: 13.5px; font-weight: 600; letter-spacing: 0; color: var(--ob-ink-2); }
    .q[open] summary { color: var(--ob-ink); }
    .tog { flex-basis: 12px; color: var(--ob-faint-1); }
    .a { padding: 0 4px 14px 22px; }       /* 22: the arrow and the gap, so the words line up */
    .ob-copy { font-size: 13px; line-height: 1.55; }
}

/* NARROW: no room for a panel beside the cards, so it stacks under them, as Account's does
   (dev/docs/responsive.md, the stacked panel) - every question is still on the page */
@media (max-width: 1199px) {
    .ob-act.billing { grid-template-columns: minmax(0, 1fr); }
    .ob-act.billing > .ob-panel:not(.sheet) {
        display: block;
        position: static;
        height: auto;
        overflow: visible;
        border-left: 0;
        border-top: 1px solid var(--ob-line);
        padding: 24px var(--ob-body-pad-right) 40px var(--ob-body-pad-left);
    }
}

/* the picked row on Profiles, an account's or an agent's: one at a time across both lists */
.ob-act.profiles .ob-card.oc {
    /* the picked row: the accent edge and the accent head, as the log's picked line wears it */
    &.selected { border-color: var(--ob-accent); }
    &.selected .oc-head,
    &.selected .oc-head:hover { background: var(--ob-accent-bg); --ob-badge-ring: var(--ob-accent-bg); }
}

/* ---- the panel: one account, or one agent ----
   The two panels share their head - the face, the name and the line under it, the switch at the
   right - and the chips under it (2026-10-01). What is the agent's alone is okaybot-agent.css. */
.ob-profpanel,
.ob-agentpanel {
    font-family: var(--ob-font);
    color: var(--ob-ink);

    .pp-head { display: flex; align-items: center; gap: 14px; }
    .pp-head .who { min-width: 0; }
    .pp-head .name { font-size: 20px; font-weight: 800; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .pp-head .handle { font-size: 13.5px; font-weight: 600; color: var(--ob-muted); }
    .pp-head .grow { flex: 1 1 auto; }

    .pp-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 16px; }

    /* NO HAIRLINES BETWEEN THE SECTIONS (Dave, 2026-10-01): the labels are the boundary, and
       a line between every part was too much on one panel. The rows inside a section keep
       theirs - one account from the next, one sign-in from the next */
    .ob-psect { margin-top: 28px; padding-top: 0; border-top: 0; }
    .ob-psect .ob-lbl { margin-bottom: 10px; }

    /* the network's switches: the words and who set it, the switch at the right (2026-10-01) */
    .pp-switch { display: flex; align-items: center; gap: 12px; }
    .pp-switch + .pp-switch { margin-top: 12px; }
    .pp-switch .words { flex: 1 1 auto; min-width: 0; }
    .pp-switch .t { font-size: 14px; font-weight: 700; color: var(--ob-ink); }
    .pp-switch .ob-meta { margin-top: 1px; }

    /* off, the face and the name fade as they do on the row; the switch says why. A profile
       waiting on a reconnect is not off here: it keeps its colour (Dave, 2026-10-04) */
    &.off .pp-head .ob-circle,
    &.off .pp-head .ob-square { filter: grayscale(1); opacity: .55; }
    &.off .pp-head .name { color: var(--ob-muted); }
}

/* Reconnect, at the right under a profile's last section (Dave, 2026-10-04): the foot's old gap
   above it, so it does not crowd the last switch */
.ob-profpanel .pp-reconnect { display: flex; justify-content: flex-end; margin-top: 36px; }

/* THE FOOT AT THE PANEL'S FOOT (Dave, 2026-10-04): Remove profile and Remove agent sit at the
   bottom of the panel, not under its last section. Beside the list the panel is the viewport's
   height, so the slot drawing the one panel shown fills it, the panel fills the slot, and the
   foot's margin takes what is left; the 36px it kept above it is padding now, the least room
   when the sections run long. As a sheet the panel is as tall as what is in it, so the foot
   follows the last section as before */
.ob-act.profiles > .ob-panel:not(.sheet) {
    > [widget]:not(:empty) { display: flex; flex-direction: column; min-height: 100%; }
    .ob-profpanel,
    .ob-agentpanel { display: flex; flex-direction: column; flex: 1 0 auto; }
    .ob-pfoot { margin-top: auto; padding-top: 36px; }
}

/* the blank face on a call to action: the shape of the thing it makes, with a plus in it */
.ob-square.blank,
.ob-circle.blank {
    background: var(--ob-canvas);
    color: var(--ob-muted);
    border: 1px dashed var(--ob-faint-3);
}
 /* ============================================
   OKAYBOT - MOVE IT
   The detail's Move it tab (popups/itemmove.js): the calendar's picker on the left, the picked
   day as a rail on the right, the two buttons under both. It takes the picture's place inside
   the detail (okaybot-detail.css), so it is scoped under .ob-detail and draws nothing the
   detail does not already give it room for.

   THE RAIL IS A DAY, MIDNIGHT TO MIDNIGHT, and every position on it is a percentage of that
   day, set inline by the widget. Nothing here knows how tall the rail is: the modal's white box
   gives it the whole height under the switch, the Activity panel gives it a fixed one, and a
   pixel is worth whatever 1440 minutes over that height makes it.

   NO HOUR IS SHADED. A faint line an hour and a label every three, the same from 00 to 24: it
   is not ours to say when a post should go out. The one thing drawn differently is Now, on
   today, because the past is the one time a post cannot be moved to.
   ============================================ */

/* the slot the detail draws the tab in, and how the modal lets it fill the box. The slot is in
   the template whichever tab is showing, so the chooser can be made and filled behind the
   picture; hidden is hidden whatever the layout below says. */
.ob-detail .ob-moveslot { min-width: 0; }
.ob-detail .ob-moveslot[hidden] { display: none !important; }
.ob-detail.modal .pic.moving { display: flex; flex-direction: column; overflow: hidden; }
.ob-detail.modal .pic.moving .ob-moveslot { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }

.ob-move {
    display: grid;
    grid-template-columns: 280px minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) auto;
    gap: 18px 36px;
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
    font-family: var(--ob-font);

    .pick { min-width: 0; }
    /* the picker's head keeps its right end clear for the calendar page's Options button;
       nothing sits in that corner here */
    .pick .ob-calpicker .pk-head { padding-right: 0; }

    .acts { grid-column: 1 / -1; margin-top: 0; }
}

/* the panel has no box to fill, so the day gets a height of its own */
.ob-detail.column .ob-move .dayrail { height: 620px; }

/* ---- the rail ---- */
.ob-move .dayrail {
    position: relative;
    min-height: 0;
    min-width: 0;
    padding: 9px 0 9px 34px;        /* room for the labels on the left and half a label at either end */
}

.ob-move .rail {
    position: relative;
    height: 100%;
    min-height: 240px;
    touch-action: none;             /* the card is dragged with a finger too */

    /* the line the day runs down, and its hours */
    &::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 2px; border-radius: 1px; background: var(--ob-line); }
    .hr { position: absolute; left: 0; right: 0; height: 1px; background: var(--ob-line-in); transform: translateY(-.5px); }
    .hr.big { left: -4px; background: var(--ob-line); }
    .hl {
        position: absolute;
        right: calc(100% + 10px);
        transform: translateY(-50%);
        font-size: 11px;
        font-weight: 700;
        color: var(--ob-muted);
        font-variant-numeric: tabular-nums;
        line-height: 1;
    }

    /* now, on today: the same line the week draws across today's column */
    .now { position: absolute; left: -3px; right: 0; height: 2px; background: var(--ob-accent); opacity: .6; transform: translateY(-50%); z-index: 1; }
    .now::before { content: ''; position: absolute; left: 0; top: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--ob-accent); }

    /* the account's other posts on this day: the chip the month draws, centred on its time.
       Two an hour apart overlap, and that is what an hour with two posts in it looks like.

       OPAQUE, AND FADED BY COLOUR RATHER THAN BY OPACITY (2026-09-20). They were drawn at 62
       percent, and where two overlapped the lower one bled through the upper. Each is solid now,
       with a rim of the card's white so a stack reads as chips on chips, and the fading is done
       by mixing every colour towards white: the same five tones, at a third of their strength.

       ITS WORDS ARE .w, NOT .x. The modal's close button is .ob-modal .x, and a span called .x
       inside the modal became a 32px grey circle pinned to the chip's corner. */
    .gh {
        position: absolute;
        left: 14px;
        right: 0;
        display: flex;
        align-items: center;
        gap: 6px;
        height: 22px;
        padding: 0 8px 0 5px;
        border-radius: 7px;
        background: var(--ob-card);
        box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--ob-accent-ring) 70%, var(--ob-card)), 0 0 0 2px var(--ob-card);
        color: color-mix(in srgb, var(--ob-accent-ink) 55%, var(--ob-card));
        font-size: 11.5px;
        font-weight: 700;
        white-space: nowrap;
        overflow: hidden;
        transform: translateY(-50%);
        z-index: 2;
        pointer-events: none;
    }
    .gh .ob-circle { width: 14px; height: 14px; font-size: 7px; opacity: .6; }
    .gh .t { color: var(--ob-faint-1); font-variant-numeric: tabular-nums; flex: 0 0 auto; }
    .gh .w { overflow: hidden; text-overflow: ellipsis; min-width: 0; }
    .gh.went { box-shadow: 0 0 0 2px var(--ob-card); background: color-mix(in srgb, var(--ob-mint-bg) 55%, var(--ob-card)); color: color-mix(in srgb, var(--ob-mint-ink) 55%, var(--ob-card)); }
    .gh.wait { box-shadow: 0 0 0 2px var(--ob-card); background: color-mix(in srgb, var(--ob-butter-bg) 60%, var(--ob-card)); color: color-mix(in srgb, var(--ob-butter-ink) 55%, var(--ob-card)); }
    .gh.gone { box-shadow: 0 0 0 2px var(--ob-card); background: color-mix(in srgb, var(--ob-grey-bg) 70%, var(--ob-card)); color: color-mix(in srgb, var(--ob-grey-ink) 55%, var(--ob-card)); }
    .gh.gone .w { text-decoration: line-through; }

    /* the pin on the line at the card's time, and the arm from it to the card */
    .pin { position: absolute; left: -5px; width: 12px; height: 12px; border-radius: 50%; background: var(--ob-accent); box-shadow: 0 0 0 3px var(--ob-card); transform: translateY(-50%); z-index: 4; }

    /* THE CARD, which is the handle. The week's card, lit the way a card is lit under the
       pointer, and the time on it is the loudest thing because the time is what is being set */
    .hand {
        position: absolute;
        left: 14px;
        right: 0;
        z-index: 5;
        display: flex;
        flex-direction: column;
        gap: 4px;
        padding: 9px 12px 9px 11px;
        border-radius: 14px;
        background: var(--ob-card);
        box-shadow: 0 0 0 1.5px var(--ob-accent), 0 10px 26px rgba(79, 102, 240, .26);
        cursor: grab;
        user-select: none;
        transform: translateY(-50%);
    }
    .hand::before { content: ''; position: absolute; left: -12px; top: 50%; width: 12px; height: 2px; background: var(--ob-accent); transform: translateY(-50%); }
    .hand.held { cursor: grabbing; box-shadow: 0 0 0 1.5px var(--ob-accent), 0 16px 36px rgba(79, 102, 240, .34); }
    .hand .top { display: flex; align-items: center; gap: 8px; }
    .hand .top .ob-circle { width: 16px; height: 16px; font-size: 7px; }
    .hand .tm { display: flex; align-items: baseline; gap: 7px; font-size: 17px; font-weight: 800; letter-spacing: -.01em; color: var(--ob-ink); font-variant-numeric: tabular-nums; line-height: 1.1; }
    .hand .tm .d { font-size: 11.5px; font-weight: 700; color: var(--ob-accent-ink); letter-spacing: 0; }
    .hand .tx { display: block; font-size: 12px; line-height: 1.35; color: var(--ob-ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .hand img { pointer-events: none; }

    /* A MINUTE EARLIER AND A MINUTE LATER (2026-10-01): a solid blue tab coming out of the
       middle of the card's top edge, and one out of its foot - wider than tall, rounded on the
       side away from the card and square where it meets it, so it reads as part of the card's
       blue edge rather than a button laid over it. Each sits just outside the card and the
       card's ring runs into it. At an end of the day, or at the Now line, the one that cannot go
       further fades */
    .nudge {
        position: absolute;
        left: 50%;
        width: 36px;
        height: 16px;
        padding: 0;
        border: 0;
        display: grid;
        place-items: center;
        background: var(--ob-accent);
        color: #fff;
        cursor: pointer;
        transform: translateX(-50%);
    }
    .nudge.up { bottom: calc(100% + 1px); border-radius: 8px 8px 0 0; }
    .nudge.dn { top: calc(100% + 1px); border-radius: 0 0 8px 8px; }
    .nudge svg { width: 12px; height: 12px; pointer-events: none; }
    .nudge:hover { filter: brightness(1.08); }
    .nudge:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
    .nudge.off { opacity: .35; cursor: default; }
}
 /* ============================================
   OKAYBOT MODAL
   The item modal: a call, expanded out of the row that was clicked. The shade darkens the
   page; the card is three quarters of the screen with the row's radius; the header is fixed
   and the body scrolls. popups/item.html is the shell, popups/itemview.html the inside.
   ============================================ */

.ob-popup {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--ob-font);

    .shade {
        position: absolute;
        inset: 0;
        background: var(--ob-shade);
        cursor: pointer;
        animation: ob-shade-in .25s ease-out;
    }

    /* SETTLED: back from under a message or a question opened from it (popups/item.js), drawn
       again where it was - neither the shade nor the card comes in a second time */
    &.settled .shade, &.settled .ob-modal { animation: none; }
}
@keyframes ob-shade-in { from { opacity: 0; } }

/* the card grows from the clicked row: item.js sets --dx --dy --sx --sy from its rectangle */
.ob-modal {
    position: relative;
    width: min(75vw, 1100px);
    height: 75vh;
    display: flex;
    flex-direction: column;
    background: var(--ob-card);
    border-radius: var(--ob-radius-row);
    box-shadow: var(--ob-shadow-modal);
    overflow: hidden;
    transform-origin: 0 0;
    animation: ob-expand .32s cubic-bezier(.2, .8, .2, 1);

    .x {
        position: absolute;
        top: 16px;
        right: 16px;
        z-index: 2;
        width: 32px;
        height: 32px;
        border: 0;
        border-radius: 50%;
        background: var(--ob-canvas);
        color: var(--ob-ink-2);
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .x svg { width: 14px; height: 14px; }
    .x:hover { background: var(--ob-line); color: var(--ob-ink); }

    .slot { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
}
@keyframes ob-expand {
    from { transform: translate(var(--dx, 0px), var(--dy, 24px)) scale(var(--sx, .96), var(--sy, .96)); opacity: .5; }
}

/* ---- inside ----
   The modal's contents are the DETAIL, in its modal arrangement: the switch, the title and the
   picture in the white box on the left, and the call - who, the reason, the facts, what
   happened - down the tinted column on the right. All of it is okaybot-detail.css, because the
   same widget is drawn as one column in Activity's panel; this sheet owns the card it sits in
   and nothing inside it.

   The old .ob-item header and body went with the rewrite (2026-09-11), and the .ob-post /
   .ob-list / .ob-people markup they held went with them: the picture is the stage now. */

/* ---- the size, below large (dev/docs/responsive.md) ----
   On narrow the modal is the only place a call is ever read, so it takes nearly the whole width;
   on a short window, whatever the width, three quarters of 650px is too little to read a thread
   in. ItemPopup.expand_from() works out the same box for its opening animation - change one,
   change the other. (The phone's full-screen rule went with the phone: the console is not drawn
   below 900, okaybot-desktop.css.) */
@media (max-width: 1199px), (max-height: 819px) {
    .ob-modal { height: 90vh; }
}
@media (max-width: 1199px) {
    .ob-modal { width: min(94vw, 1100px); }
}


/* ============================================
   THE PANEL AS A SHEET (narrow, 1199 and below)
   A page that loses its side panel on narrow and still has something in it to reach - an
   account's switch and agents on Profiles, X's prices on Usage, the calendar's picker and Held -
   opens THE SAME ELEMENT over the page, restyled: fixed and centred over a shade, with the item
   modal's radius, shadow and way of arriving. The widget in it is not moved or rebuilt, so it
   keeps its state and nothing is fetched. PanelSheet (shell/panelsheet.js) puts `.sheet` on the
   panel and the shade on the body, and takes both away again - on the ×, a click on the shade,
   Escape, or the window widening back past narrow.

   The close button is the panel's LAST child, so the calendar panel's "* + *" spacing between
   its sections never counts it; it is absolute, in a band the sheet leaves clear at its top.
   ============================================ */
.ob-sheetx { display: none; }

@media (max-width: 1199px) {
    .ob-sheetshade {
        position: fixed;
        inset: 0;
        z-index: 1900;              /* over the sidebar (40), under an item modal opened from inside a sheet (2000) */
        background: var(--ob-shade);
        cursor: pointer;
        animation: ob-shade-in .25s ease-out;
    }

    .ob-panel.sheet,
    .ob-calpanel.sheet {
        position: fixed;
        inset: 0;
        z-index: 1901;
        margin: auto;
        width: min(520px, 92vw);
        height: fit-content;
        max-height: 88vh;
        overflow-y: auto;
        padding: 56px 28px 26px;
        border: 0;
        border-radius: var(--ob-radius-row);
        background: var(--ob-card);
        box-shadow: var(--ob-shadow-modal);
        animation: ob-sheet-in .28s cubic-bezier(.2, .8, .2, 1);
    }
    .ob-calpanel.sheet { width: min(420px, 92vw); }

    .sheet > .ob-sheetx {
        position: absolute;
        top: 14px;
        right: 14px;
        margin: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 32px;
        height: 32px;
        padding: 0;
        border: 0;
        border-radius: 50%;
        background: var(--ob-canvas);
        color: var(--ob-ink-2);
        cursor: pointer;
    }
    .sheet > .ob-sheetx svg { width: 14px; height: 14px; }
    .sheet > .ob-sheetx:hover { background: var(--ob-line); color: var(--ob-ink); }
    .sheet > .ob-sheetx:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
    .ob-panel.sheet:focus, .ob-calpanel.sheet:focus { outline: none; }
}
@keyframes ob-sheet-in {
    from { transform: translateY(14px) scale(.98); opacity: 0; }
}
 /* ============================================
   OKAYBOT - THE LIST PAGE
   Activity and Posts are one page drawn twice (2026-09-23): the title and a menu down the left,
   a bar of faces over the list, and the event in full on the right in Activity's panel
   (.ob-act, .ob-panel - okaybot-activity.css). The menu says WHAT is in the list, the faces say
   WHOSE; they are two widgets in listpage/ that both pages mount, and this is their sheet and
   the grid that holds them. What goes IN the list - log rows, post cards - is each page's own.

   EVERYTHING IS ON THE GREY. The menu is the sidebar's own kind of item - no panel, no card
   around it - so the rows of the list are the one white thing on the left.

   THE LEFT IS HALF THE SCREEN, and two columns in half a screen is narrow, so the menu is a
   narrow column and the list takes what is left.
   ============================================ */

.ob-listcols {
    display: grid;
    grid-template-columns: var(--ob-listmenu-w) minmax(0, 1fr);
    gap: var(--ob-listmenu-gap);
    align-items: start;

    > .side {
        position: sticky;
        top: var(--ob-body-pad-top);
    }

    /* the title stands on the faces' line, so the menu and the list start level */
    > .side .ob-title {
        line-height: 44px;
        padding-left: 4px;
        margin-bottom: 26px;
    }
}

/* ---- the menu: which rows the list is ---- */
.ob-sidemenu {
    display: flex;
    flex-direction: column;
    gap: 2px;

    .item {
        display: flex;
        align-items: center;
        gap: 10px;
        width: 100%;
        height: 38px;
        padding: 0 12px;
        border: 0;
        border-radius: 10px;
        background: transparent;
        font-family: var(--ob-font);
        font-size: 14px;
        font-weight: 600;
        color: var(--ob-ink-2);
        text-align: left;
        cursor: pointer;
    }
    .item:hover { background: var(--ob-line); }
    .item:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 1px; }
    .item.on { background: var(--ob-accent-bg); color: var(--ob-accent-ink); font-weight: 700; }

    /* the colour the calendar gives the same state (okaybot-calendar.css, the picker's dots) */
    .dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; background: var(--ob-accent); }
    .dot.went { background: var(--ob-mint-ink); }
    .dot.wait, .dot.held { background: var(--ob-butter-ink); }
    .dot.gone { background: var(--ob-faint-1); }
    .dot.stop { background: var(--ob-rose-ink); }
    .dot.all { background: var(--ob-ink-2); }

    hr { border: 0; border-top: 1px solid var(--ob-line); margin: 12px; }
}

/* ---- the faces: whose. One lit, by the ring and nothing else. NOT .ob-faces, which is the
   feed's run of overlapping circles (okaybot-feed.css) ----

   ALL AT THE RIGHT, IN TWO RUNS (2026-09-23): the accounts, a 30px gap, the agents. It was
   accounts at the left edge and agents at the right, which put the width of the list between
   two halves of one control. Inside a run the faces all but touch - 2px between the buttons,
   which are a few pixels wider than the pictures - so a run reads as one set.

   THE RING STAYS INSIDE THE GAP: 1px of ground and 2px of ring, so it reaches 3px past its
   button and stops a pixel short of the next picture. It was 2px and 2px on buttons that
   touched, and a lit or hovered agent's ring ran over its neighbour.

   THE SAME ON EVERY PAGE: over the list on Activity and Posts, on the title's row on Usage and
   the calendar (the head's control slot, below), and the calendar draws accounts only. */
.ob-facebar {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 2px;
    height: 44px;
    margin-bottom: 26px;

    .gap { flex: 0 0 26px; }      /* 30 between the runs: this and the 2px gap either side of it */

    .face {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: calc(var(--ob-face-lg) + 4px);
        height: calc(var(--ob-face-lg) + 4px);
        padding: 0;
        border: 0;
        border-radius: 50%;
        background: transparent;
        cursor: pointer;
        transition: box-shadow .12s ease;
    }
    .face .ob-circle, .face .ob-square { width: var(--ob-face-lg); height: var(--ob-face-lg); }

    /* AN AGENT IS A STEP SMALLER THAN AN ACCOUNT (2026-09-23): the accounts are what the posts
       are on, the agents are who sent them, and the two ends of the bar should not read as one
       row of equals. The button shrinks with the face so the ring still sits 2px off it */
    .face.agent { width: calc(var(--ob-face-sm) + 4px); height: calc(var(--ob-face-sm) + 4px); }
    .face.agent .ob-square { width: var(--ob-face-sm); height: var(--ob-face-sm); }
    .face:hover { box-shadow: 0 0 0 1px var(--ob-canvas), 0 0 0 3px var(--ob-accent-ring); }
    .face.on { box-shadow: 0 0 0 1px var(--ob-canvas), 0 0 0 3px var(--ob-accent); }
    .face:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 4px; }
}

/* on a title's row (PageHead's control slot): the row's own height, and nothing under it */
.ob-head .ob-facebar { height: auto; margin: 0; }

/* ---- PAST SIX FACES, ONE BUTTON (2026-09-25) ----
   Seven faces or more, accounts and agents together, and the bar is one white pill: the lit face
   and its name, ringed like a lit face, or All behind a cluster of three faces. It opens a menu
   under its right edge - All, a rule, then the accounts and the agents in two equal columns -
   that scrolls when the workspace has more accounts than the screen has room for.

   EVERYTHING IS SIZED OFF --ob-face-lg, so the pill and the cluster step down with the faces on
   the medium tier. At 34 the pill is 42 high with the first face 4 in, so the face sits in the
   pill's round end; the second face is 24 at 19 across, the third 16 at 21 across and 18 down,
   on top: both lie over the first face's right half. The rings are the ground the face sits
   on, so each face cuts the one under it. */
.ob-facebar.folded {
    position: relative;

    .fold {
        display: inline-flex;
        align-items: center;
        gap: 10px;
        height: calc(var(--ob-face-lg) + 8px);
        padding: 0 12px 0 4px;
        border: 0;
        border-radius: 999px;
        background: var(--ob-card);
        box-shadow: 0 0 0 1px var(--ob-line);
        font-family: var(--ob-font);
        font-size: 14px;
        font-weight: 600;
        color: var(--ob-ink-2);
        white-space: nowrap;
        cursor: pointer;
        transition: box-shadow .12s ease;

        &:hover, &.open { box-shadow: 0 0 0 2px var(--ob-accent-ring); }
        &.on { box-shadow: 0 0 0 2px var(--ob-accent); color: var(--ob-ink); font-weight: 700; }
        &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 3px; }

        .one { width: var(--ob-face-lg); height: var(--ob-face-lg); }
        .words { max-width: 240px; overflow: hidden; text-overflow: ellipsis; }
        .chev { flex: 0 0 auto; width: 16px; height: 16px; color: var(--ob-muted); transition: transform .12s ease; }
        &.open .chev { transform: rotate(180deg); color: var(--ob-accent); }
    }

    .cluster {
        position: relative;
        flex: 0 0 auto;
        width: calc(var(--ob-face-lg) * 1.265);
        height: var(--ob-face-lg);

        > * { position: absolute; box-shadow: 0 0 0 2px var(--ob-card); }
        .c0 { z-index: 1; left: 0; top: 0; width: var(--ob-face-lg); height: var(--ob-face-lg); }
        .c1 { z-index: 2; left: calc(var(--ob-face-lg) * .559); top: 0; width: calc(var(--ob-face-lg) * .706); height: calc(var(--ob-face-lg) * .706); }
        .c2 { z-index: 3; left: calc(var(--ob-face-lg) * .618); top: calc(var(--ob-face-lg) * .53); width: calc(var(--ob-face-lg) * .47); height: calc(var(--ob-face-lg) * .47); }
    }

    /* the menu, hung from the pill's right edge, over the list or the grid below it */
    .fmenu {
        position: absolute;
        top: calc(100% + 8px);
        right: 0;
        z-index: 1000;
        width: 216px;
        padding: 6px;
        background: var(--ob-card);
        border-radius: var(--ob-radius-panel);
        box-shadow: 0 0 0 1px var(--ob-line), var(--ob-shadow-menu);

        &.two { width: 404px; }
        hr { border: 0; border-top: 1px solid var(--ob-line); margin: 6px 8px; }
    }

    .lists {
        display: grid;
        grid-template-columns: minmax(0, 1fr);
        column-gap: 6px;
        align-items: start;
        max-height: min(60vh, 520px);
        overflow-y: auto;
        overscroll-behavior: contain;

        .col { display: flex; flex-direction: column; min-width: 0; }
        .rule { align-self: stretch; margin: 4px 0; background: var(--ob-line); }
    }
    .fmenu.two .lists { grid-template-columns: minmax(0, 1fr) 1px minmax(0, 1fr); }

    .frow {
        display: flex;
        align-items: center;
        gap: 11px;
        width: 100%;
        min-width: 0;
        height: 40px;
        padding: 0 10px 0 6px;
        border: 0;
        border-radius: 10px;
        background: transparent;
        font-family: var(--ob-font);
        text-align: left;
        cursor: pointer;

        > .ob-circle, > .ob-square { width: var(--ob-face-sm); height: var(--ob-face-sm); }
        .nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; font-weight: 600; color: var(--ob-ink); }
        .ck { flex: 0 0 auto; width: 16px; height: 16px; margin-left: auto; color: var(--ob-accent); }

        &:hover { background: var(--ob-canvas); }
        &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: -2px; }
        &.on { background: var(--ob-accent-bg); }
        &.on .nm { color: var(--ob-accent-ink); font-weight: 700; }

        /* All: the pill's cluster again, its rings the row's own ground */
        &.all { height: calc(var(--ob-face-lg) + 10px); padding-left: 5px; }
        &:hover .cluster > * { box-shadow: 0 0 0 2px var(--ob-canvas); }
        &.on .cluster > * { box-shadow: 0 0 0 2px var(--ob-accent-bg); }
    }
}

/* THE MENU STAYS BESIDE THE LIST AT EVERY SIZE (2026-09-24). It went above the list below 900,
   which is below the console's floor now (okaybot-desktop.css), and on narrow the list has the
   whole page so the menu's 148 costs it nothing. See dev/docs/responsive.md. */
 /* ============================================
   OKAYBOT IDENTITY
   The sign-in ceremony (apps/frontend/identity/), the account page and the unlock popup.

   THE CEREMONY IS A FULL-VIEWPORT TAKEOVER on the canvas, under body.ob-site: the sidebar and
   the avatar step aside, #Main runs static, and the page paints two regions - the brand panel
   on the left, the ceremony column on the right. One primary action per screen, and the
   layout should make a second one look wrong. dev/docs/identity.md.
   ============================================ */

/* ---- the shell ---- */
.ob-ceremony {
    --ob-cer-col: 400px;

    display: flex;
    gap: 20px;
    min-height: 100vh;
    padding: 20px;
    box-sizing: border-box;
    font-family: var(--ob-font);
    color: var(--ob-ink);

    /* the brand panel: nothing interactive lives here. The logo, then the picture (the keeper
       and its bots, images/site/keeper.webp) in all the room left under it, never behind it.
       The ground is the picture's own flat blue, so wherever `contain` leaves a band the
       picture has no edge. */
    .brand {
        flex: 0 0 40%;
        max-width: 560px;
        display: flex;
        flex-direction: column;
        align-items: stretch;       /* the shared brand.css centres a .brand */
        gap: 24px;
        padding: 40px;
        border-radius: var(--ob-radius);
        background: var(--ob-site-art-bg);
        box-sizing: border-box;

        .ob-sitelogo { align-self: flex-start; }
    }

    .art {
        flex: 1;
        min-height: 0;
        background: url(/images/site/keeper.webp) center / contain no-repeat;
    }

    /* the stage: the ceremony column, centred */
    .stage {
        flex: 1;
        position: relative;
        display: flex;
        min-width: 0;
    }

    .corner {
        position: absolute;
        top: 24px;
        right: 32px;
        min-height: 20px;
        font-size: 13.5px;
        font-weight: 600;
        white-space: nowrap;
        color: var(--ob-muted);
    }
    .corner .ob-clink { margin-left: 4px; }

    .body {
        margin: auto;
        width: var(--ob-cer-col);
        max-width: 100%;
        display: flex;
        flex-direction: column;
    }

    .foot {
        position: absolute;
        bottom: 20px;
        left: 0;
        right: 0;
        display: flex;
        justify-content: center;
        gap: 22px;
        font-size: 12.5px;
        font-weight: 600;
    }
    .foot a { color: var(--ob-muted); text-decoration: none; }
    .foot a:hover { color: var(--ob-ink); }
}

/* the one link style on these screens */
.ob-clink {
    color: var(--ob-accent-ink);
    font-weight: 700;
    text-decoration: none;
}
.ob-clink:hover { text-decoration: underline; }

/* ---- one step of the ceremony ---- */
.ob-cstep {
    display: flex;
    flex-direction: column;
    width: 100%;

    .title {
        margin: 0;
        font-size: 40px;
        font-weight: 800;
        letter-spacing: -.035em;
        line-height: 1.05;
        color: var(--ob-ink);
    }
    .title.sm { font-size: 30px; }

    .sub {
        margin-top: 12px;
        font-size: 15px;
        font-weight: 500;
        line-height: 1.5;
        color: var(--ob-ink-2);
    }
    .sub strong { color: var(--ob-ink); font-weight: 700; }
    .sub .ob-clink { margin-left: 6px; font-size: 13.5px; }

    .controls {
        margin-top: 30px;
        display: flex;
        flex-direction: column;
        gap: 12px;
    }

    .err {
        min-height: 1.5em;
        line-height: 1.5em;
        font-size: 13px;
        font-weight: 600;
        color: var(--ob-rose-ink);
        text-align: center;
    }
    .err.ok { color: var(--ob-mint-ink); }

    .alt {
        margin-top: 20px;
        text-align: center;
        font-size: 13.5px;
        font-weight: 600;
    }
    .alt.muted { color: var(--ob-muted); }

    .fine {
        margin-top: 22px;
        font-size: 12.5px;
        font-weight: 500;
        line-height: 1.6;
        text-align: center;
        color: var(--ob-muted);
    }
    .fine a { color: var(--ob-ink-2); font-weight: 600; text-decoration: none; }
    .fine a:hover { text-decoration: underline; }

    /* the taller field and button the ceremony uses. 16px, never less: iOS Safari zooms the page
       into any field set smaller when it is tapped (2026-10-06) */
    .ob-input.lg {
        height: 50px;
        padding: 0 18px;
        font-size: 16px;
        border-radius: var(--ob-radius-tile);
    }
    .ob-input.has-error { border-color: var(--ob-rose-ink); box-shadow: 0 0 0 3px var(--ob-rose-bg); }

    /* the help triage and any other list of ways forward */
    .choices {
        margin-top: 24px;
        display: flex;
        flex-direction: column;
        gap: 10px;
    }
    .choice {
        display: flex;
        align-items: center;
        gap: 14px;
        width: 100%;
        padding: 14px 16px;
        border: 1px solid var(--ob-line);
        border-radius: var(--ob-radius-tile);
        background: var(--ob-card);
        text-align: left;
        font-family: inherit;
        cursor: pointer;
        transition: border-color .12s, background .12s;
    }
    .choice:hover { border-color: var(--ob-accent-ring); background: var(--ob-accent-tint); }
    .choice .icon {
        flex: 0 0 auto;
        width: 36px;
        height: 36px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 50%;
        background: var(--ob-accent-bg);
        color: var(--ob-accent-ink);
    }
    .choice .icon svg { width: 18px; height: 18px; }
    .choice .text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
    .choice .t { font-size: 14px; font-weight: 700; color: var(--ob-ink); }
    .choice .n { font-size: 12.5px; font-weight: 500; line-height: 1.45; color: var(--ob-muted); }
    .choice .go { flex: 0 0 auto; color: var(--ob-faint-3); }
    .choice .go svg { width: 16px; height: 16px; }

    /* the passkey nudge */
    .benefits {
        margin-top: 22px;
        display: flex;
        flex-direction: column;
        gap: 10px;
    }
    .benefit {
        display: flex;
        align-items: center;
        gap: 12px;
        font-size: 14px;
        font-weight: 600;
        color: var(--ob-ink-2);
    }
    .benefit .icon {
        flex: 0 0 auto;
        width: 28px;
        height: 28px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 50%;
        background: var(--ob-accent-bg);
        color: var(--ob-accent-ink);
    }
    .benefit .icon svg { width: 14px; height: 14px; }
}

.ob-pill.lg { height: 50px; font-size: 15px; }
.ob-pill.lg svg { width: 20px; height: 20px; }

/* ---- the code field: one real input under a row of drawn digits ---- */
.ob-otp {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    margin-bottom: 4px;

    .real {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        opacity: 0;
        z-index: 1;
        cursor: text;
        border: none;
        background: transparent;
        font-size: 16px;     /* stops iOS zooming the page on focus */
    }

    /* The digits share the row rather than claiming 40px each: eight fixed boxes plus their
       gaps and the separator come to 386, which fits the ceremony page and the dialog at any
       sensible width but not a 360px phone. flex-basis 0 with min-width 0 lets them give way
       instead of pushing the row out of its container; max-width is the size they want. */
    .digit {
        flex: 1 1 0;
        min-width: 0;
        max-width: 40px;
        height: 50px;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 1px solid var(--ob-line);
        border-radius: 12px;
        background: var(--ob-card);
        font-family: var(--ob-font);
        font-size: 22px;
        font-weight: 800;
        color: var(--ob-ink);
        transition: border-color .12s, box-shadow .12s;
        user-select: none;
        pointer-events: none;
    }
    .sep { width: 10px; height: 2px; border-radius: 2px; background: var(--ob-faint-2); }

    .digit.is-active { border-color: var(--ob-accent); box-shadow: 0 0 0 3px var(--ob-accent-bg); }
    .digit.is-active::after {
        content: '';
        display: inline-block;
        width: 2px;
        height: 22px;
        border-radius: 2px;
        background: var(--ob-accent);
        animation: ob-otp-blink 1s step-end infinite;
    }
    .digit.has-value { border-color: var(--ob-accent-ring); }
    .digit.has-value::after { display: none; }

    &.has-error .digit { border-color: var(--ob-rose-ink); background: var(--ob-rose-card); }
    &.is-disabled .digit { background: var(--ob-canvas); color: var(--ob-muted); }
}

/* the status line under a code field (the unlock popup's "we've sent a code") */
.ob-otp ~ .status,
/* The two identity popups - elevation, and changing the address - are one shape inside the
   dialog chrome (okaybot-dialog.css): a code field or a single input, centred messages under
   it, and any second way out below. Everything here is the contents; the card, the header and
   the x are the dialog sheet's. */
.ob-unlock,
.ob-changemail {
    .status {
        margin: 0 0 14px;
        min-height: 1.5em;
        text-align: center;
        font-size: 13.5px;
        font-weight: 600;
        line-height: 1.5;
        color: var(--ob-ink-2);
    }

    /* the passkey view's line is empty until a passkey fails */
    [widget-dom="passkey_status"]:empty { display: none; }

    .err {
        min-height: 1.5em;
        line-height: 1.5em;
        font-size: 13px;
        font-weight: 600;
        color: var(--ob-rose-ink);
        text-align: center;
    }
    .err.ok { color: var(--ob-mint-ink); }
}

/* the address step: the ceremony's tall field and button, inside a dialog */
.ob-changemail {
    .controls { display: flex; flex-direction: column; gap: 12px; }
    .ob-input.lg { height: 50px; padding: 0 18px; font-size: 16px; border-radius: var(--ob-radius-tile); }
}

/* ---- choosing the zone every screen draws its times in ----

   A searchable list inside the dialog sheet. The clock on the right is drawn in each row's
   own zone, so the list reads as a set of live clocks rather than a set of names - which is
   what makes it possible to pick one without knowing the catalogue. Fixed height so the
   sheet does not resize as the search narrows it. */
.ob-tzpick {
    .ob-input.lg { height: 50px; padding: 0 18px; font-size: 16px; border-radius: var(--ob-radius-tile); }

    .tzwrap { display: contents; }

    .tzlist {
        margin-top: 12px;
        height: 46vh;
        min-height: 240px;
        overflow-y: auto;
        display: flex;
        flex-direction: column;
        gap: 2px;
    }

    .tzrow {
        display: flex;
        align-items: center;
        gap: 12px;
        width: 100%;
        padding: 10px 12px;
        border: 0;
        border-radius: var(--ob-radius-tile);
        background: transparent;
        text-align: left;
        cursor: pointer;
        font-family: var(--ob-font);
    }
    .tzrow:hover { background: var(--ob-canvas); }
    .tzrow.on { background: var(--ob-accent-bg); }

    .tzrow .nm { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; font-weight: 700; color: var(--ob-ink); }
    .tzrow .hm { font-size: 13.5px; font-weight: 600; color: var(--ob-muted); font-variant-numeric: tabular-nums; }
    .tzrow.on .nm,
    .tzrow.on .hm { color: var(--ob-accent-ink); }
}

@keyframes ob-otp-blink {
    0%, 100% { opacity: 1; }
    50% { opacity: 0; }
}

/* ---- who is already signed in, above the step while adding an account ---- */
.ob-cstrip {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 44px;
    font-size: 13.5px;
    font-weight: 600;
    line-height: 20px;
    color: var(--ob-ink-2);

    .faces { display: flex; flex: none; }
    .faces > * + * { margin-left: -9px; }
    .faces img { width: 100%; height: 100%; object-fit: cover; }
    /* margin and size reset: forms.css styles every .label as a field label */
    .label { margin: 0; font-size: inherit; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    /* the way back sits at the far end of the row, the same size and line as the label */
    .ob-clink { margin-left: auto; flex: none; font-size: inherit; font-weight: 600; line-height: inherit; white-space: nowrap; }
}

/* ---- the account page, band 1: the faces ----

   THE ACCOUNTS ARE NOT A CARD ANY MORE (2026-09-18). They were three rows in a box - one per
   account, then Add another account - which is 210px of card to say a thing two faces say. The
   row under the title is the whole of signing in and out now: the account you are in, lit; the
   ones you can switch to, at a size down; the + that adds another; and Log out at the far end,
   which belongs here rather than in the card below because it ENDS one of these faces.

   That is also what lets both of the top two bands go without a heading. The faces are
   obviously accounts and the card under them is obviously the one you are in, so a word over
   either would be a word saying what the picture already says.

   IT SITS ON THE CANVAS, NOT ON A CARD, so the lit ring's inner gap is the canvas colour - the
   .lit on a card's white is the sidebar's avatar and the ceremony's. */
/* THE ACCOUNTS ON THIS BROWSER, as a bar: the one you are in, the ones you can switch to, the
   + that adds another, and Log out at the far end. It was .ob-faces until 2026-09-20 and so is
   the feed's run of overlapping faces (okaybot-feed.css) - a different thing, inline-flex, and
   loaded after this file, so it won and this bar shrank to its contents. */
.ob-acctbar {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 22px;

    .face {
        flex: 0 0 auto;
        display: inline-flex;
        padding: 0;
        border: 0;
        background: none;
        font-family: inherit;
    }
    .face.other { cursor: pointer; }

    /* the ones you are not in, plainly quieter - and back to full strength under the pointer,
       which is the whole of the affordance: a face that lifts is a face you can go to */
    .face.other .ob-circle { opacity: .45; transition: opacity .12s; }
    .face.other:hover .ob-circle { opacity: 1; }
    .face:focus-visible { outline: none; }
    .face.other:focus-visible .ob-circle { opacity: 1; box-shadow: 0 0 0 3px var(--ob-canvas), 0 0 0 5px var(--ob-accent); }

    /* the one you are in, wearing the ring the sidebar's avatar wears, on the canvas */
    .ob-circle.lit { box-shadow: 0 0 0 3px var(--ob-canvas), 0 0 0 5px var(--ob-accent); }

    /* add another: a dashed outline rather than a filled circle, because it is not a face */
    .add {
        flex: 0 0 auto;
        width: 44px;
        height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        border: 1.5px dashed var(--ob-faint-3);
        border-radius: 50%;
        background: none;
        color: var(--ob-muted);
        cursor: pointer;
        transition: border-color .12s, color .12s, background .12s;
    }
    .add svg { width: 18px; height: 18px; }
    .add:hover { border-color: var(--ob-accent-ring); background: var(--ob-accent-bg); color: var(--ob-accent-ink); }
    .add:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }

    .grow { flex: 1; }
}

/* ---- band 2: what a person sets about themselves ----

   The photo, the name and the address the codes go to. Nothing in this card ends a session or
   starts one - that is band 1's job, and keeping the two apart is what makes both of them
   legible without a heading. It is also the card that grows: a handle, a timezone, whatever
   else arrives is another .field in the stack and nothing above it has to move. */
.ob-profile {
    display: flex;
    flex-direction: column;
    gap: 20px;

    .photo { display: flex; align-items: center; gap: 14px; }

    /* the face, and the camera on its corner. The badge is a real button and so is the pill
       beside it: the badge is for somebody who goes straight for the picture, the pill for
       somebody reading the card. Two doors, one action. */
    .face { position: relative; flex: 0 0 auto; display: inline-flex; }
    .face .cam {
        position: absolute;
        right: -2px;
        bottom: -2px;
        width: 24px;
        height: 24px;
        padding: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 1px solid var(--ob-line);
        border-radius: 50%;
        background: var(--ob-card);
        color: var(--ob-ink-2);
        cursor: pointer;
        transition: background .12s, color .12s, border-color .12s;
    }
    .face .cam svg { width: 13px; height: 13px; }
    .face .cam:hover { background: var(--ob-accent-bg); border-color: var(--ob-accent-bg); color: var(--ob-accent-ink); }
    .face .cam:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }

    .field { display: flex; flex-direction: column; }
    .field .ob-lbl { margin-bottom: 8px; }

    /* A FIELD WHOSE VALUE IS BARE TEXT, not a box. The 8px under a label is measured to the
       top of the input's border, which is a line the eye lands on; a row with no box has no
       such line, and its text is pushed a further 7px down by the row centring itself on the
       button beside it. So the label sits straight on the row and the gap reads as the one
       over the name field. */
    .field.plain .ob-lbl { margin-bottom: 0; }

    .emailrow { display: flex; align-items: center; gap: 12px; min-height: var(--ob-pill-h); }
    .emailrow .addr { font-size: 14.5px; font-weight: 700; color: var(--ob-ink); overflow: hidden; text-overflow: ellipsis; }
    .emailrow .grow { flex: 1; }

    /* THE CLOCK BESIDE THE ZONE'S NAME - the thing that tells you it is the one you meant. It is
       the house chip and not bare text: loose beside the name it read as part of it, half a
       second name rather than a reading taken now. Tabular figures so a clock that ticks over
       does not change width under the word. */
    .emailrow .zonenow { font-variant-numeric: tabular-nums; }
}

/* ---- the name, and the Save that arrives when you type ----

   NO SAVE BUTTON UNTIL THERE IS SOMETHING TO SAVE, and no Cancel ever: leaving the page
   without saving is the cancel, and what comes back is the name that was stored. The field
   makes room for the button rather than the card growing a row, so a person typing a name
   never sees anything below it move.

   The width is what animates, which is why the button carries a fixed one here instead of the
   pill's own inline size - auto does not transition. The dirty class is put on this row by hand
   in account.js: routing it through state would re-render the field on every keystroke and
   take the caret with it. */
.ob-profile .namerow {
    display: flex;
    align-items: center;

    .ob-input { flex: 1 1 auto; min-width: 0; }

    .save {
        flex: 0 0 auto;
        width: 0;
        margin-left: 0;
        padding: 0;
        overflow: hidden;
        opacity: 0;
        pointer-events: none;
        transition: width .16s ease, margin-left .16s ease, opacity .12s ease;
    }
}
.ob-profile .namerow.dirty .save {
    width: 84px;
    margin-left: 10px;
    opacity: 1;
    pointer-events: auto;
}

/* ---- the foot: the two ways out that are not about this browser's current account ----

   Both are links rather than buttons, and that is the point: neither is something a person
   came to this page to do. Delete account is the only red thing on the screen until it is
   clicked. */
.ob-acctfoot {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-top: 26px;
    min-height: 20px;

    .grow { flex: 1; }

    .lnk {
        padding: 0;
        border: 0;
        background: none;
        font-family: inherit;
        font-size: 12.5px;
        font-weight: 700;
        color: var(--ob-muted);
        cursor: pointer;
    }
    .lnk:hover { color: var(--ob-ink); }
    .lnk.rose { color: var(--ob-rose-ink); }
    .lnk.rose:hover { color: #971616; }
    .lnk:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 3px; border-radius: 4px; }
}

/* ---- the danger card ----

   ASKED IN PLACE, NEVER IN A MODAL - the .ob-ask rule (okaybot-atoms.css), at the size this
   question deserves. The link at the foot opens this where the link was, so the answer sits
   under everything it is about and the page behind it is still readable. Log out of all does
   not move when it opens, because the row it is on stays exactly where it was.

   IT IS THE ONLY EXPLAINING CARD IN THE PRODUCT. Every other screen says one well-worded label
   and stops; this one lists what happens, because it is the one click that cannot be taken back
   and a person is entitled to know what they are about to lose. What it may truthfully say is
   dev/docs/deletion.md, and the lines here are the ones the code actually does today.

   .ob-card.ob-zone, BOTH CLASSES: okaybot-atoms.css loads after this sheet (the sheets go in
   reverse alphabetical order), so a single class here loses its ground and its edge to
   .ob-card at equal specificity. Anything in this file that re-colours a card needs both. */
.ob-card.ob-zone {
    margin-top: 16px;
    background: var(--ob-rose-card);
    border-color: var(--ob-rose-edge);

    h2 {
        margin: 0 0 12px;
        font-size: 16px;
        font-weight: 800;
        letter-spacing: -.01em;
        color: var(--ob-rose-ink);
    }

    ul { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 8px; }
    li { display: flex; gap: 10px; font-size: 13.5px; font-weight: 600; line-height: 1.45; color: var(--ob-ink-2); }
    li .dot { flex: 0 0 auto; width: 5px; height: 5px; border-radius: 50%; background: var(--ob-rose-chip); margin-top: 7px; }

    .acts { display: flex; align-items: center; gap: 10px; margin-top: 18px; }
}

/* the lit ring as the rest of the app wears it: on a card's white, not on the canvas */
.ob-circle.lit { box-shadow: 0 0 0 2px var(--ob-card), 0 0 0 3px var(--ob-accent); }

.ob-passkeys {
    padding: 8px 22px 16px;

    .row {
        display: flex;
        align-items: center;
        gap: 14px;
        padding: 14px 0;
        border-bottom: 1px solid var(--ob-line-in);
    }
    .row .icon {
        flex: 0 0 auto;
        width: 36px;
        height: 36px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 50%;
        background: var(--ob-accent-bg);
        color: var(--ob-accent-ink);
    }
    .row .icon svg { width: 18px; height: 18px; }
    .row .text { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
    .row .t { font-size: 14.5px; font-weight: 700; color: var(--ob-ink); }
    .row .n { font-size: 12.5px; font-weight: 500; color: var(--ob-muted); }
    .grow { flex: 1; }

    /* And the add row, the accounts card's twin: the icon column is the passkey rows' 36px
       rather than the 44px of a face, so the words line up with the devices above it. It
       clears its own bottom rule rather than leaning on :last-child, because the card's real
       last child is the message under it. */
    .row.add {
        border-bottom: 0;
        color: var(--ob-ink-2);
        font-size: 14px;
        font-weight: 700;
        cursor: pointer;
    }
    .row.add .icon { background: var(--ob-canvas); color: var(--ob-ink-2); }
    .row.add:hover .icon { background: var(--ob-accent-bg); color: var(--ob-accent-ink); }
}

/* ============================================================
   THE ACCOUNT PAGE'S SPLIT, AND THE TWO LISTS IN ITS PANEL (2026-09-17)

   TWO KINDS OF THING, TWO GROUNDS. The left is what this browser HOLDS and how to end it -
   the accounts signed in here, the passkeys on the current one, the way to delete it. Five
   things you do once. The right is what has HAPPENED to the account - the browsers it is
   signed in on, and every security event it has - which is a hundred lines you run your eye
   down. The page was one column of cards and the record would have been a fifth card: a long
   list of small rows inside a rounded box that does nothing for it.

   So the right half is the Activity page's panel (okaybot-activity.css owns .ob-panel, and
   this is the second page to use it): white, flush to the top, the right edge and the foot of
   the viewport, its own scroll, a hairline down its left. The settings keep the canvas.

   The split is even, not Activity's 40/60: neither half of this page is the subject. */
.ob-acct {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, var(--ob-acct-panel));
    min-height: 100vh;
    font-family: var(--ob-font);
    color: var(--ob-ink);
}

.ob-acct .left {
    min-width: 0;
    padding: var(--ob-body-pad-top) var(--ob-body-pad-right) 60px var(--ob-body-pad-left);
}

/* the cards down the left column space themselves, as they do on a cards page */
.ob-acct .left > .ob-card,
.ob-acct .left > [widget] > .ob-card { margin-bottom: var(--ob-card-gap); }

/* ---- a section inside the panel ----
   The detail panel's hairline between its parts (okaybot-detail.css's .ob-dsect), because the
   two lists in here are one column rather than two cards that lost their borders. */
.ob-psect {
    margin-top: 30px;
    padding-top: 18px;
    border-top: 1px solid var(--ob-line-in);
}
.ob-psect.first { margin-top: 0; padding-top: 0; border-top: 0; }

/* ---- the browsers this account is signed in on ----

   THE PASSKEY ROW, EXACTLY: a 36px icon, two lines, a ghost pill at the right edge. A browser
   and a passkey are the same kind of thing on this page - a credential this account handed out
   and can take back - so drawing them differently would be saying something untrue.

   The rows carry no card here, because the panel is the card. */
.ob-devices {
    .row {
        display: flex;
        align-items: center;
        gap: 14px;
        padding: 14px 0;
        border-bottom: 1px solid var(--ob-line-in);
    }
    /* of-type, not last-child: the hidden error note is the last child */
    .row:last-of-type { border-bottom: 0; }

    .icon {
        flex: 0 0 auto;
        width: 36px;
        height: 36px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 50%;
        background: var(--ob-canvas);
        color: var(--ob-ink-2);
    }
    .icon svg { width: 18px; height: 18px; }

    /* the one you are reading this on, wearing the accent the current account's face wears */
    .row.here .icon { background: var(--ob-accent-bg); color: var(--ob-accent-ink); }

    .text { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
    .t { font-size: 14.5px; font-weight: 700; color: var(--ob-ink); }
    .n { font-size: 12.5px; font-weight: 500; color: var(--ob-muted); }
    .n .here { font-weight: 700; color: var(--ob-accent-ink); }
    .grow { flex: 1; }
}

/* ---- one line of the history ----

   THE LOG ROW FROM HOME AND ACTIVITY (.ob-logrow, okaybot-home.css) with the money column
   given to the browser: the quiet fact you read off one row when you want it, never down the
   column. The time column is wider than the log's 34 because this one prints HH:mm rather
   than a relative hour.

   COLOUR, ON A LOG, DELIBERATELY. Nothing else in this product tints a log line. This page
   exists so somebody can find the sign-in that was not theirs, and a column of identical grey
   ticks is the one drawing that would hide it. Four inks and no more: mint got in, accent
   changed a credential, rose was refused or destroyed something, grey is everything else. */
.ob-hrow {
    display: grid;
    grid-template-columns: 22px minmax(0, 1fr) auto 44px;
    gap: var(--ob-log-gap);
    align-items: center;
    min-height: var(--ob-log-h);
    padding: 0 8px;
    margin: 0 -8px;
    border-radius: 10px;
    color: var(--ob-ink);

    .ico {
        width: 22px;
        height: 22px;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        background: var(--ob-canvas);
        color: var(--ob-ink-2);
    }
    .ico svg { width: 12px; height: 12px; }
    .ico.ok { background: var(--ob-mint-bg); color: var(--ob-mint-ink); }
    .ico.bad { background: var(--ob-rose-bg); color: var(--ob-rose-ink); }
    .ico.key { background: var(--ob-accent-bg); color: var(--ob-accent-ink); }

    .act {
        min-width: 0;
        font-size: 13.5px;
        font-weight: 700;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .dev { font-size: 12px; font-weight: 500; color: var(--ob-faint-1); white-space: nowrap; text-align: right; }
    .when { font-size: 12px; font-weight: 600; color: var(--ob-faint-1); text-align: right; font-variant-numeric: tabular-nums; }

    &.fail .act { color: var(--ob-rose-ink); }
}

/* the way to the page under this one. A button, not an observer: the history is something you
   visit rather than work in, and Activity's sentinel is the only one of those on this site. */
.ob-history .more {
    display: flex;
    justify-content: center;
    margin-top: 12px;
}

/* the heading is the break between devices and history; no hairline on top of it */
.ob-psect.ob-history { padding-top: 0; border-top: 0; }

/* the day, a label over the time column rather than a divider across the list */
.ob-hday {
    margin: 14px 0 2px;
    text-align: right;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ob-faint-1);
}
.ob-secthead + .ob-hday { margin-top: 0; }

/* ---- the fold ---- */

/* NARROW: one column, the settings first: the record is the thing you scroll to. The panel
   gives up its edges and becomes the bottom of the page. The one panel on narrow that STACKS
   rather than going: it is this page's content, not a detail of something picked, and a list
   you scroll to is better than a list you open (dev/docs/responsive.md). */
@media (max-width: 1199px) {
    .ob-acct { grid-template-columns: minmax(0, 1fr); }
    .ob-acct .ob-panel {
        position: static;
        height: auto;
        overflow: visible;
        border-left: 0;
        border-top: 1px solid var(--ob-line);
        padding: 24px var(--ob-body-pad-right) 40px var(--ob-body-pad-left);
    }
}

@media (max-width: 900px) {
    .ob-ceremony {
        padding: 0;
        gap: 0;
    }

    /* the panel is decoration; on a phone the ceremony is the whole job */
    .ob-ceremony .brand { display: none; }
    .ob-ceremony .stage { padding: 64px 20px; }
    .ob-ceremony .corner { top: 20px; right: 20px; }
    .ob-ceremony .body { width: 100%; }
    .ob-cstep .title { font-size: 32px; }
    .ob-otp .digit { max-width: 34px; height: 44px; font-size: 19px; }
}

/* ---- THE AGENT SIGN-IN (oauth/authorize, dev/docs/agent-connect.md) ----
   A host asking to act as one of this person's agents, in the ceremony's own shell. Under the
   title one card says who meets whom, then the agents as a list of choices with the picked one
   ringed, the name field when there is something to name, and Connect over Cancel. */
.ob-grantstep {
    /* WHO MEETS WHOM (Dave, 2026-10-06): the host on top - its mark, its name and where it is -
       an arrow down, and the person signed in. A site we do not know is the top row in the
       waiting butter, inset in the card */
    .ob-meets {
        display: flex;
        flex-direction: column;
        margin-top: 22px;
        padding: 4px 16px;
        border: 1px solid var(--ob-line);
        border-radius: var(--ob-radius);
        background: var(--ob-card);

        .side {
            display: flex;
            align-items: center;
            gap: 12px;
            padding: 12px 0;
        }
        .ob-square, .ob-circle { width: 36px; height: 36px; }
        .ob-square img { width: 20px; height: 20px; object-fit: contain; }
        .ob-square svg { width: 18px; height: 18px; }
        .text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
        .t { font-size: 14.5px; font-weight: 700; color: var(--ob-ink); overflow-wrap: anywhere; }
        .n { font-size: 12.5px; font-weight: 600; color: var(--ob-ink-2); overflow-wrap: anywhere; }

        .side.unknown {
            margin: 6px -10px;
            padding: 12px 10px;
            border-radius: 12px;
            background: var(--ob-butter-bg);
        }
        .side.unknown .ob-square { background: var(--ob-butter-edge); color: var(--ob-butter-ink); }
        .side.unknown .n { font-weight: 700; color: var(--ob-butter-ink); }

        /* the arrow under the host's mark, and a rule out to the edge */
        .to {
            display: flex;
            align-items: center;
            gap: 12px;
            color: var(--ob-muted);

            svg { flex: none; width: 36px; height: 14px; }
            &::after { content: ""; flex: 1; height: 1px; background: var(--ob-line-in); }
        }
    }

    .ob-warnacts { margin-top: 28px; }

    > .ob-lbl { margin: 26px 0 0; }
    .choices { margin-top: 10px; gap: 8px; }
    .choice { padding: 11px 14px; gap: 12px; }
    .choice .dot {
        flex: 0 0 auto;
        width: 16px;
        height: 16px;
        border-radius: 50%;
        border: 2px solid var(--ob-line);
        background: var(--ob-card);
    }
    .choice.on { border-color: var(--ob-accent-ring); background: var(--ob-accent-tint); box-shadow: 0 0 0 3px var(--ob-accent-bg); }
    .choice.on .dot { border: 5px solid var(--ob-accent); }

    .ob-namefield { display: flex; flex-direction: column; gap: 8px; margin-top: 22px; }
    .ob-namefield .ob-lbl { margin: 0; }

    .err { margin-top: 10px; }

    .ob-connectacts { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; }

    .fine code {
        font-family: var(--ob-mono);
        font-size: 12px;
        padding: 2px 7px;
        border-radius: 6px;
        background: var(--ob-canvas);
        color: var(--ob-ink-2);
    }
}
.ob-cstep .ob-connectcancel { margin-top: 26px; }
 /* ============================================
   OKAYBOT HOME
   The home page and only the home page: two columns, no rail. The decisions on the left at
   60 percent, the log on the right at 40, and one shared top line across both so the account
   avatar has a row to sit on.

   The two things it draws are not shared with any other screen, and that is deliberate:

   THE DECISION CARD is one quiet line of machine over a picture. The feed hangs the agent's
   square out in a 50px column with an arrow into the card, which reads well across a full-width
   body and costs 66px a 60 percent column has not got - so the square goes inline in the header,
   next to the name it belongs to, and the header is drawn at the size of a caption because the
   thing being answered is the picture under it. The picture itself is the stage
   (css/okaybot-stage.css), which this sheet does not style and must not: a card decides how much
   air goes round a stage, never what is in one.

   THE LOG ROW is the slim feed row with the card taken off it. No background, no shadow, no
   radius - a run of facts down the page, with the money and the time in fixed columns so they
   line up whatever the line above said. It is meant to be watched while the left column is
   being answered, so nothing in it is the size of a target.

   Both take their colours, their ink ramp and their identity shapes from the atoms; nothing
   here carries a literal colour, size or radius.
   ============================================ */

/* ---- the two columns ----
   THE LOG IS A PANEL NOW (2026-09-18). It was a second column on the same canvas with a 34px
   gap beside it, which is a column that has to be told apart from the one next to it by the
   space between them. It is the Activity page's panel instead - okaybot-activity.css owns
   .ob-panel and this is the third page to use it - so the log sits on the sidebar's white,
   flush to the top, the right edge and the foot of the viewport, with a hairline down its
   left and its own scroll. The split is the one it already had: the decisions at 60, the log
   at 40. What this page loses is the gap, and the log gains it as width.

   The page's padding moved onto the left column with it. A grid that holds a panel cannot
   carry its own padding - the panel has to reach the edge - so each half pads itself, exactly
   as Activity and the account page do. */
.ob-home {
    display: grid;
    grid-template-columns: minmax(0, var(--ob-home-split)) minmax(0, var(--ob-home-panel));
    min-height: 100vh;
    font-family: var(--ob-font);
    color: var(--ob-ink);
}

.ob-hcol { min-width: 0; }

/* One shared top line. There is no page title above these: a screen with two columns that
   each name themselves does not need a third word saying it is the home page, and the 40px
   that would cost is 40px of the first card. */
.ob-colhead {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: var(--ob-head-h);
    margin-bottom: var(--ob-head-gap);

    .t { font-size: 20px; font-weight: 800; letter-spacing: -.02em; color: var(--ob-ink); }
    .grow { flex: 1 1 auto; }
    .lnk { font-size: 12.5px; font-weight: 700; color: var(--ob-muted); text-decoration: none; }
    .lnk:hover { color: var(--ob-accent-ink); text-decoration: none; }

    /* the Live line sits inline here rather than in a rail card, so it loses the card's margin */
    .ob-live { margin-bottom: 0; font-size: 13px; }
}

/* the right column's head used to stop short of this corner to clear the fixed account avatar.
   The avatar is on the sidebar's foot since 2026-09-16, so the head runs the column's width. */

/* ============================================================
   THE LEFT COLUMN: the decisions
   ============================================================ */
/* The page's own padding, because the grid cannot carry it any more - see the split above.
   The gutter that used to sit inside this column went with the gap: the card is against the
   panel's hairline on one side now, which is an edge rather than a neighbour, and a timeline
   that has an edge to sit against does not need air to be told apart from one. */
.ob-hcol.left {
    padding: var(--ob-body-pad-top) var(--ob-body-pad-right) 60px var(--ob-body-pad-left);
}

.ob-queue {
    .cards { display: flex; flex-direction: column; gap: var(--ob-home-card-gap); }
    .ob-empty { padding: 24px 0; }
}

/* ---- THINGS TO TRY (2026-10-04, Dave): five prompts under an empty queue while the workspace
   is new ----
   Option K of the "Home: things to try" canvas (2026-10-05): one blue box per prompt, and one level
   of box - the words, and down the right Copy over the kind of ask in italic, with setup's stars
   twinkling beside Copy. No title of its own: the column's heading says "Try saying this to your
   agent" over it (home/queuehead.html). It was option A, one blue box of glass rows, for a day.
   Whether it is drawn is the server's (code/Setup/TryAsking.cs); home/tryasking.js.

   THE QUEUE'S EMPTY LINE STEPS ASIDE FOR IT. Both are drawn only when nothing is waiting, and the
   boxes say what the line says and what to do about it. The line is the queue's own and the queue
   is not rebuilt for this (columns.js), so the column hides it while the boxes are there. */
.ob-hcol.left:has(.ob-tryask) .ob-queue > .ob-empty { display: none; }

.ob-tryask {
    container-type: inline-size;
    display: flex;
    flex-direction: column;
    gap: 12px;

    /* the box is the button: a click anywhere in the blue copies its prompt (tryasking.html) */
    .box {
        position: relative;
        overflow: hidden;
        display: flex;
        align-items: stretch;
        gap: 12px;
        width: 100%;
        padding: 12px 12px 12px 22px;
        border: 0;
        border-radius: var(--ob-radius-row);
        background: var(--ob-accent);
        color: var(--ob-on-accent);
        font-family: var(--ob-font);
        text-align: left;
        cursor: pointer;
    }
    .box:hover { background: var(--ob-accent-lift); }
    .box:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 3px; }
    .t {
        position: relative;
        z-index: 1;
        flex: 1;
        min-width: 0;
        align-self: center;
        padding: 4px 12px 4px 0;
        font-size: 15.5px;
        font-weight: 600;
        line-height: 1.5;
    }

    /* down the right: Copy at the top, the kind of ask in the bottom corner, and the stars in the
       space left of Copy - which is why the column is wider than the label needs */
    .end {
        position: relative;
        z-index: 1;
        flex: none;
        display: flex;
        flex-direction: column;
        align-items: flex-end;
        justify-content: space-between;
        gap: 6px;
        min-width: 150px;
    }
    .lb { padding-right: 8px; font-size: 12.5px; font-style: italic; font-weight: 500; white-space: nowrap; }

    /* a copy flashes "Copied to clipboard" into the corner for a moment, then the kind of ask
       fades back (tryasking.js, copy) */
    .lb .was, .lb .now { display: inline-block; }
    .lb .now { display: none; font-weight: 700; }
    .box.done .lb .was { display: none; }
    .box.done .lb .now { display: inline-block; animation: ob-tryask-copied .45s ease-out; }
    .box.back:not(.done) .lb .was { animation: ob-tryask-back .3s ease-out; }

    /* the Copy mark: a picture of what the box does, lit while the box is hovered and a tick once
       it has copied */
    .copy {
        display: grid;
        place-items: center;
        width: 32px;
        height: 32px;
        border-radius: 9px;
        opacity: .85;

        svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
        .i-done { display: none; stroke-width: 2.2; }
    }
    .box:hover .copy { background: var(--ob-glass); opacity: 1; }
    .box.done .copy { opacity: 1; }
    .box.done .i-copy { display: none; }
    .box.done .i-done { display: block; }

    /* two stars to a box, each pair placed differently so the five do not read as a pattern; the
       twinkle is setup's plan box's (ob-setup-twinkle, okaybot-setup.css) */
    .star {
        position: absolute;
        color: var(--ob-on-accent);
        animation: ob-setup-twinkle 2.6s ease-in-out infinite;

        svg { display: block; width: 100%; height: 100%; fill: currentColor; }
    }
    .star.s1  { right: 62px;  top: 8px;  width: 16px; height: 16px; }
    .star.s2  { right: 96px;  top: 20px; width: 10px; height: 10px; animation-delay: .9s; }
    .star.s3  { right: 118px; top: 6px;  width: 13px; height: 13px; animation-delay: .4s; }
    .star.s4  { right: 74px;  top: 26px; width: 9px;  height: 9px;  animation-delay: 1.6s; }
    .star.s5  { right: 60px;  top: 10px; width: 18px; height: 18px; animation-delay: 1.1s; }
    .star.s6  { right: 100px; top: 8px;  width: 11px; height: 11px; animation-delay: 2s; }
    .star.s7  { right: 88px;  top: 22px; width: 14px; height: 14px; animation-delay: .2s; }
    .star.s8  { right: 124px; top: 10px; width: 9px;  height: 9px;  animation-delay: 1.3s; }
    .star.s9  { right: 66px;  top: 6px;  width: 12px; height: 12px; animation-delay: .7s; }
    .star.s10 { right: 106px; top: 18px; width: 15px; height: 15px; animation-delay: 1.8s; }
}
@keyframes ob-tryask-copied {
    0%   { opacity: 0; transform: translateY(6px) scale(.92); }
    55%  { opacity: 1; transform: translateY(0) scale(1.08); }
    100% { opacity: 1; transform: none; }
}
@keyframes ob-tryask-back {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .ob-tryask .star { animation: none; opacity: .6; }
    .ob-tryask .box.done .lb .now,
    .ob-tryask .box.back:not(.done) .lb .was { animation: none; }
}

/* too narrow for a column down the right (a phone): Copy stays beside the words and the kind of
   ask drops under them, still in the corner */
@container (max-width: 519px) {
    .ob-tryask .box { display: grid; grid-template-columns: minmax(0, 1fr) auto; }
    .ob-tryask .end { display: contents; }
    .ob-tryask .copy { align-self: start; }
    .ob-tryask .lb { grid-column: 1 / -1; justify-self: end; }
}

/* ---- A PROFILE WHOSE LOGIN STOPPED WORKING (2026-10-05, Dave): a blue box each, at the top of the
   column ----
   Option C of the "Reconnect notice on Home" canvas: the things to try's blue and stars, the face
   ringed in white, one calm line, and a white Reconnect. Never red: a login running out is ordinary
   and nothing is lost. home/reconnect.js; drawn off Workspaces.Home's `reconnect`.

   NO PLAN RUNNING (2026-10-05, Dave) is the same box with Start a plan in place of Reconnect, and no
   face: home/noplan.js, drawn off Workspaces.Home's `no_plan`. The queue's empty line steps aside for
   it, as it does for the things to try. */
.ob-hcol.left:has(.ob-noplan) .ob-queue > .ob-empty { display: none; }

.ob-reconnect, .ob-noplan {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 0 0 var(--ob-home-card-gap);

    .box {
        --ob-badge-ring: var(--ob-on-accent);
        position: relative;
        overflow: hidden;
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 12px 16px;
        padding: 18px 18px 18px 22px;
        border-radius: var(--ob-radius-row);
        background: var(--ob-accent);
        color: var(--ob-on-accent);
        font-family: var(--ob-font);
    }
    .ob-circle { box-shadow: 0 0 0 2px var(--ob-on-accent); }
    .words {
        position: relative;
        z-index: 1;
        flex: 1 1 260px;
        min-width: 0;
        display: flex;
        flex-direction: column;
        gap: 3px;
    }
    b { font-size: 16px; font-weight: 800; letter-spacing: -.01em; }
    .why { font-size: 14px; font-weight: 600; line-height: 1.5; opacity: .9; }
    .ob-pill {
        position: relative;
        z-index: 1;
        flex: none;
        background: var(--ob-on-accent);
        color: var(--ob-accent-ink);
        font-weight: 800;
        box-shadow: var(--ob-shadow-on-accent);
    }
    .ob-pill:hover { background: var(--ob-accent-bg); }

    /* the things to try's twinkle, two stars beside the first box's words (ob-setup-twinkle) */
    .star {
        position: absolute;
        color: var(--ob-on-accent);
        animation: ob-setup-twinkle 2.6s ease-in-out infinite;

        svg { display: block; width: 100%; height: 100%; fill: currentColor; }
    }
    .star.s1 { right: 150px; top: 12px; width: 14px; height: 14px; }
    .star.s2 { right: 132px; bottom: 14px; width: 9px; height: 9px; animation-delay: .9s; }
}
/* AN AGENT SIGN-IN WAITING FOR A YES (2026-10-05, Dave): the same box, with a key in a white-edged
   tile where the face would be, and two buttons - a white Confirm and a quiet outlined Remove.
   home/confirm.js, drawn off Workspaces.Home's and Setup.Steps' `confirm`, on Home and over the
   setup steps */
.ob-confirmconn {
    .ob-keytile {
        position: relative;
        z-index: 1;
        flex: none;
        display: grid;
        place-items: center;
        width: 44px;
        height: 44px;
        border-radius: 12px;
        background: rgba(255, 255, 255, .16);
        box-shadow: inset 0 0 0 2px var(--ob-on-accent);

        svg { width: 22px; height: 22px; }
    }
    .acts {
        position: relative;
        z-index: 1;
        flex: none;
        display: flex;
        gap: 8px;
    }
    .ob-pill.quiet {
        background: transparent;
        color: var(--ob-on-accent);
        box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, .6);
    }
    .ob-pill.quiet:hover { background: rgba(255, 255, 255, .12); }
}
/* over the setup steps it keeps the steps' own gap below it */
.ob-setup > .ob-confirmslot { margin-bottom: 18px; }

/* Start a plan is wider than Reconnect, so its stars sit further left */
.ob-noplan {
    .star.s1 { right: 172px; }
    .star.s2 { right: 154px; }
}
@media (prefers-reduced-motion: reduce) {
    .ob-reconnect .star,
    .ob-noplan .star { animation: none; opacity: .6; }
}

/* ONE LEVEL (2026-09-24). A card still waiting draws nothing of its own: the stage inside it IS
   the card - its border, its ground, the chip on its top line, the breathing of the oldest one
   (okaybot-stage.css). There used to be a card here with a header and the agent's reason, and
   the frame inside it; a card inside a card was the thing the redesign took away. Only the
   answered line below is a card of this element's own. */
.ob-decide { min-width: 0; }

/* ---- the two animations, and why neither is bound to a state ----
   A rule bound to a class the template writes - .settled, say - re-runs every time the element
   is inserted in the document, and a widget's element is re-inserted whenever its list reorders
   or its parent rebuilds. That is not a person settling anything, so it must not look like one.
   Both of these are put on by hand and taken off when they finish (DecisionCard._play, _fold). */

/* an arriving card: it grows in past its size and settles, so a call landing while you watch
   catches the eye */
.ob-decide.pop { animation: ob-card-in .55s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes ob-card-in {
    0%   { opacity: 0; transform: scale(.94); }
    100% { opacity: 1; transform: scale(1); }
}

/* the moment of the decision: the card folds up into its one line. The script walks the
   inline height from the card's to the line's; this is only how fast. Keep it under
   DecisionCard.FOLD_MS, which takes the class off. */
.ob-decide.folding {
    overflow: hidden;
    transition: height .42s cubic-bezier(.2, .8, .2, 1);
}
.ob-decide.folding .ob-dline { animation: ob-line-in .36s .12s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes ob-line-in {
    0%   { opacity: 0; transform: translateY(6px); }
    100% { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .ob-decide.pop, .ob-decide.folding .ob-dline { animation: none; }
}

/* ---- answered: the card is one line ----
   What it became and a chevron, the whole width a door back to the item modal. The card's
   border goes a shade quieter so the lines read as done beside the cards still waiting. */
.ob-decide.settled {
    background: var(--ob-card);
    border: 1px solid var(--ob-line-in);
    border-radius: var(--ob-radius-row);
}

.ob-dline {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 14px 18px;
    border: 0;
    border-radius: inherit;
    background: none;
    font-family: var(--ob-font);
    text-align: left;
    cursor: pointer;

    .mk {
        flex: none;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 22px;
        height: 22px;
        border-radius: 50%;
        background: var(--ob-mint-bg);
    }
    .mk svg { width: 12px; height: 12px; fill: none; stroke: var(--ob-mint-ink); stroke-width: 3.2; stroke-linecap: round; stroke-linejoin: round; }
    .t { font-size: 13.5px; font-weight: 700; color: var(--ob-mint-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .grow { flex: 1 1 auto; }
    .more { font-size: 15px; font-weight: 700; color: var(--ob-faint-3); line-height: 1; }

    &:hover .more { color: var(--ob-muted); }
    &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: -2px; }
}
.ob-decide.stopped .ob-dline {
    .mk { background: var(--ob-rose-bg); }
    .mk svg { width: 11px; height: 11px; stroke: var(--ob-rose-ink); }
    .t { color: var(--ob-rose-ink); }
}
/* a post the claim held after the Okay: not sent, not refused - waiting on a person, in the
   held list's butter (2026-10-05; it drew the green tick of a sent post) */
.ob-decide.held .ob-dline {
    .mk { background: var(--ob-butter-bg); }
    .mk svg { stroke: var(--ob-butter-ink); }
    .t { color: var(--ob-butter-ink); }
}

/* A SEND INSIDE THE CLICK (2026-10-03): the card folds the moment Okay is pressed, with a spinner
   where the tick goes until the answer is back. A post still on its way - a long thread, sent
   behind the answer - has a clock. The line's light half is the last step's words: why it failed,
   or when to look again. A failed post that may go again has Try again beside the line */
.ob-dline {
    .mk .ob-spin { width: 11px; height: 11px; color: var(--ob-mint-ink); }
    .mk svg.clock { width: 13px; height: 13px; stroke-width: 2.4; }
    .t .lt { margin-left: 6px; font-weight: 500; color: var(--ob-muted); }
    &:disabled { cursor: default; }
}
.ob-dline-row {
    display: flex;
    align-items: center;
    padding-right: 14px;

    .ob-dline { flex: 1 1 auto; min-width: 0; }
    .ob-dline .more { display: none; }
    .ob-pill { flex: none; height: 32px; padding: 0 14px; font-size: 13px; }
}

/* ============================================================
   THE RIGHT COLUMN: the log
   ============================================================ */
.ob-log {
    .rows { display: flex; flex-direction: column; }
    .ob-empty { padding: 20px 0; max-width: none; }
}

/* The line. Four columns: the square, what it did, then the money and the time, both right
   aligned so they hold a column down the page whatever the line beside them says - that column
   of numbers is what makes a log readable at a glance.

   THE MONEY COLUMN HOLDS A WORD SOMETIMES. A refused or blocked call was never charged, so the
   column is free and its status goes there instead of a struck-out number beside it. It takes a
   floor rather than a fixed width so the numbers still line up on the rows either side of it,
   and a longer word than "Refused" pushes out into the line rather than off the row. */
.ob-logrow {
    display: grid;
    grid-template-columns: var(--ob-log-sq) minmax(0, 1fr) minmax(var(--ob-log-amt-w), auto) var(--ob-log-when-w);
    gap: var(--ob-log-gap);
    align-items: center;
    min-height: var(--ob-log-h);
    padding: 0 8px;
    margin: 0 -8px;
    border-radius: 10px;
    color: var(--ob-ink);
    cursor: pointer;

    /* the row is a fact, not a card: the only thing hovering does is say it can be opened.
       THE HOVER IS THE OTHER SURFACE. On the Activity page the log sits on the canvas, so the
       row lifts to the card's white; on the home page it sits in the white panel (since
       2026-09-18), where white on white said nothing, so there it drops to the canvas's grey -
       the sidebar's own hover, on the sidebar's own white. */
    &:hover { background: var(--ob-card); }
    .ob-panel &:hover { background: var(--ob-canvas); }

    .act {
        min-width: 0;
        font-size: 13.5px;
        font-weight: 700;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .act .lt { font-weight: 600; color: var(--ob-muted); }

    /* the status where the money would be. Rose for now - this is the one word of colour on
       this side of the page, and which colour it should be is not settled: a refusal is a
       person's own decision rather than a failure, and may want the quiet grey that a blocked
       or failed call does not. */
    .why { font-size: 12.5px; font-weight: 700; color: var(--ob-rose-ink); white-space: nowrap; text-align: right; }

    /* THE COST IS THERE WHEN YOU WANT IT, NOT WHEN YOU DO NOT. A column of dollar amounts
       down a log is a lot of precision about something nobody is reading line by line - what
       the row is for is what the agent did, and the money is the answer to a question you ask
       of one row at a time. So it sits back, and hovering the row brings it up to the weight
       of the time beside it. The status word (.why) is not money and does not fade: it is the
       only thing on the row that says something went differently. */
    .ob-amt {
        font-size: 12px;
        text-align: right;
        font-weight: 500;
        color: var(--ob-faint-2);
        transition: color .12s ease, font-weight .12s ease;
    }
    &:hover .ob-amt { font-weight: 600; color: var(--ob-faint-1); }

    .when { font-size: 12px; font-weight: 600; color: var(--ob-faint-1); text-align: right; font-variant-numeric: tabular-nums; }
}

/* the workspace itself, not an agent: a ceiling, a sweep */
.ob-logrow .ob-square.sys { background: var(--ob-grey-bg); color: var(--ob-muted); }

/* a call still waiting on a person is the only row here with a state worth colouring, because
   it is the only one that is also sitting in the column to the left */
.ob-logrow.waiting .act { color: var(--ob-accent); }

/* a session's first line: the run everything under it belongs to */
/* .ob-logrow.start { margin-top: 8px; }
.ob-logrow.start .act { font-weight: 800; } */
.ob-logitem:first-child .ob-logrow.start { margin-top: 0; }

/* an arriving line drops in from above and settles */
.ob-logrow.fresh { animation: ob-log-in .45s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes ob-log-in {
    0%   { opacity: 0; transform: translateY(-6px); }
    100% { opacity: 1; transform: none; }
}

/* the day divider: a log long enough to cross midnight says where yesterday starts */
.ob-logday {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 16px 0 8px;

    .l { font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ob-faint-1); }
    .r { flex: 1 1 auto; height: 1px; background: var(--ob-line); }
}

/* ---- NARROW: 1199 and below (dev/docs/responsive.md) ----
   THE LOG GOES AND THE DECISIONS TAKE THE PAGE. The log is Activity's, one click away in the
   sidebar, and a decision card already opens the item modal. What does NOT go is the Emergency
   stop: it is the one thing in that panel that must never be out of reach, so it moves onto the
   decisions' own heading row, and the card it opens spans the page under that row.

   It is done without moving a widget. The two columns and the log's heading are unboxed
   (display: contents), so the four things inside them - the decisions' heading, the stop, its
   card and the decisions - are laid out by this one grid. There is still exactly one LogHead,
   holding the one answer to whether the workspace is running. */
@media (max-width: 1199px) {
    .ob-home {
        grid-template-columns: minmax(0, 1fr) auto;
        grid-template-rows: auto auto auto auto 1fr;
        grid-template-areas: "wait stop" "card card" "notes notes" "queue queue" "tries tries";
        column-gap: 12px;
        padding: var(--ob-body-pad-top) var(--ob-body-pad-right) 60px var(--ob-body-pad-left);
    }
    .ob-home > .ob-hcol.left,
    .ob-home > .ob-panel,
    .ob-home > .ob-panel > [widget="loghead"],
    .ob-home .ob-loghead { display: contents; }

    .ob-home [widget="queuehead"] { grid-area: wait; min-width: 0; }
    .ob-home .ob-homenotes { grid-area: notes; min-width: 0; }      /* Start a plan, Reconnect and the money line, the page's width */
    .ob-home [widget="queue"] { grid-area: queue; min-width: 0; }
    .ob-home [widget="tries"] { grid-area: tries; min-width: 0; align-self: start; }     /* the last row takes what height is left; the box keeps its own */
    .ob-home .ob-loghead > .ob-colhead { grid-area: stop; }
    .ob-home .ob-loghead > .ob-colhead > :not(.ob-stopbtn) { display: none; }
    .ob-home .ob-loghead > .ob-stopcard { grid-area: card; }
    .ob-home > .ob-panel > [widget="log"] { display: none; }
}

/* GETTING STARTED is not on this sheet any more (2026-09-12). A workspace that is not set up
   gets the setup flow in place of these two columns - setup/flow.js, css/okaybot-setup.css -
   and the three "getting started" cards that used to sit above the queue went with it. */

/* ============================================================
   EMERGENCY STOP (2026-09-18)

   IT WAS A SWITCH, AND A SWITCH WAS THE WRONG SHAPE FOR IT. A green toggle beside "Nothing
   waiting" is the same control the agents and the profiles wear, sitting on the one thing in
   the product that stops everything at once - so it read as a setting, and it read as ON,
   which is a claim the page was making about the workspace before anybody asked. The question
   it opened had to fit on the heading's row, so the sentence explaining what stopping means
   was squeezed to 42 characters and right-aligned into a corner.

   So: a grey button on the log's heading, and a card under it. The button says what it is and
   nothing else - it is not coloured, because a red button at the top of the page is a page
   asking to be pressed. The explanation is in the card it opens, where there is room to write
   it, and the card is red because by then it is the subject.

   THE SAME CARD IS THE STOPPED STATE. A frozen workspace draws it permanently, with one
   button on it, so the screen that would otherwise look idle says why it is idle in the place
   where the activity should have been.
   ============================================================ */

/* the button: grey, quiet, and no bigger than the heading beside it */
.ob-stopbtn {
    background: var(--ob-grey-bg);
    border-color: transparent;
    color: var(--ob-grey-ink);

    .e { font-size: 13px; line-height: 1; }
}
.ob-stopbtn:hover { background: var(--ob-rose-bg); color: var(--ob-rose-ink); }

.ob-stopcard {
    margin-bottom: var(--ob-head-gap);
    padding: 15px 16px 16px;
    border-radius: var(--ob-radius-row);
    background: var(--ob-rose-card);
    border: 1px solid var(--ob-rose-edge);

    .t {
        display: flex;
        align-items: center;
        gap: 8px;
        font-size: 14px;
        font-weight: 800;
        letter-spacing: -.01em;
        color: var(--ob-rose-ink);
    }

    .p {
        margin: 8px 0 0;
        font-size: 12.5px;
        font-weight: 500;
        line-height: 1.5;
        color: var(--ob-ink-2);
    }

    .btns {
        display: flex;
        align-items: center;
        gap: 8px;
        margin-top: 14px;
    }

    /* the pills sit on the card's own tint, so the ghost one has to carry the card's ground
       rather than the canvas's */
    .ob-pill.ghost { background: var(--ob-card); border-color: var(--ob-rose-chip); }
    .ob-pill.ghost:hover { background: var(--ob-rose-bg); color: var(--ob-rose-ink); }
}
 /* ============================================
   OKAYBOT DIALOG
   The small centred popup: a message (popups/popup.html), a confirm (confirm.html), the
   unlock (unlock.html), the email change (change_email.html) and connecting an account
   (connect.html). All five share this chrome; the connect's own steps are at the foot.

   It is the small sibling of the item modal (okaybot-modal.css) and speaks the same
   language: the shade over the page, a white card with the row radius, and the round x in
   the corner. It replaced the Shift design system's components/popup.css on 2026-09-15 -
   that sheet drew a purple gradient header bar with a centred white title, which is another
   product's look and nothing else on this site does it. The plan change popup's blue head
   (2026-10-02) is the one exception, and it is ours: the lit pricing card's own gradient,
   carried into the popup the person opened from that card.

   The markup classes are the scaffold's (.popup / .modal / .modal-header / .modal-content /
   .modal-actions / .modal-close) because four templates and every caller of
   $$.app.show_popup already speak them. Only the drawing changed.
   ============================================ */

.popup {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    font-family: var(--ob-font);

    /* `shade`, not `mask`: components/mask.css owns a bare .mask for the app's loading
       overlay, and its z-index: 900 leaked in here and painted the shade OVER the card (the
       card is z-index auto, so it lost). The item modal already calls this the shade. */
    .shade {
        position: absolute;
        inset: 0;
        z-index: 0;
        background: var(--ob-shade);
        cursor: pointer;
        animation: ob-shade-in .25s ease-out;
    }

    /* 440 gives the code field the width it wants - eight 40px digits, their gaps and the
       separator come to 386, and the content padding takes the rest. Below that the digits
       give way rather than push the card out (.ob-otp in okaybot-identity.css). */
    .modal {
        position: relative;
        z-index: 1;
        width: 100%;
        max-width: 440px;
        background: var(--ob-card);
        border-radius: var(--ob-radius-row);
        box-shadow: var(--ob-shadow-modal);
        animation: ob-dialog-in .28s cubic-bezier(.2, .8, .2, 1);
    }

    /* SETTLED: a popup drawn again in place (its next state - busy, done) is a new card in the DOM,
       which would come in again. A popup that marks itself settled after its first paint comes in
       once (popups/planchange.js) */
    &.settled .shade, &.settled .modal { animation: none; }

    /* HANDED: opened as another popup steps aside or closes - a message or a question over the
       item modal (app.js show_popup), the plan started popup after the plan change popup - so the
       shade is already down and only the card comes in */
    &.handed .shade { animation: none; }

    .modal-header {
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 22px 22px 0;

        h4 {
            margin: 0;
            flex: 1;
            min-width: 0;
            font-size: 17px;
            font-weight: 800;
            line-height: 1.3;
            color: var(--ob-ink);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        .modal-icon {
            flex: 0 0 auto;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        /* the message popup's tone (popups/popup.js): info in the accent, warn in the butter of
           something waiting on a person, error in the rose of blocked. Only the chip changes */
        .modal-tone {
            flex: 0 0 auto;
            width: 32px;
            height: 32px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            background: var(--ob-accent-bg);
            color: var(--ob-accent-ink);

            svg { width: 16px; height: 16px; }
        }
    }

    &.tone-warn .modal-tone { background: var(--ob-butter-bg); color: var(--ob-butter-ink); }
    &.tone-error .modal-tone { background: var(--ob-rose-bg); color: var(--ob-rose-ink); }

    .modal-close {
        flex: 0 0 auto;
        width: 30px;
        height: 30px;
        padding: 0;
        border: 0;
        border-radius: 50%;
        background: var(--ob-canvas);
        color: var(--ob-ink-2);
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: background .12s, color .12s;

        svg { width: 13px; height: 13px; }
        &:hover { background: var(--ob-line); color: var(--ob-ink); }
        &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
    }

    .modal-content {
        display: flex;
        flex-direction: column;
        padding: 16px 22px 22px;

        p {
            margin: 0;
            font-size: 13.5px;
            font-weight: 500;
            line-height: 1.55;
            color: var(--ob-ink-2);
        }
        /* a why in paragraphs, and its link to where the thing is changed (2026-10-01) */
        p + p { margin-top: 10px; }
        p a { color: var(--ob-accent-ink); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }
        p a:hover { color: var(--ob-accent); }
    }

    /* Side by side by default - two ways out of a confirm. `stack` is the other arrangement,
       one full-width choice above another, which is what the two identity popups want. It is
       a class on the row rather than an override in another sheet, so which one a popup gets
       does not depend on which sheet the bundle happens to concatenate last. */
    .modal-actions {
        display: flex;
        gap: 10px;
        margin-top: 18px;

        .ob-pill { flex: 1 1 auto; }

        &.stack {
            flex-direction: column;
            gap: 8px;
            margin-top: 14px;
        }
        &.stack .ob-pill.text { align-self: center; }
    }
}

@keyframes ob-dialog-in {
    from { transform: translateY(10px) scale(.97); opacity: 0; }
}

/* the phone: the card takes the width it can get, and the code field has to keep fitting */
@media (max-width: 600px) {
    .popup {
        padding: 16px;

        .modal-header { padding: 18px 16px 0; }
        .modal-content { padding: 14px 16px 18px; }
    }
    .popup .ob-otp { gap: 6px; }
}

/* ============================================
   CONNECT AN ACCOUNT (popups/connect.html, dev/docs/connect.md): the same chrome, and every step
   drawn one way (Dave, 2026-09-30) - the state as the title beside its tone, the account as one
   row with its network on the right, the buttons on the right under it.
   ============================================ */
.popup.ob-connect {

    /* the tone beside the title: the accent while nothing has happened, then how it went */
    .modal-tone.ok { background: var(--ob-mint-bg); color: var(--ob-mint-ink); }
    .modal-tone.warn { background: var(--ob-butter-bg); color: var(--ob-butter-ink); }
    .modal-tone.error { background: var(--ob-rose-bg); color: var(--ob-rose-ink); }

    .cn-rows { display: flex; flex-direction: column; gap: 8px; }

    /* the account, or the network before there is one */
    .cn-row {
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 12px 14px;
        border: 1px solid var(--ob-line);
        border-radius: var(--ob-radius-panel);
        background: var(--ob-card);
        font-family: var(--ob-font);
        text-align: left;

        .net-icon { width: 44px; height: 44px; flex: none; border-radius: 50%; }
        .ob-circle img { width: 100%; height: 100%; object-fit: cover; }

        .who { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
        .nm, .sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .nm { font-size: 15px; font-weight: 800; line-height: 1.3; color: var(--ob-ink); }
        .sub { font-size: 13.5px; font-weight: 600; color: var(--ob-muted); }

        .net {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            flex: none;
            height: 28px;
            padding: 0 10px 0 4px;
            border-radius: 999px;
            background: var(--ob-canvas);
            font-size: 13px;
            font-weight: 700;
            color: var(--ob-ink);

            img { width: 20px; height: 20px; border-radius: 50%; }
        }
    }

    /* a tester's workspace that may connect through several providers: the rows are the choice */
    .cn-row.pick {
        width: 100%;
        cursor: pointer;
        transition: border-color .12s, background .12s;

        &:hover { border-color: var(--ob-accent-ring); }
        &.on { border-color: var(--ob-accent); background: var(--ob-accent-tint); }
        &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
    }

    .cn-tick { margin-top: 14px; }

    .modal-content p.cn-line { margin-top: 14px; }

    /* a refusal on Start: the tab never left, and this says why */
    .modal-content p.cn-refused {
        margin: 14px 0 0;
        color: var(--ob-rose-ink);
        font-weight: 600;

        a { color: inherit; font-weight: 800; }
    }

    .modal-actions {
        justify-content: flex-end;

        .ob-pill { flex: 0 0 auto; }
    }
}

/* THE WAY BACK FROM X (connect/landing.html): on the screen for the length of one call */
.ob-landing {
    min-height: 60vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    font-family: var(--ob-font);

    .spin {
        width: 28px;
        height: 28px;
        border-radius: 50%;
        border: 3px solid var(--ob-accent-bg);
        border-top-color: var(--ob-accent);
        animation: ob-landing-turn .9s linear infinite;
    }
    .what { font-size: 14px; font-weight: 700; color: var(--ob-ink-2); }
}

@keyframes ob-landing-turn {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .ob-landing .spin { animation: none; }
}

/* CHANGING PLAN (popups/planchange.js): the blue head of 2026-10-02 - the lit pricing card's own
   gradient - holds the move and the price change in every state, and the white card under it is the
   preview, the busy line over the preview, or what was done */
.popup .ob-planchange {
    width: 460px;

    .modal-header {
        display: block;
        padding: 22px 22px 24px;
        border-radius: var(--ob-radius-row) var(--ob-radius-row) 0 0;
        background: linear-gradient(160deg, var(--ob-accent-lift), var(--ob-accent) 45%, var(--ob-accent-ink));
        color: #fff;

        .row { display: flex; align-items: center; gap: 12px; }
        h4 { color: #fff; }
        .modal-close { background: rgba(255, 255, 255, .18); color: #fff; }
        .modal-close:hover { background: rgba(255, 255, 255, .28); color: #fff; }
        .modal-close:focus-visible { outline-color: #fff; }
        /* busy: Stripe has the call, so there is no closing it */
        .modal-close.gone { visibility: hidden; }
    }

    /* the price change: the price paid struck through in faint white, the arrow, the new price
       large - or the day a cancel ends. The arrow nudges while busy and is a tick once done */
    .prices { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 18px; font-variant-numeric: tabular-nums; }
    .prices .old { position: relative; font-size: 17px; font-weight: 700; color: rgba(255, 255, 255, .62); }
    .prices .old::after { content: ""; position: absolute; left: -2px; right: -2px; top: 52%; border-top: 1.5px solid rgba(255, 255, 255, .7); transform: rotate(-14deg); }
    .prices .mid { flex: 0 0 auto; width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; background: rgba(255, 255, 255, .18); }
    .prices .mid svg { width: 15px; height: 15px; }
    .prices .mid.busy svg { animation: ob-planchange-nudge 1.1s ease-in-out infinite; }
    .prices .mid.done { background: #fff; color: var(--ob-accent-ink); }
    .prices .mid.done svg { width: 16px; height: 16px; }
    .prices .new { display: flex; align-items: baseline; gap: 6px; }
    .prices .new b { font-size: 32px; font-weight: 800; letter-spacing: -.03em; line-height: 1; color: #fff; }
    .prices .new.words b { font-size: 26px; letter-spacing: -.02em; }
    .prices .new small { font-size: 13px; font-weight: 600; color: rgba(255, 255, 255, .75); }

    .modal-content { position: relative; display: grid; gap: 16px; padding-top: 22px; }
    .modal-actions { margin-top: 2px; }

    /* BUSY: the card's content stays in place, unseen, so the popup keeps its height; the spinner
       and the server's line sit over it */
    .modal-content.busy > :not(.loading) { visibility: hidden; }
    .loading { position: absolute; inset: 0; display: grid; place-content: center; justify-items: center; gap: 14px; }
    .loading .what { font-size: 14px; font-weight: 700; color: var(--ob-ink-2); }
    /* the preview on its way: the spinner alone */
    .wait { display: grid; place-items: center; min-height: 120px; }
    .spin { width: 28px; height: 28px; border-radius: 50%; border: 3px solid var(--ob-accent-bg); border-top-color: var(--ob-accent); animation: ob-landing-turn .9s linear infinite; }

    /* DONE: what happened, under the tick in the head */
    .done { display: grid; gap: 4px; }
    .done .t { margin: 0; font-size: 17px; font-weight: 800; letter-spacing: -.01em; line-height: 1.3; color: var(--ob-ink); }

    /* the bank's check or a declined card, in place of the preview */
    .stage { display: grid; gap: 4px; padding: 12px 14px; border-radius: 12px; font-size: 13.5px; line-height: 1.5; }
    .stage .h { font-weight: 800; }
    .stage.bank { background: var(--ob-butter-bg); border: 1px solid var(--ob-butter-edge); color: var(--ob-butter-ink); }
    .stage.declined { background: var(--ob-rose-bg); color: var(--ob-rose-ink); }

    /* the one fact: today's charge, large */
    .fact { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
    .fact .l { color: var(--ob-ink-2); font-weight: 600; font-size: 14px; }
    .fact .v { color: var(--ob-ink); font-weight: 800; font-size: 16px; text-align: right; font-variant-numeric: tabular-nums; }
    .fact.big .v { font-size: 26px; letter-spacing: -.02em; }

    .sum { display: grid; gap: 6px; padding: 12px 14px; background: var(--ob-accent-bg); border-radius: 12px; font-size: 13px; color: var(--ob-accent-ink); font-variant-numeric: tabular-nums; }
    .sum div { display: flex; justify-content: space-between; gap: 12px; }
    .sum div span:last-child { white-space: nowrap; font-weight: 700; }
    /* a line whose parts fold under it: the whole line is the button, the arrow turns when open */
    .sum .parts { display: flex; justify-content: space-between; gap: 12px; width: 100%; padding: 0; border: 0; background: none; font: inherit; color: inherit; text-align: left; cursor: pointer; }
    .sum .parts > span:first-child { display: inline-flex; align-items: center; gap: 6px; }
    .sum .parts svg { width: 14px; height: 14px; transition: transform .15s ease; }
    .sum.open .parts svg { transform: rotate(180deg); }
    .sum .parts > span:last-child { white-space: nowrap; font-weight: 700; }
    .sum .parts:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 3px; border-radius: 4px; }
    /* the parts of the line above, set in under it and quieter */
    .sum div.part { display: none; padding-left: 14px; font-size: 12px; opacity: .8; }
    .sum.open div.part { display: flex; }
    .sum div.part span:last-child { font-weight: 600; }

    /* a "\n" in the server's sentence starts a new line */
    .say { margin: 0; color: var(--ob-ink-2); font-size: 14px; line-height: 1.55; white-space: pre-line; }
    .say b { color: var(--ob-ink); font-weight: 700; }

    /* what has to go before a downgrade lands, and only then (Dave, 2026-10-02): one line in the
       butter of a warning. Nothing is said when everything fits */
    .warn { display: flex; gap: 8px; align-items: flex-start; padding: 10px 12px; background: var(--ob-butter-bg); color: var(--ob-butter-ink); border-radius: 12px; font-size: 13.5px; font-weight: 600; line-height: 1.45; }
    .warn svg { width: 16px; height: 16px; flex: 0 0 auto; margin-top: 2px; }

    .modal-actions .ob-pill { flex: 1; }
    .modal-actions .ob-pill.wide { flex: 1.5; }
}

@keyframes ob-planchange-nudge {
    0%, 100% { transform: translateX(-2px); }
    50% { transform: translateX(3px); }
}

@media (prefers-reduced-motion: reduce) {
    .popup .ob-planchange .spin,
    .popup .ob-planchange .prices .mid.busy svg { animation: none; }
}

/* THE PLAN STARTED (popups/planstarted.js), 2026-10-02: the plan change popup above, its head lit a
   little brighter at one corner, a tick where the arrow is and the plan's name large, a scatter of
   still confetti behind the words, then what the plan holds and two facts. The fireworks are a canvas
   between the shade and the card. Handed on from the plan change popup, the shade is already down,
   so only the card comes in (HANDED, at the top) */
.popup .ob-sky { position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%; pointer-events: none; }

.popup .ob-planstarted {
    .modal-header {
        position: relative;
        overflow: hidden;
        background:
            radial-gradient(120% 90% at 92% 8%, rgba(255, 255, 255, .22), transparent 55%),
            linear-gradient(160deg, var(--ob-accent-lift), var(--ob-accent) 45%, var(--ob-accent-ink));
    }
    .modal-header .row, .prices { position: relative; z-index: 1; }

    .prices .mid.done { width: 34px; height: 34px; box-shadow: 0 0 0 6px rgba(255, 255, 255, .16); }
    .prices .mid.done svg { width: 18px; height: 18px; }
    .prices .new { flex-wrap: wrap; gap: 4px 10px; min-width: 0; }
    .prices .new b { font-size: 34px; }
    .prices .new small { color: rgba(255, 255, 255, .8); }

    /* the still confetti: the head reads as a celebration with the motion off, and after the fireworks */
    .glitter { position: absolute; inset: 0; pointer-events: none; }
    .glitter i { position: absolute; display: block; width: 9px; height: 4px; border-radius: 1.5px; background: rgba(255, 255, 255, .7); }
    .glitter i:nth-child(1)  { left: 58%; top: 58%; transform: rotate(-24deg); background: #ffd76a; }
    .glitter i:nth-child(2)  { left: 66%; top: 30%; width: 6px; height: 6px; border-radius: 50%; }
    .glitter i:nth-child(3)  { left: 72%; top: 70%; transform: rotate(38deg); background: #7fe3b4; }
    .glitter i:nth-child(4)  { left: 79%; top: 46%; transform: rotate(-62deg); }
    .glitter i:nth-child(5)  { left: 86%; top: 76%; width: 6px; height: 6px; border-radius: 50%; background: #ff9fb0; }
    .glitter i:nth-child(6)  { left: 90%; top: 54%; transform: rotate(20deg); background: #b9c4ff; }
    .glitter i:nth-child(7)  { left: 63%; top: 84%; transform: rotate(70deg); background: #ff9fb0; }
    .glitter i:nth-child(8)  { left: 83%; top: 26%; transform: rotate(12deg); background: #ffd76a; }
    .glitter i:nth-child(9)  { left: 95%; top: 36%; width: 6px; height: 6px; border-radius: 50%; background: #7fe3b4; }
    .glitter i:nth-child(10) { left: 70%; top: 50%; width: 6px; height: 6px; border-radius: 50%; background: #b9c4ff; }
    .glitter i:nth-child(11) { left: 76%; top: 88%; transform: rotate(-10deg); }
    .glitter i:nth-child(12) { left: 52%; top: 82%; width: 6px; height: 6px; border-radius: 50%; }

    /* what the plan holds, as the pricing card lists it */
    .room { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
    .room div { display: grid; gap: 2px; align-content: start; padding: 12px 12px 11px; border-radius: 14px; background: var(--ob-accent-bg); color: var(--ob-accent-ink); }
    .room b { font-size: 20px; font-weight: 800; letter-spacing: -.02em; line-height: 1.15; font-variant-numeric: tabular-nums; }
    .room span { font-size: 12px; font-weight: 600; line-height: 1.3; opacity: .85; }

    /* what was paid and when it renews: the plan change popup's fact, one under the other */
    .facts { display: grid; }
    .fact { padding: 9px 0; }
    .fact + .fact { border-top: 1px solid var(--ob-line); }
    .fact .v small { display: block; font-size: 12px; font-weight: 600; color: var(--ob-muted); }

    .wait { min-height: 220px; }

    /* the plan arriving: the tick pops, the name rises, the figures follow, the confetti settles */
    &.arrive .prices .mid.done { animation: ob-started-pop .55s cubic-bezier(.2, 1.7, .45, 1) both; }
    &.arrive .prices .new { animation: ob-started-rise .45s .08s cubic-bezier(.2, .8, .2, 1) both; }
    &.arrive .room div { animation: ob-started-rise .4s cubic-bezier(.2, .8, .2, 1) both; }
    &.arrive .room div:nth-child(1) { animation-delay: .14s; }
    &.arrive .room div:nth-child(2) { animation-delay: .19s; }
    &.arrive .room div:nth-child(3) { animation-delay: .24s; }
    &.arrive .glitter i { animation: ob-started-fall .7s .1s ease-out both; }
}

@keyframes ob-started-pop { from { transform: scale(0) rotate(-40deg); } }
@keyframes ob-started-rise { from { opacity: 0; transform: translateY(8px); } }
@keyframes ob-started-fall { from { opacity: 0; translate: 0 -14px; scale: .4; } }

@media (prefers-reduced-motion: reduce) {
    .popup .ob-planstarted.arrive .prices .mid.done,
    .popup .ob-planstarted.arrive .prices .new,
    .popup .ob-planstarted.arrive .room div,
    .popup .ob-planstarted.arrive .glitter i { animation: none; }
}
 /* ============================================
   OKAYBOT DETAIL
   One call in full: the heading, the three-way switch, the picture, what happened, the facts
   and the actions. One widget (popups/itemview.js) in two arrangements, so this sheet is in
   two halves - the parts, which are shared, and then the two layouts, which only move them.

   NOTHING HERE STYLES THE PICTURE. That is okaybot-stage.css, and it is the same picture Home
   draws on a card it is answering. A detail decides how much air goes round a stage, never
   what is in one.

   The class names are scoped under .ob-detail on purpose: .ob-dhead is also the home page's
   decision card, where it means something else entirely.
   ============================================ */

.ob-detail {
    font-family: var(--ob-font);
    color: var(--ob-ink);
    min-width: 0;
}

/* the panel before a line has been picked, and the call that is not there any more */
.ob-detail > .ob-empty { padding: 40px 0; }

/* A load in flight draws nothing rather than a spinner: the panel is redrawn every time a line
   is clicked, and a shape that flashed between two calls would be the loudest thing on a screen
   whose whole argument is quiet. */
.ob-detail .ob-dwait { min-height: 200px; }

/* ============================================================
   THE PARTS
   ============================================================ */

/* ---- 1. the heading ----
   The title as a sentence with the target in its light half; the agent's face and name on one
   line under it; the agent's own words under that.

   THE TITLE LEADS (2026-09-16). The account had the first line and the agent had a third one,
   so the head was three stacked blocks at nearly one weight and the eye had nowhere to land.
   The title is what the call is, so it is first and it is the only loud thing.

   ONE FACE UNDER IT (2026-09-19). The byline then carried the account, the agent and the time
   on one line, and the modal's side stacked the same three in two - two somebodies at one
   weight, reading as two subjects. The account went to the picture, which is where the eye
   is anyway: a post draws it, and a read's card has the account's face at the right of its
   foot (okaybot-stage.css, .ob-sfoot .who). The
   time went with it: the day is in the heading over the timeline and the minutes are in its
   gutter. What is left is who asked and why.

   The parts are set here once and each layout only says how big and how far apart. */
.ob-detail .who .h { font-weight: 700; color: var(--ob-ink); }

/* THE HOME PAGE'S DECISION CARD OWNS THIS CLASS TOO, and there it is one clickable line
   (okaybot-home.css). Everything that card sets and this heading does not want is unset here
   rather than renamed, because the name is the artboard's: the heading of an item detail. */
.ob-detail .ob-dhead {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    padding: 0;
    cursor: default;

    .ttl { margin: 0; font-size: 21px; font-weight: 800; letter-spacing: -.01em; line-height: 1.25; }
    .ttl .lt { font-weight: 600; color: var(--ob-muted); }

    /* the agent's line: one face and a name, quieter than the title above it and quieter than
       the words below it, because it is the only line here that is furniture */
    .who, &:hover .who {
        display: flex;
        align-items: center;
        flex-wrap: wrap;
        gap: 8px;
        font-size: 13px;
        font-weight: 600;
        color: var(--ob-ink-2);
        white-space: normal;
        overflow: visible;
    }

    .say { font-size: 13.5px; font-weight: 500; line-height: 1.55; color: var(--ob-ink-2); }
}

/* ---- 2. the switch ----
   TABS ON A RULE, NOT A PILL (2026-09-16). It was an .ob-seg pill group - a filled control with
   the weight of a heading, standing in the reading path between the head and the picture it
   changes. As tabs it belongs to the box below it: the live one underlined onto the rule, the
   rule running the full width, so the strip reads as the lid of the picture. They start at the
   left edge, on the same vertical as the title, the byline and the reason above them.

   The rule is the same one the picture would otherwise want above it, so there is no second
   line here - the strip IS the divider. The composer's Write and Schedule are the same strip
   (okaybot-compose.css, 2026-10-03). */
.ob-detail .ob-dswitch,
.ob-compose .ob-dswitch {
    display: flex;
    align-items: flex-end;
    gap: 20px;
    margin: 26px 0 24px;
    border-bottom: 1px solid var(--ob-line);
    overflow-x: auto;
    scrollbar-width: none;

    &::-webkit-scrollbar { display: none; }

    .item {
        position: relative;
        flex: 0 0 auto;
        padding: 0 0 9px;
        border: 0;
        background: none;
        font-family: var(--ob-font);
        font-size: 13px;
        font-weight: 600;
        line-height: 1.2;
        color: var(--ob-muted);
        white-space: nowrap;
        cursor: pointer;
    }
    .item:hover { color: var(--ob-ink-2); }
    .item.on { color: var(--ob-ink); font-weight: 700; }
    .item.on::after {
        content: "";
        position: absolute;
        left: 0;
        right: 0;
        bottom: -1px;
        height: 2px;
        border-radius: 2px;
        background: var(--ob-ink);
    }
    .item:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }

    /* the first of the wire tabs pushes itself and the one after it to the right end of the
       rule: two groups on one lid, and the gap between them is what says so (itemswitch.html) */
    .item.far { margin-left: auto; }
}

/* ---- 3. the picture, and the one line under it ----
   The foot is a caption on the post above it, not a status bar, so it is inset to the
   timeline's own left edge rather than to the panel's. */
.ob-detail .ob-dfoot {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin: 26px calc(var(--ob-frame-pad-x) + var(--ob-frame-line)) 0;
    padding-top: 16px;
    border-top: 1px solid var(--ob-line-in);

    .t, .t:hover { font-size: 12.5px; font-weight: 700; color: var(--ob-mint-ink); cursor: default; text-decoration: none; }
    .lnk { font-size: 12.5px; font-weight: 700; color: var(--ob-muted); text-decoration: none; margin-left: auto; }
    .lnk:hover { color: var(--ob-accent-ink); }

    /* Delete on X (2026-09-24): quieter than the caption - lighter, and the muted grey -
       because it is the rare, final thing on the line. It sits at the right beside View on X,
       the two ways to X together, and it takes the push to the right from View on X when it is
       there; the 50px after it plus the row's 10px gap keep the two a clear 60px apart, so the
       final one is not a slip away from the harmless one. It warms to the stop colour only
       under the pointer, and asks before it does anything. */
    .del {
        font: inherit;
        font-size: 12.5px;
        font-weight: 600;
        color: var(--ob-muted);
        background: none;
        border: 0;
        padding: 0;
        margin-left: auto;
        cursor: pointer;
    }
    .del + .lnk { margin-left: 50px; }
    .del:hover { color: var(--ob-rose-ink); }
    .del:focus-visible { outline: 2px solid var(--ob-accent-ring); outline-offset: 2px; border-radius: 3px; }
}
.ob-detail.stopped .ob-dfoot .t { color: var(--ob-rose-ink); }

/* The raw request and the raw response, in the picture's place. NO CEILING, AND SO NO SCROLL
   OF THEIR OWN: the panel scrolls, the way it already does for a read whose summary is forty
   posts long. A box that scrolls inside a box that scrolls is two scrollbars for one list,
   and the tab you are on should not change how the panel moves. */
.ob-detail .ob-raw { margin: 0; max-height: none; }

/* ---- a section under a rule ---- */
.ob-detail .ob-dsect {
    margin-top: 26px;
    padding-top: 18px;
    border-top: 1px solid var(--ob-line-in);

    > .ob-lbl { margin-bottom: 12px; }
}

/* ---- 4. what happened ----
   The call as a timeline: the time in a fixed gutter so the column of them reads down the
   page, a dot for the step and a line joining it to the next, then the words. The dot's
   colour is the state it records - accent for the ask, butter for the wait, mint for a yes,
   rose for anything that stopped.

   ONE LINE HEIGHT FOR ALL THREE COLUMNS (2026-09-23). The time is 12px and the words 13px, so
   left to their own line boxes the first lines sat at different heights and the dot, placed by
   a guess of a margin, matched neither. All three are 18px lines now and the 8px dot is centred
   on one: (18 - 8) / 2 = 5px. */
.ob-tl {
    display: flex;
    flex-direction: column;

    .step {
        display: grid;
        grid-template-columns: 36px 18px minmax(0, 1fr);
        gap: 0 10px;
        align-items: start;
        min-height: 30px;
    }

    .step .t {
        font-size: 12px;
        font-weight: 700;
        color: var(--ob-muted);
        line-height: 18px;
        font-variant-numeric: tabular-nums;
        text-align: left;
    }

    .step .m { display: flex; flex-direction: column; align-items: center; align-self: stretch; }
    .step .m i {
        width: 8px;
        height: 8px;
        border-radius: 50%;
        background: var(--ob-faint-3);
        margin-top: 5px;
        flex: 0 0 auto;
    }
    .step .m i.on { background: var(--ob-accent); }
    .step .m i.ok { background: var(--ob-mint-ink); }
    .step .m i.wait { background: var(--ob-butter-ink); }
    .step .m i.bad { background: var(--ob-rose-ink); }

    .step .m .l { width: 2px; flex: 1 1 auto; background: var(--ob-line); margin: 3px 0 -1px; }
    .step:last-child .m .l { display: none; }

    .step .w { font-size: 13px; font-weight: 600; line-height: 18px; color: var(--ob-ink); padding-bottom: 10px; }
    .step .w .lt { font-weight: 600; color: var(--ob-muted); margin-left: 6px; }
}

/* ---- 5. the facts ----
   The facts row is an atom (okaybot-atoms.css, .ob-facts): the detail is the only screen that
   draws one, but it draws two of them, and a component that is laid out differently in two
   arrangements of the same widget wants one definition and one override.

   THE ACTIONS ARE IN THE PICTURE (2026-09-24): the foot of the stage's card, the same one Home
   draws (okaybot-stage.css, .ob-sfoot). The row of pills that sat under it here is gone. */

/* ============================================================
   THE TWO LAYOUTS
   ============================================================ */

/* ---- the panel: one column, top to bottom ---- */
/* THE ONE BIG BREAK. Above here is what the agent did, drawn; below is the record of it.
   Everything else on the panel is 14 to 26, so this being 44 is what tells the eye that the
   page has changed subject rather than carried on. */
.ob-detail.column > .ob-stage + .ob-dsect,
.ob-detail.column > .ob-dfoot + .ob-dsect { margin-top: 44px; }

.ob-detail.column { display: block; }

/* ---- the modal: the picture on the left, the call on the right ----
   The white box holds the switch, the title and the thing itself; the tinted column holds
   everything that is about the CALL rather than about the object, and it stays put while the
   switch changes what is in the box beside it. */
.ob-detail.modal {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) var(--ob-detail-side);
    flex: 1 1 auto;
    min-height: 0;

    .pic {
        min-width: 0;
        overflow-y: auto;
        padding: 22px 28px 24px;
    }
    /* the title is the first thing in the box now, and the tabs are the last thing before the
       picture, so the air belongs between the title and the strip rather than under it */
    .pic .h { margin: 0; font-size: 20px; font-weight: 800; letter-spacing: -.01em; line-height: 1.25; }
    .pic .ob-dswitch { margin: 22px 0 22px; }
    .pic .h .lt { font-weight: 600; color: var(--ob-muted); }

    .side {
        min-width: 0;
        overflow-y: auto;
        display: flex;
        flex-direction: column;
        gap: 18px;
        padding: 26px 24px 24px;
        background: var(--ob-card-head);
        border-left: 1px solid var(--ob-line-in);
    }
    .side .who {
        display: flex;
        align-items: center;
        flex-wrap: wrap;
        gap: 8px;
        font-size: 13.5px;
        font-weight: 700;
        padding-right: 42px;        /* the close button holds that corner */
    }
    /* the two lines sit closer to each other than to the facts below them: they are one block
       - who asked, and what it said about it */
    .side .who + .say { margin-top: -8px; }
    .side .say { font-size: 13px; font-weight: 500; line-height: 1.5; color: var(--ob-ink-2); }
    .side .ob-facts { gap: 14px 28px; }
    .side .ob-lbl { margin-bottom: 10px; }
}

/* the empty and missing states inside the modal have no columns to fill */
.ob-detail.modal > .ob-empty { grid-column: 1 / -1; padding: 40px 28px; }

/* NO FOLD. The modal keeps its two columns at every size the console is drawn at: on narrow it
   is 94% of the window with a 300 side column (okaybot-modal.css, --ob-detail-side), which
   leaves the picture 546 at the 900 floor. The one-column fold below 900 went with the phone. */
 /* ============================================
   OKAYBOT DESKTOP ONLY
   The phone is out of scope for the MVP (dev/docs/product.md). The outside site - the
   homepage, pricing, own-key, the docs and the two legal pages - reads properly on a phone
   and is left exactly as it is: somebody can learn what this is and what it costs from a
   train. Everything behind the sign-in is a desktop screen, so there are three doors to shut
   and this sheet shuts two of them.

     A click on Get started or Sign in    ->  $$.app.show_desktop_only_popup(), the shared
                                              dialog over an outside page that is rendering
                                              perfectly well behind it (okaybot-dialog.css)
     A landing straight on /signin        ->  here: the panel replaces .ob-ceremony
     A live session opened on a phone     ->  here: the panel replaces the console

   The last two are css on purpose. A guard written into the router is a list of routes
   somebody has to remember to add to; a rule that keys off what is on the screen is not.

   900px, the same fold the ceremony already used: below it the console has nowhere to put a
   200px sidebar (--ob-sidebar-w below 1600) beside a page. A window at half a 1920 screen is
   960 and gets the narrow layout (dev/docs/responsive.md). This is the floor under all three
   tiers, and no console sheet needs a rule below it.
   ============================================ */

#DesktopOnly {
    display: none;                  /* raised only by the media query below */
    position: fixed;
    inset: 0;
    z-index: 1500;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 16px;
    padding: 32px;
    text-align: center;
    background: var(--ob-canvas);
    font-family: var(--ob-font);

    .mark { width: 56px; height: 56px; }
    h1 { margin: 0; font-size: 26px; font-weight: 800; color: var(--ob-ink); }
    p { margin: 0; max-width: 34ch; font-size: 15px; font-weight: 600; line-height: 1.55; color: var(--ob-ink-2); }
    .ob-pill { margin-top: 6px; }
}

@media (max-width: 900px) {

    /* The ceremony. It is an outside page by menu (signin.js names "site"), so it cannot be
       told from /pricing by a body class - :has() asks the only question that separates them,
       which is whether the ceremony is actually on the screen. */
    .ob-root:has(.ob-ceremony) #DesktopOnly { display: flex; }
    .ob-root:has(.ob-ceremony) .ob-ceremony,
    .ob-root:has(.ob-ceremony) #Footer { display: none; }

    /* The console. body.ob-app means signed in, not "the console is showing" (sidebar.js sets
       it from $$.me.current), and a signed-in person reading /pricing carries both classes -
       so this asks for ob-app WITHOUT ob-site rather than ob-app on its own, or the outside
       site would go dark for everybody who has an account. */
    /*  ONE KNOWN HOLE, and it is not this sheet's to fix. Landing on /signin while already
        signed in bounces to '/' from inside SigninPage.route(), and the outer route finishes
        last and re-selects "site" - so the console draws with a stale ob-site on the body and
        this rule does not fire. It is visible on a desktop too, as the console with no
        sidebar, which is why the fix belongs at the bounce (bounce_signed_in on the route
        node, so the router never mounts the ceremony) rather than in a selector here. */
    body.ob-app:not(.ob-site) #DesktopOnly { display: flex; }
    body.ob-app:not(.ob-site) #Main,
    body.ob-app:not(.ob-site) #Sidebar,
    body.ob-app:not(.ob-site) #Footer { display: none; }
}
 /* ============================================
   OKAYBOT COMPOSE
   The composer (popups/compose.js, 2026-10-03): a person writing a post of their own. The item
   modal's card (okaybot-modal.css) at a writing width and with no side column - the title and the
   account's faces on one row, the tabs on a rule (okaybot-detail.css, .ob-dswitch), then the post
   open in its frame (okaybot-stage.css, .ob-frame.editing) or the Move it chooser in its place
   (okaybot-move.css).
   ============================================ */

/* A WRITING WIDTH, NOT A READING ONE. The detail is three quarters of the screen because it holds
   a call and its record side by side; this holds one post, and the chooser's picker and rail fit
   in it as they fit in the detail's white box */
.ob-modal.compose {
    width: min(94vw, 760px);
    height: min(86vh, 640px);
}

.ob-compose {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding: 22px 28px 24px;
    overflow-y: auto;
    font-family: var(--ob-font);

    /* the title and the account on one row; the close button holds the corner */
    .hrow { display: flex; align-items: center; gap: 16px; min-height: 42px; padding-right: 44px; }
    .h { flex: 1 1 auto; min-width: 0; margin: 0; font-size: 20px; font-weight: 800; letter-spacing: -.01em; line-height: 1.25; }

    /* the bar of faces on a title's row: its own height and nothing under it, as on a page head */
    .ob-facebar { height: auto; margin: 0; }

    .ob-dswitch { margin: 22px 0 22px; }

    /* the hidden tab is hidden, whatever its own display says */
    .write[hidden], .ob-moveslot[hidden] { display: none !important; }

    /* the chooser takes everything under the tabs, as Move it takes the detail's box */
    .ob-moveslot { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
    &.moving { overflow: hidden; }

    .ob-empty { padding: 8px 0; font-size: 14px; font-weight: 600; color: var(--ob-muted); }
}
 /* ============================================
   OKAYBOT CHARTS
   The segmented breakdown bar, the legend, the money bars on BY AGENT, and the charge-type
   colours that mean the same thing on every chart. design.md section 6. The stacked column
   chart itself arrives with the money in phase 9 and takes these tokens.
   ============================================ */

.charge-read_post { background: var(--ob-charge-read_post); }
.charge-post { background: var(--ob-charge-post); }
.charge-post_link { background: var(--ob-charge-post_link); }
.charge-read_user { background: var(--ob-charge-read_user); }
.charge-read_own { background: var(--ob-charge-read_own); }
.charge-reply { background: var(--ob-charge-reply); }
.charge-manage { background: var(--ob-charge-manage); }

/* WHAT IT WAS SPENT ON: one 10px bar split by charge type */
.ob-segbar {
    display: flex;
    height: 10px;
    border-radius: 999px;
    overflow: hidden;
    background: var(--ob-line);
    margin: 4px 0 14px;

    .seg { display: block; height: 100%; min-width: 2px; }
}

/* BY AGENT'S MONEY BAR WEARS THE AGENT'S OWN COLOUR (2026-09-12), not one blue for all of
   them. The stacked chart above is coloured by agent and this card is the key to it, so a row
   whose bar is a different colour from its band in the chart is a key that does not fit its
   lock. A row with no agent - a person, spending by hand - keeps the plain accent.

   THE COLOUR IS SET IN usage.html, as var(--ob-tN-chart) on the element (2026-09-25). This
   file listed .fill.t1 to .t8, and the day chart's bands the same, while the palette has fifty
   slots - so a ninth agent drew in the plain accent here and in grey in the chart. The template
   names the token for whatever slot it is handed, and the list is gone. */

/* a legend: swatch, label, amount, share */
.ob-legend {
    display: flex;
    flex-direction: column;
    gap: 9px;
    font-family: var(--ob-font);
    font-size: 13.5px;
    font-weight: 600;
    color: var(--ob-ink-2);

    .row { display: flex; align-items: center; gap: 9px; }
    .swatch { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
    .grow { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ob-ink); }
    .num { font-weight: 800; color: var(--ob-ink); }
    .pct { width: 36px; text-align: right; color: var(--ob-muted); font-variant-numeric: tabular-nums; }
    .ob-bar { margin: -2px 0 4px; }
}
.ob-legend + .ob-meta { margin-top: 12px; }

/* ============================================
   BY DAY, BY AGENT
   One column a day, one band an agent, in the agent's own tint - the same tint its square
   wears everywhere else, so the chart needs no legend of its own. The stack is ordered
   biggest-first at the foot, so a band does not jump between days.

   The heights are percentages worked out in spend.js, so the chart reflows with the card
   rather than being pinned to a pixel width. No library: this is a row of rectangles.
   ============================================ */

.ob-daychart {
    --ob-daychart-h: 172px;

    .bars {
        display: flex;
        align-items: flex-end;
        gap: 6px;
        height: var(--ob-daychart-h);
        padding-top: 4px;
        border-bottom: 1px solid var(--ob-line);
    }
    .col { flex: 1 1 0; min-width: 0; height: 100%; display: flex; align-items: flex-end; }

    /* the stack is the full height of the plot; the bands inside it are a share of the scale,
       so an empty day draws nothing at all rather than a sliver */
    .stack {
        display: flex;
        flex-direction: column-reverse;
        justify-content: flex-start;
        gap: 0;                     /* something upstream sets 16px, which put daylight in the stack */
        width: 100%;
        height: 100%;
    }
    /* the chart tints, not the square tints: --ob-tN-bg is a face's fill and is far too light
       to read as a bar. Each agent keeps the same slot it wears everywhere else, so no legend.
       The template sets the tint on each band (see BY AGENT above), falling back to this grey
       for a slot with no token - an inline var() that misses does not fall back to this rule,
       it goes transparent, so the fallback lives in the var() itself. */
    /* flex: 0 0 auto or the percentage heights get shrunk by the flex algorithm and the bands
       stop touching - a stack with daylight in it is not a stack */
    .band { display: block; width: 100%; min-height: 2px; flex: 0 0 auto; background: var(--ob-faint-2); }

    /* THE TOP OF THE COLUMN IS ROUNDED (2026-09-12), and the rounding used to be on the stack
       with overflow hidden - but the stack is the full height of the plot whatever is in it,
       so the rounded corners were up in the empty air above the bar and every column was drawn
       square. It goes on the topmost band instead: column-reverse puts that last. */
    .band:last-child { border-radius: 4px 4px 0 0; }

    /* A DAY WITH NOTHING ON IT STILL DRAWS ITS DAY (2026-09-12): a line on the baseline, so the
       column is plainly there and plainly empty. The chart ends today, so there are no days
       still to come on it (2026-10-01). */
    .nil { display: block; width: 100%; height: 2px; flex: 0 0 auto; border-radius: 2px; background: var(--ob-line); }

    /* BEFORE THE PERIOD, FADED (2026-10-01). The chart reaches back 4, 6 or 8 weeks; a whole day
       before the plan's period began is history, not what the tiles count, so its bands keep
       their agent's tint at a third of the strength */
    .col.before .band { opacity: .38; }

    /* THE PERIOD IS A GROUND (2026-10-01, Dave): a pale wash from the moment it
       started to the right-hand end, part-way into the column of the day it started on, and its
       name at the top. No line and no notch - an edge in the plot reads as data. Under the bars,
       over the card. Near the right-hand end the name sits outside the wash, to its left */
    .wash { position: absolute; top: 0; right: 0; height: var(--ob-daychart-h); background: var(--ob-chart-period); pointer-events: none; }
    /* over the bars (they sit at z 1), on a chip of the card so a tall bar never cuts it */
    .wtag { position: absolute; z-index: 3; top: 4px; left: 6px; padding: 1px 5px; border-radius: 5px; background: var(--ob-card); font-size: 10.5px; font-weight: 700; color: var(--ob-accent-ink); white-space: nowrap; }
    .wtag.flip { left: auto; right: calc(100% + 7px); }

    /* TODAY IS A SHORT BLUE LINE UNDER ITS COLUMN (2026-10-01, Dave), a little below the
       baseline so it never touches the bar. It was a faint ground behind the whole column
       (2026-09-12), which read as a second, darker wash once the period drew one. Before that
       it was an outline round the stack, a tall empty box on a day with nothing spent yet.
       Anything in the plot with a top edge reads as a bar, so the mark stays under the plot.
       Recolouring the bands is out; that would break the one-tint-one-agent rule. */
    .col.today { position: relative; }
    .col.today::after { content: ""; position: absolute; left: 0; right: 0; bottom: -7px; height: 3px; border-radius: 2px; background: var(--ob-accent); }

    .ticks { display: flex; gap: 6px; margin-top: 8px; }
    .tk {
        flex: 1 1 0;
        min-width: 0;
        font-size: 10.5px;
        font-weight: 600;
        color: var(--ob-faint-1);
        text-align: center;
        white-space: nowrap;
        overflow: hidden;
        font-variant-numeric: tabular-nums;
    }
    .tk.on { color: var(--ob-accent-ink); font-weight: 700; }

    /* a whole calendar month is thirty-one columns in the width seven had, so the gap closes
       and the axis counts in bare day numbers */
    &.dense {
        .bars, .ticks { gap: 3px; }
        .band:last-child { border-radius: 3px 3px 0 0; }

        /* a column is about twenty pixels here, so "1 Sep" and "today" were cut to "1 Se" and
           "toda" (2026-09-25). Every other day is blank on this axis, so a longer label has
           empty neighbours to spill into */
        .tk { overflow: visible; }
    }
}

/* THE SCALE, so a column means a number. The chart had no axis at all: the tallest bar was the
   only clue to how big anything was, which is no clue, because it is always the tallest. Labels
   sit on the lines rather than beside them, and the bars keep the full width by having the axis
   overlay them - a gutter would cost more room than the labels are worth. */
.ob-daychart { position: relative; }
.ob-daychart .axis { position: absolute; left: 0; right: 0; top: 0; bottom: 26px; pointer-events: none; }
.ob-daychart .axis .ax { position: absolute; left: 0; right: 0; height: 0; border-top: 1px solid var(--ob-line); }
.ob-daychart .axis .ax:first-child { border-top-style: dashed; }
/* the top label hangs UNDER its line - above it, it lands in the card heading */
.ob-daychart .axis .ax:first-child i { bottom: auto; top: 2px; }
.ob-daychart .axis .ax i {
    position: absolute; left: 0; bottom: 2px;
    font-size: 10.5px; font-weight: 700; font-style: normal;
    color: var(--ob-muted); background: var(--ob-card); padding: 0 5px 0 0;
    font-variant-numeric: tabular-nums;
}
.ob-daychart .bars { position: relative; z-index: 1; }
 /* ============================================
   OKAYBOT - THE CALENDAR
   The two drawings a post sits in - the Month grid and the hour rail that Week, Days and Day
   share - with the picker, the options and the Held list in the panel beside them.
   Every card carries the account's circle, because the page spans every account.

   A POST IS THE POST CARD (2026-09-25; cards/postcard_card.html, okaybot-postcard.css), filled
   with its state's colours: the bigger card, three lines of the post, or the one-line chip, as
   the size switch under each view's zoom buttons says. What a card IS lives in that sheet; where
   it SITS - down the rail, in a cell, in the air during a drag, stepped along a stack - lives
   here.
   On the clock no card carries a time: the tooltip and its place on the rail say when. In the
   list mode (2026-09-29) every card does, in a tab in its corner, because a place in a list says
   the order and not the hour.

   MONTH is a seven-column grid inside one white card. A day of another month is on a plainly
   greyer ground, so the month has an edge against the page rather than fading into it.

   THE RAIL is Week, Days and Day: midnight to midnight, one column per day sharing the hours,
   and every post a card at the minute it happens. Today's column is tinted and carries the now
   line. (A week of cards and a list under a two-week strip were the views before it; both went
   on 2026-09-23 with the widgets that drew them.)
   ============================================ */

/* the three views share a root, so a page can swap one for another in one slot */
.ob-calview { min-width: 0; }

/* the whole control, mounted in PageHead's one control slot */
.ob-calhead { display: flex; align-items: center; gap: 10px; }

/* THE ACCOUNTS BESIDE IT (2026-09-23): the list pages' bar of faces (okaybot-listpage.css), on
   the header's line, the same size as on every other page - accounts only, so no agent run */
.ob-calhead .ob-facebar { margin-right: 6px; }

/* THE MODE (2026-09-29): the clock and the list, two glyphs in a pill of their own right of the
   five views (calhead.html) */
.ob-calhead .ob-seg.calmode .item { display: grid; place-items: center; width: var(--ob-seg-h); padding: 0; color: var(--ob-muted); }
.ob-calhead .ob-seg.calmode .item:hover { color: var(--ob-ink); }
.ob-calhead .ob-seg.calmode .item.on { color: var(--ob-ink); }
.ob-calhead .ob-seg.calmode svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }

/* ---- the month ---- */
.ob-cal {
    background: var(--ob-card);
    border: 1px solid var(--ob-line);
    border-radius: var(--ob-radius);
    overflow: hidden;

    .dow { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); border-bottom: 1px solid var(--ob-line-in); }
    .dow span { padding: 10px 12px; font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ob-muted); }

    .grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); grid-auto-rows: var(--mrow, 132px); }

    .cell {
        padding: 8px 8px 6px;
        border-right: 1px solid var(--ob-line-in);
        border-bottom: 1px solid var(--ob-line-in);
        display: flex; flex-direction: column; gap: 4px;
        min-width: 0;
        overflow: hidden;
        /* the drag draws inside a cell - its wash under the chips, its line over them */
        position: relative;
        isolation: isolate;
    }
    .cell:nth-child(7n) { border-right: 0; }
    .grid .cell:nth-last-child(-n+7) { border-bottom: 0; }

    .cell .d { font-size: 12.5px; font-weight: 700; color: var(--ob-ink-2); padding: 0 4px 2px; display: flex; align-items: center; justify-content: space-between; }
    .cell.past .d { color: var(--ob-faint-1); }

    .cell.out { background: var(--ob-cal-out); border-color: var(--ob-cal-out-line); }
    .cell.out .d { color: var(--ob-faint-2); }

    .cell.today { background: var(--ob-accent-tint); box-shadow: inset 0 0 0 1.5px var(--ob-accent-ring); }
    .cell.today .d b { display: inline-flex; align-items: center; justify-content: center; min-width: 22px; height: 22px; padding: 0 6px; border-radius: 999px; background: var(--ob-accent); color: #fff; font-weight: 800; }
    .cell.today .d .t { font-size: 10.5px; font-weight: 700; color: var(--ob-accent-ink); }

    /* the overflow line when a day has more posts than the cell is tall. It and the date open
       the day (CalMonthCell.open_day); the wrapper that carries the click takes no room */
    .cell .more { padding: 0 4px; font-size: 11px; font-weight: 700; color: var(--ob-muted); cursor: pointer; }
    .cell .more:hover { color: var(--ob-accent-ink); }
    .cell .morehit { display: contents; }
    .cell .d { cursor: pointer; }
    .cell .d:hover > span:first-child { color: var(--ob-accent-ink); }
}

/* THE CARDS ON THE MONTH are the post card (cards/postcard_card.html, okaybot-postcard.css): the
   bigger card or the chip, as the size switch under the zoom buttons says (2026-09-25). A cell
   stands them in a column and never squeezes one to fit - what does not fit is "+3 more" */
.ob-cal .cell .ob-pc { flex: 0 0 auto; }

.ob-callegend {
    display: flex; gap: 14px; margin-top: 12px;
    font-size: 12px; font-weight: 600; color: var(--ob-muted);

    span { display: inline-flex; align-items: center; gap: 6px; }
    i { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
    i.went { background: var(--ob-mint-bg); }
    i.going { background: var(--ob-card); box-shadow: inset 0 0 0 1.5px var(--ob-accent-ring); }
    i.wait { background: var(--ob-butter-bg); }
    i.stop { background: var(--ob-rose-bg); }
    i.gone { background: var(--ob-card); box-shadow: inset 0 0 0 1px var(--ob-grey-edge); }
}

/* ---- the rail: one day, midnight to midnight, and one column per day sharing the hours ----

   WEEK, DAYS AND DAY ARE ONE DRAWING (2026-09-21), with seven columns, three or one. It replaced
   the seven stacks of cards that were here before, which could say what was on Thursday and not
   what time: drawn on a rail the shape of the day is the picture.

   ONE SCREEN, ALWAYS. The height is whatever the page has left under its header and the day
   never scrolls, which is about half a pixel a minute - so a post is a 22px chip, and two less
   than forty minutes apart overlap. They are OPAQUE and rimmed in the ground they sit on, so a
   stack reads as chips on chips; the later one is on top, because the day runs downwards.

   UNLESS IT IS STRETCHED (2026-09-23). The hour gutter is a handle, and --stretch says how many
   of those screens the day is tall: 1 is the one screen above, and anything more lets the page
   scroll, with the day headings held at the top so a column still says which day it is. The
   whole box is multiplied rather than the rail alone, so one screen stays one exact number.

   NO HOUR IS SHADED. A faint line an hour and a numeral on each, the same from 00 to 24: it is
   not ours to say when a post should go out. The one thing drawn differently is Now, on today. */

.ob-calrail {
    display: flex;
    flex-direction: column;
    min-width: 0;
    /* the page's own header and padding, taken off the screen: the body pad, the 40px head, the
       gap under it, and the left column's 60px foot (.ob-calpage > .left) */
    --fit: max(460px, calc(100vh - var(--ob-body-pad-top) - var(--ob-head-h) - var(--ob-head-gap) - 60px));
    height: calc(var(--fit) * var(--stretch, 1));
    /* the browser's scroll anchoring would chase the growing rail and move the page under the
       hand that is stretching it */
    overflow-anchor: none;

    /* the gutter the hour numerals stand in, on both rows so the columns line up with them */
    .heads, .cols { display: flex; gap: 6px; }
    .heads { padding-left: 36px; flex: 0 0 auto; }
    &.stretched .heads { position: sticky; top: 0; z-index: 90; padding-top: 10px; margin-top: -10px; background: var(--ob-canvas); }
    &.few .heads, &.few .cols { gap: 10px; }
    &.one .heads, &.one .cols { gap: 0; }
}

/* a column and its heading, the rail's and the list mode's alike */
:is(.ob-calrail, .ob-callist) {
    .col { flex: 1 1 0; min-width: 0; position: relative; }

    .dh { display: flex; align-items: baseline; gap: 5px; padding: 0 4px 6px; font-size: 11.5px; font-weight: 700; color: var(--ob-muted); white-space: nowrap; overflow: hidden; }
    .dh b { font-size: 14px; font-weight: 800; color: var(--ob-ink); }
    .dh.past b { color: var(--ob-faint-1); }
    .dh.today b { display: inline-flex; align-items: center; justify-content: center; min-width: 22px; height: 22px; padding: 0 6px; border-radius: 999px; background: var(--ob-accent); color: #fff; }
}

/* half a chip of room at either end, so midnight and 23:59 are not cut in half by the edge */
.ob-calrail .railwrap {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    padding: 12px 0 12px 36px;
}

.ob-calrail .rail {
    position: relative;
    height: 100%;

    .hr { position: absolute; left: 0; right: 0; height: 1px; background: var(--ob-line-in); transform: translateY(-.5px); }
    .hr.big { left: -5px; background: var(--ob-line); }
    .hl {
        position: absolute;
        right: calc(100% + 9px);
        transform: translateY(-50%);
        font-size: 10.5px;
        font-weight: 700;
        color: var(--ob-muted);
        font-variant-numeric: tabular-nums;
        line-height: 1;
    }

    .cols { position: absolute; inset: 0; }

    /* a column is a day: a hairline down its left edge, and today on the barely-there tint the
       month grid gives its own cell */
    /* ISOLATED: a card's z-index is its place in the column and can run past the sticky
       headings' 90, so each column keeps its cards' numbers to itself */
    .dcol { border-left: 1px solid var(--ob-line-in); isolation: isolate; }
    .dcol.today { background: var(--ob-accent-tint); box-shadow: inset 1.5px 0 0 var(--ob-accent-ring); }
}

/* THE THREE HEIGHTS, stacked in the page's own left margin beside the numerals, the first
   hanging from the 00 line. The rail they ride is the whole height of the day, so the stack can stick: once the
   day scrolls it stays just under the sticky headings (10px of their padding and a 28px row).
   The month draws the same stack beside its grid (2026-09-24, .ob-monthwrap below). */
.ob-calrail .zoomrail,
.ob-monthwrap .zoomrail,
.ob-callist .zoomrail {
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(-1 * var(--ob-body-pad-left));
    width: var(--ob-body-pad-left);
    z-index: 6;
    pointer-events: none;
}
.ob-calrail .zoomstack,
.ob-monthwrap .zoomstack,
.ob-callist .zoomstack {
    position: sticky;
    top: 42px;
    /* the rail's 12px of padding, less the pill's own 2px: the first button's top is on the 00
       line, as the month's is on the first week's (2026-09-24), so a switch between them does
       not move the stack */
    margin: 10px auto 0;
    width: max-content;
    pointer-events: auto;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.ob-calrail .zoom,
.ob-monthwrap .zoom,
.ob-callist .zoom {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 2px;
    border-radius: 999px;
    background: var(--ob-line);

    /* WORDS, NOT GLYPHS (2026-09-24): Fit, 2x, 4x. Three arrows nobody could tell apart said
       nothing without their tooltips */
    .z {
        display: grid;
        place-items: center;
        min-width: 26px;
        height: 26px;
        padding: 0 4px;
        border: 0;
        border-radius: 999px;
        background: transparent;
        font-family: var(--ob-font);
        font-size: 10.5px;
        font-weight: 800;
        letter-spacing: -.01em;
        color: var(--ob-muted);
        cursor: pointer;
    }
    .z:hover { color: var(--ob-ink); }
    .z.on { background: var(--ob-card); color: var(--ob-accent-ink); }

    /* THE CARDS' SIZE (2026-09-25), its own group under the heights: the bigger card, and the
       chip. Glyphs here, because the two are pictures of what they give you */
    .zs {
        display: grid;
        place-items: center;
        width: 26px;
        height: 26px;
        padding: 0;
        border: 0;
        border-radius: 999px;
        background: transparent;
        color: var(--ob-muted);
        cursor: pointer;
    }
    .zs svg { width: 14px; height: 14px; fill: currentColor; }
    .zs:hover { color: var(--ob-ink); }
    .zs.on { background: var(--ob-card); color: var(--ob-accent-ink); }
}

/* THE MONTH, TALLER (2026-09-24). The grid's rows are --mrow tall, set by CalMonth from the
   window (Fit) times its stretch, and a taller day holds more chips before "+3 more". The same
   three buttons sit in the margin beside it, level with the first week, and the margin is the
   same handle the rail's gutter is: drag down to make the weeks taller. */
.ob-monthwrap { position: relative; }
.ob-monthwrap .zoomstack { top: 12px; margin-top: 38px; }
.ob-monthwrap .axis {
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(-1 * var(--ob-body-pad-left));
    width: var(--ob-body-pad-left);
    z-index: 5;
    cursor: ns-resize;
    touch-action: none;
}
.ob-monthwrap.stretching { user-select: none; }

/* THE HANDLE: everything left of the rail - the numerals' gutter and the page's margin out to
   the sidebar - the whole height of the day, drawn as nothing */
.ob-calrail .axis {
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(-1 * var(--ob-body-pad-left));
    width: calc(var(--ob-body-pad-left) + 36px);
    z-index: 5;
    cursor: ns-resize;
    touch-action: none;
}
.ob-calrail .hl { pointer-events: none; }
.stretching .ob-calrail, .stretching .ob-calrail * { cursor: ns-resize !important; user-select: none; }

/* the page held by the bare ground of a day and dragged up or down (calpan.js): the hand closed,
   and nothing selected on the way */
html.ob-panning, html.ob-panning * { cursor: grabbing !important; user-select: none; }

/* now, on today, at the minute it is - not between two cards, which is where the stack drew it */
.ob-calrail .nowrail {
    position: absolute;
    left: -4px;
    right: 0;
    height: 2px;
    border-radius: 1px;
    background: var(--ob-accent);
    opacity: .6;
    transform: translateY(-50%);
    z-index: 850;       /* over every card in the column, a hovered one too */
}
.ob-calrail .nowrail::before { content: ''; position: absolute; left: 0; top: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--ob-accent); }

/* THE CARD ON THE RAIL (the post card, okaybot-postcard.css): placed down the day as a share of
   it, opaque and rimmed in the ground it sits on, so a stack reads as cards on cards and the later
   one sits on top.

   A CHIP IS CENTRED ON ITS MINUTE, as it always was. THE BIGGER CARD HANGS FROM IT (2026-09-25),
   as an event does on any calendar: the time a post goes out is the top edge of the card that
   says it, and its three lines run on down the day. */
.ob-calrail .ob-pc {
    position: absolute;
    /* --lean is how far the card steps right for the cards it still overlaps, written by
       CalRailCol.stack after every draw and every change of size */
    left: calc(5px + var(--lean, 0px));
    right: 2px;
    width: auto;
    box-shadow: 0 0 0 2px var(--ob-canvas);
}
.ob-calrail .ob-pc.xs { transform: translateY(-50%); }
.ob-calrail .ob-pc.going, .ob-calrail .ob-pc.held { box-shadow: inset 0 0 0 1.5px var(--ob-accent-ring), 0 0 0 2px var(--ob-canvas); }

/* THE ONE HOVER, at card size, and it brings the card to the front of its stack so a covered one
   can be read, and grabbed, without moving anything. Important because the card's own z-index is
   inline (its place in the column), which no selector beats - until 2026-09-25 this lift did not
   happen at all. The column is its own stacking context, so the number only has to beat the
   cards beside it and stays under the sticky headings. */
.ob-calrail .ob-pc:hover { box-shadow: inset 0 0 0 1.5px var(--ob-accent), 0 0 0 2px var(--ob-canvas), 0 3px 10px rgba(79, 102, 240, .26); z-index: 800 !important; }
.ob-calrail .ob-pc:focus-visible { z-index: 800 !important; }

/* ---- a stack too deep for its column (2026-09-25, calrailcol.js) ----
   In Week and Days a stack may take a quarter of its column. The cards that would go deeper are
   not drawn, and "+N" stands at the first of them, over the right of the stack: it opens the day
   in the Day view, where the column is wide enough. The Day view squeezes instead, so it has none. */
.ob-calrail .ob-pc.spilt { display: none; }

.ob-calrail .stackmore {
    position: absolute;
    right: 5px;
    z-index: 830;
    height: 18px;
    min-width: 26px;
    margin-top: 4px;
    padding: 0 7px;
    border: 0;
    border-radius: 999px;
    background: var(--ob-accent);
    color: #fff;
    box-shadow: 0 0 0 2px var(--ob-canvas);
    font-family: var(--ob-font);
    font-size: 11px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    line-height: 18px;
    cursor: pointer;

    &:hover { background: var(--ob-accent-ink); }
    &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 1px; }
}
/* a chip is centred on its minute, so the "+N" is too */
.ob-calrail.cards-chip .stackmore { margin-top: 0; transform: translateY(-50%); }
.ob-calrail .dcol.today .stackmore { box-shadow: 0 0 0 2px var(--ob-accent-tint); }

/* today's tinted column is not the page's grey, so a card standing on it needs its own rim */
.ob-calrail .dcol.today .ob-pc { box-shadow: 0 0 0 2px var(--ob-accent-tint); }
.ob-calrail .dcol.today .ob-pc.going { box-shadow: inset 0 0 0 1.5px var(--ob-accent-ring), 0 0 0 2px var(--ob-accent-tint); }
.ob-calrail .dcol.today .ob-pc:hover { box-shadow: inset 0 0 0 1.5px var(--ob-accent), 0 0 0 2px var(--ob-accent-tint), 0 3px 10px rgba(79, 102, 240, .26); }

/* a span with nothing on it says so once, over the middle of the rail, rather than once per day */
.ob-calrail .none {
    position: absolute;
    left: 36px;
    right: 0;
    top: 42%;
    text-align: center;
    font-size: 13px;
    font-weight: 600;
    color: var(--ob-faint-2);
}

/* SEVEN COLUMNS ON A LAPTOP used to drop the words and keep the time. The time went from the chip
   on 2026-09-23, so the words are what is left to show, however few of them fit. */

/* ---- the list mode: Week, Days and Day as lists (2026-09-29, callist.js) ----

   THE RAIL'S DAYS WITHOUT ITS CLOCK. The same columns under the same headings, but a column is its
   day's posts one under the other in time order, each card wearing its time in its corner, so
   nothing covers anything. No hour gutter: the columns start at the rail's left edge, and the one
   control in the margin is the cards' size.

   EVERY DAY IS ONE SCREEN TALL, the rail's own sum, AND ONLY ITS OWN CARDS MAKE IT LONGER (Dave,
   2026-09-29). A day's height is its 24 hours when a post is dragged down it (CalCellDrag), so a
   quiet day beside a busy one must not be stretched to the busy one's length: its evening would be
   below the fold, where nothing can be dropped. So the columns do not stretch to the tallest; each
   is a screen, or as long as its own cards, and the page scrolls with the headings held at the top. */
.ob-callist {
    /* the carried chip of a drag is placed in it (CalCellDrag) */
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    --fit: max(460px, calc(100vh - var(--ob-body-pad-top) - var(--ob-head-h) - var(--ob-head-gap) - 60px));
    --list-heads: 28px;         /* the headings' row, which a day's screen is measured under */
    min-height: var(--fit);

    .heads, .cols { display: flex; gap: 6px; }
    /* the row's own height, pinned, so a day under it is exactly the rest of the screen: the 10px of
       padding over it is taken back by its margin, there only for the sticky row to cover */
    .heads { flex: 0 0 auto; position: sticky; top: 0; z-index: 90; height: calc(var(--list-heads) + 10px); padding-top: 10px; margin-top: -10px; background: var(--ob-canvas); }
    &.few .heads, &.few .cols { gap: 10px; }
    &.one .heads, &.one .cols { gap: 0; }
}

.ob-callist .listwrap { position: relative; flex: 1 1 auto; display: flex; flex-direction: column; }
/* NOT STRETCHED: each day is as tall as itself (above) */
.ob-callist .cols { flex: 1 1 auto; align-items: flex-start; }

/* a column is a day: the rail's hairline down its left edge and today's tint, and its cards in a
   column of their own with a little room at the foot. ISOLATED, as a month cell is, so a drag's
   wash sits under the cards and its line over them inside the day */
.ob-callist .lcol {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 8px 5px 24px;
    border-left: 1px solid var(--ob-line-in);
    isolation: isolate;
    /* one screen under the headings, longer only for its own cards */
    min-height: calc(var(--fit) - var(--list-heads));
}
.ob-callist.cards-chip .lcol { gap: 4px; }
.ob-callist .lcol.today { background: var(--ob-accent-tint); box-shadow: inset 1.5px 0 0 var(--ob-accent-ring); }
.ob-callist .lcol .ob-pc { flex: 0 0 auto; }

/* the first card stands level with the size switch, as the rail's 00 line does with its buttons */
.ob-callist .zoomstack { margin-top: 6px; }

/* now, between what has gone and what is to come: the rail's line, reaching back over the
   column's edge the same 4px, with its dot */
.ob-callist .nowline {
    position: relative;
    flex: 0 0 auto;
    height: 2px;
    margin: 1px -5px 1px -9px;
    border-radius: 1px;
    background: var(--ob-accent);
    opacity: .6;
}
.ob-callist .nowline::before { content: ''; position: absolute; left: 0; top: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--ob-accent); }

/* a span with nothing on it says so once, over the middle, as the rail does */
.ob-callist .none {
    position: absolute;
    left: 0;
    right: 0;
    top: 42%;
    text-align: center;
    font-size: 13px;
    font-weight: 600;
    color: var(--ob-faint-2);
}

/* ---- moving a post by dragging it (2026-09-21) ----

   Only a queued post takes a grip: everything else on the rail is a record, and Posts.Move
   refuses it anyway.

   WHERE IT MAY GO IS LIT, RATHER THAN WHERE IT MAY NOT. Every column that can take the post
   wears a pale accent wash - a whole future day, and today from five minutes' time to midnight.
   A day that has already run is left exactly as it is drawn the rest of the time. It is then the
   only part of the screen that is NOT lit, which says "not there" without hatching it, striking
   it or greying it: the past is not a warning, it is just the past. */

.ob-calrail.dragging, .ob-calrail.dragging .ob-pc { cursor: grabbing; }

.ob-calrail .dropzone {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    background: var(--ob-accent-bg);
    opacity: .75;
    z-index: 0;
    pointer-events: none;
}

/* the column under the pointer, which is the one that will take it */
.ob-calrail .dcol.over { box-shadow: inset 0 0 0 1.5px var(--ob-accent-ring); border-radius: 10px; }

/* where it came from, held until the drop - so letting go over nothing puts it back and the
   place it left is never in doubt */
.ob-calrail .ob-pc.ghosted, .ob-calheld .ob-pc.ghosted, .ob-monthwrap .ob-pc.ghosted, .ob-callist .ob-pc.ghosted {
    background: none;
    box-shadow: inset 0 0 0 1.5px var(--ob-accent-ring);
    color: var(--ob-faint-1);
}
.ob-calrail .ob-pc.ghosted .face, .ob-calheld .ob-pc.ghosted .face, .ob-monthwrap .ob-pc.ghosted .face, .ob-callist .ob-pc.ghosted .face { opacity: .35; }

/* THE ONE IN THE AIR, and the time it is being given. The stamp is the loudest thing on it,
   because the time is what is being set - the same rule the Move it tab's card follows. */
.ob-calrail .ob-pc.flying {
    right: auto;
    z-index: 80;
    pointer-events: none;
    overflow: visible;
    box-shadow: inset 0 0 0 1.5px var(--ob-accent), 0 10px 24px rgba(79, 102, 240, .34);
}
.ob-calrail .ob-pc.flying.nowhere { opacity: .7; box-shadow: 0 10px 24px rgba(22, 24, 29, .2); }

.ob-calrail .dragstamp {
    position: absolute;
    left: 0;
    top: calc(100% + 5px);
    display: inline-flex;
    align-items: center;
    height: 22px;
    padding: 0 9px;
    border-radius: 7px;
    background: var(--ob-ink);
    color: #fff;
    font-size: 11.5px;
    font-weight: 800;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.ob-calrail .dragstamp:empty { display: none; }

/* where it will land */
.ob-calrail .dragline { position: absolute; left: 0; right: 0; height: 2px; border-radius: 1px; background: var(--ob-accent); transform: translateY(-50%); z-index: 950; pointer-events: none; }
.ob-calrail .dragpin { position: absolute; left: -5px; width: 11px; height: 11px; border-radius: 50%; background: var(--ob-accent); box-shadow: 0 0 0 3px var(--ob-card); transform: translateY(-50%); z-index: 951; pointer-events: none; }

/* ...and the picker, which takes a drop of its own: the day keeps the post's time, so the drop
   changes the date and nothing else */
.ob-calpicker.dropping .pk-day.past, .ob-calpicker.dropping .pk-day.off { opacity: .4; }
.ob-calpicker .pk-day.drop { background: var(--ob-accent-bg); box-shadow: inset 0 0 0 1.5px var(--ob-accent); border-radius: 10px; }
.ob-calpicker .pk-day.drop .n { color: var(--ob-accent-ink); }

/* ---- and on the month (2026-09-24), and the list mode's columns (2026-09-29) ----

   THE SAME DRAWING, CONDENSED. A cell or a list column is a day with no hours on it, so how far
   down it the post is let go is its time (CalCellDrag.move): the days that can take it wear the
   rail's wash, the one under the pointer its ring, and the chip sits on a line across that day
   with the time in a dark tag in front of its words - the rail's stamp, folded into the chip. */

.ob-monthwrap.dragging, .ob-monthwrap.dragging .ob-pc,
.ob-callist.dragging, .ob-callist.dragging .ob-pc { cursor: grabbing; user-select: none; }

.ob-cal .cell.drop-ok::before,
.ob-callist .lcol.drop-ok::before {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--ob-accent-bg);
    opacity: .75;
    z-index: -1;
    pointer-events: none;
}
.ob-cal .cell.over, .ob-callist .lcol.over { box-shadow: inset 0 0 0 1.5px var(--ob-accent); }

.ob-cal .dragline, .ob-callist .dragline { position: absolute; left: 0; right: 0; height: 2px; background: var(--ob-accent); transform: translateY(-50%); z-index: 2; pointer-events: none; }

/* where it came from is the rail's ghost, above. THE ONE IN THE AIR is always the chip
   (CalMonth.carried), with the time it is being given in a dark tag in front of its words */
.ob-monthwrap .ob-pc.flying,
.ob-callist .ob-pc.flying {
    position: absolute;
    z-index: 80;
    pointer-events: none;
    align-items: center;
    transform: translateY(-50%);
    box-shadow: inset 0 0 0 1.5px var(--ob-accent), 0 10px 24px rgba(79, 102, 240, .34);

    .dt {
        flex: 0 0 auto;
        display: inline-flex;
        align-items: center;
        height: 16px;
        margin: -1px 0;
        padding: 0 5px;
        border-radius: 5px;
        background: var(--ob-ink);
        color: #fff;
        font-size: 10.5px;
        font-weight: 800;
        font-variant-numeric: tabular-nums;
        opacity: 1;
    }
    .dt:empty { display: none; }
}
.ob-monthwrap .ob-pc.flying.nowhere, .ob-callist .ob-pc.flying.nowhere { opacity: .7; box-shadow: 0 10px 24px rgba(22, 24, 29, .2); }
/* a list's headings stick at 90 while its days scroll, and the chip in the air goes over them */
.ob-callist .ob-pc.flying { z-index: 95; }

/* ============================================
   THE PICKER
   Six or seven weeks of days in the panel, and the only navigator the calendar has. It replaced
   the header's arrows and the List's own fortnight strip on 2026-09-11: one grid says where you
   are whichever way the body is drawing it, and drawing whole weeks either side of the selection
   means the end of last month and the start of next are on screen without moving. Month takes
   the seventh row because a month that starts on the 30th needs it (CalPicker.grid).

   THE RANGE IS A BAND, NOT SEVEN BOXES. A week or a month is one continuous thing, so the
   selected days share a tint and each line of them rounds off at its ends. That is what makes
   "the week of the 7th" read as a week rather than as seven days that happen to be shaded - and
   the hairline down every join is what keeps them seven days inside it.
   ============================================ */

.ob-calpicker {
    font-family: var(--ob-font);

    .pk-head {
        display: flex;
        align-items: center;
        gap: 12px;
        margin-bottom: 14px;
        padding-right: 88px;        /* the calendar page's Options button owns that corner */
    }
    .pk-head .m { flex: 0 0 auto; font-size: 14px; font-weight: 800; letter-spacing: -.01em; color: var(--ob-ink); white-space: nowrap; }
    .pk-head .grow { flex: 1 1 auto; }

    /* THE ARROWS ARE BESIDE THE LABEL, NOT ACROSS THE PANEL FROM IT (2026-09-12). They step the
       span the label names; sat at the far right they read as two loose controls of the panel
       rather than as the two ends of the thing next to them. Filled, because a chevron on the
       bare card is a mark and a chevron on a tile is a button. */
    .pk-head .nav { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
    .pk-head .b {
        display: grid;
        place-items: center;
        width: 26px;
        height: 26px;
        border: 0;
        border-radius: 9px;
        background: var(--ob-canvas);
        color: var(--ob-muted);
        cursor: pointer;
    }
    .pk-head .b:hover { background: var(--ob-accent-bg); color: var(--ob-accent-ink); }
    .pk-head .b svg { width: 14px; height: 14px; }

    .pk-dow {
        display: grid;
        grid-template-columns: repeat(7, 1fr);
        margin-bottom: 4px;
    }
    .pk-dow span {
        text-align: center;
        font-size: 10px;
        font-weight: 700;
        letter-spacing: .06em;
        color: var(--ob-faint-1);
    }

    .pk-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px 1px; }

    .pk-day {
        position: relative;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 4px;
        min-height: 39px;
        padding: 4px 0;
        border: 0;
        background: transparent;
        cursor: pointer;
        border-radius: 0;
    }

    /* THE NUMERAL SITS IN A SQUARE, ALWAYS. Today's ring and today's fill are drawn on this box,
       and a box sized by padding round a digit is never round: "12" is wider than "1" and both
       are shorter than they are wide, so the ring came out an oval sitting low. A fixed square
       with the digits centred in it makes the ring a circle around the number rather than a
       shape fitted to it - and every day carries the same box, so a row with today in it is no
       taller than the rest. 24 + the dots + the button's own padding is the 42px the cell had. */
    .pk-day .n {
        flex: 0 0 auto;
        display: grid;
        place-items: center;
        width: 24px;
        height: 22px;
        border-radius: 8px;
        font-size: 13.5px;
        font-weight: 700;
        color: var(--ob-ink);
        font-variant-numeric: tabular-nums;
        line-height: 1;
        padding: 1px 1px 0 0;
    }
    .pk-day.other .n { color: var(--ob-faint-2); }
    .pk-day.past .n { color: var(--ob-muted); }
    .pk-day.other.past .n { color: var(--ob-faint-2); }
    .pk-day:not(.in):hover { background: var(--ob-canvas); border-radius: 9px; }

    /* BELOW THE FLOOR (2026-09-20): a day that cannot be picked - on the Move it tab, one that
       has already run. Faint rather than hidden, so the week keeps its shape and today keeps
       its place in it; dead to the pointer, so nothing lights up under it. Its dots go with it. */
    .pk-day.off { opacity: .32; pointer-events: none; }
    .pk-head .b[disabled] { opacity: .32; pointer-events: none; }

    /* THE ROWS ARE ON THEIR WAY (2026-09-20): the grid is drawn at once and the numerals wait
       faint for the answer; the render that brings it fades the dots in. Additive - nothing on
       the grid changes colour twice. */
    &.pending .pk-day .n { opacity: .45; }
    &.fresh .pk-dots i { animation: ob-dot-in .32s ease-out both; }

    /* THE BAND IS THE ACCENT ITSELF, not a wash of it. This is the one thing on the panel that
       says what you are looking at, and a tint so pale it could be a hover state was not saying
       it. Same blue as a primary button, and the ink goes to the inverse over it. */
    .pk-day.in { background: var(--ob-accent); }
    .pk-day.in .n { color: #fff; }
    .pk-day.in.past .n { color: rgba(255, 255, 255, .72); }

    /* A DAY IN THE BAND IS STILL A DAY. One flat blue block of seven or thirty made the band
       read as a bar with numbers on it, so each join carries a hairline of the same white the
       numbers are in - the grid under the selection, not a second colour on top of it. The
       Monday of every row is skipped: its left edge is the band's own edge. */
    .pk-day.in + .pk-day.in:not(:nth-child(7n + 1)) {  }

    /* HOVERING A SELECTED DAY LIFTS THE BLUE (2026-09-12). It used to take the plain hover with
       it - a grey tile behind the blue one, which showed as white rounded corners eating into
       the band. Inside the selection the hover has to be a shade of the selection. */
    .pk-day.in:hover { background: var(--ob-accent-lift); }

    /* the ends of the band round off, and so does the end of every ROW of it: a month runs over
       four or five lines, and each line is a bar rather than a block cut square at the margin */
    .pk-day.in.first, .pk-day.in:nth-child(7n + 1) { border-top-left-radius: 9px; border-bottom-left-radius: 9px; }
    .pk-day.in.last, .pk-day.in:nth-child(7n) { border-top-right-radius: 9px; border-bottom-right-radius: 9px; }

    /* today is a ring, not a fill: a fill here would fight the band it usually sits inside. The
       ring is inset so it is drawn on the square rather than around it, and stays a circle. */
    .pk-day.today .n {
        color: var(--ob-accent-ink);
        box-shadow: inset 0 0 0 1.5px var(--ob-accent-ring);
    }

    /* ...but INSIDE the band it is a fill, the other way up: white oval, blue numeral. A ring of
       white on blue was a hollow pill next to the solid dots under it and read as the weaker of
       the two marks, when today is the strongest thing on the grid. */
    .pk-day.today.in .n { background: #fff; color: var(--ob-accent); box-shadow: none; }

    .pk-dots {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 2px;
        min-height: 5px;
    }
    .pk-dots i {
        width: 4px;
        height: 4px;
        border-radius: 50%;
        background: var(--ob-faint-2);
    }
    .pk-dots i.went { background: var(--ob-mint-ink); }
    .pk-dots i.going { background: var(--ob-accent); }
    .pk-dots i.wait { background: var(--ob-butter-ink); }
    .pk-dots i.stop { background: var(--ob-rose-ink); opacity: .55; }
    .pk-dots i.more { background: var(--ob-faint-1); width: 3px; height: 3px; }

    /* on the band, a dot in its own colour would disappear into the blue - they all go white
       and lean on opacity instead, because inside the selection the question is how many, not
       which: which is what the grid underneath is for */
    .pk-day.in .pk-dots i { background: rgba(255, 255, 255, .92); }
    .pk-day.in .pk-dots i.stop { background: rgba(255, 255, 255, .5); opacity: 1; }
    .pk-day.in .pk-dots i.more { background: rgba(255, 255, 255, .7); }
}

@keyframes ob-dot-in { from { opacity: 0; transform: scale(.5); } }

/* ============================================
   THE CALENDAR PAGE
   Posts and one account, which are the same screen with a filter on it. Shaped like Activity:
   a body and a full-height panel, not a body and a rail of cards. The panel is the sidebar's
   white on the other side of the screen, flush to the top, the right edge and the foot, with
   its own scroll.

   75/25, because a month grid is seven columns of real text and wants the room, while the
   picker is a fixed thing about 280px wide and gains nothing from more.
   ============================================ */

.ob-calpage {
    display: grid;
    /* a fixed panel rather than a share of the page: the picker is a fixed thing seven columns
       wide and gains nothing from a wider screen, while the month grid gains everything */
    grid-template-columns: minmax(0, 1fr) var(--ob-side-panel-w);
    min-height: 100vh;
    font-family: var(--ob-font);
    color: var(--ob-ink);
}

.ob-calpage > .left {
    min-width: 0;
    padding: var(--ob-body-pad-top) var(--ob-body-pad-right) 60px var(--ob-body-pad-left);
}

.ob-calpanel {
    position: sticky;
    top: 0;
    align-self: start;
    height: 100vh;
    overflow-y: auto;
    min-width: 0;
    /* Activity's panel keeps 84px clear on the right for the account avatar, which is fixed to
       the page. Here that would be 84px taken out of the middle of a seven-column grid, so the
       panel is padded evenly and only the picker's own header row steps around the avatar. */
    padding: var(--ob-act-panel-top) var(--ob-body-pad-right) 40px var(--ob-body-pad-left);
    background: var(--ob-card);
    border-left: 1px solid var(--ob-line);
    /* a column, so the Held section can take whatever height is left under the options and be
       one big place to drop a post (2026-09-24) */
    display: flex;
    flex-direction: column;

    > * + * { margin-top: 30px; }
}

/* THE OPTIONS BUTTON, in the picker's top-right corner (2026-09-24). It was "Calendar options"
   with a chevron, under the grid. Now it is the word alone on the grey tile of the step buttons,
   at their height and on their row, so the head reads as one line. Open, it keeps the accent a
   hovered step button takes. The tick boxes still open under the days they act on. */
.ob-calpanel .pn-top { position: relative; }
.ob-calpanel .pn-fold {
    position: absolute;
    top: 0;
    right: 0;
    height: 26px;
    padding: 0 11px;
    border: 0;
    border-radius: 9px;
    background: var(--ob-canvas);
    font-family: var(--ob-font);
    font-size: 13px;
    font-weight: 700;
    color: var(--ob-ink-2);
    cursor: pointer;

    &:hover, &.open { background: var(--ob-accent-bg); color: var(--ob-accent-ink); }
    &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
}
/* tucked up under the days they act on, not a section's gap away from them */
.ob-calpanel > .pn-opts { margin-top: 22px; }
.ob-calpanel > .pn-opts:not(.open) { display: none; }

/* HELD: a heading and the list, no rule over it and no count - the list is the count. THE
   SECTION TAKES WHAT IS LEFT OF THE PANEL AND THE LIST SCROLLS INSIDE IT (2026-09-24). It used
   to grow with the list, so opening the options pushed the panel past the foot of the screen,
   the panel grew a scrollbar, and the picker jumped sideways by its width every time the fold
   was toggled. Now the options shorten the list and the panel never moves. Everything below the
   options is still the drop target and the dashed ring shows all of it (CalHeld.drag). */
.ob-calpanel .pn-held {
    flex: 1 1 0;
    min-height: 140px;      /* three chips at the least: a screen shorter than that scrolls the panel */
    display: flex;
    flex-direction: column;

    > [widget="held"] { flex: 1 1 0; min-height: 0; display: flex; flex-direction: column; }
    .ob-calheld-wrap { display: contents; }     /* the heading and the list are the slot's own rows */
    .ob-calheld-wrap > .ob-lbl { flex: none; margin-bottom: 12px; }
    .ob-calheld { flex: 1 1 0; min-height: 0; align-content: flex-start; }
}

/* THE LIST'S OWN SCROLLBAR: always there, so the chips never change width when it is needed,
   and slim - a lane for the cards, not a second page scrollbar. It sits out in the panel's right
   padding, so the chips still end where the picker does, and 2px all round keeps the chips'
   outer ring from being cut off at the edges they scroll under. The page's scrollbar-color is
   inherited, and while it is set Chrome ignores ::-webkit-scrollbar, so it goes back to auto
   here; Firefox has no such pseudo-element and gets its own thin bar instead. */
.ob-calpanel .ob-calheld {
    overflow-y: scroll;
    margin: -2px -16px -2px -2px;
    padding: 2px 10px 2px 2px;
    scrollbar-width: auto;
    scrollbar-color: auto;

    &::-webkit-scrollbar { width: 6px; }
    &::-webkit-scrollbar-track { background: var(--ob-canvas); border-radius: 3px; }
    &::-webkit-scrollbar-thumb { background: var(--ob-faint-2); border: 0; border-radius: 3px; }
    &::-webkit-scrollbar-thumb:hover { background: var(--ob-muted); }
}
@supports not selector(::-webkit-scrollbar) {
    .ob-calpanel .ob-calheld { scrollbar-width: thin; scrollbar-color: var(--ob-faint-2) var(--ob-canvas); }
}

/* THE HELD LIST: the view's cards stood in a column, drawn as they are anywhere else. Only what
   a person held has a grip; a post held for its agent's cap is read and clicked, not dragged. */
.ob-calheld {
    display: flex;
    flex-direction: column;
    gap: 5px;

    .ob-pc { flex: 0 0 auto; }
}

/* A SCHEDULED POST IN THE AIR: the Held list is where it can be put down - a dashed ring round
   the list, which runs to the foot of the panel, once a drag starts, and the accent tint while
   the pointer is over it */
.ob-calpanel .pn-held.drop-armed .ob-calheld { outline: 2px dashed var(--ob-line); outline-offset: 6px; border-radius: 10px; }
.ob-calpanel .pn-held.drop-over .ob-calheld { outline-color: var(--ob-accent-ring); background: var(--ob-accent-tint); }

/* THE CALENDAR'S THREE TICK BOXES, directly under the days they act on, on both the Posts page
   and an account's: what a week and a month mean, then two saying what the grid carries. One
   line each: what the second hides is refused, blocked and failed, and "rejected / failed" says
   that in the width the row has - the sentence under it was a second line explaining the first.
   A form's checkbox would be the page's heaviest line here, so it takes the panel's own weights
   and its box sits on the middle of the words rather than on the top of them. */
.ob-calpanel .ob-check.pn-filter {
    align-items: center;
    margin: 0;

    input { margin: 0; }
    .what { font-size: 13px; }

    /* one list, evenly spaced: the three are read together and a gap in the middle of them
       would say two of them belong to something the third does not */
    & + .ob-check.pn-filter { margin-top: 10px; }
}

/* ---- NARROW: 1199 and below (dev/docs/responsive.md) ----
   The grid takes the page and the panel goes. It comes back as a sheet, in one of TWO HALVES -
   the only page where narrow opens a panel in parts. The title ("September 2026", with the
   calendar's glyph and the two steps beside it) opens the picker and its options; Held, a pill
   on the title's row with a bulb while anything is held, opens the list. They are separate
   because they answer separate questions, and on narrow neither has to share the screen with
   the other so that a post can be dragged between them: Hold and Move it in the item modal are
   how a post goes into and out of Held here. */
@media (max-width: 1199px) {
    .ob-calpage { grid-template-columns: minmax(0, 1fr); }
    .ob-calpage > .ob-calpanel:not(.sheet) { display: none; }

    .ob-calpanel.sheet[data-sheet="picker"] > .pn-held,
    .ob-calpanel.sheet[data-sheet="held"] > .pn-top,
    .ob-calpanel.sheet[data-sheet="held"] > .pn-opts { display: none; }
    .ob-calpanel.sheet[data-sheet="held"] > .pn-held { margin-top: 0; }

    /* in a sheet the sheet scrolls, so the Held list is as long as it is */
    .ob-calpanel.sheet .pn-held,
    .ob-calpanel.sheet .pn-held > [widget="held"],
    .ob-calpanel.sheet .pn-held .ob-calheld { flex: none; min-height: 0; }
    .ob-calpanel.sheet .ob-calheld { overflow: visible; margin: 0; padding: 0; }

    /* the title's row carries the steps and Held as well now: the five views a little tighter */
    .ob-calhead .ob-seg .item { padding: 0 11px; }
}


/* ============================================================
   A NEW POST, STARTED ON THE CALENDAR (2026-10-03, Dave)
   Every mark is drawn only where a post could go out - today after now, or a day still to come -
   and only while the day is pointed at (popups/compose.js opens on the press).
   ============================================================ */

/* THE RAIL'S GROUND (Dave's (a)): a faint line across the day at the quarter hour nearest the
   pointer, a small hollow + at its left end and the time beside it, all quiet enough to read past
   (CalRail.hover). It replaced a dashed card, which drew the eye like a post. Under the cards, and
   the pointer goes through it to the ground it marks */
.ob-calrail .dcol .newslot {
    position: absolute;
    left: 0;
    right: 0;
    height: 0;
    z-index: 0;
    pointer-events: none;

    .ln { position: absolute; left: 0; right: 0; top: -.5px; height: 1px; background: color-mix(in srgb, var(--ob-accent) 30%, transparent); }
    .pl {
        position: absolute;
        left: 4px;
        top: -8px;
        display: grid;
        place-items: center;
        width: 16px;
        height: 16px;
        border-radius: 50%;
        background: var(--ob-card);
        box-shadow: inset 0 0 0 1.25px color-mix(in srgb, var(--ob-accent) 55%, transparent);
        color: var(--ob-accent);
    }
    .pl svg { width: 9px; height: 9px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; }
    /* the time sits just above the line, so the line never runs through its figures */
    .tm {
        position: absolute;
        left: 24px;
        top: -15px;
        font-size: 10.5px;
        font-weight: 700;
        line-height: 12px;
        color: color-mix(in srgb, var(--ob-accent-ink) 60%, transparent);
        font-variant-numeric: tabular-nums;
        white-space: nowrap;
    }
}

/* THE DAY'S + AT ITS HEAD (Dave's (b)), on the rail and in list mode: drawn on the heading of the
   day pointed at (.hot, CalRail.heat and CalList.heat) or while the heading itself is, at the end
   of the heading's line, the way the month draws its + at the end of the date's */
:is(.ob-calrail, .ob-callist) .dh .newpost {
    display: grid;
    place-items: center;
    align-self: center;
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    margin-left: auto;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--ob-accent);
    color: #fff;
    cursor: pointer;
    opacity: 0;
    transition: opacity .12s, background .12s;

    svg { width: 10px; height: 10px; fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: round; }
    &:hover { background: var(--ob-accent-ink); }
}
:is(.ob-calrail, .ob-callist) .heads > .col.hot .newpost,
:is(.ob-calrail, .ob-callist) .heads > .col:hover .newpost,
:is(.ob-calrail, .ob-callist) .dh .newpost:focus-visible { opacity: 1; }

/* THE MONTH: a small solid + at the end of the date's row, on the cell pointed at. On today it
   sits beside "Today", which keeps its place at the right */
.ob-cal .cell .newpost {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    margin-left: auto;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--ob-accent);
    color: #fff;
    cursor: pointer;
    opacity: 0;
    transition: opacity .12s, background .12s;

    svg { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: round; }
    &:hover { background: var(--ob-accent-ink); }
}
.ob-cal .cell.today .d .t { margin-left: auto; }
.ob-cal .cell .d .t + .newpost { margin-left: 6px; }
.ob-cal .cell:hover .newpost,
.ob-cal .cell .newpost:focus-visible { opacity: 1; }

/* a post in the air is the only thing on the calendar while it is held */
.ob-monthwrap.dragging .newpost,
.ob-calrail.dragging .newpost,
.ob-callist.dragging .newpost { display: none; }
 /* ============================================
   OKAYBOT ATOMS
   Card, pill, chip, count, bar, segmented control, section label, the two identity shapes and
   the small text roles. design.md section 2 "Atoms" and "Identity has a shape". Every class is
   prefixed ob- so nothing collides with the scaffold's own atoms.
   ============================================ */

/* ---- the slot that is not there ----
   A widget list needs a container element of its own, and the framework keeps that container
   holding EXACTLY the list's cards - anything else in it is evicted. So a slot dropped into a
   flex or grid parent would become one child where there should be many, and the gap, the
   columns and the scroll would all be applied to the wrapper instead of the rows.

   display: contents removes the box and lets the cards inherit the parent's layout, so a list
   can be mounted anywhere a run of rows used to be written out by hand. Every list slot and
   every empty line in the product carries it. */
.ob-slot { display: contents; }

/* ---- card: white, hairline, 18px radius, no shadow ---- */
.ob-card {
    background: var(--ob-card);
    border: 1px solid var(--ob-line);
    border-radius: var(--ob-radius);
    padding: 20px 22px;
    font-family: var(--ob-font);
    color: var(--ob-ink);

    &.chart { padding: 18px 20px 20px; }
}

/* ---- pill: 38px, full radius, one primary per card ---- */
.ob-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    height: var(--ob-pill-h);
    padding: 0 18px;
    border-radius: 999px;
    border: 1px solid transparent;
    font-family: var(--ob-font);
    font-size: 13.5px;
    font-weight: 700;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    text-decoration: none;
    background: var(--ob-card);
    color: var(--ob-ink);
    transition: background .12s, color .12s, border-color .12s, transform .12s;

    svg { width: 16px; height: 16px; flex: 0 0 auto; }

    &.primary { background: var(--ob-accent); color: #fff; }
    &.primary:hover { background: var(--ob-accent-ink); }
    &.ghost { background: var(--ob-card); border-color: var(--ob-line); color: var(--ob-ink); }
    &.ghost:hover { background: var(--ob-canvas); }
    &.ghost.on { background: var(--ob-accent-bg); border-color: var(--ob-accent-bg); color: var(--ob-accent-ink); }
    /* THE BLUE OUTLINE (2026-09-30): heavier than a ghost, never the main button - Post it yourself */
    &.outline { background: var(--ob-card); border: 1.5px solid var(--ob-accent); color: var(--ob-accent-ink); }
    &.outline:hover { background: var(--ob-accent-bg); }
    svg.out { width: 13px; height: 13px; margin-right: -3px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
    &.text { background: transparent; color: var(--ob-ink-2); padding: 0 12px; }
    &.text:hover { color: var(--ob-ink); }
    &.danger { background: var(--ob-rose-bg); color: var(--ob-rose-ink); }
    &.danger:hover { background: #ffd5d9; }
    /* THE RED OUTLINE (2026-10-01): an action that ends something for good, waiting at the foot of a
       panel - Remove agent, Remove profile. It opens a question; the question's last click is .solid-danger */
    &.outline-danger { background: var(--ob-card); border: 1.5px solid var(--ob-rose-ink); color: var(--ob-rose-ink); }
    &.outline-danger:hover { background: var(--ob-rose-bg); }
    /* THE LAST CLICK, and the only one in the product that wears a solid red. The tinted
       .danger above is for an action that opens a question; this is for answering it, so it
       is the rose ink used as a ground rather than as text. If a second one of these ever
       appears on a screen, one of them is not really the last click. */
    &.solid-danger { background: var(--ob-rose-ink); color: #fff; }
    &.solid-danger:hover { background: #971616; }
    &.dark { background: var(--ob-ink); color: #fff; }
    &.dark:hover { background: #2a2d36; }

    &.full { width: 100%; }
    &.sm { height: 30px; padding: 0 13px; font-size: 12.5px; }
    &.foot { margin-top: auto; }

    &:active { transform: translateY(1px); }
    &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
    &:disabled { opacity: .5; cursor: default; }

    /* BUSY (2026-10-03): the pill a person pressed while its call is out - a send goes out inside
       the click - drawn at full strength with a spinner before its words; its neighbours are
       disabled beside it */
    &.busy, &.busy:disabled { opacity: 1; transform: none; }

    /* A PILL IS A BUTTON, EVEN WHEN IT IS AN ANCHOR. The core sheet underlines every a:hover,
       which drew a line under the label of every pill that routes rather than submits. The
       ground already answers the pointer; a rule under the word is the link's answer, not the
       button's. */
    &:hover { text-decoration: none; }
}

/* ---- spinner: a call is out. A ring in the text's own colour, so it sits in a pill or a line ---- */
.ob-spin {
    flex: 0 0 auto;
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    border: 2px solid color-mix(in srgb, currentColor 22%, transparent);
    border-top-color: currentColor;
    animation: ob-spin .8s linear infinite;
}
@keyframes ob-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .ob-spin { animation-duration: 2.4s; }
}

/* ---- chip: 24px, full radius, tinted. A state tints its chip ---- */
.ob-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: var(--ob-chip-h);
    padding: 0 10px;
    border-radius: 999px;
    font-family: var(--ob-font);
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
    white-space: nowrap;
    background: var(--ob-canvas);
    color: var(--ob-ink-2);
    text-decoration: none;

    &.mint { background: var(--ob-mint-bg); color: var(--ob-mint-ink); }
    &.butter { background: var(--ob-butter-bg); color: var(--ob-butter-ink); }
    &.rose { background: var(--ob-rose-bg); color: var(--ob-rose-ink); }
    &.grey { background: var(--ob-grey-bg); color: var(--ob-grey-ink); }
    /* THE FILLED ONE (2026-09-17). Every tone above is a pale ground that belongs with a card
       tinted the same, and beside a page title one of those reads as a label rather than a
       badge. This is the badge: the ground is the ink and the word is the card. */
    &.slate { background: var(--ob-ink-2); color: var(--ob-card); }
    /* THE CHIP THAT IS A BUTTON (Dave, 2026-10-04): the primary pill's accent and white on a
       chip's shape, where pressing the state is the way to put it right - a profile's Needs
       reconnecting on its row */
    &.primary { background: var(--ob-accent); color: #fff; cursor: pointer; }
    &.primary:hover { background: var(--ob-accent-ink); }
    &.lg { height: 32px; padding: 0 12px 0 6px; font-size: 13px; background: var(--ob-canvas); color: var(--ob-ink); }

    &.t1 { background: var(--ob-t1-bg); color: var(--ob-t1-fg); --ob-tint-solid: var(--ob-t1-solid); }
    &.t2 { background: var(--ob-t2-bg); color: var(--ob-t2-fg); --ob-tint-solid: var(--ob-t2-solid); }
    &.t3 { background: var(--ob-t3-bg); color: var(--ob-t3-fg); --ob-tint-solid: var(--ob-t3-solid); }
    &.t4 { background: var(--ob-t4-bg); color: var(--ob-t4-fg); --ob-tint-solid: var(--ob-t4-solid); }
    &.t5 { background: var(--ob-t5-bg); color: var(--ob-t5-fg); --ob-tint-solid: var(--ob-t5-solid); }
    &.t6 { background: var(--ob-t6-bg); color: var(--ob-t6-fg); --ob-tint-solid: var(--ob-t6-solid); }
    &.t7 { background: var(--ob-t7-bg); color: var(--ob-t7-fg); --ob-tint-solid: var(--ob-t7-solid); }
    &.t8 { background: var(--ob-t8-bg); color: var(--ob-t8-fg); --ob-tint-solid: var(--ob-t8-solid); }
    &.t9 { background: var(--ob-t9-bg); color: var(--ob-t9-fg); --ob-tint-solid: var(--ob-t9-solid); }
    &.t10 { background: var(--ob-t10-bg); color: var(--ob-t10-fg); --ob-tint-solid: var(--ob-t10-solid); }
    &.t11 { background: var(--ob-t11-bg); color: var(--ob-t11-fg); --ob-tint-solid: var(--ob-t11-solid); }
    &.t12 { background: var(--ob-t12-bg); color: var(--ob-t12-fg); --ob-tint-solid: var(--ob-t12-solid); }
    &.t13 { background: var(--ob-t13-bg); color: var(--ob-t13-fg); --ob-tint-solid: var(--ob-t13-solid); }
    &.t14 { background: var(--ob-t14-bg); color: var(--ob-t14-fg); --ob-tint-solid: var(--ob-t14-solid); }
    &.t15 { background: var(--ob-t15-bg); color: var(--ob-t15-fg); --ob-tint-solid: var(--ob-t15-solid); }
    &.t16 { background: var(--ob-t16-bg); color: var(--ob-t16-fg); --ob-tint-solid: var(--ob-t16-solid); }
    &.t17 { background: var(--ob-t17-bg); color: var(--ob-t17-fg); --ob-tint-solid: var(--ob-t17-solid); }
    &.t18 { background: var(--ob-t18-bg); color: var(--ob-t18-fg); --ob-tint-solid: var(--ob-t18-solid); }
    &.t19 { background: var(--ob-t19-bg); color: var(--ob-t19-fg); --ob-tint-solid: var(--ob-t19-solid); }
    &.t20 { background: var(--ob-t20-bg); color: var(--ob-t20-fg); --ob-tint-solid: var(--ob-t20-solid); }
    &.t21 { background: var(--ob-t21-bg); color: var(--ob-t21-fg); --ob-tint-solid: var(--ob-t21-solid); }
    &.t22 { background: var(--ob-t22-bg); color: var(--ob-t22-fg); --ob-tint-solid: var(--ob-t22-solid); }
    &.t23 { background: var(--ob-t23-bg); color: var(--ob-t23-fg); --ob-tint-solid: var(--ob-t23-solid); }
    &.t24 { background: var(--ob-t24-bg); color: var(--ob-t24-fg); --ob-tint-solid: var(--ob-t24-solid); }
    &.t25 { background: var(--ob-t25-bg); color: var(--ob-t25-fg); --ob-tint-solid: var(--ob-t25-solid); }
    &.t26 { background: var(--ob-t26-bg); color: var(--ob-t26-fg); --ob-tint-solid: var(--ob-t26-solid); }
    &.t27 { background: var(--ob-t27-bg); color: var(--ob-t27-fg); --ob-tint-solid: var(--ob-t27-solid); }
    &.t28 { background: var(--ob-t28-bg); color: var(--ob-t28-fg); --ob-tint-solid: var(--ob-t28-solid); }
    &.t29 { background: var(--ob-t29-bg); color: var(--ob-t29-fg); --ob-tint-solid: var(--ob-t29-solid); }
    &.t30 { background: var(--ob-t30-bg); color: var(--ob-t30-fg); --ob-tint-solid: var(--ob-t30-solid); }
    &.t31 { background: var(--ob-t31-bg); color: var(--ob-t31-fg); --ob-tint-solid: var(--ob-t31-solid); }
    &.t32 { background: var(--ob-t32-bg); color: var(--ob-t32-fg); --ob-tint-solid: var(--ob-t32-solid); }
    &.t33 { background: var(--ob-t33-bg); color: var(--ob-t33-fg); --ob-tint-solid: var(--ob-t33-solid); }
    &.t34 { background: var(--ob-t34-bg); color: var(--ob-t34-fg); --ob-tint-solid: var(--ob-t34-solid); }
    &.t35 { background: var(--ob-t35-bg); color: var(--ob-t35-fg); --ob-tint-solid: var(--ob-t35-solid); }
    &.t36 { background: var(--ob-t36-bg); color: var(--ob-t36-fg); --ob-tint-solid: var(--ob-t36-solid); }
    &.t37 { background: var(--ob-t37-bg); color: var(--ob-t37-fg); --ob-tint-solid: var(--ob-t37-solid); }
    &.t38 { background: var(--ob-t38-bg); color: var(--ob-t38-fg); --ob-tint-solid: var(--ob-t38-solid); }
    &.t39 { background: var(--ob-t39-bg); color: var(--ob-t39-fg); --ob-tint-solid: var(--ob-t39-solid); }
    &.t40 { background: var(--ob-t40-bg); color: var(--ob-t40-fg); --ob-tint-solid: var(--ob-t40-solid); }
    &.t41 { background: var(--ob-t41-bg); color: var(--ob-t41-fg); --ob-tint-solid: var(--ob-t41-solid); }
    &.t42 { background: var(--ob-t42-bg); color: var(--ob-t42-fg); --ob-tint-solid: var(--ob-t42-solid); }
    &.t43 { background: var(--ob-t43-bg); color: var(--ob-t43-fg); --ob-tint-solid: var(--ob-t43-solid); }
    &.t44 { background: var(--ob-t44-bg); color: var(--ob-t44-fg); --ob-tint-solid: var(--ob-t44-solid); }
    &.t45 { background: var(--ob-t45-bg); color: var(--ob-t45-fg); --ob-tint-solid: var(--ob-t45-solid); }
    &.t46 { background: var(--ob-t46-bg); color: var(--ob-t46-fg); --ob-tint-solid: var(--ob-t46-solid); }
    &.t47 { background: var(--ob-t47-bg); color: var(--ob-t47-fg); --ob-tint-solid: var(--ob-t47-solid); }
    &.t48 { background: var(--ob-t48-bg); color: var(--ob-t48-fg); --ob-tint-solid: var(--ob-t48-solid); }
    &.t49 { background: var(--ob-t49-bg); color: var(--ob-t49-fg); --ob-tint-solid: var(--ob-t49-solid); }
    &.t50 { background: var(--ob-t50-bg); color: var(--ob-t50-fg); --ob-tint-solid: var(--ob-t50-solid); }
}

/* ---- count: 22px, black on Approvals, grey on Posts, inverted on the active item ---- */
/* ---- the bulb: one bit, not a figure ----
   Something arrived that this person has not been to look at. It replaced the counts in the
   sidebar (2026-09-22): a number has to be kept true to be worth drawing, and neither "7 waiting"
   nor "26 posts" was a fact anybody acted on. The dot says the one thing that is worth saying,
   and Home putting it out is the whole of its behaviour. */
.ob-bulb {
    display: inline-block;
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--ob-accent);
    box-shadow: 0 0 0 3px var(--ob-accent-wash, rgba(79, 102, 240, .18));

    /* a profile needs reconnecting (2026-10-04): the same dot, in the accent's ink - never red, a
       login running out is ordinary (Dave, 2026-10-05) */
    &.warn {
        background: var(--ob-accent-ink);
        box-shadow: 0 0 0 3px var(--ob-accent-bg);
    }
}

/* ---- bar: money, and only money. Blue with room, red past 80 percent, grey when frozen ---- */
.ob-bar {
    display: block;
    width: 100%;
    border-radius: 999px;
    background: var(--ob-line);
    overflow: hidden;

    .fill {
        display: block;
        height: 100%;
        border-radius: 999px;
        background: var(--ob-accent);
        transition: width .25s;
    }
    .fill.red { background: var(--ob-bar-red); }
    .fill.grey { background: var(--ob-faint-2); }

    &.xs { width: 30px; height: 4px; flex: 0 0 auto; }
    /* the xs bar, lengthened: the workspace's own line on the sidebar's Usage item, above the
       agent lines that added up to it, until the rail of agents went (2026-10-01) */
    &.xs.long { width: 56px; }
    &.sm { height: 6px; }
    &.md { height: 6px; }
    &.lg { height: 8px; }
}

/* ---- segmented control: a #e8eaf0 pill, 3px padding, 32px items ---- */
.ob-seg {
    display: inline-flex;
    gap: 2px;
    padding: 3px;
    border-radius: 999px;
    background: var(--ob-line);

    .item {
        height: var(--ob-seg-h);
        padding: 0 14px;
        border: 0;
        border-radius: 999px;
        background: transparent;
        font-family: var(--ob-font);
        font-size: 13px;
        font-weight: 600;
        color: var(--ob-ink-2);
        cursor: pointer;
        line-height: 1;
    }
    .item.on { background: var(--ob-card); color: var(--ob-ink); font-weight: 700; }
    .item:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 1px; }
}

/* ---- section label: caps, spaced, muted, above a group ---- */
.ob-lbl {
    font-family: var(--ob-font);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ob-muted);
    margin: 0 0 10px;
}

/* ---- identity: the square is an agent, the circle is a profile ---- */
.ob-square,
.ob-circle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    font-family: var(--ob-font);
    font-weight: 700;
    overflow: hidden;
    background: var(--ob-canvas);
    color: var(--ob-ink-2);
    vertical-align: middle;

    &.t1 { background: var(--ob-t1-bg); color: var(--ob-t1-fg); --ob-tint-solid: var(--ob-t1-solid); }
    &.t2 { background: var(--ob-t2-bg); color: var(--ob-t2-fg); --ob-tint-solid: var(--ob-t2-solid); }
    &.t3 { background: var(--ob-t3-bg); color: var(--ob-t3-fg); --ob-tint-solid: var(--ob-t3-solid); }
    &.t4 { background: var(--ob-t4-bg); color: var(--ob-t4-fg); --ob-tint-solid: var(--ob-t4-solid); }
    &.t5 { background: var(--ob-t5-bg); color: var(--ob-t5-fg); --ob-tint-solid: var(--ob-t5-solid); }
    &.t6 { background: var(--ob-t6-bg); color: var(--ob-t6-fg); --ob-tint-solid: var(--ob-t6-solid); }
    &.t7 { background: var(--ob-t7-bg); color: var(--ob-t7-fg); --ob-tint-solid: var(--ob-t7-solid); }
    &.t8 { background: var(--ob-t8-bg); color: var(--ob-t8-fg); --ob-tint-solid: var(--ob-t8-solid); }
    &.t9 { background: var(--ob-t9-bg); color: var(--ob-t9-fg); --ob-tint-solid: var(--ob-t9-solid); }
    &.t10 { background: var(--ob-t10-bg); color: var(--ob-t10-fg); --ob-tint-solid: var(--ob-t10-solid); }
    &.t11 { background: var(--ob-t11-bg); color: var(--ob-t11-fg); --ob-tint-solid: var(--ob-t11-solid); }
    &.t12 { background: var(--ob-t12-bg); color: var(--ob-t12-fg); --ob-tint-solid: var(--ob-t12-solid); }
    &.t13 { background: var(--ob-t13-bg); color: var(--ob-t13-fg); --ob-tint-solid: var(--ob-t13-solid); }
    &.t14 { background: var(--ob-t14-bg); color: var(--ob-t14-fg); --ob-tint-solid: var(--ob-t14-solid); }
    &.t15 { background: var(--ob-t15-bg); color: var(--ob-t15-fg); --ob-tint-solid: var(--ob-t15-solid); }
    &.t16 { background: var(--ob-t16-bg); color: var(--ob-t16-fg); --ob-tint-solid: var(--ob-t16-solid); }
    &.t17 { background: var(--ob-t17-bg); color: var(--ob-t17-fg); --ob-tint-solid: var(--ob-t17-solid); }
    &.t18 { background: var(--ob-t18-bg); color: var(--ob-t18-fg); --ob-tint-solid: var(--ob-t18-solid); }
    &.t19 { background: var(--ob-t19-bg); color: var(--ob-t19-fg); --ob-tint-solid: var(--ob-t19-solid); }
    &.t20 { background: var(--ob-t20-bg); color: var(--ob-t20-fg); --ob-tint-solid: var(--ob-t20-solid); }
    &.t21 { background: var(--ob-t21-bg); color: var(--ob-t21-fg); --ob-tint-solid: var(--ob-t21-solid); }
    &.t22 { background: var(--ob-t22-bg); color: var(--ob-t22-fg); --ob-tint-solid: var(--ob-t22-solid); }
    &.t23 { background: var(--ob-t23-bg); color: var(--ob-t23-fg); --ob-tint-solid: var(--ob-t23-solid); }
    &.t24 { background: var(--ob-t24-bg); color: var(--ob-t24-fg); --ob-tint-solid: var(--ob-t24-solid); }
    &.t25 { background: var(--ob-t25-bg); color: var(--ob-t25-fg); --ob-tint-solid: var(--ob-t25-solid); }
    &.t26 { background: var(--ob-t26-bg); color: var(--ob-t26-fg); --ob-tint-solid: var(--ob-t26-solid); }
    &.t27 { background: var(--ob-t27-bg); color: var(--ob-t27-fg); --ob-tint-solid: var(--ob-t27-solid); }
    &.t28 { background: var(--ob-t28-bg); color: var(--ob-t28-fg); --ob-tint-solid: var(--ob-t28-solid); }
    &.t29 { background: var(--ob-t29-bg); color: var(--ob-t29-fg); --ob-tint-solid: var(--ob-t29-solid); }
    &.t30 { background: var(--ob-t30-bg); color: var(--ob-t30-fg); --ob-tint-solid: var(--ob-t30-solid); }
    &.t31 { background: var(--ob-t31-bg); color: var(--ob-t31-fg); --ob-tint-solid: var(--ob-t31-solid); }
    &.t32 { background: var(--ob-t32-bg); color: var(--ob-t32-fg); --ob-tint-solid: var(--ob-t32-solid); }
    &.t33 { background: var(--ob-t33-bg); color: var(--ob-t33-fg); --ob-tint-solid: var(--ob-t33-solid); }
    &.t34 { background: var(--ob-t34-bg); color: var(--ob-t34-fg); --ob-tint-solid: var(--ob-t34-solid); }
    &.t35 { background: var(--ob-t35-bg); color: var(--ob-t35-fg); --ob-tint-solid: var(--ob-t35-solid); }
    &.t36 { background: var(--ob-t36-bg); color: var(--ob-t36-fg); --ob-tint-solid: var(--ob-t36-solid); }
    &.t37 { background: var(--ob-t37-bg); color: var(--ob-t37-fg); --ob-tint-solid: var(--ob-t37-solid); }
    &.t38 { background: var(--ob-t38-bg); color: var(--ob-t38-fg); --ob-tint-solid: var(--ob-t38-solid); }
    &.t39 { background: var(--ob-t39-bg); color: var(--ob-t39-fg); --ob-tint-solid: var(--ob-t39-solid); }
    &.t40 { background: var(--ob-t40-bg); color: var(--ob-t40-fg); --ob-tint-solid: var(--ob-t40-solid); }
    &.t41 { background: var(--ob-t41-bg); color: var(--ob-t41-fg); --ob-tint-solid: var(--ob-t41-solid); }
    &.t42 { background: var(--ob-t42-bg); color: var(--ob-t42-fg); --ob-tint-solid: var(--ob-t42-solid); }
    &.t43 { background: var(--ob-t43-bg); color: var(--ob-t43-fg); --ob-tint-solid: var(--ob-t43-solid); }
    &.t44 { background: var(--ob-t44-bg); color: var(--ob-t44-fg); --ob-tint-solid: var(--ob-t44-solid); }
    &.t45 { background: var(--ob-t45-bg); color: var(--ob-t45-fg); --ob-tint-solid: var(--ob-t45-solid); }
    &.t46 { background: var(--ob-t46-bg); color: var(--ob-t46-fg); --ob-tint-solid: var(--ob-t46-solid); }
    &.t47 { background: var(--ob-t47-bg); color: var(--ob-t47-fg); --ob-tint-solid: var(--ob-t47-solid); }
    &.t48 { background: var(--ob-t48-bg); color: var(--ob-t48-fg); --ob-tint-solid: var(--ob-t48-solid); }
    &.t49 { background: var(--ob-t49-bg); color: var(--ob-t49-fg); --ob-tint-solid: var(--ob-t49-solid); }
    &.t50 { background: var(--ob-t50-bg); color: var(--ob-t50-fg); --ob-tint-solid: var(--ob-t50-solid); }

    img { width: 100%; height: 100%; object-fit: cover; display: block; }
}

/* ONE AGENT, ONE SQUARE. The square is the Okaybot mark in white on the agent's own colour:
   .bot draws it, and the tint class says which colour. Nothing goes inside the element - the
   mark is a CSS mask on ::after, so the day it changes it changes in one rule and every screen
   follows, and no template carries the path. The pale -bg tint stays for the squares that draw
   no mark, and for everywhere else the agent's colour is read.

   AN AGENT IS ROUND NOW (2026-09-19), so .bot is a circle and the class name is the only square
   thing left about it. The element keeps its name because .ob-square is still the right box for
   everything else it draws - a harness glyph, a blank, a plus - and renaming it would touch
   thirty-six templates to say the same thing.

   The face fills the element at 100 percent, because okaybot-face-mono.svg carries its own
   framing: its box is 544 rather than 512, which is the mark at 94 percent so the antenna does
   not crowd a round crop, and its y is 12, which sits the face 8 below the middle where it
   settles against a name. Move this and Faces.BotSvg together - they are the same picture by
   two routes. An svg inside one is a mark (the system square) and keeps the old 56 percent.
   Never a letter: letters survive only on circles. */
.ob-square {
    border-radius: var(--ob-radius-sq);
    svg { width: 56%; height: 56%; }

    &.bot {
        background: var(--ob-tint-solid, var(--ob-ink));
        border-radius: 50%;

        /* A SQUARE CARRYING AN IMG IS ALREADY THE WHOLE FACE (2026-09-19), so this rule takes
           the CSS drawing of it back off: the mask would paint the mark a second time over the
           picture, and the ground would show at the four corners the drawn circle does not
           reach - the radius below is a square s, and Faces.BotSvg draws a circle. Both go, and
           what is left is the image.

           What the mask is still for is a square drawn from nothing but a tint, which is what
           the static design and marketing pages do. */
        &:has(img) {
            background: transparent;
            &::after { content: none; }
        }

        &::after {
            content: "";
            width: 100%;
            height: 100%;
            background: #fff;
            -webkit-mask: url(/images/logo/okaybot-face-mono.svg) center / contain no-repeat;
            mask: url(/images/logo/okaybot-face-mono.svg) center / contain no-repeat;
        }
    }

    &.xs { width: 18px; height: 18px; border-radius: 6px; }
    &.sm { width: 22px; height: 22px; border-radius: 7px; }
    &.md { width: 32px; height: 32px; }
    &.lg { width: 44px; height: 44px; border-radius: 13px; }
    &.xl { width: 52px; height: 52px; border-radius: 15px; }
}

/* THE CIRCLE CARRIES A PICTURE, and since 2026-09-19 that is all it carries on any screen that
   draws a row: the face is a url the entity handed over, photograph or drawn letter alike
   (code/Faces/Face.cs), and no template works one out. The letter sizes below stay because the
   static pages - the design sheet, the marketing mock-ups - still spell a circle out by hand,
   and because a letter is what a circle falls back to if an image never arrives. */
.ob-circle {
    border-radius: 50%;
    svg { width: 50%; height: 50%; }

    &.xs { width: 18px; height: 18px; font-size: 9px; }
    &.sm { width: 22px; height: 22px; font-size: 9px; }
    &.md { width: 30px; height: 30px; font-size: 12px; }
    &.lg { width: 44px; height: 44px; font-size: 18px; }
    &.xl { width: 56px; height: 56px; font-size: 22px; }
    &.ring { box-shadow: 0 0 0 2px var(--ob-canvas); }
}

/* A FACE WITH ITS PROVIDER'S BADGE on the bottom right: the provider's round icon
   (Provider.Icon, images/providers/), so a person can tell which provider a profile is on. The
   circle clips what is inside it, so the badge sits on a wrapper beside the circle and is sized
   off the face. Its ring is --ob-badge-ring, the ground it sits on: a row that changes its ground
   on hover sets it too, or the ring would show as a stray white edge. */
.ob-badged {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
    vertical-align: middle;

    > .badge {
        position: absolute;
        right: -3px;
        bottom: -3px;
        width: 46%;
        height: 46%;
        border-radius: 50%;
        background: var(--ob-badge-ring, var(--ob-card));
        box-shadow: 0 0 0 2px var(--ob-badge-ring, var(--ob-card));
    }

    &:has(> .ob-circle.xl) > .badge { width: 40%; height: 40%; right: -2px; bottom: -2px; }
}

/* ---- text roles ---- */
.ob-big {
    font-family: var(--ob-font);
    font-size: 28px;
    font-weight: 800;
    letter-spacing: -.01em;
    font-variant-numeric: tabular-nums;
    color: var(--ob-ink);
    line-height: 1.1;
    margin: 0 0 6px;

    .unit { font-size: 15px; font-weight: 600; color: var(--ob-muted); letter-spacing: 0; }
    &.mint { color: var(--ob-mint-ink); }
}

.ob-meta {
    font-family: var(--ob-font);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.45;
    color: var(--ob-muted);
}
p.ob-meta { margin: 0 0 12px; }

.num { font-variant-numeric: tabular-nums; letter-spacing: -.01em; }

.ob-empty {
    font-family: var(--ob-font);
    font-size: 14px;
    font-weight: 600;
    line-height: 1.5;
    color: var(--ob-muted);
    padding: 34px 0;
    max-width: 52ch;

    &.sm { padding: 8px 0 0; font-size: 12.5px; }
}

.ob-sentence {
    font-family: var(--ob-font);
    font-size: 15px;
    font-weight: 600;
    line-height: 1.5;
    color: var(--ob-ink);
}

/* a label / value pair down a rail card */
.ob-line-row {
    display: flex;
    align-items: center;
    gap: 9px;
    min-height: 28px;
    font-family: var(--ob-font);
    font-size: 13.5px;
    font-weight: 600;
    color: var(--ob-ink);

    .grow { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    /* the tally at the end of the row is the row's point - "2 going, 9 went" - so a long name
       is clipped by .grow above rather than wrapping the number onto a second line */
    .ob-meta { flex: 0 0 auto; white-space: nowrap; }
    .num { font-weight: 800; }
    &.who { gap: 7px; margin-bottom: 14px; flex-wrap: wrap; }
}

/* ...and the same row when it is the way to the thing it names. A name on a panel that has a
   page behind it should be the link to that page, rather than sitting under a note explaining
   where to go. The tint is left alone: the square and the words are the row, and colouring
   them blue would make three links out of one. */
a.ob-line-row.go {
    display: flex;
    margin: 0 -8px;
    padding: 0 8px;
    border-radius: 9px;
    text-decoration: none;
    color: inherit;

    &:hover { background: var(--ob-canvas); }
    &:hover .grow { color: var(--ob-accent-ink); }
}

/* THE FACTS on the item detail: a small label and a bold value. What the call cost at the
   provider and the reference - the harness and the model it reported are recorded but not drawn
   for now. It sits at the foot of the detail because the picture is what the screen is about.
   A row rather than a grid, so the number of facts can change without a column count changing
   with it: each one takes an equal share of the row, so two facts are two halves and the second
   sits out at the middle rather than up against the first. They wrap when the column is narrow. */
.ob-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 14px 40px;
    font-family: var(--ob-font);

    .fact { flex: 1 1 0; min-width: 0; }

    .fact .k {
        font-size: 10.5px;
        font-weight: 700;
        letter-spacing: .08em;
        text-transform: uppercase;
        color: var(--ob-muted);
        margin-bottom: 3px;
    }
    .fact b { display: block; font-size: 14px; font-weight: 800; color: var(--ob-ink); font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; }
    .fact b a { color: inherit; text-decoration: none; }
    .fact b a:hover { color: var(--ob-accent-ink); }
}

@media (max-width: 760px) {
    .ob-facts { gap: 14px 28px; }
}

/* a raw request or response, verbatim */
.ob-raw {
    margin: 8px 0 12px;
    padding: 12px 14px;
    border-radius: var(--ob-radius-tile);
    background: var(--ob-canvas);
    font-family: var(--ob-mono);
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--ob-ink-2);
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 320px;
    overflow: auto;
}

/* ...and the same payload drawn rather than dumped: json/json.js builds the lines, this
   colours them. It goes ON an .ob-raw tile, which keeps the ground, the mono face and the
   scroll; everything here is about the shape of what is inside it.

   THE INDENT IS PADDING. A line carries its depth in --d and pays for it in padding-left, so
   the second line of a wrapped post hangs under the first instead of starting at the margin.
   Spaces in a <pre> cannot do that, which is the whole reason for the element per line. */
.ob-json {
    white-space: normal;            /* the newlines between lines are ours, not the payload's */
    word-break: normal;

    .ln {
        padding-left: calc(var(--d, 0) * var(--ob-json-step));
        white-space: pre-wrap;      /* ...but inside one line, a value keeps its own spacing */
        word-break: break-word;
    }

    .ln.raw { padding-left: 0; }    /* not JSON: one block, verbatim, no structure to indent */

    .k { color: var(--ob-json-key); font-weight: 600; }
    .s { color: var(--ob-json-str); }
    .n { color: var(--ob-json-num); }
    .w { color: var(--ob-json-word); }
    .z { color: var(--ob-json-word); opacity: .7; }
    .p { color: var(--ob-json-punc); }
}

/* the config block on Setup */
.ob-config {
    position: relative;
    margin: 4px 0 0;

    pre {
        margin: 0;
        padding: 14px 16px;
        border-radius: var(--ob-radius-tile);
        background: var(--ob-ink);
        color: #e8eaf1;
        font-family: var(--ob-mono);
        font-size: 12.5px;
        line-height: 1.55;
        white-space: pre;
        overflow-x: auto;
    }
    .ob-pill { position: absolute; top: 10px; right: 10px; }
}

/* WHAT HAS GONE AGAINST WHAT THERE IS: a figure, its limit beside it, the bar under them.
   The Usage page's three meters and its ceiling card.

   IT WAS THE SIDEBAR'S BLOCK FIRST, and the rail borrowed it on 2026-09-12 with a .flat that
   dropped the panel it sat on. The sidebar's copy went on 2026-09-16 - today's spend is a line
   on the Usage item now - so every one of these is inside a card, .flat was on all of them, and
   the modifier is folded back in here. It lives in the atoms rather than on a page because
   three screens draw it. */
.ob-money {
    margin: 0 0 12px;

    .row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 10px; }
    .total { font-size: 22px; font-weight: 800; letter-spacing: -.01em; }
    .of { font-size: 12.5px; font-weight: 600; color: var(--ob-muted); }
}

/* the price list */
.ob-prices {
    display: flex;
    flex-direction: column;
    gap: 7px;
    font-family: var(--ob-font);
    font-size: 13.5px;
    font-weight: 600;
    color: var(--ob-ink-2);

    .row { display: flex; align-items: baseline; gap: 10px; }
    .grow { flex: 1 1 auto; }
    /* A REFERENCE TABLE, NOT A FIGURE (2026-09-12). These were ink at 800, the same weight as
       the money this page is actually about, so seven prices nobody had asked about were the
       heaviest block in the rail. They are what a thing costs, sitting there to be looked up. */
    .num { font-weight: 600; color: var(--ob-muted); font-variant-numeric: tabular-nums; }
}

/* the Live card */
.ob-live {
    display: flex;
    align-items: center;
    gap: 9px;
    margin-bottom: 14px;
    font-family: var(--ob-font);
    font-size: 15px;
    font-weight: 700;
    color: var(--ob-ink);

    .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--ob-live); flex: 0 0 auto; }
    .dot.off { background: var(--ob-faint-2); }
    .muted { font-size: 13px; font-weight: 600; color: var(--ob-muted); }
}

/* ---- the switch: on or off for one object, and the only two-state control in the product ----
   Used at the right edge of an object card on Setup. Green when on because mint already means
   running everywhere else here (the Active chip, a published row); the blue accent stays the
   colour of a thing you press, not the colour of a state. */
.ob-toggle {
    position: relative;
    flex: 0 0 auto;
    width: 38px;
    height: 22px;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: var(--ob-faint-2);
    cursor: pointer;
    transition: background .16s;

    .knob {
        position: absolute;
        top: 3px;
        left: 3px;
        width: 16px;
        height: 16px;
        border-radius: 50%;
        background: #fff;
        box-shadow: 0 1px 2px rgba(16, 18, 24, .3);
        transition: transform .16s;
    }

    &.on { background: var(--ob-mint-ink); }
    &.on .knob { transform: translateX(16px); }

    &:hover { background: var(--ob-faint-1); }
    &.on:hover { background: #11603b; }

    &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
    &:disabled { opacity: .45; cursor: default; }
    &:disabled:hover { background: var(--ob-faint-2); }
}

/* the small segmented control the agent page's Works on uses: the page's own, one size down */
.ob-seg.sm {
    padding: 2px;
    .item { height: 26px; padding: 0 11px; font-size: 12.5px; }
}

/* a checkbox with a label and a line of consequence under it, on a form */
.ob-check {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 2px 10px;
    align-items: start;
    margin: 16px 0 0;
    cursor: pointer;
    font-family: var(--ob-font);

    input {
        grid-row: 1;
        width: 16px;
        height: 16px;
        margin: 1px 0 0;
        accent-color: var(--ob-accent);
        cursor: pointer;
    }
    .what { grid-row: 1; font-size: 14px; font-weight: 700; color: var(--ob-ink); }
    .hint { grid-column: 2; font-size: 12px; font-weight: 600; line-height: 1.45; color: var(--ob-muted); }
}

/* a line under something, saying what just happened or what is about to. Plain is muted; mint is
   news the person has to act on now (a key showing for its only time); rose is a refusal */
.ob-note {
    margin: 10px 0 0;
    font-family: var(--ob-font);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.55;
    color: var(--ob-muted);

    code {
        font-family: var(--ob-mono);
        font-size: 11.5px;
        padding: 1px 5px;
        border-radius: 5px;
        background: var(--ob-canvas);
        color: var(--ob-ink-2);
    }

    &.mint {
        padding: 9px 12px;
        border-radius: var(--ob-radius-tile);
        background: var(--ob-mint-bg);
        color: var(--ob-mint-ink);
    }
    &.rose {
        padding: 9px 12px;
        border-radius: var(--ob-radius-tile);
        background: var(--ob-rose-bg);
        color: var(--ob-rose-ink);
    }
    &.butter {
        padding: 9px 12px;
        border-radius: var(--ob-radius-tile);
        background: var(--ob-butter-bg);
        color: var(--ob-butter-ink);
    }
}

/* THE ONE MONEY LINE over a page's posts (Caps.PlanWarning, Dave 2026-10-02): no card carries
   money, so Home, Posts and the calendar say it once, above them, as a butter note */
.ob-planline { margin: 0 0 12px; }

/* the row of actions at the foot of a card: the one that does the thing first, the way out
   beside it, and anything destructive pushed to the far end by a .grow spacer */
.ob-card .acts {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 14px;
    flex-wrap: wrap;

    .grow { flex: 1 1 auto; }
}

/* a confirmation asked in place, never in a modal: what it is called, what it costs you, two
   buttons. It replaces the action row it was asked from, so the question cannot be missed */
.ob-ask {
    margin-top: 14px;
    padding: 14px 16px;
    border-radius: var(--ob-radius-tile);
    background: var(--ob-canvas);
    font-family: var(--ob-font);

    .q { font-size: 15px; font-weight: 800; color: var(--ob-ink); margin-bottom: 5px; }
    .ob-meta { margin: 0 0 12px; }
    .row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
}

/* ---- a form on a card: a stack of labelled fields, then the action row ---- */
.ob-form {
    display: flex;
    flex-wrap: wrap;
    gap: 16px 22px;
    margin: 14px 0 0;
    font-family: var(--ob-font);

    .field { display: flex; flex-direction: column; flex: 1 1 260px; min-width: 0; }
    .field.narrow { flex: 0 1 200px; }
    .field .ob-lbl { margin-bottom: 7px; }
    .field .hint { margin-top: 6px; font-size: 12px; font-weight: 600; line-height: 1.45; color: var(--ob-muted); }

    /* the dollar sits inside the field, so the number starts where a number should */
    .money { position: relative; display: block; }
    .money .sym { position: absolute; left: 13px; top: 50%; transform: translateY(-50%); font-size: 14px; font-weight: 700; color: var(--ob-muted); }
    .money .ob-input { padding-left: 26px; }
}

.ob-input {
    width: 100%;
    height: var(--ob-pill-h);
    padding: 0 13px;
    border: 1px solid var(--ob-line);
    border-radius: var(--ob-radius-tile);
    background: var(--ob-card);
    font-family: var(--ob-font);
    font-size: 14.5px;
    font-weight: 700;
    color: var(--ob-ink);
    outline: none;
    transition: border-color .12s, box-shadow .12s;

    &::placeholder { color: var(--ob-faint-2); font-weight: 600; }
    &:focus { border-color: var(--ob-accent); box-shadow: 0 0 0 3px var(--ob-accent-bg); }
    &:disabled { background: var(--ob-canvas); color: var(--ob-muted); }
}

/* AN AMOUNT EDITED WHERE IT IS READ used to be an atom, .ob-capedit: a column of field, hint
   and buttons, drawn in a rail card. One page ever used it and it is gone (2026-09-17) - the
   cap is now edited on the line it is read on, inside the money card, and that row is the
   agent page's own (okaybot-agent.css). The dollar inside a field lives on in .ob-form above,
   which is where every other amount is typed.
   ---- */

/* The two tiles, and the air under them. The bottom margin is here rather than nowhere because
   the unit switch used to sit between these and the chart and carried it (.ob-filters, 18/16);
   the switch went with credits on 2026-09-17 and took the gap with it. */
.ob-allow { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; margin-bottom: 16px; }
.ob-allow .ob-card { padding: 18px 20px 16px; }
.ob-allow .ob-money .num.total { font-size: 30px; }

.ob-allow .ob-lbl { display: flex; align-items: center; gap: 7px; }
/* when the plan's period renews, after the limit in brackets; the exact moment is on hover (2026-10-01) */
.ob-allow .of .renews { margin-left: 8px; cursor: default; }
.ob-allow .of .renews .sep { margin-right: 8px; }     /* the same room either side of the bullet */

/* a limit that exists but cannot be spent against yet: the figure stays, so a person sees the
   headroom their plan already carries, and the chip says why the bar is empty */
.ob-chip.soon {
    height: 18px; padding: 0 7px; border-radius: 999px;
    background: var(--ob-butter-bg); color: var(--ob-butter-ink);
    font-size: 9px; font-weight: 800; letter-spacing: .4px; text-transform: uppercase;
    display: inline-flex; align-items: center;
}

/* THE PLAN'S ROW WAS DRAWN HERE (billing/planrow.js, 2026-10-01 to 2026-10-02): the plan's name and
   chips, the line under it, and Manage billing under a hairline. It sat on Usage's panel and Account,
   then under the cards on /billing, and went: the title over the cards says the plan, and its line
   ends in the link to the Customer Portal. */
/* USAGE'S PANEL HAS NO RULES (Dave, 2026-10-02): no hairline above View on X - the space does that */
.ob-psect.ob-pricepanel .src { border-top: 0; }

/* THE CREDIT MARK WAS DRAWN HERE and went with credits on 2026-09-17. A dollar sign is a glyph
   the font already has, which is the end of the three escaping traps the drawn one carried: a
   title attribute, an SVG <text> and any string a template renders escaped all printed its tags
   as words. */
/* a figure and what follows it are one thing and never break across a line */
.ob-money .of, .ob-money .num { white-space: nowrap; }
/* "$6.10 of $10 this month" is one sentence, so the space inside it is a word space and not the
   gap between two things. At 16px it read as two columns AND pushed the rail onto a second line
   by five pixels; 8px both reads right and fits. */
.ob-money .row { gap: 8px; }

/* THE AMOUNT ON THE RAMP, moved here from okaybot-feed.css when the feed went (2026-09-24): the
   log line and the decision card draw every figure through it */
.ob-amt {
    font-variant-numeric: tabular-nums;
    letter-spacing: -.01em;
    white-space: nowrap;

    &.c1 { color: var(--ob-c1); font-weight: var(--ob-c1-w); }
    &.c2 { color: var(--ob-c2); font-weight: var(--ob-c2-w); }
    &.c3 { color: var(--ob-c3); font-weight: var(--ob-c3-w); }
    &.c4 { color: var(--ob-c4); font-weight: var(--ob-c4-w); }
    &.c5 { color: var(--ob-c5); font-weight: var(--ob-c5-w); }
    &.cx { color: var(--ob-cx); font-weight: var(--ob-cx-w); text-decoration: line-through; text-decoration-thickness: 1.5px; }
}

/* ---- HOW TO CONNECT AN AGENT (agents/connect_block.js) ----
   Setup's first step, the agent's panel and the docs all draw it (Dave, 2026-10-04: option E of
   the connect box mockups). WIDE, a box of two parts: the harnesses as a named list down the side,
   and beside it the tabs, then a dark block per thing to paste, each with a bar saying where it
   goes and its own Copy. NARROW - the agent's panel, a phone - the same buttons fold to a row of
   marks over the rest, with only the picked one named. The fold is a container query on the
   block's own width, so no page has to say which it is. Empty until it is given an agent, and
   takes no room then. */
.ob-connectbox {
    container-type: inline-size;
    font-family: var(--ob-font);

    .split {
        display: grid;
        grid-template-columns: 184px minmax(0, 1fr);
        border: 1px solid var(--ob-line-in);
        border-radius: var(--ob-radius-panel);
        background: var(--ob-card);
        overflow: hidden;
    }

    /* THE HARNESSES: greyed until pointed at; the picked one in its own colours */
    .side {
        display: flex;
        flex-direction: column;
        gap: 2px;
        padding: 10px;
        background: var(--ob-canvas);
        border-right: 1px solid var(--ob-line-in);
    }
    .h {
        display: flex;
        align-items: center;
        gap: 11px;
        height: 40px;
        padding: 0 12px;
        border: 0;
        border-radius: 10px;
        background: transparent;
        font-family: var(--ob-font);
        font-size: 13.5px;
        font-weight: 600;
        color: var(--ob-ink-2);
        text-align: left;
        cursor: pointer;

        img, svg { flex: 0 0 auto; width: 18px; height: 18px; filter: grayscale(1); opacity: .55; color: var(--ob-ink); }
        &:hover { color: var(--ob-ink); }
        &:hover img, &:hover svg { filter: none; opacity: 1; }
        &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 1px; }
        &.on { background: var(--ob-card); color: var(--ob-ink); font-weight: 700; box-shadow: 0 0 0 1px var(--ob-line); cursor: default; }
        &.on img, &.on svg { filter: none; opacity: 1; }
    }

    .main { display: flex; flex-direction: column; gap: 16px; min-width: 0; padding: 18px 20px 20px; }

    /* THE TABS: words on a rule, the picked one underlined in the accent */
    .tabs { display: flex; gap: 24px; border-bottom: 1px solid var(--ob-line-in); margin-bottom: 4px; }
    .tabs button {
        padding: 0 0 11px;
        margin-bottom: -1px;
        border: 0;
        border-bottom: 2px solid transparent;
        background: transparent;
        font-family: var(--ob-font);
        font-size: 14px;
        font-weight: 600;
        color: var(--ob-ink-2);
        cursor: pointer;

        &:hover { color: var(--ob-ink); }
        &.on { color: var(--ob-ink); font-weight: 700; border-bottom-color: var(--ob-accent); cursor: default; }
    }

    /* A BLOCK TO PASTE: the bar says where it goes - Terminal, or the file's path - and holds Copy,
       so nothing runs under the button; under it, a line at a time, wrapping with a hanging indent
       so a long command reads as one line carrying on */
    .win {
        border-radius: var(--ob-radius-tile);
        background: var(--ob-ink);
        color: var(--ob-term-ink);
        font-family: var(--ob-mono);
        font-size: 13px;
        line-height: 1.6;
        overflow: hidden;
    }
    .win + .win { margin-top: -4px; }
    .bar {
        display: flex;
        align-items: center;
        gap: 8px;
        height: 40px;
        padding: 0 8px 0 16px;
        background: var(--ob-term-bar);
        font-size: 12px;
        font-weight: 500;
        color: var(--ob-term-bar-ink);

        svg { flex: 0 0 auto; width: 15px; height: 15px; }
        .where { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    }
    .wcopy {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        flex: 0 0 auto;
        height: 28px;
        padding: 0 11px;
        border: 0;
        border-radius: 8px;
        background: var(--ob-term-btn);
        font-family: var(--ob-font);
        font-size: 12.5px;
        font-weight: 700;
        color: var(--ob-term-ink);
        cursor: pointer;

        svg { width: 14px; height: 14px; }
        &:hover { background: var(--ob-ink-2); }
    }
    .body { padding: 14px 18px 16px; }
    .ln { padding-left: 2ch; text-indent: -2ch; white-space: pre-wrap; overflow-wrap: anywhere; }
    .pr { color: var(--ob-term-prompt); user-select: none; }
    .key { color: var(--ob-term-key); }

    /* the same done inside the harness instead, under the sign-in block */
    .alt { margin: 0; font-size: 13px; font-weight: 600; line-height: 1.5; color: var(--ob-ink-2); }
    .alt code { font-family: var(--ob-mono); font-size: 12.5px; font-weight: 500; color: var(--ob-ink); background: var(--ob-canvas); border-radius: 6px; padding: 2px 7px; }

    .ob-note { margin: 0; }
    .main > .ob-pill { align-self: flex-start; }
}

/* NARROW: no room beside the list, so it folds to a row of marks over the tabs, the box loses its
   frame, and only the picked harness wears its name */
@container (max-width: 559px) {
    .ob-connectbox .split { display: block; border: 0; border-radius: 0; background: transparent; overflow: visible; }
    .ob-connectbox .side { flex-direction: row; flex-wrap: wrap; gap: 5px; padding: 0 0 16px; background: transparent; border: 0; }
    .ob-connectbox .h {
        justify-content: center;
        gap: 7px;
        min-width: 32px;
        height: 32px;
        padding: 0 7px;
        background: var(--ob-canvas);
        font-size: 13px;

        .nm { display: none; }
        &.on { padding: 0 10px 0 8px; background: var(--ob-card); box-shadow: inset 0 0 0 1.5px var(--ob-accent-ring); }
        &.on .nm { display: inline; }
    }
    .ob-connectbox .main { padding: 0; gap: 12px; }
    .ob-connectbox .tabs { gap: 20px; }
    .ob-connectbox .tabs button { font-size: 13.5px; padding-bottom: 10px; }
    .ob-connectbox .win { font-size: 12px; border-radius: 12px; }
    .ob-connectbox .bar { height: 36px; padding: 0 6px 0 14px; font-size: 11.5px; }
    .ob-connectbox .wcopy { height: 26px; padding: 0 10px; font-size: 12px; }
    .ob-connectbox .body { padding: 12px 14px 14px; }
    .ob-connectbox .alt { font-size: 12.5px; }
}

/* a key just made: Shown once and Done, under the block that holds it */
.ob-keyonce {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--ob-mint-ink);
}
 /* ============================================
   OKAYBOT - ONE AGENT, IN ITS PANEL
   What an agent's panel on Profiles draws beyond the head it shares with a profile's
   (okaybot-page.css, .ob-profpanel and .ob-agentpanel), and the grant row both panels draw.
   Everything here takes its colour, size and radius from the tokens; nothing carries a literal.

   THERE IS NO PAGE PER AGENT (2026-10-01, Dave). The agent page had the money and thirty days of
   columns, then Recent activity and Recent posts, on its left, and this panel on its right. The
   left went - Usage, Activity, Posts and Calendar draw all of it, filtered to one agent - and the
   panel moved under the accounts on Profiles, taking the cap with it.

   THE PANEL: the head, Today (the spend against the cap, and the cap changed in place), Access
   (the machines it is signed in on, its key, and Connect another agent, one list), and Remove
   agent at the foot. Its Permissions went on 2026-10-04 (Dave): the grant row below is the
   account's panel's now, which lists its agents.
   ============================================ */

/* ---- a band's heading: what it is, what it is for, and the way past it ---- */
.ob-secthead {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin: 26px 0 10px;

    .t { font-size: 15px; font-weight: 800; letter-spacing: -.01em; color: var(--ob-ink); }
    .m { font-size: 12.5px; font-weight: 600; color: var(--ob-muted); }
    .lnk { margin-left: auto; font-size: 12.5px; font-weight: 700; color: var(--ob-muted); text-decoration: none; }
    .lnk:hover { color: var(--ob-accent-ink); }

    /* the first heading in a column, where there is no card above it to be clear of */
    &.first { margin-top: 0; }
}

/* ---- the panel: THE DAILY CAP (2026-10-01) ----
   The label, and at the right of its line one small button, Change the cap; while it is changed
   the field with Save and Cancel beside it takes the button's place. The field is the small
   pill's height, so the line is the same height open as shut; a field that is a flex item
   carries no descender under it. What it spent today against the cap is on the agent's row,
   not here (Dave). */
.ob-capline {
    display: flex;
    align-items: center;
    gap: 6px;

    .money { position: relative; display: flex; flex: 0 1 104px; min-width: 0; }
    .money .sym { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); font-size: 13px; font-weight: 800; color: var(--ob-muted); }
    .money .ob-input { width: 100%; height: 30px; padding-left: 22px; font-size: 13.5px; font-weight: 800; }
    .money .ob-input.bad { border-color: var(--ob-rose-ink); }
}
/* the label at the left of the line, the button at the right. As specific as the panels' own
   label rule (okaybot-page.css), which gives every section's label its 10px under */
.ob-agentpanel .ob-psect .ob-capline > .ob-lbl { flex: 1 1 auto; margin: 0; }

/* ---- A GRANT: one row per account on an agent's panel, one per agent on a profile's panel
   (2026-10-01, the same row from both ends: agents/grant.html) ----
   Every account in the workspace: the face, then the name and the handle under it, then the two answer
   chips at the right edge. THE NAME GIVES WAY FIRST: it is cut with an ellipsis before the chips
   are pushed, so a long display name never shoves a control off the panel. It was
   a block with the two axes stacked under the name, each with three answers, until 2026-10-01. */
.ob-grant {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 12px 0;
    border: 0;
    border-top: 1px solid var(--ob-line-in);
    background: transparent;
    font-family: var(--ob-font);
    text-align: left;
    color: var(--ob-ink);

    > .ob-circle, > .ob-square { flex: 0 0 auto; }

    /* the name and the handle sit tight, a pair rather than two lines (Dave, 2026-10-01) */
    .who { display: flex; flex-direction: column; gap: 0; flex: 1 1 auto; min-width: 0; }
    /* ...and go to that account or that agent, from either end (2026-10-01): a link that looks
       like the name until it is pointed at */
    a.who { text-decoration: none; }
    a.who:hover b { color: var(--ob-accent-ink); }
    .who b { font-size: 14px; font-weight: 700; line-height: 1.3; color: var(--ob-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
    .who .handle { font-size: 12.5px; font-weight: 600; line-height: 1.3; color: var(--ob-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .who .handle.warn { color: var(--ob-accent-ink); }     /* the account needs reconnecting, in place of its handle: blue, never red (2026-10-05) */

    .chips { display: flex; gap: 5px; flex: 0 0 auto; }
}

/* MEDIUM AND NARROW (1599 and below, dev/docs/responsive.md): the panel is 380 wide, and beside
   the two chips a name had 24px. The chips take a line of their own under the name instead. */
@media (max-width: 1599px) {
    .ob-grant:not(.pick) { flex-wrap: wrap; row-gap: 8px; }
    .ob-grant .chips { flex: 1 0 100%; padding-left: 44px; }
}

/* the first row sits under the section's label, which is the line above it */
.ob-lbl + .ob-grant { border-top: 0; padding-top: 2px; }

/* A WORKSPACE WITH NO ACCOUNTS: the same row as a grant, with the way to connect one on it. A
   link, because it is one, so it takes the hover the grant rows do not. */
.ob-grant.pick {
    cursor: pointer;
    border-radius: var(--ob-radius-tile);
    padding: 12px 10px;
    margin: 0 -10px;
    width: calc(100% + 20px);
    text-decoration: none;

    .who b { color: var(--ob-ink-2); }
    &:hover { background: var(--ob-canvas); }
    &:hover .who b { color: var(--ob-ink); }
}

/* ============================================================
   THE ANSWER CHIPS: THE ANSWER IS ON OR IT IS OFF (2026-09-17, chips since 2026-10-01)
   One chip per axis says its answer, and a press opens the three under it. The answer IS the
   fact: this is the only place a person says what an agent may do without asking.

     Yes     solid accent   it may do this
     Ask me  pale accent    it may do this once you have said so - still a thing it may do
     No      grey           it may not, so there is nothing to light up

   Yes and Ask me are both a grant, so both are blue; No is the absence of one. The accent is the
   colour of a thing you press in this product rather than of a state (see .ob-toggle in
   okaybot-atoms.css), and here it is both: the chip is the state and the control at once.
   ============================================================ */
.ob-ans {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    height: 28px;
    padding: 0 10px;
    border: 0;
    border-radius: 999px;
    font-family: var(--ob-font);
    font-size: 11.5px;
    font-weight: 700;
    white-space: nowrap;
    cursor: pointer;

    .k { font-weight: 600; opacity: .8; }

    &.auto { background: var(--ob-accent); color: var(--ob-card); }
    &.ask { background: var(--ob-accent-bg); color: var(--ob-accent-ink); }
    &.no { background: var(--ob-canvas); color: var(--ob-ink-2); }
    &.open { box-shadow: 0 0 0 2px var(--ob-card), 0 0 0 4px var(--ob-accent); }
    &:focus-visible { outline: 2px solid var(--ob-accent); outline-offset: 2px; }
}

/* the three answers, hung under the row's right edge, over whatever is below it */
.ob-ansmenu {
    position: absolute;
    top: calc(100% - 4px);
    right: 0;
    z-index: 1000;
    width: 176px;
    padding: 5px;
    background: var(--ob-card);
    border-radius: 12px;
    box-shadow: 0 0 0 1px var(--ob-line), var(--ob-shadow-menu);

    button {
        display: flex;
        align-items: center;
        justify-content: space-between;
        width: 100%;
        padding: 8px 10px;
        border: 0;
        border-radius: 8px;
        background: transparent;
        font-family: var(--ob-font);
        font-size: 13px;
        font-weight: 600;
        color: var(--ob-ink);
        cursor: pointer;
    }
    button:hover { background: var(--ob-canvas); }
    button.on { background: var(--ob-accent-bg); color: var(--ob-accent-ink); font-weight: 700; }
    svg { width: 14px; height: 14px; }
}

/* Connect is the connect block (agents/connect_block.js), folded to its narrow form by its own
   container query at the panel's width: nothing of it is styled here */

/* ---- the panel: ACCESS (2026-10-01) ----
   Every way the agent gets in, one list: a row per machine it is signed in on, then its key, each
   with an icon tile, the name and the line under it, and its own actions at the right. A press
   opens .ob-sure under the row and leaves the row alone. With no key the last row is Create a
   key, a button the width of the list. */
.ob-conn {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 11px;
    width: 100%;
    padding: 11px 0;
    border: 0;
    border-top: 1px solid var(--ob-line-in);
    background: transparent;
    font-family: var(--ob-font);
    text-align: left;
    color: var(--ob-ink);

    .who { display: flex; flex-direction: column; gap: 2px; flex: 1 1 0; min-width: 0; }
    .who b { font-size: 14px; font-weight: 700; color: var(--ob-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .who .line { font-size: 12.5px; font-weight: 600; color: var(--ob-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .who code { font-family: var(--ob-mono); font-size: 12px; font-weight: 500; color: var(--ob-ink-2); }
    .btns { display: flex; gap: 2px; flex: 0 0 auto; }
    .btns .ob-pill, > .ob-pill { padding: 0 8px; }
    &.make { cursor: pointer; }
    &.make .who b { color: var(--ob-accent-ink); }
    &.make .ob-tile { background: var(--ob-accent-bg); color: var(--ob-accent-ink); }
    &.make:hover .who b { color: var(--ob-accent); }
    &.make:disabled { opacity: .5; cursor: default; }

    /* Connect another agent, open: its plus turns to a cross, the way to shut it */
    &.make .ob-tile svg { transition: transform .15s ease; }
    &.make.open .ob-tile svg { transform: rotate(45deg); }
}
.ob-lbl + .ob-conn { border-top: 0; padding-top: 2px; }

/* the connect block under Connect another agent, while it is open */
.ob-connopen { padding: 4px 0 8px; }

/* ARE YOU SURE, under the row it is about (Dave, 2026-10-01): one line of question on the left,
   No and Yes on the right, in a tinted box so it reads as about the row above and not a row of
   its own. The row itself does not change. */
.ob-sure {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0 0 10px;
    padding: 10px 10px 10px 14px;
    border-radius: 12px;
    background: var(--ob-rose-card);
    box-shadow: inset 0 0 0 1px var(--ob-rose-edge);

    .q { flex: 1 1 160px; font-size: 13px; font-weight: 700; color: var(--ob-ink); }
}

/* the square an Access row leads with: a machine, the key, or the plus of Create a key */
.ob-tile {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border-radius: 10px;
    background: var(--ob-canvas);
    color: var(--ob-ink-2);

    svg { width: 17px; height: 17px; }
}

/* ---- a panel's foot (2026-10-01) ----
   Right-aligned, at the bottom of the panel: Remove agent or Remove profile in the danger outline.
   Asked in place: "Are you sure?" on the left, Cancel and Yes, remove it on the right. No hairline
   over it since the panels lost theirs (Dave) - the space is the boundary. */
.ob-pfoot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 36px;

    .q { flex: 1 1 auto; font-size: 14px; font-weight: 800; color: var(--ob-ink); }
}
 /* ============================================
   OKAYBOT ACTIVITY
   The page, and only the page: two columns and no rail, the log on the left and the detail
   panel on the right, half each (--ob-act-panel; 40/60 until 2026-09-23). Everything inside either column is somebody else's
   sheet - the log rows are okaybot-home.css, the detail is okaybot-detail.css, the picture is
   okaybot-stage.css - because both of those are drawn on other screens too and a page must not
   decide what they look like. What this file owns is the split, and the panel's edges.
   ============================================ */

.ob-act {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, var(--ob-act-panel));
    min-height: 100vh;
    font-family: var(--ob-font);
    color: var(--ob-ink);
}

/* the column itself, not any .left inside it: the agent page's money card has one of its own */
.ob-act > .left {
    min-width: 0;
    padding: var(--ob-body-pad-top) var(--ob-body-pad-right) 60px var(--ob-body-pad-left);
}

/* ---- THE PANEL ----
   The sidebar's white and the sidebar's hairline, on the other side of the screen: flush to
   the top, to the right edge and to the foot of the viewport, with its own scroll. It is the
   item modal with the shade taken off and the lid left open, which is the whole idea - on a
   screen this wide there is no reason to cover the log to read one line of it.

   IT IS SHARED NOW (2026-09-17). The account page puts its devices and its history in one of
   these, on its own split (.ob-acct, okaybot-identity.css). The class stays here because this
   is where the shape was drawn and this is the page that explains it; what each page owns is
   its own grid, and the fold below is Activity's. A third page wanting one would be the point
   at which it moves to a sheet of its own.

   THE RIGHT PADDING IS THE PAGE'S OWN GAP AGAIN. It was 84px because the account avatar was
   fixed over that corner and the panel had to leave it the room it already occupied. The avatar
   moved to the sidebar's foot on 2026-09-16, so the panel reads 40px wider on every screen. */
.ob-panel {
    position: sticky;
    top: 0;
    align-self: start;
    height: 100vh;
    overflow-y: auto;
    min-width: 0;
    padding: var(--ob-act-panel-top) var(--ob-act-panel-pad) 56px var(--ob-body-pad-left);
    background: var(--ob-card);
    border-left: 1px solid var(--ob-line);
}

/* the live line under the title. It pulls up into the title's margin and gives it back below,
   so on the usual visit - nothing off, nothing drawn - the menu sits exactly where Posts' does */
.ob-acthead { margin: -18px 4px 18px; }

/* the sentinel the log pages against. It has height so it can be reached and so a page that
   is still coming does not read as the end of the list. */
.ob-act .ob-act-more { height: 28px; }

/* ---- the selected line ----
   The one place in the log that carries the accent. A row here is not a door any more - the
   thing it opens is already on the screen - so the highlight says which of the forty the panel
   is drawing, and hovering says which one it would draw instead. */
.ob-logrow.sel,
.ob-logrow.sel:hover { background: var(--ob-accent-bg); }
.ob-logrow.sel .act { color: var(--ob-accent-ink); }
.ob-logrow.sel .when { color: var(--ob-accent-ink); opacity: .7; }

/* ---- NARROW: 1199 and below (dev/docs/responsive.md) ----
   NO PANEL. The list takes the page, and what the panel drew opens over it when it is asked
   for: on Activity and Posts a line or a card opens the item modal (EventRow.show and
   PostCard.open ask $$.app.narrow() at the click), and on Profiles and Usage the panel itself
   comes back as a sheet (PanelSheet, okaybot-modal.css). It used to stack under the list here,
   which put the call a whole log's scroll away from the line that was clicked.

   Nothing in the list is drawn as the one the panel shows, because nothing is showing it: the
   selection is still held, so widening the window brings the same call back into the panel. */
@media (max-width: 1199px) {
    .ob-act { grid-template-columns: minmax(0, 1fr); }
    .ob-act > .ob-panel:not(.sheet) { display: none; }

    .ob-act .ob-logrow.sel { background: transparent; }
    .ob-act .ob-logrow.sel:hover { background: var(--ob-card); }
    .ob-act .ob-logrow.sel .act { color: inherit; }
    .ob-act .ob-logrow.sel .when { color: var(--ob-faint-1); opacity: 1; }
    /* the post keeps the edge it has when nothing has picked it - a hairline, or the waiting and
       stopped cards' own */
    .ob-act .ob-pc.quiet.sel:not(:hover) { box-shadow: inset 0 0 0 1px var(--ob-line-card); }
    .ob-act .ob-pc.quiet.wait.sel:not(:hover) { box-shadow: inset 0 0 0 1px var(--ob-butter-edge); }
    .ob-act .ob-pc.quiet.stop.sel:not(:hover) { box-shadow: inset 0 0 0 1px var(--ob-rose-edge); }
}
 