/*
 * Quark 2 — Cal.com-inspired design system atop PicoCSS
 * ----------------------------------------------------
 * Principles (per DESIGN.md):
 *  - Monochrome palette; color is foreign.
 *  - Cal Sans for display, Inter for body.
 *  - Shadow-first depth: ring + soft + contact.
 *  - Generous whitespace; 80-96px between sections.
 */

/* ---------- Tokens ---------- */

:root,
:root[data-theme="light"] {
  --q2-bg: #fbfbfc; /* page canvas — whisper-soft gray */
  --q2-bg-elev: #ffffff; /* cards / content surface */
  --q2-bg-muted: #f2f2f4; /* recessed */
  --q2-bg-inverse: #111111;

  --q2-text: #242424;
  --q2-text-strong: #111111;
  /* Was #898989, which measured 3.50:1 on a white card, 3.38:1 on the canvas
     and 3.13:1 on the recessed surface -- all well under the 4.5:1 WCAG AA
     needs for body text, and this token carries most of the secondary copy on
     the site (card bodies, captions, "Learn more"). axe flagged 397 instances.
     #6b6b73 clears 4.5 against all three surfaces, worst case 4.72:1. The dark
     theme's muted value already passed and is unchanged. */
  --q2-text-muted: #6b6b73;
  --q2-text-inverse: #ffffff;

  /* Brand blues.
   *
   * --q2-navy is the logo's own colour, straight off the SVG. It is the
   * identity: the wordmark and the hero gradient resolve to it. It is not
   * usable as link text on the dark canvas (1.95:1), which is why it is not
   * the interactive colour.
   *
   * --q2-brand is that same navy lifted for interactive use -- identical hue
   * (206deg) and saturation, higher lightness, 6.32:1 on the light canvas.
   * Previously #1460bf, which sat 7deg off the logo: close enough to look
   * right, far enough to be a second blue rather than one blue in two steps. */
  --q2-navy: #074372;
  --q2-brand: #0a60a3;

  --q2-border-ring: rgba(34, 42, 53, 0.08);
  --q2-border-strong: rgba(34, 42, 53, 0.14);
  --q2-divider: rgba(34, 42, 53, 0.08);

  --q2-shadow-card:
    rgba(19, 19, 22, 0.7) 0 1px 5px -4px, rgba(34, 42, 53, 0.08) 0 0 0 1px,
    rgba(34, 42, 53, 0.05) 0 4px 8px 0;
  --q2-shadow-raised:
    rgba(19, 19, 22, 0.7) 0 1px 5px -4px, rgba(34, 42, 53, 0.1) 0 0 0 1px,
    rgba(34, 42, 53, 0.08) 0 12px 28px -8px;
  --q2-shadow-dropdown:
    rgba(19, 19, 22, 0.7) 0 1px 5px -4px, rgba(34, 42, 53, 0.1) 0 0 0 1px,
    rgba(34, 42, 53, 0.12) 0 16px 32px -12px;
  --q2-shadow-inset: inset 0 1px 1.9px rgba(0, 0, 0, 0.16);
  --q2-shadow-hl: inset 0 2px 0 rgba(255, 255, 255, 0.15);

  --q2-radius-xs: 4px;
  --q2-radius-sm: 6px;
  --q2-radius: 8px;
  --q2-radius-md: 12px;
  --q2-radius-lg: 16px;
  --q2-radius-pill: 9999px;

  --q2-nav-bg: rgba(255, 255, 255, 0.78);
  --q2-nav-border: rgba(34, 42, 53, 0.08);

  /* Pico overrides */
  --pico-font-family-sans-serif:
    "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --pico-font-family-display:
    "Cal Sans", "Inter", sans-serif;
  --pico-font-family-monospace:
    ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --pico-font-size: 16px;
  --pico-line-height: 1.6;
  --pico-font-weight: 400;
  --pico-border-radius: var(--q2-radius);
  --pico-spacing: 1rem;
  --pico-background-color: var(--q2-bg);
  --pico-color: var(--q2-text);
  --pico-h1-color: var(--q2-text);
  --pico-h2-color: var(--q2-text);
  --pico-h3-color: var(--q2-text);
  --pico-h4-color: var(--q2-text);
  --pico-h5-color: var(--q2-text);
  --pico-h6-color: var(--q2-text);
  --pico-muted-color: var(--q2-text-muted);
  --pico-muted-border-color: var(--q2-divider);
  --pico-primary: var(--q2-text);
  --pico-primary-hover: #000000;
  --pico-primary-focus: var(--q2-focus-ring);
  --pico-primary-inverse: var(--q2-text-inverse);
  --pico-secondary: var(--q2-text-muted);
  --pico-secondary-hover: var(--q2-text);
  --pico-contrast: var(--q2-text);
  --pico-contrast-hover: #000000;
  --pico-contrast-inverse: var(--q2-text-inverse);
  --pico-card-background-color: var(--q2-bg-elev);
  --pico-card-border-color: transparent;
  --pico-card-box-shadow: var(--q2-shadow-card);
  --pico-card-sectioning-background-color: var(--q2-bg-muted);
  --pico-form-element-background-color: var(--q2-bg);
  --pico-form-element-border-color: rgba(34, 42, 53, 0.16);
  --pico-form-element-color: var(--q2-text);
  --pico-form-element-focus-color: var(--q2-focus-ring);
  /* Pico draws checked checkbox/radio fill from these — point them at the accent */
  --pico-primary-background: var(--q2-cta, var(--q2-accent));
  --pico-primary-border: var(--q2-cta, var(--q2-accent));
}

:root[data-theme="dark"] {
  --q2-bg: #08090b;
  --q2-bg-elev: #1d1f25; /* card surface — lifted for visibility */
  --q2-bg-muted: #2a2d34; /* recessed / hover state */
  --q2-bg-inverse: #f5f5f5;

  --q2-text: #f2f2f3;
  --q2-text-strong: #ffffff;
  --q2-text-muted: #9a9aa0;
  --q2-text-inverse: #111111;

  /* Same hue again (206deg), lifted far enough to clear AA on the near-black
     canvas: 6.87:1. The navy itself is unusable here at 1.95:1. */
  --q2-navy: #074372;
  --q2-brand: #499edf;
  /* Pin the link colour to the same value. base.html.twig otherwise derives it
     via color-mix from the accent, which lands dark enough to fail contrast on
     the elevated card surface (#1d1f25) -- axe caught it on the signpost
     links. theme.css loads after that inline block, so this wins. */
  --q2-link: #499edf;

  --q2-border-ring: rgba(255, 255, 255, 0.14);
  --q2-border-strong: rgba(255, 255, 255, 0.2);
  --q2-divider: rgba(255, 255, 255, 0.1);

  --q2-shadow-card:
    rgba(0, 0, 0, 0.85) 0 1px 5px -4px, rgba(255, 255, 255, 0.14) 0 0 0 1px,
    rgba(0, 0, 0, 0.55) 0 4px 12px 0;
  --q2-shadow-raised:
    rgba(0, 0, 0, 0.85) 0 1px 5px -4px, rgba(255, 255, 255, 0.14) 0 0 0 1px,
    rgba(0, 0, 0, 0.65) 0 14px 32px -8px;
  --q2-shadow-dropdown:
    rgba(0, 0, 0, 0.85) 0 1px 5px -4px, rgba(255, 255, 255, 0.14) 0 0 0 1px,
    rgba(0, 0, 0, 0.75) 0 16px 32px -12px;
  --q2-shadow-inset: inset 0 1px 1.9px rgba(0, 0, 0, 0.5);
  --q2-shadow-hl: inset 0 1px 0 rgba(255, 255, 255, 0.06);

  --q2-nav-bg: rgba(11, 11, 12, 0.78);
  --q2-nav-border: rgba(255, 255, 255, 0.06);

  --pico-background-color: var(--q2-bg);
  --pico-color: var(--q2-text);
  --pico-muted-color: var(--q2-text-muted);
  --pico-muted-border-color: var(--q2-divider);
  --pico-primary: var(--q2-text-strong);
  --pico-primary-hover: #ffffff;
  --pico-primary-inverse: var(--q2-text-inverse);
  --pico-contrast: var(--q2-text-strong);
  --pico-contrast-inverse: var(--q2-text-inverse);
  --pico-card-background-color: var(--q2-bg-elev);
  --pico-card-sectioning-background-color: var(--q2-bg-muted);
  --pico-form-element-background-color: var(--q2-bg-elev);
  --pico-form-element-border-color: rgba(255, 255, 255, 0.12);
  --pico-form-element-color: var(--q2-text);
  --pico-primary-background: var(--q2-cta, var(--q2-accent));
  --pico-primary-border: var(--q2-cta, var(--q2-accent));
}

/* ---------- Base ---------- */

html {
  background: var(--q2-bg);
  color-scheme: light dark;
}

/* Reset Pico's blanket <article> chrome — we style cards explicitly */
article {
  margin-bottom: 0;
  padding: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

/* Pico styles `article > header` / `article > footer` as sectioning blocks
 * (negative margins, sectioning bg, border, top/bottom radii). Neutralize so
 * the card-header / card-footer behave as plain content slots. */
article > header,
article > footer,
.card > header,
.card > footer {
  margin: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
}

body {
  font-family: var(--pico-font-family-sans-serif);
  font-weight: 400;
  color: var(--q2-text);
  background-color: var(--q2-bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "cv11", "ss01", "ss03";
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

body.sticky-footer #page-wrapper {
  flex: 1 0 auto;
}
body.sticky-footer #footer {
  flex-shrink: 0;
}

/* ---------- Typography ---------- */

h1,
h2,
h3,
h4,
h5,
h6,
.display,
.h-display {
  font-family: var(--pico-font-family-display);
  color: var(--q2-text);
  letter-spacing: -0.01em;
  line-height: 1.15;
  margin-top: 0;
  margin-bottom: 0.4em;
}

/* h1 — airy, display scale, lighter weight */
h1 {
  font-size: clamp(2.25rem, 5vw + 1rem, 3.75rem);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.025em;
}

/* h2 — confident, tighter, bolder, subtle accent bar */
h2 {
  font-size: clamp(1.5rem, 2.5vw + 0.5rem, 2.125rem);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--q2-text-strong);
  position: relative;
}
h2::before {
  content: "";
  display: block;
  width: 28px;
  height: 3px;
  border-radius: 2px;
  background: var(--q2-brand);
  margin-bottom: 0.7rem;
}

/* h3 — workhorse section heads, medium weight */
h3 {
  font-size: clamp(1.25rem, 1.5vw + 0.5rem, 1.5rem);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.005em;
}

h4 {
  font-size: 1.1875rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0;
}
h5 {
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0.2px;
}
h6 {
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--q2-text-muted);
}

/* Headings that immediately follow another heading — don't double-space */
:is(h1, h2, h3) + :is(h1, h2, h3) {
  margin-top: 0.25em;
}

/* Generous top margin when headings follow body content — creates section rhythm */
:is(p, ul, ol, dl, blockquote, pre, table, figure, hr, iframe, .card) + h1 {
  margin-top: 1.75em;
}
:is(p, ul, ol, dl, blockquote, pre, table, figure, hr, iframe, .card) + h2 {
  margin-top: 1.65em;
}
:is(p, ul, ol, dl, blockquote, pre, table, figure, hr, iframe, .card) + h3 {
  margin-top: 1.55em;
}
:is(p, ul, ol, dl, blockquote, pre, table, figure, hr, iframe, .card) + h4 {
  margin-top: 1.45em;
}
:is(p, ul, ol, dl, blockquote, pre, table, figure, hr, iframe, .card)
  + :is(h5, h6) {
  margin-top: 1.35em;
}

/* Drop the accent bar when h2 acts as a subtitle (directly after h1) or is centered/hero */
h1 + h2::before,
.hero h2::before,
.text-center h2::before {
  display: none;
}
h1 + h2 {
  margin-top: -0.25em;
  color: var(--q2-brand);
}

p,
li {
  font-size: 1rem;
  line-height: 1.7;
  color: var(--q2-text);
}
.lead {
  font-size: 1.125rem;
  font-weight: 300;
  color: var(--q2-text-muted);
  letter-spacing: -0.2px;
  line-height: 1.55;
}
.text-muted {
  color: var(--q2-text-muted);
}
.text-center {
  text-align: center;
}

a {
  color: var(--q2-link);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
  transition: color 0.15s ease;
}
a:hover {
  color: var(--q2-text-strong);
  text-decoration-thickness: 2px;
}

strong,
b {
  font-weight: 600;
  color: inherit;
}
code,
pre,
kbd,
samp {
  font-family: var(--pico-font-family-monospace);
  font-size: 0.9em;
}
pre {
  background: var(--q2-bg-muted);
  border-radius: var(--q2-radius);
  padding: 1rem 1.25rem;
  box-shadow: var(--q2-shadow-card);
  overflow-x: auto;
}
code:not(pre code) {
  background: var(--q2-bg-muted);
  /* Pico supplies its own code colour, a mid grey that does not track the
     theme -- against the recessed surface in dark mode it fell under AA (axe
     caught the hex codes on /brand). Inline code is prose; give it the prose
     colour so it follows both themes. */
  color: var(--q2-text);
  padding: 0.12em 0.35em;
  border-radius: var(--q2-radius-xs);
}

/* The PHP Debug Bar ships its own complete, always-dark UI. The classless
 * content rules above (color on p/li, link color, pre/code chrome) were
 * bleeding into it, rendering message text unreadable dark-on-dark in light
 * mode. Fence the bar off so its own styles apply. */
.phpdebugbar :is(p, li) {
  color: inherit;
  font-size: inherit;
  line-height: inherit;
}
.phpdebugbar a {
  color: inherit;
}
.phpdebugbar :is(pre, code) {
  background: none;
  box-shadow: none;
}

hr {
  border: 0;
  border-top: 1px solid var(--q2-divider);
  margin: 2.5rem 0;
}

blockquote {
  border-left: 2px solid var(--q2-text);
  margin: 1.75rem 0;
  padding: 0.25rem 1.25rem;
  color: var(--q2-text);
  font-family: var(--pico-font-family-display);
  font-size: 1.25rem;
  line-height: 1.4;
  letter-spacing: 0;
}
blockquote cite,
blockquote footer {
  display: block;
  font-family: var(--pico-font-family-sans-serif);
  font-size: 0.875rem;
  color: var(--q2-text-muted);
  margin-top: 0.5rem;
  letter-spacing: 0;
}

/* ---------- GitHub-style Markdown Alerts ---------- */
/* Emitted by the `github-markdown-alerts` plugin as:
 *   <div class="markdown-alert markdown-alert-{note|tip|important|warning|caution}">
 *     <p class="markdown-alert-title"><svg>…</svg> Note</p>
 *     <p>…body…</p>
 *   </div>
 */
.markdown-alert {
  --alert-accent: var(--q2-text);
  --alert-bg: color-mix(in oklab, var(--alert-accent) 6%, var(--q2-bg-elev));
  border-left: 4px solid var(--alert-accent);
  background: var(--alert-bg);
  border-radius: var(--q2-radius-sm);
  padding: 0.875rem 1.125rem;
  margin: 1.5rem 0;
  font-family: var(--pico-font-family-sans-serif);
  font-size: 1rem;
  font-style: normal;
  line-height: 1.55;
  letter-spacing: 0;
  color: var(--q2-text);
}
.markdown-alert > :first-child {
  margin-top: 0;
}
.markdown-alert > :last-child {
  margin-bottom: 0;
}

