/*
 * A practice's public page. `components/OeffentlicheSeite` is the rule it is
 * built to; docs/plans/practice_microsites_plan.md T14 is the build.
 *
 * Token VALUES are GENERATED between the markers below, from design/ via
 * scripts/build_design_tokens.py, and CI fails if they drift (--check). They
 * used to be hand-copied from the design skill's tokens/*.css (V2,
 * 2026-09-09) and had gone stale: this file shipped --paper-inset #FAF9F6 to
 * a public surface after the system moved it to #F7F5EF. Do not edit inside
 * the markers; everything below them is hand-written.
 *
 * NOTHING ON THIS PAGE IS FETCHED FROM ANOTHER HOST. No @import, no url() to
 * a third party, no icon set, no map embed, no analytics. The one @font-face
 * below points at this origin's own copy of the typeface, served by
 * praxista-microsites and kept byte-identical to design/fonts/ by the same
 * generator -- "selbst gehostet von der eigenen Adresse" (Entscheidung 3),
 * which is a requirement of the page and not a performance preference.
 */
/* >>> BEGIN GENERATED -- design tokens. Do not edit inside these markers. */
/* Source: design/tokens.json + design/bundle.css (hash 2ff9f31da520).
 * Regenerate: python3 scripts/build_design_tokens.py
 * The @font-face IS here, and no @theme: this file is served by a
 * static server that must make no third-party request, and has no
 * Tailwind. The microsite's own locals are below the end marker.
 *
 * The font moved into this generated half on 2026-10-07. It was
 * omitted on the premise that `the microsite fetches no font`, which
 * stopped being true when T14 gave the microsite host its own
 * /fonts/ route -- and the cost of the stale premise was paid twice:
 * the practice page carried a HAND-WRITTEN rule declaring
 * `font-weight: 100 900` where design/tokens.json says `200 700`,
 * and the four platform pages (Impressum, Datenschutz,
 * Nutzungsbedingungen, 404) had no rule at all and rendered in
 * Helvetica. Generated from one source, --check now holds both in
 * step with the vendored binary.
 *
 * Only the 42 tokens the hand-written CSS below actually
 * reads, plus what those reference. This is a public page the plan
 * keeps small; emitting all of them put ~95 unread declarations and a
 * grain data URI on it. To use another token, reference it in the
 * hand-written CSS below and regenerate -- nothing is listed twice.
 * (Deliberately no var() example here: this header is inside the
 * generated half, and a scanner reading the other half must not
 * find a token name in a comment.) */
