Typography

Three families, a ten step ramp with eight fluid sizes above it, three named weights and six leadings. Every specimen below wears the token by name. The weights are measured by ink rather than by the number in the declaration, because a weight that has no face behind it renders as the nearest one that does and says nothing about it.

3 families 18 faces 10 + 8 sizes 1 literal size in 128

Three families, and a job each

Three is the ceiling. A fourth family would have to name a job the other three cannot do, and there is not one.

Space Grotesk, display

Will it happen?

The question, the heading, the number a person came for. Never a paragraph: its tighter fit and its wider counters are read as voice at 24px and as noise at 13.

DM Sans, body

Will it happen?

Everything a person reads rather than scans: prose, labels, buttons, rows. It is the root family, so anything that does not say otherwise is this.

IBM Plex Mono, data

38% $84,200

A number that has to line up with the number under it: odds, volume, an amount, a wallet address, a timestamp. Mono is a promise that the figure is exact, so it is never used for a word that is not a value.

The families do not overlap and that is what makes them readable as roles. A display face on a paragraph reads as a typo; a mono face on a sentence reads as code. Every crossing of that line in this product is a bug, and there are none.

Served from this repo, and 242 KB of it is the same file

Every face is a woff2 in assets/fonts/, declared once in fonts.css. No page may call a font host: the request carries a visitor's IP and User-Agent to a third party before the consent banner this product ships has asked them anything, which the Munich district court ruling of 20 January 2022 treats as a transfer of personal data. It is also three round trips on the critical path, two of them DNS and TLS to a host that serves nothing else. Latin and latin-ext only, font-display:swap on every face.

And since 2026-08-14 the four faces the product uses are not fetched at all: they are data: URIs inside fonts.css. A font is fetched in CORS mode even from its own origin, and file:// gives every file its own opaque origin, so from a page opened off the disk there was nothing for the fetch to match. Chromium refused the preload, said so in the console, and loaded the face through @font-face anyway; WebKit refused both and drew the whole product in a fallback face. Measured with a probe string at 40px from disk: 'DM Sans',serif came back at 369px, the serif fallback to the pixel, against 410 in Chromium, and 410 in both after. Only four of the eight files were ever requested by anything (163, 163, 109 and 104 times across the 163 documents, and 0 for each of the four latin-ext cuts), which is what made inlining small enough to take: +105,980 bytes of base64 against 68,725 of font requests, and the 326 <link rel="preload"> lines deleted. Layout shift re-measured at 400 Kbps over 27 screens: 0.0000 mean before and after.

18 files, 373 KB, and 131 KB of unique typeface. Checksums say why:

DM Sans
4 declared weights, 2 distinct files. 400, 500, 600 and 700 latin are byte for byte the same 36,980 bytes, and the four latin-ext the same 18,192.147,920 shipped for 36,980 of data
Space Grotesk
3 declared weights, 2 distinct files. The three latin faces are one 22,320 byte file, the three latin-ext one 18,924 byte file.66,960 shipped for 22,320 of data
IBM Plex Mono
2 declared weights, 4 distinct files, all different. This family is the one that was fetched as static faces, and it is the only one with no duplication.correct

DM Sans and Space Grotesk were fetched as variable fonts and then copied once per weight. One file carries the whole weight axis; the repository holds four and three copies of it and declares each copy at a single weight. It works, because a browser given a variable font with a single font-weight descriptor pins the axis to that value, and the ink measurements in the next section prove it works. What it costs is 242 KB of duplication, 65 per cent of the font payload, and a cache that has to hold seven copies of two typefaces.

The repair is one @font-face per family with font-weight:400 700 as a range and one file behind it. That is a value change, so it belongs to the consolidation and not to this page.

The weights, measured by ink

A declared weight with no face behind it does not fail. It renders as the nearest face that exists, silently. So the reading below is not the number in the CSS: it is the count of dark pixels the browser actually paints for one string at 48px, which is the only thing that can tell a real weight from a fallback.

Space Grotesk
Hand Hand Hand Hand ink 5188 / 5188 / 5627 / 6072 at 400, 500, 600, 700. 400 and 500 are the same face, because only 500, 600 and 700 are declared and 400 falls to the nearest. Nothing asks this family for 400, so it is correct rather than lucky
DM Sans
Hand Hand Hand Hand ink 4333 / 4886 / 5554 / 6005. Four distinct weights from one variable file. This is the proof that the duplication in section 02 is waste and not function
IBM Plex Mono
1234 1234 1234 1234 ink 5380 / 5380 / 5993 / 5993. Two faces, not four. Asking this family for 700 renders 600, and asking for 400 renders 500

