/* * Motion tokens (D220). One curve and three durations, on :root so portals and * the view-transition pseudo-elements can read them. Enters take --v2-dur, * exits and closing take --v2-dur-fast, anything that travels takes --v2-dur-slow. * Animate transform and opacity only; folds are the one height exception. */ :root { --v2-dur-fast: 120ms; --v2-dur: 200ms; --v2-dur-slow: 320ms; --v2-ease: cubic-bezier(0.2, 0, 0, 1); } /* * V2 design tokens + base styles. All v2 styles are scoped under .v2-root * so the existing app's global CSS does not bleed in (and vice versa). */ /* The ONE deliberate exception to the .v2-root scope: the page canvas. App.tsx unconditionally stamps `modern-ui` (V1's dark gradient) on ; .v2-root paints only its own box, so the V1 dark showed through on load flash, overscroll rubber-banding, and every gap. V2App stamps `v2-canvas` while mounted; the compound selector out-ranks body.modern-ui (0-2-1 > 0-1-1) so bundle order cannot decide this. Literal colors: body sits outside .v2-root, so tokens don't reach it. Each theme's value is --v2-page-bg of that theme's set below, and the theme selectors are the ones those sets use; the two dark rules sit right after the dark token sets (signalTokens.test reads the first prefers-color-scheme block as the token set). utils/theme.ts stamps data-v2-theme on ; index.html does it before first paint, and this sheet ships in the entry bundle, so dark never opens on a light body. Until 11-04 the body was #f8f8fb in both themes, so a dark page rubber-banded onto a light canvas (consistency audit, finding 12). v2-layout-invariants pins each literal to its token. */ body.v2-canvas, body.modern-ui.v2-canvas { background: #eef0f4; } .v2-root { /* Palette */ --v2-bg: #ffffff; --v2-page-bg: #eef0f4; --v2-bg-subtle: #f9fafb; --v2-surface: #ffffff; --v2-surface-tint: #f2f4f7; --v2-surface-hover: #f2f4f7; --v2-border: #d0d5dd; --v2-border-soft: #e4e7ec; --v2-border-strong: #98a2b3; --v2-text-primary: #101828; --v2-message-text: #1f2937; /* TASK-701: inactive rail glyphs, join-radio outlines (3:1 on white and #f2f4f7) and the neutral selected pod row. Dark values sit in both dark sets below. */ /* D223 ruling (Cofounder, 11-01): the artboard's --ph (#98a2b3) is under 3:1 on the rail's own surface, --v2-shell-bg #eef0f4 (2.26:1), so this is the nearest grey that passes there: 3.05:1, and 3.47:1 on the white tab bar. Dark #5f6979 is 3.48:1 on the dark shell and 3.17:1 on its surface. */ --v2-rail-icon: #828a99; --v2-control-outline: #667085; /* D223 (TASK-735): the artboard's .pod.on in both themes, a neutral tint row whose mark alone turns cobalt. */ --v2-row-active: #f2f4f7; --v2-row-active-text: #101828; --v2-row-active-mute: #667085; --v2-row-active-mark: #1d3fd1; --v2-text-secondary: #475467; --v2-text-tertiary: #667085; --v2-text-muted: #667085; --v2-text-placeholder: #98a2b3; /* The tint step (TASK-122) in the Signal identity: shell ground #eef0f4 behind rail, pods and inspector; the content pane is an inset white card with a 1px #d0d5dd ring and a 6px radius — hard edges, no shadow. Mirrors --c-shell-bg / --c-content-radius in design-system/tokens.css. */ --v2-shell-bg: #eef0f4; --v2-content-radius: 6px; /* Character-avatar grounds (TASK-679, Lit Signal). The fill behind each face is generated in utils/avatars.ts CHARACTER_GROUNDS, which must match these values; avatarGrounds.test.ts pins the two together. Agents are cool, people warm, so the family still tells the species apart. Mirrors --c-avatar-* in design-system/tokens.css. */ --v2-avatar-agent-1: #dbe5f1; --v2-avatar-agent-2: #e1e6ee; --v2-avatar-agent-3: #d6e7e4; --v2-avatar-human-1: #f1e7df; --v2-avatar-human-2: #f2e8d8; --v2-avatar-human-3: #efe1dc; --v2-avatar-human-4: #f4e3d7; --v2-avatar-frame: rgba(16, 24, 40, 0.06); /* Dark ring (TASK-679 dark note): the tile stays light on dark, so the ring flips to a light hairline. No dark theme on main yet; TASK-680 points --v2-avatar-frame at this value under its dark scope. */ --v2-avatar-frame-dark: rgba(255, 255, 255, 0.10); /* Ink primary — filled buttons are ink; cobalt (#1d3fd1) is a MARK inside the app: links, the live dot, the agent name, the one card that needs you. On marketing surfaces cobalt is used as blocks. Mirrors --c-ink. */ --v2-ink: #101828; --v2-ink-hover: #1d2939; --v2-on-ink: #ffffff; --v2-on-accent: #ffffff; --v2-accent-fill: #1d3fd1; /* BEND-1 (spec 2026-08-30): the featured-tier name needs a step between 16 and 20. Mirrors --c-fs-feature in design-system/tokens.css; the two move together. */ --v2-fs-feature: 17px; --v2-accent: #1d3fd1; --v2-accent-strong: #1633a8; --v2-accent-soft: #e9edfb; --v2-accent-text: #1d3fd1; --v2-accent-deep: #0f2378; --v2-focus-ring: 0 0 0 3px rgba(29, 63, 209, 0.18); /* Platform brand + tint tokens (connectors) — mirror of --c-platform-* in design-system/tokens.css; the two move together. */ --v2-platform-telegram: #2aabee; --v2-platform-telegram-soft: #e4f3fb; --v2-platform-slack: #611f69; --v2-platform-slack-soft: #f3e9f4; --v2-platform-discord: #5865f2; --v2-platform-discord-soft: #eaecfe; --v2-platform-whatsapp: #1fa855; --v2-platform-whatsapp-soft: #e5f6ec; --v2-platform-x: #111827; --v2-platform-x-soft: #eef0f3; --v2-platform-instagram: #d62976; --v2-platform-instagram-soft: #fbe9f1; --v2-platform-messenger: #0084ff; --v2-platform-messenger-soft: #e5f2ff; --v2-platform-groupme: #00aff0; --v2-platform-groupme-soft: #e3f5fd; --v2-success: #10b981; /* Text-on-soft success — one canonical value (#1368: a second ad-hoc success-text hex is how a third one happens). Mirrors --c-success-text in design-system/tokens.css. */ --v2-success-text: #0f7a53; --v2-success-soft: #e3f6ec; --v2-success-ring: rgba(16, 185, 129, 0.40); --v2-success-ring-strong: rgba(16, 185, 129, 0.50); --v2-warning: #f4a23a; --v2-warning-soft: #fdefdc; --v2-info: #0891b2; --v2-info-soft: #dff4f8; --v2-danger: #ef4444; --v2-danger-text: #b91c1c; --v2-danger-soft: rgba(239, 68, 68, 0.10); /* TASK-687/698: board status and priority colour, the D215 B-lite choice (Sam, 10-31). Blocked and P0 are red; in progress stays cobalt; P1, P2, pending and done stay grey, with no green. Each state keeps its shape. Dark values are in both dark blocks below. */ --v2-prio-p0-bg: #b42318; --v2-prio-p0-fg: #ffffff; --v2-prio-ink: var(--v2-text-secondary); --v2-prio-p2-fg: #667085; --v2-prio-edge: var(--v2-border); --v2-status-live: var(--v2-accent); --v2-status-idle: var(--v2-border-strong); --v2-status-pending: #667085; --v2-status-blocked: #d92d20; /* Agent role colors */ --v2-pink: #f472b6; --v2-violet: #a78bfa; --v2-amber: #fbbf24; --v2-emerald: #34d399; --v2-sky: #60a5fa; --v2-rose: #fb7185; /* Typography — the Signal identity (direction C on the Shell Parity canvas, 2026-09-03). Three faces, each with one job: Bricolage Grotesque for display (headings, wordmarks — weight 700/800), IBM Plex Sans for every line of body and control text, IBM Plex Mono for meta (timestamps, ids, status). All self-hosted via @fontsource, imported in V2App.tsx above this sheet; the system stacks are fallbacks only while woff2 loads. Mirrors --c-font-* in design-system/tokens.css. */ --v2-font: "IBM Plex Sans", "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; --v2-font-display: "Bricolage Grotesque Variable", "Bricolage Grotesque", "Helvetica Neue", Arial, sans-serif; --v2-fs-body: 14px; --v2-lh-body: 20px; --v2-fs-meta: 12px; --v2-lh-meta: 16px; --v2-fs-label: 11px; --v2-lh-label: 14px; --v2-font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; /* Geometry */ --v2-radius-sm: 4px; --v2-radius: 4px; --v2-radius-lg: 6px; --v2-radius-pill: 999px; /* D223 Signal primitives (TASK-730), from the d215 artboard :root. Additive: the old --v2-radius and --v2-shadow values above stay until PR2 moves consumers onto the scale. Pinned by __tests__/signalTokens.test.ts. */ --v2-ink-2: #1d2433; --v2-fill-a: #333c4e; --v2-fill-b: #161d2c; --v2-fill-edge: #0b101b; --v2-on-fill: #ffffff; --v2-accent-fill-a: #2e50e2; --v2-accent-fill-b: #1b3bc6; --v2-key-a: #ffffff; --v2-key-b: #f4f5f7; --v2-key-edge: rgba(15, 23, 42, 0.14); --v2-key-inset: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 1px 2px rgba(16, 24, 40, 0.06); --v2-shadow-pane: 0 0 0 1px rgba(16, 24, 40, 0.07), 0 1px 2px rgba(16, 24, 40, 0.04), 0 12px 28px -20px rgba(16, 24, 40, 0.18); --v2-shadow-pop: 0 0 0 1px rgba(16, 24, 40, 0.08), 0 2px 4px rgba(16, 24, 40, 0.04), 0 18px 36px -14px rgba(16, 24, 40, 0.28); /* The card edge (D215): a 1px ring plus a 1px lift, no border. Cards that are not a .v2p-tc take it from here rather than copying the pair. */ --v2-ring-card: 0 0 0 1px var(--v2-border-soft), 0 1px 2px rgba(16, 24, 40, 0.04); --v2-av-ring: rgba(16, 24, 40, 0.07); --v2-scrim: rgba(16, 24, 40, 0.18); --v2-r-6: 6px; --v2-r-7: 7px; --v2-r-8: 8px; --v2-r-9: 9px; --v2-r-10: 10px; --v2-r-12: 12px; --v2-r-14: 14px; /* Elevation */ --v2-shadow-sm: none; --v2-shadow: none; --v2-shadow-lg: none; --v2-shadow-pending: none; /* the one chrome-card shadow: a PENDING decision or approval only */ /* Layout */ --v2-rail-w: 56px; --v2-pods-w: 260px; --v2-inspector-w: 272px; /* Keep production's scoped V2 layer in step with design-system/tokens.css. The typing cadence belongs to the minimal mark; pulse/spin retain their separate state-affordance semantics. */ --motion-stagger: 0.18s; --motion-breath: 1.2s; --motion-pulse: 1.6s; --motion-spin: 0.8s; --motion-ease-breath: ease-in-out; --motion-ease-state: ease-out; --motion-ease-linear: linear; font-family: var(--v2-font); color: var(--v2-text-primary); background: var(--v2-page-bg); height: 100vh; width: 100vw; overflow: hidden; display: flex; flex-direction: column; font-size: var(--v2-fs-body); line-height: var(--v2-lh-body); min-width: 0; } /* D216 dark set. Values are the artboard's dark :root (index.html, the @media + :root[data-theme="dark"] pair), mapped onto v2 token names. Scoped to .v2-root under the html attribute set by utils/theme.ts: explicit dark, or auto when the OS asks for dark. The light set is the block above. Not from the artboard, pending ux-lead: --v2-focus-ring, the success and danger text lifts, and the soft fills. Avatar and persona tints stay literal. */ :root[data-v2-theme="dark"] .v2-root { /* D223 Signal primitives (TASK-730); dark values from the d215 artboard. */ --v2-ink-2: #d3d8e1; --v2-fill-a: #f4f5f8; --v2-fill-b: #dce0e8; --v2-fill-edge: #c4cad5; --v2-on-fill: #0b0e13; --v2-accent-fill-a: #3d5ef2; --v2-accent-fill-b: #2948d8; --v2-key-a: #1e2531; --v2-key-b: #191f29; --v2-key-edge: rgba(255, 255, 255, 0.10); --v2-key-inset: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 1px 2px rgba(0, 0, 0, 0.3); --v2-shadow-pane: 0 0 0 1px rgba(255, 255, 255, 0.06), 0 12px 28px -20px rgba(0, 0, 0, 0.6); --v2-shadow-pop: 0 0 0 1px rgba(255, 255, 255, 0.10), 0 18px 36px -14px rgba(0, 0, 0, 0.75); --v2-av-ring: rgba(255, 255, 255, 0.10); --v2-scrim: rgba(0, 0, 0, 0.45); --v2-bg: #141922; --v2-page-bg: #0b0e13; --v2-bg-subtle: #181e28; --v2-surface: #141922; --v2-surface-tint: #1e2531; --v2-surface-hover: #1e2531; --v2-border: #364051; --v2-border-soft: #262d3a; --v2-border-strong: #5f6979; --v2-prio-p0-bg: #f97066; --v2-prio-p0-fg: #0b0e13; --v2-prio-p2-fg: #8b94a5; --v2-status-pending: #7c8696; --v2-status-blocked: #f97066; --v2-text-primary: #e8ebf1; --v2-message-text: #e8ebf1; --v2-rail-icon: #5f6979; --v2-control-outline: #737e92; --v2-row-active: #1e2531; --v2-row-active-text: #e8ebf1; --v2-row-active-mute: #8b94a5; --v2-row-active-mark: #6680ff; --v2-text-secondary: #aeb6c4; --v2-text-tertiary: #8b94a5; --v2-text-muted: #8b94a5; --v2-text-placeholder: #5f6979; --v2-shell-bg: #0b0e13; --v2-ink: #f4f5f8; --v2-ink-hover: #dce0e8; --v2-on-ink: #0b0e13; --v2-accent: #6680ff; --v2-accent-strong: #3d5ef2; --v2-accent-soft: #1b2546; --v2-accent-text: #8aa2ff; --v2-accent-deep: #2948d8; --v2-focus-ring: 0 0 0 2px var(--v2-bg), 0 0 0 4px #8aa2ff; --v2-success-text: #3ccf95; --v2-success-soft: rgba(16, 185, 129, 0.16); --v2-danger-text: #ff8a8a; --v2-danger-soft: rgba(239, 68, 68, 0.16); --v2-on-accent: #ffffff; --v2-accent-fill: #3d5ef2; --v2-warning-soft: rgba(244, 162, 58, 0.16); --v2-info-soft: rgba(8, 145, 178, 0.16); --v2-platform-x: #e8ebf1; --v2-platform-x-soft: rgba(17, 24, 39, 0.16); --v2-platform-telegram-soft: rgba(42, 171, 238, 0.16); --v2-platform-slack-soft: rgba(97, 31, 105, 0.16); --v2-platform-discord-soft: rgba(88, 101, 242, 0.16); --v2-platform-whatsapp-soft: rgba(31, 168, 85, 0.16); --v2-platform-instagram-soft: rgba(214, 41, 118, 0.16); --v2-platform-messenger-soft: rgba(0, 132, 255, 0.16); --v2-platform-groupme-soft: rgba(0, 175, 240, 0.16); color-scheme: dark; } @media (prefers-color-scheme: dark) { :root:not([data-v2-theme="light"]) .v2-root { /* D223 Signal primitives (TASK-730); dark values from the d215 artboard. */ --v2-ink-2: #d3d8e1; --v2-fill-a: #f4f5f8; --v2-fill-b: #dce0e8; --v2-fill-edge: #c4cad5; --v2-on-fill: #0b0e13; --v2-accent-fill-a: #3d5ef2; --v2-accent-fill-b: #2948d8; --v2-key-a: #1e2531; --v2-key-b: #191f29; --v2-key-edge: rgba(255, 255, 255, 0.10); --v2-key-inset: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 1px 2px rgba(0, 0, 0, 0.3); --v2-shadow-pane: 0 0 0 1px rgba(255, 255, 255, 0.06), 0 12px 28px -20px rgba(0, 0, 0, 0.6); --v2-shadow-pop: 0 0 0 1px rgba(255, 255, 255, 0.10), 0 18px 36px -14px rgba(0, 0, 0, 0.75); --v2-av-ring: rgba(255, 255, 255, 0.10); --v2-scrim: rgba(0, 0, 0, 0.45); --v2-bg: #141922; --v2-page-bg: #0b0e13; --v2-bg-subtle: #181e28; --v2-surface: #141922; --v2-surface-tint: #1e2531; --v2-surface-hover: #1e2531; --v2-border: #364051; --v2-border-soft: #262d3a; --v2-border-strong: #5f6979; --v2-prio-p0-bg: #f97066; --v2-prio-p0-fg: #0b0e13; --v2-prio-p2-fg: #8b94a5; --v2-status-pending: #7c8696; --v2-status-blocked: #f97066; --v2-text-primary: #e8ebf1; --v2-message-text: #e8ebf1; --v2-rail-icon: #5f6979; --v2-control-outline: #737e92; --v2-row-active: #1e2531; --v2-row-active-text: #e8ebf1; --v2-row-active-mute: #8b94a5; --v2-row-active-mark: #6680ff; --v2-text-secondary: #aeb6c4; --v2-text-tertiary: #8b94a5; --v2-text-muted: #8b94a5; --v2-text-placeholder: #5f6979; --v2-shell-bg: #0b0e13; --v2-ink: #f4f5f8; --v2-ink-hover: #dce0e8; --v2-on-ink: #0b0e13; --v2-accent: #6680ff; --v2-accent-strong: #3d5ef2; --v2-accent-soft: #1b2546; --v2-accent-text: #8aa2ff; --v2-accent-deep: #2948d8; --v2-focus-ring: 0 0 0 2px var(--v2-bg), 0 0 0 4px #8aa2ff; --v2-success-text: #3ccf95; --v2-success-soft: rgba(16, 185, 129, 0.16); --v2-danger-text: #ff8a8a; --v2-danger-soft: rgba(239, 68, 68, 0.16); --v2-on-accent: #ffffff; --v2-accent-fill: #3d5ef2; --v2-warning-soft: rgba(244, 162, 58, 0.16); --v2-info-soft: rgba(8, 145, 178, 0.16); --v2-platform-x: #e8ebf1; --v2-platform-x-soft: rgba(17, 24, 39, 0.16); --v2-platform-telegram-soft: rgba(42, 171, 238, 0.16); --v2-platform-slack-soft: rgba(97, 31, 105, 0.16); --v2-platform-discord-soft: rgba(88, 101, 242, 0.16); --v2-platform-whatsapp-soft: rgba(31, 168, 85, 0.16); --v2-platform-instagram-soft: rgba(214, 41, 118, 0.16); --v2-platform-messenger-soft: rgba(0, 132, 255, 0.16); --v2-platform-groupme-soft: rgba(0, 175, 240, 0.16); color-scheme: dark; } } /* The page canvas in dark (see body.v2-canvas at the top of this sheet). */ :root[data-v2-theme="dark"] body.v2-canvas, :root[data-v2-theme="dark"] body.modern-ui.v2-canvas { background: #0b0e13; color-scheme: dark; } @media (prefers-color-scheme: dark) { :root:not([data-v2-theme="light"]) body.v2-canvas, :root:not([data-v2-theme="light"]) body.modern-ui.v2-canvas { background: #0b0e13; color-scheme: dark; } } .v2-root *, .v2-root *::before, .v2-root *::after { box-sizing: border-box; } /* Thin overlay-style scrollbars on every scroll container inside v2. Without this, Linux + Windows Chrome show a chunky default scrollbar with a gray track that clashes with the otherwise-flat surfaces. macOS Chrome paints overlay scrollbars by default and is unaffected. */ .v2-root *::-webkit-scrollbar { width: 8px; height: 8px; } .v2-root *::-webkit-scrollbar-track { background: transparent; } .v2-root *::-webkit-scrollbar-thumb { background: var(--v2-border-strong); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; } .v2-root *::-webkit-scrollbar-thumb:hover { background: var(--v2-text-tertiary); background-clip: padding-box; border: 2px solid transparent; } .v2-root *::-webkit-scrollbar-corner { background: transparent; } /* Firefox + modern Chrome standard property. */ .v2-root * { scrollbar-width: thin; scrollbar-color: var(--v2-border-strong) transparent; } .v2-root :where(button:not(.MuiButtonBase-root)) { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; padding: 0; } .v2-root input, .v2-root textarea { font-family: inherit; color: inherit; } .v2-root a { color: inherit; text-decoration: none; } /* ---------- Language switch ---------- */ /* Language dropdown. The trigger deliberately reads like a plain nav option (no pill chrome) so it sits naturally among nav links; the menu is a small anchored card. */ .v2-lang-switch { position: relative; display: inline-flex; } .v2-lang-switch__trigger { display: inline-flex; align-items: center; gap: 4px; padding: 4px 6px; border-radius: var(--v2-radius-sm, 8px); color: var(--v2-text-secondary); font-size: 13px; font-weight: 500; line-height: 1; transition: color 80ms ease; } .v2-lang-switch__trigger:hover { color: var(--v2-text-primary); } .v2-lang-switch__menu { position: absolute; top: calc(100% + 6px); right: 0; z-index: 40; min-width: 132px; margin: 0; padding: 6px; list-style: none; background: var(--v2-surface); border: 1px solid var(--v2-border); border-radius: 10px; box-shadow: var(--v2-shadow-md, 0 8px 24px rgba(16, 24, 40, 0.12)); } .v2-lang-switch__item { display: flex; width: 100%; align-items: center; justify-content: space-between; gap: 14px; min-height: 34px; padding: 8px 12px; border-radius: 7px; color: var(--v2-text-secondary); font-size: 13px; text-align: left; transition: background 80ms ease, color 80ms ease; } .v2-lang-switch__item:hover { background: var(--v2-bg-subtle); color: var(--v2-text-primary); } .v2-lang-switch__item--active { color: var(--v2-accent-text); font-weight: 600; } .v2-lang-switch__check { display: inline-flex; } /* ---------- Shared filter grammar ---------- */ .v2-root button:focus-visible, .v2-root input:focus-visible, .v2-root textarea:focus-visible, .v2-root a:focus-visible { outline: none; box-shadow: var(--v2-focus-ring); } /* ---------- Layout shell ---------- */ /* Authenticated v2 routes reserve a compact shell row for the email verification reminder. The inner shell/panes must use the remaining space, not 100vh, or the composer and feature-page footers would clip. */ .v2-authenticated-shell { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); height: 100vh; min-height: 0; overflow: hidden; } .v2-authenticated-shell__content { /* Row 2 always, not by auto-placement. Row 1 is the `auto` row the banner sits in, so a verified user (no banner) has the content as the FIRST grid child and auto-placement put it in that row instead: the inset card then sized to its own content and any page shorter than the viewport ended 178-195px above the bottom gutter instead of 14 (TASK-157, ux-lead at 1200). Row 1 collapses to 0 with nothing in it, which is the same layout the banner variant already produced. */ grid-row: 2; min-width: 0; min-height: 0; overflow: hidden; } .v2-authenticated-shell__content > .v2-shell, .v2-authenticated-shell__content .v2-pane { height: 100%; } .v2-verification-banner { box-sizing: border-box; display: grid; grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: "content resend dismiss"; align-items: center; gap: 8px; min-width: 0; min-height: 40px; padding: 4px 16px; border-bottom: 1px solid var(--v2-border-soft); background: var(--v2-warning-soft); color: var(--v2-text-primary); font-size: 14px; line-height: 20px; } .v2-verification-banner__content { grid-area: content; min-width: 0; } .v2-verification-banner__text { display: block; overflow-wrap: anywhere; } /* Resend feedback stays in the visible status reminder so a sighted person receives the same confirmation as assistive technology. */ .v2-verification-banner__notice { display: block; margin-top: 2px; color: var(--v2-text-secondary); font-size: 13px; line-height: 18px; } .v2-root button.v2-verification-banner__resend, .v2-root button.v2-verification-banner__dismiss { flex: none; min-height: 44px; border: 0; background: transparent; color: var(--v2-accent-text); font: inherit; font-weight: 650; cursor: pointer; } .v2-root button.v2-verification-banner__resend { grid-area: resend; padding: 4px 4px; } .v2-root button.v2-verification-banner__dismiss { grid-area: dismiss; display: grid; width: 44px; height: 44px; padding: 0; place-items: center; color: var(--v2-text-secondary); } .v2-root button.v2-verification-banner__resend:disabled { cursor: progress; opacity: 0.7; } .v2-shell { display: grid; grid-template-columns: var(--v2-rail-w) var(--v2-pods-w) 1fr var(--v2-inspector-w); grid-template-rows: 1fr; height: 100vh; width: 100%; background: var(--v2-shell-bg); } .v2-shell--no-inspector { grid-template-columns: var(--v2-rail-w) var(--v2-pods-w) 1fr; } .v2-shell--feature { grid-template-columns: var(--v2-rail-w) var(--v2-pods-w) minmax(0, 1fr); } .v2-shell--feature-wide { grid-template-columns: var(--v2-rail-w) minmax(0, 1fr); } .v2-pane { height: 100vh; overflow: hidden; display: flex; flex-direction: column; border-right: 1px solid transparent; /* the content card's ring replaces the pane hairlines */ background: var(--v2-shell-bg); } .v2-pane--rail { background: var(--v2-shell-bg); /* The 56px rail lets the language menu, attention badge and nav hover labels escape the pane instead of clipping them at its edge. */ overflow: visible; } /* The content pane is the inset card: 8px gutter top/right/bottom, 1px ring, 14px radius (TASK-122). Height subtracts the gutters; the banner variant beats `.v2-authenticated-shell__content .v2-pane { height: 100% }` on specificity. */ .v2-pane--main { margin: 8px 8px 8px 0; height: calc(100vh - 16px); background: var(--v2-surface); border: 1px solid var(--v2-border); border-radius: var(--v2-content-radius); overflow: hidden; } .v2-authenticated-shell .v2-pane--main { height: calc(100% - 16px); } /* Inspector open: the card meets the inspector gutter. --feature-wide has no inspector column, so it keeps its right gutter. */ .v2-shell:not(.v2-shell--no-inspector):not(.v2-shell--feature):not(.v2-shell--feature-wide) .v2-pane--main { margin-right: 0; } .v2-inspector-backdrop { display: none; } /* ---------- Nav rail ---------- */ .v2-rail { height: 100%; /* 56px rail - 1px pane border - 2 x 8px = 39px, room for the 38px nav glyph. At 10px the nav was 35px wide and scrolled sideways by 2px, which the staging smoke's "rail no h-scroll @1440px" check caught after #3139. */ padding: 6px 8px; display: flex; flex-direction: column; gap: 8px; justify-content: space-between; } /* Bottom of the rail: a help/preferences cluster (language · guide · feedback) sits above a divider, then the account cluster (avatar · sign out). */ .v2-rail__foot { display: flex; flex-direction: column; align-items: center; gap: 10px; } .v2-rail__utility { display: flex; flex-direction: column; align-items: center; gap: 8px; } /* The language switch left the rail for Settings › Look (D215, TASK-691). */ .v2-rail__brand { display: flex; align-items: center; justify-content: center; gap: 0; font-weight: 700; font-size: 17px; padding: 0; margin-bottom: 8px; color: var(--v2-text-primary); letter-spacing: -0.02em; height: 36px; } .v2-rail__brand > span:not(.v2-rail__brand-icon) { display: none; } /* D223 (TASK-735): the artboard's .logo, a 36px cobalt key. */ .v2-rail__brand-icon { width: 36px; height: 36px; border-radius: var(--v2-r-10, 10px); background: var(--v2-accent); background: linear-gradient(180deg, var(--v2-accent-fill-a, var(--v2-accent)), var(--v2-accent-fill-b, var(--v2-accent))); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 6px 12px -6px rgba(29, 63, 209, 0.5); color: #fff; display: grid; place-items: center; font-weight: 800; font-size: 14px; } .v2-rail__nav { display: flex; flex: 1; flex-direction: column; gap: 8px; min-height: 0; overflow: visible; padding-right: 0; } /* Below the rail's fixed chrome (245px) plus six nav items (268px), scroll the nav vertically while keeping its horizontal overflow hidden. */ @media (max-height: 460px) { .v2-rail__nav { overflow-y: auto; overflow-x: hidden; } } .v2-root button.v2-rail__item { position: relative; display: flex; align-items: center; justify-content: center; gap: 0; /* D223 (TASK-735): the artboard's .nav, a bare 38px glyph at rest, coloured by --v2-rail-icon: the nearest grey to the artboard's that clears 3:1 on the shell (values and ratios at the token). */ width: 38px; min-height: 38px; height: 38px; align-self: center; padding: 0; border-radius: var(--v2-r-10, 10px); font-size: 14px; font-weight: 600; color: var(--v2-rail-icon); background: transparent; text-align: left; transition: background 80ms ease; } .v2-rail__item:hover::after, .v2-rail__item:focus-visible::after { content: attr(data-label); position: absolute; left: calc(100% + 8px); top: 50%; transform: translateY(-50%); z-index: 10; padding: 5px 8px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-surface); color: var(--v2-text-primary); font-size: 12px; font-weight: 600; white-space: nowrap; } .v2-root button.v2-rail__item:hover { background: var(--v2-surface-hover); color: var(--v2-text-primary); } .v2-root button.v2-rail__item--active { background: var(--v2-ink); background: linear-gradient(180deg, var(--v2-fill-a, var(--v2-ink)), var(--v2-fill-b, var(--v2-ink))); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16), 0 4px 10px -3px rgba(16, 24, 40, 0.3); color: var(--v2-on-fill, var(--v2-on-ink)); font-weight: 600; } .v2-rail__item-icon { width: 20px; height: 20px; display: grid; place-items: center; flex-shrink: 0; } .v2-rail__item-label { display: none; } .v2-rail__divider { width: 24px; height: 1px; margin: 6px auto; background: var(--v2-border); } .v2-rail__user { min-height: 48px; padding: 0; display: flex; flex-direction: column; align-items: center; gap: 8px; border-top: none; } .v2-rail__user > div { display: none; } .v2-rail__feedback, .v2-rail__account { width: 24px; height: 24px; display: grid; place-items: center; border-radius: var(--v2-radius-sm); color: var(--v2-text-tertiary); background: transparent; border: none; cursor: pointer; transition: background 80ms ease, color 80ms ease; } .v2-rail__feedback:hover, .v2-rail__feedback[aria-expanded="true"], .v2-rail__account:hover, .v2-rail__account[aria-expanded="true"] { background: var(--v2-surface-hover); color: var(--v2-text-primary); } .v2-rail__feedback svg { width: 18px; height: 18px; } .v2-rail__account { width: 36px; height: 36px; padding: 0; } /* The artboard's .av--36: a 36px square, 9px corners, a hairline ring. */ .v2-rail__account .v2-avatar { width: 36px; height: 36px; border: 0; border-radius: var(--v2-r-9, 9px); box-shadow: 0 0 0 1px var(--v2-av-ring, var(--v2-border-soft)); pointer-events: none; } .v2-root .v2-account-menu__popover.MuiPaper-root { min-width: 190px; margin-left: 8px; overflow: hidden; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-lg); background: var(--v2-surface); color: var(--v2-text-primary); box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12); } .v2-account-menu__list { display: flex; flex-direction: column; gap: 2px; padding: 6px; } .v2-root button.v2-account-menu__item { display: flex; align-items: center; gap: 10px; min-height: 38px; padding: 8px 10px; border: 0; border-radius: var(--v2-radius-sm); background: transparent; color: var(--v2-text-secondary); font-size: 13px; font-weight: 600; text-align: left; cursor: pointer; } .v2-root button.v2-account-menu__item:hover, .v2-root button.v2-account-menu__item:focus-visible { background: var(--v2-surface-hover); color: var(--v2-text-primary); outline: none; } .v2-root button.v2-account-menu__item:focus-visible { box-shadow: var(--v2-focus-ring); } .v2-account-menu__item svg { width: 18px; height: 18px; color: var(--v2-text-tertiary); } .v2-root .v2-feedback-menu__popover.MuiPaper-root { min-width: 210px; margin-left: 8px; overflow: hidden; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-lg); background: var(--v2-surface); color: var(--v2-text-primary); box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12); } .v2-feedback-menu__list { display: flex; flex-direction: column; gap: 2px; padding: 6px; } .v2-feedback-menu__item { display: flex; align-items: center; gap: 10px; min-height: 38px; padding: 8px 10px; border-radius: var(--v2-radius-sm); color: var(--v2-text-secondary); font-size: 13px; font-weight: 600; transition: background 80ms ease, color 80ms ease; } .v2-feedback-menu__item:hover, .v2-feedback-menu__item:focus-visible { background: var(--v2-surface-hover); color: var(--v2-text-primary); outline: none; } .v2-feedback-menu__item:focus-visible { box-shadow: var(--v2-focus-ring); } .v2-feedback-menu__item svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--v2-text-tertiary); } /* ---------- Pods sidebar ---------- */ .v2-pods { display: flex; flex-direction: column; height: 100%; padding: 24px 16px; } .v2-pods__create { display: flex; flex-direction: column; gap: 7px; padding: 10px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius); background: var(--v2-surface); margin-bottom: 14px; } /* Who can join is separate from public visibility. The default remains open; * invite-only is needed before a private pod can hold an outsider-writable * connector grant. These are radios rather than the old option buttons whose * styling lost to the global button reset (#870). */ .v2-pods__join-policy { display: flex; min-width: 0; flex-direction: column; gap: 6px; margin: 3px 0; padding: 0; border: 0; color: var(--v2-text-secondary); font: 400 12px/16px var(--v2-font); } .v2-pods__join-policy legend { margin-bottom: 6px; color: var(--v2-text-primary); font-weight: 600; } .v2-pods__join-policy label { display: flex; align-items: center; gap: 8px; min-height: 40px; cursor: pointer; } /* Native radios ignore border tokens, so draw the outline from one. Checked, the border thickens into the accent dot, so no cobalt background is added. */ .v2-pods__join-policy input { appearance: none; width: 14px; height: 14px; margin: 0; flex-shrink: 0; border: 1.5px solid var(--v2-control-outline); border-radius: 50%; background: var(--v2-surface); cursor: pointer; } .v2-pods__join-policy input:checked { border: 4px solid var(--v2-accent); } .v2-pods__join-policy-hint { margin: 0; color: var(--v2-text-secondary); font: 400 var(--v2-fs-meta)/16px var(--v2-font); } .v2-pods__create-input { width: 100%; padding: 8px 9px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-surface); font-size: 12px; outline: none; } .v2-pods__create-input:focus { border-color: var(--v2-accent); } .v2-pods__create-error { font-size: 11px; color: var(--v2-danger); } .v2-pods__create-actions { display: flex; justify-content: flex-end; gap: 8px; } .v2-root button.v2-pods__create-cancel, .v2-root button.v2-pods__create-submit { padding: 6px 9px; border-radius: var(--v2-radius-sm); font-size: 12px; font-weight: 700; } .v2-root button.v2-pods__create-cancel { color: var(--v2-text-tertiary); } .v2-root button.v2-pods__create-submit { background: var(--v2-ink); color: var(--v2-on-ink); } .v2-root button.v2-pods__create-submit:disabled { background: var(--v2-border-strong); cursor: not-allowed; } @media (max-width: 760px) { .v2-root button.v2-pods__create-cancel, .v2-root button.v2-pods__create-submit { min-height: 40px; } } /* Must sit AFTER the base chip rule: both this and the shared .v2-root button.v2-filter-segment__item--active rule weigh 0-2-1, so source order decides — with the base chip rule later in the file, the selected chip shipped grey-on-blue (measured live 2026-08-23). */ .v2-pods__list { flex: 1; overflow-y: auto; padding: 0; } /* Title-row sibling — sits to the right of the pod name. Slack/iMessage pattern: the timestamp lives on the same line as the title so the second line is reserved entirely for the message preview. */ .v2-pods__empty { padding: 32px 16px; text-align: center; color: var(--v2-text-tertiary); font-size: 13px; } /* First-run is a shell-level dialog. The overlay separates the one-time setup path from pod content, while the dialog itself owns overflow on short mobile viewports so every step and the dismissal control stay reachable. */ .v2-first-run__overlay { position: fixed; inset: 0; z-index: 120; display: grid; place-items: center; overflow-y: auto; padding: 24px; background: var(--v2-scrim); } /* D215 .v2-first-run: the pop surface at 16px, its edge carried by the pop shadow rather than a border. */ .v2-first-run { width: min(720px, 100%); max-height: calc(100dvh - 48px); overflow-y: auto; padding: 28px; border-radius: 16px; background: var(--v2-surface); color: var(--v2-text-primary); box-shadow: var(--v2-shadow-pop); outline: none; } .v2-first-run__eyebrow { margin-bottom: 8px; color: var(--v2-text-muted); font: 500 11.5px/16px var(--v2-font-mono); } .v2-first-run__heading-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; } .v2-first-run__title { margin: 0; color: var(--v2-text-primary); font: 700 28px/1.15 var(--v2-font-display); letter-spacing: -0.025em; /* Two even lines on a phone, not a lone last word. */ text-wrap: balance; } .v2-first-run__lede { max-width: 560px; margin: 10px 0 0; color: var(--v2-text-secondary); font-size: 14px; line-height: 1.55; } .v2-root button.v2-first-run__skip { flex: none; display: inline-flex; align-items: center; height: 32px; padding: 0 10px; border-radius: var(--v2-r-9); color: var(--v2-text-secondary); font: 600 12.5px/1 var(--v2-font); } .v2-root button.v2-first-run__skip:hover { background: var(--v2-surface-tint); color: var(--v2-text-primary); } .v2-first-run__steps { display: grid; gap: 10px; margin: 22px 0 0; padding: 0; list-style: none; } .v2-first-run__step { display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 12px; align-items: start; padding: 14px; border-radius: var(--v2-r-12); background: var(--v2-bg-subtle); box-shadow: inset 0 0 0 1px var(--v2-border-soft); } .v2-first-run__step-mark { display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; background: var(--v2-surface); box-shadow: inset 0 0 0 1px var(--v2-border); color: var(--v2-text-secondary); font: 600 12px/1 var(--v2-font-mono); } .v2-first-run__step-mark--done { background: var(--v2-accent-soft); box-shadow: inset 0 0 0 1px var(--v2-accent); color: var(--v2-accent-text); } .v2-first-run__step-body { display: flex; min-width: 0; flex-direction: column; align-items: flex-start; gap: 5px; color: var(--v2-text-secondary); font-size: 13px; line-height: 1.45; } .v2-first-run__step-body strong { color: var(--v2-text-primary); font-size: 14px; font-weight: 600; } /* D215 keys: setup is the key material, hello the one filled action (TASK-647 item 4: the card keeps one ink action). */ .v2-root button.v2-first-run__hello, .v2-root a.v2-first-run__setup { display: inline-flex; align-items: center; gap: 7px; height: 32px; margin-top: 6px; padding: 0 13px; border-radius: var(--v2-r-10); font: 600 13px/1 var(--v2-font); text-decoration: none; } .v2-root a.v2-first-run__setup { border: 1px solid var(--v2-key-edge); background: linear-gradient(180deg, var(--v2-key-a), var(--v2-key-b)); box-shadow: var(--v2-key-inset); color: var(--v2-text-primary); } .v2-root a.v2-first-run__setup:hover { border-color: var(--v2-border-strong); } .v2-root button.v2-first-run__hello { border: 1px solid var(--v2-fill-edge); background: linear-gradient(180deg, var(--v2-fill-a), var(--v2-fill-b)); color: var(--v2-on-fill); } .v2-root button.v2-first-run__hello:hover:not(:disabled) { background: var(--v2-ink-hover); } /* Disabled before the agent connects, as drawn; busy while the room opens. */ .v2-root button.v2-first-run__hello:disabled { cursor: default; opacity: 0.45; } .v2-root button.v2-first-run__hello[aria-busy="true"] { cursor: wait; opacity: 0.65; } .v2-first-run__waiting, .v2-first-run__connected, .v2-first-run__error { display: inline-flex; align-items: center; gap: 8px; } /* The waiting dot is the presence dot: cobalt, with the shared pulse. */ .v2-first-run__dot { position: relative; flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--v2-accent); } .v2-first-run__dot::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; border: 2px solid var(--v2-accent); opacity: 0; animation: v2-presence-pulse var(--motion-pulse) var(--motion-ease-state) infinite; } @media (prefers-reduced-motion: reduce) { .v2-first-run__dot::after { animation: none; } } .v2-first-run__connected { color: var(--v2-accent-text); font-weight: 600; } .v2-first-run__error { color: var(--v2-text-primary); font-size: 12.5px; } .v2-first-run__error svg { flex: none; color: var(--v2-danger); } @keyframes v2-typing-dot { 0%, 60%, 100% { transform: translateY(0); opacity: 0.35; } 30% { transform: translateY(-3px); opacity: 1; } } /* ---------- Message bubble ---------- */ .v2-msg { display: grid; grid-template-columns: 38px minmax(0, 1fr); align-items: start; gap: 12px; padding: 10px 0; } /* The message action cluster (taste rulings 2026-08-23): Reply, Thread, and the reaction trigger share ONE floating pill at the row's top-right, identical for headed and grouped rows. Absolute + hover means zero layout shift; icon buttons instead of text keep it quiet at rest and compact under the cursor. The reaction picker anchors to the cluster so the trigger sits with its siblings instead of orphaned in an empty band below the message (the old absolute "+"). */ .v2-msg { position: relative; } /* ---------- Inspector ---------- */ .v2-inspector { height: 100%; overflow-y: auto; display: flex; flex-direction: column; gap: 0; position: relative; } .v2-inspector { display: flex; flex-direction: column; height: 100%; } /* Danger zone — destructive actions (delete pod, etc.) live in the Manage tab inside this red-accent card with proper warning chrome. */ @keyframes v2-now-pulse { 0% { box-shadow: 0 0 0 0 var(--v2-success-ring); } 70% { box-shadow: 0 0 0 6px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } } /* ---------- Inspector: Members ---------- */ /* ---------- Your Team category tab bar ---------- */ /* ---------- Invite modal + redeem page ---------- */ .v2-modal__overlay { position: fixed; inset: 0; z-index: 100; background: rgba(15, 23, 42, 0.42); display: flex; align-items: center; justify-content: center; padding: 24px; } .v2-modal { width: min(560px, 100%); max-height: calc(100vh - 48px); overflow: auto; background: var(--v2-surface); border: 1px solid var(--v2-border-soft); border-radius: var(--v2-radius-md, 12px); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18); display: flex; flex-direction: column; } .v2-modal__head { display: flex; align-items: center; justify-content: space-between; padding: 16px 18px 12px; border-bottom: 1px solid var(--v2-border-soft); } .v2-modal__title { font-size: 14px; font-weight: 700; color: var(--v2-text-primary); } .v2-root button.v2-modal__close, .v2-modal__close { display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: 6px; border: none; background: transparent; color: var(--v2-text-secondary); font-size: 20px; line-height: 1; cursor: pointer; } .v2-root button.v2-modal__close:hover, .v2-modal__close:hover { background: var(--v2-surface-hover); color: var(--v2-text-primary); } .v2-modal__tabs { display: flex; gap: 0; padding: 0 18px; border-bottom: 1px solid var(--v2-border-soft); } .v2-root button.v2-modal__tab, .v2-modal__tab { height: 36px; padding: 0 14px; border: none; background: transparent; color: var(--v2-text-secondary); font-size: 12px; font-weight: 600; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; } .v2-root button.v2-modal__tab:hover, .v2-modal__tab:hover { color: var(--v2-text-primary); } .v2-root button.v2-modal__tab--active, .v2-modal__tab--active { color: var(--v2-accent); border-bottom-color: var(--v2-accent); } .v2-modal__body { padding: 16px 18px 18px; display: flex; flex-direction: column; gap: 12px; } .v2-modal__hint { font-size: 12px; color: var(--v2-text-secondary); margin: 0; line-height: 1.5; } .v2-modal__error { font-size: 11px; color: var(--v2-danger); margin-top: 4px; } .v2-modal__section-title { font-size: 12px; font-weight: 700; color: var(--v2-text-primary); } .v2-invite-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; } .v2-invite-options__field { display: flex; flex-direction: column; gap: 5px; min-width: 0; font-size: 11px; font-weight: 600; color: var(--v2-text-secondary); } .v2-invite-options__select { width: 100%; height: 36px; padding: 0 10px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-surface); color: var(--v2-text-primary); font: inherit; font-weight: 500; outline: none; } .v2-invite-options__select:focus { border-color: var(--v2-accent); box-shadow: var(--v2-focus-ring); } .v2-invite-link-row { display: flex; gap: 6px; align-items: stretch; } /* The Copy button inside the invite-link row inherits `width: 100%` from * the base `.v2-invite-card__cta` rule (which is meant for full-width * primary CTAs). Inside the row it needs to stay sized to its content so * the URL input gets the remaining flex space. Without this override the * input collapses to ~20px and the URL is invisible. */ .v2-root .v2-invite-link-row button.v2-invite-card__cta, .v2-invite-link-row .v2-invite-card__cta { width: auto; flex-shrink: 0; margin-top: 0; padding: 0 12px; } .v2-invite-link { flex: 1; min-width: 0; padding: 8px 10px; border: 1px solid var(--v2-border-soft); border-radius: var(--v2-radius-sm); background: var(--v2-bg-subtle); font-size: 11px; color: var(--v2-text-secondary); font-family: ui-monospace, SFMono-Regular, monospace; outline: none; } .v2-invite-link:focus { border-color: var(--v2-accent); } .v2-invite-manage { display: flex; flex-direction: column; gap: 8px; padding-top: 14px; margin-top: 4px; border-top: 1px solid var(--v2-border-soft); } .v2-invite-manage__empty { padding: 12px; border: 1px dashed var(--v2-border); border-radius: var(--v2-radius-sm); color: var(--v2-text-tertiary); font-size: 11px; text-align: center; } .v2-invite-manage__list { display: flex; flex-direction: column; gap: 8px; } .v2-invite-manage__item { min-width: 0; padding: 10px; border: 1px solid var(--v2-border-soft); border-radius: var(--v2-radius-sm); background: var(--v2-surface); } .v2-invite-manage__summary, .v2-invite-manage__meta { display: flex; justify-content: space-between; gap: 8px; min-width: 0; } .v2-invite-manage__summary { align-items: baseline; font-size: 11px; color: var(--v2-text-tertiary); } .v2-invite-manage__summary strong { overflow: hidden; color: var(--v2-text-primary); font-weight: 650; text-overflow: ellipsis; white-space: nowrap; } .v2-invite-manage__meta { flex-wrap: wrap; margin-top: 3px; color: var(--v2-text-secondary); font-size: 10px; } .v2-invite-manage__url { box-sizing: border-box; width: 100%; min-width: 0; margin-top: 8px; padding: 7px 9px; border: 1px solid var(--v2-border-soft); border-radius: 6px; background: var(--v2-bg-subtle); color: var(--v2-text-secondary); font-family: var(--v2-font-mono); font-size: 10px; outline: none; } .v2-invite-manage__url:focus { border-color: var(--v2-accent); } .v2-invite-manage__actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; margin-top: 8px; } .v2-root button.v2-invite-manage__action, .v2-invite-manage__action { min-width: 0; height: 30px; border: 1px solid var(--v2-border); border-radius: 6px; background: var(--v2-surface); color: var(--v2-text-primary); font-size: 11px; font-weight: 600; cursor: pointer; } .v2-root button.v2-invite-manage__action:hover, .v2-invite-manage__action:hover { background: var(--v2-surface-hover); } .v2-root button.v2-invite-manage__action--danger, .v2-invite-manage__action--danger { border-color: var(--v2-danger-soft); color: var(--v2-danger); } .v2-root button.v2-invite-manage__action--danger:hover, .v2-invite-manage__action--danger:hover { background: var(--v2-danger-soft); } .v2-root button.v2-invite-manage__action:disabled, .v2-invite-manage__action:disabled { opacity: 0.6; cursor: not-allowed; } @media (max-width: 480px) { .v2-verification-banner { grid-template-columns: minmax(0, 1fr) 44px; grid-template-areas: "content dismiss" "resend resend"; align-items: start; gap: 6px 8px; min-height: 0; padding: 8px 12px; font-size: 16px; } .v2-root button.v2-verification-banner__resend { justify-self: start; } /* Auth is a primary phone flow: 16px prevents Safari's focus zoom and a 44px target keeps signup usable with a thumb. */ .v2-login__input { min-height: 44px; font-size: 16px; } .v2-root button.v2-login__submit { min-height: 44px; font-size: 16px; } .v2-modal__overlay { padding: 12px; } .v2-invite-options { grid-template-columns: minmax(0, 1fr); } .v2-invite-link-row { flex-wrap: wrap; } .v2-root .v2-invite-link-row button.v2-invite-card__cta, .v2-invite-link-row .v2-invite-card__cta { width: 100%; height: 34px; } } .v2-root button.v2-invite-card__cta, .v2-invite-card__cta { width: 100%; height: 38px; border: none; border-radius: var(--v2-radius-sm); background: var(--v2-accent-fill); color: var(--v2-on-accent); font-size: 13px; font-weight: 600; cursor: pointer; margin-top: 8px; } .v2-root button.v2-invite-card__cta:hover, .v2-invite-card__cta:hover { filter: brightness(1.05); } .v2-root button.v2-invite-card__cta:disabled, .v2-invite-card__cta:disabled { opacity: 0.6; cursor: not-allowed; } .v2-root button.v2-invite-card__cta--secondary, .v2-invite-card__cta--secondary { background: var(--v2-surface); color: var(--v2-text-primary); border: 1px solid var(--v2-border-soft); } .v2-root button.v2-invite-card__cta--secondary:hover, .v2-invite-card__cta--secondary:hover { background: var(--v2-bg-subtle); } /* ---------- Avatar ---------- */ .v2-avatar { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; color: #fff; font-weight: 650; font-size: 11px; flex-shrink: 0; border: 2px solid var(--v2-surface); text-transform: uppercase; position: relative; /* Two initials at 700 with default tracking collide at 24px — the pair reads as one glyph. 650 with positive tracking separates them without making the avatar shout; the optical centre then sits ~0.5px right, so the tracking is compensated with an equal indent rather than left as a visible lean. */ letter-spacing: 0.04em; text-indent: 0.04em; /* The seeded gradient is the identity; a flat inner highlight over it reads as a lit sphere rather than a filled circle, and costs nothing. */ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), inset 0 -1px 0 rgba(0, 0, 0, 0.10); overflow: hidden; } /* A photo is the identity when one exists — the gradient underneath is only a backing plate for transparent PNGs, so the inner highlight must not sit on top of someone's face. */ .v2-avatar:has(img) { box-shadow: none; } /* Character tier gets a hairline ring (TASK-679). The compact transcript frame (--flat) stays unframed; the :has(img) rule above outranks a plain class, so the selector repeats it to win. */ .v2-avatar:has(img).v2-avatar--character:not(.v2-avatar--flat) { box-shadow: 0 0 0 1px var(--v2-avatar-frame); } .v2-avatar--lg { width: 34px; height: 34px; font-size: 12px; } .v2-avatar--md { width: 30px; height: 30px; font-size: 11px; } .v2-avatar--sm { width: 24px; height: 24px; font-size: 10px; } .v2-avatar__online { position: absolute; right: -1px; bottom: -1px; width: 9px; height: 9px; border-radius: 50%; background: var(--v2-success); border: 2px solid var(--v2-surface); } /* ---------- Login ---------- */ /* The auth page is its own scroll container: .v2-root and html hide overflow, so a card taller than the window (a short laptop, a phone with the keyboard up) would otherwise lose its last rows. The card centres with auto margins, which fall to zero when it overflows; place-items: center would push its top out of reach instead. */ .v2-login { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; padding: 24px; background: var(--v2-page-bg); } /* The d215 auth card: the pane surface on the shell ground (every auth page shares it; sign-in, sign-up, reset and forgot frames all draw .authcard). */ .v2-login__card { width: 100%; max-width: 400px; margin: auto; background: var(--v2-surface); border-radius: 14px; padding: 32px; box-shadow: var(--v2-shadow-pane); } /* Each auth card stacks its rows on one 16px rhythm, as drawn. A form inside the card (invite-required has two) lends its rows to that grid. */ .v2-login__card--stack { display: grid; /* minmax(0, 1fr): one long unbroken word (a pod description, a URL) wraps inside the card instead of widening the grid column past it. */ grid-template-columns: minmax(0, 1fr); gap: 16px; } .v2-login__card--stack > form { display: contents; } .v2-login__card--stack .v2-login__brand, .v2-login__card--stack .v2-login__field, .v2-login__card--stack .v2-login__or, .v2-login__card--stack .v2-login__oauth, .v2-login__card--stack .v2-login__notice, .v2-login__card--stack .v2-login__subtitle, .v2-login__card--stack .v2-login__hint, .v2-login__card--stack .v2-login__legal, .v2-login__card--stack .v2-login__field-hint { margin: 0; } /* The one prose line a page keeps (the frames' .sub), and a plain hint under it: no box on a lifted card. */ .v2-login__card--stack .v2-login__subtitle { font-size: 13.5px; line-height: 1.5; } .v2-login__card--stack .v2-login__hint { padding: 0; border-radius: 0; background: none; font-size: 13px; line-height: 1.5; color: var(--v2-text-secondary); } .v2-login__card--stack .v2-login__title { margin: 4px 0 0; } .v2-login__card--stack .v2-login__oauth { gap: 16px; } .v2-root .v2-login__card--stack button.v2-login__submit { margin-top: 0; /* A key can carry a pod name ("Join "); an unbroken one wraps inside the key instead of painting past the card. One line stays 40px (border-box 9 + 20 + 9 + 2). No min-height here: it would outrank the phone rule's 44px. */ height: auto; padding-block: 9px; line-height: 20px; overflow-wrap: anywhere; } .v2-login__brand { display: flex; align-items: center; gap: 10px; font: 800 18px/1 var(--v2-font-display); letter-spacing: -0.03em; margin-bottom: 4px; } .v2-login__brand .v2-rail__brand-icon { width: 30px; height: 30px; border-radius: 9px; } .v2-login__brand .v2-rail__brand-icon svg { width: 17px; height: 17px; } .v2-login__title { font: 800 28px/1.1 var(--v2-font-display); margin: 16px 0 4px; letter-spacing: -0.03em; text-wrap: balance; } .v2-login__subtitle { font-size: 13px; color: var(--v2-text-secondary); margin-bottom: 22px; } .v2-login__field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; } .v2-login__label { display: flex; align-items: baseline; justify-content: space-between; font-size: 12.5px; font-weight: 600; color: var(--v2-text-primary); } /* "optional" rides in the label, not the field, at its far end. */ .v2-login__label em { margin-left: 8px; font: 500 11px/1 var(--v2-font-mono); font-style: normal; color: var(--v2-text-muted); } /* "Forgot password?" sits on the password label's line. */ .v2-login__label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; } .v2-login__input { height: 40px; padding: 0 12px; border: 0; border-radius: var(--v2-r-10, 10px); background: var(--v2-bg-subtle); box-shadow: inset 0 0 0 1px var(--v2-border); color: var(--v2-text-primary); font-size: 14px; outline: none; transition: box-shadow 80ms ease; } /* Outranks the shared .v2-root input:focus-visible ring (0,2,1), which would otherwise drop the accent edge. */ .v2-root input.v2-login__input:focus-visible { box-shadow: inset 0 0 0 1px var(--v2-accent), var(--v2-focus-ring); } /* A failed sign-in marks both fields with the ink ring, as drawn. */ .v2-login__input[aria-invalid="true"] { box-shadow: inset 0 0 0 1.5px var(--v2-text-primary); } /* The ink fill, the auth frames' one primary act. The root/button selector keeps it owned by the submit control (login + register). */ .v2-root button.v2-login__submit { width: 100%; height: 40px; margin-top: 8px; padding: 0 14px; border: 1px solid var(--v2-fill-edge); background: linear-gradient(180deg, var(--v2-fill-a), var(--v2-fill-b)); color: var(--v2-on-fill); font-weight: 600; font-size: 14px; border-radius: var(--v2-r-10, 10px); } .v2-root button.v2-login__submit:hover:not(:disabled) { background: var(--v2-ink-hover); } .v2-root button.v2-login__submit:disabled { opacity: 0.45; cursor: not-allowed; } /* One quiet row, as the auth frames draw both errors (alert mark) and confirmations (check mark). V2AuthNotice renders it. */ .v2-login__notice { display: flex; align-items: flex-start; gap: 10px; margin-top: 10px; padding: 11px 12px; border-radius: var(--v2-r-10, 10px); background: var(--v2-surface-tint); box-shadow: inset 0 0 0 1px var(--v2-border); color: var(--v2-text-primary); font-size: 13px; line-height: 19px; } .v2-login__notice svg { flex: none; margin-top: 1.5px; } .v2-login__forgot { margin-top: 8px; font-size: 12px; text-align: right; } /* The "or" rule between the providers and the form. On invite-required it carries the waitlist line in place of "or". */ .v2-login__or { display: flex; align-items: center; gap: 12px; margin-top: 16px; color: var(--v2-text-muted); font: 500 11.5px/1 var(--v2-font-mono); } .v2-login__or::before, .v2-login__or::after { content: ''; flex: 1; height: 1px; background: var(--v2-border-soft); } .v2-login__oauth { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; } /* OAuth links and invite forms share one button treatment. Prefix with `.v2-root` to beat the global button/link rules. */ .v2-root :where(a, button).v2-login__oauth-btn { display: flex; align-items: center; justify-content: center; gap: 10px; height: 40px; padding: 0 14px; border-radius: var(--v2-r-10, 10px); border: 1px solid var(--v2-key-edge); background: linear-gradient(180deg, var(--v2-key-a), var(--v2-key-b)); box-shadow: var(--v2-key-inset); color: var(--v2-text-primary); font-weight: 600; font-size: 14px; text-decoration: none; cursor: pointer; transition: border-color 80ms ease; } .v2-root :where(a, button).v2-login__oauth-btn:hover { border-color: var(--v2-border-strong); } .v2-root :where(a, button).v2-login__oauth-btn:focus-visible { box-shadow: var(--v2-key-inset), var(--v2-focus-ring); } /* Inline helper under a single field — plain small text, unlike the boxed .v2-login__hint. Born from the invite-code placeholder truncating. */ .v2-login__field-hint { display: block; margin-top: 6px; font-size: 12px; line-height: 1.5; color: var(--v2-text-tertiary); } /* The terms line: muted, under the key. */ .v2-login__legal { font-size: 12px; line-height: 1.5; color: var(--v2-text-muted); text-align: center; } .v2-login__hint { margin-top: 18px; padding: 10px 12px; border-radius: var(--v2-radius); background: var(--v2-bg-subtle); font-size: 12px; color: var(--v2-text-tertiary); } .v2-login__hint code { font-family: 'SF Mono', Menlo, monospace; background: var(--v2-surface); padding: 1px 5px; border-radius: 4px; border: 1px solid var(--v2-border); color: var(--v2-text-primary); } /* `.v2-root a { color: inherit }` (0,1,1) beats a single-class rule on specificity, so prefix to win. Same pattern as the v2-button reset. */ .v2-root a.v2-login__link { color: var(--v2-accent-text); text-decoration: none; font-weight: 500; } /* The card's last line: one plain sentence and the link to the other page. */ .v2-login__alt { margin: 0; text-align: center; font-size: 13.5px; color: var(--v2-text-secondary); } .v2-root .v2-login__alt a.v2-login__link, .v2-root .v2-login__legal a.v2-login__link, .v2-root .v2-login__hint a.v2-login__link, .v2-root .v2-login__notice a.v2-login__link { font-weight: 600; } .v2-root a.v2-login__link:hover { text-decoration: underline; } /* Extra login-card affordances — used by the invite-required page, which stacks two forms (invite code + waitlist) in one card. A bare
picks up the dark global form background (V2Login/V2Register dodge this by putting the card class ON the single form); here the card is a
with two s inside, so neutralize the form background/box explicitly. */ .v2-login__card form { background: transparent; border: 0; padding: 0; margin: 0; box-shadow: none; } /* The second path on a page is a key, not a second fill: the waitlist, and the way out of a failed verification. */ .v2-root button.v2-login__submit--ghost { border-color: var(--v2-key-edge); background: linear-gradient(180deg, var(--v2-key-a), var(--v2-key-b)); box-shadow: var(--v2-key-inset); color: var(--v2-text-primary); } .v2-root button.v2-login__submit--ghost:hover:not(:disabled) { background: linear-gradient(180deg, var(--v2-key-a), var(--v2-key-b)); border-color: var(--v2-border-strong); } .v2-root button.v2-login__submit--ghost:focus-visible { box-shadow: var(--v2-key-inset), var(--v2-focus-ring); } /* ---------- Sign-in handoff (d215 s-handoff) ---------- The OAuth and Discord return pages: the auth card, with the brand row telling the handoff. Provider tile, a wire, the mark. Working, a cobalt dot runs the dotted wire; done, the wire is solid with a check; error, an ink cross on it. V2AuthBrand draws the row; V2DiscordCallback the rest. */ .v2-handoff__provider { flex: none; width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; background: var(--v2-surface); box-shadow: inset 0 0 0 1px var(--v2-border-soft); color: var(--v2-text-secondary); } .v2-handoff__provider > svg { width: 17px; height: 17px; } /* The Discord tile inside the provider tile: its brand fill, the white mark. */ .v2-handoff__tile { width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; } .v2-handoff__tile--discord { background: var(--v2-platform-discord); color: var(--v2-on-accent); } .v2-handoff__tile svg { width: 14px; height: 14px; } .v2-handoff__wire { position: relative; flex: none; width: 52px; height: 18px; display: grid; place-items: center; } .v2-handoff__wire::before { content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 0; border-top: 2px dotted var(--v2-border); } .v2-handoff__wire--done::before { border-top: 2px solid var(--v2-accent); } .v2-handoff__run { position: absolute; top: calc(50% - 3px); left: 0; width: 6px; height: 6px; border-radius: 50%; background: var(--v2-accent); animation: v2-handoff-run 1.4s ease-in-out infinite; } @keyframes v2-handoff-run { 0% { left: 0; opacity: 0; } 20% { opacity: 1; } 80% { opacity: 1; } 100% { left: calc(100% - 6px); opacity: 0; } } /* Still, the dot rests mid-wire: the title already says it is working. */ @media (prefers-reduced-motion: reduce) { .v2-handoff__run { animation: none; left: calc(50% - 3px); } } .v2-handoff__knot { position: relative; width: 18px; height: 18px; border-radius: 50%; display: grid; place-items: center; background: var(--v2-surface); box-shadow: inset 0 0 0 1.5px var(--v2-accent); color: var(--v2-accent-text); } .v2-handoff__knot svg { width: 11px; height: 11px; stroke-width: 2.4; } .v2-handoff__wire--error .v2-handoff__knot { box-shadow: inset 0 0 0 1.5px var(--v2-text-primary); color: var(--v2-text-primary); } /* The server as a fact row: label left, value mono right (the CLI card's list). */ .v2-handoff__facts { margin: 0; border-radius: var(--v2-r-10, 10px); box-shadow: inset 0 0 0 1px var(--v2-border-soft); } .v2-handoff__facts div { display: flex; justify-content: space-between; gap: 16px; padding: 10px 14px; border-top: 1px solid var(--v2-border-soft); font-size: 13px; } .v2-handoff__facts div:first-child { border-top: 0; } .v2-handoff__facts dt { color: var(--v2-text-secondary); } .v2-handoff__facts dd { margin: 0; min-width: 0; font: 500 12.5px/1.4 var(--v2-font-mono); color: var(--v2-text-primary); text-align: right; overflow-wrap: anywhere; } /* The channel is the auth field with a caret; the native list does the rest. */ .v2-handoff__select { position: relative; display: grid; } .v2-root select.v2-login__input { width: 100%; min-width: 0; padding-right: 36px; appearance: none; -webkit-appearance: none; font-family: inherit; cursor: pointer; } .v2-root select.v2-login__input:disabled { cursor: default; } .v2-root select.v2-login__input:focus-visible { box-shadow: inset 0 0 0 1px var(--v2-accent), var(--v2-focus-ring); } .v2-handoff__select > svg { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); pointer-events: none; color: var(--v2-text-muted); } /* Cancel and Connect share the row, Connect in ink. */ .v2-handoff__keys { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } /* The 390 auth frames are full-bleed: the card is the screen. This block sits after the base rules above, which it overrides at equal specificity. The side padding is the app's 12px phone gutter (design-system README, Layout rules; 11-04 consistency rulings), not the 20 the d215 auth frames draw, so sign-in lines up with every reference page at 390. */ @media (max-width: 480px) { .v2-login { padding: 0; background: var(--v2-surface); } .v2-login__card { margin: 0; max-width: none; box-shadow: none; border-radius: 0; /* The d215 auth frames draw a 20px side inset on every auth page at 390. Pages drawn after Sam's pick follow their frames (G11 ruling, 11-04). */ padding: 28px 20px; } .v2-root :where(a, button).v2-login__oauth-btn { min-height: 44px; } } /* ---------- Empty / loading states ---------- */ /* The shell's one-line state: memory's `p.v2m-state` grammar (V2AgentMemory.css) for anything outside that page, such as the admin and dev access check (components/ProtectedRoute.tsx). Plex 13.5 in the secondary grey; no icon, no red, no exclamation mark. No side padding: the page body already owns the gutter. v2-layout-invariants keeps the type in step with .v2m-state. */ .v2-state { margin: 0; padding: 14px 0; font-size: 13.5px; line-height: 20px; color: var(--v2-text-secondary); } .v2-feature { min-width: 0; background: var(--v2-surface); /* inside the card the ground is white; the card is the tint step */ } /* The shared page head bar (V2PageHead with .v2-feature__header): the reference heads' 64px bar with the title 29px from the pane edge, a divider under it and the count in mono 11.5. Pages that draw their own head (Your team, the person and agent profiles) use this class too, with .v2-feature--bar below. */ .v2-feature__header { flex-shrink: 0; box-sizing: border-box; min-height: 64px; padding: 0 20px 0 28px; border-bottom: 1px solid var(--v2-border-soft); background: var(--v2-surface); } .v2-page-head.v2-feature__header { align-items: center; gap: 12px; } .v2-feature__header > .v2-page-head__identity { flex: 1 1 auto; } /* The display face is set here, not only in the phone rule: without it the desktop title fell back to Plex 24/850 (ui-audit-1104 finding 2). */ .v2-feature__title { margin: 0; color: var(--v2-text-primary); font: 800 24px/1 var(--v2-font-display); letter-spacing: -0.03em; } .v2-feature__header .v2-page-head__meta { font: 500 11.5px/16px var(--v2-font-mono); } /* A page that draws its own head bar inside the feature pane passes v2-feature--bar: the body stops padding and scrolling, the head sits at the pane edge, and .v2-feature__scroll under it scrolls and pads the content to a centred column (--v2-feature-col, 1100 for lists and grids). */ .v2-feature--bar > .v2-feature__body { padding: 0; display: flex; flex-direction: column; overflow: hidden; } .v2-feature--bar .v2-feature__legacy { flex: 1 1 auto; width: 100%; max-width: none; min-height: 0; margin: 0; display: flex; flex-direction: column; } .v2-feature__scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; box-sizing: border-box; padding: 24px max(28px, calc((100% - var(--v2-feature-col, 1100px)) / 2)) 42px; } .v2-page-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; min-width: 0; } .v2-page-head__identity { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; min-width: 0; } .v2-page-head__identity h1 { min-width: 0; } .v2-page-head__meta { color: var(--v2-text-muted); font: 500 12px/16px var(--v2-font-mono); } .v2-page-head__actions { display: flex; align-items: center; gap: 8px; } .v2-hint { position: relative; display: inline-flex; align-items: center; flex: 0 0 auto; } .v2-root button.v2-hint__trigger { display: inline-grid; place-items: center; width: 18px; height: 18px; padding: 0; border: 1px solid var(--v2-border); border-radius: 50%; background: transparent; color: var(--v2-text-muted); font: 600 12px/1 var(--v2-font); cursor: pointer; } .v2-root button.v2-hint__trigger:hover { background: var(--v2-surface-hover); } .v2-root button.v2-hint__trigger:focus-visible { border-color: var(--v2-accent); outline: 0; box-shadow: var(--v2-focus-ring); } .v2-hint__content { display: none; position: absolute; z-index: 10; top: calc(100% + 6px); left: 0; width: min(320px, calc(100vw - 32px)); padding: 10px 12px; border: 1px solid var(--v2-border); border-radius: 6px; background: var(--v2-surface); color: var(--v2-text-secondary); font: 400 13px/18px var(--v2-font); white-space: normal; box-shadow: 0 8px 24px rgba(15, 23, 42, .12); } .v2-hint--open .v2-hint__content { display: block; } @media (max-width: 760px) { .v2-root button.v2-hint__trigger { width: 28px; height: 28px; flex: 0 0 28px; } .v2-hint--open { display: flex; min-width: 0; max-width: 100%; align-items: flex-start; gap: 8px; } .v2-page-head__identity .v2-hint--open { display: contents; } .v2-hint--open .v2-hint__content { position: static; width: auto; min-width: 0; padding: 0; border: 0; background: transparent; box-shadow: none; } .v2-page-head__identity .v2-hint__content { width: 100%; flex-basis: 100%; order: 1; } } .v2-feature__body { flex: 1; min-height: 0; overflow-y: auto; padding: 28px 34px 42px; } .v2-feature__legacy { max-width: 1240px; margin: 0 auto; color: var(--v2-text-primary); } .v2-feature__legacy > .MuiContainer-root { max-width: none; padding-left: 0; padding-right: 0; } .v2-feature__legacy .MuiBox-root { color: var(--v2-text-primary); } .v2-feature__legacy .MuiTypography-root { color: inherit; } .v2-feature__legacy .MuiTypography-h1, .v2-feature__legacy .MuiTypography-h2, .v2-feature__legacy .MuiTypography-h3, .v2-feature__legacy .MuiTypography-h4, .v2-feature__legacy .MuiTypography-h5, .v2-feature__legacy .MuiTypography-h6 { color: var(--v2-text-primary); letter-spacing: -0.02em; } .v2-feature__legacy .MuiTypography-colorPrimary, .v2-feature__legacy .MuiSvgIcon-colorPrimary { color: var(--v2-accent-text); } .v2-feature__legacy .MuiTypography-colorTextSecondary, .v2-feature__legacy .MuiTypography-colorTextDisabled { color: var(--v2-text-secondary); } .v2-feature__legacy .MuiPaper-root { background: var(--v2-surface) !important; background-color: var(--v2-surface) !important; color: var(--v2-text-primary) !important; border: 1px solid var(--v2-border) !important; box-shadow: var(--v2-shadow-sm) !important; } .v2-feature__legacy .MuiPaper-elevation1, .v2-feature__legacy .MuiPaper-elevation2, .v2-feature__legacy .MuiPaper-elevation3, .v2-feature__legacy .MuiPaper-elevation4, .v2-feature__legacy .MuiPaper-elevation5, .v2-feature__legacy .MuiPaper-elevation6, .v2-feature__legacy .MuiPaper-elevation7, .v2-feature__legacy .MuiPaper-elevation8, .v2-feature__legacy .MuiPaper-elevation9, .v2-feature__legacy .MuiPaper-elevation10, .v2-feature__legacy .MuiPaper-elevation11, .v2-feature__legacy .MuiPaper-elevation12, .v2-feature__legacy .MuiPaper-elevation13, .v2-feature__legacy .MuiPaper-elevation14, .v2-feature__legacy .MuiPaper-elevation15, .v2-feature__legacy .MuiPaper-elevation16, .v2-feature__legacy .MuiPaper-elevation17, .v2-feature__legacy .MuiPaper-elevation18, .v2-feature__legacy .MuiPaper-elevation19, .v2-feature__legacy .MuiPaper-elevation20, .v2-feature__legacy .MuiPaper-elevation21, .v2-feature__legacy .MuiPaper-elevation22, .v2-feature__legacy .MuiPaper-elevation23, .v2-feature__legacy .MuiPaper-elevation24 { box-shadow: var(--v2-shadow-sm); } .v2-feature__legacy .MuiPaper-root, .v2-feature__legacy .MuiCard-root { border-radius: 12px; } .v2-feature__legacy .MuiCard-root { background: var(--v2-surface) !important; background-color: var(--v2-surface) !important; color: var(--v2-text-primary) !important; border: 1px solid var(--v2-border) !important; box-shadow: var(--v2-shadow-sm) !important; transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease !important; } .v2-feature__legacy .MuiCard-root:hover { border-color: var(--v2-border-strong) !important; box-shadow: var(--v2-shadow) !important; transform: translateY(-1px) !important; } .v2-feature__legacy .MuiCardContent-root, .v2-feature__legacy .MuiCardActions-root { background: transparent !important; color: var(--v2-text-primary) !important; } .v2-feature__legacy .MuiCardActions-root { border-top-color: var(--v2-border-soft) !important; } .v2-feature__legacy .MuiCard-root .MuiTypography-root { color: var(--v2-text-primary) !important; } .v2-feature__legacy .MuiCard-root .MuiTypography-colorTextSecondary, .v2-feature__legacy .MuiCard-root .MuiTypography-caption, .v2-feature__legacy .MuiCard-root .MuiTypography-body2 { color: var(--v2-text-secondary) !important; } .v2-feature__legacy .MuiCard-root [style*="color: rgb(226, 232, 240)"], .v2-feature__legacy .MuiCard-root [style*="color: #e2e8f0"], .v2-feature__legacy .MuiCard-root [style*="color: #cbd5f5"], .v2-feature__legacy .MuiCard-root [style*="color: #f8fafc"] { color: var(--v2-text-primary) !important; } .v2-feature__legacy .MuiCard-root [style*="color: rgb(148, 163, 184)"], .v2-feature__legacy .MuiCard-root [style*="color: #94a3b8"], .v2-feature__legacy .MuiCard-root [style*="color: #64748b"], .v2-feature__legacy .MuiCard-root [style*="color: #475569"] { color: var(--v2-text-secondary) !important; } .v2-feature__legacy .MuiButton-containedPrimary { background: var(--v2-accent); color: #ffffff; box-shadow: none; } .v2-feature__legacy .MuiButton-containedPrimary:hover { background: var(--v2-accent-strong); } .v2-feature__legacy .MuiButton-outlined { border-color: var(--v2-border); } .v2-feature__legacy .MuiButton-text, .v2-feature__legacy .MuiButton-outlined { color: var(--v2-accent-text); } .v2-feature__legacy .MuiIconButton-root { color: var(--v2-text-tertiary); } .v2-feature__legacy .MuiIconButton-root:hover { color: var(--v2-text-primary); background-color: var(--v2-surface-hover); } .v2-feature__legacy .MuiInputBase-root, .v2-feature__legacy .MuiSelect-select, .v2-feature__legacy .MuiAutocomplete-inputRoot { background-color: var(--v2-surface) !important; color: var(--v2-text-primary) !important; } .v2-feature__legacy .MuiOutlinedInput-notchedOutline { border-color: var(--v2-border); } .v2-feature__legacy .MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline { border-color: var(--v2-border-strong); } .v2-feature__legacy .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline { border-color: var(--v2-accent); box-shadow: var(--v2-focus-ring); } .v2-feature__legacy .MuiInputLabel-root, .v2-feature__legacy .MuiFormLabel-root { color: var(--v2-text-secondary); } /* Flatten MUI outlined form controls to the v2 look. The recolor above kept the MUI signature — a floated label sitting in a notched border. Here we finish the job: continuous border (kill the notch legend), v2 radius, and lift the label to a static v2-style label ABOVE the field. One block skins every legacy admin form (Global Integrations model policy, etc.) without a component rewrite. */ .v2-feature__legacy .MuiFormControl-root { margin-top: 20px; } .v2-feature__legacy .MuiOutlinedInput-root { border-radius: var(--v2-radius); } .v2-feature__legacy .MuiOutlinedInput-notchedOutline legend { max-width: 0.01px; } .v2-feature__legacy .MuiInputLabel-outlined { position: static; transform: none; margin-bottom: 6px; max-width: 100%; font-size: 12px; font-weight: 600; color: var(--v2-text-secondary); pointer-events: auto; overflow: visible; } .v2-feature__legacy .MuiInputBase-input, .v2-feature__legacy .MuiSelect-select { padding: 10px 12px; font-size: 14px; min-height: 20px; box-sizing: border-box; } .v2-feature__legacy .MuiDivider-root { border-color: var(--v2-border-soft); } .v2-feature__legacy .MuiTabs-root { border-bottom-color: var(--v2-border-soft); min-height: 42px; } .v2-feature__legacy .MuiTab-root { color: var(--v2-text-secondary); min-height: 42px; text-transform: none; font-weight: 700; } .v2-feature__legacy .MuiTab-root.Mui-selected { color: var(--v2-accent-text); } .v2-feature__legacy .MuiTabs-indicator { background-color: var(--v2-accent); } .v2-feature__legacy .MuiChip-root { border-radius: var(--v2-radius-pill); font-weight: 700; border-color: var(--v2-border) !important; } .v2-feature__legacy .MuiChip-filled:not(.MuiChip-colorSuccess):not(.MuiChip-colorWarning):not(.MuiChip-colorError):not(.MuiChip-colorInfo) { background-color: var(--v2-accent-soft) !important; color: var(--v2-accent-text) !important; } .v2-feature__legacy .MuiChip-outlined:not(.MuiChip-colorSuccess):not(.MuiChip-colorWarning):not(.MuiChip-colorError):not(.MuiChip-colorInfo) { background-color: var(--v2-surface) !important; color: var(--v2-text-secondary) !important; } .v2-feature__legacy .MuiChip-colorPrimary { background-color: var(--v2-accent-soft); color: var(--v2-accent-text); } .v2-feature__legacy .MuiAlert-root { border-radius: 12px; border: 1px solid transparent; } .v2-feature__legacy .MuiTableContainer-root { background: var(--v2-surface) !important; border: 1px solid var(--v2-border); border-radius: 12px; box-shadow: var(--v2-shadow-sm); } .v2-feature__legacy .MuiTableCell-root { border-bottom-color: var(--v2-border-soft); } .v2-feature__legacy .MuiTableHead-root .MuiTableCell-root { background-color: var(--v2-page-bg); color: var(--v2-text-secondary); font-weight: 800; } .v2-feature__legacy .MuiListItem-root { border-radius: 10px; } .v2-feature__legacy .MuiListItem-root:hover { background-color: var(--v2-surface-hover); } .v2-feature__legacy .MuiSkeleton-root { background-color: #f0eff8; } .v2-feature__legacy .MuiDialog-paper, .v2-feature__legacy .MuiPopover-paper, .v2-feature__legacy .MuiMenu-paper { background: var(--v2-surface) !important; color: var(--v2-text-primary) !important; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-lg); box-shadow: var(--v2-shadow-lg); } .v2-feature__legacy .MuiBackdrop-root { background-color: rgba(17, 24, 39, 0.34); } .v2-feature__legacy .MuiAvatar-root { border: 2px solid var(--v2-surface); } /* Reset top padding for embedded dev tools so the v2 feature header is the only top-of-page chrome the user sees. The dev pages were authored to stand alone and add their own 24px+ top padding. */ .v2-feature__legacy .api-dev-root, .v2-feature__legacy .pod-context-dev-root { padding-top: 0 !important; } .v2-feature__legacy .api-dev-container, .v2-feature__legacy .pod-context-dev-container { padding-left: 0 !important; padding-right: 0 !important; } .v2-feature__legacy [style*="#1d9bf0"], .v2-feature__legacy [style*="#1976d2"], .v2-feature__legacy [style*="#6366f1"], .v2-feature__legacy [style*="rgb(29, 155, 240)"] { color: var(--v2-accent-text) !important; border-color: var(--v2-border-strong) !important; } .v2-feature__legacy [style*="background: linear-gradient"], .v2-feature__legacy [style*="background-color: rgba(15, 23, 42"], .v2-feature__legacy [style*="background-color: rgba(30, 41, 59"], .v2-feature__legacy [style*="background-color: #0f172a"], .v2-feature__legacy [style*="background-color: #0b1220"] { background: var(--v2-surface) !important; background-color: var(--v2-surface) !important; color: var(--v2-text-primary) !important; } .v2-feature__legacy [style*="background-color: rgb(245, 248, 250)"], .v2-feature__legacy [style*="background-color: #f5f8fa"], .v2-feature__legacy [style*="bgcolor: #f5f8fa"] { background-color: var(--v2-page-bg) !important; } .v2-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 48px; color: var(--v2-text-tertiary); } /* App boot has no content state to impersonate, so the minimal mark can own the moment. The dots use the exact typing keyframes; inline spinners retain their state-specific v2-spin affordance below. */ .v2-boot { flex: 1; display: grid; place-items: center; min-height: 0; background: var(--v2-page-bg); } .v2-boot__mark { width: 64px; height: 64px; color: var(--v2-accent); overflow: visible; } .v2-boot__mark-dot { transform-box: fill-box; transform-origin: center; animation: v2-typing-dot var(--motion-breath) infinite var(--motion-ease-breath); } .v2-boot__mark-dot:nth-of-type(2) { animation-delay: var(--motion-stagger); } .v2-boot__mark-dot:nth-of-type(3) { animation-delay: calc(var(--motion-stagger) + var(--motion-stagger)); } .v2-empty__title { font-size: 16px; font-weight: 700; color: var(--v2-text-primary); } .v2-empty__text { font-size: 13px; text-align: center; max-width: 360px; } .v2-spinner { width: 18px; height: 18px; border: 2px solid var(--v2-border); border-top-color: var(--v2-accent); border-radius: 50%; animation: v2-spin var(--motion-spin) var(--motion-ease-linear) infinite; display: inline-block; } @keyframes v2-spin { to { transform: rotate(360deg); } } /* ---------- Misc utilities ---------- */ @media (max-width: 1023px) { .v2-root { min-width: 0; } .v2-shell:not(.v2-shell--feature-wide) { grid-template-columns: var(--v2-rail-w) var(--v2-pods-w) minmax(0, 1fr); } } @media (max-width: 760px) { /* Phones: the card fills the screen; the drawers keep the shell tint. Both selectors, or the banner variant keeps the desktop height. */ .v2-pane--main, .v2-authenticated-shell .v2-pane--main { margin: 0; height: 100%; border: 0; border-radius: 0; } .v2-shell, .v2-shell--no-inspector, .v2-shell--feature { grid-template-columns: var(--v2-rail-w) minmax(0, 1fr); } /* D215 phone: a sheet on the bottom edge, 44px touch targets. */ .v2-first-run__overlay { padding: 12px; place-items: end center; } .v2-first-run { max-height: calc(100dvh - 24px); padding: 20px 16px; } .v2-first-run__heading-row { flex-direction: column; gap: 8px; } .v2-first-run__title { font-size: 24px; } .v2-root button.v2-first-run__skip { align-self: flex-start; height: 44px; padding-left: 0; } .v2-root a.v2-first-run__setup, .v2-root button.v2-first-run__hello { height: 44px; font-size: 14px; } /* Hide secondary panes — but NOT the pods sidebar OR the inspector, which are both drawers now (pods slide-over below; inspector fixed-right in the 1023px block above). This :not chain out-specifies the drawer rules (0-4-0 vs 0-1-0), so an omission here is an invisible mounted pane: the inspector was exactly that on phones until 2026-08-26 — the header avatar button mounted it, this rule erased it. */ .v2-pane:not(.v2-pane--rail):not(.v2-pane--main):not(.v2-pods-aside):not(.v2-pane--inspector) { display: none; } /* Pods sidebar → slide-over drawer. Off-canvas by default; .--open slides it in. Sits above the backdrop (z 60 vs 55). */ .v2-pods-aside { position: fixed; top: 0; left: 0; z-index: 60; width: min(86vw, 320px); height: 100vh; transform: translateX(-100%); transition: transform 0.22s ease; box-shadow: 2px 0 24px rgba(15, 17, 21, 0.18); border-right: 1px solid var(--v2-border-soft); } .v2-pods-aside--open { transform: translateX(0); } .v2-inspector-backdrop { display: block; position: fixed; inset: 0; z-index: 55; border: none; margin: 0; padding: 0; background: rgba(15, 17, 21, 0.42); cursor: pointer; } } /* ---------- Admin · Users (waitlist + invitation codes) ---------- */ .v2-admin-users { display: flex; flex-direction: column; gap: 32px; max-width: 1100px; } .v2-admin-users__section { display: flex; flex-direction: column; gap: 14px; } .v2-admin-users__section-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; } .v2-admin-users__section-title { font-size: 17px; font-weight: 700; color: var(--v2-text-primary); margin: 0; } .v2-admin-users__section-sub { font-size: 13px; color: var(--v2-text-tertiary); margin: 2px 0 0 0; } .v2-admin-users__controls { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } .v2-admin-users__input { flex: 1 1 280px; min-width: 220px; padding: 9px 12px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-surface); font-size: 14px; color: var(--v2-text-primary); outline: none; transition: border-color 0.15s ease, box-shadow 0.15s ease; } .v2-admin-users__input:focus { border-color: var(--v2-accent); box-shadow: var(--v2-focus-ring); } .v2-admin-users__tabs { display: inline-flex; gap: 4px; padding: 3px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-pill); background: var(--v2-surface); } .v2-root button.v2-admin-users__tab { padding: 6px 14px; border: none; border-radius: var(--v2-radius-pill); background: transparent; color: var(--v2-text-secondary); font-size: 13px; font-weight: 600; cursor: pointer; transition: background 0.12s ease, color 0.12s ease; } .v2-root button.v2-admin-users__tab:hover { color: var(--v2-text-primary); } .v2-root button.v2-admin-users__tab--active { background: var(--v2-accent-soft); color: var(--v2-accent-text); } .v2-admin-users__table-wrap { border: 1px solid var(--v2-border); border-radius: var(--v2-radius-lg); overflow-x: auto; background: var(--v2-surface); } .v2-admin-users__table { width: 100%; border-collapse: collapse; font-size: 13px; } .v2-admin-users__table th { text-align: left; padding: 10px 14px; font-size: 12px; font-weight: 700; color: var(--v2-text-tertiary); text-transform: uppercase; letter-spacing: 0.03em; background: var(--v2-bg-subtle); border-bottom: 1px solid var(--v2-border); white-space: nowrap; } .v2-admin-users__table td { padding: 12px 14px; border-bottom: 1px solid var(--v2-border-soft); color: var(--v2-text-secondary); vertical-align: top; } .v2-admin-users__table tbody tr:last-child td { border-bottom: none; } .v2-admin-users__cell-strong { color: var(--v2-text-primary); font-weight: 600; } .v2-admin-users__cell-muted { color: var(--v2-text-tertiary); white-space: nowrap; } .v2-admin-users__cell-wrap { max-width: 260px; } .v2-admin-users__note { display: block; font-size: 12px; color: var(--v2-text-tertiary); margin-top: 2px; } .v2-admin-users__actions { white-space: nowrap; text-align: right; } .v2-admin-users__badge { display: inline-block; padding: 2px 10px; border-radius: var(--v2-radius-pill); font-size: 12px; font-weight: 700; text-transform: capitalize; } .v2-admin-users__badge--ok { background: var(--v2-success-soft); color: #0f7a52; } .v2-admin-users__badge--warn { background: var(--v2-warning-soft); color: #9a5a12; } .v2-admin-users__badge--muted { background: var(--v2-surface-hover); color: var(--v2-text-tertiary); } .v2-root button.v2-admin-users__btn { padding: 8px 14px; border-radius: var(--v2-radius-sm); font-size: 13px; font-weight: 600; cursor: pointer; border: 1px solid transparent; transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; } .v2-root button.v2-admin-users__btn:disabled { opacity: 0.6; cursor: default; } .v2-root button.v2-admin-users__btn--primary { background: var(--v2-accent-fill); border-color: var(--v2-accent); color: var(--v2-on-accent); } .v2-root button.v2-admin-users__btn--primary:hover:not(:disabled) { background: var(--v2-accent-strong); border-color: var(--v2-accent-strong); } .v2-root button.v2-admin-users__btn--ghost { background: var(--v2-surface); border-color: var(--v2-border); color: var(--v2-text-secondary); } .v2-root button.v2-admin-users__btn--ghost:hover:not(:disabled) { background: var(--v2-surface-hover); border-color: var(--v2-border-strong); color: var(--v2-text-primary); } .v2-root button.v2-admin-users__btn--danger { background: var(--v2-surface); border-color: var(--v2-border); color: var(--v2-danger); } .v2-root button.v2-admin-users__btn--danger:hover:not(:disabled) { background: #fee2e2; border-color: #fca5a5; color: #991b1b; } .v2-admin-users__loading { display: flex; align-items: center; gap: 10px; padding: 24px 4px; color: var(--v2-text-tertiary); font-size: 14px; } .v2-admin-users__error { padding: 10px 12px; border-radius: var(--v2-radius-sm); background: #fee2e2; border: 1px solid #fca5a5; color: #991b1b; font-size: 13px; } .v2-admin-users__notice-row td { padding-top: 0; } .v2-admin-users__notice { padding: 8px 12px; border-radius: var(--v2-radius-sm); background: var(--v2-warning-soft); border: 1px solid #f0c98a; color: #9a5a12; font-size: 12px; } .v2-admin-users__newcode { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 14px 16px; border: 1px solid var(--v2-accent-soft); border-radius: var(--v2-radius-lg); background: var(--v2-accent-soft); } .v2-admin-users__newcode-label { font-size: 13px; font-weight: 600; color: var(--v2-accent-text); flex: 1 1 100%; } .v2-admin-users__newcode-value { font-family: var(--v2-font-mono); font-size: 15px; font-weight: 700; letter-spacing: 0.04em; color: var(--v2-text-primary); padding: 6px 12px; background: var(--v2-surface); border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); } .v2-admin-users__code { font-family: var(--v2-font-mono); font-size: 13px; color: var(--v2-text-primary); } .v2-root button.v2-admin-users__inline-copy { margin-left: 8px; padding: 2px 8px; border-radius: var(--v2-radius-sm); border: 1px solid var(--v2-border); background: var(--v2-surface); color: var(--v2-text-secondary); font-size: 11px; font-weight: 600; cursor: pointer; } .v2-root button.v2-admin-users__inline-copy:hover { background: var(--v2-surface-hover); color: var(--v2-text-primary); } /* Registered-users moderation (V2AdminUsers users section). */ .v2-admin-users__search { height: 34px; padding: 0 12px; min-width: 240px; font-size: 13.5px; border: 1px solid var(--v2-border-soft); border-radius: var(--v2-radius-sm); background: var(--v2-surface); color: var(--v2-text-primary); } .v2-admin-users__badge--banned { background: #fdecea; color: #c0392b; } .v2-root button.v2-admin-users__btn--danger { color: #c0392b; border-color: #e5b4ae; } .v2-root button.v2-admin-users__btn--danger:hover:not(:disabled) { background: #fdecea; } .v2-admin-users__row-actions { display: flex; gap: 8px; } /* Narrow screens: the team-card actions (Profile + Talk to) would otherwise squeeze the body column to zero width — the agent NAME disappeared at 390px (mobile smoke 2026-07-05, worsened by the added Profile button). Wrap the actions onto their own full-width row instead of competing with the name. */ /* Reply/Thread inline head buttons retired 2026-08-23 (taste ruling): the actions live in the .v2-msg__actions hover cluster now — one pill, all row variants, zero layout shift. See the cluster block near .v2-msg. Touch layouts keep ≥44px targets via the media block below. */ @media (hover: none), (pointer: coarse) { /* iOS Safari auto-zooms on focusing any input under 16px and does not zoom back on blur — the shell's inputs ranged 12–15px, so every tap on the composer or a search field left the page stuck zoomed ( 2026-08-26). 16px on touch devices removes the trigger entirely; this is the root fix, NOT the viewport maximum-scale hack, which blocks pinch-zoom on Android and is an accessibility regression. !important is deliberate: it must beat every per-component size including the legacy 15px !important, and a device-scoped accessibility floor is the rare case that earns it. */ .v2-root input, .v2-root textarea, .v2-root select { font-size: 16px !important; } } /* ============ Admin analytics (GH#662) ============ */ .v2-admin-analytics { display: flex; flex-direction: column; gap: 24px; max-width: 1100px; } .v2-admin-analytics__head-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; } .v2-root a.v2-admin-analytics__crosslink { font-size: 13px; font-weight: 600; color: var(--v2-accent-text); } .v2-admin-analytics__cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; } .v2-admin-analytics__card { padding: 14px 16px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-lg); background: var(--v2-surface); } .v2-admin-analytics__card-value { font-size: 26px; font-weight: 700; color: var(--v2-text-primary); font-variant-numeric: tabular-nums; } .v2-admin-analytics__card-label { font-size: 12px; color: var(--v2-text-tertiary); margin-top: 2px; } .v2-admin-analytics__charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 12px; } .v2-admin-analytics__chart-card { padding: 14px 16px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-lg); background: var(--v2-surface); min-width: 0; } .v2-admin-analytics__chart-title { font-size: 12px; font-weight: 700; color: var(--v2-text-tertiary); text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 8px; } .v2-admin-analytics__chart { display: block; width: 100%; height: 120px; } .v2-admin-analytics__bar { fill: var(--v2-accent); opacity: 0.85; } .v2-admin-analytics__row--empty td { color: var(--v2-text-tertiary); } /* ── Threading: headline card + reply rail (W-T 4/4) ──────────────────────── Built to @ux-lead's render brief. Numbers here are theirs, not invented: NOTE the `.v2-root button.` prefix on every button rule below. It keeps the card's interactive treatment explicit while the global reset remains a low-specificity default. This used to be a specificity workaround for a real-browser defect: computed colour was rgb(17,24,39) while the rule said #4b5563 — which no presence guard can see, because the rule IS present. card radius 10 / border #e5e7eb / hover #d7dce7 / no shadow; type 13px secondary with the count at 500; rail 2px #eef0f6 at 28px, replies +40px, dropping to 24px at 390; layout transition 300ms. */ .v2-decision-card__option-recommended { font: 500 11px/14px var(--v2-font-mono); opacity: 0.9; } /* --------------------------------------------------------------------------- * zh-CN: no negative tracking on CJK (TASK-055, rule class 1). * The 12 title selectors above tighten Latin display type with negative * letter-spacing. CJK glyphs are full-width squares with no side bearings to * reclaim, so the same value only crushes strokes together. Measured in the * real browser (.dev/task-055-zh-harness.html): .v2-chat__title -0.5px, * .v2-pods__title -0.32px, .v2-inspector__now-title -0.15px on Chinese text. * `html[lang]` is set by frontend/src/i18n/index.ts, so :lang(zh) matches * zh-CN. Guarded by v2-layout-invariants.test.ts. * ------------------------------------------------------------------------ */ .v2-root:lang(zh) .v2-rail__brand, .v2-root:lang(zh) .v2-tools__count strong, .v2-root:lang(zh) .v2a-title, .v2-root:lang(zh) .v2-pods__page-title, .v2-root:lang(zh) .v2-first-run__title, .v2-root:lang(zh) .v2-login__title, .v2-root:lang(zh) .v2-login__brand, .v2-root:lang(zh) .v2-feature__title, .v2-root:lang(zh) .v2-feature__header .v2-feature__title, .v2-root:lang(zh) .v2-agent-settings > .v2-page-head h1, .v2-root:lang(zh) .v2-agent-settings > .v2-agent-memory__head .v2-agent-memory__ident h1, .v2-root:lang(zh) .v2-connector-row__name, .v2-root:lang(zh) .v2-connector-aside__card h2, .v2-root:lang(zh) .v2-feature__legacy .MuiTypography-h1, .v2-root:lang(zh) .v2-feature__legacy .MuiTypography-h2, .v2-root:lang(zh) .v2-feature__legacy .MuiTypography-h3, .v2-root:lang(zh) .v2-feature__legacy .MuiTypography-h4, .v2-root:lang(zh) .v2-feature__legacy .MuiTypography-h5, .v2-root:lang(zh) .v2-feature__legacy .MuiTypography-h6, .v2-root:lang(zh) .v2-empty--room .v2-empty__title { letter-spacing: 0; } /* --------------------------------------------------------------------------- * zh-CN: mono labels drop `text-transform: lowercase` (TASK-164, rule class 3). * `.v2-connector-gates__title` and `.v2-tools__field > span:first-child` set * lowercase for Latin kickers. The zh copy for both is CJK plus one Latin * token — `Pod`, the product noun, which is never a synonym — * and CSS lowercases it to `pod`, so a label contradicts the sentence beside * it. Case is not a property of CJK; the reset exists only for the Latin token * the copy carries. Guarded by v2-layout-invariants.test.ts. * ------------------------------------------------------------------------ */ .v2-root:lang(zh) .v2-connector-gates__title, .v2-root:lang(zh) .v2-tools__field > span:first-child, .v2-root:lang(zh) .v2-tools__field > legend { text-transform: none; } /* --------------------------------------------------------------------------- * zh-CN: CJK body line-height 1.6 and a 12px floor (TASK-055, rule class 2). * Latin body copy sits at 1.45–1.55 here; CJK glyphs fill the em box, so the * same leading leaves no white between lines. Measured in the real browser * (.dev/task-055-zh-harness.html): .v2-msg__content 21.7px/14px = 1.55, * .v2-chat__composer-hint 1.45, .v2-inspector__pod-meta 1.45, and the * composer hint (11px) / inspector tab (11.5px) render CJK below the 12px * legibility floor. Single-line nowrap+ellipsis rows are not touched — their * height is not text-driven. Guarded by v2-layout-invariants.test.ts. * ------------------------------------------------------------------------ */ .v2-root:lang(zh) .v2-first-run__lede, .v2-root:lang(zh) .v2-first-run__step-body, .v2-root:lang(zh) .v2-modal__hint, .v2-root:lang(zh) .v2-invite__description, .v2-root:lang(zh) .v2-login__field-hint { line-height: 1.6; } /* ── Connectors: the page itself is connectors.css; what is left here is the panels the drawer holds (rows, asides, gates). ──────────────────────────── */ .v2-connectors__loading { color: var(--v2-text-tertiary); font-family: var(--v2-font-mono); font-size: 12px; } .v2-connector-row { display: grid; /* Keep columns aligned while giving details the only flexible track. The old fixed 200px + 200px + 120px tracks left details ~20px wide at 1184px, while a flexible action track let each row drift and clipped its label. */ /* Direction A (2026-09-19): the age column is gone — it rides the mono kicker above line 1 — so the row is name · details · one act. */ grid-template-columns: 140px minmax(150px, 1fr) 120px; /* TASK-162 (2): the channel name and its details met at 0px — the 140px name track ends exactly where the details track begins, so a name that fills its track touches the sentence. A 12px gutter on BOTH row grids (here and the selection button's inner grid below) keeps the two columns apart. */ column-gap: 12px; align-items: center; min-height: 82px; padding: 16px 18px; border-bottom: 1px solid var(--v2-border-soft); background: var(--v2-surface); } .v2-connector-row:last-child { border-bottom: none; } .v2-connector-row--selected { background: var(--v2-surface-tint); } .v2-root button.v2-connector-row__selection { display: grid; grid-column: 1 / 3; grid-template-columns: 140px minmax(150px, 1fr); column-gap: 12px; align-items: center; min-width: 0; padding: 0; border: none; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; } .v2-connector-row__name { display: inline-flex; align-items: center; gap: 10px; color: var(--v2-ink); font-family: var(--v2-font-display); font-size: 18px; font-weight: 800; letter-spacing: -0.02em; } .v2-connector-row__dot { width: 8px; height: 8px; flex: none; border-radius: var(--v2-radius-sm); background: var(--v2-border-soft); } .v2-connector-row__glyph { display: inline-flex; width: 20px; height: 20px; flex: 0 0 20px; color: inherit; } .v2-connector-row__glyph svg { width: 20px; height: 20px; } /* Direction A: the state dot is a mark — ink filled, hollow, dashed, grey — never cobalt (cobalt is who asks) and never a status colour (ux-lead 70159). */ .v2-connector-row__dot--live { background: var(--v2-ink); } .v2-connector-row--dead .v2-connector-row__dot { background: var(--v2-text-placeholder); } .v2-connector-row__dot--empty { border: 1px solid var(--v2-border-strong); background: transparent; } .v2-connector-row__dot--pulse { animation: v2-connector-pulse 1.6s ease-in-out infinite; } @keyframes v2-connector-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(16, 24, 40, 0); } 50% { box-shadow: 0 0 0 5px rgba(16, 24, 40, 0.14); } } @media (prefers-reduced-motion: reduce) { .v2-connector-row__dot--pulse { animation: none; } } .v2-connector-row__details { display: flex; min-width: 0; padding-right: 8px; flex-direction: column; gap: 2px; color: var(--v2-text-secondary); font-size: 13px; line-height: 18px; } /* Line 1 wraps like meta does; a sentence never ellipsises at 390. */ .v2-connector-row__details strong { color: var(--v2-ink); font-size: 14px; font-weight: 600; overflow-wrap: anywhere; } .v2-connector-row__detail { color: var(--v2-text-tertiary); font-family: var(--v2-font-mono); font-size: 12px; line-height: 16px; } /* Meta wraps to a second line; it never ellipsises. */ .v2-connector-row__detail { overflow-wrap: anywhere; } /* Direction A rule 3: `pod · verb age` in mono 11 above line 1, on every row. */ .v2-connector-row__kicker { color: var(--v2-text-tertiary); font-family: var(--v2-font-mono); font-size: 11px; font-weight: 500; line-height: 16px; text-transform: lowercase; overflow-wrap: anywhere; } /* The mode word rides the kicker only where the glyph's tooltip cannot (≤760). */ .v2-connector-row__kicker-mode { display: none; } /* Direction A rule 1 (as revised by TASK-162): a category on the row is a mark, the sentence in title + aria-label — and the mark shares the Tools glyph's rule (search `.v2-connector-row__mark, .v2-tools__mode`) for layout and colour. That sharing does NOT carry the size: the two agree at 14px through two independent literals in two files, so a same-size pin in ConnectorsPage.tools.test.tsx is what keeps them from drifting (TASK-177). */ .v2-root button.v2-connector-row__action, .v2-root a.v2-connector-row__action { justify-self: end; min-height: 32px; padding: 6px 12px; border: 1px solid var(--v2-ink); border-radius: var(--v2-radius-sm); background: var(--v2-ink); color: var(--v2-on-ink); font: 600 13px/18px var(--v2-font); text-decoration: none; cursor: pointer; } /* TASK-162 (1): a hovered secondary action turned INK behind an ink glyph — `.v2-root button....__action:hover:not(:disabled)` (0,4,1) outranks the `--secondary:hover` tint (0,3,1), so the gear read as a solid black square with an invisible glyph. Excluding secondary from the ink rule fixes it WITHOUT relying on source order: raising the tint's specificity would work only until someone moves a rule, and the `.v2-root` prefix that every selector here carries makes order-fragile fixes easy to write by accident. */ .v2-root button.v2-connector-row__action:hover:not(:disabled):not(.v2-connector-row__action--secondary), .v2-root a.v2-connector-row__action:hover:not(.v2-connector-row__action--secondary) { background: var(--v2-ink-hover); } .v2-root button.v2-connector-row__action:disabled { cursor: progress; opacity: 0.65; } .v2-root button.v2-connector-row__action--secondary, .v2-root a.v2-connector-row__action--secondary { border-color: var(--v2-border); background: var(--v2-surface); color: var(--v2-ink); } .v2-root button.v2-connector-row__action--secondary:hover, .v2-root a.v2-connector-row__action--secondary:hover { background: var(--v2-surface-tint); } /* Direction A rule 2: a housekeeping act (Manage) is a 32px square glyph button; the word lives in title + aria-label. 44px under 760 for the thumb. */ .v2-root button.v2-connector-row__action--icon { display: inline-grid; place-items: center; width: 32px; min-height: 32px; padding: 0; } .v2-root button.v2-connector-row__action--icon svg { width: 16px; height: 16px; } /* TASK-140: the Revoke ✕ is a SQUARE. It carries `--secondary` too, whose `min-height: 36px` (below) applies at lower specificity, and min-height beats height — so setting `height: 32px` alone measured 32×36 (ux-lead's gate on #1799, computed style). The composed selector must therefore override the MINIMUM, not just the height. Keep both: the height is the intent, the min-height is what actually releases the 36px clamp. */ .v2-root button.v2-connector-aside__secondary.v2-connector-aside__icon { display: inline-grid; place-items: center; width: 32px; height: 32px; min-height: 32px; padding: 0; } .v2-root button.v2-connector-aside__icon svg { width: 16px; height: 16px; } .v2-root button.v2-connectors__create, .v2-root button.v2-connector-aside__primary { min-height: 40px; padding: 9px 14px; border: 1px solid var(--v2-ink); border-radius: var(--v2-radius-sm); background: var(--v2-ink); color: var(--v2-on-ink); font: 600 13px/18px var(--v2-font); cursor: pointer; } .v2-root button.v2-connectors__create:hover:not(:disabled), .v2-root button.v2-connector-aside__primary:hover:not(:disabled) { background: var(--v2-ink-hover); } .v2-root button.v2-connectors__create:disabled, .v2-root button.v2-connector-aside__primary:disabled { cursor: progress; opacity: 0.65; } .v2-connectors__new-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(180px, 240px) auto; gap: 10px; margin-top: 16px; } .v2-connectors__new-row--aside { grid-template-columns: minmax(0, 1fr) auto; } .v2-connectors__new-row--aside .v2-connectors__select { grid-column: 1 / -1; } .v2-connectors__empty-pods { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between; gap: 12px; } .v2-connectors__empty-pods p { margin: 0; color: var(--v2-text-secondary); font-size: 13px; line-height: 18px; } .v2-connectors__empty-pods > .v2-connectors__create { flex: 0 0 auto; } .v2-connectors__providers { display: inline-flex; overflow: hidden; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); } .v2-root button.v2-connectors__provider { min-height: 38px; padding: 7px 12px; border: none; border-right: 1px solid var(--v2-border); background: var(--v2-surface); color: var(--v2-text-secondary); font: 600 13px/18px var(--v2-font); cursor: pointer; } .v2-root button.v2-connectors__provider:last-child { border-right: none; } .v2-root button.v2-connectors__provider--selected { background: var(--v2-surface-tint); color: var(--v2-ink); } .v2-connectors__select { min-width: 0; min-height: 40px; padding: 0 10px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-surface); color: var(--v2-ink); font: 500 13px var(--v2-font); } .v2-connectors__aside { display: flex; min-width: 0; flex-direction: column; gap: 16px; } .v2-connector-aside__step, .v2-connector-aside__card { padding: 18px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-md); background: var(--v2-panel, var(--v2-bg-subtle)); } .v2-connector-aside__step p, .v2-connector-aside__card p { margin: 0; color: var(--v2-text-secondary); font-size: 13px; line-height: 20px; } .v2-connector-aside__eyebrow { margin-bottom: 8px !important; color: var(--v2-accent-text) !important; font-family: var(--v2-font-mono); font-size: 11px !important; font-weight: 500; text-transform: lowercase; } .v2-connector-aside__card h2 { margin: 0 0 8px; color: var(--v2-ink); font-family: var(--v2-font-display); font-size: 16px; font-weight: 800; letter-spacing: -0.02em; } .v2-connector-aside__actions, .v2-connector-aside__controls { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 14px; } .v2-root button.v2-connector-aside__secondary { min-height: 36px; padding: 7px 12px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-surface); color: var(--v2-ink); font: 600 13px/18px var(--v2-font); cursor: pointer; } .v2-root button.v2-connector-aside__secondary:hover { background: var(--v2-surface-tint); } .v2-connector-aside__card > .v2-connector-aside__secondary, .v2-connector-aside__card > .v2-connector-aside__primary { margin-top: 18px; } .v2-connector-aside__relay { display: inline-flex; align-items: center; gap: 7px; color: var(--v2-text-secondary); font-size: 13px; cursor: pointer; } .v2-connector-aside__relay input { accent-color: var(--v2-ink); } .v2-connector-aside__mode { display: inline-flex; overflow: hidden; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); } .v2-root button.v2-connector-aside__mode-opt { min-height: 30px; padding: 4px 10px; border: none; border-right: 1px solid var(--v2-border); background: var(--v2-surface); color: var(--v2-text-secondary); font: 600 12px/16px var(--v2-font); cursor: pointer; } .v2-root button.v2-connector-aside__mode-opt:last-child { border-right: none; } .v2-root button.v2-connector-aside__mode-opt--on { background: var(--v2-surface-tint); color: var(--v2-ink); cursor: default; } .v2-connector-code { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px; align-items: center; margin-top: 14px; padding: 12px; border-radius: var(--v2-radius-sm); background: var(--v2-ink); } .v2-connector-code code { overflow-wrap: anywhere; color: var(--v2-on-ink); font-family: var(--v2-font-mono); font-size: 14px; line-height: 20px; } .v2-root button.v2-connector-code__copy { min-height: 30px; padding: 5px 9px; border: 1px solid var(--v2-accent); border-radius: var(--v2-radius-sm); background: var(--v2-accent); color: var(--v2-on-ink); font: 600 12px/16px var(--v2-font); cursor: pointer; } .v2-connector-row__refusal { grid-column: 1 / -1; margin: 10px 0 0; padding: 8px 10px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-surface-tint); color: var(--v2-ink); font-size: 13px; line-height: 18px; overflow-wrap: anywhere; } .v2-connector-row__refusal a, .v2-connector-aside__refusal a { color: var(--v2-accent-text); text-decoration: underline; } .v2-connector-aside__note { margin-top: 12px !important; color: var(--v2-text-tertiary) !important; font-size: 12px !important; } /* A hosted connection's name beside its own Disconnect: the note's type, centred on the button. */ .v2-connector-aside__conn { color: var(--v2-text-tertiary); font-size: 12px; } /* Row C: the refusal the row's own button earned, beside that button in the aside (and on the row itself) — same bordered grammar as the page slot. */ .v2-connector-aside__refusal { margin-top: 10px !important; padding: 8px 10px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-surface-tint); color: var(--v2-ink); font-size: 13px; line-height: 18px; overflow-wrap: anywhere; } /* D4 gate list: one row per pod the user is in. The switch is the gate (pod → me); the active tag marks where typed messages land (me → pod). */ .v2-connector-gates { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--v2-border-soft); } .v2-connector-gates__title { margin: 0 0 6px !important; color: var(--v2-text-tertiary) !important; font-family: var(--v2-font-mono); font-size: 11px !important; line-height: 16px !important; text-transform: lowercase; } .v2-connector-gates__empty { color: var(--v2-text-tertiary) !important; font-size: 12px !important; } .v2-connector-gate { border-bottom: 1px solid var(--v2-border-soft); } .v2-connector-gate:last-child { border-bottom: none; } .v2-connector-gate__row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 10px; min-height: 36px; } .v2-root button.v2-connector-gate__name { display: inline-flex; align-items: center; gap: 8px; min-width: 0; padding: 6px 0; border: none; background: transparent; color: var(--v2-ink); font: 500 13px/18px var(--v2-font); text-align: left; cursor: pointer; } .v2-connector-gate__mark { width: 4px; height: 4px; flex: none; background: var(--v2-ink); } /* A long pod name wraps under the mark; it never ellipsises. */ .v2-connector-gate__pod { min-width: 0; overflow-wrap: anywhere; } .v2-connector-gate__tag { flex: none; color: var(--v2-ink); font-family: var(--v2-font-mono); font-size: 11px; line-height: 16px; } .v2-connector-gate__since { color: var(--v2-text-tertiary); font-family: var(--v2-font-mono); font-size: 11px; line-height: 16px; white-space: nowrap; } .v2-connector-gate__since--off { color: var(--v2-text-placeholder); } /* The gate switch is the system's checkbox — the bordered square the Relay line already uses, ink when on. No pill: the system has hard edges only. */ .v2-connector-gate__switch { width: 16px; height: 16px; margin: 0; accent-color: var(--v2-ink); cursor: pointer; } .v2-connector-gate__switch:disabled { cursor: progress; opacity: 0.65; } .v2-connector-gate__more { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 4px 0 12px 12px; } .v2-connector-gate__lead { min-height: 30px; padding: 0 8px; font-size: 12px; } .v2-tools__facts { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2px; margin: 14px 0 0; } .v2-tools__facts dt { margin-top: 8px; color: var(--v2-text-tertiary); font-family: var(--v2-font-mono); font-size: 11px; line-height: 16px; text-transform: lowercase; } .v2-tools__facts dt:first-child { margin-top: 0; } .v2-tools__facts dd { margin: 0; min-width: 0; color: var(--v2-ink); font-size: 13px; line-height: 18px; overflow-wrap: anywhere; } /* A tool name is one token: the chip never breaks inside it, it wraps to the next line whole. */ .v2-tools__facts dd code { display: inline-block; margin: 0 6px 4px 0; padding: 1px 6px; border: 1px solid var(--v2-border-soft); border-radius: var(--v2-radius-sm); font-family: var(--v2-font-mono); font-size: 12px; overflow-wrap: normal; white-space: nowrap; } .v2-tools__counts { display: flex; flex-wrap: wrap; gap: 24px; margin-bottom: 12px; } .v2-tools__count { display: flex; flex-direction: column; gap: 2px; } .v2-tools__count strong { color: var(--v2-ink); font-family: var(--v2-font-display); font-size: 22px; font-weight: 800; letter-spacing: -0.02em; line-height: 26px; } .v2-tools__count span { color: var(--v2-text-tertiary); font-family: var(--v2-font-mono); font-size: 11px; line-height: 16px; text-transform: lowercase; } .v2-tools__trail { margin: 0; padding: 0; list-style: none; } .v2-tools__trail-line { display: flex; justify-content: space-between; gap: 12px; padding: 6px 0; border-top: 1px solid var(--v2-border-soft); color: var(--v2-text-muted); font-family: var(--v2-font-mono); font-size: 12px; line-height: 16px; } .v2-tools__trail-line > span:first-child { min-width: 0; overflow-wrap: anywhere; } .v2-tools__trail-when { flex: none; } .v2-tools__mode, .v2-tools__outcome { display: inline-flex; align-items: center; margin-right: 6px; vertical-align: -2px; color: var(--v2-text-tertiary); } .v2-tools__form { display: grid; gap: 14px; margin-top: 14px; } .v2-tools__field { display: grid; gap: 6px; margin: 0; padding: 0; border: none; min-width: 0; } .v2-tools__field > span:first-child, .v2-tools__field > legend { color: var(--v2-text-tertiary); font-family: var(--v2-font-mono); font-size: 11px; line-height: 16px; text-transform: lowercase; padding: 0; } .v2-tools__input { min-width: 0; min-height: 40px; padding: 0 10px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-surface); color: var(--v2-ink); font: 500 13px var(--v2-font); } .v2-tools__input:focus-visible { outline: 2px solid var(--v2-accent); outline-offset: 0; } .v2-tools__agents { display: grid; gap: 6px; } .v2-tools__hint { color: var(--v2-text-secondary); font-size: 12px; line-height: 18px; } .v2-tools__hint code { display: inline-block; margin: 0 6px 4px 0; padding: 1px 6px; border: 1px solid var(--v2-border-soft); border-radius: var(--v2-radius-sm); font-family: var(--v2-font-mono); font-size: 12px; white-space: nowrap; } @media (max-width: 760px) { .v2-connector-row { grid-template-columns: minmax(0, 1fr) auto; min-height: 0; padding: 14px 12px; } .v2-root button.v2-connector-row__selection { grid-column: 1 / -1; grid-template-columns: minmax(0, 1fr) auto; row-gap: 4px; } .v2-root button.v2-connector-row__selection .v2-connector-row__name { grid-column: 1; grid-row: 1; } .v2-root button.v2-connector-row__selection .v2-connector-row__details { grid-column: 1 / -1; grid-row: 2; } /* A phone keeps the row's REASON. This used to be a blanket hide with enumerated exceptions (`:not(.v2-connector-row--dead):not(.v2-connector-row--not-enabled)`), which silently swallowed every state the list had not anticipated: the not-yet row that IS available (ToolPanels.tsx) lost "install the GitHub App first" / "read, or read and write", and a stranger saw a "not granted" kicker with no reason under it (eng lead 73726). Default is now SHOW; if one row ever needs its detail hidden on phones, hide that named case here rather than re-introducing an exception list. */ .v2-root button.v2-connector-row__action, .v2-root a.v2-connector-row__action { grid-column: 2; grid-row: 1; z-index: 1; } /* Direction A at 390 (after the rule above so it wins): no tooltips, so the mode word rides the kicker; the gear is 44px in row 1 beside the name; a row's word act is full width under the sentence; rows keep one height. */ .v2-connector-row { min-height: 72px; } .v2-root button.v2-connector-row__selection { grid-row: 1; } .v2-connector-row__kicker-mode { display: inline; } .v2-root button.v2-connector-row__action--icon { width: 44px; min-height: 44px; grid-column: 2; grid-row: 1; justify-self: end; align-self: start; } .v2-root button.v2-connector-row__action:not(.v2-connector-row__action--icon), .v2-root a.v2-connector-row__action { display: inline-flex; grid-column: 1 / -1; grid-row: 3; width: 100%; justify-content: center; min-height: 44px; margin-top: 8px; } /* The gear sits over the selection button's top-right; keep the kicker out from under it. */ .v2-connector-row:has(.v2-connector-row__action--icon) .v2-root button.v2-connector-row__selection, .v2-root .v2-connector-row:has(.v2-connector-row__action--icon) button.v2-connector-row__selection { padding-right: 52px; } .v2-root button.v2-connector-aside__secondary.v2-connector-aside__icon { width: 44px; height: 44px; min-height: 44px; } .v2-connectors__new-row { grid-template-columns: minmax(0, 1fr); } .v2-connectors__providers { width: fit-content; } .v2-connector-gate__more { padding-left: 0; } } /* ---------- Workspace sidebar (TASK-129 PR 1) ---------- The workspace artboard is a ground-colour grid, not three inset cards. These rules intentionally follow the retired sidebar rules above while the chat and inspector receive their own structural passes in PRs 2 and 3. */ .v2-shell { gap: 14px; padding: 14px; box-sizing: border-box; } .v2-shell .v2-pane { height: 100%; min-height: 0; } .v2-shell .v2-pane--main { margin: 0; height: 100%; } .v2-pods { position: relative; height: 100%; padding: 6px 0; background: var(--v2-shell-bg); } @media (max-width: 760px) { .v2-shell, .v2-shell--no-inspector, .v2-shell--feature { gap: 0; padding: 0; } .v2-shell .v2-pane { height: 100vh; } /* A pane INSIDE the authenticated shell is bounded by the shell's content box, not by the viewport: that box has already had the verify-email banner subtracted from it, so a 100vh pane overflows below the fold by the banner's height. With its own `overflow: hidden` the pod header then sits under the banner — the back button, the title and the inspector toggle all untappable, and there is nothing to scroll (TASK-157, ux-lead at 390, ux-lead's P1). Ranked (0,3,0) against the (0,2,0) rule above so it wins on specificity, not on coming later: the same lesson as #1868, where a tie resolved by file order silently erased a component's own declarations. The 100vh rule itself stays — it is the right height outside this shell — and this guard is what keeps it from winning here. */ .v2-authenticated-shell__content .v2-shell .v2-pane { height: 100%; } .v2-pods-aside { width: min(86vw, 320px); } } /* ---------- Workspace decision card (TASK-129 PR 3) ---------- A DecisionRequest is an in-thread fork, not prose the human must parse. The cobalt 2px ring is reserved for this unresolved, human-owned choice. */ .v2-decision-card { display: flex; flex-direction: column; gap: 10px; margin: 8px 0; padding: 14px; border: 1px solid var(--v2-border-soft); border-radius: 6px; background: var(--v2-surface); box-shadow: 0 0 0 2px var(--v2-accent); } .v2-decision-card--ruled { box-shadow: none; } .v2-decision-card__head { display: flex; align-items: baseline; gap: 8px; min-width: 0; } .v2-decision-card__head h3 { min-width: 0; margin: 0; color: var(--v2-text-primary); font: 600 14px/20px var(--v2-font); } .v2-decision-card__agent { flex-shrink: 0; color: var(--v2-accent-text); font: 500 11px/14px var(--v2-font-mono); } .v2-decision-card__question { margin: 0; color: var(--v2-text-secondary); font: 400 13px/19px var(--v2-font); } .v2-decision-card__options { display: grid; gap: 12px; } .v2-decision-card__option { display: grid; min-width: 0; gap: 4px; } .v2-root button.v2-decision-card__choice, .v2-root button.v2-decision-card__other, .v2-root button.v2-decision-card__other-form button { min-height: 32px; padding: 6px 10px; border: 1px solid var(--v2-border); border-radius: 4px; background: var(--v2-surface); color: var(--v2-text-primary); font: 600 13px/20px var(--v2-font); } .v2-root button.v2-decision-card__choice { width: 100%; justify-content: flex-start; text-align: left; white-space: normal; } .v2-root button.v2-decision-card__choice--primary { border-color: var(--v2-accent); background: var(--v2-accent); color: var(--v2-on-ink); } .v2-root button.v2-decision-card__choice:hover:not(:disabled), .v2-root button.v2-decision-card__other:hover:not(:disabled) { background: var(--v2-surface-hover); } .v2-root button.v2-decision-card__choice--primary:hover:not(:disabled) { background: var(--v2-accent-strong); } .v2-decision-card__option-description { color: var(--v2-text-secondary); font: 400 14px/20px var(--v2-font); } .v2-decision-card__option-recommended { color: var(--v2-text-muted); font: 500 11px/14px var(--v2-font-mono); } .v2-root button.v2-decision-card__other { border: 0; background: transparent; color: var(--v2-accent-text); } .v2-decision-card__footer { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; } .v2-decision-card__other-form { display: flex; flex: 1 1 100%; gap: 6px; } .v2-decision-card__other-form input { min-width: 0; flex: 1; min-height: 32px; border: 1px solid var(--v2-border); border-radius: 4px; padding: 5px 8px; background: var(--v2-surface); color: var(--v2-text-primary); font: 400 13px/18px var(--v2-font); } .v2-decision-card__settled, .v2-decision-card__error { margin: 0; font: 500 12px/18px var(--v2-font); } .v2-decision-card__settled { color: var(--v2-text-secondary); } .v2-decision-card__error { color: #b91c1c; } /* ---------- Workspace thread and composer (TASK-129 PR 3) ---------- */ /* The pod header shows the toggle only while the panel is hidden; the open panel carries its own Hide button in its head (D215, TASK-734). */ @media (max-width: 760px) { /* The phone artboard makes every decision option a full-width, 44px row; Other remains a centred text escape hatch beneath them. */ .v2-decision-card__options { align-items: stretch; flex-direction: column; } .v2-decision-card__option { width: 100%; } .v2-root button.v2-decision-card__choice { width: 100%; min-height: 44px; font-size: 15px; } .v2-root button.v2-decision-card__other { align-self: flex-start; min-height: 44px; } /* The workspace is edge-to-edge on a phone. Rail and pod list are drawers, while the transcript, composer, and tab bar share the white canvas. */ /* The 1023px rule is more specific and otherwise leaves three tracks active at phone width. The rail is hidden below, so every shell must explicitly collapse to one main track. */ .v2-shell:not(.v2-shell--feature-wide), .v2-shell--feature-wide { grid-template-columns: minmax(0, 1fr); } .v2-shell, .v2-shell--no-inspector, .v2-shell--feature { grid-template-columns: minmax(0, 1fr); padding: 0; gap: 0; background: var(--v2-surface); } .v2-pane--rail { display: none; } .v2-pane--main { background: var(--v2-surface); } .v2-mobile-tabs { position: fixed; right: 0; bottom: 0; left: 0; z-index: 40; display: grid; grid-template-columns: repeat(4, 1fr); height: 54px; padding: 5px 14px; border-top: 1px solid var(--v2-border); background: var(--v2-surface); } .v2-root button.v2-mobile-tabs__item { position: relative; display: grid; width: 44px; height: 32px; justify-self: center; padding: 0; place-items: center; border: 0; border-radius: 4px; background: transparent; color: var(--v2-text-secondary); } .v2-root button.v2-mobile-tabs__item svg { width: 18px; height: 18px; } .v2-root button.v2-mobile-tabs__item--active { background: var(--v2-ink); color: var(--v2-on-ink); } .v2-root button.v2-mobile-tabs__item:hover:not(:disabled) { background: var(--v2-surface-hover); color: var(--v2-text-primary); } .v2-root button.v2-mobile-tabs__item--active:hover:not(:disabled) { background: var(--v2-ink-hover); color: var(--v2-on-ink); } } .v2-mobile-tabs { display: none; } @media (max-width: 760px) { .v2-mobile-tabs { display: grid; } } /* ---------- Workspace at scale · direction C · PR 1 (sidebar + header + 390 list page) ---------- Ruled 2026-09-06 (walk 1): grid 56 / 260 / fluid / 272 with 14px gaps; the sidebar is a white bordered panel; Pinned, Recent (eight by my last visit), Everything folded by kind; every row carries a 22px mark, the needs-you pill and the last-message time; the pod header is 50px with a 15/600 sans title and inline meta. On the phone the pods list is a page and the drawer is gone. Nothing under 11px mono / 12px sans. */ .v2-pods-aside { background: var(--v2-shell-bg); } /* D223 (TASK-735): the artboard's .pane.pods. A 14px card on the shell ground, its edge a shadow ring rather than a border, with the rows inset. */ .v2-pods { display: flex; min-height: 0; height: 100%; flex-direction: column; padding: 12px 10px; border: 0; border-radius: var(--v2-r-14, 14px); background: var(--v2-surface); box-shadow: var(--v2-shadow-pane, 0 0 0 1px var(--v2-border-soft)); overflow: hidden; } /* The phone page is the whole screen: no card, a 52px title bar (.mtop). */ .v2-pods-aside--page .v2-pods { padding: 0; border-radius: 0; box-shadow: none; } .v2-pods__bar { display: flex; flex: none; height: 52px; align-items: center; gap: 8px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--v2-border-soft); } .v2-pods__page-title { flex: 1; min-width: 0; margin: 0; overflow: hidden; color: var(--v2-text-primary); font: 800 21px/1 var(--v2-font-display, var(--v2-font)); letter-spacing: -0.03em; text-overflow: ellipsis; white-space: nowrap; } /* The 390 bar's controls are 44px touch targets. The frame draws them at 34px, so they go to 44px at this width. The theme buttons keep their pill and reach 44px tall through ::after, which stays inside each button's own column. */ .v2-pods-aside--page .v2-pods__bar .v2p-ib { width: 44px; height: 44px; } /* New pod carries its own flex-basis (.v2-pods__new, 32px); the bar overrides it. */ .v2-root .v2-pods-aside--page .v2-pods__bar button.v2-pods__new { flex-basis: 44px; } .v2-pods-aside--page .v2-pods__bar .seg.v2-theme-seg button { position: relative; width: 44px; height: 40px; } .v2-pods-aside--page .v2-pods__bar .seg.v2-theme-seg button::after { content: ''; position: absolute; inset: -2px 0; } .v2-pods-aside--page .v2-pods__body { display: flex; min-height: 0; flex: 1; flex-direction: column; padding: 12px; } .v2-pods__body { display: contents; } .v2-pods__tools { display: flex; flex: 0 0 auto; align-items: center; gap: 8px; padding: 0 2px 10px; } .v2-pods__search { display: flex; min-width: 0; flex: 1; align-items: center; gap: 7px; margin: 0; height: 34px; padding: 0 8px 0 10px; border: 0; border-radius: var(--v2-r-10, 10px); background: var(--v2-bg-subtle); box-shadow: inset 0 0 0 1px var(--v2-border-soft); color: var(--v2-text-placeholder); } .v2-pods__search > svg { flex: none; } .v2-pods__search:focus-within { border-color: var(--v2-border-strong); box-shadow: inset 0 0 0 2px var(--v2-accent); } .v2-pods__search-input { min-width: 0; flex: 1; border: 0; padding: 0; background: transparent; color: var(--v2-text-primary); font: 400 13px/18px var(--v2-font); outline: none; } .v2-pods__search-input::placeholder { color: var(--v2-text-placeholder); } .v2-pods__search-input::-webkit-search-cancel-button { -webkit-appearance: none; } .v2-pods__search-kbd { flex: 0 0 auto; padding: 3px 5px; border: 0; border-radius: 5px; background: var(--v2-surface); box-shadow: 0 0 0 1px var(--v2-border-soft); color: var(--v2-text-muted); font: 500 11px/1 var(--v2-font-mono); } @media (max-width: 760px), (hover: none) and (pointer: coarse) { .v2-pods__search-kbd { display: none; } } /* The artboard's .b.b-s.ib: a 32px raised key holding a plus glyph. */ /* Look from the D223 primitives (primitives.css): v2p-b v2p-b-s v2p-ib. */ .v2-root button.v2-pods__new { flex: 0 0 32px; } .v2-root button.v2-pods__new[aria-expanded="true"] { background: var(--v2-surface-hover); } /* ---------- D215 sidebar controls (TASK-691) ---------- Controls look like controls: an icon button, a chip or a segment, never grey words that click. The segment is shared by the pod list (Recent · All · Pinned), the panel's Who can join and Settings › Look. */ /* The segment's look is the primitives' v2p-seg; these keep its states. */ .v2-root button.v2-seg__option:hover:not(:disabled) { color: var(--v2-text-primary); } .v2-root button.v2-seg__option:focus-visible { outline: none; box-shadow: var(--v2-focus-ring); } .v2-root button.v2-seg__option:disabled { cursor: default; opacity: 0.55; } .v2-seg__count { color: var(--v2-text-muted); font: 500 11px/1 var(--v2-font-mono); } .v2-pods__view { padding: 2px 2px 8px; } .v2-pods-aside--page .v2-pods__view { padding: 0 0 10px; } /* The phone page's Recent · All · Pinned segment is the frame's 28px (d215 sidebar 390), not the primitives' 44px phone size, which made this bar 58 tall and pushed the list down (#3373). Each option keeps a 44px touch area through a transparent ::before; the segment sits alone in its row, so no neighbour's area covers it. The bar keys and rows stay at the 44px floor (Cofounder ruling 11-04). */ @media (max-width: 760px) { .v2-pods-aside--page .v2-pods__view .v2p-seg button { position: relative; height: 28px; } .v2-pods-aside--page .v2-pods__view .v2p-seg button::before { content: ''; position: absolute; top: 50%; left: 50%; width: max(100%, 44px); height: max(100%, 44px); transform: translate(-50%, -50%); background: transparent; } } .v2-pods__list { display: flex; min-height: 0; flex: 1; flex-direction: column; gap: 0; padding: 0; overflow-y: auto; /* The minute poll re-reads /api/pods and the rows re-sort by lastMessage, so a message to any pod moves a row and native anchoring drags the list to keep that row's viewport offset — 4 of 17 rows stayed in view (ux-lead gate on #1989). Opting this scroller out holds 16 of 17. Unlike .v2-chat__messages, which keeps anchoring at rest for late media above the viewport, these rows are fixed-height text with no late layout to lose. */ overflow-anchor: none; } .v2-pods__section { min-width: 0; } .v2-pods__section-head, .v2-root button.v2-pods__section-head { display: flex; width: 100%; min-width: 0; align-items: center; gap: 6px; margin: 0; padding: 10px 12px 4px; border: 0; background: transparent; color: var(--v2-text-secondary); font: 600 12px/16px var(--v2-font); text-align: left; } .v2-root button.v2-pods__section-head { cursor: pointer; } .v2-root button.v2-pods__section-head:hover { color: var(--v2-text-primary); } .v2-pods__section-count { color: var(--v2-text-muted); font: 400 11px/16px var(--v2-font-mono); } .v2-pods__section-chevron { margin-left: auto; color: var(--v2-text-muted); font-size: 11px; } .v2-pods__section-chevron::before { content: '▸'; } [aria-expanded="true"] > .v2-pods__section-chevron::before { content: '▾'; } .v2-root .v2-pods__section--kind button.v2-pods__section-head { padding-left: 24px; text-transform: lowercase; } .v2-pods__rows { display: flex; flex-direction: column; gap: 2px; } /* The artboard's .pod: a 36px inset row with 8px corners. */ .v2-root button.v2-pods__row { display: grid; width: 100%; min-width: 0; min-height: 36px; grid-template-columns: 24px minmax(0, 1fr) auto; align-items: center; gap: 9px; padding: 0 8px; border: 0; border-radius: var(--v2-r-8, 8px); background: transparent; color: var(--v2-ink-2, var(--v2-text-primary)); font: 500 13.5px/18px var(--v2-font); text-align: left; } .v2-root button.v2-pods__row--in-fold { padding-left: 24px; } /* A row and its pin control are siblings (a button cannot nest a button). */ .v2-pods__rowwrap { position: relative; } .v2-root button.v2-pods__pin { position: absolute; top: 50%; right: 6px; display: grid; width: 28px; height: 28px; place-items: center; padding: 0; transform: translateY(-50%); border: 0; border-radius: var(--v2-r-8, 8px); background: var(--v2-surface-tint); color: var(--v2-text-secondary); visibility: hidden; } /* D215: the pin is an icon on row hover that takes the time's slot. Out of sight at rest, pinned or not; the Pinned segment lists the pinned pods. visibility (not opacity) so a hidden pin cannot catch a tap. Keyboard focus anywhere in the row shows it, so Tab reaches it; a mouse click does not set :focus-visible, so a clicked row keeps its time. Touch screens have no hover, so they show the pin at rest in place of the time. */ .v2-pods__rowwrap:hover .v2-pods__pin, .v2-pods__rowwrap:has(:focus-visible) .v2-pods__pin { visibility: visible; } .v2-root button.v2-pods__pin:focus-visible { outline: none; box-shadow: var(--v2-focus-ring); } .v2-root button.v2-pods__pin--active { color: var(--v2-accent-text); } .v2-root button.v2-pods__pin:hover { background: var(--v2-surface-hover); color: var(--v2-text-primary); } .v2-pods__row-time { display: inline-block; min-width: 24px; text-align: right; } .v2-pods__rowwrap:hover .v2-pods__row-time, .v2-pods__rowwrap:has(:focus-visible) .v2-pods__row-time { visibility: hidden; } @media (hover: none) and (pointer: coarse) { .v2-root button.v2-pods__pin { visibility: visible; } .v2-pods__row-time { visibility: hidden; } } .v2-root button.v2-pods__row:hover { background: var(--v2-surface-hover); } .v2-root button.v2-pods__row:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--v2-accent); } /* D223 (TASK-735): the artboard's .pod.on, in both themes. The row goes to the neutral tint and the mark alone turns cobalt; no full-row block, no accent bar. */ .v2-root button.v2-pods__row--selected, .v2-root button.v2-pods__row--selected:hover { background: var(--v2-row-active); color: var(--v2-row-active-text); font-weight: 600; } .v2-pods__row-mark { display: grid; width: 24px; height: 24px; place-items: center; border-radius: var(--v2-r-6, 6px); background: var(--v2-surface-tint); color: var(--v2-text-secondary); /* The artboard's .mk draws 600 10px mono; 11px is the mono floor (ruling h). */ font: 600 11px/1 var(--v2-font-mono); } /* A DM row carries the peer's avatar in the same 24px slot. */ .v2-pods__row-mark.v2-avatar { width: 24px; height: 24px; border-radius: var(--v2-r-6, 6px); border: 0; font-size: 11px; } .v2-root button.v2-pods__row--selected .v2-pods__row-mark:not(.v2-avatar) { background: var(--v2-row-active-mark); color: var(--v2-on-accent); } .v2-pods__row-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .v2-root button.v2-pods__row--unread .v2-pods__row-name { font-weight: 600; } .v2-pods__row-meta { display: flex; flex: 0 0 auto; align-items: center; gap: 6px; color: var(--v2-text-muted); font: 500 11px/16px var(--v2-font-mono); } .v2-root button.v2-pods__row--selected .v2-pods__row-meta { color: var(--v2-row-active-mute); } .v2-root button.v2-pods__row--selected:focus-visible { box-shadow: inset 0 0 0 2px var(--v2-accent); } /* The artboard's .un: an 18px ink pill, the same on the selected row. */ .v2-pods__row-pill { display: grid; min-width: 18px; height: 18px; padding: 0 5px; place-items: center; border-radius: 9px; background: var(--v2-ink); color: var(--v2-on-ink); font: 600 11px/18px var(--v2-font-mono); } /* At 760px and below the row keeps a 44px touch target. The artboard draws 36px rows at 390; its 24px mark and 13.5px name hold, the row height does not (TASK-735: a deliberate touch floor, flagged for UX Lead). */ @media (max-width: 760px) { .v2-root button.v2-pods__row { min-height: 44px; grid-template-columns: 24px minmax(0, 1fr) auto; } .v2-root button.v2-pods__pin { width: 28px; height: 28px; } /* The pin draws at 28 inside the 44px row and takes its 44px touch area from a transparent ::before, which stays inside the row (ruling 11-04): it ends at the row's right edge (the pin sits 6px in), since the list clips past it. */ .v2-root button.v2-pods__pin::before { content: ''; position: absolute; top: 50%; right: -6px; width: 44px; height: 44px; transform: translateY(-50%); background: transparent; } } .v2-pods__create { margin: 0 10px 6px; } .v2-pods__empty { padding: 20px 12px; } .v2-pods__empty--mono { color: var(--v2-text-muted); font: 400 12px/16px var(--v2-font-mono); } @media (max-width: 760px) { /* The list is a page: full width, no drawer, no backdrop. The list is also the one route where the aside IS a page pane, so the (0,3,0) bounding rule added for TASK-157 reaches it: the aside stops being `calc(100vh - 56px)` and becomes 100% of the shell's content box. Two consequences, both measured by ux-lead at 390 on both banner states (2026-09-25), and both fixed here rather than in that rule — the bounding rule is right for pod, Connectors, Agents and Settings: 1. Nothing reserved the tab bar inside the list's box, so the aside grew to the full 844 and ran under the fixed bar: 0 of 4 tabs hit-testable, on a short list and a long one, banner up or down. The bottom padding below IS that reservation, and its value is the bar's own height — see the guard, which compares the two rather than restating either. The two-class selector is required: a one-class rule of the same name would take the padding back off by document order. 2. The drawer's reset was incomplete — z-index stayed 60. `.v2-shell--list` is a flex container, and a flex item with a z-index other than `auto` forms a stacking context even while static, so the aside painted over the bar (fixed, z 40) and the bar could be neither seen nor tapped. `calc(100vh - 56px)` was dropped rather than kept as a fallback: the rule above outranks it in every render of this shell, so it was already inert. The banner state is fixed by the same padding — main failed the banner case before this change. */ .v2-shell.v2-shell--list { display: flex; flex-direction: column; gap: 0; padding: 0 0 58px; } .v2-shell--list .v2-pods-aside--page { position: static; z-index: auto; width: 100%; transform: none; box-shadow: none; border: 0; } .v2-shell--list .v2-pods { border: 0; border-radius: 0; } .v2-pods-aside:not(.v2-pods-aside--page) { display: none; } /* Tab bar with labels; the count lives inside the label, one counter. */ .v2-mobile-tabs { height: 58px; padding: 6px 8px 4px; border-top-color: var(--v2-border-soft); } /* Feature pages mount this same bar (V2FeaturePage). Reserve its height on the pane so a scrolling body stops above the fixed bar, not under it. */ .v2-feature { padding-bottom: 58px; } .v2-root button.v2-mobile-tabs__item { display: flex; width: auto; height: 44px; flex-direction: column; align-items: center; justify-content: center; gap: 3px; padding: 0 6px; background: transparent; /* Icons are controls: the rail's 3:1 grey, not the artboard's 2.58:1 placeholder (D223 standing rule, 11-01). The label sets its own colour. */ color: var(--v2-rail-icon); } .v2-root button.v2-mobile-tabs__item--active, .v2-root button.v2-mobile-tabs__item--active:hover:not(:disabled) { background: transparent; color: var(--v2-text-primary); } .v2-mobile-tabs__icon { display: grid; width: 24px; height: 20px; place-items: center; } .v2-root button.v2-mobile-tabs__item--active .v2-mobile-tabs__icon { color: var(--v2-ink); } /* D223 ruling (Cofounder, 11-01): the artboard's sans family, at the 12px sans floor rather than its 10px. */ .v2-mobile-tabs__label { font: 500 12px/14px var(--v2-font); white-space: nowrap; } /* The artboard greys the whole tab at rest; only the glyph takes that placeholder grey here, so the label text keeps 4.5:1. */ .v2-root button.v2-mobile-tabs__item:not(.v2-mobile-tabs__item--active) .v2-mobile-tabs__label { color: var(--v2-text-muted); } /* D215: the needs-you count is a cobalt badge on the icon, not a sentence in the label. */ .v2-mobile-tabs__icon { position: relative; } .v2-mobile-tabs__count { position: absolute; top: -6px; left: calc(50% + 4px); min-width: 16px; height: 15px; padding: 0 3px; border-radius: 8px; background: var(--v2-accent); color: var(--v2-on-accent); font: 600 11px/15px var(--v2-font-mono); text-align: center; } } /* ---------- Workspace at scale · direction C · PR 2a (composer, attachments, history) ---------- One-line composer with a plus menu, Send only with text, no identity line; images as 156×104 thumbnails with a lightbox, files as chips with a text type badge, code past six lines collapsed; older messages load on scroll with a mono edge line and a Jump-to-latest pill. */ /* Lightbox */ .v2-lightbox { position: fixed; inset: 0; z-index: 90; display: grid; place-items: center; background: rgba(16, 24, 40, 0.9); cursor: zoom-out; } .v2-lightbox__image { max-width: min(92vw, 1400px); max-height: 88vh; object-fit: contain; border-radius: 4px; cursor: default; } .v2-lightbox__name { position: absolute; top: 14px; left: 16px; padding: 2px 6px; border-radius: 3px; background: var(--v2-surface); color: var(--v2-text-secondary); font: 500 11px/16px var(--v2-font-mono); } .v2-root button.v2-lightbox__close, .v2-root button.v2-lightbox__nav { position: absolute; display: inline-grid; width: 28px; height: 28px; place-items: center; padding: 0; border: 1px solid var(--v2-border); border-radius: 4px; background: var(--v2-surface); color: var(--v2-text-primary); font: 500 16px/1 var(--v2-font); cursor: pointer; } .v2-root button.v2-lightbox__close { top: 12px; right: 12px; } .v2-root button.v2-lightbox__nav--prev { left: 12px; top: 50%; transform: translateY(-50%); } .v2-root button.v2-lightbox__nav--next { right: 12px; top: 50%; transform: translateY(-50%); } /* ---------- Workspace at scale · direction C · PR 2b (threading restyle, walk-3 misses 51–63) ---------- Runtime tag on agent rows; 20px reaction chips; a two-line quote that jumps to its source; the thread summary is one chip; the expanded thread is one var(--v2-bg-subtle) band with a 2px rule and a Collapse foot; the action strip is a 28px white box top-right on hover (inline under the body on long-press at 390); the Jump pill is white with a cobalt count; transcript avatars are two-tone. */ /* Two-tone avatars inside the transcript: agents cobalt, humans tint. */ /* Mention token: wash + cobalt (ux-lead 64476 (7)); the row tint stays. */ /* Jump pill: white, #dde0e6 border, r14, ink 13/600, cobalt count (walk-3 miss 58). */ /* Rail badge (66390): the exact count at any size — 118 reads as 118. Mono, bordered, cobalt, no clip. */ .v2-rail__item-icon .MuiBadge-badge { top: -7px; right: -14px; height: 15px; min-width: 16px; padding: 0 3px; border: 0; border-radius: 8px; background: var(--v2-accent); box-shadow: 0 0 0 2px var(--v2-shell-bg); color: var(--v2-on-accent); font: 600 11px/15px var(--v2-font-mono); transform: none; white-space: nowrap; } /* TASK-363: only the Activity badge is offset inward. Three-digit counts (230, 1234) overflowed the rail at -14px; the other rail badges keep the base offset. */ .v2-rail__item-icon .v2-rail__badge--activity .MuiBadge-badge { right: -6px; } /* Public Privacy and Terms pages (TASK-530). Outside .v2-root, so no root-scoped selectors. */ /* .v2-root sets height:100vh and overflow:hidden; the legal pages must scroll, so override both (same as the landing). */ .v2-root.v2-legal { display: block; height: 100vh; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; padding: 48px 24px; background: var(--v2-bg-subtle); color: var(--v2-text-primary); } .v2-legal__doc { max-width: 680px; margin: 0 auto; } .v2-legal__home { display: inline-block; margin: 0 0 24px; font-size: 14px; color: var(--v2-text-secondary); text-decoration: none; } .v2-legal__home:hover { text-decoration: underline; } .v2-legal__section a { color: var(--v2-text-primary); text-decoration: underline; } .v2-legal__title { font-size: 32px; font-weight: 600; line-height: 1.2; margin: 0 0 32px; } .v2-legal__section { margin-bottom: 28px; } .v2-legal__section h2 { font-size: 18px; font-weight: 600; margin: 0 0 8px; } .v2-legal__section p { font-size: 15px; line-height: 1.6; color: var(--v2-text-secondary); margin: 0 0 8px; } /* D223 (TASK-730 shell): the artboard's 390 frame is a 52px top bar over one column. Feature pages (connectors, team, settings) share V2PageHead and the V2FeaturePage body, so the bar and the tighter body live here once instead of in each page. The tab bar's height is already reserved on .v2-feature. */ @media (max-width: 760px) { .v2-feature__header { display: flex; align-items: center; min-height: 52px; padding: 0 10px 0 12px; gap: 8px; } .v2-feature__header .v2-page-head__identity { align-items: center; gap: 8px; } .v2-feature__header .v2-feature__title { font: 800 21px/1 var(--v2-font-display); letter-spacing: -0.03em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .v2-feature__header .v2-page-head__meta { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .v2-feature__body { padding: 12px 12px 16px; } .v2-feature__scroll { padding: 12px 12px 16px; } /* A head with actions gives them their own full-width row under the title (Your team, the profiles): 44px keys never squeeze the title at 390. */ .v2-feature__header.v2-page-head--wrap { flex-wrap: wrap; row-gap: 0; padding-bottom: 12px; } .v2-feature__header.v2-page-head--wrap > .v2-page-head__identity { flex: 1 1 auto; min-height: 52px; } .v2-feature__header.v2-page-head--wrap > .v2-page-head__actions { flex: 1 0 100%; display: grid; grid-auto-columns: minmax(0, 1fr); grid-auto-flow: column; gap: 8px; } } /* ---------- Motion (D220) ---------- */ /* Theme switch: utils/theme.ts makes the change inside document.startViewTransition, and the root snapshots crossfade once. No per-element colour transitions. */ ::view-transition-old(root), ::view-transition-new(root) { animation-duration: var(--v2-dur); animation-timing-function: var(--v2-ease); } /* Folds: every
body opens over --v2-dur and closes over --v2-dur-fast, by size, with no measured pixel height. Browsers without interpolate-size or ::details-content open and close at once. The clip margin keeps focus rings. */ .v2-root details { interpolate-size: allow-keywords; } .v2-root details::details-content { block-size: 0; overflow: clip; overflow-clip-margin: 6px; opacity: 0; transition: block-size var(--v2-dur-fast) var(--v2-ease), opacity var(--v2-dur-fast) var(--v2-ease), content-visibility var(--v2-dur-fast) var(--v2-ease) allow-discrete; } .v2-root details[open]::details-content { block-size: auto; opacity: 1; transition-duration: var(--v2-dur); } /* Custom folds (V2Fold): the body is a grid whose one row goes 0fr <-> 1fr, fading on the same curve. It clips only while it moves, so an open fold keeps focus rings and anything that hangs past its edge. */ @keyframes v2-fold-open { from { grid-template-rows: 0fr; opacity: 0; } to { grid-template-rows: 1fr; opacity: 1; } } @keyframes v2-fold-close { from { grid-template-rows: 1fr; opacity: 1; } to { grid-template-rows: 0fr; opacity: 0; } } .v2-fold { display: grid; grid-template-rows: 1fr; } .v2-fold > .v2-fold__inner { display: flow-root; min-height: 0; } .v2-fold--opening { animation: v2-fold-open var(--v2-dur) var(--v2-ease) both; } .v2-fold--closing { animation: v2-fold-close var(--v2-dur-fast) var(--v2-ease) both; } .v2-fold--opening > .v2-fold__inner, .v2-fold--closing > .v2-fold__inner { overflow: clip; overflow-clip-margin: 6px; } /* One reduced-motion switch, global rather than scoped to .v2-root, so it reaches portals too. Every moment lands in its end state at once. */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } ::view-transition-old(*), ::view-transition-new(*), ::view-transition-group(*) { animation: none !important; } /* A rule of its own: the wildcards above don't reach this pseudo-element, and a browser without it would drop any selector list that named it. */ ::details-content { transition-duration: 0.01ms !important; } }