/* * 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 color: body sits outside .v2-root, so tokens don't reach it — keep in lockstep with --v2-page-bg below. */ body.v2-canvas, body.modern-ui.v2-canvas { background: #f8f8fb; } .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-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; /* 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; /* BEND-1 (Wren spec 2026-08-30, Sam-approved): 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 (Wren on #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); /* 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 (Sam chose 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; /* 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; } .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 { font-size: 11px; } /* ---------- Shared filter grammar ---------- */ .v2-filter-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 10px; margin-bottom: 16px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-lg); background: var(--v2-surface); } .v2-filter-bar--flat { padding: 0; border: none; background: transparent; } .v2-filter-bar__search { flex: 1 1 280px; min-width: 220px; } .v2-filter-bar__control { flex: 0 1 180px; min-width: 160px; } .v2-filter-bar__action { flex: 0 0 auto; } .v2-filter-bar__summary { margin-left: auto; color: var(--v2-text-tertiary); font-size: 12px; font-weight: 500; white-space: nowrap; } .v2-filter-segment { display: flex; flex-wrap: wrap; gap: 6px; } .v2-filter-segment__item { min-height: 30px; padding: 0 10px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-pill); background: var(--v2-surface); color: var(--v2-text-secondary); font-size: 13px; font-weight: 600; transition: background 80ms ease, border-color 80ms ease, color 80ms ease; } .v2-filter-segment__item:hover { border-color: var(--v2-border-strong); background: var(--v2-surface-hover); color: var(--v2-text-primary); } .v2-root button.v2-filter-segment__item--active { border-color: var(--v2-accent); background: var(--v2-accent); color: var(--v2-surface); font-weight: 700; } .v2-filter-count { margin-left: 5px; color: inherit; font-size: 11px; font-weight: 700; opacity: 0.68; } .v2-active-filter-strip { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: -4px 0 16px; color: var(--v2-text-tertiary); font-size: 12px; } .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: #ffffff; 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-pane--inspector { border-right: none; border-left: 1px solid var(--v2-border-soft); background: var(--v2-shell-bg); } .v2-inspector-backdrop { display: none; } /* Mobile pods-drawer affordances — hidden on desktop, where the pods sidebar is a permanent grid column. The <=760px media query (bottom of file) turns these on and converts .v2-pods-aside into a slide-over overlay. Declared here so resizing a phone-open drawer up to desktop can't leak the overlay or backdrop into the normal multi-column layout. */ .v2-mobile-backdrop { display: none; } .v2-pods__mobile-close { display: none; } /* ---------- Nav rail ---------- */ .v2-rail { height: 100%; padding: 6px 10px; 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; } .v2-rail__foot-sep { width: 24px; height: 1px; background: var(--v2-border); } .v2-root .v2-rail__utility button.v2-lang-switch__trigger { min-height: 24px; padding: 2px 6px; gap: 3px; font: 700 11px/16px var(--v2-font-mono); color: var(--v2-text-tertiary); border-radius: var(--v2-radius-sm); } .v2-rail__utility .v2-lang-switch__trigger:hover { background: var(--v2-surface-hover); color: var(--v2-text-primary); } /* At the foot of the rail the menu must open upward + toward content, not downward off the bottom of the viewport. */ /* Opens upward from the foot and floats rightward over the pods sidebar as a clean popover (the pane is overflow:visible so it isn't clipped). */ .v2-rail__utility .v2-lang-switch__menu { top: auto; bottom: calc(100% + 8px); right: auto; left: 0; z-index: 200; min-width: 152px; } .v2-rail__brand { display: flex; align-items: center; justify-content: center; gap: 0; font-weight: 700; font-size: 17px; padding: 0; margin-bottom: 16px; color: var(--v2-text-primary); letter-spacing: -0.02em; height: 32px; } .v2-rail__brand > span:not(.v2-rail__brand-icon) { display: none; } .v2-rail__brand-icon { width: 32px; height: 32px; border-radius: var(--v2-radius-sm); background: var(--v2-accent); color: #fff; display: grid; place-items: center; font-weight: 800; font-size: 14px; } .v2-rail__nav { display: flex; flex: 1; flex-direction: column; gap: 4px; min-height: 0; overflow: visible; padding-right: 0; } /* Below the rail's fixed chrome (245px) plus six nav items (212px), 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; width: 32px; min-height: 32px; height: 32px; align-self: center; padding: 0; border-radius: var(--v2-radius); font-size: 14px; font-weight: 600; color: #374151; background: var(--v2-surface-hover); 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); color: 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__signout, .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__signout:hover, .v2-rail__account:hover, .v2-rail__account[aria-expanded="true"] { background: var(--v2-surface-hover); color: var(--v2-text-primary); } .v2-rail__feedback svg, .v2-rail__signout svg { width: 18px; height: 18px; } .v2-rail__account { width: 32px; height: 32px; padding: 0; } .v2-rail__account .v2-avatar { width: 32px; height: 32px; border: 0; border-radius: var(--v2-radius-sm); box-shadow: none; 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); } .v2-rail__user-name { font-weight: 700; font-size: 13px; } .v2-rail__user-status { font-size: 12px; color: var(--v2-success); display: flex; align-items: center; gap: 4px; } .v2-online-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--v2-success); display: inline-block; } /* ---------- Pods sidebar ---------- */ .v2-pods { display: flex; flex-direction: column; height: 100%; padding: 24px 16px; } .v2-pods__header { padding: 0; display: flex; flex-direction: column; gap: 0; } .v2-pods__title { height: 32px; font-size: 16px; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 14px; } .v2-root button.v2-pods__new-btn { display: flex; align-items: center; justify-content: center; height: 40px; gap: 8px; background: var(--v2-accent); color: #fff; font-weight: 600; font-size: 14px; padding: 0 14px; border-radius: var(--v2-radius-sm); transition: background 80ms ease; margin-bottom: 16px; } .v2-root button.v2-pods__new-btn:hover { background: var(--v2-accent-strong); } .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, .v2-pod-join-settings { 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, .v2-pod-join-settings legend { margin-bottom: 6px; color: var(--v2-text-primary); font-weight: 600; } .v2-pods__join-policy label, .v2-pod-join-settings label { display: flex; align-items: center; gap: 8px; min-height: 40px; cursor: pointer; } .v2-pods__join-policy input, .v2-pod-join-settings input { accent-color: var(--v2-accent); } .v2-pods__join-policy-hint { margin: 0; color: var(--v2-text-tertiary); font: 400 var(--v2-fs-meta)/16px var(--v2-font); } .v2-pod-join-settings__error { margin: 8px 0 0; color: var(--v2-danger); font: 400 12px/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: #fff; } .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; } } .v2-pods__filters { /* Fourth mechanism for this row (Sam's ruling 2026-08-23): every chip the SAME size, at least as roomy as the widest label. Content-sized chips read ragged (EN measured 33/49/44/84px), and four equal cells cannot share one 239px rail — "Community" alone needs 84px. Equal therefore means a 2×2 grid, the exact mechanism .v2-pods__community-tabs below already uses, so the two stacked sections align by construction. This retires the 2026-08-13 one-row intent deliberately. */ display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px; padding: 0; margin-bottom: 18px; flex-shrink: 0; overflow: visible; } /* Keep the explicit button prefix on the chip state rules so their visual treatment remains intentional above the low-specificity default reset. This used to be required to beat the reset; the reset is now scoped through `:where()` so bare component rules can own their declarations. */ .v2-root button.v2-pods__filter { height: 32px; min-width: 0; padding: 0 10px; font-size: 12px; font-weight: 500; color: #4b5563; border-radius: var(--v2-radius-sm); text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: background 80ms ease, color 80ms ease; } .v2-pods__filter:hover { color: var(--v2-text-primary); background: var(--v2-surface-hover); } /* 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-root button.v2-pods__filter--active { background: var(--v2-accent); color: var(--v2-surface); font-weight: 700; } .v2-pods__community-tabs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px; margin: -10px 0 14px; flex-shrink: 0; overflow: hidden; } .v2-pods__community-tab { min-width: 0; padding-inline: 8px; white-space: nowrap; } .v2-pods__list { flex: 1; overflow-y: auto; padding: 0; } .v2-pods__group-label { padding: 0; margin: 18px 0 8px; font-size: 12px; font-weight: 600; color: var(--v2-text-muted); } .v2-pods__item { display: grid; grid-template-columns: 34px minmax(0, 1fr); align-items: start; gap: 10px; height: 64px; padding: 10px; overflow: hidden; border-radius: var(--v2-radius); cursor: pointer; width: 100%; text-align: left; transition: background 80ms ease; border: 1px solid transparent; } .v2-pods__item:hover { background: var(--v2-surface-hover); } /* The explicit root/button selector keeps the selected row treatment above the low-specificity default reset. Source order then resolves the local active-row variants. */ .v2-root button.v2-pods__item--active { background: var(--v2-accent-soft); border-color: transparent; position: relative; } .v2-root button.v2-pods__item--active::before { content: ''; position: absolute; left: -8px; top: 12px; bottom: 12px; width: 3px; border-radius: 0 2px 2px 0; background: var(--v2-accent); } .v2-pods__item-icon { width: 34px; height: 34px; border-radius: var(--v2-radius); display: grid; place-items: center; flex-shrink: 0; font-size: 11px; letter-spacing: 0.04em; color: var(--v2-text-secondary); font-weight: 700; background: var(--v2-surface-hover); text-transform: uppercase; border: 1px solid var(--v2-border-soft); } .v2-pods__item-icon--team { background: var(--v2-surface-hover); border-color: var(--v2-border-soft); color: var(--v2-text-secondary); } .v2-pods__item-icon--private { background: #f3f4f6; color: var(--v2-text-secondary); } .v2-pods__item--active .v2-pods__item-icon--team, .v2-pods__item--active .v2-pods__item-icon--private { background: var(--v2-accent); border-color: transparent; color: #fff; } .v2-pods__item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; } .v2-pods__item-title-row { display: flex; align-items: flex-start; gap: 6px; min-width: 0; min-height: 20px; } /* Craft baseline rule 1: the pod name is the row's primary identifier and never loses to its own timestamp — wrap to two lines instead of ellipsizing (same idiom as the team-card name fix, #1104). The clamp keeps pathological names from unbounding the row; the timestamp lives on the snippet row so it never shares a line with the name. */ .v2-pods__item-title { flex: 1; min-width: 0; font-size: 14px; line-height: 20px; font-weight: 600; color: var(--v2-text-primary); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; overflow-wrap: anywhere; } .v2-pods__item-snippet-row { display: flex; align-items: baseline; gap: 6px; min-width: 0; } .v2-pods__item-snippet-row .v2-pods__item-snippet { flex: 1; min-width: 0; } .v2-pods__status { height: 18px; padding: 0 6px; border-radius: 999px; display: inline-flex; align-items: center; flex-shrink: 0; font-size: 10px; font-weight: 700; color: var(--v2-text-tertiary); background: var(--v2-surface); } .v2-pods__status--agents { color: var(--v2-text-secondary); background: var(--v2-surface-hover); } .v2-pods__status--empty { color: var(--v2-text-tertiary); background: var(--v2-surface); } .v2-pods__status--direct { color: var(--v2-text-secondary); background: #f3f4f6; } .v2-pods__item--active .v2-pods__item-title { color: var(--v2-accent-text); } /* Unread state: title becomes bolder + body picks up an accent dot. Active pods are never marked unread, so the active treatment above wins. */ .v2-pods__item--unread .v2-pods__item-title { font-weight: 700; color: var(--v2-text-primary); } .v2-pods__item--unread .v2-pods__item-snippet { color: var(--v2-text-secondary); } .v2-pods__item-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--v2-accent); flex-shrink: 0; /* The title-row went align-items: flex-start for the 2-line name wrap (#1110), which parked this dot flush at the row's top-right corner — inside the row's border-radius + overflow:hidden clip, so the rounding visibly shaved the dot (Sam, 2026-08-22). Center it on the first 20px text line and inset it clear of the 10px corner curve. */ margin-top: 6px; margin-right: 4px; } .v2-pods__item-snippet--typing { color: var(--v2-accent-text); font-style: italic; } .v2-pods__item-meta { font-size: 12px; color: var(--v2-text-tertiary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .v2-pods__item-snippet { font-size: 12px; line-height: 16px; color: var(--v2-text-tertiary); display: block; height: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* 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__item-time { flex-shrink: 0; /* Relative-time formatting follows the viewer's browser locale. Do not cap it to an English measurement: title ellipsis is safer than clipping a clock, and visibility:hidden still reserves this row's slot on hover. */ font-size: 11px; color: var(--v2-text-tertiary); font-variant-numeric: tabular-nums; align-self: baseline; text-align: right; white-space: nowrap; } .v2-pods__empty { padding: 32px 16px; text-align: center; color: var(--v2-text-tertiary); font-size: 13px; } .v2-pods__join-notice { margin-bottom: 8px; padding: 9px 10px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-surface-hover); color: var(--v2-text-secondary); font-size: 12px; line-height: 1.4; } .v2-pods__discover-row { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 10px; margin-bottom: 6px; border: 1px solid var(--v2-border-soft); border-radius: var(--v2-radius); background: var(--v2-surface); } .v2-pods__discover-body { min-width: 0; display: flex; flex-direction: column; gap: 3px; } .v2-pods__discover-title { overflow: hidden; color: var(--v2-text-primary); font-size: 13px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; } .v2-pods__discover-description { overflow: hidden; color: var(--v2-text-tertiary); display: -webkit-box; font-size: 12px; line-height: 1.35; -webkit-box-orient: vertical; -webkit-line-clamp: 2; } .v2-pods__discover-meta { color: var(--v2-text-muted); font-size: 11px; } .v2-root button.v2-pods__discover-join { min-width: 44px; min-height: 30px; padding-inline: 9px; border-radius: var(--v2-radius-sm); background: var(--v2-accent-soft); color: var(--v2-accent-text); font-size: 12px; font-weight: 700; } .v2-root button.v2-pods__discover-join:hover { background: var(--v2-accent); color: var(--v2-surface); } .v2-root button.v2-pods__discover-join:disabled { cursor: wait; opacity: 0.65; } .v2-pods__community { flex-shrink: 0; margin-top: 12px; padding: 12px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius); background: var(--v2-surface); } .v2-pods__community-title { color: var(--v2-text-primary); font-size: 13px; font-weight: 700; } .v2-pods__community-copy { margin-top: 3px; color: var(--v2-text-tertiary); font-size: 12px; line-height: 1.4; } .v2-root button.v2-pods__community-button { width: 100%; min-height: 34px; margin-top: 10px; border-radius: var(--v2-radius-sm); background: var(--v2-accent-soft); color: var(--v2-accent-text); font-size: 12px; font-weight: 700; transition: background 80ms ease, color 80ms ease; } .v2-root button.v2-pods__community-button:hover { background: var(--v2-accent); color: var(--v2-surface); } /* ---------- Pod chat (main) ---------- */ .v2-chat { display: flex; flex-direction: column; height: 100%; min-width: 0; } .v2-chat__header { min-height: 78px; padding: 14px 24px; border-bottom: 1px solid var(--v2-border-soft); background: var(--v2-surface); display: flex; flex-direction: column; justify-content: center; gap: 7px; } .v2-chat__header-row { display: flex; align-items: center; gap: 10px; } .v2-chat__title { font-size: 20px; font-weight: 700; display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; letter-spacing: -0.025em; } .v2-chat__title-mark { width: 28px; height: 28px; border-radius: var(--v2-radius-sm); display: grid; place-items: center; background: var(--v2-surface-hover); color: var(--v2-text-secondary); font-size: 11px; font-weight: 700; letter-spacing: 0.04em; flex-shrink: 0; } .v2-chat__title-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .v2-chat__avatars { display: flex; align-items: center; } .v2-chat__avatars > * + * { margin-left: -8px; } /* When the avatar group is the inspector entry, treat it like a button: subtle hover shadow that hints "this is clickable" without an explicit chevron. Active state means the inspector is open. */ .v2-chat__avatars--button { background: transparent; border: 1px solid transparent; border-radius: var(--v2-radius-pill); padding: 3px 6px; cursor: pointer; transition: background 80ms ease, border-color 80ms ease; } .v2-chat__avatars--button:hover { background: var(--v2-surface-hover); } .v2-chat__avatars--active { background: var(--v2-surface-hover); border-color: var(--v2-surface-hover); } .v2-chat__avatars--active:hover { background: var(--v2-surface-hover); } .v2-chat__avatars-more { background: var(--v2-bg-subtle); color: var(--v2-text-secondary); font-size: 11px; font-weight: 600; width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; border: 2px solid var(--v2-surface); } .v2-chat__btn { display: flex; align-items: center; height: 34px; gap: 6px; padding: 0 14px; font-size: 13px; font-weight: 600; color: #374151; border: 1px solid #e5e7eb; border-radius: var(--v2-radius-sm); background: var(--v2-surface); transition: background 80ms ease, color 80ms ease; } .v2-chat__btn:hover { background: var(--v2-surface-hover); color: var(--v2-text-primary); } .v2-chat__icon-btn { width: 34px; height: 34px; display: grid; place-items: center; color: #6b7280; border: 1px solid #e5e7eb; border-radius: var(--v2-radius-sm); background: var(--v2-surface); transition: background 80ms ease, color 80ms ease; } .v2-chat__icon-btn:hover { background: var(--v2-surface-hover); color: var(--v2-text-primary); } .v2-chat__mode-toggle { display: inline-flex; align-items: center; gap: 2px; height: 34px; padding: 3px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius); background: var(--v2-bg-subtle); } /* Keep the explicit root/button selector on the mode control so the active state owns its background and color rather than relying on reset order. */ .v2-root button.v2-chat__mode-option { height: 26px; display: inline-flex; align-items: center; gap: 5px; padding: 0 9px; border-radius: var(--v2-radius-sm); background: transparent; color: var(--v2-text-secondary); font-size: 12px; font-weight: 600; } .v2-root button.v2-chat__mode-option:hover { color: var(--v2-text-primary); background: var(--v2-surface); } .v2-root button.v2-chat__mode-option--active { background: var(--v2-ink); color: var(--v2-on-ink); } .v2-root button.v2-chat__mode-option--active:hover { background: var(--v2-ink-hover); color: var(--v2-on-ink); } .v2-chat__goal { font-size: 13px; color: #4b5563; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 720px; } .v2-chat__state-strip { min-height: 42px; margin: 12px 24px 0; padding: 8px 12px; border: 1px solid var(--v2-border-soft); border-radius: var(--v2-radius-lg); background: var(--v2-bg-subtle); display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; align-items: center; gap: 12px; } .v2-chat__state-strip--execute { border-color: var(--v2-border-soft); background: var(--v2-bg-subtle); } .v2-chat__state-primary { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 700; } .v2-chat__state-copy { min-width: 0; color: var(--v2-text-secondary); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .v2-chat__state-meta { color: var(--v2-text-tertiary); font-size: 12px; font-weight: 600; } /* "Load older messages" control — the pod chat equivalent of the showcase pager. Same pill treatment so the two reading surfaces match. */ .v2-chat__older { display: flex; justify-content: center; padding: 4px 0 12px; } .v2-chat__older-btn { padding: 7px 16px; font: inherit; font-size: 13px; font-weight: 600; color: var(--v2-text-secondary); background: var(--v2-surface); border: 1px solid var(--v2-border); border-radius: 999px; cursor: pointer; } .v2-chat__older-btn:hover:not(:disabled) { color: var(--v2-text-primary); border-color: var(--v2-text-tertiary); } .v2-chat__older-btn:disabled { opacity: 0.6; cursor: default; } /* Catch-up strip — the constant agents-TL;DR pinned above the transcript (Sam 2026-09-01). One thin row; the expanded body is bounded so a long summary scrolls inside the strip, never the pane. */ .v2-catchup { border-bottom: 1px solid var(--v2-border); background: var(--v2-surface); padding: 6px 20px; font-size: 13px; } .v2-catchup__row { display: flex; align-items: center; gap: 10px; min-width: 0; } .v2-root button.v2-catchup__toggle { background: transparent; border: none; padding: 0; font-size: 13px; font-weight: 650; color: var(--v2-text-secondary); cursor: pointer; white-space: nowrap; } .v2-root button.v2-catchup__toggle:hover:not(:disabled) { color: var(--v2-text-primary); } .v2-root button.v2-catchup__toggle:disabled { cursor: default; } .v2-catchup__chevron { display: inline-block; margin-right: 4px; transition: transform 0.08s ease; } .v2-catchup__chevron--open { transform: rotate(90deg); } .v2-catchup__time { font-weight: 400; color: var(--v2-text-tertiary); } .v2-catchup__snippet { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--v2-text-tertiary); } .v2-root button.v2-catchup__refresh { background: transparent; border: none; padding: 0; font-size: 12px; color: var(--v2-accent); cursor: pointer; white-space: nowrap; } .v2-root button.v2-catchup__refresh:hover:not(:disabled) { text-decoration: underline; } .v2-root button.v2-catchup__refresh:disabled { color: var(--v2-text-tertiary); cursor: default; } .v2-root button.v2-catchup__dismiss { background: transparent; border: none; padding: 0 2px; font-size: 14px; line-height: 1; color: var(--v2-text-tertiary); cursor: pointer; } .v2-root button.v2-catchup__dismiss:hover { color: var(--v2-text); } .v2-catchup__body { margin-top: 6px; max-height: 220px; overflow-y: auto; white-space: pre-wrap; color: var(--v2-text-secondary); line-height: 1.5; } .v2-chat__messages { flex: 1; overflow-y: auto; /* The transcript never scrolls sideways — wide content (code, tables) manages its own overflow-x inside its message. Without this, any mis-sized row makes the whole pane horizontally swipeable (the thread-block did exactly that; Sam's mobile report 2026-08-24). */ overflow-x: hidden; padding: 0 24px 12px; display: flex; flex-direction: column; gap: 0; } /* Only the managed history operation disables native anchoring. Leaving the default on at rest keeps late image loads above a reader's viewport stable; V2Thread measures and compensates the history row itself. */ .v2-chat__messages[data-history-anchor="active"] { overflow-anchor: none; } /* The transcript is the chat viewport's positioning context. History recovery belongs to this viewport rather than the top of the scrollable message list, otherwise a failure after a deep landing is stranded above the reader's current scroll position. */ .v2-thread__transcript { position: relative; display: flex; flex: 1; min-height: 0; } .v2-thread__transcript > .v2-chat__messages { min-height: 0; } /* Conversation width — FIFTH revision of craft rule 2, ruled by Sam 2026-08-23: FULL-WIDTH, the Slack/Discord model. The measure cap died with its last corner: v1 centered messages alone (island), v2/v4 left-anchored the cap (dead space on the right once the inspector closes), v3 centered the whole column (rejected outright). With a line cap, leftover space must pool somewhere — so the cap goes. Rows and text span the pane and wrap at its edge; lines run long on wide windows, the familiar chat tradeoff. What survives every revision: one shared left edge for every conversation row, and margin-inline stays an EXPLICIT 0 so a stray auto cannot re-center a subset. */ .v2-chat__messages > * { width: 100%; margin-inline: 0; } .v2-chat__composer > * { margin-inline: 0; } /* Sender-only teaching cue shown once per pod/session when a message reaches a pod with agents but addresses none of them. Aligns with message text, stays quiet/secondary, and disappears naturally with transcript scroll. Box geometry is NOT here: it belongs to the side-row slot rule below, which this row shares with the no-agent row. */ .v2-chat__delivery-hint { color: var(--v2-text-tertiary); font-size: 12px; line-height: 1.4; } .v2-chat__delivery-hint strong { color: var(--v2-ink); font-weight: 600; } /* The transcript's two side-rows — the sender-only delivery cue above and the no-agent row below — occupy ONE slot, and that slot IS the message text column of the transcript they sit in. Both are siblings of `.v2-msg` inside `.v2-chat__messages`, so the indent is the avatar track plus the gap of the rule that governs those rows HERE, which is the thread override `.v2-thread .v2-msg { grid-template-columns: 28px minmax(0, 1fr); gap: 8px }` — 28 + 8 = 36, NOT the base `.v2-msg` grid (38 + 12 = 50), which that override replaces (0,3,0 beats 0,1,0). The first cut of this rule derived from the base grid and was 14px out; ux-lead's #1860 gate measured it at 390, 720 and 1440 — 50px put both rows 14px right of `.v2-msg__content`, 36px put them on it (injection on their fixture, right edge and scrollWidth unchanged). Both placements of these rows share their message's container (transcript level, or inside a `.v2-thread-block` for a thread root), so the same relative indent holds in both. The width subtracts the same indent: `.v2-chat__messages > *` sets width: 100%, so an indent without its complement overruns the pane by exactly the indent. That was the #1846 defect — at 390 the full-width act ran 64→426 and the body line was cut (ux-lead's shot, 2026-09-23) — and Sam hit the same class one indent over on `.v2-thread-block` (2026-08-24): indent + width must sum to 100%. One rule for both rows, so they cannot drift apart again: this is the only place either row's box is declared, and v2-layout-invariants.test.ts derives the expected indent from `.v2-thread .v2-msg`'s own grid, so moving that column fails the suite instead of silently misaligning these rows. */ .v2-chat__delivery-hint, .v2-chat__no-agents, .v2-chat__starts { width: calc(100% - 36px); margin: -4px 0 8px 36px; } /* The delivery hint above requires an agent to name (`agentsInPod > 0`), which leaves out the pod where nothing can answer at all — a team pod with no agent installed. This row sits in the same slot under the sent message, says why the send went quiet, and carries the one step that changes it. Ink and tint only: nothing here succeeded, and no accent or toast should say otherwise. */ .v2-chat__no-agents { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; /* Box geometry lives in the shared side-row slot rule above, next to the delivery cue it shares the slot with. */ } .v2-chat__no-agents-kicker { color: var(--v2-text-tertiary); font-family: var(--v2-font-mono); font-size: 11px; font-weight: 500; line-height: 16px; text-transform: lowercase; } .v2-chat__no-agents-body { color: var(--v2-text-secondary); font-size: 12px; line-height: 1.4; } .v2-root button.v2-chat__no-agents-action { min-height: 32px; padding: 0 12px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius); background: var(--v2-surface); color: var(--v2-text-primary); font-size: 12px; font-weight: 600; transition: background 80ms ease, border-color 80ms ease; } .v2-root button.v2-chat__no-agents-action:hover { background: var(--v2-surface-hover); } /* Scout's starts (first session, direction B, ruled 2026-10-15): the welcome's offers, one bordered button per start, in one column at one width so they read as a set. A click sends the start as the person's own message. Box geometry lives in the shared side-row slot rule above. */ .v2-chat__starts { display: grid; grid-template-columns: minmax(0, max-content); gap: 6px; } .v2-root button.v2-chat__start { min-height: 32px; padding: 6px 12px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius); background: var(--v2-surface); color: var(--v2-text-primary); font-size: 13px; font-weight: 500; line-height: 1.35; text-align: left; white-space: normal; transition: background 80ms ease, border-color 80ms ease; } .v2-root button.v2-chat__start:hover:not(:disabled) { background: var(--v2-surface-hover); border-color: var(--v2-border-strong); } .v2-root button.v2-chat__start:disabled { opacity: 0.6; cursor: default; } /* A line that belongs to the start above it: the hosted start's beta limits, or why it could not start. Same slot and size as the delivery hint. */ .v2-chat__start-note { margin: -2px 0 2px; color: var(--v2-text-tertiary); font-size: 12px; line-height: 1.4; } .v2-chat__start-note--error { color: var(--v2-danger); } @media (max-width: 760px) { /* The row's single act is a touch target on a phone: full width of the row, 44 tall. This must override the MINIMUM, not a height — a `height` here would lose to the base rule's own min-height and render 32 (TASK-140). */ .v2-root button.v2-chat__no-agents-action { width: 100%; min-height: 44px; } /* Scout's starts are touch targets too: 44 tall on a phone, as drawn. */ .v2-root button.v2-chat__start { min-height: 44px; } } /* 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: rgba(15, 23, 42, 0.42); } .v2-first-run { width: min(720px, 100%); max-height: calc(100dvh - 48px); overflow-y: auto; padding: 28px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-lg); background: var(--v2-surface); color: var(--v2-text-primary); box-shadow: var(--v2-shadow-md, 0 8px 24px rgba(15, 23, 42, 0.12)); outline: none; } .v2-first-run__eyebrow { margin-bottom: 8px; color: var(--v2-text-tertiary); font-family: var(--v2-font-mono); font-size: 11px; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; } .v2-first-run__heading-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; } .v2-first-run__title { margin: 0; font-size: clamp(22px, 3vw, 30px); line-height: 1.15; letter-spacing: -0.025em; } .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-shrink: 0; padding: 4px 0; color: var(--v2-text-tertiary); font-size: 12px; font-weight: 600; } .v2-root button.v2-first-run__skip:hover { color: var(--v2-text-primary); } .v2-first-run__steps { display: grid; gap: 14px; margin: 24px 0 0; padding: 0; list-style: none; } .v2-first-run__step { display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 12px; align-items: start; padding: 14px; border-radius: var(--v2-radius); background: var(--v2-bg-subtle); } .v2-first-run__step-mark { display: grid; place-items: center; width: 28px; height: 28px; border: 1px solid var(--v2-border); border-radius: 50%; background: var(--v2-surface); color: var(--v2-text-secondary); font-size: 12px; font-weight: 700; } .v2-first-run__step-mark--done { border-color: var(--v2-border); background: var(--v2-surface); color: var(--v2-accent); } .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; } .v2-root a.v2-first-run__setup, .v2-root button.v2-first-run__hello { display: inline-flex; align-items: center; gap: 6px; min-height: 34px; margin-top: 6px; padding: 8px 12px; border-radius: var(--v2-radius-sm); background: var(--v2-ink); color: var(--v2-on-ink); font-size: 12px; font-weight: 700; text-decoration: none; } .v2-root a.v2-first-run__setup:hover, .v2-root button.v2-first-run__hello:hover:not(:disabled) { background: var(--v2-ink-hover); color: var(--v2-on-ink); } .v2-root button.v2-first-run__hello:disabled { cursor: wait; opacity: 0.65; } .v2-first-run__connected { color: var(--v2-success); font-weight: 700; } .v2-first-run__error { color: var(--v2-danger); font-size: 12px; } .v2-chat__error { padding: 9px 12px; border-radius: 8px; background: rgba(239, 68, 68, 0.08); color: var(--v2-danger); font-size: 12px; border: 1px solid rgba(239, 68, 68, 0.16); } .v2-chat__system { display: flex; align-items: center; gap: 10px; padding: 10px 14px; background: var(--v2-surface); border: 1px solid var(--v2-border); border-radius: var(--v2-radius); font-size: 13px; color: var(--v2-text-secondary); } .v2-chat__system-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--v2-success); flex-shrink: 0; } /* Sender-only delivery feedback sits under the post as a quiet system line. */ .v2-msg__sender-notice { color: var(--v2-text-tertiary); font: 12px/16px var(--v2-font); margin-top: 6px; } .v2-root .v2-msg__sender-notice a { color: var(--v2-accent); text-decoration: underline; } /* Agent typing indicator — sits between message list and composer. Collapses to nothing when typingAgents is empty. Subtle so it doesn't compete with the messages above; the animated dots carry the "alive" cue. */ .v2-chat__typing { display: flex; align-items: center; gap: 8px; padding: 6px 24px; border-top: 1px solid var(--v2-border-soft); color: var(--v2-text-tertiary); font-size: 12px; flex-shrink: 0; min-height: 28px; } .v2-chat__typing-avatars { display: inline-flex; } .v2-chat__typing-avatars > * + * { margin-left: -6px; } .v2-chat__typing-label { flex-shrink: 0; } .v2-chat__typing-dots { display: inline-flex; align-items: center; gap: 3px; margin-left: 2px; } .v2-chat__typing-dots > span { width: 4px; height: 4px; border-radius: 50%; background: var(--v2-text-tertiary); animation: v2-typing-dot var(--motion-breath) infinite var(--motion-ease-breath); } .v2-chat__typing-dots > span:nth-child(2) { animation-delay: var(--motion-stagger); } .v2-chat__typing-dots > span:nth-child(3) { animation-delay: calc(var(--motion-stagger) + var(--motion-stagger)); } @keyframes v2-typing-dot { 0%, 60%, 100% { transform: translateY(0); opacity: 0.35; } 30% { transform: translateY(-3px); opacity: 1; } } .v2-chat__composer { padding: 12px 24px 14px; border-top: 1px solid var(--v2-border-soft); background: var(--v2-surface); flex-shrink: 0; } /* A 1:1 agent room must not make a successful POST look like an active agent. The liveness row is deliberately adjacent to, but outside, the composer: it remains visible while someone writes and never becomes part of the scrollable transcript. */ .v2-chat__agent-room-status { margin: 8px 24px 0; padding: 8px 10px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-bg-subtle); color: var(--v2-text-secondary); font-size: 12px; line-height: 1.4; } .v2-chat__agent-room-status--wait { border-color: var(--v2-border); background: var(--v2-surface-hover); color: var(--v2-text-secondary); font-family: var(--v2-font-mono); font-weight: 500; } .v2-chat__agent-room-status-dot { width: 6px; height: 6px; flex: 0 0 auto; border-radius: 50%; background: var(--v2-success); } .v2-chat__agent-room-status--timeout { border-color: var(--v2-border-strong); background: var(--v2-bg-subtle); color: var(--v2-text-secondary); font-weight: 500; } .v2-chat__starter-prompts { display: flex; flex-wrap: wrap; gap: 8px; /* Part of the conversation column: siblings of the messages list share its full pane width and left edge (rule 2 v5, full-width) — a second edge here recreates the v1 island. max-width keeps the mobile-shrink property the 390px pane needs. */ width: 100%; max-width: 100%; margin-inline: 0; padding: 10px 24px 0; background: var(--v2-surface); flex-shrink: 0; } .v2-chat__new-pod { align-self: center; width: min(760px, 100%); margin: auto; padding: 24px; border: 1px solid var(--v2-border-strong); border-radius: var(--v2-radius-lg); background: var(--v2-bg-subtle); } .v2-chat__new-pod-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 16px; } .v2-chat__new-pod-title { color: var(--v2-text-primary); font-size: 20px; font-weight: 700; letter-spacing: -0.02em; } .v2-chat__new-pod-text { max-width: 520px; margin-top: 4px; color: var(--v2-text-secondary); font-size: 13px; line-height: 1.45; } .v2-root button.v2-chat__new-pod-dismiss { display: grid; flex: 0 0 auto; width: 28px; height: 28px; place-items: center; border-radius: var(--v2-radius-sm); color: var(--v2-text-tertiary); } .v2-root button.v2-chat__new-pod-dismiss:hover { background: var(--v2-surface-hover); color: var(--v2-text-primary); } .v2-chat__new-pod-actions { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; } .v2-root button.v2-chat__new-pod-action, .v2-chat__new-pod-action { display: flex; min-width: 0; min-height: 88px; flex-direction: column; align-items: flex-start; gap: 5px; padding: 12px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius); background: var(--v2-surface); color: var(--v2-text-primary); text-align: left; } .v2-root button.v2-chat__new-pod-action { transition: background 80ms ease, border-color 80ms ease; } .v2-root button.v2-chat__new-pod-action:hover { background: var(--v2-surface-hover); } .v2-root button.v2-chat__new-pod-action--hosted { background: var(--v2-ink); color: var(--v2-on-ink); font-weight: 600; } .v2-root button.v2-chat__new-pod-action--hosted:hover { background: var(--v2-ink-hover); } .v2-root button.v2-chat__new-pod-action--hosted:disabled { opacity: 0.7; } .v2-chat__new-pod-action-title { color: var(--v2-text-primary); font-size: 13px; font-weight: 700; } .v2-chat__new-pod-action-text, .v2-chat__new-pod-status { color: var(--v2-text-secondary); font-size: 11px; line-height: 1.4; } .v2-chat__new-pod-action--invite .v2-invite-link-row { width: 100%; margin-top: auto; } .v2-root button.v2-chat__new-pod-copy { flex: 0 0 auto; padding: 0 10px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-sm); background: var(--v2-surface); color: var(--v2-accent-text); font-size: 11px; font-weight: 700; } .v2-chat__new-pod-action--invite > .v2-chat__new-pod-copy { min-height: 32px; margin-top: auto; } @media (max-width: 760px) { .v2-chat__new-pod-action--invite > .v2-chat__new-pod-copy { min-height: 44px; } } .v2-root button.v2-chat__new-pod-copy:hover { background: var(--v2-surface-hover); } .v2-chat__new-pod-error { display: flex; align-items: center; gap: 8px; margin-top: auto; color: var(--v2-danger); font-size: 11px; } .v2-root .v2-chat__new-pod-error button { flex: 0 0 auto; color: var(--v2-accent-text); font-size: 11px; font-weight: 700; } .v2-root button.v2-chat__starter-prompt { min-width: 0; max-width: 100%; padding: 7px 11px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius-pill); background: var(--v2-surface); color: var(--v2-text-secondary); font-size: 12px; font-weight: 600; line-height: 1.35; text-align: left; white-space: normal; transition: background 80ms ease, border-color 80ms ease, color 80ms ease; } .v2-root button.v2-chat__starter-prompt:hover { background: var(--v2-surface-hover); } @media (max-width: 700px) { .v2-chat__new-pod { padding: 16px; } .v2-chat__new-pod-actions { grid-template-columns: minmax(0, 1fr); } .v2-root button.v2-chat__new-pod-action, .v2-chat__new-pod-action { min-height: 0; } } .v2-chat__composer-input-wrap { position: relative; display: flex; align-items: flex-end; gap: 10px; min-height: 60px; border: 1px solid var(--v2-border); border-radius: var(--v2-radius); padding: 10px 10px 10px 12px; background: var(--v2-bg); transition: border-color 120ms ease, box-shadow 120ms ease; } .v2-chat__composer-input-wrap:focus-within { border-color: var(--v2-accent); box-shadow: var(--v2-focus-ring); } .v2-chat__composer-input { flex: 1; min-height: 38px; max-height: 96px; padding: 0; border-radius: 0; border: none; background: transparent; font-size: 14px; line-height: 1.45; resize: vertical; outline: none; transition: background 80ms ease, border-color 80ms ease; } .v2-chat__composer-input:focus { border-color: transparent; background: transparent; } .v2-chat__composer-actions { display: flex; align-items: center; gap: 2px; padding: 0; align-self: flex-end; order: 2; } .v2-chat__composer-icon-btn { width: 32px; height: 32px; display: grid; place-items: center; border-radius: var(--v2-radius-sm); color: var(--v2-text-tertiary); font-size: 14px; transition: background 80ms ease, color 80ms ease; } .v2-chat__composer-icon-btn:hover { background: var(--v2-bg-subtle); color: var(--v2-text-primary); } /* Explicit root/button selector for the composer control; the global reset is deliberately a low-specificity default and must not own this state. */ .v2-root button.v2-chat__send { width: 36px; height: 36px; display: grid; place-items: center; border-radius: 9px; background: var(--v2-ink); border-color: var(--v2-ink); color: var(--v2-on-ink); transition: background 80ms ease; flex-shrink: 0; order: 3; } .v2-chat__composer-input { order: 1; } .v2-root button.v2-chat__send--execute { background: var(--v2-ink); border-color: var(--v2-ink); } .v2-root button.v2-chat__send--execute:hover { background: var(--v2-ink-hover); } .v2-root button.v2-chat__send:hover { background: var(--v2-ink-hover); } .v2-root button.v2-chat__send:disabled { background: var(--v2-border-strong); cursor: not-allowed; } .v2-chat__composer-footer { height: 18px; padding-top: 0; margin-top: 5px; font-size: 11px; color: var(--v2-text-muted); display: flex; align-items: center; gap: 4px; justify-content: center; } .v2-chat__composer-footer-link { color: var(--v2-text-secondary); font-weight: 600; text-decoration: underline; } .v2-chat__composer-error { color: var(--v2-danger); font-weight: 600; } /* Composer hint row — keyboard affordances below the input wrap. Mirrors the .composer__hint pattern in components-composer.html. */ .v2-chat__composer-hint { display: flex; align-items: center; justify-content: space-between; margin-top: 6px; padding: 0 4px; font-size: 11px; color: var(--v2-text-muted); } .v2-chat__composer-hint kbd { font-family: var(--v2-font-mono); font-size: 10.5px; font-weight: 600; color: var(--v2-text-tertiary); background: var(--v2-surface-hover); border: 1px solid var(--v2-border-soft); border-radius: 4px; padding: 1px 5px; margin: 0 2px; } /* Read-only banner shown in place of the composer when the viewer can read an agent-dm pod (§3.7) but isn't a formal member. Styled as a soft note, not an error — being a read-only observer is the intended state. */ .v2-chat__readonly { display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px; margin: 12px 16px 16px; background: var(--v2-bg-subtle); border: 1px solid var(--v2-border-soft); border-radius: var(--v2-radius); } .v2-chat__readonly-icon { flex: 0 0 auto; display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--v2-radius-pill); background: var(--v2-surface-hover); color: var(--v2-ink); } .v2-chat__readonly-body { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; } .v2-chat__readonly-title { font-size: 13px; font-weight: 600; color: var(--v2-text-primary); } .v2-chat__readonly-text { font-size: 12.5px; line-height: 1.45; color: var(--v2-text-secondary); } /* TASK-406: quiet, non-interactive kernel action line in the pod timeline. */ .v2-system-action-line { box-sizing: border-box; width: calc(100% - 36px); margin-left: 36px; display: grid; grid-template-columns: 16px minmax(0, max-content) auto minmax(0, 1fr) auto; align-items: center; gap: 8px; padding: 4px 0; font: 400 13px/16px var(--v2-font); } .v2-thread .v2-system-action-line .v2-avatar { width: 16px; height: 16px; border-width: 1px; font: 650 11px/1 var(--v2-font-mono); letter-spacing: 0; text-indent: 0; } .v2-system-action-line__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--v2-text-primary); font-weight: 500; } .v2-system-action-line__action { grid-column: 3; display: flex; gap: 5px; min-width: 0; overflow: hidden; white-space: nowrap; } .v2-system-action-line__verb { color: var(--v2-text-tertiary); } .v2-system-action-line__object { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--v2-text-tertiary); font: 400 11px/16px var(--v2-font-mono); } .v2-system-action-line time { grid-column: 5; color: var(--v2-text-placeholder); font: 400 11px/16px var(--v2-font-mono); } /* §3.8 system event card — replaces the regular author/avatar bubble for commonly-bot announcements like "Pixel and codex started a DM". One-row, chrome-light, with a primary CTA that navigates internally. */ .v2-msg.v2-msg--system { /* Override the two-column avatar grid from .v2-msg: a system card is a single child and would otherwise be crammed into the 38px avatar column, collapsing the headline and wrapping the timestamp. Block = full width. Selector MUST be `.v2-msg.v2-msg--system` (two classes): `.v2-msg` sets `display: grid` LATER in this file, so an equal-specificity `.v2-msg--system` loses on source order. Higher specificity wins regardless of order. */ display: block; padding: 6px 0; } .v2-syscard { display: flex; align-items: center; gap: 12px; padding: 10px 14px; background: var(--v2-surface-hover); border: 1px solid var(--v2-border-soft); border-radius: var(--v2-radius); } .v2-syscard--action { background: var(--v2-surface); box-shadow: 0 0 0 2px var(--v2-accent); } .v2-syscard__icon { flex: 0 0 auto; display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--v2-radius-pill); background: var(--v2-surface-hover); color: var(--v2-ink); } .v2-syscard__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; } .v2-syscard__headline { font-size: 13px; font-weight: 600; color: var(--v2-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .v2-syscard__time { font-size: 11.5px; color: var(--v2-text-secondary); } .v2-root button.v2-syscard__cta, .v2-syscard__cta { flex: 0 0 auto; font-size: 12.5px; font-weight: 600; color: var(--v2-on-ink); background: var(--v2-ink); border: 1px solid var(--v2-ink); border-radius: var(--v2-radius-pill); padding: 6px 14px; cursor: pointer; transition: background 120ms ease; } .v2-root button.v2-syscard__cta:hover, .v2-syscard__cta:hover { background: var(--v2-ink-hover); } /* GitHub PR preview card. Rendered below a message body when the content contains a `https://github.com/