:root {
  --color-info: #17a2b8;
  --color-success: #28a745;
  --color-warning: #ffc107;
  --color-danger: #dc3545;
  --color-muted: #6c757d;
  --lte-sidebar-menu-active-bg: rgba(0, 0, 0, 0.1);
  --lte-sidebar-menu-active-color: #000000;

  /* --color-primary / --color-secondary are NOT defined here — they come from
     each site's theme_var('primary-color')/('secondary-color'), injected as
     an inline <style> in base.html.twig (right after this stylesheet, so it
     wins the cascade). Rules below that want a per-site brand accent use
     var(--color-primary, var(--amber)) etc. so a site with nothing configured
     still gets the original amber/navy look — see freezeprotector.com's own
     primary-color (#D97706) and secondary-color (#D9770638), which already
     match --amber and its rgba(217,119,6,.3) border exactly, confirming that
     was the original intent, just never wired up. */
  --amber:    #D97706;
  --navy:     #0F1D2E;
  --navy-mid: #162538;
  --navy-sur: #1E3046;
  --txt-dark: #0B1522;
  --white: #ffffff;
  --txt-soft: #5B6B7D;
  --ice-mid: #E2E8F0;
  
  /* Heading/mono font roles, deliberately generic here — each site
     (ecosensors.cloud, freezeprotector.com, ...) brings its own @font-face
     rule + --f-disp override in its own user/env/<site>/css/custom.css
     (loaded right after this file, see base.html.twig). Body text is
     intentionally NOT themed here — it stays on adminlte.css's own
     --bs-body-font-family (Source Sans 3) for every site; only h1-h6 opt
     into the site's special font. */
  --f-disp: var(--bs-font-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
  --f-mono: ui-monospace, Menlo, Consolas, "Courier New", monospace;
}

/* Headings: adminlte.css's h1-h6 sizes come straight from Bootstrap's
   fluid RFS scale (e.g. h1 = calc(1.375rem + 1.5vw), capped at 2.5rem
   only above 1200px) — tuned for marketing pages, but too heavy/loud for
   an app UI where headings sit above dashboard widgets and admin forms.
   clamp() replaces that with a narrower, calmer range that still scales
   a little between mobile and desktop instead of jumping straight to
   its cap. Weight/line-height are relaxed too (Bootstrap's 500/1.2 reads
   dense at these sizes); color/font-family are untouched. */
h1, h2, h3, h4, h5, h6 {
    font-family: var(--f-disp);
    font-weight: 600;
    line-height: 1.3;
}

h1, .h1 {
    font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2rem);
}

h2, .h2 {
    font-size: clamp(1.35rem, 1.05rem + 1.2vw, 1.75rem);
}

h3, .h3 {
    font-size: clamp(1.2rem, 1rem + 0.8vw, 1.5rem);
}

h4, .h4 {
    font-size: clamp(1.1rem, 0.95rem + 0.5vw, 1.25rem);
}

h5, .h5 {
    font-size: 1.1rem;
}

h6, .h6 {
    font-size: 1rem;
}

a {
    text-decoration: none;
}

h1[id], h2[id], h3[id], h4[id] {
    scroll-margin-top: 90px;
}

.text-xl {
    font-size: 2rem !important;
}

/* # features */
.hover-shadow:hover {
    transform: translateY(-5px);
    box-shadow: 0 15px 30px rgba(0,0,0,0.1);
}

.feature-icon {
  width: 50px !important;
  height: 50px !important;
  border-radius: 6px;
}

/* hero ############################################################################################ */

.hero {
    /* .ribbon-wrapper (top:-2px; right:-2px; position:absolute) is a direct
       child of .hero and needs .hero itself to be its positioning context —
       without this it anchors to whatever ancestor further up happens to be
       positioned instead, which is why it was drifting to the top of the
       page (not the top-right corner of the hero) specifically at widths
       where that "accidental" ancestor stopped applying. */
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    padding-top: 6rem;
    background-size: cover;
    background-position: 50% 0px;
    height: 650px;
}

/* Purely decorative — tints .hero's own background-image. Deliberately its
   own element (modular/hero.html.twig), separate from .image-overlay's
   content: inset:0 always matches whatever box .hero currently renders as,
   whether that's the fixed 650px on desktop or content-driven auto height
   on mobile (see below), with zero coupling to what .image-overlay's
   content needs. Two earlier attempts at this (padding-placement tricks on
   a single combined element) kept leaving a gap where .hero's raw
   background — a pale cream sky at the top of the actual image — showed
   through uncovered on narrow screens; this avoids the coupling instead of
   trying to patch around it. */
.hero-tint {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(to bottom, rgba(110, 110, 110, 0.8), rgba(110, 110, 110, 0.3));
}
[data-bs-theme=dark] .hero-tint {
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.3));
}

.image-overlay {
    position: relative;
    color: #ffffff;
    padding-top: 7rem;
    width: 100%;
}

/* .hero's text and image columns (col-12 col-md-5 each) stack vertically
   below md, needing far more total height than the fixed 650px .hero
   hardcodes above — the image was overflowing that clipped box and
   spilling into whatever section follows. .image-overlay is always
   in-flow now (see above), so .hero's auto height here grows to fit it
   correctly; .hero-tint (inset:0, absolute) automatically follows suit. */
@media (max-width: 767.98px) {
    .hero {
        height: auto;
        padding-top: 0;
        padding-bottom: 0;
    }
    .image-overlay {
        padding-top: 3rem;
        padding-bottom: 2rem;
    }
}

.hero-kpis {
  display: flex; gap: 0;
  border-top: 1px solid rgba(255,255,255,1);
  padding-top: 28px;
}
.kpi {
  flex: 1;
  padding: 0 20px 0 0;
  border-right: 1px solid rgba(255,255,255,1);
  margin-right: 20px;
}
.kpi:last-child { border-right: none; margin-right: 0; }
.kpi-val {
  font-size: 1.9rem;
  font-weight: bold;
  line-height: 1;
  color: var(--white);
  letter-spacing: .01em;
}
.kpi-val small {
  color: var(--color-primary, var(--amber));
  font-size: 1.2rem;
}
.kpi-lbl {
  font-size: .75rem; font-weight: 500;
  color: rgba(255,255,255,.7);
  margin-top: 4px;
  letter-spacing: .04em;
}

/* landingpage ############################################################################################## */

.landingpage {
    margin-top: 0;
    margin-left: calc(-1 * var(--bs-gutter-x, 1.5rem));
    margin-right: calc(-1 * var(--bs-gutter-x, 1.5rem));
    margin-bottom: calc(-1 * var(--bs-gutter-x, 1.5rem));
    padding-top: 130px;
    padding-left: 50px;
    padding-right: 50px;
    padding-bottom: 80px;
}
/* Fixed 50px side padding is fine on desktop but eats a real chunk of a
   360-430px phone viewport before any content even starts — combined with
   .how-steps' missing breakpoint above, this was the other half of "how it
   works doesn't fit". Also trims the top/bottom padding, which otherwise
   stacks up fast scrolling through six sections on a phone. */
@media (max-width: 767.98px) {
  .landingpage {
    padding-top: 70px;
    padding-left: 20px;
    padding-right: 20px;
    padding-bottom: 50px;
  }
}

.landingpage h1::before {
    display: inline-block;
    width: 22px;
    height: 2px;
    background: var(--color-primary, var(--amber));
}

.landingpage h1 {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-size: 0.9rem !important;
    font-weight: bold;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--color-primary, var(--amber)) !important;
    margin-bottom: 20px !important;
}

.landingpage h2 {
    font-size: clamp(3rem, 5.5vw, 5rem);
    font-weight: 800;
    line-height: 1.0;
    letter-spacing: .01em;
    text-transform: uppercase;
    margin-bottom: 24px;
}
.landingpage h3 {
    font-size: clamp(1.8rem, 3vw, 2.6rem);
    font-weight: 800;
    line-height: 1.1;
    letter-spacing: .01em;
    color: var(--txt-dark);
    text-transform: uppercase;
    margin-bottom: 20px;
}

.landingpage.inverted h3,
.landingpage.inverted h4 {
  color: #ffffff;
}

.landingpage.inverted p {
  color: rgba(255, 255, 255, .65);
}

.landingpage h4 {
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: .03em;
    color: var(--txt-dark);
    text-transform: uppercase;
    margin-bottom: 20px;
}

.landingpage em {
    font-style: normal;
    color: var(--color-primary, var(--amber));
    display: block;
}

section {
	background-color: var(--section-bg-light);
}

[data-bs-theme="dark"] section {
	background-color: color-mix(in srgb, var(--section-bg-light) 25%, #1f2730);
}
[data-bs-theme="dark"] .card {
	background-color: color-mix(in srgb, var(--section-bg-light) 25%, #131a21);
}




/* funktionsweise ################################################################################################# */

.how-steps {
  display: grid;
  grid-template-columns: repeat(4,1fr);
  gap: 0;
  position: relative; z-index: 1;
}
.how-steps::before {
  content: '';
  position: absolute;
  top: 36px;
  left: calc(12.5% + 24px);
  right: calc(12.5% + 24px);
  height: 1px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--color-primary, var(--amber)) 60%, transparent) 0%,
    color-mix(in srgb, var(--color-primary, var(--amber)) 20%, transparent) 100%);
}
/* Unlike the other landingpage sections (Bootstrap .row/.col-*, which
   already collapse responsively), .how-steps is a hand-rolled 4-column
   CSS grid with no breakpoint at all — on a phone that's 4 columns barely
   wider than the 72px .step-node circle each, with no room left for the
   heading/body text, which is what "doesn't fit" actually was. Stack to
   one column at the same breakpoint Bootstrap's own col-md-* uses
   elsewhere on this page, and drop the horizontal connector line — it
   spans left-to-right between columns, which stops meaning anything once
   there's only one column per row. */
