diff --git a/docs/design/artboards/d215/cap/measure_after.js b/docs/design/artboards/d215/cap/measure_after.js
index b54c10ee3..37bb4d7f8 100644
--- a/docs/design/artboards/d215/cap/measure_after.js
+++ b/docs/design/artboards/d215/cap/measure_after.js
@@ -18,7 +18,7 @@ const fs = require('fs');
const HERE = __dirname;
const TARGETS = path.join(HERE, '..', 'targets.json');
-const SURFACES = ['settings', 'agent-settings', 'agent-memory', 'board', 'connectors', 'chat', 'sidebar', 'byo', 'byo-handoff', 'firstrun-welcome', 'firstrun-waiting', 'firstrun-connected', 'firstrun-error', 'connect'];
+const SURFACES = ['settings', 'agent-settings', 'agent-memory', 'board', 'connectors', 'chat', 'sidebar', 'byo', 'byo-handoff', 'firstrun-welcome', 'firstrun-waiting', 'firstrun-connected', 'firstrun-error', 'connect', 'team'];
// byo-handoff is drawn but can't be captured on prod: reaching it issues a real key. The first-run
// dialog is a latch-gated overlay, and the Scout welcome only exists in a brand-new pod, so neither is measured on prod.
const PROD_SURFACES = SURFACES.filter(s => s !== 'byo-handoff' && !s.startsWith('firstrun-'));
@@ -166,6 +166,7 @@ async function prod(only) {
['connectors', () => '/v2/connectors'],
['byo', () => '/v2/agents/byo'],
['connect', () => '/v2/connect'],
+ ['team', () => '/v2/agents'],
['chat', () => `/v2/pods/${pod}`],
['sidebar', vp => (vp === '1440' ? `/v2/pods/${side}` : '/v2'), { list390: SIDEBAR_390 }],
];
diff --git a/docs/design/artboards/d215/cap/measure_after.test.js b/docs/design/artboards/d215/cap/measure_after.test.js
index d40f3a3e1..06cefaa92 100644
--- a/docs/design/artboards/d215/cap/measure_after.test.js
+++ b/docs/design/artboards/d215/cap/measure_after.test.js
@@ -29,7 +29,7 @@ test('--only with an unknown surface throws', () => {
test('the committed targets.json passes its own validation', () => {
const t = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'targets.json'), 'utf8'));
- assert.strictEqual(Object.keys(t).length, 28);
+ assert.strictEqual(Object.keys(t).length, 30);
validateTargets(t);
});
@@ -39,7 +39,7 @@ test('an empty or partial measurement is refused', () => {
const partial = { ...t }; delete partial['board-390'];
assert.throws(() => validateTargets(partial), /missing board-390/);
assert.throws(() => validateTargets({ ...t, 'chat-1440': { helperWords: NaN, innerRatio: 2 } }), /invalid chat-1440/);
- assert.strictEqual(EXPECTED.length, 28);
+ assert.strictEqual(EXPECTED.length, 30);
});
// A page whose SPA shell answers 200 and then redirects to `landsOn`. Its metrics would score
diff --git a/docs/design/artboards/d215/index.html b/docs/design/artboards/d215/index.html
index 3f4e0adfb..2743b5b3b 100644
--- a/docs/design/artboards/d215/index.html
+++ b/docs/design/artboards/d215/index.html
@@ -517,6 +517,55 @@
.app.m .v2-connect .v2-connect__actions { flex-direction: column; }
.app.m .v2-connect .v2-connect__cta, .app.m .v2-connect .v2-connect__lane > .v2-connect__secondary, .app.m .v2-connect .v2-connect__secondary { align-self: stretch; height: 44px; font-size: 14px; }
+/* your team: the page's own v2-team__* / v2-team-card__* names, in the connectors family (14px bordered
+ cards, the ink key and the plain key, mono status and pod chips). Scoped to .v2-team, the page's root. */
+.v2-team { display: flex; flex-direction: column; gap: 20px; max-width: 1100px; margin: 0 auto; }
+.v2-team .v2-team__head { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 44px; }
+.v2-team .v2-team__heading { display: flex; align-items: baseline; gap: 12px; min-width: 0; }
+.v2-team .v2-team__title { margin: 0; font: 800 24px/1 var(--f-display); letter-spacing: -0.03em; color: var(--ink); }
+.v2-team .v2-team__meta { font: 500 12px/16px var(--f-mono); color: var(--mu); white-space: nowrap; }
+.v2-team .v2-team__actions { display: flex; gap: 8px; flex-shrink: 0; }
+.v2-team .v2-team__byo, .v2-team .v2-team__hire, .v2-team .v2-team-card__answer, .v2-team .v2-team-card__talk { all: unset; box-sizing: border-box; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; height: 34px; padding: 0 13px; border-radius: 10px; font: 600 13px/1 var(--f-body); white-space: nowrap; }
+.v2-team .v2-team__hire, .v2-team .v2-team-card__answer { color: var(--on-fill); background: linear-gradient(180deg, var(--fill-a), var(--fill-b)); border: 1px solid var(--fill-edge); }
+.v2-team .v2-team__byo, .v2-team .v2-team-card__talk { color: var(--ink); background: linear-gradient(180deg, var(--key-a), var(--key-b)); border: 1px solid var(--key-edge); box-shadow: var(--sh-key); }
+.v2-team .v2-team__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
+.v2-team .v2-team-card { display: flex; flex-direction: column; gap: 10px; min-width: 0; padding: 16px; border: 1px solid var(--d); border-radius: 14px; background: var(--sf); }
+.v2-team .v2-team-card--needsYou { border: 2px solid var(--c); padding: 15px; }
+.v2-team .v2-team-card--own { border: 1px dashed var(--b); background: transparent; }
+.v2-team .v2-team-card__head { display: flex; align-items: center; gap: 12px; min-width: 0; }
+.v2-team .v2-team-card__mark { all: unset; box-sizing: border-box; cursor: pointer; flex: none; display: grid; place-items: center; width: 40px; height: 40px; border-radius: 10px; overflow: hidden; box-shadow: 0 0 0 1px var(--av-ring); font: 600 12px/1 var(--f-body); letter-spacing: 0.02em; text-transform: uppercase; }
+.v2-team .v2-team-card__mark .v2-avatar { width: 100%; height: 100%; border: 0; border-radius: inherit; box-shadow: none; }
+.v2-team .v2-team-card__mark img, .v2-team .v2-team-card__mark svg { display: block; width: 100%; height: 100%; object-fit: cover; }
+.v2-team .v2-team-card__mark--needsYou { background: var(--c); color: var(--on-c); }
+.v2-team .v2-team-card__mark--working { background: var(--ink); color: var(--sf); }
+.v2-team .v2-team-card__mark--idle { background: var(--d); color: var(--se); }
+.v2-team .v2-team-card__title { min-width: 0; }
+.v2-team .v2-team-card__name { font: 700 18px/1.2 var(--f-display); letter-spacing: -0.02em; color: var(--ink); overflow-wrap: anywhere; white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-width: 0; }
+.v2-team .v2-team-card__status { margin-top: 3px; font: 500 11.5px/16px var(--f-mono); color: var(--mu); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.v2-team .v2-team-card__status--needsYou, .v2-team .v2-team-card__status--working { color: var(--c-text); }
+.v2-team .v2-team-card__status--needsYou { white-space: normal; overflow: visible; text-overflow: clip; }
+.v2-team .v2-team-card__status--working .v2-team-card__dot { animation: v2-team-pulse 1.6s ease-in-out infinite; }
+@keyframes v2-team-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
+.v2-team .v2-team-card__where { display: inline-flex; flex: none; margin-left: auto; align-self: flex-start; color: var(--mu); }
+.v2-team .v2-team-card__desc { margin: 0; font-size: 13px; line-height: 18px; color: var(--se); }
+.v2-team .v2-team-card__pods { display: flex; flex-wrap: wrap; gap: 6px; }
+.v2-team .v2-team-card__pod { display: inline-block; max-width: 100%; height: 24px; padding: 0 8px; border-radius: 7px; font: 500 11.5px/24px var(--f-mono); color: var(--se); background: var(--sf); box-shadow: inset 0 0 0 1px var(--d); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.v2-team .v2-team-card__meter { display: flex; align-items: center; gap: 10px; }
+.v2-team .v2-team-card__meter-track { flex: 1; min-width: 0; height: 4px; border-radius: 2px; background: var(--d); overflow: hidden; }
+.v2-team .v2-team-card__meter-fill { display: block; height: 100%; background: var(--c); }
+.v2-team .v2-team-card__meter-text { font: 500 11.5px/16px var(--f-mono); color: var(--mu); white-space: nowrap; }
+.v2-team .v2-team-card__command { display: block; padding: 10px 12px; border-radius: 10px; background: var(--p); box-shadow: inset 0 0 0 1px var(--d); font: 500 14px/20px var(--f-mono); color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
+.v2-team .v2-team-card__actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px; }
+.app.m .v2-team { gap: 14px; }
+.app.m .v2-team .v2-team__head { flex-direction: column; align-items: stretch; gap: 12px; }
+.app.m .v2-team .v2-team__heading { flex-direction: column; align-items: flex-start; gap: 6px; }
+.app.m .v2-team .v2-team__meta { white-space: normal; }
+.app.m .v2-team .v2-team__actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
+.app.m .v2-team .v2-team__byo, .app.m .v2-team .v2-team__hire, .app.m .v2-team .v2-team-card__answer, .app.m .v2-team .v2-team-card__talk { height: 44px; font-size: 14px; }
+.app.m .v2-team .v2-team__grid { grid-template-columns: repeat(1, minmax(0, 1fr)); gap: 10px; }
+.app.m .v2-team .v2-team-card__mark { width: 44px; height: 44px; border-radius: 11px; }
+.app.m .v2-team .v2-team-card__actions > button { flex: 1 1 0; }
+
/* phone */
.mtop { flex: none; height: 52px; display: flex; align-items: center; gap: 8px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--d); }
.mtop h1 { font: 800 21px/1 var(--f-display); letter-spacing: -0.03em; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -687,7 +736,7 @@
First run
/v2/pods/:id · Scout welcome + Guide dialoghelper words welcome – → 16dialog – → 68scroll 390 – → 1.0×
diff --git a/docs/design/artboards/d215/targets.json b/docs/design/artboards/d215/targets.json
index fe89f9530..0afa663f0 100644
--- a/docs/design/artboards/d215/targets.json
+++ b/docs/design/artboards/d215/targets.json
@@ -79,6 +79,14 @@
"helperWords": 20,
"innerRatio": 1.77
},
+ "team-1440": {
+ "helperWords": 56,
+ "innerRatio": 1
+ },
+ "team-390": {
+ "helperWords": 34,
+ "innerRatio": 1.93
+ },
"firstrun-welcome-1440": {
"helperWords": 16,
"innerRatio": 1
diff --git a/frontend/src/v2/__tests__/signalTokens.test.ts b/frontend/src/v2/__tests__/signalTokens.test.ts
index 8b61591fe..bcf13ffb0 100644
--- a/frontend/src/v2/__tests__/signalTokens.test.ts
+++ b/frontend/src/v2/__tests__/signalTokens.test.ts
@@ -111,13 +111,14 @@ describe('dark-safe surfaces (TASK-681)', () => {
expect(rd('../landing/v2-landing.css')).toMatch(/\.v2-landing__bar\s*\{[^}]*color-mix\(in srgb, var\(--v2-bg\) 86%/);
expect(rd('../agents/v2-agent-profile.css')).toMatch(/\.v2-aprofile__bar\s*\{[^}]*color-mix\(in srgb, var\(--v2-bg\) 86%/);
});
- test('ink primary buttons take --v2-ink / --v2-on-ink', () => {
- const css = rd('../v2.css');
- ['v2-team__hire', 'v2-team-card__answer'].forEach((cls) => {
- const rule = css.match(new RegExp(`button\\.${cls}\\s*\\{[^}]*\\}`));
- expect(rule?.[0]).toContain('background: var(--v2-ink)');
- expect(rule?.[0]).toContain('color: var(--v2-on-ink)');
- });
+ test('ink primary buttons take theme tokens, never a literal colour', () => {
+ // R06 lifted Your team onto the d215 frame: Hire and Answer are its ink
+ // key, the --v2-fill-* gradient with --v2-on-fill text, in the page's sheet.
+ const css = rd('../components/V2YourTeamPage.css');
+ const rule = css.match(/\n\.v2-team \.v2-team__hire, \.v2-team \.v2-team-card__answer \{[^}]*\}/);
+ expect(rule?.[0]).toContain('background: linear-gradient(180deg, var(--v2-fill-a), var(--v2-fill-b))');
+ expect(rule?.[0]).toContain('color: var(--v2-on-fill)');
+ expect(rule?.[0]).not.toMatch(/#[0-9a-f]{3,8}\b|rgba?\(/i);
});
test('the board drawer scrim is --v2-scrim', () => {
// S4: the board's rules live in its own sheet; the scrim backs the phone sheet.
diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts
index addf7a449..df646cfbf 100644
--- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts
+++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts
@@ -174,6 +174,8 @@ describe('v2 layout invariants (CSS rule presence)', () => {
const byoCss = read('../components/V2AgentBYO.css');
// R05 lifted the Connect page onto the d215 frame (V2ConnectPage.css).
const connectCss = read('../components/V2ConnectPage.css');
+ // R06 lifted the Your team page onto the d215 frame (V2YourTeamPage.css).
+ const teamCss = read('../components/V2YourTeamPage.css');
const landing = read('../landing/v2-landing.css');
const podChat = read('../components/V2Thread.tsx');
const podsSidebar = read('../components/V2PodsSidebar.tsx');
@@ -310,15 +312,19 @@ describe('v2 layout invariants (CSS rule presence)', () => {
test('Your Team card name owns its line so the category chip cannot crush it', () => {
// Direction C: the name sits in its own column of the card head; the
// crush guard is min-width 0 on the name and the head, not a flex basis.
- const rule = ruleBody(v2, '.v2-team-card__name');
+ const rule = ruleBody(teamCss, '.v2-team .v2-team-card__name');
expect(rule).toContain('min-width: 0');
- expect(ruleBody(v2, '.v2-team-card__head')).toContain('min-width: 0');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__head')).toContain('min-width: 0');
+ // ruleBody reads the first, desktop rule; no phone override may undo it.
+ for (const phone of [...teamCss.matchAll(/@media \(max-width: 760px\) \{[\s\S]*?\n\}/g)].map((m) => m[0])) {
+ expect(phone).not.toMatch(/v2-team-card__name[^{]*\{[^}]*(nowrap|min-width: auto|flex-shrink: 0)/);
+ }
});
test('Your Team card name WRAPS — a primary identifier never one-line-ellipsizes (craft audit rule 1)', () => {
// At 1440px half the fleet's names truncated ("Commonly…", "Pod Summ…").
// The name wraps to two lines; single-line nowrap+ellipsis was the defect.
- const rule = ruleBody(v2, '.v2-team-card__name');
+ const rule = ruleBody(teamCss, '.v2-team .v2-team-card__name');
expect(rule).toContain('white-space: normal');
expect(rule).toContain('-webkit-line-clamp: 2');
expect(rule).not.toContain('white-space: nowrap');
@@ -652,7 +658,8 @@ describe('v2 layout invariants (CSS rule presence)', () => {
test('runtime vocabulary stays off Your Team cards (ADR-022 D1, ratified)', () => {
// The chip shipped in violation of the ratified rule; the craft audit
// (finding 2) removed it. This pins the removal against reintroduction.
- expect(v2).not.toContain('.v2-team-card__runtime {');
+ expect(v2).not.toContain('.v2-team-card__runtime');
+ expect(teamCss).not.toContain('.v2-team-card__runtime');
});
test('touch inputs are 16px — iOS auto-zoom never fires and never strands the page zoomed', () => {
@@ -1102,8 +1109,8 @@ describe('v2 layout invariants (CSS rule presence)', () => {
// Direction C: three equal columns that can shrink to zero, and ONE column
// at ≤760 — the same guarantee (a column never wider than its container)
// expressed on the new grid.
- expect(ruleBody(v2, '.v2-team__grid')).toContain('repeat(3, minmax(0, 1fr))');
- expect(ruleBody(v2, '.v2-team__grid', { within: teamPhoneBlock(v2) })).toContain('repeat(1, minmax(0, 1fr))');
+ expect(ruleBody(teamCss, '.v2-team .v2-team__grid')).toContain('repeat(3, minmax(0, 1fr))');
+ expect(ruleBody(teamCss, '.v2-team .v2-team__grid', { within: teamPhoneBlock(teamCss) })).toContain('repeat(1, minmax(0, 1fr))');
});
test('the agent profile page overrides the app-shell overflow too (sibling invariant)', () => {
@@ -1181,7 +1188,7 @@ describe('v2 layout invariants (CSS rule presence)', () => {
// (2026-07-05 mobile smoke; same crush family as the #568 chip bug).
// Direction C: the action row is its own flex row under the body and wraps;
// it never shares a line with the name.
- expect(ruleBody(v2, '.v2-team-card__actions')).toContain('flex-wrap: wrap');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__actions')).toContain('flex-wrap: wrap');
});
test('the a2a-DM system card overrides the two-column message grid', () => {
@@ -1562,38 +1569,46 @@ describe('v2 layout invariants (CSS rule presence)', () => {
});
test('Your Team (direction C): three-column card grid, state-coloured marks, cobalt ring for needs-you, ink Hire, no green, old tiers gone', () => {
- const grid = ruleBody(v2, '.v2-team__grid');
+ const grid = ruleBody(teamCss, '.v2-team .v2-team__grid');
expect(grid).toContain('grid-template-columns: repeat(3, minmax(0, 1fr))');
expect(grid).toContain('gap: 12px');
- expect(ruleBody(v2, '.v2-team-card--needsYou')).toContain('border: 2px solid var(--v2-accent)');
- expect(ruleBody(v2, '.v2-root button.v2-team-card__mark--needsYou')).toContain('var(--v2-accent)');
- expect(ruleBody(v2, '.v2-root button.v2-team-card__mark--working')).toContain('var(--v2-text-primary)');
- expect(ruleBody(v2, '.v2-root button.v2-team-card__mark--idle')).toContain('var(--v2-border-soft)');
- expect(ruleBody(v2, '.v2-root button.v2-team__hire')).toContain('background: var(--v2-ink)');
- expect(ruleBody(v2, '.v2-root button.v2-team-card__answer')).toContain('background: var(--v2-ink)');
- expect(ruleBody(v2, '.v2-root button.v2-team-card__talk')).toContain('border: 1px solid var(--v2-border)');
- expect(ruleBody(v2, '.v2-team-card__status')).toContain('var(--v2-font-mono)');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card--needsYou')).toContain('border: 2px solid var(--v2-accent)');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__mark--needsYou')).toContain('var(--v2-accent)');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__mark--working')).toContain('var(--v2-text-primary)');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__mark--idle')).toContain('var(--v2-border-soft)');
+ // R06: Hire and Answer share the frame's ink key, the fill gradient
+ // (--v2-fill-*) with on-fill text, the same key as BYO's submit and
+ // Connect's CTA; Talk (and Bring your own) is the bordered plain key.
+ const ink = selectorRuleBody(teamCss, '\n.v2-team .v2-team__hire, .v2-team .v2-team-card__answer {');
+ expect(ink).toContain('background: linear-gradient(180deg, var(--v2-fill-a), var(--v2-fill-b))');
+ expect(ink).toContain('color: var(--v2-on-fill)');
+ const plain = selectorRuleBody(teamCss, '\n.v2-team .v2-team__byo, .v2-team .v2-team-card__talk {');
+ expect(plain).toContain('border: 1px solid var(--v2-key-edge)');
+ expect(plain).not.toContain('var(--v2-fill-');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__status')).toContain('var(--v2-font-mono)');
// ux-lead 66246: the ask wraps, the command wraps at mono 14, the name is display-18,
// the mark is 600, and the title stacks over its meta on a phone.
- expect(ruleBody(v2, '.v2-team-card__status--needsYou')).toContain('white-space: normal');
- expect(ruleBody(v2, '.v2-team-card__command')).toContain('white-space: pre-wrap');
- expect(ruleBody(v2, '.v2-team-card__command')).toContain('14px/20px');
- expect(ruleBody(v2, '.v2-team-card__name')).toContain('font: 700 18px/1.2 var(--v2-font-display)');
- expect(ruleBody(v2, '.v2-root button.v2-team-card__mark')).toContain('font: 600 12px/1');
- const teamAvatar = ruleBody(v2, '.v2-root button.v2-team-card__mark .v2-avatar');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__status--needsYou')).toContain('white-space: normal');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__command')).toContain('white-space: pre-wrap');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__command')).toContain('14px/20px');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__name')).toContain('font: 700 18px/1.2 var(--v2-font-display)');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__mark')).toContain('font: 600 12px/1');
+ const teamAvatar = ruleBody(teamCss, '.v2-team .v2-team-card__mark .v2-avatar');
expect(teamAvatar).toContain('width: 100%');
expect(teamAvatar).toContain('height: 100%');
expect(teamAvatar).toContain('border-radius: inherit');
// Read the rule inside the team's phone block — a `[\s\S]*?` regex across
// the sheet passed with the rule deleted (sprint-review at 58fb4147).
- const teamPhone = teamPhoneBlock(v2);
- expect(ruleBody(v2, '.v2-team__heading', { within: teamPhone })).toContain('flex-direction: column');
- expect(ruleBody(v2, '.v2-team__grid', { within: teamPhone })).toContain('repeat(1, minmax(0, 1fr))');
+ const teamPhone = teamPhoneBlock(teamCss);
+ expect(ruleBody(teamCss, '.v2-team .v2-team__heading', { within: teamPhone })).toContain('flex-direction: column');
+ expect(ruleBody(teamCss, '.v2-team .v2-team__grid', { within: teamPhone })).toContain('repeat(1, minmax(0, 1fr))');
// The retired surfaces: feature rows, standard cards with icon buttons, quiet rows, green dot.
- expect(v2).not.toContain('.v2-team-feature ');
- expect(v2).not.toContain('.v2-team-quiet');
- expect(v2).not.toContain('v2-team-feature__dot');
- expect(v2).toMatch(/@media \(max-width: 760px\)[\s\S]*?\.v2-team__grid \{[\s\S]*?repeat\(1, minmax\(0, 1fr\)\)/);
+ for (const css of [v2, teamCss]) {
+ expect(css).not.toContain('.v2-team-feature ');
+ expect(css).not.toContain('.v2-team-quiet');
+ expect(css).not.toContain('v2-team-feature__dot');
+ }
+ expect(teamCss).toMatch(/@media \(max-width: 760px\)[\s\S]*?\.v2-team__grid \{[\s\S]*?repeat\(1, minmax\(0, 1fr\)\)/);
});
test('history: a mono edge line that loads on scroll and a Jump-to-latest pill', () => {
@@ -2446,7 +2461,9 @@ describe('v2 layout invariants (CSS rule presence)', () => {
const ds = fs.readFileSync(path.join(__dirname, '../../../design-system/tokens.css'), 'utf8');
expect(v2).toContain('--v2-fs-feature: 17px');
expect(ds).toContain('--c-fs-feature: 17px');
- expect(v2).toContain('var(--v2-fs-feature)');
+ // Its one consumer, the empty team's title, left v2.css with the Your team
+ // lift (R06); the step is still defined here and used there.
+ expect(read('../components/V2YourTeamPage.css')).toContain('font: 700 var(--v2-fs-feature)/1.2');
});
it('v2.css braces balance — an unclosed block silently swallows every later rule', () => {
@@ -2463,8 +2480,8 @@ describe('v2 layout invariants (CSS rule presence)', () => {
it('team card on a phone: one column, so the name column never one-chars (spec §5, #568 class, re-pinned on direction C)', () => {
// The featured row is gone; the same guarantee on the card grid is one
// column at ≤760 and a card head whose name column can shrink.
- expect(ruleBody(v2, '.v2-team__grid', { within: teamPhoneBlock(v2) })).toContain('repeat(1, minmax(0, 1fr))');
- expect(ruleBody(v2, '.v2-team-card__title')).toContain('min-width: 0');
+ expect(ruleBody(teamCss, '.v2-team .v2-team__grid', { within: teamPhoneBlock(teamCss) })).toContain('repeat(1, minmax(0, 1fr))');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__title')).toContain('min-width: 0');
});
it('platform tint tokens stay in v2.css and tokens.css, while Signal rows use the cobalt mark', () => {
@@ -5435,10 +5452,10 @@ describe('v2 board on a phone (TASK-576)', () => {
});
test('hosted meter track keeps its flex width and clips the fill inside the card', () => {
- const v2 = readV2Css();
- expect(ruleBody(v2, '.v2-team-card__meter-track')).toContain('flex: 1');
- expect(ruleBody(v2, '.v2-team-card__meter-track')).toContain('min-width: 0');
- expect(ruleBody(v2, '.v2-team-card__meter-track')).toContain('overflow: hidden');
+ const teamCss = read('../components/V2YourTeamPage.css');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__meter-track')).toContain('flex: 1');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__meter-track')).toContain('min-width: 0');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__meter-track')).toContain('overflow: hidden');
});
test('character avatars keep their hairline ring rule (TASK-679, load-bearing)', () => {
@@ -5593,6 +5610,78 @@ describe('Connect lifted from the d215 frame (R05)', () => {
});
});
+describe('Your team lifted from the d215 frame (R06)', () => {
+ const v2 = read('../v2.css');
+ const teamCss = read('../components/V2YourTeamPage.css');
+ const team = read('../components/V2YourTeamPage.tsx');
+
+ test('the page imports its own sheet and v2.css carries no Your team rule', () => {
+ expect(team).toContain("import './V2YourTeamPage.css';");
+ // `v2-team__`, `v2-team-card` and the bare root; `v2-teammate` is
+ // V2BringTeammate's own surface (V2BringTeammate.css), not this page's.
+ expect(v2).not.toMatch(/\.v2-team(__|-card|[\s,{:.])/);
+ });
+
+ // The keys and the avatar mark paint a box-shadow. Class-only selectors
+ // (0,2,0) leave `.v2-root button:focus-visible` (0,2,1) free to paint the
+ // focus ring over it; a `button.` prefix would tie it and, loading later,
+ // erase the ring.
+ test('the keyed controls stay class-only so the shared focus ring outranks their shadow', () => {
+ expect(teamCss).not.toMatch(/button\.v2-team(__byo|__hire|-card__answer|-card__talk|-card__mark)/);
+ expect(selectorRuleBody(teamCss, '\n.v2-team .v2-team__byo, .v2-team .v2-team-card__talk {')).toContain('box-shadow: var(--v2-key-inset)');
+ });
+
+ test('on a phone, every drawn act and the avatar mark are 44px', () => {
+ const phone = mediaBlockContaining(teamCss, '.v2-team .v2-team__grid');
+ expect(selectorRuleBody(phone, '.v2-team .v2-team__byo, .v2-team .v2-team__hire, ')).toContain('height: 44px');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__mark', { within: phone })).toContain('width: 44px');
+ expect(ruleBody(teamCss, '.v2-team .v2-team-card__mark', { within: phone })).toContain('height: 44px');
+ });
+
+ // Deviation from the frame: the two text links (Have an invitation code?,
+ // 20px; Connected agents, 16px) are not drawn. On a phone an invisible ring
+ // 14px above and below makes each hit area at least 44px.
+ test('on a phone, the text links gain a 44px hit ring', () => {
+ expect(ruleBody(teamCss, '.v2-team button.v2-team__link')).toContain('position: relative');
+ const phone = mediaBlockContaining(teamCss, '.v2-team button.v2-team__link::after');
+ const ring = ruleBody(teamCss, '.v2-team button.v2-team__link::after', { within: phone });
+ expect(ring).toContain('content: ""');
+ expect(ring).toContain('position: absolute');
+ expect(ring).toContain('inset: -14px 0');
+ });
+
+ // Undrawn controls in the phone block that follows the base rules: the code
+ // input matches its 44px buttons, and the Connected agents ring (14px below
+ // the link) clears the first chip row.
+ test('on a phone, the code input is 44px and the internal ring clears the chips', () => {
+ const phone = mediaBlockContaining(teamCss, '.v2-team .v2-team__redeem-input');
+ expect(ruleBody(teamCss, '.v2-team .v2-team__redeem-input', { within: phone })).toContain('height: 44px');
+ expect(ruleBody(teamCss, '.v2-team .v2-team__internal', { within: phone })).toContain('gap: 14px');
+ // The override must come after the base rules or the base wins.
+ expect(teamCss.indexOf(phone)).toBeGreaterThan(teamCss.indexOf('.v2-team .v2-team__redeem-input {'));
+ expect(teamCss.indexOf(phone)).toBeGreaterThan(teamCss.indexOf('.v2-team .v2-team__internal {'));
+ });
+
+ // The v2.css :lang(zh) reset (TASK-055) scans only v2.css, so the entries
+ // that left it with this page are held here: every negative-tracking
+ // selector in the sheet has a zh reset in the same sheet.
+ test('zh-CN: every negative-tracking Your team title is reset under :lang(zh)', () => {
+ const negative = [...teamCss.matchAll(/\n([^\n{}]+) \{[^}]*letter-spacing:\s*-[^;]+;/g)]
+ .flatMap((m) => m[1].split(',').map((sel) => sel.trim()))
+ .filter(Boolean);
+ expect(negative).toEqual([
+ '.v2-team .v2-team__title',
+ '.v2-team .v2-team-card__name',
+ '.v2-team .v2-team__empty-title',
+ ]);
+ const start = teamCss.indexOf('.v2-root:lang(zh) .v2-team .v2-team__title');
+ expect(start).toBeGreaterThan(-1);
+ const reset = teamCss.slice(start, teamCss.indexOf('}', start));
+ expect(reset).toContain('letter-spacing: 0');
+ for (const sel of negative) expect(reset).toContain(`.v2-root:lang(zh) ${sel}`);
+ });
+});
+
describe('chat sheets lifted out of v2.css (S3)', () => {
const chatSheets = ['chat-message', 'chat-pane', 'chat-composer', 'chat-inspector', 'chat-states']
.map((name) => read(`../chat/${name}.css`));
diff --git a/frontend/src/v2/__tests__/v2-type-floors.test.ts b/frontend/src/v2/__tests__/v2-type-floors.test.ts
index 11260e233..736ee6d47 100644
--- a/frontend/src/v2/__tests__/v2-type-floors.test.ts
+++ b/frontend/src/v2/__tests__/v2-type-floors.test.ts
@@ -27,8 +27,8 @@ import { readV2Css } from '../__testkit__/v2Css';
*/
// v2.css plus the surface files lifted out of it (D223): the chat sheets V2App
// loads, and the per-surface sheets their components import (R04 BYO, S4 board,
-// R05 Connect).
-const CSS = [readV2Css(), ...['../components/V2AgentBYO.css', '../components/V2PodBoard.css', '../components/V2ConnectPage.css']
+// R05 Connect, R06 Your team).
+const CSS = [readV2Css(), ...['../components/V2AgentBYO.css', '../components/V2PodBoard.css', '../components/V2ConnectPage.css', '../components/V2YourTeamPage.css']
.map((rel) => fs.readFileSync(path.join(__dirname, rel), 'utf8'))]
.join('\n');
diff --git a/frontend/src/v2/components/V2YourTeamPage.css b/frontend/src/v2/components/V2YourTeamPage.css
new file mode 100644
index 000000000..61bc3c78c
--- /dev/null
+++ b/frontend/src/v2/components/V2YourTeamPage.css
@@ -0,0 +1,108 @@
+/* Your team (/v2/agents), lifted from the d215 frame (R06):
+ docs/design/artboards/d215/index.html, section s-team, frame "team" at 1440
+ and 390. The rules below are the frame's own, same selectors and values; only
+ the tokens are translated to --v2-* (so light, dark and auto switch together)
+ and the frame's `.app.m` phone rules sit in the page's 760px phone query.
+ Scoped to .v2-team, the page's root. */
+.v2-team { display: flex; flex-direction: column; gap: 20px; max-width: 1100px; margin: 0 auto; }
+.v2-team .v2-team__head { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 44px; }
+.v2-team .v2-team__heading { display: flex; align-items: baseline; gap: 12px; min-width: 0; }
+.v2-team .v2-team__title { margin: 0; font: 800 24px/1 var(--v2-font-display); letter-spacing: -0.03em; color: var(--v2-text-primary); }
+.v2-team .v2-team__meta { font: 500 12px/16px var(--v2-font-mono); color: var(--v2-text-muted); white-space: nowrap; }
+.v2-team .v2-team__actions { display: flex; gap: 8px; flex-shrink: 0; }
+.v2-team .v2-team__byo, .v2-team .v2-team__hire, .v2-team .v2-team-card__answer, .v2-team .v2-team-card__talk { all: unset; box-sizing: border-box; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; height: 34px; padding: 0 13px; border-radius: 10px; font: 600 13px/1 var(--v2-font); white-space: nowrap; }
+.v2-team .v2-team__hire, .v2-team .v2-team-card__answer { color: var(--v2-on-fill); background: linear-gradient(180deg, var(--v2-fill-a), var(--v2-fill-b)); border: 1px solid var(--v2-fill-edge); }
+.v2-team .v2-team__byo, .v2-team .v2-team-card__talk { color: var(--v2-text-primary); background: linear-gradient(180deg, var(--v2-key-a), var(--v2-key-b)); border: 1px solid var(--v2-key-edge); box-shadow: var(--v2-key-inset); }
+.v2-team .v2-team__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
+.v2-team .v2-team-card { display: flex; flex-direction: column; gap: 10px; min-width: 0; padding: 16px; border: 1px solid var(--v2-border-soft); border-radius: 14px; background: var(--v2-surface); }
+/* The one that needs you: a ring, never a tinted background (state grammar §3). */
+.v2-team .v2-team-card--needsYou { border: 2px solid var(--v2-accent); padding: 15px; }
+.v2-team .v2-team-card--own { border: 1px dashed var(--v2-border); background: transparent; }
+.v2-team .v2-team-card__head { display: flex; align-items: center; gap: 12px; min-width: 0; }
+.v2-team .v2-team-card__mark { all: unset; box-sizing: border-box; cursor: pointer; flex: none; display: grid; place-items: center; width: 40px; height: 40px; border-radius: 10px; overflow: hidden; box-shadow: 0 0 0 1px var(--v2-av-ring); font: 600 12px/1 var(--v2-font); letter-spacing: 0.02em; text-transform: uppercase; }
+.v2-team .v2-team-card__mark .v2-avatar { width: 100%; height: 100%; border: 0; border-radius: inherit; box-shadow: none; }
+.v2-team .v2-team-card__mark img, .v2-team .v2-team-card__mark svg { display: block; width: 100%; height: 100%; object-fit: cover; }
+.v2-team .v2-team-card__mark--needsYou { background: var(--v2-accent); color: var(--v2-on-accent); }
+.v2-team .v2-team-card__mark--working { background: var(--v2-text-primary); color: var(--v2-surface); }
+.v2-team .v2-team-card__mark--idle { background: var(--v2-border-soft); color: var(--v2-text-secondary); }
+.v2-team .v2-team-card__title { min-width: 0; }
+/* A primary identifier never one-line-ellipsizes (craft audit rule 1, #568):
+ two lines, then clamp; min-width 0 so the grid column can shrink under it. */
+.v2-team .v2-team-card__name { font: 700 18px/1.2 var(--v2-font-display); letter-spacing: -0.02em; color: var(--v2-text-primary); overflow-wrap: anywhere; white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-width: 0; }
+.v2-team .v2-team-card__status { margin-top: 3px; font: 500 11.5px/16px var(--v2-font-mono); color: var(--v2-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.v2-team .v2-team-card__status--needsYou, .v2-team .v2-team-card__status--working { color: var(--v2-accent-text); }
+/* The ask is the line's content: it wraps, it never ellipsizes (66246 (3)). */
+.v2-team .v2-team-card__status--needsYou { white-space: normal; overflow: visible; text-overflow: clip; }
+.v2-team .v2-team-card__status--working .v2-team-card__dot { animation: v2-team-pulse 1.6s ease-in-out infinite; }
+@keyframes v2-team-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
+/* Where the seat runs: a cloud (Commonly) or a laptop (your computer), top-right. */
+.v2-team .v2-team-card__where { display: inline-flex; flex: none; margin-left: auto; align-self: flex-start; color: var(--v2-text-muted); }
+.v2-team .v2-team-card__desc { margin: 0; font-size: 13px; line-height: 18px; color: var(--v2-text-secondary); }
+.v2-team .v2-team-card__pods { display: flex; flex-wrap: wrap; gap: 6px; }
+.v2-team .v2-team-card__pod { display: inline-block; max-width: 100%; height: 24px; padding: 0 8px; border-radius: 7px; font: 500 11.5px/24px var(--v2-font-mono); color: var(--v2-text-secondary); background: var(--v2-surface); box-shadow: inset 0 0 0 1px var(--v2-border-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+/* Hosted meter: the server's count against the server's cap, as a bar and a mono line. */
+.v2-team .v2-team-card__meter { display: flex; align-items: center; gap: 10px; }
+.v2-team .v2-team-card__meter-track { flex: 1; min-width: 0; height: 4px; border-radius: 2px; background: var(--v2-border-soft); overflow: hidden; }
+.v2-team .v2-team-card__meter-fill { display: block; height: 100%; background: var(--v2-accent); }
+.v2-team .v2-team-card__meter-text { font: 500 11.5px/16px var(--v2-font-mono); color: var(--v2-text-muted); white-space: nowrap; }
+.v2-team .v2-team-card__command { display: block; padding: 10px 12px; border-radius: 10px; background: var(--v2-bg-subtle); box-shadow: inset 0 0 0 1px var(--v2-border-soft); font: 500 14px/20px var(--v2-font-mono); color: var(--v2-text-primary); white-space: pre-wrap; overflow-wrap: anywhere; }
+.v2-team .v2-team-card__actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px; }
+@media (max-width: 760px) {
+ .v2-team { gap: 14px; }
+ .v2-team .v2-team__head { flex-direction: column; align-items: stretch; gap: 12px; }
+ /* The title never wraps beside its meta: stack them (66246 (1)). */
+ .v2-team .v2-team__heading { flex-direction: column; align-items: flex-start; gap: 6px; }
+ .v2-team .v2-team__meta { white-space: normal; }
+ .v2-team .v2-team__actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
+ .v2-team .v2-team__byo, .v2-team .v2-team__hire, .v2-team .v2-team-card__answer, .v2-team .v2-team-card__talk { height: 44px; font-size: 14px; }
+ .v2-team .v2-team__grid { grid-template-columns: repeat(1, minmax(0, 1fr)); gap: 10px; }
+ .v2-team .v2-team-card__mark { width: 44px; height: 44px; border-radius: 11px; }
+ .v2-team .v2-team-card__actions > button { flex: 1 1 0; }
+ /* Not in the frame: the text links (Have an invitation code? at 20px,
+ Connected agents at 16px) get an invisible ring 14px above and below, so
+ each hit area is at least 44px tall. */
+ .v2-team button.v2-team__link::after { content: ""; position: absolute; inset: -14px 0; }
+}
+
+/* Live behaviour and states the frame does not draw, kept working with prod's values. */
+.v2-team .v2-team__byo:hover, .v2-team .v2-team-card__talk:hover { background: var(--v2-surface-hover); }
+.v2-team .v2-team__hire:disabled { opacity: 0.5; cursor: default; }
+.v2-team .v2-team-card__talk:disabled { color: var(--v2-text-placeholder); cursor: default; }
+@media (prefers-reduced-motion: reduce) {
+ .v2-team .v2-team-card__status--working .v2-team-card__dot { animation: none; }
+}
+.v2-team .v2-team__notice { font-size: 13px; color: var(--v2-text-secondary); }
+.v2-team .v2-team__notice--ok { color: var(--v2-text-primary); }
+.v2-team .v2-team__redeem { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
+.v2-team .v2-team__redeem-input { height: 36px; padding: 0 12px; border: 1px solid var(--v2-border); border-radius: 4px; font: 400 14px/20px var(--v2-font); }
+.v2-team .v2-team__redeem-error { color: var(--v2-text-primary); font-size: 13px; }
+.v2-team button.v2-team__link { position: relative; padding: 0; border: 0; background: transparent; color: var(--v2-accent-text); font: 500 13px/20px var(--v2-font); cursor: pointer; }
+.v2-team .v2-team__error { font-size: 13px; color: var(--v2-text-primary); border: 1px solid var(--v2-border); border-radius: 4px; padding: 10px 12px; }
+.v2-team .v2-team__empty { border: 1px dashed var(--v2-border); border-radius: 4px; padding: 20px; }
+.v2-team .v2-team__empty-title { font: 700 var(--v2-fs-feature)/1.2 var(--v2-font-display); letter-spacing: -0.02em; margin-bottom: 6px; }
+.v2-team .v2-team__empty-text { font-size: 13px; color: var(--v2-text-secondary); }
+.v2-team .v2-team__internal { display: flex; flex-direction: column; gap: 8px; }
+.v2-team button.v2-team__internal-toggle { font: 500 11px/16px var(--v2-font-mono); color: var(--v2-text-muted); }
+.v2-team .v2-team__internal-list { display: flex; gap: 6px; flex-wrap: wrap; }
+.v2-team .v2-team-card__fix {
+ display: block;
+ margin-top: 4px;
+ font-family: var(--v2-font-mono, ui-monospace, monospace);
+ font-size: 12px;
+ color: var(--v2-text-secondary);
+ overflow-wrap: anywhere;
+ user-select: all;
+}
+/* Phone overrides for the undrawn rules above; they sit after them so they win
+ at equal specificity. The code input matches the 44px buttons beside it, and
+ the gap keeps the Connected agents ring (14px below the link) off the first
+ chip row. */
+@media (max-width: 760px) {
+ .v2-team .v2-team__redeem-input { height: 44px; }
+ .v2-team .v2-team__internal { gap: 14px; }
+}
+/* CJK never takes negative letter-spacing (TASK-055); these entries left the
+ v2.css :lang(zh) reset list with the rest of the page. */
+.v2-root:lang(zh) .v2-team .v2-team__title,
+.v2-root:lang(zh) .v2-team .v2-team-card__name,
+.v2-root:lang(zh) .v2-team .v2-team__empty-title { letter-spacing: 0; }
diff --git a/frontend/src/v2/components/V2YourTeamPage.tsx b/frontend/src/v2/components/V2YourTeamPage.tsx
index 850b1a0c7..3cddc21fb 100644
--- a/frontend/src/v2/components/V2YourTeamPage.tsx
+++ b/frontend/src/v2/components/V2YourTeamPage.tsx
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import axios from 'axios';
import { useAuth } from '../../context/AuthContext';
import V2Avatar from './V2Avatar';
+import './V2YourTeamPage.css';
import { AgentConnection, connectionLabel } from '../utils/agentConnection';
/**
diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css
index 1a18745b8..994aab8a3 100644
--- a/frontend/src/v2/v2.css
+++ b/frontend/src/v2/v2.css
@@ -2153,16 +2153,6 @@ body.modern-ui.v2-canvas {
flex-shrink: 0;
}
-.v2-team-card__fix {
- display: block;
- margin-top: 4px;
- font-family: var(--v2-font-mono, ui-monospace, monospace);
- font-size: 12px;
- color: var(--v2-text-secondary);
- overflow-wrap: anywhere;
- user-select: all;
-}
-
.v2-chat__new-pod-text {
max-width: 520px;
margin-top: 4px;
@@ -6393,9 +6383,6 @@ body.modern-ui.v2-canvas {
.v2-root:lang(zh) .v2-rail__brand,
.v2-root:lang(zh) .v2-tools__count strong,
.v2-root:lang(zh) .v2-artifacts__title,
-.v2-root:lang(zh) .v2-team__title,
-.v2-root:lang(zh) .v2-team-card__name,
-.v2-root:lang(zh) .v2-team__empty-title,
.v2-root:lang(zh) .v2-pods__title,
.v2-root:lang(zh) .v2-pods__page-title,
.v2-root:lang(zh) .v2-chat__title,
@@ -7885,129 +7872,6 @@ body.modern-ui.v2-canvas {
.v2-activity__start-act { grid-column: 1 / -1; }
.v2-root .v2-activity__start-act button { width: 100%; min-height: 44px; }
}
-/* ---------------------------------------------------------------------------
- Your Team — direction C, Signal (docs/design/signal-identity.md §4 "Cards in a
- grid"). One card per agent; the mark's colour is the state; the one that
- needs you carries the 2px cobalt ring. Ink acts (Hire, Answer); secondaries
- are bordered; status is mono because it is data.
- --------------------------------------------------------------------------- */
-.v2-team { display: flex; flex-direction: column; gap: 24px; }
-.v2-team__head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
-.v2-team__heading { display: flex; align-items: baseline; gap: 12px; min-width: 0; }
-.v2-team__title { margin: 0; font: 700 32px/1.1 var(--v2-font-display); letter-spacing: -0.03em; color: var(--v2-text-primary); }
-.v2-team__meta { font-size: 13px; color: var(--v2-text-muted); white-space: nowrap; }
-.v2-team__actions { display: flex; gap: 8px; flex-shrink: 0; }
-.v2-root button.v2-team__byo { height: 36px; padding: 0 14px; border: 1px solid var(--v2-border); border-radius: 4px; background: var(--v2-surface); color: var(--v2-text-primary); font: 600 13px/1 var(--v2-font); cursor: pointer; }
-.v2-root button.v2-team__byo:hover { background: var(--v2-surface-hover); }
-/* Ink acts (signal-identity §2): Hire is the page's one filled control. */
-.v2-root button.v2-team__hire { height: 36px; padding: 0 14px; border: 1px solid var(--v2-ink); border-radius: 4px; background: var(--v2-ink); color: var(--v2-on-ink); font: 600 13px/1 var(--v2-font); cursor: pointer; }
-.v2-root button.v2-team__hire:disabled { opacity: 0.5; cursor: default; }
-.v2-team__notice { font-size: 13px; color: var(--v2-text-secondary); }
-.v2-team__notice--ok { color: var(--v2-text-primary); }
-.v2-team__redeem { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
-.v2-team__redeem-input { height: 36px; padding: 0 12px; border: 1px solid var(--v2-border); border-radius: 4px; font: 400 14px/20px var(--v2-font); }
-.v2-team__redeem-error { color: var(--v2-text-primary); font-size: 13px; }
-.v2-root button.v2-team__link { padding: 0; border: 0; background: transparent; color: var(--v2-accent-text); font: 500 13px/20px var(--v2-font); cursor: pointer; }
-.v2-team__error { font-size: 13px; color: var(--v2-text-primary); border: 1px solid var(--v2-border); border-radius: 4px; padding: 10px 12px; }
-.v2-team__empty { border: 1px dashed var(--v2-border); border-radius: 4px; padding: 20px; }
-.v2-team__empty-title { font: 700 var(--v2-fs-feature)/1.2 var(--v2-font-display); letter-spacing: -0.02em; margin-bottom: 6px; }
-.v2-team__empty-text { font-size: 13px; color: var(--v2-text-secondary); }
-
-.v2-team__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
-.v2-team-card {
- display: flex;
- flex-direction: column;
- gap: 10px;
- padding: 16px;
- border: 1px solid var(--v2-border);
- border-radius: 4px;
- background: var(--v2-surface);
- min-width: 0;
-}
-/* The one that needs you: a ring, never a tinted background (state grammar §3). */
-.v2-team-card--needsYou { border: 2px solid var(--v2-accent); padding: 15px; }
-.v2-team-card--own { border-style: dashed; }
-.v2-team-card__head { display: flex; align-items: center; gap: 10px; min-width: 0; }
-.v2-root button.v2-team-card__mark {
- width: 40px;
- height: 40px;
- flex-shrink: 0;
- display: grid;
- place-items: center;
- border: 0;
- border-radius: 4px;
- font: 600 12px/1 var(--v2-font);
- letter-spacing: 0.02em;
- text-transform: uppercase;
- cursor: pointer;
- overflow: hidden;
- padding: 0;
-}
-.v2-root button.v2-team-card__mark img { width: 100%; height: 100%; object-fit: cover; }
-.v2-root button.v2-team-card__mark .v2-avatar { width: 100%; height: 100%; border: 0; border-radius: inherit; }
-.v2-root button.v2-team-card__mark--needsYou { background: var(--v2-accent); color: var(--v2-bg); }
-.v2-root button.v2-team-card__mark--working { background: var(--v2-text-primary); color: var(--v2-bg); }
-.v2-root button.v2-team-card__mark--idle { background: var(--v2-border-soft); color: #4f5c6e; }
-.v2-team-card__title { min-width: 0; }
-/* A primary identifier never one-line-ellipsizes (craft audit rule 1, #568):
- two lines, then clamp; min-width 0 so the grid column can shrink under it. */
-.v2-team-card__name {
- font: 700 18px/1.2 var(--v2-font-display);
- letter-spacing: -0.02em;
- color: var(--v2-text-primary);
- overflow-wrap: anywhere;
- white-space: normal;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
- min-width: 0;
-}
-.v2-team-card__status { margin-top: 2px; font: 500 11px/16px var(--v2-font-mono); color: var(--v2-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
-.v2-team-card__status--needsYou,
-.v2-team-card__status--working { color: var(--v2-accent-text); }
-/* The ask is the line's content — it wraps, it never ellipsizes (66246 (3)). */
-.v2-team-card__status--needsYou { white-space: normal; overflow: visible; text-overflow: clip; }
-.v2-team-card__status--working .v2-team-card__dot { animation: v2-team-pulse 1.6s ease-in-out infinite; }
-@keyframes v2-team-pulse {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.35; }
-}
-@media (prefers-reduced-motion: reduce) {
- .v2-team-card__status--working .v2-team-card__dot { animation: none; }
-}
-.v2-team-card__desc { margin: 0; font-size: 13px; line-height: 18px; color: var(--v2-text-secondary); }
-.v2-team-card__pods { display: flex; gap: 6px; flex-wrap: wrap; }
-.v2-team-card__pod { font: 500 11px/16px var(--v2-font-mono); padding: 2px 6px; border: 1px solid var(--v2-border); border-radius: 4px; color: var(--v2-text-secondary); }
-.v2-team-card__command { display: block; padding: 8px 10px; border-radius: 4px; background: #101828; color: #fff; font: 500 14px/20px var(--v2-font-mono); white-space: pre-wrap; overflow-wrap: anywhere; }
-.v2-team-card__actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px; }
-/* Where the seat runs: a cloud (Commonly) or a laptop (your computer), top-right. */
-.v2-team-card__where { display: inline-flex; flex: none; margin-left: auto; align-self: flex-start; color: var(--v2-text-muted); }
-/* Hosted meter: the server's count against the server's cap, as a bar and a mono line. */
-.v2-team-card__meter { display: flex; align-items: center; gap: 10px; }
-.v2-team-card__meter-track { flex: 1; min-width: 0; height: 4px; border-radius: 2px; background: var(--v2-border-soft); overflow: hidden; }
-.v2-team-card__meter-fill { display: block; height: 100%; background: var(--v2-accent); }
-.v2-team-card__meter-text { font: 500 11px/16px var(--v2-font-mono); color: var(--v2-text-muted); white-space: nowrap; }
-.v2-root button.v2-team-card__answer { height: 32px; padding: 0 12px; border: 1px solid var(--v2-ink); border-radius: 4px; background: var(--v2-ink); color: var(--v2-on-ink); font: 600 13px/1 var(--v2-font); cursor: pointer; }
-.v2-root button.v2-team-card__talk { height: 32px; padding: 0 12px; border: 1px solid var(--v2-border); border-radius: 4px; background: var(--v2-surface); color: var(--v2-text-primary); font: 600 13px/1 var(--v2-font); cursor: pointer; }
-.v2-root button.v2-team-card__talk:hover { background: var(--v2-surface-hover); }
-.v2-root button.v2-team-card__talk:disabled { color: var(--v2-text-placeholder); cursor: default; }
-.v2-team__internal { display: flex; flex-direction: column; gap: 8px; }
-.v2-root button.v2-team__internal-toggle { font: 500 11px/16px var(--v2-font-mono); color: var(--v2-text-muted); }
-.v2-team__internal-list { display: flex; gap: 6px; flex-wrap: wrap; }
-
-@media (max-width: 760px) {
- .v2-team__head { flex-direction: column; align-items: flex-start; }
- /* The title never wraps beside its meta: stack them (66246 (1)). */
- .v2-team__heading { flex-direction: column; align-items: flex-start; gap: 2px; }
- .v2-team__meta { white-space: normal; }
- .v2-team__grid { grid-template-columns: repeat(1, minmax(0, 1fr)); }
- .v2-root button.v2-team-card__answer,
- .v2-root button.v2-team-card__talk,
- .v2-root button.v2-team__byo,
- .v2-root button.v2-team__hire { min-height: 44px; }
-}
-
/* ---------------------------------------------------------------------------
Artifacts — direction C, PR 5 (ScaleArtifacts.dc.html).
Every file and page shared in your pods. Display head + muted meta; two