/* themes/weg/theme.css — WEG: a warm ground, a blue spine, green nodes.

   The argument: a practice explains itself as a sequence. This is what happens,
   in this order — Erstgespräch, Blutanalyse, Ihr Plan, Begleitung. So the page
   is built on a line. A 2px --blau spine runs down the article and every named
   section attaches to it at a green node, which makes the spine the separator
   rather than a decoration laid over one; there is no section rule, no card and
   no tinted band anywhere in this file, because the line and the nodes are
   already doing that work.

   Numbers appear only where the content is genuinely ordered — the four
   pathway steps, Metabolic Balance's four phases, the /news years, and the
   client's own "Paket 1-4". An unordered section gets a node and no numeral.
   A run of 01/02/03 over content that is not a sequence would be the same
   defect as a section rule that separates nothing.

   Colour is split by MEANING, and the split is not a preference — it is what
   the contrast arithmetic leaves available (see the palette note below).
     --gruen  is the practice's own voice: h1, section titles, the nodes, and
              the one filled control.
     --blau   is the clinical layer: every label, numeral, unit, date, table
              head and inline link.
     --tinte  is running prose.
   Two signals a reader can learn in one screen, and the second one is set in a
   different typeface as well as a different colour, so the split survives being
   printed in greyscale.

   What WEG deliberately does NOT share with KLAR, which is the other variant on
   Aspekta and the same two logo greens (see docs/getting-started.md §5 — the
   themes are competing proposals, and convergence between them is the defect):
     ground     warm #FDFBF8, not pure white
     h1 weight  550, a statement — KLAR's is 150, a whisper
     structure  one measure with a spine, not two fixed vertical tracks
     separator  a node on a line, not a hairline
     imagery    full saturation — KLAR knocks every photograph down to .8
     type       a second face doing exactly one job */

@font-face {
  font-family: "Aspekta Variable";
  font-weight: 100 900;
  font-display: swap;
  font-style: normal;
  src: url("fonts/AspektaVF.woff2") format("woff2");
}
/* IBM Plex Mono ships as static weights on Google's css2 endpoint, not as a
   variable font, so the clinical layer costs two files rather than Aspekta's
   one — 10,052 and 10,060 bytes for the latin subset, which is what
   tools/fetch-fonts.sh keeps (U+0000-00FF, the range that carries ä ö ü ß).
   Two weights and no more: 400 sets the numerals and the hours table, 500 the
   tracked uppercase labels, which need the extra weight to hold at .72rem. */
