Components

Navigation

One opaque plate, centred, on a full-bleed sheet of frosted glass: the logo at its left, then the seven routes, then the language switch. The glass is the band, not the plate — that is the manual's stacking order, opaque surfaces on top of the frosted layer, and it is what both mockups draw. It is also the only arrangement that holds its contrast, because an opaque plate reads the same over every frame of the hero video underneath it: 10.6:1 for the dim link ink, 21:1 for white.

It was three plates across the full width until the site owner asked for “alles in einen balken, zentriert” — one bar, centred, logo left, glass kept. The plate is max-content wide and the band it floats in is --nav-height exactly.

A plate, not a pill. A rectangle with a 2 px corner.

Default

<!-- in the <head>, after the three stylesheets -->
<script src="/design-system/assets/js/cf-nav.js"></script>

<nav class="cf-nav" aria-label="Hauptnavigation">
  <div class="cf-nav__bar">
    <a class="cf-logo" href="/">
      <img src="/assets/img/logo/cf-logo-horizontal-white.svg" alt="Control-F">
    </a>
    <button class="cf-nav__toggle" type="button" aria-controls="hauptnav" aria-expanded="false">Menü</button>
    <ul class="cf-nav__list" id="hauptnav" role="list">
      <li><a class="cf-nav__link" href="/" aria-current="page">Home</a></li>
      …
    </ul>
    <a class="cf-nav__lang" href="/en/kontakt" hreflang="en" lang="en" aria-label="Switch to English">EN</a>
  </div>
</nav>

The language switch

Last in the plate, after the seven routes, because it is the one control there that does not move you through the site but between two copies of it. It names where it goes, not where you are — the seven links and their current-page marker already answer “where am I”, and at 375 px a DE|EN pair is 72 px the bar does not have once the lockup, the toggle and the gutters are paid for.

It takes .cf-nav__link's padding, so the switch and the routes are one row inside the plate; its contour is all that separates a control from a label. It carried --space-4 / --space-6 when it stood in the band beside the link plate, which was that plate's padding plus the label's added up, so that the two plates stood level — there is one plate now and the sum with it. Below 48.75 rem the routes leave the flow and the switch takes .cf-nav__toggle's metric instead — 44 px, the touch floor.

AttributeWhy
hrefthe same page in the other edition, never the other edition's home. It is the one part of the bar that legitimately moves per page — see scripts/check-nav-parity.py, which normalises it away for exactly that reason
hreflangnames the language at the other end, so the link is typed rather than guessed from its label
langon the label, because the label is not in the document's language. “EN” inside a German document is read “eh en” by a German synthesiser; this hands the two letters to the English voice
aria-labelthe whole sentence. “EN” read on its own is a letter pair, not an offer

The head carries the matching pair — <link rel="alternate" hreflang> for both editions, each naming itself as well as the other, because a crawler treats an alternate set as complete only when every member of it says so. The English pages are generated: scripts/build-i18n.py builds patterns/en/ from the German pattern beside it and replaces only the words, from design-system/i18n/en.json. It writes both sides of this control, so the two editions cannot disagree about where the other one is.

Behaviour