.markdown-alert .markdown-alert-title {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0 0 0.375rem;
  font-family: var(--pico-font-family-sans-serif);
  font-weight: 600;
  font-size: 0.9375rem;
  line-height: 1;
  letter-spacing: 0;
  color: var(--alert-accent);
  text-transform: none;
}
.markdown-alert .markdown-alert-title svg,
.markdown-alert .markdown-alert-title .octicon {
  width: 16px;
  height: 16px;
  fill: currentColor;
  flex: 0 0 auto;
}

.markdown-alert p {
  margin: 0 0 0.375rem;
}
.markdown-alert p:last-child {
  margin-bottom: 0;
}

/* Accent per alert type — matches GitHub's light-mode palette */
.markdown-alert-note {
  --alert-accent: #0969da;
}
.markdown-alert-tip {
  --alert-accent: #1a7f37;
}
.markdown-alert-important {
  --alert-accent: #8250df;
}
.markdown-alert-warning {
  --alert-accent: #9a6700;
}
.markdown-alert-caution {
  --alert-accent: #cf222e;
}

:root[data-theme="dark"] .markdown-alert-note {
  --alert-accent: #539bf5;
}
:root[data-theme="dark"] .markdown-alert-tip {
  --alert-accent: #57ab5a;
}
:root[data-theme="dark"] .markdown-alert-important {
  --alert-accent: #b083f0;
}
:root[data-theme="dark"] .markdown-alert-warning {
  --alert-accent: #c69026;
}
:root[data-theme="dark"] .markdown-alert-caution {
  --alert-accent: #e5534b;
}

/* ---------- Layout ---------- */

.container {
  max-width: 1200px;
  width: 100%;
  margin-inline: auto;
  padding-inline: clamp(1rem, 4vw, 2rem);
}
.container-narrow {
  max-width: 880px;
  margin-inline: auto;
}

/* Default content column — white rounded "card" sitting on the gray canvas */
#body-wrapper > .container > .content-item {
  max-width: 960px;
  margin-inline: auto;
  background: var(--q2-bg-elev);
  border-radius: var(--q2-radius-lg);
  padding: clamp(1.75rem, 4vw, 3rem) clamp(1.5rem, 4vw, 3.25rem);
  box-shadow:
    rgba(19, 19, 22, 0.04) 0 1px 2px,
    rgba(19, 19, 22, 0.04) 0 8px 24px -8px;
}
#body-wrapper > .container > .content-item > :first-child {
  margin-top: 0;
}
#body-wrapper > .container > .content-item > :last-child {
  margin-bottom: 0;
}

:root[data-theme="dark"] #body-wrapper > .container > .content-item {
  box-shadow:
    rgba(0, 0, 0, 0.35) 0 1px 2px,
    rgba(0, 0, 0, 0.45) 0 10px 30px -10px,
    inset 0 0 0 1px rgba(255, 255, 255, 0.04);
}

.section {
  padding-block: clamp(2.5rem, 7vw, 5rem);
  position: relative;
}
.section-tight {
  padding-block: clamp(2rem, 5vw, 3.5rem);
}
/* For pairing two modular sections that should read as one visual block
   (e.g. a heading section immediately followed by its content section). */
.section-flush-bottom {
  padding-bottom: 0.5rem;
}
.section-flush-top {
  padding-top: 0.5rem;
}
/* Sections default to a 1rem bottom margin (Pico). It's invisible between
   same-background sections, but shows the page background through as a
   white strip when a tinted section abuts another tinted section or the
   footer. Collapse it so tinted blocks meet their neighbors cleanly. */
.section.bg-gray {
  margin-bottom: 0;
}

#body-wrapper {
  padding-block: clamp(2.5rem, 5vw, 4rem);
  background: var(--q2-bg);
}

/* Simple column grid used by blog layout. Sidebar layout only kicks in when
 * the layout has actually rendered an <aside> (signalled via .has-sidebar
 * by partials/layout.html.twig) — otherwise the item column gets the full
 * container width. */
.columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(2rem, 4vw, 3rem);
}
@media (min-width: 960px) {
  .columns.has-sidebar {
    grid-template-columns: minmax(0, 2.5fr) minmax(0, 1fr);
  }
  .columns.has-sidebar.reverse {
    grid-template-columns: minmax(0, 1fr) minmax(0, 2.5fr);
  }
}
.column {
  min-width: 0;
}

.extra-spacing > *:first-child {
  margin-top: 0;
}

/* ---------- Header / Navigation ---------- */

#header {
  position: relative;
  top: 0;
  z-index: 50;
  padding-block: 0;
  background: var(--q2-nav-bg);
  backdrop-filter: saturate(180%) blur(12px);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--q2-nav-border);
  transition:
    padding 0.2s ease,
    background 0.2s ease,
    border-color 0.2s ease;
}

/* header-fixed: sticky at the top of the viewport as the user scrolls */
body.header-fixed #header {
  position: sticky;
  top: 0;
}

/* header-transparent: sits over the hero until scrolled, then fills in.
 * Requires a hero/image behind it. Paired with `.scrolled` (set by site.js). */
body.header-transparent #header {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: transparent;
}
body.header-transparent.scrolled #header {
  background: var(--q2-nav-bg);
  backdrop-filter: saturate(180%) blur(12px);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  border-bottom-color: var(--q2-nav-border);
}

#header .navbar {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  min-height: 72px;
  transition: min-height 0.2s ease;
}
.header-animated.scrolled #header .navbar {
  min-height: 60px;
}

.navbar-section {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.navbar-section.logo {
  flex: 0 0 auto;
}
.navbar-section.actions {
  margin-left: auto;
  gap: 0.75rem;
}

.navbar-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  color: var(--q2-text-strong);
  text-decoration: none;
  font-family: var(--pico-font-family-display);
  font-size: 1.125rem;
  font-weight: 600;
  letter-spacing: 0;
}
.navbar-brand:hover {
  color: var(--q2-text-strong);
  text-decoration: none;
}
.navbar-brand img,
.navbar-brand svg {
  height: 28px;
  width: auto;
  display: block;
}
.navbar-brand svg,
.navbar-brand svg * {
  fill: var(--q2-text-strong);
}

/* Desktop nav */
.desktop-menu {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 1.25rem;
}

/* Reset Pico's nav-spawned rules for our dropmenu */
.dropmenu,
.dropmenu ul {
  display: block;
}
.dropmenu ul,
.dropmenu ul.navigation,
.dropmenu ul:first-of-type,
.dropmenu ul:last-of-type {
  list-style: none;
  margin: 0;
  padding: 0;
}
.dropmenu > ul,
.dropmenu ul.navigation {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.dropmenu li {
  position: relative;
  display: block;
  margin: 0;
  padding: 0;
}
.dropmenu li::before {
  content: none !important;
}

.dropmenu a {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  margin: 0; /* override Pico's negative margin on nav li a */
  padding: 0.5rem 0.9375rem;
  font-family: var(--pico-font-family-sans-serif);
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--q2-text-strong);
  text-decoration: none;
  border-radius: var(--q2-radius-sm);
  letter-spacing: -0.1px;
  line-height: 1.35;
  transition:
    background 0.15s ease,
    color 0.15s ease;
  white-space: nowrap;
}
.dropmenu a:hover {
  background: var(--q2-bg-muted);
  color: var(--q2-text-strong);
  text-decoration: none;
}
/* "You are here", not an action. This was a solid near-black pill, which made
   it the heaviest element in the header -- and once the gold Contact CTA landed
   beside it, two filled shapes competed for the same attention while meaning
   completely different things (one is a state, the other is the primary
   action). A recessed tint plus weight carries the state clearly and leaves
   "filled" to mean "clickable action". */
.dropmenu a.active {
  color: var(--q2-text-strong);
  background: var(--q2-bg-muted);
  font-weight: 600;
}
.dropmenu a.active:hover {
  background: var(--q2-bg-muted);
  color: var(--q2-text-strong);
}

/* Dropdown submenu — vertical stack (override Pico's nav ul flex rules) */
.dropmenu ul ul,
.dropmenu li > ul {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  min-width: 280px;
  max-width: 340px;
  margin: 0;
  padding: 0.375rem;
  list-style: none;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  background: var(--q2-bg-elev);
  border-radius: var(--q2-radius-md);
  box-shadow: var(--q2-shadow-dropdown);
  opacity: 0;
  transform: translateY(-4px);
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity 0.15s ease,
    transform 0.15s ease,
    visibility 0.15s;
  z-index: 40;
}
.dropmenu li:hover > ul,
.dropmenu li:focus-within > ul,
.dropmenu li.open > ul {
  opacity: 1;
  transform: translateY(0);
  visibility: visible;
  pointer-events: auto;
}

/* Invisible hover bridge so the cursor can cross the gap between the parent
 * menu item and the dropdown without losing :hover state. */
.dropmenu li > ul::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -10px;
  height: 10px;
}
.dropmenu li li > ul::before {
  top: 0;
  left: -10px;
  right: auto;
  width: 10px;
  height: 100%;
}

.dropmenu li > ul > li {
  display: block;
  width: 100%;
  align-self: stretch;
}
.dropmenu li > ul a {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 0.375rem 0.875rem;
  border-radius: var(--q2-radius-sm);
  white-space: normal;
  font-weight: 500;
  font-size: 0.9375rem;
  line-height: 1.4;
  letter-spacing: -0.1px;
}

/* Nested (third-level) dropdown opens to the side */
.dropmenu li li > ul {
  top: 0;
  left: calc(100% + 6px);
}

/* Belt-and-suspenders: anywhere `nav ul ul` inside dropmenu must be column */
nav .dropmenu ul ul,
nav .dropmenu li > ul {
  flex-direction: column !important;
  align-items: stretch !important;
  flex-wrap: nowrap !important;
}

/* Caret for parents */
.dropmenu li.has-children > a::after {
  content: "";
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
  display: inline-block;
  margin-left: 0.25rem;
  opacity: 0.6;
}
.dropmenu li li.has-children > a::after {
  transform: rotate(-45deg);
  margin-left: auto;
}

/* Theme toggle button — override Pico's button chrome entirely */
button.theme-toggle,
.theme-toggle {
  --size: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--size);
  height: var(--size);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--q2-radius-pill);
  background: transparent;
  color: var(--q2-text-strong);
  cursor: pointer;
  line-height: 0;
  font-size: 0;
  box-shadow: none;
  flex: 0 0 auto;
  transition: background 0.15s ease;
}
button.theme-toggle:hover {
  background: var(--q2-bg-muted);
}
.theme-toggle:hover {
  background: var(--q2-bg-muted);
}
.theme-toggle:focus-visible {
  outline: 2px solid var(--q2-focus-ring);
  outline-offset: 2px;
}
.theme-toggle .icon {
  width: 18px;
  height: 18px;
  display: none;
  font-size: 16px;
  line-height: 1;
}
:root[data-theme-preference="light"] .theme-toggle .icon-sun {
  display: block;
}
:root[data-theme-preference="dark"] .theme-toggle .icon-moon {
  display: block;
}
:root[data-theme-preference="auto"] .theme-toggle .icon-auto {
  display: block;
}

/* Header search — icon button that reveals the tntsearch widget in a small
 * dropdown anchored below it, rather than expanding inline. An inline expand
 * would shrink the nav's available width and wrap nav items onto a second
 * row; an absolutely-positioned panel overlays the page instead, so opening
 * it never reflows the header. .site-search-labeled (mobile menu, search
 * results page) shows the panel inline instead, since those contexts
 * already have their own room for it. built_in_css is disabled on the
 * plugin, so its markup (.tntsearch-form/.tntsearch-field/etc) is styled
 * here rather than with its bundled CSS. */
.site-search {
  display: flex;
  align-items: center;
  position: relative;
}
.site-search .search-toggle {
  --size: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--size);
  height: var(--size);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--q2-radius-pill);
  background: transparent;
  color: var(--q2-text-strong);
  cursor: pointer;
  box-shadow: none;
  flex: 0 0 auto;
  transition: background 0.15s ease;
}
.site-search .search-toggle:hover {
  background: var(--q2-bg-muted);
}
.site-search .search-toggle:focus-visible {
  outline: 2px solid var(--q2-focus-ring);
  outline-offset: 2px;
}
.site-search:not(.site-search-labeled) .site-search-panel {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  width: 340px;
  max-width: calc(100vw - 2rem);
  max-height: min(70vh, 560px);
  overflow-y: auto;
  background: var(--q2-bg);
  border: 1px solid var(--q2-border-ring);
  border-radius: var(--q2-radius-md);
  box-shadow: var(--q2-shadow-card);
  padding: .75rem;
  opacity: 0;
  /* Not visibility:hidden -- that also blocks JS's input.focus() call, and it
     does so specifically for one render tick right after the class toggle
     below (the "hidden -> visible" flip on a transitioned property isn't
     reflected in a same-tick synchronous focus() check), which broke both
     click-to-open and the Ctrl+K shortcut's auto-focus. pointer-events
     covers the "don't let the invisible panel eat clicks/hovers while
     closed" job instead; search-toggle.js keeps it out of the Tab order via
     tabindex while closed, since neither opacity nor pointer-events do that. */
  pointer-events: none;
  transform: translateY(-4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
  z-index: 60;
}
/* "See all results" footer link in the header dropdown. Separated by a rule so
   it reads as an action on the list rather than another result in it.

   Sticky to the bottom of the panel: the results list scrolls (max-height +
   overflow-y above), so a statically placed link sat below eight results and
   had to be scrolled to -- which is the same discoverability problem it exists
   to solve. Pinned, it is visible the moment there is a query. */
.site-search-all {
  position: sticky;
  bottom: -0.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  margin: .5rem -0.75rem -0.75rem;
  padding: .7rem;
  border-top: 1px solid var(--q2-divider);
  background: var(--q2-bg);
  border-radius: 0 0 var(--q2-radius-md) var(--q2-radius-md);
  font-size: .875rem;
  font-weight: 600;
  color: var(--q2-link);
  text-decoration: none;
}
.site-search-all:hover { text-decoration: underline; text-underline-offset: .15em; }
.site-search-all i { font-size: .75rem; }
.site-search-all[hidden] { display: none; }

.site-search.is-open:not(.site-search-labeled) .site-search-panel {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
.site-search-labeled .site-search-panel {
  width: 100%;
}

.tntsearch-form { position: relative; }
.tntsearch-form .form-group {
  position: relative;
  display: flex;
  align-items: center;
}
.tntsearch-field {
  width: 100%;
  height: 36px;
  padding: 0 2rem 0 .75rem;
  border: 1px solid var(--q2-border-ring);
  border-radius: var(--q2-radius-pill);
  background: var(--q2-bg);
  color: var(--q2-text);
  font-size: .9rem;
}
.tntsearch-field:focus-visible {
  outline: 2px solid var(--q2-focus-ring);
  outline-offset: 1px;
}
.tntsearch-clear {
  position: absolute;
  right: .65rem;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
  color: var(--q2-text-muted);
  font-size: 1.1rem;
  line-height: 1;
  user-select: none;
}
.tntsearch-clear:hover {
  color: var(--q2-text);
}
.tntsearch-results {
  margin-top: .25rem;
}
.tntsearch-results:empty {
  margin-top: 0;
}

/* Mobile hamburger */
.mobile-menu {
  display: none;
  position: fixed;
  top: 14px;
  right: 14px;
  z-index: 70;
}
button.button_container,
.button_container {
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--q2-radius-pill);
  background: var(--q2-bg-elev);
  color: var(--q2-text-strong);
  box-shadow: var(--q2-shadow-card);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  cursor: pointer;
  font-size: 0;
  line-height: 0;
}
button.button_container:hover {
  background: var(--q2-bg-muted);
}
button.button_container:focus-visible {
  outline: 2px solid var(--q2-focus-ring);
  outline-offset: 2px;
}
.button_container span {
  display: block;
  width: 18px;
  height: 1.5px;
  background: var(--q2-text-strong);
  border-radius: 2px;
  transition:
    transform 0.25s ease,
    opacity 0.2s ease;
}
.button_container.active .top {
  transform: translateY(6.5px) rotate(45deg);
}
.button_container.active .middle {
  opacity: 0;
}
.button_container.active .bottom {
  transform: translateY(-6.5px) rotate(-45deg);
}

/* Mobile overlay */
.mobile-container {
  display: none;
}
.overlay {
  position: fixed;
  inset: 0;
  background: var(--q2-bg);
  z-index: 60;
  display: flex;
  flex-direction: column;
  padding: 5rem 2rem 2rem;
  transform: translateX(100%);
  transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
  overflow-y: auto;
}
.overlay.open {
  transform: translateX(0);
}
.overlay .mobile-logo {
  margin-bottom: 2rem;
}

/* Force a proper vertical tree — override Pico's nav ul/li inline-block rules */
.overlay .overlay-menu,
.overlay .overlay-menu ul {
  display: block;
  list-style: none;
  margin: 0;
  padding: 0;
  width: 100%;
}
.overlay .overlay-menu li {
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
}
.overlay .overlay-menu li::before {
  content: none !important;
}
.overlay .overlay-menu > ul > li + li {
  margin-top: 0.125rem;
}
.overlay .overlay-menu a {
  display: block;
  width: 100%;
  margin: 0; /* kill Pico's negative nav a margin */
  padding: 0.625rem 0;
  font-family: var(--pico-font-family-display);
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--q2-text-strong);
  text-decoration: none;
  letter-spacing: -0.01em;
  line-height: 1.25;
  border-radius: 0;
}
.overlay .overlay-menu a:hover {
  color: var(--q2-text-strong);
  text-decoration: none;
}