@font-face {
  font-family: "IBM Plex Mono";
  font-weight: 400;
  font-display: swap;
  font-style: normal;
  src: url("fonts/IBMPlexMono.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-weight: 500;
  font-display: swap;
  font-style: normal;
  src: url("fonts/IBMPlexMono-Medium.woff2") format("woff2");
}

/* WEG's palette is theme-local for the same reason KLAR's is: assets/tokens.css
   encodes #507F34/#90C048 as the brand greens and per-pixel counting of both
   logo PNGs finds neither value (spec §9.1, still open). The files contain
   #548238 and #91C24C, and those are what ships here, byte for byte.

   The ground is where WEG parts company with KLAR, and it has a consequence
   that turned out to be the making of this variant rather than a cost.
   #548238 measures 4.5385:1 on pure white — it clears AA by 0.04, which is the
   entire reason KLAR sits on #ffffff. On this warm ground it measures 4.394:1
   and does not. Recomputed with tools/check-contrast.mjs against every warm
   off-white considered; the palest tried, #FBFDFE, still only reached 4.45.
   There is no warm ground on which the true logo green is AA body text.

   So the rule is: GREEN IS NEVER SET BELOW 24px. WCAG's large-text allowance
   is 3:1 at >=24px (or >=18.66px at 700+, which Aspekta's 550 is not), and
   4.394 clears 3:1 with room. Two selectors in this file declare
   `color: var(--gruen)` — `h1` and `.block__title` — and both floor their
   font-size at 1.5rem, so neither can drift under the gate. Everything smaller
   that used to want green is --blau at 7.694:1, which is the clinical layer
   this variant already needed a colour for.

   The alternative was a darkened companion green for small text. It was
   rejected: it means shipping a green that is not in the logo, and
   test/contrast asserts of the other themes that they do not do that. */
.theme-weg {
  --papier:  #FDFBF8;   /* warm ground */
  --tinte:   #46423C;   /* every run of body text — 9.659:1 */
  --gruen:   #548238;   /* logo dark green — 4.394:1, so >=24px or non-text ONLY */
  --limette: #91C24C;   /* logo light green — DECORATIVE ONLY, 2.028:1, never text */
  --blau:    #17557F;   /* the clinical layer — 7.694:1, and 7.948:1 under white */
  --linie:   #E2DCD3;   /* hairlines — decorative, 1.319:1, never a control edge */
  /* The boundary of a form control is the one grey here that is not
     decorative. WCAG 1.4.11 asks a control's edge for 3:1 against its ground,
     and an empty field in this theme has no fill, no box and no placeholder —
     the rule under it IS the control. --linie at 1.319:1 would read as a stray
     line under a label rather than as somewhere to type, and this practice's
     visitors skew older, which makes that a real failure and not a
     technicality. #8C867E is --linie's own warm grey taken to 3.489:1. */
  --feld:    #8C867E;   /* control boundary — 3.489:1 on --papier */

  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --rand: clamp(1.25rem, 5vw, 3rem);   /* outer gutter */
  /* The spine gutter: the distance from the article's content edge out to the
     line, and therefore the width the nodes hang in. It is small on purpose.
     KLAR's equivalent left column is --marge: 20rem, and the note there records
     what a wide margin costs across 43 pages — every section that puts nothing
     in it pays for the width in blank paper. A spine holds a 2px line and a
     12px disc and nothing else, so 28-48px is the whole of it and there is no
     emptiness for a section to pay for. */
  --spur: clamp(1.75rem, 4vw, 3rem);
  --lauf: 64ch;         /* prose measure — 642px at this type */
  /* The article measure. Prose is capped at --lauf wherever it appears, but an
     inherently wide object — a hero, a gallery, an offer row, a post list, a
     price table — takes all of --bahn. That split is why this variant can be
     photography-led on a single column: at 1280px the pictures are 880px wide
     (--bahn minus --spur) while the text still sets at 64 characters. */
  --bahn: 58rem;
  --mass: calc(var(--bahn) + 2 * var(--rand));
}

/* --- Foundations ------------------------------------------------------- */

body {
  font-family: "Aspekta Variable", -apple-system, "Segoe UI", Roboto, sans-serif;
  font-weight: 350;
  font-size: clamp(1rem, .96rem + .2vw, 1.05rem);
  line-height: 1.7;
  background: var(--papier);
  color: var(--tinte);
  -webkit-font-smoothing: antialiased;
  /* Real WordPress prose, not copy authored for this layout: several German
     compounds ("Datenschutzerklärungen", "Gesundheitsprogramme") are longer
     than a 375px column at display sizes. */
  overflow-wrap: break-word;
}

/* A link is information, so it belongs to the clinical layer and is --blau at
   7.694:1 — not --gruen, which at 4.394:1 on this ground cannot carry text at
   body size at all. The underline stays: a colour-only link on a page whose
   headings are a different colour again would be identifiable by hue alone. */
a { color: var(--blau);
    text-decoration-thickness: 1px;
    text-underline-offset: .18em; }
a:hover, a:focus-visible { text-decoration-thickness: 2px; }

/* The --limette stroke, written once. It is the light logo green at 2.028:1,
   so it is never ink and never a ground under ink — it is the hover mark on
   the three navigational idioms that do not carry a standing underline: a
   drawer/menu item, a link-list row, and a post title. Gathering the recipe
   here rather than spelling it out in three places is what keeps the three
   from drifting apart; each section below keeps only what is its own. */
.linklist a:hover, .linklist a:focus-visible,
.postcard h3 a:hover, .postcard h3 a:focus-visible,
.offer__title a:hover, .offer__title a:focus-visible,
.site-footer nav a:hover, .site-footer nav a:focus-visible {
  text-decoration: underline; text-decoration-color: var(--limette);
  text-decoration-thickness: 2px; text-underline-offset: 4px;
}

:focus-visible { outline: 2px solid var(--blau); outline-offset: 3px; }

/* assets/tokens.css makes the skip link visible on focus but paints it in the
   shared petrol/green-600 tokens and rounds its corners. Repainted in WEG's own
   palette: white on --blau, 7.948:1. */
.visually-hidden:focus, .visually-hidden:focus-visible {
  background: var(--blau); color: #fff;
  border-radius: 0; outline: 2px solid var(--blau); outline-offset: 3px;
}

/* `hyphens: manual` is the initial value and is written out because it is a
   decision here rather than an omission. `auto` was tried in KLAR and broke
   "Vital-Praxis" as "Vital-Pra-/xis" and "Schwellungen" as "Schwellun-/gen" —
   the second is not a legal German break, so the engine is guessing rather
   than consulting a dictionary despite <html lang="de">. `manual` still
   honours a real U+00AD if the content ever ships one; there is none today.
   The safety net is `overflow-wrap: break-word` on body, which breaks only a
   string that would not fit on a line of its own. */
h1, h2, h3, h4, h5, h6 {
  font-weight: 550; line-height: 1.25; letter-spacing: -.01em;
  color: var(--tinte); margin: var(--sp-12) 0 var(--sp-3);
  overflow-wrap: break-word; -webkit-hyphens: manual; hyphens: manual;
}
/* Green, and >=32px at every viewport — see the palette note on the 24px gate.
   Weight 550 rather than KLAR's 150: the same typeface used to say the opposite
   thing. A practice states its name; it does not whisper it. */
h1 { color: var(--gruen); font-weight: 550;
     font-size: clamp(2rem, 1.2rem + 3.2vw, 3.25rem);
     line-height: 1.08; letter-spacing: -.025em; }
h2 { font-size: clamp(1.5rem, 1.3rem + .8vw, 2rem); }
h3 { font-size: 1.15rem; }
h4, h5, h6 { font-size: 1rem; }
strong, b { font-weight: 550; }

/* The clinical layer, written once and applied by class below: mono, tracked,
   uppercase, and --blau. Every element that takes it is a label, a numeral or
   a credential — never a run of prose.
   .78rem, not the .72rem this started at. Measured in Chrome across all 43
   pages: at .72rem every label on the site — the form's field names, the two
   footer panel headings, every table head — rendered at 11.52px, and IBM Plex
   Mono's x-height is smaller than Aspekta's at the same size, so uppercase and
   tracked it read smaller still. KLAR sets the same elements at .78rem and only
   its brand tagline at .72rem; collapsing all of them into one rule quietly
   shrank four of them. This practice's visitors skew older, which is stated as
   a design constraint throughout this repo, so the label size follows KLAR's
   and the tagline keeps its own smaller value below, where header width is the
   reason rather than taste. */
.eyebrow, .block__title + .pathway .pathway__num, .form-field label,
.footer-hours__title, .footer-focus__title, .brand__tagline {
  font-family: var(--mono); font-weight: 500; font-size: .78rem;
  letter-spacing: .12em; text-transform: uppercase; color: var(--blau);
}
/* The one exception, for space rather than for hierarchy: the tagline shares
   the header's first row with the brand mark, the telephone number and the
   partner badge, and at .78rem it wraps to a third line at 1150px. */
.brand__tagline { font-size: .72rem; letter-spacing: .08em; }

/* 20px between paragraphs. KLAR's is 16px because that theme separates with
   hairlines and measure and wants the tightest gap of the set; WEG has no
   hairline between sections at all — the node and the air around it are the
   separator — so the air is what has to carry it, and it is looser here on
   purpose. The pseudo-bullet run below is 8px, which makes the group gap 2.5x
   the within-group gap (test/rhythm asserts >= 2x). */
p { margin: 0 0 1.25rem; max-width: var(--lauf); }
/* Lists are scoped rather than global for the reason KLAR's note records: a
   <ul> in this document is more often structure than prose — the header menu,
   the submenus, the galleries, the post lists, the team grid and the two footer
   navs all want their container's width, not 64ch. Capped only in the two
   wrappers where a list IS running text. */
:where(.post__body, .col) :where(ul, ol) { max-width: var(--lauf); }
.post__body > :is(ul, ol), .col > :is(ul, ol) { margin: 0 0 1.25rem; }

/* Full saturation, and this is the one line where WEG's brief and KLAR's part
   company outright. KLAR knocks every photograph down to saturate(.8) so the
   pictures sit quietly against the white. WEG is asked to read as medical but
   WARM, and the clinical layer — mono, tracked, blue, precise — is already
   spending the whole page's supply of cool. The warmth has to come from
   somewhere, and it comes from the photography being left alone. */
.partner-badge img { filter: grayscale(1); transition: filter 300ms ease; }
.partner-badge:hover img, .partner-badge:focus-visible img { filter: none; }
@media (prefers-reduced-motion: reduce) { .partner-badge img { transition: none; } }

address { font-style: normal; margin: 0 0 1.25rem; }

/* --- Header ------------------------------------------------------------ */

/* The bar is capped at --bahn, the page measure, and its gutter is expressed as
   WIDTH rather than as padding. A border-bottom is painted on the padding box,
   so capping to --mass and insetting with `padding-inline: var(--rand)` would
   put the brand where the article's edge is while leaving the rule itself one
   full --rand wider on each side than the article beneath it. In a variant
   whose separators are all drawn from the same two vertical lines, the topmost
   one lining up with none of the others is the one visible seam. */
.site-header { position: sticky; top: 0; z-index: 50; background: var(--papier);
               border-bottom: 1px solid var(--linie);
               display: flex; flex-wrap: wrap; align-items: center;
               gap: var(--sp-2) var(--sp-6);
               width: calc(100% - 2 * var(--rand));
               max-width: var(--bahn); margin: 0 auto;
               padding: var(--sp-4) 0; }
.brand { text-decoration: none; display: flex; align-items: center; gap: var(--sp-3); }
/* filter: none is not needed here — WEG does not knock images down at all — so
   the mark simply inherits. Sized, and nothing else. */
.brand__mark { height: 34px; width: auto; }
/* The tagline takes the clinical layer's caps treatment from the rule above:
   it is the practice's discipline, which is a credential rather than prose.
   `ch` is the advance of the "0" glyph and measures neither the uppercasing nor
   the tracking, so a max-width in `ch` resolved far short of the longest token
   and `overflow-wrap: break-word` on body then split it mid-word as
   GESUNDHEI / TS- / VITAL- / PRAXIS. The three wrap properties remove every
   intra-word break opportunity and `min-width: min-content` — which always wins
   over max-width — keeps the box at least as wide as its longest token, so a
   mid-word break is impossible for any max-width and any future font.
   max-width now only chooses the line count. */
.brand__tagline { max-width: 12em; min-width: min-content; line-height: 1.3;
                  overflow-wrap: normal; word-break: normal; hyphens: none; }

/* The practice's telephone number, top right, in the clinical layer. It is the
   most looked-up fact on a walk-in practice's site after the opening hours, and
   this is the only variant of the five that puts it in the chrome rather than
   only in the footer and on /kontakt. Hidden below the nav breakpoint for the
   same reason the partner badge is: at 375px the brand, the badge, the number
   and the Menü button do not fit the content box, and the number is reachable
   there in one tap from the footer of every page. */
.header-phone { display: none; margin: 0 0 0 auto; max-width: none;
                font-family: var(--mono); font-weight: 400; font-size: .85rem;
                color: var(--blau); font-variant-numeric: tabular-nums; }
.header-phone a { text-decoration: none; }
.header-phone a:hover, .header-phone a:focus-visible { text-decoration: underline; }
.header-partners { display: none; margin: 0; max-width: none; }
.header-partners .partner-badge img { width: 96px; }

/* Drawer by default; from 1150px up the header becomes two rows — brand, phone
   and badge on the first, the whole menu on its own full-width second row
   (#hauptmenue keeps flex-basis: 100% at every width).
   The drawer follows the same rule as the submenus below: visible by default,
   COLLAPSED by script. With script execution disabled a `#hauptmenue { display:
   none }` baseline would leave a 375px visitor with none of the menu's links,
   because the Menü button is the only way in and nothing is listening to it.
   Unscripted, the header simply lists the whole menu and the button — which
   would be inert — is hidden. */
.nav-toggle { min-height: 44px; min-width: 44px; font: inherit; font-weight: 350;
              color: var(--tinte); background: none; border: 1px solid var(--feld);
              border-radius: 0; cursor: pointer; padding: 0 var(--sp-4);
              display: none; margin-left: auto; }
.js .nav-toggle { display: block; }
#hauptmenue { display: block; flex-basis: 100%; }
.js #hauptmenue { display: none; }
.js .nav-toggle[aria-expanded="true"] + #hauptmenue { display: block; }
/* This rule deliberately does not also carry `max-width: none`. It is an ID
   selector at (1,0,1), and the desktop panel rule below is `.js .subnav` at
   (0,2,0), so a `max-width` declared here would silently defeat that cap on all
   43 pages and the declared ceiling would do nothing. */
#hauptmenue ul { list-style: none; margin: 0; padding: 0; }
#hauptmenue a { text-decoration: none; color: var(--tinte); font-weight: 350;
                min-height: 44px; display: flex; align-items: center;
                border-bottom: 1px solid var(--linie);
                border-left: 2px solid transparent; padding-left: var(--sp-3); }
