78 Commits

Author SHA1 Message Date
Manav Arya Singh 31bd34f487 fix(extraction): code faces aren't body fonts; weigh colour evidence by chroma
Measured by replaying the extractors against cached crawls of the 27
benchmark sites, so only the code varied between runs.

Typography: syntax highlighting draws every token as its own text element, so
counting text-rendering elements made plexMono (tailwindcss, 450 vs Inter 131),
commitMono (resend) and Departure Mono (supabase) the body font. Families whose
name marks a code face are labelled "mono" and sort after the text faces.

Colours: usage, interactive backgrounds and area now count in proportion to
chroma (full weight from chroma 30), and area scores 60*sqrt(share).
Near-black #101214 (chroma 1.5) had won atlassian on 702 uses, #231e15 had
won mailchimp, and one element painting 84% of discord.com had won on area.

Replay, previous -> this: primary colour 10/17 -> 13/17, body font
24/27 -> 27/27, no site regressed. Still missed: linear (brand colour on one
element), slack, paypal, supabase (a lighter shade of the brand green). The three
new tests fail on the previous code.
2026-09-15 17:51:10 +04:00
Manav Arya Singh 9b3c3dfdd9 fix(colors): rank brand colours by chroma, repetition and area, not one CTA
Measured per cluster on real sites, the linear weights let a single button
decide: 100 points per interactive background against at most ~3.5 for usage
and ~2.5 for painted area. gov.uk's CTA green on 4 buttons outranked the
GOV.UK blue, Framer's one #0066ff button outranked Framer Blue used 44 times,
and HSL saturation scored near-black #002533 (l=10) as vivid as a real brand
colour.

- interactive backgrounds: 40 * log2(1 + n), strong with diminishing returns
- chroma instead of HSL saturation
- 25 * log10(count) and 300 * areaShare so repetition and surface count
- the browser's default link colours (#0000ee, #551a8b) are never the brand

Re-extracted: gov.uk, duolingo and framer now return the brand colour; hubspot
unchanged; supabase (lighter shade of the brand green), paypal and linear still
miss. Three of the five new tests fail on the previous ranking.
2026-09-15 17:12:08 +04:00
Manav Arya Singh 63efb00fc3 fix(typography): count a font only where it renders text; stop fallbacks taking the body slot
Head and metadata elements carry the browser default (Times) without drawing
anything. On gov.uk they were 35 of Times's 36 uses; on dropbox.com Times had
1257 elements but only 90 with text, against 201 for Atlas Grotesk, so
designlang reported Times as dropbox's body font.

- families count only text-rendering elements (records without hasText, from
  older snapshots, still count)
- a family used on neither headings nor body copy is labelled "other", not body
- React/MUI theme, Tailwind and CSS vars give the body slot to the most used
  body-capable family and never overwrite it (supabase's tailwind body had
  become Source Code Pro)
2026-09-15 17:07:20 +04:00
Manav Arya Singh 49fcb190c1 fix(cli): --json output was truncated at 64KB when piped
process.exit() straight after process.stdout.write() drops whatever the pipe
hasn't flushed. `designlang https://gov.uk --json | jq` received exactly
65536 bytes of a 70583-byte document, so any site with a large design broke
JSON consumers in CI. Found by the benchmark runner, which reads through a pipe.

- writeThenExit(): exit once the write is handed to the OS; used by extraction
  --json and drift --json (which exits non-zero on drift right after writing)
- the export command's error path uses the new exit codes
- bench: a family designlang marks usage "all" counts as body text, and both
  readers take the most used body family
2026-09-15 16:34:53 +04:00
Manav Arya Singh b73e1a6b27 fix(bench): don't score a dimension that has no ground truth
Several brands are monochrome or render body text in a system stack, so no
sourced brand colour or font appears on the live page. Scoring either tool
against an absent truth would be noise. Such a dimension now scores null and
is left out of that dimension's denominator; a failed run still counts as a
miss wherever truth exists.
2026-09-15 16:08:13 +04:00
Manav Arya Singh 46c504a2bd feat(bench): reproducible head-to-head extraction benchmark tooling
- bench/score.js: unit-tested scoring. Primary colour counts within CIE76
  dE 10 of the brand colour; body font by normalised family name. The same
  reading rule for both tools: the family labelled body, else the most used