@media (max-width: 767.98px) {
  .how-steps {
    grid-template-columns: 1fr;
    gap: 40px;
  }
  .how-steps::before {
    display: none;
  }
}
.step {
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
  padding: 0 16px;
  position: relative; z-index: 1;
}
.step-node {
  width: 72px; height: 72px;
  border-radius: 50%;
  background: var(--navy-mid);
  border: 2px solid color-mix(in srgb, var(--color-primary, var(--amber)) 30%, transparent);
  display: flex; align-items: center; justify-content: center;
  color: var(--color-primary, var(--amber));
  margin-bottom: 24px;
  position: relative;
  transition: all .3s;
}
.step:hover .step-node {
  border-color: var(--color-primary, var(--amber));
}
.step-n {
  position: absolute; top: -8px; right: -8px;
  width: 24px; height: 24px;
  background: var(--color-primary, var(--amber));
  border-radius: 50%;
  font-family: var(--f-mono);
  font-size: 0.88rem;
  font-weight: 700;
  /* White rather than --navy: --navy reads fine on amber (5.2:1) but poorly
     on a darker per-site primary-color like ecosensors' green (2.9:1) —
     white clears the 3:1 UI-component contrast minimum against both. */
  color: var(--white);
  display: flex; align-items: center; justify-content: center;
}
.step h3 {
  font-size: 1rem; font-weight: 700;
  color: #ffffff;
  text-transform: uppercase;
  letter-spacing: .04em;
  margin-bottom: 8px;
}
.step p {
  font-size: .82rem;
  color: rgba(255,255,255,.46);
  line-height: 1.6;
}

/* specs ################################################################################################# */

.specs-table {
  width: 100%;
  border-collapse: collapse;
}
.specs-table tr {
  border-bottom: 1px solid var(--ice-mid);
}
.specs-table tr:last-child { border-bottom: none; }
.specs-table td {
  padding: 15px 0;
  font-size: .9rem;
  vertical-align: top;
}
.specs-table td:first-child {
  color: var(--txt-soft);
  font-weight: 500;
  width: 42%;
  padding-right: 24px;
}

[data-bs-theme="dark"] .specs-table td:first-child {
	color: color-mix(in srgb, var(--navy) 25%, #131a21);
}

.specs-table td:last-child {
  font-family: var(--f-mono);
  font-size: 1rem;
  font-weight: 600;
  color: var(--txt-dark);
}
.spec-badges {
  display: flex; gap: 10px; flex-wrap: wrap;
  margin-top: 28px;
}
.sbadge {
  display: inline-flex; align-items: center; gap: 6px;
  background: rgba(217,119,6,.1);
  color: #92560A;
  font-size: .73rem; font-weight: 600;
  padding: 5px 12px;
  border-radius: 99px;
  border: 1px solid rgba(217,119,6,.2);
}
[data-bs-theme="dark"]  .sbadge {
  background: rgba(217,119,6,.1);
  color: #d88d2b;
  border: 1px solid rgba(186, 137, 82, 0.442);
}
.sbadge svg { flex-shrink: 0; }

/* Amber/orange text accent - Bootstrap only ships a yellow-ish
   .text-warning, no distinct "orange", but the documentation pages use a
   4-level severity scale (GEFAHR=rot, WARNUNG=orange, VORSICHT=gelb,
   HINWEIS=grün) for their Line Awesome icons, e.g.
   <i class="las la-exclamation-triangle text-amber"></i>. Lightened for
   dark mode the same way .sbadge already is above, for readable
   contrast on a dark background. */
.text-amber {
  color: var(--amber) !important;
}
[data-bs-theme="dark"] .text-amber {
  color: #d88d2b !important;
}

/* Small feature-checklist cards (documentation_tables.html.twig). The
   generic ".card" dark-mode rule mixes toward --section-bg-light, a
   variable only ever set inline on a <section> wrapper (see the
   landingpage's modular/features.html.twig) - outside a <section> it's
   unset, so that color-mix() is invalid and the card silently falls back
   to Bootstrap's default (--bs-card-bg: var(--bs-body-bg)), i.e. the same
   color as the page behind it. --bs-secondary-bg is Bootstrap's own
   already-theme-aware "raised panel" gray (#343a40 in dark mode here) -
   a neutral gray rather than this site's navy/amber brand accent, which
   reads as a mismatched blue tint outside the hero/landingpage context
   these cards actually live in. Only overridden in dark mode; light mode
   keeps the plain white .card background untouched. */
[data-bs-theme="dark"] .feature-card-sm {
  background-color: var(--bs-secondary-bg);
}




/* ribbons ################################################################################################# */
.ribbon-wrapper.ribbon-xl {
    height: 180px;
    width: 180px;
}
.ribbon-wrapper.ribbon-xxl {
    height: 280px;
    width: 280px;
}
.ribbon-wrapper {
    height: 70px;
    overflow: hidden;
    position: absolute;
    right: -2px;
    top: -2px;
    width: 70px;
    z-index: 10;
}
.ribbon-wrapper.ribbon-xl .ribbon {
    right: 4px;
    top: 47px;
    width: 240px;
}
.ribbon-wrapper.ribbon-xxl .ribbon {
    right: 4px;
    top: 78px;
    width: 350px;
}
.ribbon-wrapper .ribbon {
    box-shadow: 0 0 3px rgba(0, 0, 0, .3);
    font-size: .8rem;
    line-height: 100%;
    padding: .375rem 0;
    position: relative;
    right: -2px;
    text-align: center;
    text-shadow: 0 -1px 0 rgba(0, 0, 0, .4);
    top: 10px;
    -webkit-transform: rotate(45deg);
    transform: rotate(45deg);
    width: 90px;
}
.ribbon-link {
    color: #ffffff;
}

/* badges ############################################################################################ */

/* .badge a {
    color: #ffffff;
}
.badge {
    display: inline-block;
    margin-right: 5px;
    border-radius: .25rem;
    white-space: nowrap;
    font-size: 75%;
    font-weight: 700;
    text-align: center;
    vertical-align: baseline;
    padding: .25em .4em;
}
.badge-danger {
    background-color: #dc3545;
}
.badge-primary {
    background-color: #007bff;
} */


/* logo ############################################################################################## */

.sidebar-brand {
    border-bottom: none;
}

.sidebar-brand .brand-link .brand-image-xs {
  max-height: 55px;
}

.sidebar-brand .brand-link .brand-image-xl {
  height: 55px;
}

/* header logo — navigation_top.html.twig reuses the exact same
   sidebar-brand/brand-link/brand-image-xs/logo-xl markup as
   .app-sidebar's collapse/expand brand swap above, but without any of
   the supporting layout that makes it work there (a reserved-size
   wrapper for .logo-xl's position:absolute;top:12px;left:18px from
   adminlte.css to anchor against). In the sidebar that's harmless — it's
   pinned within a reserved area. In the top nav it has no positioned
   ancestor of its own to anchor to, so it anchors against whichever
   positioned ancestor it finds. That became .app-header itself once it
   gained container-type:inline-size for the adaptive-nav container
   query (which also establishes a containing block for absolutely
   positioned descendants), landing the logo straight on top of the
   nav-center links right next to it. Same fix as the footer logo bug
   below: pin it back into normal flow so it actually reserves space
   instead of floating over its siblings. */
.app-header .sidebar-brand .brand-link .brand-image-xs {
  position: static;
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 55px;
}

.app-header .sidebar-brand .brand-link .brand-image-xl {
  display: none;
}

/* dark-mode logo/favicon swap — logo.html.twig only emits a .logo-scheme-light/
   .logo-scheme-dark pair when custom_logo_dark/custom_favicon_dark are configured; with no
   dark variant set it renders the single unclassed light image as before, so these rules
   have nothing to match and are a no-op. When both are present, the two images share the
   exact same brand-image-xs/xl + logo-xl/xs classes and positioning above, so whichever one
   this hides just drops out — the other is left to the normal cascade (collapse/expand
   crossfade, header/footer full-logo-only overrides) exactly as an unscoped image would be.
   !important is needed to beat those more specific display rules. Keyed off data-bs-theme
   (set by the theme-changer script) rather than prefers-color-scheme, and :not("dark") rather
   than ="light" so it still defaults to the light logo for the "auto" attribute value the
   theme-changer script can set (see base.html.twig). */
:root:not([data-bs-theme="dark"]) .logo-scheme-dark {
  display: none !important;
}
[data-bs-theme="dark"] .logo-scheme-light {
  display: none !important;
}


/* toc ############################################################################################## */

.sticky-column {
    position: sticky;
    top: 80px; /* offset for the fixed AdminLTE header (.app-header) so TOC isn't hidden under it */
    max-height: calc(100vh - 100px);
    overflow-y: auto;
}

#page-toc-nav .nav-sidebar a {
    color: var(--lte-sidebar-menu-active-color);

}
#page-toc-nav .nav-sidebar .nav-link.active,
#page-toc-nav .nav-sidebar .nav-link.active .nav-icon {
    background-color: var(--lte-sidebar-menu-active-bg);
    color: var(--lte-sidebar-menu-active-color);

}

/* --lte-sidebar-menu-active-color/-bg (set at :root above) are black /
   transparent-black by design for the real AdminLTE sidebar, which gets
   its own dark-mode swap to white / transparent-white scoped to
   [data-bs-theme="dark"] .app-sidebar (see adminlte.css). #page-toc-nav
   reuses the same nav-sidebar classes for a consistent look but isn't
   inside .app-sidebar, so it never picked up that swap and stayed black
   text on a dark background. Mirror the same swap here, scoped to the TOC. */