StateAppearance
Defaultone black plate at --radius-sm, centred in the band, links at white 72 % — 10.6:1
Hoverlink goes to white, 120 ms
Current pagearia-current="page" → white link on a white 14 % plate
Stickystays at the top on scroll; over the first 80 px the edge light resolves and the bar becomes a separate plane
Below 780 pxlinks fold under the bar, the toggle button appears — both opaque
Menu openthe panel is drawn out from under the bar by a clip edge travelling down it, 480 ms; the toggle's contour lifts to white, 120 ms
Esccloses the panel and puts focus back on the toggle
Focus leaves the barcloses the panel. Focus leaving the document does not — that is the reader going to the browser, not away from the menu
Pointer outside the barcloses the panel
A link in the panelcloses the panel. Every link here leaves the page, except one to a fragment on the current page — and a menu left standing over what it has just scrolled to is the failure that costs
Widened past 780 pxthe open state is dropped, so narrowing again does not drop open a menu nobody asked for
No scriptthe links are a stacked list under a bar that does not stick, and the toggle is not drawn — see The script
The menu is the one panel in the system that opens and closes, and it is a wipe rather than a fade. That is a contrast decision, not a taste one: the panel is an opaque plate over the hero video, the only backdrop on the site nobody controls. Fading it in would put its labels over a partly transparent plate for the length of the transition, and the floor would then depend on which frame happened to be playing. Clipped, the plate is fully opaque in every frame of the motion and the labels hold 16:1 from the first one — the glass button's light only adds, applied to a reveal. 480 ms and --ease-standard, the same as the accordion: both are a panel opening, and two panel reveals in one system should not move on two different curves. → Motion
The clip is inset past the panel on its three still sides, and the obvious inset(0) is a bug. A clip-path clips everything the element paints, and the focus ring is painted outside the border box. Measured at 375 px, the first link's ring reaches 3 px above and 3 px left of the panel's own box, so a clip sitting exactly on that box removes it — 330 lit pixels on the ring's top stroke against none. 4 px of slop on every side but the travelling edge (2 px of outline plus the 2 px offset) keeps the whole ring inside the region and leaves the wipe visually unchanged.
The bar is a rectangle, not a capsule. The nav plate is 417 × 41 at rx="2" in landing-page-full.svg, where a pill would be rx="20.5". The implementation carried --radius-pill here until the vectors were counted — at that height, a 2 px corner in a compressed JPG is indistinguishable from a capsule. The current-page marker was a capsule inside a capsule, which is what made the whole bar read as one. → Geometry
The export has no current-page marker at all — the nav is one black rect and five identical text runs. That is a mockup of a single page rather than a rule, and a five-item bar that never says where you are is a real loss, so the marker stays. It is now a 2 px plate inside a 2 px plate.
The glass band is nearly clear on purpose. It carries --surface-glass-veil (white 4 %), because nothing sits on it but the one opaque plate — the blur is what you are meant to see, not a wash of white. Sampled from the mockups, CF-Grau reads 207 above the band and 208 inside it. That also means the band is invisible over a flat surface, which is correct: on Über uns it does nothing, and the manual reserves frosted glass for calming complex graphics.
The bar is sticky, never fixed. A backdrop-filter on a fixed element repaints its blurred region on every scroll frame in iOS Safari. The glass sheet also reaches --glass-lookahead past the bar's bottom edge and is masked back, so content arrives through the blur instead of appearing at the boundary already sharp.

The script

assets/js/cf-nav.js is the fourth file in the system that ships, and like cf-consent.js it creates no markup of its own: the bar, the button and the panel are ordinary HTML in the page and the file only decides when the panel is shown. It is ~2 kB, has no dependencies and no build step, and it is the only implementation of this behaviour.

It used to be eight implementations, and none of them shipped. The toggle was wired by an inline <script> at the foot of each of the seven pattern pages — seven byte-identical copies — and an eighth in assets/js/docs.js, which is documentation chrome and explicitly does not ship. So the one interactive component every page carries had its behaviour written down everywhere except in the system: anyone building a page from the overview's three stylesheets got a menu button that did nothing, and any fix to the behaviour had to be made eight times.
WritesWhereMeaning
data-cf-nav="ready"<html>the stylesheet may fold the bar
aria-expanded.cf-nav__togglethe announced state
data-open.cf-nav__listthe drawn state

The last two describe one fact and are written by one function, which is the point: they were set in two places on seven pages, and that is how an announced state and a drawn one drift apart.

The tag goes in the <head>, and the placement is load-bearing rather than a habit. Every rule that folds this bar is gated on [data-cf-nav], so with scripting off — or the file lost to a dropped connection, or an error inside it — the five links are a stacked list and the control that would do nothing is not drawn at all. That fallback is only free if the attribute is there before the first paint. Written any later, every load below 780 px would show the open list and then collapse it, and the panel would spend that window in the layout with its links focusable. A script element in the head runs before the body exists, which is the whole of the trick.
The fallback is a stacked list, not the open panel. The panel is out of flow, so it costs the bar no height; six rows of it pinned to the top of a 375 px screen would leave the page about a third of itself to be read in. In the fallback the bar is relative rather than sticky for exactly that reason — it is a header at the top of the page, not a plane over it.
The 780 is not repeated in the script. tokens.css keeps the register of every threshold in the shipping CSS and states that the register is only worth having if it is complete; a copy in a script is one the register cannot see. The question the file actually has is not how wide the viewport is but whether the button is on screen, and the stylesheet already answers that — so it asks getComputedStyle instead of restating the number.

Accessibility

<a class="skip-link" href="#inhalt">Zum Inhalt springen</a>
<main id="inhalt" tabindex="-1">

Site structure

The new navigation is deliberately short — five entries, no dropdowns:

LabelTargetContent
Home/landing page
Über uns/ueber-unsvalues, team
News/newsblog archive in the subdivision grid
Karriere/karriereopen roles
Kontakt/kontaktform, directions

The topic pages of the old site (Maschinenbau, Energie, Dienstleistungen) have been absorbed into the process sequence on the landing page. Their existing URLs need 301 redirects at relaunch.