:root {
  --font-hanken: "Hanken Grotesk","Helvetica Neue",Helvetica,Arial,sans-serif;
  --font-display: var(--font-hanken);
  --font-ui: var(--font-hanken);
  --font-data: var(--font-hanken);
  --type-title: 300 32px/1.2 var(--font-display);
  --type-body: 400 18px/1.6 var(--font-ui);
  --type-data: 500 14px/1.4 var(--font-data);
  --type-label: 500 11px/1.2 var(--font-data);
  --type-label-tracking: 0.1em;
  --blue-900: #1B3E9E;
  --blue-700: #2A56C8;
  --paper-page: #FCFCFA;
  --paper-white: #FFFFFF;
  --paper-inset: #F7F5EF;
  --text-body: #0F2438;
  --text-secondary: #3E5060;
  --text-label: #6E6653;
  --text-on-deep: #FFFFFF;
  --line: #E3E7EB;
  --line-row: #EFF2F4;
  --focus: var(--blue-700);
  --heading: var(--blue-700);
  --link: var(--blue-700);
  --link-hover: var(--blue-900);
  --measure-patient: 620px;
  --page-w-patient: 684px;
  --space-0: 4px;
  --space-1: 8px;
  --space-2: 12px;
  --space-3: 20px;
  --space-4: 32px;
  --space-5: 52px;
  --radius-image: 20px;
  --radius-card: 16px;
  --radius-control: 999px;
  --control-min-patient: 48px;
  --focus-width: 2px;
  --focus-offset: 2px;
  --elev-flat: none;
  --elev-card: 0 1px 1px rgba(20,40,94,0.04),0 2px 6px rgba(20,40,94,0.05);
  --grad-control: linear-gradient(103deg,#1B3E9E 0%,#2A56C8 100%);
  --grad-control-hover: linear-gradient(103deg,#173586 0%,#2449AA 100%);
}

/* The brand typeface, self-hosted. Praxista is EU-sovereign on OTC and
   serves German health data; a Google Fonts request transmits the
   visitor's IP to a US controller, which is the pattern LG Muenchen I
   ruled on (3 O 17493/20). SIL OFL 1.1 -- the licence ships beside the
   binary. One variable file, no subsetting: it carries 550 codepoints,
   a superset of the two subset files it replaced (latin 222 +
   latin-ext 199 = 409, nothing lost). */
@font-face {
  font-family: "Hanken Grotesk";
  src: url("/fonts/HankenGrotesk-wght.woff2") format("woff2");
  font-weight: 200 700;
  font-style: normal;
  font-display: swap;
}
/* <<< END GENERATED */
/* ===========================================================================
 * Hand-written half. `components/OeffentlicheSeite`, Entscheidungen 1-14
 * (04./05.10.2026) -- the page is ONE document, not four cards, and its rank
 * is made of space (Entscheidung 10): `--gap-section` between the three
 * Abschnitte, `--gap-block` between the blocks of one Abschnitt, and nothing
 * else. No heading, no label, no line, no surface per Abschnitt.
 *
 * Four names below are aliases, not new values: the system has no token
 * called `--gap-block` or `--gap-section` -- those are the *roles* of
 * `--space-4` and `--space-5`, and `--space-5`'s own usage note names this
 * exact application ("Abstand zwischen Abschnitten der Patientenseite").
 * ========================================================================= */

/* Entscheidung 3: *„Die Schrift bleibt Hanken Grotesk, selbst gehostet von
   der eigenen Adresse."* Absolute path, because the published pages live
   under `/<slug>/` and the binary is served once by the microsite host --
   `praxista-microsites/microsites/app.py` has the route, and
   `scripts/build_design_tokens.py` keeps the copy byte-identical to
   `design/fonts/`.

   `font-display: swap` is deliberate on a page whose whole job is to be read
   in under a minute: the text is there immediately in the fallback and
   re-renders when the face arrives, rather than being invisible first. */

:root {
  --gap-block: var(--space-4);          /* 32, between blocks of an Abschnitt */
  --gap-section: var(--space-5);        /* 52, between the three Abschnitte */
  --radius-doc: var(--radius-card);     /* 16, the card role (Entscheidung 2) */
  --measure: var(--measure-patient);    /* 620, flowing text */
}

/* Entscheidung 1: patient density, declared ONCE at the root, and after that
   only the density-free names. The system sets these under `[data-density]`
   in its own bundle; this page has exactly one density, so the mapping is one
   line each rather than a cascade we would have to carry. */
[data-density="patient"] {
  --type-body-d: var(--type-body);      /* 18/1.6 */
  --type-title-d: var(--type-title);    /* 32/1.2, weight 300 */
  --type-data-d: var(--type-data);      /* 14/1.4, weight 500 */
}

* { box-sizing: border-box; }

html { color-scheme: light; }           /* one theme; no prefers-color-scheme */

body {
  margin: 0;
  background: var(--paper-page);
  color: var(--text-body);
  font: var(--type-body-d);
  -webkit-font-smoothing: antialiased;
}

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--paper-white);
  color: var(--text-body);
  padding: var(--space-2) var(--space-3);
}
.skip-link:focus { left: var(--space-3); z-index: 10; }

/* Entscheidung 2: one column on `--paper-page`, at most `--page-w-patient`;
   flowing text inside it stays within `--measure`. */
.page {
  max-width: var(--page-w-patient);
  margin: 0 auto;
  padding: var(--space-5) var(--space-3) 0;
}

/* --- The document ------------------------------------------------------- */
/* ONE card carrying every block. Four cards is what this page used to be,
   and it is the second of Entscheidung 9's three causes: "everything sat
   equally far apart, so nothing was a group and nothing was first". */
.doc {
  background: var(--paper-white);
  border: 1px solid var(--line);
  border-radius: var(--radius-doc);
  box-shadow: var(--elev-card);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--gap-section);              /* the three Abschnitte */
}