[data-bs-theme="dark"] #page-toc-nav .nav-sidebar a {
    color: var(--bs-body-color);
}
[data-bs-theme="dark"] #page-toc-nav .nav-sidebar .nav-link.active,
[data-bs-theme="dark"] #page-toc-nav .nav-sidebar .nav-link.active .nav-icon {
    background-color: rgba(255, 255, 255, 0.1);
    color: #fff;
}

/* footer logo ############################################################################################## */

/* logo.html.twig is built for the sidebar's collapse/expand animation, where
   both images are absolutely positioned and swapped via .sidebar-collapse.
   The footer has no such toggle, so pin the logo back into normal document
   flow instead of relying on absolute positioning + a fixed padding-top to
   "clear" it — that gap was only ~3px (top:12px + max-height:55px vs a 70px
   padding-top) and broke as soon as the tagline wrapped differently,
   letting the logo bleed into the link columns below it at some widths. */
.footer-brand .brand-link {
  display: inline-block;
}

.footer-brand .logo-xl.brand-image-xs {
  position: static;
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 55px;
  margin-bottom: 0.75rem;
}

.footer-brand .logo-xs.brand-image-xl {
  display: none;
}


/* for images ################################################################################################### */

.image-with-caption {
  margin: 0;
  text-align: center;
}

.image-with-caption img {
  max-width: 100%;
  height: auto;
  display: block;
  margin: 0 auto; /* centers the image */
}
.image-with-caption {
    border-radius: 5px;
    padding: 1rem 1rem 1rem 1rem;
}
[data-bs-theme=light] .image-with-caption {
    background: #ffffff;
    border: 1px solid #ccc;
    filter: drop-shadow(0.75rem 0.75rem 2rem rgba(0, 0, 0, 0.15));
}
[data-bs-theme=dark] .image-with-caption {
    background: #1e1e1e;
    border: 1px solid #333;
    filter: drop-shadow(0.75rem 0.75rem 2rem rgba(0, 0, 0, 0.6));
}
.image-caption {
  margin-top: 0.4rem;
  font-size: 0.85rem;
  color: #6c757d;
  text-align: center;
  font-style: italic;
  letter-spacing: 0.02em;
  opacity: 0.8;
}


/* featherlight images ################################################################################################# */
.featherlight-content {
  display: flex;
  flex-direction: column;
  max-height: 85vh;
}

.featherlight-inner {
  max-height: calc(85vh - 6rem); /* leave space for caption */
  width: auto;
  object-fit: contain;
}

.featherlight .caption {
  margin-top: 0.4rem;
  font-size: 0.85rem;
  color: #6c757d;
  text-align: center;
  font-style: italic;
  letter-spacing: 0.02em;
  opacity: 0.8;
}
 
/* Login ############################################################################################## */
.login-box {
    max-width: 100%;
    min-width: 30%;
}
.login-logo h1 {
    font-weight: 100 !important;
    font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
}

/* error pages */
.error-page {
    margin: 20px auto 0;
    max-width: 700px;
    width: 100%;
    padding: 0 15px;
    box-sizing: border-box;
}
.error-page>.headline {
    float: left;
    font-size: 100px;
    font-weight:500;
}
.error-page>.error-content {
    display: block;
    margin-left: 190px;
}
@media (max-width: 767.98px) {
    .error-page>.headline {
        float: none;
        font-size: 60px;
        text-align: center;
    }
    .error-page>.error-content {
        margin-left: 0;
        text-align: center;
    }
}


/* cards ############################################################################################ */

.card {
    border-radius: 6px;
    box-shadow: none;
}
.card-header {
  display: flex;
}


/* nav ########################################################################################### */

.navbar .dropdown-menu {
  width: max-content;
  min-width: 200px;
}

.nav-item {
  text-wrap: nowrap;
}

.dropdown-menu .nav-link {
  white-space: nowrap;
}

.navbar-center a:hover {
    text-decoration: underline;
}

.app-header .navbar-nav > .nav-item > .nav-link {
    font-size: 1.2rem;
    font-weight: 500;
}

/* Adaptive top nav ##########################################################
   Bootstrap's navbar-expand-lg collapses at a fixed 992px *viewport* width,
   with no regard for how much room the nav content itself needs — which
   varies with the number of top-nav links (has changed more than once this
   build) and with the active language (German/French labels run noticeably
   longer than English). Below that width the toggler hasn't appeared yet but
   the full link row + logo + language switcher + login button no longer fit,
   so they visibly overlap instead of wrapping to the hamburger menu.

   Fix: query the header's own rendered width via a CSS container instead of
   the viewport. This still needs a threshold — a container query isn't a
   literal "does the content overflow" check, just a width check against the
   right variable instead of the wrong one — but now it's immune to anything
   that changes viewport-to-header-width mapping (sidebars, page padding,
   embedding), and reacts to the header's *actual* available space rather
   than an unrelated proxy for it. THRESHOLD below is deliberately generous
   for today's link count in the longest (German) label set; if more nav
   items get added later and it starts collapsing too late again, this is
   the one number to raise.

   .app-header carries navbar-adaptive instead of Bootstrap's navbar-expand-lg
   (see navigation_top.html.twig) — the base .navbar/.navbar-collapse/
   .navbar-toggler classes still provide the actual show/hide mechanics via
   Bootstrap's own Collapse JS component, unchanged; only the *breakpoint*
   deciding which state applies is replaced here. */
.app-header {
    container-type: inline-size;
    container-name: appnav;
}

/* Flex items shrink by default (flex-shrink: 1), so as the header narrows,
   the brand <ul> — sized only by its logo image's own width:auto — was
   shrinking right along with everything else, scaling the logo down before
   the link row ran out of room. The logo should stay a constant size
   regardless of viewport; it's the link row's fit (or lack of it) that
   should decide when to collapse to the hamburger menu, not the other way
   around. Unconditional (not inside a nav-fits/@container block) since the
   brand <ul> is always .container-fluid's first direct child in every
   layout state. */
.app-header > .container-fluid > .navbar-nav {
    flex-shrink: 0;
}

@container appnav (min-width: 1200px) {
    .navbar-adaptive {
        flex-wrap: nowrap;
        justify-content: flex-start;
    }
    .navbar-adaptive .navbar-nav {
        flex-direction: row;
    }
    .navbar-adaptive .navbar-nav .dropdown-menu {
        position: absolute;
    }
    .navbar-adaptive .navbar-nav .nav-link {
        padding-right: var(--bs-navbar-nav-link-padding-x);
        padding-left: var(--bs-navbar-nav-link-padding-x);
    }
    .navbar-adaptive .navbar-nav-scroll {
        overflow: visible;
    }
    .navbar-adaptive .navbar-collapse {
        display: flex !important;
        flex-basis: auto;
        /* Bootstrap's own .navbar > .container-fluid uses
           justify-content: space-between across its 3 direct children
           (brand, toggler, this collapse div), which by itself only
           pushes .navbar-collapse flush right, sized to its own content
           — it does NOT stretch to fill the remaining width. The old
           absolute-position/transform trick on .navbar-center below used
           to paper over that by ignoring flex layout entirely. flex-grow
           here makes .navbar-collapse actually span the leftover space
           instead, which is what makes margin:auto centering on
           .navbar-center below have any free space to center within. */
        flex-grow: 1;
    }
    .navbar-adaptive .navbar-toggler {
        display: none;
    }

    /* Was position:absolute + translate(-50%,-50%) — "true" centering
       that ignored the brand/controls siblings entirely, which is
       exactly how it could sit directly on top of them (visual overlap)
       at widths where the combined content was too wide, without ever
       registering as overflow (scrollWidth only ever sees *overflow*,
       never *overlap* — an absolutely positioned element doesn't push
       or get pushed by anything). margin:auto keeps this in normal flex
       flow: siblings can only ever sit side by side, wrap, or overflow —
       never silently overlap — and overflow IS something the adaptive
       nav JS (base.html.twig) can actually detect and react to. */
    .navbar-nav.navbar-center {
        flex-direction: row;
        margin-left: auto;
        margin-right: auto;
    }
}

@container appnav (max-width: 1199.98px) {
    #mainNavCollapse .navbar-nav .nav-item {
        border-bottom: 1px solid rgba(0, 0, 0, .06);
    }
    #mainNavCollapse .navbar-nav .nav-link {
        padding: 12px 4px;
    }
    #mainNavCollapse .navbar-nav .nav-item:last-child {
        border-bottom: none;
    }
}

/* JS override (base.html.twig) — the @container rules above are a static
   guess and remain the pre-JS/no-JS fallback, but once the nav's actual
   content has been measured against its actual available width, that gets
   the final say in either direction: .nav-fits force-expands even below
   1200px when there's genuinely room, .nav-overflow force-collapses even
   above it if unusually long content still doesn't fit. !important to
   reliably win over the @container block's own !important declarations
   regardless of which condition also happens to be true at the same time.

   .nav-fits/.nav-overflow are applied to .app-header itself (same element
   as .navbar-adaptive, NOT an ancestor of it — the very first version of
   this rule wrote ".app-header.nav-fits .navbar-adaptive" as if it were a
   descendant, which never matched anything and silently dropped the
   flex-wrap override while the other, genuinely-descendant rules below
   still applied — a broken partial state that reintroduced the overlap
   bug this whole thing exists to fix). */
.app-header.nav-fits {
    flex-wrap: nowrap !important;
    justify-content: flex-start !important;
}
.app-header.nav-fits .navbar-nav {
    flex-direction: row !important;
}
.app-header.nav-fits .navbar-nav .dropdown-menu {
    position: absolute !important;
}
.app-header.nav-fits .navbar-nav .nav-link {
    padding-right: var(--bs-navbar-nav-link-padding-x) !important;
    padding-left: var(--bs-navbar-nav-link-padding-x) !important;
}
.app-header.nav-fits .navbar-collapse {
    display: flex !important;
    flex-basis: auto !important;
    flex-grow: 1 !important;
}
.app-header.nav-fits .navbar-toggler {
    display: none !important;
}
.app-header.nav-fits .navbar-nav.navbar-center {
    flex-direction: row !important;
    margin-left: auto !important;
    margin-right: auto !important;
}