/* Nested children — smaller, lighter, indented */
.overlay .overlay-menu ul ul {
  padding-left: 1rem;
  margin: 0.125rem 0 0.5rem;
  border-left: 2px solid var(--q2-divider);
}
.overlay .overlay-menu ul ul a {
  font-size: 1rem;
  font-weight: 500;
  font-family: var(--pico-font-family-sans-serif);
  color: var(--q2-text-muted);
  letter-spacing: 0;
  padding: 0.5rem 0 0.5rem 0.75rem;
}

@media (max-width: 860px) {
  .desktop-menu {
    display: none;
  }
  .mobile-menu {
    display: block;
  }
  .mobile-container {
    display: block;
  }
  #header .navbar {
    min-height: 64px;
  }
  .navbar-section.actions {
    display: none;
  }
}

body.overlay-open {
  overflow: hidden;
}

/* ---------- Hero ---------- */

#blog-hero,
.modular-hero,
.hero {
  position: relative;
  padding-block: clamp(5rem, 12vw, 9rem);
  background-color: var(--q2-bg);
  background-size: cover;
  background-position: center;
  overflow: hidden;
  isolation: isolate;
}
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(
      ellipse at 20% 0%,
      color-mix(in oklab, var(--q2-text) 5%, transparent),
      transparent 60%
    ),
    radial-gradient(
      ellipse at 80% 100%,
      color-mix(in oklab, var(--q2-text) 4%, transparent),
      transparent 55%
    );
  pointer-events: none;
  z-index: -1;
}
.hero[style*="background-image"] {
  color: var(--q2-text-inverse);
  background-color: var(--q2-bg-inverse);
}
.hero[style*="background-image"] .image-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(0, 0, 0, 0.35) 0%,
    rgba(0, 0, 0, 0.55) 100%
  );
  z-index: -1;
}
.hero[style*="background-image"] h1,
.hero[style*="background-image"] h2,
.hero[style*="background-image"] h3,
.hero[style*="background-image"] p,
.hero.hero-brand-gradient h1,
.hero.hero-brand-gradient h2,
.hero.hero-brand-gradient h3,
.hero.hero-brand-gradient p {
  color: #ffffff;
}

/* Interior-page hero background — same blue family as the home hero's
 * carousel gradient (see hero-carousel.html.twig: #1460bf -> #002d83 ->
 * #00092e), but flattened and centered rather than a dramatic off-center
 * spotlight fading to near-black. Home gets the loud, animated treatment
 * as the flagship page; interior pages get a calmer variant of the same
 * palette instead of an unrelated blue, so the two read as one brand
 * rather than two different blues that happen to both be "corporate blue." */
.hero.hero-brand-gradient {
  background: radial-gradient(ellipse at center, #1c5fac 0%, #123f80 55%, #0a2c5e 100%);
  color: var(--q2-text-inverse);
  /* Sections default to a 1rem bottom margin (Pico) -- collapses against
     whatever follows. Invisible against a light section below, but shows
     the page background through as a white strip against another tinted
     section immediately after (e.g. .pipeline-section). Same root cause
     already fixed once for .section.bg-gray; zero it here too. */
  margin-bottom: 0;
}

/* Hero icon badge — a small "frosted glass" circle marking what a page is
 * about, using a real Font Awesome glyph rather than custom illustration. */
.hero-icon-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.28);
  margin-bottom: 1.25rem;
}
.hero-icon-badge i {
  font-size: 1.75rem;
  color: #ffffff;
}

/* Split hero layout -- text beside a real foreground image (e.g. the Nexie
 * mascot), for the flagship pages that have brand art worth featuring
 * rather than the plain centered-text hero every other page uses. Opt-in
 * via page.header.hero_side_image; other heroes are unaffected. */
.hero-flex {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(2rem, 5vw, 4rem);
  text-align: left;
}
.hero-flex-text {
  flex: 1 1 420px;
  max-width: 640px;
}
.hero-flex-image {
  flex: 0 0 auto;
  width: clamp(280px, 32vw, 460px);
}
.hero-flex-image img {
  width: 100%;
  height: auto;
  display: block;
}
@media (max-width: 860px) {
  .hero-flex {
    flex-direction: column;
    text-align: center;
  }
  .hero-flex-image {
    width: clamp(220px, 65vw, 320px);
    order: -1;
  }
}

.hero .container {
  position: relative;
  z-index: 1;
}
.hero h1 {
  margin-bottom: 1rem;
}
.hero p,
.hero .lead {
  color: var(--q2-text-muted);
  max-width: 48ch;
  margin-inline: auto;
}

/* Links on the dark brand bands. --q2-link is the brand blue, which is the
   right choice on the light canvas (5.88:1) and unreadable here — blue text on
   the blue hero gradient lands around 1.3:1. The old amber accent happened to
   survive this context, so the problem only became visible once links moved to
   blue; it was latent either way. Inverse treatment: near-white with a visible
   underline, since on a saturated ground weight alone does not signal a link. */
/* Excludes the pill and node links: both are already box-shaped affordances,
   and an underline inside a bordered button reads as a mistake. */
.hero a:not(.btn),
.pipeline-section a:not(.btn):not(.pipeline-service):not(.pipeline-node) {
  color: #ffffff;
  text-decoration: underline;
  text-decoration-color: rgba(255, 255, 255, 0.55);
  text-underline-offset: 0.2em;
}
.hero a:not(.btn):hover,
.pipeline-section a:not(.btn):not(.pipeline-service):not(.pipeline-node):hover {
  color: #ffffff;
  text-decoration-color: #ffffff;
}
.hero a:not(.btn):focus-visible,
.pipeline-section a:not(.btn):focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.85);
  outline-offset: 2px;
}

/* ...except in the split layout, where the text column is left-aligned.
   `margin-inline: auto` centres the 48ch measure inside that column, so the
   paragraph sat visibly indented from the left edge of its own <h1> — on every
   split hero on the site. Centring is right for the centred hero variant and
   wrong here, so it is unset rather than removed above. */
.hero-flex-text p,
.hero-flex-text .lead {
  margin-inline: 0;
}
@media (max-width: 860px) {
  /* The split hero stacks and re-centres below 860px; the measure has to go
     back to being centred with it or the paragraph hangs off to one side. */
  .hero-flex-text p,
  .hero-flex-text .lead {
    margin-inline: auto;
  }
}

#to-start {
  position: absolute;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  color: var(--q2-text-muted);
  font-size: 1.5rem;
  cursor: pointer;
  animation: q2-bob 2.4s ease-in-out infinite;
  z-index: 2;
}
@keyframes q2-bob {
  0%,
  100% {
    transform: translate(-50%, 0);
  }
  50% {
    transform: translate(-50%, 6px);
  }
}

/* ---------- Buttons ---------- */

/* Action color system — three ranks, each visually distinct at a glance:
 *
 *   .btn-cta   primary   filled gold, dark label  ("Talk to an expert")
 *   .btn       secondary neutral surface + border ("View all articles")
 *   .btn-ghost tertiary  bare text + hover wash   (prev/next, inline nav)
 *
 * The previous default .btn filled itself with `--q2-accent 28% + white 72%`.
 * Two things were wrong with that. It produced a pale cream wash that reads as
 * a *disabled* control rather than a secondary one — and because the mix was
 * hardcoded against `white` with no dark-mode branch, the same cream slab was
 * painted onto the near-black canvas, where it was the brightest thing on the
 * page while being the least important. Secondary actions are now built from
 * the neutral surface tokens, which already track the active theme, so the
 * rank reads correctly in both without a second set of rules.
 *
 * Rank is carried by fill weight, not by hue: gold is loud and filled, neutral
 * is quiet and outlined, ghost has no chrome at all. That keeps gold meaning
 * exactly one thing (see the accent-color note in norconex.yaml). */
/* The element selectors are wrapped in :where() so this whole list carries the
   specificity of `.btn` alone (0,1,0).

   Without it, `button:not([role="button"]).btn` scores (0,2,1) -- element +
   attribute + class -- which outranks `.btn.btn-cta` at (0,2,0). The result:
   `<a class="btn btn-cta">` rendered gold while `<button class="btn btn-cta">`
   silently fell back to the neutral secondary fill, so the same modifier
   produced two different buttons depending on the tag. :where() contributes
   zero specificity, so every modifier below now reliably wins. */
:where(button:not([role="button"]), a, [type="button"], [type="submit"]).btn,
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Pico sets `width: 100%` on button elements, so a <button class="btn"> in a
     flex row takes the full container as its base size and shoves whatever sits
     beside it onto the next line. Anchors were unaffected, so this only showed
     up on real <button>s. Places that genuinely want a full-width button
     (.overlay-cta, .pricing-cta) set it themselves at higher specificity. */
  width: auto;
  gap: 0.5rem;
  padding: 0.625rem 1.125rem;
  font-family: var(--pico-font-family-sans-serif);
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.1px;
  color: var(--q2-text-strong);
  background: var(--q2-bg-elev);
  border: 1.5px solid var(--q2-border-strong);
  border-radius: var(--q2-radius);
  cursor: pointer;
  text-decoration: none;
  box-shadow: var(--q2-shadow-card);
  transition:
    opacity 0.15s ease,
    transform 0.05s ease,
    background 0.15s ease,
    border-color 0.15s ease,
    box-shadow 0.15s ease;
}
.btn:hover {
  background: var(--q2-bg-muted);
  border-color: var(--q2-text-muted);
  color: var(--q2-text-strong);
  text-decoration: none;
}
.btn:active {
  transform: translateY(0.5px);
}
.btn:focus-visible {
  outline: 2px solid var(--q2-focus-ring);
  outline-offset: 2px;
}

/* Tertiary — no fill, no border, no shadow. Used where a button would
   otherwise compete with the content it sits beside (post prev/next). */
.btn.btn-ghost,
.btn.btn-outline {
  color: var(--q2-text-strong);
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}
.btn.btn-ghost:hover,
.btn.btn-outline:hover {
  background: var(--q2-bg-muted);
  border-color: transparent;
  opacity: 1;
}

/* Primary CTAs (Get Started / Book a Demo style conversion actions) —
   filled gold with a dark label (7.84:1), outranking the neutral secondary. */
.btn.btn-cta {
  color: var(--q2-cta-contrast, var(--q2-accent-contrast));
  background: var(--q2-cta, var(--q2-accent));
  border-color: transparent;
  box-shadow: var(--q2-shadow-hl);
}
.btn.btn-cta:hover {
  opacity: 0.9;
  background: var(--q2-cta, var(--q2-accent));
  color: var(--q2-cta-contrast, var(--q2-accent-contrast));
  border-color: transparent;
}

/* Buttons sitting on the dark brand hero/pipeline bands. The neutral secondary
   derives from --q2-bg-elev, which in light mode is white-on-white against the
   navy — nearly invisible as a *secondary* cue and easily mistaken for the
   primary. On these bands the ranks invert to a translucent light treatment. */
.hero .btn:not(.btn-cta),
.pipeline-section .btn:not(.btn-cta) {
  color: #ffffff;
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.38);
  box-shadow: none;
}
.hero .btn:not(.btn-cta):hover,
.pipeline-section .btn:not(.btn-cta):hover {
  background: rgba(255, 255, 255, 0.2);
  border-color: rgba(255, 255, 255, 0.6);
  color: #ffffff;
}

.btn.btn-pill {
  border-radius: var(--q2-radius-pill);
}
.btn.btn-sm {
  padding: 0.375rem 0.75rem;
  font-size: 0.8125rem;
}
.btn.btn-lg {
  padding: 0.875rem 1.5rem;
  font-size: 1rem;
}

/* ---------- Cards ---------- */

.card {
  display: flex;
  flex-direction: column;
  background: var(--q2-bg-elev);
  border-radius: var(--q2-radius-md);
  box-shadow:
    rgba(19, 19, 22, 0.04) 0 1px 2px,
    rgba(19, 19, 22, 0.06) 0 8px 24px -8px;
  overflow: hidden;
  transition:
    box-shadow 0.2s ease,
    transform 0.2s ease;
  break-inside: avoid;
}
.card:hover {
  box-shadow:
    rgba(19, 19, 22, 0.06) 0 1px 2px,
    rgba(19, 19, 22, 0.1) 0 14px 32px -8px;
  transform: translateY(-2px);
}
/* Dark mode — borderless too, just with deeper ambient shadow. */
:root[data-theme="dark"] .card {
  box-shadow:
    rgba(0, 0, 0, 0.4) 0 1px 2px,
    rgba(0, 0, 0, 0.55) 0 10px 28px -8px;
}
:root[data-theme="dark"] .card:hover {
  box-shadow:
    rgba(0, 0, 0, 0.5) 0 1px 2px,
    rgba(0, 0, 0, 0.65) 0 16px 36px -8px;
}

