diff --git a/docs/design/ui-contract/20260717-answer-arrival/after-answer-dark-desktop-scrolled.png b/docs/design/ui-contract/20260717-answer-arrival/after-answer-dark-desktop-scrolled.png new file mode 100644 index 000000000..e0f85ca74 Binary files /dev/null and b/docs/design/ui-contract/20260717-answer-arrival/after-answer-dark-desktop-scrolled.png differ diff --git a/docs/design/ui-contract/20260717-answer-arrival/after-answer-dark-desktop.png b/docs/design/ui-contract/20260717-answer-arrival/after-answer-dark-desktop.png new file mode 100644 index 000000000..80aa4ee2d Binary files /dev/null and b/docs/design/ui-contract/20260717-answer-arrival/after-answer-dark-desktop.png differ diff --git a/docs/design/ui-contract/20260717-answer-arrival/after-answer-dark-mobile.png b/docs/design/ui-contract/20260717-answer-arrival/after-answer-dark-mobile.png new file mode 100644 index 000000000..9292b3c07 Binary files /dev/null and b/docs/design/ui-contract/20260717-answer-arrival/after-answer-dark-mobile.png differ diff --git a/docs/design/ui-contract/20260717-answer-arrival/after-answer-light-desktop.png b/docs/design/ui-contract/20260717-answer-arrival/after-answer-light-desktop.png new file mode 100644 index 000000000..fdb30da17 Binary files /dev/null and b/docs/design/ui-contract/20260717-answer-arrival/after-answer-light-desktop.png differ diff --git a/docs/design/ui-contract/20260717-answer-arrival/manifest.json b/docs/design/ui-contract/20260717-answer-arrival/manifest.json new file mode 100644 index 000000000..80fee8f71 --- /dev/null +++ b/docs/design/ui-contract/20260717-answer-arrival/manifest.json @@ -0,0 +1,13 @@ +{ + "schema": "nodebench-ui-contract-v1", + "capturedAt": "2026-07-17T22:20:00Z", + "slice": "answer-arrival", + "app": "nodebench-ai (decision workspace answer moment, /redesign/chat?qaState=answer)", + "notes": "The answer moment joins the ember motion identity: the packet settles in on --rd-ease-settle (rd-answer-settle, 480ms) and every section eyebrow carries a 24px terracotta ember tick that sweeps in (rd-ember-sweep, 180ms delay) — the recurring motif that evidence underlines every claim. The thinking state becomes 'the thread being drawn': an ember-tipped hairline grows beneath the phase label on a 1.6s settle loop, dots recolored to ember. prefers-reduced-motion flattens all three systems. NO BEFORE PAIR EXISTS honestly: this state was previously unphotographable without a paid authenticated run — this slice also introduces the QA-gated seeded answer state (?qaState=answer under the nodebench-redesign-qa-chrome flag, sampleAnswer fixture, unreachable organically) that makes the answer moment capturable by the nightly loop and forcible by future contract states. Filmstrip: motion-frame-{40,280,980}ms.png shows the settle-rise + tick sweep mid-flight.", + "pages": [ + { "route": "/redesign/chat?qaState=answer", "viewport": { "width": 1440, "height": 900 }, "theme": "dark", "after": "after-answer-dark-desktop.png" }, + { "route": "/redesign/chat?qaState=answer", "viewport": { "width": 1440, "height": 900 }, "theme": "dark", "after": "after-answer-dark-desktop-scrolled.png" }, + { "route": "/redesign/chat?qaState=answer", "viewport": { "width": 1440, "height": 900 }, "theme": "light", "after": "after-answer-light-desktop.png" }, + { "route": "/redesign/chat?qaState=answer", "viewport": { "width": 390, "height": 844 }, "theme": "dark", "after": "after-answer-dark-mobile.png" } + ] +} diff --git a/docs/design/ui-contract/20260717-answer-arrival/motion-frame-280ms.png b/docs/design/ui-contract/20260717-answer-arrival/motion-frame-280ms.png new file mode 100644 index 000000000..e0f85ca74 Binary files /dev/null and b/docs/design/ui-contract/20260717-answer-arrival/motion-frame-280ms.png differ diff --git a/docs/design/ui-contract/20260717-answer-arrival/motion-frame-40ms.png b/docs/design/ui-contract/20260717-answer-arrival/motion-frame-40ms.png new file mode 100644 index 000000000..1b3e9e673 Binary files /dev/null and b/docs/design/ui-contract/20260717-answer-arrival/motion-frame-40ms.png differ diff --git a/docs/design/ui-contract/20260717-answer-arrival/motion-frame-980ms.png b/docs/design/ui-contract/20260717-answer-arrival/motion-frame-980ms.png new file mode 100644 index 000000000..e0f85ca74 Binary files /dev/null and b/docs/design/ui-contract/20260717-answer-arrival/motion-frame-980ms.png differ diff --git a/docs/design/ui-contract/20260717-ember-landing/after-decision-workspace-light.png b/docs/design/ui-contract/20260717-ember-landing/after-decision-workspace-light.png new file mode 100644 index 000000000..bfbd189e5 Binary files /dev/null and b/docs/design/ui-contract/20260717-ember-landing/after-decision-workspace-light.png differ diff --git a/docs/design/ui-contract/20260717-ember-landing/after-decision-workspace-mobile-light.png b/docs/design/ui-contract/20260717-ember-landing/after-decision-workspace-mobile-light.png new file mode 100644 index 000000000..fe57f0f21 Binary files /dev/null and b/docs/design/ui-contract/20260717-ember-landing/after-decision-workspace-mobile-light.png differ diff --git a/docs/design/ui-contract/20260717-ember-landing/after-decision-workspace-mobile.png b/docs/design/ui-contract/20260717-ember-landing/after-decision-workspace-mobile.png new file mode 100644 index 000000000..22e18036f Binary files /dev/null and b/docs/design/ui-contract/20260717-ember-landing/after-decision-workspace-mobile.png differ diff --git a/docs/design/ui-contract/20260717-ember-landing/after-decision-workspace.png b/docs/design/ui-contract/20260717-ember-landing/after-decision-workspace.png new file mode 100644 index 000000000..1bc1b1ef3 Binary files /dev/null and b/docs/design/ui-contract/20260717-ember-landing/after-decision-workspace.png differ diff --git a/docs/design/ui-contract/20260717-ember-landing/before-decision-workspace-light.png b/docs/design/ui-contract/20260717-ember-landing/before-decision-workspace-light.png new file mode 100644 index 000000000..490c398a2 Binary files /dev/null and b/docs/design/ui-contract/20260717-ember-landing/before-decision-workspace-light.png differ diff --git a/docs/design/ui-contract/20260717-ember-landing/before-decision-workspace-mobile-light.png b/docs/design/ui-contract/20260717-ember-landing/before-decision-workspace-mobile-light.png new file mode 100644 index 000000000..16c5ee01f Binary files /dev/null and b/docs/design/ui-contract/20260717-ember-landing/before-decision-workspace-mobile-light.png differ diff --git a/docs/design/ui-contract/20260717-ember-landing/before-decision-workspace-mobile.png b/docs/design/ui-contract/20260717-ember-landing/before-decision-workspace-mobile.png new file mode 100644 index 000000000..cc3146877 Binary files /dev/null and b/docs/design/ui-contract/20260717-ember-landing/before-decision-workspace-mobile.png differ diff --git a/docs/design/ui-contract/20260717-ember-landing/before-decision-workspace.png b/docs/design/ui-contract/20260717-ember-landing/before-decision-workspace.png new file mode 100644 index 000000000..ff2676ba5 Binary files /dev/null and b/docs/design/ui-contract/20260717-ember-landing/before-decision-workspace.png differ diff --git a/docs/design/ui-contract/20260717-ember-landing/manifest.json b/docs/design/ui-contract/20260717-ember-landing/manifest.json new file mode 100644 index 000000000..3e125bedd --- /dev/null +++ b/docs/design/ui-contract/20260717-ember-landing/manifest.json @@ -0,0 +1,13 @@ +{ + "schema": "nodebench-ui-contract-v1", + "capturedAt": "2026-07-17T21:44:53Z", + "slice": "ember-landing", + "app": "nodebench-ai (decision workspace landing, /redesign/chat)", + "notes": "Signature landing composition: 'Ember threads' — abstract source nodes whose hairline threads converge past the SVG floor into the composer dock, giving form to the product truth stated by the new tagline 'Answers with receipts.' Headline lifted to display scale (clamp 28-40px, -0.02em). Motion identity: one easing (--rd-ease-settle cubic-bezier(.22,1,.36,1)), staggered thread draw-in (110ms steps, 840ms) + node bloom, then a 12s near-imperceptible drift; composer focus warms the constellation (0.5 -> 0.85 opacity). prefers-reduced-motion flattens ALL of it to the static composition — verified: computed animationName 'none' on threads and container (motion-reduced-motion-static.png). Deterministic node geometry (no randomness) keeps screenshots replay-stable. Mobile prunes the two low side nodes and halves constellation opacity. Filmstrip frames: motion-frame-{0ms,380ms,1280ms}.png. Before = feat/chip-label-reduction head; surface contracts green on both sides.", + "pages": [ + { "route": "/redesign/chat", "viewport": { "width": 1440, "height": 900 }, "theme": "dark", "before": "before-decision-workspace.png", "after": "after-decision-workspace.png" }, + { "route": "/redesign/chat", "viewport": { "width": 1440, "height": 900 }, "theme": "light", "before": "before-decision-workspace-light.png", "after": "after-decision-workspace-light.png" }, + { "route": "/redesign/chat", "viewport": { "width": 390, "height": 844 }, "theme": "dark", "before": "before-decision-workspace-mobile.png", "after": "after-decision-workspace-mobile.png" }, + { "route": "/redesign/chat", "viewport": { "width": 390, "height": 844 }, "theme": "light", "before": "before-decision-workspace-mobile-light.png", "after": "after-decision-workspace-mobile-light.png" } + ] +} diff --git a/docs/design/ui-contract/20260717-ember-landing/motion-frame-0ms.png b/docs/design/ui-contract/20260717-ember-landing/motion-frame-0ms.png new file mode 100644 index 000000000..d36d695b7 Binary files /dev/null and b/docs/design/ui-contract/20260717-ember-landing/motion-frame-0ms.png differ diff --git a/docs/design/ui-contract/20260717-ember-landing/motion-frame-1280ms.png b/docs/design/ui-contract/20260717-ember-landing/motion-frame-1280ms.png new file mode 100644 index 000000000..af6b574ee Binary files /dev/null and b/docs/design/ui-contract/20260717-ember-landing/motion-frame-1280ms.png differ diff --git a/docs/design/ui-contract/20260717-ember-landing/motion-frame-380ms.png b/docs/design/ui-contract/20260717-ember-landing/motion-frame-380ms.png new file mode 100644 index 000000000..67b082b44 Binary files /dev/null and b/docs/design/ui-contract/20260717-ember-landing/motion-frame-380ms.png differ diff --git a/docs/design/ui-contract/20260717-ember-landing/motion-reduced-motion-static.png b/docs/design/ui-contract/20260717-ember-landing/motion-reduced-motion-static.png new file mode 100644 index 000000000..7d529704a Binary files /dev/null and b/docs/design/ui-contract/20260717-ember-landing/motion-reduced-motion-static.png differ diff --git a/src/features/redesign/agent-workspace.css b/src/features/redesign/agent-workspace.css index d8cfc2793..67efd31c5 100644 --- a/src/features/redesign/agent-workspace.css +++ b/src/features/redesign/agent-workspace.css @@ -282,6 +282,7 @@ } [data-redesign] .rd-chat-empty { + position: relative; display: flex; flex: 1; flex-direction: column; @@ -293,13 +294,191 @@ padding: 28px 0; } +/* ── Ember threads — the landing's signature composition ────────────────── + Motion identity: one easing ("settle"), three durations. Draw-in runs once + on arrival (staggered, < 1s total), then the whole constellation drifts + almost imperceptibly. Composer focus warms the threads — the evidence + leans in. Reduced motion flattens everything to the static composition. */ +[data-redesign] { + --rd-ease-settle: cubic-bezier(0.22, 1, 0.36, 1); + --rd-dur-quick: 240ms; + --rd-dur-move: 480ms; + --rd-dur-enter: 840ms; +} + +[data-redesign] .rd-ember { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + color: var(--rd-ink-mute); + pointer-events: none; + z-index: 0; + opacity: 0.5; + transition: opacity var(--rd-dur-move) var(--rd-ease-settle); + animation: rd-ember-drift 12s var(--rd-ease-settle) infinite alternate; +} + +[data-redesign] .rd-chat-empty__hero, +[data-redesign] .rd-chat-empty__chips { + position: relative; + z-index: 1; +} + +[data-redesign] .rd-ember__thread { + fill: none; + stroke: currentColor; + stroke-width: 1; + opacity: 0.26; + stroke-dasharray: 1; + stroke-dashoffset: 0; + animation: rd-ember-draw var(--rd-dur-enter) var(--rd-ease-settle) both; + animation-delay: calc(var(--i) * 110ms); +} + +[data-redesign] .rd-ember__node { + fill: var(--rd-accent); + opacity: 0.85; + animation: rd-ember-node var(--rd-dur-move) var(--rd-ease-settle) both; + animation-delay: calc(var(--i) * 110ms); +} + +/* The evidence leans in when the user commits attention to the composer. */ +[data-redesign] .rd-chat-workspace:focus-within .rd-ember { + opacity: 0.85; +} + +@keyframes rd-ember-draw { + from { stroke-dashoffset: 1; } + to { stroke-dashoffset: 0; } +} + +@keyframes rd-ember-node { + from { opacity: 0; transform: scale(0.4); transform-origin: center; transform-box: fill-box; } + to { opacity: 0.85; transform: scale(1); transform-origin: center; transform-box: fill-box; } +} + +@keyframes rd-ember-drift { + from { transform: translateY(0); } + to { transform: translateY(-7px); } +} + +@media (prefers-reduced-motion: reduce) { + [data-redesign] .rd-ember, + [data-redesign] .rd-ember__thread, + [data-redesign] .rd-ember__node { + animation: none; + } +} + +/* ── Answer arrival — the threads deliver ──────────────────────────────── + The packet settles in on the same easing the landing draws with, and each + section eyebrow carries a small terracotta ember tick that sweeps in: the + recurring motif that evidence, not decoration, underlines every claim. */ +[data-redesign] .rd-answer-arrive { + animation: rd-answer-settle var(--rd-dur-move) var(--rd-ease-settle) both; +} + +[data-redesign] .rd-answer-arrive .rd-eyebrow { + position: relative; + padding-bottom: 3px; +} + +[data-redesign] .rd-answer-arrive .rd-eyebrow::after { + content: ""; + position: absolute; + left: 0; + bottom: 0; + width: 24px; + height: 1px; + background: var(--rd-accent); + transform-origin: left center; + animation: rd-ember-sweep var(--rd-dur-move) var(--rd-ease-settle) both; + animation-delay: 180ms; +} + +@keyframes rd-answer-settle { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes rd-ember-sweep { + from { transform: scaleX(0); } + to { transform: scaleX(1); } +} + +/* Thinking = the thread being drawn. A hairline grows beneath the phase + label on the settle easing, ember-tipped; the dots become embers. */ +[data-redesign] .rd-chat-thinking { + position: relative; + padding-bottom: 6px; +} + +[data-redesign] .rd-chat-thinking::after { + content: ""; + position: absolute; + left: 0; + bottom: 0; + width: 72px; + height: 1px; + background: linear-gradient(90deg, var(--rd-accent), transparent); + transform-origin: left center; + animation: rd-thinking-thread 1.6s var(--rd-ease-settle) infinite; +} + +[data-redesign] .rd-chat-thinking__dots span { + background: var(--rd-accent); +} + +@keyframes rd-thinking-thread { + 0% { transform: scaleX(0); opacity: 0.9; } + 70% { transform: scaleX(1); opacity: 0.9; } + 100% { transform: scaleX(1); opacity: 0; } +} + +@media (prefers-reduced-motion: reduce) { + [data-redesign] .rd-answer-arrive, + [data-redesign] .rd-answer-arrive .rd-eyebrow::after, + [data-redesign] .rd-chat-thinking::after { + animation: none; + } +} + +/* Narrow viewports: quieter constellation, and the two low side nodes give + their space back to the stacked chips. */ +@media (max-width: 760px) { + [data-redesign] .rd-ember { + opacity: 0.35; + } + [data-redesign] .rd-ember path:nth-of-type(5), + [data-redesign] .rd-ember path:nth-of-type(6), + [data-redesign] .rd-ember circle:nth-of-type(5), + [data-redesign] .rd-ember circle:nth-of-type(6) { + display: none; + } +} + [data-redesign] .rd-chat-empty__hero { - margin-bottom: 24px; + margin-bottom: 28px; } [data-redesign] .rd-chat-empty__h { - font-size: 24px; - line-height: 1.2; + font-size: clamp(28px, 4vw, 40px); + font-weight: 620; + letter-spacing: -0.02em; + line-height: 1.12; +} + +[data-redesign] .rd-chat-empty__tag { + margin: 10px 0 0; + color: var(--rd-ink-mute); + font-size: 15px; + letter-spacing: 0.01em; +} + +[data-redesign] .rd-chat-empty__tag em { + font-style: normal; + color: var(--rd-accent-strong); } [data-redesign] .rd-chat-empty__chips { diff --git a/src/features/redesign/components/ChatEmptyState.tsx b/src/features/redesign/components/ChatEmptyState.tsx index 86f6b9837..321694788 100644 --- a/src/features/redesign/components/ChatEmptyState.tsx +++ b/src/features/redesign/components/ChatEmptyState.tsx @@ -85,11 +85,70 @@ const STARTERS = [ { icon: STARTER_ICONS.watch, title: "What's new in my watchlist?", prompt: "Summarize what changed in my watchlist over the last 7 days. Group by signal class." }, ]; +/** + * "Ember threads" — the landing's signature composition. Abstract source + * nodes whose threads converge into the composer below: the product truth + * (answers drawing on evidence) as form, not copy. Deterministic geometry + * (no randomness — replay-stable screenshots), aria-hidden decoration, and + * the draw-in/drift motion lives entirely in CSS so prefers-reduced-motion + * can flatten it to the static composition. + */ +const EMBER_NODES: Array<{ x: number; y: number }> = [ + { x: 96, y: 96 }, + { x: 232, y: 34 }, + { x: 598, y: 48 }, + { x: 756, y: 128 }, + { x: 152, y: 296 }, + { x: 694, y: 312 }, +]; +// Past the viewBox floor on purpose: threads clip at the SVG's bottom edge +// still converging, so they read as running INTO the composer dock below +// rather than dying in the empty band. +const EMBER_FOCUS = { x: 420, y: 560 }; + +function EmberThreads() { + return ( + + ); +} + export function ChatEmptyState({ onPick, starters = STARTERS }: ChatEmptyStateProps) { return (
+ Answers with receipts. +