How this site is drawn
Everything below is the real thing, with one captioned exception. The color sheet prints both themes at once, the ramp wears the real classes, and the controls are the controls. Nothing here is authored twice — every value on this page is read out of the block that declares it, which is why the page can tell you what each color is and still not be able to go out of date. Change the theme and the page re-tones around a record that does not, which is what keeps both columns readable in either one.
Colors
The tokens below carry the whole design. Each is defined twice — once per theme — and nowhere else, so a design is on-brand exactly to the degree it reaches for these and nothing else. A mark meant for an ink-flooded surface is drawn on one here, because showing it against the page ground renders the pale half of every pair as a mistake. Each token has two keys in this document, {colors.light-accent} and {colors.dark-accent} for one token; in CSS it is var(--accent), and the live theme decides which of the two you get. Guidance below cites the light key, because a reference has to name one and light is what this site serves by default — the instruction is about the token, never about that theme.
Set data-theme, or nothing is styled
Every token is defined only under the two theme blocks — there is no bare :root fallback — so a page without the attribute resolves every color to an invalid value and renders unstyled text on unstyled ground. Both themes are equal citizens and every design has to work in each; light is what the site serves by default. Put it on the root element:
<html data-theme="light"><html data-theme="dark">
--backgroundlight#FAFAFAdark#111111the page ground
--card-backgroundlight#F5F5F5dark#171717a card's plate, one step off the ground
--card-borderlight#E5E5E5dark#2C2C2Cthat plate's edge
Do
- Reach for the token whose role matches what you are drawing, not the one whose color you like.
- Design in both themes. Every token is defined in each, and several swap polarity rather than merely darkening.
- Use an -on-ink variant on a surface flooded with {colors.light-text}, which is the only place it is right.
Don't
- Hardcode a hex, even one printed here. Only the token name carries BOTH values, so a literal is right in at most one theme and wrong in the other.
- Reach for {colors.light-brand-ink} to draw something interactive. That is {colors.light-accent}'s job, and the two only coincide in the light theme.
- Assume dark is light with the lightness inverted. {colors.light-progress-fill} and {colors.light-progress-track} deliberately trade places.
Typography
A deliberately short ramp. There is no webfont and no display face — the system sans stack is the typeface, and restraint in the ramp is what carries hierarchy instead. Each step below is set in the size it names.
- text-3xl
How this site is drawn
- text-xl
How this site is drawn
- text-base
How this site is drawn
- text-sm
How this site is drawn
- text-xs
How this site is drawn
Do
- Carry hierarchy with size, weight and space, taken from the ramp as it stands.
- Let the reader's own text size drive the layout: every breakpoint and every box here is sized in rem for exactly that reason.
- Space sibling groups with a gap on a flex or grid parent.
Don't
- Introduce a decorative or display face. There is no webfont to pair one with.
- Invent an intermediate step because something is a little too big.
- Pin a height in pixels. Text that grows then clips instead of pushing.
Brand Mark
A sunrise over a bar, and the bar is not ornament: it is filled to how far this year's two goals have come, averaged, so the mark moves as the year does. That makes it the one drawing here the rules below about quantities do not govern. Those rules ask that a bar name its scale in words the reader meets first, and the smallest place this mark appears is a browser tab, which has room for no words at all — so it is an identity device whose proportion happens to be measured, rather than a quantity offered for reading. Where there IS room to say so, it is said: the mark in the home page's own heading carries the figure and its scale in its accessible name. One drawing serves every size below. The rays are its thinnest ink and are the first thing to close against the dome as the box shrinks; at the smallest step they are barely ink at all, and that is accepted rather than answered with a second drawing to keep in step.
- 120px
- 48px
- 24px
- 16px
- light
- dark
- --brand-ink
- The part of this year that has been ridden and run — the two goals' own fractions, averaged so a strong year in the shorter sport is visible rather than rounded away by the longer one.
- --progress-track
- The whole year, which is what the filled part is measured against. It is the quieter of the pair in both themes, so the region that stands further from the ground is always the one that has been earned.
The bar on every mark above is at 61% today, and it moves with the kilometres rather than with a redraw. The one exception is the icon file the oldest browsers fall back to, which is a raster frozen at the proportion the mark was designed at.
Do
- Fetch the mark rather than redrawing it. /brand/mark.svg carries its own dark-mode block and is what a browser should be pointed at; the pinned light and dark files beside it are for a consumer that cannot evaluate CSS at all.
- Draw it in {colors.light-brand-ink} over {colors.light-progress-track}, which is the one place this palette's brand ink is spent on identity rather than on a flourish.
- Let it take its size from the font-size of whatever contains it, the way every mark here is sized, so it grows with the reader's text instead of being pinned beside it.
Don't
- Recolor it. The ink token is the only one carrying this mark in both themes, so any other choice is a different mark in one of them — and {colors.light-accent} in particular would claim the mark is interactive.
- Draw it where a control's own mark belongs. The way back in a page header, and the way home on a missing page, are their control's signifier; identity is not, and putting it there adds a thing to press that does nothing.
- Take /favicon.ico as the live mark or redraw the mark from the geometry printed here. That file is a raster fallback, frozen at the proportion the mark was designed at; the geometry is published so a consumer can lay the mark out, not so the drawing can be retyped and quietly stop agreeing with this year.
Controls
Which kind to reach for is decided by what the control CONTAINS and by how loud it should be. The surface belongs to the kind rather than to all of them: the offset plate under an --accent hairline is the mark for a page's ONE action and is spent on nothing else, and a quiet hairline at a fraction of the ink is for chrome — getting somewhere, and setting a preference. So there is no plated box for a mark alone: an action names itself in words, and a control that is only a glyph is a member of a set or a preference, which is the quiet kind. Every specimen below is a working link to the page it names.
- My eventscontrol-ctaThe plated surface at the width of whatever contains it, holding a label and its mark centered together as one legend. It is the mark for a card's ONE action and is spent nowhere else. Its label comes from data and must be allowed to wrap, so its height is floored rather than pinned. The intro card's way in, and each goal card's way out.
- My running eventstext-linkA link that is a run of words inside a sentence or a column of figures. Each role card's company name.
- CyclingchipThe quiet surface, holding a label that names it in a word. For getting somewhere and for setting a preference — chrome rather than a page's one action, so it wears no plate. Its label comes from data, so its box is floored rather than pinned. The patch wall's filter row, and every item in a page header.
- Runningchip-iconThat same quiet surface holding one mark, for a member of a set where the marks are the vocabulary, and for a preference. Its content is a glyph the design picked the size of, so its box is pinned. The intro card's row of destinations, and the theme toggle wherever it appears.
Do
- Give every link a signifier a reader can perceive: an underline, a mark, or a border.
- Let a labeled control wrap. Its width belongs to its container; its height belongs to its text.
- Draw the press, and snap it. A tap is over long before a 300ms color ramp finishes, so a pressed state must not ease.
Don't
- Reach for a surface class. control-surface and chip-surface are source-level shortcuts the boxes compose, nothing wears either directly, and neither is in the shipped stylesheet.
- Draw a link exactly like the prose beside it.
- Pin a control's height in pixels. The box is sized in rem so that it grows with the reader's text.
Iconography
Every mark here is in the stylesheet because some page uses it, so the set grows with the site rather than ahead of it. Nothing outside this set is available: a class with no rule renders as a mask box at zero size, which is an absent icon with correct markup and a green build.
- i-fa6-brands-github
- i-fa6-brands-instagram
- i-fa6-brands-lastfm
- i-fa6-brands-linkedin
- i-fa6-brands-strava
- i-fa6-brands-telegram
- i-ri-arrow-left-line
- i-ri-arrow-right-line
- i-ri-file-list-3-line
- i-ri-file-pdf-2-line
- i-ri-heart-fill
- i-ri-information-line
- i-ri-markdown-line
- i-ri-moon-line
- i-ri-riding-line
- i-ri-run-line
- i-ri-search-line
- i-ri-sun-line
- i-ri-tools-line
Do
- Size a mark with font-size. They are background images scaled to the text box.
- Pair a mark with a word wherever the mark alone would be a guess.
- Give an icon-only control an accessible name, since the mark is the whole control.
Don't
- Substitute an emoji for a mark that is not in the set.
- Mix another icon family in. The ones that ship do different jobs and were chosen against each other.
- Recolor a brand mark away from what the surface it sits on needs for contrast.
Status Indicators
A dot with a halo, worn by whatever is happening now: the card that says what its owner is doing at the moment, and the week of the training year still in progress. The dot is the whole indicator. The halo pulses, and only for a reader who has not asked for less motion, so a still frame, a screenshot and a printout all keep the dot and lose nothing. It is geometry rather than a glyph, in colors of its own, and it says one thing — this one is live — so the word beside it is what says which thing that is.
Now
Do
- Put it after the name of the thing that is live, on that name's own line. The dot says now; the name says what, and a dot that has drifted from its name is a dot beside the wrong thing.
- Hold the dot at the contrast a status graphic needs against the surface it sits on. It is neither text nor a control, so neither of those floors reaches it on its own, and it once borrowed a decorative plate's color and sat under the floor for as long as nobody measured.
- Say in the markup what the dot says on screen. A current item is a state the accessibility tree has a word for, and a listener meets that word on the row where a sighted reader meets the dot.
Don't
- Tint it with the interactive affordance. A status and an affordance are different claims, and a status colored like a control is a thing that asks to be pressed and does nothing.
- Let the halo carry anything. It is the one thing here that keeps moving after the page has arrived, so it is the first thing taken away — by a motion preference, a still capture, a printer — and whatever it carried goes with it.
- Wear it twice in one view. Now is one moment; a page with several live dots on it has stopped saying which thing is happening.
Data Visualization
A quantity is drawn as a flat two-pixel stroke: a marked region over the remainder, and nothing else in the box. The same stroke answers two different questions, and which one it is answering is the whole of what a reader has to be told. A FRACTION is drawn once and measured against a target the design chose — how much of a year's goal is banked. A SERIES is drawn many times and measured against its own largest member, so the lengths are comparable to each other and to nothing outside the set: that is what makes a ramp, a taper and a gap visible, and it is the one thing a grid of cards cannot show however the cards are sorted. The brand mark's bar is the one drawing this section does not govern, and the Brand Mark section says why.
Do
- Say what a bar is measured against, in words the reader meets before the bars. A length means nothing until the scale is named, and a target and the largest value in the set are two different pictures drawn identically.
- Give the marked region more contrast against the surface than the unmarked remainder has. Whichever region stands further from the ground is the one a reader takes for the mark, so a bar drawn the other way round reads as full when it is empty.
- Draw a value that does not exist yet as an absence rather than as a zero, and print the word for it. A quantity nobody has measured and a measured zero are different facts, and the drawing can only separate them by leaving one of them undrawn.
Don't
- Split one bar into segments a reader can only separate by hue. Two categories at the same lightness arrive as one bar in two indistinguishable halves; give each category its own view and let the reader choose one.
- Put ink inside a bar. The fill flips polarity between the themes, so a label on it has to be legible against both poles, and the same words have more room beside the bar than on it.
- Let the drawing be the only carrier of a figure it encodes. Print the number as well: a bar is a shape, and the reader who cannot resolve the shape is the reader who most needs the value.
Interaction States
Every control here has states, and the states are what tell a reader that something can be pressed and that a press landed. This is the half of the system that is invisible on a desktop with a mouse and obvious on a phone: hover is not a state a finger can enter and leave, and a tap is over long before an easing curve has finished, so a design carrying its whole affordance in hover and its whole feedback in a transition arrives with neither.
Do
- Hold a press on anything that navigates until the page actually changes. The press ends when the finger lifts and the reader then waits, with nothing on screen saying the tap landed.
- Draw keyboard focus on every device, and draw it apart from hover. Hover is a pointer's affordance and some readers have no pointer; focus is how anyone driving the page from a keyboard knows where they are.
- Honor a reduced-motion preference. A reader who set it is saying that movement costs them something, so the design has to still work with every transition taken out.
Don't
- Write a hover style a touch device will apply. A touch browser puts hover on whatever was tapped and leaves it there until something else is tapped, so an affordance carried by hover arrives as a state stuck on the last thing the reader touched.
- Put a hover rule and a focus rule in one selector list. One is a pointer's affordance and the other is a keyboard indicator every device needs, so suppressing the first takes the second with it.
- Carry information in motion alone. A still frame — a reduced-motion preference, a screenshot, a device that dropped the animation — has to say what the moving one said.
Voice & Tone
The words in this interface are design material, and this vocabulary is decided rather than inherited. A control's label is the name of the thing it opens; two states that share a treatment are told apart by the word each one prints; and the word for a thing somebody finished is not the word for the set it belongs to. Every one of those was learned by shipping the other version first.
Do
- Name a destination with the same words at both ends. A control that says one thing and opens a page headed with another breaks the vocabulary at the click, which is the moment a reader is least able to absorb it.
- Where two states share a treatment, let the word carry the difference, and print it where the reader is already looking rather than somewhere they have to go and find it.
- Say what a thing is in the reader's terms rather than the system's. A name that only makes sense once you know how the data is stored is a name every reader has to be taught.
Don't
- Use the word for the earned thing as the heading for the whole set. A page listing everything that was entered cannot be headed with the word for the ones that were finished.
- Leave two states that share a treatment with no word between them. The treatment can say that neither of them is the finished thing, and nothing but a word can say which of them this one is.
- Let a label change between the control and its destination. Two strings that have to agree are one string, and a label that does not fit is shortened in both places at once.
Accessibility
Reaching and reading, which is the one subject here that is not about drawing. A design is finished when somebody can get to every part of it with a finger, with a keyboard, at the text size they chose, and with the colors replaced — and every one of those is a different reader rather than the same one described again.
Do
- Give every control a target a fingertip can find, on both axes. Something comfortable under a mouse can still be a target a thumb misses, and the two dimensions fail separately: a wide, thin row is the usual one.
- Put one landmark around each region a reader might skip to, and make the page's own name its first heading. Skipping is how a page is read without being seen, and it only works on regions that were declared.
- Let a reader double the text without the page seeing a font-size change. That size is the reader's own setting rather than an input the design gets to read, so every box has to survive the result already.
Don't
- Let reading order drift from visual order. A keyboard meets the markup, so a column moved by the layout is still read where it was written.
- Depend on a color surviving. A forced-colors mode replaces every one of them, so whatever a color alone was carrying arrives blank.
- Hide from the accessibility tree something a sighted reader can act on. A control nobody can name is a control only some readers have.
Share Cards
A square image posted beside an activity on somebody else's platform, drawn from these tokens and wearing this mark. It is the only thing this system draws that will be read somewhere it cannot control: about a third of its drawn size, in a feed, under a chip the platform puts there itself. So the constraints are the container's rather than the design's, and the guidance below is mostly about them. One rule here is not, and it is the one that generalizes: a card and the text posted with it are two renderings of one dataset, and the discipline that keeps them honest is that no fact appears on both.
Do
- When one dataset feeds two surfaces, let no fact appear on both. They share a join key and a citation; everything else belongs to exactly one of them, because a fact told twice is a fact that can drift and nothing makes the two renderings of it agree.
- Size the type for where the card is read rather than where it is drawn. It arrives at about a third of its drawn size, so a step that is comfortable at full size is decoration by the time anybody sees it.
- Say where a drawing's data came from, in the drawing's own words. A figure shaded from a published list and one shaded from a category look identical, and only the sentence beside them separates a measurement from an assumption.
Don't
- Put identity where the container draws its own. The platform overlays an attribution chip on the top-left of every activity photo, so a mark there is a mark nobody ever sees.
- Make the card taller than it is wide. The feed's carousel crops a portrait, which hands the choice of what to lose to somebody else.
- Move a fact into the image to make it fit. There is no alt text on the other side, so anything drawn rather than written is unreachable to a reader who cannot see it.