/* === _base.css (generic) === */

/* ============================================================================
 * _base.css — generic overrides applied to EVERY ported WP page.
 *
 * Loaded by `scripts/port/mirror.ts` BEFORE `<slug>.css` so per-slug files
 * can override anything here. Both files are scoped to `.mirror-root` via
 * `lib/css-scope.ts` and copied to `public/extracted/<slug>/` for the
 * mirror layout's `<link rel="stylesheet">` tags.
 *
 * Keep this file generic. Slug-specific tweaks (BEM ids `XX-YYY`, hero
 * sub-classes pinned to a single page, motion.page floating labels)
 * belong in `<slug>.css`. If you find yourself adding a rule keyed off
 * `--23-` or `--7-`, that's a per-slug rule — move it.
 * ========================================================================= */

/* --- Body restoration ----------------------------------------------------
 * Upstream's `.breakdance { ... }` block applies font-family, color, and
 * line-height to <body class="... breakdance">. Phase B.1 (translate.ts)
 * drops the `.breakdance` class entirely (no project meaning), silently
 * stripping these three declarations. Recreate them on <body> directly. */
body {
  background-color: var(--bde-background-color);
  color: var(--bde-body-text-color);
  font-family: var(--bde-body-font-family);
  font-size: var(--bde-body-font-size);
  font-weight: 200;
  line-height: 1.6;
}

/* --- Long-token safety net (Polish orphan NBSPs) -------------------------
 * strip-chrome.ts / bake-orphans.ts glue single-letter words to the word
 * after them with a NBSP (lib/typography/orphans.ts), which creates
 * unbreakable tokens. `maxGlueLength` caps how long those get; this is the
 * belt to that pair of braces — at 320 px a pathological token can still
 * never push the page into horizontal scroll. */
.mirror-root .text,
.mirror-root .rich-text,
.mirror-root .heading,
.mirror-root p,
.mirror-root li,
.mirror-root h1,
.mirror-root h2,
.mirror-root h3,
.mirror-root h4,
.mirror-root h5,
.mirror-root h6 {
  overflow-wrap: break-word;
}

/* Tailwind v4 Preflight resets margins on p / h1-h6 to 0. Upstream relies
 * on the browser's default UA stylesheet (1em top + bottom on <p>) for
 * paragraph spacing inside rich-text blocks. Restore the UA default ONLY
 * inside `.rich-text` to avoid inflating other widgets that use <p> as a
 * structural element (e.g. counter `<p class="bde-simple-counter__number">`). */
.mirror-root .rich-text p { margin-block-start: 1em; margin-block-end: 1em; }

/* --- Font shadowing ------------------------------------------------------
 * The upstream bundle's @font-face URLs point at
 * `https://imprero.com/.../conthrax_*.woff2` which fail to load from
 * localhost (cross-origin). We have the same fonts locally under
 * /fonts/conthrax/. These rules SHADOW the upstream @font-face by
 * declaring the same family name later in the cascade so the browser
 * picks our URL. */
