/**
 * Constrained blocks — pilot, /implantologie/ only.
 *
 * WHAT THIS IS
 *
 * Five page-level blocks, each owning its own internal layout and spacing. The
 * nine compositions on /implantologie/ were built from seven different
 * mechanisms; these are the five that survive rationalisation.
 *
 * Deliberately a NEW prefix (.ocb-) rather than an edit to the .oc-* section
 * types. The rest of the site keeps running on theme.css untouched — this is a
 * pilot, and a pilot that modifies the thing it is piloting against cannot be
 * compared to it.
 *
 * THE DIVISION OF OWNERSHIP
 *
 * Global (theme.css, reused — no new spacing system):
 *     --oc-inset           where content starts, horizontally
 *     --oc-content-max     the section width
 *     --oc-prose-max       the reading measure
 *     --oc-space-section   the space between sections
 *     typography, colour   theme.json + theme.css
 *
 * Each block: everything inside itself. No block reaches outside its own root,
 * and nothing outside reaches in. There are no per-element max-widths and no
 * per-element margins added to make one screenshot look right — if a gap is
 * wrong here, exactly one rule is responsible for it.
 *
 * WHY layout:"default" AND NOT "constrained"
 *
 * This is the single most load-bearing decision in the file. WordPress gives
 * `.is-layout-constrained > *` both a max-width AND
 * `margin-left/right: auto !important`. Combined with a theme rule capping bare
 * paragraphs, that centres a paragraph under a left-aligned heading — the defect
 * in docs/spacing-audit.md §4 that affects 11 of 18 pages and 40 containers.
 *
 * Every block here emits `layout:{"type":"default"}` instead, so the
 * constrained rule never applies and the measure is owned by these rules alone.
 * The bug is not patched; it is made impossible.
 *
 * WHY THE RHYTHM IS ALL margin-top
 *
 * Bottom margins are zeroed on every block child, so two margins never meet and
 * nothing collapses. The gap between any two elements is therefore decided in
 * exactly one place, and it reads the same whether the parent is flow or flex.
 * theme.css:3113-3126 discovered this the hard way for one container; it is the
 * general rule here.
 */

/* ═══ Shared ═════════════════════════════════════════════════════════════ */

/* Tint, so no two neighbouring blocks share a background
   (docs/section-types.md). A block's tone is the only thing about it the page
   decides; everything else the block owns. */