.card-image {
  display: block;
}
.card-image img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.card-header {
  padding: 1.25rem 1.375rem 0.625rem;
}
.card-body {
  padding: 0 1.375rem 0.75rem;
  flex: 1;
}
.card-footer {
  padding: 0.5rem 1.375rem 1.25rem;
  margin-top: auto;
}

.card-subtitle {
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--q2-text-muted);
}
.card-subtitle .blog-date i {
  font-size: 0.8125rem;
  margin-right: 0.25rem;
  opacity: 0.75;
}

.card-title {
  margin-top: 0.5rem;
}
.card-title :is(h1, h2, h3, h4, h5, h6) {
  margin: 0;
  font-family: var(--pico-font-family-display);
  font-size: 1.1875rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.005em;
  color: var(--q2-text-strong);
}
.card-title :is(h1, h2)::before {
  display: none;
}
.card-title a {
  color: inherit;
  text-decoration: none;
}
.card-title a:hover {
  color: var(--q2-link);
  text-decoration: none;
}

/* Tone down any headings in the rendered summary — so markdown `# H1` inside
 * a summary doesn't blow up the card. */
.card-body :is(h1, h2, h3, h4, h5, h6) {
  margin: 0 0 0.25rem;
  font-family: var(--pico-font-family-sans-serif);
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: 0;
  color: var(--q2-text);
}
.card-body :is(h1, h2)::before {
  display: none;
}
.card-body p {
  margin: 0 0 0.5rem;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--q2-text-muted);
}
.card-body p:last-child {
  margin-bottom: 0;
}
.card-body > :first-child {
  margin-top: 0;
}

/* Bricklayer (masonry) */
.bricklayer {
  display: block;
}
.bricklayer [data-bricklayer-column] {
  padding: 0 0.75rem;
}
.bricklayer .card {
  margin-bottom: 1.5rem;
}
@media (max-width: 720px) {
  .bricklayer [data-bricklayer-column] {
    padding: 0;
  }
}

/* ---------- Tags / Labels ---------- */

.tags {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}
.label {
  display: inline-flex;
  align-items: center;
  font-family: var(--pico-font-family-sans-serif);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0;
  padding: 0.25rem 0.625rem;
  border-radius: var(--q2-radius-pill);
  background: var(--q2-bg-muted);
  color: var(--q2-text-strong);
  text-decoration: none;
  box-shadow: inset 0 0 0 1px var(--q2-border-ring);
}
.label:hover {
  background: var(--q2-bg);
  text-decoration: none;
  color: var(--q2-text-strong);
}
.label-primary {
  background: var(--q2-text);
  color: var(--q2-text-inverse);
  box-shadow: none;
}
.label-primary:hover {
  color: var(--q2-text-inverse);
  opacity: 0.9;
}

/* ---------- Forms ---------- */

.form-group {
  margin-bottom: 1.25rem;
}
.form-label {
  display: block;
  font-weight: 500;
  font-size: 0.9375rem;
  margin-bottom: 0.375rem;
  color: var(--q2-text-strong);
  letter-spacing: 0;
}
.form-input,
.form-select,
input[type="text"],
input[type="email"],
input[type="url"],
input[type="password"],
input[type="search"],
textarea,
select {
  background: var(--q2-bg-elev);
  color: var(--q2-text);
  border: 1px solid rgba(34, 42, 53, 0.18);
  border-radius: var(--q2-radius);
  padding: 0.625rem 0.75rem;
  font-family: inherit;
  font-size: 0.9375rem;
  transition:
    box-shadow 0.15s ease,
    border-color 0.15s ease;
  width: 100%;
}
:root[data-theme="dark"] .form-input,
:root[data-theme="dark"] .form-select,
:root[data-theme="dark"] input[type="text"],
:root[data-theme="dark"] input[type="email"],
:root[data-theme="dark"] input[type="url"],
:root[data-theme="dark"] input[type="password"],
:root[data-theme="dark"] input[type="search"],
:root[data-theme="dark"] textarea,
:root[data-theme="dark"] select {
  border-color: rgba(255, 255, 255, 0.14);
}

.form-input:focus,
.form-select:focus,
textarea:focus,
select:focus,
input:focus {
  outline: none;
  border-color: transparent;
  box-shadow:
    0 0 0 2px var(--q2-focus-ring),
    0 0 0 3px var(--q2-bg);
}

.form-checkbox,
.form-radio {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9375rem;
}
.form-errors {
  color: #d12c2c;
  font-size: 0.8125rem;
  margin-top: 0.25rem;
}

/* ---------- Form plugin fields ---------- */

/* Tabs — defeat form plugin's box-around-active + forced span color, give proper underlined active state */
.form-tabs .tabs-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  padding: 0;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--q2-divider);
}
.form-tabs .tabs-nav a.tab__link,
.form-tabs .tabs-nav .tab__link {
  flex: 0 0 auto;
  padding: 0.75rem 1.25rem;
  margin: 0 0 -1px 0;
  color: var(--q2-text-muted);
  background: transparent;
  font-weight: 500;
  font-size: 0.9375rem;
  text-align: center;
  text-decoration: none;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  cursor: pointer;
  transition:
    color 0.15s ease,
    border-color 0.15s ease;
}
.form-tabs .tabs-nav a.tab__link span,
.form-tabs .tabs-nav .tab__link span {
  color: inherit;
}
.form-tabs .tabs-nav a.tab__link:hover,
.form-tabs .tabs-nav .tab__link:hover {
  color: var(--q2-text-strong);
  text-decoration: none;
}
.form-tabs .tabs-nav a.tab__link.active,
.form-tabs .tabs-nav .tab__link.active {
  color: var(--q2-text-strong);
  border: 0;
  border-bottom: 2px solid var(--q2-accent);
  margin: 0 0 -1px 0;
}
.form-tabs .tabs-nav a.tab__link.active span,
.form-tabs .tabs-nav .tab__link.active span {
  color: var(--q2-text-strong);
}
.form-tabs .tabs-content .tab__content {
  display: none;
  padding-top: 1.5rem;
}
.form-tabs .tabs-content .tab__content.active {
  display: block;
}

/* Toggle (switch-toggle) — single segmented control, no inter-label margin */
.switch-toggle.switch-grav {
  display: inline-flex;
  align-items: stretch;
  background: var(--q2-bg-elev);
  border: 1px solid var(--q2-border-strong);
  border-radius: var(--q2-radius);
  overflow: hidden;
  line-height: 1;
}
.switch-toggle.switch-grav input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 0;
  height: 0;
  margin: 0;
}
/* Neutralize Pico's `[type=radio]~label:not(:last-of-type)` 1em margin inside the toggle */
.switch-toggle.switch-grav input[type="radio"] ~ label:not(:last-of-type),
.switch-toggle.switch-grav input[type="checkbox"] ~ label:not(:last-of-type) {
  margin-inline-end: 0;
}
.switch-toggle.switch-grav label {
  margin: 0;
  padding: 0.625rem 1rem;
  line-height: 1.15;
  color: var(--q2-text-muted);
  background: transparent;
  cursor: pointer;
  user-select: none;
  font-size: 0.9375rem;
  white-space: nowrap;
  border-inline-start: 1px solid var(--q2-border-strong);
  transition:
    background 0.15s ease,
    color 0.15s ease;
}
.switch-toggle.switch-grav label:first-of-type {
  border-inline-start: 0;
}
.switch-toggle.switch-grav label:hover {
  color: var(--q2-text-strong);
}
/* Beat form plugin's `.switch-toggle input:checked+label { color:#fff; background:#999 }` */
.switch-toggle.switch-grav input[type="radio"]:checked + label,
.switch-toggle.switch-grav input[type="radio"].highlight:checked + label {
  background: var(--q2-accent);
  color: var(--q2-accent-contrast);
  border-inline-start-color: var(--q2-accent);
}
.switch-toggle.switch-grav input[type="radio"]:focus-visible + label {
  outline: 2px solid var(--q2-focus-ring);
  outline-offset: 2px;
}

/* Submit / reset buttons — match .btn, never full-width.
 * Wrapper class varies by form (`buttons` default, `button-wrapper` in some blueprints). */
.form-wrapper .buttons,
.form-wrapper .button-wrapper {
  display: flex;
  flex-wrap: wrap;
  gap: 0.625rem;
  margin-top: 1.75rem;
}
.form-wrapper .buttons button,
.form-wrapper .buttons input[type="submit"],
.form-wrapper .buttons input[type="reset"],
.form-wrapper .button-wrapper button,
.form-wrapper .button-wrapper input[type="submit"],
.form-wrapper .button-wrapper input[type="reset"] {
  width: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.625rem 1.125rem;
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.1px;
  color: var(--q2-cta-contrast, var(--q2-accent-contrast));
  background: var(--q2-cta, var(--q2-accent));
  border: 0;
  border-radius: var(--q2-radius);
  cursor: pointer;
  text-decoration: none;
  box-shadow: var(--q2-shadow-hl);
  transition:
    opacity 0.15s ease,
    transform 0.05s ease;
}
.form-wrapper .buttons button:hover,
.form-wrapper .buttons input[type="submit"]:hover,
.form-wrapper .button-wrapper button:hover,
.form-wrapper .button-wrapper input[type="submit"]:hover {
  opacity: 0.9;
  color: var(--q2-accent-contrast);
}
.form-wrapper .buttons button:active,
.form-wrapper .button-wrapper button:active {
  transform: translateY(0.5px);
}
.form-wrapper .buttons button[type="reset"],
.form-wrapper .buttons input[type="reset"],
.form-wrapper .button-wrapper button[type="reset"],
.form-wrapper .button-wrapper input[type="reset"] {
  background: var(--q2-bg-elev);
  color: var(--q2-text);
  box-shadow: var(--q2-shadow-card);
}
.form-wrapper .buttons button[type="reset"]:hover,
.form-wrapper .button-wrapper button[type="reset"]:hover {
  background: var(--q2-bg-muted);
  color: var(--q2-text);
  opacity: 1;
}

/* Checkboxes & radios — accent-tinted, dark-mode-friendly, consistent sizing */
.form-wrapper input[type="checkbox"],
.form-wrapper input[type="radio"] {
  accent-color: var(--q2-accent);
  width: 1.25rem;
  height: 1.25rem;
  vertical-align: middle;
  margin-right: 0.5rem;
}
/* Force squares for checkboxes — Pico's 8px radius on a 17px input renders nearly circular */
.form-wrapper input[type="checkbox"] {
  border-radius: var(--q2-radius-xs);
}

/* `.checkboxes` group AND `.checkboxes.toggleable` (the field-enable toggle):
 * form plugin display:none's the native input and uses label::before for the box,
 * absolutely positioned at left:0 — that breaks when the label is empty (toggleable)
 * and forces every consumer to reserve 20px of padding. Flatten the pseudo to
 * static flow so it sits inline like a real checkbox glyph. */
.form-wrapper .checkboxes {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.25rem;
}
.form-wrapper .checkboxes > label,
.form-wrapper .checkboxes.toggleable > label {
  position: static;
  margin: 0;
  padding: 0;
}
.form-wrapper .checkboxes label::before,
.form-wrapper .checkboxes.toggleable label::before {
  position: static;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0 0.5rem 0 0;
  padding: 0;
  vertical-align: middle;
  line-height: 1;
  font-size: 0.875rem;
  border: 1px solid var(--q2-border-strong);
  background: var(--q2-bg-elev);
  border-radius: var(--q2-radius-xs);
  color: var(--q2-text);
}
.form-wrapper .checkboxes input[type="checkbox"]:checked + label::before,
.form-wrapper
  .checkboxes.toggleable
  input[type="checkbox"]:checked
  + label::before {
  content: "";
  background-color: var(--q2-accent);
  background-image: var(--pico-icon-checkbox);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 64%;
  border-color: var(--q2-accent);
  color: transparent;
}

/* Basic captcha — strip the form plugin's hard-coded #ccc chrome and the
 * accent-button styling that bleeds onto the reload icon. Constrain the
 * image so the reload button stays visible. */
.form-wrapper .form-data.basic-captcha .form-input-wrapper {
  border-color: var(--q2-border-strong);
  border-radius: var(--q2-radius);
  background: var(--q2-bg-elev);
  align-items: stretch;
}
.form-wrapper .form-data.basic-captcha .form-input-prepend {
  background: var(--q2-bg-muted);
  color: var(--q2-text);
  align-items: center;
  padding: 0 0.25rem 0 0;
}
.form-wrapper .form-data.basic-captcha .form-input-prepend img {
  display: block;
  height: 100%;
  width: auto;
  max-width: 130px;
  margin: 0;
  align-self: stretch;
  object-fit: cover;
}
.form-wrapper
  .form-data.basic-captcha
  .form-input-prepend
  .reload-captcha-button {
  background: transparent;
  color: var(--q2-text-muted);
  border: 0;
  border-radius: var(--q2-radius-sm);
  padding: 0.25rem 0.5rem;
  margin: 0;
  width: auto;
  height: auto;
  box-shadow: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition:
    color 0.15s ease,
    background 0.15s ease;
}
.form-wrapper
  .form-data.basic-captcha
  .form-input-prepend
  .reload-captcha-button:hover {
  color: var(--q2-text-strong);
  background: var(--q2-bg-elev);
  opacity: 1;
}
.form-wrapper
  .form-data.basic-captcha
  .form-input-prepend
  .reload-captcha-button
  svg {
  width: 16px;
  height: 16px;
  margin: 0;
}
.form-wrapper .form-data.basic-captcha input.form-input {
  margin: 0;
  border: 0;
  background: transparent;
  border-radius: 0;
}
.form-wrapper .form-data.basic-captcha input.form-input:focus {
  box-shadow: none;
}

/* Dropzone (default `file` field) — strip hardcoded white + #ccc dashed border for dark mode */
.form-wrapper .form-input-file.dropzone,
.form-wrapper .dropzone {
  background: var(--q2-bg-elev);
  border: 2px dashed var(--q2-border-strong);
  border-radius: var(--q2-radius);
  color: var(--q2-text-muted);
  padding: 1.25rem 0.5rem;
  transition:
    border-color 0.15s ease,
    color 0.15s ease;
}
.form-wrapper .dropzone:hover {
  border-color: var(--q2-accent);
  color: var(--q2-text);
}
.form-wrapper .dropzone .dz-default.dz-message {
  color: var(--q2-text-muted);
}
/* Neutralize the dz-button — Pico styles it as a primary accent pill by default */
.form-wrapper .dropzone .dz-button,
.form-wrapper .dropzone .dz-default button {
  background: var(--q2-bg-muted);
  color: var(--q2-text);
  border: 1px solid var(--q2-border-ring);
  border-radius: var(--q2-radius);
  padding: 0.5rem 1rem;
  margin: 0;
  font-weight: 500;
  font-size: 0.9375rem;
  box-shadow: none;
  cursor: pointer;
  transition:
    background 0.15s ease,
    color 0.15s ease;
}
.form-wrapper .dropzone .dz-button:hover,
.form-wrapper .dropzone .dz-default button:hover {
  background: var(--q2-bg-elev);
  color: var(--q2-text-strong);
  opacity: 1;
}
.form-wrapper .dropzone .dz-preview .dz-image {
  background: var(--q2-bg-muted);
}
.form-wrapper .dropzone .dz-preview .dz-remove {
  text-decoration: none;
  line-height: 1;
}
.form-wrapper .dropzone .dz-preview .dz-remove:hover {
  text-decoration: none;
}