#hauptmenue a:hover, #hauptmenue a:focus-visible { border-left-color: var(--limette); }

/* --- Sub-navigation ----------------------------------------------------- */

/* The no-JS baseline is the VISIBLE state and script collapses it. Written the
   other way round, a visitor with JavaScript off would lose all eighteen
   submenu destinations, and this is a health practice whose visitors skew
   older. With no `.js` class on <html> (base.njk adds it in <head>) every
   submenu renders as an ordinary indented nested list and the toggle buttons,
   which nothing could act on, are hidden. */
.has-subnav { display: flex; flex-wrap: wrap; align-items: center; position: relative; }
.has-subnav > a { flex: 1 1 auto; }
.subnav { flex-basis: 100%; }
.subnav-toggle { display: none; min-height: 44px; min-width: 44px; padding: 0 var(--sp-3);
                 align-items: center; justify-content: center; cursor: pointer;
                 font: inherit; color: var(--tinte); background: none;
                 border: 0; border-radius: 0; }
/* A shape, not a glyph: "▾" is absent from Aspekta and would silently fall back
   to a system font at a different size on every platform. */
.subnav-toggle__chevron { width: 0; height: 0; border-left: 5px solid transparent;
                          border-right: 5px solid transparent; border-top: 6px solid var(--blau);
                          transition: transform 150ms ease; }
.subnav-toggle[aria-expanded="true"] .subnav-toggle__chevron { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .subnav-toggle__chevron { transition: none; } }

.js .subnav-toggle { display: flex; }
.js .subnav { display: none; }
.js .subnav-toggle[aria-expanded="true"] + .subnav { display: block; }

.has-subnav > .subnav-toggle { border-bottom: 1px solid var(--linie); }
#hauptmenue .subnav a { padding-left: var(--sp-8); font-size: .95rem; }