.app-header.nav-overflow .navbar-collapse {
    display: none !important;
}
/* Without this, force-collapsing would also break the hamburger button's
   own ability to open the menu — Bootstrap's Collapse JS still adds .show
   on click regardless of which layout mode is active. More specific than
   the plain .navbar-collapse rule above, so it wins even among
   !important declarations. */
.app-header.nav-overflow .navbar-collapse.show {
    display: block !important;
}
.app-header.nav-overflow .navbar-toggler {
    display: block !important;
}

/* Icon cluster spacing (langswitcher / theme switcher / user avatar,
   navigation_top.html.twig + langswitcher.html.twig, each marked
   .nav-cluster-item): Bootstrap's default nav-link horizontal padding is
   1rem each side, and with these three sitting flush against each other
   (no dedicated gap of their own - each item's own nav-link padding IS
   the visual space between them) that read as loose/uneven. Tightened to
   0.5rem.

   Redefining the *variable* rather than overriding padding-left/right
   directly: the @container/nav-fits rules above both set
   `padding-right/left: var(--bs-navbar-nav-link-padding-x)` on
   `.navbar-nav .nav-link`, the nav-fits one with !important (see
   .app-header.nav-fits .navbar-nav .nav-link) - a plain padding override
   here would lose to that in the common expanded-desktop state. Redefining
   the variable's value on these items instead still wins regardless,
   since the !important rule reads whatever the variable currently
   resolves to. */
.navbar-nav.ms-auto > .nav-cluster-item > .nav-link,
.navbar-nav.ms-auto > .nav-cluster-item > .btn.nav-link {
    --bs-navbar-nav-link-padding-x: 0.5rem;
}

/* Theme switcher icon: base.html.twig's showActiveTheme() copies the
   selected option's <i> classes onto this one so it shows the right
   glyph (sun/moon/adjust) — it now strips the dropdown-item-only
   utilities (me-2, fs-5) before doing that, but pin the size here too
   so it always matches the flag/nav-link icons around it regardless of
   what the dropdown items carry in the future.

   1.3rem, not 1rem: the toggle's dropdown caret was dropped (no
   `.dropdown-toggle` class on #bd-theme any more in navigation_top.
   html.twig — the dropdown still opens fine via data-bs-toggle=
   "dropdown" alone, that class only drew the arrow) to match the
   language switcher's plain icon+label look, and at 1rem the Line
   Awesome sun/moon/adjust glyphs read visibly smaller next to the
   langswitcher's flag-icon rectangles (those fill a 1.333em x 1em box;
   the glyphs carry more internal padding) — bumped up a notch to match
   that visual weight. */
.theme-icon-active i {
    font-size: 1.3rem;
    line-height: 1;
    /* Line Awesome's sun/moon/adjust glyphs each sit a couple px above
       their own font-metrics center (an artifact baked into the font
       itself, not this layout - it scales with font-size, e.g. it was
       ~2.2px at the old 1.4rem and shrank to ~1.5px at today's 1.3rem),
       so even with the flex-centered button around it (#bd-theme) the
       icon reads slightly high next to the flag icon beside it. Nudge
       it down to compensate. */
    position: relative;
    top: 1.5px;
}

/* debug-flag badges ###########################################################
   Dev/debug state indicators (theme dev mode, debugger on, cache off,
   error display on) — badges in the top nav instead of the corner ribbon
   base.html.twig used to render, so they stay legible at any page width
   and don't compete with page content for the same corner. Tooltip text
   comes from the title attribute, see base.html.twig's Bootstrap
   Tooltip init. */
.debug-flag-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    font-size: .8rem;
    cursor: default;
}

/* Actionable variant (see navigation_top.html.twig's can_fix_debug_flags
   gate) is a real <button>, not a <span> — undo the browser's default
   button chrome (border/background/font) that .badge alone doesn't
   reset, then layer on a hover/busy state so it reads as clickable and
   gives feedback while its PATCH request to the api plugin is in
   flight. */
button.debug-flag-badge {
    appearance: none;
    -webkit-appearance: none;
    border: 0;
    font: inherit;
}
.debug-flag-badge--actionable {
    cursor: pointer;
    transition: filter .15s ease;
}
.debug-flag-badge--actionable:hover,
.debug-flag-badge--actionable:focus-visible {
    filter: brightness(1.2);
}
.debug-flag-badge--actionable.is-busy {
    cursor: progress;
    opacity: .5;
    pointer-events: none;
}

/* Primary buttons, site-wide ##################################################
   Bootstrap's .btn-primary is the framework's own default blue everywhere it
   appears on this site — the top-nav login link, the login/signup/forgot-
   password/magic-link form submits, every Form-plugin "Submit"/"Save" button
   (settings, contact forms, device registration, ...) — never wired up to
   the site's own accent (var(--color-primary, var(--amber)), the same token
   every other CTA/highlight already uses: ribbons, headings, step-nodes, kpi
   values). Recolors every .btn-primary to that token and rounds it to a
   pill, echoing the rounded-pill badges elsewhere in the nav. flex+gap here
   is only for buttons that carry a leading <i> icon (see navigation_top.
   html.twig and forms/layouts/button/default-button.html.twig) — a no-op
   for any .btn-primary that doesn't have one. Bootstrap's own .btn padding
   already sizes these consistently; nothing else needed changing. */
.btn-primary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    color: #fff;
    background-color: var(--color-primary, var(--amber));
    border-color: var(--color-primary, var(--amber));
    border-radius: 999px;
}
.btn-primary:hover,
.btn-primary:focus-visible {
    color: #fff;
    background-color: color-mix(in srgb, var(--color-primary, var(--amber)) 85%, black);
    border-color: color-mix(in srgb, var(--color-primary, var(--amber)) 85%, black);
}
.btn-primary.active,
.btn-primary:active {
    background-color: color-mix(in srgb, var(--color-primary, var(--amber)) 80%, black);
    border-color: color-mix(in srgb, var(--color-primary, var(--amber)) 80%, black);
}

/* Secondary/danger buttons — shape only #########################################
   .btn-primary above got recolored AND reshaped; these are the buttons that
   were deliberately left square-cornered at the time (logout, the 2FA/reset-
   form "Cancel"/"Formular zurücksetzen" outline buttons, settings.html.twig's
   per-device outline links) because their color is semantic (danger = a
   destructive/exit action, secondary = "not the form's main CTA") and
   shouldn't become the brand accent. But being the only square-cornered
   buttons left next to a page full of pill-shaped ones reads as
   unfinished/inconsistent, not intentional - round these the same way,
   color untouched. Doesn't touch plain .btn/.btn-link (search box submit,
   captcha reload, theme-toggle dropdown) - those are icon-only utility
   controls embedded in input groups/dropdowns, not part of this CTA family. */
.btn-secondary,
.btn-outline-secondary,
.btn-danger,
.btn-outline-danger {
    border-radius: 999px;
}

/* Both the small nav-bar ring avatar and the big dropdown-header avatar
   ARE sized into a true square box by adminlte.css itself (.navbar-nav >
   .user-menu .user-image: 2rem/2rem; > li.user-header > img: 90px/90px -
   confirmed in the compiled CSS, so border-radius:50% already draws a
   real circle, not the settings-page avatar's old bug where the box
   itself wasn't square at all). What adminlte.css never sets is
   object-fit, though - the browser's default (fill) STRETCHES a non-
   square source photo to fill that square box instead of cropping it,
   which is what actually reads as "wrong aspect ratio": a perfectly
   circular frame with visibly warped image content inside it. cover
   crops instead of stretching, same fix .settings-avatar below applies
   for the same underlying reason on the settings page's own avatar. */
.navbar-nav > .user-menu .user-image {
    object-fit: cover;
    /* adminlte.css bakes a `margin-top: -2px` into this rule to hand-tune
       the avatar's vertical position for stock AdminLTE's nav-link
       metrics. This theme's toggle is flex-centered instead (see
       navigation_top.html.twig's d-flex align-items-center on the
       .user-menu toggle <a>), so that legacy offset just under-corrects
       and leaves the avatar sitting visibly low against the icon/flag
       cluster beside it - zero it out and let flex do the centering. */
    margin-top: 0;
}
.navbar-nav > .user-menu > .dropdown-menu > li.user-header > img {
    object-fit: cover;
}

/* user-menu avatar frame ####################################################
   Blue ring+shield for a plain administrator, amber/gold for a super admin
   (either ACL scheme - see navigation_top.html.twig's is_super_admin/
   is_administrator). Deliberately fixed brand-agnostic colors (not
   var(--color-primary), which is per-site and could be anything) so the
   meaning stays recognizable across every site this theme serves; amber
   reuses the site's own --amber token since gold/amber IS the intended
   color regardless of site branding. */
/* outline, not box-shadow: adminlte.css's `.shadow` utility (already on
   this <img>, for the drop shadow) sets box-shadow with !important, which
   would silently replace rather than combine with a second box-shadow
   here regardless of selector specificity. outline draws independently
   of box-shadow/border and isn't fought over by any utility class. */