/* Filepond — panels are filepond's own light-cream backgrounds; align to theme */
.form-wrapper .filepond--panel-root,
.form-wrapper .filepond--panel-top,
.form-wrapper .filepond--panel-center,
.form-wrapper .filepond--panel-bottom {
  background: var(--q2-bg-elev);
}
.form-wrapper .filepond--root {
  border: 1px solid var(--q2-border-strong);
  border-radius: var(--q2-radius);
  color: var(--q2-text);
}
.form-wrapper .filepond--drop-label {
  color: var(--q2-text-muted);
}
.form-wrapper .filepond--drop-label label {
  color: var(--q2-text-muted);
}
.form-wrapper .filepond--label-action {
  color: var(--q2-text-strong);
  text-decoration-color: var(--q2-text-muted);
}
.form-wrapper .filepond--credits {
  color: var(--q2-text-muted);
  opacity: 0.5;
}
.form-wrapper .filepond--file-action-button {
  background-color: rgba(0, 0, 0, 0.4);
  color: #ffffff;
}
.form-wrapper .filepond--item-panel {
  background: var(--q2-bg-muted);
}

.toast {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.75rem 1rem;
  border-radius: var(--q2-radius);
  background: var(--q2-bg-elev);
  color: var(--q2-text);
  box-shadow: var(--q2-shadow-card);
  margin-bottom: 1rem;
}
.toast-success,
.toast.success,
.toast.green {
  box-shadow:
    var(--q2-shadow-card),
    inset 3px 0 0 #059669;
}
.toast-error,
.toast.error,
.toast.red {
  box-shadow:
    var(--q2-shadow-card),
    inset 3px 0 0 #d12c2c;
}
.toast-warning,
.toast.warning,
.toast.yellow {
  box-shadow:
    var(--q2-shadow-card),
    inset 3px 0 0 #d97706;
}

/* ---------- Sidebar ---------- */

#sidebar {
  position: relative;
}
.sidebar-content {
  background: var(--q2-bg-elev);
  border-radius: var(--q2-radius-md);
  box-shadow:
    rgba(19, 19, 22, 0.04) 0 1px 2px,
    rgba(19, 19, 22, 0.06) 0 8px 24px -8px;
  padding: 1.25rem;
  margin-bottom: 1.5rem;
}
:root[data-theme="dark"] .sidebar-content {
  box-shadow:
    rgba(0, 0, 0, 0.4) 0 1px 2px,
    rgba(0, 0, 0, 0.55) 0 10px 28px -8px;
}
.sidebar-content h4 {
  font-size: 0.875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--q2-text-muted);
  margin: 0 0 0.75rem;
}
.sidebar-content .btn,
.sidebar-content .button {
  width: auto;
}
.sidebar-content ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.sidebar-content ul.archives li,
.sidebar-content ul.menu li {
  margin: 0;
}
.sidebar-content ul.archives a,
.sidebar-content ul.menu a {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.4375rem 0;
  color: var(--q2-text);
  text-decoration: none;
  line-height: 1.4;
}
.sidebar-content ul.archives a:hover,
.sidebar-content ul.menu a:hover {
  color: var(--q2-link);
  text-decoration: none;
}
.sidebar-content ul.archives a:hover .menu-text,
.sidebar-content ul.menu a:hover .menu-text {
  text-decoration: underline;
}

/* Count/score badge — perfect circle at one digit, pill when wider */
.sidebar-content .menu-score {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.625rem;
  height: 1.625rem;
  padding: 0 0.4375rem;
  border-radius: 9999px;
  background: var(--q2-text);
  color: var(--q2-text-inverse);
  font-family: var(--pico-font-family-sans-serif);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0;
}
.sidebar-content .menu-text {
  min-width: 0;
  font-size: 0.9375rem;
}

.syndicate .btn {
  margin-right: 0.375rem;
  margin-bottom: 0.375rem;
}

/* ---------- Breadcrumbs ---------- */
.breadcrumbs {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  padding: 0;
  margin: 0 0 1.5rem;
  color: var(--q2-text-muted);
  font-size: 0.875rem;
}
.breadcrumbs li + li::before {
  content: "›";
  margin-right: 0.375rem;
  opacity: 0.6;
}
.breadcrumbs a {
  color: var(--q2-text-muted);
  text-decoration: none;
}
.breadcrumbs a:hover {
  color: var(--q2-text-strong);
}

/* ---------- Blog ---------- */

.blog-listing .content-item {
  max-width: 760px;
  margin-inline: auto;
}

/* Blog page header (rendered when there's no hero image) */
.blog-page-header {
  text-align: center;
  max-width: 720px;
  margin: 0 auto clamp(2rem, 4vw, 3rem);
}
.blog-page-header h1 {
  margin: 0 0 0.5rem;
}
.blog-page-header .lead {
  margin: 0;
}
.blog-page-header .blog-page-intro {
  margin-top: 1rem;
  color: var(--q2-text-muted);
  font-size: 1rem;
  line-height: 1.6;
}

/* Blog card grid — capped at 3 cols max so cards have room to breathe */
.blog-grid {
  display: grid;
  gap: clamp(1.25rem, 2vw, 1.75rem);
  grid-template-columns: minmax(0, 1fr);
  align-items: stretch;
}
@media (min-width: 560px) {
  .blog-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (min-width: 1200px) {
  .blog-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  /* When a sidebar is present, the cards live in the narrower main column,
   * so 2 cols is the most the space comfortably allows. */
  .columns.has-sidebar .blog-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
.blog-grid > .card {
  height: 100%;
  align-self: stretch;
}

/* If sidebar plugins render nothing, collapse the empty aside so it doesn't
 * take a column slot. Requires .has-sidebar to also be conditionally swapped
 * if sidebar widgets are all disabled — handled in layout.html.twig. */
.blog-listing .columns > aside:empty {
  display: none;
}
@media (min-width: 960px) {
  .blog-listing .columns.has-sidebar:has(> aside:empty) {
    grid-template-columns: minmax(0, 1fr);
  }
}
.content-title {
  margin-bottom: 2rem;
}
.content-title h1,
.content-title h2 {
  margin-bottom: 0.5rem;
}

/* Search results (tntsearch) — plugin's built_in_css is disabled in favor
 * of matching the site's own card/typography conventions. Shared between
 * the compact header dropdown and the full /search page, since both render
 * through the same tntquery-ajax.html.twig override. */
.tntsearch-page {
  max-width: 760px;
  margin: 0 auto;
}
.tntsearch-page .search-header {
  text-align: center;
  margin-bottom: 1.5rem;
}
/* Results page (templates/search.html.twig). */
.hero-search h1 { max-width: 100%; }
.tntsearch-page .tntsearch-page-field { margin-bottom: 1.75rem; }
.tntsearch-empty { color: var(--q2-text-muted); }
.tntsearch-info {
  color: var(--q2-text-muted);
  font-size: .85rem;
  margin: 0 0 1rem;
}
.tntsearch-page .tntsearch-info {
  text-align: center;
}
.tntsearch-no-results {
  color: var(--q2-text-muted);
}
.search-row {
  border: 1px solid var(--q2-border-ring);
  border-radius: var(--q2-radius-md);
  box-shadow: var(--q2-shadow-card);
  padding: 1rem 1.25rem;
  margin-bottom: 1rem;
}
.search-row .search-item {
  min-width: 0;
}
.search-row h3 {
  margin: 0 0 .35rem;
  font-size: 1.05rem;
}
/* Result titles were bold AND blue AND underlined -- three signals for one
   link, which made a page of results read as a wall of heavy blue. Colour plus
   weight is enough; the underline arrives on hover, where it confirms the
   target rather than shouting at rest. */
.search-row h3 a {
  color: var(--q2-link);
  text-decoration: none;
}
.search-row h3 a:hover {
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
.search-row .search-route {
  color: var(--q2-text-muted);
  font-size: .8rem;
  margin: 0 0 .5rem;
}
.search-row p {
  margin: 0;
  color: var(--q2-text-muted);
  font-size: .9rem;
}
.search-row mark,
.search-row em {
  background: color-mix(in oklab, var(--q2-accent) 25%, transparent);
  color: var(--q2-text);
  font-style: normal;
  border-radius: 2px;
  padding: 0 .1em;
}

.blog-date {
  font-size: 0.875rem;
  color: var(--q2-text-muted);
}
.blog-date i {
  margin-right: 0.375rem;
  opacity: 0.7;
}

.prev-next {
  display: flex;
  gap: 0.75rem;
  justify-content: space-between;
  margin-top: 3rem;
}
.prev-next .btn {
  flex: 1;
  max-width: 48%;
}

/* ---------- Modular: Features ---------- */

.modular-features {
  background: var(--q2-bg);
}
.modular-features .frame-box {
  max-width: 1100px;
  margin-inline: auto;
}
.modular-features .columns {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  margin-top: 2.5rem;
}
.modular-features .column {
  background: var(--q2-bg-elev);
  border-radius: var(--q2-radius-md);
  box-shadow: var(--q2-shadow-card);
  padding: 1.5rem 1.25rem;
  border: 1px solid transparent;
  transition:
    border-color 0.2s ease,
    box-shadow 0.2s ease,
    transform 0.2s ease;
}
.modular-features .column:hover {
  box-shadow: var(--q2-shadow-raised);
  transform: translateY(-2px);
}
.modular-features .column-link:hover {
  border-color: var(--q2-cta, var(--q2-accent));
}
.modular-features .column-link {
  display: flex;
}
.modular-features .column-link > a {
  display: flex;
  flex-direction: column;
  width: 100%;
  text-decoration: none;
  color: inherit;
}
.modular-features .column-link .feature-content {
  flex: 1;
}
/* "Learn more →" affordance — quiet by default, gold on hover so the
   whole card reads as clickable without a persistent heavy outline. */
.modular-features .feature-more {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 1rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--q2-text-muted);
  transition: color 0.2s ease, gap 0.2s ease;
}
.modular-features .feature-more i {
  font-size: 0.75rem;
  transition: transform 0.2s ease;
}
.modular-features .column-link:hover .feature-more {
  color: var(--q2-cta, var(--q2-accent));
  gap: 0.6rem;
}
.modular-features .column-link:hover .feature-more i {
  transform: translateX(2px);
}
.modular-features .feature-icon {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.modular-features .feature-icon i {
  font-size: 1.5rem;
  color: var(--q2-text-strong);
}
/* Card titles are h3 now, not h6: they sit under the section's h2, and h6
   skipped three levels (axe: heading-order). The look is unchanged -- it was
   never about the element, only about this rule. */
.modular-features .feature-title {
  font-size: 1.0625rem;
  font-weight: 600;
  margin: 0;
  letter-spacing: 0;
  line-height: 1.3;
  color: var(--q2-text-strong);
}
.modular-features .feature-title::before { display: none; }
.modular-features .feature-content p {
  color: var(--q2-text-muted);
  font-size: 0.9375rem;
  line-height: 1.55;
  margin: 0;
}

/* ---------- Modular: Text ---------- */

.modular-text.bg-gray {
  background: var(--q2-bg-muted);
}
.modular-text .columns {
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
}
@media (min-width: 860px) {
  .modular-text .columns {
    grid-template-columns: 1fr 1fr;
    gap: 3.5rem;
  }
  .modular-text .columns.align-left > div:first-child {
    order: 2;
  }
}
/* Text-only variant of the text+image section: spans the grid, but keeps a
   readable measure rather than running the full container width. */
.modular-text .column-full { grid-column: 1 / -1; max-width: 72ch; }

.modular-text img {
  border-radius: var(--q2-radius-md);
  box-shadow: var(--q2-shadow-card);
  width: 100%;
  height: auto;
}
/* For self-contained illustrations (already visually delimited) that
   shouldn't be framed like a photo card. */
.modular-text.img-frameless img {
  border-radius: 0;
  box-shadow: none;
}

/* ---------- Footer ---------- */

/* Override Pico classless body>footer max-width so we can go full-bleed */
body > footer,
body > header,
body > main {
  max-width: none;
  padding: 0;
  width: 100%;
}

#footer {
  border-top: 1px solid var(--q2-divider);
  padding-block: 2.5rem;
  background: var(--q2-bg);
  color: var(--q2-text-muted);
  font-size: 0.875rem;
  text-align: center;
}
#footer a {
  color: var(--q2-text);
  text-decoration: none;
  font-weight: 500;
}
#footer a:hover {
  text-decoration: underline;
}
#footer p {
  margin: 0;
}
.bg-gray {
  background: var(--q2-bg-muted);
}

/* ---------- Pulse / icons ---------- */
.pulse {
  animation: q2-pulse 1.8s ease-in-out infinite;
  display: inline-block;
}
@keyframes q2-pulse {
  0%,
  100% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(1.15);
    opacity: 0.7;
  }
}

.fa,
.fas,
.fa-solid,
.fa-regular,
.far,
.fa-brands,
.fab {
  display: inline-block;
  line-height: 1;
}

/* ---------- Error ---------- */
#error {
  min-height: 60vh;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 4rem 1rem;
}
#error h1 {
  font-size: clamp(3rem, 10vw, 6rem);
  margin-bottom: 0.5rem;
}
#error p {
  max-width: 52ch;
  margin-inline: auto;
  color: var(--q2-text-muted);
}

/* ---------- Pagination ---------- */
.pagination {
  display: flex;
  justify-content: center;
  /* The row is capped at ~11 items by the pagination plugin's `delta: 2` (see
     user/config/plugins/pagination.yaml), but 11 targets still do not fit a
     390px phone. Wrapping is the graceful failure -- without it the row simply
     ran off the side and /blog became the only horizontally-scrolling page on
     the site. Belt and braces on purpose: the delta cap is config a future
     edit could undo, whereas this keeps the layout contained regardless. */
  flex-wrap: wrap;
  gap: 0.375rem;
  margin: 2rem 0 0;
  list-style: none;
  padding: 0;
}
.pagination li {
  list-style: none; /* override Pico's `ul li { list-style: square }` */
}
.pagination li a,
.pagination li span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  height: 36px;
  padding: 0 0.625rem;
  border-radius: var(--q2-radius-sm);
  text-decoration: none;
  color: var(--q2-text);
  background: var(--q2-bg-elev);
  box-shadow: var(--q2-shadow-card);
  font-size: 0.875rem;
}
.pagination li.active a,
.pagination li.active span {
  background: var(--q2-text);
  color: var(--q2-text-inverse);
}

/* ---------- Scroll-top ---------- */
body.scrolled #header {
  box-shadow:
    0 1px 0 var(--q2-nav-border),
    0 8px 24px -12px rgba(0, 0, 0, 0.08);
}

/* ---------- Utilities ---------- */
.mt-1 {
  margin-top: 0.5rem;
}
.mt-2 {
  margin-top: 1rem;
}
.mb-2 {
  margin-bottom: 1rem;
}
.mr-10 {
  margin-right: 0.625rem;
}

img {
  max-width: 100%;
  height: auto;
}

/* Focus ring everywhere */
:focus-visible {
  outline: 2px solid var(--q2-focus-ring);
  outline-offset: 2px;
  border-radius: var(--q2-radius-xs);
}