@media (min-width: 1150px) {
  /* Both halves of each pair are needed: the `.js`-prefixed rules above win on
     specificity, so unsetting only the bare selector would leave a scripted
     document showing the Menü button next to a full inline menu. */
  .nav-toggle, .js .nav-toggle { display: none; }
  .header-partners, .header-phone { display: block; }
  .header-partners { margin-left: var(--sp-6); }
  #hauptmenue, .js #hauptmenue { display: block; }
  #hauptmenue > ul { display: flex; flex-wrap: wrap; align-items: flex-start;
                     gap: clamp(.75rem, 1.6vw, 1.25rem); }
  /* The header's own bottom padding moves onto the menu items. Two things
     follow, and both were defects when KLAR first rendered them: a dropdown
     anchored at `top: 100%` now hangs FROM the header's rule instead of
     punching through it 16px above, and the pointer can travel from an item
     into its panel without crossing a gap that is outside :hover and would
     snap the panel shut. */
  .site-header { padding-bottom: 0; }
  #hauptmenue > ul > li { padding-bottom: var(--sp-4); }
  #hauptmenue a { font-size: .9rem; padding-left: 0;
                  border-left: 0; border-bottom: 2px solid transparent; }
  #hauptmenue a:hover, #hauptmenue a:focus-visible { border-bottom-color: var(--limette); }
  .has-subnav { gap: var(--sp-1); }
  .has-subnav > a { flex: 0 1 auto; }
  .has-subnav > .subnav-toggle { border-bottom: 2px solid transparent;
                                 min-width: 24px; padding: 0 var(--sp-1); }
  /* Only a scripted document gets the floating panel: unscripted, the submenu
     stays the in-flow nested list above, which grows the menu row instead of
     covering the page with three permanently-open panels.
     20rem, not 15: at 15rem the four longest package names each wrapped to two
     lines, which breaks the 44px row rhythm the drawer keeps at 375px.
     Gesundheitsprogramme has twelve children — 544px of rows — so the height
     cap keeps even that inside a short laptop viewport. */
  .js .subnav { position: absolute; top: 100%; left: 0; z-index: 60;
                min-width: 20rem; max-width: 24rem;
                background: var(--papier); border: 1px solid var(--linie);
                padding: var(--sp-2) 0;
                max-height: min(70vh, 36rem); overflow-y: auto; }
  /* There is deliberately no bare `:hover` selector on this element anywhere in
     this file. Opening the panel straight from CSS would show it independently
     of aria-expanded, which desyncs the two the moment a mouse is the input —
     the panel would be visibly open while the button still reported
     aria-expanded="false" to a screen reader. base.njk's script listens for
     pointerenter/pointerleave and sets the attribute itself, so the
     `[aria-expanded="true"] + .subnav` rule above is the only thing that ever
     shows a panel, by hover or by click, and the attribute is never wrong. */
  #hauptmenue .subnav a { padding: 0 var(--sp-4); font-size: .9rem;
                          border-bottom: 0; border-left: 2px solid transparent; }
  #hauptmenue .subnav a:hover, #hauptmenue .subnav a:focus-visible {
    border-bottom-color: transparent; border-left-color: var(--limette); }
}

/* --- Page / post shell -------------------------------------------------- */

main { max-width: var(--mass); margin: 0 auto; padding: 0 var(--rand);
       /* the header is sticky, so the skip link's jump must clear it */
       scroll-margin-top: 6rem; }
/* From 1150px up the header is two rows and measures ~119px, so a 6rem offset
   would land the skip link's target behind it. */
@media (min-width: 1150px) { main { scroll-margin-top: 8.5rem; } }

/* Everything in the article is indented by --spur, the title and the hero
   included, so the spine's line has a column of its own and no element on the
   page straddles it. Prose is capped at --lauf inside that; wide objects take
   the rest. */
.page, .post { padding-block: clamp(2.5rem, 6vw, 4.5rem);
               padding-left: var(--spur);
               max-width: var(--bahn); margin-inline: auto; }
.page__title, .post__title { max-width: 20ch; margin: 0; }
.post__title { margin-top: var(--sp-2); }

/* --- The spine ---------------------------------------------------------- */

/* The line. `.spine` wraps the whole block run in page.njk — both halves of
   `blockRun`, the news teaser between them and the contact section after them —
   so it is ONE continuous line rather than one per run, which is the difference
   between a spine and a set of tick marks.
   It is drawn at `left: calc(-1 * var(--spur))`: `.spine` is position: relative
   and its padding box starts at the article's content edge, so that lands the
   line exactly on the article's padding edge, in the column --spur reserves.
   `.post` draws its own with the same geometry — a post is one continuous read
   rather than a sequence, so it gets the thread and a single node at its title,
   and no numerals. */
/* `display: flow-root` is load-bearing, not tidiness. Without it the first
   block's `margin-top` collapses out through the wrapper, which moves the
   wrapper's own top edge down past it — and since the line is drawn from the
   wrapper's padding box, the line would start at the first section's title
   instead of coming down to meet it, losing a section's worth of air at the
   top and the same again at the bottom. */
.spine { display: flow-root; }
.spine, .post { position: relative; }
.spine::before, .post::before {
  content: ""; position: absolute; left: calc(-1 * var(--spur));
  top: 0; bottom: 0; width: 2px; background: var(--blau);
}
/* The post's line stops short of the article's own top and bottom padding, so
   it begins at the date and ends with the last line of the body rather than
   floating in the page's margin. */
.post::before { top: clamp(2.5rem, 6vw, 4.5rem); bottom: clamp(2.5rem, 6vw, 4.5rem); }

/* A node marks a NAMED section. The 16 pages whose first block is an untitled
   lead paragraph get no node for it, which is correct rather than a gap: a
   lead-in is not a section, and the line simply runs past it to the first one
   that is.
   Geometry: the disc is centred on the 2px line. The line's centre sits at
   `-var(--spur) + 1px` from the title's left edge, so a 12px disc starts 6px
   left of that. Positioned on `.block__title` rather than on `.block` so `top`
   resolves against the TITLE's own font-size and the node stays on the title's
   optical centre through the whole clamp range, instead of drifting as the
   heading grows from 24px to 32px. */
.block__title { position: relative; }
.block__title::before {
  content: ""; position: absolute;
  left: calc(-1 * var(--spur) - 5px); top: .5em;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--gruen);
}
/* A post's single node, on the same geometry, against the post title. */
.post__title { position: relative; }
.post__title::before {
  content: ""; position: absolute;
  left: calc(-1 * var(--spur) - 5px); top: .45em;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--gruen);
}

/* Sections are separated by the node and the air around it — never by a rule, a
   card, a shadow or a tinted band. That is the whole variant, and it is why
   there is no `border-top` on `.block` anywhere in this file: the spine already
   states where one section ends and the next begins, and a hairline drawn
   across it would be a second answer to a question that has one. */
.block, .newsyear { margin-top: clamp(2.75rem, 5vw, 4.5rem); }
/* The second of the two selectors in this file that put --gruen in `color`, and
   the reason the h2 rule above floors its clamp at 1.5rem rather than at the
   1.35rem that would otherwise have read better on a phone: 24px is the gate,
   and a section title is green. `.block__title` is (0,1,0) and the h1-h6 group
   rule that paints headings --tinte is (0,0,1), so this wins without an
   override. test/contrast reads both selectors out of this file, checks the set
   against the two that are allowed, and resolves the small end of each clamp. */
.block__title { color: var(--gruen); margin: 0 0 var(--sp-6); }
.block > :last-child, .page > :last-child, .post > :last-child,
.spine > :last-child { margin-bottom: 0; }

/* The one filled link in the theme, and the second of the two places --gruen
   appears behind text (the other is the form's submit button). White on
   #548238 is 4.538:1 and is measured against the FILL, not against the page, so
   the brand green can carry text here on a warm ground that its own ink could
   not sit on — which is the whole reason a call to action in this variant is a
   fill rather than a coloured word. Hover swaps to --blau at 7.948:1 rather
   than inverting to an outline, so the target never changes size under the
   pointer. */
