
Lavish Design
- 81 installs
- 2.5k repo stars
- Updated July 31, 2026
- kunchenguid/lavish-axi
Helps with design & ui/ux tasks.
About
lavish-design is a Claude Code skill for design & ui/ux. It helps solo builders move faster with AI-assisted development.
- lavish-design
- Design & UI/UX
- AI-coding skill
Lavish Design by the numbers
- 81 all-time installs (skills.sh)
- +12 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #1,173 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/kunchenguid/lavish-axi --skill lavish-designAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 81 |
|---|---|
| repo stars | ★ 2.5k |
| Last updated | July 31, 2026 |
| Repository | kunchenguid/lavish-axi ↗ |
What it does
Helps with design & ui/ux tasks.
Files
Read the README.md file within this skill, and explore the other available files. If creating visual artifacts (slides, mocks, throwaway prototypes, etc), copy assets out and create static HTML files for the user to view. If working on production code, you can copy assets and read the rules here to become an expert in designing with this brand. If the user invokes this skill without any other guidance, ask them what they want to build or design, ask some questions, and act as an expert designer who outputs HTML artifacts _or_ production code, depending on the need.
Where things live
README.md— product context, content fundamentals, visual foundations, iconography, caveatscolors_and_type.css— drop-in CSS custom properties for the full token system (ink, brass, sage, amber, rust, steel, cream + type + spacing + radii + shadows)assets/— wordmark, lavish-mark icon, flow diagram SVGpreview/— Design-System-tab specimen cards (one concept per card)ui_kits/editor/— clickable React recreation of the Lavish Editor chrome
Quick rules of thumb
1. Dark ink + cream type + one brass accent. No second accent, no gradient, no glassmorphism. 2. Serif for prose and brand moments (EB Garamond italic). Sans for chrome (Geist). Mono for technical (Geist Mono). 3. No emoji. No exclamation marks. No "🚀 powerful seamless" copy. Read the on-brand/off-brand examples in README's Content Fundamentals. 4. The element annotation outline = the focus outline: 2px brass, 2px offset. Text range annotations use a translucent brass highlight instead; both mean "attention is here." 5. Shadows only on floating surfaces (tooltip, annotation card). Never on buttons or in-panel cards. 6. Sage = agent. Amber = user. Rust = danger. Don't mix. 7. Words first, icons second. When you must reach for an icon, use Lucide (stroke, 1.5, currentColor).
To produce a new HTML artifact
1. @import "../colors_and_type.css"; in your stylesheet (or copy the tokens block inline if standalone). 2. Build with the semantic vars (--bg, --fg, --accent, etc.) — never hard-code hex. 3. Set font-family: var(--font-sans) on the body; reach for var(--font-serif) on headlines and pull-quotes. 4. Reference assets/lavish-wordmark.svg for the brand mark; do not redraw it. 5. If you need a component (button, pill, bubble, annotation card, top bar), open the matching JSX file in ui_kits/editor/ and copy the styles object — they're already token-driven.
{
"plugins": [
"react",
"import"
],
"rules": {
"react/forbid-elements": [
"warn",
{
"forbid": []
}
],
"no-restricted-imports": [
"warn",
{
"patterns": [
{
"group": [
"ui_kits/editor/**"
],
"message": "Import design-system components from 'index.js', not component internals."
}
]
}
],
"no-restricted-syntax": [
"warn",
{
"selector": "Literal[value=/#[0-9a-fA-F]{3,8}\\b/]",
"message": "Raw hex color — use a design-system color token via var()."
},
{
"selector": "Literal[value=/\\b\\d+px\\b/]",
"message": "Raw px value — use a design-system spacing token via var()."
},
{
"selector": "Literal[value=/font-family\\s*:\\s*(?!['\\\"]?(?:EB Garamond|Geist|Geist Mono))/i]",
"message": "Font not provided by the design system. Available: EB Garamond, Geist, Geist Mono."
}
]
},
"overrides": [
{
"files": [
"**/index.js"
],
"rules": {
"no-restricted-imports": "off"
}
}
],
"x-omelette": {
"components": {},
"tokens": [
"--accent",
"--accent-hover",
"--accent-ink",
"--amber-700",
"--amber-900",
"--annotate-offset",
"--annotate-outline",
"--bar-h",
"--bg",
"--bg-bar",
"--bg-elevated",
"--bg-panel",
"--border",
"--border-strong",
"--border-subtle",
"--brass-400",
"--brass-500",
"--brass-ink",
"--cream-100",
"--cream-200",
"--cream-50",
"--danger",
"--dur",
"--dur-fast",
"--dur-slow",
"--ease",
"--fg",
"--fg-dim",
"--fg-faint",
"--fg-label",
"--fg-muted",
"--font-mono",
"--font-sans",
"--font-serif",
"--hairline",
"--hairline-subtle",
"--ink-600",
"--ink-700",
"--ink-800",
"--ink-900",
"--lh-2xl",
"--lh-3xl",
"--lh-4xl",
"--lh-5xl",
"--lh-base",
"--lh-display",
"--lh-lg",
"--lh-md",
"--lh-sm",
"--lh-xl",
"--lh-xs",
"--panel-w",
"--radius-lg",
"--radius-md",
"--radius-pill",
"--radius-sm",
"--radius-xl",
"--rust-500",
"--sage-300",
"--sage-700",
"--sage-900",
"--shadow-floating",
"--shadow-tooltip",
"--space-1",
"--space-10",
"--space-12",
"--space-16",
"--space-2",
"--space-20",
"--space-24",
"--space-3",
"--space-32",
"--space-4",
"--space-5",
"--space-6",
"--space-8",
"--steel-100",
"--steel-200",
"--steel-300",
"--steel-400",
"--steel-500",
"--steel-600",
"--steel-700",
"--text-2xl",
"--text-3xl",
"--text-4xl",
"--text-5xl",
"--text-base",
"--text-display",
"--text-lg",
"--text-md",
"--text-sm",
"--text-xl",
"--text-xs",
"--track-brand",
"--track-caps",
"--track-label",
"--track-normal",
"--track-tight",
"--w-bold",
"--w-brand",
"--w-medium",
"--w-regular",
"--w-semi"
],
"tokenKinds": {
"--ink-900": "color",
"--ink-800": "color",
"--ink-700": "color",
"--ink-600": "color",
"--steel-700": "color",
"--steel-600": "color",
"--steel-500": "color",
"--steel-400": "color",
"--steel-300": "color",
"--steel-200": "color",
"--steel-100": "color",
"--cream-50": "color",
"--cream-100": "color",
"--cream-200": "color",
"--brass-500": "color",
"--brass-400": "color",
"--brass-ink": "color",
"--sage-900": "color",
"--sage-700": "color",
"--sage-300": "color",
"--amber-900": "color",
"--amber-700": "color",
"--rust-500": "color",
"--bg": "color",
"--bg-panel": "color",
"--bg-bar": "color",
"--bg-elevated": "color",
"--fg": "color",
"--fg-muted": "color",
"--fg-dim": "color",
"--fg-faint": "color",
"--fg-label": "color",
"--border": "color",
"--border-subtle": "color",
"--border-strong": "color",
"--accent": "color",
"--accent-hover": "color",
"--accent-ink": "color",
"--danger": "color",
"--font-serif": "font",
"--font-sans": "font",
"--font-mono": "font",
"--text-xs": "font",
"--lh-xs": "font",
"--text-sm": "font",
"--lh-sm": "font",
"--text-base": "font",
"--lh-base": "font",
"--text-md": "font",
"--lh-md": "font",
"--text-lg": "font",
"--lh-lg": "font",
"--text-xl": "font",
"--lh-xl": "font",
"--text-2xl": "font",
"--lh-2xl": "font",
"--text-3xl": "font",
"--lh-3xl": "font",
"--text-4xl": "font",
"--lh-4xl": "font",
"--text-5xl": "font",
"--lh-5xl": "font",
"--text-display": "font",
"--lh-display": "font",
"--w-regular": "font",
"--w-medium": "font",
"--w-semi": "font",
"--w-bold": "font",
"--w-brand": "font",
"--track-tight": "font",
"--track-normal": "font",
"--track-brand": "font",
"--track-label": "font",
"--track-caps": "font",
"--space-1": "spacing",
"--space-2": "spacing",
"--space-3": "spacing",
"--space-4": "spacing",
"--space-5": "spacing",
"--space-6": "spacing",
"--space-8": "spacing",
"--space-10": "spacing",
"--space-12": "spacing",
"--space-16": "spacing",
"--space-20": "spacing",
"--space-24": "spacing",
"--space-32": "spacing",
"--radius-sm": "radius",
"--radius-md": "radius",
"--radius-lg": "radius",
"--radius-xl": "radius",
"--radius-pill": "radius",
"--hairline": "color",
"--hairline-subtle": "color",
"--shadow-tooltip": "shadow",
"--shadow-floating": "shadow",
"--bar-h": "spacing",
"--panel-w": "spacing",
"--ease": "other",
"--dur-fast": "other",
"--dur": "other",
"--dur-slow": "other",
"--annotate-outline": "color",
"--annotate-offset": "spacing"
},
"fontFamilies": [
"EB Garamond",
"Geist",
"Geist Mono"
]
}
}{"namespace":"LavishDesignSystem_019e18","components":[],"startingPoints":[],"cards":[{"path":"preview/brand-wordmark.html","group":"Brand","viewport":"700x220","subtitle":"Lavish in serif italic, Editor in Geist caps","name":"Brand · Wordmark"},{"path":"preview/content-voice.html","group":"Brand","viewport":"700x240","subtitle":"On-brand vs off-brand example copy","name":"Brand · Voice"},{"path":"preview/iconography.html","group":"Brand","viewport":"700x240","subtitle":"Words first; Lucide stroke icons when needed","name":"Brand · Iconography"},{"path":"preview/colors-brass.html","group":"Colors","viewport":"700x240","subtitle":"The single accent — CTA, outline, brand moment","name":"Colors · Brass accent"},{"path":"preview/colors-ink.html","group":"Colors","viewport":"700x200","subtitle":"Four surface tones from artifact-surround to elevated","name":"Colors · Ink"},{"path":"preview/colors-signal.html","group":"Colors","viewport":"700x240","subtitle":"One brass accent; rust only for destructive","name":"Colors · Signal"},{"path":"preview/colors-steel-cream.html","group":"Colors","viewport":"700x320","subtitle":"Borders, muted type, paper-warm cream","name":"Colors · Steel & cream"},{"path":"preview/components-annotation-card.html","group":"Components","viewport":"700x320","subtitle":"Floats over the artifact with brass border","name":"Components · Annotation card"},{"path":"preview/components-bubbles.html","group":"Components","viewport":"700x280","subtitle":"User right + elevated; Agent left + sunken","name":"Components · Chat bubbles"},{"path":"preview/components-buttons.html","group":"Components","viewport":"700x260","subtitle":"Brass primary, ink secondary, rust destructive","name":"Components · Buttons"},{"path":"preview/components-composer.html","group":"Components","viewport":"700x280","subtitle":"Textarea, empty-send hint, split send menu","name":"Components · Composer"},{"path":"preview/components-pills.html","group":"Components","viewport":"700x200","subtitle":"Pending-prompt chips with close affordance","name":"Components · Queued pills"},{"path":"preview/components-topbar.html","group":"Components","viewport":"700x200","subtitle":"Wordmark, Annotate switch, overflow menu","name":"Components · Top bar"},{"path":"preview/states-annotation.html","group":"Components","viewport":"700x220","subtitle":"Same brass ring for hover, focus, and selected","name":"Components · Annotation outline"},{"path":"preview/radii.html","group":"Spacing","viewport":"700x220","subtitle":"8 / 10 / 12 / 14 / 999 — no 4px","name":"Spacing · Radii"},{"path":"preview/shadows.html","group":"Spacing","viewport":"700x280","subtitle":"Tooltip and floating-surface shadow only","name":"Spacing · Shadows"},{"path":"preview/spacing-scale.html","group":"Spacing","viewport":"700x200","subtitle":"2 / 4 / 6 / 8 / 12 / 16 / 24 / 32 / 48 / 64","name":"Spacing · 8-pt scale"},{"path":"preview/type-display.html","group":"Type","viewport":"700x230","subtitle":"EB Garamond italic at 84 — the brand moment","name":"Type · Display"},{"path":"preview/type-families.html","group":"Type","viewport":"700x240","subtitle":"EB Garamond · Geist · Geist Mono","name":"Type · Families"},{"path":"preview/type-pullquote.html","group":"Type","viewport":"700x220","subtitle":"Garamond italic on a 2px brass rule","name":"Type · Pull quote"},{"path":"preview/type-scale.html","group":"Type","viewport":"700x320","subtitle":"h1 → eyebrow with sizes and line-heights","name":"Type · Scale"},{"path":"ui_kits/editor/index.html","group":"UI Kit — Editor","viewport":"1280x760","subtitle":"Clickable chrome: artifact + annotation + chat","name":"Lavish Editor — UI kit"}],"globalCssPaths":["colors_and_type.css","preview/_base.css"],"tokens":[{"name":"--ink-900","value":"#0f1115","kind":"color","definedIn":"colors_and_type.css"},{"name":"--ink-800","value":"#11141a","kind":"color","definedIn":"colors_and_type.css"},{"name":"--ink-700","value":"#171a21","kind":"color","definedIn":"colors_and_type.css"},{"name":"--ink-600","value":"#1c212b","kind":"color","definedIn":"colors_and_type.css"},{"name":"--steel-700","value":"#2a2f3a","kind":"color","definedIn":"colors_and_type.css"},{"name":"--steel-600","value":"#303745","kind":"color","definedIn":"colors_and_type.css"},{"name":"--steel-500","value":"#3c4557","kind":"color","definedIn":"colors_and_type.css"},{"name":"--steel-400","value":"#8c96aa","kind":"color","definedIn":"colors_and_type.css"},{"name":"--steel-300","value":"#aeb6c6","kind":"color","definedIn":"colors_and_type.css"},{"name":"--steel-200","value":"#b9c0cf","kind":"color","definedIn":"colors_and_type.css"},{"name":"--steel-100","value":"#d8deea","kind":"color","definedIn":"colors_and_type.css"},{"name":"--cream-50","value":"#fffbf3","kind":"color","definedIn":"colors_and_type.css"},{"name":"--cream-100","value":"#f7f3ea","kind":"color","definedIn":"colors_and_type.css"},{"name":"--cream-200","value":"#e8e1cf","kind":"color","definedIn":"colors_and_type.css"},{"name":"--brass-500","value":"#f4c95d","kind":"color","definedIn":"colors_and_type.css"},{"name":"--brass-400","value":"#ffd877","kind":"color","definedIn":"colors_and_type.css"},{"name":"--brass-ink","value":"#17130a","kind":"color","definedIn":"colors_and_type.css"},{"name":"--sage-900","value":"#172419","kind":"color","definedIn":"colors_and_type.css"},{"name":"--sage-700","value":"#315f3a","kind":"color","definedIn":"colors_and_type.css"},{"name":"--sage-300","value":"#8fe39e","kind":"color","definedIn":"colors_and_type.css"},{"name":"--amber-900","value":"#25230f","kind":"color","definedIn":"colors_and_type.css"},{"name":"--amber-700","value":"#5d4d1b","kind":"color","definedIn":"colors_and_type.css"},{"name":"--rust-500","value":"#f06464","kind":"color","definedIn":"colors_and_type.css"},{"name":"--bg","value":"var(--ink-900)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--bg-panel","value":"var(--ink-800)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--bg-bar","value":"var(--ink-700)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--bg-elevated","value":"var(--ink-600)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--fg","value":"var(--cream-100)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--fg-muted","value":"var(--steel-100)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--fg-dim","value":"var(--steel-200)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--fg-faint","value":"var(--steel-300)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--fg-label","value":"var(--steel-400)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--border","value":"var(--steel-600)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--border-subtle","value":"var(--steel-700)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--border-strong","value":"var(--steel-500)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--accent","value":"var(--brass-500)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--accent-hover","value":"var(--brass-400)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--accent-ink","value":"var(--brass-ink)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--danger","value":"var(--rust-500)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--font-serif","value":"'EB Garamond', 'Iowan Old Style', 'Georgia', serif","kind":"font","definedIn":"colors_and_type.css"},{"name":"--font-sans","value":"'Geist', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif","kind":"font","definedIn":"colors_and_type.css"},{"name":"--font-mono","value":"'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace","kind":"font","definedIn":"colors_and_type.css"},{"name":"--text-xs","value":"12px","kind":"font","definedIn":"colors_and_type.css"},{"name":"--lh-xs","value":"1.35","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--text-sm","value":"13px","kind":"font","definedIn":"colors_and_type.css"},{"name":"--lh-sm","value":"1.4","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--text-base","value":"14px","kind":"font","definedIn":"colors_and_type.css"},{"name":"--lh-base","value":"1.45","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--text-md","value":"16px","kind":"font","definedIn":"colors_and_type.css"},{"name":"--lh-md","value":"1.5","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--text-lg","value":"18px","kind":"font","definedIn":"colors_and_type.css"},{"name":"--lh-lg","value":"1.45","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--text-xl","value":"22px","kind":"font","definedIn":"colors_and_type.css"},{"name":"--lh-xl","value":"1.3","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--text-2xl","value":"28px","kind":"font","definedIn":"colors_and_type.css"},{"name":"--lh-2xl","value":"1.25","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--text-3xl","value":"36px","kind":"font","definedIn":"colors_and_type.css"},{"name":"--lh-3xl","value":"1.18","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--text-4xl","value":"48px","kind":"font","definedIn":"colors_and_type.css"},{"name":"--lh-4xl","value":"1.1","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--text-5xl","value":"64px","kind":"font","definedIn":"colors_and_type.css"},{"name":"--lh-5xl","value":"1.05","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--text-display","value":"92px","kind":"font","definedIn":"colors_and_type.css"},{"name":"--lh-display","value":"1","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--w-regular","value":"400","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--w-medium","value":"500","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--w-semi","value":"600","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--w-bold","value":"700","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--w-brand","value":"750","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--track-tight","value":"-0.01em","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--track-normal","value":"0","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--track-brand","value":"0.02em","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--track-label","value":"0.08em","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--track-caps","value":"0.12em","kind":"font","definedIn":"colors_and_type.css","annotation":"font"},{"name":"--space-1","value":"2px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--space-2","value":"4px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--space-3","value":"6px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--space-4","value":"8px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--space-5","value":"10px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--space-6","value":"12px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--space-8","value":"16px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--space-10","value":"20px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--space-12","value":"24px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--space-16","value":"32px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--space-20","value":"40px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--space-24","value":"48px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--space-32","value":"64px","kind":"spacing","definedIn":"colors_and_type.css"},{"name":"--radius-sm","value":"8px","kind":"radius","definedIn":"colors_and_type.css"},{"name":"--radius-md","value":"10px","kind":"radius","definedIn":"colors_and_type.css"},{"name":"--radius-lg","value":"12px","kind":"radius","definedIn":"colors_and_type.css"},{"name":"--radius-xl","value":"14px","kind":"radius","definedIn":"colors_and_type.css"},{"name":"--radius-pill","value":"999px","kind":"radius","definedIn":"colors_and_type.css"},{"name":"--hairline","value":"1px solid var(--border)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--hairline-subtle","value":"1px solid var(--border-subtle)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--shadow-tooltip","value":"0 16px 44px rgba(0, 0, 0, 0.35)","kind":"shadow","definedIn":"colors_and_type.css"},{"name":"--shadow-floating","value":"0 20px 70px rgba(0, 0, 0, 0.35)","kind":"shadow","definedIn":"colors_and_type.css"},{"name":"--bar-h","value":"56px","kind":"spacing","definedIn":"colors_and_type.css","annotation":"spacing"},{"name":"--panel-w","value":"360px","kind":"spacing","definedIn":"colors_and_type.css","annotation":"spacing"},{"name":"--ease","value":"cubic-bezier(0.2, 0.6, 0.2, 1)","kind":"other","definedIn":"colors_and_type.css","annotation":"other"},{"name":"--dur-fast","value":"120ms","kind":"other","definedIn":"colors_and_type.css","annotation":"other"},{"name":"--dur","value":"180ms","kind":"other","definedIn":"colors_and_type.css","annotation":"other"},{"name":"--dur-slow","value":"320ms","kind":"other","definedIn":"colors_and_type.css","annotation":"other"},{"name":"--annotate-outline","value":"2px solid var(--accent)","kind":"color","definedIn":"colors_and_type.css"},{"name":"--annotate-offset","value":"2px","kind":"spacing","definedIn":"colors_and_type.css","annotation":"spacing"}],"themes":[],"fonts":[],"brandFonts":[{"family":"EB Garamond","status":"ok","tokens":["--font-serif"],"path":"colors_and_type.css"},{"family":"Geist","status":"ok","tokens":["--font-sans"],"path":"colors_and_type.css"},{"family":"Geist Mono","status":"ok","tokens":["--font-mono"],"path":"colors_and_type.css"}],"source":"spa"}RIFF~WEBPVP8X
0?�ICCP��0mntrRGB XYZ �acsp���- desc�$rXYZgXYZ(bXYZ<wtptPrTRCd(gTRCd(bTRCd(cprt�<mlucenUSsRGBXYZ o�8��XYZ b����XYZ $����XYZ ���-paraff�
Y�
[mlucenUS Google Inc. 2016ALPH) �M�m.�҈�XPԶs�!������
�������$�&VP8 ^0O�*@�>m2�H�"�!#����
�in�q�0<F7y�������Z�������b?��?��On}t�?�������˿��a���������#��A���7������r���+Џ�^�ǿ�������ׁ��{#������������y��w�/���_�?�?������������'����#�hJ(��)�}��2Q`g��َ�m�.J3�����q>��U�
�.�Ӟ�zF�n3�ſ�vRMm��.�*�d��!92{8����q��N�+?P!��j$j�ٷ,^�2*k��y���j+W !P��zW�aeX��>U��ēv���y��b�Ϸ����;��[�A��^���4��F����椆�X�N��I�H}�cA� ����ir|�х����j��d>Z�!���*^C��±g�G 7���%�?���tL�j�_c߷U���>�c��#�У��i1�V$_d�@���O��o��;����L�Yh?�5}��o�R8����R�(�zՙ�,�Yfz�B��=e��,�U�SVg��=e���%����9���5�A"�h��E��|Vڌ=*����g.�[u�z�`;ʦ+������B�k��r���<Kި��h�l��Eb5�P�O��9���#g��X�P�f��� ����o���Aԯ 3���=S�I��~v������J��I���l���Ćo��<1���$�{����v�*]a0mN����w���a��o�\>�����2�?�>z1 Eeg�z�D���Vd�}�)$6FME �2g�T�(E&?岍e�m�T�a����ʤd�86�r��a��TXeWn��8�.���R����A�ٟ�T�EB��
^���X�J?�vR
_�O�[�]��PC� �P��M���5
V
���^S0o�K-��۰��I8Շu�۟��]��)�dm���[K�c�6�IR�z�r/u�������'D�k���&m��t���ݥ��lZw�!Z����� ���ʰ
n���G�)�l�]5�5��?b$����.M����ti�K���C���0�w��*�j��)�!�pK��%���Os��SDZ�>�SXfv$n�ݏ��MCTC��hc�A�<+J-M��}����fB��aqP�6#�95H��7��{�Ϡ����������4���P/��%�F�pS�AB�Ux�U�Gq�"B��|��b�l����Xg%���L(vP3����~meY������B*��ڛ�یê`*jz<fs�k�Y�����jl��������C\��[�a�W������2�k�������x�<MhN�X�$g����I�B���(
� ��;̃�Z��_E�C�v3�x&����Ա�ߵ��]k�ÝR��(�mr�͒�+�i���xV'��%%Mj��Rj�`�wV��>t�@$�}��\�r�4�_�ef�j�?+zʷ��3�����[Kl�;wzmZ�}�bѤ���ȯ
�t�T5f2��F������ Fbx��e+g�����I�S�\%�&��I\VT����y�Uf��>��l�X����?]��l�-9yp�
����0��wRX������X������M���#s��P[�Ǡb�0'TeR�x����ļ2v��j�q0Z�<R.h�:L�7�bUo��BfYG���~2���q{T'��h�rZŴb��qt�t}�L�>�;�]tU�n!*���=�%/�6�-������?ˍ.�9��*Wӌa���^�qIx�!�$\����џ�/����l��jT��ۦ�RG��7G�Р��.D��~Ud��D�=4�F/�l|{���flA�]�x�q���t{� W����'�)l%�M��^!���4��*+�IA��i�?��
�\�����f��ӷ"�
�t6�+ �śq�],8�z7
H�
��XL�n��m���m~��e���G�E���wq�|4���u��k� �9���4]�
ot�7��D[�_`�e�]��*�#�o���Nφd���rp&/NӿÑn�zoD/��h*�%�į��f�M����])�S' �x
8�Dڸ������R��m�n��Ir����omwh,�����<�p���p�W���YjaT1�����2"g���V�L�����d՟l��4ML���i�C��Y���䏓��V�&*8[ATX9�V�u�K�4���Z6L{����5�)�5��d߬AV*Sc�?��]3��@���~gj̑��m��:d�~���y������M,����-�q ³=5D�N(s6�
��[g��sn?�f���)���"�=�֠�?�+�}�9�Eº�-���F},������vg3���A�z����"9ȁL�$��#��T���@��}�ʈ*���\?ne����y[���DZĒM�GF�1��ػ�v2^W�,��q³!·�ü���J5ľ21���^���=0[AD�����?���Ubg�`?o}a�[��̩}*�À_4� ֯L�O� $����~����x8H@B�bҚ�K7����貢��),���Ή�>ƹ�t# ђ�d���E�� ����̵$��
/M�uv�d�4m��Bw0+)V?/�djk�
�L��b�G�_
�+N�<t�@�*:콘�}��F�����sR�P�m�ׅn���;��o*FnI�'�`L�*�_�����0�IǬ#xt;�AK��a�����yL���0��yؖ�G�V��O6�p��E�;S-�+��z,��m��j]�l�zPX1�S��+�$B�Vt��>4jӃ��]v�E:2�=
�̡nO�b�/T�@�D9�{�����B�<?�Jzrq)�&��pp灳���7"�a]W�Q�M���Fy�SL�Jb|A����1��t�����4��3�ƃ/�Z��M��/��E̠=�(o��@-��F��`ĥƶ�.U�Hah�}4�������/Y���L~Go����ר����'/P�6��ɿ��6�+$o�<�yc^��&an�.��ƻ���ep�N���z}�;���t+77�^��JH��0V��[������汌��[�ٙ¾^3a����Z��\Z��;e���n� ��9����M!�0*���طD��cw��27�� �+�$}V�Io���Yd��G����CT�8�-�C*ז���8g�h���7v{F�7�3���T��]���k/�ŀ���3����ₙ#]��0� )�e /�WP=����
�X�\S�ݿ�����{8XSK���&U��_B�Yج�h�I���V�%��]r���>�%�Bgaw�:��g�}�J������S/E�Wk���7�=D���
꾊�I�"r���1b��Buʻi����{4s�f���C�ʊ��'IV����i���aq��5Kȏ��z�M���U/����-����\�=*f�m)��>��*lk�B�s|h��E�fZZ��d����T����9��#�)fw���.��P��w�W;ޏ� |
X��/���NJ*N��b�����^]�#�p`C��������Mf�M%~��䬁�J�/�MV�+�R�����`s���T-�. kz������`1�L��)�6]�2��-s���VH̄�d)ӳ�\�2��dq��6�%Z7���OI���K"ɚμ?�ꩶ*�� p5A=�?cS9�4}����ɳߧ�����t�S��.�'D�1���P����G���o�JY&�ns`y����w�c�{�<َJ�� /�'�Bdl@��C�&g�WŶE���t�����l�v�
2�{@T�R��0�`ٯ�
�&���G��<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 110" width="760" height="110" font-family="Geist Mono, ui-monospace, Menlo, monospace" font-size="12">
<g fill="none" stroke="#3c4557" stroke-width="1">
<rect x="8" y="34" width="160" height="42" rx="10"></rect>
<rect x="208" y="34" width="180" height="42" rx="10"></rect>
<rect x="428" y="34" width="160" height="42" rx="10"></rect>
<rect x="628" y="34" width="120" height="42" rx="10"></rect>
</g>
<g fill="#f7f3ea" text-anchor="middle">
<text x="88" y="60">agent writes</text>
<text x="298" y="54">lavish-axi <file></text>
<text x="298" y="70" fill="#b9c0cf">opens browser UI</text>
<text x="508" y="54">human annotates</text>
<text x="508" y="70" fill="#b9c0cf">queues prompts</text>
<text x="688" y="60">poll → return</text>
</g>
<g stroke="#f4c95d" stroke-width="1.25" fill="none">
<path d="M168 55 L208 55" marker-end="url(#a)"></path>
<path d="M388 55 L428 55" marker-end="url(#a)"></path>
<path d="M588 55 L628 55" marker-end="url(#a)"></path>
</g>
<defs>
<marker id="a" viewBox="0 0 8 8" refX="6" refY="4" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0 0 L8 4 L0 8 z" fill="#f4c95d"></path>
</marker>
</defs>
</svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="80" height="80">
<rect width="80" height="80" rx="18" fill="#0f1115"></rect>
<rect x="0.5" y="0.5" width="79" height="79" rx="17.5" fill="none" stroke="#2a2f3a"></rect>
<circle cx="40" cy="40" r="10" fill="#f4c95d"></circle>
<circle cx="40" cy="40" r="22" fill="none" stroke="#f4c95d" stroke-width="1.25" opacity="0.55"></circle>
<circle cx="40" cy="40" r="32" fill="none" stroke="#f4c95d" stroke-width="0.75" opacity="0.25"></circle>
</svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420 64" width="420" height="64">
<circle class="dot" cx="14" cy="42" r="5"></circle>
<text class="word cream" x="28" y="50" font-size="44">Lavish</text>
<text class="editor dim" x="170" y="46" font-size="18">EDITOR</text>
</svg>/* ============================================================
Lavish Design System — Tokens
For when a rich editor is not rich enough.
============================================================ */
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Geist:wght@400;500;600;700;750&family=Geist+Mono:wght@400;500;600&display=swap');
:root {
/* --- Ink (surfaces) ---------------------------------------- */
--ink-900: #0f1115; /* artifact-frame surround, composer input */
--ink-800: #11141a; /* side panels, annotation card */
--ink-700: #171a21; /* top bar */
--ink-600: #1c212b; /* queued-prompt pill, tooltip body */
/* --- Steel (borders & muted type) -------------------------- */
--steel-700: #2a2f3a; /* subtle border */
--steel-600: #303745; /* default border */
--steel-500: #3c4557; /* strong border */
--steel-400: #8c96aa; /* labelmuted */
--steel-300: #aeb6c6; /* helper text */
--steel-200: #b9c0cf; /* file path, secondary */
--steel-100: #d8deea; /* primary muted */
/* --- Cream (type) ----------------------------------------- */
--cream-50: #fffbf3; /* hover-lifted type */
--cream-100: #f7f3ea; /* default type */
--cream-200: #e8e1cf;
/* --- Brass (accent — singular) ---------------------------- */
--brass-500: #f4c95d; /* THE accent — CTA, annotation outline */
--brass-400: #ffd877; /* hover */
--brass-ink: #17130a; /* type on brass */
/* --- Sage (agent signal) ---------------------------------- */
--sage-900: #172419;
--sage-700: #315f3a;
--sage-300: #8fe39e;
/* --- Amber (user signal) ---------------------------------- */
--amber-900: #25230f;
--amber-700: #5d4d1b;
/* --- Rust (danger) ---------------------------------------- */
--rust-500: #f06464;
/* --- Semantic aliases ------------------------------------- */
--bg: var(--ink-900);
--bg-panel: var(--ink-800);
--bg-bar: var(--ink-700);
--bg-elevated: var(--ink-600);
--fg: var(--cream-100);
--fg-muted: var(--steel-100);
--fg-dim: var(--steel-200);
--fg-faint: var(--steel-300);
--fg-label: var(--steel-400);
--border: var(--steel-600);
--border-subtle: var(--steel-700);
--border-strong: var(--steel-500);
--accent: var(--brass-500);
--accent-hover: var(--brass-400);
--accent-ink: var(--brass-ink);
--danger: var(--rust-500);
/* --- Type families ---------------------------------------- */
--font-serif: 'EB Garamond', 'Iowan Old Style', 'Georgia', serif;
--font-sans: 'Geist', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-mono: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
/* --- Type scale (px, line-height) ------------------------- */
/* line-heights below: @kind font */
--text-xs: 12px; --lh-xs: 1.35; /* @kind font */
--text-sm: 13px; --lh-sm: 1.4; /* @kind font */
--text-base: 14px; --lh-base: 1.45; /* @kind font */ /* product chrome default */
--text-md: 16px; --lh-md: 1.5; /* @kind font */
--text-lg: 18px; --lh-lg: 1.45; /* @kind font */
--text-xl: 22px; --lh-xl: 1.3; /* @kind font */
--text-2xl: 28px; --lh-2xl: 1.25; /* @kind font */
--text-3xl: 36px; --lh-3xl: 1.18; /* @kind font */
--text-4xl: 48px; --lh-4xl: 1.1; /* @kind font */
--text-5xl: 64px; --lh-5xl: 1.05; /* @kind font */
--text-display: 92px; --lh-display: 1; /* @kind font */
/* --- Weight ----------------------------------------------- */
--w-regular: 400; /* @kind font */
--w-medium: 500; /* @kind font */
--w-semi: 600; /* @kind font */
--w-bold: 700; /* @kind font */
--w-brand: 750; /* @kind font */ /* the wordmark + chrome brand */
/* --- Letter-spacing --------------------------------------- */
--track-tight: -0.01em; /* @kind font */
--track-normal: 0; /* @kind font */
--track-brand: 0.02em; /* @kind font */ /* "Lavish Editor" wordmark */
--track-label: 0.08em; /* @kind font */ /* UPPERCASE eyebrows */
--track-caps: 0.12em; /* @kind font */
/* --- Spacing (8-pt + 4/2 half steps) ---------------------- */
--space-1: 2px;
--space-2: 4px;
--space-3: 6px;
--space-4: 8px;
--space-5: 10px;
--space-6: 12px;
--space-8: 16px;
--space-10: 20px;
--space-12: 24px;
--space-16: 32px;
--space-20: 40px;
--space-24: 48px;
--space-32: 64px;
/* --- Radii ----------------------------------------------- */
--radius-sm: 8px;
--radius-md: 10px;
--radius-lg: 12px;
--radius-xl: 14px;
--radius-pill: 999px;
/* --- Borders --------------------------------------------- */
--hairline: 1px solid var(--border);
--hairline-subtle: 1px solid var(--border-subtle);
/* --- Shadows (reserved for floating surfaces) ------------ */
--shadow-tooltip: 0 16px 44px rgba(0, 0, 0, 0.35);
--shadow-floating: 0 20px 70px rgba(0, 0, 0, 0.35);
/* --- Layout --------------------------------------------- */
--bar-h: 56px; /* @kind spacing */
--panel-w: 360px; /* @kind spacing */
/* --- Motion -------------------------------------------- */
--ease: cubic-bezier(0.2, 0.6, 0.2, 1); /* @kind other */ /* ease-out */
--dur-fast: 120ms; /* @kind other */
--dur: 180ms; /* @kind other */
--dur-slow: 320ms; /* @kind other */
/* --- Annotation outline (focus + selected element) ------ */
--annotate-outline: 2px solid var(--accent);
--annotate-offset: 2px; /* @kind spacing */
}
/* ============================================================
Semantic element styles — drop-in defaults
============================================================ */
.lavish-root,
body.lavish {
background: var(--bg);
color: var(--fg);
font-family: var(--font-sans);
font-size: var(--text-base);
line-height: var(--lh-base);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
.lavish .h-display, h1.lavish-display {
font-family: var(--font-serif);
font-size: var(--text-display);
line-height: var(--lh-display);
letter-spacing: var(--track-tight);
font-weight: var(--w-regular);
font-style: italic;
color: var(--fg);
}
.lavish h1, h1.lavish-h1 {
font-family: var(--font-serif);
font-size: var(--text-4xl);
line-height: var(--lh-4xl);
letter-spacing: var(--track-tight);
font-weight: var(--w-regular);
}
.lavish h2, h2.lavish-h2 {
font-family: var(--font-sans);
font-size: var(--text-xl);
line-height: var(--lh-xl);
font-weight: var(--w-semi);
letter-spacing: var(--track-normal);
}
.lavish h3, h3.lavish-h3 {
font-family: var(--font-sans);
font-size: var(--text-md);
line-height: var(--lh-md);
font-weight: var(--w-semi);
}
.lavish .eyebrow, .lavish-eyebrow {
font-family: var(--font-sans);
font-size: 10px;
font-weight: var(--w-bold);
letter-spacing: var(--track-label);
text-transform: uppercase;
color: var(--fg-label);
}
.lavish p, p.lavish-p {
font-family: var(--font-sans);
font-size: var(--text-base);
line-height: var(--lh-base);
color: var(--fg);
text-wrap: pretty;
}
.lavish .lede, p.lavish-lede {
font-family: var(--font-serif);
font-size: var(--text-lg);
line-height: var(--lh-md);
font-style: italic;
color: var(--fg-muted);
}
.lavish code, .lavish kbd, .lavish samp, code.lavish-code {
font-family: var(--font-mono);
font-size: 0.92em;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 1px 6px;
color: var(--cream-200);
}
.lavish .brand, .lavish-brand {
font-family: var(--font-sans);
font-weight: var(--w-brand);
letter-spacing: var(--track-brand);
}
.lavish .pull-quote, blockquote.lavish-quote {
font-family: var(--font-serif);
font-size: var(--text-2xl);
line-height: var(--lh-2xl);
font-style: italic;
color: var(--fg);
border-left: 2px solid var(--accent);
padding-left: var(--space-12);
margin: var(--space-16) 0;
}
/* Preview card shell — used by every card in /preview */
@import url('../colors_and_type.css');
html, body { margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--fg);
font-family: var(--font-sans);
font-size: var(--text-base);
line-height: var(--lh-base);
-webkit-font-smoothing: antialiased;
}
.card {
box-sizing: border-box;
width: 100%;
min-height: 100vh;
padding: 20px 24px;
display: flex;
flex-direction: column;
gap: 16px;
}
.row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.grid { display: grid; gap: 12px; }
.label {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--fg-label);
}
.mono { font-family: var(--font-mono); font-size: 12px; color: var(--fg-faint); }
.serif { font-family: var(--font-serif); }
<!doctype html>
<!-- @dsCard group="Brand" name="Brand · Wordmark" subtitle="Lavish in serif italic, Editor in Geist caps" viewport="700x220" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="row" style="gap:14px;align-items:baseline">
<div style="font-family:var(--font-serif);font-style:italic;font-size:54px;line-height:1;letter-spacing:-0.01em;color:var(--fg)">Lavish</div>
<div style="font-family:var(--font-sans);font-weight:600;letter-spacing:.06em;text-transform:uppercase;font-size:18px;color:var(--fg-dim)">Editor</div>
</div>
<div style="font-family:var(--font-serif);font-style:italic;font-size:22px;line-height:1.25;max-width:560px;color:var(--fg)">
For when a rich editor is not rich enough.
</div>
<div class="mono" style="margin-top:auto;color:var(--fg-faint)">EB Garamond italic · Geist uppercase · v0.1</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Colors" name="Colors · Brass accent" subtitle="The single accent — CTA, outline, brand moment" viewport="700x240" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Brass — the singular accent</div>
<div style="display:grid;grid-template-columns:1.4fr 1fr 1fr;gap:14px;align-items:stretch">
<div style="background:#f4c95d;border-radius:14px;padding:18px;color:#17130a;display:flex;flex-direction:column;justify-content:space-between;min-height:120px">
<div style="font-family:var(--font-serif);font-style:italic;font-size:26px;line-height:1.05">One lamp.<br>One accent.</div>
<div style="display:flex;justify-content:space-between;font-family:var(--font-mono);font-size:11px;opacity:.78">
<span>brass-500</span><span>#f4c95d</span>
</div>
</div>
<div style="background:#ffd877;border:1px solid #f4c95d;border-radius:14px;padding:14px;color:#17130a;display:flex;flex-direction:column;justify-content:space-between">
<div style="font-weight:700">hover</div>
<div class="mono" style="color:#17130a">#ffd877</div>
</div>
<div style="background:#17130a;border:1px solid var(--border);border-radius:14px;padding:14px;display:flex;flex-direction:column;justify-content:space-between">
<div style="font-weight:700;color:#f4c95d">ink on brass</div>
<div class="mono">#17130a</div>
</div>
</div>
<div style="font-family:var(--font-serif);font-style:italic;color:var(--fg-dim);font-size:14px">Used for the primary CTA, the annotation outline, the brand mark — and nothing else.</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Colors" name="Colors · Ink" subtitle="Four surface tones from artifact-surround to elevated" viewport="700x200" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Ink — surfaces</div>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px">
<div><div style="height:88px;background:#0f1115;border:1px solid var(--border);border-radius:12px"></div><div style="margin-top:6px"><div style="font-weight:600">ink-900</div><div class="mono">#0f1115</div></div></div>
<div><div style="height:88px;background:#11141a;border:1px solid var(--border);border-radius:12px"></div><div style="margin-top:6px"><div style="font-weight:600">ink-800</div><div class="mono">#11141a</div></div></div>
<div><div style="height:88px;background:#171a21;border:1px solid var(--border);border-radius:12px"></div><div style="margin-top:6px"><div style="font-weight:600">ink-700</div><div class="mono">#171a21</div></div></div>
<div><div style="height:88px;background:#1c212b;border:1px solid var(--border);border-radius:12px"></div><div style="margin-top:6px"><div style="font-weight:600">ink-600</div><div class="mono">#1c212b</div></div></div>
</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Colors" name="Colors · Signal" subtitle="One brass accent; rust only for destructive" viewport="700x240" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Signal — one lamp</div>
<div style="display:grid;grid-template-columns:1.4fr 1fr;gap:14px;align-items:stretch">
<div style="background:#f4c95d;border-radius:14px;padding:18px;color:#17130a;display:flex;flex-direction:column;justify-content:space-between;min-height:120px">
<div style="font-family:var(--font-serif);font-style:italic;font-size:26px;line-height:1.05">Brass.<br>That's the signal.</div>
<div style="display:flex;justify-content:space-between;font-family:var(--font-mono);font-size:11px;opacity:.78">
<span>attention · accent · CTA</span><span>#f4c95d</span>
</div>
</div>
<div style="background:transparent;border:1px solid #f06464;border-radius:14px;padding:14px;display:flex;flex-direction:column;justify-content:space-between;min-height:120px">
<div style="color:#f06464;font-weight:700">Destructive</div>
<div style="font-family:var(--font-serif);font-style:italic;font-size:14px;color:var(--fg-dim);line-height:1.3">Rust outlines and the word <em>End</em>. Used only when something is about to go away.</div>
<div class="mono" style="color:#f06464">#f06464</div>
</div>
</div>
<div style="font-family:var(--font-serif);font-style:italic;color:var(--fg-dim);font-size:14px;max-width:560px">One accent does the work. Agent and user bubbles share one neutral surface — distinguished by the word above them, not the color underneath.</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Colors" name="Colors · Steel & cream" subtitle="Borders, muted type, paper-warm cream" viewport="700x320" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Steel & cream — supporting</div>
<div class="label" style="color:var(--fg-faint)">Steel (borders & muted type)</div>
<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:8px">
<div style="background:#2a2f3a;height:48px;border-radius:8px"></div>
<div style="background:#303745;height:48px;border-radius:8px"></div>
<div style="background:#3c4557;height:48px;border-radius:8px"></div>
<div style="background:#8c96aa;height:48px;border-radius:8px"></div>
<div style="background:#aeb6c6;height:48px;border-radius:8px"></div>
<div style="background:#b9c0cf;height:48px;border-radius:8px"></div>
<div style="background:#d8deea;height:48px;border-radius:8px"></div>
</div>
<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:8px" class="mono">
<span>700</span><span>600</span><span>500</span><span>400</span><span>300</span><span>200</span><span>100</span>
</div>
<div class="label" style="color:var(--fg-faint);margin-top:8px">Cream (type on ink)</div>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px">
<div style="background:#fffbf3;height:48px;border-radius:8px"></div>
<div style="background:#f7f3ea;height:48px;border-radius:8px"></div>
<div style="background:#e8e1cf;height:48px;border-radius:8px"></div>
</div>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px" class="mono">
<span>50 #fffbf3</span><span>100 #f7f3ea</span><span>200 #e8e1cf</span>
</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Components" name="Components · Annotation card" subtitle="Floats over the artifact with brass border" viewport="700x320" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card" style="padding:24px">
<div class="label">Annotation card — floats over the artifact</div>
<div style="position:relative;height:240px;background:#ffffff;border-radius:12px;border:1px solid var(--border);overflow:hidden">
<!-- fake artifact content -->
<div style="position:absolute;left:24px;top:24px;color:#222;font-family:'EB Garamond',serif;font-size:34px;font-style:italic;outline:2px solid #f4c95d;outline-offset:2px;padding:2px 6px">A well-edited line.</div>
<div style="position:absolute;left:24px;top:84px;color:#444;font-size:14px;max-width:240px">Body copy in the artifact iframe. The selected heading shows the brass outline.</div>
<!-- floating card -->
<div style="position:absolute;left:230px;top:46px;width:280px;padding:12px;border-radius:14px;background:#11141a;color:#f7f3ea;border:1px solid #f4c95d;box-shadow:0 20px 70px rgba(0,0,0,.35);font:14px/1.4 var(--font-sans)">
<div style="font-weight:700;margin-bottom:6px">Annotate <h1></div>
<textarea placeholder="Tell the agent what to change about this element…" rows="3" style="width:100%;min-height:66px;resize:vertical;border-radius:10px;border:1px solid #303745;background:#0f1115;color:#f7f3ea;padding:9px;font:inherit;box-sizing:border-box"></textarea>
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:8px">
<button style="border:0;border-radius:9px;padding:8px 10px;background:#2a2f3a;color:#f7f3ea;font:inherit;font-weight:700;cursor:pointer">Cancel</button>
<button style="border:0;border-radius:9px;padding:8px 10px;background:#f4c95d;color:#17130a;font:inherit;font-weight:700;cursor:pointer">Queue</button>
</div>
</div>
</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Components" name="Components · Chat bubbles" subtitle="User right + elevated; Agent left + sunken" viewport="700x280" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Chat bubbles — alignment + shade</div>
<div style="display:flex;flex-direction:column;gap:10px;max-width:520px">
<div style="align-self:flex-end;max-width:80%;border-radius:14px;padding:10px 12px;background:var(--bg-elevated);border:1px solid var(--border-strong)">
<small style="display:block;color:var(--fg-faint);margin-bottom:4px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;text-align:right">You</small>
<div>Tighten the heading — fewer words, more confidence.</div>
</div>
<div style="align-self:flex-start;max-width:80%;border-radius:14px;padding:10px 12px;background:transparent;border:1px solid var(--border-subtle)">
<small style="display:block;color:var(--fg-faint);margin-bottom:4px;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase">Agent</small>
<div>Done. I shortened it to "For when a rich editor is not rich enough."</div>
</div>
<div style="align-self:flex-start;max-width:80%;border-radius:14px;padding:10px 12px;background:transparent;border:1px solid var(--border-subtle);display:flex;align-items:center;gap:8px;color:var(--fg-muted)">
<span style="width:14px;height:14px;border-radius:999px;border:2px solid var(--steel-600);border-top-color:var(--accent);display:inline-block;animation:spin .8s linear infinite"></span>
<span>Working…</span>
</div>
</div>
<div class="mono" style="color:var(--fg-faint)">user · right-aligned · elevated shade · agent · left-aligned · sunken shade</div>
<style>@keyframes spin{to{transform:rotate(360deg)}}</style>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Components" name="Components · Buttons" subtitle="Brass primary, ink secondary, rust destructive" viewport="700x260" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Buttons</div>
<div class="row">
<button style="border:0;border-radius:10px;padding:9px 14px;background:#f4c95d;color:#17130a;font:inherit;font-weight:700;cursor:pointer">Send to Agent</button>
<button style="border:0;border-radius:10px;padding:9px 14px;background:#ffd877;color:#17130a;font:inherit;font-weight:700;cursor:pointer">hover</button>
<button disabled style="border:0;border-radius:10px;padding:9px 14px;background:#f4c95d;color:#17130a;font:inherit;font-weight:700;opacity:.55;cursor:not-allowed">disabled</button>
</div>
<div class="row">
<button style="border:0;border-radius:10px;padding:9px 14px;background:#2a2f3a;color:#f7f3ea;font:inherit;font-weight:600;cursor:pointer">Annotate</button>
<button style="border:0;border-radius:10px;padding:9px 14px;background:#303745;color:#f7f3ea;font:inherit;font-weight:600;cursor:pointer">hover</button>
<button style="border:0;border-radius:10px;padding:9px 14px;background:transparent;color:#d8deea;font:inherit;font-weight:500;cursor:pointer;border:1px solid #303745">Cancel</button>
</div>
<div class="row">
<button style="border:0;border-radius:10px;padding:9px 14px;background:#f06464;color:white;font:inherit;font-weight:700;cursor:pointer">End session</button>
</div>
<div class="mono" style="color:var(--fg-faint)">9–10 / 12–14 padding · radius 10 · no shadow</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Components" name="Components · Composer" subtitle="Textarea, empty-send hint, split send menu" viewport="700x280" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Composer - split send</div>
<div style="display:grid;gap:8px;padding:12px 16px;border:1px solid var(--border-subtle);border-radius:14px;background:var(--bg-panel);max-width:520px">
<textarea placeholder="Write a message for the agent…" rows="3"
style="width:100%;min-height:82px;resize:vertical;border-radius:12px;border:1px solid #303745;background:#0f1115;color:#f7f3ea;padding:10px;font:inherit;box-sizing:border-box"></textarea>
<div style="display:flex;gap:8px;justify-content:flex-end;align-items:center;position:relative">
<span style="margin-right:auto;color:var(--fg-faint);font-size:11px;line-height:1.35">Write a message or annotate an element first.</span>
<div style="display:inline-flex;align-items:stretch">
<button style="border:0;border-radius:10px 0 0 10px;padding:9px 14px;background:#f4c95d;color:#17130a;font:inherit;font-weight:700;cursor:pointer;white-space:nowrap">Send to Agent</button>
<button title="Send options" style="border:0;border-left:1px solid rgba(23,19,10,.22);border-radius:0 10px 10px 0;padding:9px;background:#f4c95d;color:#17130a;font:inherit;font-weight:700;cursor:pointer;display:grid;place-items:center;line-height:1">⌄</button>
</div>
<div style="position:absolute;right:0;bottom:44px;width:208px;background:var(--ink-700);border:1px solid var(--steel-600);border-radius:11px;box-shadow:var(--shadow-tooltip);padding:6px;box-sizing:border-box">
<div style="display:flex;align-items:center;gap:9px;padding:8px 9px;border-radius:8px;font-size:13px;color:var(--cream-100)">Send to Agent</div>
<div style="display:flex;align-items:center;gap:9px;padding:8px 9px;border-radius:8px;font-size:13px;color:var(--rust-500)">Send & end session</div>
</div>
</div>
</div>
<div class="mono" style="color:var(--fg-faint)">empty send keeps button enabled · inline hint · split action menu</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Components" name="Components · Queued pills" subtitle="Pending-prompt chips with close affordance" viewport="700x200" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Queued-prompt pills</div>
<div style="display:flex;flex-wrap:wrap;gap:6px;max-width:560px">
<div style="display:inline-flex;align-items:center;gap:6px;border:1px solid #3c4557;border-radius:999px;background:#1c212b;color:#d8deea;padding:5px 7px 5px 11px;font-size:12px;font-weight:700">
<span style="max-width:240px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">Tighten the heading — fewer words, more confidence</span>
<button style="width:18px;height:18px;border:0;border-radius:999px;padding:0;background:#303745;color:#d8deea;line-height:1;font-size:14px;cursor:pointer">×</button>
</div>
<div style="display:inline-flex;align-items:center;gap:6px;border:1px solid #3c4557;border-radius:999px;background:#1c212b;color:#d8deea;padding:5px 7px 5px 11px;font-size:12px;font-weight:700">
<span>Make the CTA italic</span>
<button style="width:18px;height:18px;border:0;border-radius:999px;padding:0;background:#303745;color:#d8deea;line-height:1;font-size:14px;cursor:pointer">×</button>
</div>
<div style="display:inline-flex;align-items:center;gap:6px;border:1px solid #3c4557;border-radius:999px;background:#1c212b;color:#d8deea;padding:5px 7px 5px 11px;font-size:12px;font-weight:700">
<span>Move the footer below the diagram</span>
<button style="width:18px;height:18px;border:0;border-radius:999px;padding:0;background:#303745;color:#d8deea;line-height:1;font-size:14px;cursor:pointer">×</button>
</div>
</div>
<div class="mono" style="color:var(--fg-faint)">12px / 700 · pill radius · max-preview 220px ellipsised · × chip is a smaller round button</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Components" name="Components · Top bar" subtitle="Wordmark, Annotate switch, overflow menu" viewport="700x200" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card" style="padding:0;gap:0">
<div style="padding:16px 24px 8px"><div class="label">Top bar — 56px sticky</div></div>
<div style="height:56px;display:flex;align-items:center;gap:14px;padding:0 16px;background:#171a21;border-bottom:1px solid #2a2f3a;box-sizing:border-box">
<div style="display:flex;align-items:center;gap:8px;white-space:nowrap">
<span style="font-family:var(--font-serif);font-style:italic;font-size:22px;line-height:1;color:var(--fg)">Lavish</span>
<span style="font-family:var(--font-sans);font-size:10px;font-weight:600;letter-spacing:.18em;text-transform:uppercase;color:var(--fg-muted);position:relative;top:1px">Editor</span>
</div>
<div style="flex:1"></div>
<button style="display:inline-flex;align-items:center;gap:8px;color:var(--fg-muted);font:inherit;font-size:13px;white-space:nowrap;background:none;border:0;padding:0;cursor:pointer" aria-pressed="true">
<span style="width:34px;height:20px;border-radius:999px;background:var(--accent);position:relative;flex-shrink:0"><span style="position:absolute;top:2px;left:16px;width:16px;height:16px;border-radius:999px;background:#17130a"></span></span>
<span>Annotate</span>
</button>
<button style="width:34px;height:34px;display:grid;place-items:center;border:1px solid #2a2f3a;background:transparent;color:var(--fg-muted);border-radius:9px;padding:0;cursor:pointer" title="More">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1.4"/><circle cx="12" cy="12" r="1.4"/><circle cx="12" cy="19" r="1.4"/></svg>
</button>
</div>
<div style="padding:10px 24px 18px" class="mono">wordmark · spacer · Annotate switch · overflow menu (path + copy, reload, snapshot, end session)</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Brand" name="Brand · Voice" subtitle="On-brand vs off-brand example copy" viewport="700x240" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Voice — example copy</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:24px">
<div>
<div class="label" style="color:#8fe39e;margin-bottom:8px">On brand</div>
<ul style="margin:0;padding-left:18px;display:flex;flex-direction:column;gap:8px;font-family:var(--font-serif);font-style:italic;font-size:17px;line-height:1.35;color:var(--fg)">
<li>For when a rich editor is not rich enough.</li>
<li>HTML is the new markdown.</li>
<li>Tell the agent what to change about this element…</li>
<li>Session ended. Return to your agent to continue.</li>
</ul>
</div>
<div>
<div class="label" style="color:#f06464;margin-bottom:8px">Off brand</div>
<ul style="margin:0;padding-left:18px;display:flex;flex-direction:column;gap:8px;color:var(--fg-dim);text-decoration:line-through;font-size:15px;line-height:1.35">
<li>🚀 The seamless, powerful way to edit HTML!</li>
<li>Hey there! Let us know what you'd like 😊</li>
<li>Click here to revolutionize your workflow.</li>
<li>Whoops! Session is gone. Bye!</li>
</ul>
</div>
</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Brand" name="Brand · Iconography" subtitle="Words first; Lucide stroke icons when needed" viewport="700x240" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Iconography — words first, Lucide second</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:18px;align-items:start">
<div>
<div style="font-family:var(--font-serif);font-style:italic;font-size:22px;line-height:1.25;margin-bottom:10px">In a literary product,<br>words are the iconography.</div>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button style="border:0;border-radius:10px;padding:9px 12px;background:#2a2f3a;color:#f7f3ea;font:inherit;font-weight:600">Annotate</button>
<button style="border:0;border-radius:10px;padding:9px 12px;background:#2a2f3a;color:#f7f3ea;font:inherit;font-weight:600">Queue</button>
<button style="border:0;border-radius:10px;padding:9px 12px;background:#2a2f3a;color:#f7f3ea;font:inherit;font-weight:600">Snapshot</button>
</div>
</div>
<div>
<div class="label" style="margin-bottom:8px">When needed, Lucide</div>
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:12px;color:#f7f3ea">
<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>
<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg>
<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 9h6v6H9z"/></svg>
<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</div>
<div class="mono" style="margin-top:10px;color:var(--fg-faint)">1.5 stroke · round joins · 20–24 box · currentColor only</div>
</div>
</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Spacing" name="Spacing · Radii" subtitle="8 / 10 / 12 / 14 / 999 — no 4px" viewport="700x220" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Radii — no 4px</div>
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:14px;align-items:end">
<div><div style="height:80px;background:var(--bg-elevated);border:1px solid var(--border);border-radius:8px"></div><div class="mono" style="margin-top:6px">8 · sm</div><div style="font-size:11px;color:var(--fg-dim)">chips, tooltips</div></div>
<div><div style="height:80px;background:var(--bg-elevated);border:1px solid var(--border);border-radius:10px"></div><div class="mono" style="margin-top:6px">10 · md</div><div style="font-size:11px;color:var(--fg-dim)">buttons</div></div>
<div><div style="height:80px;background:var(--bg-elevated);border:1px solid var(--border);border-radius:12px"></div><div class="mono" style="margin-top:6px">12 · lg</div><div style="font-size:11px;color:var(--fg-dim)">inputs</div></div>
<div><div style="height:80px;background:var(--bg-elevated);border:1px solid var(--border);border-radius:14px"></div><div class="mono" style="margin-top:6px">14 · xl</div><div style="font-size:11px;color:var(--fg-dim)">bubbles, cards</div></div>
<div><div style="height:80px;background:var(--bg-elevated);border:1px solid var(--border);border-radius:999px"></div><div class="mono" style="margin-top:6px">999 · pill</div><div style="font-size:11px;color:var(--fg-dim)">queued pills</div></div>
</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Spacing" name="Spacing · Shadows" subtitle="Tooltip and floating-surface shadow only" viewport="700x280" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Shadows — reserved for floating surfaces only</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:24px;padding:8px">
<div>
<div style="background:var(--ink-800);border:1px solid var(--accent);border-radius:14px;padding:14px;box-shadow:0 16px 44px rgba(0,0,0,.35)">
<div style="font-weight:700;margin-bottom:6px">Tooltip</div>
<div style="color:var(--fg-dim);font-size:12px">Sits over chrome. 0 16px 44px / .35</div>
</div>
<div class="mono" style="margin-top:10px">tooltip · 0 16 44 / .35</div>
</div>
<div>
<div style="background:var(--ink-800);border:1px solid var(--accent);border-radius:14px;padding:14px;box-shadow:0 20px 70px rgba(0,0,0,.35)">
<div style="font-weight:700;margin-bottom:6px">Annotation card</div>
<div style="color:var(--fg-dim);font-size:12px">Sits over the artifact. 0 20px 70px / .35</div>
</div>
<div class="mono" style="margin-top:10px">floating · 0 20 70 / .35</div>
</div>
</div>
<div style="font-family:var(--font-serif);font-style:italic;color:var(--fg-dim)">Never on buttons. Never on cards inside a panel. Paper on ink, not card stack.</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Spacing" name="Spacing · 8-pt scale" subtitle="2 / 4 / 6 / 8 / 12 / 16 / 24 / 32 / 48 / 64" viewport="700x200" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Spacing — 8-pt grid</div>
<div style="display:flex;align-items:flex-end;gap:10px">
<div style="display:flex;flex-direction:column;align-items:center;gap:6px"><div style="width:36px;height:2px;background:var(--brass-500)"></div><div class="mono">2</div></div>
<div style="display:flex;flex-direction:column;align-items:center;gap:6px"><div style="width:36px;height:4px;background:var(--brass-500)"></div><div class="mono">4</div></div>
<div style="display:flex;flex-direction:column;align-items:center;gap:6px"><div style="width:36px;height:6px;background:var(--brass-500)"></div><div class="mono">6</div></div>
<div style="display:flex;flex-direction:column;align-items:center;gap:6px"><div style="width:36px;height:8px;background:var(--brass-500)"></div><div class="mono">8</div></div>
<div style="display:flex;flex-direction:column;align-items:center;gap:6px"><div style="width:36px;height:12px;background:var(--brass-500)"></div><div class="mono">12</div></div>
<div style="display:flex;flex-direction:column;align-items:center;gap:6px"><div style="width:36px;height:16px;background:var(--brass-500)"></div><div class="mono">16</div></div>
<div style="display:flex;flex-direction:column;align-items:center;gap:6px"><div style="width:36px;height:24px;background:var(--brass-500)"></div><div class="mono">24</div></div>
<div style="display:flex;flex-direction:column;align-items:center;gap:6px"><div style="width:36px;height:32px;background:var(--brass-500)"></div><div class="mono">32</div></div>
<div style="display:flex;flex-direction:column;align-items:center;gap:6px"><div style="width:36px;height:48px;background:var(--brass-500)"></div><div class="mono">48</div></div>
<div style="display:flex;flex-direction:column;align-items:center;gap:6px"><div style="width:36px;height:64px;background:var(--brass-500)"></div><div class="mono">64</div></div>
</div>
<div style="font-family:var(--font-serif);font-style:italic;color:var(--fg-dim);max-width:560px">8-pt grid with 4 and 2 half-steps. The chrome lives between 6 and 32.</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Components" name="Components · Annotation outline" subtitle="Same brass ring for hover, focus, and selected" viewport="700x220" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Annotation outline = focus outline</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:center">
<div style="background:#fff;border-radius:12px;padding:32px;display:flex;align-items:center;justify-content:center">
<div style="font-family:'EB Garamond',serif;font-style:italic;font-size:32px;color:#222;outline:2px solid #f4c95d;outline-offset:2px;padding:4px 8px">selected element</div>
</div>
<div>
<div style="font-family:var(--font-serif);font-style:italic;font-size:18px;color:var(--fg);max-width:300px">Attention is here. The hover, the selected element, and the focus ring share one visual language — a 2px brass outline, 2px offset.</div>
<div class="mono" style="margin-top:10px;color:var(--fg-faint)">outline: 2px solid #f4c95d · offset 2px</div>
</div>
</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Type" name="Type · Display" subtitle="EB Garamond italic at 84 — the brand moment" viewport="700x230" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Type — display</div>
<div style="font-family:var(--font-serif);font-style:italic;font-size:84px;line-height:1;letter-spacing:-0.01em">Lavish.</div>
<div style="font-family:var(--font-serif);font-size:18px;color:var(--fg-dim);max-width:520px">EB Garamond italic · 84 / 100 · used for the brand moment, marketing headlines, the title of a piece.</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Type" name="Type · Families" subtitle="EB Garamond · Geist · Geist Mono" viewport="700x240" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Type families</div>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px">
<div>
<div style="font-family:'EB Garamond',serif;font-size:56px;line-height:1;font-style:italic">Aa</div>
<div style="margin-top:6px;font-weight:600">EB Garamond</div>
<div class="mono">serif · display</div>
<div style="font-family:'EB Garamond',serif;font-style:italic;color:var(--fg-dim);margin-top:6px">brand · headlines · pull quotes</div>
</div>
<div>
<div style="font-family:'Geist',sans-serif;font-size:56px;line-height:1;font-weight:750;letter-spacing:.02em">Aa</div>
<div style="margin-top:6px;font-weight:600">Geist</div>
<div class="mono">sans · body & UI</div>
<div style="color:var(--fg-dim);margin-top:6px">chrome · buttons · labels</div>
</div>
<div>
<div style="font-family:'Geist Mono',monospace;font-size:56px;line-height:1">Aa</div>
<div style="margin-top:6px;font-weight:600">Geist Mono</div>
<div class="mono">mono · technical</div>
<div style="color:var(--fg-dim);margin-top:6px">selectors · paths · CLI</div>
</div>
</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Type" name="Type · Pull quote" subtitle="Garamond italic on a 2px brass rule" viewport="700x220" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Type — pull quote</div>
<blockquote style="font-family:var(--font-serif);font-style:italic;font-size:30px;line-height:1.22;border-left:2px solid var(--accent);padding-left:20px;margin:0;color:var(--fg);max-width:640px">
HTML is the new markdown.<br>
Lavish is the new editor for your HTML artifacts.
</blockquote>
<div class="mono" style="margin-top:auto">EB Garamond italic · 30 / 1.22 · 2px brass rule, 20px gutter</div>
</div></body></html>
<!doctype html>
<!-- @dsCard group="Type" name="Type · Scale" subtitle="h1 → eyebrow with sizes and line-heights" viewport="700x320" --><html><head><meta charset="utf-8"><link rel="stylesheet" href="_base.css"></head>
<body><div class="card">
<div class="label">Type — scale</div>
<div style="display:grid;grid-template-columns:80px 1fr 120px;column-gap:16px;row-gap:6px;align-items:baseline">
<div class="mono">48 / 1.1</div><div style="font-family:var(--font-serif);font-size:48px;line-height:1.1">A well-edited line.</div><div class="mono">h1 · serif</div>
<div class="mono">28 / 1.25</div><div style="font-size:28px;line-height:1.25;font-weight:600">An honest section.</div><div class="mono">h2 · sans</div>
<div class="mono">18 / 1.45</div><div style="font-family:var(--font-serif);font-style:italic;font-size:18px;color:var(--fg-muted)">A lede, set in serif italic.</div><div class="mono">lede</div>
<div class="mono">14 / 1.45</div><div style="font-size:14px">Body copy in Geist Sans, 14px / 1.45 — the product chrome default.</div><div class="mono">body</div>
<div class="mono">12 / 1.35</div><div style="font-size:12px;color:var(--fg-dim)">Helper. Captions. File paths in the bar.</div><div class="mono">small</div>
<div class="mono">10 / 1.4</div><div style="font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--fg-label)">EYEBROW</div><div class="mono">eyebrow</div>
</div>
</div></body></html>
Lavish Design System
_For when a rich editor is not rich enough._
This is the brand & design system for Lavish — an in-browser agentic editor that opens agent-generated HTML artifacts in a local browser, lets a human pinpoint elements or selected text, annotate them, and ship that feedback back to the agent.
The product feels like a quiet reading room with a brass lamp: dark ink walls, a single warm gold accent, generous type set in a literary serif beside a clean technical sans. Elegant. Minimal. Futuristic. _Lavish._
---
Sources
This system was reverse-engineered from the public source of truth:
- Codebase: `kunchenguid/lavish-axi` — the CLI + local HTTP server that ships the Lavish Editor UI. The chrome HTML originates in
src/server.js(createChromeHtml), chrome styling and browser behavior originate insrc/chrome.cssandsrc/chrome-client.js, and the in-iframe annotation SDK and its styling originate insrc/artifact-sdk.js(createArtifactSdk). The README, AGENTS.md and CONTRIBUTING.md provided the product narrative. - Brand promise is paraphrased from the repository README: _"HTML is the new markdown. Lavish is the new editor for your HTML artifacts."_
No Figma file or slide deck was provided. Slide templates were therefore not generated; if you have a deck, point this skill at it and I'll add a slides/ folder.
---
Product context
Lavish is one product wearing two hats:
| Surface | Audience | What it looks like |
|---|---|---|
CLI (lavish-axi) | Coding agents (Claude Code, Cursor, etc.) and the developers driving them | A long-polling, AXI-shaped command surface. Output is TOON-serialized. Not visually designed — it's _agent-ergonomic._ |
| Editor chrome | Humans reviewing an agent's HTML artifact | A two-pane browser app: the artifact in an iframe on the left, a conversation panel on the right, a session bar across the top. Click any element or select text in the artifact, queue a prompt, send it to the agent. |
The brand surface this design system serves is the Editor chrome. Marketing, docs, decks and screenshots should all feel like they came from the same hand that drew the chrome.
---
Index
.
├── README.md ← you are here
├── SKILL.md ← skill manifest (read this when invoking as an Agent Skill)
├── colors_and_type.css ← design tokens: colors, type scale, spacing, radii, shadows
├── fonts/ ← self-hosted webfonts (EB Garamond, Geist, Geist Mono)
├── assets/ ← logos, marks, glyphs, background imagery
├── preview/ ← Design System tab cards (color, type, components…)
├── ui_kits/
│ └── editor/ ← high-fidelity recreation of the Lavish Editor chrome
│ ├── index.html ← clickable prototype: artifact iframe + chat + annotation card
│ ├── *.jsx ← React components (TopBar, ChatPanel, ArtifactFrame, …)
│ └── README.md ← how the kit is composed---
Content fundamentals
Lavish copy reads like a well-edited reading-room sign — _short, declarative, a little literary._ It is written by people who care about a comma.
Voice
- Second-person, agent-aware. Most copy addresses the user (_"Tell your agent…"_), but it never forgets there are _two_ readers: a human and an agent. Where it's helpful, the copy explicitly addresses the agent in the same breath (_"agents should normally omit it"_).
- Confident without bravado. No exclamation marks. No "🚀". The product asserts what it _is_ in one line — _"For when a rich editor is not rich enough"_ — and lets the rest of the page work.
- Literary cadence. Sentences are crafted, not assembled. Words like _artifact_, _snapshot_, _canonical_, _ergonomic_ show up unforced. Avoid SaaS adjectives ("seamless," "powerful," "revolutionary").
Tone
- Calm. Considered. Specific. When something is technical, name it precisely. When something is a feeling, write it as a feeling.
- A wink, occasionally. _"git push no-mistakes."_ _"For when a rich editor is not rich enough."_ A dry, deadpan sense of humor — never goofy.
Casing
- Sentence case for most user-facing labels. Primary action labels may use title case when they read as proper actions (_"Send to Agent"_); menu actions such as _"Reload artifact"_, _"Copy DOM snapshot"_, _"Send & end session"_, and _"End session"_ use sentence case.
- lowercase command names (
lavish-axi,pnpm run build) — never SHOUTY, neverLavish-Axi. - The brand mark Lavish is Title Case in prose; in product chrome it's set in a slightly heavier weight (
font-weight: 750) with a touch of tracking (letter-spacing: .02em).
Pronouns
- Speak to the user as you.
- Speak of the agent as the agent — third person, no anthropomorphizing pet name.
- _"We"_ is reserved for the team writing docs (rare); never _"our AI."_
Emoji & ornamentation
- No emoji. Anywhere. Not in headers, not in buttons, not in error toasts.
- Unicode dingbats sparingly. An em dash (—), a middle dot (·), a section sign (§) are welcome. ASCII box-drawing diagrams are _on-brand_ (see the README's How It Works diagram).
- No exclamation marks except in code (
fn!()).
Examples lifted directly from the product
_"For when a rich editor is not rich enough."_
_"HTML is the new markdown. Lavish is the new editor for your HTML artifacts."_
_"Browser-native review · Precise feedback · Agent-ergonomic interface"_
_"Session ended. Return to your agent to continue."_
_"Write a message for the agent…"_
_"Write a message or annotate an element first."_
_"Tell the agent what to change about this element…"_
If new copy doesn't sit comfortably next to those lines, rewrite it.
---
Visual foundations
The mood. Late-evening reading light. A dark wood desk. One brass lamp. The page you're editing is the only bright thing in the room.
Colors
- Ink — the canvas.
#0f1115for the artifact frame surround and composer input.#11141afor side panels.#171a21for the top bar. Never pure black: the warm cream type would feel clinical against it. - Cream — the type.
#f7f3ea. A paper-warm off-white that reads at low contrast as candlelit, at high contrast as legible. All primary type sits on ink in cream. - Brass — the single accent.
#f4c95d. Used for the primary CTA, the annotation outline (2px solid, 2px offset), selected-text range highlights (rgba(244,201,93,.28)with argba(244,201,93,.45)stroke), the brand mark moment, and absolutely nothing else. Its ink-on-brass pair is#17130a— a deep almost-black that keeps the gold from feeling like a neon button. - Sage — the _agent_ signal.
#172419background,#315f3aborder,#8fe39efor the working spinner. Used only on agent chat bubbles and the working indicator. - Amber — the _user_ signal.
#25230fbackground,#5d4d1bborder. Used only on user chat bubbles and queued-prompt pills. - Rust — the danger signal.
#f06464. Reserved for _End session_, _Send & end session_, and destructive confirmations. - Steels — the grays.
#2a2f3a/#303745/#3c4557for borders, ranked subtle → strong.#b9c0cf/#d8deea/#aeb6c6/#8c96aafor muted type, ranked bright → dim.
No gradients. No glassmorphism. No bluish-purple anything.
Type
Three families do all the work:
- Display & literary moments → EB Garamond (serif). The brand mark, marketing headlines, the title of a piece. Italic feels especially right for callouts and pull-quotes — Garamond's italic is one of the most beautiful in the world.
- Body & UI → Geist Sans (sans). The repository's chrome uses
ui-sans-serif, system-ui, sans-serif; Geist is the closest modern foundry sans with the same low-fuss tone. 14px / 1.4 line-height in product chrome, scaled up for marketing. - Code & technical → Geist Mono (mono). Replaces
ui-monospace, SFMono-Regular, Menlo. Used for selectors, file paths, CLI snippets, anywhere the user is reading literal characters.
The brand mark Lavish Editor is set in Geist Sans at font-weight: 750 with letter-spacing: .02em. Bold but not heavy. The serif is for prose — never UI labels.
Spacing & rhythm
- 8-pt grid with a
4and2half-step. The chrome uses6 / 8 / 10 / 12 / 16 / 24 / 32repeatedly. - Top bar height: 56px. Sticky. The one truly fixed element.
- Panel gutter: 360px for the side conversation panel; the artifact takes the rest.
- Composer padding:
12px 16px. Buttons:9–10px 12px.
Radii
8px— input affordances inside cards (the selector chip, tooltip panels)10px— buttons, small cards12px— textareas, large inputs, composer pills' parent14px— chat bubbles, annotation card999px— queued-prompt pills only
There is no `border-radius: 4px` in this system. We round generously or not at all.
Borders
Always 1px. Borders are the _only_ way one ink surface separates from another — there are no drop shadows between panels.
Shadows
Shadows are reserved for _floating_ surfaces:
- Tooltip / popover:
0 16px 44px rgba(0,0,0,.35) - Annotation card (lives over the artifact):
0 20px 70px rgba(0,0,0,.35)
Never on buttons. Never on cards that sit _inside_ a panel. The aesthetic is _paper on ink,_ not _card stack._
Backgrounds & imagery
- No background images in chrome. The product is the artifact; the chrome gets out of the way.
- Marketing imagery: when present, prefer warm-toned photography (candlelight, brass, leather, vellum). Subtle film grain is welcome. No stock-photo handshakes. No 3D blobs.
- No repeating patterns. A single hairline rule, occasionally a thin top-border on a section, is plenty.
Animation
- Fades over moves. Opacity and color transitions, 120–180ms,
ease-out. - One exception: the agent-working spinner — a steady
0.8s linearrotation. Borrowed directly from the product. - No bounce. No spring. No elastic. Lavish does not boing.
States
- Hover (interactive surfaces): raise opacity, never lighten. On already-bright elements (the brass CTA), shift the background toward
#ffd877. On muted text, lift cream to#fffbf3. - Press: drop opacity to ~0.85; no scale, no shadow shift.
- Disabled:
opacity: .55; cursor: not-allowed;— directly from the product. - Focus: a 2px brass outline, 2px offset. Identical to the annotation hover/select outline — focus and "this element is being annotated" use the _same_ visual language because they mean the same thing: _attention is here._
Transparency & blur
Almost never. The annotation card is solid #11141a over the artifact — not a translucent veil. The only acceptable blur is the artifact iframe being momentarily hidden during a session-ended flash.
Cards
A "card" in Lavish is a slab of #11141a or #1c212b with a 1px border (#303745) and a generous radius (12–14px). No shadow. No left-color-accent stripe. The content is the card; the chrome is the frame.
Layout rules
- The top bar is fixed at 56px, full-width, sticky.
- The side conversation panel is a fixed 360px wide on the right.
- The artifact takes the remainder.
- The annotation card is positioned relative to the clicked element or selected text range via
getBoundingClientRect()and clamped 12px from any viewport edge. - The chat input lives at the bottom of the side panel; pills (queued prompts) sit _above_ the textarea, never inside it.
---
Iconography
Lavish uses icons only where they reduce chrome, never as ornament. Read that twice.
The v2 product chrome keeps primary affordances word-first: the brand is text, the mode control reads _Annotate_, the main button reads _Send to Agent_, and the annotation card opens with <h2>Annotate <div></h2> for elements or Annotate text for selected text. Compact chrome may use small current-color SVGs for the overflow menu, copy affordance, reload, snapshot, end, send, and split-button caret, but those icons support visible labels or tooltips rather than replacing product language.
When iconography is genuinely needed (marketing, an empty state, a settings menu in a future surface), follow these rules:
1. Stroke, not fill. 1.5px stroke, round joins, round caps. 2. 20×20 viewBox for inline UI icons; 24×24 for larger ones. 3. Currentcolor only. Icons inherit the parent's color so they tint cream on ink, brass on the CTA, sage on agent bubbles. 4. Lucide as the substitute set. The codebase does not ship its own icon font or SVG sprite, so this system uses Lucide at the CDN as the closest aesthetic match (thin, geometric, monoline). Flagged substitution — see Caveats. 5. No emoji. No Unicode dingbat icons in the chrome. The em dash and middle dot are fine in prose; ✅ ✨ 🔥 are not on brand and will never be. 6. The brand mark is wordmark only — _Lavish Editor_ set in Geist Sans 750 with .02em tracking. There is no symbol. (assets/lavish-wordmark.svg.)
To use Lucide via CDN:
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
<i data-lucide="square-pen"></i>
<script>
lucide.createIcons();
</script>If you reach for an icon and can't justify it in one sentence, delete it.
---
Caveats (please read)
1. Font substitution. The repo uses system fonts (ui-sans-serif, ui-monospace). For the brand surface I've upgraded to Geist Sans + Geist Mono (both Google Fonts) and added EB Garamond as the literary serif. These are aesthetic _additions_ — if you prefer to stay system-only, swap the --font-sans and --font-mono variables in colors_and_type.css and remove the @import. If you have licensed faces you'd rather use (e.g. _Söhne_, _GT America_, _Berkeley Mono_), drop them into fonts/ and update the same variables. 2. No icon font in the repo. I'm substituting Lucide (CDN). If you want a different set (Phosphor, Heroicons, Tabler), say the word. 3. No Figma / no slides. I built only what the codebase justifies. Hand me a Figma URL or a deck and I'll add slides/ and a marketing kit. 4. The product surface I've recreated is the Editor chrome. The CLI is intentionally not "designed" — it's TOON output and tables — so it lives only in README.md, not in ui_kits/.
Your turn: open the Design System tab to flip through every card, then open ui_kits/editor/index.html to play with the clickable chrome. Tell me what feels off. Specifically: the gold, the serif/sans pairing, the chat-bubble palette, and whether the agent-as-sage / user-as-amber metaphor lands. Those four decisions cascade into everything else.
/* global React */
function AnnotationCard({ target, onCancel, onQueue }) {
const { useState, useEffect, useRef } = React;
const [value, setValue] = useState("");
const ref = useRef(null);
const taRef = useRef(null);
// Position: clamp 12px from any viewport edge, prefer below the target
const [pos, setPos] = useState({ left: 0, top: 0 });
useEffect(() => {
if (!target || !ref.current) return;
const card = ref.current;
const w = card.offsetWidth, h = card.offsetHeight;
const r = target.rect;
const left = Math.min(Math.max(12, r.left), window.innerWidth - w - 12);
const top = Math.min(Math.max(12, r.bottom + 8), window.innerHeight - h - 12);
setPos({ left, top });
taRef.current && taRef.current.focus();
}, [target]);
const annoStyles = {
card: {
position: "fixed",
left: pos.left,
top: pos.top,
width: "min(320px, calc(100vw - 24px))",
padding: 12,
borderRadius: 14,
background: "var(--ink-800)",
color: "var(--cream-100)",
border: "1px solid var(--brass-500)",
boxShadow: "var(--shadow-floating)",
fontFamily: "var(--font-sans)",
fontSize: 14,
lineHeight: 1.4,
zIndex: 50,
},
heading: { fontWeight: 700, marginBottom: 6 },
ta: {
width: "100%",
minHeight: 86,
resize: "vertical",
borderRadius: 10,
border: "1px solid var(--steel-600)",
background: "var(--ink-900)",
color: "var(--cream-100)",
padding: 9,
font: "inherit",
boxSizing: "border-box",
},
row: { display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 8 },
btn: { border: 0, borderRadius: 9, padding: "8px 10px", fontWeight: 700, fontFamily: "inherit", cursor: "pointer", fontSize: 13 },
cancel: { background: "var(--steel-700)", color: "var(--cream-100)" },
send: { background: "var(--brass-500)", color: "var(--brass-ink)" },
};
if (!target) return null;
return (
<div ref={ref} style={annoStyles.card} onClick={(e) => e.stopPropagation()}>
<div style={annoStyles.heading}>Annotate <{target.tag}></div>
<textarea
ref={taRef}
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Tell the agent what to change about this element…"
style={annoStyles.ta}
/>
<div style={annoStyles.row}>
<button style={{ ...annoStyles.btn, ...annoStyles.cancel }} onClick={onCancel}>Cancel</button>
<button
style={{ ...annoStyles.btn, ...annoStyles.send, opacity: value.trim() ? 1 : 0.55, cursor: value.trim() ? "pointer" : "not-allowed" }}
onClick={() => value.trim() && onQueue(value.trim())}
>
Queue
</button>
</div>
</div>
);
}
window.AnnotationCard = AnnotationCard;
/* global React, ReactDOM, TopBar, Artifact, AnnotationCard, ChatPanel */
const { useState, useEffect, useCallback } = React;
const CANNED_REPLIES = [
"Done. I tightened the heading and softened the lede — take a look.",
"Updated. The CTA is now italic Garamond on brass; let me know if you'd rather a sans label.",
"Got it. Moved the footer below the bullets and dropped the v0.1 chip.",
"Adjusted. The eyebrow color is now sage to match the agent palette.",
];
function App() {
const [annotationOn, setAnnotationOn] = useState(true);
const [hovered, setHovered] = useState(null);
const [annotating, setAnnotating] = useState(null); // { id, tag, rect }
const [pills, setPills] = useState([
"Tighten the heading — fewer words, more confidence",
]);
const [draft, setDraft] = useState("");
const [chat, setChat] = useState([
{ role: "agent", text: "Opened landing/index.html. Annotation is on — click any element to start." },
]);
const [working, setWorking] = useState(false);
const [ended, setEnded] = useState(false);
const [replyIdx, setReplyIdx] = useState(0);
const [artifactKey, setArtifactKey] = useState(0);
const handleArtifactClick = useCallback((target) => {
if (!annotationOn) return;
setAnnotating(target);
}, [annotationOn]);
const closeCard = () => setAnnotating(null);
const queuePrompt = (text) => {
setPills((p) => [...p, text]);
setAnnotating(null);
};
const removePill = (i) => setPills((p) => p.filter((_, idx) => idx !== i));
const sendToAgent = () => {
if (working) return;
const messages = [...pills];
if (draft.trim()) messages.push(draft.trim());
if (messages.length === 0) return;
setChat((c) => [...c, ...messages.map((t) => ({ role: "user", text: t }))]);
setPills([]);
setDraft("");
setWorking(true);
setTimeout(() => {
const reply = CANNED_REPLIES[replyIdx % CANNED_REPLIES.length];
setReplyIdx((i) => i + 1);
setChat((c) => [...c, { role: "agent", text: reply }]);
setWorking(false);
}, 1400);
};
// Send the queued/typed messages, then end — no waiting for a reply.
const sendAndEnd = () => {
if (working) return;
const messages = [...pills];
if (draft.trim()) messages.push(draft.trim());
if (messages.length) {
setChat((c) => [...c, ...messages.map((t) => ({ role: "user", text: t }))]);
setPills([]);
setDraft("");
}
endSession();
};
const endSession = () => {
setEnded(true);
setAnnotationOn(false);
setAnnotating(null);
setChat((c) => [...c, { role: "agent", text: "Session ended. Return to your agent to continue." }]);
};
const reloadArtifact = () => {
setArtifactKey((k) => k + 1);
};
const copySnapshot = () => {
setChat((c) => [...c, { role: "agent", text: "Copied a DOM snapshot of the artifact to your clipboard." }]);
};
// Click outside annotation card closes it
useEffect(() => {
if (!annotating) return;
const onKey = (e) => { if (e.key === "Escape") closeCard(); };
document.addEventListener("keydown", onKey);
return () => document.removeEventListener("keydown", onKey);
}, [annotating]);
const appStyles = {
root: {
position: "fixed",
inset: 0,
display: "flex",
flexDirection: "column",
background: "var(--ink-900)",
color: "var(--cream-100)",
fontFamily: "var(--font-sans)",
fontSize: 14,
lineHeight: 1.45,
overflow: "hidden",
},
layout: { flex: 1, minHeight: 0, display: "flex" },
endedOverlay: {
position: "absolute",
inset: "56px 0 0 0",
background: "rgba(15, 17, 21, 0.86)",
display: ended ? "flex" : "none",
alignItems: "center",
justifyContent: "center",
zIndex: 60,
pointerEvents: "none",
},
endedCard: {
background: "var(--ink-800)",
border: "1px solid var(--steel-600)",
borderRadius: 14,
padding: "20px 24px",
maxWidth: 360,
textAlign: "center",
pointerEvents: "auto",
},
endedQuote: {
fontFamily: "var(--font-serif)",
fontStyle: "italic",
fontSize: 22,
lineHeight: 1.3,
color: "var(--cream-100)",
marginBottom: 8,
},
endedSub: {
fontFamily: "var(--font-mono)",
fontSize: 12,
color: "var(--steel-300)",
},
};
return (
<div style={appStyles.root}>
<TopBar
filePath="~/projects/landing/index.html"
annotationOn={annotationOn}
onToggleAnnotation={() => setAnnotationOn((v) => !v)}
onEndSession={endSession}
onReload={reloadArtifact}
onSnapshot={copySnapshot}
ended={ended}
/>
<div style={appStyles.layout}>
<Artifact
key={artifactKey}
annotationOn={annotationOn && !ended}
hoveredEl={hovered}
selectedEl={annotating}
onHover={setHovered}
onClick={handleArtifactClick}
/>
<ChatPanel
chat={chat}
working={working}
pills={pills}
draft={draft}
onDraftChange={setDraft}
onRemovePill={removePill}
onSend={sendToAgent}
onSendAndEnd={sendAndEnd}
/>
</div>
{annotating && (
<AnnotationCard target={annotating} onCancel={closeCard} onQueue={queuePrompt} />
)}
<div style={appStyles.endedOverlay}>
<div style={appStyles.endedCard}>
<div style={appStyles.endedQuote}>Session ended.<br/>Return to your agent to continue.</div>
<div style={appStyles.endedSub}>~/projects/landing/index.html</div>
</div>
</div>
</div>
);
}
window.App = App;
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
/* global React */
const { useState, useCallback } = React;
// The faked landing-page artifact. Click any element to open the annotation card.
// We expose hover/click hooks so app.jsx can drive the annotation flow.
function Artifact({ annotationOn, selectedEl, hoveredEl, onHover, onClick }) {
const artifactStyles = {
frame: {
background: "#ffffff",
color: "#1a1a1a",
flex: 1,
minWidth: 0,
minHeight: 0,
overflow: "auto",
cursor: annotationOn ? "default" : "auto",
position: "relative",
},
page: {
maxWidth: 720,
margin: "0 auto",
padding: "64px 48px 96px",
fontFamily: "'EB Garamond', Georgia, serif",
},
eyebrow: {
fontFamily: "'Geist', system-ui, sans-serif",
fontSize: 11,
fontWeight: 700,
letterSpacing: "0.14em",
textTransform: "uppercase",
color: "#7a6a3a",
marginBottom: 28,
},
h1: {
fontStyle: "italic",
fontSize: 64,
lineHeight: 1.05,
letterSpacing: "-0.01em",
fontWeight: 400,
margin: "0 0 24px",
color: "#1a1a1a",
},
lede: {
fontSize: 22,
lineHeight: 1.45,
color: "#3a3a3a",
margin: "0 0 36px",
fontStyle: "italic",
},
cta: {
fontFamily: "'Geist', system-ui, sans-serif",
background: "#f4c95d",
color: "#17130a",
border: 0,
borderRadius: 10,
padding: "12px 18px",
fontWeight: 700,
fontSize: 14,
cursor: "pointer",
},
p: {
fontFamily: "'Geist', system-ui, sans-serif",
fontSize: 15,
lineHeight: 1.6,
color: "#3a3a3a",
margin: "36px 0 16px",
},
list: { fontFamily: "'Geist', system-ui, sans-serif", fontSize: 15, lineHeight: 1.7, color: "#3a3a3a", paddingLeft: 18, margin: 0 },
footer: {
fontFamily: "'Geist Mono', monospace",
fontSize: 11,
color: "#7a7a7a",
marginTop: 64,
borderTop: "1px solid #e5e5e5",
paddingTop: 18,
display: "flex",
justifyContent: "space-between",
},
};
const outlineFor = (id) => {
if (!annotationOn) return null;
const hot = (selectedEl && selectedEl.id === id) || (hoveredEl && hoveredEl.id === id);
return hot ? { outline: "2px solid #f4c95d", outlineOffset: "2px" } : null;
};
// Each annotatable element wraps in an Anno
const Anno = ({ id, tag, children, style }) => {
const handleEnter = () => annotationOn && onHover({ id, tag, el: id });
const handleLeave = () => annotationOn && onHover(null);
const handleClick = (e) => {
if (!annotationOn) return;
e.preventDefault();
e.stopPropagation();
onClick({ id, tag, rect: e.currentTarget.getBoundingClientRect() });
};
const Tag = tag === "h1" ? "h1" : tag === "p" ? "p" : tag === "button" ? "button" : "div";
return (
<Tag
style={{ ...style, ...outlineFor(id) }}
onMouseEnter={handleEnter}
onMouseLeave={handleLeave}
onClick={handleClick}
>
{children}
</Tag>
);
};
return (
<div style={artifactStyles.frame}>
<div style={artifactStyles.page}>
<Anno id="eyebrow" tag="div" style={artifactStyles.eyebrow}>v0.1 · the rich editor</Anno>
<Anno id="h1" tag="h1" style={artifactStyles.h1}>For when a rich editor is not rich enough.</Anno>
<Anno id="lede" tag="p" style={artifactStyles.lede}>
Lavish opens an agent-generated HTML artifact in a local browser, lets you pinpoint elements,
and ships your feedback back to the agent.
</Anno>
<Anno id="cta" tag="button" style={artifactStyles.cta}>Open an artifact →</Anno>
<Anno id="p1" tag="p" style={artifactStyles.p}>
Agents are good at producing rich HTML artifacts, but the human–agent collaboration loop
on such artifacts is lacking and falls back to screenshots and long responses for
"tell me what to change." That loses the thing HTML is best at: interactivity.
</Anno>
<Anno id="list" tag="div" style={artifactStyles.list}>
<ul style={{ margin: 0, paddingLeft: 18 }}>
<li><strong>Browser-native review</strong> — relative JS/CSS/assets intact.</li>
<li><strong>Precise feedback</strong> — click elements, queue prompts.</li>
<li><strong>Agent-ergonomic interface</strong> — TOON output, long polling.</li>
</ul>
</Anno>
<Anno id="footer" tag="div" style={artifactStyles.footer}>
<span>lavish-axi · v0.1.0</span>
<span>~/projects/landing/index.html</span>
</Anno>
</div>
</div>
);
}
window.Artifact = Artifact;
/* global React */
function UserBubble({ children }) {
const userBubbleStyles = {
bubble: {
alignSelf: "flex-end",
maxWidth: "85%",
borderRadius: 14,
padding: "10px 12px",
background: "var(--bg-elevated)",
border: "1px solid var(--border-strong)",
},
label: {
display: "block",
color: "var(--fg-faint)",
marginBottom: 4,
fontSize: 10,
fontWeight: 700,
letterSpacing: "0.08em",
textTransform: "uppercase",
textAlign: "right",
},
};
return (
<div style={userBubbleStyles.bubble}>
<small style={userBubbleStyles.label}>You</small>
<div>{children}</div>
</div>
);
}
function AgentBubble({ children }) {
const agentBubbleStyles = {
bubble: {
alignSelf: "flex-start",
maxWidth: "85%",
borderRadius: 14,
padding: "10px 12px",
background: "transparent",
border: "1px solid var(--border-subtle)",
},
label: {
display: "block",
color: "var(--fg-faint)",
marginBottom: 4,
fontSize: 10,
fontWeight: 700,
letterSpacing: "0.08em",
textTransform: "uppercase",
},
};
return (
<div style={agentBubbleStyles.bubble}>
<small style={agentBubbleStyles.label}>Agent</small>
<div>{children}</div>
</div>
);
}
function WorkingBubble() {
const workingStyles = {
bubble: {
alignSelf: "flex-start",
maxWidth: "85%",
borderRadius: 14,
padding: "10px 12px",
background: "transparent",
border: "1px solid var(--border-subtle)",
display: "flex",
alignItems: "center",
gap: 8,
color: "var(--fg-muted)",
},
spinner: {
width: 14,
height: 14,
borderRadius: 999,
border: "2px solid var(--steel-600)",
borderTopColor: "var(--accent)",
animation: "lavish-spin 0.8s linear infinite",
display: "inline-block",
},
};
return (
<div style={workingStyles.bubble}>
<span style={workingStyles.spinner} />
<span>Working…</span>
</div>
);
}
window.UserBubble = UserBubble;
window.AgentBubble = AgentBubble;
window.WorkingBubble = WorkingBubble;
/* global React, UserBubble, AgentBubble, WorkingBubble, Pill */
const { useEffect: useEffectChat, useRef: useRefChat, useState: useStateChat } = React;
function SendIcon({ size = 14 }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
<path d="M22 2 11 13" /><path d="M22 2 15 22l-4-9-9-4Z" />
</svg>
);
}
function CaretIcon({ size = 13 }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="m6 9 6 6 6-6" />
</svg>
);
}
function ExitIcon({ size = 14 }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /><polyline points="16 17 21 12 16 7" /><line x1="21" y1="12" x2="9" y2="12" />
</svg>
);
}
function ChatPanel({ chat, working, pills, draft, onDraftChange, onRemovePill, onSend, onSendAndEnd }) {
const logRef = useRefChat(null);
const splitRef = useRefChat(null);
const [splitOpen, setSplitOpen] = useStateChat(false);
useEffectChat(() => {
if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
}, [chat, working]);
useEffectChat(() => {
if (!splitOpen) return;
const onDown = (e) => { if (splitRef.current && !splitRef.current.contains(e.target)) setSplitOpen(false); };
const onKey = (e) => { if (e.key === "Escape") setSplitOpen(false); };
document.addEventListener("mousedown", onDown);
document.addEventListener("keydown", onKey);
return () => { document.removeEventListener("mousedown", onDown); document.removeEventListener("keydown", onKey); };
}, [splitOpen]);
// The shipped product keeps Send enabled when the composer is empty and nudges instead:
// a transient inline hint, with disabled styling reserved for the working state.
const hasContent = pills.length > 0 || draft.trim().length > 0;
const canSend = !working;
const [hint, setHint] = useStateChat(false);
const hintTimer = useRefChat(null);
const trySend = () => {
if (working) return;
if (!hasContent) {
setHint(true);
clearTimeout(hintTimer.current);
hintTimer.current = setTimeout(() => setHint(false), 2600);
return;
}
setHint(false);
onSend();
};
const s = {
panel: { borderLeft: "1px solid var(--steel-700)", background: "var(--ink-800)", display: "flex", flexDirection: "column", minWidth: 0, minHeight: 0, width: 360, flexShrink: 0 },
h2: { fontSize: 15, margin: "16px 16px 8px", fontWeight: 600 },
log: { flex: 1, minHeight: 0, overflow: "auto", padding: "0 16px 12px", display: "flex", flexDirection: "column", gap: 10 },
composer: { display: "grid", gap: 8, padding: "12px 16px", borderTop: "1px solid var(--steel-700)", minWidth: 0, flexShrink: 0, boxSizing: "border-box" },
pills: { display: "flex", flexWrap: "wrap", gap: 6, minWidth: 0 },
ta: { width: "100%", maxWidth: "100%", minWidth: 0, minHeight: 82, resize: "vertical", borderRadius: 12, border: "1px solid var(--steel-600)", background: "var(--ink-900)", color: "var(--cream-100)", padding: 10, font: "inherit", fontFamily: "var(--font-sans)", fontSize: 14, boxSizing: "border-box" },
actions: { display: "flex", alignItems: "center", gap: 8, justifyContent: "flex-end", position: "relative" },
hint: { marginRight: "auto", color: "var(--fg-faint)", fontSize: 11, lineHeight: 1.35 },
split: { display: "inline-flex", alignItems: "stretch" },
sendMain: { border: 0, borderTopLeftRadius: 10, borderBottomLeftRadius: 10, borderTopRightRadius: 0, borderBottomRightRadius: 0, padding: "9px 14px", background: "var(--brass-500)", color: "var(--brass-ink)", fontFamily: "inherit", fontWeight: 700, cursor: canSend ? "pointer" : "not-allowed", opacity: canSend ? 1 : 0.55, fontSize: 13, whiteSpace: "nowrap" },
sendCaret: { border: 0, borderTopRightRadius: 10, borderBottomRightRadius: 10, borderTopLeftRadius: 0, borderBottomLeftRadius: 0, padding: "9px 9px", background: "var(--brass-500)", color: "var(--brass-ink)", cursor: working ? "not-allowed" : "pointer", opacity: working ? 0.55 : 1, borderLeft: "1px solid rgba(23,19,10,.22)", display: "grid", placeItems: "center" },
menu: { position: "absolute", right: 0, bottom: 44, width: 208, background: "var(--ink-700)", border: "1px solid var(--steel-600)", borderRadius: 11, boxShadow: "var(--shadow-tooltip)", padding: 6, zIndex: 40 },
};
const SplitMenuItem = ({ icon, label, danger, disabled, onClick }) => {
const [hover, setHover] = useStateChat(false);
return (
<button
type="button"
onClick={disabled ? undefined : onClick}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
display: "flex", alignItems: "center", gap: 9, width: "100%", textAlign: "left",
padding: "8px 9px", borderRadius: 8, fontSize: 13, fontFamily: "inherit", border: 0,
cursor: disabled ? "not-allowed" : "pointer", opacity: disabled ? 0.5 : 1,
background: hover && !disabled ? (danger ? "rgba(240,100,100,.10)" : "var(--steel-700)") : "transparent",
color: danger ? "var(--rust-500)" : "var(--cream-100)",
}}
>
<span style={{ display: "flex", opacity: danger ? 1 : 0.85 }}>{icon}</span>{label}
</button>
);
};
return (
<aside style={s.panel}>
<h2 style={s.h2}>Conversation</h2>
<div ref={logRef} style={s.log}>
{chat.map((m, i) =>
m.role === "user" ? <UserBubble key={i}>{m.text}</UserBubble> : <AgentBubble key={i}>{m.text}</AgentBubble>
)}
{working && <WorkingBubble />}
</div>
<div style={s.composer}>
{pills.length > 0 && (
<div style={s.pills}>
{pills.map((p, i) => (
<Pill key={i} prompt={p} onRemove={() => onRemovePill(i)} />
))}
</div>
)}
<textarea
placeholder="Write a message for the agent…"
value={draft}
onChange={(e) => { setHint(false); onDraftChange(e.target.value); }}
style={s.ta}
/>
<div style={s.actions} ref={splitRef}>
{hint && <span style={s.hint}>Write a message or annotate an element first.</span>}
<div style={s.split}>
<button style={s.sendMain} disabled={!canSend} onClick={trySend}>Send to Agent</button>
<button style={s.sendCaret} title="Send options" disabled={working} onClick={working ? undefined : () => setSplitOpen((v) => !v)}>
<CaretIcon />
</button>
</div>
{splitOpen && (
<div style={s.menu}>
<SplitMenuItem icon={<SendIcon />} label="Send to Agent" disabled={!canSend} onClick={() => { setSplitOpen(false); trySend(); }} />
<SplitMenuItem icon={<ExitIcon />} label="Send & end session" danger onClick={() => { setSplitOpen(false); onSendAndEnd(); }} />
</div>
)}
</div>
</div>
</aside>
);
}
window.ChatPanel = ChatPanel;
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Lavish Editor — making “end session” discoverable</title>
<link rel="stylesheet" href="../../colors_and_type.css">
<style>
html, body { margin: 0; padding: 0; height: 100%; }
body { font-family: var(--font-sans); }
@keyframes lavish-spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script type="text/babel" src="design-canvas.jsx"></script>
<script type="text/babel" src="options-chrome-v3.jsx"></script>
<script type="text/babel" src="options-app-v3.jsx"></script>
</body>
</html>
<!doctype html>
<!-- @dsCard group="UI Kit — Editor" name="Lavish Editor — UI kit" subtitle="Clickable chrome: artifact + annotation + chat" viewport="1280x760" -->
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Lavish Editor — UI kit</title>
<link rel="stylesheet" href="../../colors_and_type.css">
<style>
html, body { margin: 0; padding: 0; height: 100%; background: var(--ink-900); }
body { font-family: var(--font-sans); color: var(--cream-100); }
@keyframes lavish-spin { to { transform: rotate(360deg); } }
/* Hide native button focus ring; the brass outline IS the focus language */
button:focus-visible {
outline: 2px solid var(--brass-500);
outline-offset: 2px;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script type="text/babel" src="TopBar.jsx"></script>
<script type="text/babel" src="Bubbles.jsx"></script>
<script type="text/babel" src="Pills.jsx"></script>
<script type="text/babel" src="ChatPanel.jsx"></script>
<script type="text/babel" src="Artifact.jsx"></script>
<script type="text/babel" src="AnnotationCard.jsx"></script>
<script type="text/babel" src="app.jsx"></script>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Lavish Editor — emptied bar, relocated session action</title>
<link rel="stylesheet" href="../../colors_and_type.css">
<style>
html, body { margin: 0; padding: 0; height: 100%; }
body { font-family: var(--font-sans); }
@keyframes lavish-spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script type="text/babel" src="design-canvas.jsx"></script>
<script type="text/babel" src="options-chrome-v2.jsx"></script>
<script type="text/babel" src="options-app-v2.jsx"></script>
</body>
</html>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Lavish Editor — quieter layout options</title>
<link rel="stylesheet" href="../../colors_and_type.css">
<style>
html, body { margin: 0; padding: 0; height: 100%; }
body { font-family: var(--font-sans); }
@keyframes lavish-spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script type="text/babel" src="design-canvas.jsx"></script>
<script type="text/babel" src="options-chrome.jsx"></script>
<script type="text/babel" src="options-app.jsx"></script>
</body>
</html>
/* global React, ReactDOM, DesignCanvas, DCSection, DCArtboard, ChromePaired, ChromeSplit, ChromeFinish */
const noteStyle2 = { fontFamily: "var(--font-sans)", fontSize: 13, lineHeight: 1.5, color: "var(--steel-100)" };
const tagStyle2 = { display: "inline-block", fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--brass-500)", marginBottom: 6, letterSpacing: "0.04em" };
function Cap({ tag, children }) {
return (
<div style={{ width: 840, marginTop: 10 }}>
<div style={tagStyle2}>{tag}</div>
<div style={noteStyle2} dangerouslySetInnerHTML={{ __html: children }} />
</div>
);
}
function OptionsV2() {
return (
<DesignCanvas>
<DCSection
id="bar"
title="The bar, emptied"
subtitle="Wordmark · Annotate switch · ⋯. The file path, reload and snapshot all move inside the ⋯ menu — shown open on the third board."
>
<DCArtboard id="paired" label="D · Paired footer actions" width={840} height={560}>
<ChromePaired />
<Cap tag="D · PAIRED FOOTER — clearest hierarchy">
Bar drops to three things. Both session actions move to the composer footer, where your attention already is: <b>End session</b> sits left as a quiet rust text-button, <b>Send to Agent</b> filled on the right. Reads like a form — secondary action left, primary right. Nothing destructive in the chrome's top edge anymore.
</Cap>
</DCArtboard>
<DCArtboard id="split" label="E · Split send button" width={840} height={560}>
<ChromeSplit />
<Cap tag="E · SPLIT SEND — one footprint, two intents">
A single primary control. <b>Send to Agent</b> with a <b>▾</b> that opens <b>Send & end session</b> — your “send” muscle memory works, and ending is one deliberate notch away in the same spot. Tightest footer; the trade is that ending is slightly hidden.
</Cap>
</DCArtboard>
<DCArtboard id="finish" label="F · Send is the finish (⋯ open)" width={840} height={560}>
<ChromeFinish />
<Cap tag="F · SEND IS THE FINISH — boldest reframe">
Treats ending as a <i>kind</i> of send. Full-width <b>Send to Agent</b>, and a whisper-quiet text link beneath: <b>Send a final note & end session</b>. The ⋯ menu is shown open here so you can see where the file path lands — under an <b>“Editing”</b> label, with Reload and Copy DOM snapshot below.
</Cap>
</DCArtboard>
</DCSection>
</DesignCanvas>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<OptionsV2 />);
/* global React, ReactDOM, DesignCanvas, DCSection, DCArtboard, PanelIdleNudge, PanelDualHome, PanelLabeledCaret */
const ns3 = { fontFamily: "var(--font-sans)", fontSize: 13, lineHeight: 1.5, color: "var(--steel-100)" };
const ts3 = { display: "inline-block", fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--brass-500)", marginBottom: 6, letterSpacing: "0.04em" };
function Cap3({ tag, children }) {
return (
<div style={{ width: 320, marginTop: 10 }}>
<div style={ts3}>{tag}</div>
<div style={ns3} dangerouslySetInnerHTML={{ __html: children }} />
</div>
);
}
function OptionsV3() {
return (
<DesignCanvas>
<DCSection
id="discover"
title="Making “end session” discoverable"
subtitle="Option E keeps the tiny split button. These three mechanisms make sure nobody misses the end-session path — without adding permanent real estate."
>
<DCArtboard id="nudge" label="E1 · Contextual idle nudge" width={320} height={520}>
<PanelIdleNudge />
<Cap3 tag="E1 · IDLE NUDGE — teaches at the right moment">
The agent has replied and your box is empty — the moment you’re <i>most</i> likely done. A quiet one-liner fades in above the composer pointing at the <b>▾</b>. It costs zero space while you’re actively working and disappears the instant you start typing. Teaches the fast path exactly when it’s relevant.
</Cap3>
</DCArtboard>
<DCArtboard id="dual" label="E2 · Dual home in ⋯" width={320} height={520}>
<PanelDualHome />
<Cap3 tag="E2 · DUAL HOME — the reliable safety net">
<b>End session</b> also lives in the <b>⋯</b> menu (rust, set off below a divider). The split-button ▾ stays the <i>fast</i> path; the ⋯ menu is the <i>discoverable</i> path everyone opens to explore. Belt and suspenders — anyone poking around finds it.
</Cap3>
</DCArtboard>
<DCArtboard id="labeled" label="E3 · Labeled caret + tooltip" width={320} height={520}>
<PanelLabeledCaret />
<Cap3 tag="E3 · LABELED CARET — visible at a glance">
The trigger isn’t a bare ▾ — it reads <b>“end ▾”</b>, and hovering surfaces a <b>“Send & end session”</b> tooltip. The word is always on screen, so the option is never truly hidden. Adds a sliver of width back, but no second control.
</Cap3>
</DCArtboard>
</DCSection>
</DesignCanvas>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<OptionsV3 />);
/* global React, DesignCanvas, DCSection, DCArtboard, ChromeBaseline, ChromeQuiet, ChromeOverflow, ChromeUnified */
const noteStyle = {
fontFamily: "var(--font-sans)",
fontSize: 13,
lineHeight: 1.5,
color: "var(--steel-100)",
};
const tagStyle = {
display: "inline-block",
fontFamily: "var(--font-mono)",
fontSize: 11,
color: "var(--brass-500)",
marginBottom: 6,
letterSpacing: "0.04em",
};
function Caption({ tag, children }) {
return (
<div style={{ width: 820, marginTop: 10 }}>
<div style={tagStyle}>{tag}</div>
<div style={noteStyle} dangerouslySetInnerHTML={{ __html: children }} />
</div>
);
}
function OptionsApp() {
return (
<DesignCanvas>
<DCSection
id="baseline"
title="Where it is today"
subtitle="Five competing controls: two filled-weight bar buttons, a floating Cancel/Queue pair, pill × chips, and Send."
>
<DCArtboard id="current" label="Current chrome" width={820} height={500}>
<ChromeBaseline />
</DCArtboard>
</DCSection>
<DCSection
id="options"
title="Three ways to quiet it down"
subtitle="Each keeps a single filled brass action — Send — and demotes everything else."
>
<DCArtboard id="quiet" label="A · Quiet bar" width={820} height={560}>
<ChromeQuiet />
<Caption tag="A · QUIET BAR — smallest change">
The two bar buttons become a <b>switch</b> (Annotate) and a single <b>icon</b> (end session). The floating card loses its filled Cancel — now a quiet text link beside a compact <b>Queue</b>. One filled button survives anywhere on screen: <b>Send</b>. Lowest-risk; keeps every existing flow intact.
</Caption>
</DCArtboard>
<DCArtboard id="overflow" label="B · Overflow menu" width={820} height={560}>
<ChromeOverflow />
<Caption tag="B · OVERFLOW MENU — emptiest bar">
The bar collapses to <b>wordmark · file · Annotate switch · ⋯</b>. End Session, snapshot, reload all live behind the <b>⋯</b> menu. Send becomes an <b>icon inside the textarea</b>, so the composer reads as one object instead of input-plus-button. Calmest top edge; the trade is one extra click to reach session controls.
</Caption>
</DCArtboard>
<DCArtboard id="unified" label="C · Unified composer" width={820} height={560}>
<ChromeUnified />
<Caption tag="C · UNIFIED COMPOSER — fewest controls (recommended)">
The floating annotation card <b>disappears entirely</b>. Clicking an element drops a <b>context chip</b> (“editing <h1>”) above the one composer — you type and Send like any message. This removes the whole Cancel/Queue cluster <i>and</i> the separate floating input. One place to write, one action to send. Biggest behavioral change, but the most elegant end state.
</Caption>
</DCArtboard>
</DCSection>
</DesignCanvas>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<OptionsApp />);
/* global React */
function Pill({ prompt, onRemove }) {
const pillStyles = {
wrap: { position: "relative", maxWidth: "100%" },
pill: {
display: "flex",
alignItems: "center",
gap: 6,
maxWidth: "100%",
border: "1px solid var(--steel-500)",
borderRadius: 999,
background: "var(--ink-600)",
color: "var(--steel-100)",
padding: "5px 7px 5px 11px",
fontSize: 12,
fontWeight: 700,
},
preview: {
display: "block",
maxWidth: 220,
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
},
close: {
width: 18,
height: 18,
border: 0,
borderRadius: 999,
padding: 0,
background: "var(--steel-600)",
color: "var(--steel-100)",
lineHeight: "18px",
fontSize: 14,
cursor: "pointer",
},
};
return (
<div style={pillStyles.wrap}>
<div style={pillStyles.pill}>
<span style={pillStyles.preview} title={prompt}>{prompt}</span>
<button style={pillStyles.close} aria-label="Remove queued prompt" onClick={onRemove}>×</button>
</div>
</div>
);
}
window.Pill = Pill;