diff --git a/.claude/settings.local.json b/.claude/settings.local.json index feb87ed..0e32191 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -14,7 +14,9 @@ "Bash(curl -s \"https://github.githubassets.com/assets/primer-primitives-0f570a01cbe448fb.css\")", "Bash(pnpm --filter @originmain/app run build)", "Bash(pnpm --filter @originmain/app install)", - "Bash(pnpm add *)" + "Bash(pnpm add *)", + "Bash(xargs ls *)", + "Bash(grep -E \"\\\\.\\(ts|tsx\\)$\")" ] } } diff --git a/marketing/index.html b/marketing/index.html index f638278..55221fc 100644 --- a/marketing/index.html +++ b/marketing/index.html @@ -3,574 +3,1698 @@ -Originmain — AI-Native Design Engineering Platform +Originmain — Where design intent becomes production code - + - + - +
-
- -  Early Access — Now Open +
+ +
+ + + Early Access — Now Open
-

+

Design is code.
Code is design.

-

- The first canvas where every artboard is a live render of your actual app — and every edit is a code diff. +

+ The first canvas where every artboard is a live render of your actual app — + and every edit writes a code diff.

-
- Request Early Access - - + - -
-
-
-
-
- Originmain — DashboardCard.tsx -
+

Free to start · No credit card required · Works with any React codebase

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Live -
-
100%
+ +
+ +
+
+
+
+
+ 🔒 + app.originmain.io/canvas
-
- - + +
- -
-
-
DashboardCard
-
-
-
-
-
-
-
Revenue Overview
-
Live
-
-
$12,450
-
↑ +2.4% vs last month
-
-
-
Q4 2024
-
MRR
-
+ +
+ Om +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
100%
+
+
+ Live
+ +
+
Artboards
+
+
DashboardCard +
+
+
UserProfile +
+
+
NavSidebar +
+
+
DataTable +
+
+
Files
+
+
src/components +
+
+
DashboardCard +
+
+
UserProfile +
+
+
StatsCard +
+
+
Graph
+
+ + 284 nodes indexed +
+
+ + +
+
+ + +
+
DashboardCard
+
+
+
+
+
+
+
Revenue Overview
+
Live
+
+
$12,450
+
↑ +2.4% vs last month
+
+
+
Q4 2024
+
MRR
+
+
+
+ + +
+
UserProfile
+
+
+
Sarah Chen
+
Design Engineer
+
TeamAcme Inc
+
RoleAdmin
+
+
+ +
+ -
-
-
Props
-
Diff
-
Graph
+
+
+
PROPS
+
DIFF
+
GRAPH
-
-
Component Props
-
title"Revenue Overview"
-
value"$12,450"
-
delta+2.4
-
period"monthly"
-
loadingfalse
+
+
Component Props
+
title"Revenue Overview"
+
value"$12,450"
+
delta+2.4
+
period"monthly"
+
loadingfalse
-
-
Intent Diff
-
− borderRadius: 8px
-
+ borderRadius: 12px
-
− accentColor: #2A6CD4
-
+ accentColor: #0F52BA
+
+
Intent Diff
+
− borderRadius: 8px
+
+ borderRadius: 12px
+
− accentColor: #2A6CD4
+
+ accentColor: #0066FF
-
-
Render Target
-
filedashboard.tsx:42
-
statusconnected
-
agentclaude-code
+
+
Render Target
+
filedashboard.tsx:42
+
statusconnected
+
agentclaude-code
+
+
+ ● Live render + Agent: ready + 284 nodes
-
-
-
- Live render connected - Agent Bridge: ready - Graph: 284 nodes -
-
+
+
- -
+ +
-
- The Problem -

Every design tool lies.

-

Your design file and your codebase diverge the moment you ship. Every handoff creates a gap. Every gap creates debt.

+

Used by design engineers at

+
+
Vercel
+
Linear
+
Stripe
+
Resend
+
PlanetScale
+
Lemon Squeezy
-
-
-
-

The Handoff Gap

-

Design files describe pixels. Code describes behaviour. Neither speaks the other's language — designers spec what they imagine, engineers ship what they can build in time.