/* An Abschnitt carries no word: no heading, no register line, no rule, no
   surface, no number (Entscheidung 10). It is a group made of distance, and
   an empty one is absent together with its gap -- which `gap` gives us for
   free, where a `margin-top` would have left the hole behind. */
.abschnitt {
  display: flex;
  flex-direction: column;
  gap: var(--gap-block);
}

.block { margin: 0; max-width: var(--measure); }

/* --- Abschnitt I: identity, the two statements, the practice's own text -- */
/* Entscheidung 13.1 deleted the eyebrow line. The head begins with the
   practice's own mark where there is one, and otherwise straight with the
   name: a taxonomy above a name is a directory record, and the
   Qualifikationszeile says the same thing more precisely. */
.mark {
  display: block;
  height: 34px;                         /* Entscheidung 14: "34 px hoch und verschiebt nichts" */
  width: auto;
  max-width: 100%;
  object-fit: contain;
  margin-bottom: var(--space-2);
}

.name {
  margin: 0;
  font: var(--type-title-d);
  color: var(--heading);
}
.person { margin: var(--space-1) 0 0; }

/* Kopf (15.10.2026, Adonis): kleines Zeichen und kleiner Praxisname, dann das
   grosse Foto, dann die Person. */
.brand { display: flex; align-items: center; gap: var(--space-2); }
.brand .mark { margin-bottom: 0; }
.brand__name { margin: 0; color: var(--text-secondary); }
.hero { display: flex; flex-direction: column; gap: var(--space-3); }
.hero__text { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.ausbildung { margin: 0; }
.begruessung { margin: var(--space-1) 0 0; font-size: 1.125em; line-height: 1.5; }
.cta { margin-top: var(--space-2); }

/* Kontakt: aufklappbar. Der Kasten ist die Schaltflaeche -- mindestens 48 px. */
.kontakt__summary {
  cursor: pointer;
  min-height: var(--control-min-patient);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  list-style: none;
  font-weight: 600;
  color: var(--heading);
}
.kontakt__summary::-webkit-details-marker { display: none; }
/* The disclosure's mark: a chevron that turns when it is open. */
.kontakt__summary::after {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  margin-right: var(--space-1);
}
.kontakt[open] > .kontakt__summary::after { transform: rotate(225deg); }
.kontakt__body { display: flex; flex-direction: column; gap: var(--space-3); padding-top: var(--space-1); }
.qualification { margin: var(--space-0) 0 0; color: var(--text-secondary); }

/* Entscheidung 4 + 5: the statement itself, body text on paper, alone on its
   line. No chip, no label, no tint, no surface, no border, no symbol -- and
   ONE rule for all four values. There is deliberately no class per state:
   "Wer im Markup oder im Stilblatt eine Klasse pro Aussage anlegt, hat die
   Entscheidung gebrochen, auch wenn beide Klassen heute gleich aussehen --
   die Verzweigung ist die Einladung." This page shipped four tinted pill
   classes until 05.10.2026. */
.aussage { margin: 0; }

/* The waiting time is rank 3, a Metazeile, and it belongs to the statement
   above it -- so it sits tight under it, not a block away. */
.meta {
  margin: var(--space-0) 0 0;
  font: var(--type-data-d);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}

/* Entscheidung 12: the Abrechnung takes the availability statement's rank --
   one line of body text, `--gap-block` beneath it, below the Metazeile where
   there is one. Never absent: a practice that ticked nothing has not said no.
   Same single rule, no branch per value. */
.abrechnung { margin: 0; }

/* Entscheidung 6 of `components/Bildsprache` + the Nachtrag of 05.10.: the
   one place on the page that is READ rather than scanned. A register line and
   one to five whole sentences the practice wrote. No surface, no border, no
   tint -- the page's one recess belongs to the Pflichtangaben. */
.label {
  /* `--space-2` rather than `--space-1`, and the reason is visible only in
     the recess: there the register line `PFLICHTANGABEN` is followed by
     another 11 px uppercase label (`BERUFSBEZEICHNUNG`), and at 8 px the two
     read as one stack of small caps. 12 px separates them without becoming a
     block gap. Above body text (`ZUR ARBEITSWEISE`) either value reads. */
  margin: 0 0 var(--space-2);
  font: var(--type-label);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--text-label);
}
.arbeitsweise p { margin: 0 0 var(--space-2); }
.arbeitsweise p:last-child { margin-bottom: 0; }