- bench/run.mjs: designlang (this checkout) vs pinned dembrandt 0.33.0,
  default settings, sequential, alternating order per site, hard timeout,
  writes bench/results/<date>.{json,md} with losses included
- bench/evidence.mjs: ground-truth evidence independent of either extractor
  (the family that actually renders body copy; closest painted colour to each
  claimed brand colour)
- keep bench/ out of the npm tarball
2026-09-15 15:57:20 +04:00
Manav Arya Singh 13cf3aed17 feat(ci): distinct exit codes, a pinned and gating Action; release 13.3.0
Every failure exited 1, so a CI gate could not tell a design regression from a
flaky preview deploy.

- exit 0 ok, 1 drift over threshold, 2 extraction failed, 3 navigation timeout;
  wired into extraction, drift, ci and lint
- the Action installs the designlang version it was tagged with, runs
  install-browser, fails on drift by default, annotates each changed token
  (escaped), says why a job failed, and lists Manavarya09 as author
- fix changed-count being written as "0" twice when nothing changed
- README: install-browser, live MCP tools, CLI reference
- 13.3.0 changelog with the breaking changes called out
2026-09-15 15:54:34 +04:00
Manav Arya Singh a6b8671864 feat(mcp): v2 server extracts live URLs as jobs; serve version-less initialize
The MCP server could only read an extraction already on disk, so an agent
could not point it at a URL.

- extract_design returns a job id; get_job_status, list_jobs, cancel_job
- get_tokens, get_colors, get_typography, get_components by job id
- compute_drift (job vs baseline job), get_findings (token lint + WCAG),
  export (dtcg, tailwind, shadcn, figma, css, design-md)