/* Selection */
::selection {
  background: var(--q2-text);
  color: var(--q2-text-inverse);
}

/* Reduce motion */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================
   Modular section styles
   ------------------------------------------------------------
   These used to be generated per-request via each template's own
   assets.addInlineCss() call. Moved here as static CSS because
   Grav's full-page cache can serve a cached page whose content
   didn't actually re-run through Twig this request -- addInlineCss
   is a runtime side effect on the shared Assets service, not part
   of the cached HTML string, so a cache hit silently dropped these
   <style> blocks (confirmed live, 2026-07-29: modular sections
   rendered unstyled once page caching was enabled). None of these
   rules ever depended on page content -- only the page.header/media
   values these templates use live in the HTML markup, never in the
   styling -- so moving them here changes nothing about how they
   look, only when the CSS gets generated.
   ============================================================ */

/* Footer layout (base link/color treatment lives further up, see #footer above) */
#footer { text-align: left; }
#footer .footer-newsletter {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 2rem;
  padding-bottom: 2rem;
  margin-bottom: 2rem;
  border-bottom: 1px solid var(--q2-divider);
}
#footer .footer-newsletter-copy { max-width: 40ch; }
#footer .footer-newsletter-copy .footer-heading { margin-bottom: .375rem; }
#footer .footer-newsletter-copy p { margin: 0; color: var(--q2-text-muted); }
#footer .footer-newsletter-form { display: flex; gap: .5rem; flex-shrink: 0; }
#footer .footer-newsletter-form input[type="email"] { width: auto; flex: 1 1 220px; }
/* quark2's blades.min.css has `button[type="submit"] { width: 100% }`
   ((0,1,1) specificity), which outranks the theme's own `.btn { width: auto }`
   ((0,1,0)) -- the Subscribe button stretched to fill the flex row and
   overflowed past the container edge until width was reasserted here. */
#footer .footer-newsletter-form .btn { flex-shrink: 0; white-space: nowrap; width: auto; }
@media (max-width: 768px) {
  #footer .footer-newsletter { flex-direction: column; align-items: stretch; }
  #footer .footer-newsletter-form { flex-direction: column; }
  #footer .footer-newsletter-form input[type="email"] { flex-basis: auto; }
}
/* 4 columns now the contact block exists. The brand column keeps the widest
   share because it carries the description paragraph; the three link columns
   are equal so their headings line up on a common grid. */
#footer .footer-grid { display: grid; gap: 2rem; grid-template-columns: 1.6fr 1fr 1fr 1.1fr; margin-bottom: 2rem; }
#footer .footer-address { font-style: normal; color: var(--q2-text-muted); margin-top: .9rem; font-size: .875rem; line-height: 1.55; }
@media (max-width: 1024px) {
  /* Four columns get too narrow to hold "Data & Content Enrichment" on one
     line before the single-column breakpoint takes over at 860px. */
  #footer .footer-grid { grid-template-columns: 1fr 1fr; }
}
#footer .footer-brand .navbar-brand img { height: 28px; width: auto; }
#footer .footer-brand p { color: var(--q2-text-muted); margin-top: 1rem; max-width: 32ch; }
#footer .footer-social { display: flex; gap: 1rem; margin-top: 1.25rem; }
#footer .footer-social a { color: var(--q2-text-muted); font-size: 1.25rem; }
#footer .footer-social a:hover { color: var(--q2-text); }
/* Footer column headings are h2 now, not h6 -- coming after the page's own
   h2/h3s, an h6 was a level skip. The small-caps treatment is presentation,
   so it moved to a class and the element got the level it should have had.
   ::before is suppressed because the global h2 rule draws the eyebrow bar. */
#footer .footer-heading { margin: 0 0 1rem; text-transform: uppercase; letter-spacing: .04em; font-size: .8rem; font-weight: 600; color: var(--q2-text-muted); }
#footer .footer-heading::before { display: none; }
#footer .footer-links { list-style: none; margin: 0; padding: 0; display: grid; gap: .6rem; }
/* Pico ships a bare `li { list-style: square }`, which beats the `none`
   inherited from the <ul> above -- a rule on the element always wins over an
   inherited value, however specific the ancestor selector is. Without this the
   footer nav renders as a bulleted list on every page. Same override the
   pagination already needs; see `.pagination li`. */
#footer .footer-links li { list-style: none; }

/* ---------- Blog post (templates/item.html.twig) ---------- */

/* Shorter than the marketing heroes: an article header is wayfinding, not a
   billboard, and the reader came for the text below it. */
.hero.hero-post {
  padding-block: clamp(2.5rem, 6vw, 4rem);
  text-align: left;
}
.hero.hero-post h1 {
  /* Post titles are sentences, not 3-word page names -- the marketing h1 scale
     (up to 3.75rem) turns a normal headline into four stacked lines. */
  font-size: clamp(1.875rem, 3.2vw + 0.75rem, 2.75rem);
  font-weight: 700;
  line-height: 1.15;
  max-width: 24ch;
  margin-bottom: 1rem;
}
.hero.hero-post .post-back { margin-bottom: 1rem; font-size: 0.9375rem; }
.hero.hero-post .post-back a { color: rgba(255, 255, 255, 0.85); text-decoration: none; }
.hero.hero-post .post-back a:hover { color: #ffffff; text-decoration: underline; }
/* The article header has no content below it inside the hero, so the scroll
   affordance has nothing to reveal -- it just adds a dangling glyph. */
.hero.hero-post #to-start { display: none; }

/* Date and tags were sibling inline <span>s, so tag chips wrapped on top of the
   date on any post with more than two or three tags. */
.hero.hero-post .post-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.875rem;
}
.hero.hero-post .post-meta .blog-date { color: rgba(255, 255, 255, 0.8); font-size: 0.875rem; }
.hero.hero-post .post-meta .tags { display: flex; flex-wrap: wrap; gap: 0.375rem; }
.hero.hero-post .post-meta .tags a {
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: #ffffff;
  border-radius: var(--q2-radius-pill);
  padding: 0.15rem 0.6rem;
  font-size: 0.75rem;
  text-decoration: none;
}
.hero.hero-post .post-meta .tags a:hover { background: rgba(255, 255, 255, 0.26); text-decoration: none; }

/* Article measure. Body copy previously ran the full container width -- around
   1300px, or ~180 characters per line, which is roughly triple a comfortable
   measure and the single biggest readability problem on the old post pages. */
.blog-post .content-item,
.blog-post .prev-next,
.blog-post .post-cta { max-width: 68ch; margin-inline: auto; }

.blog-post .e-content > :first-child { margin-top: 0; }

/* Migrated posts use <h1> for in-body section headings (a WordPress-era habit),
   so a single article could contain five elements all rendering at the 3.75rem
   page-title scale. Normalise every in-body heading down one visual step; the
   real page title lives in the hero and is the only h1-scale element left. */
.blog-post .e-content h1 {
  font-size: clamp(1.5rem, 2.5vw + 0.5rem, 2.125rem);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--q2-text-strong);
  position: relative;
}
.blog-post .e-content h1::before {
  content: "";
  display: block;
  width: 28px;
  height: 3px;
  border-radius: 2px;
  background: var(--q2-brand);
  margin-bottom: 0.7rem;
}
.blog-post .e-content h2 { font-size: clamp(1.25rem, 1.5vw + 0.5rem, 1.5rem); font-weight: 600; }
.blog-post .e-content h2::before { display: none; }

/* Legacy image handling.
 *
 * The migrated posts overwhelmingly open with `<p><img ...>Body text...</p>` --
 * the image is *inline*, not floated. An inline image sets the line box to its
 * own height, so exactly one line of text sits on its baseline, then the rest
 * of the paragraph resumes underneath: a 400px image with six words beside it
 * and a large empty gutter. It reads as a layout bug, and it is on every post
 * that carried a featured image (65 of 111).
 *
 * Promoting a leading image to a block turns it into the article's cover, which
 * is what it was in WordPress anyway. Scoped to `:first-child` so genuinely
 * inline imagery mid-sentence (badges, emoji-sized icons) is left alone. */
.blog-post .e-content img { border-radius: var(--q2-radius); }
.blog-post .e-content p > img:first-child {
  display: block;
  margin: 0 auto 1.5rem;
}
.blog-post .e-content p > img:first-child:only-child {
  margin-bottom: 0;
}

/* Posts that *do* use WordPress's float classes keep them on wide screens, but
   a 420px float inside a 68ch measure leaves a two-word column beside it once
   the viewport narrows. */
@media (max-width: 700px) {
  .blog-post .e-content img[style*="float"],
  .blog-post .e-content img.alignleft,
  .blog-post .e-content img.alignright {
    float: none !important;
    display: block;
    margin: 1.5rem auto !important;
  }
}

.blog-post .prev-next { margin-top: 3rem; }

.blog-post .post-cta {
  margin-top: 3rem;
  padding: clamp(1.75rem, 4vw, 2.5rem);
  background: var(--q2-bg-elev);
  border: 1px solid var(--q2-border-ring);
  border-radius: var(--q2-radius-lg);
  box-shadow: var(--q2-shadow-card);
  text-align: center;
}
.blog-post .post-cta h2 { margin-top: 0; font-size: clamp(1.25rem, 1.5vw + 0.5rem, 1.5rem); }
.blog-post .post-cta h2::before { display: none; }
.blog-post .post-cta p { color: var(--q2-text-muted); max-width: 48ch; margin-inline: auto; }
.blog-post .post-cta-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: center;
  margin-top: 1.5rem;
}

/* ---------- Brand page (templates/brand.html.twig) ---------- */

.brand-page > .container { max-width: 960px; }
.brand-page h2 { margin-top: 3.5rem; }
.brand-page h2:first-of-type { margin-top: 0; }
.brand-page .brand-intro { max-width: 62ch; }

.brand-logos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.5rem;
  margin-top: 1.75rem;
}
.brand-logo-card { margin: 0; }
/* Each plate shows the variant on the background it is *for*, which is the
   only way a reversed logo can be judged -- shown on white it just looks
   broken. Fixed colours in both themes for the same reason: these are
   specimens, not UI. */
.brand-logo-plate {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 150px;
  padding: 1.75rem;
  border-radius: var(--q2-radius-md);
  background: #ffffff;
  box-shadow: inset 0 0 0 1px var(--q2-border-strong);
}
.brand-logo-card.is-dark .brand-logo-plate {
  background: radial-gradient(ellipse at center, #1c5fac 0%, #123f80 55%, #0a2c5e 100%);
  box-shadow: none;
}
.brand-logo-plate img { max-width: 100%; max-height: 100%; width: auto; height: auto; display: block; }
/* Pico tints <figcaption> with its muted colour, which the label inherits --
   so the name of each asset read fainter than the description under it. */
.brand-logo-card figcaption { display: flex; flex-direction: column; gap: .2rem; margin-top: .75rem; font-size: .875rem; }
.brand-logo-card figcaption strong { color: var(--q2-text-strong); }
.brand-logo-card figcaption span { color: var(--q2-text-muted); }
.brand-download { margin-top: .35rem; font-weight: 600; font-size: .875rem; }
.brand-filemeta { color: var(--q2-text-muted); font-weight: 400; font-size: .8125rem; }

.brand-rules { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1.5rem; margin-top: 1.5rem; }
.brand-rule h3 { margin-top: 0; font-size: 1.0625rem; }
.brand-rule p { color: var(--q2-text-muted); font-size: .9375rem; margin: 0; }

.brand-donts { list-style: none; margin: 1.5rem 0 0; padding: 0; display: grid; gap: .65rem; max-width: 68ch; }
.brand-donts li { list-style: none; display: flex; gap: .65rem; align-items: flex-start; font-size: .9375rem; }
.brand-donts i { color: #c0392b; margin-top: .3rem; font-size: .8125rem; }

.brand-swatches { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1.25rem; margin-top: 1.5rem; }
.brand-swatch { margin: 0; }
.brand-chip { height: 88px; border-radius: var(--q2-radius); }
.brand-swatch figcaption { display: flex; flex-direction: column; gap: .15rem; margin-top: .6rem; font-size: .875rem; }
.brand-swatch figcaption strong { color: var(--q2-text-strong); }
.brand-swatch code { font-size: .8125rem; color: var(--q2-text-muted); background: none; padding: 0; }
.brand-swatch figcaption span { color: var(--q2-text-muted); font-size: .8125rem; line-height: 1.45; }

.brand-fonts { display: grid; gap: 1.75rem; margin-top: 1.5rem; }
.brand-font-sample { font-size: clamp(1.75rem, 4vw, 2.75rem); line-height: 1.15; margin: 0 0 .4rem; color: var(--q2-text-strong); }
.brand-font-meta { margin: 0; color: var(--q2-text-muted); font-size: .9375rem; }

.brand-footnote { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--q2-divider); color: var(--q2-text-muted); font-size: .9375rem; }

/* ---------- Highlights strip (modular/highlights.html.twig) ---------- */

.highlights {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0;
  margin: 2rem 0 0;
  padding: 0;
}
.highlight { padding: .25rem 2rem; }
/* Hairline separators instead of card borders: these are three facets of one
   claim, not three separate objects. `:not(:first-child)` rather than a border
   on every item so the row does not open with a stray rule. */
.highlight:not(:first-child) { border-left: 1px solid var(--q2-divider); }
.highlight:first-child { padding-left: 0; }
.highlight-term {
  font-family: var(--pico-font-family-display);
  font-size: clamp(1.5rem, 2vw + .5rem, 2rem);
  line-height: 1.1;
  color: var(--q2-text-strong);
  margin: 0 0 .4rem;
}
.highlight-text {
  margin: 0;
  color: var(--q2-text-muted);
  font-size: .9375rem;
  line-height: 1.5;
  max-width: 30ch;
}
@media (max-width: 720px) {
  /* Stacked, the vertical rules become horizontal ones between rows. */
  .highlight { padding: 1rem 0; }
  .highlight:first-child { padding-top: 0; }
  .highlight:not(:first-child) { border-left: 0; border-top: 1px solid var(--q2-divider); }
}

/* ---------- Third-party embed (modular/embed.html.twig) ---------- */

.modular-embed .embed-frame {
  position: relative;
  height: var(--embed-height, 700px);
  margin-top: 1.5rem;
  border: 1px solid var(--q2-border-ring);
  border-radius: var(--q2-radius-md);
  overflow: hidden;
  /* The ClickUp form paints its own white background. On the dark canvas an
     untreated iframe is a hard white slab against near-black; the surface
     token keeps the container consistent while the vendor content loads. */
  background: var(--q2-bg-elev);
}
.modular-embed .embed-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .9rem;
  color: var(--q2-text-muted);
  font-size: .9375rem;
}
.modular-embed .embed-spinner {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 2px solid var(--q2-border-strong);
  border-top-color: var(--q2-cta, var(--q2-accent));
  animation: q2-spin .8s linear infinite;
}
@keyframes q2-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .modular-embed .embed-spinner { animation: none; }
}
.modular-embed .embed-iframe {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  /* Revealed on load rather than the placeholder being removed, so the swap
     costs no reflow. */
  opacity: 0;
  transition: opacity .25s ease;
}
.modular-embed .embed-iframe.is-loaded { opacity: 1; }
.modular-embed .embed-fallback { margin-top: 1rem; color: var(--q2-text-muted); font-size: .9375rem; }