.cta { display: inline-flex; align-items: center; min-height: 44px;
       padding: var(--sp-3) var(--sp-8); font-weight: 550;
       color: #fff; background: var(--gruen); text-decoration: none; }
.cta:hover, .cta:focus-visible { background: var(--blau); text-decoration: none; }
/* Prose is capped at the measure; the wide objects below opt out where they are
   defined. A `.cols` grid with a single column is the common case in this
   dataset — thirteen blocks declare `col21` and then carry one column — so the
   column caps itself rather than relying on the grid template. */
.cols { display: grid; gap: var(--sp-8) clamp(2rem, 4vw, 4rem); }
.col { min-width: 0; max-width: var(--lauf); }
@media (min-width: 768px) {
  /* Only where a second column actually exists. The layout name on a block says
     what the SOURCE asked for, not what the block turned out to contain: every
     "Kosten" price panel and nine of the eleven package pages declare `col21`
     and carry a single column, and templating those into two tracks opens an
     empty one beside the text. */
  /* Only the even split opens at this width. An uneven one cannot produce two
     readable columns out of a 660px content box — 1:2 gives 210px, about 21
     characters — so col12/col21 stay stacked here and open at 1024px instead,
     where the floor below can hold them. An even split is 314px each, which is
     tight but is still reading. */
  .cols--col11:has(.col + .col) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cols .col:only-child { grid-column: 1 / -1; }
  /* Two columns of prose are each narrower than the measure, so the cap that
     keeps a single column readable would otherwise also stop the pair from
     using the width they were given. */
  .cols:has(.col + .col) .col { max-width: none; }
}

/* A declared ratio is not a licence to make a column unreadable.
   Six blocks in the whole content tree carry two columns, and `col12` accounts
   for two of them. One is /metabolic-balance/'s opening block, and it is
   inverted: the dump put 291 characters of prose in the 1fr field and a 300px
   logo in the 2fr field, so the flagship treatment page opened with a 276px
   column — about 27 characters a line, over five lines — beside a 553px track
   holding a graphic that needs 300 of it.
   Flooring the narrow track at 20rem gives that block 320px against 526px:
   ~32 characters, which is short but is reading rather than a word ladder, and
   the source's declared emphasis still operates above the floor.
   Held back to 1024px rather than sharing the 768px breakpoint above: at 768px
   the article's content box is 660px, and two 320px floors plus the gutter
   want 736px, which would push the page into a horizontal scroll — the one
   failure tools/screenshot.mjs exits non-zero for.
   WEG-only. The other four themes render this block at their own ratios (KLAR
   at 207:415, narrower still) and correcting it there would re-open their
   verification for a defect this variant does not have to inherit. */
@media (min-width: 1024px) {
  .cols--col12:has(.col + .col) { grid-template-columns: minmax(20rem, 1fr) minmax(0, 2fr); }
  .cols--col21:has(.col + .col) { grid-template-columns: minmax(0, 2fr) minmax(20rem, 1fr); }
}

/* --- The pathway -------------------------------------------------------- */

/* The numbered spine object, and the one place in this theme where 01/02/03 is
   honest: the four steps are an order the visitor actually moves through, and
   Metabolic Balance's four phases are named as a sequence in the client's own
   copy ("der Vorbereitung einer Fastenkur, einer strengen Umstellungsphase,
   einer gelockerten Phase, sowie der abschließenden Erhaltungsphase").
   Rendered as an <ol> because it is one; the numerals are drawn from the
   markup's own order rather than authored into the content.

   A step's node is a RING where a section's node is a disc — same 12px circle
   on the same line, hollowed out. That is the hierarchy: a section is a place
   on the page, a step is a stop along the way. The ring is --gruen and not
   --limette because at 2.028:1 the lighter logo green would be a mark nobody
   could find; --limette's job in this file is the hover stroke, where it sits
   against text that is already legible without it. */
.pathway__intro { margin: 0 0 var(--sp-8); }
.pathway { list-style: none; margin: 0; padding: 0; max-width: var(--lauf); }
.pathway__step { position: relative; padding-bottom: var(--sp-12); }
.pathway__step:last-child { padding-bottom: 0; }
.pathway__step::before {
  content: ""; position: absolute;
  left: calc(-1 * var(--spur) - 5px); top: .3em;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--papier); border: 2px solid var(--gruen);
}
/* The numeral is the clinical layer: mono, tabular, --blau. `tabular-nums` so
   01 and 04 occupy the same advance and the four sit on one vertical edge. */
.pathway__num { font-family: var(--mono); font-weight: 500; font-size: .78rem;
                letter-spacing: .12em; color: var(--blau);
                font-variant-numeric: tabular-nums; margin: 0 0 var(--sp-1); }
.pathway__title { margin: 0 0 var(--sp-2); font-size: 1.25rem; color: var(--tinte); }
.pathway__text { margin: 0; }

/* --- Hero, galleries, lists --------------------------------------------- */

/* The hero takes the full article width rather than the prose measure — 880px
   at 1280px, against KLAR's 622px — which is what "photography-led" costs and
   buys. No frame: KLAR sets its hero between two hairlines because a hairline
   is that theme's separator, and WEG's separator is the spine. */
.page__hero { margin: clamp(1.5rem, 4vw, 2.5rem) 0 0; }
.post__hero { margin: clamp(1.5rem, 4vw, 2.5rem) 0 0; }
.page__hero img, .post__hero { width: 100%; aspect-ratio: 21 / 9; object-fit: cover; }
@media (max-width: 767px) { .page__hero img, .post__hero { aspect-ratio: 4 / 3; } }

/* auto-fill rather than auto-fit for the multi-image case: with fewer images
   than fill a row, auto-fill leaves the unused tracks empty instead of
   stretching the existing photographs to cover them, so a two-image row stays
   two photographs at their natural size. A lone image — three of the four
   galleries in this dataset hold exactly one — is not a row at all and is
   handled separately, because auto-fill would strand it in the first track.
   Square is the DEFAULT crop, not the only one: content/pages/*.json may give
   an image an `aspect`, emitted by block.njk as --ar. The practice room is a
   4:3 photograph and says so rather than losing a third of the room to a
   square. */
.gallery { list-style: none; margin: var(--sp-8) 0 0; padding: 0;
           display: grid; gap: var(--sp-4);
           grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr)); }
.gallery:not(:has(li + li)) { grid-template-columns: minmax(0, 1fr); }
.gallery img { width: 100%; aspect-ratio: var(--ar, 1); object-fit: cover; }

