Faris Zacina

Design system · v1.0

Keyframe

The system behind this site, reverse-engineered from its own stylesheet and rendered by its own components. Named after the small black dot that taught me design is a system.

keyframe.flaScene 1
Keyframe
124 fps
1611162126313641465156
accent
word
mark
Tokens
25
Components
19
Stylesheet
38 KB raw
Web fonts
0
Blocking requests
0
00

Before the system

From graphic design to Flash, Flex and rich internet applications, and why a blog has a design system at all.

I started out as a graphic designer. Before I wrote a line of code I spent my days on type, grids and colour, nudging things a pixel at a time until a layout felt right. I didn’t know it yet, but I was learning the thing I’d keep coming back to for the rest of my career: how lots of small decisions add up to one coherent feeling.

Then I opened Flash, and the page got a fourth dimension. There was a stage, a timeline and those little black dots called keyframes. You drew where something started, you drew where it ended, you chose Create Motion Tween, and the software filled in everything in between. I was hooked. I spent whole evenings with the Ease slider, learning that the gap between “it moves” and “it feels alive” is about a hundred milliseconds and the right curve.

Flash also handed me my first component library, though nobody called it that. Every file had a Library panel full of symbols: buttons, graphics and movie clips you drew once and reused everywhere. Edit the symbol and every instance on every frame changed with it. A button symbol had its Up, Over, Down and Hit states. Movie clips nested inside movie clips, each with its own timeline. That panel taught me more about systematic design than any book: define it once, reuse it everywhere, make every state explicit.

The Library panel was my first design system. I just didn’t have the word for it.

Tweens weren’t enough for long. I wanted things to react, so I started writing ActionScript: a stop() on a frame, then an onEnterFrame handler, then Robert Penner’s easing equations like everyone else at the time, then whole classes. That’s how a designer quietly turns into a programmer. One small script at a time, because the thing you imagined needs it.

Then came RIAs, rich internet applications, and with them Flex. Flex was Flash grown up for software. You described an interface in MXML, styled it with CSS, bound it to live data, and declared its states and the transitions between them. These were applications, not banners: dashboards that animated as numbers arrived, forms that rearranged themselves, desktop apps on Adobe AIR. For someone who loved design and code in equal measure it felt like the future had arrived early, and I loved that era more than I probably should admit.

It didn’t last. In April 2010 Steve Jobs published Thoughts on Flash, the iPhone never ran it, and within a few years the platform faded. Flash Player was switched off for good at the end of 2020. I had long since moved on to JavaScript, C# and .NET, then to architecture, and eventually to building a company.

But the ideas didn’t die. They moved. Components with explicit states are how we build every interface now. A transition is one line of CSS. The kind of BitmapData.perlinNoise() experiment we all ran in Flash now lives as a two-kilobyte WebGL shader behind the introduction on this site’s home page. Even the reveals on these essays are keyframes on a timeline; the timeline just happens to be your scrollbar.

So why a design system for a personal blog? Partly because it’s how I think. I can’t build anything without seeing the system underneath it. And partly as a small tribute. This page is my Library panel. It reads the site’s real stylesheet every time it renders, and every specimen on it is the same code that renders the essays, so it can’t drift from the truth. I named it Keyframe, after the little black dot where all of this started.

01

Principles

Six rules every token and component on this site answers to.

  1. Reading comes first

    Serif-led, a 680 px measure, 20 px body type with 1.7 leading. Every component is judged by whether it helps or interrupts a long read.

  2. Zero requests

    No web fonts, no icon fonts, no framework. CSS is inlined into every page, so the first paint needs nothing but the HTML.

  3. One signal colour

    Warm paper, layered inks and a single orange that means “this is alive”: links you hover, the section you’re in, the place you’ve focused.

  4. Motion with a reason

    Things rise in, reveal on scroll and spring when touched, always under a second and switched off when you ask for reduced motion.

  5. States are explicit

    Hover, focus, open, pressed, disabled, light and dark. If a state exists, it is designed. A lesson from Flash button symbols.

  6. Documentation is the code

    This page parses site.css and calls the real renderers. If it says a token exists, it ships.

02

Colour

Warm paper, three inks, one signal. Every value below is parsed from site.css as this page renders.