--weight-bold is 700 and IBM Plex Mono has no 700. Every mono element that reached for bold would render semibold and never say so. Today none does: all five mono declarations in components/ use --weight-medium or --weight-semibold, so this is a trap rather than a defect, and it is the kind that is only found by measuring the paint.

And the most used weight in the product has no name

The ramp is --weight-medium:500, --weight-semibold:600, --weight-bold:700. 400 is not a token, and 400 is what 192 of 260 text elements on the feed render at, because it is the root default and nothing overrides it. One component already needs it and had to type it: betpanel.css writes font-weight:normal, the single weight literal in the whole system. A step of a scale that the scale does not name is a step somebody will type.

400, unnamed
The odds moved twice this week.192 elements. No token
--weight-medium
The odds moved twice this week.500. 6 declarations
--weight-semibold
The odds moved twice this week.600. 41 declarations
--weight-bold
The odds moved twice this week.700. 61 declarations

The ramp: ten fixed steps, eight fluid ones

The ramp steps by 1 up to 14, then by 2 and by 4. 10px is the floor a mobile product should set. The half pixels it used to carry, 9.5 and 10.5 and 11.5, were rem arithmetic inherited from the grey wireframe rather than sizes anyone chose; they were rounded up, so no line got smaller.

--text-10
Will the US government shut down before March 1?27 declarations
--text-11
Will the US government shut down before March 1?48, the most used size in the system
--text-12
Will the US government shut down before March 1?46
--text-13
Will the US government shut down before March 1?47
--text-14
Will the US government shut down before March 1?32
--text-16
Will the US government shut down?10
--text-18
Will the US government shut down?4
--text-20
Will the government shut down?4
--text-24
Will it shut down?2
--text-30
Will it shut down?1

The ramp is bottom heavy and that is the product. 200 of the 218 size declarations are 10 to 14. This is a dense screen of odds, volumes and dates, not an article, and the four steps above 16 exist for one heading each.

Eight fluid sizes, and each is one token rather than a size plus a rule

A display size is fluid, so it is declared as a clamp and not as a size with a media query beside it. Resize this window and the eight below move; the ten above do not.

--display-hiw
How it worksclamp(30px, 7vw, 38px)
--display-feed
Live marketsclamp(28px, 4vw, 38px)
--display-sheet
Place your betclamp(24px, 5.6vw, 30px)
--display-tagline
Odds you can checkclamp(23px, 2.3vw, 31px)
--display-quote
A market is a questionclamp(20px, 1.85vw, 26px)
--display-seo
How resolution worksclamp(20px, 2.6vw, 24px)
--display-question
Will the US government shut down before March 1, 2027?clamp(19px, 2vw, 24px)
--display-hero
Will the US government shut down before March 1, 2027?clamp(19px, 1.5vw, 23px), the same question one rank down

A correction to the census, and it retires a finding

census.md lists seven font sizes on the anchor screens and calls 19.2px "the only value on the list that no one typed", the cheapest fix in the pass. Nobody typed it and it is not a defect. It is --display-hero: clamp(19px, 1.5vw, 23px) on the featured hero question, and 1.5vw of a 1280 viewport is exactly 19.2. There are two more of them at that width, 23.68 and 29.44, from --display-quote and --display-tagline, and the census did not report those only because no control wore them. A fluid size lands where the viewport puts it; that is the whole point of declaring it fluid. The item is closed here rather than carried into the consolidation.

Leading, six steps, and every one of them is used

Unitless, so a line box scales with its own size and not with the root. Fifty nine declarations, all six tokens live, and not one raw value in a component.

--leading-none
An icon or a badge: the box is the line.
An icon or a badge: the box is the line.1, 4 declarations
--leading-flat
A display heading holds its lines close.
A display heading holds its lines close.1.05, 11 declarations
--leading-tight
A card question, a headline.
A card question, a headline.1.15, 4 declarations
--leading-snug
A compact row, where two lines share one target.
A compact row, where two lines share one target.1.3, 5 declarations
--leading-base
Body, and every long line. The default for anything a person reads rather than scans.1.5, 32 declarations, the workhorse
--leading-loose
A reading column: the SEO plate and the how-it-works prose, where a line is long and the eye needs the return.1.6, 3 declarations

