How General Translation diagrams are drawn.
node scripts/install-skills.mjs gt-diagrams --project <dir>Run it from a Prototemplate checkout. It links skills/gt-diagrams into the project’s .claude/skills and .agents/skills; --copy vendors the folder instead, and --dry-run prints each step first.
A General Translation (GT) diagram follows one grammar on every surface: the Prototemplate site, the landing app, the brand deck, blog graphics and the films. Pages and slides draw it as inline SVG; the blog graphics are HTML pages and some films draw on a canvas. It mounts a drawn component before it draws anything new, links objects with the doubled line, and is read by eye at 2x in both themes, because the line auditor skips everything inside an SVG. Paths are relative to a Prototemplate checkout ($PROTOTEMPLATE, github.com/Kevin-Liu-01/Prototemplate, Kevin's design hub with the canon in DESIGN.md and BRAND.md) unless they name $GT_CLOUD (a checkout of github.com/generaltranslation/gt-cloud at origin/main, whose apps/landing serves generaltranslation.com).
A diagram earns its place when it shows a relationship the text alone does not: a flow of three to six steps, a before and after pair, a scale or an axis, a stacked layer model, a grid or a ladder, or a timeline (deck/DECK-GRAMMAR.md, Diagrams). A clean list of statements stays a list, set as ruled rows. Boxes that restate a list with no relationship drawn between them fail review.
app/page.tsx and the three JSON files the config writes; SentenceWidth lays out and measures each locale; SdkLedger replaced a plate stack that drew slabs where the package names belonged. Grey bars or squiggles in place of text fail review.src/app/d/_v0/sections/StackTower.tsx). Text seated in an isometric face at chip scale is never legible, so the enterprise diagram's three context keys stay bare and a stage label names them.Kevin measures diagrams against generaltranslation.com and resend.com (Kevin, 2026-07-30). On 2026-08-11 he restarted the landing diagrams: "these diagrams are so bad. i literally want you to restart these". Build one exemplar, get his sign-off on its grammar, then repeat it (redesign-v0-verdict). Explorations stay on localhost until he has reviewed them.
Mount an existing component when the content matches. The redesign's Figma mocks were screenshots of these components (Kevin, 2026-08-04). A new drawing is for new content only, seated inside a framed cell.
| content | mount | file under src/app/d/toolchain/diagrams/ |
|---|---|---|
| source file to translated locale files | TranslationFlow | TranslationFlow.tsx |
| locale prefixes, localized paths, detection order | LocaleRouting | LocaleRouting.tsx |
| text expansion and layout width | SentenceWidth, ExpansionBars | lang/ |
| one string, two meanings, context decides | ContextResolve | lang/ContextResolve.tsx |
| plural rules | PluralForms | lang/PluralForms.tsx |
| right to left layout | RtlMirror | lang/RtlMirror.tsx |
| writing systems, one term in every locale | ScriptSampler, WordMorph | lang/ |
| glossary, live translation, previews, detection hook | GlossarySurface and the other surfaces | surface/ |
| delivery from the edge | EdgeGlobe | EdgeGlobe.tsx |
| the full stack | TcStackIso | tc-stack-iso.tsx |
| context inheritance | TcCtxLayers (unmounted) | tc-ctx-layers.tsx |
| the SDKs | SdkLedger; SdkStack (unmounted) | SdkLedger.tsx, SdkStack.tsx |
| a number, a benchmark | StatRow; BenchmarkBars (unmounted) | StatRow.tsx, BenchmarkBars.tsx |
| a locale's name anywhere | LocaleTag | ../components/LocaleTag.tsx |
| any connector | DoubledLine | src/components/shared/diagrams/DoubledLine.tsx |
| isometric objects | IsoFrame, IsoSolid, iso.ts | see gt-isometric |
No route in Prototemplate mounts the rows marked unmounted, nor TcStackTrace or the TcMini set. Read them as reference drawings and check them against sections 3 to 6 before mounting one: TcCtxLayers draws its thread as two separate paths and ends it in a 14 by 11 arrowhead, which breaks both rules. Five directions keep forked copies of the lang/ or surface/ set under src/app/d/<direction>/diagrams/; change the toolchain original.
The components also run live on /docs, in the deck and in gt-cloud.
/docs (the readme's last sections; /craft redirects there). The libraries section mounts the DoubledLine plate (src/app/craft/ThreadsDemo.tsx), the iso plate and EdgeGlobe. RailFigure.tsx and CornerFigure.tsx draw the ownership law and the border crosses. docs/LIBRARIES.md indexes every instrument.30-lines.html (line rules), 31-doubled-line.html, 33-diagrams.html (the diagram grammar and four examples), 35-iso.html and 76-line-law.html (the line law in chrome) in deck/slides/. They are slides 30, 31, 33, 35 and 74: a file's number prefix is its sort key, and past slide 35 it no longer equals the slide's position. Start a new slide diagram from their markup.apps/landing/src/components/landing/shell/engine.css. gt-cloud's .agents/skills/gt-landing is their code map.references/components.md lists every component with what it shows and where its accent goes.
The deck's grammar (deck/DECK-GRAMMAR.md, Diagrams; slide 33) states the rules for every surface:
class="ink" (ink), class="mid" (--ink-2, the body text tone) and class="hair" (--hair). On a page the root's semantic layer gives the same three, for example --tc-ink, --tc-ink-2 and --tc-hair on the toolchain root.stroke-width 1 or 1.5. Square caps, no rounded joins. A 1px line at a 1:1 scale sits on a half-pixel coordinate (y1="70.5") so it fills one row of device pixels.var(--ink), var(--paper) and var(--plate) on the deck; the root's tokens on a page; currentColor with fill-opacity steps for faces. In a live component under src/app, src/components or src/lib, gt-ui's no-hex-colors rule (pnpm lint:code) fails a hex in a style prop or a Tailwind utility, but a hex written as an SVG attribute (stroke='#2f5ce0') passes it, so review catches that case. Any raw color in src/components/shell or src/components/viewer fails pnpm lint:shell. The directions under src/app/d are outside both lints, and that includes the toolchain diagrams. Their dark code panels use literal whites because the panel stays dark in both themes.vector-effect: non-scaling-stroke, so a 1px line stays 1px.DECK-GRAMMAR.md tolerates a filled triangle up to 8px, and older toolchain drawings carry arrowheads (a 14 by 11 triangle in tc-ctx-layers.tsx, IsoArrow in IsoSolid.tsx); slide 33 states the stricter rule, and new drawings follow it.docs/GRAPHICS.md).Each surface spends the accent differently.
| surface | accent rule | source |
|---|---|---|
| site page | One spectral accent per page (#2f5ce0 on paper, #86a8ff on the dark band), spent by each drawing on exactly one element. On the toolchain page the accent's six places are all diagram states. | DESIGN.md sections 1 and 6; src/app/d/toolchain/styles.css |
| deck | No accent on text, lines or fills. The four semantic hues appear on icons only. | DECK-GRAMMAR.md, Color |
| film | One accent per film, spent as an edge: one pulse on a thread, one active row, one lit word. The round directions at the top of the brief override this: since round 4 a film's palette is its gem smoke material (the fuma-nama pulse is fire). | motion/MOTION.md, Color and Round 4 |
| blog graphic | Red for what was removed, blue for the page and what replaced it. | docs/GRAPHICS.md |
When a page's accent budget is spent, emphasis is ink weight or an ink underline, as on LocaleRouting's localized pathname.
.panel. The shared DiagramFrame set's 9.5px mono text predates this rule.lang and dir (DESIGN.md section 8).Label sizes and floors differ by surface.
| surface | label sizes | floor |
|---|---|---|
| deck sheet, 1600 by 900 | 20px in --ink-2 (svg.dia text), 26px in ink at weight 500 (.lab) | 18px (.sm); any text under 15px on the sheet is a defect |
| blog graphic, 1600 by 900 stage shown at 0.44x in the article column | label chips 28px Inter 600; measurement labels 28px Geist Mono on a backing pill | 26px after zoom-to-fit (MIN_TEXT in graphics/build/gen-lib.js, held by pnpm graphics:audit) |
| film, 1920 by 1080 | the deck ladder at 1920 in motion/kit/tokens.css (.t-cap 18 to .t-display 106) | 18px |
| page | the page's own type tokens | the page's smallest label token, at the narrowest width the figure renders (check 390) |
Size after scale. A viewBox that does not render 1:1 scales its text with it. Compute the rendered scale and size the text so it lands on the target. Slide 31's 800-unit viewBox renders at 0.914 in its column, so its labels are set at 22px and land at 20.1px; the slide records that in a comment beside the rule. scripts/figure-check.mjs in this skill prints every label's rendered size. In a blog graphic a composition wider than 90% of the frame is scaled down with its labels, so the fix for a small label is a narrower composition.
The doubled line is the brand's connector. It is one path stroked twice: a full-width stroke in ink, then a narrower stroke in the surface color on top, which leaves two parallel threads at a constant gap along any curve (DESIGN.md section 5). Kevin asked for it in diagrams on 2026-07-29 ("the double line 'adidas' like aesthetic of gt").
--thread-gauge: 1.5px per thread, --thread-gap: 3px between them. The thread layer is 6px wide and the core is 3px. Use the tokens; do not invent gauges.DoubledLine (src/components/shared/diagrams/DoubledLine.tsx) takes d, core (required), gauge, gap, ink, inkB with splitD, and children for the pulse. In markup the same three layers are classes, as in .tf-thread, .tf-pulse and .tf-core in src/app/d/toolchain/diagrams/flow.css.core is the actual surface behind the drawing (var(--tc-plate), var(--tc-panel), var(--color-ink)). A core in another color shows as a painted stripe.d is rewritten to the slice under the window on each tick. It never moves by stroke-dasharray, which drifts under a stretched viewBox. It ships hidden in CSS, and under reduced motion it never starts.splitD, the same center path closed off one side of the viewBox. The seam lies inside the core's gap on every bend.references/doubled-line.md has the code, the pulse helpers and their timing, the two-tone recipe, the sizes per surface and the auditor's allow list for doubled lines drawn in CSS.
Every line has one owner (DESIGN.md section 2).
non-scaling-stroke; slide 30 draws a 15-unit plus at stroke-width 1.5 in ink; the sheet's corner crosses are 11px in --cross.preserveAspectRatio="none" lets a connector strip fill a fluid column; TranslationFlow's fork is a 72 by 260 viewBox stretched between two panels. Under that stretch:
vector-effect: non-scaling-stroke;So a stretched SVG holds straight taps and no text. The sign-in workflow diagram replaced its quadratic connectors with straight taps for this reason (k-pages-restart-round, 2026-08-11). TranslationFlow's fork predates that round and still bends its branches with cubic curves; figure-check.mjs warns on each one, and a new stretched strip uses straight taps. Labels for a stretched strip live in HTML beside it. Under 620px TranslationFlow hides the fork and stacks its panels; a figure that cannot hold its shape on a phone reflows and is never squashed.
Dashes and draw-ons carry their own traps (DESIGN.md section 9; gt-motion section 7 and its references/traps.md hold the full list). The ones that decide how a diagram is built:
non-scaling-stroke and ignores pathLength there. A progress arc drops both and writes the dash in user units: stroke-dasharray: calc(var(--dial) * <perimeter/100>px) <perimeter>px.non-scaling-stroke. Move a pulse as geometry, or translate a short <line> in user units. The Locadex connector pulses keep a pathLength dash and drop non-scaling-stroke from the pulse paths, which holds because that SVG scales uniformly (src/app/d/production/sections/Locadex.tsx).dasharray d (L - d)).story timeline in src/app/d/_v0/sections/FullStack.tsx). The svg-dash-gotchas note states the reverse; the code and a headless Chromium check (offset 50 inks the first half of a 100-unit path, -50 the last half) agree with this rule. When a draw-on runs backwards, read the first point of d before touching the timing.d (el.dataset.traceD) before an animation rewrites it, and guard an empty path: getPointAtLength throws on one (Kevin, 2026-07-30).currentColor, so dark mode is a token remap and the drawing needs no second version (DESIGN.md section 1). IsoFrame and the toolchain family take no color props; a page themes the whole family from one ancestor.figure-check.mjs fails a figure whose box or label count differs between themes.core a token that remaps with the ground; a literal paper core shows as a white stripe in the dark theme.<mask style={{ maskType: 'alpha' }}> holding the mark's image (public/brand/no-bg-gt-logo-light.png; gt-logo-light.svg may carry a background), with a currentColor or token-filled rect drawn through the mask, so the mark takes the surface's ink in both themes. gt-isometric section 5 has the construction, the asset for each size and the seat inside a plane() face. The Locadex diagram carries the Locadex mark and is never a gif (Kevin, 2026-08-04), and gt-ui's no-gif-mark rule fails a gif used as a mark. The deck pastes marks as currentColor markup from public/marks and never redraws them.data-theme flip (DESIGN.md section 11).data-dark twin where one exists in deck/shots/, and otherwise keeps a 1px --hair border. Blog covers ship in a dark and a light version at the same framing.--tc-panel white), so check every dark plate in the light theme.scripts/lint-lines.mjs reconstructs lines from computed CSS and returns early for any element inside an svg or a canvas. A figure's strokes, junctions and crossings are therefore checked by eye at 2x crops of the junctions in both themes (DESIGN.md section 2).
scripts/figure-check.mjs in this skill does the capture and the checks a script can make. It needs playwright-core (a Prototemplate dependency) and a Chromium (the Playwright build, or installed Chrome through --chrome or CHROME_PATH), and runs from the Prototemplate root. A copy of the skill outside a Prototemplate checkout needs playwright-core installed where it runs, and deck-page.mjs then takes --deck <path to deck/>. Crops are x,y,w,h in CSS px from the figure's top left corner:
# the DoubledLine plate on /docs, with a 2x crop of the merge where the two forks join the trunk
node skills/gt-diagrams/scripts/figure-check.mjs http://localhost:3005/docs \
--selector .ptc-threads --crop 350,105,90,70 --out /tmp/gt-fig
# the same figure on a phone
node skills/gt-diagrams/scripts/figure-check.mjs http://localhost:3005/docs \
--selector .ptc-threads --width 390 --height 844 --out /tmp/gt-fig-390
# a deck slide: assemble the deck with its fonts, then open slide 30 in present mode,
# where CSS px are sheet px, with a crop of the first border cross
node skills/gt-diagrams/scripts/deck-page.mjs /tmp/gt-deck.html
node skills/gt-diagrams/scripts/figure-check.mjs "file:///tmp/gt-deck.html#30" \
--selector '#stage .slide.is-on svg.dia' --width 1600 --height 900 --press p --min 18 \
--crop 430,40,40,40 --out /tmp/gt-fig-deckIt writes the figure and each crop at 2x for light and dark, prints each SVG's viewBox and rendered scale, and fails on a label under --min, a label that is rotated, skewed or stretched, a stroke in a stretched viewBox without non-scaling-stroke, a dash that relies on pathLength under non-scaling-stroke, and geometry that differs between themes. It warns on curves in a stretched viewBox. It cannot judge a junction; read the crops.
Read each crop at full size and check:
The surface's own gates run too: node deck/shoot-slide.mjs <n> for a slide (both themes, with an overflow report; it loads playwright-core from a gt-cloud worktree path and a Chromium path on Kevin's machine, so on another machine use deck-page.mjs with figure-check.mjs), pnpm graphics:audit for a blog graphic, pnpm check:pages for the route, and pnpm lint:lines:shell for the chrome around the figure.
non-scaling-stroke where the SVG scales, square caps, no rounded joins.currentColor; no raw hex in a page component, including SVG attributes the lint does not read.pathLength under non-scaling-stroke.figure-check.mjs passes and its 2x junction crops were read in light and dark.Related skills: create-graphics (choosing a route for a figure outside these surfaces), agent-browser (captures and live checks), design-engineering-polish (the final visual pass), hyperframes-animation and hyperframes-keyframes (diagrams that move in a film), animated-component-libraries (before hand-building a chart or interaction). In this set: gt-isometric (isometric objects and seated marks), gt-dither (Bayer grounds behind a figure), gt-deck (slide layout), gt-graphics (blog graphics), gt-motion (lines in motion and the full dash traps) and gt-films (diagrams in a film), gt-lints (the line law and its auditor), gt-voice (label and caption copy). The wiki's diagram-to-html is not used for GT work: its Nacelle type, 700 weights and uppercase labels conflict with the brand's type rules (Inter at 400 and 500, sentence case).
DESIGN.md sections 1 (the four-color system), 2 (the line law, crossings, the auditor's blind spot for SVG), 3 (the rails), 5 (the doubled line), 6 (the isometric family), 8 (the moving type law), 9 (motion discipline, the dash gotchas), 11 (engine lifecycle).deck/DECK-GRAMMAR.md (Type, Color, Diagrams, Dark mode, What a defect is); deck/slides/30-lines.html, 31-doubled-line.html, 33-diagrams.html, 35-iso.html, 76-line-law.html; deck/parts/head.html (svg.dia rules, --cross); deck/shoot-slide.mjs.src/components/shared/diagrams/DoubledLine.tsx, DiagramFrame.tsx, diagrams.css, src/components/shared/FeatureBento.tsx..oxlintrc.json and scripts/oxlint-plugins/gt-ui.ts (no-hex-colors, no-gif-mark); scripts/lint-shell.mjs (TARGETS).src/app/d/toolchain/diagrams/TranslationFlow.tsx, LocaleRouting.tsx, flow.css (.tf-pulse in --tc-ink), lang/lang.css (.lang-cr-pulse in --lang-accent), EdgeGlobe.tsx, SdkStack.tsx, SdkLedger.tsx, StatRow.tsx, IsoFrame.tsx, IsoSolid.tsx, tc-ctx-layers.tsx, tc-stack-iso.tsx, lang/*.tsx, surface/*.tsx; src/app/d/toolchain/styles.css (--thread-*, the accent's six places); src/app/d/_v0/sections/StackTower.tsx (taps, buried leader ends, founder notes) and FullStack.tsx (the taps' -101 park); src/app/d/production/sections/Locadex.tsx (seated marks, the pulses without non-scaling-stroke) and Global.tsx (GlobeAtmosphere).src/app/craft/ThreadsDemo.tsx, CraftArticle.tsx, libraries.ts, craft.css; docs/LIBRARIES.md; docs/GRAPHICS.md (Sizing); graphics/build/gen-lib.js (MIN_TEXT, line, elbow); scripts/lint-lines.mjs (ALLOW, the SVG skip); BRAND.md sections 6 and 9 (mono as an instrument voice, the avoid list).motion/MOTION.md (Round 4 direction, Color, Type, Line), motion/films/blog-fuma-nama/index.html (doubledH), motion/films/blog-designing-docs/index.html (F.doubled, the casing); local and untracked, owned by the Videos session.apps/landing/src/components/landing/shell/engine.css (--thread-*); .agents/skills/gt-landing/SKILL.md and references/design.md (the landing's diagram files).skills/engineering/create-graphics/SKILL.md; skills/misc/diagram-to-html/SKILL.md.references/traps.md (dash traps).The folder skills/gt-diagrams as agents fetch it. Each file opens raw.
SKILL.mdreferences/components.mdreferences/doubled-line.mdscripts/deck-page.mjsscripts/figure-check.mjs