feat(web): add brand design systems, card thumbnails, and DESIGN.md side-by-side preview (#289)

* feat(web): add brand design systems, card thumbnails, and DESIGN.md side-by-side preview

- Add 7 new brand design systems (arc, canva, discord, duolingo, github, huggingface, openai)
- Show live showcase HTML thumbnails on Design Systems cards
- Add toggleable DESIGN.md side panel in preview modal with syntax-highlighted spec view
- Make preview iframe responsive: render at fixed design viewport and scale to fit so opening the side panel never reflows showcases into broken breakpoints
- Add floating collapse/expand handles on the sidebar boundary for direct hide/show

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): guard ResizeObserver and re-fire sidebar lazy-load on content swap

- Guard `new ResizeObserver(...)` in PreviewModal so the modal mounts in
  jsdom (the existing preview-modal-fullscreen test was failing with
  `ReferenceError: ResizeObserver is not defined`) and in older embedded
  WebViews. Fall back to a window resize listener when the constructor
  is unavailable.
- Add a `contentKey` hint to PreviewSidebar so the lazy-load `onToggle`
  callback re-fires when the underlying side-panel source swaps while
  the sidebar stays open. Wire `system.id` through from
  DesignSystemPreviewModal so swapping design systems with the spec
  panel open primes a fresh DESIGN.md fetch instead of leaving it stuck.

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

* fix(web/i18n): add missing ds/preview keys to hu locale

The Validate workspace check failed after main's hu.ts landed
without the four i18n keys introduced by this PR (ds.specToggle,
ds.specLoading, preview.showSidebar, preview.hideSidebar).

Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Tom Huang 2026-05-02 23:19:00 +08:00 committed by GitHub
parent bf44394f91
commit 9ee2c1994c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 1883 additions and 75 deletions

View file

@ -0,0 +1,94 @@
import { useMemo } from 'react';
interface Props {
source: string | null | undefined;
loading?: boolean;
loadingLabel: string;
}
// Render a DESIGN.md as a lightly syntax-coloured monospace source view —
// the right-hand panel of the preview modal, mirroring the layout used by
// styles.refero.design where the rendered showcase sits next to the spec
// text. Highlights are CSS-class only; no innerHTML for untrusted text.
export function DesignSpecView({ source, loading, loadingLabel }: Props) {
const lines = useMemo(() => (source ? source.split(/\r?\n/) : []), [source]);
if (loading || source === undefined || source === null) {
return <div className="design-spec-empty">{loadingLabel}</div>;
}
return (
<pre className="design-spec-pre">
<code>
{lines.map((line, idx) => (
<span key={idx} className={`design-spec-line ${classifyLine(line)}`}>
{renderInline(line)}
{'\n'}
</span>
))}
</code>
</pre>
);
}
function classifyLine(line: string): string {
if (/^#{1,6}\s+/.test(line)) {
const hashes = /^(#+)\s/.exec(line)?.[1]?.length ?? 1;
return `is-h${Math.min(hashes, 4)}`;
}
if (/^>\s/.test(line)) return 'is-quote';
if (/^[-*+]\s/.test(line.trimStart())) return 'is-list';
if (/^\|.*\|\s*$/.test(line)) return 'is-table';
if (/^\s*```/.test(line)) return 'is-fence';
if (/^\s*$/.test(line)) return 'is-blank';
return '';
}
const TOKEN_RE = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|#[0-9a-fA-F]{3,8}\b)/g;
function renderInline(line: string) {
if (!line) return null;
const out: (string | JSX.Element)[] = [];
let last = 0;
let key = 0;
for (const match of line.matchAll(TOKEN_RE)) {
const start = match.index ?? 0;
if (start > last) out.push(line.slice(last, start));
const token = match[0];
if (token.startsWith('**')) {
out.push(
<span key={key++} className="md-tk-bold">
{token.slice(2, -2)}
</span>,
);
} else if (token.startsWith('*')) {
out.push(
<span key={key++} className="md-tk-em">
{token.slice(1, -1)}
</span>,
);
} else if (token.startsWith('`')) {
out.push(
<span key={key++} className="md-tk-code">
{token.slice(1, -1)}
</span>,
);
} else if (token.startsWith('#')) {
out.push(
<span key={key++} className="md-tk-color" style={{ color: 'inherit' }}>
<span
className="md-tk-color-swatch"
style={{ backgroundColor: token }}
aria-hidden
/>
{token}
</span>,
);
} else {
out.push(token);
}
last = start + token.length;
}
if (last < line.length) out.push(line.slice(last));
return out;
}

View file

@ -1,10 +1,12 @@
import { useCallback, useEffect, useState } from 'react';
import { useT } from '../i18n';
import {
fetchDesignSystem,
fetchDesignSystemPreview,
fetchDesignSystemShowcase,
} from '../providers/registry';
import type { DesignSystemSummary } from '../types';
import { DesignSpecView } from './DesignSpecView';
import { PreviewModal } from './PreviewModal';
interface Props {
@ -14,11 +16,14 @@ interface Props {
// Two-tab DS preview: a complete Showcase webpage rendered from the system's
// tokens, and the original Tokens view (palette / typography / components +
// rendered DESIGN.md prose).
// rendered DESIGN.md prose). A toggleable side panel surfaces the raw
// DESIGN.md so users can compare spec to render at the same time, mirroring
// the styles.refero.design layout.
export function DesignSystemPreviewModal({ system, onClose }: Props) {
const t = useT();
const [showcaseHtml, setShowcaseHtml] = useState<string | null | undefined>(undefined);
const [tokensHtml, setTokensHtml] = useState<string | null | undefined>(undefined);
const [specBody, setSpecBody] = useState<string | null | undefined>(undefined);
// Lazy-load each view on first reveal. Both endpoints are cheap, but this
// keeps the network panel quiet when the user only opens one tab.
@ -36,10 +41,24 @@ export function DesignSystemPreviewModal({ system, onClose }: Props) {
[system.id, showcaseHtml, tokensHtml],
);
// If the system swaps under us (rare but possible), wipe both caches.
// Fetch DESIGN.md the first time the side panel opens. Once we have it we
// never re-fetch unless the underlying system swaps.
const handleSidebarToggle = useCallback(
(open: boolean) => {
if (!open || specBody !== undefined) return;
setSpecBody(null);
void fetchDesignSystem(system.id).then((detail) =>
setSpecBody(detail?.body ?? null),
);
},
[system.id, specBody],
);
// If the system swaps under us (rare but possible), wipe all caches.
useEffect(() => {
setShowcaseHtml(undefined);
setTokensHtml(undefined);
setSpecBody(undefined);
}, [system.id]);
return (
@ -54,6 +73,20 @@ export function DesignSystemPreviewModal({ system, onClose }: Props) {
onView={handleView}
exportTitleFor={(viewId) => `${system.title}${viewId}`}
onClose={onClose}
sidebar={{
label: t('ds.specToggle'),
defaultOpen: true,
onToggle: handleSidebarToggle,
// Re-fire onToggle when the system swaps under us so the new
// DESIGN.md fetch starts even if the sidebar never closed.
contentKey: system.id,
content: (
<DesignSpecView
source={specBody}
loadingLabel={t('ds.specLoading')}
/>
),
}}
/>
);
}

View file

@ -1,9 +1,11 @@
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useI18n } from '../i18n';
import {
localizeDesignSystemCategory,
localizeDesignSystemSummary,
} from '../i18n/content';
import { fetchDesignSystemShowcase } from '../providers/registry';
import { buildSrcdoc } from '../runtime/srcdoc';
import type { DesignSystemSummary, Surface } from '../types';
interface Props {
@ -45,6 +47,10 @@ export function DesignSystemsTab({ systems, selectedId, onSelect, onPreview }: P
const [filter, setFilter] = useState('');
const [surfaceFilter, setSurfaceFilter] = useState<SurfaceFilter>('all');
const [category, setCategory] = useState<string>('All');
// Cache fetched showcase HTML across re-renders so cards never re-flicker
// when the user filters / scrolls back. null = "in flight"; undefined =
// "not yet requested". Mirrors the pattern used by ExamplesTab.
const [thumbs, setThumbs] = useState<Record<string, string | null>>({});
const surfaceScoped = useMemo(
() => surfaceFilter === 'all' ? systems : systems.filter((s) => surfaceOf(s) === surfaceFilter),
@ -93,6 +99,16 @@ export function DesignSystemsTab({ systems, selectedId, onSelect, onPreview }: P
return localizeDesignSystemCategory(locale, c);
};
function loadThumb(id: string) {
setThumbs((prev) => {
if (prev[id] !== undefined) return prev;
void fetchDesignSystemShowcase(id).then((html) => {
setThumbs((p) => ({ ...p, [id]: html }));
});
return { ...prev, [id]: null };
});
}
return (
<div className="tab-panel">
<div className="tab-panel-toolbar">
@ -135,55 +151,154 @@ export function DesignSystemsTab({ systems, selectedId, onSelect, onPreview }: P
{filtered.length === 0 ? (
<div className="tab-empty">{t('ds.emptyNoMatch')}</div>
) : (
<div className="ds-list">
{filtered.map((s) => {
const active = s.id === selectedId;
return (
<div
key={s.id}
className={`ds-row ${active ? 'active' : ''}`}
onClick={() => onSelect(s.id)}
>
<div className="ds-row-body">
<div className="ds-row-title">
{s.title}
{active ? (
<span className="ds-row-default">
{t('ds.badgeDefault')}
</span>
) : null}
</div>
<div className="ds-row-summary">
{localizeDesignSystemSummary(locale, s)}
</div>
</div>
{s.swatches && s.swatches.length > 0 ? (
<div className="ds-row-swatches" aria-hidden>
{s.swatches.map((c, i) => (
<span
key={i}
className="ds-row-swatch"
style={{ background: c }}
title={c}
/>
))}
</div>
) : null}
<button
className="ghost"
onClick={(e) => {
e.stopPropagation();
onPreview(s.id);
}}
title={t('ds.previewTitle')}
>
{t('ds.preview')}
</button>
</div>
);
})}
<div className="ds-grid">
{filtered.map((s) => (
<DesignSystemCard
key={s.id}
system={s}
active={s.id === selectedId}
thumbHtml={thumbs[s.id]}
onIntersect={() => loadThumb(s.id)}
onSelect={() => onSelect(s.id)}
onPreview={() => onPreview(s.id)}
/>
))}
</div>
)}
</div>
);
}
interface CardProps {
system: DesignSystemSummary;
active: boolean;
thumbHtml: string | null | undefined;
onIntersect: () => void;
onSelect: () => void;
onPreview: () => void;
}
function DesignSystemCard({
system,
active,
thumbHtml,
onIntersect,
onSelect,
onPreview,
}: CardProps) {
const { locale, t } = useI18n();
const ref = useRef<HTMLDivElement | null>(null);
// Lazy-load the showcase iframe only when the card scrolls into the
// viewport. With ~120 design systems we can't afford to mount every
// iframe up front — even with `loading="lazy"`, srcDoc iframes ignore
// the native lazy hint, so we gate via IntersectionObserver.
useEffect(() => {
if (thumbHtml !== undefined) return;
const node = ref.current;
if (!node || typeof IntersectionObserver === 'undefined') {
onIntersect();
return;
}
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
onIntersect();
observer.disconnect();
break;
}
}
},
{ rootMargin: '200px' },
);
observer.observe(node);
return () => observer.disconnect();
}, [thumbHtml, onIntersect]);
const localizedSummary = localizeDesignSystemSummary(locale, system);
const categoryLabel = localizeDesignSystemCategory(
locale,
system.category || 'Uncategorized',
);
return (
<div
ref={ref}
className={`ds-card ${active ? 'active' : ''}`}
role="button"
tabIndex={0}
onClick={onSelect}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onSelect();
}
}}
>
<div
className="ds-card-thumb"
onClick={(e) => {
e.stopPropagation();
onPreview();
}}
title={t('ds.previewTitle')}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
e.stopPropagation();
onPreview();
}
}}
>
{thumbHtml ? (
<iframe
title={`${system.title} preview`}
sandbox="allow-scripts"
srcDoc={buildSrcdoc(thumbHtml)}
tabIndex={-1}
aria-hidden
/>
) : (
<div className="ds-card-thumb-fallback" aria-hidden>
{system.swatches && system.swatches.length > 0 ? (
<div className="ds-card-thumb-swatches">
{system.swatches.map((c, i) => (
<span key={i} style={{ background: c }} />
))}
</div>
) : (
<span className="ds-card-thumb-placeholder">
{thumbHtml === null ? '' : ''}
</span>
)}
</div>
)}
<span className="ds-card-thumb-overlay" aria-hidden>
{t('ds.preview')}
</span>
</div>
<div className="ds-card-meta">
<div className="ds-card-title-row">
<span className="ds-card-title">{system.title}</span>
{active ? (
<span className="ds-card-badge">{t('ds.badgeDefault')}</span>
) : null}
</div>
<div className="ds-card-summary">{localizedSummary}</div>
<div className="ds-card-footer">
<span className="ds-card-category">{categoryLabel}</span>
{system.swatches && system.swatches.length > 0 ? (
<div className="ds-card-swatches" aria-hidden>
{system.swatches.map((c, i) => (
<span key={i} style={{ background: c }} title={c} />
))}
</div>
) : null}
</div>
</div>
</div>
);
}

