Skip to content

Brand & UI profile

The design system for prompt2md: the story, the marks, the tokens, and the rules. Everything here is implemented in apps/web/app/globals.css — this document explains why, the CSS is the source of truth for what.

1. The story: The Fold

Every competitor in this category cuts. They truncate, they drop the middle, they "summarize" and hope you don't need what left. Cutting is destructive and irreversible — and none of them show you what it cost.

prompt2md folds.

Folding makes something smaller without removing anything from it. Unfold it and you have the original, exactly. Cutting is not reversible. Folding is.

That is not a metaphor bolted on after the fact — it is literally the architecture. The original is stored content-addressed before any transformation, every summarized section carries a p2md:src anchor, and retrieve_original returns the byte-exact source. The product's central promise and the brand's central image are the same thing.

Everything downstream follows from it:

Story elementProduct truthDesign consequence
Folding, not cuttingLossless compression, anchors, retrieve_originalThe mark is an accordion fold
Paper, not screensMarkdown is text; text is paperWarm ink substrate + grain texture
The ledgerHonest, reproducible token accountingNumbers are the hero, tabular and large
Magic, but shown"A Markdown Magic" — yet every claim is auditableRestrained motion; no mystique without proof

Tagline: A Markdown Magic (fixed — do not reword).

Voice: plain, exact, unhedged. Report numbers, never adjectives about numbers. "150 → 120 tokens (80%)" — never "dramatically smaller". If a number isn't measured, it doesn't appear.

Two claims that must never be blurred

This is the easiest way for this project to become dishonest, and it has already happened once (the launch image claimed "98.3% saved · LOSSLESS"):

ClaimWhat it meansWhere it is true
ReductionThe output is smaller than the inputEvery run, and the size varies enormously by input
LosslessnessThe source is stored and recoverable byte-for-byteAlways — via p2md:src anchors and retrieve_original

Losslessness never means the output retains everything. A summary necessarily drops detail from the output; the guarantee is that the detail is still retrievable, not that it is still present. Putting a large savings percentage and the word "lossless" side by side implies the first came free of the second, which is false.

Never present a best-case number as typical. Real measured figures span a wide range, and the number depends far more on the input than on the tool:

State percentages as a share of the input, never bare. "81%" next to a compression figure reads to almost everyone as "81% saved", when it means the output is 81% of the input — a 19% saving. That misreads four-fold in our favour, so the unit is always named.

InputTokens in → outOutput as share of inputNote
Rambling chat prompt150 → 12080% of inputDeterministic path, no LLM key
Rambling chat prompt, structured150 → 12785% of inputThe +7 is the cost of Goal/Requirements headings
HTML article fixture363 → 25771% of inputChrome and nav stripped. NB: the input is already-converted Markdown, not raw HTML — see below
Markdown doc, budgeted1,834 → 1,48181% of inputcompress --token-budget 500
Daily Digest79,497 → 1,3632% of inputSelection + summary of raw JSON feeds — not compression of the same content
Same doc, repeat call1,834 → 164 effective9% of inputCache-aware layout. The strongest honest figure this project has.

The HTML row measures the wrong stage, and must not be quoted as an HTML→Markdown saving. 363 tokens is far too small for a real article's raw HTML, which means the input was already converted by the engine before our measurement began. The large markup-stripping win happens in MarkItDown or Docling, upstream of us. To claim it, measure from raw bytes end to end — until that exists, the industry's 60-90% figure is not ours to cite.

The digest figure is the largest and the least representative: most of the 79,497 is JSON scaffolding nobody wanted, and the output is a curated brief rather than a smaller version of the input. Quote it only with that label attached.

The third claim that must never be blurred: Markdown is not the saving

Markdown syntax does not reduce tokens. It costs tokens. Adding #, **, and - to clean prose makes it larger, by roughly 5-15%. Anyone who says "convert to Markdown to save tokens" is either wrong or is silently comparing against HTML.

Our own measurement says so plainly: the same rambling prompt lands at 120 tokens as cleaned prose and 127 tokens with Goal/Requirements/Constraints headings. Those 7 tokens are the price of structure, and SAMPLE_CONVERSION in apps/web/lib/facts.ts deliberately quotes the 127 — the number that has already paid that price.

Where the reduction actually comes from, in order of size:

MechanismAgainstTypicalWhy
Stripping markupHTML, PDF, DOCX, JSONlargeTags, styling, and layout scaffolding carry no meaning for a model
Removing redundancyRambling prosemoderateRepeated sentences, hedges, meta-commentary, pleasantries
Summarizing the middleOversized contextlargeHead and tail stay verbatim; only safe middle prose is condensed
Markdown formattingClean plain textnegativeSyntax is characters, and characters are tokens

So the honest sentence is: the savings come from removing what the model does not need; Markdown is the structure the remainder is kept in. Structure is worth its small cost because it survives compression legibly and because an LLM parses it more reliably — not because it is smaller.