- existing folder-backed tools and resources are unchanged
- an initialize without protocolVersion is served on the default revision
  instead of rejected (#182, #183)
2026-09-15 15:48:24 +04:00
Manav Arya Singh 037db816b4 feat(install): drop the postinstall Chromium download; add install-browser and one launcher
postinstall downloaded Chromium on every npm install, which fails in CI, Docker
and behind proxies. Browsers are now installed on demand.

- designlang install-browser [--with-deps] installs the Chromium that matches
  the bundled playwright
- src/browser.js launchChromium(): bundled Chromium, then system Chrome, then a
  BROWSER_UNAVAILABLE error that names the fix; all 13 launch sites use it
- doctor, the extraction error and smithery.dockerfile point at install-browser
2026-09-15 15:45:12 +04:00
Manav Arya Singh ddd4ad1b2c ci(release): publish to npm on version tags; fix MCP version and designlang/mcp entry
- release.yml publishes with provenance when a vX.Y.Z tag matches package.json
- MCP serverInfo reports the package version instead of a hard-coded 7.0.0
- add src/mcp/index.js, which the ./mcp export pointed at but never existed
- test a stock SDK client handshake over stdio
2026-09-15 15:41:20 +04:00
Manav Arya Singh cd7d968b52 fix(extraction): tighten measure, container and role naming against real sites
Running the new extractors against Stripe, Linear, Vercel and Tailwind
surfaced four ways the heuristics lied:

- **Measure read paragraph boxes, not text.** A <p> is as wide as its
  container; its text usually is not, so Stripe reported 22 characters
  per line. The crawler now measures real line boxes with a Range over
  each prose element (budgeted at 120 elements) and the extractor takes
  the median of the reading column only — the narrow <p> furniture in
  navs and footers is excluded. Stripe now reports 53, Linear 47.
- **The content column was whatever repeated hardest** — a 400px card on
  Stripe. It is now the constrained width governing the most page area,
  with a genuine full-bleed shell reported as `fullBleed` plus the widest
  constrained block inside it, rather than having some card promoted in
  its place.
- **`display` went to the first untagged size**, which put a 32px step
  above a 48px <h1>. It is now reserved for the largest step on the page;
  other untagged sizes become ordered title rungs.
- **Fluid ranges serialized as null** via Infinity when no declaration
  resolved to px (`min(var(--x), 2.75vw)`), and measure asserted a verdict
  off a single sample. Both now carry their own caveat — `resolvedCount`
  and a confidence level.
2026-08-31 10:09:12 +04:00
Manav Arya Singh d417afef2c feat(colors): tonal ramps, measured semantic pairs, area-weighted dominance
Extraction handed back one hex per role and left every consumer to invent
the rest of the palette by hand. Three additions:

- **Tonal ramps.** A 50–950 ladder per role plus the dominant neutral,
  generated in OKLCH so the steps are perceptually even — HSL lightness
  is not, which is why hand-built ramps drift. Chroma peaks mid-ladder
  and falls off at both ends, and the site's own colour keeps its rung
  (reported as `anchor`), so the ramp contains the real brand colour
  rather than a re-derived approximation of it.
- **Semantic pairs.** surface.page, surface.raised and the action colours,
  each paired with the foreground that actually reaches contrast on it,
  with the ratio and WCAG level attached. A colour the site already uses
  for text wins; black or white is a marked fallback.
- **Area-weighted dominance.** Colour usage counted elements, so two
  hundred 16px icons outranked a hero background. Backgrounds now
  accumulate painted area, exposed as `dominance` and folded into brand
  ranking as a weak term.

Ramps and pairs are emitted through css-vars and the DTCG tokens, where
semantic action/surface tokens now carry a measured `onX` foreground.
Also stops `semantic.shadow.elevated` pointing at sh0 — the faintest
hairline on the page — in favour of a mid rung of the elevation ladder.
2026-08-31 10:09:12 +04:00
Manav Arya Singh e326c8bfec feat(layout): derive the layout system, not just a grid inventory
extractLayout counted grids and listed every max-width it saw. What a
clone actually needs is the system underneath: the content column width
and its gutters (read from rendered width, so percentage and clamp()
containers land in the ladder too), the column count the grids agree on
weighted by governed area rather than by instance count, the vertical
rhythm full-bleed sections use — the number that decides whether a clone
feels right or cramped, and one that never survives a spacing scale built
from every padding on the page — a numeric gap ladder, and the fluid
max-width/gap declarations.

Also fixes `gaps`, which was string-sorted: '10px' ranked before '4px'.
2026-08-31 10:09:12 +04:00
Manav Arya Singh 0ba177a78a feat(typography): resolve the size dump into an actual type system
`typography.scale` was an inventory — every distinct px size found, in
descending order, with no way to tell a display size from body copy. Four
additions turn it into a system:

- **Modular ratio inference.** Fits the scale against the eight standard
  ratios with a power penalty (without one, a minor second explains any
  scale by raising itself to the 4th) and reports fit, ladder coverage and
  the anchor size, or refuses to name a ratio at all.
- **Named scale.** Every step gets a usable role — heading tags claim
  their own, the rest are placed relative to body size.
- **Fluid type.** Computed styles resolve clamp() to one px value at the
  capture viewport, so a site's responsive ramp was invisible. The crawler
  now harvests authored fluid declarations off the stylesheets (including
  cross-origin sheets fetched as text) and the extractor parses them into
  min/max px — flagging clamps whose preferred term has no viewport unit
  and therefore never scale.
- **Measure.** Characters per line for body copy, with a narrow/
  comfortable/wide verdict. Needed rendered box widths, so computed styles
  now carry width, height and document-relative top.

Also fixes the DTCG `semantic.typography.body` token, which read from
scale[0] — the largest size — and so emitted the headline size as body
text. It now resolves the real body step, and primitive font sizes are
keyed by role rather than index.
2026-08-31 10:09:12 +04:00
Manav Arya Singh e421704d84 feat(shadows): parse multi-layer shadows and derive an elevation ladder
Composite box-shadow values (`0 1px 2px …, 0 10px 15px -3px …` — what
Tailwind and most design systems emit) were parsed as a single shadow:
the colour regex grabbed the first layer's colour and the numeric regex
swept offsets across every layer, so blur, offset and spread were wrong
for the majority of real sites.

Layers are now split on top-level commas and parsed individually; the
composite reports from the key (widest) layer, so an ambient hairline
stacked under a key shadow no longer drags the reported elevation down.

On top of that the raw shadow set collapses into an ordered elevation
ladder — buckets within 25% visual weight merge, the most-used raw value
wins its bucket — plus per-shadow tint (neutral vs brand-coloured, with
hue and alpha) and a system summary carrying level count and redundancy.

The css-vars, DTCG tokens and Tailwind emitters now key off the ladder.
They previously keyed off `label`, which collides whenever a site has
more than one shadow in the same size band — those tokens were silently
overwriting each other.
2026-08-31 10:09:12 +04:00
Manav Arya Singh 2ade86c881 test(dna): the properties that keep the space honest
Unit coverage aside, these are the acceptance criteria for the space itself:

  * two dark minimal systems land nearer each other than either does to a
    playful one — the space discriminates by design family
  * extraction noise moves a vector by <0.02 while a real restyle moves it
    an order of magnitude further — it measures design, not jitter
  * features either side is missing are skipped, never invented
  * a corpus built for another vector version is refused, not compared

Two of these caught real bugs against live sites: the extractors emit HSL
on a 0-100 scale while the readers assumed 0-1, and font weights and
line-heights arrive as CSS strings. Stripe went from 93% to 100% of features
measurable once both were fixed.
2026-08-24 14:11:00 +04:00
Manav Arya Singh d6c60b0a7f feat(cli): add designlang doctor health-check command
Prints a one-screen sanity check of the local install so a failing
extraction can be diagnosed in seconds: Node version against the
`engines` floor, designlang version, playwright version, whether the
resolved Chromium binary actually exists on disk, output-dir
writability, and network reachability.

Failing checks print a fix hint. Exits 0 when everything passes, 1 when
any check fails. The network probe uses a 3s timeout and reports WARN
rather than FAIL so an offline environment does not produce a
misleading exit code.

No new dependencies. Closes #107.
2026-08-22 07:36:59 +04:00
Manav Arya Singh 5ce0b9249a Merge pull request #174 from Manavarya09/feat/fidelity-loop
Release 13.0.0 — Live Extraction Theatre + 37-site gallery
2026-06-25 12:10:58 +04:00
Manav Arya Singh 602722d0cd refactor(theatre): relocate screencast util to src/ (core layer, used by crawler) 2026-06-25 11:24:58 +04:00
Manav Arya Singh 438bb99a74 Merge pull request #163 from Manavarya09/feat/fidelity-loop
feat: measured clone fidelity (visual + motion) loop + shareable gallery (v12.24.0)
2026-06-22 22:56:00 +04:00
Manav Arya Singh 8d06697eac feat(clone): add --fidelity pass + fix -o/--out across clone/verify/visual-diff
Two things:

1. clone --fidelity: after generating the Next.js starter, rebuild its
   components from the extracted tokens, pixel-diff vs the live site, and write
   FIDELITY.md + fidelity.json + fidelity-card.svg + a ranked correction plan
   into the clone. Reuses the existing extraction (no second crawl) and points
   the user at `designlang fidelity` for the full visual+motion score of the
   running clone.

2. resolveOut(): the root command greedily owns -o/--out, and a sub-level
   *default* masked any override (opts.out was always truthy). clone/verify/
   visual-diff dropped their masking defaults and now resolve via resolveOut,
   which prefers an explicit sub value, else the root value only when actually
   passed on the CLI (getOptionValueSource === 'cli'), else the fallback.

Verified end-to-end: cloned stripe.com builds clean (next build, 4/4 pages),
served, and `fidelity` scored it 63/100 (motion 52 — the gap competitors miss).
-o now writes where asked. Adds a clone --fidelity CLI smoke test.
2026-06-22 22:52:55 +04:00
Manav Arya Singh ee68490eb7 docs: document fidelity + gallery, bump to 12.24.0
README quick-start, a dedicated 'Measured clone fidelity' section, the plugin
command table, and the full CLI reference now cover /fidelity and /gallery.
CHANGELOG 12.24.0 entry. Version bumped in lockstep across package.json,
plugin.json, marketplace.json (manifests describe the two new commands); the
version-sync test now guards fidelity + gallery too.
2026-06-22 22:33:52 +04:00
Manav Arya Singh d5f962ca80 feat(gallery): wire 'designlang gallery' command + report loader
designlang gallery [dir] scans a directory for fidelity.json reports, builds the
gallery model, and writes a deployable static site (index + permalink pages +
OG cards) to --out. load.js does a safe recursive scan (skips node_modules and
malformed files). Adds command doc + loader tests + 2 CLI smoke tests. Verified
end-to-end against real reports.
2026-06-22 22:30:39 +04:00
Manav Arya Singh 80ff54bd3a feat(gallery): static-site formatter — index grid, permalink pages, OG cards
formatGalleryIndex renders a dark card grid (best-first) with summary stats;
formatGalleryItemPage renders a permalink page that embeds the SVG score card,
OG/Twitter meta (so a shared link unfurls the number), a motion table, and the
ranked correction plan. renderGallerySite emits the full [{path,content}] tree
(index + per-clone page + per-clone card.svg). 4 unit tests.
2026-06-22 22:28:57 +04:00
Manav Arya Singh c919acc0dd feat(gallery): gallery item model + builder over fidelity reports
The distribution surface for the fidelity loop (feature C). buildGallery() turns
a set of fidelity reports into a deduped, best-first item list with summary
stats (count, avg, best/worst). slugify() + toGalleryItem() are tolerant of
partial reports. Pure data shaping; 6 unit tests.
2026-06-22 22:27:47 +04:00
Manav Arya Singh d67a82d85c feat(fidelity): wire 'designlang fidelity' command + clone-vs-original runner
New CLI verb: designlang fidelity <original> --clone <url>. Captures full-page
screenshots of both (pixel-diff → visual score), extracts motion from both
(→ motion score), blends them, and writes fidelity.md / fidelity.json /
fidelity-card.svg / fidelity-diff.png. --min gates CI; --motion-runtime captures
real durations + choreography.

run.js is the integration glue over existing primitives (extractDesignLanguage,
verify's diff) and the new scoring modules. Adds command doc + 2 CLI smoke
tests.
2026-06-22 22:22:48 +04:00
Manav Arya Singh 0cc19c665a feat(fidelity): report formatters — JSON, Markdown, shareable SVG score card
Renders a combined fidelity result three ways: machine JSON, a Markdown report
(headline score, motion breakdown table, ranked correction plan), and a
self-contained SVG share card ('88% · B · stripe.com'). The card is the gallery
share hook — a measured number no competitor can post. 6 unit tests.
2026-06-22 22:19:48 +04:00
Manav Arya Singh 8a0fba1dab feat(fidelity): correction-plan emitter + combiner + closed-loop driver
The wedge neither competitor ships: don't just measure fidelity — prescribe the
next edit and loop on it.

- correction-plan.js: turns verify's per-token-family attribution and the motion
  aspect scores into ranked, actionable directives (hardest-hitting first), each
  with an expectedGain heuristic so the loop spends its budget well.
- index.js: combineFidelity() blends visual (verify) + motion into one score and
  letter grade; runFidelityLoop() drives measure→plan→rebuild→re-measure until a
  threshold or round budget, with measure/rebuild injected for offline testing.

10 unit tests covering ranking, threshold gating, blend math, convergence, the
stall path, and the no-directives early exit.
2026-06-22 22:18:37 +04:00
Manav Arya Singh 1c5521fdb9 feat(fidelity): motion-fidelity comparator — score a clone's motion vs the original
verify/ scores static visual fidelity but deliberately ignores motion. This
fills that gap: a pure function over two extractMotion() outputs that blends
seven aspects (feel, durations, easings, springs, keyframe kinds, scroll-linked,
choreography/stagger) into a 0-100 motion fidelity score with per-aspect gaps.

Missing-on-clone motion is the dominant penalty; over-animation is penalised
gently. No browser, no I/O — fully unit-tested (7 cases).
2026-06-22 22:16:55 +04:00
Manav Arya Singh e7ad883cdc Merge pull request #157 from cycsmail/fix/prefers-color-scheme-depth-order
fix(crawler): capture prefers-color-scheme colors before the depth crawl
2026-06-22 01:36:13 +04:00
Manav Arya Singh 53e42cc769 test(brand): cover deriveBrandEssence axes, archetype, copy, rendering
13 tests: warm/cool axis polarity, archetype selection from the twelve,
adjective/positioning/evidence shape, dangling-dash guard, empty/null
robustness, string-form inputs, and Essence chapter in html + md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 22:30:04 +04:00
Manav Arya Singh f12ae1d7d2 test(crawler): guard prefers-color-scheme restore invariant (#157)
The media-dark capture runs on the shared page before the --depth crawl
navigates it away, so it must restore the scheme to light afterwards or
the crawl reads dark colours off internal pages. Adds two regressions:
restore-after-success and restore-on-evaluate-throw (the try/finally).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 22:27:59 +04:00
Manav Arya Singh 62d1e78891 test(formatters): regression tests for #135 narrative bugs
Asserts no literal [object Object], semantic/easing/z-index values render,
and component class names keep their last character.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 14:16:35 +04:00
Sebastian Cao 32b98d6c0c feat(crawler): detect dark mode via prefers-color-scheme media queries (#145) 2026-06-18 13:54:25 +04:00
Manav Arya Singh 3d9fab5a0f fix(site): preserve full colour swatch fields in canonical
Synthesis reduced swatches to {hex,count}, dropping rgb/contexts and
crashing formatMarkdown (reads c.contexts.join and c.rgb) when the
canonical pack was emitted. Keep the whole swatch (spread) so every
emitter works. Caught by an end-to-end run on example.com; covered by a
regression test asserting canonical swatches retain rgb + contexts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 13:40:44 +04:00
Manav Arya Singh 78fdf58add chore(plugin): add version-sync guard script + test
scripts/check-plugin-version.mjs fails loud when plugin.json or
marketplace.json drift from package.json; wired as 'npm run check-plugin'
and enforced by tests/plugin-version.test.js (also asserts every new
command has a backing file and is described). Prevents the drift recurring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 13:35:28 +04:00
Manav Arya Singh 762a0c4281 test(site): unit tests for whole-site synthesis engine
Covers scope thresholds, OKLab distance + clustering, canonical
election, outlier flagging, grade/letter, single-page no-grade, missing
fields/errored pages, and determinism. 9 tests, all pure (no browser).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 13:30:19 +04:00
Manav Arya Singh f10f4c4c08 feat(studio): Motion preview tab — playable easing curves + choreography (v3)
New Motion tab in the local studio renders extracted easing as SVG curves
(springs styled distinctly), duration chips, and runtime choreography +
scroll recipes. Click a curve to preview its timing; play a choreography
sequence to see the real captured stagger. Degrades to a --motion-runtime
prompt when no motion tokens are present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:53:37 +04:00
Manav Arya Singh fc08b03931 feat(motionlang): emitters consume runtime choreography (v3 fidelity)
motion-tokens.json gains choreography + scroll-recipe + observed-duration
blocks. Framer Motion stagger and the new GSAP stagger() helper now use the
real runtime-observed staggerMs instead of a heuristic guess when
--motion-runtime captured one. Degrades cleanly with no runtime data.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:50:35 +04:00
Manav Arya Singh 294030e5aa feat(motionlang): runtime motion model + choreography/scroll detection (v3 core)
Pure processing layer for Motion Lang v3: processRuntimeMotion() turns raw
document.getAnimations() observations into a structured per-trigger model with
real durations; detectChoreography() finds evenly-staggered sequences;
detectScrollRecipes() classifies scroll motion into parallax/reveal/pin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:45:23 +04:00
Manav Arya Singh a51e406c01 feat(studio): craft pass — intentional specimen + harmonised colors (v12.21.0)
The preview felt random. This rebuilds it as a deliberate design-system
specimen instead of a scatter of placeholder components.

- Composed preview: masthead, a centered hero using the site's own strongest
  heading + top CTA verbs, labeled Color and Type foundations, then Buttons /
  Inputs / Status / In-context cards / Metrics — each under a clear section
  eyebrow, on a consistent vertical rhythm. The rebuilt-Page tab reuses the
  same crafted components. Honest product copy, no lorem.
- Harmonised deriveTokens: guarantee a legible surface/text pair, filter greys
  out of accent selection (no neutral promoted to brand), deepen a near-
  invisible accent to read on the surface, hold secondary text above a
  legibility floor. Keep white-on-warm-accent convention for accent foreground.
- Calmer chrome: quieter hairlines, consistent 30px rounded controls, pill
  tabs, segmented light/dark — the frame recedes so the system is the focus.

Shared studio engine, so the CLI and website /studio get it identically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 13:34:00 +04:00
Manav Arya Singh c652df1b96 feat(studio): one-click generated dark mode (v12.20.0)
Add a Light/Dark toggle that generates a dark variant from the extracted
light tokens: surface → near-black tinted toward the brand hue, text →
near-white, muted/border/card recomputed; the brand accent is preserved
(lightened only if it would vanish on dark). Type/shape/spacing/motion carry
over. Contrast grading recomputes live, inspector edits layer on top of
either theme, and the theme is encoded in the shareable URL.

Pure shared deriveDark() in src/studio-tokens.js keeps the CLI and website
studios in lock-step. New unit tests cover the dark derivation and control.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 03:42:56 +04:00
Manav Arya Singh 8a2c425f4f feat(studio): living design-system editor + /studio on the website (v12.19.0)
Turn `designlang studio` from a read-only viewer into an interactive
playground, and ship the same experience on the website.

CLI (`designlang studio`):
- Inspector (color/type/shape/spacing/motion) writes a small `--p-*` CSS
  variable set; a tabbed live preview — component wall + a page rebuilt from
  the extraction's reading order — restyles instantly off those variables.
- Live WCAG contrast grading per color, paper/white/dark backdrops, an edit
  counter, and smooth token-driven transitions.
- Export edited system as DTCG tokens / CSS variables / Tailwind theme
  (zero-dep client downloads); share via URL (edits encoded as hash deltas).

Website (`/studio`):
- Paste any URL → extract → embed the identical editable studio in a framed
  viewport. Shareable via `?url=`; shares the free 2/day demo budget.
- New POST /api/studio reuses the same `studioHtml` engine server-side.

Internals:
- Token derivation extracted to a pure, dep-free module (src/studio-tokens.js)
  shared by the CLI and the website route, keeping both surfaces in lock-step.
- New unit tests: derivation, WCAG contrast math, rendered studio document.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 02:49:47 +04:00
Manav Arya Singh f9e0c4770a feat(verify): fidelity loop — rebuild from tokens, pixel-diff vs live (v12.18.0) (#139)
`designlang verify <url>` closes the extract→prove loop. For each detected
component (button+card in v1) it rebuilds a clone styled ONLY by the extracted
tokens (each visual property snapped to nearest token, colours in CIE-Lab ΔE),
renders it, and pixel-diffs against the live component → 0-100 fidelity score +
per-token-family attribution + a verify.html triptych (original | rebuilt |
loss heatmap) + verify.json. `--min` gates CI.

Honest by construction: missing component → n/a (excluded, no silent 100);
property with no token → unmapped (counted as loss, surfaced in attribution);
size mismatch letterboxed, never stretched.

New isolated modules under src/verify/ (tokens/restyle/render/diff/index) +
formatters/verify.js + verify CLI command. Adds pixelmatch+pngjs. 13 unit tests,
full suite green (438). Live smoke vs github.com: button 89/100.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 17:50:42 +04:00
Manav Arya Singh 95eae1702d feat(motionlang): add GSAP and Web Animations API emitters (v12.17.0) (#138)
Two new framework targets for the extracted motion language:

- motion-gsap.js: registers extracted cubic-bezier curves as named GSAP
  CustomEases, emits gsap.from() reveal helpers + a ScrollTrigger batch
  reveal when the page uses scroll/view-timeline.
- motion-waapi.js: zero-dependency Web Animations API preset. WAAPI takes
  cubic-bezier easing strings verbatim, so curves reproduce exactly (no
  spring approximation); helpers honour prefers-reduced-motion.

Wired into the CLI emit list (`<host>-motion.gsap.js`, `-motion.waapi.js`),
the /api/motion exports, and the motionlang analyzer export cards. Adds 8
emitter tests (425 -> all green). Also ignores .npmrc.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 17:13:29 +04:00
lucaszhu-hue 1d4b071042 feat(atlascloud): support Atlas smart classifier 2026-06-09 11:35:54 +08:00
Manav Arya Singh 619d44ad21 v12.16.0 — motionlang CSS + Tailwind emitters; fix website brand-book PDF
motionlang:
- New formatter src/formatters/motion-css.js — <host>-motion.css with
  --duration-*/--ease-* custom properties, reusable + reconstructed
  @keyframes, .mo-* utility classes, and a prefers-reduced-motion guard.
- New formatter src/formatters/motion-tailwind.js — <host>-motion.tailwind.js
  require-able theme.extend (transitionDuration, transitionTimingFunction
  incl. a spring curve from detected overshoot beziers, keyframes, animation).
- Wired both through api.js (RENDERERS + ext map) and the bin files array as
  the motion-css and motion-tailwind renderer ids.
- 13 new tests; full suite 414 pass.

website PDF fix ("downloaded PDF won't open"):
- extract route awaits the Blob cache write before the stream closes, so the
  serverless instance can't freeze mid-write and leave /api/pdf/<hash> with
  no cached design (was a fire-and-forget write returning 404 JSON).
- ShareExtractionButton fetches + validates content-type is application/pdf
  before saving, surfacing an error instead of writing a broken file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 22:46:52 +04:00
Manav Arya Singh 613948a09c motionlang: add Motion One emitter, derive springs in Framer Motion
- New formatter src/formatters/motion-one.js — emits <host>-motion.one.js
  with animate() helpers (fadeIn, slideUp, scaleIn, pop), springs derived
  from detected overshoot beziers, reconstructed keyframe maps, and
  inView/scroll helpers when the source uses scroll-/view-timeline.

- Framer Motion: springs now derived from detected overshoot beziers
  (was hard-coded), added pop variant on a spring, keyframe-derived
  variants reconstructed from @keyframes, and a whileInView inView
  block when the source uses scroll motion.

- Wire motion-one through api.js (RENDERERS + ext map) and the
  bin/design-extract.js files array so it ships alongside the existing
  Framer Motion preset.

- Tests: 7 new cases covering spring derivation, scroll-conditional
  emit, keyframe reconstruction, and minimal-design degradation. All
  pass; 154 existing formatter tests still green.

Shared springFromBezier heuristic keeps Motion One and Framer Motion
outputs feel-consistent for the same site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 15:11:54 +04:00
Manav Arya Singh f403f8c536 v12.11.0: brand --pdf
Adds native PDF brand-guide output to the brand command:
  --pdf, --paper a4|letter|tabloid, --landscape,
  --no-print-background, --attach-tokens

PDF features: per-chapter page breaks, running footer with page
numbers, selectable text, embedded fonts, optional DTCG-tokens
JSON attached as a file inside the PDF (paperclip in Acrobat).

One new dep: pdf-lib (lazy-imported, only used when --attach-tokens
is passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:25:11 +04:00
Manav Arya Singh be37b82526 feat(v12.10): designlang stats + low-confidence note in grade card
Two tight quality-of-life additions on top of the v12.9 extraction pass.

stats command

  $ npx designlang stats stripe.com

    Grade        B · 87/100
    Primary      #533afd ×899 59% conf
    Fonts        sohne-var
    Type scale   14 sizes
    Spacing      base 2px · 13 steps
    Shape        5 radii · 6 shadows
    Colours      31 tokens
    WCAG         79%
    Stack        next · unknown
    Material     skeuomorphic
    Tone         neutral
    Intent       landing

  One-screen summary, no files written. Use -j / --as-json for
  machine-readable output (CI, scripting).

  (--json is already a global program flag for the main extract
  command's raw-JSON-to-stdout mode, so the subcommand flag is
  --as-json to avoid the clash. Both --as-json and -j shortcuts work.)

Low-confidence note in grade.html

  v12.9 added primary.confidence (0-1) on the colours payload but no
  surface used it. v12.10 renders an amber callout above the
  dimensions grid in the grade report card when confidence < 0.5:

    "Primary detection was low-confidence (32%). The brand colour
     may be a soft pick — review the palette below."

  Soft inline note, not an error. Stays hidden on the common high-
  confidence path. Dark-mode aware.

Tests
  - 2 new tests cover the present + absent paths of the confidence
    note.
  - 398/398 total.

No breaking changes, no new dependencies.
2026-05-13 23:36:13 +04:00