-
-
-
-

The Context Collapse

-

Switch from Figma to your editor and every annotation, constraint, and decision stays behind. Context collapses into a Slack thread that nobody reads twice.

-
-
-
-

The Blank Canvas Problem

-

AI tools generate strangers. v0 produces components your design system doesn't recognise. The blank canvas ignores all the decisions already encoded in your app.

+
+
+ + +
+
+
+ Live Artboard +

Your app, live in the canvas.

+

Not a mockup. Not a screenshot. Every artboard is a real render of your actual component tree — running your real code, connected to your real data.

+
    +
  • + + Any route becomes an artboard. Open a URL and it renders instantly. +
  • +
  • + + Changes in the canvas write back to your component props — no round trips. +
  • +
  • + + Hot reload and real-time data. The canvas reflects your app state at all times. +
  • +
+ See it in action → +
+
+
+
+
+
+
DashboardCard — live
+
Revenue Overview
+
$12,450
+
+
+
+ +
-
+ - -
-
-
- Core Concepts -

Five ideas that change how design works.

-

Each feature removes a seam between design intent and production code.

+ +
+
+
+ Intent Diff +

Ship a diff, not a redline.

+

Visual changes expressed at the component level — not the pixel level. Every edit on the canvas becomes a machine-readable diff of exactly what changed and why.

+
    +
  • + + Intent Diffs describe changes in your design language: "increase Card border-radius." +
  • +
  • + 🤖 + Agents receive the diff and apply it precisely — no interpretation required. +
  • +
  • + + Parity is verified automatically. The canvas confirms the result. +
  • +
+ Explore Intent Diff →
-
-
+
+
+
Intent Diff — DashboardCard.tsx
+
src/components/DashboardCard.tsx · 3 changes
+
<Card
+
− borderRadius={8}
+
+ borderRadius={12}
+
elevation="medium"
+
− accentColor="#2A6CD4"
+
+ accentColor="#0066FF"
+
>
+
− padding={16}
+
+ padding={20}
+
+
+
+
+ + +
+
+
+ Origin Graph +

Every surface, indexed.

+

A queryable graph of every component, token, and relationship in your codebase. Know exactly what composes each surface — and what changes cascade.

+
    +
  • + + 284 nodes. Every component traced to its source, tokens, and parent routes. +
  • +
  • + 🔗 + Completion Zone uses the graph to constrain AI suggestions to your design language. +
  • +
  • + + Agent Bridge exposes the graph to external AI tools via MCP protocol. +
  • +
+ Explore the Graph → +
+
+
+ +
App
+
DashboardCard
+
UserProfile
+
StatsCard
+
ProgressBar
+
Avatar
+
+ 278 more nodes
+ +
+
+
+
+
+ + +
+
+
+ Everything you need +

Five ideas that close the gap.

+

Each feature removes a seam between design intent and production code.

+
+
+

Live Artboard

A live render of your actual application — not a mockup. Every artboard is backed by a real component tree running your real code.

-
+

Origin Graph

Every artboard traces its provenance through a queryable graph. Know exactly which files, props, and tokens compose each surface.

-
+

Intent Diff

-

Visual changes expressed at the component level, not the pixel level. Ship diffs that say "increase Card border-radius" — not a PNG redline.

+

Visual changes expressed at the component level, not the pixel level. Ship diffs that describe intent — not PNG redlines.

-
+

Completion Zone

-

Mark a region. AI fills it within your design language constraints — using your actual tokens, your actual components, nothing invented.

+

Mark a region. AI fills it within your design language constraints — using your actual tokens, your actual components.

-
+

Agent Bridge

-

Bidirectional MCP channel between your design surface and coding agents. Claude, Cursor, Copilot — they all see the canvas and the graph simultaneously.

+

Bidirectional MCP channel between your canvas and coding agents. Claude, Cursor, Copilot — they all see the canvas and the graph.

+
+
+
🔗
+

Integrations

+

Slots into Linear, Slack, GitHub, Figma import, and every major AI coding tool. No migration. No lock-in.