Never write copy that implies the format is the mechanism. "Convert to Markdown and save tokens" is banned. "Token-optimized Markdown" is fine: it describes Markdown that has been optimized, which is what the pipeline produces. The product also refuses to make things worse — convert declines a result larger than its input and warns layout-skipped, and compress is measured never to grow (44 → 43). Keep that guarantee, and the claim stays true even for input that was already clean.

2. The mark

The icon is an origami crane: the fold, made into a figure. Fold a sheet into a crane and every square millimetre of the paper is still there; unfold it and you have the original sheet, exactly. That is the product's guarantee drawn as an object, and it is a symbol no letterform can be mistaken for.

Construction: a flat violet badge, paper-white facets, and crease lines that are simply the badge showing through the gaps between facets — the fold drawn in two colours. One dominant wing, a steep neck, a low tail, and a single ink accent at the beak. The beak kink is what makes the silhouette a crane at a glance; the dominant wing is what keeps it from reading as a crown at 16 px. No gradients anywhere, no outline, no ornament: the fold is the whole idea, and the magic stays in the tagline.

  • apps/web/public/brand-icon.svg — badge mark, square, for favicons, avatars, tool listings

  • apps/web/public/logo.svg — horizontal lockup (mark + wordmark + tagline)

  • apps/web/app/icon.svg — Next.js favicon route

  • apps/web/components/CraneVideo.tsx — the mark, made real: the crane modelled and rendered as an actual papercraft object in Blender (blender/build_crane.py), swinging gently beside the hero headline. Baked onto the page's own paper colour at render time, so the loop is a plain <video> — no WebGL, no runtime 3D dependency, works everywhere a video tag does.

  • blender/build_crane.py — the render pipeline. Four lessons paid for by failed renders, worth keeping if this is ever touched again:

    1. Model the object, don't extrude the icon. The first version mapped icon.svg's 2D facets onto near-coplanar panels with small tilts. That renders fine head-on and collapses to an invisible sliver at 90°. The 3D crane is built from real anatomy instead — a centre keel front to back, wings spreading to both sides with dihedral, neck forward, tail back, left half mirrored — so no viewing angle degenerates. The side view still matches the flat mark, because the flat mark was drawn from a crane's profile in the first place.
    2. A uniform material colour lit by ordinary lights reads as grey plastic regardless of intensity. Give every facet its own emission-based hex floor (white / lavender / ink, the flat mark's own scheme) instead of trusting light to create colour.
    3. A visible backdrop and the ambient light hitting the object are the same world-background value unless separated — colouring the world background to match the page baked the crane out to solid white. Fix: a plain emissive backdrop plane with Base Color black (zero diffuse response to the key/fill lights, which were adding on top of its emission and clipping it to white) carries the visible colour; the world stays a low, separate, ordinary ambient light.
    4. Swing, don't spin. A full 360° turntable necessarily passes through head-on and tail-on, where a crane's wings go edge-on and the lavender body is fully occluded — it reads as a paper dart. The loop oscillates between two three-quarter views (35°–135°) instead. sin() drives it, which eases at both extremes for free and loops seamlessly. Pass --spin for a full turntable if the geometry ever changes enough to make the dead angles worth revisiting.

A cursor of its own. components/MdCursor.tsx replaces the system arrow with a small # — a markdown heading mark, the smallest unit of branding that still says "this is a Markdown tool" wherever the pointer is. It grows and inverts over anything clickable, and steps aside for the real text caret over inputs and textareas — a studio built on pasting large blocks of text cannot lose precise placement to a mascot. Only activates for a fine pointer with motion allowed; touch devices and prefers-reduced-motion keep the native cursor.

Rules

  • Minimum size 24 px; below that use the mark with the crease detail removed.
  • Never re-colour the facets; never outline; never rotate.
  • Clear space on all sides ≥ the badge's corner radius.
  • On light surfaces use the mark as-is (the badge carries its own background).

3. Palette

Dev tooling has converged on cool near-black with a violet/cyan gradient. It signals "technical" but no longer distinguishes anything. Our substrate is paper: a warm off-white page with ink text, hairline borders, and one violet accent used sparingly. Folding happens on paper, and paper is light — the story and the surface are the same thing. The gradient is retired; the minimal system is ink pills for primary actions and a single accent for identity.

Substrate (warm paper — the differentiator)
  --surface-0  #FAF9F6   page
  --surface-1  #FFFFFF   panels
  --surface-2  #F4F2ED   inset fields, outputs
  --surface-3  #ECE9E2   raised / hover
  --border     #E7E4DC   hairlines
  --border-lit #CFCAC0   focus, active edges

Ink (warm near-black, never pure black)
  --text       #17151A
  --text-muted #5F5B66
  --text-faint #8B8794

Brand (one accent — identity, never status)
  --brand      #5B3DF5   violet (the only accent; --brand-2 aliases it)

Semantic (status only — never decoration)
  --ok         #15803D   savings, success, "within budget"
  --warn       #B45309   degraded path, budget exceeded
  --err        #DC2626   failure

Rules

  • One accent per page, locked. The violet marks identity (wordmark accent, active states, the emphasized phrase); it never encodes status.
  • Primary actions are ink pills (--text on --surface-1), not accent buttons — high contrast, zero decoration.
  • --ok green is reserved for measured savings. It is the payoff colour; spending it elsewhere devalues it.
  • Ink is never pure black and paper is never pure white — both keep a warm bias so the grain reads as paper rather than noise.

4. Type

RoleStackUse
--font-uisystem sans (ui-sans-serif, Segoe UI, Roboto…)all interface text
--font-displayui-serif, Iowan Old Style, Palatino, Georgiahero line + tagline only
--font-monoui-monospace, Cascadia, SF Mono, Menloall content: input, output, numerals

A serif appears almost nowhere in developer tooling, which is exactly why a restrained amount of it signals deliberate design rather than a template. Used sparingly — the hero sentence and the tagline — it reads as considered. Used everywhere it would read as a blog. Two families of display serif per screen is already too much.

All figures use font-variant-numeric: tabular-nums so digits don't jitter as values update.

5. Motion

Motion exists to explain change, never to decorate.

  • --t-fast 120ms — hover, focus, press
  • --t-base 220ms — panel and state transitions
  • --ease cubic-bezier(.2,.7,.3,1) — a single easing curve everywhere
  • Bars animate on value change so a savings drop is seen, not just read.
  • Everything is wrapped in prefers-reduced-motion: reduce, which collapses all durations to 0.01ms. This is not optional.

Scroll-scrubbed story (GSAP + ScrollTrigger, landing only). The Fold chapter (components/FoldStory.tsx) ties its animation to scroll position directly, via a CSS-sticky viewport rather than a ScrollTrigger pin — a pasted "sheet" fades into the crane facets one at a time as the reader scrolls, then the crane fades back into a sheet carrying a retrieve_original: byte-exact tag. This is the fold/unfold claim shown as motion instead of only asserted as a headline. Proof-bar numbers (components/Count.tsx) count up from 0 on first scroll into view for the same reason bars animate on change: a jump from 0 to 149 is seen.

Both are progressive enhancements over a valid static state, never the only carrier of the claim: the crane's facets and the proof numbers render correct and final in the base markup, and JS only scatters/zeroes them at mount before animating back — so a blocked script, a slow connection, or prefers-reduced-motion all land on the same complete page, just without motion. Chapter markers (components/Chapter.tsx, [ 01 / 06 ] style) are static text, not gated on JS at all.

The fold as a UI system, not one illustration. Two more pieces carry the same idea into the interface itself, not into a caption about it:

  • .paper (product.css) is a dog-eared corner — a small folded-paper triangle — applied to every card-like surface (bento tiles, code windows, the cut/fold comparison columns). The whole page reads as sheets of paper you could peel a corner off, not a features grid with a fold graphic bolted onto one section.
  • components/FoldProgress.tsx is the scroll position itself drawn as a page corner folding further down the more you've read, labelled with the chapter you're currently in (read from the same .chapter markers, so it can't drift out of sync with the content). It is reading progress and chapter navigation and the fold metaphor, as one fixed corner widget — not three separate UI elements.

Static default for both: a small resting dog-ear, and a fold-progress corner reading "Ch. 01 / 06" — correct at the top of the page even before any script runs.

6. Layout

  • Shell max-width 1240 px; the studio is a two-column workbench that collapses to one column below 940 px.
  • Stats use a bento grid — the savings tile spans wider because it is the claim the product is making.
  • Radii: --r-sm 8px, --r-md 12px, --r-lg 16px, --r-full 999px.
  • Hairline borders over shadows; one soft shadow (--shadow-lift) reserved for genuinely raised surfaces.

7. What the research changed

Findings from a survey of current leading developer-tool sites, and what we did:

FindingOur response
Dark hero is a saturated default, not a differentiatorWent the other way entirely: warm paper + ink, hairline borders, one accent — the light minimalist system the strongest current devtool sites use
Differentiate on the one claim rivals can't makeHero states losslessness + auditable numbers, not "fast conversion"
Warm colour in a cold categoryWarm ink substrate rather than a warm accent, avoiding semantic collision
Default sans reads "competent"; distinctive type reads intentionalSerif display, used sparingly
Show the product, don't describe itThe studio is the landing page — no marketing wrapper
Specific quantified claims beat aspirational copyEvery figure on screen comes from a real run
Strongest proof is a site built in its own productThe docs corpus is processed by our own pipeline; the digest is generated by it daily
Fewer than five nav links, one primary actionThree tabs, one primary button, two nav links

These are conclusions, not a survey: the underlying review is not published.

A Markdown Magic · Apache-2.0