/* findd public website theme. Per-customer colours/fonts come from CSS vars
 * injected by SiteSettings::cssVars(): --c-primary, --c-secondary, --font-body. */

/* Self-hosted Rajdhani (DSGVO, no CDN) so the "Rajdhani" font choice also renders
 * on the live site, not just in the admin editor (app.css). Same woff2 files. */
@font-face { font-family: 'Rajdhani'; font-style: normal; font-weight: 400; font-display: swap; src: url('fonts/rajdhani-400.woff2') format('woff2'); }
@font-face { font-family: 'Rajdhani'; font-style: normal; font-weight: 500; font-display: swap; src: url('fonts/rajdhani-500.woff2') format('woff2'); }
@font-face { font-family: 'Rajdhani'; font-style: normal; font-weight: 600; font-display: swap; src: url('fonts/rajdhani-600.woff2') format('woff2'); }
@font-face { font-family: 'Rajdhani'; font-style: normal; font-weight: 700; font-display: swap; src: url('fonts/rajdhani-700.woff2') format('woff2'); }

/* THE chevron — same geometry as app.css and icon('chevron'); see the note there.
   Duplicated on purpose: tenant pages never load app.css. */
.chev {
  display: inline-block; flex: none; width: 1em; height: 1em; background: currentColor;
  -webkit-mask: var(--chev-mask); mask: var(--chev-mask);
}
.chev-down { transform: rotate(90deg); }
.chev-left { transform: rotate(180deg); }
.chev-up   { transform: rotate(-90deg); }

:root {
  --chev: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m8 3 9 9-9 9'/%3E%3C/svg%3E");
  --chev-mask: var(--chev) center / contain no-repeat;
  --c-primary: #19BCB9;
  --c-secondary: #18D6BA;
  /* THE brand gradient of the FILLED controls — a button and the active gallery
     category are the same affordance in two shapes, so the value lives once.
     15deg like the gallery category, not the 135deg of the decorative gradients
     (section background, document/payment icon tiles): on a control one line high
     a steep diagonal reads as a colour shift ALONG the label, which is the effect
     that was asked for; 135deg on a 40px-tall box is nearly a vertical fade. */
  --grad-brand: linear-gradient(15deg, var(--c-primary) 0%, var(--c-secondary) 100%);
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --ink: #1c2530;
  --ink-soft: #5a6675;
  /* Hairlines: card borders, dividers, input borders, the switch track. Was
     #e6e9ee = 1.22:1 on white — a line that faint reads as a rendering artefact
     rather than a boundary, and it disappeared entirely on the muted footer
     (1.13:1). The new value matches the presence the DARK mode always had
     (#2b333d on its page background is 1.44:1), so the two modes now draw the
     same weight of line instead of light mode being the quiet outlier. */
  --line: #d0d7e0;   /* 1.45:1 on white, 1.35:1 on --muted-bg */
  --muted-bg: #f5f7f9;
  --page-bg: #fff;   /* page background */
  --surface: #fff;   /* cards / header / inputs */
  /* Scrollbar thumb of the dialogs that scroll inside themselves (see
     "dialog scrollbars"). Not a hairline and not text: a control that should be
     findable without competing with the content — 1.63:1 on the sheet, 2.45:1
     under the pointer. The dark pair is measured to the same two ratios so both
     modes draw the same weight of bar (contrast_ratio(), i.e. WCAG, not the
     brightness approximation of hex_is_light()). */
  --scroll-thumb: #c2ccd6;
  --scroll-thumb-hover: #9aa7b4;
  --radius: 6px;
  --maxw: 1120px;
  --gutter: 22px;   /* single horizontal page gutter — applied once at the outermost content level */
  /* Height of a wide stage inside the content column: the big Events/Jobs/News box
     (.fb-feat-big) and the teaser banner (.fb-tl-overlay). One decision, one place —
     a page carrying both looks ragged the moment the two drift apart. Deliberately
     flat: 16/9 would be 720px on a 1280 column and push everything below the fold. */
  --fb-banner-h: 340px;
  /* Height of an OVERLAYING header (the "transparent over hero" option), measured
     and published by findd-public.js on the live page and by builder.js on the
     canvas — it depends on the variant, the utility bar and the breakpoint, so it
     cannot be a constant. 0 means "no header covers anything", which is both the
     safe no-JS fallback and the value on every non-hero page. Consumed via the
     inherited --hero-offset, see "hero under an overlaying header". */
  --header-h: 0px;
}

/* Dark theme — remap the neutral tokens; accent colours stay tenant-defined.
   Applied to <html> by an early head script (no flash) + the header toggle. */