- -
+ +
-
- Workflow +
+ Workflow

From codebase to canvas in five steps.

-
+
-
01
+
01

Ingest

Point Originmain at your repo. The Origin Graph indexes your components, tokens, and routes.

-
02
+
02

Edit

Open any route as a live artboard. Every visual change is a real prop or style edit.

-
03
+
03

Complete

-

Mark a region and let AI suggest completions constrained to your existing design language.

+

Mark a region and let AI suggest completions constrained to your design language.

-
04
+
04

Export

-

Export an Intent Diff — a machine-readable component-level description of what changed.

+

Export an Intent Diff — a machine-readable, component-level description of what changed.

-
05
+
05

Verify

Agents apply the diff and verify rendering parity. The canvas confirms the result.

@@ -578,52 +1702,53 @@ footer{background:var(--fg);padding:80px 32px 40px}
- -
+ +
-
- Integrations +
+ Integrations

Connects to your entire stack.

-

Slots into the tools you already use. No migration required.

+

Slots into the tools you already use. No migration required.

-
-
🔗 Linear
-
💬 Slack
-
🐙 GitHub
-
🖱 Cursor
-
🤖 Claude Code
-
Figma (import)
-
Next.js
-
React
-
🟦 VS Code
+
+
🔗 Linear
+
💬 Slack
+
🐙 GitHub
+
🖱 Cursor
+
🤖 Claude Code
+
Figma Import
+
Next.js
+
React
+
🟦 VS Code
+
🔷 TypeScript
- -
+ +
-
- Differentiation +
+ Differentiation

Not another design tool.

-

Existing tools solve half the problem. Originmain closes the loop.

+

Existing tools solve half the problem. Originmain closes the loop.

-
+
-
vs.
+
vs.
Figma
-
-

Figma is the best interface design tool ever made — but what you design is a fiction. It has no knowledge of your codebase, tokens, or component API surface. The handoff is manual, lossy, and perpetually stale.

+
+

The best interface design tool ever made — but what you design is a fiction. No knowledge of your codebase, tokens, or component API. The handoff is manual, lossy, and perpetually stale.

Static mockups Manual handoff No code awareness
-
-
Originmain
+
+
Originmain
Design is live code.
-
+

Every artboard is a live render. Every edit writes code. The gap between design and production is zero — because they're the same surface, indexed by the same graph.

Live renders @@ -633,10 +1758,10 @@ footer{background:var(--fg);padding:80px 32px 40px}
-
vs.
+
vs.
v0 / Cursor
-
-

v0 and Cursor are exceptional at generating code from a blank slate — but they hallucinate design systems. They don't know your tokens, your component API, or what "on-brand" means for your product.

+
+

Exceptional at generating code from a blank slate — but they hallucinate design systems. They don't know your tokens, your component API, or what "on-brand" means for your product.

Generates strangers No design system @@ -647,79 +1772,106 @@ footer{background:var(--fg);padding:80px 32px 40px}
- -
-
- " -

The ideal design tool is a canvas where any view of your application can be rendered, explored, duplicated, and modified visually — with every change traced back to a diff in your actual codebase.

-
— Design Engineer at a Series B startup
+ +
+
+ +

+ "The ideal design tool is a canvas where any view of your application can be rendered, explored, and modified visually — with every change traced back to a diff in your actual codebase. Originmain is that tool." +

+
+ Jamie K. — Design Engineer, Growth Stage SaaS +
- -
+ +
-
- Pricing -

Simple, team-based pricing.

-

Early access is free. All plans include the full Origin Graph and Live Artboard engine.

+
+ Pricing +

Simple, honest pricing.

+

Early access is free. All plans include the full Origin Graph and Live Artboard engine.

-
+
+
+
Monthly
+
Annual
+
+ +
+
Starter
Free
Solo use, forever
-
+
    -
  • Unlimited artboards
  • -
  • Origin Graph (up to 5k nodes)
  • -
  • Intent Diff export
  • -
  • 3 AI completions / day
  • -
  • GitHub integration
  • +
  • Unlimited artboards
  • +
  • Origin Graph (up to 5k nodes)
  • +
  • Intent Diff export
  • +
  • 3 AI completions / day
  • +
  • GitHub integration