Each chip shows the light and dark value. Figures are WCAG contrast ratios: text tokens are measured on paper, surface tokens with ink on top. Click a chip to copy its variable. Callouts add five tints mixed into paper with color-mix(), so they follow the theme without tokens of their own.

bluegreenyellowredpurpleaccent
03

Typography

A serif to read and a sans to operate, both from the system.

AaCharter--serifCharter, "Bitstream Charter", "Sitka Text", Cambria, "Iowan Old Style", Georgia, serif
AaSystem UI--sansui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif
AaSystem mono--monoui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace
Display.post-title650 clamp(34px, 5.4vw, 54px)/1.08 var(--serif)

The AI paradox, one year later

Intro.intro-text400 clamp(28px, 4.4vw, 44px)/1.18 var(--serif)

Essays on AI, software and building companies.

Headline.featured-title600 clamp(28px, 3.4vw, 40px)/1.12 var(--serif)
Section.gh-content h2650 30px/1.2 var(--serif)

From symbols to components

Subsection.gh-content h3650 23px/1.3 var(--serif)

States and transitions

Dek.post-excerpt400 clamp(19px, 2.2vw, 23px)/1.45 var(--serif)

A short standfirst that tells you why the next twenty minutes are worth it.

Body.gh-content400 20px/1.7 var(--serif)

Long-form text set in Charter with old-style figures (1984, 2010, 2026), real italics, bold, links, inline code and a highlighter.

Pull quote.gh-content .kg-blockquote-altitalic 400 clamp(25px, 3vw, 31px)/1.35 var(--serif)
Define it once, reuse it everywhere.
Label.gh-content h4700 15px/1.4 var(--sans)

Further reading

Kicker.kicker600 12px/1.4 var(--sans)

Design · Motion

Interfacebody17px/1.6 var(--sans)

System sans for interface: buttons, forms, navigation, metadata.

Meta.meta14px, inherited family

Oct 26, 2026 · 9 min read

Mono--monovar(--mono)

new Tween(mark, "y", Strong.easeOut, 46, 0, .9, true);

No web fonts are loaded. Charter ships with macOS and iOS, and the stack falls back through Sitka, Cambria and Iowan to Georgia, so text paints on the first frame and never reflows.

04

Layout & spacing

One measure for reading, one width for images, one gutter.

full · 100vw
wide · var(--wide) 1140px
main · var(--measure) 680px
gutter 20px

Essays sit on a named CSS grid (.gh-canvas, compatible with Ghost themes). Text takes the main track; images and galleries can step out to wide or bleed full, and floating UI (contents, share rail) steps aside while they pass.

Breakpoints in use

  1. 600×3
  2. 640×1
  3. 700×1
  4. 860×1
  5. 900×1
  6. 1200×2

Spacing, as actually used

Spacing isn’t tokenised yet. This chart is an audit: every margin, padding and gap in the stylesheet, counted. The tall bars are the de facto scale and the next candidates for tokens.

  1. 1×3
  2. 2×6
  3. 4×18
  4. 5×3
  5. 6×16
  6. 8×15
  7. 9×3
  8. 10×17
  9. 12×20
  10. 14×16
  11. 16×18
  12. 18×14
  13. 20×12
  14. 22×10
  15. 24×5
  16. 26×7
  17. 28×6
  18. 32×4
  19. 40×5
  20. 48×3
  21. 64×5
  22. 96×3
05

Shape & depth

Soft corners on a five-step scale. Shadows only for things that float.

  • --r-xs4px · used 3×
  • --r-sm6px · used 2×
  • --r-md8px · used 9×
  • --r-lg12px · used 2×
  • --r-pill999px · used 7×
  • 50%avatars, dots · 8×

Audit: 6 off-scale values still live in the stylesheet: 3px ×4, 2px ×1, 14px ×1, 10px ×2, 7px ×1, 5px ×1. Small image and highlighter corners are deliberate; the rest will fold into the scale.

  • 0 6px 20px rgba(0, 0, 0, .18)
  • 0 8px 28px rgba(0, 0, 0, .25)
  • 0 0 0 1px var(--line), 0 10px 30px -12px rgba(0, 0, 0, .35)

Depth is rare on purpose. Hairlines do most of the structural work and shadows are kept for things that float above the page: the portrait, the quote-share toolbar, the edit button.

06

Motion