View file

@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
import { useT } from '../i18n';
import { exportAsHtml, exportAsPdf, exportAsZip } from '../runtime/exports';
import { buildSrcdoc } from '../runtime/srcdoc';
@ -12,6 +12,25 @@ export interface PreviewView {
html: string | null | undefined;
}
export interface PreviewSidebar {
// Header label and toggle button label.
label: string;
// Side-pane content — caller renders whatever it likes (markdown source
// view, swatch grid, etc.). Always optional; when absent the toggle is
// not shown.
content: ReactNode;
// Default open state on first mount. Defaults to false.
defaultOpen?: boolean;
// Called whenever the open state changes — useful so the parent can
// lazy-fetch the side content the first time it is revealed.
onToggle?: (open: boolean) => void;
// Stable identity for the side-panel source. When this changes while the
// sidebar is open, the lazy-load `onToggle` callback re-fires so the parent
// can prime a fresh fetch — e.g. swapping between design systems while the
// DESIGN.md panel stays open.
contentKey?: string | number;
}
interface Props {
title: string;
subtitle?: string;
@ -25,6 +44,16 @@ interface Props {
// a loader callback in.
onView?: (viewId: string) => void;
onClose: () => void;
// Optional split-view companion pane shown to the right of the iframe.
// Used by the design-system preview to surface the raw DESIGN.md beside
// the rendered showcase, matching the styles.refero.design layout.
sidebar?: PreviewSidebar;
// Logical viewport width the iframe content is rendered at. The iframe is
// then visually scaled (transform: scale) to fit the actual stage width
// so squeezing the preview behind a sidebar never reflows the inner page
// into a half-broken responsive breakpoint. Defaults to 1280 — wide
// enough that desktop-shaped showcases keep their intended layout.
designWidth?: number;
}
// A full-screen overlay that renders an iframe of arbitrary HTML, with an
@ -39,6 +68,8 @@ export function PreviewModal({
exportTitleFor,
onView,
onClose,
sidebar,
designWidth = 1280,
}: Props) {
const t = useT();
const initial = initialViewId && views.some((v) => v.id === initialViewId)
@ -47,8 +78,32 @@ export function PreviewModal({
const [activeId, setActiveId] = useState<string>(initial);
const [shareOpen, setShareOpen] = useState(false);
const [fullscreen, setFullscreen] = useState(false);
const [sidebarOpen, setSidebarOpen] = useState<boolean>(
sidebar?.defaultOpen ?? false,
);
const shareRef = useRef<HTMLDivElement | null>(null);
const stageRef = useRef<HTMLDivElement | null>(null);
const stageFrameRef = useRef<HTMLDivElement | null>(null);
const [stageSize, setStageSize] = useState<{ w: number; h: number }>({
w: 0,
h: 0,
});
// Capture the toggle handler in a ref so the lazy-load effect below
// depends only on sidebarOpen — without this, a new `sidebar` object on
// every parent render would re-fire the load on each render.
const sidebarToggleRef = useRef(sidebar?.onToggle);
sidebarToggleRef.current = sidebar?.onToggle;
// Tell the parent every time the side pane toggles so it can lazy-load
// the spec body the first time it is revealed. Also re-fires when
// `sidebar.contentKey` changes so the parent can prime a fresh fetch when
// its underlying source swaps (e.g. another design system) while the
// sidebar stays open. `sidebar` itself is a fresh object on every parent
// render so we can't depend on it.
const sidebarContentKey = sidebar?.contentKey;
useEffect(() => {
sidebarToggleRef.current?.(sidebarOpen);
}, [sidebarOpen, sidebarContentKey]);
// Tell the parent the initial view id so it can prime a fetch. Re-fires on
// tab change. Guarded against re-firing while the same id is active to
@ -115,6 +170,31 @@ export function PreviewModal({
};
}, []);
// Track the iframe stage size so we can render the document at a fixed
// logical width and visually scale it down to fit. Without this, opening
// the side panel squeezes the iframe to ~60% width and triggers awkward
// mid-breakpoint reflows in the showcase HTML.
// ResizeObserver is missing from jsdom and from some older embedded
// WebViews — guard the constructor and fall back to a window resize
// listener so the modal still mounts and just loses element-level
// resize tracking.
useEffect(() => {
const el = stageFrameRef.current;
if (!el) return;
const measure = () => {
const r = el.getBoundingClientRect();
setStageSize({ w: r.width, h: r.height });
};
measure();
if (typeof ResizeObserver !== 'undefined') {
const ro = new ResizeObserver(measure);
ro.observe(el);
return () => ro.disconnect();
}
window.addEventListener('resize', measure);
return () => window.removeEventListener('resize', measure);
}, []);
const activeView = views.find((v) => v.id === activeId) ?? views[0];
const activeHtml = activeView?.html ?? null;
const srcDoc = useMemo(
@ -123,6 +203,24 @@ export function PreviewModal({
);
const exportTitle = exportTitleFor(activeView?.id ?? '');
// Only down-scale: when the stage is wider than the design viewport we
// render the iframe at native size instead of upscaling pixels.
const scale = stageSize.w > 0 ? Math.min(1, stageSize.w / designWidth) : 1;
const scalerStyle = useMemo(() => {
if (scale >= 1 || stageSize.w === 0) {
return {
width: '100%',
height: '100%',
transform: 'none',
} as const;
}
return {
width: designWidth,
height: stageSize.h / scale,
transform: `scale(${scale})`,
} as const;
}, [scale, stageSize.w, stageSize.h, designWidth]);
function openInNewTab() {
if (!activeHtml) return;
const blob = new Blob([activeHtml], { type: 'text/html' });
@ -177,6 +275,16 @@ export function PreviewModal({
<span aria-hidden="true" />
)}
<div className="ds-modal-actions">
{sidebar ? (
<button
className={`ghost ${sidebarOpen ? 'is-active' : ''}`}
onClick={() => setSidebarOpen((v) => !v)}
aria-pressed={sidebarOpen}
title={sidebar.label}
>
{sidebar.label}
</button>
) : null}
<button
className="ghost"
onClick={fullscreen ? exitFullscreen : enterFullscreen}
@ -263,22 +371,54 @@ export function PreviewModal({
</button>
</div>
</header>
<div className="ds-modal-stage" ref={stageRef}>
{activeHtml === null || activeHtml === undefined ? (
<div className="ds-modal-empty">
{t('preview.loading', {
label:
activeView?.label.toLowerCase() ?? t('common.preview').toLowerCase(),
})}
</div>
) : (
<iframe
key={activeView?.id ?? 'view'}
title={`${title} ${activeView?.label ?? ''}`}
sandbox="allow-scripts allow-same-origin"
srcDoc={srcDoc}
/>
)}
<div
className={`ds-modal-stage ${sidebar && sidebarOpen ? 'has-sidebar' : ''}`}
ref={stageRef}
>
<div className="ds-modal-stage-iframe" ref={stageFrameRef}>
{activeHtml === null || activeHtml === undefined ? (
<div className="ds-modal-empty">
{t('preview.loading', {
label:
activeView?.label.toLowerCase() ?? t('common.preview').toLowerCase(),
})}
</div>
) : (
<div className="ds-modal-stage-iframe-scaler" style={scalerStyle}>
<iframe
key={activeView?.id ?? 'view'}
title={`${title} ${activeView?.label ?? ''}`}
sandbox="allow-scripts allow-same-origin"
srcDoc={srcDoc}
/>
</div>
)}
{sidebar && !sidebarOpen ? (
<button
type="button"
className="ds-modal-stage-handle is-expand"
onClick={() => setSidebarOpen(true)}
title={t('preview.showSidebar', { label: sidebar.label })}
aria-label={t('preview.showSidebar', { label: sidebar.label })}
>
<span aria-hidden="true"></span>
</button>
) : null}
</div>
{sidebar && sidebarOpen ? (
<aside className="ds-modal-sidebar" aria-label={sidebar.label}>
<button
type="button"
className="ds-modal-stage-handle is-collapse"
onClick={() => setSidebarOpen(false)}
title={t('preview.hideSidebar', { label: sidebar.label })}
aria-label={t('preview.hideSidebar', { label: sidebar.label })}
>
<span aria-hidden="true"></span>
</button>
{sidebar.content}
</aside>
) : null}
</div>
</div>
</div>

View file

@ -282,11 +282,13 @@ const DE_DESIGN_SYSTEM_IDS_WITH_EN_FALLBACK = [
'agentic',
'ant',
'application',
'arc',
'artistic',
'bento',
'bold',
'brutalism',
'cafe',
'canva',
'claymorphism',
'clean',
'colorful',
@ -295,9 +297,11 @@ const DE_DESIGN_SYSTEM_IDS_WITH_EN_FALLBACK = [
'cosmic',
'creative',
'dashboard',
'discord',
'dithered',
'doodle',
'dramatic',
'duolingo',
'editorial',
'elegant',
'energetic',
@ -307,8 +311,10 @@ const DE_DESIGN_SYSTEM_IDS_WITH_EN_FALLBACK = [
'flat',
'friendly',
'futuristic',
'github',
'glassmorphism',
'gradient',
'huggingface',
'levels',
'lingo',
'luxury',
@ -319,6 +325,7 @@ const DE_DESIGN_SYSTEM_IDS_WITH_EN_FALLBACK = [
'neobrutalism',
'neon',
'neumorphism',
'openai',
'pacman',
'paper',
'perspective',

View file

@ -310,6 +310,8 @@ export const de: Dict = {
'ds.categoryUncategorized': 'Nicht kategorisiert',
'ds.showcase': 'Showcase',
'ds.tokens': 'Tokens',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': 'DESIGN.md wird geladen…',
'avatar.title': 'Konto & Einstellungen',
'avatar.localCli': 'Lokale CLI',
@ -405,6 +407,8 @@ export const de: Dict = {
'preview.fullscreen': '⤢ Vollbild',
'preview.closeTitle': 'Schließen (Esc)',
'preview.loading': '{label} wird geladen…',
'preview.showSidebar': '{label} einblenden',
'preview.hideSidebar': '{label} ausblenden',
'misc.savedTemplate': 'Gespeichertes Template',
'misc.primary': 'Primär',

View file

@ -310,6 +310,8 @@ export const en: Dict = {
'ds.categoryUncategorized': 'Uncategorized',
'ds.showcase': 'Showcase',
'ds.tokens': 'Tokens',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': 'Loading DESIGN.md…',
'avatar.title': 'Account & settings',
'avatar.localCli': 'Local CLI',
@ -405,6 +407,8 @@ export const en: Dict = {
'preview.fullscreen': '⤢ Fullscreen',
'preview.closeTitle': 'Close (Esc)',
'preview.loading': 'Loading {label}…',
'preview.showSidebar': 'Show {label}',
'preview.hideSidebar': 'Hide {label}',
'misc.savedTemplate': 'Saved template',
'misc.primary': 'Primary',

View file

@ -311,6 +311,8 @@ export const esES: Dict = {
'ds.categoryUncategorized': 'Sin categoría',
'ds.showcase': 'Vitrina',
'ds.tokens': 'Tokens',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': 'Cargando DESIGN.md…',
'avatar.title': 'Cuenta y ajustes',
'avatar.localCli': 'CLI local',
@ -406,6 +408,8 @@ export const esES: Dict = {
'preview.fullscreen': '⤢ Pantalla completa',
'preview.closeTitle': 'Cerrar (Esc)',
'preview.loading': 'Cargando {label}…',
'preview.showSidebar': 'Mostrar {label}',
'preview.hideSidebar': 'Ocultar {label}',
'misc.savedTemplate': 'Plantilla guardada',
'misc.primary': 'Principal',

View file

@ -310,6 +310,8 @@ export const fa: Dict = {
'ds.categoryUncategorized': 'دسته‌بندی نشده',
'ds.showcase': 'ویترین',
'ds.tokens': 'توکن‌ها',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': 'بارگذاری DESIGN.md…',
'avatar.title': 'حساب و تنظیمات',
'avatar.localCli': 'CLI محلی',
@ -405,6 +407,8 @@ export const fa: Dict = {
'preview.fullscreen': '⤢ تمام صفحه',
'preview.closeTitle': 'بستن (Esc)',
'preview.loading': 'در حال بارگذاری {label}…',
'preview.showSidebar': 'نمایش {label}',
'preview.hideSidebar': 'پنهان کردن {label}',
'misc.savedTemplate': 'قالب ذخیره شده',
'misc.primary': 'اصلی',

View file

@ -310,6 +310,8 @@ export const hu: Dict = {
'ds.categoryUncategorized': 'Kategorizálatlan',
'ds.showcase': 'Bemutató',
'ds.tokens': 'Tokenek',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': 'DESIGN.md betöltése…',
'avatar.title': 'Fiók és beállítások',
'avatar.localCli': 'Helyi CLI',
@ -405,6 +407,8 @@ export const hu: Dict = {
'preview.fullscreen': '⤢ Teljes képernyő',
'preview.closeTitle': 'Bezárás (Esc)',
'preview.loading': '{label} betöltése…',
'preview.showSidebar': '{label} megjelenítése',
'preview.hideSidebar': '{label} elrejtése',
'misc.savedTemplate': 'Mentett sablon',
'misc.primary': 'Elsődleges',

View file

@ -309,6 +309,8 @@ export const ja: Dict = {
'ds.categoryUncategorized': '未分類',
'ds.showcase': 'ショーケース',
'ds.tokens': 'トークン',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': 'DESIGN.md を読み込み中…',
'avatar.title': 'アカウントと設定',
'avatar.localCli': 'ローカル CLI',
@ -404,6 +406,8 @@ export const ja: Dict = {
'preview.fullscreen': '⤢ フルスクリーン',
'preview.closeTitle': '閉じる (Esc)',
'preview.loading': '{label} を読み込み中…',
'preview.showSidebar': '{label} を表示',
'preview.hideSidebar': '{label} を非表示',
'misc.savedTemplate': '保存済みテンプレート',
'misc.primary': 'プライマリ',

View file

@ -310,6 +310,8 @@ export const ko: Dict = {
'ds.categoryUncategorized': '미분류',
'ds.showcase': '쇼케이스',
'ds.tokens': '토큰',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': 'DESIGN.md 불러오는 중…',
'avatar.title': '계정 및 설정',
'avatar.localCli': '로컬 CLI',
@ -405,6 +407,8 @@ export const ko: Dict = {
'preview.fullscreen': '⤢ 전체 화면',
'preview.closeTitle': '닫기 (Esc)',
'preview.loading': '{label} 불러오는 중…',
'preview.showSidebar': '{label} 표시',
'preview.hideSidebar': '{label} 숨기기',
'misc.savedTemplate': '저장된 템플릿',
'misc.primary': '기본색 (Primary)',

View file

@ -310,6 +310,8 @@ export const pl: Dict = {
'ds.categoryUncategorized': 'Niekategoryzowane',
'ds.showcase': 'Galeria',
'ds.tokens': 'Tokeny',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': 'Ładowanie DESIGN.md…',
'avatar.title': 'Konto i ustawienia',
'avatar.localCli': 'Lokalne CLI',
@ -405,6 +407,8 @@ export const pl: Dict = {
'preview.fullscreen': '⤢ Pełny ekran',
'preview.closeTitle': 'Zamknij (Esc)',
'preview.loading': 'Ładowanie {label}…',
'preview.showSidebar': 'Pokaż {label}',
'preview.hideSidebar': 'Ukryj {label}',
'misc.savedTemplate': 'Zapisany szablon',
'misc.primary': 'Główny',

View file

@ -309,6 +309,8 @@ export const ptBR: Dict = {
'ds.categoryUncategorized': 'Sem categoria',
'ds.showcase': 'Vitrine',
'ds.tokens': 'Tokens',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': 'Carregando DESIGN.md…',
'avatar.title': 'Conta e configurações',
'avatar.localCli': 'CLI local',
@ -404,6 +406,8 @@ export const ptBR: Dict = {
'preview.fullscreen': '⤢ Tela cheia',
'preview.closeTitle': 'Fechar (Esc)',
'preview.loading': 'Carregando {label}…',
'preview.showSidebar': 'Mostrar {label}',
'preview.hideSidebar': 'Ocultar {label}',
'misc.savedTemplate': 'Template salvo',
'misc.primary': 'Principal',

View file

@ -309,6 +309,8 @@ export const ru: Dict = {
'ds.categoryUncategorized': 'Без категории',
'ds.showcase': 'Витрина',
'ds.tokens': 'Токены',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': 'Загрузка DESIGN.md…',
'avatar.title': 'Аккаунт и настройки',
'avatar.localCli': 'Локальный CLI',
@ -404,6 +406,8 @@ export const ru: Dict = {
'preview.fullscreen': '⤢ Полноэкранный',
'preview.closeTitle': 'Закрыть (Esc)',
'preview.loading': 'Загрузка {label}…',
'preview.showSidebar': 'Показать {label}',
'preview.hideSidebar': 'Скрыть {label}',
'misc.savedTemplate': 'Сохраненный шаблон',
'misc.primary': 'Основной',

View file

@ -309,6 +309,8 @@ export const tr: Dict = {
'ds.categoryUncategorized': 'Kategorilendirilmemiş',
'ds.showcase': 'Tanıtım',
'ds.tokens': 'Tokenler',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': 'DESIGN.md yükleniyor…',
'avatar.title': 'Hesap & ayarlar',
'avatar.localCli': 'Yerel CLI',
@ -404,6 +406,8 @@ export const tr: Dict = {
'preview.fullscreen': '⤢ Tam ekran',
'preview.closeTitle': 'Kapat (Esc)',
'preview.loading': '{label} yükleniyor…',
'preview.showSidebar': '{label} göster',
'preview.hideSidebar': '{label} gizle',
'misc.savedTemplate': 'Kaydedilmiş şablonlar',
'misc.primary': 'Birincil',

View file

@ -305,6 +305,8 @@ export const zhCN: Dict = {
'ds.categoryUncategorized': '未分类',
'ds.showcase': '展示',
'ds.tokens': 'Token',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': '正在加载 DESIGN.md…',
'avatar.title': '账户与设置',
'avatar.localCli': '本机 CLI',
@ -397,6 +399,8 @@ export const zhCN: Dict = {
'preview.fullscreen': '⤢ 全屏',
'preview.closeTitle': '关闭Esc',
'preview.loading': '正在加载{label}…',
'preview.showSidebar': '展开{label}',
'preview.hideSidebar': '收起{label}',
'misc.savedTemplate': '已保存的模板',
'misc.primary': '主体系',

View file

@ -305,6 +305,8 @@ export const zhTW: Dict = {
'ds.categoryUncategorized': '未分類',
'ds.showcase': '展示',
'ds.tokens': 'Token',
'ds.specToggle': 'DESIGN.md',
'ds.specLoading': '正在載入 DESIGN.md…',
'avatar.title': '帳號與設定',
'avatar.localCli': '本機 CLI',
@ -397,6 +399,8 @@ export const zhTW: Dict = {
'preview.fullscreen': '⤢ 全螢幕',
'preview.closeTitle': '關閉Esc',
'preview.loading': '正在載入{label}…',
'preview.showSidebar': '展開{label}',
'preview.hideSidebar': '收合{label}',
'misc.savedTemplate': '已儲存的範本',
'misc.primary': '主系統',

View file

@ -329,6 +329,8 @@ export interface Dict {
'ds.categoryUncategorized': string;
'ds.showcase': string;
'ds.tokens': string;
'ds.specToggle': string;
'ds.specLoading': string;
// Avatar menu (project topbar)
'avatar.title': string;
@ -420,6 +422,8 @@ export interface Dict {
'preview.fullscreen': string;
'preview.closeTitle': string;
'preview.loading': string;
'preview.showSidebar': string;
'preview.hideSidebar': string;
// Misc fallback names
'misc.savedTemplate': string;

View file

@ -3058,7 +3058,168 @@ code {
flex-shrink: 0;
}
/* Design systems list */
/* Design systems gallery masonry-style cards with lazy showcase iframes
serving as thumbnails. The grid mirrors the prompt-templates and example
gallery surfaces so the three browse tabs feel uniform. */
.ds-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.ds-card {
display: flex;
flex-direction: column;
background: var(--bg-panel);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
cursor: pointer;
transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
.ds-card:hover {
border-color: var(--border-strong);
transform: translateY(-1px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
}
.ds-card:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.ds-card.active {
border-color: var(--accent);
box-shadow: 0 0 0 2px var(--accent-tint);
}
.ds-card-thumb {
position: relative;
width: 100%;
aspect-ratio: 4 / 3;
background: var(--bg-subtle);
overflow: hidden;
border-bottom: 1px solid var(--border);
}
.ds-card-thumb iframe {
width: 200%;
height: 200%;
border: none;
display: block;
background: white;
transform: scale(0.5);
transform-origin: top left;
pointer-events: none;
}
.ds-card-thumb-fallback {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
}
.ds-card-thumb-swatches {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
width: 100%;
height: 100%;
}
.ds-card-thumb-swatches > span { display: block; }
.ds-card-thumb-overlay {
position: absolute;
right: 8px;
bottom: 8px;
background: rgba(15, 15, 18, 0.78);
color: #fff;
font-size: 10px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
padding: 4px 10px;
border-radius: 999px;
opacity: 0;
transition: opacity 0.15s ease;
}
.ds-card:hover .ds-card-thumb-overlay,
.ds-card-thumb:focus-visible .ds-card-thumb-overlay {
opacity: 1;
}
.ds-card-meta {
display: flex;
flex-direction: column;
gap: 6px;
padding: 12px 14px 14px;
flex: 1;
}
.ds-card-title-row {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.ds-card-title {
font-size: 14px;
font-weight: 600;
color: var(--text);
letter-spacing: -0.005em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
min-width: 0;
}
.ds-card-badge {
font-size: 9px;
font-weight: 600;
letter-spacing: 0.06em;
padding: 2px 6px;
background: var(--accent-soft);
color: var(--accent);
border-radius: 4px;
flex-shrink: 0;
}
.ds-card-summary {
font-size: 12px;
color: var(--text-muted);
line-height: 1.45;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.ds-card-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-top: auto;
padding-top: 6px;
}
.ds-card-category {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--text-muted);
}
.ds-card-swatches {
display: inline-flex;
align-items: center;
border-radius: 6px;
border: 1px solid var(--border);
overflow: hidden;
flex-shrink: 0;
height: 18px;
}
.ds-card-swatches > span {
display: block;
width: 14px;
height: 100%;
}
.ds-card-swatches > span + span {
border-left: 1px solid rgba(0, 0, 0, 0.05);
}
/* Legacy list classes kept for any consumer outside the tab the gallery
itself no longer renders these. Safe to remove once nothing references
them. */
.ds-list { display: flex; flex-direction: column; gap: 8px; }
.ds-row {
display: flex;
@ -3090,10 +3251,6 @@ code {
border-radius: 4px;
}
.ds-row-summary { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
/* Palette thumbnail next to each design system in the picker. Reads as a
tiny brand mark bg + support + fg + accent. Lets users scan the list
visually instead of relying on summary copy alone. */
.ds-row-swatches {
display: inline-flex;
align-items: center;
@ -5534,16 +5691,79 @@ code {
min-height: 0;
background: white;
position: relative;
display: flex;
align-items: stretch;
}
.ds-modal-stage iframe {
.ds-modal-stage-iframe {
flex: 1;
min-width: 0;
position: relative;
overflow: hidden;
background: white;
}
.ds-modal-stage-iframe-scaler {
position: absolute;
top: 0;
left: 0;
transform-origin: top left;
background: white;
/* Prevent the GPU layer from blurring the scaled iframe on Retina. */
will-change: transform;
}
.ds-modal-stage-iframe-scaler iframe {
width: 100%;
height: 100%;
border: none;
display: block;
background: white;
}
.ds-modal-fullscreen .ds-modal-stage:fullscreen iframe,
.ds-modal-stage:fullscreen iframe {
.ds-modal-stage.has-sidebar .ds-modal-stage-iframe {
flex: 1 1 60%;
}
.ds-modal-sidebar {
position: relative;
flex: 1 1 40%;
min-width: 320px;
max-width: 560px;
border-left: 1px solid var(--border);
background: var(--bg-panel);
overflow: auto;
display: flex;
flex-direction: column;
}
.ds-modal-stage-handle {
position: absolute;
top: 50%;
width: 18px;
height: 56px;
transform: translateY(-50%);
background: var(--bg-panel);
border: 1px solid var(--border);
color: var(--text-muted);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
line-height: 1;
padding: 0;
z-index: 3;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
transition: color 120ms ease, background 120ms ease;
}
.ds-modal-stage-handle:hover { color: var(--text); background: var(--bg-subtle); }
.ds-modal-stage-handle.is-expand {
right: 0;
border-right: none;
border-radius: 8px 0 0 8px;
}
.ds-modal-stage-handle.is-collapse {
left: 0;
border-left: none;
border-radius: 0 8px 8px 0;
}
.ds-modal-fullscreen .ds-modal-stage:fullscreen .ds-modal-stage-iframe-scaler,
.ds-modal-stage:fullscreen .ds-modal-stage-iframe-scaler {
height: 100vh;
}
.ds-modal-empty {
@ -5555,11 +5775,85 @@ code {
color: var(--text-muted);
font-size: 13px;
}
.ds-modal-actions .ghost.is-active {
background: var(--accent-tint);
color: var(--accent);
border-color: var(--accent);
}
/* DESIGN.md side panel monospace source view with light syntax tints,
echoing the styles.refero.design "compact" markdown source pane. */
.design-spec-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
color: var(--text-muted);
font-size: 12px;
}
.design-spec-pre {
margin: 0;
padding: 16px 18px;
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
font-size: 12px;
line-height: 1.6;
white-space: pre;
overflow: auto;
color: var(--text);
background: var(--bg-panel);
flex: 1;
}
.design-spec-pre code { font: inherit; color: inherit; background: transparent; }
.design-spec-line { display: inline; }
.design-spec-line.is-h1 { color: #2563eb; font-weight: 700; }
.design-spec-line.is-h2 { color: #0891b2; font-weight: 700; }
.design-spec-line.is-h3 { color: #0d9488; font-weight: 600; }
.design-spec-line.is-h4 { color: #16a34a; font-weight: 600; }
.design-spec-line.is-quote { color: #6b7280; font-style: italic; }
.design-spec-line.is-list { color: var(--text); }
.design-spec-line.is-table { color: #7c3aed; }
.design-spec-line.is-fence { color: #dc2626; }
.design-spec-line.is-blank { color: var(--text-muted); }
.md-tk-bold { font-weight: 700; color: var(--text); }
.md-tk-em { font-style: italic; color: var(--text); }
.md-tk-code {
background: var(--bg-subtle);
padding: 0 4px;
border-radius: 3px;
color: #0f766e;
}
.md-tk-color {
display: inline-flex;
align-items: center;
gap: 4px;
color: #be185d;
}
.md-tk-color-swatch {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 2px;
border: 1px solid rgba(0, 0, 0, 0.12);
vertical-align: middle;
}
@media (max-width: 760px) {
.ds-modal-backdrop { padding: 0; }
.ds-modal { border-radius: 0; }
.ds-modal-header { grid-template-columns: 1fr; gap: 8px; }
.ds-modal-actions { justify-content: flex-start; flex-wrap: wrap; }
.ds-modal-stage { flex-direction: column; }
.ds-modal-stage.has-sidebar .ds-modal-stage-iframe { flex: 1 1 50%; }
.ds-modal-sidebar {
border-left: none;
border-top: 1px solid var(--border);
flex: 1 1 50%;
min-width: 0;
max-width: none;
}
/* On stacked layout the side handles (which assume horizontal split)
would float over content awkwardly fall back to the header toggle. */
.ds-modal-stage-handle { display: none; }
}
/* Examples gallery toolbar — filter pills + richer card metadata */

View file

@ -0,0 +1,152 @@
# Design System Inspired by Arc Browser
> Category: Productivity & SaaS
> "The browser that browses for you." Translucent surfaces, gradient warmth, sidebar-first layout.
## 1. Visual Theme & Atmosphere
Arc Browser dissolves the boundary between the chrome and the page. Where Chrome and Safari treat the browser frame as a container, Arc treats it as scenery — the toolbar fades into the system wallpaper, the sidebar carries gradient warmth from the user's chosen "theme color", and translucency is everywhere. The visual signature is **frosted glass plus a single saturated gradient** — most often a peach-to-coral or violet-to-fuchsia bloom — that sets the emotional temperature of the entire window.
Typography uses **Inter** for chrome and a custom display serif (`Argent CF` or similar) for marketing — when Arc speaks publicly it speaks editorially, in a serif voice unusual for tech. The product itself is sans-only, with tight tracking and generous line-height.
Shapes are squircle-soft: 1216px radii on cards, 8px on tabs, 9999px pills for tags. Borders are rare — Arc prefers tinted background washes (`rgba(255, 255, 255, 0.5)` over the gradient) to delineate panes.
**Key Characteristics:**
- Translucent frosted-glass surfaces over a saturated gradient background
- Theme-color gradients (peach-coral, violet-fuchsia, mint-cyan) as the primary mood
- Inter for product chrome, Argent CF (serif) for marketing display
- Squircle-soft 1216px radii everywhere
- Sidebar-first layout: tabs, spaces, and bookmarks live on the left, not the top
- Color picker is a brand surface — themes are user-driven, not fixed
- Subtle shadows (`0 8px 32px rgba(0,0,0,0.08)`) over the gradient backdrop
## 2. Color Palette & Roles
### Primary Theme Gradients (User-selectable; default is "Sunset")
- **Sunset Start** (`#ff7e5f`): Peach gradient origin.
- **Sunset End** (`#feb47b`): Soft coral gradient terminus.
- **Twilight Start** (`#7f5af0`): Violet gradient origin.
- **Twilight End** (`#e84393`): Fuchsia gradient terminus.
- **Aurora Start** (`#16f2b3`): Mint gradient origin.
- **Aurora End** (`#0db4f7`): Cyan gradient terminus.
### Surface (Frosted)
- **Glass Light** (`rgba(255, 255, 255, 0.7)`): Standard frosted pane over gradient.
- **Glass Medium** (`rgba(255, 255, 255, 0.5)`): Hover state, tab pill background.
- **Glass Heavy** (`rgba(255, 255, 255, 0.85)`): Active pane, command bar.
- **Glass Dark** (`rgba(20, 20, 25, 0.6)`): Dark-mode frosted surface.
### Ink & Text
- **Ink Primary** (`#1a1a1f`): Primary text on light frosted surface.
- **Ink Secondary** (`#54545a`): Secondary text, tab title at rest.
- **Ink Muted** (`#8c8c93`): Tertiary, captions, URL bar.
- **Ink Inverse** (`#fafafa`): Text on dark frosted surface.
### Border & Divider
- **Border Glass** (`rgba(255, 255, 255, 0.4)`): Frosted-edge border.
- **Border Hairline** (`rgba(0, 0, 0, 0.06)`): Hairline divider on light surface.
- **Border Active** (`rgba(0, 0, 0, 0.18)`): Active tab outline.
### Brand Accent
- **Arc Coral** (`#ff5f5f`): Default brand color — used in marketing, `arc.net`.
- **Arc Lavender** (`#b794f4`): Secondary brand accent.
### Semantic
- **Success** (`#48bb78`): Toast confirmation.
- **Warning** (`#f6ad55`): Permission prompt.
- **Error** (`#f56565`): Form validation.
## 3. Typography Rules
### Font Family
- **Display / Marketing**: `Argent CF`, with fallback: `'Source Serif Pro', Georgia, serif`
- **Body / UI**: `Inter`, with fallback: `system-ui, -apple-system, BlinkMacSystemFont, sans-serif`
- **Code / Mono**: `Berkeley Mono`, with fallback: `ui-monospace, Menlo, Consolas, monospace`
### Hierarchy
| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |
|------|------|------|--------|-------------|----------------|-------|
| Marketing Hero | Argent CF | 72px (4.5rem) | 400 | 1.05 | -0.03em | Editorial display, marketing only |
| Section Heading | Argent CF | 40px (2.5rem) | 400 | 1.15 | -0.02em | Marketing section titles |
| Page H1 | Inter | 32px (2rem) | 700 | 1.2 | -0.02em | Settings, command bar header |
| Page H2 | Inter | 22px (1.375rem) | 600 | 1.25 | -0.01em | Sub-section |
| Tab Title | Inter | 13px (0.8125rem) | 500 | 1.3 | -0.005em | Sidebar tab label |
| Body | Inter | 15px (0.9375rem) | 400 | 1.55 | normal | Settings prose, tooltips |
| Caption | Inter | 12px (0.75rem) | 500 | 1.4 | 0.01em | URL bar protocol, metadata |
| Code | Berkeley Mono | 13px (0.8125rem) | 400 | 1.5 | normal | URL bar, devtools |
### Principles
- **Serif moments are rare**: Argent CF appears only in marketing. The product is sans-only.
- **Title size is small**: tabs render at 13px so a long sidebar of 30+ tabs stays scannable.
- **Tracking tightens with size**: -0.03em at 72px, returning to normal by 15px.
## 4. Component Stylings
### Buttons
**Primary (Filled)**
- Background: linear-gradient on theme color (e.g., `linear-gradient(135deg, #ff7e5f, #feb47b)`)
- Text: `#ffffff`
- Padding: 10px 20px
- Radius: 12px
- Shadow: `0 4px 16px rgba(255, 127, 95, 0.3)`
- Hover: shadow grows to `0 8px 24px rgba(255, 127, 95, 0.4)`
**Glass (Secondary)**
- Background: `rgba(255, 255, 255, 0.7)`
- Backdrop: `blur(20px)`
- Text: `#1a1a1f`
- Border: 1px solid `rgba(255, 255, 255, 0.4)`
- Padding: 10px 20px
- Radius: 12px
**Subtle**
- Background: transparent
- Text: theme color
- Hover: background `rgba(255, 127, 95, 0.1)`
### Tabs (Sidebar)
- Background at rest: transparent
- Background on hover: `rgba(255, 255, 255, 0.5)`
- Background active: `rgba(255, 255, 255, 0.85)` + soft shadow
- Padding: 8px 12px
- Radius: 8px
- Favicon: 16px square at left, 8px gap to title.
### Cards / Panes
- Background: `rgba(255, 255, 255, 0.7)`
- Backdrop: `blur(24px)` saturate 180%
- Border: 1px solid `rgba(255, 255, 255, 0.4)`
- Radius: 16px
- Shadow: `0 8px 32px rgba(0, 0, 0, 0.08)`
- Padding: 24px
### Inputs (Command Bar)
- Background: `rgba(255, 255, 255, 0.85)`
- Backdrop: `blur(40px)`
- Text: `#1a1a1f`
- Border: 1px solid `rgba(255, 255, 255, 0.4)`
- Radius: 14px
- Padding: 14px 18px
- Focus: shadow `0 0 0 4px rgba(255, 127, 95, 0.2)`
### Pills (Spaces / Bookmarks Folder)
- Background: theme color at 16% alpha
- Text: theme color (full)
- Padding: 4px 10px
- Radius: 9999px
- Font: 12px / 600
## 5. Spacing & Layout
- **Base unit**: 4px. Scale: 4, 8, 12, 16, 24, 32, 48, 64.
- **Sidebar**: 240px wide; collapsible to 56px.
- **Window radius**: 12px on the OS window itself (macOS-only flourish).
- **Padding inside panes**: 24px.
## 6. Motion
- **Duration**: 200ms for hover; 320ms for tab create/close; 480ms for "Little Arc" window expand.
- **Easing**: `cubic-bezier(0.32, 0.72, 0, 1)` for window expand (Apple's spring-style).
- **Tab swap**: 1px translate + opacity blend, no scale change.

View file

@ -0,0 +1,157 @@
# Design System Inspired by Canva
> Category: Design & Creative
> Visual creation platform. Vivid purple-blue gradient, generous spacing, friendly geometry.
## 1. Visual Theme & Atmosphere
Canva is the friendly face of design tools — the brand makes a point of looking inviting where Adobe looks intimidating. The page is built on a clean white canvas (`#ffffff`) with a signature **purple-to-blue gradient** (`#7d2ae8` → `#00c4cc`) used in the brand mark, hero buttons, and Pro/Magic moments. Surfaces are generously padded, edges are gently rounded (816px), and shadows are soft and cool-toned.
Typography uses **Canva Sans** (a custom geometric sans) for chrome and prose, with rounded letterforms that share DNA with brands like Airbnb and Asana. Weight contrast does the heavy lifting — 800 for hero display, 700 for section heads, 400 for body — while size hierarchy is more compressed than typical product brands so cards and templates read at a glance.
The shape system is ultra-soft: 12px on most cards, 1620px on larger panels, 9999px on chips. Buttons are rectangles with a subtle elevation shadow (`0 2px 8px rgba(0,0,0,0.06)`) that grows on hover. Iconography is filled and rounded, never line-only — Canva's icons speak the same shape language as its UI.
**Key Characteristics:**
- White canvas with a violet-to-cyan gradient (`#7d2ae8` → `#00c4cc`)
- Canva Sans (rounded geometric) for everything; weight contrast over color
- 1220px radii everywhere; 9999px pills for chips and tags
- Soft cool-toned shadows that grow on hover
- Filled rounded iconography — never outlined
- Vibrant secondary palette (coral, mint, tangerine) for category tags
- Pro/Magic moments lit by a static gradient — no animation
## 2. Color Palette & Roles
### Brand Gradient
- **Canva Purple** (`#7d2ae8`): Brand primary; gradient origin.
- **Canva Cyan** (`#00c4cc`): Brand secondary; gradient terminus.
- **Canva Pink** (`#ff5757`): Tertiary brand accent (Magic Studio).
### Surface
- **Canvas** (`#ffffff`): Primary background.
- **Surface Subtle** (`#f4f5f7`): Section break, sidebar.
- **Surface Inset** (`#e8eaed`): Disabled, inset block.
- **Surface Cool** (`#eef0fc`): Hover tint on purple-themed cards.
### Ink & Text
- **Ink Primary** (`#0e1318`): Primary text.
- **Ink Secondary** (`#3c4043`): Body prose.
- **Ink Muted** (`#5f6368`): Captions, descriptions.
- **Ink Faint** (`#9aa0a6`): Placeholder, disabled label.
### Semantic
- **Success** (`#00b894`): Saved, exported.
- **Warning** (`#ffb020`): Storage limit, advisory.
- **Error** (`#ff5757`): Validation, destructive.
- **Info** (`#0d99ff`): Tip, link.
### Category Accents (Template Tags)
- **Coral** (`#ff7059`): Social posts.
- **Tangerine** (`#ff9633`): Marketing.
- **Mint** (`#48c997`): Education.
- **Sky** (`#3ea6ff`): Business.
- **Lavender** (`#9b87f5`): Personal.
### Border
- **Border Default** (`#e1e3e6`): Standard hairline.
- **Border Strong** (`#c7cdd3`): Emphasized border, hover state.
## 3. Typography Rules
### Font Family
- **Display / UI / Body**: `Canva Sans`, with fallback: `'YS Text', system-ui, -apple-system, sans-serif`
- **Editorial (rare)**: `Canva Sans Display`, with fallback: `'Canva Sans', sans-serif`
- **Code (devtools only)**: `JetBrains Mono`, with fallback: `ui-monospace, Menlo, Consolas, monospace`
### Hierarchy
| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |
|------|------|------|--------|-------------|----------------|-------|
| Hero | Canva Sans | 64px (4rem) | 800 | 1.05 | -0.02em | Marketing hero, "Design anything." |
| H1 | Canva Sans | 36px (2.25rem) | 700 | 1.15 | -0.01em | Page heading |
| H2 | Canva Sans | 24px (1.5rem) | 700 | 1.2 | -0.005em | Section heading |
| H3 | Canva Sans | 18px (1.125rem) | 600 | 1.3 | normal | Sub-section, card title |
| Body Large | Canva Sans | 16px (1rem) | 400 | 1.55 | normal | Lede, marketing body |
| Body | Canva Sans | 14px (0.875rem) | 400 | 1.5 | normal | Standard UI prose |
| Caption | Canva Sans | 12px (0.75rem) | 500 | 1.4 | 0.005em | Metadata, hint text |
| Button | Canva Sans | 14px (0.875rem) | 600 | 1.2 | normal | Default button label |
| Tag | Canva Sans | 11px (0.6875rem) | 600 | 1.2 | 0.04em | Uppercase category chip |
### Principles
- **Weight contrast over color contrast**: hierarchy steps via 800→700→600→400; the surface stays neutral.
- **Tight line-height for cards**: card titles use 1.151.2 so a 3-line title still fits a 4:3 thumbnail.
- **No display serif**: Canva is sans-only across all surfaces; serifs appear only as user-selectable template fonts inside the editor.
## 4. Component Stylings
### Buttons
**Primary (Gradient)**
- Background: `linear-gradient(135deg, #7d2ae8, #00c4cc)`
- Text: `#ffffff`
- Padding: 12px 20px
- Radius: 8px
- Shadow: `0 2px 8px rgba(125, 42, 232, 0.2)`
- Hover: shadow grows to `0 4px 14px rgba(125, 42, 232, 0.3)`
- Use: hero CTAs, "Try Canva Pro"
**Primary (Solid Purple)**
- Background: `#7d2ae8`
- Text: `#ffffff`
- Padding: 12px 20px
- Radius: 8px
- Hover: `#6815d4`
**Secondary**
- Background: `#ffffff`
- Text: `#0e1318`
- Border: 1px solid `#e1e3e6`
- Radius: 8px
- Hover: background `#f4f5f7`, border `#c7cdd3`
**Subtle / Tertiary**
- Background: `rgba(125, 42, 232, 0.08)`
- Text: `#7d2ae8`
- Hover: background `rgba(125, 42, 232, 0.14)`
### Cards / Template Tiles
- Background: `#ffffff`
- Border: 1px solid `#e1e3e6`
- Radius: 12px
- Shadow at rest: `0 1px 3px rgba(0,0,0,0.04)`
- Shadow on hover: `0 8px 24px rgba(0,0,0,0.08)`, lift 2px
- Aspect ratio: thumbnail respects template (1:1, 4:3, 9:16)
### Inputs
- Background: `#ffffff`
- Border: 1px solid `#e1e3e6`
- Radius: 8px
- Padding: 10px 14px
- Focus: border `#7d2ae8`, ring `0 0 0 3px rgba(125, 42, 232, 0.16)`
### Chips / Tags
- Background: category-tinted soft.
- Text: matching strong category color.
- Padding: 4px 10px
- Radius: 9999px
- Font: 11px / 600 / uppercase
### Pro Badge
- Background: `linear-gradient(135deg, #7d2ae8, #ff5757)`
- Text: `#ffffff`
- Padding: 2px 8px
- Radius: 9999px
- Font: 10px / 700 / uppercase
## 5. Spacing & Layout
- **Base unit**: 4px. Scale: 4, 8, 12, 16, 24, 32, 48, 64, 96.
- **Container**: max 1320px, 32px gutter.
- **Sidebar (editor)**: 320px wide; collapses to 56px icons.
- **Card grid gap**: 16px on mobile, 24px on desktop.
## 6. Motion
- **Duration**: 180ms for hover; 280ms for menu open; 420ms for editor sidebar collapse.
- **Easing**: `cubic-bezier(0.4, 0, 0.2, 1)` (Material-style).
- **Card lift**: translateY(-2px) + shadow grow on hover, single transition.

View file

@ -0,0 +1,162 @@
# Design System Inspired by Discord
> Category: Productivity & SaaS
> Voice / chat platform. Deep blurple, dark-first surfaces, playful accent moments.
## 1. Visual Theme & Atmosphere
Discord's product is engineered for evenings, raids, and group voice — so the entire surface is dark-first. The default canvas is the deep `Background Primary` (`#313338` light theme, `#1e1f22` dark theme), with chat columns layered on slightly lighter or darker shades to denote channels, threads, and side panels. The signature **Blurple** (`#5865f2`) is reserved for the brand mark, primary CTAs, mentions, and the "you" affordance — used sparingly so it pops against the muted neutrals.
Typography is **gg sans** (Discord's custom Whitney-replacement) for prose and chrome, with rounded geometric shapes that feel approachable but still legible at the small sizes a chat client demands. Headings step up incrementally; chat rows are tight (48px between message groups) so hours of scrollback feel scannable.
The shape language is rounded but not balloon-soft: 8px radii on cards, 4px on inputs, full pills on status badges and tags. Servers are rounded-square avatars at 48px that morph to circles on hover — a tiny piece of motion that has become part of the brand's identity.
**Key Characteristics:**
- Dark-first surfaces: `#1e1f22` / `#2b2d31` / `#313338` (3-step depth)
- Blurple `#5865f2` as the only saturated accent in the chat surface
- gg sans (Whitney-style) for all text — friendly, geometric, neutral
- Rounded-square server avatars (16px radius) that snap to circles on hover
- Tight chat-row spacing, generous side-panel padding
- Status dots: green online, yellow idle, red dnd, gray offline
- Pixel-snapped 1px dividers in subtle off-white at low alpha
## 2. Color Palette & Roles
### Primary
- **Blurple** (`#5865f2`): Brand primary, primary CTA, mention highlight.
- **Blurple Hover** (`#4752c4`): Hover/active for blurple.
- **Blurple Soft** (`#7289da`): Legacy blurple, secondary accent in marketing.
### Surface (Dark Theme — default)
- **Background Tertiary** (`#1e1f22`): Server list rail, deepest background.
- **Background Secondary** (`#2b2d31`): Channel sidebar, settings sidebar.
- **Background Primary** (`#313338`): Chat surface, message column.
- **Background Floating** (`#111214`): Floating popovers, tooltips, autocomplete.
- **Background Modifier Hover** (`rgba(78, 80, 88, 0.3)`): Hover overlay on rows.
- **Background Modifier Selected** (`rgba(78, 80, 88, 0.6)`): Active row.
### Surface (Light Theme)
- **Light Bg Primary** (`#ffffff`): Chat surface in light theme.
- **Light Bg Secondary** (`#f2f3f5`): Sidebar in light theme.
- **Light Bg Tertiary** (`#e3e5e8`): Deepest light surface.
### Text
- **Header Primary** (`#f2f3f5`): Channel headers, modal titles in dark theme.
- **Header Secondary** (`#b5bac1`): Muted headers.
- **Text Normal** (`#dbdee1`): Body text in dark theme — slightly cooler than pure white.
- **Text Muted** (`#949ba4`): Timestamps, server names, secondary metadata.
- **Text Link** (`#00a8fc`): Hyperlinks in messages — sky blue, distinct from blurple.
- **Channels Default** (`#80848e`): Inactive channel name in sidebar.
### Status & Semantic
- **Status Online** (`#23a55a`): Online dot, success states.
- **Status Idle** (`#f0b232`): Idle dot, away.
- **Status DND** (`#f23f43`): Do-not-disturb, also serves as destructive red.
- **Status Streaming** (`#593695`): "Streaming" purple.
- **Status Offline** (`#80848e`): Offline gray.
- **Mention Highlight** (`rgba(88, 101, 242, 0.1)`): Soft blurple wash on @mention rows.
### Border & Divider
- **Background Modifier Accent** (`rgba(255, 255, 255, 0.06)`): Standard divider in dark.
- **Border Subtle** (`#3f4147`): Solid divider for cards.
## 3. Typography Rules
### Font Family
- **Body / UI / Headings**: `gg sans`, with fallback: `"Helvetica Neue", Helvetica, Arial, sans-serif`
- **Display (legacy / Whitney)**: `Whitney`, with fallback: `gg sans`
- **Code / Mono**: `"gg mono"`, with fallback: `Consolas, Andale Mono, Courier New, Courier, monospace`
### Hierarchy
| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |
|------|------|------|--------|-------------|----------------|-------|
| Display Hero | gg sans | 56px (3.5rem) | 800 | 1.1 | -0.02em | Marketing hero |
| Page Heading | gg sans | 24px (1.5rem) | 700 | 1.25 | normal | Settings/profile titles |
| Channel Name | gg sans | 16px (1rem) | 600 | 1.25 | normal | `#general`, channel header |
| Message Body | gg sans | 16px (1rem) | 400 | 1.375 | normal | Standard chat text |
| Username | gg sans | 16px (1rem) | 500 | 1.25 | normal | Author of a message |
| Timestamp | gg sans | 12px (0.75rem) | 500 | 1.25 | normal | "Today at 4:32 PM" |
| Sidebar Channel | gg sans | 16px (1rem) | 500 | 1.25 | normal | Channel list rows |
| Server Name | gg sans | 16px (1rem) | 600 | 1.25 | normal | Server header |
| Caption / Meta | gg sans | 12px (0.75rem) | 400 | 1.3 | 0.02em | Status text, edited tag |
| Code Inline | gg mono | 0.875em | 400 | inherit | normal | Inline `code` |
| Code Block | gg mono | 14px (0.875rem) | 400 | 1.5 | normal | ```triple-fenced``` block |
### Principles
- **Friendly geometry**: gg sans replaces Whitney with rounded terminals on a/g/s — the brand wants warmth without breaking legibility.
- **Weight contrast over color contrast**: hierarchy comes from 400→500→600→700→800 weight steps; the surface stays neutral.
- **16px body**: chat messages do not shrink below 16px. Density comes from line-height (1.375), not font size.
## 4. Component Stylings
### Buttons
**Primary**
- Background: `#5865f2`
- Text: `#ffffff`
- Padding: 8px 16px
- Radius: 4px
- Hover: `#4752c4`
- Use: Primary CTAs, "Continue", "Join Server"
**Secondary**
- Background: `#4e5058`
- Text: `#ffffff`
- Padding: 8px 16px
- Radius: 4px
- Hover: `#6d6f78`
**Tertiary / Subtle (Link-style)**
- Background: transparent
- Text: `#dbdee1`
- Hover: text underlined, no background change
**Danger**
- Background: `#da373c`
- Text: `#ffffff`
- Hover: `#a12d2f`
### Inputs
- Background: `#1e1f22`
- Text: `#dbdee1`
- Border: 1px solid `#1e1f22`
- Radius: 4px
- Padding: 10px 12px
- Focus: border `#5865f2`
### Server Avatars
- Size: 48×48px
- Radius: 16px (rounded square) by default; transitions to 50% on hover and active.
- Active state: 4px white pill on the left edge of the icon column.
### Status Dots
- Size: 10×10px
- Border: 3px solid background-tertiary (creates the "notch" effect)
- Position: bottom-right of avatar.
### Cards / Embeds
- Background: `#2b2d31` (dark) or `#f2f3f5` (light)
- Left border: 4px solid embed accent color.
- Radius: 4px
- Padding: 8px 16px
### Mention Pill
- Background: `rgba(88, 101, 242, 0.3)`
- Text: `#c9cdfb`
- Padding: 0 2px
- Radius: 3px
## 5. Spacing & Layout
- **Base unit**: 4px. Scale: 4, 8, 12, 16, 20, 24, 32, 40.
- **Server rail**: 72px wide, fixed.
- **Channel sidebar**: 240px wide.
- **Member list**: 240px wide on desktop.
- **Chat column**: fluid, min 380px.
## 6. Motion
- **Duration**: 200ms for hover; 350ms for the avatar circle-morph; 80ms for tooltip fade.
- **Easing**: `cubic-bezier(0.215, 0.61, 0.355, 1)` for the avatar morph (snappy then settle).
- **Notification pulse**: 1.4s ease-in-out infinite on unread mention indicator.

View file

@ -0,0 +1,154 @@
# Design System Inspired by Duolingo
> Category: Productivity & SaaS
> Language-learning platform. Bright owl green, chunky shadows, gamified joy.
## 1. Visual Theme & Atmosphere
Duolingo is gamification rendered as visual language. The interface is unapologetically bright, with **owl green** (`#58cc02`) as the brand primary and a chunky 4px bottom-shadow on every interactive element that reads like a 3D button waiting to be pressed. The page is white (`#ffffff`) with thick 23px borders in a deep gray (`#e5e5e5`) and the entire system reads like an iOS app from 2015 reborn with better hierarchy.
Typography uses **Feather Bold** (a custom rounded sans) for chrome and **Mona Sans** (or Inter) for body. Display sizes are big and confident — Duolingo never whispers. Headings often carry the green underline-stroke or sit on a green pill, and the mascot Duo (a green owl) appears as an active illustration character, not a static logo.
Shape language is friendly: 1620px radii on cards, 12px on buttons, 9999px on chips and progress bars. Iconography is filled, rounded, and color-coded by skill — every lesson surface has an instantly identifiable color pairing.
**Key Characteristics:**
- Owl green (`#58cc02`) as the dominant brand color, used in 30%+ of the surface
- Chunky 4px bottom-shadow on every button (the "tactile press" affordance)
- 23px solid borders, never hairlines
- Feather Bold (rounded display) + Mona Sans (body)
- Big confident type — display sizes start at 48px and climb
- Mascot-as-character: Duo the owl appears in onboarding, errors, streaks
- Streak orange (`#ff9600`) and gem pink (`#ce82ff`) as secondary brand colors
## 2. Color Palette & Roles
### Primary
- **Owl Green** (`#58cc02`): Brand primary, primary CTA, correct answer.
- **Owl Green Deep** (`#58a700`): Pressed/shadow color for green buttons.
- **Owl Green Light** (`#89e219`): Hover, soft fills.
- **Owl Green Pale** (`#dbf8c5`): Soft surface, success banner.
### Secondary Accents
- **Streak Orange** (`#ff9600`): Streak counter, fire icon, premium energy.
- **Streak Orange Deep** (`#cc7a00`): Pressed orange.
- **Gem Pink** (`#ce82ff`): Gem currency, Super Duolingo.
- **Eel Blue** (`#1cb0f6`): Hint button, info link.
- **Cardinal Red** (`#ff4b4b`): Wrong answer, life lost.
- **Bee Yellow** (`#ffc800`): Pro badge, achievement.
### Surface
- **Snow** (`#ffffff`): Primary background.
- **Eel** (`#f7f7f7`): Section break, secondary surface.
- **Swan** (`#e5e5e5`): Disabled background, inset block.
- **Wolf** (`#777777`): Dark divider, secondary text.
### Ink & Text
- **Eel Black** (`#3c3c3c`): Primary text.
- **Wolf** (`#777777`): Secondary text, captions.
- **Hare** (`#afafaf`): Disabled, placeholder.
### Border
- **Swan** (`#e5e5e5`): Standard 2px border.
- **Hare** (`#afafaf`): Emphasized border on hover.
## 3. Typography Rules
### Font Family
- **Display / UI / Headings**: `Feather Bold`, with fallback: `'DIN Round Pro', 'Helvetica Neue', sans-serif`
- **Body / Long-form**: `Mona Sans`, with fallback: `'Helvetica Neue', system-ui, sans-serif`
- **Code (rare, schools/admin)**: `JetBrains Mono`, with fallback: `ui-monospace, Menlo, monospace`
### Hierarchy
| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |
|------|------|------|--------|-------------|----------------|-------|
| Display | Feather Bold | 56px (3.5rem) | 800 | 1.05 | -0.01em | Onboarding hero |
| H1 | Feather Bold | 32px (2rem) | 800 | 1.15 | -0.005em | Page title |
| H2 | Feather Bold | 24px (1.5rem) | 800 | 1.2 | normal | Section heading |
| H3 | Feather Bold | 18px (1.125rem) | 700 | 1.25 | normal | Card title, lesson row |
| Body Large | Mona Sans | 17px (1.0625rem) | 500 | 1.5 | normal | Lesson prompt, instruction |
| Body | Mona Sans | 15px (0.9375rem) | 400 | 1.5 | normal | Standard prose |
| Caption | Mona Sans | 13px (0.8125rem) | 600 | 1.4 | 0.01em | XP counter, metadata |
| Button | Feather Bold | 16px (1rem) | 800 | 1.2 | 0.02em | Standard button label |
| Streak | Feather Bold | 14px (0.875rem) | 800 | 1.2 | normal | Streak number, on flame |
### Principles
- **800 is default**: Feather Bold runs at 800 across headings and buttons. 700 feels weak in this system.
- **Big type**: heading sizes are 2540% larger than typical product brands — confidence as identity.
- **Rounded letterforms**: every glyph has soft terminals; sharp serifs would break the friendliness contract.
## 4. Component Stylings
### Buttons
**Primary (Owl Green)**
- Background: `#58cc02`
- Text: `#ffffff`
- Padding: 14px 24px
- Radius: 16px
- Border-bottom: 4px solid `#58a700` (the chunky shadow)
- Hover: background `#89e219`
- Active: translate-y 4px, border-bottom 0 (button "presses")
- Use: "Continue", "Check", main CTA.
**Secondary (White with Bottom-Shadow)**
- Background: `#ffffff`
- Text: `#777777`
- Border: 2px solid `#e5e5e5`
- Border-bottom: 4px solid `#e5e5e5`
- Radius: 16px
- Padding: 14px 24px
- Hover: text `#3c3c3c`, border `#afafaf`
**Streak Orange**
- Background: `#ff9600`
- Text: `#ffffff`
- Border-bottom: 4px solid `#cc7a00`
- Use: streak goal, "Start streak"
**Error (Cardinal Red)**
- Background: `#ff4b4b`
- Text: `#ffffff`
- Border-bottom: 4px solid `#cc3b3b`
- Use: wrong answer feedback.
### Cards / Lesson Tiles
- Background: `#ffffff`
- Border: 2px solid `#e5e5e5`
- Border-bottom: 4px solid `#e5e5e5`
- Radius: 16px
- Padding: 16px
- Hover: lift 2px, shadow `0 4px 0 #d7d7d7`
### Skill Tree Node (Lesson Bubble)
- Size: 80×72px
- Background: skill-color tinted (green for active, gray for locked)
- Border-bottom: 6px solid darker variant
- Radius: 50% (circular)
- Active: pulses 1.0 → 1.05 every 1.6s
### Inputs
- Background: `#ffffff`
- Border: 2px solid `#e5e5e5`
- Radius: 12px
- Padding: 12px 16px
- Focus: border `#1cb0f6` (eel blue), ring `0 0 0 3px rgba(28, 176, 246, 0.2)`
### Progress Bar
- Track: `#e5e5e5`
- Fill: `#58cc02` (or `#ff9600` for streak)
- Radius: 9999px
- Height: 16px
- Animated fill: 320ms ease-out on increment.
## 5. Spacing & Layout
- **Base unit**: 4px. Scale: 4, 8, 12, 16, 24, 32, 48, 64.
- **Container**: max 1080px, 24px gutter.
- **Lesson tree column**: 320px wide; centered on desktop.
## 6. Motion
- **Duration**: 180ms for button press; 320ms for skill-node unlock; 1.6s for active-node pulse.
- **Easing**: `cubic-bezier(0.34, 1.56, 0.64, 1)` (back-out, slight overshoot) for unlocks.
- **Mascot**: Duo blinks every 46s, jumps on streak milestones (480ms ease-out spring).

View file

@ -0,0 +1,155 @@
# Design System Inspired by GitHub
> Category: Developer Tools
> Code-forward platform. Functional density, blue-on-white precision, Primer foundations.
## 1. Visual Theme & Atmosphere
GitHub's surface is engineered, not decorated. Every pixel announces a stance: this is a tool for people who care about diffs, builds, and pull requests. The page background is a clean `#ffffff` (light) or `#0d1117` (dark), with content arranged on dense rectangular panes separated by hairline borders rather than negative space. Information density is the brand — list rows, code lines, repository headers, and notification cards are all packed close together so a power user can scan a hundred items without scrolling.
The signature accents are the **Primer blue** (`#0969da`) for links and primary actions, and **GitHub green** (`#1a7f37`) for merged states, success, and the merge button itself. Both feel slightly muted compared to consumer-product blues and greens — saturated enough to read against the dense gray text, restrained enough to disappear into the background when several appear in one viewport.
Typography uses the **system-ui** stack across the entire product so text renders crisply on every OS, paired with **SFMono / Menlo / Consolas** for code. There is no editorial display font; GitHub's voice is the voice of the system you're already on.
**Key Characteristics:**
- True white canvas (`#ffffff`) or deep navy-black (`#0d1117`) — no warmth, no tint
- Hairline gray borders (`#d0d7de`) define every pane and panel
- Primer blue (`#0969da`) for links/primary; GitHub green (`#1a7f37`) for success/merge
- system-ui for prose; SFMono for code — no custom typeface
- Dense list rows with minimal padding; whitespace is rare
- Octicon iconography at 16px / 24px — single-stroke, geometric, consistent
- Pill-shaped status badges with strong color semantics
## 2. Color Palette & Roles
### Primary
- **Canvas Default** (`#ffffff`): Primary page background, light theme.
- **Canvas Subtle** (`#f6f8fa`): Secondary surface, sidebar, input background, header strip.
- **Canvas Inset** (`#eaeef2`): Code block background, deep-inset surface.
- **Fg Default** (`#1f2328`): Primary text, headlines, ink.
- **Fg Muted** (`#656d76`): Secondary text, captions, file paths.
### Brand Accent
- **Primer Blue** (`#0969da`): Links, primary CTAs, focus ring base — the universal interactive color.
- **Primer Blue Hover** (`#0550ae`): Hover/pressed for primary blue.
- **Accent Subtle** (`#ddf4ff`): Soft blue surface for callouts, info banners.
### Semantic
- **Success / Merge Green** (`#1a7f37`): Merged PRs, success badges, merge button.
- **Success Subtle** (`#dafbe1`): Success surface tint.
- **Open Green** (`#1a7f37`): "Open" issue/PR state.
- **Closed / Danger Red** (`#cf222e`): Closed PRs, destructive action, validation error.
- **Danger Subtle** (`#ffebe9`): Error banner surface.
- **Attention / Warning Yellow** (`#9a6700`): Warning text on amber surface.
- **Attention Subtle** (`#fff8c5`): Warning banner surface.
- **Done Purple** (`#8250df`): Merged-and-archived, "done" state, premium badge.
- **Sponsor Pink** (`#bf3989`): Sponsors heart, GitHub sponsors brand.
### Border & Divider
- **Border Default** (`#d0d7de`): Standard hairline border, panel outline.
- **Border Muted** (`#d8dee4`): Inner dividers within a panel.
- **Border Subtle** (`#eaeef2`): Faint table row dividers.
### Dark Theme
- **Dark Canvas** (`#0d1117`): Dark page background.
- **Dark Surface** (`#161b22`): Sidebar, header, secondary surface.
- **Dark Border** (`#30363d`): Standard dark-mode border.
- **Dark Fg** (`#e6edf3`): Primary text on dark.
## 3. Typography Rules
### Font Family
- **Body / UI**: `-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif`
- **Code / Mono**: `ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace`
- **Emoji**: `"Apple Color Emoji", "Segoe UI Emoji"`
### Hierarchy
| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |
|------|------|------|--------|-------------|----------------|-------|
| Display | system-ui | 32px (2rem) | 600 | 1.25 | -0.01em | Repo header, marketing hero |
| H1 | system-ui | 24px (1.5rem) | 600 | 1.25 | normal | Page heading |
| H2 | system-ui | 20px (1.25rem) | 600 | 1.25 | normal | Section heading |
| H3 | system-ui | 16px (1rem) | 600 | 1.25 | normal | Sub-section, panel header |
| Body | system-ui | 14px (0.875rem) | 400 | 1.5 | normal | Default text size — not 16px |
| Body Small | system-ui | 12px (0.75rem) | 400 | 1.4 | normal | Captions, file metadata |
| Code | SFMono | 12px (0.75rem) | 400 | 1.45 | normal | Code blocks, diff |
| Code Inline | SFMono | 0.85em | 400 | inherit | normal | Inline `code` spans |
### Principles
- **14px body, not 16px**: GitHub's prose density is its identity. The product reads at 14px to fit more rows in a viewport.
- **Weight binary**: 400 for everything by default; 600 for headlines and emphasis. No 500, no 700.
- **System fonts always**: never load a webfont for chrome — text must render instantly on slow connections.
## 4. Component Stylings
### Buttons
**Primary (Green)**
- Background: `#1f883d`
- Text: `#ffffff`
- Border: 1px solid `rgba(31, 35, 40, 0.15)`
- Padding: 5px 16px
- Radius: 6px
- Shadow: `0 1px 0 rgba(31,35,40,0.1)`
- Hover: background `#1a7f37`
- Use: "Create repository", "Merge pull request"
**Default**
- Background: `#f6f8fa`
- Text: `#1f2328`
- Border: 1px solid `#d0d7de`
- Padding: 5px 16px
- Radius: 6px
- Hover: background `#f3f4f6`, border `#d0d7de`
**Outline (Blue Link Style)**
- Background: `#ffffff`
- Text: `#0969da`
- Border: 1px solid `#d0d7de`
- Hover: background `#0969da`, text `#ffffff`
**Danger**
- Background: `#ffffff`
- Text: `#cf222e`
- Border: 1px solid `#d0d7de`
- Hover: background `#a40e26`, text `#ffffff`, border `#a40e26`
### Cards / Boxes
- Background: `#ffffff`
- Border: 1px solid `#d0d7de`
- Radius: 6px
- Padding: 16px (header) + 16px (body)
- Header has a `#f6f8fa` strip with bottom border.
### Inputs
- Background: `#ffffff`
- Border: 1px solid `#d0d7de`
- Radius: 6px
- Padding: 5px 12px
- Focus: border `#0969da`, ring `0 0 0 3px rgba(9,105,218,0.3)`
### Status Pills (Issue / PR)
- **Open**: background `#1a7f37`, text white, padding 4px 10px, radius 9999px.
- **Closed**: background `#cf222e`, text white.
- **Merged**: background `#8250df`, text white.
- **Draft**: background `#6e7781`, text white.
### Labels (Tags on Issues/PRs)
- Padding: 0 7px
- Radius: 9999px
- Font: 12px / 500
- Background and text are programmatic (label color → text computed for contrast).
## 5. Spacing & Layout
- **Base unit**: 4px. Spacing scale: 4, 8, 12, 16, 24, 32, 40, 48.
- **Page max-width**: 1280px (`Container-xl`).
- **Sidebar**: 296px on desktop, collapses below 1012px.
- **Row padding**: 16px horizontal, 12px vertical (lists are dense by design).
## 6. Motion
- **Duration**: 80ms for hover; 200ms for menu/popover open.
- **Easing**: `ease-out` for opens, `ease-in` for closes.
- **Avoided**: page-load animation, parallax, persistent micro-interactions. Things appear; they do not perform.

View file

@ -0,0 +1,149 @@
# Design System Inspired by Hugging Face
> Category: AI & LLM
> ML community hub. Sunny yellow accent, monospace identity, cheerful and dense.
## 1. Visual Theme & Atmosphere
Hugging Face is the rare ML brand that refuses to look serious. The hub leans into a sunshine-yellow accent (`#ffd21e`), a cartoon hugging-face emoji as the logo, and a confident **IBM Plex Mono** voice that reads more like a community zine than a research lab. The page background is a clean off-white (`#fafafa`) with text in a deep slate (`#0d1117`), and the yellow appears in pull quotes, tags, "new" badges, and the model-card header strip — never as an entire surface, always as punctuation.
The typographic system is monospace-forward in a way few product brands attempt: **IBM Plex Mono** for headings and tags, **Source Sans Pro** (or Inter) for body. The mix gives every page a "config file is the README" vibe — fitting for a platform built around `.gitattributes` and `model-card.md`.
Shapes are crisp, not soft: 46px radii, 1px solid borders that announce themselves rather than hide. Tables are dense, with row hover in a faint gray (`#f3f4f6`). The brand emoji punctuates everything — chips, headings, even error states wear a 🤗 — so the system feels human even when displaying technical data.
**Key Characteristics:**
- Sunshine yellow `#ffd21e` as the lone saturated accent
- IBM Plex Mono for headings and tags; Source Sans Pro for body
- Off-white canvas (`#fafafa`) with crisp 1px borders (`#e5e7eb`)
- 46px radii — closer to brutalist than rounded
- Hugging-face emoji 🤗 used unironically as a system glyph
- Dense tables, minimal padding — a community hub for power users
- Color-coded model categories (NLP blue, vision green, audio purple)
## 2. Color Palette & Roles
### Primary
- **HF Yellow** (`#ffd21e`): Brand primary, badges, "new" pill, model-card header bar.
- **HF Yellow Deep** (`#f59e0b`): Hover/active for yellow.
- **HF Yellow Soft** (`#fff4cc`): Surface tint, callout background.
### Surface & Background
- **Canvas** (`#ffffff`): Primary page background.
- **Canvas Subtle** (`#fafafa`): Alternate section background, footer.
- **Canvas Inset** (`#f3f4f6`): Code block background, hover row.
- **Canvas Dark** (`#0d1117`): Dark theme background.
### Ink & Text
- **Ink Primary** (`#0d1117`): Primary text, headings.
- **Ink Secondary** (`#374151`): Body prose.
- **Ink Muted** (`#6b7280`): Captions, file paths, model authors.
- **Ink Inverse** (`#f9fafb`): Text on dark surface.
### Category Accents (Model Tasks)
- **NLP Blue** (`#2563eb`): Text/NLP task badges.
- **Vision Green** (`#16a34a`): Computer-vision task badges.
- **Audio Purple** (`#9333ea`): Audio/speech task badges.
- **Multimodal Pink** (`#db2777`): Multimodal/diffusion task badges.
- **Tabular Orange** (`#ea580c`): Tabular/structured task badges.
### Semantic
- **Success** (`#16a34a`): Build succeeded, deploy live.
- **Warning** (`#f59e0b`): Slow inference, rate limit.
- **Error** (`#dc2626`): Failed build, broken model.
- **Info** (`#2563eb`): Notice banner.
### Border
- **Border Default** (`#e5e7eb`): Standard 1px hairline.
- **Border Strong** (`#d1d5db`): Emphasized border on hover.
- **Border Subtle** (`#f3f4f6`): Inner divider.
## 3. Typography Rules
### Font Family
- **Display / UI / Headings / Tags**: `IBM Plex Mono`, with fallback: `ui-monospace, SFMono-Regular, Menlo, Consolas, monospace`
- **Body / Prose**: `Source Sans Pro`, with fallback: `Inter, system-ui, -apple-system, sans-serif`
- **Editorial (rare, blog only)**: `Source Serif Pro`, with fallback: `Georgia, serif`
### Hierarchy
| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |
|------|------|------|--------|-------------|----------------|-------|
| Display | IBM Plex Mono | 48px (3rem) | 600 | 1.1 | -0.01em | Marketing hero |
| H1 | IBM Plex Mono | 32px (2rem) | 600 | 1.2 | normal | Page heading, model name |
| H2 | IBM Plex Mono | 24px (1.5rem) | 600 | 1.25 | normal | Section heading |
| H3 | IBM Plex Mono | 18px (1.125rem) | 600 | 1.3 | normal | Sub-section |
| Body Large | Source Sans Pro | 18px (1.125rem) | 400 | 1.6 | normal | Lede, blog intro |
| Body | Source Sans Pro | 15px (0.9375rem) | 400 | 1.55 | normal | Standard prose, model card |
| Caption | Source Sans Pro | 13px (0.8125rem) | 500 | 1.4 | 0.01em | Author byline, timestamp |
| Tag / Badge | IBM Plex Mono | 12px (0.75rem) | 500 | 1.2 | 0.02em | Task tags, framework chips |
| Code | IBM Plex Mono | 14px (0.875rem) | 400 | 1.55 | normal | Code blocks, inline `model_id` |
### Principles
- **Mono everywhere it matters**: nav links, headings, tags, and metadata are all monospaced. Sans is reserved for paragraphs of prose.
- **Weight under 600**: 600 is the cap; 700 is too loud against yellow. Hierarchy is size and color.
- **Tags read as code**: model tags use mono so they look like the strings developers will paste into Python.
## 4. Component Stylings
### Buttons
**Primary**
- Background: `#0d1117`
- Text: `#ffffff`
- Padding: 8px 16px
- Radius: 6px
- Hover: `#374151`
- Use: "Use this model", primary forms.
**Yellow CTA**
- Background: `#ffd21e`
- Text: `#0d1117`
- Padding: 8px 16px
- Radius: 6px
- Hover: `#f59e0b`
- Use: "Pro upgrade", "Sponsor".
**Outline**
- Background: `#ffffff`
- Text: `#0d1117`
- Border: 1px solid `#e5e7eb`
- Hover: background `#f3f4f6`
### Cards / Model Cards
- Background: `#ffffff`
- Border: 1px solid `#e5e7eb`
- Radius: 6px
- Padding: 16px 20px
- Header strip: `#ffd21e` background, 4px tall, only on featured model cards.
### Inputs
- Background: `#ffffff`
- Border: 1px solid `#e5e7eb`
- Radius: 6px
- Padding: 8px 12px
- Focus: border `#0d1117`, ring `0 0 0 3px rgba(13,17,23,0.1)`
### Tags / Chips (Task / Framework)
- Background: category-tinted soft (`#dbeafe` for NLP, `#dcfce7` for vision, etc.)
- Text: matching strong category color.
- Padding: 2px 8px
- Radius: 4px
- Font: IBM Plex Mono 12px / 500
### Tables
- Header: background `#fafafa`, border-bottom 1px `#e5e7eb`.
- Row: border-bottom 1px `#f3f4f6`, hover `#f3f4f6`.
- Padding: 8px 16px per cell — dense by design.
## 5. Spacing & Layout
- **Base unit**: 4px. Scale: 4, 8, 12, 16, 24, 32, 48, 64.
- **Container**: max 1280px, 24px gutter.
- **Sidebar (model browser)**: 280px wide.
- **Section rhythm**: 6496px vertical between major sections.
## 6. Motion
- **Duration**: 120ms for hover; 200ms for menu open.
- **Easing**: `ease-out`.
- **Tag pop**: a 1.05× scale on hover at 120ms — the only exception to flat-on-hover.

View file

@ -0,0 +1,140 @@
# Design System Inspired by OpenAI
> Category: AI & LLM
> Calm, near-monochrome system anchored in deep teal-black with generous white space and editorial typography.
## 1. Visual Theme & Atmosphere
OpenAI's product surface reads like a research lab dressed for the public — clinical, restrained, deliberately quiet. The page background is a true white (`#ffffff`) layered against a near-black ink (`#0d0d0d`) with a subtle teal undertone, so even the text feels slightly cooled rather than aggressively dark. The result is a chromatic neutrality that puts model output, code, and prose front and center, not the chrome around them.
The signature move is the use of **Söhne** (or its system stand-in `inter`) at restrained weights — 400 for body, 500 for nav and labels, 600 for emphasis — paired with **Signifier**, a contemporary serif used for editorial display. Where most AI brands lean futuristic, OpenAI's serif headlines give the product a quietly literary tone, as if every announcement is an essay.
The shape system is uniformly soft: 8px12px radii, 9999px pills for tags and chips, no harsh corners anywhere. Section transitions are denoted by whitespace rather than dividers; when borders appear they are `#e5e5e5` hairlines that read as the absence of color rather than its presence.
**Key Characteristics:**
- True white canvas (`#ffffff`) with deep teal-black ink (`#0d0d0d`)
- Söhne / Inter at modest weights (400, 500, 600) — restraint over assertion
- Signifier serif for editorial display headlines
- Soft 812px radii everywhere; 9999px pills for chips
- Hairline borders (`#e5e5e5`) used sparingly; whitespace as primary divider
- Single-color illustrations in deep teal — no gradients in marks
- Generous line-height (1.551.65) and tracking near zero
## 2. Color Palette & Roles
### Primary
- **Pure White** (`#ffffff`): Primary background, card surface, button background.
- **Ink Black** (`#0d0d0d`): Primary text, brand mark, primary CTA.
- **Soft Black** (`#1a1a1a`): Secondary heading, alternative ink for non-critical text.
### Surface & Background
- **Mist** (`#fafafa`): Section break background, footer surface.
- **Pearl** (`#f5f5f5`): Card surface, elevated panel.
- **Cloud** (`#ececec`): Disabled background, divider tint.
### Brand Accent
- **OpenAI Teal** (`#10a37f`): Brand primary, link, highlight badge — the lone color in an otherwise neutral system.
- **Teal Deep** (`#0a7a5e`): Hover and pressed state for the brand color.
- **Teal Soft** (`#e8f5f0`): Surface tint for success badges, highlight callouts.
### Neutrals & Text
- **Graphite** (`#3c3c3c`): Body text, default reading color.
- **Slate** (`#6e6e6e`): Secondary text, captions, metadata.
- **Ash** (`#9b9b9b`): Tertiary text, placeholder, disabled label.
- **Stone** (`#c4c4c4`): Decorative dividers, faint icons.
### Semantic & Border
- **Border Hairline** (`#e5e5e5`): Standard hairline separator.
- **Border Soft** (`#ededed`): Card outline on white surface.
- **Error** (`#ef4146`): Validation, destructive action.
- **Warning** (`#f5a623`): Soft amber for advisory states.
- **Info** (`#2563eb`): Informational link tone (used sparingly; teal still wins).
## 3. Typography Rules
### Font Family
- **Display / Editorial**: `Signifier`, with fallback: `'Source Serif Pro', Georgia, serif`
- **Body / UI**: `Söhne`, with fallback: `Inter, system-ui, -apple-system, 'Segoe UI', sans-serif`
- **Code / Mono**: `Söhne Mono`, with fallback: `ui-monospace, 'JetBrains Mono', Menlo, Consolas, monospace`
### Hierarchy
| Role | Font | Size | Weight | Line Height | Letter Spacing | Notes |
|------|------|------|--------|-------------|----------------|-------|
| Display | Signifier | 56px (3.5rem) | 400 | 1.08 | -0.02em | Editorial hero, announcement titles |
| H1 | Söhne | 40px (2.5rem) | 600 | 1.15 | -0.01em | Page heading |
| H2 | Söhne | 28px (1.75rem) | 600 | 1.2 | -0.005em | Section heading |
| H3 | Söhne | 20px (1.25rem) | 600 | 1.3 | normal | Sub-section |
| Body Large | Söhne | 18px (1.125rem) | 400 | 1.6 | normal | Lede paragraphs |
| Body | Söhne | 16px (1rem) | 400 | 1.65 | normal | Standard reading text |
| Body Small | Söhne | 14px (0.875rem) | 400 | 1.55 | normal | Card body, dense UI |
| Caption | Söhne | 13px (0.8125rem) | 500 | 1.4 | 0.01em | Metadata, badges |
| Label | Söhne | 12px (0.75rem) | 500 | 1.3 | 0.04em | Eyebrow, uppercase nav links |
| Code | Söhne Mono | 14px (0.875rem) | 400 | 1.55 | normal | Code blocks, terminal output |
### Principles
- **Restraint as identity**: weights cap at 600; 700+ feels off-brand. Hierarchy comes from size and color, not weight.
- **Serif for soul, sans for system**: Signifier appears only in editorial display moments. The product UI is sans-only.
- **Negative tracking on display**: -0.02em on display sizes; tracking returns to zero by 16px.
## 4. Component Stylings
### Buttons
**Primary**
- Background: `#0d0d0d`
- Text: `#ffffff`
- Padding: 10px 18px
- Radius: 9999px (full pill) on chips, 12px on rectangular CTAs
- Hover: `#1a1a1a` background
- Use: Primary CTA, "Try ChatGPT", "Sign in"
**Secondary**
- Background: `#ffffff`
- Text: `#0d0d0d`
- Border: 1px solid `#e5e5e5`
- Padding: 10px 18px
- Radius: 12px
- Hover: background `#fafafa`, border `#d4d4d4`
**Brand Accent**
- Background: `#10a37f`
- Text: `#ffffff`
- Padding: 10px 18px
- Radius: 12px
- Hover: `#0a7a5e`
- Use: Highlighted upgrade CTA, success path
### Cards
- Background: `#ffffff`
- Border: 1px solid `#ededed`
- Radius: 16px
- Padding: 24px32px
- Shadow: none by default; on hover `0 4px 16px rgba(13,13,13,0.06)`
### Inputs
- Background: `#ffffff`
- Border: 1px solid `#e5e5e5`
- Radius: 12px
- Padding: 12px 14px
- Focus: border `#10a37f`, ring `0 0 0 3px rgba(16,163,127,0.12)`
### Pills & Tags
- Background: `#f5f5f5`
- Text: `#3c3c3c`
- Padding: 4px 10px
- Radius: 9999px
- Font: 12px / 500
## 5. Spacing & Layout
- **Base unit**: 4px. Scale: 4, 8, 12, 16, 24, 32, 48, 64, 96, 128.
- **Container**: max-width 1200px, 24px gutter on mobile, 48px on desktop.
- **Section rhythm**: 96128px vertical between major sections; 64px on mobile.
- **Grid**: 12-column desktop, 4-column mobile, 24px gap.
## 6. Motion
- **Duration**: 150220ms for hover; 280360ms for layout transitions.
- **Easing**: `cubic-bezier(0.16, 1, 0.3, 1)` (smooth out) for entrances.
- **Restraint**: no parallax, no scroll-jacking. Subtle fade and translate only.