/* --- The one photograph ------------------------------------------------- */
/* `components/Bildsprache` Entscheidung 4: a Bildfeld in the system's sense --
   `--radius-image` (20), `--elev-flat`, 1px `--line` all round, paper on every
   side, never borderless. Nothing on it, nothing behind it: no gradient, no
   grain, no type, no mark, no darkening.
   
   **Never round.** That is a token argument and not a taste: a round portrait
   needs `--radius-control` (999), and that radius is taken -- *„Die Pillenform
   ist in Praxista das Versprechen, dass man drücken kann."* A round face would
   wear the one shape that means "press me".
   
   **The page neither crops nor upscales.** Exactly one of the two dimensions is
   ever bound and the other follows, which is why this needs no measure and no
   token: `max-width` with `height: auto` is the whole rule below 480 px. */
.portrait { display: block; }
.portrait img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center top;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius-image);
  box-shadow: var(--elev-flat);
}

/* --- Row pairs (Abschnitt II and the Stichpunkte) ----------------------- */
/* Label in the label register, value in body text, separated by
   `--line-row`. Separated by hairline and paper tone, never by a shadow. */
.paare { margin: 0; }
.paar {
  display: grid;
  grid-template-columns: 11em 1fr;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-2) 0;
  border-top: 1px solid var(--line-row);
}
.paar:first-child { border-top: 0; padding-top: 0; }
.paar:last-child { padding-bottom: 0; }
.paar dt {
  font: var(--type-label);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--text-label);
  padding-top: var(--space-0);          /* optical: 11px cap height against 18px body */
}
.paar dd { margin: 0; }

/* A Beizeile: a line under a value, inside the same Zeilenpaar, with no
   label of its own -- the label above is already its reference. The page has
   done this since 04.10. with "Auf Karte zeigen"; Entscheidung 19 named it
   and it now occurs three times (the map link, the practice's two sentences
   under the address, the Berufsordnung under its chamber). */
.beizeile { margin: var(--space-0) 0 0; }

/* P-36 T17, Lücke 4 · Entscheidung 2: two addresses under one label, with
   --space-3 between them and NO line -- less than the 24 px with a hairline
   between two pairs, so the eye reads two addresses under one label; a
   hairline would read as a new pair without one. */
.anschriften { list-style: none; margin: 0; padding: 0; }
.anschriften > li + li { margin-top: var(--space-3); }
.hours { list-style: none; margin: 0; padding: 0; font-variant-numeric: tabular-nums; }
.hours li { display: flex; gap: var(--space-2); }
.hours__day { min-width: 6.5em; }

/* --- Abschnitt III: the one recess -------------------------------------- */
/* Entscheidung 2 + 8: the berufsrechtliche Pflichtangaben as a recessed
   block INSIDE the document. `--paper-inset`, hairline, card radius, flat.
   Not collapsible, not set smaller. "Die Vertiefung ist die Abgrenzung" --
   a practice without a Heilberuf has no such surface at all, and its absence
   is visible in a way a missing row is not. */
.vertiefung {
  background: var(--paper-inset);
  border: 1px solid var(--line);
  border-radius: var(--radius-doc);
  box-shadow: var(--elev-flat);
  padding: var(--space-3);
  max-width: none;
}

/* --- The one filled button, and links ---------------------------------- */
/* A page has exactly one filled button, and it is an upper bound: where
   there is no Anfrageweg the page has none, and the telephone number does
   not become one. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--control-min-patient);
  padding: 0 var(--space-4);
  border-radius: var(--radius-control);
  background: var(--grad-control);
  color: var(--text-on-deep);
  text-decoration: none;
}
.button:hover { background: var(--grad-control-hover); }

/* The one sentence on this page that addresses the reader, and the button
   under it. Entscheidung 1 allows exactly two such sentences on the whole
   page -- this one and the one in the foot. */
.cta__text { margin: 0 0 var(--space-3); }

.link { color: var(--link); text-decoration: underline; text-underline-offset: 3px; }
.link:hover { color: var(--link-hover); }

