import { Avatar, Row, Stage } from '../kit' import './contrasts.css' import './composition.css' export const compositionLessons = [ { id: 'framing', title: 'Give the product room', note: 'The same exports window and status in a small frame. Protect the header and useful rows; let the window continue beyond a deliberate edge.', bad: 'Tight edges and a status over the content', good: 'Clear gutters and an intentional bottom crop' }, { id: 'ui-anatomy', title: 'Make the UI read like a product', note: 'The same project, tasks and action. Type hierarchy and shared alignment make the relationships clear without adding decoration.', bad: 'Labels, metadata and actions compete', good: 'A clear heading and consistent reading columns' }, { id: 'visual-restraint', title: 'Choose where emphasis belongs', note: 'The same plan details and action. When every edge and value is emphasized, the primary action loses its place.', bad: 'Borders, shadows and color on every part', good: 'Quiet details and one clear action' }, { id: 'compact-reflow', title: 'Reflow a small composition', note: 'The same three tasks in a 300px slot. A smaller host needs a readable arrangement, not a miniature desktop table.', bad: 'The desktop table is scaled to half size', good: 'The same content reflows at readable sizes' }, { id: 'edge-separation', title: 'Make edges feel deliberate', note: 'The same sync panel and confirmation. Nearly touching edges look accidental; a clear gap makes two surfaces easy to distinguish.', bad: 'The confirmation almost touches the panel', good: 'A deliberate gap separates the two surfaces' }, { id: 'nested-spacing', title: 'Carry padding around every edge', note: 'The same preferences and action. Check the bottom and the corners as carefully as the space above the first label.', bad: 'The inner surface and action crowd the bottom', good: 'Each layer has a consistent inset' }, { id: 'control-alignment', title: 'Fit the contents of a control', note: 'The same search field and actions. Shared content alignment balances labels and icons within their actual controls.', bad: 'Icons sit low and labels crowd the upper edge', good: 'Icons and labels share a balanced content row' }, { id: 'content-density', title: 'Give whitespace a job', note: 'The same three appointments. Keep each time with its subject and separate the appointments, rather than spreading every line equally.', bad: 'Uniform gaps weaken the appointment groups', good: 'Tighter groups make the schedule easier to scan' }, { id: 'surface-contrast', title: 'Keep the foreground readable', note: 'The same sharing controls on a tinted ground. A quiet surface still needs enough contrast for its text and available actions.', bad: 'Foreground, surface and ground blend together', good: 'Readable text and a distinct foreground surface' }, { id: 'icon-consistency', title: 'Give icons a shared construction', note: 'The same three destinations. Similar boxes do not guarantee similar visual weight; compare the shapes, stroke and visible size.', bad: 'Mixed fill, stroke weight and visible size', good: 'One construction with balanced optical weight' }, { id: 'decorative-noise', title: 'Let the information lead', note: 'The same activity report and chart. Gradients and glow can compete with a useful result even when the layout is orderly.', bad: 'Decorative effects compete with the report', good: 'The value and chart carry the emphasis' }, { id: 'product-anatomy', title: 'Choose chrome that serves the task', note: 'The same document, permission and sharing action. Familiar browser shapes are misleading when the task is a small sharing panel.', bad: 'Browser chrome disguises a sharing task', good: 'Document, permission and action have clear roles' }, { id: 'unread-gutter', title: 'Never push only the unread rows in', note: 'The same inbox with two unread messages. An unread dot must not move its row: pushing only unread rows in makes the column zigzag, and every read changes the layout. Hang the dot in the margin, as here, reserve a dot column on every row as Mail and Messages do, or put it by the time as Gmail does; follow the product.', bad: 'Unread rows push their avatar in; the column zigzags', good: 'Every avatar on one edge; the dot hangs in the margin' }, ] as const type LessonId = typeof compositionLessons[number]['id'] const EXPORTS = [['Campaign assets', '24 files'], ['Brand library', '18 files'], ['Product launch', '32 files'], ['Homepage', '8 files']] const TASKS = [ { name: 'Design review', owner: 'Maya', due: 'Today', status: 'Ready' }, { name: 'Onboarding', owner: 'Jon', due: 'Tomorrow', status: 'In review' }, { name: 'Billing audit', owner: 'Ana', due: 'Friday', status: 'Ready' }, ] function FileIcon() { return } function ExportWindow() { return
Exports
Recent exportsThis week
{EXPORTS.map(([name, count]) =>
{name}{count}Ready
)}
} function Framing({ good }: { good: boolean }) { return
Export complete24 files saved
} function Anatomy({ good }: { good: boolean }) { return

Release checklist

Due Friday · 4 tasks

Review copyReady
Test mobileIn review
2 tasks remainingOpen project
} function Restraint({ good }: { good: boolean }) { return

Starter plan

Active
$24/ month
Team seats3 of 5 seats
Manage plan
} function Compact({ good }: { good: boolean }) { return

Project tasks

3 open
TaskOwnerDueStatus
{TASKS.map(task =>
{task.name}{task.owner}{task.due}{task.status}
)}
} type SymbolName = 'check' | 'search' | 'download' | 'arrow' | 'folder' | 'inbox' | 'archive' | 'link' | 'chevron' function Symbol({ name, className = '' }: { name: SymbolName; className?: string }) { const paths: Record = { check: 'm4.5 10 3.5 3.5 7.5-7.5', search: 'M14 8.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0Zm-1.5 4 4 4', download: 'M10 3v9m-3-3 3 3 3-3M4 13v4h12v-4', arrow: 'M4 10h12m-5-5 5 5-5 5', folder: 'M2.5 5.5h6l2 2h7v9h-15z', inbox: 'M3 4h14v12H3V4Zm0 7h4l1.5 2h3l1.5-2h4', archive: 'M3 3.5h14v4H3zM4.5 7.5v9h11v-9M8 11h4', link: 'm8 12 4-4M7 13H5a3 3 0 0 1 0-6h3m4 0h3a3 3 0 0 1 0 6h-2', chevron: 'm6 8 4 4 4-4', } return } function EdgeSeparation({ good }: { good: boolean }) { return

Workspace sync

Design libraryUp to date
Brand assets2 min ago
All files synced
} function NestedSpacing({ good }: { good: boolean }) { return

Weekly summary

Send by email
Every Monday9:00 AM
Save preferences
} function ControlAlignment({ good }: { good: boolean }) { return

Project files

Search files
Export filesPreview
} function Density({ good }: { good: boolean }) { const appointments = [['9:30 AM', 'Design review', 'Maya and Jon'], ['11:00 AM', 'Customer call', 'Ana · 30 minutes'], ['2:00 PM', 'Planning', 'Product team']] return

Today

Wednesday, 14 May
{appointments.map(([time, title, people]) =>
{title}{people}
)}
} function Contrast({ good }: { good: boolean }) { return

Share document

Launch brief

Anyone with the linkCan view
Copy link
} function IconConsistency({ good }: { good: boolean }) { const items: { icon: SymbolName; title: string; count: string }[] = [{ icon: 'inbox', title: 'Inbox', count: '4' }, { icon: 'folder', title: 'Project files', count: '24' }, { icon: 'archive', title: 'Archive', count: '12' }] return

Workspace

{items.map(item =>
{item.title}{item.count}
)}
} function Decoration({ good }: { good: boolean }) { return

Weekly activity

124tasks completed

18 more than last week

{[20, 26, 18, 28, 32].map((value, i) =>
{['M', 'T', 'W', 'T', 'F'][i]}
)}
View report
} function ProductAnatomy({ good }: { good: boolean }) { return

Share handoff

Launch brief
Anyone with the linkCan view
Copy link
} const INBOX = [['Maya Chen', 'Launch checklist', true], ['Jon Park', 'Re: pricing page', false], ['Ana Ruiz', 'Q3 board deck', true]] as const function UnreadGutter({ good }: { good: boolean }) { return

Inbox

{INBOX.map(([name, subject, unread]) => } title={name} subtitle={subject} meta="9:41" unread={unread} />)}
} function Study({ id, good }: { id: LessonId; good: boolean }) { if (id === 'unread-gutter') return if (id === 'framing') return if (id === 'ui-anatomy') return if (id === 'visual-restraint') return if (id === 'compact-reflow') return if (id === 'edge-separation') return if (id === 'nested-spacing') return if (id === 'control-alignment') return if (id === 'content-density') return if (id === 'surface-contrast') return if (id === 'icon-consistency') return if (id === 'decorative-noise') return return } export function CompositionComparison({ lessonId }: { lessonId: string }) { const lesson = compositionLessons.find(item => item.id === lessonId) if (!lesson) return null const height = 302 return

{lesson.title}

{lesson.note}

{() =>
{[false, true].map(good =>
{good ? lesson.good : lesson.bad}
)}
}
}