Components

Each one is a React component on window.DeliPaper, with a dp-* class in bundle.css for pages without React. The previews below run the real bundle.

Surfaces

Card

The one white sheet on the paper: square corners, space-16 inside, card-width wide at most, centred.

Use exactly one per page and put everything readable in it. Nothing goes straight on the paper. Under 720px the padding drops to 32px by space-6.

  • Children: the Masthead, then main content.
  • as: the element, div by default.
  • Don't round it, shadow it or nest one inside another. The card is the page.

Paper

The checked deli paper every page sits on: a WebGL sheet, painted once per size and theme, that never moves.

Mount one per page, behind everything, before the Card. It is decorative and hidden from assistive tech.

  • colourway: "deli" (the portfolio: deli-ink on deli-paper), "ledger" (splitova: ledger-credit and ledger-debit checks with + and − rupee amounts that count up), "hook" (mhook: hook-ink on hook-paper), or your own { ink, paper } as 0–1 RGB.
  • amounts: print counting amounts in the checks. On by default for ledger only; it is the only colourway where the numbers mean something.
  • contained: fill the parent (position absolute) instead of the window. Use it for previews and embeds, never on a real page.
  • The consumer provides nothing else. Give each new app its own colourway rather than reusing another app's, and add its two colours to the tokens as <app>-ink and <app>-paper.
  • Without WebGL the paper falls back to the page colour, so text on the card stays legible.

Layout

Columns

Two equal columns with the space-8 gutter, stacking to one under 720px.

Use it for the opening paragraphs of a page, two to a side. Children are the columns, usually a div of paragraphs each. Text inside is set in body.

Hero

The page's one statement: a display headline, a muted lede and the actions, with the app's screens below.

Use it on app sites, not on the portfolio itself. The headline says what the app does in under ten words and wraps at 15ch; the lede is one or two sentences of measure width. One Button (ink) and at most one text link follow.

  • title, lede: strings or nodes. actions: a fragment of Buttons. Children: the figures, usually Phones.
  • Size it to its content, never to the viewport.

Masthead

The header: a stacked list on the left (the brand or socials) and the nav stacked on the right, both in nav type.

When the page scrolls the nav to the top, it pins there as one row on a card strip with a rule underline, and each link glides to its new place (duration-glide, 30ms apart, ease-out). The left list fades out as it pins, so nothing scrolls over the strip, and the brand from lead slides in at the strip's left end, lined up with the card's content. On a phone only its mark shows, and the pinned row steps down to 16px (14px under 360px) so the links stay clear of it.

  • lead: [{ label, href, brand? }]. The first item is the app: brand is its mark (an <img> or SVG) shown before the label in heading, with no tile behind it. After it, one or two useful links (socials, the beta, the reader). No byline: the app's own name is enough.
  • nav: [{ label, href, current? }], three or four links. current marks the page you are on.
  • pin: false to keep it static (previews, short pages).
  • Links are muted at rest and heading on hover or when current. The text size never changes when it pins.

Section

A light title heading and its content, space-30 below the last section (space-18 under 720px).

Give it an id when the nav links to it; it clears the pinned nav when jumped to. Headings are short nouns ("Projects", "Now", "Ways to answer"), sentence case, no colon.

  • title: the heading text. id: the anchor. style: overrides, such as marginTop: 0 for the first section under a hero. Children: the content.

Text

Code

Inline code in running text: mono at 0.85em on card-sunken with radius-sm.

Use it for names a reader would type: commands, flags, file names. Children: the text.

CodeBlock

A block of code on card-sunken with radius-md, mono at 0.9rem / 1.55, scrolling sideways inside itself.

Show a real, runnable example with the reader's placeholders in $CAPS. Children: the code as a string with its own line breaks.

Marker

The hand-drawn highlighter stroke behind the one line that matters, drawn left to right as it enters the window (duration-draw after 450ms, ease-draw).