.ocb-tone-white { background: #fff; }
.ocb-tone-muted { background: var(--wp--preset--color--surface-muted, #ECF6FD); }

:root {
	/*
	 * FOUR measures. Not five, and never a number typed into a section.
	 *
	 * The audit found six competing maxima plus eight ad-hoc caps on individual
	 * elements (docs/spacing-audit.md §4). A page picks one of these names; it
	 * does not get to invent a width.
	 */
	--ocb-prose-sm: 28rem;   /* a narrow aside — captions, meta, trust lines */
	--ocb-prose:    38rem;   /* the reading measure, 57–59ch */
	--ocb-prose-lg: 56rem;   /* wide text, image rows that carry weight */
	--ocb-full:     80rem;   /* the section container */
}

.ocb {
	box-sizing: border-box;
	padding-block: var(--oc-space-section);
	padding-inline: var(--oc-inset);
}

.ocb-w-sm  { max-width: var(--ocb-prose-sm); margin-inline: 0; }
.ocb-w     { max-width: var(--ocb-prose); margin-inline: 0; }
.ocb-w-lg  { max-width: var(--ocb-prose-lg); margin-inline: 0; }
.ocb-w-full { max-width: var(--ocb-full); margin-inline: 0; }

/* The measure lives on the container, never on a child
   (docs/section-types.md:18). */
.ocb__body {
	max-width: var(--oc-prose-max);
	margin-inline: 0;
}

.ocb__wide {
	max-width: var(--oc-content-max);
	margin-inline: 0;
}

/* ── Layout containers own their children's margins ──────────────────────
 *
 * Every grid a block creates zeroes the block margins of its own items.
 * WordPress applies its 24px block gap as margin-top to every sibling, and in a
 * grid those margins do not collapse — so without this the SECOND item of every
 * grid sits 24px lower than the first. It put the hero photo 24px below its
 * headline and knocked the two column headings 25px out of register, which
 * reads as a bug in the content rather than in the rig.
 *
 * A gap between grid items is the grid's `gap`, never a child's margin.
 */
.ocb :is(.ocb-hero__grid, .ocb-split__grid, .ocb-columns__grid) > * {
	margin-block: 0;
}

/* ── Rhythm ──────────────────────────────────────────────────────────────
 * Six relationships, named by what they mean rather than by size. Applies to
 * the direct children of any block's text flow.
 *
 * Specificity is exactly (0,2,0) so these beat core's
 * `:root :where(.is-layout-flow) > * { margin-block-start: 24px }` at (0,1,0)
 * without a single !important.
 */
.ocb .ocb-flow > * { margin-block: 0; }

.ocb .ocb-flow > * + *                                   { margin-block-start: 1.25rem; } /* 20 · same thought   */
.ocb .ocb-flow > :is(h2, h3, h4) + *                     { margin-block-start: 0.625rem; } /* 10 · heading → its copy */
.ocb .ocb-flow > :is(h2, h3, h4) + :is(ul, ol)           { margin-block-start: 1rem; }     /* 16 · heading → list */
.ocb .ocb-flow > * + :is(h3, h4)                         { margin-block-start: 2rem; }     /* 32 · new group      */
.ocb .ocb-flow > * + h2                                  { margin-block-start: 3rem; }     /* 48 · new section    */
.ocb .ocb-flow > * + .wp-block-buttons                   { margin-block-start: 1.75rem; }  /* 28 · copy → action  */
.ocb .ocb-flow > * + figure                              { margin-block-start: 2rem; }
.ocb .ocb-flow > .oc-eyebrow + :is(h1, h2)               { margin-block-start: 0.25rem; }  /*  4 · welded         */

/* ── Check list ───────────────────────────────────────────────────────────
 * A list whose items are things the practice DOES, rather than steps in an
 * argument. The tick carries that distinction; plain bullets do not, and the
 * old .oc-* pages made it with .oc-check-list. Rebuilding /parodontologie/ in
 * this grammar silently downgraded eight such items to bullets, which is what
 * prompted this rule.
 *
 * Not a new component: same <ul>, same flow spacing, same measure. Only the
 * marker changes, and the glyph is the one theme.css already inlines, so no
 * new asset and no extra request.
 *
 * Use it for capability lists. Do NOT use it for ordered steps (that is the
 * timeline) or for prose broken into bullets.
 */
.ocb .ocb-flow > ul.ocb-list--check {
	list-style: none;
	padding-left: 0;
}

.ocb .ocb-flow > ul.ocb-list--check > li {
	position: relative;
	padding-left: 1.75rem;
}

.ocb .ocb-flow > ul.ocb-list--check > li + li {
	margin-block-start: 0.5rem;
}

.ocb .ocb-flow > ul.ocb-list--check > li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 0.4em;
	width: 1rem;
	height: 1rem;
	background-color: var(--wp--preset--color--accent-deep, #0073A1);
	-webkit-mask: var(--oc-check-mask) center / contain no-repeat;
	mask: var(--oc-check-mask) center / contain no-repeat;
}

/* ═══ 1 · Hero ═══════════════════════════════════════════════════════════
 * One per page, first. Breadcrumb, H1, a short intro, one CTA, a quiet meta
 * line, photo.
 *
 * THREE LEVELS, and only three:
 *   1. what is this          the H1
 *   2. why should I care     the intro — three or four lines, not nine
 *   3. what do I do          the CTA, with the meta line as quiet evidence
 *
 * The meta line is deliberately NOT a fourth content block. It is one muted
 * row under the button, which is why it is 14px and grey rather than a card.
 *
 * The only block allowed an eyebrow (DESIGN.md's One-Kicker Rule).
 */

.ocb-hero {
	background: #fff;
	/* Matches .oc-hero-lite's original 56px at 1440 — the hero's own value, not
	   the section scale, because a hero opens the page rather than sitting in
	   the rhythm of the ones below it. */
	padding-block: clamp(2rem, 4vw, 3.5rem) 0;
	padding-inline: 0;
}

.ocb-hero__crumbs,
.ocb-hero > .oc-breadcrumbs {
	padding-inline: var(--oc-inset);
	margin-block-end: clamp(1.5rem, 3vw, 2.5rem);
}

/* theme.css colours breadcrumb links for the navy hero every other page uses.
   This hero is white, so the block states its own. */
.ocb-hero .oc-breadcrumbs,
.ocb-hero .oc-breadcrumbs a {
	color: var(--wp--preset--color--text-muted, #4C6880);
}

.ocb-hero .oc-breadcrumbs a:hover { color: var(--wp--preset--color--primary, #1A3062); }
.ocb-hero .oc-breadcrumbs [aria-current] { color: var(--wp--preset--color--primary, #1A3062); }

/*
 * The hero owns its own height.
 *
 * It did not, and that was the bug: the photo's intrinsic aspect at 52% of the
 * viewport set the row, so the block grew without limit while the text stayed
 * two lines. Measured: 614px tall at 1440, 781px at 1920, 877px at 2200 — with
 * 275px, 441px and 538px of empty column beside a headline that never changes
 * size. A block whose height is decided by whatever image it happens to contain
 * is not a designed block.
 *
 * Bounded now, and the photo covers it — the same approach .oc-hero-full
 * already takes (theme.css:3515). This is not a new crop: the hero image is
 * already cropped responsively by its container, rendering 1.47:1 at 1440 and
 * 2.67:1 at 768 against a 1.5:1 original.
 */
.ocb-hero__grid {
	display: grid;
	gap: 0;
	align-items: start;
}

.ocb-hero__text {
	padding-inline: var(--oc-inset) clamp(1.75rem, 4vw, 3.5rem);
	padding-block: 0 clamp(2rem, 4vw, 3rem);
}

.ocb-hero__media { align-self: stretch; }

.ocb-hero__media img,
.ocb-split__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.ocb-hero__media figure { margin: 0; height: 100%; }

@media (min-width: 60rem) {
	.ocb-hero__grid {
		grid-template-columns: 48% 52%;
		/* Text centres inside the bounded row. Safe now in a way it was not
		   before: when the row was 660px tall, centring pushed the H1 281px
		   down the page. */
		align-items: center;
	}

	.ocb-hero__text { padding-block: clamp(2rem, 4vw, 3.5rem); }

	/*
	 * 30rem was the cap. At 1920 that made the box 998×480 — 2.1:1 against a
	 * 1.5:1 team photograph — and cover sliced 14% off the top, through the
	 * foreheads (Marco, 2026-09-23, on a large screen). Letting the height
	 * follow the width to 36rem keeps the box near 1.7:1 at 1920 and 2200;
	 * below 1600 nothing changes, since 30vw is under 30rem there.
	 */
	.ocb-hero__media { height: clamp(22rem, 30vw, 36rem); }

	/* And whatever the ratio, crop from the bottom, not the faces. The hero
	   photographs are people; their heads sit in the top third. */
	.ocb-hero__media img { object-position: 50% 30%; }
}

/* The intro is capped at the reading measure even inside a 48% column, so it
   breaks at a sane line length instead of running the full column. */
.ocb-hero__text > p { max-width: var(--ocb-prose); }

/* The hero was reading compressed: headline, copy, button and trust line all
   stacked tight against the top-left while the photo carried the weight. */
.ocb-hero__cta { margin-block-start: 2.25rem; }

/*
 * The trust row. One line, quiet, under the button — evidence, not content.
 *
 * NB the star rating is deliberately absent: there is no defensible source for
 * a number in this repo, and practitioner-pages.md §4.3 rules out review
 * markup as a Heilmittelwerbegesetz risk while content-polish-review.md is
 * separately proposing to drop the visible "Ø 5 Sternen" claim. The slot is
 * here; the claim needs a decision and a source.
 */
.ocb-hero__meta {
	margin-block-start: 1.5rem;
	font-size: 0.875rem;
	line-height: 1.5;
	color: var(--wp--preset--color--text-muted, #4C6880);
	max-width: var(--ocb-prose);
}

.ocb-hero__meta strong { font-weight: 600; color: var(--wp--preset--color--primary, #1A3062); }

/* The photo keeps bleeding when it stacks; a band across the width reads as
   deliberate. The cap is what stops it eating the fold. */
@media (max-width: 59.999rem) {
	.ocb-hero__media img { max-height: 18rem; }
}

/*
 * --fit: the hero picture is an illustration, not a photograph.
 *
 * /parodontologie/ opens on the 480px Parodontitis cutout (Marco, 2026-09-22:
 * the tooth picture goes in the hero). Covering a 600px box with it means
 * upscaling and cropping a drawing that is evidence. Contained on the muted
 * ground instead, centred, with room around it — the same call
 * .ocb-split--fit makes for the CERALOG radiograph. Photographs keep cover.
 */
.ocb-hero--fit .ocb-hero__media {
	background: var(--wp--preset--color--surface-muted, #ECF6FD);
	display: flex;
	align-items: center;
	justify-content: center;
	padding: clamp(1.5rem, 3vw, 2.5rem);
}

.ocb-hero--fit .ocb-hero__media figure { height: auto; max-width: 100%; }

.ocb-hero--fit .ocb-hero__media img {
	width: auto;
	height: auto;
	max-width: 100%;
	max-height: min(22rem, 100%);
	object-fit: contain;
}

@media (max-width: 59.999rem) {
	.ocb-hero--fit .ocb-hero__media img { max-height: 14rem; }
}

/* ═══ 2 · Prose ══════════════════════════════════════════════════════════
 * A heading and its copy, at the reading measure.
 *
 * --rail puts the heading in a left column at desktop so the section fills the
 * width while keeping one left edge. Explicit, and therefore predictable: the
 * version in theme.css:3218 fires automatically on any section whose only child
 * is a prose block starting with a heading, which is why prose sections on this
 * page currently disagree with each other.
 */

/*
 * A prose column that IS the whole section is centred, not pinned left.
 *
 * It was `margin-inline: 0`, which is right for a column that sits beside
 * something — a photo, a rail heading, a card row — because then it shares that
 * section's left edge. But a plain Content section has no such neighbour: at
 * 1280 the column ran 608px inside a 1101px band and left 493px of nothing to
 * its right, which reads as a Split whose picture failed to load.
 *
 * This is the FOURTH time this exact rule has been got wrong on this site —
 * docs/section-types.md records three earlier instances, the last of them the
 * same fix applied to .oc-prose on 2026-09-16 (`:has(> .oc-prose:only-child)`).
 * The constrained blocks reintroduced it because they were written fresh.
 *
 * A rail section is unaffected: above 64rem its body is the full content width,
 * so `auto` resolves to 0 and it keeps the shared left edge.
 */
.ocb-prose__body { max-width: var(--oc-prose-max); margin-inline: auto; }

@media (min-width: 64rem) {
	.ocb-prose--rail .ocb-prose__body {
		max-width: var(--oc-content-max);
		display: grid;
		grid-template-columns: minmax(0, 22rem) minmax(0, var(--oc-prose-max));
		column-gap: clamp(2rem, 5vw, 4.5rem);
		align-items: start;
	}

	/* `span 99`, not `1 / -1`: the rows are implicit, and -1 resolves against
	   the last explicit line — which would leave the heading spanning one row
	   and dragging the copy down with it. */
	.ocb-prose--rail .ocb-prose__body > * { grid-column: 2; }
	.ocb-prose--rail .ocb-prose__body > :is(h2, h3):first-child {
		grid-column: 1;
		grid-row: 1 / span 99;
		margin-block-start: 0;
	}
}

/*
 * The disclosure that opens a page. An aside to the intro it sits under, so it
 * is part of that block rather than a section of its own.
 *
 * THE ONLY !important RULE IN THIS FILE (two declarations), and it is here
 * for the reason
 * docs/spacing-audit.md §11 lists as legitimate: it is beating an existing
 * !important, not winning a specificity race.
 *
 * theme.css:2799 sets `details.oc-page-toc { margin: 0 auto 1.5rem 0 !important }`
 * for the old section types, which forces margin-top to 0 — so the bar sat
 * flush against the paragraph above it with no gap at all. An !important
 * outranks any non-important rule whatever its specificity, so the block cannot
 * own this spacing without matching it.
 *
 * Retire it when the old TOC rule goes.
 */
.ocb-prose .oc-page-toc {
	max-width: var(--oc-prose-max);
	margin-block: 2rem 0 !important;
	margin-inline: 0 !important;
}

/*
 * A trust badge sits with the copy it belongs to. It used to float centred at a
 * third left edge, under a heading at 101px and copy at 525px.
 *
 * The white chip is not decoration: the Straumann asset is a JPEG with a baked-in
 * white background, so on a tinted band it renders as a bare white rectangle.
 * Giving it the card's own ground and radius makes that white deliberate rather
 * than accidental — and it keeps working whichever tone the section lands on,
 * which matters now that tone alternates by position.
 */
.ocb-prose figure {
	margin-inline: 0;
	max-width: 12rem;
	background: #fff;
	border-radius: var(--oc-radius-card, 12px);
	padding: 0.875rem;
}

.ocb-prose figure img { display: block; width: 100%; height: auto; }

/* ═══ 3 · Split ══════════════════════════════════════════════════════════
 * Text one side, photo bleeding off the opposite edge. --reverse alternates.
 */

.ocb-split { padding-inline: 0; }

/*
 * Media stretches to the text column rather than centring beside it.
 *
 * With `center`, a long text column left the photo floating with empty bands
 * above and below it. Stretching is also what makes the Split block absorb a
 * long section without a special case — which is the point of the grammar.
 */
.ocb-split__grid {
	display: grid;
	gap: 0;
	align-items: stretch;
}

.ocb-split__text { padding-inline: var(--oc-inset) clamp(1.75rem, 4vw, 3.5rem); }
.ocb-split__media { align-self: stretch; }
.ocb-split__media figure { margin: 0; height: 100%; }

/*
 * --fit: the picture is evidence, so all of it has to stay visible.
 *
 * `cover` is right for a photograph — a practice interior, a portrait — where
 * losing the edges costs nothing and filling the band is worth more. It is
 * wrong for a radiograph. The media column stretches to the text column, and
 * when the accordions beside it are collapsed that column is short: at 1920 the
 * box goes 2.7:1 and cover keeps 29% of the image, slicing off the implant the
 * section exists to show.
 *
 * So this variant contains the image and centres it in its half instead. It
 * gives up the bleed, which is the right trade for a clinical picture: a
 * cropped photo is a styling choice, a cropped radiograph is a wrong picture.
 */
.ocb-split--fit .ocb-split__media {
	display: flex;
	/*
	 * flex-start on BOTH axes, not center.
	 *
	 * Centred, the picture floated 66px inside its own column while its caption
	 * sat on the page inset — the caption visibly jutted out to the left of the
	 * image it belonged to — and its top edge hung ~30px below the heading
	 * opposite. It read as unplaced rather than placed. Now it starts where
	 * every other image on the page starts: on the gutter, level with the text.
	 */
	align-items: flex-start;
	justify-content: flex-start;
	padding: 0 var(--oc-inset);
}

.ocb-split--fit .ocb-split__media figure { height: auto; max-width: 100%; }

.ocb-split--fit .ocb-split__media img {
	/* `flex: 0 0 auto` is load-bearing. The captioned-figure rule further down
	   gives the image `flex: 1 1 0` so it can absorb the leftover height in a
	   bleeding Split; inherited here it resolves the basis to 0 and the picture
	   collapsed to nothing. This variant sizes by the image, not the band. */
	flex: 0 0 auto;
	/*
	 * flex-start, not center.
	 *
	 * The figure is a column flex container, so align-self here controls the
	 * HORIZONTAL position. It still stops the image box stretching to the full
	 * column width — which would wrap the rounded corners around empty space
	 * beside the picture — but `center` also floated the picture 66px inside
	 * its own column while the caption underneath sat on the page inset, so the
	 * caption visibly jutted out to the left of the image it described.
	 */
	align-self: flex-start;
	width: auto;
	height: auto;
	max-width: 100%;
	max-height: 28rem;
	object-fit: contain;
	border-radius: var(--oc-radius-card, 12px);
}

/* Back to the page inset on the bleeding side — there is no bleed here. */
.ocb-split--fit .ocb-split__media figcaption { padding-inline: 0; }

@media (min-width: 60rem) {
	.ocb-split__grid { grid-template-columns: 48% 52%; }

	.ocb-split--reverse .ocb-split__media { grid-column: 1; grid-row: 1; }
	.ocb-split--reverse .ocb-split__text {
		grid-column: 2;
		grid-row: 1;
		padding-inline: clamp(1.75rem, 4vw, 3.5rem) var(--oc-inset);
	}
}

@media (max-width: 59.999rem) {
	/* Media last and bleeding, so the section closes on the photo instead of on
	   a band of tint below it. */
	.ocb-split { padding-block-end: 0; }
	.ocb-split__text { padding-block-end: var(--oc-space-section); }
	.ocb-split__media img { max-height: 18rem; }
}

/* ═══ 4 · Columns ════════════════════════════════════════════════════════
 * One heading, then two text columns. No eyebrow: hero-only.
 */

.ocb .ocb-flow > .ocb-columns__grid {
	display: grid;
	gap: clamp(2rem, 5vw, 5rem);
	align-items: start;
	/* The section heading, then its columns. Out-specifies the generic
	   `* + *` rhythm rule above rather than overriding it with !important. */
	margin-block-start: 2.5rem;
}

@media (min-width: 60rem) {
	.ocb-columns__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ═══ Accordion — a component, not a block ═══════════════════════════════
 *
 * Native <details>/<summary>: keyboard operable and announced by screen readers
 * with no JavaScript, and still editable in Gutenberg. Same mechanism the "Auf
 * dieser Seite" bar already uses (theme.css:2786).
 *
 * A component rather than a sixth block, deliberately. An accordion is
 * something a Prose block CONTAINS, the way it contains a list or a figure —
 * it is not a kind of page section. Adding a block for it would grow the
 * vocabulary without adding a layout.
 *
 * Which means an FAQ is not a new block either: it is a Prose block whose body
 * is a heading and a stack of accordions. The only thing .ocb-faq adds is a
 * hook the schema emitter can find (oralchirurgie-site/includes/schema.php).
 */

.ocb-accordion {
	border-top: 1px solid rgba(26, 48, 98, 0.14);
}

/* The last row closes the stack, so the group reads as a bounded list rather
   than as rows that ran out. */
/*
 * No closing rule. Every other accordion stack on the page draws a rule between
 * its rows and none under the last one; the FAQ used to draw one because the
 * source note below it needed separating, and the source note has its own
 * spacing now. Six rules for five questions read as a sixth empty row.
 */

.ocb-accordion > summary {
	display: flex;
	align-items: baseline;
	gap: 0.75rem;
	/* Padding lives on the summary, not the details, so the whole bar is the
	   hit target rather than a strip of text inside a padded box. */
	padding: 1.125rem 0;
	font-family: var(--wp--preset--font-family--display, Georgia, serif);
	font-size: clamp(1.0625rem, 0.99rem + 0.3vw, 1.1875rem);
	font-weight: 600;
	line-height: 1.35;
	color: var(--wp--preset--color--primary, #1A3062);
	cursor: pointer;
	list-style: none;
}

.ocb-accordion > summary::-webkit-details-marker { display: none; }

.ocb-accordion > summary::before {
	content: "";
	width: 0.45rem;
	height: 0.45rem;
	margin-top: 0.45em;
	border-right: 2px solid var(--wp--preset--color--accent, #44ADE2);
	border-bottom: 2px solid var(--wp--preset--color--accent, #44ADE2);
	transform: rotate(-45deg);
	transition: transform 0.18s ease-out;
	flex: none;
}

.ocb-accordion[open] > summary::before { transform: rotate(45deg); }

.ocb-accordion > summary:focus-visible {
	outline: 2px solid var(--wp--preset--color--accent, #44ADE2);
	outline-offset: 3px;
}

.ocb-accordion > summary:hover { color: var(--wp--preset--color--accent-deep, #0073A1); }

/* The panel's own rhythm: tighter than the page's, because an answer is one
   unit rather than a passage. Its first child needs no top margin — the
   summary's padding already separated them. */
.ocb-accordion > :not(summary) { margin-block: 0 0; }
.ocb-accordion > :not(summary):first-of-type { margin-block-start: 0; }
.ocb-accordion > summary + * { margin-block-start: 0; }
.ocb-accordion > :not(summary) + :not(summary) { margin-block-start: 1rem; }
.ocb-accordion > :not(summary):last-child { padding-bottom: 1.5rem; }

/* The answer runs at the reading measure, not the full container, so a long
   answer in a wide section does not become a 120-character line. */
.ocb-accordion > :not(summary) { max-width: var(--oc-prose-max); }

@media (prefers-reduced-motion: reduce) {
	.ocb-accordion > summary::before { transition: none; }
}

/* An accordion stack inside a flow container is a list, not a run of
   paragraphs: consecutive rows meet on their shared border. */
.ocb .ocb-flow > .ocb-accordion + .ocb-accordion { margin-block-start: 0; }
.ocb .ocb-flow > * + .ocb-accordion { margin-block-start: 2rem; }
.ocb .ocb-flow > .ocb-accordion + :not(.ocb-accordion) { margin-block-start: 2rem; }

/*
 * Any disclosure following copy, not just .ocb-accordion.
 *
 * The "Auf dieser Seite" bar is a details.oc-page-toc, so it matched none of
 * the rules above and fell through to the generic 20px paragraph step — which
 * left it sitting almost against the paragraph before it. A collapsed bar is a
 * new thing on the page, not the next line of a thought, so it takes the
 * new-group step like any other block-level change of subject.
 */
.ocb .ocb-flow > * + details { margin-block-start: 2rem; }

/* ═══ 5 · CTA band ═══════════════════════════════════════════════════════
 * One per page, before the footer. Tighter than a section on purpose: it is a
 * band, not a section, and it closes the page.
 */

.ocb-cta {
	padding-block: clamp(2.75rem, 6vw, 4rem);
	text-align: center;
	background: var(--wp--preset--color--action, #2E3092);
	color: #fff;
}

.ocb-cta :is(h1, h2, h3) { color: #fff; }

.ocb-cta__body { max-width: var(--oc-prose-max); margin-inline: auto; }
.ocb-cta .wp-block-buttons { justify-content: center; }

/* The band is the action colour, so its button inverts. Stated here because the
   block owns its own contents — the equivalent rule in theme.css is scoped to
   .oc-cta-band and needs four !important declarations to win; this one needs
   none, because nothing else is competing for it. */
.ocb-cta .wp-block-button__link {
	background: #fff;
	color: var(--wp--preset--color--action, #2E3092);
	font-weight: 600;
	border-radius: var(--oc-radius-button, 6px);
	padding: 0.85rem 2rem;
}

.ocb-cta .wp-block-button__link:hover { background: var(--wp--preset--color--surface-muted, #ECF6FD); }

/* ═══ Section nav ════════════════════════════════════════════════════════
 * A sticky bar of anchors, directly under the hero.
 *
 * Replaces the "Auf dieser Seite" disclosure for navigation. A collapsed bar
 * that has to be opened before it tells you anything is a worse answer than a
 * row you can read at a glance — and on a phone a horizontal scroller is a
 * native, familiar gesture where a dropdown is a tap plus a decision.
 *
 * The "Schritt für Schritt" accordion stays an accordion: that is genuinely
 * collapsible content, not navigation.
 *
 * Universal by design — every treatment page can use this with nothing changed
 * but labels and anchors.
 */

.ocb-nav {
	position: sticky;
	/* Measured, not guessed: the sticky header is 76px at desktop and 73px
	   below 768. An earlier 4.5rem/3.75rem guess put the bar 13px UNDER the
	   header, so the first nav item was clipped while scrolling. */
	top: var(--ocb-nav-top, 76px);
	z-index: 20;
	background: #fff;
	border-block: 1px solid rgba(26, 48, 98, 0.12);
	/* Full-bleed bar, container-aligned contents — the links start on the same
	   left edge as every other block on the page. */
	padding-inline: var(--oc-inset);
}

.ocb-nav__list {
	display: flex;
	gap: clamp(1.25rem, 2.5vw, 2.25rem);
	align-items: stretch;
	margin: 0;
	padding: 0;
	list-style: none;
	/*
	 * Centred on desktop, start-aligned once it scrolls.
	 *
	 * `width: fit-content` + `margin-inline: auto`, NOT `justify-content:
	 * center`. Centring an overflowing scroller pushes the first items to a
	 * negative offset that cannot be scrolled back to, so the start of the list
	 * becomes unreachable — the items you most want on a phone.
	 *
	 * The max-width cap alone was not enough either: the list was centred but
	 * 1280px wide, so its items still sat against the left edge with 532px of
	 *空 to the right. Shrinking the box to its contents is what actually
	 * centres them.
	 */
	width: fit-content;
	max-width: min(var(--ocb-full), 100%);
	margin-inline: auto;
	/* Mobile: one line, swipe to scroll. No wrapping, no dropdown. */
	overflow-x: auto;
	overscroll-behavior-x: contain;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: none;
}

/* Hidden visually, still swipeable and trackpad-scrollable. */
.ocb-nav__list::-webkit-scrollbar { display: none; }

.ocb-nav__list > li { flex: none; }

.ocb-nav__link {
	display: block;
	padding-block: 1rem;
	font-size: 0.9375rem;
	font-weight: 600;
	line-height: 1.3;
	white-space: nowrap;
	color: var(--wp--preset--color--text-muted, #4C6880);
	text-decoration: none;
	/* The active marker is reserved at rest, so nothing shifts when it appears. */
	border-bottom: 2px solid transparent;
	transition: color 0.15s ease-out, border-color 0.15s ease-out;
}

.ocb-nav__link:hover { color: var(--wp--preset--color--primary, #1A3062); }

/* Restrained: an underline and a darker weight of the same colour. No pill. */
.ocb-nav__link[aria-current="true"] {
	color: var(--wp--preset--color--primary, #1A3062);
	border-bottom-color: var(--wp--preset--color--accent, #44ADE2);
}

.ocb-nav__link:focus-visible {
	outline: 2px solid var(--wp--preset--color--accent, #44ADE2);
	outline-offset: -2px;
}

/*
 * Anchors must clear BOTH sticky bars, or a jumped-to section lands underneath
 * them. theme.css sets 6.25rem for the header alone.
 *
 * On :has() rather than a class, because scroll-padding has to sit on the
 * scrolling element and block content cannot put a class on <html>. The page
 * with a nav asks for the offset; no page without one is affected.
 */
html:has(.ocb-nav) { scroll-padding-top: 10rem; }   /* 76 header + 56 nav + air */

@media (max-width: 47.999rem) {
	.ocb-nav { top: var(--ocb-nav-top-sm, 73px); }
	/* Start-aligned once it scrolls, so the first item is reachable. */
	.ocb-nav__list { width: auto; margin-inline: 0; }
	.ocb-nav__link { padding-block: 0.875rem; font-size: 0.875rem; }
	html:has(.ocb-nav) { scroll-padding-top: 8.5rem; }  /* 73 header + 50 nav + air */
}

@media (prefers-reduced-motion: reduce) {
	.ocb-nav__link { transition: none; }
}

/* ═══ Rating row ═════════════════════════════════════════════════════════
 * Stars, the figure, and a link out — one quiet line under the hero CTA.
 *
 * Evidence, not content: it sits at 14px in the muted text colour so it reads
 * as a footnote to the button rather than as a fourth block competing with the
 * headline.
 *
 * Accessibility: the stars are decorative (aria-hidden) and the row carries a
 * single aria-label with the real figure, so a screen reader hears "4,6 von 5
 * Sternen bei Google" once instead of five bullet characters.
 */

.ocb-rating {
	display: inline-flex;
	align-items: baseline;
	gap: 0.5rem;
	flex-wrap: wrap;
}

/*
 * One star colour for the page.
 *
 * The hero row was accent blue and the review cards would have been gold, which
 * is two different stars on one page saying the same thing. Gold wins because
 * it is what a star rating looks like everywhere a patient has seen one — the
 * token exists so there is exactly one place to change it.
 */
:root { --oc-star: #F2A93B; }

.ocb-rating__stars {
	color: var(--oc-star);
	font-size: 1rem;
	letter-spacing: 0.08em;
	line-height: 1;
}

.ocb-rating__value { color: var(--wp--preset--color--text-muted, #4C6880); }

/*
 * min-height is WCAG 2.5.8 AA, not styling. At 14px with the row's line-height
 * the link measured 23.5px tall — half a pixel under the 24px minimum, which
 * the harness failed on the run it was added. A text link in a meta row has no
 * padding to spare, so the floor is set explicitly.
 */
.ocb-rating__link {
	display: inline-flex;
	align-items: center;
	/* Match every other arrow link on the page: a real gap before the arrow,
	   and the arrow outside the underline. It had neither, so it read as
	   "ansehen→" with the glyph welded to the word. */
	gap: 0.35rem;
	min-height: 24px;
	color: var(--wp--preset--color--accent-deep, #0073A1);
	font-weight: 600;
	text-decoration: underline;
	text-underline-offset: 3px;
}

.ocb-rating__link > span { text-decoration: none; }

.ocb-rating__link:hover { color: var(--wp--preset--color--primary, #1A3062); }

/*
 * The hero's location line.
 *
 * Secondary metadata, not prose: "kein Fliesstext … nicht als Keyword-Liste
 * prominent hervorheben" (Marco). So it steps down from the trust row above it
 * in colour and weight — but NOT in size. 14px is the functional-text floor
 * (docs/design-tokens.md:29) and the harness fails anything below it, so the
 * quietness has to come from somewhere other than shrinking the type.
 *
 * Wraps freely on a phone; the separators are decorative and hidden from
 * screen readers, which hear the aria-label instead.
 */
.ocb-hero__places {
	margin-block-start: 0.75rem;
	font-size: 0.875rem;
	line-height: 1.6;
	color: var(--wp--preset--color--text-muted, #4C6880);
	opacity: 0.85;
	max-width: var(--ocb-prose);
}

.ocb-hero__places span {
	color: rgba(26, 48, 98, 0.3);
	padding-inline: 0.15rem;
}

/* The separator between rating and place. Hidden when the row wraps, because a
   leading middot on its own line reads as a bullet. */
.ocb-rating__sep { color: rgba(26, 48, 98, 0.35); }

@media (max-width: 29.999rem) {
	.ocb-hero__meta { line-height: 1.7; }
	.ocb-rating__sep { display: none; }
	.ocb-rating__link { display: inline-block; margin-block-start: 0.25rem; }
}

/* ═══ Card grid ══════════════════════════════════════════════════════════
 * Heading plus a row of cards. One block, two card kinds:
 *
 *   .ocb-card--person   portrait, name, role, link       (the practitioners)
 *   .ocb-card--quote    excerpt, attribution             (the reviews)
 *
 * This is the primitive the contract's §9 and §10 both need, and the third
 * member of the Content / Split / Grid vocabulary.
 *
 * MOBILE: a horizontal scroller, not a stack.
 * The indicator is the layout itself — cards are 82% of the viewport, so the
 * next one is always visibly cut off at the edge. A row that ends flush reads
 * as finished; a row that ends mid-card reads as scrollable without needing an
 * arrow, a hint or a row of dots to say so. Scroll-snap then makes it land
 * cleanly instead of drifting.
 */

.ocb-cards {
	display: grid;
	gap: clamp(1rem, 2vw, 1.5rem);
	align-items: stretch;
}

.ocb .ocb-flow > .ocb-cards { margin-block-start: 2.5rem; }

@media (min-width: 48rem) {
	.ocb-cards { grid-template-columns: repeat(var(--ocb-cards-cols, 3), minmax(0, 1fr)); }
	/*
	 * --wrap steps 4 → 2 → 1, never 3.
	 *
	 * auto-fit was doing the arithmetic instead, and at 1024 it produced three
	 * columns and left the fourth review alone on a second row. An orphaned
	 * card reads as a mistake. Two even rows do not, so the widths are named
	 * rather than derived.
	 */
	.ocb-cards--wrap { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Four across only once each card can hold ~240px — below that two is better. */
@media (min-width: 75rem) {
	.ocb-cards--wrap { grid-template-columns: repeat(var(--ocb-cards-cols, 4), minmax(0, 1fr)); }
}

@media (max-width: 47.999rem) {
	.ocb-cards {
		grid-auto-flow: column;
		/* 85% leaves a deliberate sliver of the next card visible. Was 82%;
		   Marco, 2026-09-22: the card "kann ein bisschen breiter sein, wenn es
		   klar ist, dass man da auch scrollen kann" — at 390 the next card still
		   shows ~25px past the gap, which is what says "scroll". */
		grid-auto-columns: 85%;
		overflow-x: auto;
		scroll-snap-type: x mandatory;
		overscroll-behavior-x: contain;
		scrollbar-width: none;
		/* Bleed to the page edge so the cut-off card reaches the screen edge —
		   a card that stops short of it looks like a mistake, not a hint. */
		margin-inline: calc(var(--oc-inset) * -1);
		padding-inline: var(--oc-inset);
		/* Room for the last card to snap flush against the left inset. */
		scroll-padding-inline: var(--oc-inset);
	}

	.ocb-cards::-webkit-scrollbar { display: none; }
	.ocb-cards > * { scroll-snap-align: start; }
}

/* ── The card ────────────────────────────────────────────────────────── */

.ocb-card {
	display: flex;
	flex-direction: column;
	background: #fff;
	border: 1px solid rgba(26, 48, 98, 0.10);
	border-radius: var(--oc-radius-card, 12px);
	overflow: hidden;
}

.ocb-card > * { margin-block: 0; }

.ocb-card__body {
	display: flex;
	flex-direction: column;
	gap: 0.375rem;
	padding: 1.25rem 1.25rem 1.5rem;
	flex: 1;
}

/* ── Person card ─────────────────────────────────────────────────────── */

/*
 * A fixed aspect ratio on every portrait, so three photos shot at different
 * distances still present at the same size. object-position is set per image
 * in the markup — that is the only per-instance value here, and it exists
 * because where a face sits in its frame is a property of the photograph, not
 * of the layout.
 */
.ocb-card__portrait {
	aspect-ratio: 4 / 3;
	background: var(--wp--preset--color--surface-muted, #ECF6FD);
}

.ocb-card__portrait img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.ocb-card__name {
	font-family: var(--wp--preset--font-family--display, Georgia, serif);
	font-size: clamp(1.0625rem, 1rem + 0.3vw, 1.1875rem);
	font-weight: 600;
	line-height: 1.3;
	color: var(--wp--preset--color--primary, #1A3062);
}

.ocb-card__role {
	font-size: 0.9375rem;
	color: var(--wp--preset--color--text-muted, #4C6880);
}

/*
 * The qualification line — what the pill chips became.
 *
 * As text it wraps naturally instead of breaking into 2/2/1 rows of tags, which
 * is what made the three cards ragged. Lengths still differ, so the link pins
 * to the bottom of the card rather than following the text.
 */
.ocb-card__qual {
	font-size: 0.875rem;
	line-height: 1.55;
	color: var(--wp--preset--color--text-muted, #4C6880);
	margin-block-start: 0.25rem;
}

.ocb-card__link {
	margin-block-start: auto;
	padding-block-start: 1rem;
	font-size: 0.9375rem;
	font-weight: 600;
	color: var(--wp--preset--color--accent-deep, #0073A1);
	text-decoration: none;
	/* WCAG 2.5.8 AA — the hero rating link failed this at 23.5px. */
	min-height: 24px;
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
}

.ocb-card__link:hover { color: var(--wp--preset--color--primary, #1A3062); }

/* ── Quote card ──────────────────────────────────────────────────────── */

.ocb-card--quote .ocb-card__body { padding: 1.5rem; gap: 0.875rem; }

.ocb-card__quote {
	font-size: 1rem;
	line-height: 1.65;
	color: var(--wp--preset--color--text, #1D2735);
}

/*
 * Attribution, not decoration. No quote glyph: it was doing the same generic
 * work as the qualification pills on the practitioner cards, and a review on a
 * medical page is more credible for saying plainly who said it and where.
 */
.ocb-card__cite {
	margin-block-start: auto;
	padding-block-start: 0.25rem;
	font-size: 0.875rem;
	font-style: normal;
	color: var(--wp--preset--color--text-muted, #4C6880);
}

.ocb-card__cite b { font-weight: 600; color: var(--wp--preset--color--primary, #1A3062); }

/* ═══ Figures ════════════════════════════════════════════════════════════
 * Added for full-copy-v2, which gives five sections an image role and a
 * caption. Two different jobs, so two different treatments:
 *
 *   .ocb-figure          a picture standing IN the text column, at its own
 *                        aspect, with a caption under it (§8 radiograph)
 *   Hero / Split media   a picture that bleeds off the page edge and is
 *                        cropped to the text column's height (hero, §2)
 *
 * The second kind carries NO .ocb-figure class — those slots already size
 * their own image and the two treatments would fight.
 */

/*
 * Scoped `.ocb .ocb-figure` (0,2,0) rather than `!important`, because
 * `.ocb-prose figure` (0,1,1) above is the small white badge tile and would
 * otherwise win and shrink a clinical image to 12rem on a white card.
 */
.ocb .ocb-figure {
	margin-inline: 0;
	max-width: var(--oc-prose-max);
	padding: 0;
	background: none;
	border-radius: 0;
}

/*
 * width:auto with a height cap, not width:100%.
 *
 * The §8 radiograph is 4:5 portrait. At the full reading measure it would run
 * ~760px tall and read as the subject of the page rather than as the single
 * documented case it evidences. Capping the height sizes it by how much room a
 * piece of evidence deserves, and leaves a landscape illustration in the same
 * slot free to run the full measure.
 */
.ocb .ocb-figure img {
	display: block;
	width: auto;
	max-width: 100%;
	height: auto;
	max-height: 26rem;
	border-radius: var(--oc-radius-card, 12px);
}

.ocb .ocb-figure figcaption {
	margin-block-start: 0.75rem;
	max-width: var(--oc-prose-max);
	/* 14px exactly — the functional-text floor (docs/design-tokens.md:29).
	   The step down from body copy is colour, not size. */
	font-size: 0.875rem;
	line-height: 1.55;
	color: var(--wp--preset--color--text-muted, #4C6880);
}

/* The credit is a source, not a second caption: its own line, one step down. */
.ocb-figure__credit {
	display: block;
	margin-block-start: 0.25rem;
	font-size: 0.875rem;
}

/*
 * A caption inside a Split.
 *
 * The media column stretches to the text column, so the caption takes its own
 * row and the picture keeps what is left — `flex: 1 1 0` with `min-height: 0`,
 * or the image's intrinsic height wins and pushes the caption out of the band.
 * :has() so a Split without a caption is untouched.
 */
.ocb-split__media figure:has(figcaption) {
	display: flex;
	flex-direction: column;
}

/*
 * `flex: 1 1 0` ONLY above 60rem, and that scoping is the whole rule.
 *
 * It exists so the picture absorbs whatever height is left after the caption
 * when the media column is stretched to the text column — which only happens
 * in the two-column grid. Below 60rem the sections stack, the media column is
 * sized by its content, and a flex-basis of 0 in a container with no height to
 * hand out collapsed the image to **zero pixels**. Three captioned images were
 * invisible on every phone, each leaving its caption stranded under a blank
 * band — a reader saw a sentence describing a picture that was not there.
 *
 * Found by a screenshot review, NOT by the harness: the snapshots record
 * padding, text position, rhythm and background, and an image of height 0
 * moves none of them. tests/geometry.spec.js now asserts non-zero image boxes
 * so the next one fails a build instead of shipping.
 */
@media (min-width: 60rem) {
	.ocb-split__media figure:has(figcaption) img {
		flex: 1 1 0;
		min-height: 0;
		height: auto;
	}
}

.ocb-split__media figcaption {
	padding: 0.75rem var(--oc-inset) 0;
	font-size: 0.875rem;
	line-height: 1.55;
	color: var(--wp--preset--color--text-muted, #4C6880);
}

/*
 * Above 60rem the media bleeds off ONE page edge, and which one depends on
 * --reverse. The caption has to know that: it lines up with the picture's inner
 * edge on the side that meets the text, and with the page inset on the side
 * that runs off the page.
 *
 * Symmetric padding got this wrong in one direction — on a reversed Split the
 * caption started 58px from the screen edge while every other line on the page
 * started at 101px, so it hung outside the layout.
 */
@media (min-width: 60rem) {
	.ocb-split__media figcaption { padding-inline: 0 var(--oc-inset); }
	.ocb-split--reverse .ocb-split__media figcaption { padding-inline: var(--oc-inset) 0; }
	/* --fit does not bleed; its caption sits under its picture. */
	.ocb-split--fit .ocb-split__media figcaption { padding-inline: 0; }
}

/*
 * ONE DELIBERATE EXCEPTION, so it is not read as an oversight.
 *
 * A caption normally sits flush with its picture's left edge. On a REVERSED
 * Split the picture bleeds off the left of the screen, and flush would put the
 * caption at x=0, touching the viewport. So it takes the page inset instead and
 * reads as indented under the photo — which is what any print layout does with
 * a full-bleed image.
 *
 * Three of the four captions on /implantologie/ are flush with their picture;
 * the reversed one is on the page grid. That is the rule working, not four
 * sections disagreeing.
 */

/* ═══ Source note ════════════════════════════════════════════════════════
 * page-contract: "ein kurzer, verlinkter Quellenhinweis nach dem FAQ".
 *
 * It lives inside the FAQ section rather than in a band of its own. Six links
 * do not warrant a full-width tinted section, and giving them one would end the
 * page on a bibliography instead of on the appointment.
 */

.ocb .ocb-flow > * + .ocb-sources { margin-block-start: 3rem; }

/*
 * No border of its own: the FAQ's last row already draws one
 * (.ocb-faq > .ocb-accordion:last-child). Having both produced two hairlines
 * with an empty 53px band between them, which read as a stray empty accordion.
 */
.ocb-sources {
	max-width: var(--oc-prose-max);
}

.ocb-sources__heading {
	margin: 0 0 0.75rem;
	font-family: inherit;
	font-size: 0.875rem;
	font-weight: 600;
	letter-spacing: 0.02em;
	color: var(--wp--preset--color--text-muted, #4C6880);
}

.ocb-sources__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: 0.5rem;
}

.ocb-sources__list a {
	font-size: 0.875rem;
	line-height: 1.5;
	color: var(--wp--preset--color--text-muted, #4C6880);
	text-decoration: underline;
	text-underline-offset: 0.15em;
	/* WCAG 2.5.8 AA is 24px; a 14px link on a 1.5 line-box is 21px, so the row
	   is padded rather than the type enlarged. */
	display: inline-block;
	min-height: 24px;
}

.ocb-sources__list a:hover { color: var(--wp--preset--color--primary, #1A3062); }

/* ═══ Card body text ═════════════════════════════════════════════════════
 * The review card's summary is a <p>, not a <blockquote> — see
 * ocb_card_review() for why. Both it and the attribution need their default
 * paragraph margins removed, because .ocb-card__body is a flex column whose
 * gap already sets the spacing and a stray margin would add to it.
 */
.ocb-card__body > * { margin-block: 0; margin-inline: 0; }

/*
 * …but NOT the two that are deliberately pushed to the card's foot. The reset
 * above is later in the file and would otherwise cancel their `auto`, which is
 * what keeps the attribution and the profile link on one line across a row of
 * cards whose text runs to different lengths.
 */
.ocb-card__cite,
.ocb-card__link { margin-block-start: auto; }

/* ═══ Composition — rhythm from layout, never from new components ════════
 * Marco, 2026-09-21, after a consolidation pass that went too far:
 *
 *   "I am looking for a template to easily replicate — so nested in nested in
 *    nested will not work… I am not doing cards for anything other than
 *    doctors or reviews or USPs."
 *
 * So Panel, Feature row, Callout and `--prose--wide` are GONE. They solved this
 * page and would have cost the next twenty: each put a second container inside
 * a section, and a grammar that permits that gets nested to death.
 *
 * What remains is the whole vocabulary — Standard, 2-column, Timeline, Table,
 * Cards, CTA, on white or pale blue. Rhythm is which of those a section uses,
 * not what is bolted inside it. docs/section-types.md is the rule;
 * tests/geometry.spec.js fails a build that breaks it.
 *
 * Steps (below) survives because a numbered process IS one of the primitives,
 * not a container nested in another section.
 */

/* ── Steps ───────────────────────────────────────────────────────────────
 * The one genuinely sequential section on the page, and five collapsed
 * accordion bars made it look like every other section. Numbers say "this is
 * an order, and there are five" before a word is read — and the content is
 * open, because a reader scanning a process should not have to click five
 * times to see it.
 *
 * The number comes from a CSS counter, so a step added or removed in Gutenberg
 * renumbers itself instead of contradicting its own markup.
 */

.ocb-steps {
	counter-reset: ocb-step;
	display: grid;
	gap: clamp(1.75rem, 3vw, 2.5rem);
}

.ocb-step {
	counter-increment: ocb-step;
	position: relative;
	padding-inline-start: 3.25rem;
}

.ocb-step::before {
	content: counter(ocb-step);
	position: absolute;
	inset-inline-start: 0;
	inset-block-start: 0;
	width: 2.25rem;
	height: 2.25rem;
	display: grid;
	place-items: center;
	border-radius: 50%;
	background: var(--wp--preset--color--primary, #1A3062);
	color: #fff;
	font-size: 0.9375rem;
	font-weight: 600;
	font-variant-numeric: tabular-nums;
}

/* The thread between the steps. Stops at the last one: a line trailing past
   the final step suggests a sixth. */
.ocb-step:not(:last-child)::after {
	content: '';
	position: absolute;
	inset-inline-start: 1.09375rem;
	inset-block: 2.75rem calc(clamp(1.75rem, 3vw, 2.5rem) * -1);
	width: 1px;
	background: rgba(26, 48, 98, 0.16);
}

/* The heading sits on the number's line rather than above it. */
.ocb-step > :is(h3, h4):first-child { margin-block-start: 0.1875rem; }

.ocb .ocb-flow > * + .ocb-steps { margin-block-start: 2.5rem; }

/* ── Review cards ────────────────────────────────────────────────────────
 * Marco: they "need to look like reviews, not four more tiny text columns —
 * larger type, more whitespace". Four across at 1440 gave each card 292px,
 * which is a column of body copy, not a quotation. Two across doubles that.
 */

.ocb-cards--review .ocb-card__body {
	padding: clamp(1.75rem, 3vw, 2.25rem);
	gap: 1.125rem;
}

.ocb-cards--review .ocb-card__quote {
	font-size: 1.0625rem;
	line-height: 1.7;
}

/* ── Review card ─────────────────────────────────────────────────────────
 * Stars, what the review says, and where it came from. More air than the
 * practitioner card: this one is read, not scanned.
 */

.ocb-cards--review .ocb-card__body {
	padding: clamp(1.75rem, 3vw, 2.25rem);
	gap: 1rem;
}

.ocb-card__stars {
	color: var(--oc-star);
	font-size: 1.0625rem;
	letter-spacing: 0.12em;
	line-height: 1;
}

.ocb-cards--review .ocb-card__quote {
	font-size: 1.0625rem;
	line-height: 1.7;
}

/*
 * The source sits at the card's foot, separated by a rule rather than by more
 * air — with four cards of different lengths the rule is what makes the row
 * read as one row. `margin-block-start: auto` keeps it pinned when the text
 * above is short (see the card-body reset further up, which would otherwise
 * cancel it).
 */
.ocb-cards--review .ocb-card__cite {
	margin-block-start: auto;
	padding-block-start: 1rem;
	border-top: 1px solid rgba(26, 48, 98, 0.10);
	display: flex;
	align-items: center;
	gap: 0.5rem;
	/* 14px — the functional-text floor, not below it. */
	font-size: 0.875rem;
}

.ocb-card__platform { font-weight: 600; color: var(--wp--preset--color--primary, #1A3062); }

/* The review's own title (homepage cards), between stars and text. Welded to
   the text below it: the gap is the card body's gap, halved. */
.ocb-card__title {
	font-family: var(--wp--preset--font-family--display, Georgia, serif);
	font-size: 1.125rem;
	font-weight: 600;
	line-height: 1.3;
	color: var(--wp--preset--color--primary, #1A3062);
}

.ocb-cards--review .ocb-card__title + .ocb-card__quote { margin-block-start: -0.5rem; }

/* The official mark, when the file exists. Capped by HEIGHT so two logos of
   different proportions sit on one optical line. */
.ocb-card__logo {
	display: block;
	height: 1.125rem;
	width: auto;
	max-width: 7rem;
}

/* A square mark beside the platform's name — the same height as the text's
   cap line, so "G Google" reads as one word. */
.ocb-card__logo--mark { height: 1rem; }

/* A rating below five needs its unlit stars to read as unlit, not as absent —
   four gold stars alone looks like a four-star scale, not four out of five. */
.ocb-card__stars-off { color: rgba(26, 48, 98, 0.22); }

/*
 * Buttons inside the blocks take the site's radius.
 *
 * The hero button rendered with square corners while the identical button in
 * the CTA band, the header and every other page rendered at 6px — same label,
 * same action, two shapes. The block buttons are raw HTML in a wp:html block,
 * so they miss whatever the theme's rule keys on; blocks.css loads after
 * theme.css, so stating it here is enough without !important.
 */
.ocb .wp-block-button__link {
	border-radius: var(--wp--custom--oralchirurgie--border-radius--button, 6px);
}
