Components
Statement & Value Table
Four blocks for saying something plainly. A statement is one claim beside one object. A value table is several claims stacked in a ruled field. A stat is a claim compressed to one word, usually a number. A hint is an aside about the interface rather than the content. All four ship on the two designed pages; none of them had a page here until now.
Statement
The "Tausende Sensoren erzeugen Daten" moment on the landing page. A field spanning the whole block and a claim standing inside it, nothing around them. It is the one place in the system where a claim gets a whole section to itself, so it is used once per page at most.
It was a claim beside a square object until the square was measured. At 1280 the figure cell was 608 × 608 — to carry five hairlines, a 14 px dot and a small grey mark — while the claim it existed to support filled 168 px of the 608 px row next to it. Seventy-two per cent of the component was reserved for nothing, and the section stood 848 px tall on a 900 px viewport to say one sentence. It is 688 px now, with more drawing in it, because the figure is a band the claim sits inside rather than a column the claim sits next to.
The drawing changed for a second reason, and it is the one worth keeping. The old figure was a symmetric star with a lit lime centre — which is the picture of an answer found, next to a sentence whose whole shape is many signals in, nothing out. The illustration contradicted the copy.
What replaces it is built out of Illustration rather than by eye, because a first attempt that was not broke two of its rules at once and looked fine doing it. It had 186 sources, which is a scatter — and the manual says of nodes, in as many words, that they are "not a scatter" and that "twelve is too many". Each of those sources aimed a tick straight at the centre, which put 186 distinct angles in a drawing where every diagonal on the site is 0°, 26.57°, 45° or 63.43°. Both were invisible in a screenshot. Both were wrong.
The version after that drew five traces on brand angles, each with the chevron head the manual specifies, each stopping short of the void with nothing to land on. It was correct by every rule in the manual and it was still the wrong drawing: five arrowheads pointing at an empty circle is a picture of arrival, and arrival is the half of the sentence that does not happen. The arrows are gone.
What is left is what the sentence actually needs. The lattice is both 2:1 families and the level step, at 1-4, the line type the manual assigns to a lattice. Standing on its vertices is the sensor field: sixty-nine glows, wider than the spacing so they merge where they crowd. Each is a hot core in a cool halo — which is this system's one ramp used as a glow rather than as a surface: lime is the centre, Glas is the pale ring, CF-Grau is what both fall away to. Two ramps serve all of them, the hot one carrying the full lime leg with the oklab waypoint at 19 % of it; which one a sensor gets is its own intensity, and radius and opacity do the rest.
The middle is quiet, not missing. A hole was cut here once — first a
ring and a nucleus, then a disc of nothing — and it read as a white circle laid over the
drawing. An edge is the one thing a field of glows must not have. The instruments in the
middle are floored rather than culled now: small, cool, halo only, rising to hot as the
field spreads and falling away again at its rim. The sentence still gets what it needs —
nothing is found at the centre — out of intensity instead of out of absence, and
nothing in the drawing is cut. scripts/check-void-departure.py holds it as a
measurement rather than as a shape: the root must leave from the quietest fifth of the
field, and it leaves from 0.00 of ink against a bar of 0.10.
On the landing page the field then collapses into the line: every glow travels to the root's departure and goes out as it arrives, nearest first, while the root grows downward out of the point they are arriving at. The measurement becomes the measurement's route.
The figure also takes itself apart on the way out. The same view
timeline that assembles it on entry runs the mirror on its exit phase, in reverse
order of arrival: the sensor field collapses into the departure point, and the lattice,
which is ground and does not scatter, goes dark last.
One rule at every scale, which is what keeps the scatter the same drawing coming
apart rather than debris. Each part's exit vector and slot live in the markup as
--sx/--sy/--scat-in; the component only says
that parts scatter. Scrubbed, not played: scroll back up and it reassembles. On the
landing page the scroll that runs this is the same scroll that draws the process
frame in below, so the section hands off to the one that answers it.
Tausende Sensoren erzeugen Daten, aber daraus keine Antworten.
Wir machen mehr aus Ihren Daten und entwickeln die Infrastruktur, die Datenströme in Geschäftsmodelle verwandelt.
<div class="cf-statement"> <div class="cf-statement__figure"> <svg class="cf-iso" viewBox="0 0 1200 288" preserveAspectRatio="xMidYMid slice" aria-hidden="true">…</svg> </div> <div class="cf-statement__text"> <h2 class="cf-statement__title">Tausende Sensoren erzeugen Daten…</h2> <p class="cf-statement__body">Wir machen mehr aus Ihren Daten…</p> </div> </div>
| Part | Required | What it is |
|---|---|---|
.cf-statement | yes | The grid. Stacked by default; one row of two layers once the block itself is 56 rem wide. |
.cf-statement__figure | no | Holds one .cf-iso band. Spans both columns above 56 rem, so the field runs the full width of the block and the claim sits over its faded half. 3 / 1 stacked, 1200 / 288 side by side, slice from the drawing’s own centre either way. Stacked the crop is a constant 864 of the 1200 units at every width, so the anchor — not the viewport — decides how much of the void the frame keeps: xMid keeps 44.4 % of the frame for it against the 46 % the side-by-side band gives it, and puts its left edge 78 units clear of the void’s rim. |
.cf-statement__text | yes | The claim and its follow-up. Second column above 56 rem, painted over the figure. |
.cf-statement__title | yes | The claim. --text-h2, Medium, text-wrap: balance. One or two sentences. |
.cf-statement__body | no | The follow-up. --text-md, --text-secondary, capped at 46 ch so it stays a paragraph rather than a column. |
@container (min-width: 56rem), so a statement dropped into a
narrow column stacks even on a wide screen. Stacked is the declared default, which
means a browser without container query support gets the safe layout rather than a
broken one. The query is unnamed, so it measures whichever query container is
nearest — named or not. At a 1280 px viewport that is the page
.container's content box, 1139 px; in the demo above it is the
documentation frame instead, at 912 px. inline-size queries measure the
content box, not the border box, so the gutters are already subtracted. That 227 px
is why the demo crosses 56 rem later than the real page does.
.cf-statement__title is a class, not a level. On the landing page the
statement is a section heading and the element is an h2; in the demo above
it is an h3 because it sits under this page's own h2. Never
skip a level to get a size — the size is already in the class.
Value table
The "wer wir sind" block on Über uns. A ruled field, one row per idea, each row an isometric figure beside a short run of paragraphs. The figure cell is a fixed 15 rem once there is room; below that the row folds and the figure sits above the copy with the divider turning from vertical to horizontal.
The figure cell is square, and that is what sets the row height.
15 rem across and 15 rem down, so a row is 240 px tall unless its copy needs more —
which is how the mockup draws it: three rows of very unequal copy, all three
240 px to the pixel. One custom property, --value-figure on
.cf-value-table, carries both numbers, because they are one decision and
two literals would drift. The minimum applies only inside the container query; below
48 rem the figure is a band above the copy rather than a column, and 240 px of it
there would be a wall.
-
Als echte Daten-Nerds bauen wir seit 2022 Big Data-Plattformen für Telemetriedaten in der Industrie.
Wir sind eine Datenboutique aus Konstanz für die DACH-Region. Klein genug, um agil zu sein. Erfahren genug, um zu wissen, was funktioniert.
-
Wir managen Lösungen, nicht ausschließlich Projekte.
Wir bauen Code statt PowerPoints.
-
Remote-first mit voller Flexibilität. Selbstverantwortung statt Mikromanagement.
<ul class="cf-value-table" role="list"> <li class="cf-value-row"> <div class="cf-value-row__figure"> <svg class="cf-iso" viewBox="0 0 160 120" width="140" aria-hidden="true">…</svg> </div> <div class="cf-value-row__body"> <p>Als echte Daten-Nerds bauen wir seit 2022…</p> <p>Wir sind eine Datenboutique aus Konstanz…</p> </div> </li> </ul>
| Part | Required | What it is |
|---|---|---|
.cf-value-table | yes | The outer hairline frame. Also the query container the rows size against. |
.cf-value-row | yes | One idea. Figure cell plus body cell; the row above supplies the divider. |
.cf-value-row__figure | yes | Centred drawing cell. A 15 rem square from 48 rem up — width and minimum height both --value-figure — full width and content height below it. |
.cf-value-row__body | yes | The copy. --text-md stacked, --text-lg side by side — the row gets more generous as it gets wider. |
<div>s tell a screen reader nothing; a <ul>
announces "list, 3 items" and lets the user step through or skip past the whole block.
role="list" is not redundant here: the system removes list bullets, and
Safari drops list semantics from any list whose list-style is
none. The same pairing is already used by the nav and the footer links.
A validator will still call the attribute redundant. It is not — and note that
.cf-value-table carries its own margin,
padding and list-style reset precisely so that deleting
the role costs you only the semantics, never the box.
aria-hidden="true" and contribute no
accessible name. A figure that carries information the copy does not would need
role="img" and a label instead — but that figure does not belong in this
component.
Stat
One value set large in mono with its unit underneath: "10 Mitarbeiter", "seit 2022". Usually a number — the tabular figures exist for that case — but a short token like a region code sets the same way. Used for a fact worth stopping on, not for decoration: a row of four invented metrics reads as filler, and the brand does not do filler.
It is the smallest of the four blocks and the one most likely to outgrow this page. If the system gains charts, comparisons or a diagram language, this section is where that work starts — extend it rather than opening a second page beside it.
10 Mitarbeiter
2022 gegründet
DACH Region
<div class="cluster"> <p class="cf-stat"> <span class="cf-stat__value">10</span> <span class="cf-stat__label">Mitarbeiter</span> </p> <p class="cf-stat">…</p> </div>
.cf-stat
sets no margins and no gap, so several of them need a container that does.
.cluster is the one to reach for — it brings its own
--space-4 and wraps, so a row of stats folds on a narrow screen without
a query. Do not put the gap in a style attribute: spacing is a system
decision, and the system already has a class for it.
| Part | Required | What it is |
|---|---|---|
.cf-stat | yes | One block per fact. A <p> — the number and its unit are one sentence. |
.cf-stat__value | yes | Geist Mono Medium at --text-h1, tabular figures so a column of numbers aligns. Block. |
.cf-stat__label | yes | Mono 11 px uppercase, --text-secondary. Block, --space-2 under the number. |
1M is dictated as "one em". Write 1 Mio., or move the unit
into the label. Anything the eye expands silently — +40%, ~3x
— is worth reading aloud once before it ships.
Hint
The mono parenthetical from the mockups — "( klick um scroll zu simulieren )". It is an instruction about the interface, not a piece of the content, and it is the only place the system uses brackets as ornament. Centred, secondary, small.
Klick um Scroll zu simulieren
<p class="cf-hint">Klick um Scroll zu simulieren</p>
.cf-hint adds
( and ) through ::before and
::after, so never type them into the markup — you would get two pairs.
Generated content of this kind is announced by most screen readers, which is
acceptable for punctuation but is a reason to keep it to punctuation: no hint text
belongs in a pseudo-element.
Choosing between them
| You have | Use | Because |
|---|---|---|
| one claim, worth a whole section | statement | it is the only block that gets an object of its own |
| three to five related claims | value table | the shared frame is what makes them read as a set |
| a fact that fits in one value | stat | mono set large is how the brand writes a quantity |
| a note about the page itself | hint | it is visibly not content, which is the point |
| a step in a sequence | process card | sequences are numbered and ordered; these blocks are neither |
Do
- Keep one statement per page. It stops being emphasis the second time.
- Let the value table's rows run to different lengths — the frame already holds them together.
- Give every figure
.cf-iso, so its contour stays 1 px at any size. - Round stats to the number a person would say out loud.
Don't
- No lime inside these blocks beyond a single accent in one figure — lime is light, and the page has one such moment.
- No borders on a statement. The white space is the frame.
- Don't nest a value table inside a statement, or a stat inside a value row. Each block is a whole section's worth of emphasis.
- Don't invent metrics to fill a stat row. Three real facts beat six plausible ones.