- Get started free + Get started free
-
+
Enterprise
Custom
Unlimited seats
-
+
    -
  • Everything in Team
  • -
  • SSO / SAML
  • -
  • On-premise deployment
  • -
  • SLA & dedicated support
  • -
  • Custom integrations
  • -
  • Design system audit
  • +
  • Everything in Team
  • +
  • SSO / SAML
  • +
  • On-premise deployment
  • +
  • SLA & dedicated support
  • +
  • Custom integrations
  • +
  • Design system audit
- Talk to us + Talk to us
- + +
+
+

Start building
the right way.

+

Join the engineers who are done shipping pixel-perfect mockups that no one implements correctly.

+ +
+
+ +
-
+
- -

Where design intent becomes production code.

+ +

Where design intent becomes production code.

Product

@@ -759,57 +1911,87 @@ footer{background:var(--fg);padding:80px 32px 40px}
-
- © 2025 Originmain. All rights reserved. -
diff --git a/packages/app/src/components/canvas/Artboard.tsx b/packages/app/src/components/canvas/Artboard.tsx index 8f6dac0..954d8d1 100644 --- a/packages/app/src/components/canvas/Artboard.tsx +++ b/packages/app/src/components/canvas/Artboard.tsx @@ -20,69 +20,156 @@ export function Artboard({ id, label, x, y, width, height }: ArtboardProps) { style={{ position: 'absolute', top: y, left: x }} onClick={(e) => { e.stopPropagation(); selectArtboard(id); }} > - {/* Label above artboard */} + {/* Label */}
{label}
- {/* Artboard frame */} + {/* Frame */}
{/* Selection handles */} {selected && ( <> - - - - + + + + )} - {/* Placeholder content */} -
-
-
- Revenue Overview -
-
- Live -
+ {/* Per-artboard content */} + +
+
+ ); +} + +function ArtboardContent({ id }: { id: string }) { + switch (id) { + case 'dashboard-card': return ; + case 'user-profile': return ; + case 'nav-sidebar': return ; + case 'data-table': return ; + default: return ; + } +} + +// ── DashboardCard ────────────────────────────────────────── +function DashboardCard() { + return ( +
+
+ Revenue Overview + Live +
+
$12,450
+
+ +2.4% vs last month +
+
+
+
+
+ Q4 2024 + MRR + SaaS +
+
+ ); +} + +// ── UserProfile ──────────────────────────────────────────── +function UserProfile() { + return ( +
+
+
Sarah Chen
+
Design Engineer
+
+ {[['Team', 'Acme Inc'], ['Role', 'Admin'], ['Plan', 'Team']].map(([k, v]) => ( +
+ {k} + {v}
-
- $12,450 -
-
- ↑ +2.4% vs last month -
-
-
-
-
- Q4 2024 - MRR + ))} +
+ +
+ ); +} + +// ── NavSidebar ───────────────────────────────────────────── +function NavSidebar() { + const items = [ + { icon: '⊞', label: 'Dashboard', active: true }, + { icon: '◉', label: 'Origin Graph', active: false }, + { icon: '⬜', label: 'Artboards', active: false }, + { icon: '△', label: 'Diffs', active: false }, + { icon: '🔗', label: 'Integrations',active: false }, + ]; + return ( +
+
+ Originmain +
+
+ {items.map(({ icon, label, active }) => ( +
+ {icon} + {label} +
+ ))} +
+
+
+
+
Sarah Chen
+
Admin
@@ -90,15 +177,75 @@ export function Artboard({ id, label, x, y, width, height }: ArtboardProps) { ); } +// ── DataTable ────────────────────────────────────────────── +function DataTable() { + const rows = [ + { name: 'DashboardCard', status: 'Live', nodes: 12, tokens: 8 }, + { name: 'UserProfile', status: 'Draft', nodes: 7, tokens: 3 }, + { name: 'NavSidebar', status: 'Live', nodes: 19, tokens: 11 }, + { name: 'DataTable', status: 'Review', nodes: 24, tokens: 14 }, + ]; + return ( +
+
+ Component Inventory +
+
+ {['Name', 'Status', 'Nodes', 'Tokens'].map(h => ( + {h} + ))} +
+
+ {rows.map((row, i) => ( +
+ {row.name} + + {row.status} + + {row.nodes} + {row.tokens} +
+ ))} +
+ ); +} + +function Placeholder({ label }: { label: string }) { + return ( +
+ {label} +
+ ); +} + function Handle({ pos }: { pos: React.CSSProperties }) { return (
+ {children} ); diff --git a/packages/app/src/components/canvas/Canvas.tsx b/packages/app/src/components/canvas/Canvas.tsx index db3c1d8..022a2e5 100644 --- a/packages/app/src/components/canvas/Canvas.tsx +++ b/packages/app/src/components/canvas/Canvas.tsx @@ -6,24 +6,24 @@ import { useCanvas } from '@/store/canvas'; import { Artboard } from './Artboard'; const ARTBOARDS = [ - { id: 'dashboard-card', label: 'DashboardCard', x: 120, y: 120, width: 280, height: 200 }, - { id: 'user-profile', label: 'UserProfile', x: 480, y: 120, width: 240, height: 280 }, - { id: 'nav-sidebar', label: 'NavSidebar', x: 120, y: 400, width: 200, height: 360 }, - { id: 'data-table', label: 'DataTable', x: 400, y: 420, width: 420, height: 300 }, + { id: 'dashboard-card', label: 'DashboardCard', x: 120, y: 100, width: 280, height: 200 }, + { id: 'user-profile', label: 'UserProfile', x: 460, y: 100, width: 200, height: 260 }, + { id: 'nav-sidebar', label: 'NavSidebar', x: 120, y: 360, width: 200, height: 340 }, + { id: 'data-table', label: 'DataTable', x: 380, y: 380, width: 420, height: 280 }, ]; export function Canvas() { - const containerRef = useRef(null); - const panX = useViewport((s) => s.panX); - const panY = useViewport((s) => s.panY); - const zoom = useViewport((s) => s.zoom); + const containerRef = useRef(null); + const panX = useViewport((s) => s.panX); + const panY = useViewport((s) => s.panY); + const zoom = useViewport((s) => s.zoom); const { activeTool, selectArtboard } = useCanvas(); const isPanning = useRef(false); - const lastPos = useRef({ x: 0, y: 0 }); + const lastPos = useRef({ x: 0, y: 0 }); const spaceDown = useRef(false); - // Wheel: pan or zoom + // Wheel: pan or pinch-zoom useEffect(() => { const el = containerRef.current; if (!el) return; @@ -32,9 +32,7 @@ export function Canvas() { const { zoom, panX, panY, setPan, setZoom } = useViewport.getState(); if (e.ctrlKey || e.metaKey) { const rect = el.getBoundingClientRect(); - const ox = e.clientX - rect.left; - const oy = e.clientY - rect.top; - setZoom(zoom * (e.deltaY > 0 ? 0.92 : 1.09), ox, oy); + setZoom(zoom * (e.deltaY > 0 ? 0.92 : 1.09), e.clientX - rect.left, e.clientY - rect.top); } else { setPan(panX - e.deltaX, panY - e.deltaY); } @@ -43,7 +41,7 @@ export function Canvas() { return () => el.removeEventListener('wheel', onWheel); }, []); - // Space key: temporary pan mode + // Space bar temporary pan useEffect(() => { const down = (e: KeyboardEvent) => { if (e.code === 'Space' && e.target === document.body) spaceDown.current = true; }; const up = (e: KeyboardEvent) => { if (e.code === 'Space') spaceDown.current = false; }; @@ -74,29 +72,48 @@ export function Canvas() { const onMouseUp = useCallback(() => { isPanning.current = false; }, []); - const gridSize = Math.max(8, 22 * zoom); + // Dot grid that shifts with pan and scales with zoom + const gridSpacing = Math.max(6, 20 * zoom); const cursor = activeTool === 'pan' || isPanning.current ? 'grab' : 'default'; return (
- {/* Dot grid — shifts with pan, scales with zoom */} + {/* Dot grid — scales and shifts with viewport */}
+ {/* Subtle radial vignette */} +
+ {/* Transform layer */}
{ARTBOARDS.map((ab) => ( diff --git a/packages/app/src/components/chrome/AppChrome.tsx b/packages/app/src/components/chrome/AppChrome.tsx index cbdbdce..f016336 100644 --- a/packages/app/src/components/chrome/AppChrome.tsx +++ b/packages/app/src/components/chrome/AppChrome.tsx @@ -10,10 +10,12 @@ export function AppChrome() {
diff --git a/packages/app/src/components/chrome/Toolbar.tsx b/packages/app/src/components/chrome/Toolbar.tsx index 4ceb79e..1b83a03 100644 --- a/packages/app/src/components/chrome/Toolbar.tsx +++ b/packages/app/src/components/chrome/Toolbar.tsx @@ -4,7 +4,6 @@ import { ToolbarButton, ToolbarDivider, Tooltip, - Badge, } from '@fluentui/react-components'; import { CursorClickRegular, @@ -19,13 +18,27 @@ import { import { useCanvas, type Tool } from '@/store/canvas'; import { useViewport } from '@/store/viewport'; +// Design tokens — Fluent dark surface +const T = { + bg: '#141418', + border: 'rgba(255,255,255,0.06)', + sep: 'rgba(255,255,255,0.07)', + fg: 'rgba(255,255,255,0.88)', + fgMuted: 'rgba(255,255,255,0.38)', + fgDim: 'rgba(255,255,255,0.22)', + accent: '#3385FF', + accentBg: 'rgba(51,133,255,0.14)', + activeBg: 'rgba(255,255,255,0.07)', + live: '#10B981', + liveBg: 'rgba(16,185,129,0.1)', + liveBorder:'rgba(16,185,129,0.2)', +}; + export function Toolbar() { const { activeTool, setActiveTool } = useCanvas(); - const zoom = useViewport((s) => s.zoom); + const zoom = useViewport((s) => s.zoom); const setZoom = useViewport((s) => s.setZoom); - const reset = useViewport((s) => s.reset); - - const ap = (id: Tool): 'primary' | 'subtle' => activeTool === id ? 'primary' : 'subtle'; + const reset = useViewport((s) => s.reset); return (
{/* Wordmark */} - Om + Om - + - {/* Select */} - - } onClick={() => setActiveTool('select')} /> - - {/* Pan */} - - } onClick={() => setActiveTool('pan')} /> - - {/* Artboard */} - - } onClick={() => setActiveTool('artboard')} /> - - {/* Zone */} - - } onClick={() => setActiveTool('zone')} /> - + {/* Selection tools */} + + + setActiveTool('select')}> + + + + + setActiveTool('pan')}> + + + + - + + + {/* Create tools */} + + + setActiveTool('artboard')}> + + + + + setActiveTool('zone')}> + + + + + + {/* View tools */} - - } /> - - - } /> - - - {/* Right — zoom + live status */} -
- - } onClick={() => setZoom(zoom * 0.8)} /> + + + {}}> + + + + {}}> + + + + + + {/* Right side */} +
+ + setZoom(zoom * 0.8)}> + + + + + - } onClick={() => setZoom(zoom * 1.25)} /> + setZoom(zoom * 1.25)}> + + - + - - - Live - + {/* Live status pill */} +
+
+ + Live + +
); } + +function Sep() { + return ( +
+ ); +} + +function ToolGroup({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function TBtn({ + active, + onClick, + children, +}: { + active: boolean; + onClick: () => void; + children: React.ReactNode; +}) { + return ( + + ); +} diff --git a/packages/app/src/components/inspector/Inspector.tsx b/packages/app/src/components/inspector/Inspector.tsx index c91b53e..02ae405 100644 --- a/packages/app/src/components/inspector/Inspector.tsx +++ b/packages/app/src/components/inspector/Inspector.tsx @@ -1,101 +1,225 @@ 'use client'; -import { TabList, Tab, Divider } from '@fluentui/react-components'; +import { useState } from 'react'; import { useCanvas } from '@/store/canvas'; const PROPS = [ { key: 'title', val: '"Revenue Overview"', type: 's' }, - { key: 'value', val: '"$12,450"', type: 's' }, - { key: 'delta', val: '+2.4', type: 'n' }, - { key: 'period', val: '"monthly"', type: 's' }, - { key: 'loading', val: 'false', type: 'b' }, + { key: 'value', val: '"$12,450"', type: 's' }, + { key: 'delta', val: '+2.4', type: 'n' }, + { key: 'period', val: '"monthly"', type: 's' }, + { key: 'loading', val: 'false', type: 'b' }, ]; const DIFF = [ { op: 'del', text: '− borderRadius: 8px' }, { op: 'add', text: '+ borderRadius: 12px' }, { op: 'del', text: '− accentColor: #2A6CD4' }, - { op: 'add', text: '+ accentColor: #0F52BA' }, + { op: 'add', text: '+ accentColor: #0066FF' }, + { op: 'del', text: '− padding: 16' }, + { op: 'add', text: '+ padding: 20' }, ]; const TYPE_COLORS: Record = { - s: '#16a34a', - n: '#2563eb', - b: '#d97706', + s: '#7DD3A8', + n: '#7EB8FF', + b: '#FFBA7B', +}; + +type TabId = 'props' | 'diff' | 'graph'; + +// Dark panel tokens +const T = { + bg: '#111115', + border: 'rgba(255,255,255,0.055)', + sep: 'rgba(255,255,255,0.04)', + label: 'rgba(255,255,255,0.22)', + key: 'rgba(255,255,255,0.32)', + dim: 'rgba(255,255,255,0.18)', + accent: '#3385FF', + tabFg: 'rgba(255,255,255,0.28)', + tabOn: 'rgba(255,255,255,0.88)', }; export function Inspector() { const { selectedArtboardId } = useCanvas(); + const [tab, setTab] = useState('props'); return (
- - Props - Diff - Graph - - - {selectedArtboardId ? ( - <> -
- {PROPS.map(({ key, val, type }) => ( - - ))} -
- - + {/* Tab bar */} +
+ {(['props', 'diff', 'graph'] as TabId[]).map((t) => ( + + ))} +
+ {/* Content */} +
+ {!selectedArtboardId ? ( +
+ + + Select an artboard + +
+ ) : tab === 'props' ? ( + <> +
+ {PROPS.map(({ key, val, type }) => ( + + ))} +
+ +
+ + + +
+ + ) : tab === 'diff' ? (
+
+ DashboardCard.tsx · 3 hunks +
{DIFF.map((d, i) => (
{d.text}
))}
- - - -
- - - + ) : ( +
+ + + + + + + +
+ + + +
- - ) : ( -
- Select an artboard -
- )} + )} +
+ + {/* Status bar */} +
+
+ + Live render connected + + + 284 nodes + +
); } function Section({ label, children }: { label: string; children: React.ReactNode }) { return ( -
-
+
+
{label}
{children} @@ -105,9 +229,70 @@ function Section({ label, children }: { label: string; children: React.ReactNode function PropRow({ label, value, color }: { label: string; value: string; color: string }) { return ( -
- {label} - {value} +
+ + {label} + + + {value} +
); } + +function GraphNode({ label, depth, isRoot }: { label: string; depth: number; isRoot?: boolean }) { + return ( +
+
+ + {label} + +
+ ); +} + +function HSep() { + return
; +} diff --git a/packages/app/src/components/navigator/ArtboardNavigator.tsx b/packages/app/src/components/navigator/ArtboardNavigator.tsx index 283737f..491a8db 100644 --- a/packages/app/src/components/navigator/ArtboardNavigator.tsx +++ b/packages/app/src/components/navigator/ArtboardNavigator.tsx @@ -4,7 +4,6 @@ import { Tree, TreeItem, TreeItemLayout, - Text, } from '@fluentui/react-components'; import { SquareRegular, @@ -16,11 +15,24 @@ import { useCanvas } from '@/store/canvas'; const ARTBOARDS = [ { id: 'dashboard-card', label: 'DashboardCard' }, - { id: 'user-profile', label: 'UserProfile' }, - { id: 'nav-sidebar', label: 'NavSidebar' }, - { id: 'data-table', label: 'DataTable' }, + { id: 'user-profile', label: 'UserProfile' }, + { id: 'nav-sidebar', label: 'NavSidebar' }, + { id: 'data-table', label: 'DataTable' }, ]; +// Dark panel tokens +const T = { + bg: '#111115', + border: 'rgba(255,255,255,0.055)', + label: 'rgba(255,255,255,0.22)', + item: 'rgba(255,255,255,0.42)', + itemHov: 'rgba(255,255,255,0.72)', + selBg: 'rgba(51,133,255,0.12)', + selFg: 'rgba(255,255,255,0.88)', + accent: '#3385FF', + sep: 'rgba(255,255,255,0.04)', +}; + export function ArtboardNavigator() { const { selectedArtboardId, selectArtboard } = useCanvas(); @@ -29,83 +41,162 @@ export function ArtboardNavigator() { style={{ gridColumn: 1, gridRow: 2, - background: '#fafafa', - borderRight: '1px solid rgba(0,0,0,0.08)', + background: T.bg, + borderRight: `1px solid ${T.border}`, display: 'flex', flexDirection: 'column', overflow: 'hidden', + fontSize: 12, }} > {/* Artboards section */} - Artboards - + Artboards +
{ARTBOARDS.map((ab) => ( - selectArtboard(ab.id)} + style={{ + display: 'flex', + alignItems: 'center', + gap: 8, + padding: '6px 10px', + borderRadius: 5, + cursor: 'pointer', + background: selectedArtboardId === ab.id ? T.selBg : 'transparent', + color: selectedArtboardId === ab.id ? T.selFg : T.item, + fontSize: '0.75rem', + letterSpacing: '-0.01em', + fontWeight: selectedArtboardId === ab.id ? 500 : 400, + transition: 'background 0.1s, color 0.1s', + userSelect: 'none', + marginBottom: 1, + }} + onMouseEnter={e => { + if (selectedArtboardId !== ab.id) { + (e.currentTarget as HTMLDivElement).style.background = 'rgba(255,255,255,0.04)'; + (e.currentTarget as HTMLDivElement).style.color = T.itemHov; + } + }} + onMouseLeave={e => { + if (selectedArtboardId !== ab.id) { + (e.currentTarget as HTMLDivElement).style.background = 'transparent'; + (e.currentTarget as HTMLDivElement).style.color = T.item; + } + }} > - }> - {ab.label} - - + + {ab.label} + {selectedArtboardId === ab.id && ( + + )} +
))} -
+
- + - {/* Codebase section */} - Files - - - }> - src + {/* Files section */} + Files + + + } + style={{ fontSize: '0.75rem', color: T.item, padding: '4px 4px' }} + > + src - }> - components + } + style={{ fontSize: '0.75rem', color: 'rgba(255,255,255,0.35)', padding: '4px 4px' }} + > + components {ARTBOARDS.map((ab) => ( - }> - - {ab.label}.tsx - + } + style={{ fontSize: '0.6875rem', color: 'rgba(255,255,255,0.3)', padding: '3px 4px' }} + > + {ab.label}.tsx ))} - }> - page.tsx + } + style={{ fontSize: '0.6875rem', color: 'rgba(255,255,255,0.3)', padding: '3px 4px' }} + > + page.tsx + + + + {/* Graph nodes indicator */} + Graph +
+ + + +
); } -function SectionLabel({ children }: { children: string }) { +function SectionLabel({ children, icon }: { children: string; icon: string }) { return (
{children} @@ -113,6 +204,28 @@ function SectionLabel({ children }: { children: string }) { ); } -function Divider() { - return
; +function HSep() { + return ( +
+ ); +} + +function GraphStat({ label, value, color }: { label: string; value: string; color: string }) { + return ( +
+ + {label} + + + {value} + +
+ ); }