--- title: ChartStyle created: 2026-07-27 15:16 updated: 2026-07-27 15:22 revision: 4 author: Auriea summary: cell text contrast flips instead of fading — mid readings were unreadable --- [[Scriptorium]] A stylesheet for **charts drawn in CSS**, so a measurement can live on a page as text you can read, search, correct and translate — instead of being baked into a PNG. Declare it on any page with `[[style:ChartStyle]]`. It rides on top of whatever skin the site is wearing and borrows that skin's own `--ink`, `--bg`, `--hair` and `--quiet`, so a chart is never a foreign object on the page and follows the lamplight palette into dark mode by itself. **A note for anyone editing this page.** `/style/` unwraps *every* fence it finds, and a fence it does not recognise still ends with three backticks that it reads as the *start* of one. So this page carries exactly **one** fenced block — the CSS at the bottom — and every example above is written as indented code instead. Add a second fence and the stylesheet breaks. ## The heatmap A grid of readings where the **value paints its own cell**. You author the real number twice — once in a custom property, once as the text — and the colour is computed from it, so the ink can never drift out of step with the figure.
grooveColumn BColumn C row label 0.95 0.50 1.00
The first `` is the corner; the rest are column heads. Each `` starts a row, and the ``s after it are that row's readings. `--v` is the reading itself. `--lo` (default `.5`) is the value that means *nothing*; `--k` (default `2`) is one divided by the span from nothing to full, because `calc()` may not divide by a `var()`. The cell's ink is the reading's distance between them, and its text flips toward paper as its ground darkens. `class="null"` rings a cell to say it is indistinguishable from nothing at all. For a scale that runs 0 → 1 rather than 0.5 → 1, set `--lo:0` and `--k:1` on the container. ## The bar An inline bar that fills its own width behind the number — `0.64`. Same `--lo` and `--k`. ```css /* ChartStyle — charts as markup, not pictures. Everything is keyed off ONE custom property, --v, holding the real reading; the ink is derived from it. Nothing here is a hard-coded colour except the accent, which a page may override. */ .heat, .barlist { --chart: #2d5c96; /* the accent a value is painted in */ --null: #9b3d28; /* the ring on "this reads as nothing" */ --lo: .5; /* the reading that means nothing */ --k: 2; /* 1 / (full - nothing) — calc() may not divide by a var() */ --cols: 3; font-family: var(--sans, ui-sans-serif, system-ui, sans-serif); font-feature-settings: "onum" 0, "lnum" 1, "tnum" 1; font-variant-numeric: lining-nums tabular-nums; } :root[data-theme="dark"] .heat, :root[data-theme="dark"] .barlist { --chart: #6d9bd4; } @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .heat, :root:not([data-theme="light"]) .barlist { --chart: #6d9bd4; } } /* how far a reading sits between "nothing" and "full", clamped to 0..1 */ .heat > span, .bar { --f: clamp(0, calc((var(--v) - var(--lo)) * var(--k)), 1); } .heat { display: grid; grid-template-columns: minmax(4.5rem, auto) repeat(var(--cols), 1fr); gap: 3px; margin: 1.6rem 0; font-size: .82rem; } /* the column heads */ .heat > b { font-weight: 600; font-size: .69rem; letter-spacing: .09em; text-transform: uppercase; color: var(--quiet); text-align: center; padding: 0 0 .3rem; align-self: end; } .heat > b:first-child { text-align: right; padding-right: .7rem; } /* the row labels */ .heat > i { font-style: normal; color: var(--quiet); text-align: right; padding-right: .7rem; align-self: center; white-space: nowrap; } /* a reading: its own ground, its own contrast */ .heat > span { padding: .55rem .4rem; text-align: center; border-radius: 2px; background: color-mix(in srgb, var(--chart) calc(var(--f) * 92%), color-mix(in srgb, var(--hair) 45%, transparent)); /* contrast must FLIP, not fade. A linear ink-to-paper ramp puts mid readings at mid-grey on a mid-ground — the one place contrast is worst, and exactly where 0.76 and 0.86 became unreadable. This crosses over quickly around the middle instead. */ color: color-mix(in srgb, var(--bg) clamp(0%, calc((var(--f) - .45) * 400%), 100%), var(--ink)); } .heat > span.null { box-shadow: inset 0 0 0 1.5px var(--null); } /* an inline bar — the number sits on its own fill */ .bar { display: inline-block; min-width: 5.5rem; padding: .12rem .5rem; border-radius: 2px; text-align: right; background: linear-gradient(to right, color-mix(in srgb, var(--chart) 55%, transparent) calc(var(--f) * 100%), color-mix(in srgb, var(--hair) 40%, transparent) 0); } @media (max-width: 640px) { .heat { font-size: .74rem; gap: 2px; } .heat > span { padding: .45rem .2rem; } } ``` [[tag:css]]