.user-image.avatar-frame--admin {
    outline: 3px solid #0d6efd;
    outline-offset: 2px;
}
.user-image.avatar-frame--super {
    outline: 3px solid var(--amber, #f59e0b);
    outline-offset: 2px;
}

/* The big dropdown-header avatar is sized/bordered by adminlte.css's
   `.navbar-nav > .user-menu > .dropdown-menu > li.user-header > img` - a
   direct-child selector wrapping the <img> would silently break (see
   navigation_top.html.twig's comment there). Recoloring the border THAT
   rule already draws, via a modifier class on the <li> itself, keeps the
   existing sizing intact. */
.navbar-nav > .user-menu > .dropdown-menu > li.user-header.avatar-frame--admin > img {
    border-color: #0d6efd;
}
.navbar-nav > .user-menu > .dropdown-menu > li.user-header.avatar-frame--super > img {
    border-color: var(--amber, #f59e0b);
}
.navbar-nav > .user-menu > .dropdown-menu > li.user-header {
    position: relative;
}
/* Positioned against the avatar's own fixed geometry (90px circle, 10px
   li padding - see adminlte.css above), not the <li>'s width, so `left:
   50%` + a fixed px offset lands on the circle's bottom-right edge
   regardless of the dropdown's own width. */
.avatar-frame__badge {
    position: absolute;
    top: 74px;
    left: calc(50% + 19px);
    z-index: 6;
    font-size: 1.1rem;
    background: var(--bs-body-bg, #fff);
    border-radius: 50%;
    padding: 2px;
    line-height: 1;
}
.user-header.avatar-frame--admin .avatar-frame__badge {
    color: #0d6efd;
}
.user-header.avatar-frame--super .avatar-frame__badge {
    color: var(--amber, #f59e0b);
}

/* Card layout shared by settings.html.twig and login.html.twig - reuses
   user-dashboard's own .dashboard-grid/.dashboard-widget classes for the
   exact same card look as /app/dashboard. That plugin only queues
   dashboard.css itself when page.template == 'dashboard' AND the visitor
   is authenticated (onTwigSiteVariables()'s own guard) - neither holds
   on these two pages, so each queues the asset explicitly instead (see
   either template's own comment). These aren't real dashboard widgets
   though - no header controls, no resize/reorder/hide - so
   dashboard-widget--static turns off the "grab to drag" cursor
   affordance dashboard.css sets unconditionally on .dashboard-widget,
   since nothing here is actually draggable. */
.dashboard-widget--static {
    cursor: default;
}
.settings-grid {
    margin-top: 1rem;
}
.settings-devices-table td {
    vertical-align: middle;
}

/* "Kontostatus" card's own collapsible groups (Konto / Benachrichtigungen,
   settings.html.twig) - plain Bootstrap accordion markup, just reassigning
   its own CSS custom properties to the site's configured brand accent
   (var(--color-primary, var(--amber)), the same per-site-accent token
   every other rule in this file uses - see this file's own docblock at
   the top) instead of Bootstrap's default blue, so an expanded group's
   header tints with the site's actual brand color. Scoped to
   .settings-accordion rather than overridden globally - the chevron icon
   itself is an embedded SVG data-uri with its own hardcoded stroke color
   and isn't practically re-themeable via a CSS variable, so it stays
   Bootstrap's default regardless. */
.settings-accordion {
    --bs-accordion-active-color: var(--color-primary, var(--amber));
    --bs-accordion-active-bg: color-mix(in srgb, var(--color-primary, var(--amber)) 12%, transparent);
    --bs-accordion-btn-focus-box-shadow: 0 0 0 0.2rem color-mix(in srgb, var(--color-primary, var(--amber)) 25%, transparent);
}

/* Login page - a single card with nothing to share its row with, so the
   grid becomes a centered flex container instead of the 12-column grid
   .dashboard-grid otherwise sets up (grid-column: span N on the card
   itself is simply inert under flex, no separate override needed there). */
.login-grid {
    margin-top: 1rem;
    display: flex;
    justify-content: center;
}
.login-grid .dashboard-widget {
    width: 100%;
    max-width: 26rem;
}

/* Hero image next to a short form (contact/signup/login/forgot_password/
   login_request) - img-fluid alone stretches to the column's full width,
   which on these pages is a plain 1200x900 canvas with a small centered
   icon and lots of transparent padding (the theme's bundled *-default.png
   fallbacks), so it ballooned to ~450px wide with a tiny glyph floating
   inside it, way taller than the form card next to it - see the two-
   column .row in those templates. max-width caps it to a sane size and
   centers it; the modifier below shrinks it further and drops the photo-
   style rounded/shadow treatment for the bundled icon specifically (a
   real per-site uploaded photo - signup_image/contact_image/login_image/
   forgot_image - keeps that, just also capped so it can't blow out a
   short form's row either). Pair with align-items-center on the row
   itself so the (now much shorter) image centers vertically against the
   form card instead of sitting top-anchored with empty space below it. */
.auth-hero-image {
    display: block;
    max-width: 320px;
    width: 100%;
    height: auto;
    margin: 0 auto;
}
.auth-hero-image--icon {
    max-width: 300px;
    opacity: 0.85;
}

/* Settings-page avatar (forms/fields/avatar/avatar.html.twig override) -
   same admin/super-admin ring+shield language as the account popup's own
   avatar above, just sized/positioned for a standalone form field
   instead of the popup's fixed 90px header structure.

   width+height+object-fit:cover is the actual fix for "the circle
   renders as an oval": the original field template only ever constrained
   max-width, leaving height auto - border-radius:50% (via Bootstrap's
   rounded-circle, already on this <img> via field.img_classes) only
   draws a true circle when the box itself is square, so any avatar that
   isn't already exactly square (or the theme/site ever changes the
   Gravatar fallback's own ?s= size) rendered elliptical. Forcing an
   explicit square box here crops to fit instead of stretching. */
.settings-avatar {
    width: 160px;
    height: 160px;
    object-fit: cover;
}
.settings-avatar-frame {
    position: relative;
    display: inline-block;
}
.settings-avatar-frame.avatar-frame--admin .settings-avatar {
    outline: 3px solid #0d6efd;
    outline-offset: 2px;
}
.settings-avatar-frame.avatar-frame--super .settings-avatar {
    outline: 3px solid var(--amber, #f59e0b);
    outline-offset: 2px;
}
/* Overrides .avatar-frame__badge's own top/left (positioned for the
   popup's fixed 90px circle) - higher specificity via this descendant
   selector, not !important, so source order doesn't matter. */
.settings-avatar-frame .avatar-frame__badge {
    top: auto;
    left: auto;
    bottom: 4px;
    right: 4px;
    font-size: 1.4rem;
}
.settings-avatar-frame.avatar-frame--admin .avatar-frame__badge {
    color: #0d6efd;
}
.settings-avatar-frame.avatar-frame--super .avatar-frame__badge {
    color: var(--amber, #f59e0b);
}

/* user-menu meta (member-since / groups) + sensor list ######################
   Both sit in the account dropdown, between the header and the footer -
   see navigation_top.html.twig. */
.user-menu-meta__row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .35rem;
    line-height: 1.6;
}
.user-menu-meta__row + .user-menu-meta__row {
    margin-top: .25rem;
}
.user-menu-sensors .dropdown-item {
    display: flex;
    align-items: center;
    gap: .4rem;
    white-space: normal;
}
/* group-restricted nav links #################################################
   A page whose header.permissions.groups names a usergroup (this site's own
   group-ACL, not Grav's classic access.*, see macros.html.twig's can_access
   checks) gets colored by that restriction's first-listed group - resolved
   once per page by Adminlte::getNavGroupBadge() (adminlte.php) from the
   admin-editable `group_colors` theme setting (blueprints.yaml) - so the
   people who DO qualify for a gated link get a visible cue that it's
   special, not just people who don't qualify having it hidden outright.
   "First group wins" when several are listed is a deliberate simple
   tie-break, not an attempt to blend/average colors.

   The color itself travels as a --group-color custom property set once on
   the <a> (see macros.html.twig's nav_group_badge() call sites) and read by
   both rules below via inheritance, rather than being repeated per rule.
   The color declaration needs !important because some call sites (the
   group-badge icon-color inline styles set alongside nav_group_badge()
   throughout macros.html.twig) already set an inline `color:` themselves,
   and !important is the only way an external rule outranks *any* inline
   style, including ones this class doesn't control. */
.nav-link--group-restricted {
    color: var(--group-color) !important;
    font-weight: 700;
}
.nav-link--group-restricted:hover,
.nav-link--group-restricted:focus-visible {
    filter: brightness(1.15);
}

/* Small pill/dot rendered next to the label. A configured badge_text (e.g.
   "Beta") shows as a real pill; when it's unset the pill collapses to a
   plain colored dot (nav-group-badge--dot) instead of an empty floating
   capsule, so an unlabeled group restriction still reads as "gated" without
   looking like a rendering bug. */
.nav-group-badge {
    background-color: var(--group-color);
    color: #fff;
    font-size: .65rem;
    line-height: 1;
    vertical-align: middle;
    margin-left: .4rem;
    cursor: default;
}
.nav-group-badge--dot {
    display: inline-block;
    width: .5rem;
    height: .5rem;
    padding: 0;
    border-radius: 50%;
    vertical-align: middle;
}

/* Sidebar section headers (side_nav_sections() in macros.html.twig) - the
   top-level accordion rows (Dashboard, Dokumentation, Shop, ...). Restyled
   as a small uppercase/letter-spaced "headline" label - closer to a plain
   AdminLTE .nav-header, per a reference screenshot the user gave of that
   plain-label style (icon-less, arrow-less, muted grey) - instead of
   looking like an ordinary nav link.
   The `>` combinators mirror adminlte.css's own .sidebar-wrapper .sidebar-menu
   > .nav-item > .nav-link selector (13064 in adminlte.css) at the same
   specificity, so only the top-level rows are affected - nested treeview
   items (the actual page links under each section) keep their normal
   link/icon/arrow styling. Click-to-expand/collapse behavior itself is
   untouched (still the same <a data-lte-toggle="treeview">, just with less
   drawn on top of it) - hiding the icon/arrow is display:none, not removing
   the elements, and nothing here touches adminlte.js's click handling. */
.sidebar-wrapper .sidebar-menu > .nav-item > .nav-link > p {
    /* 12.48px read as too small next to the 16px child links (Sicherheits-
       hinweise, Installation, ...) it's heading - a section label should
       hold its own in that context, not look like a caption. .95rem
       (15.2px) keeps it a hair under the children so the size hierarchy
       still reads top-to-bottom, while uppercase/letter-spacing/weight
       and the muted opacity below do the rest of the "this is a label,
       not a link" distinction instead of undersizing it. */
    font-size: .95rem;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    /* No icon in front any more (see below) to eat this - the label should
       start flush at the link's own left padding, same as adminlte.css's
       plain .nav-header, not indented as if an icon were still there. */
    padding-left: 0;
}
/* Icon (icon_icon's <i class="las la-...">, or icon_image's <img
   class="menu-icon">) is always a direct child of .nav-link, before the
   <p> - the nav-arrow <i> lives inside the <p> instead, so this `> i`/
   `> img` combinator can't accidentally hide that too. */
.sidebar-wrapper .sidebar-menu > .nav-item > .nav-link > i,
.sidebar-wrapper .sidebar-menu > .nav-item > .nav-link > img.menu-icon {
    display: none;
}
.sidebar-wrapper .sidebar-menu > .nav-item > .nav-link .nav-arrow {
    display: none;
}
/* Muted grey rather than the full sidebar-text color, so a section heading
   reads as a label *about* the group below it rather than another link in
   it - the visual separation the user asked for between "section" and
   "pages inside the section". Opacity, not a flat grey hex, so it stays
   correct relative to both the light and dark sidebar backgrounds (and to
   nav-link--group-restricted's red admin marker, which just comes out as a
   softer red here rather than needing a separate override) without needing
   its own light/dark var pair. */
.sidebar-wrapper .sidebar-menu > .nav-item > .nav-link {
    opacity: .6;
}
/* adminlte.css's own hover/active/menu-open rules (13064-13079) paint a
   solid rounded pill behind the row, which reads fine on an ordinary link
   but turns a plain heading label into a stray button - most visibly, the
   open section (e.g. Dokumentation) would end up looking like a different
   control than its still-collapsed sibling headings (App, Shop) right
   above it. These labels aren't links you click for their own content
   (they're pure accordion toggles - see the `> .nav-link` combinator, same
   one as above), so they get no pressed/highlighted chrome at all in any
   state; whether the section's contents are showing below it is the only
   cue now that the arrow (above) is hidden. Same selector specificity as
   the adminlte.css rules being overridden, so no !important needed - this
   file just loads after it. */
.sidebar-wrapper .sidebar-menu > .nav-item > .nav-link:hover,
.sidebar-wrapper .sidebar-menu > .nav-item > .nav-link:focus,
.sidebar-wrapper .sidebar-menu > .nav-item > .nav-link.active,
.sidebar-wrapper .sidebar-menu > .nav-item.menu-open > .nav-link,
.sidebar-wrapper .sidebar-menu > .nav-item.menu-open > .nav-link.active {
    background-color: transparent;
}

/* Per-level indent for nested sidebar treeview rows (indent_class in
   side_nav_tree(), macros.html.twig). Bootstrap's ps-* spacing utilities
   jump from 1.5rem (ps-4) straight to 3rem (ps-5) with nothing in between -
   too coarse a step for a 2-3 level page tree, where each level should read
   as "a bit more indented" than its parent, not suddenly doubled. These are
   evenly spaced by 1rem instead, starting a little past the top-level row's
   own icon/text so a depth-1 child (e.g. "Passwort vergessen" under
   "Benutzerregistrierung") reads as clearly nested under it. */
.sidebar-wrapper .sidebar-menu .nav-link.ps-nav-1 { padding-left: 1.25rem; }
.sidebar-wrapper .sidebar-menu .nav-link.ps-nav-2 { padding-left: 2.25rem; }
.sidebar-wrapper .sidebar-menu .nav-link.ps-nav-3 { padding-left: 3.25rem; }
.sidebar-wrapper .sidebar-menu .nav-link.ps-nav-4 { padding-left: 4.25rem; }

/* Settings shortcut in the account dropdown footer (navigation_top.html.twig) -
   a plain icon rather than a bordered btn-outline-secondary, so it reads
   as a lower-emphasis secondary action next to the real Logout button
   beside it. Same opacity-on-hover affordance as the header icon buttons
   in dashboard.css's own .dashboard-widget__controls button. */
.user-footer__icon-link {
    display: inline-flex;
    align-items: center;
    padding: .375rem .25rem;
    color: var(--bs-secondary-color, inherit);
    opacity: .7;
}
.user-footer__icon-link:hover,
.user-footer__icon-link:focus-visible {
    opacity: 1;
    color: var(--bs-secondary-color, inherit);
}


/* for tables ############################################################################################ */

/* Tables get styled via Bootstrap's own .table/.table-striped/.table-hover
   classes now (see documentation.html.twig, which injects them onto
   markdown-generated tables) rather than bare-element CSS here - that
   gets alternating rows and light/dark mode for free from Bootstrap's
   theme-aware --bs-table-* custom properties. The previous rules here gave
   <tbody> text a 10% larger font-size than <thead> with no color/border
   styling at all - removed as it fought with, rather than matched, the
   site's normal typography. */

/* Markdown tables require a header row syntactically even when there's
   nothing to put in it (e.g. a two-column key/value spec table) - the
   result is a visible-but-blank header bar. Hide only header rows whose
   cells are ALL empty; a row with at least one non-empty <th> (a real
   header) is left alone. */
table thead tr:has(th:empty):not(:has(th:not(:empty))) {
  display: none;
}

/* blog ############################################################################################ */
/* .blog-card {
    width: 95%;
    margin: 10px;
    float: left;
}
.bricklayer-column-sizer {
    width: 25%;
  }
.blog-date {
    color:var(--bs-secondary);
    float: right;
    font-size: 0.8rem;
    padding-top: 5px;
}
.archives, .related-pages, .recent-posts li {
    list-style: none;
    padding-left: 0px;
    padding-top: 10px;
}
.recent_date {
    font-size: 0.7rem;
    color: #aaaaaa;
} */







/* ############################################################################################ */
/* janolaw #################################################################################### */
div.janolaw-paragraph {
    font-weight: bold;
    font-size: 1.4rem;
}
.janolaw-text .paragraph strong {
    font-size: 1.2rem;
}

/* ############################################################################################ */
/* Callouts (Markdown notices) ################################################################ */

.callout, .notices {
    border-radius: .25rem;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);
    background-color: #fff;
    color: var(--txt-dark);
    border-left: 5px solid #e9ecef;
    margin-bottom: 1rem;
    padding: 1rem;
}

/* descendant selector — .inverted is on the ancestor <section>, not on .callout itself */
.inverted .callout,
.inverted .notices {
    background-color: var(--navy-mid);
    color: rgba(255, 255, 255, .85);
    border-left: 5px solid rgba(255, 255, 255, .2);
}

.callout.callout-info {
    border-left-color: var(--color-info);
}
.callout.callout-success {
    border-left-color: var(--color-success);
}
.callout.callout-warning {
    border-left-color: var(--color-warning);
}
.callout.callout-danger {
    border-left-color: var(--color-danger);
}

.inverted .callout.callout-info    { border-left-color: var(--color-info); }
.inverted .callout.callout-success { border-left-color: var(--color-success); }
.inverted .callout.callout-warning { border-left-color: var(--color-warning); }
.inverted .callout.callout-danger  { border-left-color: var(--color-danger); }

.inverted .notices.red    { border-color: var(--color-danger); background-color: var(--color-danger); }
.inverted .notices.yellow { border-color: var(--color-warning); }
.inverted .notices.green  { border-color: var(--color-success); }
.inverted .notices.blue   { border-color: var(--color-info); }

/* Dark mode, outside .inverted sections only — .inverted already gets a
   navy background from ".inverted .callout/.notices" above regardless of
   theme (an "inverted" section is a dark/colored hero-style section, not
   the same thing as [data-bs-theme="dark"]). `:not(.inverted *)` matches
   "not a descendant of .inverted" rather than relying on !important +
   source order (the previous attempt at this used !important, which
   would have clobbered .inverted's background too, and had a stray ")"
   that broke the selector outright — this rule never actually applied). */
[data-bs-theme="dark"] .callout:not(.inverted *),
[data-bs-theme="dark"] .notices:not(.inverted *) {
    background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity, 1));
    color: var(--bs-body-color);
}

/* markdown-notices ships its colored variants (.notices.red/.yellow/...)
   with hardcoded near-white pastel backgrounds (see
   user/plugins/markdown-notices/assets/notices.css) that never adapted
   for dark mode at all — not even via the (broken) rule above, since it
   only ever targeted the unqualified .notices. Darken each toward the
   same dark base used for section backgrounds elsewhere in this file
   (see "[data-bs-theme="dark"] section" above), and switch the text
   color to that variant's own accent (its border-left color) since the
   original dark, saturated text colors don't have enough contrast on a
   dark background. */
[data-bs-theme="dark"] .notices.yellow:not(.inverted *) { background-color: color-mix(in srgb, #fcf8f2 15%, #1f2730); color: #f0ad4e; }
[data-bs-theme="dark"] .notices.red:not(.inverted *)    { background-color: color-mix(in srgb, #fdf7f7 15%, #1f2730); color: #d9534f; }
[data-bs-theme="dark"] .notices.blue:not(.inverted *)   { background-color: color-mix(in srgb, #f4f8fa 15%, #1f2730); color: #5bc0de; }
[data-bs-theme="dark"] .notices.green:not(.inverted *)  { background-color: color-mix(in srgb, #f1f9f1 15%, #1f2730); color: #5cb85c; }

/* ############################################################################################ */
/* documentation ############################################################################## */

.menu-icon {
  height: 0.9em;
  width: auto;
  margin-right: .4rem;
  vertical-align: -0.15em;
}

/* CE / VDE certification-mark icons (30.certificates/*, no Line Awesome
   equivalent exists, so they're page-relative SVGs via icon_image
   instead - see macros.html.twig) are solid black with no currentColor
   support, rendered as a plain <img> rather than inlined - same "black
   on dark background" problem as the callouts/TOC fixes above, just via
   filter instead of a color/background-color property since an <img>'s
   pixels aren't otherwise themeable. Scoped by filename rather than the
   shared .menu-icon class: other sites use .menu-icon for full-color
   logos (e.g. ecosensors.cloud's Growatt logo) that inverting would
   distort. `*=` (contains) rather than `$=` (ends-with) in case the
   media pipeline ever appends a query string to the URL. */
[data-bs-theme="dark"] img.menu-icon[src*="ce.svg"],
[data-bs-theme="dark"] img.menu-icon[src*="vde.svg"] {
  filter: invert(1);
}

.doc-index {
  list-style: none;
  padding: 0;
  margin: 2rem 0;
}

.doc-index li {
  margin-bottom: 1.2rem;
}

.doc-link {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  text-decoration: none;
  color: inherit;
  padding-left: 6px;   /* slight indent */
}

.doc-icon {
  width: 26px;
  flex-shrink: 0;
  margin-top: 3px;
  color: #6c757d;
  font-size: 20px;
}

.doc-text {
  line-height: 1.25;
}

.doc-title {
  font-weight: 600;
  font-size: 1.05rem;
}

.doc-subtitle {
  font-size: 0.9rem;
  color: #6c757d;
  margin-top: 2px;
}

.doc-link:hover .doc-title {
  text-decoration: underline;
}

.doc-section {
  margin-bottom: 2.5rem;
  padding-bottom: 1.2rem;
  border-bottom: 1px solid rgba(0,0,0,0.06);
}

.doc-section-heading {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  margin-bottom: 0.8rem;
}

.doc-section-heading i {
  font-size: 22px;
  color: #6c757d;
}

.doc-section-heading img {
  width: 22px;
  height: 22px;
}

.doc-section-body {
  line-height: 1.6;
}

.doc-section-image {
  max-width: 600px;
  margin: 0 0 1rem 1.5rem;
}

.doc-section-image.float-left {
  margin: 0 1.5rem 1rem 0;
}

.doc-section-caption {
  text-align: center;
  font-size: 0.85rem;
  color: #6c757d;
  margin-top: 6px;
}

.doc-section-gallery-number {
  position: absolute;
  top: 6px;
  left: 6px;
  background: rgba(0,0,0,0.7);
  color: white;
  font-size: 0.75rem;
  padding: 2px 6px;
  border-radius: 4px;
}
.doc-section-separator {
  margin: 1rem 0; /* spacing between sections */
  height: 2px;
  background: linear-gradient(to right, transparent, #ccc, transparent);
}

/* modular assembly steps (modular/step.html.twig, documentation_modular.html.twig) ##### */

/* Local tokens for this section only, light values on :root, dark
   overrides below - mostly aliases onto Bootstrap's own already
   theme-aware variables (see the .feature-card-sm comment above for why
   that's preferred over this theme's --section-bg-light/--txt-dark
   tokens, which are NOT redefined for dark mode and go near-invisible
   text-on-text if used directly on a dark background). */
:root {
    --step-panel-bg: #EEF1F4;
    --step-border: #E2E8F0;
    --step-divider: #F1F3F5;
    --step-muted: #6c757d;
    --step-muted-soft: #5B6B7D;
    --step-tag: #C3CAD2;
}
[data-bs-theme="dark"] {
    --step-panel-bg: var(--bs-tertiary-bg);
    --step-border: var(--bs-border-color);
    --step-divider: var(--bs-border-color-translucent, rgba(255, 255, 255, .09));
    --step-muted: var(--bs-secondary-color);
    --step-muted-soft: var(--bs-secondary-color);
    --step-tag: var(--bs-tertiary-color);
}

/* Replaces the old plain heading + <hr> per module: each step is now its
   own bordered card, so a photo-heavy step's height comes from its own
   rows instead of a fixed 50/50 split against a short instruction list. */
.stepcard {
    background: var(--bs-body-bg);
    border: 1px solid var(--step-border);
    border-radius: 10px;
    padding: 22px 26px 20px;
    margin-bottom: 28px;
    box-shadow: 0 1px 2px rgba(15, 29, 46, .04);
}
[data-bs-theme="dark"] .stepcard {
    box-shadow: none;
}
.stepcard-head {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 6px;
}
.step-badge {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--color-primary, var(--amber));
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    font-family: var(--f-disp);
    font-weight: 700;
    font-size: 17px;
}
.stepcard-head h3 {
    margin: 0;
    flex: 1;
}
.step-of {
    font-size: 12.5px;
    color: var(--step-muted);
    font-weight: 600;
    white-space: nowrap;
}
.step-intro, .step-outro {
    font-size: 14.5px;
    color: var(--bs-body-color);
}
.step-intro { margin-bottom: 10px; }
.step-outro { margin-top: 6px; }

.step-rows {
    display: flex;
    flex-direction: column;
}
.step-row {
    display: grid;
    grid-template-columns: 30px 1fr 236px;
    gap: 20px;
    align-items: center;
    padding: 18px 0;
    border-top: 1px solid var(--step-divider);
}
.step-rows .step-row:first-child {
    border-top: none;
}
.row-num {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--color-secondary, rgba(217, 119, 6, .16));
    color: var(--color-primary, var(--amber));
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12.5px;
    font-weight: 700;
    align-self: start;
    margin-top: 2px;
}
.row-text {
    font-size: 14.5px;
    line-height: 1.6;
    color: var(--bs-body-color);
    align-self: start;
    padding-top: 2px;
}
.row-text small,
.row-text .text-muted {
    display: block;
    font-size: 12.5px;
    margin-top: 2px;
}

.row-photo {
    width: 236px;
    height: 150px;
    border-radius: 8px;
    border: 1px solid var(--step-border);
    background: var(--step-panel-bg);
    position: relative;
    overflow: hidden;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
}
[data-bs-theme="dark"] .row-photo {
    box-shadow: none;
}
.row-photo a {
    display: block;
    width: 100%;
    height: 100%;
}
.row-photo-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
/* No photo yet: a plain background-image (not an <img src>) so the light
   /dark swap is pure CSS - no server-side theme detection needed (that
   wouldn't work for the "Auto" theme option anyway, which follows the
   OS/browser at runtime, not anything Grav knows when it renders the
   page) and it updates instantly if the reader flips the theme toggle. */
.row-photo-placeholder {
    width: 100%;
    height: 100%;
    background-image: url(../images/placeholder.png);
    background-repeat: no-repeat;
    background-position: center;
    /* The source PNG is 1200x900 (4:3, generated at that size for a crisp
       lightbox view even though it's shown small here - see the design
       canvas). A square 72x72 background-size stretched it non-uniformly
       (72/1200 horizontally vs 72/900 vertically), visibly squashing the
       icon glyph inside; 96x72 keeps the true 4:3 ratio so nothing inside
       the image gets distorted. */
    background-size: 96px 72px;
}
[data-bs-theme="dark"] .row-photo-placeholder {
    background-image: url(../images/placeholder-dark.png);
}
.zoom-badge {
    position: absolute;
    right: 8px;
    bottom: 8px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: rgba(11, 21, 34, .6);
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none; /* the lightbox <a> underneath is the real click target */
}

/* Documentation content images (documentation.html.twig's .doc-content,
   wrapping page.content) - thumbnail-sized and marked with the same
   magnifying-glass badge as .zoom-badge above, so it's clear at a glance
   that clicking opens the full size (via Featherlight, off Grav core's
   ?lightbox=<size> media directive - see the <a rel="lightbox"> it wraps
   around the image). Convention: every content image on a
   documentation.*.md page should use ?lightbox= so it lands here - see
   CLAUDE.md's "Plugin-owned documentation pages" section.
   Unlike .zoom-badge (a real sibling <span> next to the image in
   step.html.twig's own markup), markdown-authored content can only ever
   produce the <a rel="lightbox"><img></a> pair itself - there's no way to
   inject a sibling badge element from markdown. Same look, reimplemented
   as a ::after on the link instead of requiring extra markup. */
.doc-content a[rel="lightbox"] {
    position: relative;
    display: inline-block;
    margin: .25rem 0;
}
.doc-content a[rel="lightbox"] img {
    display: block;
    max-width: 360px;
    height: auto;
    cursor: zoom-in;
}
.doc-content a[rel="lightbox"]::after {
    content: "";
    position: absolute;
    right: 8px;
    bottom: 8px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background-color: rgba(11, 21, 34, .6);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='13' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2.2'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: center;
    pointer-events: none; /* the lightbox <a> underneath is the real click target */
}

@media (max-width: 767.98px) {
    .step-row {
        grid-template-columns: 30px 1fr;
    }
    .row-photo {
        grid-column: 1 / -1;
        width: 100%;
        height: 200px;
    }
}

/* Whole-manual progress strip (documentation_modular.html.twig). Sections
   with no photos yet render as a dashed, muted "not written" stop instead
   of being silently skipped - see STEP_UPCOMING in languages.yaml. Each
   item is an <a href="#slug"> now (see the twig comment there), so reset
   the link defaults it'd otherwise pick up. */
.step-progress {
    display: flex;
    /* flex-start, not center: a wrapped 2-line label makes that one item
       taller, and centering the row on each item's own (varying) height
       is exactly what threw the dots out of alignment whenever a label
       was long enough to wrap - flex-start keeps every item's dot flush
       with the top of the row regardless of how tall its label gets. */
    align-items: flex-start;
    margin: 22px 0 30px;
    overflow-x: auto;
}
.step-progress-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    width: 108px;
    color: inherit;
    text-decoration: none;
}
.step-progress-item:hover .step-progress-dot {
    box-shadow: 0 0 0 3px var(--color-secondary, rgba(217, 119, 6, .18));
}
.step-progress-line {
    flex: 1 1 auto;
    height: 1px;
    background: var(--step-border);
    /* 13px = half the 26px dot height, so the line always lands on the
       dot's vertical center - fixed from the top now that .step-progress
       is flex-start (see above), instead of a bottom offset tuned only
       for a single-line label. */
    margin: 13px -6px 0;
    min-width: 8px;
}
.step-progress-line.done {
    background: var(--color-primary, var(--amber));
}
.step-progress-dot {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    font-weight: 700;
    flex-shrink: 0;
    transition: box-shadow .15s;
}
.step-progress-dot.done {
    background: var(--color-primary, var(--amber));
    color: #fff;
}
.step-progress-dot.pending {
    background: var(--bs-body-bg);
    color: var(--step-muted);
    border: 1.5px dashed var(--step-border);
}
.step-progress-label {
    font-size: 11.5px;
    text-align: center;
    color: var(--step-muted-soft);
    line-height: 1.3;
}
.step-progress-item.pending .step-progress-label {
    color: var(--step-muted);
}
.step-progress-item.pending .step-progress-label .step-progress-tag {
    display: block;
    font-size: 9.5px;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--step-tag);
    margin-top: 1px;
}

/* Numbered TOC dots (documentation_modular.html.twig only - see the
   "numbered: true" comment at its page-toc include, and the toc-loop
   macro's <li class="nav-item"> in page-toc.html.twig for the structure
   these selectors match). Plain CSS counters rather than passing step
   numbers in from twig: the TOC's headings and the stepcards are built
   from the exact same page.collection() order, so counting TOC entries
   already gives the same numbers as the on-page step badges, with zero
   coupling between the two templates. Only top-level entries get a
   number - this site's step modules are flat (one <h3> each, no nested
   sub-headings), so a nested .nav-item under a top-level one would only
   ever come from a heading inside a module's own content, which isn't
   "step N" in the same sense. */
.page-toc-numbered .nav-sidebar {
    counter-reset: toc-step;
}
.page-toc-numbered > .nav-sidebar > .nav-item > .nav-link {
    display: flex;
    align-items: center;
    gap: 10px;
}
.page-toc-numbered > .nav-sidebar > .nav-item > .nav-link::before {
    counter-increment: toc-step;
    content: counter(toc-step);
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 10.5px;
    font-weight: 700;
    background: var(--step-panel-bg);
    color: var(--step-muted);
    border: 1px solid var(--step-border);
    transition: background-color .15s, color .15s, border-color .15s, box-shadow .15s;
}
/* .active is toggled by page-toc.html.twig's own scroll listener as the
   reader scrolls - this is what answers "show me where I am on the page". */
.page-toc-numbered > .nav-sidebar > .nav-item > .nav-link.active::before {
    background: var(--color-primary, var(--amber));
    color: #fff;
    border-color: var(--color-primary, var(--amber));
    box-shadow: 0 0 0 3px var(--color-secondary, rgba(217, 119, 6, .18));
}

/* App/doc page headings - the page h1/h4 pattern every app-shell template
   duplicates verbatim (documentation.html.twig, documentation_tables.
   html.twig, documentation_modular.html.twig, register_device.html.twig,
   settings.html.twig, default.html.twig, contact/login/forgot/signup/form.
   html.twig - grep `<h1>{{ page.title` across templates/ to confirm the
   full set before adding a new one). Lighter, real weight cuts of the same
   condensed family (see BarlowCondensed-Medium/-SemiBold in
   freezeprotector.com's own css/custom.css) instead of the sitewide
   --f-disp override, which - on this site - is a single ExtraBold face
   registered under every weight/style request, reading heavy/bulky at
   these sizes regardless of what weight a rule asks for. Falls back to
   --f-disp itself on any other site sharing this theme, where these
   family names simply don't exist. Deliberately NOT applied sitewide via
   a bare `h1`/`h4` selector - the landingpage (pages/landingpage/, its own
   modular.html.twig + .landingpage-scoped CSS further up this file) wants
   its marketing headings to stay bold/heavy, so this only ever reaches an
   element that opts in with the class. */
.page-heading-title {
    font-family: "BarlowCondensed-SemiBold", var(--f-disp);
    font-weight: 600;
    letter-spacing: .01em;
}
.page-heading-subtitle {
    font-family: "BarlowCondensed-Medium", var(--f-disp);
    font-weight: 500;
    /* var(--bs-secondary-color, var(--txt-soft)) was the first attempt
       here, but that fallback never actually applies - Bootstrap always
       defines --bs-secondary-color, in both themes, so it silently made
       light mode read as rgba(33,37,41,.75) (≈#585C5F, close to plain
       body-text darkness) instead of --txt-soft's #5B6B7D, losing exactly
       the lighter/greyer look that was the point. --step-muted-soft is
       the token that actually resolves to --txt-soft in light mode and a
       real dark-mode-safe value in dark - reused from the step section
       above rather than duplicating the same light/dark pair again. */
    color: var(--step-muted-soft);
    letter-spacing: .01em;
}
/* The TOC's own "Inhaltsverzeichnis" label (page-toc.html.twig) - every
   page.html.twig above, so this is unscoped (id-scoped to the TOC nav
   itself, not gated behind .page-toc-numbered - that class only turns on
   the step-number dots, a separate concern from this typographic match). */
#page-toc-nav h5 {
    font-family: "BarlowCondensed-Medium", var(--f-disp);
    font-weight: 500;
    font-size: .95rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--step-muted-soft); /* see the comment on .page-heading-subtitle above */
}

/* Simple Search ##################################################################### */

/* The field itself (partials/simplesearch_searchbox.html.twig) - shared by
   the sidebar (sidebar_left.html.twig, in .sidebar-search-wrapper) and the
   results page's own search form (simplesearch_results.html.twig, in
   .search-page-form), same partial in two differently-sized contexts.
   Bootstrap's .form-control/.input-group-text are already theme-aware
   (light/dark) on their own; this just unifies the corner radius with the
   rest of the theme (.card is 6px, see above) and gives focus/hover the
   same brand-color treatment as every other input/button here. */
.search-field .form-control,
.search-field .input-group-text,
.search-field .btn {
    border-radius: 6px;
}
.search-field .input-group-text.search-field-icon,
.search-field .btn.search-field-icon {
    color: var(--bs-secondary-color);
}
/* Plain .btn has a transparent border by default (see the .btn-secondary
   comment above on why this stays a plain, unshaped .btn) - without this
   it'd have no visible edge to align with the input's own border. */
.search-field .btn.search-field-icon {
    border-color: var(--bs-border-color);
}
.search-field .btn.search-field-icon:hover,
.search-field .btn.search-field-icon:focus-visible {
    color: var(--color-primary, var(--amber));
}
.search-field .form-control:focus {
    border-color: var(--color-primary, var(--amber));
    box-shadow: 0 0 0 3px var(--color-secondary, rgba(217, 119, 6, .18));
}

/* Sidebar placement: a small padded box directly under the logo, above the
   section nav - its own spacing rather than reusing .sidebar-menu's
   nav-item padding, since this is a form control, not a link. */
.sidebar-search-wrapper {
    padding: .25rem 1rem .75rem;
}
.sidebar-search-wrapper .form-control {
    font-size: .9rem;
}

/* Results page placement (simplesearch_results.html.twig): the same field,
   just given more room since it's the page's main control rather than a
   small sidebar accessory. */
.search-page-form {
    max-width: 480px;
    margin: .5rem 0 1.5rem;
}
.search-page-form .form-control {
    font-size: 1.05rem;
}

.search-results-summary {
    margin-bottom: 1.5rem;
}

/* Results list (partials/simplesearch_item.html.twig) - same thumbnail +
   text-block row shape as .doc-index/.doc-link above, plus a divider
   between rows since each item here also carries a summary paragraph
   rather than just a title. No date field - page.date isn't rendered
   here (deliberately dropped, see the item template's own history). */
.search-results-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
.search-result-item {
    display: flex;
    align-items: flex-start;
    gap: 18px;
    padding: 1.2rem 0;
    border-bottom: 1px solid var(--bs-border-color-translucent, rgba(0, 0, 0, .08));
}
.search-result-item:first-child {
    padding-top: 0;
}
.search-result-item:last-child {
    border-bottom: none;
}
.search-result-thumb {
    flex-shrink: 0;
    display: block;
    border-radius: 6px;
    overflow: hidden;
}
.search-result-thumb img {
    display: block;
    width: 96px;
    height: 96px;
    object-fit: cover;
}
.search-result-body {
    min-width: 0;
}
.search-result-title {
    margin: 0 0 .2rem;
    font-size: 1.1rem;
}
.search-result-title a {
    text-decoration: none;
    color: inherit;
}
.search-result-title a:hover {
    text-decoration: underline;
}
/* partials/simplesearch_item.html.twig already flattens page.summary to
   plain text (striptags) before this ever sees it, so this is just a
   capped-height text block - clamped to 3 lines so one wordy result
   doesn't dominate the row (a search result is meant to be scanned, not
   read in full; the title link is what's clicked to actually read the
   page). Unprefixed line-clamp still needs this -webkit- form for Safari,
   and the box/orient pair is required for either to take effect at all. */
.search-result-summary {
    margin: 0;
    color: var(--bs-body-color);
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
}
