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>
PartRequiredWhat it is
.cf-statementyesThe grid. Stacked by default; one row of two layers once the block itself is 56 rem wide.
.cf-statement__figurenoHolds 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__textyesThe claim and its follow-up. Second column above 56 rem, painted over the figure.
.cf-statement__titleyesThe claim. --text-h2, Medium, text-wrap: balance. One or two sentences.
.cf-statement__bodynoThe follow-up. --text-md, --text-secondary, capped at 46 ch so it stays a paragraph rather than a column.
The breakpoint is the block, not the browser. The two-column layout comes from @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.
Pick the heading level from the page, not from this snippet. .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>
PartRequiredWhat it is
.cf-value-tableyesThe outer hairline frame. Also the query container the rows size against.
.cf-value-rowyesOne idea. Figure cell plus body cell; the row above supplies the divider.
.cf-value-row__figureyesCentred 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__bodyyesThe copy. --text-md stacked, --text-lg side by side — the row gets more generous as it gets wider.
It is a list, so it is marked up as one. Three sibling <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.
The figures are decorative. Every drawing in a value row restates the copy next to it, so all of them are 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>
A stat is one block; a row of them is a layout. .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.
PartRequiredWhat it is
.cf-statyesOne block per fact. A <p> — the number and its unit are one sentence.
.cf-stat__valueyesGeist Mono Medium at --text-h1, tabular figures so a column of numbers aligns. Block.
.cf-stat__labelyesMono 11 px uppercase, --text-secondary. Block, --space-2 under the number.
The label has to read on its own. The number and the label reach the accessibility tree as two separate text runs, so a screen reader can announce "10" without "Mitarbeiter" following it. That is worth knowing because it cannot be markup-engineered away: inline spans, a block label and two block elements were all measured in Chromium and all produce the same two text leaves. Write labels that carry their own meaning — "Mitarbeiter", not "davon" — and if a stat is meaningless without its number, it is a sentence, not a stat.
Write the value the way it should be read. The value is text, so 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>
The brackets are CSS, the words are not. .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 haveUseBecause
one claim, worth a whole sectionstatementit is the only block that gets an object of its own
three to five related claimsvalue tablethe shared frame is what makes them read as a set
a fact that fits in one valuestatmono set large is how the brand writes a quantity
a note about the page itselfhintit is visibly not content, which is the point
a step in a sequenceprocess cardsequences 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.