/* A lone photograph sits BESIDE its prose, never under it at the article's
   full width. This is the defect the client circled on the other four themes
   (736e2cb) arriving in WEG's own geometry, and it is worth stating exactly
   what it measured here before the fix, because the cause is different:
   WEG has no label column, so a `.gallery` simply filled the block — and a
   lone image defaults to a SQUARE crop, so at 1280px the "Über uns" headshot
   rendered 880 x 880 and the practice-room photograph 880 x 660. Between them
   they added roughly 1,540px to the homepage, and the headshot alone was a
   square the height of a laptop viewport.
   Measured across the whole content tree first: exactly two blocks in 59 carry
   a single image and one carries several, so this rule is aimed at two
   sections of one page and the multi-image gallery keeps the wide grid above.
   20rem for the figure leaves the prose 504px at 1280px — about 50 characters,
   which is short of the 64ch measure and correct for it: this is a two-column
   arrangement, and the alternative was 880px of picture.
   `:has(…)` and not a class, because the condition is about what the block
   turned out to CONTAIN. block.njk is shared markup logic across five themes
   and the layout name in the source says what was asked for, not what arrived
   — the same trap the `.cols--col21:has(.col + .col)` qualifier exists for.

   The lone image is detected as `> .gallery > li:only-child` and NOT as the
   `.gallery:not(:has(li + li))` written one rule above, which would have read
   more consistently. It cannot be: Selectors 4 forbids `:has()` inside another
   `:has()`, so `.block:has(> .gallery:not(:has(li + li)))` is an invalid
   selector. It does not throw — the whole rule is simply dropped at parse time
   and every declaration in it silently does nothing, which is how it shipped
   once and measured 880 x 880 on the headshot afterwards. `:only-child` states
   the same condition with no nesting. */
@media (min-width: 64rem) {
  .block:has(> .gallery > li:only-child) {
    display: grid; align-items: start;
    grid-template-columns: minmax(0, 1fr) minmax(0, 20rem);
    column-gap: clamp(2rem, 4vw, 3.5rem);
  }
  .block:has(> .gallery > li:only-child) > * { grid-column: 1; }
  /* The title keeps the full width so its node stays on the spine and the
     heading is not squeezed into the prose track. */
  .block:has(> .gallery > li:only-child) > .block__title { grid-column: 1 / -1; }
  /* Row 2 is the prose; spanning two rows lets the picture stand beside both
     the prose and the link under it rather than forcing the row taller. */
  .block:has(> .gallery > li:only-child) > .gallery {
    grid-column: 2; grid-row: 2 / span 2; margin-top: 0; }
}

/* Link lists are the one navigational idiom inside the page. KARTEN makes them
   pills and KLAR makes them ruled rows; WEG makes them a run of nodes off the
   spine's own vocabulary — a small --limette tick at the row's left edge, the
   label in the clinical blue, a comfortable 44px target. The list keeps the
   measure: a row of link text is reading, not a wide object. */
.linklist { list-style: none; margin: var(--sp-8) 0 0; padding: 0; max-width: var(--lauf); }
.linklist li { border-top: 1px solid var(--linie); }
/* A boundary belongs to the element BELOW it and is drawn as a border-top; a
   border-bottom is never a separator here, only a TERMINATOR — the closing line
   of a run when nothing below is going to draw that line anyway. Unconditional,
   it put a closing rule and then the next section's own air on the three
   homepage sections that end in a link list. */
.linklist li:last-child { border-bottom: 1px solid var(--linie); }
.block:has(+ .block, + .newsyear) .linklist li:last-child { border-bottom: 0; }
.linklist a { display: block; padding: var(--sp-4) 0; font-weight: 550;
              color: var(--blau); text-decoration: none; }

/* Offer row — the same link data drawn as what it actually is. "Unsere
   Angebote" is the section that asks the visitor to choose between the
   practice's three treatments, so it shows them. Wide: it opts out of the prose
   measure and takes the article.
   auto-fit rather than a fixed repeat(3, 1fr): the section takes whatever
   number of entries the content declares, and three is today's, not a rule.
   The 14rem floor puts three across from 768px up and drops to two, then one,
   on a phone without a breakpoint of its own. */
.offers { list-style: none; margin: var(--sp-8) 0 0; padding: 0; max-width: none;
          display: grid; gap: var(--sp-8) clamp(1.5rem, 3vw, 2.5rem);
          grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); }
/* The picture is as much the target as the title, so the whole cell is
   clickable — but the LINK stays wrapped around the title alone, so the
   accessible name is "Metabolic Balance" and not the picture's alt text with
   the blurb read out after it. The overlay makes the cell clickable; the anchor
   text makes it announceable. */
.offer { position: relative; min-width: 0; }
.offer > img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; display: block; }
.offer__title { font-size: 1.15rem; margin: var(--sp-4) 0 0; }
.offer__title a { color: var(--blau); text-decoration: none; }
.offer__title a::after { content: ""; position: absolute; inset: 0; }
.offer__blurb { margin: var(--sp-2) 0 0; }

/* --- News --------------------------------------------------------------- */

/* §5 of the 2026-08-10 spec makes the year a required mitigation rather than an
   option: ten of the fourteen posts are promotions that expired years ago, and
   the year is what makes the list read as an archive instead of as news. It is
   a numeral, so it belongs to the clinical layer — mono, tabular, --blau at
   7.694:1 — which also happens to solve the problem KLAR had to solve another
   way: the brief sketched a pale numeral in the hairline colour, and --linie is
   1.319:1, so the one element the spec calls load-bearing would have been
   almost invisible. */
.newsyear__title { font-family: var(--mono); font-weight: 400;
                   font-size: clamp(1.75rem, 1.2rem + 1.8vw, 2.5rem);
                   line-height: 1; letter-spacing: -.01em; color: var(--blau);
                   font-variant-numeric: tabular-nums; margin: 0 0 var(--sp-8);
                   position: relative; }
/* A year is a real position in a sequence, so it takes a node like any other
   named section. Mono has no clamp-driven size drift to track, so the offset is
   flat. */
.newsyear__title::before {
  content: ""; position: absolute;
  left: calc(-1 * var(--spur) - 5px); top: .32em;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--gruen);
}

/* Post rows — a hairline above each and the thumbnail left, mirroring the live
   site's news listing, which the client asked for by name. No fill and no
   border on three sides: the single rule is the whole frame. The first row
   needs neither, since the year numeral or the block title already sits above
   it. A post list is a wide object and takes the article. */
.postlist { list-style: none; padding: 0; margin: 0; max-width: none;
            display: grid; gap: 0; }
.postcard { display: grid; gap: var(--sp-6) clamp(1.5rem, 3vw, 2.5rem); align-items: start;
            border-top: 1px solid var(--linie);
            padding-block: clamp(1.5rem, 4vw, 2.5rem); }
.postcard:first-child { border-top: 0; padding-top: 0; }
/* Capped at 1023px so the two ranges are disjoint: from 64rem up the rule below
   takes over, and without the cap both would match at every width >= 1024px
   with equal specificity — harmless as they are ordered now, but a reordering
   would silently swap them. */