Pick an easing, watch it run. Curves are drawn from the tokens themselves.

--dur-fast.15s
--dur-base.25s
--dur-slow.8s

Keyframes in the stylesheet

  • @keyframes flash:target, .comment.new
  • @keyframes grow.progress
  • @keyframes pop.quote-share
  • @keyframes rise.intro-text, .intro-links, .post-head > *
  • @keyframes reveal.gh-content > .kg-card, .gh-content > blockquote, .post-list li, .related-list li, .featured
  • @keyframes parallax.kg-width-full img

Reveals and parallax use scroll-driven animations (animation-timeline: view()), so they cost no JavaScript at all. Everything above respects prefers-reduced-motion; with it on, the page simply sits still.

07

Mark & icons

16px
26px
48px
96px
on signal

The mark is a single-colour line portrait drawn in currentColor on a 64-unit grid, so it inherits ink, flips with the theme and doubles as the favicon. Line weights stay between 1.5 and 3 units to hold up at 16 px.

  • x
  • linkedin
  • facebook
  • threads
  • bluesky
  • reddit
  • hn
  • whatsapp
  • telegram
  • email
  • link
  • share
08

Components

Live specimens rendered by the same code as the essays. Invert any one to check the other theme.

Actions

Button

Pill buttons for interface actions; the square-cornered card button is a call to action inside an essay.

.btn · .btn-ghost · .kg-btn
Source html + block
<button class="btn">Subscribe</button>
<button class="btn btn-ghost">Cancel</button>
<button class="btn" disabled>Sending…</button>

{"type":"button","text":"Download the slides","url":"…","align":"left"}

Topic pills

Live: these are this site’s real topics and counts.

.tag-cloud · .post-tags
Source html
<p class="tag-cloud"><a href="/tag/ai">AI <span>12</span></a> …</p>

Share bar

Native share sheet on mobile, UTM-tagged links everywhere else.

.share
Source js
shareBar(post, url)  // lib/templates.js

Navigation & lists

Archive row

Date, title, reading time on a three-column grid that collapses to two on phones.

.post-list
Source html
<ol class="post-list"><li><a><time/><span class="pl-title"/><span class="pl-meta"/></a></li></ol>

Timeline

Grouped by year, newest first. Here: the short life of the platform that taught me motion.

.timeline

2020

  1. End of lifeAdobe

    Flash Player is switched off

2010

2008

  1. ReleaseAdobe

    AIR 1.0 takes RIAs to the desktop

2006

  1. ReleaseAdobe

    Flash Player 9, ActionScript 3 and Flex 2

2004

  1. ReleaseMacromedia

    Flex 1.0

    MXML and ActionScript for rich internet applications.

1996

  1. ReleaseMacromedia

    Flash 1.0 ships

    FutureSplash Animator becomes Macromedia Flash.

Source json
{
  "type": "timeline",
  "items": [
    {
      "date": "1996-12",
      "kind": "Release",
      "outlet": "Macromedia",
      "title": "Flash 1.0 ships",
      "description": "FutureSplash Animator becomes Macromedia Flash."
    },
    {
      "date": "2004-03",
      "kind": "Release",
      "outlet": "Macromedia",
      "title": "Flex 1.0",
      "description": "MXML and ActionScript for rich internet applications."
    }
  ]
}

Essay blocks

Callout

Seven colours. Tints are mixed into paper, so they work in both themes.

.kg-callout-card
Source json
{
  "type": "callout",
  "color": "blue",
  "emoji": "🧭",
  "html": "…"
}

Quote & pull quote

blockquote · .kg-blockquote-alt

Flash was created during the PC era – for PCs and mice.

Define it once, reuse it everywhere, make every state explicit.
Source json
[
  {
    "type": "quote",
    "html": "…",
    "cite": "…"
  },
  {
    "type": "quote",
    "style": "pull",
    "html": "…"
  }
]

Code

Always dark, in both themes: code reads best on a stable ground.

.kg-code-card
import fl.transitions.Tween;
import fl.transitions.easing.Strong;

// 2007: tween the mark into place
new Tween(mark, "y", Strong.easeOut, 46, 0, 0.9, true);
Today the same idea is one line: transition: transform var(--dur-slow) var(--ease-out).
Source json
{
  "type": "code",
  "lang": "actionscript",
  "code": "…",
  "caption": "…"
}