html.theme-dark {
  --ink: #e7ecf2;
  --ink-soft: #9aa6b3;
  --line: #2b333d;
  --muted-bg: #161c23;
  --page-bg: #10151a;
  --surface: #1a212a;
  --scroll-thumb: #39434f;        /* 1.61:1 on --surface */
  --scroll-thumb-hover: #525e6b;  /* 2.45:1 */
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
/* Onepager: offset anchored sections so a (sticky) header doesn't cover them. */
.fb-section[id] { scroll-margin-top: 90px; }
body.findd-site {
  margin: 0; font-family: var(--font-body); color: var(--ink);
  background: var(--page-bg); line-height: 1.6;
}
.findd-site img { max-width: 100%; height: auto; display: block; }
/* Content links. --c-link is only emitted once the customer picks one
   (SiteSettings::cssVars), so the fallback here is the single place that decides
   what "no own link colour" means. Every context that needs a different colour
   for contrast — footer, utility bar, coloured sections, header CTA, 404 — still
   out-specifies this rule the way it always did; the setting is about links in
   the CONTENT, not about repainting white-on-primary text. */
.findd-site a:not(.fb-btn) { color: var(--c-link, var(--c-primary)); }

/* --- shell ---------------------------------------------------------------- */
/* Editor notices (draft / maintenance) as fixed top-right badges, stacked and
   layered above the header (which may overlay the page when transparent). */
.site-notices { position: fixed; top: 12px; right: 12px; z-index: 60; display: flex; flex-direction: column; align-items: flex-end; gap: 8px; pointer-events: none; }
.site-notice { padding: 8px 14px; border-radius: 999px; font-size: 13px; font-weight: 700; box-shadow: 0 6px 20px rgba(0, 0, 0, .25); max-width: min(320px, 70vw); }
.site-notice--draft { background: #ffce4d; color: #5a4500; }
.site-notice--maint { background: #f97316; color: #fff; }
/* Header shell carries scroll behavior (sticky); the inner .site-header is the
   centered bar; variant classes change its layout; options compose on top. */
.site-header-wrap { background: var(--surface); }
.site-header-wrap.is-sticky { position: sticky; top: 0; z-index: 40; box-shadow: 0 1px 0 var(--line); }

/* Transparent-over-hero header: a frosted-glass bar floating over the hero. It
   follows the theme — white in light mode (dark brand text and nav) and a dark
   bar in dark mode (light text; the nav rides --ink, which flips with the theme,
   so it needs no dark-mode override). Nearly solid at the top, a bit more
   translucent once scrolled (JS toggles .is-scrolled). Only applied on pages that
   open with a hero. */
/* --hdr-op / --hdr-op-scrolled are set on the element by Header::opacityStyle()
   ONLY when the owner moved the sliders. Unset keeps the per-theme defaults below,
   which deliberately differ: light gets more see-through on scroll, dark gets more
   solid. A set value is one number for both themes. */
.site-header-wrap.is-transparent {
  position: fixed; top: 0; left: 0; width: 100%; z-index: 40;
  background: rgba(255, 255, 255, var(--hdr-op, .92));
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  transition: background .5s ease, box-shadow .5s ease, -webkit-backdrop-filter .5s ease, backdrop-filter .5s ease;
}
.site-header-wrap.is-transparent :is(.site-brand, .site-brand-name) { color: var(--ink); }
.site-header-wrap.is-transparent .site-brand-sub { color: var(--ink-soft); }
.site-header-wrap.is-transparent .site-nav a,
.site-header-wrap.is-transparent .site-nav a:hover,
.site-header-wrap.is-transparent .site-nav a.active { color: var(--ink); }
.site-header-wrap.is-transparent .site-burger span { background: var(--ink); }
.site-header-wrap.is-transparent :is(.site-theme, .site-share-btn) { color: var(--ink); }
/* scrolled → a bit more see-through (the content behind shimmers through more). */
.site-header-wrap.is-transparent.is-scrolled {
  background: rgba(255, 255, 255, var(--hdr-op-scrolled, .78));
  box-shadow: 0 6px 22px rgba(0, 0, 0, .10);
}
/* dark mode: restore the dark frosted bar with light text. */
html.theme-dark .site-header-wrap.is-transparent { background: rgba(15, 20, 26, var(--hdr-op, .62)); }
html.theme-dark .site-header-wrap.is-transparent :is(.site-brand, .site-brand-name) { color: #fff; }
html.theme-dark .site-header-wrap.is-transparent .site-brand-sub { color: rgba(255, 255, 255, .85); }
html.theme-dark .site-header-wrap.is-transparent .site-burger span { background: #fff; }
html.theme-dark .site-header-wrap.is-transparent :is(.site-theme, .site-share-btn) { color: #fff; }
html.theme-dark .site-header-wrap.is-transparent.is-scrolled { background: rgba(15, 20, 26, var(--hdr-op-scrolled, .72)); box-shadow: 0 6px 22px rgba(0, 0, 0, .18); }

/* --- hero under an overlaying header ---------------------------------------
   The overlaying bar sits ON TOP of the first block: that block starts at y=0 and
   its top --header-h is behind the bar. Two things follow — the height that was
   set reads short (part of it is covered) and vertically centred content sits too
   high, because it centres in the WHOLE box while visitors only see the part below
   the bar. On a content-sized hero section it is worse than cosmetic: the headline
   can end up behind the header.

   So the first block grows by the header's height and pads the same amount back
   in. What survives is the VISIBLE hero: exactly the height that was set, content
   centred in what people actually see, background bleeding up behind the bar.

   The offset travels as an INHERITED custom property rather than through sibling
   selectors, because the first block is wrapped differently depending on context —
   bare on the public page, in a .fb-vis visibility wrapper when hide_on is set, in
   the editor's .fb-eb frame on the canvas, or both. Handing it down means the
   blocks themselves stay wrapper-agnostic and one rule covers every case.
   --header-h is 0 unless a header actually overlays (findd-public.js / builder.js
   publish it), so these rules are inert everywhere else — no gating class needed.
   The hook is .site-page — the element that HOLDS the blocks. Not .site-main: on the
   public page <main> wraps them in <article class="site-page">, so the offset landed
   on that wrapper and inherited into EVERY section (52px padding became 204px, live
   only — the editor's canvas holds the blocks directly and looked right). Both shapes
   now carry .site-page, so a rule written once matches in both. */
.site-page > :first-child { --hero-offset: var(--header-h); }
/* A container ends the inheritance of every value a section reads off itself. It
   already consumed them at this point, and a NESTED section must not pick them up:
   --hero-offset would offset a second time, and --fb-mh/--fb-pad would hand it the
   OUTER section's height/padding whenever its own class defines none — which is
   exactly the default case (`Mindesthöhe: Automatisch` renders .fb-mh-auto, a class
   with no rule). That is a real trap of moving these onto .fb-section: as long as
   min-height sat on .fb-mh-l itself it could not leak; as an inherited property it
   can. Every nested section sits in a .fb-container or a grid .fb-col. */
.fb-container, .fb-col { --hero-offset: 0px; --fb-mh: 0px; --fb-mh-grow: initial; --fb-pad: 0px; }
/* …and say it outright where the markup already says it. */
.fb-mh-auto { --fb-mh: 0px; --fb-mh-grow: initial; }
.site-header {
  /* --hpad is the bar's vertical padding; the menu items reach back through it
     (negative block margin below) so their top line meets the bar's edge. */
  --hpad: 16px;
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  max-width: var(--maxw); margin: 0 auto; padding: var(--hpad) var(--gutter);
}
.site-header-end { display: flex; align-items: center; gap: 14px; }
.site-brand { font-weight: 800; font-size: 22px; color: var(--ink); text-decoration: none; letter-spacing: .5px; }
/* The site name is a link, so `.findd-site a:not(.fb-btn)` (0-2-1) beats the --ink
   above (0-1-0) and has always painted it in the brand colour — the rule two lines
   up never won a day in its life. Harmless while that colour WAS the brand colour;
   with an own link colour the company name in the header suddenly followed it
   (measured: the wordmark turned link-red). Pinned to what every live site renders
   today, at the same 0-2-1 so the transparent-header rules (0-3-0) still win. */
.findd-site a.site-brand { color: var(--c-primary); }
/* Definite height (not just a max) so a logo with no intrinsic width — e.g. an
   SVG that carries only a viewBox — can derive its width from the aspect ratio.
   With `max-height` such an SVG resolves to 0 width and collapses .site-brand to
   0×0 (it's a flex item). max-width + contain guard a very wide logo. */
.site-brand img { height: 46px; width: auto; max-width: 260px; object-fit: contain; }
.site-nav { display: flex; flex-wrap: wrap; gap: 6px; }
/* Menu items: no box behind them — a hairline primary-colour rule rides the top
   edge and, on hover/active, grows out from the centre to the item's full width
   (like the old TYPO3 #pagemenu). The label itself stays --ink (black in light
   mode, white in dark) — the line alone carries the accent.
   The item spans the bar's FULL height: it eats back into the header padding
   (negative block margin) and pays it back as padding, so the label doesn't move
   but the rule lands on the bar's very top edge — i.e. at the top of the window
   for a header sitting at the top of the page. Also enlarges the click target.
   Skipped for `centered`, whose menu is a second row, not the top edge. */
.site-nav a { position: relative; display: flex; align-items: center; text-decoration: none; color: var(--ink); font-weight: 600; padding: 10px 12px; cursor: pointer; }
.site-header:not(.site-header--centered) :is(.site-header-end, .site-nav) { align-self: stretch; }
.site-header:not(.site-header--centered) .site-nav a {
  /* stretched to the bar's content height, then pushed back out through its
     padding: border box = full bar, so ::before lands on the bar's top edge. */
  margin-block: calc(-1 * var(--hpad));
  padding-block: 0;
}
.site-nav a::before {
  content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 0; height: 3px; background: var(--c-primary);
  transition: width .25s ease;
}
/* The site-wide link colour (.findd-site a:not(.fb-btn), 0-2-1) outranks a bare
   .site-nav a (0-1-1) — match its specificity, or the menu goes primary again. */
.findd-site .site-nav a,
.site-nav a:hover, .findd-site .site-nav a:hover,
.site-nav a.active, .findd-site .site-nav a.active { color: var(--ink); }
.site-nav a:hover::before, .site-nav a.active::before { width: 100%; }
/* Icon-only call/contact button — same 40px square as share/theme, with which it
   now shares the .site-chrome group (see Header::ctaHtml). Primary-coloured so the
   one action a visitor came for still reads as the primary one. */
/* The :not(.fb-btn) is not decoration: the global link colour
   `.findd-site a:not(.fb-btn)` (0-2-1) outranks a plain .site-header-cta, so
   currentColor came out primary-on-primary and the handset was invisible. Matching
   its specificity (and sitting later in the file) is what wins. */
.findd-site .site-header-cta:not(.fb-btn) { color: #fff; }
/* The header's call/mail button is a filled button too, so it wears the same
   gradient — transparent border and border-box origin for the same reasons as
   .fb-btn-primary (see the note there; without the origin the 1px ring shows the
   gradient repeated in the wrong colour). */
.site-header-cta { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 0; flex: none; border: 1px solid transparent; border-radius: var(--radius); background: var(--grad-brand); background-origin: border-box; }
.site-header-cta svg { width: 19px; height: 19px; }
.site-header-cta:hover { filter: brightness(.95); }

/* hamburger toggle — hidden by default; shown on small screens once JS is active */
.site-burger {
  display: none; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  width: 44px; height: 44px; padding: 0; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); cursor: pointer;
}
.site-burger span { display: block; width: 20px; height: 2px; border-radius: 6px; background: var(--ink); transition: transform .2s, opacity .2s; }
.site-header.nav-open .site-burger span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.site-header.nav-open .site-burger span:nth-child(2) { opacity: 0; }
.site-header.nav-open .site-burger span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* variant: centered — logo on top, nav (and CTA) centered below */
.site-header--centered { flex-direction: column; gap: 10px; text-align: center; }
.site-header--centered .site-nav { justify-content: center; }

/* variant: minimal — slim bar, smaller logo */
.site-header--minimal { --hpad: 10px; }
.site-header--minimal .site-brand { font-size: 19px; }
.site-header--minimal .site-brand img { height: 36px; }

/* variant: badge — slim single-row bar whose oversized round logo on the left
   overhangs below it (into the content/hero). For badge-shaped logos that need
   room; nods to the migrated TYPO3 look without a hero image. The logo IS the
   brand here — no company name beside it (the text only appears as a fallback
   when no logo is set). The negative bottom margin keeps the bar slim while the
   circle hangs below; --badge-size drives the circle. */
.site-header--badge {
  --hpad: 8px; overflow: visible;
  --badge-size: 112px; --badge-gap: 14px;
}
.site-header--badge .site-brand--badge { display: flex; align-items: center; gap: var(--badge-gap); text-decoration: none; overflow: visible; }
.site-header--badge .site-badge {
  flex: 0 0 auto; width: var(--badge-size); height: var(--badge-size); border-radius: 50%; overflow: hidden;
  margin: 0 0 -68px; align-self: flex-start; position: relative; z-index: 5;
  display: flex; align-items: center; justify-content: center;
}
/* No plate by default. The circle used to be filled with the primary colour no
   matter what, and badge logos almost always carry their own background — the
   fill then peeked out around the artwork as a coloured ring (measured: a badge
   PNG whose own circle sits at 92% of the canvas leaves a 4% ring; a wide logo
   gets bands above and below it from object-fit: contain). A cut-out logo is the
   one case that does need something behind it, so the plate is a header option
   ("Farbige Fläche hinter dem Logo") and stays out of the way until asked for. */
.site-header--badge.has-badge-plate .site-badge { background: var(--c-primary); }
/* max-height:none overrides the generic .site-brand img cap (46px) — the badge
   nests inside .site-brand, so without this the logo stays tiny in the circle. */
.site-header--badge .site-badge img { display: block; width: 100%; height: 100%; max-height: none; object-fit: contain; }
.site-header--badge .site-brand-text { display: flex; flex-direction: column; gap: 1px; }
.site-header--badge .site-brand-name { font-weight: 800; font-size: 20px; letter-spacing: .5px; color: var(--ink); line-height: 1.2; }
.site-header--badge .site-brand-sub { color: var(--ink-soft); font-size: 14px; font-weight: 300; }

/* option: utility bar above the header */
.site-utilitybar { background: var(--muted-bg); border-bottom: 1px solid var(--line); font-size: 13px; }
.site-utilitybar-inner {
  max-width: var(--maxw); margin: 0 auto; padding: 6px var(--gutter);
  display: flex; flex-wrap: wrap; gap: 18px; justify-content: flex-end; align-items: center; color: var(--ink-soft);
}
/* Must out-specify the global link colour (.findd-site a:not(.fb-btn) = 0-2-1):
   as a plain .site-utilitybar-inner a (0-2-0) the phone number and mail address
   rendered primary-coloured and therefore never followed the theme — the same
   specificity trap the footer links and the nav items hit. They are text in a
   strip, not links to be spotted, so they look like the text beside them. */
.findd-site .site-utilitybar-inner a:not(.fb-btn) { color: var(--ink-soft); text-decoration: none; }
.findd-site .site-utilitybar-inner a:not(.fb-btn):hover { color: var(--ink); }
.site-main { min-height: 50vh; }
/* Announcement bar — one time-limited sentence above everything, in the brand
   colour so it reads as the site speaking, not as a system message. Sits inside
   the header wrapper, so it scrolls away with a sticky bar and stays above a
   transparent one. */
/* --ann-bg/--ann-ink are set inline by Header::announceHtml only when the customer
   picked a colour; unset, the fallbacks ARE the previous hard-coded values, so a
   site that never touches this renders exactly as before. */
.site-announce { background: var(--ann-bg, var(--c-primary)); color: var(--ann-ink, #fff); font-size: 14px; }
.site-announce-inner { max-width: var(--maxw); margin: 0 auto; padding: 8px var(--gutter);
  display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: center; justify-content: center; text-align: center; }
/* Same specificity trap as the footer and the utility bar: a bare
   .site-announce a (0-2-0) loses to .findd-site a:not(.fb-btn) (0-2-1) and the
   link would render in the primary colour ON the primary colour — invisible. */
.findd-site .site-announce-link:not(.fb-btn) { color: var(--ann-ink, #fff); text-decoration: underline; font-weight: 600; }
.findd-site .site-announce-link:not(.fb-btn):hover { opacity: .85; }

.site-footer { border-top: 1px solid var(--line); margin-top: 60px; padding: 22px var(--gutter); background: var(--muted-bg); }
/* Everything on one line — company, contact, legal, credit; wraps only when the
   viewport is narrower than the line. */
.site-footer-inner { max-width: var(--maxw); margin: 0 auto; display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; color: var(--ink-soft); font-size: 14px; }
/* Footer links read as text, not as links: no underline, same colour as the
   unlinked text next to them. :not(.fb-btn) is needed to out-specify the
   site-wide link colour (.findd-site a:not(.fb-btn), 0-2-1) — a plain
   .site-footer a would lose and stay primary-coloured. */
.findd-site .site-footer a:not(.fb-btn) { color: var(--ink-soft); text-decoration: none; }
.findd-site .site-footer a:not(.fb-btn):hover { color: var(--ink); }
/* The tenant logo no longer renders down here (02.09.2026) — the mark belongs in
   the header, the footer carries the name in words. Only the findd mark is left,
   and it always inverts in dark mode: it is our own dark drawing. */
html.theme-dark .findd-site .site-footer-mark { filter: grayscale(1) invert(1); }
.site-footer-credit { margin-left: auto; font-size: 13px; display: inline-flex; align-items: center; gap: 6px; }
/* Scoped to .findd-site: the generic `.findd-site img { height: auto }` (0-1-1)
   would otherwise out-specify a bare .site-footer-mark and blow the mark up to
   its intrinsic size. */
.findd-site .site-footer-mark { height: 15px; width: auto; filter: grayscale(1); opacity: .7; }

/* Footer variant "columns" — the multi-column shape. The legal row keeps the
   one-line look underneath, so the two variants share their vocabulary. */
.site-footer--columns { padding-top: 34px; padding-bottom: 18px; }
.site-footer-cols { max-width: var(--maxw); margin: 0 auto; display: grid; gap: 28px 40px;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); color: var(--ink-soft); font-size: 14px; }
.site-footer-name { font-weight: 700; color: var(--ink); margin-bottom: 4px; }
.site-footer-cols address { font-style: normal; line-height: 1.6; }
.site-footer-head { font-weight: 700; color: var(--ink); margin-bottom: 8px; font-size: 13px; letter-spacing: .04em; text-transform: uppercase; }
.site-footer-head.mt { margin-top: 16px; }
.site-footer-nav { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; line-height: 1.5; }
.site-footer-today { margin: 0; }
/* The legal row: same rules as the single-line variant, one border above it. */
.site-footer-legal { max-width: var(--maxw); margin: 26px auto 0; padding-top: 14px; border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; color: var(--ink-soft); font-size: 14px; }
.site-footer--columns .site-footer-credit { margin-left: auto; }
@media (max-width: 600px) { .site-footer-cols { gap: 22px; } }
.site-page { }
/* Headings may use their own family. --font-head is emitted by
   SiteSettings::cssVars ONLY when a heading font was picked, so the fallback here
   is what "same as body text" means — one place decides it. */
/* The weight rides along on the same selector, and the fallback IS the browser's
   own default for a heading — so a site that never picks one renders byte for byte
   as before. `.findd-site :is(h1…)` is 0-1-1, which beats a single-class rule on a
   heading (.fb-pay-grouptitle is the only one), and that is deliberate: the setting
   says "headings", so it has to reach all of them. */
.findd-site :is(h1, h2, h3, h4, h5, h6) {
  font-family: var(--font-head, var(--font-body));
  font-weight: var(--font-head-weight, 700);
}
.site-empty, .site-404 { max-width: var(--maxw); margin: 80px auto; padding: 0 var(--gutter); text-align: center; color: var(--ink-soft); }
/* Suggestions on the 404 page. Prefixed with .findd-site so the link colour does
   not lose to the global .findd-site a:not(.fb-btn) rule (0-2-1). */
.site-404-links { margin: 28px auto; }
.site-404-lead { margin: 0 0 10px; font-size: .95rem; }
.site-404-links ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 10px 22px; justify-content: center; }
.findd-site .site-404-links a:not(.fb-btn) { color: var(--ink); font-weight: 600; }
.findd-site .site-404-links a:not(.fb-btn):hover { color: var(--c-primary); }

/* --- containers ----------------------------------------------------------- */
/* per-device visibility (B2): wrapper contributes no box, only toggles display */
.fb-vis { display: contents; }
@media (max-width: 600px) { .fb-hide-mobile { display: none !important; } }
@media (min-width: 901px) { .fb-hide-desktop { display: none !important; } }

/* Padding and min-height travel as custom properties so --hero-offset (see "hero
   under an overlaying header") can be added without repeating the constants. It is
   0 for every section but a hero the header overlays. */
.fb-section {
  width: 100%; position: relative;
  padding-top: calc(var(--fb-pad, 0px) + var(--hero-offset, 0px));
  padding-bottom: var(--fb-pad, 0px);
  min-height: calc(var(--fb-mh, 0px) + var(--fb-mh-grow, var(--hero-offset, 0px)));
}
/* Section background photo — same reasoning as .fb-slide-img (srcset needs an
   <img>), and the same two traps: the `.findd-site` prefix is required or
   `.findd-site img { height: auto }` wins and the image collapses to 0, and the
   overlay has to be lifted above it — that lift lives in the shared .fb-ov-* rule
   below, NOT here; see the note there for why a copy of it up here was dead. The
   container sits at z-index 2. */
.findd-site .fb-section-img {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; object-fit: cover; object-position: center;
}
.fb-section .fb-container { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--gutter); position: relative; z-index: 2; }
.fb-w-full .fb-container { max-width: none; }
/* "Reduzierte Breite" — 2/3 of the section's normal cap, the reading measure for a
   text-only section. Both rules are 0-2-0, exactly like .fb-section .fb-container
   above, so they win on ORDER alone and have to stay below it (see the .fb-ov-*
   note). 2/3 of --maxw, not of the content box: --maxw is what the customer sets
   as "Inhaltsbreite", so the reduced section is two thirds of the thing they chose;
   the gutter is unchanged, so the visible text is 65% of the normal one. No media
   query — below 747px the cap stops binding and the section is contained again. */
.fb-w-narrow .fb-container { max-width: calc(var(--maxw) * 2 / 3); }
.fb-pad-s { --fb-pad: 24px; }
.fb-pad-m { --fb-pad: 52px; }
.fb-pad-l { --fb-pad: 90px; }
/* Even section padding: a section's top/bottom edge spacing must come SOLELY from
   its own padding. A block's vertical margin is meant for spacing to its siblings;
   at the very top/bottom of a section it would stack on top of the section padding
   and make it look uneven (e.g. a heading's leading, or a trailing grid's
   margin-bottom). So collapse the first child's top margin and the last child's
   bottom margin — unwrapping the display:contents visibility wrapper. Spacing
   BETWEEN blocks (heading → content, etc.) is untouched. */
.fb-container > :first-child,
.fb-container > .fb-vis:first-child > * { margin-top: 0; }
.fb-container > :last-child,
.fb-container > .fb-vis:last-child > * { margin-bottom: 0; }
/* Many self-boxing blocks (contact, payment, docs, catalog …) wrap an inner
   headline/element whose default margin would collapse OUT through the padding-
   less block+container and stack onto the section padding — so the block's own
   margin being 0 isn't enough. Also zero the inner edge margin of the section's
   first and last block (one level down, incl. the display:contents wrapper).
   Scoped to .fb-container so bare (non-section) blocks keep this inner margin as
   their inter-block spacing. */
.fb-container > :first-child > :first-child,
.fb-container > .fb-vis:first-child > * > :first-child { margin-top: 0; }
.fb-container > :last-child > :last-child,
.fb-container > .fb-vis:last-child > * > :last-child { margin-bottom: 0; }
.fb-bg-muted { background: var(--muted-bg); }
.fb-bg-primary { background: var(--c-primary); color: #fff; }
.fb-bg-primary .fb-heading { color: #fff; }
.fb-bg-gradient { background: linear-gradient(135deg, var(--c-primary), var(--c-secondary)); color: #fff; }
.fb-bg-gradient .fb-heading { color: #fff; }
/* Links follow the light text on coloured backgrounds. Scoped with .findd-site +
   :not(.fb-btn) so this out-specifies the base link colour (.findd-site a) — without
   the extra class it loses on specificity and links stay primary-coloured. */
.findd-site .fb-bg-primary a:not(.fb-btn),
.findd-site .fb-bg-gradient a:not(.fb-btn) { color: #fff; }

/* section: background image + overlay. The image itself is a positioned <img>
   (.fb-section-img) since srcset needs one — the old .fb-bg-image sizing rule had
   nothing left to size and is gone; the class stays only as a state marker.
   ONE rule for all three, the lift included. It used to be this block with
   `z-index: 0` plus three separate `z-index: 1` lifts — and the SECTION's lift
   stood BEFORE this block. Same specificity (0-2-1: class + attribute selector +
   pseudo-element), so the later rule won and the section's scrim stayed at 0,
   tied with its own <img>; a tie is settled by DOM order, and the ::before is the
   FIRST child while the <img> comes after it — the photo painted over the scrim.
   The result: "Überlagerung" did nothing on any section with a background image,
   while slide and teaser (whose lifts stand after this block) were fine. That is
   Falle 1 in its equal-specificity form, and it stayed invisible because the two
   working cases looked like proof that the mechanism worked.
   All three stack identically anyway — image 0 · scrim 1 · content 2
   (.fb-container / .fb-slide-wrap / .fb-teaser-body) — so there is nothing for a
   per-block lift to say. Absolutely positioned so the scrim stays out of flow and
   never becomes a grid item of its own (which would open a second teaser row). */
.fb-section[class*="fb-ov-"]::before,
.fb-teaser[class*="fb-ov-"]::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none; background: #000;
}
.fb-ov-light::before  { opacity: .25; }
.fb-ov-medium::before { opacity: .45; }
.fb-ov-strong::before { opacity: .65; }

/* section: explicit text theme (e.g. custom colour → light/dark). The link rules
   carry .findd-site + :not(.fb-btn) so they out-specify the base link colour. */
.fb-text-light, .fb-text-light .fb-heading { color: #fff; }
.fb-text-dark,  .fb-text-dark .fb-heading  { color: var(--ink); }
.findd-site .fb-text-light a:not(.fb-btn) { color: #fff; }
.findd-site .fb-text-dark a:not(.fb-btn)  { color: var(--ink); }

/* section: min-height (content vertically centered when set) */
.fb-mh-s, .fb-mh-m, .fb-mh-l, .fb-mh-screen { display: flex; flex-direction: column; justify-content: center; }
.fb-mh-s { --fb-mh: 320px; }
.fb-mh-m { --fb-mh: 480px; }
.fb-mh-l { --fb-mh: 640px; }
/* One screen stays one screen: a full-height hero must not grow by the header's
   height (the header is inside the viewport), it only pushes its content down. */
.fb-mh-screen { --fb-mh: 100vh; --fb-mh-grow: 0px; }

.fb-grid { display: flex; flex-wrap: wrap; gap: 24px; max-width: var(--maxw); margin: 24px auto; padding: 0 var(--gutter); }
/* 220px is not a cosmetic minimum, it IS the wrap mechanism: with flex-wrap a
   column that would fall below it moves to the next line, which is how every grid
   becomes single-column on a phone without a media query. */
.fb-grid .fb-col { min-width: 220px; }
/* …but that floor also silently swallowed a dragged ratio: on a 620px canvas the
   splits 30, 17 and 10 all rendered 220/332 — the handle moved, the boundary did
   not (measured, that was the report). A dragged ratio is an explicit instruction,
   so above the stacking width the floor gives way. `auto`, not `0`: that is the
   flex default (≈ min-content), so the column shrinks to what its content really
   needs and the content can never overflow the column — it wraps instead, which
   is visible in the editor while dragging. Below 700px the floor wins again and
   the columns stack, exactly as for every other grid. Three classes so this beats
   the rule above on specificity and not merely on source order. */
@media (min-width: 700px) {
  .fb-grid.fb-grid--split > .fb-col { min-width: auto; }
}
/* Nesting reset — the horizontal gutter (and max-width cap) must be applied only
   ONCE, at the outermost content level. Self-boxing blocks carry their own gutter
   so they align when dropped directly on the page; but inside a section
   (.fb-container) or a grid column (.fb-col) the parent already provides the box,
   so here they drop the gutter + cap and fill the parent. Without this a block in
   a section sits at 44px (22 + 22) while a plain heading beside it sits at 22px. */
.fb-container :is(
  .fb-grid, .fb-stats, .fb-pricing, .fb-booking, .fb-socials,
  .fb-divider, .fb-gallery, .fb-team, .fb-testimonials, .fb-partners, .fb-video,
  .fb-videos, .fb-contact, .fb-opening, .fb-map, .fb-catalog,
  .fb-docs, .fb-pay),
.fb-slide-inner :is(
  .fb-grid, .fb-stats, .fb-pricing, .fb-booking, .fb-socials,
  .fb-divider, .fb-gallery, .fb-team, .fb-testimonials, .fb-partners, .fb-video,
  .fb-videos, .fb-contact, .fb-opening, .fb-map, .fb-catalog,
  .fb-docs, .fb-pay),
.fb-col :is(
  .fb-grid, .fb-stats, .fb-pricing, .fb-booking, .fb-socials,
  .fb-divider, .fb-gallery, .fb-team, .fb-testimonials, .fb-partners, .fb-video,
  .fb-videos, .fb-contact, .fb-opening, .fb-map, .fb-catalog,
  .fb-docs, .fb-pay) {
  max-width: none;
  margin-inline: 0;
  padding-inline: 0;
}
/* Narrow-measure blocks (readability width) stay centered when nested, but must
   not double up the gutter. */
.fb-container :is(.fb-faq, .fb-form-wrap, .fb-form-result),
.fb-slide-inner :is(.fb-faq, .fb-form-wrap, .fb-form-result),
.fb-col :is(.fb-faq, .fb-form-wrap, .fb-form-result) { padding-inline: 0; }
/* A section nested inside a column shouldn't re-pad its own container. */
.fb-col > .fb-section .fb-container { padding-inline: 0; }
/* "Bare" content blocks (heading/text/image/button/teaser) carry no box of their
   own — inside a section/column the parent boxes them, but placed directly on the
   page they'd run edge-to-edge and misalign with self-boxing blocks. Give them the
   same canonical box there. (.fb-vis = optional display:contents visibility wrap.)
   Keyed off .site-page, the element that HOLDS the blocks — as .site-main it never
   matched on the public page at all, where <main> wraps them in an <article>. */
.site-page > :is(.fb-heading, .fb-text, .fb-btn-wrap, .fb-image, .fb-teaser),
.site-page > .fb-vis > :is(.fb-heading, .fb-text, .fb-btn-wrap, .fb-image, .fb-teaser) {
  max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter);
}
/* The teaser is the exception in that list: it DRAWS a card (border, background),
   so the gutter as padding puts the card EDGE on the frame edge, while every
   self-boxing block next to it insets its visible box by one gutter — measured
   against the big Events box: 1120 vs 1076, i.e. the card sticking out 22px on
   each side. Inset the box itself instead. The width pair is load-bearing: the
   max-width alone stops centring below --maxw, where the box would then run
   edge-to-edge. */
.site-page > .fb-teaser,
.site-page > .fb-vis > .fb-teaser {
  padding-inline: 0; width: calc(100% - 2 * var(--gutter));
  max-width: calc(var(--maxw) - 2 * var(--gutter));
}

/* --- basic content -------------------------------------------------------- */
.fb-heading { margin: 0 0 .4em; line-height: 1.2; }
/* Widget headline + subline — ONE rule for every widget (every widget template
   renders .fb-wtitle / .fb-wsub). Before, the titles were bare <h2> with the UA
   margins and each block brought its own subline class, so the pair drifted per
   block (measured: 27px gap in the catalog widgets, 23 in the video block, 9 in
   the payment block) and a later, more specific rule (.fb-cat-feature > h2)
   silently overrode a shared one. Change the gap here and it changes everywhere.
   The top margin is spelled out rather than inherited from the UA: it is what
   spaces a free-standing widget from the block above (.fb-container collapses it
   away inside a section).
   The subline runs in the normal ink, not a soft grey: the hand-built twin of this
   pair is a heading block + a text block (see below), and a text block is body copy
   in --ink — a greyed widget subline made the two arrangements read differently. */
.fb-wtitle { font-size: 1.5rem; line-height: 1.2; margin: .83em 0 .2em; }
.fb-wsub { margin: 0 0 1rem; }
/* The tight .2em is the PAIR gap — it belongs to a subline sitting right under the
   title. With no subline (most widgets have none) it became the gap to the content
   itself and the headline sat on top of the first row. Then the title has to carry
   the subline's bottom spacing instead, so the distance from the header unit to the
   content is the same either way. */
.fb-wtitle:not(:has(+ .fb-wsub)) { margin-bottom: 1rem; }
/* One ratio for the whole scale: 1.2 per step — 1.25rem → 1.5rem → 1.8rem (20 /
   24 / 28.8px at the default root size). The h1 was clamp(28px, 4vw, 44px) and
   grew to 1.83x the h2 on a wide screen: a display size, not a headline, on a page
   whose body copy is 16px. The clamp goes with it — its floor was 28px, so phones
   see 0.8px more than before and nothing else moves. In rem (h3 was a bare 20px)
   so the step survives a reader who runs a larger root font size; h2 was already
   rem, and next to it the px h3 collapsed the scale to 1.5 : 1 on such a setup. */
h1.fb-heading { font-size: 1.8rem; }
h2.fb-heading { font-size: 1.5rem; }
h3.fb-heading { font-size: 1.25rem; }
/* A block's own text alignment. `left` needs a REAL rule, not just "no class":
   inside a slide the surrounding text-align is the slider's setting (centre by
   default), so a block whose dropdown said "Links" inherited that and rendered
   centred — the field lied, and of its three options the DEFAULT was the one that
   did nothing. Everywhere else this rule changes nothing, because text-align
   inherits left there anyway; that is exactly why it stayed invisible (Falle 1 —
   a rule that never runs against its counterpart). The class is emitted only when
   the editor picked a direction; unset means "wie im Umfeld", i.e. inherit, which
   is what keeps the slider's own control working for untouched blocks. */
.fb-align-left { text-align: left; }
.fb-align-center { text-align: center; }
.fb-align-right { text-align: right; }
.fb-text { margin: 0 0 1em; }
/* Rich text may or may not wrap its first line in a <p> (the RTE only creates
   paragraphs once the editor pressed Enter / pasted markup). A leading <p>'s top
   margin collapses out of the block and shoves it down — two text blocks side by
   side in a grid then start on different lines. Edge spacing belongs to the block,
   not to its content, so neutralize the outer margins of the content. */
.fb-text > :first-child { margin-top: 0; }
.fb-text > :last-child  { margin-bottom: 0; }
.fb-text :is(h2,h3) { margin-top: 0; }
.fb-text :is(ul,ol) { margin: .5em 0; padding-left: 1.5em; }
.fb-text li { margin: .2em 0; }
/* The underline is what this rule is for; the colour loses to the global link rule
   (0-1-1 vs 0-2-1) and is kept in step so raising this selector's specificity later
   cannot silently undo the link colour. */
.fb-text a { color: var(--c-link, var(--c-primary)); text-decoration: underline; }
.fb-text blockquote { margin: .6em 0; padding-left: 1em; border-left: 3px solid var(--line); color: var(--ink-soft); }

/* "Widget-like" intro built by hand: a heading block + a text block above the
   actual content (grid, FAQ, stats …) is the same arrangement a widget renders as
   .fb-wtitle + .fb-wsub + content, so it gets the same rhythm — measured before:
   13.4px under the heading and 27.6px before the content, vs the widget's 8.6 /
   19.6. The heading tightens to the widget's .2em, and the content block drops its
   own 24px top margin to the widget's 1rem. Only when the blocks are actually
   adjacent (an intro, not two unrelated blocks that happen to follow each other).
   .fb-vis is the optional display:contents visibility wrapper (hide_on) and sits
   between the siblings, so every rule needs the variant that reaches through it. */
.fb-heading:has(+ .fb-text),
.fb-heading:has(+ .fb-vis > .fb-text),
.fb-vis:has(+ .fb-text) > .fb-heading,
.fb-vis:has(+ .fb-vis > .fb-text) > .fb-heading { margin-bottom: .2em; }
:is(.fb-text, .fb-vis:has(> .fb-text)) + :is(.fb-grid, .fb-faq, .fb-stats, .fb-pricing, .fb-booking, .fb-form-wrap),
:is(.fb-text, .fb-vis:has(> .fb-text)) + .fb-vis > :is(.fb-grid, .fb-faq, .fb-stats, .fb-pricing, .fb-booking, .fb-form-wrap) {
  margin-top: 1rem;
}

.fb-image { margin: 0; }
.fb-image img { border-radius: var(--radius); width: 100%; }
.fb-image figcaption { font-size: 14px; color: var(--ink-soft); margin-top: 6px; }

.fb-btn-wrap { margin: 8px 0; }
.fb-btn {
  display: inline-block; text-decoration: none; font-weight: 700;
  padding: 12px 22px; border-radius: var(--radius); cursor: pointer; border: 2px solid var(--c-primary);
  /* A <button> inherits none of this from the page — the UA gives it Arial,
     13.33px and line-height:normal, while an <a> inherits all three. The same
     classes therefore rendered two different buttons, and the product card
     stacks exactly that pair ("Mehr erfahren" is a <button>, "Zum Shop" an <a>):
     measured 380x34 in Arial next to 380x41 in the site font. .fb-btn-sm still
     wins on font-size — it stands later and sets its own. */
  font-family: inherit; font-size: inherit; line-height: inherit;
}
/* Trailing chevron — the same shape as everywhere else, as a pseudo-element so the
   builder's contenteditable label never swallows it. */
/* inline-block + margin, NOT a flex gap: the teaser variant of this button is
   display:block (full width, centred), which would beat an inline-flex here and
   leave the ::after as a plain inline box — and an inline box ignores width and
   height, so the chevron would silently have no size at all. */
.fb-btn--arrow::after {
  content: ''; display: inline-block; width: .7em; height: .7em; margin-left: .45em;
  vertical-align: -.06em; background: currentColor;
  -webkit-mask: var(--chev-mask); mask: var(--chev-mask);
}
/* Optional symbol from the content icon set. Same mechanism and the same reason as
   the arrow above: a mask on a pseudo-element, because the <a> is contenteditable
   in the builder and a real <svg> child would be part of what the editor reads
   back. The per-button URL arrives as --fb-btn-ico (see icon_mask_uri()); the
   inline-block is load-bearing for the same reason as the arrow's. */
.fb-btn--ico::before,
.fb-btn--ico-end::after {
  content: ''; display: inline-block; width: 1.05em; height: 1.05em;
  vertical-align: -.19em; background: currentColor;
  -webkit-mask: var(--fb-btn-ico) center / contain no-repeat;
  mask: var(--fb-btn-ico) center / contain no-repeat;
}
.fb-btn--ico::before { margin-right: .45em; }
.fb-btn--ico-end::after { margin-left: .45em; }
/* Filled buttons carry the brand gradient. `border-color: transparent` rather than
   dropping the border: the 2px stay in the box so nothing moves, and .fb-btn-outline
   next to it keeps exactly the same size.
   **`background-origin: border-box` is load-bearing, not tidiness.** The default is
   `padding-box`: the gradient is laid out in the padding box and the border area is
   then filled by REPEATING it — so a 2px ring appeared in the wrong colour, dark
   blue along the turquoise edge and turquoise along the dark one. It reads as a
   stray outline, and it is precisely what a transparent border was supposed to
   avoid. It has to stand AFTER the `background` shorthand, which resets the origin.
   The outline style keeps its transparent fill — that IS the style — and takes the
   gradient when it fills on hover. A `custom` button is exempt by construction: it
   writes background/border/colour as INLINE styles from the colour the customer
   picked (blocks/button.php), and inline beats any rule here. Overriding that with
   the brand gradient would throw their choice away. */
.fb-btn-primary { background: var(--grad-brand); background-origin: border-box; color: #fff; border-color: transparent; }
.fb-btn-primary:hover { filter: brightness(.95); }
.fb-btn-outline { background: transparent; color: var(--c-primary); }
.fb-btn-outline:hover { background: var(--grad-brand); background-origin: border-box; color: #fff; border-color: transparent; }
.fb-btn-custom:hover { filter: brightness(.95); } /* solid custom colour set inline */
/* smaller button size (like the gallery tab switches) */
.fb-btn-sm { padding: 7px 18px; font-size: .9rem; font-weight: 600; }
/* "Mehr erfahren" on a teaser card, and the phone/mail links of the contact and
   team blocks. It reads as a link, so it takes the link colour — the same fallback
   the global rule uses. Note the two cases differ in HOW they get there: an <a
   class="fb-link"> is already covered by `.findd-site a:not(.fb-btn)` (0-2-1 beats
   0-1-0), while the teaser label is a <span> inside the card's <a> and only gets a
   colour from this rule. */
.fb-link { color: var(--c-link, var(--c-primary)); font-weight: 600; text-decoration: none; }
.fb-link:hover { text-decoration: underline; }
/* "Mehr erfahren"-style read-more links get a trailing chevron (not arrows). Not
   on the contact/team blocks, where .fb-link is a phone/email link. */
/* Was the single character "›" — rendered in the tenant's own font, so its shape
   changed from site to site. Now the product's chevron, in the link's colour. */
.fb-link::after,
.fb-feat-more::after {
  content: ''; display: inline-block; width: .62em; height: .62em; margin-left: .15em;
  background: currentColor; vertical-align: -.02em;
  -webkit-mask: var(--chev-mask); mask: var(--chev-mask);
}
.fb-contact .fb-link::after,
.fb-person .fb-link::after { content: none; }
.fb-btn-ghost { background: transparent; border-color: currentColor; color: inherit; }
.fb-btn-ghost:hover { background: rgba(255, 255, 255, .14); }

/* --- conversion: FAQ accordion ------------------------------------------- */
.fb-faq { max-width: 760px; margin: 24px auto; padding: 0 var(--gutter); }
.fb-faq-title { font-size: 1.5rem; margin: 0 0 16px; text-align: center; }
.fb-faq-item { border-bottom: 1px solid var(--line); }
.fb-faq-q {
  font-weight: 700; padding: 16px 30px 16px 0; cursor: pointer; position: relative;
  list-style: none;
}
.fb-faq-q::-webkit-details-marker { display: none; }
.fb-faq-q::after {
  content: "+"; position: absolute; right: 2px; top: 50%; transform: translateY(-50%);
  font-size: 22px; font-weight: 400; color: var(--c-primary);
}
details.fb-faq-item[open] > .fb-faq-q::after { content: "\2212"; } /* minus */
.fb-faq-a { padding: 0 0 18px; color: var(--ink-soft); }
.fb-faq-a > :first-child { margin-top: 0; }
.fb-faq-a > :last-child { margin-bottom: 0; }
/* edit mode: no toggle marker, both fields always visible */
.fb-faq-item--edit .fb-faq-q { cursor: text; }
.fb-faq-item--edit .fb-faq-q::after { display: none; }

/* --- conversion: stats / USP bar ----------------------------------------- */
.fb-stats { max-width: var(--maxw); margin: 24px auto; padding: 0 var(--gutter); }
.fb-stats-title { font-size: 1.5rem; margin: 0 0 24px; text-align: center; }
.fb-stats-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 24px; text-align: center; }
.fb-stat-ring { position: relative; width: clamp(120px, 18vmin, 168px); aspect-ratio: 1; margin: 0 auto; display: grid; place-items: center; }
.fb-stat-ring-svg { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); }
.fb-stat-ring-bg { fill: none; stroke: var(--line); stroke-width: 3.3; }
.fb-stat-ring-bar { fill: none; stroke: var(--c-primary); stroke-width: 3.3; stroke-linecap: round; stroke-dasharray: 276; stroke-dashoffset: 276; transition: stroke-dashoffset 1.2s ease; }
.fb-stats.is-visible .fb-stat-ring-bar { stroke-dashoffset: 0; }
.fb-stat-value { position: relative; font-size: clamp(24px, 4vmin, 40px); font-weight: 800; color: var(--c-primary); line-height: 1.1; }
.fb-stat-label { color: var(--ink-soft); margin-top: 10px; }

/* --- conversion: pricing ------------------------------------------------- */
.fb-pricing { max-width: var(--maxw); margin: 24px auto; padding: 0 var(--gutter); }
.fb-pricing-title { font-size: 1.5rem; margin: 0 0 28px; text-align: center; }
.fb-pricing-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 20px; align-items: start; }
.fb-price { position: relative; display: flex; flex-direction: column; gap: 14px; border: 1px solid var(--line); border-radius: var(--radius); padding: 28px 22px; background: var(--surface); }
.fb-price--featured { border-color: var(--c-primary); box-shadow: 0 8px 30px rgba(0, 0, 0, .08); }
.fb-price-badge { position: absolute; top: -12px; left: 50%; transform: translateX(-50%); background: var(--c-primary); color: #fff; font-size: 12px; font-weight: 700; padding: 4px 12px; border-radius: 999px; white-space: nowrap; }
.fb-price-name { font-weight: 700; font-size: 18px; }
.fb-price-amount { display: flex; align-items: baseline; gap: 4px; flex-wrap: wrap; }
.fb-price-value { font-size: clamp(28px, 4vw, 40px); font-weight: 800; color: var(--c-primary); line-height: 1; }
.fb-price-period { color: var(--ink-soft); font-size: 15px; }
.fb-price-features { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.fb-price-features li { display: flex; align-items: flex-start; gap: 8px; color: var(--ink-soft); }
.fb-price-features li .fb-icon { width: 18px; height: 18px; flex: none; margin-top: 2px; color: var(--c-primary); }
.fb-price-hint { font-style: italic; opacity: .7; }
.fb-price-btn { margin-top: auto; align-self: stretch; text-align: center; }

/* --- conversion: booking (click-to-load) --------------------------------- */
.fb-booking { max-width: var(--maxw); margin: 24px auto; padding: 0 var(--gutter); }
.fb-booking-title { font-size: 1.5rem; margin: 0 0 10px; }
.fb-booking-intro { color: var(--ink-soft); margin: 0 0 18px; }
.fb-booking-ctl { border: 1px solid var(--line); border-radius: var(--radius); padding: 32px 24px; text-align: center; background: var(--muted-bg); }
.fb-booking-note { color: var(--ink-soft); font-size: 14px; margin: 0 0 18px; }
.fb-booking-placeholder { border: 1px dashed var(--line); border-radius: var(--radius); padding: 28px; text-align: center; color: var(--ink-soft); }
.fb-booking-hint { margin: 12px 0 0; font-size: 13px; color: var(--ink-soft); }
.fb-booking iframe { border-radius: var(--radius); display: block; }

/* --- form ---------------------------------------------------------------- */
/* Full content width, like every other block that is a thing on the page rather
   than prose (.fb-stats/.fb-pricing/.fb-booking/.fb-catalog all cap at --maxw).
   It used to stop at 640px, and under a full-width block the narrow centred box
   read as a mistake. Inside a section or a grid column --maxw is wider than the
   column, so it simply fills it. The FAQ keeps ITS 760px on purpose: that one is
   reading text, where a short measure is the point. */
.fb-form-wrap { max-width: var(--maxw); margin: 24px auto; padding: 0 var(--gutter); }
.fb-form-title { font-size: 1.5rem; margin: 0 0 10px; }
.fb-form-intro { color: var(--ink-soft); margin: 0 0 18px; }
/* Two-column capable: a 2-track grid where fields span the full width by default;
   fields marked "Halbe Breite" (.fb-form-half) take a single column so two sit
   side by side. Hidden inputs must not occupy a grid cell. */
.fb-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 16px; align-items: start; }
.fb-form > * { grid-column: 1 / -1; }
.fb-form .fb-form-half { grid-column: span 1; }
.fb-form input[type="hidden"] { display: none; }
@media (max-width: 560px) { .fb-form .fb-form-half { grid-column: 1 / -1; } }
.fb-form-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; } /* honeypot */
.fb-form-field { display: flex; flex-direction: column; gap: 6px; }
.fb-form-label { font-weight: 600; font-size: 14px; }
/* Every text-ish control the form block can render — the list is by type, so a
   new field type without its entry here shows up in the browser's raw look next
   to styled neighbours (that is what happened when date/datetime arrived). */
.fb-form input[type="text"], .fb-form input[type="email"], .fb-form input[type="tel"],
.fb-form input[type="date"], .fb-form input[type="datetime-local"],
.fb-form textarea, .fb-form select {
  width: 100%; padding: 11px 12px; border: 1px solid var(--line); border-radius: var(--radius);
  font: inherit; background: var(--surface); color: var(--ink);
}
/* Same padding, same font, same border — and still 2px taller than the text field
   beside it (measured 51.59 vs 49.59 in Chromium): the date field's internal
   segment row does not sit on the inherited line box. Half-width fields stand
   side by side, so the mismatch is visible the moment a form has a date next to
   a name. Pinning the box to ONE line box plus the padding and border makes it
   exactly the text field's height (49.59, verified). `1lh` is the input's own
   line-height, so it follows the tenant's font size; a browser that does not know
   the unit drops the declaration and simply keeps today's auto height. */
.fb-form input[type="date"], .fb-form input[type="datetime-local"] { height: calc(1lh + 24px); }
/* A date field paints its calendar glyph and its picker panel from `color-scheme`,
   not from our colours: on the dark theme the black-on-dark glyph sat invisible in
   the corner. Switched on the CLASS like the logos below — findd has its own theme
   toggle, so prefers-color-scheme would be the wrong signal. */
html.theme-dark .fb-form input[type="date"],
html.theme-dark .fb-form input[type="datetime-local"] { color-scheme: dark; }
.fb-form input:focus, .fb-form textarea:focus, .fb-form select:focus { outline: 2px solid var(--c-primary); outline-offset: 1px; }
/* The gap is the distance from the switch to its label text — and to nothing else.
   Every text run and every inline element inside a flex container is its own item,
   so a label whose text WRAPS a link becomes three of them and the gap shows up
   before and after the link as well (the consent line read "( Datenschutz )").
   Any label with markup in it therefore keeps its text in one <span>. */
.fb-form-check { display: flex; align-items: flex-start; gap: 10px; font-size: 14px; color: var(--ink-soft); }
/* Frontend form checkboxes render as iOS-style slide switches, matching the
   backend `.switch-track` (34×19, 6px radius, knob glides), tinted with the
   tenant's primary when on. Pure CSS on the input via appearance:none — no
   extra markup, so every checkbox field + the consent box become switches. */
.fb-form-check input[type="checkbox"] {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  position: relative; flex-shrink: 0; margin: 1px 0 0; cursor: pointer;
  width: 34px; height: 19px; border-radius: 999px; background: var(--line);
  transition: background .2s;
}
.fb-form-check input[type="checkbox"]::before {
  content: ""; position: absolute; top: 3px; left: 3px; width: 13px; height: 13px;
  border-radius: 50%; background: var(--ink-soft); transition: transform .2s, background .2s;
}
.fb-form-check input[type="checkbox"]:checked { background: var(--c-primary); }
.fb-form-check input[type="checkbox"]:checked::before { transform: translateX(15px); background: #fff; }
.fb-form-check input[type="checkbox"]:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 2px; }
/* Remaining frontend inputs (radios, any stray checkbox) adopt the tenant's
   primary as their checked colour. */
.findd-site input[type="radio"] { accent-color: var(--c-primary); width: 17px; height: 17px; }
.fb-form-consent a { font-weight: 600; }
.fb-form .fb-btn { align-self: start; justify-self: start; }
.fb-form-msg { padding: 12px 14px; border-radius: var(--radius); font-weight: 600; }
.fb-form-msg.is-ok { background: #ecfdf5; color: #065f46; border: 1px solid #10b981; }
.fb-form-msg.is-err { background: #fef2f2; color: #991b1b; border: 1px solid #ef4444; }
.fb-form-result { max-width: 640px; margin: 60px auto; padding: 0 var(--gutter); text-align: center; }
.fb-form-result h1 { margin: 0 0 12px; }
.fb-form-result p { color: var(--ink-soft); margin: 0 0 20px; }

/* --- small parts: spacer / divider / socials ----------------------------- */
.fb-spacer { width: 100%; }
.fb-divider { max-width: var(--maxw); margin-inline: auto; padding: 0 var(--gutter); }
.fb-divider-s { margin-block: 10px; }
.fb-divider-m { margin-block: 26px; }
.fb-divider-l { margin-block: 48px; }
.fb-hr { border: 0; border-top: 1px solid var(--line); margin: 0; }
.fb-hr-dashed { border-top-style: dashed; }
.fb-hr-dots { border-top-style: dotted; border-top-width: 2px; }
.fb-socials { display: flex; flex-wrap: wrap; gap: 12px; max-width: var(--maxw); margin: 16px auto; padding: 0 var(--gutter); }
.fb-socials.fb-align-center { justify-content: center; }
.fb-socials.fb-align-right { justify-content: flex-end; }
/* Selector note: the colours need `a.fb-social` (0-2-1) to reach the global link
   colour `.findd-site a:not(.fb-btn)` (0-2-1) — same weight, and this rule comes
   later, so it wins. As a bare `.fb-social` (0-1-0) both colours lost: the icon
   was primary-coloured at rest AND on hover, i.e. primary on the primary-coloured
   hover background = invisible (measured). Fifth time this trap has bitten. */
.fb-social { width: 42px; height: 42px; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; background: var(--muted-bg); text-decoration: none; transition: background .15s, color .15s; }
.findd-site a.fb-social { color: var(--ink); }
.findd-site a.fb-social:hover { background: var(--c-primary); color: #fff; }
.fb-social .fb-icon { width: 20px; height: 20px; }

/* --- teaser --------------------------------------------------------------- */
/* content icon (self-hosted inline SVG, inherits text color) */
.fb-icon { width: 1em; height: 1em; display: inline-block; vertical-align: -0.125em; }

.fb-teaser { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--surface); }
/* Stretch to the row height ONLY when the card is alone in its column — that is
   what makes cards in one row line up. With several teasers stacked in one
   column, a blanket height:100% makes EACH of them as tall as the whole column:
   three cards then need 300% of the column, overflow the section and overlap the
   next one. Measured with a grid_3cols holding 3+3 teasers.
   The second selector covers the .fb-vis wrapper that a set `hide_on` inserts
   between column and card (display:contents — it is a real node in the tree). */
.fb-col > .fb-teaser:only-child,
.fb-col > .fb-vis:only-child > .fb-teaser { height: 100%; }
.fb-teaser-img { position: relative; }
.fb-teaser-img img { width: 100%; aspect-ratio: 16/10; object-fit: cover; display: block; }
.fb-teaser-body { padding: 18px; }
.fb-teaser-body h3 { margin: 0 0 8px; }
.fb-teaser-body p { margin: 0 0 14px; color: var(--ink-soft); }
/* Clamp the teaser description on the live site; the editor's <p> is
   contenteditable, so it stays fully visible while editing. */
.fb-teaser-body p:not([contenteditable]) { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.fb-teaser-btn { display: block; width: 100%; text-align: center; }
.fb-teaser-ico { color: var(--c-primary); line-height: 1; font-size: 40px; }
.fb-teaser-ico-ph { font-size: 13px; color: var(--ink-soft); border: 1px dashed var(--line); border-radius: var(--radius); padding: 6px 10px; display: inline-block; }

/* teaser layout: icon on top, centered feature card */
.fb-tl-icon { text-align: center; }
.fb-tl-icon .fb-teaser-ico { padding: 24px 18px 0; }

/* teaser layout: icon left of the text (media object) */
.fb-tl-icon-left { display: flex; align-items: flex-start; gap: 14px; padding: 18px; }
.fb-tl-icon-left .fb-teaser-ico { font-size: 30px; flex: none; }
.fb-tl-icon-left .fb-teaser-body { padding: 0; }

/* teaser layout: icon badge on the image corner */
.fb-teaser-badge {
  position: absolute; top: 12px; left: 12px; width: 44px; height: 44px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--c-primary); color: #fff; font-size: 22px;
}

/* teaser layout: banner — the photo fills the box and a dark, blurred banderole
   carrying headline, text and button sits centred on it. Grid stacking rather than
   an absolutely positioned body: both
   children share ONE cell, so the taller of (photo at its own ratio, text) sets the
   height. A long text can therefore never be cut off, and a tall photo is never
   squeezed — which an `inset: 0` body would do one way or the other. */
/* The height is the SAME ONE the big Events/Jobs/News box uses (--fb-banner-h on
   :root, see .fb-feat-big): both are wide stages inside the content column, and a
   page carrying both must not look ragged. The photo therefore does not set the
   height — it covers whatever the stage is, exactly like .fb-feat-big does.
   A dark base, not the card surface, for a banner graphic with a TRANSPARENT
   background: white text on a white card is invisible. Fixed value, not
   var(--ink): that one flips to white in dark mode, the one direction this must
   never go. */
.fb-tl-overlay { position: relative; display: grid; min-height: var(--fb-banner-h); background: #1b1f24; }
.fb-tl-overlay > .fb-teaser-img,
.fb-tl-overlay > .fb-teaser-body { grid-area: 1 / 1; }
/* The photo is taken out of flow, so it contributes NO height of its own: the box
   is min-height tall, and only a long text grows it — which is the one thing
   .fb-feat-big cannot do (its body is absolute, so text there is clamped away).
   That also settles the two cases a ratio-driven height got wrong: a portrait
   photo made the box 2137px tall at 1425px wide, and a capped photo left a strip
   under a long text (both measured before this was simplified).
   `.findd-site` prefix for the documented reason: `.findd-site img { height: auto }`
   (0-1-1) would otherwise win and the image resolves to height 0. */
.fb-tl-overlay > .fb-teaser-img { position: relative; }
.findd-site .fb-tl-overlay .fb-teaser-img img {
  position: absolute; inset: 0; width: 100%; height: 100%; aspect-ratio: auto; object-fit: cover;
}
/* The banderole. Instead of covering the whole stage, the body shrink-wraps its
   content and sits centred in the cell — `place-self` does both axes at once, and
   because the grid item is no longer stretched it takes the width of its text.
   Everything the customer typed (headline, text, button) already lives in this one
   element, so the button lands inside the band BY CONSTRUCTION; no second rule has
   to place it there and none can drift out of step.
   The dark tint plus the blur is what makes white text readable on ANY photo,
   which the whole-photo scrim (fb-ov-*) can only do by muddying the picture: this
   dims just the ground under the words. */
.fb-tl-overlay .fb-teaser-body {
  place-self: center;
  max-width: min(100% - 48px, 620px);
  position: relative; z-index: 2; display: flex; flex-direction: column;
  align-items: center; text-align: center; padding: 24px 28px; color: #fff;
  border-radius: var(--radius);
  background: rgba(0, 0, 0, .3);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
/* Without the blur, .3 alone is too see-through to rescue white text on a bright
   photo — the one thing the band exists for. Old browsers get a solid-enough tint
   instead. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .fb-tl-overlay .fb-teaser-body { background: rgba(0, 0, 0, .5); }
}
/* A banner with a photo but no words at all would otherwise render as a small
   blurred rectangle floating in the middle of it. `:empty` cannot be used — the
   template leaves whitespace inside the div, which counts as content. */
.fb-tl-overlay .fb-teaser-body:not(:has(*)) { display: none; }
/* Keeps the band's padding symmetric: the paragraph's 14px bottom margin would
   otherwise sit inside it whenever no button follows. */
.fb-tl-overlay .fb-teaser-body > :last-child { margin-bottom: 0; }
/* Editor only: with no photo yet, the body lies ON the "Bild" placeholder and
   swallowed every click on it — the slot looked pickable and was not (measured
   with elementFromPoint, not with geometry). Let clicks pass through the body's
   dead space to the slot underneath, while its own children keep theirs. Scoped
   via :has() to the empty state: with a photo, a click next to the headline must
   keep selecting the block rather than opening the media dialog. */
.fb-tl-overlay:has(.fb-img-empty) .fb-teaser-body { pointer-events: none; }
.fb-tl-overlay:has(.fb-img-empty) .fb-teaser-body > * { pointer-events: auto; }
/* …and the slot takes the dark look the finished banner has, instead of the light
   default: the body text is white, so on the light placeholder a headline typed
   before the photo was picked would be white on near-white. */
.fb-tl-overlay .fb-img-empty {
  height: 100%; background: transparent; border-color: rgba(255, 255, 255, .3); color: rgba(255, 255, 255, .7);
}
.fb-tl-overlay .fb-teaser-body h3 { color: #fff; font-size: clamp(1.25rem, 2.2vw, 1.75rem); max-width: 30ch; }
.fb-tl-overlay .fb-teaser-body p { color: rgba(255, 255, 255, .92); max-width: 46ch; }
/* Inside the band, centred under the text — width:auto undoes the full-width
   button of the card layouts, and display:inline-block (not inline-flex) keeps the
   ::after chevron sizing documented at .fb-btn--arrow. */
.fb-tl-overlay .fb-teaser-btn { margin-top: 4px; align-self: center; width: auto; display: inline-block; }
@media (max-width: 700px) {
  /* Narrower box: give the band more of it and less padding, so the text does not
     wrap after two words. */
  .fb-tl-overlay .fb-teaser-body { padding: 18px 20px; max-width: min(100% - 28px, 620px); }
  /* Full width is the bigger tap target on a phone. */
  .fb-tl-overlay .fb-teaser-btn { align-self: stretch; width: 100%; text-align: center; }
}

/* --- company elements ----------------------------------------------------- */

/* hero slider (enhanced by findd-public.js; stacked + readable without JS) */
.fb-slider { position: relative; }
.fb-slide {
  /* Side padding lives on .fb-slide-wrap (inside the content-width cap), so placed
     text sits on the same line as the rest of the page — not one gutter off. */
  /* min-height, not a definite height. Phase 28 made it definite for a good reason
     (as a minimum the slide silently grew with its content — 368px where 300 were
     dragged — and the drag handle stopped following the pointer), and that reason
     is knowingly TRADED AWAY here: a slide now holds blocks the customer drags in,
     so a fixed height means content either spills over the next block or gets cut
     off. The dragged value is the floor; a content-heavy slide grows past it and
     the handle then reads the floor, not the rendered height. Do not "fix" this
     back to `height` without re-reading docs/ref/blocks-firma.md. */
  /* --hero-offset is the strip an overlaying header covers (0 for every slider but
     a hero one) — the box grows by it and pads it straight back, so the VISIBLE
     slide stays exactly the height that was dragged. */
  position: relative;
  min-height: calc(var(--fb-slider-h, 460px) + var(--hero-offset, 0px));
  padding: calc(var(--fb-slide-pad, 48px) + var(--hero-offset, 0px)) 0
           calc(var(--fb-slide-pad, 48px) + var(--fb-sy-pad, 0px));
  /* Vertical placement of the text. The value is INHERITED from the slider and a
     slide can override it inline — see the variable note in app/blocks/slider.php. */
  display: flex; align-items: var(--fb-sy, center); justify-content: center; text-align: center;
  color: #fff; background: var(--muted-bg) center / cover no-repeat;
}
/* The scrim over the slide photo ("Überlagerung"). Driven by an inherited variable
   rather than an .fb-ov-* class, because the setting lives on the SLIDER and a class
   cannot inherit; 0 = off, so the pseudo-element is always there and costs nothing.
   z-index 1 for the documented reason — image 0 · scrim 1 · content 2; at 0 it ties
   with the <img> and loses the tie on DOM order (see the .fb-ov-* note above). */
.fb-slide::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: #000; opacity: var(--fb-ov-op, 0);
}
/* The slide photo. Was a CSS background — see slider.php for why it is an <img> now.
   z-index 0 puts it under the overlay (1, shared .fb-ov-* rule) and the text (2). */
/* Selector note: `.findd-site` prefix required. The generic `.findd-site img
   { height: auto }` is 0-1-1 and beats a bare `.fb-slide-img` (0-1-0) — the image
   then resolves to height 0 and the hero renders empty. Same trap as the footer
   logo; measured, not guessed. */
.findd-site .fb-slide-img {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; object-fit: cover; object-position: center;
}
/* Which part of the photo survives the crop (per slide, "Bildausschnitt
   senkrecht"). Two classes, no rule for the centre — that is what the base rule
   above already does. Specificity is 0-3-0 against its 0-2-0, so it wins; the
   `.findd-site` prefix is not optional here either (see the note above). */
.findd-site .fb-slide-img.fb-iy-top { object-position: center top; }
.findd-site .fb-slide-img.fb-iy-bottom { object-position: center bottom; }
/* Text placement: --fb-sy aligns the content vertically inside the slide (on
   .fb-slide above), --fb-sx the text box horizontally inside the wrapper. The
   wrapper is capped at the site's content width, so a left/right placed text lines
   up with the rest of the page instead of sticking to the raw viewport edge on wide
   screens. Both are INHERITED from the slider and overridable inline per slide —
   the reasoning is in app/blocks/slider.php. */
.fb-slide-wrap {
  position: relative; z-index: 2; width: 100%; max-width: var(--maxw); margin: 0 auto;
  padding: 0 var(--gutter); display: flex;
  justify-content: var(--fb-sx, center);
}
.fb-slide-inner {
  position: relative; max-width: min(var(--fb-slide-w, 760px), 100%);
  /* An explicit "Textausrichtung" wins; "Automatisch" declares no --fb-ta, so the
     chain falls through to --fb-ta-auto, which the slider AND an overriding slide
     write next to their --fb-sx. That two-step is what makes "follow the placement"
     work per slide: resolving it on the slider would freeze the slider's own value
     into the inherited one, and a slide's override could never change it. */
  text-align: var(--fb-ta, var(--fb-ta-auto, center));
  /* Everything in the panel takes the panel's ink, not just the headline and the
     text: a slide holds arbitrary blocks now, and a light panel would otherwise
     leave a stats widget or a list white on white. --fb-box-ink is set by the
     slider only when the panel is light (or a light custom colour) — the default
     is the white a photo needs. */
  color: var(--fb-box-ink, #fff);
  /* Coloured panel behind the slide text ("Box hinter dem Text"): the local answer
     to a busy photo, where darkening the WHOLE image (the overlay control) would be
     too much. --fb-box-pad is what switches it on — with no panel there must be no
     padding either, so the whole control is variable-driven and needs no class. */
  padding: var(--fb-box-pad, 0); border-radius: var(--radius);
}
/* The colour is painted by a ::before rather than on the box itself, because the
   opacity control has to dim the PANEL only — as `opacity` on the box it would fade
   the headline along with it. */
.fb-slide-inner::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: var(--fb-box-bg, transparent); opacity: var(--fb-box-op, 0);
}
/* Lift the content above the panel — ::before is painted after the box's own
   background, so without this the text would sit underneath it. */
.fb-slide-inner > * { position: relative; }
/* Hero type scale for the blocks the SLIDE ITSELF holds. Both scale with the
   slider's "Schriftgröße" (--fb-slide-scale), so headline and text keep their ratio
   and the fluid clamp() keeps working on small screens; the tight leading is the
   display-headline leading every other one has (the body 1.6 tore a two-line hero
   title apart, 80px between the lines at the default size).
   DIRECT children, not descendants: a section or a grid dropped into a slide is its
   own layout context and its headings must keep their block size. A descendant
   selector would reach into it, and resetting --fb-slide-scale there cannot help —
   the hero size comes from the clamp, not from the variable (CLAUDE.md Falle 14).
   Both wrapper forms are spelled out inside the :is(): .fb-vis is the
   display:contents visibility wrapper, and .fb-eb > .fb-eb-inner is the EDITOR's
   block wrapper — canvas and public page are one document (Falle 13), so a rule
   that forgets it works live and not where the customer is looking. */
:is(.fb-slide-inner, .fb-slide-inner > .fb-eb > .fb-eb-inner) > :is(.fb-heading, .fb-vis > .fb-heading) {
  font-size: calc(clamp(28px, 5vw, 50px) * var(--fb-slide-scale, 1));
  line-height: 1.15; margin: 0 0 12px;
}
:is(.fb-slide-inner, .fb-slide-inner > .fb-eb > .fb-eb-inner) > :is(.fb-text, .fb-vis > .fb-text) {
  font-size: calc(18px * var(--fb-slide-scale, 1));
  line-height: 1.45; margin: 0 0 18px;
}
/* An image in a slide takes the width the slider allows, instead of its own pixel
   size. WHY IT NEEDS A RULE AT ALL: .fb-slide-inner is a flex item — that is how
   the placement control moves it left/centre/right — so it is shrink-to-fit, and a
   shrink-to-fit box is as wide as its content asks for. An image asks for its
   INTRINSIC width, and the `width: 100%` on .fb-image img then resolves against
   exactly that. Measured: a 600px photo produced a 600px column, and switching
   "Inhaltsbreite" from Normal to Breit changed nothing, because the cap was never
   reached — while adding a paragraph below the image jumped the column to the full
   1040 and took the image with it. That is the bug report, exactly.
   A percentage cannot fix it: percentages contribute nothing to intrinsic sizing.
   A LENGTH does, and --fb-slide-w is one (see Blocks::SLIDE_WIDTHS — that is why
   'full' is var(--maxw) and not 100%). max-width keeps it inside a narrower frame.
   DIRECT children only, like the type scale above: an image inside a grid or a
   section dropped into the slide belongs to that layout, not to this one. */
:is(.fb-slide-inner, .fb-slide-inner > .fb-eb > .fb-eb-inner) > :is(.fb-image, .fb-vis > .fb-image) {
  width: var(--fb-slide-w, 760px); max-width: 100%;
}
/* End the inheritance where it should end (Falle 14) — but for the property that
   ACTUALLY leaks. Resetting the slide's custom properties here would be dead code:
   --fb-slide-scale, the --fb-box- group and --fb-ov-op are only read by the
   direct-child rules above and by .fb-slide/.fb-slide-inner themselves, and a slide
   cannot contain another slide (the builder's drop restriction). Measured, not
   assumed — the reset was written first and then removed.
   What does reach through is the ordinary inherited `color: #fff` from .fb-slide: a
   SECTION dropped into a slide brings its own background but took the slide's white
   text with it — measured as white on the near-white muted ground, unreadable.
   :where() contributes ZERO specificity on purpose. That is still a declaration, so
   it beats the inherited white; but every rule that deliberately sets a colour on a
   section (.fb-bg-primary, .fb-bg-gradient, .fb-text-light/-dark, all 0-1-0) still
   beats IT — a coloured section inside a slide keeps its own white text. */
:where(.fb-slide-inner .fb-section) { color: var(--ink); }
/* One slide at a time — as a CROSS-FADE, which is why the slides share a single
   grid cell instead of being switched with display:none. Two reasons for the grid:
   a fade needs both boxes on screen at once (display:none has nothing to fade),
   and stacking fixes the slider's height at the TALLEST slide, so the page below
   no longer jumps every time a shorter slide comes up.
   visibility, not opacity alone: a fully transparent slide still catches clicks and
   is still read out by a screen reader. The 0s delay holds it visible until the
   fade has finished, and the active slide is lifted so its buttons stay clickable
   while the outgoing one is still fading. */
.fb-slider--ready { display: grid; }
.fb-slider--ready > :is(.fb-slide, .fb-vis > .fb-slide) {
  grid-area: 1 / 1;
  opacity: 0; visibility: hidden;
}
.fb-slider--ready > :is(.fb-slide, .fb-vis > .fb-slide).is-active {
  opacity: 1; visibility: visible; z-index: 1;
}
/* The fade itself hangs on a SECOND class, which initSlider adds a frame after the
   first slide is set. Without that split the very first state change is animated
   too: before JS runs every slide is fully opaque (the no-JS fallback stacks them),
   so switching --ready on made all of them fade OUT over 600ms on every page load —
   measured at 0.15 opacity a moment after load, i.e. a visible pile-up that then
   dissolved. The first state has to be painted before the transition exists. */
.fb-slider--anim > :is(.fb-slide, .fb-vis > .fb-slide) {
  transition: opacity .6s ease, visibility 0s linear .6s;
}
.fb-slider--anim > :is(.fb-slide, .fb-vis > .fb-slide).is-active {
  transition: opacity .6s ease, visibility 0s;
}
/* Reduced motion gets the hard cut back — and initSlider stops advancing at all. */
@media (prefers-reduced-motion: reduce) {
  .fb-slider--anim > :is(.fb-slide, .fb-vis > .fb-slide),
  .fb-slider--anim > :is(.fb-slide, .fb-vis > .fb-slide).is-active { transition: none; }
}
/* Arrows and dots exist only once the slider is interactive (≥ 2 slides, see
   initSlider) — keep placed text clear of them: side room where the content
   column no longer shields the text from the arrows, bottom room when it sits low. */
@media (max-width: 1100px) { .fb-slider--ready .fb-slide-wrap { padding-left: 52px; padding-right: 52px; } }
.fb-slider-arrow {
  /* Centre on the VISIBLE slide: on a hero the box includes the strip behind the
     header, so half of that strip has to come back (0 everywhere else). */
  position: absolute; top: calc(50% + var(--hero-offset, 0px) / 2); transform: translateY(-50%); z-index: 3;
  border: 0; background: none; padding: 0; cursor: pointer;
  color: #fff; line-height: 1; opacity: .9;
  /* drop-shadow, not text-shadow: the chevron is a masked box now, not a glyph.
     Same job — keep it legible over a photo. */
  filter: drop-shadow(0 1px 8px rgba(0, 0, 0, .55));
}
/* A text glyph filled maybe 40% of its em; a mask fills its box, so the size is
   set on the chevron itself instead of via font-size. */
.fb-slider-arrow .chev { width: 30px; height: 30px; }
.fb-slider-prev { left: 8px; } .fb-slider-next { right: 8px; }
.fb-slider-arrow:hover { opacity: 1; }
.fb-slider-dots { position: absolute; bottom: 14px; left: 0; right: 0; z-index: 3; display: flex; gap: 8px; justify-content: center; }
.fb-slider-dot { width: 10px; height: 10px; padding: 0; border: 0; border-radius: 50%; cursor: pointer; background: rgba(255,255,255,.5); }
.fb-slider-dot.is-active { background: #fff; }

.fb-gallery, .fb-team, .fb-testimonials, .fb-partners, .fb-video, .fb-contact, .fb-opening, .fb-map { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--gutter); }

/* legal system pages (Impressum / Datenschutz) */
.legal-page { max-width: 760px; margin: 0 auto; padding: 3rem 0; }
.legal-page h1 { margin-bottom: 1.5rem; }
.legal-page h2 { margin-top: 2rem; margin-bottom: .5rem; font-size: 1.15rem; }
.legal-page p { margin-bottom: 1rem; line-height: 1.6; }
/* Funding logos at the foot of the imprint (EFRE & co., see Legal::imprintHtml).
   A row that wraps, because a programme can prescribe two or three emblems.
   `max-height` caps them to one common height so a wide EU flag and a square
   Land emblem read as a set; `width:auto` keeps the aspect, and it must be
   scoped to .findd-site to sit above the generic `.findd-site img` rule (0-1-1)
   — a bare .legal-funding img would be 0-1-1 as well, and then the LATER rule
   would decide, which is a coin toss nobody should have to look up. */
.findd-site .legal-funding { display: flex; flex-wrap: wrap; align-items: center; gap: 18px 26px; margin: 2.5rem 0 0; }
.findd-site .legal-funding img { max-height: 80px; width: auto; max-width: 100%; }
/* "Eingebundene Dienste" — generated from the consent registry, so it is set off
   from the tenant's own text rather than pretending to be part of it. */
.legal-services ul { margin: 0 0 1rem; padding-left: 1.2rem; line-height: 1.7; }
.legal-services li { margin-bottom: .35rem; }
/* category gallery: filter tabs + square-thumbnail grid + lightbox (ported from
   the TYPO3 mask_company_gallery). */
.fb-gallery-tabsnav { position: relative; margin: 0 0 18px; }
.fb-gallery-tabs { list-style: none; display: flex; gap: 6px; margin: 0; padding: 0 34px; overflow-x: auto; scroll-behavior: smooth; scrollbar-width: none; }
.fb-gallery-tabs::-webkit-scrollbar { display: none; }
.fb-gallery-tab { padding: 7px 18px; cursor: pointer; white-space: nowrap; font-size: .9rem; font-weight: 600; border-radius: var(--radius); color: var(--ink-soft); transition: background .2s, color .2s; }
.fb-gallery-tab:hover { color: var(--ink); background: var(--muted-bg); }
.fb-gallery-tab.is-active { color: #fff; background: var(--grad-brand); }
.fb-gallery-arrow { position: absolute; top: 0; bottom: 0; width: 30px; border: 0; padding: 0; cursor: pointer; z-index: 1; line-height: 1; color: var(--ink); background: var(--surface); display: flex; align-items: center; justify-content: center; }
.fb-gallery-arrow .chev { width: 14px; height: 14px; }
.fb-gallery-arrow.prev { left: 0; } .fb-gallery-arrow.next { right: 0; }
.fb-gallery-cat[hidden] { display: none; }
.fb-gallery-cat-title { font-size: 1rem; margin: 0 0 .6rem; color: var(--ink-soft); }
.fb-gallery-grid { list-style: none; display: grid; grid-template-columns: repeat(6, 1fr); gap: 14px; margin: 0; padding: 0; }
.fb-gallery-item { margin: 0; }
.fb-gallery-thumb { display: block; position: relative; overflow: hidden; border-radius: var(--radius); aspect-ratio: 1 / 1; background: var(--muted-bg); cursor: pointer; }
.fb-gallery-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .3s ease; }
.fb-gallery-thumb:hover img { transform: scale(1.08); }
/* "Mehr anzeigen": label, a full underline, then a chevron centered below it.
   The [hidden] rule must win over `display:flex` (author CSS otherwise defeats the
   `hidden` attribute) so the button truly disappears when there's nothing more. */
.fb-gallery-more, .fb-cat-more { display: flex; flex-direction: column; align-items: center; gap: 12px; margin: 28px auto 0; padding: 0; border: 0; cursor: pointer; background: none; color: var(--ink); }
.fb-gallery-more[hidden], .fb-cat-more[hidden] { display: none; }
.fb-gallery-more span, .fb-cat-more span { font: 600 .8rem/1 var(--font-body); text-transform: uppercase; letter-spacing: .12em; padding: 0 28px 12px; border-bottom: 1px solid var(--line); }
/* Was a CSS box rotated 45° — a fourth way of drawing the same shape. */
.fb-gallery-more::after, .fb-cat-more::after {
  content: ''; width: 13px; height: 13px; background: var(--c-primary); transform: rotate(90deg);
  -webkit-mask: var(--chev-mask); mask: var(--chev-mask);
}
.fb-gallery-more:hover span, .fb-cat-more:hover span { border-color: var(--c-primary); }
.fb-gallery-more:hover, .fb-cat-more:hover { color: var(--c-primary); }
@media only screen and (max-width: 1024px) { .fb-gallery-grid { grid-template-columns: repeat(4, 1fr); } }
@media only screen and (max-width: 640px)  { .fb-gallery-grid { grid-template-columns: repeat(2, 1fr); } }

/* lightbox overlay (dependency-free, built in findd-public.js) */
.fb-lightbox { position: fixed; inset: 0; z-index: 9999; display: flex; align-items: center; justify-content: center; background: rgba(8, 12, 16, .92); opacity: 0; transition: opacity .2s ease; }
.fb-lightbox.is-open { opacity: 1; }
.fb-lightbox img { max-width: 92vw; max-height: 84vh; object-fit: contain; border-radius: var(--radius); box-shadow: 0 10px 40px rgba(0, 0, 0, .5); }
.fb-lb-cap { position: absolute; bottom: 20px; left: 0; right: 0; text-align: center; color: #e7ecf2; font-size: .9rem; padding: 0 20px; }
.fb-lb-btn { position: absolute; border: 0; cursor: pointer; color: #fff; background: rgba(255, 255, 255, .12); border-radius: 999px; width: 46px; height: 46px; font-size: 1.5rem; line-height: 1; display: flex; align-items: center; justify-content: center; transition: background .2s; }
.fb-lb-btn:hover { background: rgba(255, 255, 255, .25); }
.fb-lb-btn .chev { width: 20px; height: 20px; }
.fb-lb-close { top: 18px; right: 18px; }
.fb-lb-prev { left: 18px; top: 50%; transform: translateY(-50%); }
.fb-lb-next { right: 18px; top: 50%; transform: translateY(-50%); }
@media only screen and (max-width: 640px) { .fb-lb-prev { left: 8px; } .fb-lb-next { right: 8px; } .fb-lb-btn { width: 40px; height: 40px; } }

/* Product detail lightbox (ported from TYPO3 product grid). Products have no
   detail page, so "Mehr erfahren" opens the fuller content in a modal — it is a
   normal .fb-btn in the card body, so it needs no rules of its own here.
   `position: relative` stays for the sold-out ribbon, which IS positioned. */
.fb-cat-grid > .fb-card { position: relative; }
.fb-prod-info { display: none; }
/* The modal box (reuses the .fb-lightbox overlay). */
.fb-modal-box { background: var(--surface); color: var(--ink); border-radius: var(--radius); max-width: 640px; width: calc(100vw - 40px); max-height: 85vh; overflow: auto; box-shadow: 0 20px 60px rgba(0, 0, 0, .5); }
/* Dialog scrollbars — the product lightbox and the consent dialog are the only
   two surfaces the public site scrolls INSIDE a box (every other overflow here is
   a carousel that hides its bar on purpose). Both are sheets on --surface, so
   they share one rule: styling only the one that happens to be long enough today
   is how the two drift apart unseen.
   Written TWICE, and the order matters. The two standard properties are what
   Firefox understands, so they come first and are the safe fallback for anything
   that cannot parse the @supports below. But a NON-auto standard value makes
   Chromium (>= 121) drop the ::-webkit-scrollbar rules and draw the platform bar
   instead — measured side by side: that bar keeps its arrow button above the
   thumb, i.e. exactly the system look this is supposed to lose. So where the
   pseudo-elements exist, the standard pair is handed back to `auto` and they take
   over. Browsers without selector() fall through to the standard pair — a plain
   recoloured bar, never an unstyled one.
   The 3px transparent border + background-clip is what makes the thumb a slim
   pill inside a 10px gutter instead of a slab filling it — and it has to be
   repeated in :hover, because the `background` shorthand resets background-clip. */
.fb-modal-box,
.fb-consent-dialog { scrollbar-width: thin; scrollbar-color: var(--scroll-thumb) transparent; }
@supports selector(::-webkit-scrollbar) {
  .fb-modal-box,
  .fb-consent-dialog { scrollbar-width: auto; scrollbar-color: auto; }
}
.fb-modal-box::-webkit-scrollbar,
.fb-consent-dialog::-webkit-scrollbar { width: 10px; height: 10px; }
.fb-modal-box::-webkit-scrollbar-track,
.fb-consent-dialog::-webkit-scrollbar-track { background: transparent; }
.fb-modal-box::-webkit-scrollbar-thumb,
.fb-consent-dialog::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb); border-radius: 999px; border: 3px solid transparent; background-clip: padding-box; }
.fb-modal-box::-webkit-scrollbar-thumb:hover,
.fb-consent-dialog::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); background-clip: padding-box; }
.fb-prod-info-img { display: block; width: 100%; max-height: 320px; object-fit: cover; }
.fb-prod-info-thumbs { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 24px 0; }
.fb-prod-thumb { width: 64px; height: 48px; padding: 0; border: 2px solid transparent; border-radius: var(--radius); overflow: hidden; cursor: pointer; background: none; }
.fb-prod-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fb-prod-thumb.is-active { border-color: var(--c-primary); }
.fb-prod-info-body { padding: 22px 24px 24px; }
.fb-prod-info-body h3 { margin: 0 0 8px; color: var(--ink); font-size: 1.3rem; }
.fb-prod-info-line { color: var(--ink-soft); font-weight: 600; margin: 0 0 12px; }
.fb-prod-info-desc { color: var(--ink-soft); line-height: 1.6; }
.fb-prod-info-price { display: inline-block; margin-top: 16px; }

.fb-team-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 20px; }
/* One team block alone in a column: fill the column, so a single card next to a
   taller neighbour (a map, a photo) ends at the same line instead of floating in
   the middle of empty space. Same rule and same reasoning as .fb-teaser above —
   including :only-child, because several stacked blocks would each claim the
   full column height, and the .fb-vis wrapper that a set `hide_on` inserts.
   Three levels have to pass the height on: the block wrapper, the grid inside it
   (flex:1), and the card, which is a grid item and stretches by itself. */
.fb-col > .fb-team:only-child,
.fb-col > .fb-vis:only-child > .fb-team { height: 100%; display: flex; flex-direction: column; }
.fb-col > .fb-team:only-child > .fb-team-grid,
.fb-col > .fb-vis:only-child > .fb-team > .fb-team-grid { flex: 1 0 auto; }
/* Card design — matches .fb-doc-card / product .fb-card (surface + line + radius,
   hover lift). Round avatar stays; content centered. */
/* A column, so the mail button can be pinned to the bottom edge. Grid items
   already stretch to the tallest card in their row (measured: three cards of
   different length all came out 378px), but the CONTENT stayed top-aligned and
   left up to 76px of air under the shortest card's button — which is what made
   the cards look ragged rather than equal. */
.fb-person { text-align: center; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 26px 18px 22px; transition: border-color .15s, box-shadow .15s;
  display: flex; flex-direction: column; }
/* The spacer that does the pinning. A pseudo-element instead of a wrapper around
   the button, and `order` instead of source position: ::after is generated LAST,
   but order 1 puts it before the button (order 2) while everything else stays at
   the default 0. That keeps the block template — which the editor canvas renders
   too — untouched.
   Why not simply `margin-top: auto` on the button: an auto margin collapses to 0
   when there is no free space, and the 14px gap to the phone number above would
   disappear on exactly the tallest card. The spacer grows from zero instead, so
   the gap is always the button's own margin. */
.fb-person::after { content: ""; flex: 1 0 0; order: 1; }
.fb-person-btn { order: 2; }
/* Text still centres via text-align, but the photo is a BLOCK in a flex column
   and text-align does not centre a block — it would sit at the left edge. */
.fb-person-photo { align-self: center; }
.fb-person:hover { border-color: var(--c-primary); box-shadow: 0 6px 18px rgba(0, 0, 0, .08); }
/* The photo sits in its own wrapper so the (i) badge has something to hang on.
   The wrapper TAKES OVER the spacing that used to be the image's own margin —
   deliberately not a second `.fb-person-photo img` rule: that would be the same
   0-1-1 specificity as the line below and would lose to it on order alone. */
.fb-person-photo { position: relative; display: inline-block; margin: 0 0 14px; }
.fb-person img { display: block; width: 116px; height: 116px; border-radius: 50%; object-fit: cover; }
.fb-person strong { display: block; font-size: 17px; }
.fb-person-pos { color: var(--ink-soft); font-size: 14px; margin-top: 2px; }
/* --- extra note on a team photo (ported from TYPO3 mask_company_employees) ---
   A small (i) on the photo's edge; the note hangs under it. The old element
   revealed it on :hover alone, which is nothing on a phone — hence a real
   button: hover and keyboard focus are CSS, the tap toggle is aria-expanded
   from findd-public.js. Without JS hover and focus still work. */
.fb-person-info {
  position: absolute; right: 0; bottom: 2px; z-index: 3;
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: 999px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: #fff; background: linear-gradient(135deg, var(--c-primary) 0%, var(--c-secondary) 100%);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .2);
  /* A <button> inherits none of the page's typography (the UA hands it Arial and
     line-height:normal) — see the same note on .fb-btn. Nothing here shows text
     today, but the note next to it does, and both must read as the site. */
  font-family: inherit; font-size: inherit; line-height: inherit;
  transition: transform .15s, box-shadow .15s;
}
.fb-person-info:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0, 0, 0, .28); }
.fb-person-info .fb-icon { width: 17px; height: 17px; }
.fb-person-tip {
  position: absolute; top: calc(100% + 10px); left: 50%; z-index: 4;
  width: max-content; min-width: 150px; max-width: 230px;
  padding: 10px 12px; border-radius: var(--radius); text-align: left;
  background: var(--surface); color: var(--ink); border: 1px solid var(--line);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .18);
  font-size: 13px; line-height: 1.45;
  /* visibility, not [hidden] or display: a hidden box is no hover target, so the
     pointer can cross the gap during the transition and land on the note itself. */
  opacity: 0; visibility: hidden; transform: translate(-50%, -4px);
  transition: opacity .15s, transform .15s, visibility .15s;
}
/* The gap between badge and note, bridged so moving the pointer down does not
   cross unhovered ground. Part of the note, so it only exists while it is open. */
.fb-person-tip::before { content: ""; position: absolute; left: 0; right: 0; top: -12px; height: 12px; }
.fb-person-info:hover ~ .fb-person-tip,
.fb-person-info:focus-visible ~ .fb-person-tip,
.fb-person-info[aria-expanded="true"] ~ .fb-person-tip,
.fb-person-tip:hover { opacity: 1; visibility: visible; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) { .fb-person-info, .fb-person-tip { transition: none; } }
/* No photo, no badge — the note is a plain line in the card body instead. */
.fb-person-note { display: block; color: var(--ink-soft); font-size: 13px; line-height: 1.45; margin-top: 6px; }
.fb-person .fb-link { display: block; margin-top: 4px; }
/* Mail button: full card width at the bottom, same shape as the teaser button.
   display:block (not the .fb-btn inline-block) so it fills the card. */
.fb-person-btn { display: block; width: 100%; text-align: center; margin-top: 14px; }

.fb-testimonials-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 22px; }
.fb-quote { margin: 0; border: 1px solid var(--line); border-radius: var(--radius); padding: 22px; background: var(--muted-bg); }
.fb-stars { letter-spacing: 2px; font-size: 17px; line-height: 1; margin-bottom: 12px; color: #d7d7db; }
.fb-stars .on { color: #f5b301; }
.fb-quote blockquote { margin: 0 0 14px; font-size: 17px; }
.fb-quote figcaption { display: flex; align-items: center; gap: 10px; font-weight: 600; }
.fb-quote figcaption img { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; }

/* Partner logos sit in bordered tiles (like the old TYPO3 element): a card grid,
   each logo centred and desaturated until hovered. */
.fb-partners-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; align-items: stretch; }
@media (max-width: 760px) { .fb-partners-grid { grid-template-columns: repeat(2, 1fr); } }
.fb-partner-tile { display: flex; align-items: center; justify-content: center; min-height: 110px; padding: 22px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); transition: box-shadow .2s, border-color .2s; }
.fb-partner-tile:hover { border-color: var(--c-primary); box-shadow: 0 8px 24px rgba(0, 0, 0, .08); }
.fb-partner-tile img { max-height: 64px; width: auto; filter: grayscale(1); opacity: .8; transition: filter .2s, opacity .2s; }
.fb-partner-tile:hover img { filter: none; opacity: 1; }
/* Dark mode: the whole row is flattened to WHITE silhouettes, the convention
   every dark site uses for a partner/customer logo strip.
   Plain invert() was the first attempt and is wrong here — measured, it lifts a
   dark logo to 7.9:1 but drops an ALREADY LIGHT one to 1.25:1, and a white logo
   is exactly the kind a partner hands over. These are other people's files with
   no second upload to fall back on, so the filter has to work for every one of
   them: brightness(0) forces one colour, invert(1) makes it white — 15:1 for
   dark and light logos alike. The price is the shading inside a logo, which
   greyscale had already flattened halfway.
   The hover then shows the real thing: the TILE turns white and the filter comes
   off, so the brand appears exactly as its owner drew it — on the paper it was
   drawn for. (Inverting on hover would show a made-up colour for someone else's
   brand, which is worse than showing none.) */
html.theme-dark .fb-partner-tile img { filter: grayscale(1) brightness(0) invert(1); }
html.theme-dark .fb-partner-tile:hover { background: #fff; }
html.theme-dark .fb-partner-tile:hover img { filter: none; }

.fb-video-frame { position: relative; padding-top: 56.25%; border-radius: var(--radius); overflow: hidden; }
.fb-video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.fb-video video { width: 100%; border-radius: var(--radius); }

/* video showcase (company_video): main stage + thumbnail carousel */
.fb-videos { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--gutter); }
.fb-videos-stage { display: grid; grid-template-columns: 1.6fr 1fr; gap: 24px; align-items: start; margin-bottom: 22px; }
.fb-videos-media { position: relative; border-radius: var(--radius); overflow: hidden; background: #000; }
.fb-videos-media video { display: block; width: 100%; border-radius: var(--radius); }
.fb-videos-title { margin: 0 0 12px; }
.fb-videos-desc { color: var(--ink-soft); line-height: 1.6; white-space: pre-line; }
/* A long description used to run past the bottom of the video beside it and left
   the stage lopsided. Eight lines ≈ 205px, plus title and button ≈ 271px — inside
   the ~379px the 16:9 video is tall in a 1120px content column, at every width the
   grid uses. The class is set by findd-public.js, never here: without JS the text
   must stay whole (there would be no way to unfold it). */
.fb-videos-desc.is-clamped {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 8; overflow: hidden;
}
.fb-videos-more {
  margin-top: 10px; padding: 0; border: 0; background: none; cursor: pointer;
  font: 700 14px/1.4 var(--font-body); color: var(--c-link, var(--c-primary));
}
.fb-videos-more:hover { text-decoration: underline; }
.fb-videos-more[hidden] { display: none; }  /* explicit — see CLAUDE.md, Falle 2 */
@media (max-width: 800px) { .fb-videos-stage { grid-template-columns: 1fr; } }

/* Carousel: the thumbnail track spans the block's full width (flush left/right,
   aligned with the stage above); the prev/next arrows sit just OUTSIDE the track
   edges, vertically centred on the thumbnails. On narrow viewports they drop
   below the track instead. */
.fb-videos-carousel { position: relative; }
/* The track scrolls, and overflow-x:auto makes overflow-y compute to auto — so
   whatever a card paints outside its own box (the hover shadow) is clipped by the
   scroll box. Give the scroll box the shadow's reach as padding and pull exactly
   that padding back off the layout with a negative margin (cards stay put, the
   shadow gets room). scroll-padding keeps the snap positions on the card edges:
   without it a snapped card would land one padding-width left of where the first
   card rests. Same treatment on .fb-feat-small below. */
.fb-videos-track {
  display: flex; gap: 14px; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none;
  padding: 6px 10px 18px; margin: -6px -10px -18px; scroll-padding-inline: 10px;
}
.fb-videos-track::-webkit-scrollbar { display: none; }
/* Exactly 4 tiles fill the visible track (4 tiles + 3×14px gaps = 100%); the
   rest scroll. min-width keeps them usable on narrow screens (fewer show there). */
.fb-video-thumb { scroll-snap-align: start; flex: 0 0 calc((100% - 3 * 14px) / 4); min-width: 160px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; padding: 0; cursor: pointer; text-align: left; font: inherit; color: inherit; transition: box-shadow .15s; }
.fb-video-thumb:hover { box-shadow: 0 6px 18px rgba(0,0,0,.08); }
.fb-video-thumb-img { position: relative; display: block; width: 100%; aspect-ratio: 16/9; background: var(--muted-bg) center/cover no-repeat; }
/* active thumbnail: a 3px primary line under the image (no inset frame → tiles keep the same size) */
.fb-video-thumb-img::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: var(--c-primary); transform: scaleX(0); transition: transform .15s; }
.fb-video-thumb.is-active .fb-video-thumb-img::after { transform: scaleX(1); }
.fb-video-play { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); width: 46px; height: 46px; border-radius: 50%; background: rgba(0,0,0,.55); }
.fb-video-play::before { content: ''; position: absolute; top: 50%; left: 54%; transform: translate(-50%,-50%); border-style: solid; border-width: 9px 0 9px 15px; border-color: transparent transparent transparent #fff; }
.fb-video-thumb-cap { display: block; padding: 10px 12px; }
.fb-video-thumb-title { display: block; font-weight: 600; font-size: 14px; line-height: 1.3; }
.fb-video-thumb-desc { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-top: 4px; font-weight: 400; font-size: 13px; line-height: 1.35; color: var(--ink-soft); }
.fb-videos-arrow, .fb-feat-arrow { border: 0; background: none; padding: 0; color: var(--ink); line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.fb-videos-arrow .chev, .fb-feat-arrow .chev { width: 26px; height: 26px; }
.fb-videos-arrow:hover, .fb-feat-arrow:hover { color: var(--c-primary); }
/* JS hides the arrows when the track can't scroll — the display:flex above would
   otherwise beat the UA's [hidden] rule and leave a dead button (see the
   "Mehr anzeigen" buttons for the same trap). */
.fb-videos-arrow[hidden], .fb-feat-arrow[hidden] { display: none; }
/* Bare chevrons (no circle/shadow), vertically centred on the thumbnail row and
   set well outside the block edges (into the gutter/margin). Shared by the video
   carousel and the catalog feature teaser row. */
.fb-videos-carousel .fb-videos-arrow, .fb-feat-carousel .fb-feat-arrow { position: absolute; top: 50%; transform: translateY(-50%); z-index: 2; }
/* The outset follows the room that actually exists beside the content column:
   half of what the viewport has left over --maxw, plus the gutter, minus a little
   slack for the scrollbar (100vw includes it). Clamped on both ends — never less
   than one gutter, so a narrow viewport keeps the arrow reachable (a fixed -45px
   used to run off-screen there), and never more than 52px, so it stays visually
   attached to the track. Inside a grid column there is no such room: back to one
   gutter, otherwise the arrow would reach into the neighbouring column. */
.fb-videos-carousel, .fb-feat-carousel {
  --arrow-out: clamp(var(--gutter), (100vw - var(--maxw)) / 2 + var(--gutter) - 10px, 52px);
}
.fb-col .fb-videos-carousel, .fb-col .fb-feat-carousel { --arrow-out: var(--gutter); }
.fb-videos-prev, .fb-feat-prev { left: 0; margin-left: calc(-1 * var(--arrow-out)); }
.fb-videos-next, .fb-feat-next { right: 0; margin-right: calc(-1 * var(--arrow-out)); }
/* Narrow viewports: arrows below the track, centred as a pair. */
@media (max-width: 640px) {
  .fb-videos-carousel, .fb-feat-carousel { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
  .fb-videos-track, .fb-feat-small { flex-basis: 100%; order: 1; }
  .fb-videos-carousel .fb-videos-arrow, .fb-feat-carousel .fb-feat-arrow { position: static; transform: none; margin: 0; order: 2; }
}

/* builder edit view: simple editable cards */
.fb-videos-edit { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
.fb-videos-editcard { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.fb-videos-editthumb { aspect-ratio: 16/9; background: var(--muted-bg) center/cover no-repeat; display: flex; align-items: center; justify-content: center; color: var(--ink-soft); font-size: 13px; cursor: pointer; }
.fb-videos-editbody { padding: 10px 12px; }
.fb-videos-editbody strong { display: block; }
.fb-videos-editbody p { margin: 4px 0 0; color: var(--ink-soft); font-size: 14px; }

/* contact details — three layouts */
.fb-contact-intro { color: var(--ink-soft); margin: 0 0 18px; }
.fb-contact-items { display: grid; gap: 16px; }
.fb-contact-item { display: flex; align-items: flex-start; gap: 12px; }
.fb-contact-ico { flex: none; font-size: 22px; color: var(--c-primary); line-height: 1.2; }
.fb-contact-ico .fb-icon { width: 1em; height: 1em; }
.fb-contact-body { display: flex; flex-direction: column; }
.fb-contact-label { font-size: 13px; color: var(--ink-soft); }
.fb-contact-value { font-weight: 600; }
/* Social profiles as a fourth box: the shared .fb-social circles, one size down
   from the free social block — in a contact card they sit next to plain text
   rows (phone, e-mail), and the full 42px circles would dominate it. NOT a
   wrapping constraint, as the older note here claimed: the text column of the
   narrowest card measures 148px, and three icons per row fit there at 34, 39 and
   even 42px (measured) — the row breaks the same way at any of them. The glyph
   keeps the shared 20px of .fb-social, so there is no second size to maintain. */
.fb-contact-socials { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
.fb-contact-socials .fb-social { width: 39px; height: 39px; }
/* cards: bordered tiles in a responsive grid */
.fb-contact-cards .fb-contact-items { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.fb-contact-cards .fb-contact-item { border: 1px solid var(--line); border-radius: var(--radius); padding: 18px; background: var(--surface); }
/* inline: one row, wraps on small screens */
.fb-contact-inline .fb-contact-items { grid-auto-flow: column; grid-template-columns: none; justify-content: start; gap: 32px; }
@media (max-width: 640px) { .fb-contact-inline .fb-contact-items { grid-auto-flow: row; } }

/* The widget title's top margin would make it sit lower than a .fb-heading next
   to it (e.g. in a 2-column box) — zero it here so the two align at the top. */
.fb-opening > .fb-wtitle { margin-top: 0; }
.fb-opening table { border-collapse: collapse; width: 100%; max-width: 420px; }
.fb-opening th, .fb-opening td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--line); }
.fb-opening th { font-weight: 600; }
.fb-opening-note { color: var(--ink-soft); font-size: 14px; margin-top: 10px; }

.fb-map iframe { width: 100%; height: 380px; border: 0; border-radius: var(--radius); }

/* --- dynamic catalog widgets (Jobs / Events / Produkte) -------------------- */
.fb-catalog { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--gutter); }

/* documents (company_documents): grid of cards, each linking a PDF */
.fb-docs { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--gutter); }
.fb-docs-grid { list-style: none; display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin: 0; padding: 0; }
.fb-doc-card { display: flex; gap: 14px; align-items: stretch; height: 100%; text-decoration: none; color: inherit; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; transition: border-color .15s, box-shadow .15s; }
a.fb-doc-card:hover { border-color: var(--c-primary); box-shadow: 0 6px 18px rgba(0, 0, 0, .08); }
.fb-doc-icon { flex: 0 0 auto; width: 84px; display: flex; align-items: center; justify-content: center; color: #fff; background: linear-gradient(135deg, var(--c-primary) 0%, var(--c-secondary) 100%); }
.fb-doc-icon .fb-icon { width: 34px; height: 34px; }
.fb-doc-body { display: flex; flex-direction: column; justify-content: center; gap: 3px; padding: 14px 16px; min-width: 0; }
.fb-doc-title { font-weight: 700; color: var(--ink); }
a.fb-doc-card:hover .fb-doc-title { color: var(--c-primary); }
.fb-doc-desc { font-size: .85rem; line-height: 1.4; color: var(--ink-soft); }
@media (max-width: 900px) { .fb-docs-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .fb-docs-grid { grid-template-columns: 1fr; } }

/* Payment methods (ported from TYPO3 company-payment): grouped cards
   (Online / Vor Ort). Styled to match the document cards — a full-height accent
   gradient icon tile on the left, method name + one-line note on the right. */
.fb-pay { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--gutter); }
.fb-pay-group + .fb-pay-group { margin-top: 2rem; }
.fb-pay-grouptitle { display: flex; align-items: center; gap: 10px; font-size: 1.15rem; font-weight: 700; color: var(--ink); margin: 0 0 1rem; }
.fb-pay-groupico { display: inline-flex; color: var(--c-primary); }
.fb-pay-groupico svg { width: 22px; height: 22px; }
.fb-pay-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.fb-pay-card { display: flex; gap: 14px; align-items: stretch; height: 100%; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.fb-pay-icon { flex: 0 0 auto; width: 84px; display: flex; align-items: center; justify-content: center; color: #fff; background: linear-gradient(135deg, var(--c-primary) 0%, var(--c-secondary) 100%); }
/* Our own glyphs share Lucide's grid, so one size keeps them balanced. Brand marks
   do NOT — each carries its own measured size as a width/height attribute (see
   $brands in company_payment.php), so this rule must not apply to them. */
.fb-pay-icon svg:not([data-mark]) { width: 34px; height: 34px; }
/* The two accent tiles (documents + payment) are the ONLY places that draw a
   stroke glyph above its 24px design size, and the stroke scales with the box:
   authored `stroke-width="2"` on a 24 viewBox renders 2 x 34/24 = 2.83px here,
   while every other icon on the site lands at 1.4-1.83px (17-22px boxes). That
   40% overshoot is what reads as "too thick" — the shape is fine, the line is
   heavy, and on the saturated gradient the white strokes bleed into each other
   (the receipt's rules and the bank's columns nearly close up). Compensating
   back to ~2.1px is Lucide's own rule for large sizes: bigger icon, thinner
   stroke. Shared by both tiles on purpose, so the number stays one decision.
   A glyph that sets stroke-width on its own path (chevron, columns-6) still
   wins — this only supplies the inherited default. */
.fb-doc-icon .fb-icon,
.fb-pay-icon svg:not([data-mark]) { stroke-width: 1.5; }
.fb-pay-body { display: flex; flex-direction: column; justify-content: center; gap: 3px; padding: 14px 16px; min-width: 0; }
.fb-pay-name { font-weight: 700; color: var(--ink); }
.fb-pay-desc { font-size: .85rem; line-height: 1.4; color: var(--ink-soft); }
@media (max-width: 900px) { .fb-pay-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .fb-pay-grid { grid-template-columns: 1fr; } }

.fb-cat-grid { display: grid; gap: 20px; }
.fb-cat-grid .fb-card-img img { width: 100%; aspect-ratio: 16/9; object-fit: cover; border-radius: var(--radius) var(--radius) 0 0; }
/* Not clipping: the sold-out banderole hangs over the card edges (the picture
   carries the top corners itself). */
.fb-cat-grid > .fb-card { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); display: flex; flex-direction: column; }
/* Column body so the price row can dock to the bottom edge of every card,
   regardless of how long the title/teaser above it runs. */
.fb-cat-grid > .fb-card .fb-card-body { padding: 16px; flex: 1; display: flex; flex-direction: column; }
.fb-cat-grid > .fb-card h3 { margin: 0 0 6px; color: var(--ink); }
.fb-cat-grid > .fb-card p { margin: 0 0 10px; color: var(--ink-soft); }
.fb-card.is-highlight { outline: 2px solid var(--c-primary); }
.fb-card-meta { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: auto; }
/* Own class on purpose: .fb-price is the pricing block's CARD (border/padding/
   background) — reusing it here framed the product price in a box. */
.fb-card-price { font-weight: 800; color: var(--c-primary); font-size: 18px; }
/* Shop link of a product (optional `link`). Sits below the price row, full width
   like the teaser button so a grid of cards keeps one baseline for its buttons. */
.fb-card-btn { display: block; width: 100%; text-align: center; margin-top: 12px; }
/* Only cards that actually lead somewhere react to the pointer — a card without
   a shop link must not look clickable. Same lift as the linked teaser cards. */
.fb-cat-grid > .fb-card.is-linked { transition: transform .15s, border-color .15s, box-shadow .15s; }
.fb-cat-grid > .fb-card.is-linked:hover { transform: translateY(-2px); border-color: var(--c-primary); box-shadow: 0 12px 30px rgba(0, 0, 0, .10); }
.fb-cat-grid > .fb-card.is-linked:hover h3 { color: var(--c-primary); }
@media (prefers-reduced-motion: reduce) { .fb-cat-grid > .fb-card.is-linked:hover { transform: none; } }
.fb-prod-info-cta { margin: 16px 0 0; }
/* "Ausverkauft" corner ribbon, ported from the old TYPO3 signage badge
   (soldout_badge_left.svg): a dark red band across the top-left corner. Built
   in CSS rather than shipping that SVG — its lettering is set in Rajdhani, and
   an <img>-referenced SVG cannot use the page's self-hosted font. The card
   clips the overhanging ends (it needs position:relative + overflow:hidden).
   Sizes in em so one rule serves the small card and the big signage variant. */
.fb-soldout {
  position: absolute; top: 0; left: 0; z-index: 3; pointer-events: none;
  line-height: 0; width: 130px; max-width: 46%;
  /* Pulled out by the size of a folded end (the old badge sat at -7px on 200px),
     so the band overhangs the corner and its folds wrap behind the card. */
  transform: translate(-3.5%, -3.5%);
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .4));
}
.fb-soldout-svg { width: 100%; height: auto; display: block; }
/* Without a picture the card starts with its headline — a band across that
   corner would sit on top of the text, so it falls back to a pill in the flow
   (the badge SVG is not rendered at all in that case). */
.fb-soldout.is-inline {
  position: static; transform: none; width: auto; max-width: none; filter: none;
  display: inline-block; align-self: flex-start; line-height: 1.35;
  background: linear-gradient(135deg, #8e0000 0%, #b80000 100%);
  color: #fff; text-transform: uppercase; font-weight: 800; font-size: 12px;
  letter-spacing: .05em; padding: .18em .8em; border-radius: 999px;
}
.fb-card-date { font-weight: 700; color: var(--c-primary); font-size: 14px; }
.fb-card-loc { color: var(--ink-soft); font-size: 14px; margin-bottom: 6px; }
/* grid layout = cards; list layout = single column, image left */
.fb-cat-grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
/* The layout-modifier class (fb-cat-<layout>) also lands on the .fb-catalog
   wrapper, so for layout=grid the wrapper matched the bare .fb-cat-grid rule and
   became a grid itself — squeezing the inner card grid into one narrow track so
   cards stacked. Keep the wrapper a plain block; only the inner container grids. */
.fb-catalog.fb-cat-grid { display: block; }
.fb-cat-list .fb-cat-grid { grid-template-columns: 1fr; }
.fb-cat-list .fb-card { flex-direction: row; }
.fb-cat-list .fb-card .fb-card-img { flex: 0 0 220px; }
.fb-cat-list .fb-card .fb-card-img img { width: 100%; height: auto; aspect-ratio: 16/9; object-fit: cover; }
@media (max-width: 560px) { .fb-cat-list .fb-card { flex-direction: column; } .fb-cat-list .fb-card .fb-card-img { flex: none; } }
/* Cards are links (events/jobs/news teaser onto their detail page). */
.fb-card { text-decoration: none; color: inherit; }
.fb-cat-grid > a.fb-card:hover { box-shadow: 0 12px 30px rgba(0,0,0,.10); }
.fb-cat-grid > a.fb-card:hover .fb-link { text-decoration: underline; }

/* Feature layout: one large hero box + up to three small teaser cards, each
   linking to the record's detail page. Shared by Events, Jobs and News (reuse). */
.fb-feat-meta { font-weight: 700; letter-spacing: .5px; text-transform: uppercase; font-size: 13px; color: var(--c-primary); }
.fb-feat-more { font-weight: 700; }
/* Deliberately NOT 16:9: at a 1280 content column that would be a 720px tall
   hero and push the teaser row below the fold. The fixed height keeps the big
   box flat and wide; only the card images carry the shared ratio. The height is
   shared with the teaser banner via --fb-banner-h (see :root). */
.fb-feat-big {
  display: block; position: relative; min-height: var(--fb-banner-h); border-radius: var(--radius); overflow: hidden;
  background-size: cover; background-position: 50%; background-color: var(--muted-bg);
  color: #fff; text-decoration: none; margin-bottom: 20px;
  transition: transform .15s, box-shadow .15s;
}
/* The same lift as the teaser cards below it, down to the pixel and the timing:
   the big box and the row are ONE widget, and a banner that stays put while the
   cards under it react reads as "the banner is not clickable" — it is. The shadow
   needs no extra room here: nothing clips it (the row's overflow belongs to the
   scroll track, and the banner is its sibling, not its child). */
.fb-feat-big:hover { transform: translateY(-3px); box-shadow: 0 12px 30px rgba(0, 0, 0, .10); }
.fb-feat-big::before { content: ""; position: absolute; inset: 0; background: linear-gradient(to top, rgba(0,0,0,.80) 0%, rgba(0,0,0,.15) 55%, rgba(0,0,0,0) 100%); }
.fb-feat-big-body { position: absolute; left: 0; right: 0; bottom: 0; z-index: 1; padding: 28px; display: flex; flex-direction: column; gap: 6px; }
/* White text over the image (the anchor's own color loses to the generic link
   colour rule, so set the text elements explicitly). */
.fb-feat-big-title {
  font-size: clamp(22px, 3.4vw, 34px); font-weight: 800; line-height: 1.15; color: #fff;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.fb-feat-big-teaser {
  max-width: 640px; opacity: .95; color: #fff;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.fb-feat-big .fb-feat-meta, .fb-feat-big .fb-feat-more { color: #fff; }
.fb-feat-big .fb-feat-meta { opacity: .85; }

/* Teaser row = a scroll track, not a grid: three cards fill the visible width
   (3 cards + 2×20px gaps = 100%), any further record scrolls into view via the
   arrows — same mechanic as the video thumbnail carousel. With three or fewer
   cards nothing overflows, so it looks exactly like the old 3-column grid.
   Vertical padding only: it gives the hover lift room without shifting the row
   out of line with the big box above (overflow-x also clips vertically). */
.fb-feat-carousel { position: relative; }
/* Room for the card hover shadow inside the scroll box — see .fb-videos-track. */
.fb-feat-small {
  display: flex; gap: 20px; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none;
  padding: 8px 16px 30px; margin: -8px -16px -30px; scroll-padding-inline: 16px;
}
.fb-feat-small::-webkit-scrollbar { display: none; }
.fb-feat-card { scroll-snap-align: start; flex: 0 0 calc((100% - 2 * 20px) / 3); display: flex; flex-direction: column; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--surface); color: inherit; text-decoration: none; transition: transform .15s, box-shadow .15s; }
.fb-feat-card:hover { transform: translateY(-3px); box-shadow: 0 12px 30px rgba(0,0,0,.10); }
/* Whoever asked the system not to animate gets the shadow but not the movement.
   Written for the whole family, not just the banner that was added last. */
@media (prefers-reduced-motion: reduce) {
  .fb-feat-big, .fb-feat-card { transition: none; }
  .fb-feat-big:hover, .fb-feat-card:hover { transform: none; }
}
/* 16:9 for every teaser tile, whatever the source image is (cover crops it) —
   the row only reads as one strip if the images share an aspect ratio. */
.fb-feat-card-img { display: block; aspect-ratio: 16/9; background-size: cover; background-position: 50%; background-color: var(--muted-bg); }
.fb-feat-card-body { padding: 15px; display: flex; flex-direction: column; gap: 6px; }
.fb-feat-card-title { font-weight: 700; font-size: 17px; line-height: 1.25; color: var(--ink); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.fb-feat-card-desc { font-size: 14px; line-height: 1.4; color: var(--ink-soft); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* Same label, feature layout. The one on the BIG box stays white a few rules up —
   it sits on the photo, where the question is contrast, not brand. */
.fb-feat-card .fb-feat-more { color: var(--c-link, var(--c-primary)); font-size: 14px; }
@media (max-width: 800px) { .fb-feat-card { flex-basis: calc((100% - 20px) / 2); } }
@media (max-width: 520px) { .fb-feat-card { flex-basis: 100%; } }

/* Catalog detail page (Events/Jobs/News), reached via /<kind>/<slug>. */
.fb-detail { max-width: var(--maxw); margin: 0 auto; }
.fb-detail-hero { position: relative; min-height: 360px; overflow: hidden; border-radius: 0 0 var(--radius) var(--radius); background-size: cover; background-position: 50%; color: #fff; display: flex; align-items: flex-end; }
.fb-detail-hero::before { content: ""; position: absolute; inset: 0; background: linear-gradient(to top, rgba(0,0,0,.78), rgba(0,0,0,0) 60%); }
.fb-detail-hero-in { position: relative; z-index: 1; padding: 32px; }
.fb-detail-hero-in h1 { margin: 0 0 6px; color: #fff; font-size: clamp(26px, 4vw, 40px); }
.fb-detail-head { padding: 40px var(--gutter) 0; }
.fb-detail-head h1 { margin: 0 0 6px; }
.fb-detail-meta { font-weight: 600; color: var(--c-primary); }
.fb-detail-hero .fb-detail-meta { color: #fff; opacity: .9; }
/* Extra images under the hero. auto-fill (not auto-fit) so two pictures stay two
   picture-sized tiles instead of stretching across the whole row; 4/3 with cover
   crops every source format alike, the same rule the catalog cards follow. The
   text column below is capped at 820px, but the strip may use the full width —
   it is a row of images, not a line of text. */
.fb-detail-gallery {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 10px; padding: 24px var(--gutter) 0;
}
.fb-detail-gallery-item {
  display: block; border-radius: var(--radius); overflow: hidden;
  background: var(--muted-bg); aspect-ratio: 4 / 3;
}
/* Needs the .findd-site prefix: `.findd-site img { height: auto }` (0-1-1) would
   otherwise beat a bare class rule and collapse the thumbnail. */
.findd-site .fb-detail-gallery-item img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .25s;
}
.fb-detail-gallery-item:hover img { transform: scale(1.04); }
@media (prefers-reduced-motion: reduce) { .fb-detail-gallery-item img { transition: none; } }
@media (max-width: 640px) { .fb-detail-gallery { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); } }
.fb-detail-body { padding: 28px var(--gutter) 44px; max-width: 820px; }
/* Job ad with a facts rail on the right (catalog_detail.php: $withSide).
   MUST stay after .fb-detail-body — both are 0-1-0, so only source order decides
   which max-width wins.
   The pair uses the FULL page width: max-width: none hands the grid the whole
   --maxw container (the hero above spans the same width, so the rail now ends
   flush with it). The text track therefore grows with the tenant's content-width
   setting — 896px at the 1280 default, 1056px at 1440 — which is wider than the
   820px the single-column body was capped at. That is the deliberate trade for
   using the space; the rail is one number (--jobside-w) if the balance should
   shift the other way.
   Not sticky: the tenant's header may or may not be sticky and publishes no
   usable height (--header-h is 0 unless a header OVERLAYS), so a sticky card
   would either slide under the bar or float below a phantom one. */
.fb-detail-body--aside {
  --jobside-w: 300px;
  --jobside-gap: 40px;
  max-width: none;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--jobside-w);
  gap: var(--jobside-gap);
  align-items: start;
}
/* min-width:0 — without it a long word or a wide table in the description blows
   the track past its share (a grid item's min-width is auto = min-content). */
.fb-detail-body--aside > .fb-detail-main { grid-column: 1; grid-row: 1; min-width: 0; }
.fb-detail-body--aside > .fb-detail-side { grid-column: 2; grid-row: 1; }
.fb-detail-side .fb-job-facts { margin-bottom: 0; }
@media (max-width: 900px) {
  /* Below this the text track would fall under ~520px. Back to one column — and
     because the aside is first in the document, the facts sit above the text
     exactly as they did before. */
  .fb-detail-body--aside { display: block; max-width: 820px; }
  .fb-detail-side .fb-job-facts { margin-bottom: 22px; }
}
.fb-detail-lead { font-size: 19px; color: var(--ink-soft); margin: 0 0 18px; }
.fb-detail-text { line-height: 1.7; }
.fb-detail-cta { margin: 26px 0 0; }
.fb-detail-back { margin-top: 30px; }
.fb-detail-back a { color: var(--ink-soft); text-decoration: none; }
.fb-detail-back a:hover { text-decoration: underline; }

/* Header → hamburger below 768px (only when JS is active, so no-JS keeps the
   nav visible/stacked). .site-header-end becomes display:contents so nav + CTA
   are laid out directly by the header flex — unifies classic/minimal/centered. */
@media (max-width: 768px) {
  .has-js .site-header { flex-wrap: wrap; }
  .has-js .site-header-end { display: contents; }
  /* .site-chrome dissolves too, so its three buttons can be ordered individually:
     the call button belongs in the bar, share and theme belong in the menu. Four
     icons plus a hamburger next to the company name did not fit on a phone — the
     group wrapped onto a second row and the bar grew to two storeys (measured at
     390px). Explicit orders for every item, because the DOM order puts the nav
     second and a 100%-wide item there would push everything after it downwards. */
  .has-js .site-chrome { display: contents; }
  .has-js .site-brand { order: 1; margin-right: auto; }
  .has-js .site-header-cta { order: 3; }
  .has-js .site-burger { display: flex; order: 4; }
  .has-js .site-nav { order: 5; flex-basis: 100%; flex-direction: column; gap: 2px; }
  .has-js .site-nav a { padding: 12px; }
  /* The menu's footer row: share + theme under a hairline, below the entries.
     Same order rules — it must come AFTER the nav, not merely after it in the DOM.
     (Before this the hamburger itself shared the nav's order value and dropped
     BELOW the open menu; measured at y 356 while the bar ended at 115.) */
  .has-js .site-chrome-menu {
    order: 6; flex-basis: 100%; display: flex; align-items: center; gap: 8px;
    margin-top: 8px; padding-top: 12px; border-top: 1px solid var(--line);
  }
  .has-js .site-header:not(.nav-open) .site-chrome-menu { display: none; }
  /* The name is the elastic part of that row, the hamburger is not. Measured at
     375px (an iPhone SE): "Tischlerei Weber" plus the call button plus the burger
     comes to 346px in a 331px row, and the burger dropped to a second line. It may
     shrink now, and be cut with an ellipsis if it must — the badge variant's
     overhanging circle is a sibling of the text and stays untouched. */
  /* Room the top row must keep free for the buttons that stay in it: call (40) +
     hamburger (44) + the header's two 20px gaps. Without a call button it is one
     button and one gap less. Stated as a token so the cap is one decision. */
  .has-js .site-header { --bar-actions: 124px; }
  .has-js .site-header:not(:has(.site-header-cta)) { --bar-actions: 64px; }
  /* A cap, not `flex: 1 1 0`. Growing the brand would work just as well for the
     layout, but it stretches the ANCHOR across the empty half of the bar, and a
     click on blank space would then navigate home. min-width alone does nothing
     here: with flex-wrap on, the line break is decided from the item's untouched
     content width, so an over-long name claims a line of its own BEFORE any
     shrinking happens — measured, the hamburger sat on the second row. */
  .has-js .site-brand { min-width: 0; max-width: calc(100% - var(--bar-actions)); }
  /* `display: block` is load-bearing: text-overflow needs a BLOCK container, and the
     name sits in a plain inline <span> — with it left inline the rule parsed, applied
     and did nothing (measured: the page still laid out 619px wide at a 390px
     viewport). The badge variant's text column is already flex, so only its two
     lines need the treatment. */
  .has-js .site-brand > span { display: block; }
  .has-js .site-brand > span,
  .has-js .site-brand-name,
  .has-js .site-brand-sub { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .has-js .site-brand-text { min-width: 0; }
  /* Full-width column here: undo the desktop full-height trick, or the negative
     margins stack the rows on top of each other. Needs the same base selector —
     a bare `.has-js .site-nav a` loses to it on specificity. */
  .has-js .site-header:not(.site-header--centered) .site-nav a { margin-block: 0; padding-block: 12px; }
  /* The call button sits in .site-chrome now, so it stays visible next to the
     hamburger instead of hiding inside the collapsed menu — one tap to call is
     exactly what a phone visitor wants. */
  .has-js .site-header:not(.nav-open) .site-nav { display: none; }
  /* badge variant: shrink the overhanging logo on small screens (the nav indent
     follows --badge-size automatically). */
  .site-header--badge { --badge-size: 84px; }
  .site-header--badge .site-badge { margin-bottom: -48px; }
  /* while the hamburger nav is open, stop the circle from overhanging — it would
     cover the first menu item, which sits directly below the brand row. */
  .has-js .site-header--badge.nav-open .site-badge { margin-bottom: 0; }
  .site-header--badge .site-brand-name { font-size: 18px; }
}

/* obfuscated email */
.fb-mail { cursor: pointer; }
.fb-mail .fb-at { font-style: normal; }

/* --- share (header button + its modal) ------------------------------------ */
/* The button lives in the header next to the theme toggle and borrows its look
   (.site-theme); the wrapper only carries the modal, so it must not take space. */
.site-share { display: contents; }
/* No box on the desktop — share and theme are laid out by .site-chrome exactly as
   before. It only becomes a box on a phone, where it is the menu's footer row. */
.site-chrome-menu { display: contents; }
.site-share-i { width: 20px; height: 20px; }
.fb-share-modal { position: fixed; inset: 0; background: rgba(10, 14, 18, .55); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 20px; }
.fb-share-modal[hidden] { display: none; }
.fb-share-box { position: relative; background: var(--surface); color: var(--ink); border-radius: var(--radius); padding: 26px; width: min(440px, 94vw); box-shadow: 0 20px 60px rgba(0, 0, 0, .3); }
.fb-share-box h3 { margin: 0 0 16px; }
.fb-share-close { position: absolute; top: 10px; right: 12px; border: 0; background: transparent; font-size: 26px; line-height: 1; cursor: pointer; color: var(--ink-soft); }
.fb-share-items { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 20px; }
.fb-share-item { width: 46px; height: 46px; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; background: var(--muted-bg); color: var(--ink); text-decoration: none; transition: background .15s, color .15s; }
/* Same trap as .site-brand: these are <a> elements, so the global link rule (0-2-1)
   has always beaten the --ink above and tinted the share icons in the brand colour.
   Pinned there rather than left to follow the link colour — a share sheet is a
   control, not a link in a sentence. */
.findd-site a.fb-share-item { color: var(--c-primary); }
/* Must out-specify the pin above (0-2-1), or the icon would keep the brand colour
   on a brand-coloured circle — invisible. Measured trap, same family as Falle 1. */
.fb-share-item:hover { background: var(--c-primary); }
.findd-site a.fb-share-item:hover { color: #fff; }
.fb-share-item .fb-icon { width: 22px; height: 22px; }
.fb-share-copy { display: flex; gap: 8px; }
.fb-share-url { flex: 1; min-width: 0; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius); font: inherit; background: var(--muted-bg); color: var(--ink); }

/* --- header icon buttons: theme (dark mode) toggle + share ----------------- */
/* Both travel as one group (Header::render wraps them) — as separate children of
   the space-between header row they would drift apart. */
.site-chrome { display: inline-flex; align-items: center; gap: 8px; flex: none; }
.site-theme, .site-share-btn { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 0; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); color: var(--ink); cursor: pointer; flex: none; }
.site-theme-i { width: 20px; height: 20px; }
.site-theme .site-theme-sun { display: none; }
html.theme-dark .site-theme .site-theme-moon { display: none; }
html.theme-dark .site-theme .site-theme-sun { display: inline-flex; }

/* --- selects: consistent custom arrow (public forms) ---------------------- */
.fb-form select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235a6675' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
  background-repeat: no-repeat; background-position: right 12px center; padding-right: 38px;
}

/* --- consent: third-party embeds + fingerprint centre --------------------- */
/* Placeholder standing in for a gated embed until the visitor opts in. The
   abstract, self-hosted map graphic sits blurred behind a scrim (no external
   request); consent.js injects the load overlay. Once loaded the iframe fills. */
.fb-consent { position: relative; overflow: hidden; border-radius: var(--radius); min-height: 320px; background: var(--muted-bg); }
.fb-map .fb-consent { min-height: 380px; }
.fb-consent::before {
  content: ""; position: absolute; inset: 0; background: url('brand/map-blur.svg') center/cover no-repeat;
  filter: blur(6px) saturate(.9); transform: scale(1.08); opacity: .9;
}
/* non-map placeholders (booking/video) use a neutral brand tint, not the map */
.fb-consent[data-consent="booking"]::before,
.fb-consent[data-consent="youtube"]::before,
.fb-consent[data-consent="vimeo"]::before {
  background: linear-gradient(135deg, var(--c-primary), var(--c-secondary)); filter: none; transform: none; opacity: .14;
}
.fb-consent.is-loaded { background: none; min-height: 0; }
.fb-consent.is-loaded::before { display: none; }
.fb-consent.is-loaded iframe { border-radius: var(--radius); }
.fb-consent-ov { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; padding: 20px; background: rgba(15, 20, 26, .42); }
/* "Granted, click to load" (maps): lighter veil, the whole area is the button. */
.fb-consent-ov--manual { cursor: pointer; background: rgba(15, 20, 26, .28); }
.fb-consent-ov--manual .fb-consent-card { padding: 16px 18px; }
.fb-consent-ov--manual .fb-consent-title { margin: 0 0 10px; }
.fb-consent-card {
  max-width: 420px; width: 100%; text-align: center; background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 22px 22px 18px; box-shadow: 0 18px 44px rgba(0, 0, 0, .22);
}
.fb-consent-title { margin: 0 0 6px; font-weight: 800; font-size: 1.05rem; }
.fb-consent-note { margin: 0 0 14px; font-size: .9rem; color: var(--ink-soft); line-height: 1.5; }
.fb-consent-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.fb-consent-privacy { display: inline-block; margin-top: 12px; font-size: .8rem; color: var(--ink-soft); text-decoration: none; }
.fb-consent-privacy:hover { color: var(--c-primary); }

/* fingerprint FAB — always-available privacy settings entry, bottom-right */
.fb-consent-fab {
  position: fixed; right: 16px; bottom: 16px; z-index: 60; width: 44px; height: 44px; border-radius: 50%; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; background: var(--surface); color: var(--ink-soft);
  border: 1px solid var(--line); box-shadow: 0 6px 18px rgba(0, 0, 0, .16); transition: color .15s, border-color .15s, transform .15s;
}
.fb-consent-fab:hover { color: var(--c-primary); border-color: var(--c-primary); transform: translateY(-1px); }

/* preferences modal */
.fb-consent-modal { position: fixed; inset: 0; z-index: 80; display: flex; align-items: center; justify-content: center; padding: 18px; background: rgba(10, 14, 18, .55); }
.fb-consent-modal[hidden] { display: none; } /* author display:flex would beat the UA [hidden] rule otherwise */
.fb-consent-dialog { width: 100%; max-width: 480px; max-height: 88vh; overflow-y: auto; background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: 0 24px 60px rgba(0, 0, 0, .4); }
.fb-consent-head { display: flex; align-items: center; gap: 10px; padding: 16px 18px; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--surface); z-index: 1; }
.fb-consent-head strong { font-size: 1.05rem; margin-right: auto; }
.fb-consent-head-ic { color: var(--c-primary); display: inline-flex; }
.fb-consent-x { border: 0; background: none; font-size: 24px; line-height: 1; cursor: pointer; color: var(--ink-soft); padding: 0 4px; }
.fb-consent-x:hover { color: var(--ink); }
.fb-consent-intro { margin: 0; padding: 14px 18px; font-size: .88rem; color: var(--ink-soft); line-height: 1.55; }
.fb-consent-list { padding: 0 18px; }
.fb-consent-empty { color: var(--ink-soft); font-size: .9rem; padding: 8px 0 16px; }
.fb-consent-cat { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-soft); margin: 10px 0 2px; }
.fb-consent-row { display: flex; align-items: center; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--line); cursor: pointer; }
.fb-consent-row-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.fb-consent-row-name { font-weight: 700; font-size: .95rem; }
.fb-consent-row-prov { font-size: .8rem; color: var(--ink-soft); }
.fb-consent-switch { position: relative; flex: 0 0 auto; width: 42px; height: 24px; }
.fb-consent-switch input { position: absolute; inset: 0; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.fb-consent-switch-tr { position: absolute; inset: 0; border-radius: 999px; background: var(--line); transition: background .18s; pointer-events: none; }
.fb-consent-switch-tr::before { content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .3); transition: transform .18s; }
.fb-consent-switch input:checked + .fb-consent-switch-tr { background: var(--c-primary); }
.fb-consent-switch input:checked + .fb-consent-switch-tr::before { transform: translateX(18px); }
.fb-consent-foot { display: flex; gap: 10px; justify-content: flex-end; padding: 16px 18px; border-top: 1px solid var(--line); position: sticky; bottom: 0; background: var(--surface); }
body.fb-consent-open { overflow: hidden; }

/* quiet placeholder — shown while the banner is the primary CTA (no buttons) */
.fb-consent.is-quiet { display: flex; align-items: center; justify-content: center; }
.fb-consent-quiet { position: relative; z-index: 1; padding: 10px 16px; border-radius: 999px; background: rgba(15, 20, 26, .5); color: #fff; font-size: .9rem; font-weight: 600; }

/* one-time consent banner (bottom, full width) */
.fb-consent-banner { position: fixed; left: 0; right: 0; bottom: 0; z-index: 70; background: var(--surface); border-top: 1px solid var(--line); box-shadow: 0 -6px 24px rgba(0, 0, 0, .14); }
.fb-consent-banner-in { max-width: var(--maxw); margin: 0 auto; padding: 14px var(--gutter); display: flex; align-items: center; gap: 18px; }
.fb-consent-banner-txt { margin: 0; font-size: .88rem; line-height: 1.5; color: var(--ink-soft); flex: 1; min-width: 0; }
.fb-consent-banner-act { display: flex; gap: 10px; flex: 0 0 auto; flex-wrap: wrap; justify-content: flex-end; }
@media (max-width: 720px) {
  .fb-consent-banner-in { flex-direction: column; align-items: stretch; gap: 12px; }
  .fb-consent-banner-act { justify-content: stretch; }
  .fb-consent-banner-act .fb-btn { flex: 1; text-align: center; }
}

/* video stage placeholder — fills the 16:9 frame; poster (self-hosted) as bg */
.fb-video-frame .fb-consent { position: absolute; inset: 0; min-height: 0; border-radius: 0; }
.fb-video-frame .fb-consent iframe { position: absolute; inset: 0; width: 100%; height: 100% !important; }
.fb-consent--poster::before { display: none; }
.fb-consent--poster { background-size: cover; background-position: center; }
/* "Eingewilligt, laedt auf Klick" in der Video-Ausfuehrung: kein Kasten, nur ein
   Abspiel-Knopf. Der Schleier bleibt sehr leicht — das Vorschaubild ist der Grund,
   warum der Platzhalter ueberhaupt stehen bleibt, ein Scrim wie beim Kartenfall
   (.28) wuerde es abdunkeln. Erst beim Ueberfahren zieht er an, damit der Knopf
   auf jedem Motiv lesbar wird. */
.fb-consent-ov--play { background: rgba(0, 0, 0, .12); transition: background .18s; }
.fb-consent-ov--play:hover, .fb-consent-ov--play:focus-visible { background: rgba(0, 0, 0, .3); }
.fb-consent-ov--play .fb-video-play {
  width: 74px; height: 74px; background: rgba(0, 0, 0, .62);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .35); transition: transform .18s, background .18s;
}
.fb-consent-ov--play .fb-video-play::before { border-width: 14px 0 14px 23px; }
.fb-consent-ov--play:hover .fb-video-play { transform: translate(-50%, -50%) scale(1.07); background: rgba(0, 0, 0, .75); }
@media (prefers-reduced-motion: reduce) {
  .fb-consent-ov--play, .fb-consent-ov--play .fb-video-play { transition: none; }
  .fb-consent-ov--play:hover .fb-video-play { transform: translate(-50%, -50%); }
}

/* Two logos, one per theme (only emitted when the tenant uploaded a separate
   dark-mode file — see logo_img()). Switched on the CLASS, not on
   prefers-color-scheme: findd has its own toggle and stores the visitor's choice,
   so a media query would show the wrong logo to everyone who flipped it.

   Only the INACTIVE one is hidden, never the active one — that way no `display`
   has to be handed back later, and the visible image keeps whatever its own class
   gives it (the footer logo is a flex item, the header one an inline image). */
html:not(.theme-dark) .site-logo--dark { display: none; }
html.theme-dark .site-logo--light { display: none; }

/* Job ad: the facts box every job board puts above the description. A grid of
   label/value pairs that collapses to two columns on a phone — the labels are
   short, so a stacked list would waste the width. */
.fb-job-facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 2px 18px; margin: 0 0 22px; padding: 16px 18px;
  background: var(--muted-bg); border: 1px solid var(--line); border-radius: var(--radius);
}
.fb-job-fact { padding: 6px 0; }
.fb-job-fact dt { font-size: .8rem; color: var(--ink-soft); letter-spacing: .02em; }
.fb-job-fact dd { margin: 2px 0 0; font-weight: 600; }
.fb-job-benefits { margin: 26px 0; }
.fb-job-benefits ul { margin: 0; padding-left: 1.2em; }
.fb-job-benefits li { margin-bottom: .35em; }
.fb-job-contact { margin: 22px 0; padding: 14px 16px; border-left: 3px solid var(--c-primary); background: var(--muted-bg); }
.fb-job-dates { margin-top: 26px; font-size: .85rem; }
/* The application form closes the page, so it may carry a little more weight
   than a plain form block — it is the one thing the visitor came to do.
   .fb-form-wrap carries its own gutter and a 640px cap for use as a standalone
   block; inside the detail body both are already applied one level up, so they
   are taken back here — the same nesting reset .fb-container does for sections
   (measured: the form sat one gutter to the right of everything else). */
.fb-job-apply {
  margin: 34px 0 0; padding: 26px 0 0; max-width: none;
  border-top: 1px solid var(--line);
}
@media (max-width: 560px) {
  .fb-job-facts { grid-template-columns: 1fr 1fr; padding: 14px; }
}

/* Review prompt after a submitted enquiry — the moment someone is most likely to
   say something nice. Quiet: it must not compete with the confirmation itself. */
.fb-form-review { display: block; margin-top: 10px; font-size: .95em; opacity: .9; }

/* --- accessibility -------------------------------------------------------- */
/* Skip link: off-screen until focused, then a normal button at the top left.
   Not display:none — that would hide it from keyboard users too, which defeats
   the purpose. */
/* Selector note: it has to be `.findd-site a.skip-link` (0-2-1), not `.skip-link`.
   The global link colour `.findd-site a:not(.fb-btn)` is 0-2-1 and would otherwise
   win — leaving primary-coloured text on a primary-coloured background, i.e. an
   invisible skip link. Measured, not guessed; same trap as the footer links. */
.findd-site a.skip-link {
  position: absolute; left: 8px; top: -60px; z-index: 100;
  background: var(--c-primary); color: #fff; padding: 10px 16px;
  border-radius: var(--radius); font-weight: 600; text-decoration: none;
  transition: top .15s;
}
.findd-site a.skip-link:focus { top: 8px; }
/* Visible focus everywhere, not just in forms. A keyboard user could previously
   not tell which link or button they were on outside a form. :focus-visible keeps
   it away from mouse clicks. */
.findd-site a:focus-visible,
.findd-site button:focus-visible,
.findd-site summary:focus-visible,
.findd-site [tabindex]:focus-visible {
  outline: 3px solid var(--c-primary);
  outline-offset: 2px;
  border-radius: 2px;
}
/* On a primary-coloured surface the primary outline would vanish — switch to the
   text colour, which is already chosen for contrast against that background. */
.fb-bg-primary a:focus-visible, .fb-bg-primary button:focus-visible,
.fb-text-light a:focus-visible, .fb-text-light button:focus-visible,
.site-header a:focus-visible, .site-header button:focus-visible { outline-color: currentColor; }
/* The <main> only takes focus programmatically (from the skip link) and must not
   outline the entire page when it does. Same specificity as the [tabindex] rule
   above, so it has to come AFTER it — order decides the tie. */
.findd-site .site-main:focus,
.findd-site .site-main:focus-visible { outline: none; }