@media (min-width: 768px) and (max-width: 1023px) { .postcard { grid-template-columns: minmax(0, 240px) minmax(0, 1fr); } }
@media (min-width: 1024px) { .postcard { grid-template-columns: minmax(0, 17rem) minmax(0, 1fr); } }
.postcard__thumb { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
/* Three stacked thumbnails at 4:3 were the single biggest run of height on the
   homepage, and a teaser thumbnail is the one picture on the page that buys
   nothing from being tall. */
@media (min-width: 1024px) { .postcard__thumb { aspect-ratio: 16 / 9; } }
.postcard__body { min-width: 0; }
.postcard__body > :last-child { margin-bottom: 0; }
.postcard h3 { margin: var(--sp-2) 0 0; }
.postcard h3 a { color: var(--blau); text-decoration: none; }
/* A date is a numeral: mono, tabular, clinical blue. */
.post__date { font-family: var(--mono); font-weight: 400; color: var(--blau);
              font-size: .8rem; letter-spacing: .04em;
              font-variant-numeric: tabular-nums; margin: 0; }
.post__excerpt { margin: var(--sp-3) 0 0; }
.postcard__more { font-weight: 550; color: var(--blau);
                  min-height: 44px; display: inline-flex; align-items: center; }
/* Homepage-only: the author-written excerpts run 199-752 characters, which is
   wanted on /news but would swamp a homepage that teases three posts. Clamped
   in the DOM rather than in the content, and scoped to the homepage wrapper so
   /news's own postcards are untouched. */
.block--newsteaser .post__excerpt {
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}

/* --- Body copy ---------------------------------------------------------- */

.post__body { max-width: var(--lauf); }

/* The Sommer-Special post uses <h5> as a pseudo-bullet ("<h5>-- Ausreinigung
   </h5>"), five in a row, and the client caught the resulting spacing on a
   phone. A run of same-level headings is one list, not five sections: left on
   the section-heading margin it read as 48px in / 8px between items / 8px out /
   20px to the NEXT package — far more space inside a group than between two,
   with proximity saying the opposite of what the structure means.
   WEG's rhythm is 20px between groups (the paragraph gap above, untouched) and
   8px everywhere inside one — 2.5x, where KLAR is exactly 2x because its
   paragraph gap is deliberately the tightest of the set.
   Rank as well as rhythm: the items take body ink and body weight rather than
   the heading treatment, so the contents of a package no longer read louder
   than the package. The hanging indent is the one addition — "--" is the
   client's own bullet character, so it is treated as a marker and the wrapped
   second line of "-- endermologie® Detox mit Strahlkraft Treatment" aligns
   under the text rather than under the dashes.
   Scoped to h5 and NOT to :is(h4,h5,h6) on purpose: /datenschutzerklaerungen/
   spends 21 <h4>s as real sub-headings, each followed by prose, and they must
   keep the section spacing the group rule gives them. */
.post__body h5, .col h5 { margin: var(--sp-2) 0; color: var(--tinte);
                          font-weight: inherit; letter-spacing: normal;
                          padding-left: 1.6em; text-indent: -1.6em; }
.post__body p:has(+ h5), .col p:has(+ h5) { margin-bottom: var(--sp-2); }
.post__body > :first-child, .col > :first-child { margin-top: 0; }
.post__body > :last-child, .col > :last-child { margin-bottom: 0; }

/* --- Tables ------------------------------------------------------------- */

/* A price table is data, so the whole of it belongs to the clinical layer:
   tabular figures throughout, and the head in mono caps. Ruled rows only, and
   the outer cells lose their padding so the table's ink lines up with the text
   column beside it. */
table { font-size: .95rem; max-width: none; }
th, td { border-bottom: 1px solid var(--linie); padding: var(--sp-3) var(--sp-4);
         text-align: left; vertical-align: top; font-variant-numeric: tabular-nums; }
th:first-child, td:first-child { padding-left: 0; }
th:last-child, td:last-child { padding-right: 0; }
th { font-family: var(--mono); font-weight: 500; font-size: .78rem;
     letter-spacing: .1em; text-transform: uppercase; color: var(--blau); }
/* Inline text-align:right on price cells was stripped from the source content
   (fixed pixel widths broke the responsive layout); every price in this dataset
   lands in the last cell of its row. */
.page table td:last-child, .post table td:last-child { text-align: right; }

/* --- Team --------------------------------------------------------------- */

/* The team grid is included after the last block on /ueber-uns/ and is not a
   `.block`, so it inherits none of the section spacing. Without a top margin
   the group photograph butted straight onto the closing hairline of the "Mehr
   über uns" link list — the picture read as a fifth row of that list. It takes
   the same air a section does. */
.team-photo-group { margin: clamp(2.75rem, 5vw, 4.5rem) 0 clamp(2rem, 5vw, 3.5rem); }
.team-photo-group img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.team-photo-group figcaption { font-family: var(--mono); font-size: .78rem;
                               color: var(--blau); margin-top: var(--sp-3); }

/* Three of the four members are unfilled roles the client wants shown. They get
   the identical card and the identical square, drawn as a hairline box holding
   the monogram. A placeholder has to read as a typographic decision and never
   as an image that failed to load, so it gets no dimming, no dashed border and
   no "coming soon" treatment that would mark it as lesser. */
.team { list-style: none; margin: clamp(2rem, 5vw, 3.5rem) 0 0; padding: 0; max-width: none;
        display: grid; gap: clamp(1.75rem, 4vw, 3rem);
        grid-template-columns: minmax(0, 1fr); }
@media (min-width: 640px)  { .team { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .team { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
/* No rule down the side of a card, which is what KLAR does and what this
   started as. In a theme whose one structural device is a vertical line with
   green marks on it, four more green vertical lines standing beside the spine
   read as four more spines — the eye cannot tell which line is the page's
   structure and which is decoration on a card. The photograph and the
   monogram box already delimit a card; the rule was the accessory to remove. */
.team-card { min-width: 0; }
/* --team-ar is set on .team itself (teamgrid.njk) from the member photograph's
   own dimensions. It is absent unless a member declares dimensions, which is
   why every fallback below is the square this shipped with. */
.team-photo, .team-monogram { width: 100%; aspect-ratio: 1; }
.team-photo { object-fit: cover; aspect-ratio: var(--team-ar, 1); }
/* The monogram follows the photograph's box only where a card actually shares
   a row with it — which is the entire reason it follows at all: WEG's member
   photograph is a 3:4 portrait, and three squares beside it would drop her
   name a row below the other three. Below 640px the grid is one column, so
   there is no row to align and a 3:4 monogram is 100px of extra empty box per
   placeholder — 300px of nothing on the page where it is most expensive. */
@media (min-width: 640px) { .team-monogram { aspect-ratio: var(--team-ar, 1); } }
.team-monogram { display: grid; place-items: center; border: 1px solid var(--linie);
                 font-family: var(--mono); font-weight: 400; color: var(--blau);
                 letter-spacing: .1em; font-size: clamp(1.75rem, 4vw, 2.25rem); }
.team-name { font-weight: 550; margin: var(--sp-4) 0 0; }
/* A role here is a credential — "Dipl. Ernährungsberaterin Metabolic Balance" —
   so it takes the clinical layer rather than the prose treatment. */
.team-role { font-family: var(--mono); font-weight: 400; font-size: .78rem;
             line-height: 1.5; color: var(--blau); margin: var(--sp-1) 0 0; }

/* --- Contact form ------------------------------------------------------- */

/* The /kontakt [contact-form-7] shortcode, rendered as a real but
   presentational-only form (it carries a German note saying so). WEG has no
   boxes, so a field is a baseline rule that turns blue on focus, and its label
   is a mono caps line above it — the same clinical layer as every other label
   on the site. The submit button is the one filled element in the theme, and it
   is the one place --gruen appears behind text: white on #548238 is 4.538:1 and
   is independent of the page ground, which is what lets the brand green fill a
   control on a warm surface that its own text could not sit on. */
.contact-form { max-width: 32rem; }
.contact-form__note { color: var(--tinte); font-size: .92rem;
                      margin: 0 0 var(--sp-8); padding-bottom: var(--sp-6);
                      border-bottom: 1px solid var(--linie); }
.form-field { display: grid; gap: var(--sp-2); margin-bottom: var(--sp-6); }
/* The honeypot field (CONTACT_FORM_LIVE_HTML in tools/lib/html.mjs) must
   stay hidden UNCONDITIONALLY — unlike .visually-hidden (assets/tokens.css),
   which deliberately un-hides on :focus so the skip link becomes a real
   visible control for a keyboard user. A honeypot has the opposite
   requirement: a sighted visitor who tabs or clicks into a visible field
   labelled "Website" would have their enquiry silently rejected as spam by
   the handler (Task 8), inverting the field from catching bots to catching
   the most conscientious humans. So this rule carries no :focus escape
   hatch. tabindex="-1" already keeps the field out of the tab order and
   aria-hidden="true" already keeps it out of the accessibility tree; this
   is what keeps it out of sight for a mouse/touch user too. Lives here, in
   WEG's own theme.css, rather than in the shared assets/tokens.css: WEG is
   the only theme production ever renders and the live form exists only in
   production, so this rule has no reason to reach the other four themes. */
.form-field--hp {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
/* The rule under a field is --feld, not --linie: it is the boundary of a
   control rather than a separator between sections, and it is measured against
   3:1 rather than left decorative. Resting and focus stay clearly different
   states, and the difference is not carried by the ratio between the two
   colours alone: on focus the rule turns blue AND the theme's 2px --blau ring
   appears around the whole field, so a focused field has an edge on four sides
   where a resting one has an edge on one. */
.form-field input, .form-field textarea {
  width: 100%; min-height: 44px; font: inherit; color: var(--tinte);
  background: transparent; border: 0; border-bottom: 1px solid var(--feld);
  border-radius: 0; padding: var(--sp-2) 0; }
.form-field input:focus-visible, .form-field textarea:focus-visible { border-bottom-color: var(--blau); }
.form-field textarea { resize: vertical; min-height: 8rem; }
.contact-form__submit { min-height: 44px; padding: var(--sp-3) var(--sp-8);
                        font: inherit; font-weight: 550;
                        color: #fff; background: var(--gruen);
                        border: 1px solid var(--gruen); border-radius: 0; cursor: pointer; }
.contact-form__submit:hover, .contact-form__submit:focus-visible {
  color: #fff; background: var(--blau); border-color: var(--blau); }

/* --- Footer ------------------------------------------------------------- */

/* Same geometry as .site-header and for the same reason: the footer's
   border-top is the closing rule of the page and has to land on the page
   measure, so the gutter is width rather than padding-inline and the box is
   capped at --bahn. */
.site-footer { border-top: 1px solid var(--linie); margin: clamp(3rem, 8vw, 6rem) auto 0;
               width: calc(100% - 2 * var(--rand));
               max-width: var(--bahn); padding: clamp(2rem, 5vw, 3rem) 0 clamp(3rem, 6vw, 4rem); }
/* The footer's small type sits on its CHILDREN, not on the footer box, and that
   is load-bearing rather than a style: --bahn is in rem so it does not move,
   but --lauf is in `ch` and `ch` resolves against the font-size of the element
   the length is used on, so a font-size on the footer itself would shrink every
   measure inside it against the sections above. */
.site-footer > * { font-size: .9rem; }
.site-footer nav ul { list-style: none; margin: var(--sp-6) 0 0; padding: 0;
                      display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-6); }
.site-footer nav a { text-decoration: none; }

/* Opening hours and "Praxis für", from the two legacy footer widgets. The hours
   are the most looked-up fact on a walk-in practice's site, so they are set in
   the clinical layer at full body colour rather than shrunk into fine print —
   mono and tabular, so the day column and the time column each hold one edge
   and the block reads as a plaque rather than as a paragraph.
   Both panel headings are <h2> in the markup; at footer scale a 24px green
   heading would shout, and it would also be the one place in this file where
   green fell under its own 24px gate — so they become mono caps labels, which
   is what they are. */
.footer-panels { display: grid; gap: var(--sp-8) clamp(2rem, 4vw, 4rem);
                 margin-top: var(--sp-8); padding-top: var(--sp-8);
                 border-top: 1px solid var(--linie); }
@media (min-width: 600px) { .footer-panels { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* A grid item's automatic minimum size is its content's min-content, so the
   opening-hours definition list would push its own track wider than its 1fr
   share and overflow the footer at ~600px. */
.footer-hours, .footer-focus { min-width: 0; }
.footer-hours__title, .footer-focus__title { margin: 0 0 var(--sp-4); }
.hours { display: grid; grid-template-columns: auto minmax(0, 1fr);
         gap: var(--sp-2) var(--sp-6); margin: 0;
         font-family: var(--mono); font-size: .82rem;
         font-variant-numeric: tabular-nums; }
.hours dt { font-weight: 500; color: var(--tinte); }
.hours dd { margin: 0; color: var(--tinte); }
.footer-hours__note { margin: var(--sp-4) 0 0; max-width: 34ch; }
.focus-group + .focus-group { margin-top: var(--sp-6);
                              padding-top: var(--sp-6); border-top: 1px solid var(--linie); }
.focus-group__heading { margin: 0; font-weight: 550; color: var(--tinte); max-width: none; }
.focus-group ul { list-style: none; margin: var(--sp-1) 0 0; padding: 0; }

/* Partner badge (Metabolic Balance) — a credential, not a call to action, so it
   stays neutral until hovered rather than competing with the footer nav above
   it. It is another company's mark, so it is desaturated outright; the
   treatment is declared once near the top of this file, with the header copy. */
.partner-badges { margin: var(--sp-8) 0 0; max-width: none; }
.partner-badge img { width: 120px; }