Use one per page, two at most. It wraps across lines without breaking its shape.

  • tone: "red" (deli red, the default: the line you want read), "blue" (a name in running text, with inline), "yellow", "hook" (mhook) or "ink" (splitova and other monochrome apps).
  • inline: tighter padding for a word mid-sentence, so the text does not move.
  • draw={false}: show it already drawn.

Lists

Rows

A list of things, a title on the left and its detail on the right in muted, between rule hairlines.

The portfolio's projects, posts and links are all rows. Details are one line: a technical one-liner, a date or a handle. Under 720px the detail drops under the title, unless the rows hold figures.

  • items: [{ title, detail, href?, key? }]. A href makes the title a TextLink.
  • figures: the details are numbers or money; they align as tabular figures and stay on the right at every width.

Figures

Phone

One app screenshot in a rounded frame (radius-phone, rule border, shadow-phone) with an optional caption below in muted.

Beside text, use one at 15rem wide in a two-column grid. Screens are iPhone-sized captures (1179×2556 or similar) scaled to 640px wide JPEG.

  • src, alt (what the screen shows, not "screenshot"), caption, width/height (the image's real size, 640×1387 by default), loading ("eager" in a hero).

Phones

Three app screens side by side, space-5 apart, each a Phone; under 720px they scroll sideways and snap.

Use it in a Hero or right after one. Pick screens that show the app working with realistic data, never sign-in screens or QA text. Children: three Phones.

Money

Amount

Signed money in rupees with Indian grouping: credit with a + for money coming back, debit with a − for money owed, plain for zero.

The sign is always written, because credit and debit share a lightness and colour cannot carry it alone. With count, it grows from zero over duration-count (ease-out) when it enters the window.

  • value: a number in rupees, negative for owed. count: animate. duration: ms. currency={false}: no ₹. label: an accessible name, such as "You're owed ₹7,465".

Actions

Button

Ink is the one call to action on a view; outline is the quiet second; text is a link sitting in the same row.

  • variant: "ink" (default: heading fill, on-ink label, radius-pill), "outline" (rule-strong border) or "text" (a TextLink styled as a sibling of the ink button).
  • href makes it a link; otherwise it is a button type="button". Every other prop passes through.
  • Labels are a verb and an object, sentence case: "Get the beta", "Sign in", "Add expense". No icons unless the app's own screens use them.

A link inside running text: underlined at 35% of the text colour, with a yellow marker that paints in from the left on hover (duration-instant, linear).

Use it for every link inside the card except the header and buttons. Link the words that name the place ("suitecut", "Get in touch"), never "here". Every prop passes through to the a.

A link to another site opens in a new tab (target="_blank", rel="noopener noreferrer"); links within the site and mailto: links open in place. Pass target yourself to override it. For links you don't render with TextLink, such as Markdown, one listener does the same:

document.addEventListener('click', (event) => {
  const link = event.target.closest?.('a[href]');
  if (!link || link.target || !/^https?:$/.test(link.protocol) || link.origin === location.origin) return;
  link.target = '_blank';
  link.relList.add('noopener', 'noreferrer');
});

App screens

Badge

A word or two of state in dense app screens, such as the mhook console's "Has hooks": a pill with a dot in its tone.

Intentional addition: the portfolio has no badges; the mhook console needs them for status in tables.

  • tone: "neutral" (no dot), "credit" or "debit". Children: the words, sentence case. The words carry the state; the dot repeats it.

Field

A label over a text input, with an optional hint or error below.

Intentional addition: the portfolio has no forms; the app sites' sign-in pages need them.

  • label, id (required, links the label), hint, error (replaces the hint, turns the border debit and is announced). Every other prop goes to the input.
  • Errors say what to do: "Enter the 6-digit code from the email", not "Invalid input".

Motion

Reveal

Its children rise 14px and fade in as they enter the window, 70ms apart (duration-rise, ease-out, eight steps at most).

Wrap a section's paragraphs, rows or figures. What is already on screen shows at once, and without script or with reduced motion everything is simply visible. Don't wrap the masthead: a transform there stops the nav pinning.

  • as: the wrapper element. Children: the items that rise, one by one.