/* ---------- Error page (templates/error.html.twig) ---------- */

.error-page { text-align: center; padding-block: clamp(3rem, 8vw, 6rem); }
/* The big "404". At opacity .35 it fell below even the 3:1 that large text
   needs, so axe flagged it as unreadable -- and it is the one element on the
   page that states what actually happened. Toned back up rather than hidden;
   it carries information, so it should be legible. */
.error-page .error-code {
  font-family: var(--pico-font-family-display);
  font-size: clamp(3.5rem, 10vw, 6rem);
  line-height: 1;
  margin: 0 0 .5rem;
  color: var(--q2-text-muted);
  opacity: .75;
}
.error-page h1 { margin: 0 0 .75rem; font-size: clamp(1.75rem, 3vw + 1rem, 2.5rem); }
.error-page .error-lead { color: var(--q2-text-muted); max-width: 46ch; margin: 0 auto 2rem; }
/* Just a width cap and centring -- the search component brings its own
   styling (see partials/search-toggle.html.twig). */
.error-search { max-width: 520px; margin: 0 auto 2rem; text-align: left; }
.error-links { display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; justify-content: center; font-size: .9375rem; }

/* ---------- External link marker (js/external-links.js) ---------- */

/* Drawn as a masked SVG rather than a Font Awesome glyph: the icon set loads
   from a CDN and this marker appears in body copy, so a font that fails or
   loads late would leave a tofu box mid-sentence. A mask also inherits
   currentColor for free, so the marker matches its link in both themes and on
   the dark brand bands. */
a[data-external]::after {
  content: "";
  display: inline-block;
  width: 0.72em;
  height: 0.72em;
  /* Sits on the cap height rather than the baseline, which is where the glyph
     of an arrow-out-of-box reads as belonging to the words before it. */
  vertical-align: -0.02em;
  margin-left: 0.3em;
  background-color: currentColor;
  opacity: 0.6;
  -webkit-mask: var(--q2-external-icon) no-repeat center / contain;
  mask: var(--q2-external-icon) no-repeat center / contain;
}
a[data-external]:hover::after { opacity: 0.95; }

:root {
  --q2-external-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3Cpolyline points='15 3 21 3 21 9'/%3E%3Cline x1='10' y1='14' x2='21' y2='3'/%3E%3C/svg%3E");
}

/* Buttons are NOT exempt.
 *
 * They were, on the reasoning that a marker crammed against a button label
 * would read as an artefact. That was wrong twice over: modular/cta.html.twig
 * was separately inserting the same icon into buttons by hand, so a button got
 * a marker only when a template author had remembered to add one -- and those
 * hand-marked buttons demonstrably looked fine. The result was three buttons
 * leaving for crawler.norconex.com unmarked on one page while two identical
 * ones a section below were marked.
 *
 * The manual icon has been removed from that template; this rule is the only
 * mechanism now, so every outbound control is marked the same way.
 *
 * Icon-only links (logo, footer social) never reach this rule at all -- the
 * JS skips anything with no text, so no exemption is needed for them. */
.btn[data-external]::after {
  /* Slightly tighter against a button label than in flowing body copy. */
  margin-left: 0.4em;
  opacity: 0.75;
}
.btn[data-external]:hover::after { opacity: 1; }

/* Visible to screen readers only -- the search input needs a label, but a
   visible one would crowd a deliberately sparse page. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- Signpost cross-link (modular/signpost.html.twig) ---------- */

.modular-signpost { padding-block: 0; }
.signpost {
  display: flex;
  align-items: center;
  gap: 1rem;
  max-width: 860px;
  margin-inline: auto;
  padding: 0.9rem 1.25rem;
  border: 1px solid var(--q2-border-ring);
  border-radius: var(--q2-radius-md);
  background: var(--q2-bg-elev);
  color: var(--q2-text);
  text-decoration: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.signpost:hover {
  border-color: var(--q2-border-strong);
  background: var(--q2-bg-muted);
  text-decoration: none;
  color: var(--q2-text);
}
.signpost-icon { color: var(--q2-text-muted); font-size: 1.125rem; flex: 0 0 auto; }
.signpost-text { flex: 1 1 auto; font-size: 0.9375rem; }
.signpost-detail { display: block; color: var(--q2-text-muted); font-size: 0.875rem; margin-top: 0.15rem; }
.signpost-link {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--q2-link);
  white-space: nowrap;
}
.signpost:hover .signpost-link i { transform: translateX(2px); }
.signpost-link i { font-size: 0.8125rem; transition: transform 0.15s ease; }
@media (max-width: 640px) {
  /* Stacked and left-aligned: at this width the label would otherwise be
     squeezed against the question with no breathing room between them. */
  .signpost { flex-wrap: wrap; align-items: flex-start; gap: 0.5rem 0.75rem; }
  .signpost-text { flex: 1 1 100%; order: 2; }
  .signpost-link { order: 3; }
}

/* ---------- Closing CTA band (modular/cta.html.twig) ---------- */

/* A contained surface rather than bare buttons on the page canvas, so the
   closing action reads as a deliberate destination instead of leftover
   controls. Centred here (and only here) because it is a terminal block with
   no content following it -- everything else on the site stays left-aligned. */
.modular-cta .cta-band {
  max-width: 760px;
  margin-inline: auto;
  padding: clamp(2rem, 5vw, 3rem) clamp(1.25rem, 4vw, 2.5rem);
  background: var(--q2-bg-elev);
  border: 1px solid var(--q2-border-ring);
  border-radius: var(--q2-radius-lg);
  box-shadow: var(--q2-shadow-card);
  text-align: center;
}
/* The eyebrow bar is a left-edge marker; on a centred block it floats
   unattached above the heading, so it is suppressed the same way .text-center
   already suppresses it. */
.modular-cta .cta-band h2::before { display: none; }
.modular-cta .cta-band h2 { margin-top: 0; }
.modular-cta .cta-band p { color: var(--q2-text-muted); margin-inline: auto; max-width: 52ch; }
.modular-cta .cta-actions {
  margin-top: 1.75rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: center;
}
@media (max-width: 560px) {
  /* Stacked full-width targets beat three shrunken side-by-side buttons. */
  .modular-cta .cta-actions { flex-direction: column; align-items: stretch; }
}

/* Header CTA. Hidden below the nav breakpoint, where the hamburger takes over
   and the overlay carries its own full-width Contact button instead -- keeping
   it would crowd the compact header next to the search and theme toggles. */
.navbar-section.actions .nav-cta {
  margin-right: 0.25rem;
  white-space: nowrap;
}
@media (max-width: 860px) {
  .navbar-section.actions .nav-cta { display: none; }
}
.overlay-cta {
  margin-top: 1.75rem;
  display: flex;
  justify-content: center;
}
.overlay-cta .btn { width: 100%; max-width: 260px; }

/* Theme-aware logo pair (see partials/logo.html.twig). Exactly one of the two
   is displayed; the other is removed from layout entirely so the wordmark does
   not reserve double width. Driven by the `data-theme` attribute the pre-paint
   script sets, so the correct one is showing on the very first frame. */
.navbar-brand .logo-dark { display: none; }
:root[data-theme="dark"] .navbar-brand .logo-light { display: none; }
:root[data-theme="dark"] .navbar-brand .logo-dark { display: inline-block; }
#footer .footer-bottom { border-top: 1px solid var(--q2-divider); padding-top: 1.5rem; text-align: center; }
@media (max-width: 768px) {
  #footer .footer-grid { grid-template-columns: 1fr; }
}

/* Announcement bar (modular/announcement.html.twig) */
.modular-announcement { background: var(--q2-bg-muted); border-bottom: 1px solid var(--q2-divider); padding-block: 1.25rem; }
.announcement-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 1rem 1.5rem; text-align: center; }
.announcement-badge { display: inline-flex; align-items: center; gap: .4rem; background: var(--q2-bg-inverse); color: var(--q2-text-inverse); font-weight: 700; font-size: .75rem; letter-spacing: .04em; text-transform: uppercase; padding: .3rem .7rem; border-radius: 999px; white-space: nowrap; }
.announcement-text { font-weight: 600; }
.announcement-text span { font-weight: 400; opacity: .75; }
.announcement-links { display: flex; gap: .75rem; flex-wrap: wrap; justify-content: center; }

/* Comparison columns (modular/compare.html.twig) */
.compare-columns {
  display: grid;
  gap: 2rem;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  margin-top: 2rem;
}
.compare-card {
  border: 1px solid var(--q2-border-ring);
  border-radius: var(--q2-radius-md);
  box-shadow: var(--q2-shadow-card);
  padding: 2rem;
  display: flex;
  flex-direction: column;
  text-align: center;
}
.compare-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: color-mix(in oklab, var(--q2-accent) 12%, white 88%);
  margin: 0 auto 1rem;
}
.compare-icon i {
  font-size: 1.5rem;
  color: var(--q2-accent);
}
.compare-card h3 {
  margin-top: 0;
}
.compare-intro {
  opacity: .75;
  margin-top: -.25rem;
}
.compare-card ul {
  text-align: left;
  flex-grow: 1;
  margin-top: 1rem;
}
.compare-card ul li {
  margin-bottom: .5rem;
}

/* Lightbox gallery (modular/gallery.html.twig) */
.lightbox-gallery { max-width: 1200px; margin: 0 auto; padding: 0 1.25rem; }
.lightbox-gallery .lightbox-gallery__columns { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.lightbox-gallery img { display: block; width: 100%; height: auto; border-radius: var(--q2-radius-md); box-shadow: var(--q2-shadow-card); transition: transform .2s ease, filter .2s ease; }
.lightbox-gallery img:hover { transform: translateY(-2px); filter: brightness(0.92); }
.lightbox-gallery .hidden { display: none; }

/* Client logo grid (modular/logos.html.twig) */
/* Client logos arrive as a mix of formats (JPGs, opaque PNGs, a palette
   PNG, a few genuinely-transparent PNGs) -- no way to make them all
   transparent without re-editing each source file. Giving every logo the
   same white tile makes that inconsistency invisible instead of fighting
   it, and incidentally fixes dark-mode contrast since visibility no
   longer depends on each file's own background. Fixed light color in
   both themes on purpose: the logos are drawn assuming a light backdrop. */
.logo-grid { max-width: 1200px; margin: 0 auto; padding: 0 1.25rem; display: grid; gap: 1.25rem; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
.logo-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 96px;
  padding: 1rem 1.25rem;
  background: #ffffff;
  border-radius: var(--q2-radius-md);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.08);
}
/* .88 rather than .75: greyscale already does the work of making these read as
   a set, and the extra knock-down pushed the inherently light marks (fragFINN,
   Vital Insights) close to invisible while the dark ones still held up. The
   dimming should equalise them, not punish the pale ones twice. */
.logo-tile img { display: block; width: 100%; height: 100%; object-fit: contain; filter: grayscale(1); opacity: .88; transition: filter .2s ease, opacity .2s ease; }
.logo-tile:hover img { filter: grayscale(0); opacity: 1; }

/* Wordmark fallback for entries with no usable logo file. Set in the display
   face at the same optical weight as the surrounding marks so the grid reads
   as one set rather than "logos, plus some text where we gave up". */
.logo-tile-text .logo-wordmark {
  font-family: var(--pico-font-family-display);
  font-size: 1.0625rem;
  line-height: 1.2;
  text-align: center;
  color: #4a4a4f;
  letter-spacing: -0.01em;
}

/* Pipeline / sitemap diagram (modular/pipeline.html.twig) */
/* Reusable pipeline/sitemap diagram -- shows how the Products & Services
 * pages fit together as one technical flow, with the current page's part
 * highlighted. Built as CSS Grid + real Font Awesome glyphs on purpose:
 * boxes, straight connector lines, and icon glyphs are reliable to render
 * cleanly, unlike hand-drawn/illustrative SVG. */
.pipeline-section {
  background: radial-gradient(ellipse at center, #1c5fac 0%, #123f80 55%, #0a2c5e 100%);
  padding-block: clamp(2.5rem, 6vw, 4rem);
}
/* Heading + lead for the standalone placement on /products. Centred because
   the diagram beneath it is symmetrical and left-aligning the heading against
   a centred diagram reads as a mistake. */
.pipeline-intro {
  max-width: 1000px;
  margin: 0 auto 2rem;
  padding-inline: 1.25rem;
  text-align: center;
  color: #ffffff;
}
.pipeline-intro h2 { color: #ffffff; margin-top: 0; }
.pipeline-intro h2::before { display: none; }
.pipeline-intro p {
  color: rgba(255, 255, 255, 0.82);
  max-width: 62ch;
  margin-inline: auto;
  margin-bottom: 0;
}
.pipeline-diagram {
  max-width: 1000px;
  margin: 0 auto;
  padding-inline: 1.25rem;
  display: grid;
  grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr;
  align-items: center;
  justify-items: center;
  row-gap: .5rem;
  column-gap: 1.25rem;
}
/* The two services that wrap the pipeline. Rendered as a pair of pills rather
   than one combined label so "Support" and "Professional Services" read as two
   purchasable things -- each links to its own page and lights up on it. The
   role line under each name ("design & build it" / "keep it running") is the
   shortest honest statement of the difference between them. */
.pipeline-services {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: .75rem;
  margin-bottom: .25rem;
}
.pipeline-service {
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .5rem 1.1rem;
  border-radius: var(--q2-radius-pill);
  border: 1px solid rgba(255, 255, 255, .3);
  background: rgba(255, 255, 255, .08);
  color: rgba(255, 255, 255, .82);
  font-size: .9rem;
  text-decoration: none;
  transition: background .2s ease, border-color .2s ease, color .2s ease, transform .2s ease;
}
.pipeline-service:hover {
  background: rgba(255, 255, 255, .16);
  border-color: rgba(255, 255, 255, .5);
  color: #ffffff;
  text-decoration: none;
}
.pipeline-service-name { font-weight: 600; }
.pipeline-service-role {
  opacity: .72;
  font-size: .8125rem;
}
.pipeline-service-role::before {
  content: "\00b7";
  margin-right: .4rem;
}
.pipeline-service.is-active {
  background: var(--q2-cta, var(--q2-accent));
  border-color: var(--q2-cta, var(--q2-accent));
  color: #171b26;
  transform: scale(1.04);
  box-shadow: 0 8px 20px rgba(0, 0, 0, .25);
}
.pipeline-service.is-active:hover {
  background: var(--q2-cta, var(--q2-accent));
  color: #171b26;
}
.pipeline-service i { font-size: .95rem; }
.pipeline-connector-v {
  grid-column: 1 / -1;
  width: 2px;
  height: 22px;
  background: rgba(255, 255, 255, .3);
}
.pipeline-arrow.pipeline-support-connector {
  display: none;
  grid-column: 1 / -1;
}
.pipeline-node {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .4rem;
  text-align: center;
  padding: .85rem .6rem;
  border-radius: var(--q2-radius-md);
  border: 1px solid rgba(255, 255, 255, .3);
  background: rgba(255, 255, 255, .06);
  color: rgba(255, 255, 255, .75);
  font-size: .8rem;
  font-weight: 600;
  line-height: 1.25;
  width: 100%;
  min-height: 76px;
  justify-content: center;
  transition: background .2s ease, border-color .2s ease, color .2s ease, transform .2s ease;
}
.pipeline-node i { font-size: 1.15rem; }
/* The one node that is a Norconex product, and therefore the one that is a
   link. Solid box + brighter fill distinguishes it from the dashed context
   nodes either side of it. */
a.pipeline-node { text-decoration: none; }
.pipeline-node.is-product {
  border-color: rgba(255, 255, 255, .55);
  background: rgba(255, 255, 255, .14);
  color: #ffffff;
}
a.pipeline-node.is-product:hover {
  background: rgba(255, 255, 255, .24);
  border-color: #ffffff;
  color: #ffffff;
  text-decoration: none;
}
.pipeline-node.is-context {
  border-style: dashed;
  font-weight: 500;
  opacity: .8;
}
.pipeline-arrow {
  color: rgba(255, 255, 255, .35);
  font-size: 1rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .3rem;
  min-width: 3.5rem;
}
.pipeline-arrow .pipeline-arrow-label {
  font-size: .68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  white-space: nowrap;
  color: rgba(255, 255, 255, .55);
}

/* Active state -- the current page's part of the diagram, brighter and
 * larger than the rest rather than a separate visual language. */
.pipeline-node.is-active {
  background: var(--q2-cta, var(--q2-accent));
  border-color: var(--q2-cta, var(--q2-accent));
  color: #171b26;
  transform: scale(1.06);
  box-shadow: 0 8px 20px rgba(0, 0, 0, .25);
}
.pipeline-arrow.is-active {
  color: var(--q2-cta, var(--q2-accent));
  transform: scale(1.12);
}
.pipeline-arrow.is-active .pipeline-arrow-label {
  color: var(--q2-cta, var(--q2-accent));
}

@media (max-width: 860px) {
  .pipeline-diagram {
    grid-template-columns: 1fr;
    row-gap: .65rem;
  }
  .pipeline-services,
  .pipeline-node,
  .pipeline-support-connector {
    grid-column: 1;
  }
  .pipeline-connector-v {
    display: none;
  }
  .pipeline-arrow.pipeline-support-connector {
    display: flex;
  }
  .pipeline-arrow {
    flex-direction: row;
    min-width: 0;
    width: 1.5rem;
    height: 1.5rem;
    margin-inline: auto;
    transform: rotate(90deg);
    margin-block: 0;
  }
  .pipeline-arrow.is-active {
    transform: rotate(90deg) scale(1.12);
  }
  .pipeline-arrow .pipeline-arrow-label {
    display: none;
  }
  .pipeline-node { max-width: 320px; }
  .pipeline-service.is-active,
  .pipeline-node.is-active {
    transform: none;
  }
  .pipeline-services { flex-direction: column; align-items: center; }
  .pipeline-service { width: 100%; max-width: 320px; justify-content: center; }
}

/* Recent-posts grid (modular/posts.html.twig) */
.posts-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); margin-top: 2rem; }
.posts-grid .card-title h3 { margin: 0; font-size: 1.05rem; }
.posts-grid .card-title a { color: inherit; text-decoration: none; }
.posts-grid .card-title a:hover { text-decoration: underline; }
.posts-grid .card-subtitle { color: var(--q2-text-muted); font-size: .85rem; margin-bottom: .35rem; }