@font-face {
  font-family: 'Conthrax Bold Italic';
  font-style: italic;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/conthrax/conthrax_bold_italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Conthrax SemiBold Italic';
  font-style: italic;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/conthrax/conthrax_semibold_italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Conthrax UltraThin Italic';
  font-style: italic;
  font-weight: 100;
  font-display: swap;
  src: url('/fonts/conthrax/conthrax_extralight_italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Conthrax Italic';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/conthrax/conthrax_regular_italic.woff2') format('woff2');
}

/* --- Diff equalizer (?no-atmosphere=1) -----------------------------------
 * When the flag is on, hide GradientField and flatten body bg so diff
 * capture compares content-only against the upstream equalizer (which
 * forces flat black via diff.ts EQUALIZER_CSS). Selector targets
 * GradientField's existing DOM signature without modifying the component
 * (see components/brand/GradientField.tsx). */
html[data-no-atmosphere="true"] [aria-hidden="true"].pointer-events-none.absolute.inset-0.-z-10 {
  display: none !important;
}
html[data-no-atmosphere="true"],
html[data-no-atmosphere="true"] body {
  background: #000 !important;
  background-image: none !important;
}

/* --- Animation infrastructure -------------------------------------------
 * The rough-notation library injects this @keyframes at runtime via JS,
 * so Phase A's static stylesheet capture missed it. Hardcoded so SVG
 * underline strokes draw on page load. */
@keyframes rough-notation-dash {
  to { stroke-dashoffset: 0; }
}

/* Clear entrance-state initial transforms once the element reaches its
 * final state. The bundled CSS contains unconditional per-element rules
 * (e.g. `.image--XX-YYY { transform: translateX(-10px) }`) that set the
 * starting frame of the upstream entrance animations. The upstream
 * runtime (and its Custom Code "snap-to-final" snippet) clears these via
 * inline `transform: none`. Mirror the same behavior in CSS scoped to
 * `.is-animated`. */
.mirror-root [data-entrance].is-animated {
  transform: none !important;
}

/* --- Container width sync (annotation #22) -------------------------------
 * Upstream bundle defines `:root { --bde-section-width: 1480px }` but the
 * brand Container uses `--container-max: 1520px`. Vertical edges of the
 * Nav logo + body sections didn't line up. Override scoped to
 * `.mirror-root` so body sections widen to 1520px (matching brand
 * Container). The 40px horizontal padding stays the same on both sides
 * → 1600px total stage width on desktop. */
.mirror-root {
  --bde-section-width: 1520px;
}

/* --- Replacement underline animation (annotations #21 + #43) -------------
 * Upstream's rough-notation lib draws a hand-drawn purple line under
 * `.dual-heading--secondary`. We don't run that JS; strip-chrome.ts
 * removes the captured (mispositioned) SVG. Replace with a CSS-only
 * pseudo-element underline that animates left → right over 2s after a 2s
 * delay, sitting 10px below the text baseline. Class is generic across
 * pages — any heading with `.dual-heading--secondary.js-annotate` gets
 * the underline. */
.mirror-root .dual-heading--secondary.js-annotate {
  position: relative;
  display: inline-block;
}
.mirror-root .dual-heading--secondary.js-annotate::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -10px;
  height: 5px;
  /* Full width from the first frame, revealed by scaleX — NOT animated from
   * `width: 0`.
   *
   * Animating width made this heading the Largest Contentful Paint element at
   * ~2.2s on the live site: the pseudo-element sits outside the text box
   * (bottom: -10px), so when it grew at the 2s mark the heading's painted
   * rectangle grew with it, and Chrome logged a new, larger contentful paint.
   * FCP was already ~200ms — the page just kept re-qualifying its LCP.
   *
   * scaleX is visually identical (still draws left → right) but the element's
   * rect is constant from first paint, so no late LCP candidate. It is also
   * cheaper: transforms composite on the GPU, whereas animating width forced
   * layout on every frame. */
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
  background: var(--bde-brand-primary-color, #7c3aed);
  animation: dual-underline-draw 2s ease-out 2s forwards;
}
@keyframes dual-underline-draw {
  to { transform: scaleX(1); }
}

/* --- product-spotlight section padding (annotation #46) ------------------
 * Product-spotlight sections (FotAi, PicBot, Mosaix, PixDron, ChiMind,
 * GraffitX, …) currently inherit `padding-top: 200px` from the bundled
 * section rule. Owner wants 150px on every spotlight. Hero sections have
 * their own override per-page in `<slug>.css` (typical 140px). */
.mirror-root .section.product-spotlight .section-container {
  padding-top: 150px;
}

/* ============================================================================
 * RESPONSIVE OVERRIDES — generic patterns
 *
 * Upstream WP pages were never optimized for narrow viewports. The bundle
 * ships 48 media queries but most target legacy widget-specific concerns;
 * the actual page-level layout (flex rows that don't wrap, fixed pixel
 * widths on cards, hard-coded inner paddings, sections pinned at 200px
 * vertical padding) stays desktop-shaped at every viewport. This block
 * fixes the recurring patterns.
 *
 * Constraints (owner-set):
 *  • No `!important` — match bundle specificity via `.mirror-root` ancestor.
 *
 * Breakpoints align to brand Tailwind defaults (lg=1024, md=768, sm=480).
 * ========================================================================= */

/* --- Section padding scales fluidly ------------------------------------- */
@media (max-width: 1023px) {
  .mirror-root {
    --bde-section-vertical-padding: 80px;
    --bde-section-horizontal-padding: 24px;
  }
  .mirror-root .section.section--hero .section-container { padding-top: 110px; }
  .mirror-root .section.product-spotlight .section-container { padding-top: 110px; }
}
@media (max-width: 767px) {
  .mirror-root {
    --bde-section-vertical-padding: 60px;
    --bde-section-horizontal-padding: 30px;
  }
  .mirror-root .section.section--hero .section-container { padding-top: 90px; padding-bottom: 60px; }
  .mirror-root .section.product-spotlight .section-container { padding-top: 70px; padding-bottom: 60px; }
}
@media (max-width: 479px) {
  .mirror-root {
    --bde-section-horizontal-padding: 30px;
  }
}

/* --- Headings scale fluidly (no media query — clamp handles all widths) -
 * Bundle: h3=3.4rem, h4=1.56rem flat across all viewports. Replace with
 * fluid clamps anchored to the bundled values at desktop end. */
.mirror-root .heading--h3,
.mirror-root h3 { font-size: clamp(1.8rem, 4.5vw, 3.4rem); }
.mirror-root .heading--h4,
.mirror-root h4 { font-size: clamp(1.1rem, 2.5vw, 1.56rem); }
.mirror-root .heading--h1,
.mirror-root h1 { font-size: clamp(1.6rem, 4vw, var(--bde-h1-font-size, 2.5rem)); }

/* --- Generic block stacking at <md --------------------------------------
 * Breakdance composes layouts as nested `.block` flex containers (rows of
 * text+image columns, image grids, counter strips, all using
 * `flex-direction: row; flex-wrap: nowrap`). At narrow viewports each
 * child compresses to ~100px, forcing one-word-per-line text and tiny
 * thumbnails. Convert every `.block` inside a section to flex-column so
 * children stack at full container width.
 *
 * Heavy-handed by design: targeting individual blocks per slug would mean
 * 30+ rules. Stacking all `.block` instead is one rule that also self-
 * heals as new content lands. Children that should stay inline
 * (.icon-box, .container-link's own flex layout) are unaffected — they're
 * not `.block`. */
@media (max-width: 767px) {
  .mirror-root .section .block {
    flex-direction: column;
    align-items: stretch;
    flex-wrap: wrap;
  }
}

/* --- Reset large pixel paddings on spotlight inner blocks ---------------
 * Bundle puts `padding-left: 100px` on outer spotlight rows and
 * `padding-right: 100px` on text columns to create the desktop margin
 * that frames the image grid. At 375 viewport the math is brutal: 343px
 * container minus 200px combined padding = 143px usable, then split
 * 50/50 = ~70px per child column. Rich text inside collapses to one word
 * per line. Force every `.block` inside `.product-spotlight` to
 * width:100% + zero horizontal padding at <md. The section-container
 * already supplies the gutter via `--bde-section-horizontal-padding`. */
@media (max-width: 767px) {
  .mirror-root .section.product-spotlight .block {
    width: 100%;
    padding-left: 0;
    padding-right: 0;
  }
}

/* --- Counter cards (generic) -------------------------------------------
 * Bundle pins fixed pixel widths on counter widgets (typically 400px).
 * At <1280 we let them go fluid; at <md they stack one-per-row. Per-slug
 * counters with non-standard typography or layout can override with
 * higher-specificity rules in `<slug>.css`. */
@media (max-width: 1279px) {
  .mirror-root .counter {
    width: 100%;
    max-width: 360px;
  }
}
@media (max-width: 767px) {
  .mirror-root .counter {
    max-width: 100%;
  }
}

/* --- Images respect intrinsic max-width inside narrow columns ---------- */
@media (max-width: 767px) {
  .mirror-root .image img,
  .mirror-root .image picture {
    max-width: 100%;
    height: auto;
  }
}

/* ============================================================================
 * Ported content stacking containment.
 *
 * The brand <Nav> (sticky, z-50) and <main class="mirror-root"> are siblings
 * inside the layout's `relative min-h-screen` wrapper. `mirror-root` was
 * static (not a stacking context), so the ported body's positioned
 * descendants — Breakdance assigns z-index into the 1000s — competed directly
 * in the wrapper's stacking context and could paint OVER the nav and over the
 * sticky `.page-submenu`. Make `mirror-root` its own stacking context at z-0
 * so ALL ported content is contained below the nav (z-50). The submenu, being
 * inside mirror-root, then sits above its sibling content (via its own high
 * z-index) yet still below the nav (the whole context is z-0 < 50).
 *
 * GradientField is `-z-10` inside the same wrapper, so it stays behind this
 * z-0 layer. This selector starts with the scope class so lib/css-scope.ts
 * leaves it unprefixed (targets the <main> element itself, not a descendant).
 * ========================================================================= */
.mirror-root {
  position: relative;
  z-index: 0;
}

/* ============================================================================
 * In-page section sub-menu (.page-submenu) — shared chrome across every
 * attraction page (fotai / picbot / mosaix / future ports).
 *
 * The sub-menu is the in-page jump-nav strip ("Co to jest / Jak działa / …").
 * fotai's upstream HTML carries the `page-submenu` class natively; picbot &
 * mosaix get it injected by their `<slug>.strip.ts` (addClasses) so all three
 * share one generic style here instead of per-slug BEM-id rules.
 *
 * Every page's processed bundle ships the global Breakdance rule
 * `@media (min-width:1023px){ .page-submenu { position:sticky; top:0;
 * z-index:1000; transition: top .2s ease } }`. We layer on top of it:
 *
 *   Desktop (≥1023px) — sticky dock-below-nav:
 *     • header visible → top = nav height (120px = pt-10 40 + h-20 80)
 *     • header hidden  → top = 0 (Nav writes html[data-nav-hidden="true"])
 *     • z-index stays high (above ported content) — the nav can't be covered
 *       because `.mirror-root` is a contained stacking context below it (see
 *       the rule above). The bundle's `transition: top .2s ease` animates the
 *       dock/undock. Sticky only applies ≥1023px, where the Nav is always
 *       desktop-height, so 120px is a constant.
 *
 *   Mobile (<1023px) — dropped entirely (owner: hide the 12-anchor strip on
 *     mobile). The bundle leaves it static below 1023px; we display:none it.
 *
 *   Background band — #1B0048 + GradientField-matched dot pattern. A
 *     deliberate exception to "sections stay transparent": this is fixed-style
 *     navigation chrome, not a content section (owner annotations #94/#113).
 *     Promoted here from per-slug fotai.css / picbot.css so mosaix matches too.
 *
 * NOTE: the `html[...]` rule is NOT scoped to .mirror-root by lib/css-scope.ts
 * (it skips selectors starting with html/body/:root), so it carries
 * `.mirror-root` inline. */
.mirror-root .page-submenu {
  background-color: #1b0048;
  background-image: radial-gradient(
    circle,
    rgba(255, 255, 255, 0.06) 1px,
    transparent 1px
  );
  background-size: 15px 15px;
  background-position: 0 0;
}
@media (min-width: 1023px) {
  .mirror-root .page-submenu {
    top: 120px;
    /* Above ported content (Breakdance z-index runs into the 1000s);
     * stays below the Nav (z-50) because .mirror-root is a contained
     * stacking context — see the mirror-root rule below. */
    z-index: 1000;
  }
  html[data-nav-hidden='true'] .mirror-root .page-submenu {
    top: 0;
  }
}
@media (max-width: 1022px) {
  .mirror-root .page-submenu {
    display: none;
  }
}

/* --- Self-hosted streamed video with centered play overlay -----------------
 * Shared by any ported page that swaps a captured/embedded video for a
 * self-hosted <video> (home #wideo intro, /picbot "AI widzi"). The frame,
 * pulsing play button, and native-control trimming are identical across pages;
 * only the row layout (columns / gaps / margins) lives in each <slug>.css.
 * Wired by components/brand/VideoPlayOverlayMount.tsx — it pairs every
 * .video-play-overlay with the <video> in its own .ee-video-container. */

/* Vertical 9:16 frame with the bundle's spinning `.animated-border` ring.
 * `border: 0` kills any static bundle border on the frame's BEM class so only
 * the animated ring shows. Compounded with `.video` (both frames carry it) so
 * the specificity beats bundle rules that compound `.video.<bem-id>`
 * (e.g. picbot's `.video.video--634-138 .ee-video-container`, 0,0,4,0). */
.mirror-root .video.video-selfhost {
  width: 360px;
  max-width: 100%;
  border: 0;
  --border-thickness: 4px; /* thicker ring than the bundle default 2px */
  border-radius: 14px; /* outer = 4px ring + 10px inner (.animated-border *) */
  box-shadow: 5px 20px 35px 5px #000000db;
  overflow: hidden;
}
.mirror-root .video.video-selfhost .ee-video-container {
  padding-top: 177.77777777778%; /* 9:16 vertical */
}

/* Centered violet play-circle SVG — shown over the poster while paused, faded
 * out during playback (toggled by VideoPlayOverlayMount). The SVG *is* the
 * circle, so the button itself is a transparent positioning wrapper. Native
 * controls stay for in-playback control once revealed on first play. */
.mirror-root .video-play-overlay {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 2;
  display: inline-flex;
  width: 80px;
  height: 80px;
  padding: 0;
  border: 0;
  border-radius: 50%; /* round base so the pulsing box-shadow ring stays round */
  background: transparent;
  cursor: pointer;
  --pulse-color: 107, 27, 197; /* brand violet #6B1BC5 */
  transform: translate(-50%, -50%);
  animation: video-play-pulse 2s infinite;
  transition: opacity 0.2s ease;
}
.mirror-root .video-play-overlay.is-hidden {
  opacity: 0;
  pointer-events: none;
  animation: none; /* stop pulsing while the video plays */
}
.mirror-root .video-play-overlay__icon {
  width: 100%;
  height: 100%;
  fill: var(--bde-brand-primary-color, #6b1bc5);
  filter: drop-shadow(0 6px 16px rgba(0, 0, 0, 0.5));
  transition: fill 0.2s ease;
}
.mirror-root .video-play-overlay:hover .video-play-overlay__icon {
  fill: #7d28dc;
}
/* Pulsing ring — the centering translate is folded into the keyframes so the
 * animation keeps the -50%/-50% offset. */
@keyframes video-play-pulse {
  0% {
    transform: translate(-50%, -50%) scale(0.95);
    box-shadow: 0 0 0 0 rgba(var(--pulse-color), 0.7);
  }
  70% {
    transform: translate(-50%, -50%) scale(1);
    box-shadow: 0 0 0 18px rgba(var(--pulse-color), 0);
  }
  100% {
    transform: translate(-50%, -50%) scale(0.95);
    box-shadow: 0 0 0 0 rgba(var(--pulse-color), 0);
  }
}
/* Trim native controls: hide the scrubber timeline. Blink/WebKit only
 * (Chrome/Edge/Safari); Firefox keeps full controls. The three-dots overflow
 * menu is removed via `controlslist` + `disablepictureinpicture` on <video>. */
.mirror-root .ee-video-container video::-webkit-media-controls-timeline,
.mirror-root .ee-video-container video::-webkit-media-controls-timeline-container {
  display: none !important;
}


/* === fotai.css (slug-specific) === */

/* fotai.css — slug-specific overrides layered after _base.css.
 *
 * Each rule traces back to an owner annotation. Don't add speculative
 * entries. Re-run `bun run port:mirror -- --slug=fotai` after editing
 * (the public bundle is regenerated; hot reload doesn't refresh the
 * injected <link> tag).
 */

/* ── Round 1 (desktop) ──────────────────────────────────────────────── */

/* #168 — Hero section container needs more top padding. */
.mirror-root .section.section--239-100 .section-container {
  padding-top: 100px;
}

/* #169 — In-page nav bar (anchor links section) background band + sticky
 * dock + mobile-hide are now defined generically in _base.css keyed off the
 * `.page-submenu` class (this section carries it upstream). Removed the
 * per-slug `background-color` rule here to avoid duplication. */

/* #170 + #174 (+ zastosowania) — Vertical "dot" headings that lost their
 * rotation when `[data-entrance].is-animated { transform: none !important }`
 * (in _base.css) clobbered the upstream `.text-vertical { transform: rotate(180deg) }`.
 * Re-apply the rotation. 239-337 is the "zastosowania" section label — it kept
 * the upstream rotation while off-screen (is-before) but flipped to identity
 * once its slideUp entrance animated, reading the wrong way. */
.mirror-root .heading--239-273.text-vertical,
.mirror-root .heading--239-337.text-vertical,
.mirror-root .heading--239-479.text-vertical {
  transform: rotate(180deg) !important;
}

/* #172 — "jak to działa" section (239-153) had a captured background
 * overlay that fights the global GradientField. Drop it. CLAUDE.md
 * "Visual atmosphere": every content section stays transparent. */
.mirror-root .section.section--239-153,
.mirror-root .section.section--239-153 .section-background-overlay {
  background-color: transparent !important;
  background-image: none !important;
}

/* #175 + #198 — H5 titles need a consistent 1.1rem desktop size in
 * content areas (icon-box "przepustowość", infobox h5s, etc.). The
 * in-page anchor nav (`.section--239-642` / `.block--239-643`) is the
 * one explicit EXCEPTION: its h5 links are intentionally smaller
 * (0.7rem) so the 12-anchor strip fits on one line — owner re-
 * confirmed this after the first iteration. We bump everywhere except
 * the nav, then re-pin the nav at 0.7rem. `!important` is required to
 * beat upstream per-element rules like `.heading--239-647 { 0.7rem }`
 * (0,3,0) and the `.heading--h3` class some h5s carry (0,2,0). */
@media (min-width: 1024px) {
  .mirror-root h5,
  .mirror-root .infobox-title,
  .mirror-root .ee-iconbox-title,
  .mirror-root .innova-advanced-infobox h5 {
    font-size: 1.1rem !important;
  }
  /* In-page anchor nav — restore upstream 0.7rem. Higher specificity
   * (0,2,1) plus later source order means this wins over the generic
   * h5 rule above. */
  .mirror-root .section--239-642 h5,
  .mirror-root .block--239-643 h5 {
    font-size: 0.7rem !important;
  }
  .mirror-root .section.section--239-181 h5,
  .mirror-root .section.section--239-181 .infobox-title,
  .mirror-root .section.section--239-181 .innova-advanced-infobox h5 {
    line-height: 1.3;
  }
}

/* FAQ accordion expand/collapse animation. The captured bundle CSS
 * applies `transition: height …` only on `.is-collapsing`, which makes
 * the open path tricky to animate (the class is transitional and gets
 * removed on transitionend). Add a parallel transition rule that
 * applies whenever the wrapper is `.is-active` so AccordionMount can
 * use a single state class for both opening and closing flows. The
 * panel's `overflow: hidden` is required so the content doesn't spill
 * during the height transition. */
.mirror-root .bde-accordion__content-wrapper.is-active .bde-accordion__panel {
  transition: height var(--bde-transition-duration, 300ms) ease;
  overflow: hidden;
}

/* #177 + #197 + follow-up — FAQ accordion titles. The captured bundle
 * has `.mirror-root .accordion.accordion--239-673 .bde-accordion__title
 * { font-family: "Conthrax Thin Italic" }` at specificity 0,4,0; we
 * need to match it AND switch to the regular Conthrax weight (the thin
 * italic is hard to read at 0.8rem on dark backgrounds). The chained
 * `.accordion.accordion--239-673` ties the bundle's specificity; the
 * `!important` makes intent explicit even though source order alone
 * would win on a tie. weight 500 = `conthrax_regular_italic.woff2`
 * (the brand body weight per lib/fonts). */
.mirror-root .accordion.accordion--239-673 .bde-accordion__title,
.mirror-root .accordion.accordion--239-673 .bde-accordion__button {
  font-family: var(--font-conthrax), var(--font-display), Conthrax, sans-serif !important;
  font-weight: 500 !important;
  font-size: 0.8rem !important;
}

/* #173 — 3D coverflow Swiper for the "galeria styli" gallery. The
 * FotaiGallery3D client component initializes Swiper on the captured
 * DOM; these rules give the slides their fixed width + aspect ratio so
 * the perspective math reads as a 3D carousel. */
.mirror-root .gallery--239-455 .ee-gallery-swiper {
  /* let the perspective overflow visually outside the section */
  overflow: visible;
}
.mirror-root .gallery--239-455 .swiper {
  /* clipping is applied by Swiper itself; allow horizontal overflow so
   * tilted neighbors don't get clipped at the section edge */
  overflow: visible;
  padding: 40px 0 60px;
}
.mirror-root .gallery--239-455 .swiper-slide {
  /* Fixed 320px slide width — Swiper's `slidesPerView: 'auto'` reads
   * this directly and computes the wrapper centering math from it.
   * No `!important` needed since `'auto'` doesn't override slide width. */
  width: 320px;
  height: auto;
  aspect-ratio: 2 / 3;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 24px 64px -28px rgba(0, 0, 0, 0.6);
  /* Round 2 #2 — fade slides past distance ~3 from the active so the
   * eye never sees one rotate through 90° (the "thin line" appearance).
   * Swiper writes a per-slide CSS variable `--swiper-slide-progress`
   * (signed: 0 = active, ±1 = neighbor, ±2 = next neighbor, …) when
   * `watchSlidesProgress: true`. We map abs(progress) → opacity via
   * a CSS calc fallback expressed as a step ramp. */
  --slide-progress: var(--swiper-slide-progress, 0);
  /* Default `transition-duration` for the slide-level transform change.
   * Swiper's `effect-creative.css` only sets `transition-property:
   * transform, opacity, height` and relies on Swiper's JS to write
   * `transition-duration` inline. If for any reason that inline write
   * is skipped (loopFix race, animating flag corruption, etc.), the
   * transform change snaps. Providing a CSS fallback duration here
   * guarantees the slide transition always animates over our intended
   * 1100ms. Swiper still wins when it writes inline (e.g. transition-
   * duration: 0ms for loopFix's silent rearrangement), so the fallback
   * doesn't break loopFix. */
  transition-duration: 1100ms;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-property: transform, opacity, height;
}
/* Active + first two neighbours fully visible. */
.mirror-root .gallery--239-455 .swiper-slide-active,
.mirror-root .gallery--239-455 .swiper-slide-prev,
.mirror-root .gallery--239-455 .swiper-slide-next {
  opacity: 1;
}
/* Distance ≥2 — graduated fade by progress. We can't do real CSS calc
 * on `--swiper-slide-progress` (it's signed and abs() isn't widely
 * supported in calc), so we use a layered set of selectors keyed off
 * the Swiper-managed `--swiper-slide-progress` value approximations.
 * Simple step ramp via the progress variable instead — Swiper writes a
 * different progress per slide, so a calc using the variable fades them
 * gradually with browser interpolation. */
.mirror-root .gallery--239-455 .swiper-slide:not(.swiper-slide-active):not(.swiper-slide-prev):not(.swiper-slide-next) {
  /* Soft fade — distance 2 ≈ 0.85, distance 4 ≈ 0.55, distance 6+ ≈ 0.3.
   * The `var()` reads the per-slide progress; the calc maps |progress|
   * → opacity via `1 - 0.13 × |progress|` clamped at 0.3. */
  opacity: max(
    0.3,
    calc(1 - 0.13 * max(var(--swiper-slide-progress, 0), -1 * var(--swiper-slide-progress, 0)))
  );
}
.mirror-root .gallery--239-455 .swiper-slide img,
.mirror-root .gallery--239-455 .swiper-slide .breakdance-image-object {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Pagination dots removed at owner request — hide any pagination
 * element the upstream DOM might still emit. */
.mirror-root .gallery--239-455 .swiper-pagination {
  display: none !important;
}

/* #199 — Device-gallery hover gap fix. Upstream `.expanding-cards-card`
 * uses `background-size: auto 100%`, so when the hovered column widens
 * from `1fr` to `2fr` the background image (sized to height-only) keeps
 * its original width while the column grows — exposing a black gap on
 * both sides. Force `cover` so the image fills the entire card at any
 * column width, eliminating the gap. The transition on `grid-template-
 * columns` still produces the smooth expand effect, and `cover` makes
 * the image grow with it. */
.mirror-root .expanding-cards .expanding-cards-card {
  background-size: cover !important;
  transition: background-size 0.5s ease;
}

/* ── "unikalna funkcjonalność" badge pulse animation ────────────────
 * #201 + follow-up — Subtle pulse used on two distinct badges:
 *   1. AppsSection top-right badge (25° rotation, full element).
 *   2. Hero CTA `button--239-757::after` badge (15° rotation, pseudo).
 * Different rotation angles → can't bake the angle into the keyframe.
 * Each badge sets its own `--badge-rotate` and the keyframe reads it
 * via `var()`. Scale 0.98↔1.02 + gentle drop-shadow swing per the
 * "pulse slightly" annotation. */
@keyframes badge-pulse {
  0% {
    filter: drop-shadow(0 0 12px rgba(107, 27, 197, 0.35));
    transform: rotate(var(--badge-rotate, 25deg)) scale(0.98);
  }
  50% {
    filter: drop-shadow(0 0 20px rgba(107, 27, 197, 0.65));
    transform: rotate(var(--badge-rotate, 25deg)) scale(1.02);
  }
  100% {
    filter: drop-shadow(0 0 12px rgba(107, 27, 197, 0.35));
    transform: rotate(var(--badge-rotate, 25deg)) scale(0.98);
  }
}

/* Hero CTA "unikalne" badge — upstream renders this as a `::after`
 * pseudo on the secondary CTA button with `content:'unikalne'` and a
 * background image of `unique-badge2.webp` rotated 15°. No animation
 * is defined upstream; owner wants it to pulse subtly like the
 * AppsSection badge. We swap the WP-hosted background-image for our
 * R2-hosted copy (`/site/_shared/unique-badge2.webp`) so the page has
 * no cross-origin asset dependency on imprero.com. Lives under the
 * `site/_shared/` static-page-asset prefix (see CLAUDE.md "R2 key scheme"). */
.mirror-root .button--239-757::after {
  --badge-rotate: 15deg;
  background-image: url('https://images.imprero.com/site/_shared/unique-badge2.webp') !important;
  animation: badge-pulse 2s infinite;
  transform-origin: center center;
}

/* Gale "zastosowania" card. Upstream's DESKTOP rule points at the 760 KB
 * `FotAi_Gala-scaled.jpg` (off-pattern, slow cross-origin load → the card read
 * as blank while loading). The lightweight `FotAi_Gala.webp` (~45 KB) already
 * exists upstream and is now mirrored locally by the pipeline, so use it at
 * ≥768px. Mobile (≤767px) keeps the upstream `FotAi_Gala_wide.webp` crop. */
@media (min-width: 768px) {
  .mirror-root .fotai-zastosowania-grid-karta--gale {
    background-image: url('https://images.imprero.com/site/fotai/FotAi_Gala.webp') !important;
  }
}

/* ============================================================================
 * MOBILE ROUND 1 — annotations #202-#212
 *
 * Owner constraint: only mobile changes this round. Specificity tactic
 * mirrors picbot mobile rounds (chained `.mirror-root` + `.section …` /
 * `.block …` to beat _base.css's generic `.section .block { flex-direction:
 * column }` mobile stacker).
 * ========================================================================= */
@media (max-width: 767px) {
  /* --- #202 + #228 — hero multi-header line spacing -------------------- */
  /* Bundle pins `span.multi-header__styling-2 { margin-bottom: -37px }` at
   * mobile, which yanks the next line ("platforma") up enough to overlap.
   * Round 1 (#202) zeroed margin-top and set margin-bottom:-10px on both
   * styling-2 ("multimodalna") + styling-3 ("platforma"). Owner #228
   * refined: styling-2's margin-bottom should be 0 (not -10px) so there's
   * proper breathing room between "multimodalna" and "platforma".
   * styling-3 keeps -10px so "platforma" → "Ai" stays tight. */
  .mirror-root .multi-header.multi-header--239-103 span.multi-header__styling-2 {
    margin-top: 0;
    margin-bottom: 0;
  }
  .mirror-root .multi-header.multi-header--239-103 span.multi-header__styling-3 {
    margin-top: 0;
    margin-bottom: -10px;
  }

  /* --- #203 — hero CTA badge stays on the button on mobile ------------- */
  /* Bundle: `block--239-758 { display:flex; flex-direction:column;
   * gap:40px }` has no align-items declaration, so on mobile (when block
   * 106 collapses to column-flex) the buttons inside 758 stretch to the
   * viewport width. The `::after` badge is positioned `right: -24px`
   * relative to the button — when the button is full-viewport-width the
   * badge lands 24px past the right edge of the viewport (clipped /
   * detached). Hug the button content by setting align-items: flex-start
   * on the column wrapper. The badge then sits at the upper-right of the
   * actual button, matching desktop. */
  .mirror-root .block.block--239-758 {
    align-items: flex-start;
  }

  /* --- #204 — anchor menu strip stays a row on mobile ------------------ */
  /* Bundle at max-width:479 already declares
   * `block--239-643 { display:flex; flex-flow:row; column-gap:15px;
   * row-gap:0; flex-wrap:wrap }`, but _base.css's generic
   * `.mirror-root .section .block { flex-direction: column }` at <md
   * (same specificity, loaded AFTER the bundle in the override layer)
   * silently wins, stacking the 12 anchors vertically. Re-assert row
   * with one extra `.section` ancestor (specificity 0,0,3,0 beats the
   * generic 0,0,2,0). Mirror of picbot #119. */
  .mirror-root .section.section--239-642 .block.block--239-643 {
    flex-direction: row;
    flex-wrap: wrap;
    column-gap: 15px;
    row-gap: 0;
  }

  /* --- #205 — "jak to działa" round portraits stay circular ------------ */
  /* Mirror of picbot #124 + #129 + #181: lock each image-gradient-border
   * wrapper to a 250×250 square so its `::before` conic-gradient ring
   * stays round, and force the inner <img> to fill the square with cover
   * + 50% radius. Without this the wrappers stretch (flex-stretched by
   * _base.css's column rule) and the `border-radius: 50%` produces an
   * ellipse. `align-self: flex-start` keeps the portraits flush-left in
   * the column-reverse stacks below.
   *
   * Wrappers: 439 (img 418 — gosc-robi-zdjecie), 440 (img 423 —
   * efekt-ai), 442 (img 435 — wydruk-telefon). All three live inside
   * section--239-153. */
  .mirror-root .block.block--239-439.image-gradient-border-wrapper,
  .mirror-root .block.block--239-440.image-gradient-border-wrapper,
  .mirror-root .block.block--239-442.image-gradient-border-wrapper {
    display: inline-block;
    width: 250px;
    height: 250px;
    align-self: flex-start;
    flex: 0 0 auto;
  }
  .mirror-root .image.image--239-418,
  .mirror-root .image.image--239-423,
  .mirror-root .image.image--239-435 {
    aspect-ratio: 1 / 1;
    width: 250px;
    height: 250px;
    object-fit: cover;
    border-radius: 50%;
    overflow: hidden;
  }

  /* --- #206 — sub-section 2 image-first on mobile ---------------------- */
  /* "jak to działa" zips four sub-section rows under section 153:
   *   1. block--239-408 — image (416) then text (417)  — image first
   *   2. block--239-421 — text (424) then image (422)  — text first
   *   3. block--239-433 — image (434) then text (436)  — image first
   *   4. block--239-445 — text-only ("To tylko początek")
   * Owner: "each even sub-section needs column-reverse so image is first
   * and text second". Only sub-section 2 has both an image and text in
   * a flipped order — block--239-421 gets column-reverse so its image
   * (422) renders above its text (424) on mobile. Sub-section 4 is
   * text-only; no flip needed. */
  .mirror-root .section .block.block--239-421 {
    flex-direction: column-reverse;
  }

  /* --- #207 — "galeria styli" slider keeps 2:3 + smaller slides ------- */
  /* Desktop fotai.css pins `.swiper-slide { width: 320px; aspect-ratio:
   * 2/3 }` — at mobile the bundle's multi-selector forces `height:
   * 350px` on the swiper + slides + img, which clamps the 2:3 image
   * source into 320×350 (cropped vertically). Two fixes:
   *   (a) Shrink the slide to 200px wide so neighbour slides remain
   *       visible (owner: "main image occupies 90% of the slider area").
   *   (b) Override the bundle's `height: 350px` so aspect-ratio: 2/3
   *       drives the slide height (200×300 keeps the source uncropped).
   * The override targets the full multi-selector list the bundle ships
   * so every level in the chain — swiper, slide, img — picks up
   * `height: auto`. */
  .mirror-root .gallery.gallery--239-455 .ee-gallery-swiper {
    width: 100%;
  }
  .mirror-root .gallery.gallery--239-455 .swiper-slide {
    width: 200px;
    aspect-ratio: 2 / 3;
  }
  .mirror-root .gallery.gallery--239-455 .ee-gallery-swiper,
  .mirror-root .gallery.gallery--239-455 .ee-gallery--slider.ee-gallery--custom .swiper-slide,
  .mirror-root .gallery.gallery--239-455 .ee-gallery--slider.ee-gallery--custom .ee-gallery-item-figure img,
  .mirror-root .gallery.gallery--239-455 .ee-gallery--slider.ee-gallery--custom .ee-gallery-item-figure video,
  .mirror-root .gallery.gallery--239-455 .ee-gallery--slider.ee-gallery--custom .ee-gallery-item-figure iframe {
    height: auto;
  }

  /* --- #208 — icon-box / infobox h5 sizing on mobile ------------------- */
  /* Desktop fotai.css already pins h5 at 1.1rem (line 51-69). Owner now
   * wants the same on mobile — "drop the clamp". _base.css's generic
   * `h4 { font-size: clamp(1.1rem, 2.5vw, 1.56rem) }` doesn't directly
   * hit h5, but per-element bundle rules and the `.heading--h3` class
   * some h5s carry can push the rendered size to ~1.8rem. Force 1.1rem
   * across icon-box + infobox titles. Mirror of picbot #132/#143/#144. */
  .mirror-root .icon-box h5,
  .mirror-root .icon-box .ee-iconbox-title,
  .mirror-root [class*="icon-box--239-"] h5,
  .mirror-root .infobox-title,
  .mirror-root .innova-advanced-infobox h5 {
    font-size: 1.1rem;
  }

  /* --- #210 — "nagrody" block--239-519 stays a row of two images ------- */
  /* Bundle: `block--239-519 { display:flex; flex-direction:row; flex-
   * wrap:nowrap; align-items:center; gap:80px }` outside any media
   * query. _base.css's `.section .block { flex-direction: column }` at
   * <md overrides to column. Re-assert row + size each image to 50%.
   * Mirror of picbot #133. */
  .mirror-root .section .block.block--239-519 {
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    gap: 20px;
  }
  .mirror-root .section .block.block--239-519 > .image {
    flex: 1 1 0;
    max-width: 50%;
  }

  /* --- #211 — preserve text-vertical 180deg through entrance ----------- */
  /* `.text-vertical { transform: rotate(180deg) }` (bundle) renders the
   * Polish vertical-rl heading right-side-up. _base.css's
   * `[data-entrance].is-animated { transform: none !important }` clears
   * entrance starting transforms but also nukes this intentional rotate.
   * The earlier per-heading rule (line 28-31) only covers 273 + 479;
   * owner #211 flags heading 337 ("zastosowania") needs rotation too.
   * Use the universal picbot #120 pattern so any current AND future
   * text-vertical heading on the page gets rotation preserved after
   * entrance. Specificity 0,0,3,1 beats the !important from _base.css
   * (which is 0,0,2,0 plus !important — !important is a separate tier,
   * so we need our own !important to match). */
  .mirror-root .heading.text-vertical[data-entrance].is-animated {
    transform: rotate(180deg) !important;
  }

  /* --- #212 + #243 — FAQ heading dot position on mobile --------------- */
  /* Bundle at max-width:479 sets `.heading--239-671:after { left: -24% }`
   * (top inherited 60% from 767-tier). The percentage resolves against
   * the heading's box width — for the 3-letter "faq" word that's a tiny
   * pixel offset, so the dot lands inside the text rather than to its
   * left. Round-1 first tried `-30px / 60%`; owner refined to
   * `-50px / 54%` (#243) for the final alignment. */
  .mirror-root .heading.heading--239-671::after {
    left: -50px;
    top: 54%;
  }
}

/* --- #218 + #224 — image-gradient-border-wrapper ::before z-index ----
 * Annotation #218: ::before pseudo paints over the image so the picture
 * sits behind a faint conic-gradient tint. Owner: "z-index:-1 on ::before,
 * same for all 4 images". The four wrappers are 439/440/442 (section 153)
 * + the galeria-styli-reya wrapper inside block 168 (section 167).
 *
 * #224 — first iteration of this fix also pinned `.image-gradient-border-
 * wrapper { position: relative; z-index: 0 }` on the wrapper, which
 * REGRESSED the Reya overlay: the bundle ships
 * `.galeria-styli-reya { position: absolute !important; z-index: 10 }` to
 * float Reya OVER the gallery slider, and my universal `z-index: 0` won
 * over the bundle's `z-index: 10` (same specificity, source-order later
 * in the override layer). Owner saw Reya sink under the slider.
 *
 * Correct fix is the picbot pattern: only the pseudo gets z-index:-1.
 * Bundle already gives the wrapper `position: relative; display: inline-
 * block` (a positioning context — no need to add one). With z-index:-1
 * the pseudo sinks below the wrapper's normal flow content (the <img>),
 * leaving the picture fully visible. Universal — same stacking issue
 * applies at every viewport. */
.mirror-root .image-gradient-border-wrapper::before {
  z-index: -1;
}

/* ============================================================================
 * MOBILE ROUND 2 — annotations #215-#223
 * ========================================================================= */
@media (max-width: 767px) {
  /* --- #215 — hero section padding-top tighter --------------------------
   * Earlier desktop rule (line 12-14) pins `section--239-100 .section-
   * container { padding-top: 100px }` at all viewports. Bundle's mobile
   * tier overrides to 180px (max-width:767) and 150px (max-width:479).
   * Owner wants 40px on mobile so the hero crowds tight under the nav. */
  .mirror-root .section.section--239-100 .section-container {
    padding-top: 40px;
  }

  /* --- #216 — video block 265 padding ----------------------------------
   * Bundle pins `block--239-265 { padding-left: 50px }` at <767 and the
   * universal `padding-top: 60px` from <1023. Owner: 20px top, 40px left
   * so the YouTube embed sits tighter against the section gutter. */
  .mirror-root .section .block.block--239-265 {
    padding-top: 20px;
    padding-left: 40px;
  }

  /* --- #217 — "dlaczego fotai" section padding-top --------------------- */
  /* Bundle: 160px (max-width:767) / 150px (max-width:479). Owner: 100px. */
  .mirror-root .section.section--239-385 .section-container {
    padding-top: 100px;
  }

  /* --- #219 + #246 — "To tylko początek" block spacing --------------- */
  /* Bundle pins `block--239-445 { margin-top: 80px; padding-left: 50px }`
   * at <479. Owner #219: tighten margin-top to 40px. #246: padding-left
   * 40px (consistent with the page-wide 40px gutter on ported blocks). */
  .mirror-root .section .block.block--239-445 {
    margin-top: 40px;
    padding-left: 40px;
  }

  /* --- #220 + #225 + #236 + #237 — section 181 "możliwości" -----------
   * Round 2 (#220) zeroed `block--239-506 { margin-top: -400px }` thinking
   * the negative margin was a desktop-only artifact. Round 3 (#225)
   * doubled down by switching the section-container to a 2-col grid.
   * Both wrong. Owner #236: "section containers remain flex across all
   * sections — copy properties from block--239-388". The bundle's mobile
   * overlap pattern IS the right pattern: heading 479 stays sticky at
   * the top of the natural flow (taking its rotated-text vertical
   * space), then block 506 gets a large negative margin-top to slide UP
   * over the heading column visually, with padding-left clearing the
   * heading on the left. That's how section 385 (block 388 with
   * `margin-top: -490px` at <767 / `-400px` at <479) does it — same
   * pattern.
   *
   * Restore: drop the grid override (section-container falls back to its
   * universal `display: flex; flex-direction: column`), let bundle's
   * `block--239-506 { margin-top: -400px }` at <479 stand. Replace each
   * block's bundle padding-left (50px / 45px) with the owner-specified
   * 40px (#237 for 506; consistent 40px across the page for #234, etc.).
   * Specificity 0,0,3,1 from `.section.section--239-181 .block.block--…`
   * beats bundle's 0,0,2,0 without !important. */
  /* Annotation #265: bundle pins `block--239-506 { margin-top: -400px }`
   * at <479. Owner refined to -320px so the intro paragraph sits
   * slightly lower under heading 479 (still overlapping in the side-
   * heading pattern but less aggressively). */
  .mirror-root .section.section--239-181 .block.block--239-506 {
    padding-left: 40px;
    margin-top: -320px;
  }
  .mirror-root .section.section--239-181 .block.block--239-182 {
    padding-left: 40px;
  }
  /* #250 — owner explicitly does NOT want padding-left on block 502.
   * Earlier round 2 #236+237 had applied 40px consistently here too;
   * removed per owner. Block 502 is centered (text-align:center) and
   * the bundle's flex-column + gap:30px does the layout without any
   * left padding. */

  /* --- #223 — heading 543 ("dostępne urządzenia") dot position -------- */
  /* Bundle at max-width:479 sets `.heading--239-543:after { left: -24% }`
   * (top inherited 35% from 767-tier). Owner wants `left: -15%; top: 30%`.
   * Specificity 0,0,2,1 with one extra class chain beats the bundle's
   * single-class 0,0,1,1. */
  .mirror-root .heading.heading--239-543::after {
    left: -15%;
    top: 30%;
  }

  /* --- #227 — skip slideUp entrance for text-vertical headings -------
   * Heading 337 ("zastosowania") + 479 ("możliwości") are baked with
   * `data-entrance="slideUp"` (Phase A captured the upstream pre-
   * animation state). The slide-up keyframe in components/brand/
   * entrance.css transitions `transform: translateY(20px) → none`,
   * which fights the universal `text-vertical { transform:
   * rotate(180deg) }` rule (round 1 #211): during the animation the
   * heading slides un-rotated, then SNAPS to rotated when `.is-animated`
   * arrives. Owner reads this as a double animation ("snap-in from the
   * left then sticky scroll").
   *
   * Other vertical headings on the page (e.g. heading 407 "jak to
   * działa") ship WITHOUT data-entrance, so they render rotated
   * immediately and only do the sticky scroll. Match that behavior for
   * 337 + 479 by suppressing the entrance keyframe entirely on mobile.
   *
   * `animation: none !important` on the .is-before/.is-animating phases
   * stops the keyframe (regular inline `style="animation: slide-up ..."`
   * the JS hook writes is overridden by !important author rules per
   * CSS cascade). `opacity: 1` cancels the entrance.css `.is-before {
   * opacity: 0 }` so the heading isn't briefly invisible. `transform:
   * rotate(180deg)` makes the rotated state immediate. `visibility:
   * visible` overrides the bundle's `[data-entrance] { visibility:
   * hidden }` baseline. */
  .mirror-root .heading.text-vertical[data-entrance].is-before,
  .mirror-root .heading.text-vertical[data-entrance].is-animating {
    animation: none !important;
    opacity: 1 !important;
    transform: rotate(180deg) !important;
    visibility: visible !important;
  }
}

/* ============================================================================
 * MOBILE ROUND 3 — annotations #229-#235
 *
 * Note: #228 + #236 + #237 are inline supersedes of earlier rules above
 * (multi-header #202 and section 181 #220/#225). #238 lives in
 * AppsSection.tsx (Container padding override).
 * ========================================================================= */
@media (max-width: 767px) {
  /* --- #229 + #239 — heading 273 dot position on a width:0 heading ---- */
  /* Bundle pins `.heading--239-273 { width: 0px }` at <1119px so the
   * text-vertical sticky heading takes zero inline space (its rotated
   * text overflows visually via `text-wrap: nowrap`). Consequence: the
   * `.heading-dot:after` pseudo's `right: 10%` (<1023) resolves to
   * 10% × 0 = 0 — owner could not move the dot using percent values.
   * Pixel offset escapes the bug. Round-3 first tried -10px; owner
   * refined to 5px (#239) to match heading 577's pattern. */
  .mirror-root .heading.heading--239-273::after {
    right: 5px;
  }

  /* --- #230 — heading 577 ("dlaczego fotai") dot right: 5px --------- */
  /* Bundle at <479 sets `:after { right: 15%; top: 110% }`. Heading 577
   * also has `width: 0` (universal vertical heading pattern) so the
   * percent is again unreliable. Owner pinned 5px explicitly. */
  .mirror-root .heading.heading--239-577::after {
    right: 5px;
  }

  /* --- #231 — block 408 ("Sesja w fotobudce") spacing --------------- */
  /* Bundle at <479: `padding-left: 50px; margin-top: -350px` (the
   * desktop-overlap pull-up that mirrors block 388's pattern). Universal:
   * `margin-bottom: 80px`. Owner: padding-left 40px, margin-bottom 40px
   * (the pull-up margin-top stays — it powers the heading-407 overlap). */
  .mirror-root .section .block.block--239-408 {
    padding-left: 40px;
    margin-bottom: 40px;
  }

  /* --- #232 — block 421 ("Magia AI w akcji") spacing ---------------- */
  /* Round 1 (#206) set this row to `flex-direction: column-reverse` so
   * the image renders above text on mobile (sub-section 2 of "jak to
   * działa"). Owner now adds padding-left 40px + margin-bottom 40px
   * to match block 408's rhythm. Same selector specificity as the
   * round 1 rule (0,0,3,1) — both apply (different properties). */
  .mirror-root .section .block.block--239-421 {
    padding-left: 40px;
    margin-bottom: 40px;
  }

  /* --- #233 — heading 452 ("galeria styli") dot left: -15% ---------- */
  /* Bundle at <479: `:after { left: -20%; top: 60% }`. Owner wants
   * left: -15% (slightly closer to the heading). Top inherited. */
  .mirror-root .heading.heading--239-452::after {
    left: -15%;
  }

  /* --- #234 — block 388 ("dlaczego fotai" grid) padding-left 40px --- */
  /* Bundle at <767: `padding-left: 50px; margin-top: -490px`. At <479:
   * margin-top: -400px. Owner: padding-left 40px (margin-top stays —
   * it's the desktop-overlap pattern that pulls block 388 up over
   * heading 577 to create the side-heading look). */
  .mirror-root .section .block.block--239-388 {
    padding-left: 40px;
  }

  /* --- #235 — block 433 ("Odbiór i udostępnianie") padding-left 40px - */
  /* Bundle at <479: `padding-left: 50px; margin-bottom: 20px`. Owner:
   * padding-left 40px. Margin-bottom not flagged — bundle 20px stays. */
  .mirror-root .section .block.block--239-433 {
    padding-left: 40px;
  }
}

/* --- #259 — FAQ accordion button full color (no transparency) ---------
 * Bundle pins `.accordion--239-673 .accordion__button { background-color:
 * #12002dc2 }` — the `c2` suffix is alpha (≈76%), so the button reads
 * semi-transparent against the GradientField. Owner: full opacity.
 * Universal (no @media) since the visual issue affects every viewport. */
.mirror-root .accordion.accordion--239-673 .accordion__button {
  background-color: #12002d;
}

/* --- #270 — unhide the "realizacje" section --------------------------
 * Upstream ships `section--239-620 { display: none }` in FIVE separate
 * media-query blocks (every breakpoint), so the realizacje section is
 * hidden at all viewports — a WIP choice on the legacy WP page. Owner
 * wants it visible (content will be added later, but it must not be
 * hidden). Universal `display: block` override; specificity 0,0,2,0
 * matches the bundle's per-breakpoint rules and the override layer's
 * later source order wins at every viewport. The inner
 * `.section-container` keeps its bundle `display: flex`. */
.mirror-root .section.section--239-620 {
  display: block;
}

/* ============================================================================
 * MOBILE ROUND 4 — annotations #240-#242
 *
 * Standardize the text-vertical sticky headings' horizontal offset. Bundle
 * ships disparate values across the three: 407 = -28px, 479 = -30px,
 * 337 = -30px (all at <767). Owner wants a uniform -18px so the three
 * headings sit at the same x-coordinate relative to their section gutter.
 * ========================================================================= */
@media (max-width: 767px) {
  /* --- #240 / #241 / #242 — vertical heading uniform margin-left ---- */
  /* Specificity 0,0,3,0 (`.mirror-root .heading.heading--XX-YYY`) matches
   * the bundle's per-id rules; later source order in the override layer
   * wins. No !important needed. */
  .mirror-root .heading.heading--239-407,
  .mirror-root .heading.heading--239-479,
  .mirror-root .heading.heading--239-337 {
    margin-left: -18px;
  }
}

/* ============================================================================
 * MOBILE ROUND 5 — annotations #245-#258
 * ========================================================================= */
@media (max-width: 767px) {
  /* --- #245 — "jak to działa" section padding-top -------------------- */
  /* Bundle universal: `section--239-153 .section-container { padding-top:
   * 100px }`. Owner: 60px on mobile to tighten the spacing under the
   * preceding section. */
  .mirror-root .section.section--239-153 .section-container {
    padding-top: 60px;
  }

  /* --- #247 — drop button 458 wrapper margin-top -------------------- */
  /* Bundle at <767: `.button--239-458 { margin-top: 20px }`. Owner: 0.
   * The "Zobacz galerię efektów" CTA sits below the gallery slider; the
   * 20px push down is no longer wanted at mobile. */
  .mirror-root .button.button--239-458 {
    margin-top: 0;
  }

  /* --- #248 — drop block 168 margin-bottom -------------------------- */
  /* Bundle universal: `block--239-168 { margin-bottom: 40px }`. Owner:
   * 0. Block 168 wraps the Reya portrait + the galeria-styli swiper. */
  .mirror-root .section .block.block--239-168 {
    margin-bottom: 0;
  }

  /* --- #249 — heading 479 dot right: 5px --------------------------- */
  /* Same width:0 bug as headings 273 + 577 — `.heading--239-479` is
   * narrow text-vertical so % values resolve unreliably. Owner aligned
   * the dot at 5px (matching 577). Specificity 0,0,3,1 from the chained
   * `.heading.heading--239-479::after` beats bundle's single-id rules. */
  .mirror-root .heading.heading--239-479::after {
    right: 5px;
  }

  /* --- #254 + #269 — heading 512 ("nagrody") margin + dot ----------- */
  /* Bundle universal: `heading--239-512 { margin-left: 60px; margin-
   * bottom: 80px }` + `:after { top: 60%; left: -60px }`. Owner #254:
   * margin-left 40px, dot left -45px. #269: margin-bottom 40px (tighten
   * the gap below "nagrody" before the awards images). */
  .mirror-root .heading.heading--239-512 {
    margin-left: 40px;
    margin-bottom: 40px;
  }
  .mirror-root .heading.heading--239-512::after {
    left: -45px;
  }

  /* --- #255 — block 519 images 20% smaller -------------------------- */
  /* Round 1 #210 forced row layout with `flex: 1 1 0; max-width: 50%`
   * on each child .image (50/50 split). Owner: shrink both by 20%
   * (50% × 0.8 = 40% each). The container's gap stays 20px from #210. */
  .mirror-root .section .block.block--239-519 > .image {
    flex: 0 0 40%;
    width: 40%;
    max-width: 40%;
  }

  /* --- #256 — heading 337 ("zastosowania") dot right: 18% ---------- */
  /* Bundle universal: `heading--239-337:after { right: 20px }`. At
   * <767: `:after { top: 110%; right: 30% }`. Owner: 18% on mobile.
   * Heading 337 has natural width (no width:0 quirk like 273/577), so
   * percent values work correctly. */
  .mirror-root .heading.heading--239-337::after {
    right: 18%;
  }

  /* --- #257 — heading 579 ("wymagania") dot left: -18% -------------- */
  /* text-horizontal heading. Bundle: `:after { top: 60%; left: -70px }`
   * via the heading-dot.text-horizontal universal rule. Override left
   * with percent per owner. */
  .mirror-root .heading.heading--239-579::after {
    left: -18%;
  }

  /* --- #258 — expanding-cards overlay gradient ---------------------- */
  /* Bundle universal: `.expanding-cards .section-background-overlay {
   * background: linear-gradient(0deg, #000000f0 0%, rgba(0,0,0,0.9) 15%,
   * rgba(0,0,0,0.7) 25%, rgba(255,0,0,0) 45%) }`. Owner pinned block 552
   * (one of the cards) but wants the gradient updated for every overlay
   * in this section — block 551's .expanding-cards container holds five
   * cards (552-556), each with a section-background-overlay. Tweak the
   * two bottom-anchored stops to slightly lower opacity per owner spec. */
  .mirror-root .expanding-cards .section-background-overlay {
    background: linear-gradient(
      0deg,
      #000000de 0%,
      rgb(0 0 0 / 79%) 15%,
      rgba(0, 0, 0, 0.7) 25%,
      rgba(255, 0, 0, 0) 45%
    );
  }
}

/* ── SEO H1 hygiene — preserve demoted "FotAi" heading size ──────────── */
/* The decorative outlined "FotAi" heading (.heading--239-503) was an <h1>
 * with NO explicit font-size — it inherited the bundle's bare
 * `.mirror-root h1 { font-size: var(--bde-h1-font-size) }` (= 3.4rem × 1.25
 * = 4.25rem). L0 requires exactly one <h1> per page, so it was demoted to
 * <h2>; without this it would drop to the bare-h2 size (3.4rem). Pin it. */
.mirror-root .heading.heading--239-503 {
  font-size: var(--bde-h1-font-size);
}

/* ==========================================================================
 * "realizacje" event gallery (section--239-620) + galeria-styli secondary CTA
 * --------------------------------------------------------------------------
 * Owner change: populate the (previously empty) realizacje section with a
 * clickable grid of real-event FotAi transformations that open a modal
 * slideshow (RealizacjeLightbox, mounted in app/fotai/page.tsx). Uniform 2:3
 * portrait cards in a responsive grid; hover lifts + rings the active card.
 * The thumbnails are server-rendered inside the ported section; the modal is
 * a portal outside .mirror-root (styled with Tailwind in the component).
 * ======================================================================= */
.mirror-root .realizacje-gallery {
  margin-top: 40px;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 16px;
}
.mirror-root .realizacje-gallery__item {
  display: block;
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  cursor: pointer;
  border-radius: 12px;
  overflow: hidden;
  aspect-ratio: 2 / 3;
  box-shadow: 0 0 25px 6px #0009;
  transition:
    transform 0.25s ease,
    box-shadow 0.25s ease;
}
.mirror-root .realizacje-gallery__item:hover,
.mirror-root .realizacje-gallery__item:focus-visible {
  transform: translateY(-4px);
  box-shadow: 0 10px 40px 6px #000c;
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}
.mirror-root .realizacje-gallery__thumb {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
@media (max-width: 1100px) {
  .mirror-root .realizacje-gallery {
    grid-template-columns: repeat(4, 1fr);
  }
}
@media (max-width: 767px) {
  .mirror-root .realizacje-gallery {
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
  }
}
@media (max-width: 430px) {
  .mirror-root .realizacje-gallery {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Galeria-styli CTA row now holds two buttons ("Zobacz galerię efektów" +
 * the new "Zobacz przykłady z realizacji"). Lay them side by side (wrapping
 * on narrow screens), keeping the original centered alignment. */
.mirror-root .button.button--239-458 {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  justify-content: center;
  align-items: center;
}
/* Secondary "Zobacz przykłady z realizacji" — outline variant so it reads as
 * secondary next to the filled primary. */
.mirror-root .button--239-458 .button-atom--secondary.realizacje-cta {
  background: transparent;
  border: 2px solid var(--brand-primary);
  color: #fff;
  transition:
    background 0.25s ease,
    color 0.25s ease;
}
.mirror-root .button--239-458 .button-atom--secondary.realizacje-cta:hover,
.mirror-root .button--239-458 .button-atom--secondary.realizacje-cta:focus-visible {
  background: var(--brand-primary);
  color: #fff;
}