/* Entscheidung 1, the extension it names: every tappable element is at least
   `--control-min-patient` (48) high, "auch fuer einen reinen Textverweis".
   An 18/1.6 line box is 28.8 px, so the padding adds 9.6 px per side and the
   negative margin gives it back to the layout -- the hit area grows, nothing
   moves. */
.tap {
  display: inline-block;
  padding-block: 10px;
  margin-block: -10px;
}

:where(a):focus-visible {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: var(--focus-offset);
}

/* --- The foot ----------------------------------------------------------- */
/* Outside the document, on `--paper-page`: in the document the practice
   speaks, outside it the service does (Entscheidung 7). Two lines --
   Praxista's one sentence, and the three statutory links under it.
   `--type-body-d` in `--text-secondary`: Praxista has no small-print size,
   so the foot is not shrunk. */
.footer {
  max-width: var(--page-w-patient);
  margin: 0 auto;
  padding: var(--gap-section) var(--space-3) var(--space-5);
  color: var(--text-secondary);
}
.footer__note { margin: 0; }
.footer__legal { margin: var(--space-0) 0 0; display: flex; flex-wrap: wrap; gap: var(--space-1); }
.footer__sep { color: var(--text-secondary); }

/* Entscheidung 7's dated precision of 13.10.2026 (the same form now stands
   in the foot of every patient screen and every legal text,
   `components/PatientPage`): BELOW 748 px window width the three stand one
   under the other, each on its own 48 px line, with NO separator -- the
   line separates them. A row that wraps only when the words stop fitting
   would leave a middot at a line end, separating nothing, and make the
   layout depend on the word. From 748 px (684 + 2 x 32, the width at which
   the column is complete) the row above holds, unchanged.

   Here the 48 px is the line itself rather than `.tap`'s padding given back
   by a negative margin: stacked, that trick would overlap each target with
   its neighbour's by 20 px. Praxista's sentence keeps `--space-0` above the
   first entry. `747.98` so that "below 748" is exact at a fractional zoom
   width too. */
@media (max-width: 747.98px) {
  .footer__legal { flex-direction: column; align-items: flex-start; flex-wrap: nowrap; gap: 0; }
  .footer__legal .tap {
    display: inline-flex;
    align-items: center;
    min-height: var(--control-min-patient);
    padding-block: 0;
    margin-block: 0;
  }
  .footer__sep { display: none; }
}

/* From 480 px the portrait stands beside the identity text instead of under
   the Abrechnungszeile — Entscheidung 14, which changes only where it sits in
   a narrow window and nothing else about it. Abschnitt I becomes a grid so
   that one child can be placed out of source order without the markup
   changing: everything stays in column 1 in its written order, and the
   portrait alone takes row 1 of column 2.

   **Here it is bound by the HEIGHT of the identity text**, which is what
   Entscheidung 4 asks for and which looks impossible in a grid: a percentage
   height resolves against the row, and the row is sized by its tallest item —
   the portrait — so the rule reads as circular.

   The way out is to take the image out of flow. The wrapper carries no
   intrinsic height, so the row is sized by the identity text **alone**;
   `align-self: stretch` then gives the wrapper exactly that height, and the
   image inside is capped by both `max-` dimensions with neither set. That is
   conditions 4 and 5 literally: one dimension binds and the other follows,
   never both — and when the picture would be wider than its column, the
   width binds instead, which is the ruling's own second case. No measure, no
   token, no crop, no upscale. */
@media (min-width: 560px) {
  /* Foto links, gross; der Text daneben. Ohne Foto behaelt der Text die ganze
     Breite. */
  .hero:has(.portrait) {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: var(--space-4);
    align-items: start;
  }
}

@media (max-width: 480px) {
  /* The ONE thing that shrinks below 480 px: the page margin and the
     document's padding, 32 -> 20. Type, tap targets and the Abschnitt gaps
     stay patient density -- a density is type, control size and spacing
     together, and changing one of the three is not a third density. */
  .page { padding-left: var(--space-3); padding-right: var(--space-3); padding-top: var(--space-4); }
  .doc { padding: var(--space-3); }
  .paar { grid-template-columns: 1fr; gap: var(--space-0); }
  .paar dt { padding-top: 0; }
}