Key takeaways

Opens long essays. Also feeds the summary that answer engines read.

.tldr
Source json
{
  "type": "tldr",
  "items": [
    "…"
  ]
}

Toggle

.kg-toggle-card
What was a movie clip?

A symbol with its own timeline, nested inside another timeline. Components inside components, in 1999.

Source json
{
  "type": "toggle",
  "heading": "…",
  "html": "…"
}

FAQ

Also published as FAQPage structured data.

.faq

Frequently asked questions

Why no web fonts?

They are the slowest thing on most blogs. System fonts paint on the first frame.

Is this page hand-maintained?

Mostly not. Tokens, breakpoints, keyframes and the spacing audit are read from site.css each time the page renders.

Source json
{
  "type": "faq",
  "items": [
    {
      "q": "…",
      "a": "…"
    }
  ]
}

Citations & references

Numbered citations link down; references link back. Lists longer than eight fold.

sup.cite · .references

Flash Player 9 introduced ActionScript 3 and a new virtual machine1, and Flex 2 arrived alongside it2.

References

  1. Adobe Systems, “Flash Player 9 release notes”, June 2006. ↩
  2. Apache Flex, Wikipedia. ↩
Source json
[
  {
    "type": "p",
    "html": "Flash Player 9 introduced ActionScript 3 and a new virtual machine<sup data-ref=\"1\">1</sup>, and Flex 2 arrived alongside it<sup data-ref=\"2\">2</sup>."
  },
  {
    "type": "references",
    "items": [
      {
        "html": "Adobe Systems, “Flash Player 9 release notes”, June 2006."
      },
      {
        "html": "<a href=\"https://en.wikipedia.org/wiki/Apache_Flex\">Apache Flex</a>, Wikipedia."
      }
    ]
  }
]

Bookmark

Resolved once at edit time and stored locally, so readers never load anything from a third party.

.kg-bookmark-card
Source json
{
  "type": "bookmark",
  "url": "…",
  "title": "…",
  "description": "…",
  "publisher": "…",
  "thumbnail": "…"
}

File card

Live: a real download, tracked as a custom analytics event.

.kg-file-card

The AI Paradox

DOERS Summit Dubai, November 2025.

Source json
{
  "type": "file",
  "title": "…",
  "files": [
    {
      "src": "…",
      "name": "….pdf",
      "size": 2610380
    }
  ]
}

Divider

An asterism, not a line: a pause, not a wall.

hr

Source json
{
  "type": "divider"
}

Conversation

Comment thread

Sample content. Avatars are initials on a hue derived from the name; the author gets a badge.

.comment · .c-replies
  1. A reader

    I remember the Library panel! Editing one symbol and watching the whole movie update was magic.

    1. Faris ZacinaAuthor

      Exactly that feeling. Design tokens are the same trick, twenty years later.

Source js
renderComment(comment)  // lib/comments.js

Newsletter form

Live and working. Turnstile loads only once you start typing.

.subscribe
Source js
subscribeForm(s, { source: 'design-system' })

Author card

.author-card

Faris Zacina

Co-founder and CEO of Ministry of Programming, a product studio in Sarajevo and Stockholm. Engineer turned founder and angel investor.

Source js
authorCard(s)  // lib/templates.js
09

Inventory

Every block type the renderer supports. A new type shows up here as needing a specimen.

BlockStatusWhere
pSpecimenOn this page
hIn useEverywhere
quoteSpecimenOn this page
ulIn useEverywhere
olIn useEverywhere
dividerSpecimenOn this page
imageIn useAny essay
galleryIn useAny essay
codeSpecimenOn this page
calloutSpecimenOn this page
toggleSpecimenOn this page
buttonSpecimenOn this page
bookmarkSpecimenOn this page
youtubeIn useTalks & interviews
xIn useEssays
embedIn useEssays
htmlIn useRaw, unstyled by design
markdownIn useRenders to the blocks above
tldrSpecimenOn this page
faqSpecimenOn this page
fileSpecimenOn this page
timelineSpecimenOn this page
referencesSpecimenOn this page
10

Changelog

  1. v1.0

    Reverse-engineered from site.css. Radius, easing and duration tokens introduced; theme tokens scoped so any element can flip between light and dark; Keyframe documented.