diff --git a/web/.design-sync/.gitignore b/web/.design-sync/.gitignore new file mode 100644 index 00000000..6a4c5642 --- /dev/null +++ b/web/.design-sync/.gitignore @@ -0,0 +1,3 @@ +.cache/ +learnings/ +node_modules diff --git a/web/.design-sync/NOTES.md b/web/.design-sync/NOTES.md new file mode 100644 index 00000000..7e9ff5dc --- /dev/null +++ b/web/.design-sync/NOTES.md @@ -0,0 +1,72 @@ +# design-sync notes — Web Sequence (Drafting Table) + +Repo-specific gotchas for syncing `web/src/ui` (the "Drafting Table" design system) +to the "Web Sequence Design System" claude.ai/design project. + +## Shape & build +- **Package shape, synth-entry.** `web/` is a Vite *app*, not a published library — no + `main`/`module`/`exports`, no library `dist/` with `.d.ts`. The converter synthesizes the + entry from `srcDir: src/ui` (the barrel `src/ui/index.ts`). Run `package-build.mjs` WITHOUT + `--entry`. `--node-modules ./node_modules` (web/'s own; react/radix resolve there). +- React 19 + Radix UI primitives + Tailwind 3. `@types/react` must be installed in `.ds-sync`. + +## CSS (Tailwind) — must be recompiled on re-sync +- Component classes are Tailwind utilities; `tailwind.config.js` is the source of truth. The + converter scrapes a *static* stylesheet, so we precompile one into `cssEntry`: + ```sh + cd web + npx tailwindcss -i src/styles/globals.css -o .design-sync/.cache/ds-tailwind.css + # then prepend the Google-Fonts @import (brand fonts load at runtime, like index.html): + ``` + The `.design-sync/build-css.sh` helper does both. Re-run it before every build so new + utility classes used by authored previews are included (extend its content glob to + `.design-sync/previews` once previews exist). +- **Fonts: Google Fonts at runtime** (Hanken Grotesk / IBM Plex Mono / Instrument Serif), via + a `` in `index.html`. Not shipped as woff2. We inject the same `@import url(fonts.googleapis…)` + at the top of the compiled CSS → `[FONT_REMOTE]` (loads at runtime, no woff2 to ship). + `runtimeFontPrefixes` is set as a backstop so `[FONT_MISSING]` stays quiet. + +## Preview authoring — calibration learnings (solo: Button/IconButton/Dialog) +- **Dark-surface DS → wrap preview content in a dark ink panel** (`background:#10141B`, padding, + radius). The grading capture sheet uses a WHITE bg, so `surface="dark"` controls (muted + neutrals: ondark-muted icons, ghost buttons) render as faint gray and grade poorly unless + they sit on the ink panel they're designed for. `surface="light"` variants get a warm paper + panel (`#FAF7F1`). Every dark-surface preview should follow this. +- Previews import from `'web-sequence-web'` (the converter aliases it to `window.DraftingTable`). + esbuild auto-JSX runtime — no `import React` needed; `React.FC`/`React.CSSProperties` type + annotations are fine (erased), the IDE's "Cannot find React" warnings are noise. +- Overlay components (Dialog, and likely Popover/Tooltip/Menu/Select open states) need + `cfg.overrides.: {"cardMode":"single","viewport":"WxH"}` and render open via Radix + `defaultOpen`/`open` so the floated content shows inside the card. +- Use realistic copy from the app (Save changes / Delete diagram / Run diagram), never foo/bar. + +## Per-component authoring notes (folded from the wave fan-out) +- **TextInput / Textarea**: thin native wrappers; `surface` prop (default dark) + native attrs. + Uncontrolled `defaultValue` works in previews (onChange is the raw DOM event). +- **SearchInput**: NOT a thin wrapper — `onChange(value: string)` is REQUIRED and value-style + (string, not event); `value` is controlled. A static `value="…"` + a no-op `onChange` renders + the populated state (with the clear ×). `style`/rest spread onto the inner ``. +- **Switch**: on/off via `defaultChecked` (checked = cobalt fill); compose as labelled rows + (mirrors SettingsModal's SwitchRow) on the ink panel. +- **Select / Menu / Popover** (overlays): render OPEN via Radix `defaultOpen` on the Root; the + orchestrator-set `cfg.overrides.{…}` (cardMode single + viewport) lands the portaled content + in the card. `SelectContent`/`MenuContent` are dark-ink; `PopoverContent` is light-paper (it + brings its own surface even off a dark trigger). `MenuItem tone="danger"` = red Delete. +- **Tooltip** (the context-identity trap): the DS `Tooltip` wrapper self-provides a Radix Root + with NO open-control prop, and the barrel exports only `Tooltip`+`TooltipProvider` (no raw + Root/Trigger/Content). To show it open, the preview imports raw `@radix-ui/react-tooltip` AND + supplies its OWN `` — the bundle's `cfg.provider` TooltipProvider is a + DIFFERENT module copy with a distinct React context, so a raw Root can't see it (renders blank + + throws "must be used within TooltipProvider"). **General rule**: any overlay whose package + wrapper self-provides but exposes no open prop → preview supplies its own matching Radix Provider. + +## Known render warns (re-sync should treat these as expected, not new) +- `[RENDER_THIN]` on **BrandLogo** — false positive: the logo is a pure SVG with no text nodes. + Grade `good` whenever the mark renders. + +## Re-sync risks +- `.design-sync/.cache/ds-tailwind.css` is gitignored and regenerated — re-sync MUST run + `build-css.sh` first or the bundle ships unstyled. +- Compound exports (DialogTrigger, SelectItem, MenuItem…) are PascalCase and may be discovered + as separate components — they're real API parts, not standalone cards; previews compose them + inside their parent (Dialog, Select, Menu). diff --git a/web/.design-sync/build-css.sh b/web/.design-sync/build-css.sh new file mode 100755 index 00000000..f0c7bd3b --- /dev/null +++ b/web/.design-sync/build-css.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# Compile the Drafting Table Tailwind CSS into the design-sync bundle's cssEntry. +# Content covers src/** (the DS + app usage) AND authored previews. +set -e +cd "$(dirname "$0")/.." # -> web/ +OUT=.design-sync/.cache/ds-tailwind.css +mkdir -p .design-sync/.cache +npx tailwindcss -i src/styles/globals.css -o "$OUT" \ + --content './index.html,./src/**/*.{ts,tsx},./.design-sync/previews/**/*.{ts,tsx}' +FONT="@import url('https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&family=Instrument+Serif:ital@0;1&display=swap');" +{ echo "$FONT"; cat "$OUT"; } > "$OUT.tmp" && mv "$OUT.tmp" "$OUT" +echo "wrote $OUT ($(wc -c <"$OUT") bytes)" diff --git a/web/.design-sync/config.json b/web/.design-sync/config.json new file mode 100644 index 00000000..1153c0cd --- /dev/null +++ b/web/.design-sync/config.json @@ -0,0 +1,34 @@ +{ + "projectId": "ffa534fb-665d-47a6-af0d-7dbbc27d5e88", + "shape": "package", + "pkg": "web-sequence-web", + "globalName": "DraftingTable", + "srcDir": "src/ui", + "tsconfig": "tsconfig.json", + "cssEntry": ".design-sync/.cache/ds-tailwind.css", + "readmeHeader": ".design-sync/conventions.md", + "runtimeFontPrefixes": ["Hanken Grotesk", "IBM Plex Mono", "Instrument Serif"], + "componentSrcMap": { + "Button": "src/ui/Button.tsx", + "IconButton": "src/ui/IconButton.tsx", + "BrandLogo": "src/ui/BrandLogo.tsx", + "Dialog": "src/ui/Dialog.tsx", + "TextInput": "src/ui/TextInput.tsx", + "Textarea": "src/ui/Textarea.tsx", + "Switch": "src/ui/Switch.tsx", + "Select": "src/ui/Select.tsx", + "SearchInput": "src/ui/SearchInput.tsx", + "Popover": "src/ui/Popover.tsx", + "Tooltip": "src/ui/Tooltip.tsx", + "Menu": "src/ui/Menu.tsx" + }, + "provider": { "component": "TooltipProvider" }, + "overrides": { + "Dialog": { "cardMode": "single", "viewport": "480x360" }, + "Select": { "cardMode": "single", "viewport": "360x320" }, + "Popover": { "cardMode": "single", "viewport": "400x300" }, + "Menu": { "cardMode": "single", "viewport": "360x340" }, + "Tooltip": { "cardMode": "single", "viewport": "360x200" }, + "Textarea": { "cardMode": "column" } + } +} diff --git a/web/.design-sync/conventions.md b/web/.design-sync/conventions.md new file mode 100644 index 00000000..d5fcb7b3 --- /dev/null +++ b/web/.design-sync/conventions.md @@ -0,0 +1,57 @@ +# Drafting Table — how to build with this design system + +The ZenUML Web Sequence UI kit. Compose from the real components on +`window.DraftingTable.*` (Button, IconButton, Dialog, TextInput, Textarea, Switch, +Select, SearchInput, Popover, Tooltip, Menu, BrandLogo) and style your own layout +glue with the Tailwind utilities below. Two fixed surfaces, one cobalt accent — never +a generic gray/blue palette. + +## Setup +- Load `styles.css` — it `@import`s the tokens, the component CSS (`_ds_bundle.css`), + and the brand fonts (Hanken Grotesk / IBM Plex Mono / Instrument Serif from Google + Fonts). Without it everything renders unstyled in a fallback font. +- **No global wrapper is needed** for most components — they style themselves. The one + exception: anything using `Tooltip` must be inside `` (export it from + `window.DraftingTable.TooltipProvider`, wrap once near the root). + +## The two surfaces (the core idea) +Interactive components are **surface-aware** via a `surface` prop, not a runtime theme: +- `surface="dark"` (the **default**) — the **ink** chrome: charcoal-with-blue-undertone + editor/header/toolbar/menus. Most of the app lives here. +- `surface="light"` — the warm **paper** surface (the diagram canvas / some popovers). +Pick the surface to match the panel you place the component on. `Button`, `IconButton`, +`TextInput`, `Textarea`, `SearchInput` all take `surface`; `SelectContent`/`MenuContent` +are dark ink, `PopoverContent` is light paper by design. + +## Styling idiom — semantic Tailwind utilities +Utility classes from a custom palette (NOT Tailwind's default gray/blue). Use real names: + +| role | classes | +|---|---| +| Dark surfaces (ink) | `bg-ink-950` (backdrop) · `bg-ink-900` (rail) · `bg-ink-800` (panel) · `bg-ink-700` (raised) · `border-ink-line` | +| Light surfaces (paper) | `bg-paper-50` · `bg-paper-100` · `bg-paper-200` · `border-paper-line` | +| Accent — the one cobalt signal | `bg-accent` · `bg-accent-press` (pressed) · `text-accent` (fills/rings); for accent TEXT on dark use `text-accent-onDark` (AA-safe) | +| Text on dark | `text-ondark-strong` · `text-ondark-muted` · `text-ondark-faint` | +| Text on light | `text-onlight-strong` · `text-onlight-muted` · `text-onlight-faint` | +| Intent | `text-danger` (use `text-danger-strong` for danger TEXT on light) · `text-ok` · `text-signal-amber` (sparingly) | +| Type | `font-sans` (Hanken Grotesk, default) · `font-mono` (IBM Plex Mono, code/DSL) · `font-serif` (Instrument Serif, display titles like the Dialog heading) | +| Shape | `rounded` (7px) · `rounded-lg` (11px) · `shadow-pop` / `shadow-pop-dark` for lifted surfaces | + +Rules of thumb: dark panels pair `bg-ink-*` with `text-ondark-*`; light panels pair +`bg-paper-*` with `text-onlight-*`. Reach for `bg-accent` once per view, not everywhere. + +## Where the real truth lives +- `styles.css` and its `@import` closure (`_ds_bundle.css`, tokens) — the compiled palette. +- Each component's `components///.d.ts` (its prop API) and + `.prompt.md` (usage). Read those before composing a component you're unsure of. + +## One idiomatic snippet +```tsx +// A dark toolbar with the primary action + an icon control, on the ink surface. +
+ + + +
+``` +`variant` on Button: `primary` (cobalt) · `subtle` · `ghost` · `danger`. Sizes `sm` / `md`. diff --git a/web/.design-sync/previews/BrandLogo.tsx b/web/.design-sync/previews/BrandLogo.tsx new file mode 100644 index 00000000..643166a4 --- /dev/null +++ b/web/.design-sync/previews/BrandLogo.tsx @@ -0,0 +1,30 @@ +import { BrandLogo } from 'web-sequence-web'; + +// The official ZenUML mark — a self-contained #2E94D4 rounded-square SVG that +// carries its own background, so it reads on any surface. Sized via a wrapping +// div width (the SVG fills 100% of its box at a 300×300 viewBox). +// NOTE: BrandLogo trips a [RENDER_THIN] warn — it's an SVG with no text nodes, +// which is a FALSE POSITIVE for a logo. Grade `good` whenever the mark renders. + +// The logo at the three sizes the app uses it: brand wordmark (48), header / +// menu avatar (30), and a compact favicon-scale chip (20) — on neutral paper. +export function Sizes() { + return ( +
+
+
+
+
+ ); +} + +// In context: the mark sitting in the dark app header next to the product name, +// exactly as AppMenu / HomeView place it (className-sized to 30×30). +export function InHeader() { + return ( +
+ + ZenUML +
+ ); +} diff --git a/web/.design-sync/previews/Button.tsx b/web/.design-sync/previews/Button.tsx new file mode 100644 index 00000000..c08c713e --- /dev/null +++ b/web/.design-sync/previews/Button.tsx @@ -0,0 +1,51 @@ +import { Button } from 'web-sequence-web'; + +// Drafting Table is a dark-surface DS: `dark` (default) controls live on the ink +// chrome. Preview them on that ink panel so the quiet variants (ghost/subtle) read +// with proper contrast; the `light` variant gets the warm paper panel. +const Ink: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
+ {children} +
+); + +// Intent-based variants on the dark `ink` chrome (header/toolbar surface). +export function Variants() { + return ( + + + + + + + ); +} + +// Two sizes for toolbars (sm) vs. dialogs/forms (md). +export function Sizes() { + return ( + + + + + + + ); +} + +// Disabled state (dark) + the light-paper surface variant (used inside modals/menus). +export function StatesAndSurface() { + return ( +
+ + + + +
+ + + +
+
+ ); +} diff --git a/web/.design-sync/previews/Dialog.tsx b/web/.design-sync/previews/Dialog.tsx new file mode 100644 index 00000000..10005ead --- /dev/null +++ b/web/.design-sync/previews/Dialog.tsx @@ -0,0 +1,20 @@ +import { Dialog, DialogContent, Button } from 'web-sequence-web'; + +// Modal shell over Radix Dialog on the dark ink surface. Rendered open (`defaultOpen`) +// so the card shows the lifted dialog; compose DialogContent with a title, optional +// description, and your own footer actions. (Ported from the app's ConfirmDialog.) +export function ConfirmDestructive() { + return ( + + +
+ + +
+
+
+ ); +} diff --git a/web/.design-sync/previews/IconButton.tsx b/web/.design-sync/previews/IconButton.tsx new file mode 100644 index 00000000..3a20859e --- /dev/null +++ b/web/.design-sync/previews/IconButton.tsx @@ -0,0 +1,56 @@ +import { IconButton } from 'web-sequence-web'; + +// Drafting Table is a dark-surface DS: its `dark` controls are muted neutrals meant +// to sit on the ink chrome. Preview them on that ink panel (else the icons read as +// faint gray on a white card). The `light` variant gets the warm paper panel. +const Ink: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
+ {children} +
+); +const Paper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
+ {children} +
+); + +const Close = () => ( + +); +const Plus = () => ( + +); + +// Icon-only controls for toolbars and tab affordances. aria-label is required. +export function Toolbar() { + return ( + + + + + ); +} + +export function Sizes() { + return ( + + + + + ); +} + +// The light-paper surface variant + a disabled control. +export function SurfaceAndDisabled() { + return ( + + + + + + ); +} diff --git a/web/.design-sync/previews/Menu.tsx b/web/.design-sync/previews/Menu.tsx new file mode 100644 index 00000000..0fe27ad6 --- /dev/null +++ b/web/.design-sync/previews/Menu.tsx @@ -0,0 +1,54 @@ +import { + Menu, + MenuTrigger, + MenuContent, + MenuItem, + MenuLabel, + MenuSeparator, +} from 'web-sequence-web'; + +// Design-system dropdown Menu over Radix DropdownMenu. Menus are DARK (ink) — they +// drop from the dark chrome (header, diagram-card kebab) and bring their own surface. +// Rendered OPEN (`defaultOpen`) so the card shows the floated menu items, not just +// the trigger. Content + items copied from the DiagramCard kebab menu (Duplicate / +// Export / a separator / a destructive Delete), the canonical realistic item list. +const inkPanel: React.CSSProperties = { + background: '#10141B', + padding: 16, + borderRadius: 8, +}; + +const trigger: React.CSSProperties = { + display: 'inline-flex', + alignItems: 'center', + gap: 6, + height: 32, + padding: '0 10px', + borderRadius: 6, + fontSize: 13, + color: '#E7ECF3', + background: 'rgba(255,255,255,0.06)', + border: '1px solid rgba(255,255,255,0.08)', +}; + +export function DiagramActions() { + return ( +
+ + + + + + Diagram + Rename + Duplicate + Export as HTML + + Delete + + +
+ ); +} diff --git a/web/.design-sync/previews/Popover.tsx b/web/.design-sync/previews/Popover.tsx new file mode 100644 index 00000000..fb09e54e --- /dev/null +++ b/web/.design-sync/previews/Popover.tsx @@ -0,0 +1,77 @@ +import { + Popover, + PopoverTrigger, + PopoverContent, + Button, +} from 'web-sequence-web'; + +// Design-system Popover over Radix Popover. The trigger sits on the dark ink chrome; +// PopoverContent floats as a LIGHT paper panel (bg-paper-50) — it brings its own +// surface, so we still wrap the whole thing in a dark ink panel so the trigger reads. +// Rendered OPEN (`defaultOpen`) so the card shows the floated content. Body copied +// from the app's SharePopover (read-only share link + Copy / Stop sharing). +const inkPanel: React.CSSProperties = { + background: '#10141B', + padding: 16, + borderRadius: 8, +}; + +const trigger: React.CSSProperties = { + display: 'inline-flex', + alignItems: 'center', + height: 32, + padding: '0 12px', + borderRadius: 6, + fontSize: 13, + color: '#E7ECF3', + background: 'rgba(255,255,255,0.06)', + border: '1px solid rgba(255,255,255,0.08)', +}; + +const field: React.CSSProperties = { + flex: 1, + height: 32, + padding: '0 8px', + borderRadius: 6, + fontSize: 12, + fontFamily: 'IBM Plex Mono, monospace', + color: '#2B2B2B', + background: '#FFFFFF', + border: '1px solid #E3DDD2', +}; + +export function ShareLink() { + return ( +
+ + + + + +
+

+ Anyone with this link can view a read-only copy of this diagram. +

+
+ + +
+
+ +
+
+
+
+
+ ); +} diff --git a/web/.design-sync/previews/SearchInput.tsx b/web/.design-sync/previews/SearchInput.tsx new file mode 100644 index 00000000..65f111ca --- /dev/null +++ b/web/.design-sync/previews/SearchInput.tsx @@ -0,0 +1,73 @@ +import { SearchInput } from 'web-sequence-web'; + +// SearchInput = leading magnifier glyph + input + a clear (×) affordance that +// appears only when there's a value. It's a dark-surface control (home/library +// toolbars), so the dark cells sit on the ink panel; the `light` variant gets +// the warm paper panel. `onChange` is value-style and required; the cells pass +// a fixed value + no-op so the populated state (and its clear button) render +// statically on the capture sheet. +const noop = (_: string) => {}; + +const Ink: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
+ {children} +
+); + +const Paper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
+ {children} +
+); + +// Dark surface, empty: just the placeholder + search glyph (home toolbar). +export function Placeholder() { + return ( + + + + ); +} + +// Dark surface, with a query: clear (×) button now shows on the right. +export function WithValue() { + return ( + + + + ); +} + +// Light (paper) surface: empty placeholder + a populated query with clear button. +export function Light() { + return ( + + + + + ); +} diff --git a/web/.design-sync/previews/Select.tsx b/web/.design-sync/previews/Select.tsx new file mode 100644 index 00000000..fa910095 --- /dev/null +++ b/web/.design-sync/previews/Select.tsx @@ -0,0 +1,36 @@ +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectValue, +} from 'web-sequence-web'; + +// Design-system Select over Radix Select. The trigger sits on dark ink chrome +// (the Settings modal context) and the floating SelectContent is a dark ink panel +// itself. Rendered OPEN (`defaultOpen`) so the card shows the dropdown list, not +// just the collapsed field. Realistic options copied from SettingsModal's theme row. +const inkPanel: React.CSSProperties = { + background: '#10141B', + padding: 16, + borderRadius: 8, +}; + +export function ThemeSelect() { + return ( +
+ +
+ ); +} diff --git a/web/.design-sync/previews/Switch.tsx b/web/.design-sync/previews/Switch.tsx new file mode 100644 index 00000000..7fa8bc41 --- /dev/null +++ b/web/.design-sync/previews/Switch.tsx @@ -0,0 +1,47 @@ +import { Switch } from 'web-sequence-web'; + +// Drafting Table is a dark-surface DS: settings toggles live on the ink chrome +// (the Settings modal sits on the dark surface). Preview the Switch on the ink +// panel so the paper-200 track + cobalt-accent checked fill read with proper +// contrast against the panel rather than washing out on the white grading sheet. +const Ink: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
+ {children} +
+); + +// A labelled settings row, the way SettingsModal composes Switch (SwitchRow). +const Row: React.FC<{ label: string; children: React.ReactNode }> = ({ label, children }) => ( +
+ {label} + {children} +
+); + +// On vs. off — the two resting states a settings toggle alternates between. +export function OnAndOff() { + return ( + + + + + + + + + ); +} + +// Disabled toggles — locked-on and locked-off (e.g. a Pro-gated setting). +export function Disabled() { + return ( + + + + + + + + + ); +} diff --git a/web/.design-sync/previews/TextInput.tsx b/web/.design-sync/previews/TextInput.tsx new file mode 100644 index 00000000..0fccafab --- /dev/null +++ b/web/.design-sync/previews/TextInput.tsx @@ -0,0 +1,64 @@ +import { TextInput } from 'web-sequence-web'; + +// TextInput is a dark-surface DS control by default (lives on the ink chrome — +// e.g. inline page-tab rename). The grading sheet is white, so dark-surface +// cells sit on the ink panel; the `light` variant (share URL, settings fields) +// gets the warm paper panel it's designed for. +const Ink: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
+ {children} +
+); + +const Paper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
+ {children} +
+); + +const labelDark: React.CSSProperties = { fontSize: 12, color: '#9aa4b2', fontFamily: 'Hanken Grotesk, sans-serif' }; +const labelLight: React.CSSProperties = { fontSize: 12, color: '#6b6356', fontFamily: 'Hanken Grotesk, sans-serif' }; + +// Default dark surface: placeholder vs. a typed value (page-tab rename field). +export function Dark() { + return ( + + + + + ); +} + +// Disabled (dark) — used while a save is in flight. +export function Disabled() { + return ( + + + + ); +} + +// Light (paper) surface: the share-URL field (read-only) and a settings input. +export function Light() { + return ( + + + + + ); +} diff --git a/web/.design-sync/previews/Textarea.tsx b/web/.design-sync/previews/Textarea.tsx new file mode 100644 index 00000000..87860f4c --- /dev/null +++ b/web/.design-sync/previews/Textarea.tsx @@ -0,0 +1,94 @@ +import { Textarea } from 'web-sequence-web'; + +// Textarea mirrors TextInput's surface tokens as a multi-line field. The dark +// surface (default) hosts the Atomizer JSON config editor (mono); the light +// (paper) surface hosts modal copy like the bug-report description. +const Ink: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
+ {children} +
+); + +const Paper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
+ {children} +
+); + +const labelDark: React.CSSProperties = { fontSize: 12, color: '#9aa4b2', fontFamily: 'Hanken Grotesk, sans-serif' }; +const labelLight: React.CSSProperties = { fontSize: 12, color: '#6b6356', fontFamily: 'Hanken Grotesk, sans-serif' }; + +const ACSS_CONFIG = `{ + "custom": { + "1px": "1px solid #d8d4cc" + }, + "breakPoints": { + "sm": "@media (min-width: 640px)" + } +}`; + +// Dark surface, mono — the Atomizer JSON configuration editor. +export function DarkCode() { + return ( + +