/* Pricing columns (modular/pricing.html.twig) */
/* minmax was 240px, which let four cards sit side by side at 1440 -- each one
   ~250px wide, narrow enough that plan names broke mid-phrase ("Occasional
   Hands-\non") and every feature list wrapped to double height. 300px keeps
   three across on desktop and drops cleanly to two, then one. */
.pricing-columns { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); margin-top: 2rem; align-items: start; }
.pricing-card { position: relative; background: var(--q2-bg-elev); border: 1px solid var(--q2-border-ring); border-radius: var(--q2-radius-md); box-shadow: var(--q2-shadow-card); padding: 1.75rem 1.5rem; text-align: center; display: flex; flex-direction: column; height: 100%; }
.pricing-card h3 { margin-top: 0; margin-bottom: .35rem; font-size: 1.25rem; }
.pricing-card .price { margin: 0; display: flex; align-items: baseline; justify-content: center; gap: .35rem; flex-wrap: wrap; }
.pricing-card .price-amount { font-size: 2.25rem; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; color: var(--q2-text-strong); }
.pricing-card .price-period { font-size: .875rem; color: var(--q2-text-muted); }
/* Response time is the axis the plans differ on, so it gets its own row rather
   than being buried as one bullet among eight. */
.pricing-response { display: inline-flex; align-items: center; justify-content: center; gap: .4rem; margin: .85rem auto 0; padding: .3rem .7rem; border-radius: var(--q2-radius-pill); background: var(--q2-bg-muted); color: var(--q2-text-strong); font-size: .8125rem; font-weight: 600; }
.pricing-response i { font-size: .75rem; color: var(--q2-text-muted); }
.pricing-card .subtitle { opacity: .75; margin-top: .85rem; font-size: .875rem; }
.pricing-card ul { text-align: left; flex-grow: 1; font-size: .9rem; }
.pricing-card .pricing-cta { margin-top: 1.25rem; margin-bottom: 0; }
.pricing-card .pricing-cta .btn { width: 100%; }

/* Featured plan. Raised rather than recoloured, so the steer is legible in
   both themes without introducing a second accent. */
.pricing-card.is-featured { border-color: var(--q2-cta, var(--q2-accent)); box-shadow: var(--q2-shadow-raised); }
.pricing-badge { position: absolute; top: 0; left: 50%; transform: translate(-50%, -50%); background: var(--q2-cta, var(--q2-accent)); color: var(--q2-cta-contrast, #171b26); font-size: .6875rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; padding: .25rem .7rem; border-radius: var(--q2-radius-pill); white-space: nowrap; }

/* Plan footnote. Quiet: it is a qualifier, and at body size across the full
   container it competed with the plans it qualifies. Centred under the card
   row -- left-aligned inside a centred max-width block, a one-line note starts
   at an arbitrary-looking point mid-page with nothing above it to align to. */
.pricing-note { margin: 1rem auto 0; max-width: 78ch; text-align: center; font-size: .8125rem; line-height: 1.55; color: var(--q2-text-muted); }

/* The "more than a plan" band -- custom support, professional services and
   hosting. Full width under the tiers, not a fourth column. */
/* Grid, not flex: with `flex: 1 1 420px` the text block grew to nearly the
   full width and squeezed the CTA into a gap beside the last item column,
   where it read as floating debris rather than the band's action. An explicit
   `1fr auto` reserves the button its own column at every width. */
.pricing-custom { margin-top: 2rem; padding: clamp(1.5rem, 3vw, 2rem); background: var(--q2-bg-muted); border-radius: var(--q2-radius-lg); display: grid; grid-template-columns: 1fr auto; gap: 1.5rem 2rem; align-items: center; }
.pricing-custom-text h3 { margin-top: 0; margin-bottom: .5rem; font-size: 1.25rem; }
.pricing-custom-text p { color: var(--q2-text-muted); margin-bottom: .85rem; max-width: 62ch; }
.pricing-custom-items { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .35rem 1.25rem; }
.pricing-custom-items li { list-style: none; display: flex; align-items: flex-start; gap: .5rem; font-size: .875rem; }
.pricing-custom-items i { color: var(--q2-cta, var(--q2-accent)); margin-top: .25rem; font-size: .75rem; }
.pricing-custom-cta .btn { white-space: nowrap; }
@media (max-width: 860px) {
  .pricing-custom { grid-template-columns: 1fr; }
  .pricing-custom-items { grid-template-columns: 1fr; }
  .pricing-custom-cta .btn { width: 100%; }
}

/* Success-story cards (modular/stories.html.twig) */
.stories-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); margin-top: 1.5rem; }
/* Column, not row -- see the note in modular/stories.html.twig. Side by side,
   the copy got ~230px inside a 3-up grid and client names wrapped to three
   lines. */
.story-card { display: flex; flex-direction: column; gap: 1rem; background: var(--q2-bg-elev); border: 1px solid var(--q2-border-ring); border-radius: var(--q2-radius-md); box-shadow: var(--q2-shadow-card); padding: 1.25rem; height: 100%; }
/* Fixed light tile in both themes, matching .logo-tile on the home page: these
   logos are drawn assuming a light backdrop, and several have white baked into
   the file, so letting them sit on the dark card surface would show ragged
   white rectangles of differing sizes. */
.story-card .story-logo {
  align-self: flex-start;
  width: 132px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: .5rem .75rem;
  background: #ffffff;
  border-radius: var(--q2-radius-sm);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .06), 0 0 0 1px rgba(34, 42, 53, .06);
}
/* Fill the tile rather than sit at natural size. `width/height: auto` left
   square and tall marks (the Dept. of Education seal, BC Land Title) rendering
   at a fraction of the tile while wide wordmarks filled it, so the logos read
   as inconsistently sized even though the tiles were identical. object-fit
   keeps the aspect ratio while normalising the optical size. */
.story-card .story-logo img { display: block; width: 100%; height: 100%; object-fit: contain; filter: grayscale(1); opacity: .82; transition: filter .2s ease, opacity .2s ease; }
.story-card:hover .story-logo img { filter: grayscale(0); opacity: 1; }
.story-card .story-content h3 { margin-top: 0; margin-bottom: .5rem; font-size: 1.125rem; }
.story-card .story-content p { margin: 0; font-size: .9rem; color: var(--q2-text-muted); }

/* Home hero carousel (partials/hero-carousel.html.twig) */
/* Slide count varies per page edit, so the original version computed
   animation-duration and @keyframes crossfade percentages in Twig at
   render time via addInlineCss(). That's exactly the pattern that broke
   under full-page caching -- see the header comment on this section
   above. Timing is now driven by theme://js/hero-carousel.js instead: it
   reads the slide count straight from the DOM and crossfades via a plain
   class toggle, so this stylesheet has nothing left to compute and is
   safe under any caching behavior. */
.hero-carousel {
    position: relative; overflow: hidden; min-height: 460px;
    background: radial-gradient(circle at 30% 30%, #1460bf 0%, #002d83 55%, #00092e 100%);
    color: #fff;
}
.hero-carousel h1, .hero-carousel h2, .hero-carousel h3 { color: #fff; }
.hero-carousel p { color: rgba(255,255,255,.85); }
.hero-network-bg {
    position: absolute; inset: 0;
    z-index: 0;
    pointer-events: none;
    -webkit-mask-image: radial-gradient(ellipse 72% 72% at 50% 50%, #000 40%, transparent 90%);
    mask-image: radial-gradient(ellipse 72% 72% at 50% 50%, #000 40%, transparent 90%);
}
.hero-network-bg svg { display: block; width: 100%; height: 100%; }
.hero-slide {
    position: absolute; inset: 0;
    z-index: 1;
    display: grid; grid-template-columns: 1.1fr 0.9fr; align-items: center; gap: 2rem;
    padding: 4.5rem 1.25rem;
    max-width: 1200px; margin: 0 auto;
    opacity: 0;
    transition: opacity 1s ease-in-out;
}
.hero-slide:first-child,
.hero-slide.is-active { opacity: 1; }
.hero-slide .hero-slide-text { text-align: left; }
.hero-slide .hero-slide-text h1 { margin-bottom: 1rem; }
.hero-slide .hero-slide-illustration { position: relative; display: flex; justify-content: center; align-items: center; }
.hero-slide .hero-slide-illustration::before {
    content: "";
    position: absolute;
    width: 78%;
    aspect-ratio: 1 / 1;
    background: radial-gradient(circle, rgba(255,255,255,.18) 0%, rgba(255,255,255,.05) 55%, transparent 75%);
    border-radius: 50%;
    z-index: 0;
}
.hero-slide .hero-slide-illustration img,
.hero-slide .hero-slide-illustration svg {
    position: relative; z-index: 1;
    max-width: 100%; height: auto; max-height: 320px;
    filter: drop-shadow(0 20px 30px rgba(0, 9, 46, .5));
}
@media (max-width: 768px) {
    .hero-carousel { min-height: 640px; }
    .hero-slide { grid-template-columns: 1fr; text-align: center; padding: 3rem 1.25rem 4.5rem; }
    .hero-slide .hero-slide-text { text-align: center; }
    .hero-slide .hero-slide-illustration { order: -1; }
    .hero-slide .hero-slide-illustration img,
    .hero-slide .hero-slide-illustration svg { max-height: 200px; }
}

/* ---------- Cookie consent (Orestbida Cookie Consent, --cc-* vars) ---------- */

/* Mapped once, onto tokens that already flip under
   :root[data-theme="dark"] -- the banner tracks the site's light/dark toggle
   for free, with no .cc--darkmode class or separate dark ruleset to keep in
   sync. Skips the plugin's own preset themes entirely (see
   orestbida-cookie-consent.yaml: no `theme` set).

   Scoped to #cc-main, not :root: the plugin's own cookieconsent.css defines
   its --cc-* defaults on :root too and loads after theme.css, so at equal
   :root/:root specificity its later source order won -- confirmed live, the
   primary button rendered its stock dark grey instead of the site's gold.
   #cc-main is an ID selector, which outranks :root's pseudo-class
   specificity regardless of load order. */
#cc-main {
  --cc-font-family: var(--pico-font-family-sans-serif);
  --cc-bg: var(--q2-bg-elev);
  --cc-primary-color: var(--q2-text-strong);
  --cc-secondary-color: var(--q2-text-muted);
  --cc-modal-border-radius: var(--q2-radius-md);
  --cc-btn-border-radius: var(--q2-radius);
  --cc-separator-border-color: var(--q2-divider);
  --cc-overlay-bg: rgba(0, 0, 0, 0.5);
  --cc-link-color: var(--q2-link, var(--q2-accent));

  --cc-btn-primary-bg: var(--q2-cta, var(--q2-accent));
  --cc-btn-primary-color: var(--q2-cta-contrast, var(--q2-accent-contrast));
  --cc-btn-primary-border-color: var(--cc-btn-primary-bg);
  --cc-btn-primary-hover-bg: var(--cc-btn-primary-bg);
  --cc-btn-primary-hover-color: var(--cc-btn-primary-color);

  --cc-btn-secondary-bg: var(--q2-bg-muted);
  --cc-btn-secondary-color: var(--q2-text-strong);
  --cc-btn-secondary-border-color: var(--q2-border-strong);
  --cc-btn-secondary-hover-bg: var(--q2-bg);
  --cc-btn-secondary-hover-color: var(--q2-text-strong);

  --cc-cookie-category-block-bg: var(--q2-bg-muted);
  --cc-cookie-category-block-border: var(--q2-divider);
  --cc-cookie-category-block-hover-bg: var(--q2-bg);
  --cc-cookie-category-block-hover-border: var(--q2-border-strong);

  --cc-toggle-on-bg: var(--cc-btn-primary-bg);
  --cc-toggle-off-bg: var(--q2-text-muted);
  --cc-toggle-readonly-bg: var(--q2-divider);

  --cc-footer-bg: var(--q2-bg-muted);
  --cc-footer-color: var(--q2-text-muted);
  --cc-footer-border-color: var(--q2-divider);
}
#cc-main .cm,
#cc-main .pm { box-shadow: var(--q2-shadow-raised); }
