/* Dawn palette — must stay in sync with * docs/design/new-direct-messages-design/project/stream-v2-dawn.jsx * (DAWN const, lines 4-23). The widget renders inside the Vojo chat slot * which is itself a Dawn surface; the iframe inherits the same visual * canon to feel like a continuation of the host. */ :root { --bg: #181a20; --bg2: #0d0e11; --surface: #21232b; --surface2: #2a2d36; --divider: rgba(255, 255, 255, 0.06); --hairline: rgba(255, 255, 255, 0.08); --text: #e6e6e9; --muted: rgba(230, 230, 233, 0.55); --faint: rgba(230, 230, 233, 0.32); --fleet: #9580ff; --fleet-soft: #a59cff; --green: #7dd3a8; --amber: #d4b88a; --rose: #c08e7b; --section-pad-x: 40px; } [data-theme='light'] { /* Light theme is intentionally a thin remap. Vojo is dark-default; the * theme param exists so we don't fight an explicit user/host setting, * not because we expect daily light-mode use. */ --bg: #f5f5f7; --bg2: #ffffff; --surface: #f0f0f2; --surface2: #e8e8ec; --divider: rgba(0, 0, 0, 0.08); --hairline: rgba(0, 0, 0, 0.1); --text: #1a1a1d; --muted: rgba(26, 26, 29, 0.62); --faint: rgba(26, 26, 29, 0.4); } @media (max-width: 600px) { :root { --section-pad-x: 20px; } } * { box-sizing: border-box; /* Kills the translucent grey overlay iOS/Android WebViews paint on top * of any tapped element. On the wide refresh card this overlay was * read as «button stuck on grey» — the underlying state was correct, * the WebView's tap-highlight was not. Web browsers ignore this. */ -webkit-tap-highlight-color: transparent; } html, body, #app { height: 100%; } body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font: 14px/1.45 -apple-system, 'Segoe UI', 'Inter', system-ui, sans-serif; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; } .app { display: flex; flex-direction: column; min-height: 100%; max-width: 960px; margin: 0 auto; } /* The hero (avatar + name + handle + description + three-dots menu) is * OWNED BY THE HOST, not the widget — see src/app/features/bots/BotShell.tsx. * Removing the widget-side hero collapses the duplicate header that used to * sit between the host's BotShellHero (which the user actually sees) and * the iframe content. The widget body now starts with the active-state * section directly. */ /* ── Section ──────────────────────────────────────────────────────── */ .section { padding: 24px var(--section-pad-x) 20px; } .section + .section { padding-top: 4px; } /* Section label — same dark-bg pill vocabulary as `.section-status` so the * two pieces in the section-header row read as a matched pair (label * pill + status pill). The pill chrome wraps the existing uppercase * letter-spaced typography; chip is non-interactive, no cursor. */ .section-label { display: inline-flex; align-items: center; font-size: 13px; line-height: 20px; text-transform: uppercase; letter-spacing: 1.4px; font-weight: 600; color: var(--muted); background: var(--bg2); border: 1px solid var(--divider); border-radius: 8px; padding: 8px 14px; margin: 0 0 14px; white-space: nowrap; user-select: none; } /* Status pill — button-styled but intentionally non-interactive (no * cursor:pointer, no hover). Replaces the section header for stateful * sections (disconnected / connected / unknown / logging_out) — the * pill itself carries the section's identity, so a separate * `.section-label` would just duplicate the meaning. Same dark-bg * vocabulary (--bg2 / divider border) as `.recovery-action` and the * host hero's «О боте» chip. */ .section-status { display: inline-flex; align-items: center; gap: 10px; font-size: 13px; line-height: 20px; color: var(--muted); background: var(--bg2); border: 1px solid var(--divider); border-radius: 8px; padding: 8px 14px; margin: 0 0 14px; user-select: none; white-space: nowrap; } .section-status .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--faint); flex-shrink: 0; } .section-status.connected { color: var(--green); } .section-status.connected .dot { background: var(--green); box-shadow: 0 0 0 3px rgba(125, 211, 168, 0.16); } .section-status.disconnected { color: var(--rose); } .section-status.disconnected .dot { background: var(--rose); } .section-status.checking { color: var(--amber); } .section-status.checking .dot { background: var(--amber); } /* Wraps the section-status pill + a labeled refresh action when the * state has no other affordance (unknown / logging_out / connected * without loginId). Without this row, the user can stare at a * «Проверка статуса…» pill forever if the first list-logins reply * dropped on the wire. */ .section-recovery-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; } .section-recovery-row > .section-status { margin-bottom: 0; } .recovery-action { display: inline-flex; align-items: center; gap: 8px; background: var(--bg2); border: 1px solid var(--divider); border-radius: 8px; padding: 8px 14px; font: inherit; font-size: 13px; line-height: 20px; color: var(--muted); cursor: pointer; transition: background 0.12s, color 0.12s, border-color 0.12s; } :root[data-input='mouse'] .recovery-action:hover:not(:disabled) { background: var(--surface); color: var(--text); border-color: var(--hairline); } .recovery-action:disabled { opacity: 0.5; cursor: not-allowed; } .recovery-action svg { width: 16px; height: 16px; } /* ── Command card (action card with name + desc + chevron) ──────── */ .command-card { /* The widget runs in an iframe, so it does NOT inherit the host's * `button { -webkit-appearance: button }` rule (src/index.css:112). The * browser default for