Tracking is the axis with no scale

Family, size, weight and leading are all tokens and all are held. Letter spacing is not. 59 declarations across components/, 13 distinct values, and zero tokens:

tightening
-.03em 4   -.02em 9   -.01em 5display headings, where a large size closes up
neutral
normal 3   0 3two spellings of the same thing
opening
.01em 4   .03em 12   .04em 3   .05em 3   .06em 5   .07em 3   .08em 5   .1em 2small uppercase labels, where a small size needs the air

Eight opening values between .01 and .1 is not a scale, it is a series of separate afternoons. The three groups above are real and a set of three or four tokens would hold all 59 declarations: a tightening for display, a neutral, and two or three opens keyed to size. It is the same finding the census made about padding and height, on the axis nobody thought to count.

Tracking is also the axis where 0 and normal both appear. They compute the same and they are two ways of saying it, which is how an axis with no token ends up with two spellings of nothing.

And no size literal left

Of 238 font-size declarations in the system, 238 read a token: 222 through the --text-* ramp, 10 through --display-*, 5 that are font-size:0, and one through --logo-size, which is itself set from the ramp. Re-counted 2026-08-16 from the source with comments stripped.

This section said 217 of 218, and the exception is chart.css: .chart-svg text{font-size:7px} until that count. The rule it named was deleted on 2026-08-15 and the page went on documenting it, which is the vitrine describing a system that had moved. The argument was worth keeping and chart.css keeps it in the comment where the rule stood: 7 was a USER UNIT inside a viewBox="0 0 300 100" drawn with preserveAspectRatio="none", so it was never 7 screen pixels and rem would have made it follow the reader's browser setting while the plot went on following the viewBox. What retired it was not the argument but a reading taken in the DOM instead of the source: the chart SVG carries no <text> node on any of the 106 painted screens or the 57 kit pages.

Measure

The column a person reads. Measured on the feed at 1280: the card's why line runs 40 characters at 13px with 19.5px of leading, and a card paragraph runs 26. Both are short, and deliberately: a card is scanned in a grid, not read in a column, and 40 characters is what a three-across grid at 1120 leaves.

The reading columns are the ones to hold to a measure, and they are the SEO plate and the how-it-works prose, not the cards. This page holds itself to 74ch, which is the value the deleted kit arrived at after finding a bare paragraph running at 99 characters while the footnote beside it was held to 76: the block a reader came for was the one with no max-width at all.

40ch, the card

Funding talks have stalled twice this quarter, but past deadlines settled late.

74ch, the column

A market resolves against a source named on the page before the first bet is taken, and the source cannot be changed after that. If the source does not publish by the close date, the market voids and every stake is returned.

What this decides

A method note, and this page tripped over its own subject. The three family cards in section 01 rendered in DM Sans at 12px on the first build: all three of them, one typeface shown three times. _page.css carried .tk-card p, specificity 0-1-1, and the .tk-f-display and .tk-s-30 utilities beside it are 0-1-0, so the descendant selector won every specimen in the same stylesheet. It is the exact failure the @import order in components/index.css exists to prevent, met inside the stand file, and looking at the page would not have caught it: a heading in the wrong sans at the wrong size still looks like a heading. It was found by asking the browser which family each specimen had actually resolved to.

  1. 1One file per family, declared over a weight range. DM Sans and Space Grotesk are variable fonts copied once per weight: 242 KB of the 373 KB payload is the same bytes seven times. font-weight:400 700 on one @font-face removes it without changing a single rendered glyph.
  2. 2400 gets a token. It is the weight 192 of 260 text elements render at and the only step of the ramp with no name, and one component has already had to type normal for it.
  3. 3--weight-bold is documented as unavailable in mono, or the mono 700 face is added. Nothing asks for it today; the next thing that does will get semibold and no warning.
  4. 4Tracking becomes three or four tokens. 13 values, 59 declarations, no scale, and two spellings of zero. Same shape as the padding and height findings, on the axis the census did not count.
  5. 5The 19.2px item is closed, not carried. It is a clamp resolving at 1280, and two more fluid tokens do the same thing at that width. A fluid size lands where the viewport puts it.
  6. 6Nothing here changes a value. That is step 3e.