/* ---------- Kroje z wlasnego serwera ----------------------------------------

   Wczesniej arkusz z fonts.googleapis.com blokowal render na kazdej podstronie
   i ciagnal dwie obce domeny, zanim cokolwiek sie narysowalo. Oba kroje sa
   zmienne, wiec jeden plik obsluguje wszystkie uzywane grubosci: Space Grotesk
   500-700 i Inter 400-700.

   Zapasowy Arial ma przeliczone metryki (size-adjust i nadpisania wysokosci
   wyliczone z prawdziwych krojow), zeby tekst przed podmiana kroju zajmowal
   dokladnie tyle samo miejsca co po niej. Bez tego podmiana przesuwala uklad:
   na stronie bloga dawalo to CLS 0,149. */
@font-face {
  font-family: "Inter";
  src: url("../fonts/inter.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Space Grotesk";
  src: url("../fonts/space-grotesk.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Inter zapas";
  src: local("Arial");
  size-adjust: 104.68%;
  ascent-override: 92.54%;
  descent-override: 23.04%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "Space Grotesk zapas";
  src: local("Arial");
  size-adjust: 101.77%;
  ascent-override: 96.69%;
  descent-override: 28.69%;
  line-gap-override: 0%;
}

/* ========== Design tokens ========== */
:root {
  --bg-deep: #050507;
  --bg-base: #0a0a10;
  --bg-elevated: #13131b;
  --surface: rgba(255, 255, 255, 0.055);
  --surface-strong: rgba(255, 255, 255, 0.09);
  --border: rgba(255, 255, 255, 0.09);
  --border-strong: rgba(255, 255, 255, 0.16);

  --fg: #F3F4F8;
  --fg-muted: #9CA0B4;
  --fg-faint: #6B6F84;

  --blue: #3D66E0;
  --blue-soft: #7DA0FF;
  --purple: #7C3AED;
  --purple-soft: #C29CFB;

  /* One gradient for the whole site: cyan, through periwinkle, to violet.
     There used to be two – this one for hairlines and headings, and a darker
     blue-to-purple for fills – because a fill this bright cannot carry white
     type. Keeping both meant two visual languages on one page, so the fills
     changed instead: .btn-primary and .hero-stat-icon now put near-black on the
     gradient, which clears 4.5:1 at every stop, rather than white, which fails
     at the cyan end. */
  /* Colours and angle kept apart, because the right angle depends on the shape.
     At 120deg on a wide, short pill the gradient line runs out well before the
     right edge and leaves a cyan rim on the corner; buttons therefore run the
     same stops nearly horizontally. */
  --brand-stops: #47D6E8 0%, #7C8CFF 48%, #A855F7 100%;
  --gradient-brand: linear-gradient(120deg, var(--brand-stops));
  --gradient-brand-wide: linear-gradient(100deg, var(--brand-stops));
  /* Ink for anything filled with the gradient above. */
  --on-gradient: #08080f;
  --glow-blue: rgba(76, 124, 255, 0.35);
  --glow-purple: rgba(155, 92, 246, 0.35);

  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 24px;
  --radius-pill: 999px;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 16px;
  --space-4: 24px;
  --space-5: 32px;
  --space-6: 48px;
  --space-7: 64px;
  --space-8: 96px;
  --space-9: 128px;

  --font-heading: 'Space Grotesk', 'Space Grotesk zapas', sans-serif;
  --font-body: 'Inter', 'Inter zapas', sans-serif;

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-fast: 150ms;
  --dur-base: 250ms;
  --dur-slow: 400ms;

  /* Szerokosc kolumny tresci. Wszystko, co ma sie do niej rownac, liczy z tej
     jednej wartosci, zeby przy kolejnym progu nie trzeba bylo poprawiac
     maski konstelacji i pozycji karty 3D osobno. */
  --container-max: 1200px;
  --hero-max: 1200px;
}

@media (min-width: 1280px) { :root { --hero-max: 1360px; } }
@media (min-width: 1440px) { :root { --container-max: 1320px; --hero-max: 1440px; } }
@media (min-width: 1700px) { :root { --container-max: 1480px; --hero-max: 1560px; } }
@media (min-width: 1920px) { :root { --container-max: 1640px; --hero-max: 1720px; } }
@media (min-width: 2300px) { :root { --container-max: 1860px; --hero-max: 1940px; } }
@media (min-width: 2800px) { :root { --container-max: 2120px; --hero-max: 2200px; } }

/* ========== Reset ========== */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

body {
  margin: 0;
  background: var(--bg-base);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }

h1, h2, h3, h4 {
  font-family: var(--font-heading);
  line-height: 1.15;
  margin: 0;
  color: var(--fg);
  letter-spacing: -0.01em;
}

p { margin: 0; }

a { color: inherit; text-decoration: none; }

button {
  font-family: inherit;
  cursor: pointer;
}

ol, ul { margin: 0; padding: 0; list-style: none; }

::selection { background: var(--purple); color: #fff; }

:focus-visible {
  outline: 2px solid var(--blue-soft);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--bg-elevated);
  color: var(--fg);
  padding: var(--space-3) var(--space-4);
  z-index: 1000;
  border-radius: 0 0 var(--radius-sm) 0;
}
.skip-link:focus {
  left: 0;
}

.container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--space-4);
}
@media (min-width: 1700px) { .container { padding-inline: var(--space-5); } }

.text-gradient {
  background: var(--gradient-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.type-target {
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  vertical-align: bottom;
  width: 0;
  max-width: 100%;
  transition: width var(--type-duration, 1s) var(--ease-out);
}
.type-target.is-typed {
  width: var(--type-w, auto);
}

/* ========== Buttons ========== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 14px 28px;
  border-radius: var(--radius-pill);
  font-weight: 600;
  font-size: 0.95rem;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
  min-height: 44px;
}
.btn:active { transform: scale(0.97); }

.btn-primary {
  background: var(--gradient-brand-wide);
  /* Must come after the shorthand, which resets background-origin to its
     initial padding-box – putting this on .btn had no effect for exactly that
     reason. With origin padding-box and clip border-box the gradient is laid
     out at padding-box size and then repeated to cover the 1px transparent
     border, so the start of the next tile – a slice of cyan – landed hard
     against the right edge of every filled button. */
  background-origin: border-box;
  /* Near-black, not white: on this gradient white lands at 1.6:1 over the cyan
     stop. The dark ink clears 4.5:1 across all three stops. */
  color: var(--on-gradient);
  box-shadow: 0 8px 26px -10px rgba(124, 140, 255, 0.9);
}
.btn-primary:hover {
  box-shadow: 0 14px 34px -10px rgba(124, 140, 255, 1), 0 0 0 1px rgba(255, 255, 255, 0.25) inset;
  transform: translateY(-1px);
}

.btn-ghost {
  background: var(--surface);
  border-color: var(--border);
  color: var(--fg);
  backdrop-filter: blur(12px);
}
.btn-ghost:hover {
  background: var(--surface-strong);
  border-color: var(--border-strong);
}

.btn-small {
  padding: 10px 20px;
  font-size: 0.875rem;
}

/* Outlined in the accent gradient, with the glow outside the shape rather than
   a fill inside it – the treatment in the reference screens for the portfolio
   CTA and the closing call to action.
   The ring is a masked border rather than `border-image`, which cannot follow
   border-radius; the padding-box/border-box pair cancels in the middle and
   leaves only the band painted. */
.btn-accent {
  position: relative;
  background: rgba(10, 10, 18, 0.55);
  color: var(--fg);
  backdrop-filter: blur(10px);
  box-shadow: 0 0 22px -6px rgba(124, 140, 255, 0.55);
}
.btn-accent::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  background: var(--gradient-brand);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}
.btn-accent:hover {
  background: rgba(18, 18, 32, 0.7);
  box-shadow: 0 0 34px -4px rgba(124, 140, 255, 0.75);
  transform: translateY(-1px);
}
/* The brand mark inside the pill, as in the reference. Drawn in CSS rather than
   added to every button's markup: it is decoration, and nine call sites would
   each need the same two spans and an aria-hidden. */
.btn-accent::after {
  content: "e";
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.45em;
  height: 1.45em;
  margin-inline-end: 0.55em;
  order: -1;
  border-radius: 50%;
  background: var(--gradient-brand);
  color: #0a0a12;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.82em;
  line-height: 1;
}

/* ========== Header ========== */
.site-header {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 100;
  transition: background var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out), backdrop-filter var(--dur-base) var(--ease-out);
  border-bottom: 1px solid transparent;
}
.site-header.is-scrolled {
  background: rgba(8, 8, 13, 0.72);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-bottom-color: var(--border);
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 76px;
  gap: var(--space-4);
}

.logo-mark {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 1.4rem;
  letter-spacing: -0.02em;
  color: var(--fg);
}
.logo-dot {
  background: var(--gradient-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.nav-desktop {
  display: none;
  gap: var(--space-5);
  margin-inline-start: auto;
  margin-inline-end: var(--space-5);
}
.nav-desktop a {
  color: var(--fg-muted);
  font-weight: 500;
  font-size: 0.95rem;
  transition: color var(--dur-fast) var(--ease-out);
}
.nav-desktop a:hover { color: var(--fg); }

.nav-cta { display: none; }

.nav-toggle {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  background: transparent;
  border: none;
  padding: 0;
  margin-inline-start: auto;
}
.nav-toggle-bar {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--fg);
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
  margin-inline: auto;
}
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.mobile-nav {
  max-height: 0;
  overflow: hidden;
  background: rgba(8, 8, 13, 0.97);
  backdrop-filter: blur(16px);
  border-bottom: 1px solid var(--border);
  transition: max-height var(--dur-slow) var(--ease-out);
}
/* Wysokosc otwartego menu to wysokosc ekranu pod naglowkiem, a nie sztywne
   420px: przy 846px tresci stary limit ucinal Realizacje, Blog, Kontakt
   i przycisk wyceny, i nie dalo sie do nich dojsc. Za dluga lista przewija
   sie wewnatrz menu. */
.mobile-nav.is-open {
  max-height: calc(100vh - 76px);
  max-height: calc(100dvh - 76px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
html.menu-otwarte, html.menu-otwarte body { overflow: hidden; }
html.menu-otwarte .site-header { background: rgba(8, 8, 13, 0.97); border-bottom-color: var(--border); }

/* Strzalka wstecz (wstawiana przez script.js na podstronach). */
.nav-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 40px;
  height: 40px;
  margin-inline-end: calc(var(--space-2) - var(--space-4));
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.04);
  color: var(--fg);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.nav-back svg { width: 20px; height: 20px; }
.nav-back:hover, .nav-back:focus-visible { background: rgba(124, 140, 255, 0.14); border-color: var(--blue-soft); }
.mobile-nav nav {
  display: flex;
  flex-direction: column;
  padding: var(--space-3) var(--space-4) var(--space-5);
  gap: var(--space-1);
}
.mobile-nav a {
  padding: var(--space-3) 0;
  font-size: 1.05rem;
  color: var(--fg-muted);
  border-bottom: 1px solid var(--border);
}
.mobile-nav a:last-child { border-bottom: none; margin-top: var(--space-2); }
/* The last item is the CTA button, and `.mobile-nav a` is a class plus a type
   selector, so it outranked .btn-primary and painted the label in muted grey.
   That was already wrong on the old dark fill at 2.8:1; on the brighter gradient
   it fell to 1.1:1, which is invisible. */
.mobile-nav a.btn-primary { color: var(--on-gradient); }

@media (min-width: 1024px) {
  .nav-desktop { display: flex; }
  .nav-cta { display: inline-flex; }
  .nav-toggle { display: none; }
  .mobile-nav { display: none; }
  .nav-back { display: none; }
}

/* ========== Hero ========== */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-block: calc(76px + var(--space-7)) var(--space-8);
  overflow: clip;
}

.hero-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.hero-bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 30%;
}
.hero-scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(5,5,7,0.55) 0%, rgba(6,6,10,0.82) 55%, var(--bg-base) 100%),
    linear-gradient(90deg, rgba(5,5,7,0.55) 0%, rgba(5,5,7,0.2) 50%, rgba(5,5,7,0.65) 100%);
}

.glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0.55;
  pointer-events: none;
}
.glow-blue {
  width: 480px;
  height: 480px;
  background: var(--glow-blue);
  top: -120px;
  right: -100px;
}
.glow-purple {
  width: 420px;
  height: 420px;
  background: var(--glow-purple);
  bottom: -140px;
  left: -100px;
}

.rise-arrows {
  position: fixed;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  pointer-events: none;
}
.rise-arrow {
  position: absolute;
  bottom: 0;
  width: 20px;
  height: 20px;
  opacity: 0;
  animation: rise-arrow-move var(--dur, 4.5s) linear infinite;
  animation-delay: var(--delay, 0s);
}
.rise-arrow svg { width: 100%; height: 100%; }
.rise-arrow path {
  stroke-dasharray: 17;
  stroke-dashoffset: 17;
  animation: rise-arrow-draw var(--dur, 4.5s) linear infinite;
  animation-delay: var(--delay, 0s);
}
@keyframes rise-arrow-move {
  0% { translate: 0 24px; opacity: 0; }
  10% { opacity: 0.55; }
  75% { opacity: 0.3; }
  100% { translate: 0 -70vh; opacity: 0; }
}
@keyframes rise-arrow-draw {
  0% { stroke-dashoffset: 17; }
  14% { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: 0; }
}

.hero-inner {
  position: relative;
  z-index: 1;
  max-width: 720px;
}
@media (min-width: 1280px) {
  .hero .container { max-width: var(--hero-max); }
}

.eyebrow, .section-eyebrow {
  display: inline-block;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--blue-soft);
  margin-block-end: var(--space-3);
}

.hero-title {
  font-size: clamp(2.4rem, 4.4vw, 4.2rem);
  font-weight: 700;
  margin-block-end: var(--space-4);
}

.hero-subtitle {
  font-size: clamp(1.05rem, 2vw, 1.25rem);
  color: var(--fg-muted);
  max-width: 56ch;
  margin-block-end: var(--space-5);
}
/* Only from 1280px up does the 3D card exist; below that it is display:none
   and the paragraph can have the full measure back. */
@media (min-width: 1280px) {
  .hero-subtitle { max-width: min(56ch, 47vw); }
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-5);
  margin-block-end: var(--space-7);
}

/* Ocena jako odnosnik, nie jako sam napis: liczbe, ktorej nie da sie
   sprawdzic jednym klinieciem, czyta sie jak ozdobe. */
.hero-ocena {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: 0.95rem;
  color: var(--fg-muted);
}
.hero-ocena-gwiazdka {
  display: inline-flex;
  width: 20px;
  height: 20px;
}
.hero-ocena-gwiazdka svg { width: 100%; height: 100%; }
.hero-ocena strong {
  font-family: var(--font-heading);
  font-size: 1.05rem;
  color: var(--fg);
}
.hero-ocena a {
  color: var(--fg-muted);
  border-bottom: 1px solid var(--border-strong);
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.hero-ocena a:hover { color: var(--fg); border-bottom-color: var(--purple-soft); }

/* Szesc kafelkow, wiec siatka, a nie rzad: w jednej linii nie zmieszczaja sie
   na zadnej sensownej szerokosci. */
.hero-stats {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  border-top: 1px solid var(--border);
  padding-block-start: var(--space-4);
}
.hero-stat {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--fg);
  font-size: 0.95rem;
  font-weight: 500;
  perspective: 500px;
}

/* Solid tiles rather than outlines: a real gradient face, a lit top edge and a
   dark under-edge, tilted a few degrees so the light reads as coming from above
   left. The depth is a stack of box-shadows – one inset highlight, one inset
   shade, one cast shadow – which is cheaper than building actual 3D faces and
   holds up at this size. */
.hero-stat-icon {
  display: inline-flex;
  width: 54px;
  height: 54px;
  align-items: center;
  justify-content: center;
  border-radius: 16px;
  background: var(--gradient-brand);
  border: none;
  color: var(--on-gradient);
  flex-shrink: 0;
  transform: rotateX(12deg) rotateY(-14deg);
  transform-style: preserve-3d;
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.45),
    inset 0 -3px 0 rgba(0, 0, 0, 0.35),
    inset 3px 0 0 rgba(255, 255, 255, 0.12),
    0 14px 24px -10px rgba(0, 0, 0, 0.85),
    0 6px 18px -6px var(--glow-purple);
  transition: transform var(--dur-slow) var(--ease-out), box-shadow var(--dur-slow) var(--ease-out);
}
/* Znak lezy plasko na sciance kwadracika.
   Wczesniej byl wypchniety o translateZ(14px), a poniewaz punkt zbiegu
   perspektywy lezy w srodku calego kafelka, a nie kwadracika, przesuniecie w
   glab odsuwalo go takze w bok: zostawalo 4,4px zapasu z lewej przy 21,8px z
   prawej i 11,2px u gory przy 16,9px u dolu, czyli znak wygladal, jakby
   wychodzil poza pole. */
.hero-stat-icon svg {
  width: 26px;
  height: 26px;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.5));
}
.hero-stat:hover .hero-stat-icon {
  transform: rotateX(0deg) rotateY(0deg) translateZ(10px);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.5),
    inset 0 -3px 0 rgba(0, 0, 0, 0.35),
    0 20px 32px -12px rgba(0, 0, 0, 0.9),
    0 10px 26px -6px var(--glow-purple);
}
@media (prefers-reduced-motion: reduce) {
  .hero-stat-icon { transition: none; }
}

@media (min-width: 640px) {
  .hero-stats { grid-template-columns: repeat(2, 1fr); column-gap: var(--space-5); }
}
@media (min-width: 900px) {
  .hero-stats { grid-template-columns: repeat(3, 1fr); }
}
/* Od 1280px wszystkie piec w jednym rzedzie: dwa wiersze robily z paska
   ciezki blok, a tyle miejsca w poziomie jest dopiero tutaj. */
@media (min-width: 1280px) {
  .hero-stats { grid-template-columns: repeat(5, minmax(0, 1fr)); column-gap: var(--space-4); }
  .hero-stat { font-size: 0.88rem; gap: var(--space-2); }
  .hero-stat-icon { width: 44px; height: 44px; border-radius: 13px; }
  .hero-stat-icon svg { width: 21px; height: 21px; }
}

/* ---------- Hero: "brzmi znajomo?" -----------------------------------------

   The hero is min-height:100svh with its content vertically centred, so on a
   tall screen a few hundred pixels of empty background were left under the
   facts row. What goes there is what clients actually open the conversation
   with: three sentences we hear most often, each linking to the service that
   answers it. A question about the visitor rather than one more claim about
   us, and three internal links from the top of the page. */
.hero-sytuacje {
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-4);
  border-top: 1px solid var(--border);
}
.hero-sytuacje-label {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-block-end: var(--space-3);
}
.hero-sytuacje ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-2);
}
.hero-sytuacje a {
  display: block;
  padding: var(--space-3);
  margin-inline: calc(var(--space-3) * -1);
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: background var(--dur-base) var(--ease-out);
}
.hero-sytuacje a:hover,
.hero-sytuacje a:focus-visible { background: rgba(255, 255, 255, 0.04); }
.hero-sytuacja-cytat {
  display: block;
  color: var(--fg);
  font-size: 0.98rem;
  line-height: 1.5;
  text-wrap: balance;
}
.hero-sytuacja-odp {
  display: block;
  margin-block-start: var(--space-2);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--blue-soft);
  transition: color var(--dur-base) var(--ease-out);
}
/* The arrow is a pseudo-element so it can slide without moving the label. */
.hero-sytuacja-odp::after {
  content: "\2192";
  display: inline-block;
  /* inline-block drops a leading space in content, so the gap is a margin. */
  margin-inline-start: 0.4em;
  translate: 0 0;
  transition: translate var(--dur-base) var(--ease-out);
}
.hero-sytuacje a:hover .hero-sytuacja-odp,
.hero-sytuacje a:focus-visible .hero-sytuacja-odp { color: var(--fg); }
.hero-sytuacje a:hover .hero-sytuacja-odp::after,
.hero-sytuacje a:focus-visible .hero-sytuacja-odp::after { translate: 4px 0; }

@media (min-width: 760px) {
  .hero-sytuacje ul { grid-template-columns: repeat(3, 1fr); gap: var(--space-4); }
  /* The 3D card sits over the right half of the hero, but only down to about
     its vertical middle; this band is below that, so it may use full width. */
  .hero-sytuacje { max-width: none; }
}

/* ========== Sections ========== */
.section {
  padding-block: var(--space-8);
}
.section-alt {
  /* Translucent, not var(--bg-elevated): this paints directly over the
     page-wide .brand-net, and at full opacity it would hide the constellation
     across most of the page. */
  background: rgba(19, 19, 27, 0.82);
  border-block: 1px solid var(--border);
}

.opinie-section {
  position: relative;
  overflow: clip;
}
.opinie-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.opinie-bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.6;
}
.opinie-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--bg-elevated) 0%, rgba(19,19,27,0.35) 40%, var(--bg-elevated) 100%);
}
.opinie-section .container {
  position: relative;
  z-index: 1;
}

.uslugi-section {
  position: relative;
  overflow: clip;
  /* Dense rhythm for this one section. The page-wide --space-8 (96px) block
     padding is right for narrative sections, but this is a capability index –
     a dense-scale block – and the loose scale is most of why it read as tall
     and empty. Everything inside steps down one rung on the spacing scale. */
  padding-block: var(--space-7) var(--space-6);
}
.uslugi-section .container {
  position: relative;
  z-index: 1;
}

.realizacje-section {
  position: relative;
  overflow: clip;
}
.realizacje-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}
/* The mist image and the opaque scrim under it are gone. Both were painted on
   top of the page background, so with the constellation running behind the site
   this section was the one place where it stopped dead – a dark rectangle
   between two lit ones. What is left is a faint vignette that settles the cards
   against the field without hiding it. */
.realizacje-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 80% at 50% 50%, rgba(10, 10, 16, 0.55) 0%, rgba(10, 10, 16, 0.2) 55%, transparent 100%);
}
.realizacje-section .container {
  position: relative;
  z-index: 1;
}

.tab-icon {
  position: absolute;
  bottom: 0;
  width: 22px;
  height: 22px;
  opacity: 0;
  animation: tab-drift var(--dur, 9s) linear infinite;
  animation-delay: var(--delay, 0s);
}
.tab-icon svg { width: 100%; height: 100%; }
@keyframes tab-drift {
  0% { translate: 0 24px; opacity: 0; }
  10% { opacity: 0.4; }
  80% { opacity: 0.22; }
  100% { translate: 14px -65vh; opacity: 0; }
}

.section-title {
  font-size: clamp(1.75rem, 4vw, 2.5rem);
  font-weight: 600;
  max-width: 20ch;
  margin-block-end: var(--space-3);
}
/* Cel efektu maszyny do pisania ma white-space: nowrap i nie zawija sie, wiec
   na najwezszych telefonach naglowek musi zejsc o stopien nizej, inaczej
   ostatnie slowo jest po cichu obciete. */
@media (max-width: 359px) {
  .section-title { font-size: 1.55rem; }
}
.section-lead {
  color: var(--fg-muted);
  font-size: 1.05rem;
  max-width: 60ch;
  margin-block-end: var(--space-7);
}

/* ========== Photo tile (shared duotone treatment) ========== */
.photo-tile {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  isolation: isolate;
}
.photo-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(0.3) brightness(0.6) contrast(1.15) saturate(1.05);
}
.photo-tile::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(155deg, rgba(61,102,224,0.4), rgba(124,58,237,0.4));
  mix-blend-mode: color;
}
.photo-tile::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg, transparent 40%, rgba(5,5,7,0.75) 100%);
}

/* Real client work: show it as it actually looks, no brand-color filter */
.photo-tile-flat img {
  filter: none;
}
.photo-tile-flat::after {
  content: none;
}

/* ========== Process ========== */
.process-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
}
/* Krok to jedna karta: zdjecie, numer, tytul i opis w jednej ramce. Wczesniej
   zdjecia stały w osobnym rzedzie nad opisami i nic nie mowilo, ze ktore
   nalezy do ktorego.

   Ramka gradientowa w trzech warstwach, tak jak w kartach realizacji: --surface
   jest polprzezroczyste, wiec pod nim musi lezec warstwa nieprzezroczysta,
   inaczej gradient z border-box przebija przez cala karte. */
.process-item {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  background:
    linear-gradient(var(--surface), var(--surface)) padding-box,
    linear-gradient(var(--bg-base), var(--bg-base)) padding-box,
    var(--gradient-brand) border-box;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.7s var(--ease-out), transform 0.7s var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}
.process-item:hover {
  box-shadow: 0 0 30px rgba(76, 124, 255, 0.24), 0 0 60px rgba(124, 58, 237, 0.14);
}

.process-foto {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-bottom: 1px solid var(--border);
}
.process-foto img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(1.06) contrast(1.04);
  transition: scale var(--dur-slow) var(--ease-out);
}
.process-item:hover .process-foto img { scale: 1.04; }

.process-tresc {
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  flex: 1;
}
/* Poswiata siedzi teraz pod dolna czescia karty, nie na srodku: na srodku
   wypadala pod zdjeciem, ktore i tak ja zakrywalo. */
.process-item::before {
  content: "";
  position: absolute;
  inset-block-end: -40px;
  inset-inline-start: 50%;
  width: 240px;
  height: 200px;
  transform: translateX(-50%);
  background: radial-gradient(circle, rgba(76, 124, 255, 0.34), rgba(124, 58, 237, 0.22) 55%, transparent 75%);
  filter: blur(34px);
  opacity: 0.5;
  z-index: -1;
  pointer-events: none;
}
.process-list.in-view .process-item {
  opacity: 1;
  transform: translateY(0);
}
.process-list.in-view .process-item:nth-child(1) { transition-delay: 0.2s; }
.process-list.in-view .process-item:nth-child(2) { transition-delay: 0.55s; }
.process-list.in-view .process-item:nth-child(3) { transition-delay: 0.9s; }
.process-list.in-view .process-item:nth-child(4) { transition-delay: 1.25s; }

.process-number {
  width: 42px;
  height: 42px;
  margin-block-end: var(--space-3);
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--bg-base);
  border: 1px solid var(--border-strong);
  font-family: var(--font-heading);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--blue-soft);
  opacity: 0;
  transform: scale(0.4);
  transition: opacity 0.45s var(--ease-out), transform 0.45s var(--ease-out);
}
.process-list.in-view .process-number {
  opacity: 1;
  transform: scale(1);
}
.process-list.in-view .process-item:nth-child(1) .process-number { transition-delay: 0.3s; }
.process-list.in-view .process-item:nth-child(2) .process-number { transition-delay: 0.65s; }
.process-list.in-view .process-item:nth-child(3) .process-number { transition-delay: 1s; }
.process-list.in-view .process-item:nth-child(4) .process-number { transition-delay: 1.35s; }
.process-item h3 {
  font-size: clamp(1.1rem, 1.6vw, 1.3rem);
  margin-block-end: var(--space-2);
  text-wrap: balance;
}
.process-item p {
  color: var(--fg-muted);
  font-size: 0.95rem;
  line-height: 1.65;
  margin: 0;
}

@media (min-width: 768px) {
  .process-list { grid-template-columns: repeat(2, 1fr); gap: var(--space-4); }
}
@media (min-width: 1024px) {
  .process-list { grid-template-columns: repeat(4, 1fr); }
}

/* The section heading picks up the same accent as the frames, so the colour
   reads as one decision rather than as a frame treatment and a coloured title.
   Applied on the section, not on .section-title, which every other section
   shares and must stay plain. */
#proces .section-title {
  background: var(--gradient-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
#proces .section-eyebrow {
  color: var(--purple-soft);
}

/* ========== Services ========== */
.services-header {
  margin-block-end: var(--space-6);
}
.services-header .section-lead { margin-block-end: 0; }

/* ---------- Co robimy: four areas of competence, not twelve line items ------

   This was twelve equal tiles, then four narrow columns. Four columns fit on
   the screen but gave each area 288px, so the descriptions broke after three
   words and a lot of empty background was left to the right and below. One row
   per area gives the text its full measure, makes room for an illustration,
   and fills those gaps with something to read.

   Still no card chrome: what carries the structure is the hairline between
   rows and the set number, because these are capabilities rather than objects
   to collect. */
.obszary {
  border-top: 1px solid var(--border);
}
.obszar {
  position: relative;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  align-items: center;
  padding-block: var(--space-6);
  border-bottom: 1px solid var(--border);
}

/* The top edge lights up in the brand gradient on approach, the same signature
   the rest of the page uses. */
.obszar::before {
  content: "";
  position: absolute;
  inset-block-start: -1px;
  inset-inline: 0;
  height: 2px;
  background: var(--gradient-brand);
  transform-origin: left;
  scale: 0 1;
  transition: scale var(--dur-slow) var(--ease-out);
}
.obszar:hover::before,
.obszar:focus-within::before { scale: 1 1; }

/* Numer jako duza cyfra za naglowkiem, a nie drobny podpis nad nim: na
   szerokim ekranie kolumna tekstu ma sporo wolnej wysokosci nad soba, bo
   rownamy ja do srodka ilustracji, i to wlasnie ta cyfra ja wypelnia. */
.obszar-numer {
  position: absolute;
  inset-block-start: calc(var(--space-6) * -1);
  inset-inline-start: -6px;
  z-index: -1;
  font-family: var(--font-heading);
  font-size: clamp(4rem, 6vw, 7.5rem);
  font-weight: 700;
  line-height: 1;
  color: rgba(255, 255, 255, 0.045);
  pointer-events: none;
}
.obszar-tresc { position: relative; isolation: isolate; }
.obszar h3 {
  font-size: clamp(1.3rem, 2.4vw, 1.75rem);
  letter-spacing: -0.01em;
  margin-block-end: var(--space-3);
  text-wrap: balance;
}
.obszar-tresc > p {
  color: var(--fg-muted);
  font-size: 1rem;
  line-height: 1.7;
  max-width: 52ch;
  margin-block-end: var(--space-4);
}

.obszar-lista {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
}
.obszar-lista a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--fg);
  font-size: 0.9rem;
  text-decoration: none;
  transition: color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              background var(--dur-base) var(--ease-out);
}
/* The arrow is a pseudo-element rather than an inline glyph so it can slide
   without reflowing the label next to it. */
.obszar-lista a::after {
  content: "\2192";
  color: var(--fg-faint);
  translate: 0 0;
  transition: translate var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}
.obszar-lista a:hover,
.obszar-lista a:focus-visible {
  color: #fff;
  border-color: var(--purple-soft);
  background: rgba(255, 255, 255, 0.04);
}
.obszar-lista a:hover::after,
.obszar-lista a:focus-visible::after { color: var(--blue-soft); translate: 3px 0; }

/* ---------- The illustration ------------------------------------------------

   Drawn for the service pages and reused here: same palette, no third-party
   rights. Loaded through <img> rather than inlined, because every one of these
   files declares its gradient as id="g" and in a single document they would
   overwrite each other. */
.obszar-ilustracja {
  position: relative;
  margin: 0;
  isolation: isolate;
}
.obszar-ilustracja img {
  display: block;
  width: 100%;
  /* Sufit, zeby na monitorze ultrapanoramicznym rysunek nie urosl do
     kilkuset pikselow wysokosci i nie rozpychal wiersza. */
  max-width: 760px;
  margin-inline: auto;
  height: auto;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.02);
  transition: border-color var(--dur-base) var(--ease-out);
}
/* A soft brand-coloured halo behind the drawing. Blurred and behind, so it
   never touches the drawing's own edges. */
.obszar-ilustracja::before {
  content: "";
  position: absolute;
  inset: 8% 12%;
  z-index: -1;
  background: var(--gradient-brand-wide);
  filter: blur(46px);
  opacity: 0.2;
  transition: opacity var(--dur-slow) var(--ease-out);
}
.obszar:hover .obszar-ilustracja::before { opacity: 0.34; }
.obszar:hover .obszar-ilustracja img { border-color: var(--border-strong); }

/* ---------- Entrance -------------------------------------------------------

   The row fades in as a whole through .reveal; on top of that the drawing
   slides in from its own side and the pills arrive one after another, so the
   eye is walked from the heading to the links. Only opacity and translate are
   animated, which the compositor handles without laying the page out again.
   The global prefers-reduced-motion block collapses every duration here to
   nothing, so the end state is what such a visitor sees straight away. */
.obszar-ilustracja {
  opacity: 0;
  translate: 28px 0;
  transition: opacity 0.65s var(--ease-out) 0.1s, translate 0.65s var(--ease-out) 0.1s;
}
.obszar.in-view .obszar-ilustracja { opacity: 1; translate: 0 0; }
.obszar-lista li {
  opacity: 0;
  translate: 0 10px;
  transition: opacity 0.45s var(--ease-out), translate 0.45s var(--ease-out);
}
.obszar.in-view .obszar-lista li { opacity: 1; translate: 0 0; }
.obszar.in-view .obszar-lista li:nth-child(1) { transition-delay: 0.20s; }
.obszar.in-view .obszar-lista li:nth-child(2) { transition-delay: 0.29s; }
.obszar.in-view .obszar-lista li:nth-child(3) { transition-delay: 0.38s; }

@media (min-width: 860px) {
  .obszar {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr);
    gap: var(--space-7);
    padding-block: var(--space-7);
  }
  /* Sides swap every other row so the page does not read as one long column
     of pictures down the right-hand edge. */
  .obszar:nth-child(even) .obszar-tresc { order: 2; }
  .obszar:nth-child(even) .obszar-ilustracja { translate: -28px 0; }
  .obszar:nth-child(even).in-view .obszar-ilustracja { translate: 0 0; }
}

/* ---------- Jak pracujemy: three rules, stated before the price list -------

   A consultancy is judged on how it decides, not on what it stocks, so this
   band sits above the services rather than below them. Deliberately plain:
   numbered counters, a gradient hairline over each item, no boxes. */
.podejscie-section {
  position: relative;
  isolation: isolate;
  border-block: 1px solid var(--border);
  background: rgba(19, 19, 27, 0.55);
  padding-block: var(--space-7);
  overflow: clip;
}
/* Dwie rozmyte plamy w kolorach marki po bokach pasa. Wypelniaja margines,
   ktory na szerokim ekranie i tak zostaje pusty, i robia to bez zadnego ruchu
   w petli. */
.podejscie-section::before,
.podejscie-section::after {
  content: "";
  position: absolute;
  z-index: -1;
  inline-size: 46vw;
  block-size: 130%;
  inset-block-start: -15%;
  filter: blur(90px);
  opacity: 0.16;
  pointer-events: none;
}
.podejscie-section::before {
  inset-inline-start: -22vw;
  background: radial-gradient(closest-side, #47D6E8, transparent);
}
.podejscie-section::after {
  inset-inline-end: -22vw;
  background: radial-gradient(closest-side, #A855F7, transparent);
}
.podejscie-section .section-title { margin-block-end: var(--space-6); }
.podejscie {
  list-style: none;
  counter-reset: zasada;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
}
.podejscie li {
  counter-increment: zasada;
  padding-block-start: var(--space-5);
  border-top: 1px solid var(--border);
  position: relative;
  isolation: isolate;
}
/* Kreska nad zasada rozjezdza sie na pelna szerokosc po wejsciu w kadr.
   Animowana jest sama skala, wiec nie pociaga za soba ukladu strony. */
.podejscie li::before {
  content: "";
  position: absolute;
  inset-block-start: -1px;
  inset-inline: 0;
  height: 2px;
  background: var(--gradient-brand);
  transform-origin: left;
  scale: 0.16 1;
  transition: scale 0.8s var(--ease-out);
}
.podejscie.in-view li::before { scale: 1 1; }
.podejscie.in-view li:nth-child(2)::before { transition-delay: 0.12s; }
.podejscie.in-view li:nth-child(3)::before { transition-delay: 0.24s; }
/* Numer wychodzi na wierzch: duza cyfra pod tekstem, nie drobny podpis z boku.
   Siedzi za trescia i jest przycieta do wiersza, wiec nie wchodzi w sasiednia
   kolumne. */
.podejscie li::after {
  content: counter(zasada, decimal-leading-zero);
  position: absolute;
  inset-block-start: calc(var(--space-4) * -1);
  inset-inline-end: 0;
  z-index: -1;
  font-family: var(--font-heading);
  font-size: 5.5rem;
  font-weight: 700;
  line-height: 1;
  color: rgba(255, 255, 255, 0.045);
  pointer-events: none;
}
.podejscie h3 {
  font-size: 1.12rem;
  letter-spacing: -0.005em;
  margin-block-end: var(--space-2);
  text-wrap: balance;
}
.podejscie p {
  color: var(--fg-muted);
  font-size: 0.93rem;
  line-height: 1.7;
  max-width: 46ch;
  margin: 0;
}
@media (min-width: 860px) {
  .podejscie { grid-template-columns: repeat(3, 1fr); gap: var(--space-5); }
}

/* ========== Portfolio ========== */
.portfolio-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}
.portfolio-card {
  position: relative;
  isolation: isolate;
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  overflow: hidden;
  /* Gradient frame, as in the reference. Two backgrounds rather than a masked
     pseudo-element: ::before and ::after are both already spoken for on this
     card – the blurred halo and the hover sheen.
     Three layers, not two: --surface is rgba(255,255,255,0.055), so a single
     padding-box layer of it let the border-box gradient through the whole card
     and the body turned into a slab of cyan-to-purple. An opaque layer has to
     sit underneath it to stop the gradient at the border band. */
  background:
    linear-gradient(var(--surface), var(--surface)) padding-box,
    linear-gradient(var(--bg-base), var(--bg-base)) padding-box,
    var(--gradient-brand) border-box;
}

/* Entrance: each card slides in from the side its image sits on, so the
   motion agrees with the alternating layout instead of fighting it.

   This overrides .reveal's own translateY rather than adding a second
   mechanism – the existing IntersectionObserver already staggers siblings by
   (i % 4) * 60ms, and layering another per-card transition-delay on top of
   that double-staggers the last card into a visible lag.

   The rest state is `translate: none`, not `0 0`: a card that keeps a live
   transform forever also keeps a composited layer forever, and text rendered
   on one loses subpixel antialiasing for the life of the page. */
.portfolio-card.reveal {
  transform: none;
  translate: var(--pf-from, 0) 0;
  opacity: 0;
  transition: opacity 0.65s var(--ease-out), translate 0.65s var(--ease-out);
}
.portfolio-card.reveal.in-view {
  translate: none;
  opacity: 1;
}

.portfolio-card::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 340px;
  height: 340px;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, rgba(76, 124, 255, 0.32), rgba(124, 58, 237, 0.22) 55%, transparent 75%);
  filter: blur(50px);
  opacity: 0.6;
  z-index: -1;
  pointer-events: none;
  transition: opacity var(--dur-slow) var(--ease-out);
}

/* Brand-gradient hairline ring, revealed on pointer/keyboard contact.

   Drawn as a masked 1px inset rather than a border-image so it can fade:
   background-image cannot interpolate from `none`, so a gradient swapped in
   on :hover pops instead of easing. pointer-events:none matters – this sits
   above the preview button's z-index and would otherwise swallow the click. */
/* Pierscien ::after usuniety.
   Powstal, gdy karta nie miala jeszcze stalej ramki - byl jej odpowiednikiem
   pojawiajacym sie po najechaniu. Odkad karta ma gradientowa ramke na stale
   (tlo border-box w 1-pikselowym pasie obramowania), ::after rysowal DRUGI
   pierscien tej samej grubosci tuz obok, przy krawedzi padding-box. Dwie
   rownolegle linie 1 px z ciemna szczelina miedzy nimi wygladaly jak pekajaca,
   rozdwojona ramka - i najmocniej wlasnie podczas przesuwania karuzeli, bo
   karty przejezdzaja pod nieruchomym kursorem i kazda po kolei dostaje :hover.
   Sygnal najechania niesie teraz sama ramka: rozjasnia sie i dostaje poswiate,
   zamiast dokladac druga linie. */

.portfolio-thumb {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: 0;
  border: none;
  box-shadow: 0 0 26px rgba(76, 124, 255, 0.28), 0 0 46px rgba(124, 58, 237, 0.16);
}
.portfolio-thumb img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Settles from slightly over-scaled as the card arrives, then responds to
     hover. `scale` is its own property so the two never clobber each other. */
  scale: 1.06;
  transition: scale 0.9s var(--ease-out);
}
.portfolio-card.in-view .portfolio-thumb img {
  scale: 1;
}

/* Deliberately NOT animated in. A control that fades in on a delay is a
   control a keyboard user can Tab to while it is still invisible – the focus
   ring lands on nothing. It is the primary CTA of the section; it stays put. */
.portfolio-badge {
  position: absolute;
  left: var(--space-3);
  bottom: var(--space-3);
  z-index: 2;
  padding: 6px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: rgba(8, 8, 13, 0.6);
  backdrop-filter: blur(8px);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--fg-muted);
}

/* Same accent ring as the CTA pills, so every clickable thing on this section
   is drawn from one kit. The tick stays – it says the project is finished,
   which the frame alone does not. */
.portfolio-badge-done {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--fg);
  border-color: transparent;
  background:
    linear-gradient(rgba(8, 8, 13, 0.72), rgba(8, 8, 13, 0.72)) padding-box,
    var(--gradient-brand) border-box;
  box-shadow: 0 0 18px -8px rgba(124, 140, 255, 0.8);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.portfolio-badge-done:hover,
.portfolio-badge-done:focus-visible {
  background:
    linear-gradient(rgba(24, 24, 44, 0.85), rgba(24, 24, 44, 0.85)) padding-box,
    var(--gradient-brand) border-box;
  box-shadow: 0 0 26px -6px rgba(124, 140, 255, 0.9);
}
.portfolio-badge-done svg {
  width: 14px;
  height: 14px;
  color: #6FE3F0;
}
/* The badge is a <button> on cards that open a local preview and an <a> on the
   ones that go straight to the client's live site. Same pill either way. */
a.portfolio-badge-done {
  text-decoration: none;
}

/* Second control, on cards where the client's site is public: opens it in a new
   tab. Bottom-right, so the preview pill on the left keeps its exact position. */
.portfolio-visit {
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-3);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: rgba(8, 8, 13, 0.6);
  backdrop-filter: blur(8px);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--fg-muted);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.portfolio-visit:hover,
.portfolio-visit:focus-visible {
  color: var(--fg);
  background: rgba(24, 24, 44, 0.85);
}
.portfolio-visit svg {
  width: 13px;
  height: 13px;
}
/* Narrow cards cannot fit two pills on one line, so this one moves to the top
   corner rather than overlapping the preview button. */
@media (max-width: 560px) {
  .portfolio-visit {
    bottom: auto;
    top: var(--space-3);
  }
}

.portfolio-card h3,
.portfolio-card h2,
.portfolio-card .portfolio-title-clone {
  font-size: 1.1rem;
  padding: var(--space-4) var(--space-4) 0;
}
.portfolio-card p {
  color: var(--fg-muted);
  font-size: 0.9rem;
  padding: var(--space-2) var(--space-4) var(--space-4);
}

/* ---------- Homepage: draggable 3D carousel ----------
   The stacked grid is still what realizacje.html uses – it is the right shape
   for "here is the full list". On the homepage the same four projects sit on a
   rail you can throw sideways, and each card turns to face you as it reaches
   the middle. Native overflow scrolling does the physics, so it is smooth on
   touch for free; the JS only reads scroll position and writes transforms. */
.portfolio-carousel {
  position: relative;
  overflow-x: auto;
  overflow-y: hidden;
  /* No scroll-snap: the rail drifts continuously on its own, and snap points
     fight a continuous scroll – the rail would keep getting yanked back. */
  perspective: 1100px;
  perspective-origin: 50% 50%;
  cursor: grab;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* Full-bleed so cards keep turning right out to the screen edges instead of
     hitting the container wall mid-rotation. */
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  padding-inline: 0;
}
.portfolio-carousel::-webkit-scrollbar { display: none; }
.portfolio-carousel.is-dragging { cursor: grabbing; }

.portfolio-rail {
  display: flex;
  gap: var(--space-5);
  transform-style: preserve-3d;
  /* No centring padding. The set is duplicated and loops seamlessly, so the
     rail should read as a continuous band of work filling the screen – half a
     viewport of empty space before the first card made it look half-loaded on
     arrival, which was the whole complaint. */
  padding-inline: 0;
  padding-block: var(--space-7);
  /* Under preserve-3d the rail keeps its own plane at z=0 while the cards are
     pushed back to translateZ(-260px) – so the rail sits in FRONT of them and
     swallowed every click on "Zobacz podgląd". It carries no visuals, so it
     opts out of hit testing entirely and the cards opt back in. */
  pointer-events: none;
}
.portfolio-carousel .portfolio-card {
  pointer-events: auto;
  /* 2px, a nie 1px jak w siatce: tutaj karta jest obracana i odsunieta w glab,
     wiec kreska zweza sie na ekranie i przy 1px schodzila ponizej piksela,
     co dawalo migotanie. Nie 1.5px, bo przegladarka zaokragla szerokosc ramki
     do pelnych pikseli urzadzenia i przy zwyklej gestosci 1.5px to nadal 1px.
     Rachunek jest w komentarzu przy MAX_TURN w script.js. */
  border-width: 2px;
}

/* The .reveal entrance sets `transform: none`, and at equal specificity it was
   cancelling the rotation on every card that had been revealed – hence the
   selector carrying .reveal here, so the carousel's own transform always wins. */
.portfolio-carousel .portfolio-card,
.portfolio-carousel .portfolio-card.reveal,
.portfolio-carousel .portfolio-card.reveal.in-view {
  flex: 0 0 auto;
  width: min(500px, 82vw);
  /* --turn / --near / --push / --dim are written per card by script.js from
     its distance to the centre of the viewport. translateZ is what makes this
     read as depth rather than as a flat skew. */
  transform: translateZ(var(--push, 0px)) rotateY(var(--turn, 0deg)) scale(var(--near, 1));
  transform-origin: 50% 50%;
  translate: none;
  /* Przyciemnienie filtrem, nie przezroczystoscia.
     Perspektywa sciaga karty odsuniete w glab ku punktowi zbiegu, wiec mimo
     odstepu w ukladzie zachodza na siebie na ekranie - zmierzone do 156 px,
     czyli 30% szerokosci karty, na ekranie 2560 px. Samo w sobie byloby to
     nieszkodliwe, bo blizsza karta zakrywalaby dalsza. Ale przy opacity karta
     przestaje byc nieprzezroczysta i zamiast zakrycia powstaje przenikanie:
     w miejscu zachodzenia widac dwa tytuly i dwie ramki naraz, co wyglada jak
     zepsuta ramka.
     brightness() daje ten sam efekt oddalenia - karta ciemnieje wraz z
     odlegloscia od srodka - ale zostaje nieprzezroczysta, wiec zaslania
     sasiada tak, jak powinna. */
  /* saturate obok brightness, bo samo brightness mnozy ku czerni i dziala
     tym slabiej, im jasniejsza jest karta: podglad Klasyki jest kremowy, wiec
     przy brightness 0.63 nadal byl jasniejszy niz ciemna karta przy 0.95 i
     wcale nie wygladal na dalszy. Odbarwienie cofa karte niezaleznie od tego,
     jak jasna jest sama w sobie. */
  filter: brightness(var(--dim, 1)) saturate(var(--dim, 1));
  will-change: transform, opacity;
  /* Karty nie maja tylu, a bez tego przegladarka trzyma ja w pamieci i
     przelicza przy kazdym obrocie. */
  backface-visibility: hidden;
}
/* Inside the carousel every card is the same simple vertical shape – the
   alternating two-column layout below is only for the grid on realizacje.html. */
.portfolio-carousel .portfolio-card,
.portfolio-carousel .portfolio-card:nth-child(even) {
  display: block;
  grid-template-columns: none;
}
.portfolio-carousel .portfolio-thumb,
.portfolio-carousel .portfolio-card:nth-child(even) .portfolio-thumb {
  grid-column: auto;
  grid-row: auto;
  aspect-ratio: 16 / 10;
  height: auto;
  min-height: 0;
}
/* h2 belongs here too: the carousel cards on realizacje.html use h2, so this
   rule was only ever half-applying – the padding it sets was reaching the
   paragraphs but not the titles above them. */
.portfolio-carousel .portfolio-card .portfolio-title-clone,
.portfolio-carousel .portfolio-card h2,
.portfolio-carousel .portfolio-card h3,
.portfolio-carousel .portfolio-card p,
.portfolio-carousel .portfolio-card:nth-child(even) h2,
.portfolio-carousel .portfolio-card:nth-child(even) h3,
.portfolio-carousel .portfolio-card:nth-child(even) p {
  grid-column: auto;
  grid-row: auto;
  padding-inline: var(--space-4);
}
/* The sideways reveal makes no sense for something that already lives on a
   horizontal rail. Cards off to the side never intersect the viewport, so they
   never get .in-view – the carousel's own --dim handles their opacity, and the
   thumbnail has to be told to settle here rather than waiting for a class that
   will not arrive. */
.portfolio-carousel .portfolio-card.reveal { --pf-from: 0; }
.portfolio-carousel .portfolio-thumb img { scale: 1; }

.portfolio-hint {
  margin-block-start: var(--space-3);
  text-align: center;
  font-size: 0.85rem;
  color: var(--fg-faint);
}
@media (hover: none) {
  .portfolio-hint-drag { display: none; }
}

.portfolio-more {
  margin-block-start: var(--space-5);
  text-align: center;
}

/* Hover/focus richness is gated: on a touch screen :hover sticks after a tap,
   which would leave one card permanently zoomed once the preview closes. */
@media (hover: hover) and (pointer: fine) {
  .portfolio-card:hover .portfolio-thumb img,
  .portfolio-card:focus-within .portfolio-thumb img {
    scale: 1.04;
  }
  .portfolio-card:hover,
  .portfolio-card:focus-within {
    box-shadow: 0 0 30px -10px rgba(124, 140, 255, 0.85);
  }
  .portfolio-card:hover::before,
  .portfolio-card:focus-within::before {
    opacity: 0.95;
  }
}

/* Homepage teaser only – the full carousel lives on realizacje.html.
   Two columns of compact, image-on-top cards, so all four projects are on
   screen at once. The previous alternating full-width rows put roughly one and
   a half cards in a viewport, which is the wrong shape for a teaser that ends
   in "see all". */
@media (min-width: 768px) {
  .portfolio-grid {
    grid-template-columns: repeat(2, 1fr);
    max-width: 1040px;
    margin-inline: auto;
    gap: var(--space-5);
  }
  .portfolio-card {
    display: flex;
    flex-direction: column;
  }
  .portfolio-thumb {
    aspect-ratio: 16 / 10;
  }
  .portfolio-card h3,
  .portfolio-card h2,
  .portfolio-card .portfolio-title-clone {
    padding: var(--space-4) var(--space-4) 0;
  }
  .portfolio-card p {
    padding: var(--space-2) var(--space-4) var(--space-4);
  }

  /* Entrance follows the column the card sits in, so the two halves of each
     row arrive toward each other rather than all sliding the same way. */
  .portfolio-card:nth-child(odd) { --pf-from: -34px; }
  .portfolio-card:nth-child(even) { --pf-from: 34px; }
}

@media (prefers-reduced-motion: reduce) {
  .portfolio-card.reveal { translate: none; opacity: 1; }
  .portfolio-thumb img,
  .portfolio-card.in-view .portfolio-thumb img { scale: 1; }
}

/* ========== Testimonials ========== */
/* Two rows drifting against each other. One row reads as a slow slideshow
   because the eye locks onto it; two opposed rows have nothing static to lock
   onto, so the block reads as motion. Each track is duplicated and travels
   exactly -50%, which lands on an identical frame instead of snapping. */
.testimonial-rows {
  display: grid;
  gap: var(--space-4);
  /* Full-bleed. Inside the 1200px container the rows ended mid-card against
     the container wall, and the edge mask faded the outermost quotes to
     unreadable – both read as "cut off" rather than as a running marquee. */
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  padding-inline: var(--space-4);
}
.testimonial-row {
  display: flex;
  overflow: hidden;
}
.testimonial-track {
  display: flex;
  gap: var(--space-4);
  width: max-content;
  padding-inline-end: var(--space-4);
  animation: testimonial-scroll 64s linear infinite;
}
/* Carried by the TOP row, which travels right; the bottom row keeps the base
   leftward animation. Same construction as NM's, which flips a row between
   [-50% -> 0%] and [0% -> -50%] off a single reverse flag. */
.testimonial-track.is-reverse {
  animation-name: testimonial-scroll-reverse;
  animation-duration: 78s;
  /* Negative delay starts this row part-way through its cycle. Without it both
     rows sit at the same phase and show the same three cards stacked, which
     defeats the point of having two. */
  animation-delay: -31s;
}
.testimonial-rows:hover .testimonial-track {
  animation-play-state: paused;
}
@keyframes testimonial-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@keyframes testimonial-scroll-reverse {
  from { transform: translateX(-50%); }
  to { transform: translateX(0); }
}
.testimonial-card {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  /* Widened from 250px so the longest quote fits three lines instead of four.
     Trading width for height is what makes the cards shorter without cutting
     anyone off mid-sentence – at 250px the only way to keep "polecam" was a
     fourth line, which made every card in both rows taller. */
  width: min(292px, 76vw);
  margin: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  padding: var(--space-3);
  background: var(--surface);
  backdrop-filter: blur(10px);
}
/* Gradient frame via a masked pseudo-element, unlike the portfolio cards, which
   had to use stacked backgrounds because both their pseudo-elements were taken.
   Here ::before is free, and it is the better tool: the background trick needs
   an opaque layer to stop the gradient flooding the card, which would have cost
   this card its translucency and the backdrop blur behind it. */
.testimonial-card {
  position: relative;
}
.testimonial-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: var(--gradient-brand);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}
.quote-mark {
  width: 16px;
  height: 16px;
  color: var(--purple-soft);
  opacity: 0.65;
  margin-block-end: var(--space-2);
}
.testimonial-card blockquote {
  margin: 0 0 var(--space-2);
  color: var(--fg-muted);
  font-size: 0.82rem;
  line-height: 1.5;
  font-style: italic;
  /* Rows must stay a uniform height or the opposed drift looks broken, so the
     long testimonials are clamped rather than allowed to set the height.
     Back to three lines now that the card is wide enough to hold the longest
     quote in three – a fourth line cost height on all forty cards to rescue a
     single one. Only the Kujos quote still overflows, as before. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.testimonial-author {
  margin-block-start: auto;
}
.testimonial-author {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.author-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--gradient-brand);
  opacity: 0.4;
  flex-shrink: 0;
}
.author-avatar-photo {
  opacity: 1;
  background-size: cover;
  background-position: center;
  border: 1px solid var(--border-strong);
}
/* Initials for people we have no photo of – a ringed disc reads as a real
   person, where the faded empty circle read as a missing image. Colour is set
   below, per person; a `color: #fff` here was dead, since the per-person rule
   is more specific and always wins. */
.author-avatar-initials {
  opacity: 1;
  display: grid;
  place-items: center;
  font-family: var(--font-heading);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}
/* One colour per person, so a row of testimonials reads as a row of different
   people rather than one repeated tile. Assigned so that no two neighbours in
   either track share a hue.

   A ring and coloured letters on a dark disc, as in the reference, rather than
   white letters on a filled one. This also lifts the contrast rather than
   lowering it: white on a filled disc capped out around 3.2:1 for the greens
   and ambers, because the disc had to stay dark enough to carry white type. A
   bright letter on a near-black field is not bound by that, so every colour can
   now be the bright version of itself.

   Held as an unwrapped rgb triple so one custom property drives the ring, the
   tint and the type without three declarations per colour. */
.author-avatar-initials {
  background: rgba(var(--av), 0.13);
  border: 1.5px solid rgba(var(--av), 0.7);
}
/* Two classes, because the avatar is a <span> inside .testimonial-author and
   `.testimonial-author span { color: var(--fg-faint) }` is a class plus a type
   selector – it outranks a single class and was quietly winning, leaving nine
   coloured rings around nine identical grey monograms. */
.testimonial-author .author-avatar-initials {
  color: rgb(var(--av));
}
.author-avatar-initials.av-1 { --av: 96, 165, 250; }
.author-avatar-initials.av-2 { --av: 167, 139, 250; }
.author-avatar-initials.av-3 { --av: 52, 211, 153; }
.author-avatar-initials.av-4 { --av: 251, 191, 36; }
.author-avatar-initials.av-5 { --av: 251, 113, 133; }
.author-avatar-initials.av-6 { --av: 34, 211, 238; }
.author-avatar-initials.av-7 { --av: 74, 222, 128; }
.author-avatar-initials.av-8 { --av: 232, 121, 249; }
.author-avatar-initials.av-9 { --av: 251, 146, 60; }
.testimonial-author strong {
  display: block;
  font-size: 0.82rem;
  font-weight: 600;
}
.testimonial-author span {
  font-size: 0.72rem;
  color: var(--fg-faint);
}

/* ========== CTA band ========== */
.cta-band {
  position: relative;
  padding-block: var(--space-8);
  overflow: clip;
  isolation: isolate;
}
.cta-bg {
  position: absolute;
  inset: 0;
  z-index: -1;
}
.cta-bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Meant to be seen now. The previous image was a screenshot of code, held at
     5% behind a 10px blur because its lettering competed with the headline;
     this one is a photograph with no readable text in it, so it can carry the
     band instead of hiding under it. A light blur still keeps it as depth
     rather than as something to look at. */
  opacity: 0.72;
  filter: blur(1.5px) saturate(1.08) brightness(0.8) contrast(1.06);
  transform: scale(1.05);
}
.cta-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Darkest through the middle, where the heading and the button sit, and
     thinnest at the edges, where the room is worth seeing. A flat scrim strong
     enough to carry white type over the bright screen wall would have hidden
     the photograph everywhere else at the same time. */
  background:
    radial-gradient(72% 108% at 50% 46%, rgba(8, 8, 14, 0.88) 0%, rgba(8, 8, 14, 0.66) 44%, rgba(8, 8, 14, 0.3) 80%, rgba(8, 8, 14, 0.2) 100%),
    linear-gradient(135deg, rgba(71, 214, 232, 0.09), transparent 45%, rgba(168, 85, 247, 0.11));
}
/* The page-wide constellation runs behind this band too, and two textures plus a
   photo in one place was the third thing making it read as noise. */
.cta-band { isolation: isolate; }
.cta-band::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--bg-base);
  opacity: 0.8;
  pointer-events: none;
}
.cta-inner {
  text-align: center;
  max-width: 640px;
}
.cta-inner h2 {
  font-size: clamp(1.6rem, 4vw, 2.25rem);
  font-weight: 600;
  margin-block-end: var(--space-3);
}
.cta-inner p {
  color: var(--fg-muted);
  margin-block-end: var(--space-5);
}

/* ========== Contact ========== */
.contact-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-7);
}
.contact-info .section-lead { margin-block-end: var(--space-5); }
.contact-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-block: var(--space-3);
  font-size: 1.05rem;
  font-weight: 500;
  border-bottom: 1px solid var(--border);
  transition: color var(--dur-fast) var(--ease-out);
}
.contact-link:hover { color: var(--blue-soft); }
.contact-icon {
  display: inline-flex;
  width: 40px;
  height: 40px;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--blue-soft);
  flex-shrink: 0;
}
.contact-icon svg { width: 18px; height: 18px; }

.contact-form {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.form-honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}
.form-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.form-row label {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--fg-muted);
}
.form-row input,
.form-row textarea {
  background: var(--bg-base);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  color: var(--fg);
  font-family: inherit;
  font-size: 1rem;
  min-height: 44px;
  resize: vertical;
  transition: border-color var(--dur-fast) var(--ease-out);
}
.form-row input:focus,
.form-row textarea:focus {
  border-color: var(--blue-soft);
}
.form-submit { width: 100%; }
.form-note {
  min-height: 1.2em;
  font-size: 0.9rem;
  color: var(--blue-soft);
  margin: 0;
}

@media (min-width: 1024px) {
  .contact-grid { grid-template-columns: 1fr 1fr; gap: var(--space-8); }
}

/* ========== Footer ========== */
.site-footer {
  border-top: 1px solid var(--border);
  padding-block-start: var(--space-7);
  /* Translucent so the .brand-net carries through to the bottom of the
     page rather than stopping at the footer edge. */
  background: rgba(5, 5, 7, 0.82);
}
.footer-inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding-block-end: var(--space-6);
}
.footer-brand p {
  color: var(--fg-muted);
  margin-block-start: var(--space-2);
  font-size: 0.95rem;
}
.footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}
.footer-nav a, .footer-contact a {
  color: var(--fg-muted);
  font-size: 0.95rem;
  transition: color var(--dur-fast) var(--ease-out);
}
.footer-nav a:hover, .footer-contact a:hover { color: var(--fg); }
.footer-contact {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* W stopce same ikony: trzy wiersze z podpisami zrobilyby z niej spis tresci.
   Nazwa serwisu jest w aria-label, wiec czytnik ekranu ja przeczyta. */
.footer-profile {
  display: flex;
  gap: var(--space-2);
  margin-block-start: var(--space-2);
}
.footer-profile a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  color: var(--fg-muted);
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.footer-profile a svg { width: 17px; height: 17px; }
.footer-profile a:hover,
.footer-profile a:focus-visible {
  color: var(--fg);
  border-color: var(--purple-soft);
  background: rgba(255, 255, 255, 0.05);
}
.footer-bottom {
  border-top: 1px solid var(--border);
  padding-block: var(--space-4);
}
.footer-bottom p {
  color: var(--fg-faint);
  font-size: 0.85rem;
}

@media (min-width: 768px) {
  .footer-inner { flex-direction: row; justify-content: space-between; align-items: flex-start; }
}

/* ========== Scroll reveal ========== */
.reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}
.reveal.in-view {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; }
}

/* ========== Project preview modal ========== */
html.preview-open {
  overflow: hidden;
}
.preview-modal {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-base) var(--ease-out), visibility 0s linear var(--dur-base);
}
.preview-modal.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--dur-base) var(--ease-out);
}
.preview-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(5, 5, 7, 0.8);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.preview-panel {
  position: relative;
  width: min(1100px, 100%);
  height: min(820px, 90vh);
  background: var(--bg-elevated);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: 0 40px 100px -30px rgba(0, 0, 0, 0.6), 0 0 60px -20px var(--glow-purple);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transform: scale(0.96) translateY(12px);
  transition: transform var(--dur-slow) var(--ease-out);
}
.preview-modal.is-open .preview-panel {
  transform: scale(1) translateY(0);
}
.preview-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--bg-base);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.preview-title {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.preview-header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}
.preview-external,
.preview-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-sm);
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--fg-muted);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.preview-external svg,
.preview-close svg {
  width: 16px;
  height: 16px;
}
.preview-external:hover,
.preview-close:hover {
  background: var(--surface-strong);
  border-color: var(--border-strong);
  color: var(--fg);
}
.preview-frame-wrap {
  position: relative;
  flex: 1;
  background: #0a0a0a;
}
.preview-frame {
  width: 100%;
  height: 100%;
  border: none;
  display: block;
}

@media (max-width: 640px) {
  .preview-modal { padding: 0; }
  .preview-panel { width: 100%; height: 100%; border-radius: 0; }
}

/* Cookie notice banner */
.cookie-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 900;
  display: flex;
  background: var(--bg-elevated);
  border-top: 1px solid var(--border-strong);
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.35);
  padding: var(--space-4) 0;
  transform: translateY(0);
  transition: transform 0.4s var(--ease-out), opacity 0.4s var(--ease-out);
}
.cookie-banner.is-hiding {
  transform: translateY(100%);
  opacity: 0;
}
.cookie-banner[hidden] {
  display: none;
}
.cookie-banner-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
}
.cookie-banner-inner p {
  margin: 0;
  color: var(--fg-muted);
  font-size: 0.9rem;
  max-width: 640px;
}
.cookie-banner-inner a {
  color: var(--fg);
  text-decoration: underline;
}
.cookie-banner-actions {
  display: flex;
  gap: var(--space-3);
  flex-shrink: 0;
  flex-wrap: wrap;
}
/* Accept and reject are the same size and sit side by side: RODO requires
   refusing consent to be no harder than granting it. */
.cookie-banner-actions .btn {
  flex: 1 1 auto;
  justify-content: center;
  white-space: nowrap;
}

@media (max-width: 640px) {
  .cookie-banner-inner { flex-direction: column; align-items: stretch; text-align: center; }
  .cookie-banner-actions { justify-content: center; }
  .cookie-banner-actions .btn { flex: 1 1 140px; }
}

/* Long-form article pages (blog) */
.article-main { padding-block: calc(76px + var(--space-7)) var(--space-9); }
.article-content { max-width: 720px; }
.article-content h1 { margin-block-end: var(--space-3); }
.article-content h2 { font-family: var(--font-heading); font-size: 1.4rem; font-weight: 600; margin-block: var(--space-7) var(--space-3); }
.article-content h2:first-of-type { margin-block-start: var(--space-6); }
.article-content h3 { font-family: var(--font-heading); font-size: 1.1rem; font-weight: 600; margin-block: var(--space-5) var(--space-2); }
.article-content p { color: var(--fg-muted); margin-block-end: var(--space-3); line-height: 1.7; }
.article-content ul { color: var(--fg-muted); margin-block-end: var(--space-3); padding-inline-start: 1.2em; list-style: disc; }
.article-content li { margin-block-end: var(--space-2); line-height: 1.7; }
.article-content a { color: var(--blue-soft); text-decoration: underline; }
.article-meta { color: var(--fg-faint); font-size: 0.9rem; margin-block-end: var(--space-6); }
.article-cta { margin-block-start: var(--space-7); padding: var(--space-5); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); }
.article-cta p { margin-block-end: var(--space-4); color: var(--fg); }

/* ---------- Lista wpisow ----------------------------------------------------

   Karta byla tytulem i akapitem na plaskim prostokacie: nic nie mowilo, o czym
   jest wpis ani ile zajmie, i nic nie wskazywalo, ze to w ogole odnosnik.
   Teraz niesie dzial, czas czytania i strzalke, a stopka jest dosunieta do
   dolu, wiec karty roznej dlugosci koncza sie rowno w wierszu. */
.blog-main { padding-block: calc(76px + var(--space-7)) var(--space-9); }
.blog-naglowek { margin-block-end: var(--space-7); }
.blog-naglowek .section-lead { margin-block-end: 0; }

.blog-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}
.blog-card {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  text-decoration: none;
  transition: border-color var(--dur-base) var(--ease-out),
              translate var(--dur-base) var(--ease-out),
              background var(--dur-base) var(--ease-out);
}
/* Gorna krawedz zapala sie gradientem, tak samo jak w sekcji obszarow. */
.blog-card::before {
  content: "";
  position: absolute;
  inset-block-start: -1px;
  inset-inline: var(--space-5);
  height: 2px;
  background: var(--gradient-brand);
  border-radius: 2px;
  scale: 0 1;
  transform-origin: left;
  transition: scale var(--dur-slow) var(--ease-out);
}
.blog-card:hover,
.blog-card:focus-visible {
  border-color: var(--border-strong);
  background: rgba(255, 255, 255, 0.075);
  translate: 0 -3px;
}
.blog-card:hover::before,
.blog-card:focus-visible::before { scale: 1 1; }

.blog-card-dzial {
  align-self: start;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--blue-soft);
  margin-block-end: var(--space-3);
}
.blog-card h2,
.blog-card h3 {
  font-family: var(--font-heading);
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--fg);
  margin-block-end: var(--space-2);
  text-wrap: balance;
}
.blog-card p {
  color: var(--fg-muted);
  line-height: 1.65;
  margin: 0;
}
.blog-card-stopka {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  /* Dosuwa stopke do dolu niezaleznie od dlugosci opisu. */
  margin-block-start: auto;
  padding-block-start: var(--space-4);
}
.blog-card-czas {
  font-size: 0.78rem;
  color: var(--fg-faint);
}
.blog-card-strzalka {
  display: inline-flex;
  color: var(--fg-faint);
  translate: 0 0;
  transition: color var(--dur-base) var(--ease-out), translate var(--dur-base) var(--ease-out);
}
.blog-card-strzalka svg { width: 18px; height: 18px; }
.blog-card:hover .blog-card-strzalka,
.blog-card:focus-visible .blog-card-strzalka { color: var(--blue-soft); translate: 4px 0; }

@media (min-width: 700px) {
  .blog-grid { grid-template-columns: repeat(2, 1fr); }
  .blog-card-glowna { grid-column: 1 / -1; }
  .blog-card-glowna h2 { font-size: clamp(1.4rem, 2.4vw, 1.9rem); }
  .blog-card-glowna p { font-size: 1.02rem; max-width: 62ch; }
  .blog-card-glowna { padding: var(--space-6); }
}
@media (min-width: 1100px) {
  .blog-grid { grid-template-columns: repeat(4, 1fr); }
  .blog-card-glowna { grid-column: span 2; }
}

/* Hero: auto-rotating 3D browser window model.

   Karta i tresc hero sa wysrodkowane w pionie wzgledem tej samej sekcji,
   wiec ich wzajemne polozenie jest stale niezaleznie od wysokosci okna.
   Liczy sie tylko wysokosc samej tresci: kazdy kolejny pasek dokladany na
   dole hero podnosi ja o polowe swojej wysokosci i wsuwa pod karte. */
.browser-3d-wrap {
  position: absolute;
  /* Nie 50%: patrz komentarz wyzej, karta ma stac na wysokosci naglowka
     i akapitu, a nie calej kolumny razem z paskami pod nia. */
  /* Polowa przyrostu wysokosci wzgledem dawnych 353px: karta jest
     wysrodkowana, wiec tyle wlasnie zeszlaby w dol, gdyby jej nie podniesc.
     287px to polowa nowej bazy --h. */
  --gora: calc(140px + var(--skala) * 287px - 176.5px);
  /* Dolny ogranicznik: srodek karty nie wyzej niz naglowek plus jej polowa,
     zeby przy niskim oknie nie wjechala pod przyklejone menu. */
  top: max(calc(84px + var(--skala) * 287px), calc(50% - var(--gora)));
  /* Past a certain viewport width 13% drifts further right than the hero
     column's own edge and the card gets stranded in empty margin. Kapowane
     krawedzia kolumny, zeby trzymalo sie jej niezaleznie od szerokosci ekranu
     i zeby rozszerzenie kolumny nie wymagalo poprawiania tej liczby. */
  right: max(4%, calc(50% - var(--hero-max) / 2));
  translate: 0 -50%;
  z-index: 3;
  perspective: 1600px;
  pointer-events: none;
  /* Dwa mnozniki w jednej skali: --skala to docelowa wielkosc karty na danej
     szerokosci ekranu, --hero-shrink to zanikanie przy przewijaniu hero.
     Punkt zaczepienia przy prawej krawedzi, wiec karta rosnie w lewo. */
  transform-origin: right center;
  transform: scale(calc(var(--hero-shrink, 1) * var(--skala, 1)));
  opacity: var(--hero-shrink, 1);
  transition: opacity 0.1s linear;
}
/* Wartosci to dawne mnozniki podzielone przez 1.625 (780/480), wiec karta ma
   dokladnie te sama wielkosc na ekranie, co przed zmiana. */
.browser-3d-wrap { --skala: 0.52; }
@media (min-width: 1280px) and (min-height: 800px) { .browser-3d-wrap { --skala: 0.56; } }
@media (min-width: 1440px) and (min-height: 860px) { .browser-3d-wrap { --skala: 0.62; } }
@media (min-width: 1700px) and (min-height: 940px) { .browser-3d-wrap { --skala: 0.68; } }
@media (min-width: 1920px) and (min-height: 1040px) { .browser-3d-wrap { --skala: 0.73; } }
@media (min-width: 2300px) and (min-height: 1200px) { .browser-3d-wrap { --skala: 0.84; } }
.browser-3d {
  /* Rozmiar bazowy jest najwiekszy z potrzebnych, a --skala tylko zmniejsza.
     Odwrotnie niz wczesniej i to jest cala roznica w ostrosci: warstwa jest
     rasteryzowana w tym rozmiarze, wiec przy powiekszaniu bylaby rozciagana,
     a przy zmniejszaniu jest usredniana. */
  --w: 780px;
  --h: 574px;
  --d: 18px;
  --r: 20px;
  --fw: 0px;
  --fd: 10px;
  width: var(--w);
  height: var(--h);
  position: relative;
  transform-style: preserve-3d;
  animation: browser-3d-spin 9s ease-in-out infinite, browser-3d-float 6s ease-in-out infinite;
}
@keyframes browser-3d-spin {
  0%, 100% { rotate: y -18deg; }
  50% { rotate: y 18deg; }
}
@keyframes browser-3d-float {
  0%, 100% { translate: 0 0; }
  50% { translate: 0 -16px; }
}
.browser-3d-face {
  position: absolute;
  overflow: hidden;
  backface-visibility: hidden;
  box-shadow: 0 0 0 1px var(--border-strong);
}
.browser-3d-frame-front,
.browser-3d-frame-back {
  position: absolute;
  inset: calc(-1 * var(--fw));
  border-radius: calc(var(--r) + var(--fw));
  background: linear-gradient(135deg, #5B82FF 0%, var(--blue) 40%, var(--purple) 100%);
}
.browser-3d-frame-front {
  transform: translateZ(calc(-1 * var(--d) / 2 + var(--fd) / 2));
  box-shadow: 0 20px 50px -18px rgba(0, 0, 0, 0.7);
}
.browser-3d-frame-back {
  transform: translateZ(calc(-1 * var(--d) / 2 - var(--fd) / 2));
  filter: brightness(0.7);
}
.browser-3d-front {
  inset: 0;
  border-radius: var(--r);
  box-shadow: 0 30px 70px -20px rgba(0, 0, 0, 0.65);
  background: var(--bg-elevated);
  display: flex;
  flex-direction: column;
  transform: translateZ(calc(var(--d) / 2));
}
.browser-3d-topbar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.browser-3d-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }
.browser-3d-dot-red { background: #FF5F57; }
.browser-3d-dot-yellow { background: #FEBC2E; }
.browser-3d-dot-green { background: #28C840; }
.browser-3d-url {
  margin-inline-start: var(--space-3);
  padding: 4px var(--space-4);
  background: var(--surface-strong);
  border-radius: var(--radius-pill);
  font-size: 0.75rem;
  color: var(--fg-faint);
}
.browser-3d-body { padding: var(--space-5); flex: 1; }
.browser-3d-body-escale { position: relative; padding: 0; }

/* Idle showcase, stage 1: "escale." wordmark collapsing down to the "e." mark */
.browser-3d-intro {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 1;
  transform: scale(1);
  transition: opacity 0.5s var(--ease-out), transform 0.5s var(--ease-out);
}
.browser-3d-intro.is-hidden {
  opacity: 0;
  transform: scale(0.85);
  pointer-events: none;
}
.browser-3d-intro-word {
  display: flex;
  align-items: baseline;
  font-family: var(--font-heading);
  font-size: 1.7rem;
  font-weight: 700;
  color: #fff;
}
.browser-3d-intro-e { flex-shrink: 0; }
.browser-3d-intro-rest {
  display: inline-block;
  overflow: hidden;
  max-width: 90px;
  white-space: nowrap;
  opacity: 1;
  transition: max-width 0.7s var(--ease-out), opacity 0.5s var(--ease-out);
}
.browser-3d-intro-rest.is-collapsed {
  max-width: 0;
  opacity: 0;
}
.browser-3d-intro-dot {
  background: var(--gradient-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  flex-shrink: 0;
}

/* Front face: crossfading views (escale.pl <-> idle Google-search demo) */
.browser-3d-view {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  opacity: 0;
  transition: opacity 0.7s var(--ease-out);
}
.browser-3d-view.is-active {
  opacity: 1;
}
.browser-3d-google {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  padding: var(--space-5);
}
.browser-3d-google-logo {
  font-family: var(--font-heading);
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--fg);
}
.browser-3d-google-bar {
  width: 100%;
  min-height: 32px;
  padding: 0 var(--space-4);
  display: flex;
  align-items: center;
  background: var(--surface-strong);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  font-size: 0.8rem;
  color: var(--fg);
}
.browser-3d-google-caret {
  display: inline-block;
  width: 2px;
  height: 13px;
  background: var(--fg);
  margin-inline-start: 2px;
  animation: browser-3d-caret-blink 0.9s step-end infinite;
}
@keyframes browser-3d-caret-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0; }
}
.browser-3d-google-result {
  width: 100%;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--surface);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.3s var(--ease-out), transform 0.3s var(--ease-out);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.browser-3d-google-result.is-shown {
  opacity: 1;
  transform: translateY(0);
}
.browser-3d-google-result-title {
  color: var(--blue-soft);
  font-size: 0.8rem;
  font-weight: 600;
}
.browser-3d-google-result-desc {
  color: var(--fg-faint);
  font-size: 0.7rem;
}

/* Third view: real screenshots – escale.pl, its realizacje page, then each
   client site in turn.

   `contain`, not `cover`: the point is to read the layout of each page, and
   cover would crop whichever edge does not fit. The captures are taken at the
   frame's own 16:10 so containing them costs only a couple of pixels of
   letterbox, and nothing is cut off. */
.browser-3d-work {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: hidden;
  background: var(--bg-deep);
}
.browser-3d-work-shot {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.55s var(--ease-out);
}
.browser-3d-work-shot.is-shown {
  opacity: 1;
}
/* Below 1280px `.hero .container` drops back to a centred 720px column,
   which puts the text straight under the card. The card goes away at exactly
   the same threshold. */
@media (max-width: 1279px) {
  .browser-3d-wrap { display: none; }
}

/* ========== O nas ==========
   The wordmark assembles itself: letters rise into place one after another,
   the dot lands last with a small overshoot, then settles into a slow breath.
   The order matters – "escale" is read left to right and the dot is the part
   of the mark people remember, so it gets the arrival beat to itself. */
.about-hero {
  text-align: center;
  padding-block: var(--space-7) var(--space-8);
}
.brand-anim {
  display: inline-block;
  position: relative;
}
.brand-anim-word {
  display: inline-flex;
  align-items: baseline;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: clamp(3.25rem, 13vw, 8rem);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--fg);
}
.brand-anim-letter {
  display: inline-block;
  opacity: 0;
  translate: 0 0.28em;
  /* Two animations, and the order is the contract: the entry owns `opacity` and
     `translate` through its forwards fill, and the wave – listed second, and
     starting after the entry has finished – only ever touches colour.
     The entry stagger is deliberately tight. At 90ms across six letters the word
     took 1.3s to assemble and read as a page struggling to load rather than as
     one motion; 40ms puts the whole assembly inside half a second.
     --i is set per letter in the markup, so the stagger survives any change to
     the word without touching a stylesheet. */
  animation:
    brand-letter-in 0.5s var(--ease-out) calc(var(--i) * 40ms) forwards,
    brand-letter-wave 4.4s ease-in-out calc(1.2s + var(--i) * 105ms) infinite;
}
@keyframes brand-letter-in {
  to { opacity: 1; translate: 0 0; }
}
/* A light travelling through the word, and nothing else. The previous version
   also lifted each letter 0.055em as the light reached it, which at this type
   size is a visible hop – six letters hopping 105ms apart do not read as a wave
   passing through a word, they read as the word stuttering. Brightness alone
   carries the movement, and because each letter's ramp is far longer than the
   gap between letters, three of them are always part-lit at once: a gradient
   sliding across the mark rather than six things taking turns.

   Colour, not a gradient swept with background-clip:text – that is a text-clipped
   paint under a mix-blend-mode layer, a combination some compositors resolve by
   filling the element's whole box, which showed up as a solid slab behind the
   wordmark. */
@keyframes brand-letter-wave {
  0%, 46%, 100% {
    color: var(--fg);
    text-shadow: 0 0 0 rgba(140, 120, 255, 0);
  }
  20% {
    color: #ffffff;
    text-shadow: 0 0 28px rgba(146, 128, 255, 0.45);
  }
}
.brand-anim-dot {
  display: inline-block;
  color: var(--purple);
  opacity: 0;
  scale: 0.2;
  animation:
    brand-dot-in 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) 620ms forwards,
    brand-dot-glow 3.4s ease-in-out 1.3s infinite;
}
@keyframes brand-dot-in {
  to { opacity: 1; scale: 1; }
}
/* Colour rather than filter: drop-shadow, which forced the glyph into its own
   raster on every frame of an infinite loop. */
@keyframes brand-dot-glow {
  0%, 100% {
    color: var(--purple);
    scale: 1;
    text-shadow: 0 0 12px var(--glow-purple);
  }
  50% {
    color: var(--blue);
    scale: 1.1;
    text-shadow: 0 0 30px var(--glow-blue);
  }
}
.about-tagline {
  margin-block-start: var(--space-4);
  color: var(--fg-muted);
  font-size: 1.05rem;
}

.about-lead { max-width: 68ch; }
.about-lead .section-lead { margin-block-end: 0; }

.about-focus,
.about-process { margin-block-start: var(--space-7); }
/* Sufit przenosi sie z sekcji na sam akapit: sekcja ma zajmowac cala
   kolumne, a dlugosc wiersza nadal ma byc do czytania. */
.about-focus p { max-width: 68ch; }
.about-focus h2,
.about-process h2 {
  font-family: var(--font-heading);
  font-size: 1.4rem;
  font-weight: 600;
  margin-block-end: var(--space-4);
}
.about-focus p {
  color: var(--fg-muted);
  line-height: 1.7;
  margin-block-end: var(--space-3);
}
.about-focus .btn { margin-block-start: var(--space-3); }

.about-steps {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  border-top: 1px solid var(--border);
  counter-reset: about-step;
}
.about-step {
  counter-increment: about-step;
  border-bottom: 1px solid var(--border);
  padding-block: var(--space-4);
}
/* Numbered here, unlike the services columns, because this genuinely is an
   order: nothing gets built before the first conversation. */
.about-step h3::before {
  content: counter(about-step, decimal-leading-zero);
  display: inline-block;
  min-width: 2.4em;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--blue-soft);
  vertical-align: middle;
}
.about-step h3 {
  font-size: 1.05rem;
  margin-block-end: var(--space-2);
}
.about-step p {
  color: var(--fg-muted);
  font-size: 0.92rem;
  line-height: 1.65;
  padding-inline-start: 2.4em;
}
@media (min-width: 768px) {
  .about-steps { grid-template-columns: repeat(2, 1fr); column-gap: var(--space-6); }
}

@media (prefers-reduced-motion: reduce) {
  .brand-anim-letter,
  .brand-anim-dot {
    animation: none;
    opacity: 1;
    translate: none;
    scale: 1;
    text-shadow: none;
  }
}

/* ========== Page-wide brand constellation ==========
   Drifting "e." marks joined by lines to whichever neighbours are currently
   close enough. Everything about it lives in script.js – the element is only a
   fixed, non-interactive surface. position:fixed also bounds the work to the
   viewport rather than the full document height.

   Replaced two tiled background layers plus a cursor-driven mask. That version
   could only ever be a grid at fixed intervals with nothing between the marks;
   the links here are the whole point and a repeating image cannot express them. */
.brand-net {
  position: fixed;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;

  /* Held back down the middle, where every line of text on the site lives.
     The field is drawn dense and bright so it carries at the edges of a wide
     screen; at full strength across the whole width, lit links cross the
     headings and body copy and the page becomes tiring to read.

     A mask rather than a scrim over the top: a scrim would also dim the section
     backgrounds and the photographs sitting above this layer, whereas the mask
     only ever thins the field itself.

     Pinned to .container's half-width in px, not to a percentage of the
     viewport. A percentage band is only ever correct at one width: at 27%-73%
     it protected a 1920px screen but on a 1280px one the 1200px text column
     spilled straight past it into the bright margins – measured as more
     constellation over the words than over the page as a whole. In px the quiet
     zone tracks the text wherever the text actually is, and on a wide monitor
     the untouched margins simply get wider, which is the intent.

     Stops may fall outside 0-100%; on a narrow viewport the leading pair go
     negative and the whole width is quiet, which is right, because there the
     text does span the whole width. */
  --net-quiet: calc(var(--container-max) / 2 + 20px);
  --net-mask: linear-gradient(90deg,
    #000 0,
    #000 calc(50% - var(--net-quiet) - 190px),
    rgba(0, 0, 0, 0.28) calc(50% - var(--net-quiet)),
    rgba(0, 0, 0, 0.28) calc(50% + var(--net-quiet)),
    #000 calc(50% + var(--net-quiet) + 190px),
    #000 100%);
  -webkit-mask-image: var(--net-mask);
  mask-image: var(--net-mask);
}

#main, .site-footer {
  position: relative;
  z-index: 1;
}

/* ---------- Litery wychodzace z komputera ------------------------------------

   Wypelnia miejsce pod wstepem na stronie "o nas". Wysokosc wiersza wyznacza
   tam wizytowka w prawej kolumnie, a wstep jest od niej krotszy, wiec pod nim
   zostawalo kilkaset pikseli pustego tla.

   Petla: litery wyskakuja z ekranu jedna po drugiej, kropka doskakuje na
   koncu, calosc chwile stoi i chowa sie z powrotem. Bez skryptu.

   Animowane sa tylko translate i opacity, czyli rzeczy, ktore przegladarka
   robi na karcie graficznej. Zadna klatka nie zmienia ukladu strony, wiec
   petla nie kosztuje przeliczania niczego dookola. */
.literki {
  position: relative;
  display: grid;
  justify-items: center;
  align-content: start;
  gap: var(--space-5);
  padding-block: var(--space-5);
}
@media (min-width: 1100px) {
  /* Przyklejona jest wizytowka, bo stoi teraz na dole kolumny. Element
     przyklejony, pod ktorym jest jeszcze tresc, zostaje w miejscu, a ta tresc
     wjezdza na niego przy przewijaniu. */
  .about-bok > .about-wizytowka {
    position: sticky;
    inset-block-start: calc(76px + var(--space-5));
  }
}
/* Od 1800px slowo przenosi sie na lewo od monitora, w pas, ktory i tak zostaje
   pusty, bo akapity w sasiedniej kolumnie maja sufit 68ch. Ustawienie jest
   bezwzgledne, wiec nie wplywa na uklad i nic nie moze sie przez nie rozjechac.

   Prog wziety z pomiaru, nie z oka: przy 1400px slowo wchodzilo na tekst o
   61px, przy 1600px o 9px, a od 1800px zostaje ponad 100px odstepu. Ponizej
   slowo stoi nad monitorem. */
@media (min-width: 1800px) {
  .literki { justify-items: end; }
  /* Z rodzicem w selektorze: ta regula stoi w pliku przed podstawowa
     .literki-slowo, a przy rownej wadze wygrywalaby tamta. */
  .literki .literki-slowo {
    position: absolute;
    inset-inline-end: calc(100% - var(--space-4));
    inset-block-start: 50%;
    translate: 0 -50%;
    white-space: nowrap;
    font-size: clamp(2.8rem, 4.2vw, 4.6rem);
  }
}
/* ---------- Komputer w trzech wymiarach ---------------------------------------

   Z elementow strony, a nie z pliku SVG: SVG jest plaski, a tutaj ma byc widac
   grubosc obudowy przy obrocie. Tylna plyta jest odsunieta w glab, wiec przy
   skrecie wychodzi zza ekranu jak bok pudelka.

   Obrot jest lagodny, 16 stopni. Przy wiekszym kacie krawedzie o grubosci
   jednego piksela schodza na ekranie ponizej piksela i zaczynaja migotac; to
   samo wyszlo wczesniej w karuzeli realizacji. Z tego samego powodu obramowanie
   ma 2px. */
.monitor3d-scena {
  perspective: 1000px;
  perspective-origin: 30% 45%;
  width: min(330px, 96%);
}
/* Kat jest staly, nie wahadlowy. Wahadlo zmienia go przy kazdej klatce, wiec
   przegladarka rysuje warstwe od nowa i cienkie krawedzie zaczynaja migotac
   (to samo wyszlo w karuzeli realizacji). Przy stalym kacie rysuje ja raz,
   wiec mozna bylo zejsc az do 26 stopni, gdzie bok obudowy jest widoczny.
   W petli zostaje samo unoszenie, ktore kata nie rusza. */
.monitor3d {
  position: relative;
  transform-style: preserve-3d;
  rotate: y -26deg;
  animation: monitor3d-unosi 6.5s ease-in-out infinite;
}
@keyframes monitor3d-unosi {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -12px; }
}
.monitor3d-ekran {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: 12px;
  border: 2px solid transparent;
  padding: 18px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background:
    linear-gradient(160deg, #1b1b2a, #14141f) padding-box,
    var(--gradient-brand) border-box;
  box-shadow: 0 22px 50px -18px rgba(0, 0, 0, 0.75);
  transform: translateZ(9px);
}
/* Plyta z tylu daje obudowie grubosc: przy obrocie widac ja zza ekranu. */
.monitor3d-tyl {
  position: absolute;
  inset: 0;
  border-radius: 12px;
  background: #0e0e16;
  border: 2px solid rgba(255, 255, 255, 0.10);
  transform: translateZ(-9px);
}
.monitor3d-logo {
  /* Uklad elastyczny pomija odstepy miedzy elementami, a kazda litera stoi w
     zrodle w osobnym wierszu. Bez tego miedzy nimi widac spacje. */
  display: flex;
  align-items: baseline;
  justify-content: center;
  font-family: var(--font-heading);
  font-size: 1.62rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--fg);
  line-height: 1;
}
/* Zdanie na tle w gradiencie marki, jak przycisk w hero. Ciemny tekst na
   gradiencie to ta sama para kolorow co na przyciskach, czyli sprawdzona. */
.monitor3d-haslo {
  margin: 14px 0 0;
  padding: 10px 16px;
  border-radius: 14px;
  background: var(--gradient-brand);
  color: var(--on-gradient);
  text-align: center;
}
.monitor3d-haslo strong {
  display: block;
  font-family: var(--font-heading);
  font-size: 1.02rem;
  font-weight: 700;
  line-height: 1.25;
  text-wrap: balance;
}
.monitor3d-haslo span {
  display: block;
  margin-block-start: 3px;
  font-size: 0.76rem;
  line-height: 1.4;
  color: var(--on-gradient);
  opacity: 0.86;
}
.monitor3d-logo-kropka { color: var(--purple-soft); }
/* Litera na ekranie znika dokladnie wtedy, gdy jej odpowiednik pojawia sie na
   zewnatrz, i wraca razem z nim. Ta sama dlugosc cyklu i to samo opoznienie
   liczone z tego samego numeru litery, wiec nie da sie ich rozjechac inaczej
   niz zmieniajac obie liczby naraz. */
.monitor3d-logo span {
  display: inline-block;
  animation: literka-ekran 7s var(--ease-out) infinite;
  animation-delay: calc(var(--i, 0) * 0.13s);
}
@keyframes literka-ekran {
  0%   { opacity: 1; translate: 0 0; scale: 1; }
  4%   { opacity: 1; translate: 0 0; scale: 1; }
  12%  { opacity: 0; translate: -14px 0; scale: 0.6; }
  64%  { opacity: 0; translate: -14px 0; scale: 0.6; }
  74%  { opacity: 1; translate: 0 0; scale: 1; }
  100% { opacity: 1; translate: 0 0; scale: 1; }
}
.monitor3d-noga {
  width: 19%;
  height: 24px;
  margin-inline: auto;
  /* Jasniej niz tlo strony: przy dawnych #101018 stojak zlewal sie z tlem i
     wygladal jak cien pod ekranem. */
  background: linear-gradient(180deg, #33334a, #1e1e2c);
  clip-path: polygon(22% 0, 78% 0, 100% 100%, 0 100%);
  transform: translateZ(4px);
}
.monitor3d-podstawa {
  width: 46%;
  height: 9px;
  margin-inline: auto;
  border-radius: 5px;
  background: linear-gradient(180deg, #3b3b52, #1a1a26);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 18px 34px rgba(76, 124, 255, 0.24);
  transform: translateZ(4px);
}
.literki-slowo {
  display: flex;
  align-items: baseline;
  justify-content: center;
  margin: 0;
  font-family: var(--font-heading);
  font-size: clamp(2.4rem, 5vw, 3.4rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
}
.literki-slowo span {
  display: inline-block;
  opacity: 0;
  color: var(--fg);
  /* Droga litery: im dalej na lewo lezy jej miejsce w slowie, tym dalej ma do
     przebycia, zeby wszystkie wypadaly mniej wiecej z ekranu, a nie z siedmiu
     roznych punktow. 6 to numer ostatniego znaku, czyli kropki. */
  --droga: calc((6 - var(--i, 0)) * 0.62em + 180px);
  translate: var(--droga) 0;
  /* Kazda litera ma ten sam przebieg, tylko pozniej startuje. */
  animation: literka 7s var(--ease-out) infinite;
  animation-delay: calc(var(--i, 0) * 0.13s);
}
/* Z rodzicem w selektorze: samo .literki-kropka przegrywalo z
   .literki-slowo span, bo tamto ma klase i typ, a to tylko klase. */
.literki-slowo .literki-kropka { color: var(--purple-soft); }

@keyframes literka {
  0%   { opacity: 0; translate: var(--droga) 0; scale: 0.45; }
  6%   { opacity: 1; }
  15%  { translate: 0 0; scale: 1; }
  60%  { opacity: 1; translate: 0 0; scale: 1; }
  70%  { opacity: 0; translate: var(--droga) 0; scale: 0.45; }
  100% { opacity: 0; translate: var(--droga) 0; scale: 0.45; }
}

/* Globalna regula dla prefers-reduced-motion skraca kazda animacje do zera, co
   zatrzymaloby litery na pierwszej klatce, czyli niewidoczne. Tutaj zamiast
   tego po prostu stoja gotowe. */
@media (prefers-reduced-motion: reduce) {
  .monitor3d { animation: none !important; }
  .literki-slowo span,
  .monitor3d-logo span {
    animation: none !important;
    opacity: 1;
    translate: 0 0;
    scale: 1;
  }
}

/* Na waskim ekranie kolumny stoja jedna pod druga i pustki nie ma, wiec nie ma
   czego wypelniac. */
@media (max-width: 1099px) {
  .literki { display: none; }
}

/* Odnosniki do profilow w wizytowce. Kazdy ma podpis, a nie tylko ikone:
   przy trzech serwisach sama ikona wymaga od czytajacego rozpoznania znaku,
   a przy jednym z nich (mapy) zadnego powszechnie znanego znaku nie ma. */
.about-profile {
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-4);
  border-top: 1px solid var(--border);
}
.about-profile ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }
.about-profile a {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
  margin-inline: calc(var(--space-2) * -1);
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: background var(--dur-base) var(--ease-out);
}
.about-profile a:hover,
.about-profile a:focus-visible { background: rgba(255, 255, 255, 0.05); }
.about-profil-ikona {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  color: var(--fg-muted);
  transition: color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}
.about-profil-ikona svg { width: 17px; height: 17px; }
.about-profile a:hover .about-profil-ikona,
.about-profile a:focus-visible .about-profil-ikona {
  color: var(--blue-soft);
  border-color: var(--purple-soft);
}
.about-profil-tekst { display: block; min-width: 0; }
.about-profil-tekst strong {
  display: block;
  color: var(--fg);
  font-size: 0.92rem;
  font-weight: 600;
}
.about-profil-tekst span {
  display: block;
  color: var(--fg-faint);
  font-size: 0.8rem;
}

/* ---------- Sterowanie nie jest tekstem do zaznaczania -----------------------

   Przeciagniecie myszka po pigulce wyboru zaznaczalo jej etykiete zamiast
   wybrac opcje, a podwojne klikniecie podswietlalo slowo. Przegladarka
   traktuje wtedy gest jako zaznaczanie, wiec klikniecie bywa gubione.

   Lista jest wyliczona, a nie zalatwiona gwiazdka: akapity, odpowiedzi w FAQ
   i odnosniki do podstron maja pozostac zaznaczalne, bo tam kopiowanie tekstu
   jest normalnym uzyciem. */
.config-chip,
.config-option,
.config-option-box,
.rez-dzien,
.rez-godzina,
.rez-nav,
.rez-wybor-btn,
.rez-opcja,
.faq-pozycja summary,
.nav-dropdown-toggle,
.nav-toggle,
.btn,
.portfolio-badge,
.portfolio-visit {
  -webkit-user-select: none;
  user-select: none;
}

/* ---------- O nas: wstep i wizytowka ----------------------------------------

   Wstep sam w kolumnie zostawial pol ekranu pustego tla, a sekcje ponizej maja
   juz dwie kolumny, wiec to jedno miejsce wygladalo na niedokonczone. Obok
   staje wizytowka z rzeczami, ktore ktos sprawdza, zanim zadzwoni. */
/* Jeden uklad na cala tresc strony, a nie osobny dla wstepu. Wczesniej
   wizytowka siedziala tylko przy wstepie, a ponizej niej prawa polowa ekranu
   byla pusta az do stopki: sekcje mialy sufit 68ch, wiec caly tekst trzymal
   sie lewej strony. */
.about-uklad { display: grid; grid-template-columns: 1fr; gap: var(--space-6); }
@media (max-width: 1099px) {
  .about-uklad { display: flex; flex-direction: column; }
  .about-glowna,
  .about-bok { display: contents; }
  .about-lead { order: 1; }
  .about-wizytowka { order: 2; }
  .about-focus { order: 3; }
  .about-process { order: 4; }
}
@media (min-width: 1100px) {
  .about-uklad {
    grid-template-columns: minmax(0, 1.32fr) minmax(0, 0.68fr);
    gap: var(--space-7);
    align-items: start;
  }
  .about-bok {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    /* Bok ma siegac dolu lewej kolumny, zeby przyklejony komputer mial po
       czym jechac. Bez tego sticky nie ma zapasu i element stoi w miejscu. */
    height: 100%;
  }
}
.about-wizytowka {
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--surface);
}
.about-wizytowka-tytul {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--blue-soft);
  margin-block-end: var(--space-4);
}
.about-fakty { margin: 0; display: grid; gap: var(--space-3); }
.about-fakt {
  padding-block-end: var(--space-3);
  border-bottom: 1px solid var(--border);
}
.about-fakt:last-child { border-bottom: none; padding-block-end: 0; }
.about-fakt dt {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-block-end: 4px;
}
.about-fakt dd {
  margin: 0;
  color: var(--fg);
  font-size: 0.92rem;
  line-height: 1.6;
}
.about-wizytowka-stopka {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-4);
  border-top: 1px solid var(--border);
}
.about-wizytowka-stopka a {
  color: var(--blue-soft);
  font-size: 0.92rem;
  font-weight: 600;
}
.about-wizytowka-stopka a:hover { color: var(--fg); }

/* ---------- Podstrony na szerokim ekranie ------------------------------------

   Tresc podstron to jedna kolumna blokow, a kazdy blok ma wlasny sufit w
   znakach, zeby dalo sie go czytac. Skutek byl taki, ze od 1400px wszystko
   siedzialo przy lewej krawedzi, a prawa polowa ekranu byla pusta. Od 1100px
   naglowek sekcji idzie do lewej kolumny, tresc do prawej: oko biegnie po
   naglowkach, a szczegoly sa obok. Nizej uklad wraca do jednej kolumny, bo
   tam dwie po prostu sie nie mieszcza. */
@media (min-width: 1100px) {
  .usluga-szczyt {
    display: grid;
    grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
    gap: var(--space-7);
    align-items: center;
    margin-block-end: var(--space-8);
  }
  .usluga-szczyt .usluga-hero,
  .usluga-szczyt .usluga-figura { margin-block-end: 0; max-width: none; }

  .usluga-sekcja {
    display: grid;
    grid-template-columns: minmax(0, 0.4fr) minmax(0, 1.6fr);
    column-gap: var(--space-6);
    align-items: start;
  }
  .usluga-sekcja > h2 {
    grid-column: 1;
    grid-row: 1;
    /* Odstep od gory to wysokosc przyklejonego naglowka strony. */
    position: sticky;
    inset-block-start: calc(76px + var(--space-4));
    margin-block-end: 0;
  }
  .usluga-sekcja > :not(h2) { grid-column: 2; }
  /* Odstepy miedzy kolejnymi blokami w prawej kolumnie, bo grid nie dziedziczy
     marginesow z ukladu jednokolumnowego. */
  .usluga-sekcja > :not(h2) + :not(h2) { margin-block-start: var(--space-4); }

  .usluga-akapit { max-width: 78ch; }
  .usluga-faq { max-width: 82ch; }
  .about-lead { max-width: 80ch; }
}

/* ---------- Malowane "e" -----------------------------------------------------

   Stoi obok naglowka sekcji uslug, w pasie, ktory i tak zostaje pusty, bo
   akapit obok ma sufit 60ch.

   Petla: pedzel maluje litere jednym pociagnieciem, dolatuje kropka, po chwili
   odlatuje, a pedzel wraca po swoim sladzie i litera znika.

   Calosc to jedna sciezka z przerywaniem. pathLength="100" sprawia, ze dlugosc
   liczy sie w procentach niezaleznie od geometrii: kreska 100 i przerwa 100, a
   przesuniecie od 100 do 0 odslania slad od poczatku, zas z powrotem do 100
   chowa go od konca, czyli pedzel cofa sie po swoim sladzie.

   Poswiata to druga, grubsza kopia tej samej sciezki pod spodem, a nie filtr
   CSS. Filtr zmusza przegladarke do zamiany wektora na mape pikseli i to ona
   bywa widoczna jako postrzepiona krawedz. Tutaj nic sie nie rasteryzuje. */
.services-header-tekst { min-width: 0; }
@media (min-width: 1100px) {
  .services-header {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-6);
  }
}
.pedzel {
  display: none;
  width: 230px;
  height: auto;
  overflow: visible;
  justify-self: end;
}
@media (min-width: 1100px) { .pedzel { display: block; } }

.pedzel-halo,
.pedzel-slad,
.pedzel-czubek {
  fill: none;
  stroke: url(#pedzelGrad);
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 100 100;
  stroke-dashoffset: 100;
  animation: pedzel-slad 9s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
.pedzel-slad { stroke-width: 15; }
/* Poswiata: ta sama kreska, grubsza, puszczona przez rozmycie w pliku SVG.
   Bez rozmycia byla po prostu szersza kreska o ostrych krawedziach i czytalo
   sie to jako druga obwodka litery, a nie jako swiatlo. */
.pedzel-halo { stroke-width: 20; opacity: 0.5; }
/* Czubek: ta sama sciezka, ale widac z niej tylko krotki odcinek. Jedzie z tym
   samym przesunieciem, wiec zawsze siedzi na czole malowania. */
.pedzel-czubek {
  stroke-width: 21;
  stroke-dasharray: 3 97;
  opacity: 0;
  animation: pedzel-slad 9s cubic-bezier(0.65, 0, 0.35, 1) infinite,
             pedzel-czubek 9s linear infinite;
}
/* Poswiata kropki: ta sama animacja, tylko rozmyta i szersza. */
.pedzel-kropka-halo { opacity: 0.6 !important; }
.pedzel-kropka {
  fill: #A855F7;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  animation: pedzel-kropka 9s cubic-bezier(0.34, 1.4, 0.5, 1) infinite;
}

@keyframes pedzel-slad {
  0%, 2%    { stroke-dashoffset: 100; }
  30%, 68%  { stroke-dashoffset: 0; }
  96%, 100% { stroke-dashoffset: 100; }
}
@keyframes pedzel-czubek {
  0%, 3%    { opacity: 0; }
  6%, 27%   { opacity: 1; }
  31%, 69%  { opacity: 0; }
  72%, 93%  { opacity: 1; }
  96%, 100% { opacity: 0; }
}
@keyframes pedzel-kropka {
  0%, 31%   { opacity: 0; translate: 52px -38px; scale: 0.3; }
  40%, 60%  { opacity: 1; translate: 0 0; scale: 1; }
  69%, 100% { opacity: 0; translate: 52px -38px; scale: 0.3; }
}

/* Globalna regula dla mniej ruchu skraca kazda animacje do zera, co zatrzymaloby
   to na pierwszej klatce, czyli na pustym polu. Tutaj litera i kropka po prostu
   stoja gotowe. */
@media (prefers-reduced-motion: reduce) {
  .pedzel-halo,
  .pedzel-slad,
  .pedzel-czubek,
  .pedzel-kropka { animation: none !important; }
  .pedzel-halo,
  .pedzel-slad { stroke-dashoffset: 0; }
  .pedzel-czubek { opacity: 0; }
  .pedzel-kropka { opacity: 1; translate: 0 0; scale: 1; }
}


/* ---------- Strona skladajaca sie sama -------------------------------------

   Scena jest zamodelowana i wyrenderowana w Blenderze, a tutaj wstawiona jako
   film z kanalem alpha. Karta klienta sklada sie element po elemencie, chwile
   stoi gotowa i znika, po czym cykl zaczyna sie od nowa.

   Film wazy okolo ćwierć megabajta, wiec nie wczytuje sie razem ze strona:
   skrypt podstawia
   zrodlo dopiero, gdy sekcja wchodzi w kadr, i zatrzymuje odtwarzanie po
   wyjsciu z kadru. Przy wlaczonej redukcji ruchu zostaje sam plakat.

   Pod kanal alpha jest podlozony kolor tla strony. Przegladarka, ktora nie
   zlozy przezroczystosci VP9, pokaze wtedy to samo tlo zamiast czarnej plamy. */
.scena-strony {
  display: none;
  width: min(580px, 34vw);
  justify-self: end;
}
@media (min-width: 1100px) { .scena-strony { display: block; } }

.scena-film {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 880 / 750;
}

/* Naglowek sekcji realizacji: tekst po lewej, scena 3D w pasie, ktory i tak
   zostawal pusty. */
.realizacje-naglowek-tekst { min-width: 0; }
@media (min-width: 1100px) {
  .realizacje-naglowek {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-6);
  }
}

/* ---------- Częste pytania ---------------------------------------------------

   Dwie kolumny, bo przy jednej pytania stoja waskim pasem posrodku, a reszta
   szerokosci zostaje pusta. Lewa kolumna jest przyklejona, wiec odnosnik do
   kontaktu towarzyszy calej liscie. */
.faq-section {
  position: relative;
  isolation: isolate;
  overflow: clip;
}
/* Plamy w kolorach marki wypelniaja margines, ktory i tak zostaje pusty.
   Nic sie tu nie rusza w petli: to statyczne tlo, nie animacja. */
.faq-section::before,
.faq-section::after {
  content: "";
  position: absolute;
  z-index: -1;
  inline-size: 48vw;
  block-size: 120%;
  inset-block-start: -10%;
  filter: blur(100px);
  opacity: 0.14;
  pointer-events: none;
}
.faq-section::before {
  inset-inline-start: -24vw;
  background: radial-gradient(closest-side, #7C8CFF, transparent);
}
.faq-section::after {
  inset-inline-end: -24vw;
  background: radial-gradient(closest-side, #47D6E8, transparent);
}

.faq-uklad {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
  align-items: start;
}
.faq-kolumna .section-title { margin-block-end: var(--space-3); }
.faq-kolumna .section-lead { margin-block-end: var(--space-5); }

@media (min-width: 960px) {
  .faq-uklad {
    grid-template-columns: minmax(0, 0.78fr) minmax(0, 1.22fr);
    gap: var(--space-7);
  }
  /* Odstep od gory to wysokosc przyklejonego naglowka strony. */
  .faq-kolumna { position: sticky; inset-block-start: calc(76px + var(--space-4)); }
}

.faq-lista {
  display: grid;
  gap: var(--space-2);
  /* Pozwala animowac wysokosc do auto. Celowo tutaj, a nie na :root: inaczej
     kazde przejscie wysokosci na stronie zaczeloby sie animowac. */
  interpolate-size: allow-keywords;
}
.faq-pozycja {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  overflow: clip;
  transition: border-color var(--dur-base) var(--ease-out),
              background var(--dur-base) var(--ease-out);
}
.faq-pozycja:hover { border-color: var(--border-strong); }
.faq-pozycja[open] {
  border-color: var(--border-strong);
  background: rgba(255, 255, 255, 0.065);
}
/* Pionowy pasek gradientu przy lewej krawedzi rozjezdza sie przy otwarciu. */
.faq-pozycja::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 2px;
  background: var(--gradient-brand);
  transform-origin: top;
  scale: 1 0;
  transition: scale var(--dur-slow) var(--ease-out);
}
.faq-pozycja[open]::before { scale: 1 1; }

.faq-pozycja summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  font-family: var(--font-heading);
  font-size: 1.02rem;
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}
.faq-pozycja summary::-webkit-details-marker { display: none; }
.faq-pozycja summary::marker { content: ""; }
.faq-pozycja summary:focus-visible { outline: 2px solid var(--blue-soft); outline-offset: -2px; }

/* Plus, ktory zamienia sie w minus: obracana jest tylko jedna kreska. */
.faq-znak {
  position: relative;
  flex: none;
  width: 18px;
  height: 18px;
}
.faq-znak::before,
.faq-znak::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-start: 8px;
  height: 2px;
  border-radius: 2px;
  background: var(--fg-muted);
  transition: rotate var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
}
.faq-znak::after { rotate: 90deg; }
.faq-pozycja[open] .faq-znak::before,
.faq-pozycja[open] .faq-znak::after { background: var(--blue-soft); }
.faq-pozycja[open] .faq-znak::after { rotate: 0deg; }

.faq-odp { padding: 0 var(--space-5) var(--space-5); }
.faq-odp p {
  margin: 0;
  color: var(--fg-muted);
  line-height: 1.75;
  max-width: 68ch;
}
.faq-odp a { color: var(--blue-soft); text-decoration: underline; }
.faq-odp a:hover { color: var(--fg); }

/* Plynne rozwijanie. Przegladarki bez ::details-content otwieraja pytanie
   natychmiast, co jest poprawnym zachowaniem <details>, tylko bez animacji. */
.faq-pozycja::details-content {
  block-size: 0;
  overflow: hidden;
  transition: block-size var(--dur-slow) var(--ease-out),
              content-visibility var(--dur-slow) allow-discrete;
}
.faq-pozycja[open]::details-content { block-size: auto; }

/* ========== Preview loader ==========
   The wordmark assembles while the iframe fetches underneath, and the frame is
   only faded in once it reports load. The previews are complete exported sites
   – the largest is 91 MB – so without this the panel showed a blank white
   rectangle for as long as the download took.

   The frame is hidden with opacity, not display:none: a display:none iframe is
   not laid out, and some of these exports measure the viewport on load, so
   hiding them that way makes them render at the wrong size and then never
   correct it. */
.preview-frame {
  opacity: 0;
  transition: opacity 0.45s var(--ease-out);
}
.preview-modal.is-loaded .preview-frame { opacity: 1; }

.preview-loader {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-content: center;
  gap: var(--space-4);
  justify-items: center;
  background: var(--bg-base);
  opacity: 1;
  transition: opacity 0.45s var(--ease-out);
  pointer-events: none;
}
.preview-modal.is-loaded .preview-loader { opacity: 0; }

.preview-loader-word {
  display: inline-flex;
  align-items: baseline;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: clamp(1.8rem, 5vw, 3rem);
  letter-spacing: -0.03em;
  color: var(--fg);
}
.preview-loader-word > span {
  display: inline-block;
  opacity: 0;
  translate: 0 0.3em;
}
/* Driven by a class rather than running on page load, so reopening the modal
   replays it instead of showing an already-finished wordmark. */
.preview-loader.is-running .preview-loader-word > span {
  animation: preview-letter 0.5s var(--ease-out) calc(var(--i, 0) * 55ms) forwards;
}
.preview-loader.is-running .preview-loader-dot {
  animation: preview-letter 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 340ms forwards;
}
.preview-loader-dot { color: var(--purple-soft); }
@keyframes preview-letter {
  to { opacity: 1; translate: 0 0; }
}

.preview-loader-bar {
  display: block;
  width: min(220px, 40vw);
  height: 2px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
}
/* An indeterminate sweep, not a percentage: nothing here knows how far along
   the download is, and a bar that claims a figure it cannot know is a lie the
   user will catch when it stalls at 90%. */
.preview-loader-bar > span {
  display: block;
  width: 40%;
  height: 100%;
  border-radius: 2px;
  background: var(--gradient-brand-wide);
}
.preview-loader.is-running .preview-loader-bar > span {
  animation: preview-sweep 1.15s var(--ease-out) 500ms infinite;
}
@keyframes preview-sweep {
  from { transform: translateX(-110%); }
  to { transform: translateX(360%); }
}

@media (prefers-reduced-motion: reduce) {
  .preview-loader.is-running .preview-loader-word > span,
  .preview-loader.is-running .preview-loader-dot {
    animation: none;
    opacity: 1;
    translate: none;
  }
  .preview-loader.is-running .preview-loader-bar > span { animation: none; width: 100%; }
}


/* ========== Blog: siatka wielokolumnowa ==========
   Karty artykułów były jednokolumnowe niezależnie od szerokości okna, więc na
   desktopie trzy pozycje ciągnęły się przez cały ekran w pionie. Te same karty
   wchodzą teraz także na stronę główną (sekcja "Blog"), gdzie układ pionowy
   rozbijałby rytm strony. */
@media (min-width: 768px) {
  /* siatka bloga ma wlasne progi wyzej */
}
@media (min-width: 1024px) {
  /* siatka bloga ma wlasne progi wyzej */
}

/* Karty artykułów to linki blokowe, a `.article-content a` podkreśla i koloruje
   wszystkie odnośniki w treści – bez tego cały tekst karty (tytuł i opis)
   wyglądał na blogu jak podkreślony link. */
.article-content .blog-card,
.article-content .blog-card:hover {
  color: inherit;
  text-decoration: none;
}

/* ========== Konfigurator usług ==========
   Wybór usług jako pola wyboru, nie jako kreator krok-po-kroku: cała oferta
   zostaje w dokumencie, więc jest widoczna dla wyszukiwarki i dla kogoś, kto
   chce tylko zobaczyć zakres, a formularz działa również bez JavaScriptu.
   Skrypt dokłada tylko podsumowanie na żywo. */
.config-main { padding-block: var(--space-7) 0; }
.config-intro { max-width: 62ch; margin-block-end: var(--space-7); }

.config-section { margin-block-end: var(--space-7); }
.config-heading {
  font-family: var(--font-heading);
  font-size: clamp(1.25rem, 2.6vw, 1.6rem);
  font-weight: 600;
  margin-block-end: var(--space-2);
}
.config-hint {
  color: var(--fg-muted);
  font-size: 0.92rem;
  margin-block-end: var(--space-5);
  max-width: 60ch;
}

.config-group {
  border: none;
  padding: 0;
  margin: 0 0 var(--space-6);
}
.config-group legend {
  padding: 0;
  margin-block-end: var(--space-3);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--blue-soft);
}
.config-options {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
@media (min-width: 640px) { .config-options { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .config-options { grid-template-columns: repeat(4, 1fr); } }

/* Pole wyboru jest ukryte wizualnie, ale nie usunięte: nadal przyjmuje fokus
   i klawisz spacji, a stan :checked i :focus-visible steruje wyglądem kafelka. */
.config-option input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.config-option { display: block; cursor: pointer; height: 100%; }
.config-option-box {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  height: 100%;
  padding: var(--space-4) var(--space-4) var(--space-4) calc(var(--space-4) + 30px);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background:
    linear-gradient(var(--surface), var(--surface)) padding-box,
    linear-gradient(var(--bg-base), var(--bg-base)) padding-box,
    var(--gradient-brand) border-box;
  transition: background var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
/* Znacznik rysowany obwódką i kropką, a nie znakiem ✓ w treści: znak w
   pseudoelemencie trafiłby do odczytu ekranowego jako tekst obok nazwy usługi. */
.config-option-box::before {
  content: "";
  position: absolute;
  inset-inline-start: var(--space-4);
  inset-block-start: calc(var(--space-4) + 2px);
  width: 18px;
  height: 18px;
  border-radius: 6px;
  border: 1.5px solid var(--border-strong);
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.config-option-box::after {
  content: "";
  position: absolute;
  inset-inline-start: calc(var(--space-4) + 6px);
  inset-block-start: calc(var(--space-4) + 8px);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--on-gradient);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.config-option input:checked + .config-option-box {
  background:
    linear-gradient(rgba(124, 140, 255, 0.1), rgba(124, 140, 255, 0.1)) padding-box,
    linear-gradient(var(--bg-base), var(--bg-base)) padding-box,
    var(--gradient-brand) border-box;
  box-shadow: 0 0 22px -10px rgba(124, 140, 255, 0.9);
}
.config-option input:checked + .config-option-box::before {
  background: var(--gradient-brand-wide);
  border-color: transparent;
}
.config-option input:checked + .config-option-box::after { opacity: 1; }
.config-option input:focus-visible + .config-option-box {
  outline: 2px solid var(--blue-soft);
  outline-offset: 3px;
}
.config-option-name { font-weight: 600; font-size: 0.98rem; }
.config-option-desc { color: var(--fg-muted); font-size: 0.86rem; line-height: 1.5; }

.config-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.config-chip input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.config-chip { cursor: pointer; }
.config-chip span {
  display: inline-block;
  padding: 9px 18px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: 0.9rem;
  color: var(--fg-muted);
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.config-chip input:checked + span {
  color: var(--on-gradient);
  background: var(--gradient-brand-wide);
  border-color: transparent;
  font-weight: 600;
}
.config-chip input:focus-visible + span { outline: 2px solid var(--blue-soft); outline-offset: 3px; }

/* Wlasna kwota budzetu: pole w ksztalcie chipa, stoi w tym samym rzedzie co
   przedzialy, zeby bylo jasne, ze to alternatywa, a nie osobne pytanie. */
.config-chip-kwota {
  position: relative;
  display: inline-flex;
  align-items: center;
}
.config-chip-kwota input {
  width: 190px;
  padding: 9px 40px 9px 18px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  font-size: 0.9rem;
  transition: border-color var(--dur-fast) var(--ease-out);
}
.config-chip-kwota input::placeholder { color: var(--fg-muted); opacity: 1; }
.config-chip-kwota input:focus { outline: none; border-color: var(--blue-soft); }
.config-chip-kwota input:not(:placeholder-shown) { border-color: var(--blue-soft); font-weight: 600; }
.config-chip-kwota-zl {
  position: absolute;
  inset-inline-end: 16px;
  font-size: 0.85rem;
  color: var(--fg-muted);
  pointer-events: none;
}
@media (max-width: 560px) {
  .config-chip-kwota, .config-chip-kwota input { width: 100%; }
}

.config-contact {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  max-width: 760px;
}
@media (min-width: 720px) {
  .config-contact { grid-template-columns: repeat(2, 1fr); }
  .form-row-wide { grid-column: 1 / -1; }
}
.form-optional { color: var(--fg-faint); font-weight: 400; }

/* Pasek jest przyklejony do dołu okna, żeby przycisk wysyłki był w zasięgu na
   każdym etapie wypełniania – lista usług jest długa, a przycisk na samym dole
   oznaczałby przewijanie przez cały formularz, żeby go znaleźć. */
.config-bar {
  position: sticky;
  inset-block-end: 0;
  z-index: 5;
  margin-block-start: var(--space-6);
  border-top: 1px solid var(--border);
  background: rgba(10, 10, 16, 0.92);
  backdrop-filter: blur(14px);
}
.config-bar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-3);
}
.config-bar-text { margin: 0; min-width: 0; }
.config-bar-text strong { display: block; font-size: 0.95rem; }
.config-bar-list {
  display: block;
  color: var(--fg-muted);
  font-size: 0.82rem;
  /* Jedna linia z wielokropkiem: pasek ma nie rosnąć przy dziesięciu
     zaznaczonych usługach, bo zjadłby ekran na telefonie. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.config-send { flex: 0 0 auto; }
.config-note { margin: 0; padding-block-end: var(--space-3); }
@media (max-width: 560px) {
  .config-bar-inner { flex-direction: column; align-items: stretch; gap: var(--space-2); }
  .config-send { width: 100%; }
}

/* Odnośnik pod siatką usług i alternatywa pod końcowym CTA. Oba są zwykłym
   tekstem z linkiem, nie kolejnym przyciskiem: dwa przyciski obok siebie
   konkurują ze sobą i osłabiają ten, na którym zależy. */
.services-cta {
  margin-block-start: var(--space-5);
  color: var(--fg-muted);
  font-size: 0.95rem;
}
.services-cta a { color: var(--fg); border-bottom: 1px solid var(--purple-soft); }
.services-cta a:hover { border-bottom-color: var(--fg); }
.cta-alt {
  margin-block-start: var(--space-4);
  font-size: 0.88rem;
  color: var(--fg-faint);
}
.cta-alt a { color: var(--fg-muted); border-bottom: 1px solid var(--border-strong); }
.cta-alt a:hover { color: var(--fg); }

/* ========== Podstrony uslugowe ========== */
.usluga-main { padding-block: var(--space-7) var(--space-8); }
.usluga-hero { max-width: 64ch; margin-block-end: var(--space-6); }

.usluga-figura {
  margin: 0 0 var(--space-7);
  max-width: 900px;
}
.usluga-figura img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
}
.usluga-figura figcaption {
  margin-block-start: var(--space-3);
  color: var(--fg-faint);
  font-size: 0.86rem;
  max-width: 62ch;
}

.usluga-sekcja { margin-block-end: var(--space-7); }
.usluga-sekcja h2 {
  font-family: var(--font-heading);
  font-size: clamp(1.3rem, 2.8vw, 1.75rem);
  font-weight: 600;
  margin-block-end: var(--space-4);
}
.usluga-akapit { color: var(--fg-muted); line-height: 1.75; max-width: 66ch; }

/* Lista i kroki dziela ten sam uklad: nazwa pogrubiona, wyjasnienie pod nia.
   Dwie kolumny od 820px, bo przy szesciu pozycjach jedna kolumna robi sie
   niepotrzebnie dluga. */
.usluga-lista,
.usluga-kroki {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: krok;
}
@media (min-width: 820px) {
  .usluga-lista,
  .usluga-kroki { grid-template-columns: repeat(2, 1fr); column-gap: var(--space-6); }
}
.usluga-lista li,
.usluga-kroki li {
  position: relative;
  padding-inline-start: 30px;
  padding-block: 2px;
}
.usluga-lista li strong,
.usluga-kroki li strong { display: block; font-size: 1rem; margin-block-end: 4px; }
.usluga-lista li span,
.usluga-kroki li span { display: block; color: var(--fg-muted); font-size: 0.92rem; line-height: 1.65; }

/* Znacznik listy rysowany, nie pisany: znak w pseudoelemencie trafia do odczytu
   ekranowego jako tresc obok nazwy uslugi. */
.usluga-lista li::before {
  content: "";
  position: absolute;
  inset-inline-start: 2px;
  inset-block-start: 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--gradient-brand);
}
.usluga-kroki { counter-reset: krok; }
.usluga-kroki li { counter-increment: krok; padding-inline-start: 42px; }
.usluga-kroki li::before {
  content: counter(krok, decimal-leading-zero);
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 1px;
  font-family: var(--font-heading);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--blue-soft);
}

.usluga-faq { margin: 0; max-width: 72ch; }
.usluga-faq dt {
  font-weight: 600;
  font-size: 1rem;
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-4);
  border-top: 1px solid var(--border);
}
.usluga-faq dt:first-child { border-top: none; padding-block-start: 0; margin-block-start: 0; }
.usluga-faq dd {
  margin: var(--space-2) 0 0;
  color: var(--fg-muted);
  line-height: 1.7;
  font-size: 0.94rem;
}

.usluga-cta {
  margin-block-start: var(--space-8);
  padding: var(--space-6);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  text-align: center;
  background:
    linear-gradient(var(--surface), var(--surface)) padding-box,
    linear-gradient(var(--bg-base), var(--bg-base)) padding-box,
    var(--gradient-brand) border-box;
}
.usluga-cta h2 { font-size: clamp(1.3rem, 3vw, 1.8rem); margin-block-end: var(--space-2); }
.usluga-cta p { color: var(--fg-muted); margin-block-end: var(--space-4); }
.usluga-cta .cta-alt { margin-block-start: var(--space-4); margin-block-end: 0; }

/* ========== Rozwijane menu w naglowku ==========
   Otwierane kliknieciem, nie najechaniem. Menu na hover znika, gdy kursor
   przetnie przerwe miedzy przyciskiem a lista, i jest nieosiagalne dotykiem,
   a wlasnie dotyk jest tu wiekszoscia ruchu. */
.nav-dropdown { position: relative; }
.nav-dropdown-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.nav-dropdown-toggle svg {
  width: 12px;
  height: 12px;
  transition: rotate var(--dur-base) var(--ease-out);
}
.nav-dropdown-toggle[aria-expanded="true"] svg { rotate: 180deg; }

.nav-dropdown-menu {
  position: absolute;
  inset-block-start: calc(100% + 14px);
  inset-inline-start: 50%;
  translate: -50% 0;
  z-index: 20;
  min-width: 260px;
  padding: var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background:
    linear-gradient(rgba(16, 16, 26, 0.96), rgba(16, 16, 26, 0.96)) padding-box,
    var(--gradient-brand) border-box;
  backdrop-filter: blur(16px);
  box-shadow: 0 24px 50px -20px rgba(0, 0, 0, 0.9);
  display: grid;
  gap: 2px;
  /* visibility, nie display:none: przejscie opacity nie dziala z display,
     a samo opacity zostawiloby niewidoczne, ale klikalne i dostepne z klawiatury
     pozycje menu. */
  opacity: 0;
  visibility: hidden;
  translate: -50% -6px;
  transition: opacity var(--dur-base) var(--ease-out), translate var(--dur-base) var(--ease-out), visibility var(--dur-base);
}
.nav-dropdown.is-open .nav-dropdown-menu {
  opacity: 1;
  visibility: visible;
  translate: -50% 0;
}
.nav-dropdown-menu a {
  display: block;
  padding: 10px 14px;
  border-radius: 10px;
  font-size: 0.92rem;
  color: var(--fg-muted);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.nav-dropdown-menu a:hover,
.nav-dropdown-menu a:focus-visible {
  background: rgba(124, 140, 255, 0.14);
  color: var(--fg);
}
.nav-dropdown-menu a[aria-current="page"] { color: var(--fg); background: rgba(124, 140, 255, 0.1); }

/* W menu mobilnym lista jest zawsze rozwinieta: szuflada wewnatrz szuflady to
   o jedno klikniecie za duzo, a miejsca w pionie tu nie brakuje. */
.mobile-nav .mobile-uslugi {
  display: grid;
  gap: 2px;
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--border);
}
.mobile-nav .mobile-uslugi-label {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--blue-soft);
  padding-block-end: 4px;
}
.mobile-nav .mobile-uslugi a {
  padding: 8px 0 8px 14px;
  font-size: 0.95rem;
  border-bottom: none;
  color: var(--fg-muted);
}
/* Harmonijka Uslugi (script.js zamienia etykiete na przycisk). Bez JS lista
   zostaje rozwinieta, jak wczesniej. */
.mobile-uslugi-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: var(--space-2) 0;
  background: none;
  border: none;
  color: var(--fg-muted);
  font: inherit;
  font-size: 1.05rem;
  text-align: start;
  cursor: pointer;
}
.mobile-uslugi-toggle svg { width: 18px; height: 18px; transition: transform var(--dur-base) var(--ease-out); }
.mobile-uslugi-toggle[aria-expanded="true"] { color: var(--fg); }
.mobile-uslugi-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
.mobile-nav .mobile-uslugi.is-zwijane:not(.is-open) a { display: none; }

/* ========== Demo kalendarza rezerwacji ========== */
.rez-demo { margin-block-end: var(--space-7); }
.rez-demo > .usluga-akapit { margin-block-end: var(--space-5); }
.rez {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  padding: var(--space-5);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  background:
    linear-gradient(var(--surface), var(--surface)) padding-box,
    linear-gradient(var(--bg-base), var(--bg-base)) padding-box,
    var(--gradient-brand) border-box;
}
@media (min-width: 820px) { .rez { grid-template-columns: 1.15fr 1fr; gap: var(--space-6); } }

.rez-naglowek {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-block-end: var(--space-4);
}
.rez-naglowek strong { font-family: var(--font-heading); font-size: 1.02rem; }
.rez-nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg-muted);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.rez-nav:hover { color: var(--fg); border-color: var(--border-strong); }
.rez-nav svg { width: 16px; height: 16px; }

/* Siatka ma sufit szerokosci. Bez niego kratki, ktore sa kwadratami przez
   aspect-ratio, rosly razem z kolumna i na szerokim ekranie robily sie
   kolami po siedemdziesiat kilka pikseli. */
.rez-dni,
.rez-siatka {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 5px;
  max-width: 392px;
}
.rez-dni {
  margin-block-end: 6px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-faint);
  text-align: center;
}
.rez-dzien {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.05);
  color: var(--fg);
  font: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.rez-dzien:hover:not(:disabled) { background: rgba(124, 140, 255, 0.24); }
/* Dni bez miejsc zostają widoczne, ale wygaszone i wyłączone. Ukrycie ich
   rozjechałoby siatkę, a data musi zostać na swoim miejscu w tygodniu. */
.rez-dzien:disabled {
  background: none;
  /* Bylo 0.16, czyli okolo 1.3:1 wzgledem tla panelu: liczby znikaly.
     Dzien ma byc widocznie wygaszony, a nie nieobecny. */
  color: rgba(255, 255, 255, 0.38);
  cursor: default;
}
.rez-dzien.is-wybrany {
  background: var(--gradient-brand-wide);
  color: var(--on-gradient);
  font-weight: 700;
}
.rez-dzien:focus-visible { outline: 2px solid var(--blue-soft); outline-offset: 2px; }

.rez-legenda {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-block-start: var(--space-4);
  font-size: 0.78rem;
  color: var(--fg-faint);
}
.rez-kropka {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-inline-start: var(--space-3);
}
.rez-kropka:first-child { margin-inline-start: 0; }
.rez-kropka-wolne { background: rgba(124, 140, 255, 0.6); }
.rez-kropka-brak { background: rgba(255, 255, 255, 0.14); }

.rez-label {
  display: block;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--blue-soft);
  margin-block-end: var(--space-2);
}
.rez-label-god { margin-block-start: var(--space-4); }
.rez-select {
  width: 100%;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  font-size: 0.92rem;
  /* Dotyczy juz tylko wersji bez skryptu: bez tego kartka natywnej listy jest
     biala, a pozycje dziedzicza jasny kolor tekstu i znikaja. */
  color-scheme: dark;
}
.rez-select option { background: #16161f; color: var(--fg); }

/* ---------- Wlasna lista wyboru ---------------------------------------------

   Natywna lista jest rysowana przez system: pozycji podswietlonej nadaje
   wlasne jasne tlo, a kolor tekstu bierze ten narzucony przez strone, wiec
   akurat ta jedna pozycja wychodzila jasna na jasnym. Tu wszystkie stany sa
   moje, wiec daja sie zmierzyc. */
.rez-wybor { position: relative; }
.rez-wybor .rez-select { display: none; }
.rez-wybor-btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  font-size: 0.92rem;
  text-align: start;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out);
}
.rez-wybor-btn:hover { border-color: var(--border-strong); }
.rez-wybor-btn:focus-visible { outline: 2px solid var(--blue-soft); outline-offset: 2px; }
.rez-wybor-btn svg {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--fg-faint);
  transition: rotate var(--dur-fast) var(--ease-out);
}
.rez-wybor-btn[aria-expanded="true"] { border-color: var(--purple-soft); }
.rez-wybor-btn[aria-expanded="true"] svg { rotate: 180deg; }

.rez-opcje {
  position: absolute;
  inset-inline: 0;
  inset-block-start: calc(100% + 6px);
  z-index: 20;
  margin: 0;
  padding: 5px;
  list-style: none;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  /* Nieprzezroczyste tlo, nie var(--surface): lista zaslania zawartosc pod
     soba, a przez polprzezroczysta warstwe przebijalby kalendarz. */
  background: #16161f;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55);
}
.rez-opcje[hidden] { display: none; }
.rez-opcja {
  padding: 10px 12px;
  border-radius: 8px;
  color: var(--fg);
  font-size: 0.92rem;
  cursor: pointer;
}
.rez-opcja:hover,
.rez-opcja.is-aktywna { background: rgba(124, 140, 255, 0.22); }
.rez-opcja[aria-selected="true"] {
  background: var(--gradient-brand-wide);
  color: var(--on-gradient);
  font-weight: 600;
}
.rez-godziny { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.rez-godzina {
  padding: 10px 6px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.rez-godzina:hover:not(:disabled) { border-color: var(--purple-soft); }
.rez-godzina:disabled {
  color: rgba(255, 255, 255, 0.2);
  border-color: transparent;
  background: rgba(255, 255, 255, 0.02);
  cursor: default;
  text-decoration: line-through;
}
.rez-godzina.is-wybrana {
  background: var(--gradient-brand-wide);
  color: var(--on-gradient);
  border-color: transparent;
  font-weight: 700;
}
.rez-godzina:focus-visible { outline: 2px solid var(--blue-soft); outline-offset: 2px; }
.rez-pusto { grid-column: 1 / -1; color: var(--fg-faint); font-size: 0.86rem; margin: 0; }

.rez-podsumowanie { margin-block-start: var(--space-4); min-height: 46px; }
.rez-podsumowanie strong { display: block; font-size: 0.98rem; }
.rez-podsumowanie span { display: block; color: var(--fg-muted); font-size: 0.86rem; margin-block-start: 2px; }
.rez-wyslij { width: 100%; margin-block-start: var(--space-3); }
.rez-wyslij:disabled { opacity: 0.45; cursor: default; }

/* ---------- Wersja na stronie kontaktu --------------------------------------

   Ten sam widget, ale jest formularzem, nie pokazem: dochodza pola kontaktowe
   pod lista godzin, a legenda "brak miejsc" znika, bo w tym trybie nic nie
   jest zajete. */
.rez-pola {
  margin-block-start: var(--space-4);
  display: grid;
  gap: var(--space-3);
}
.rez-pola .form-row { margin: 0; }
.rez-nota { margin-block-start: var(--space-3); font-size: 0.86rem; color: var(--fg-muted); }
.rez-nota:empty { display: none; }
.rez-zgoda {
  margin-block-start: var(--space-3);
  font-size: 0.78rem;
  line-height: 1.6;
  color: var(--fg-faint);
}

/* ========== Kampania: cztery glowne uslugi ==========
   Wtyczki dla deweloperow, panel, automatyzacje AI i e-Doreczenia sa na
   przodzie wszedzie: w menu, w "Brzmi znajomo?" i w sekcji Co robimy. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Menu: wyroznione pozycje jasniejsze i z numerem, reszta pod etykieta. */
.nav-dropdown-menu { min-width: 300px; }
.nav-dropdown-menu a.nav-wyrozniona,
.mobile-nav .mobile-uslugi a.nav-wyrozniona {
  display: flex; align-items: center; gap: 10px;
  color: var(--fg); font-weight: 600;
}
.nav-nr {
  flex: 0 0 auto;
  font-family: var(--font-heading); font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em;
  background: var(--gradient-brand); -webkit-background-clip: text; background-clip: text; color: transparent;
}
.nav-dropdown-menu a.nav-wyrozniona { background: rgba(124, 140, 255, 0.07); }
.nav-dropdown-menu a.nav-wyrozniona:hover,
.nav-dropdown-menu a.nav-wyrozniona:focus-visible { background: rgba(124, 140, 255, 0.18); }
.nav-pozostale {
  display: block; margin: 8px 14px 2px; padding-top: 8px;
  border-top: 1px solid var(--border);
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--fg-faint);
}
.mobile-nav .mobile-uslugi .nav-pozostale { margin-inline: 14px 0; }
.mobile-nav .mobile-uslugi.is-zwijane:not(.is-open) .nav-pozostale { display: none; }

/* Strona glowna: cztery karty z gradientowa ramka nad pozostalymi obszarami. */
.glowne-uslugi {
  display: grid; gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
  margin-block: var(--space-5) var(--space-7);
}
.glowna-usluga {
  position: relative; display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-5) var(--space-4) var(--space-4);
  border: 1px solid transparent; border-radius: var(--radius-lg);
  background:
    radial-gradient(120% 80% at 0% 0%, rgba(76, 124, 255, 0.16), transparent 60%) padding-box,
    radial-gradient(120% 80% at 100% 100%, rgba(155, 92, 246, 0.14), transparent 60%) padding-box,
    linear-gradient(var(--bg-elevated), var(--bg-elevated)) padding-box,
    var(--gradient-brand) border-box;
  color: var(--fg); text-decoration: none;
  box-shadow: 0 20px 50px -30px rgba(124, 140, 255, 0.55);
  transition: translate var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.glowna-usluga:hover, .glowna-usluga:focus-visible {
  translate: 0 -4px;
  box-shadow: 0 28px 60px -28px rgba(155, 92, 246, 0.7);
}
.glowna-usluga-nr {
  font-family: var(--font-heading); font-size: 2.4rem; font-weight: 700; line-height: 1;
  background: var(--gradient-brand); -webkit-background-clip: text; background-clip: text; color: transparent;
}
.glowna-usluga-znacznik {
  position: absolute; inset-block-start: var(--space-4); inset-inline-end: var(--space-4);
  padding: 4px 10px; border-radius: var(--radius-pill);
  background: var(--gradient-brand); color: var(--on-gradient);
  font-size: 0.7rem; font-weight: 700; letter-spacing: 0.04em;
}
.glowna-usluga h3 { font-family: var(--font-heading); font-size: 1.2rem; font-weight: 600; margin: var(--space-2) 0 0; }
.glowna-usluga p { color: var(--fg-muted); font-size: 0.93rem; line-height: 1.65; margin: 0; flex: 1; }
.glowna-usluga-link { margin-top: var(--space-2); font-size: 0.9rem; font-weight: 600; color: var(--blue-soft); }
.glowna-usluga:hover .glowna-usluga-link { color: var(--fg); }
.pozostale-naglowek {
  margin: 0 0 var(--space-4);
  font-size: 0.75rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--fg-faint);
}

/* ========== Podstrona: panel do zarzadzania firma ========== */
.panel-akcje { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-4); }
.panel-podglad { margin-block-end: var(--space-8); }
.panel-zakladki {
  display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none;
  margin-bottom: var(--space-3); padding-bottom: 2px;
}
.panel-zakladki::-webkit-scrollbar { display: none; }
.panel-zakladka {
  flex: 0 0 auto; font: inherit; font-size: 0.9rem; font-weight: 600; cursor: pointer;
  padding: 9px 16px; border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--fg-muted);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.panel-zakladka:hover { color: var(--fg); border-color: rgba(124, 140, 255, 0.6); }
.panel-zakladka[aria-selected="true"] {
  background: var(--gradient-brand); color: var(--on-gradient); border-color: transparent;
}
.panel-przegladarka {
  border-radius: var(--radius-lg); overflow: hidden;
  border: 1px solid transparent;
  background: linear-gradient(#0d0d15, #0d0d15) padding-box, var(--gradient-brand) border-box;
  box-shadow: 0 40px 90px -40px rgba(76, 124, 255, 0.6), 0 30px 70px -40px rgba(155, 92, 246, 0.55);
}
.panel-pasek {
  display: flex; align-items: center; gap: 7px; padding: 11px 16px;
  background: #13131d; border-bottom: 1px solid var(--border);
}
.panel-kropka { width: 11px; height: 11px; border-radius: 50%; background: #3a3a4c; }
.panel-kropka:nth-child(1) { background: #ff6b6b; }
.panel-kropka:nth-child(2) { background: #ffd166; }
.panel-kropka:nth-child(3) { background: #3ddc97; }
.panel-adres {
  margin-inline: auto; padding: 4px 18px; border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.06); color: var(--fg-faint); font-size: 0.78rem;
}
.panel-ekran { aspect-ratio: 16 / 10; background: #0a0a10; }
.panel-ekran img {
  display: block; width: 100%; height: 100%; object-fit: cover; object-position: top left;
  transition: opacity 0.16s ease;
}
.panel-ekran img.znika { opacity: 0; }
.panel-opis { margin: var(--space-3) 0 0; color: var(--fg); font-size: 0.98rem; min-height: 3em; max-width: 80ch; }
.panel-uwaga { margin: var(--space-1) 0 0; color: var(--fg-faint); font-size: 0.8rem; }

.panel-moduly {
  display: grid; gap: var(--space-3); margin: 0; padding: 0; list-style: none;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
}
.panel-moduly li {
  padding: var(--space-4); border-radius: var(--radius-md);
  border: 1px solid var(--border); background: var(--surface);
}
.panel-moduly strong { display: block; margin: var(--space-3) 0 6px; font-size: 1.02rem; }
.panel-moduly li > span:last-child { color: var(--fg-muted); font-size: 0.92rem; line-height: 1.65; }
.panel-ikona {
  display: inline-grid; place-items: center; width: 42px; height: 42px; border-radius: 12px;
  background: linear-gradient(135deg, rgba(71, 214, 232, 0.18), rgba(168, 85, 247, 0.22));
  color: var(--fg);
}
.panel-ikona svg { width: 22px; height: 22px; }

.panel-telefony {
  display: grid; gap: var(--space-6); align-items: center;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  margin-block-end: var(--space-7);
}
@media (max-width: 820px) { .panel-telefony { grid-template-columns: minmax(0, 1fr); } }
.panel-telefony h2 {
  font-family: var(--font-heading); font-size: clamp(1.3rem, 2.8vw, 1.75rem); font-weight: 600;
  margin-block-end: var(--space-4);
}
.panel-telefony-obrazy { display: flex; justify-content: center; gap: var(--space-4); padding-bottom: 28px; }
.panel-telefon {
  margin: 0; width: min(46%, 240px); aspect-ratio: 1 / 2; overflow: hidden;
  border-radius: 34px; border: 7px solid #1c1c28;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12), 0 30px 60px -30px rgba(76, 124, 255, 0.7);
}
.panel-telefon:nth-child(2) { translate: 0 28px; }
.panel-telefon img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top; }

.panel-deweloper {
  padding: var(--space-5); border-radius: var(--radius-lg);
  border: 1px solid transparent;
  background:
    radial-gradient(100% 120% at 100% 0%, rgba(155, 92, 246, 0.14), transparent 60%) padding-box,
    linear-gradient(var(--bg-elevated), var(--bg-elevated)) padding-box,
    var(--gradient-brand) border-box;
}
.panel-deweloper .usluga-akapit a { color: var(--fg); border-bottom: 1px solid var(--purple-soft); }
/* Konfigurator: grupa z czterema glownymi uslugami na gorze, z ramka marki. */
.config-group-glowna {
  border: 1px solid transparent; border-radius: var(--radius-lg); padding: var(--space-4);
  background: linear-gradient(var(--bg-elevated), var(--bg-elevated)) padding-box, var(--gradient-brand) border-box;
}

/* ========== Animowane makiety na podstronach uslug ==========
   Sceny leza jedna na drugiej w .mk-kadr i przenikaja sie. W kazdej scenie
   elementy .mk-wejdz wchodza po kolei z opoznieniem --d. Wygladem udaja
   panel escale: ciemne okno, boczne menu, karty. */
.makieta { margin-block: var(--space-6) var(--space-7); }
.mk-kroki { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: var(--space-3); }
.mk-krok {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  font: inherit; font-size: 0.86rem; font-weight: 600;
  padding: 8px 14px; border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--fg-muted);
}
.mk-krok b {
  display: inline-grid; place-items: center; width: 20px; height: 20px; border-radius: 50%;
  font-size: 0.72rem; background: rgba(255, 255, 255, 0.08);
}
.mk-krok[aria-selected="true"] { background: var(--gradient-brand); color: var(--on-gradient); border-color: transparent; }
.mk-krok[aria-selected="true"] b { background: rgba(8, 8, 15, 0.18); }
.mk-krok-byl { color: var(--fg); }
.mk-powtorz {
  margin-inline-start: auto; font: inherit; font-size: 0.8rem; cursor: pointer;
  background: none; border: none; color: var(--fg-faint);
}
.mk-powtorz:hover { color: var(--fg); }

.mk-kadr {
  position: relative; aspect-ratio: 16 / 9; border-radius: var(--radius-lg); overflow: hidden;
  border: 1px solid transparent;
  background:
    radial-gradient(90% 90% at 0% 0%, rgba(76, 124, 255, 0.22), transparent 60%) padding-box,
    radial-gradient(90% 90% at 100% 100%, rgba(155, 92, 246, 0.2), transparent 60%) padding-box,
    linear-gradient(#0b0b12, #0b0b12) padding-box,
    var(--gradient-brand) border-box;
}
@media (max-width: 720px) { .mk-kadr { aspect-ratio: 4 / 5; } }
.mk-scena {
  position: absolute; inset: 0; display: grid; place-items: center; padding: 5%;
  opacity: 0; visibility: hidden; transform: scale(0.985);
  transition: opacity 0.45s ease, transform 0.45s ease, visibility 0.45s;
}
.mk-scena.mk-aktywna { opacity: 1; visibility: visible; transform: none; }
.mk-opis { margin: var(--space-3) 0 0; color: var(--fg); min-height: 3em; max-width: 80ch; }

.mk-wejdz { opacity: 0; translate: 0 10px; }
.mk-aktywna .mk-wejdz { animation: mk-wejdz 0.5s cubic-bezier(0.16, 1, 0.3, 1) var(--d, 0s) forwards; }
@keyframes mk-wejdz { to { opacity: 1; translate: 0 0; } }
@media (prefers-reduced-motion: reduce) {
  .mk-wejdz { opacity: 1; translate: none; }
  .mk-aktywna .mk-wejdz { animation: none; }
  .mk-scena { transition: none; }
}

/* Okno w stylu panelu */
.mk-okno {
  width: 100%; max-width: 860px; height: 100%; max-height: 100%;
  display: grid; grid-template-rows: auto 1fr; overflow: hidden;
  border-radius: 14px; background: #0f0f18; border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, 0.9);
  font-size: clamp(0.62rem, 1.15vw, 0.82rem); color: #e8e8f0;
}
.mk-pasek { display: flex; align-items: center; gap: 6px; padding: 8px 12px; background: #15151f; border-bottom: 1px solid rgba(255, 255, 255, 0.07); }
.mk-pasek i { width: 9px; height: 9px; border-radius: 50%; background: #ff6b6b; }
.mk-pasek i:nth-child(2) { background: #ffd166; }
.mk-pasek i:nth-child(3) { background: #3ddc97; }
.mk-pasek span { margin-inline: auto; padding: 3px 14px; border-radius: 99px; background: rgba(255, 255, 255, 0.06); color: #8b8ba3; font-size: 0.9em; }
.mk-uklad { display: grid; grid-template-columns: 22% 1fr; min-height: 0; }
@media (max-width: 720px) { .mk-uklad { grid-template-columns: 1fr; } .mk-bok { display: none; } }
.mk-bok { padding: 12px 10px; border-right: 1px solid rgba(255, 255, 255, 0.06); display: flex; flex-direction: column; gap: 3px; }
.mk-bok strong { font-family: var(--font-heading); font-size: 1.15em; margin-bottom: 8px; }
.mk-bok strong em { font-style: normal; color: #a855f7; }
.mk-bok span { padding: 5px 8px; border-radius: 7px; color: #8b8ba3; }
.mk-bok span.tu { background: rgba(124, 140, 255, 0.14); color: #fff; }
.mk-tresc { padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; min-height: 0; overflow: hidden; }
.mk-tresc h4 { margin: 0; font-family: var(--font-heading); font-size: 1.35em; font-weight: 600; }
.mk-tresc small { color: #8b8ba3; }
.mk-karta { padding: 10px 12px; border-radius: 10px; background: #171722; border: 1px solid rgba(255, 255, 255, 0.07); }
.mk-wiersz { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.mk-plakietka { padding: 2px 8px; border-radius: 99px; font-size: 0.85em; font-weight: 600; white-space: nowrap; }
.mk-ok { background: rgba(52, 211, 153, 0.14); color: #34d399; }
.mk-info { background: rgba(71, 214, 232, 0.14); color: #47d6e8; }
.mk-zolta { background: rgba(251, 191, 36, 0.14); color: #fbbf24; }
.mk-pasek-postepu { height: 6px; border-radius: 99px; background: rgba(255, 255, 255, 0.07); overflow: hidden; margin-top: 8px; }
.mk-pasek-postepu i { display: block; height: 100%; width: 0; background: linear-gradient(90deg, #47d6e8, #7c8cff, #a855f7); }
.mk-aktywna .mk-pasek-postepu i { animation: mk-postep 2.2s ease-out 0.6s forwards; }
@keyframes mk-postep { to { width: 100%; } }
.mk-siatka2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* Portal rzadowy: jasny, zeby bylo widac, ze to inne miejsce niz panel. */
.mk-gov { background: #f5f6f8; color: #1f2430; }
.mk-gov .mk-pasek { background: #e6e8ec; border-bottom-color: #d5d8de; }
.mk-gov .mk-pasek span { background: #fff; color: #4a5160; }
.mk-gov-tresc { padding: 16px 20px; display: flex; flex-direction: column; gap: 10px; overflow: hidden; }
.mk-gov-tresc h4 { margin: 0; font-size: 1.3em; color: #12213f; }
.mk-gov-tresc small { color: #5b6272; }
.mk-tabela { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; font-size: 0.95em; }
.mk-tabela th, .mk-tabela td { text-align: left; padding: 7px 10px; border-bottom: 1px solid #e7e9ee; }
.mk-tabela th { background: #eef1f6; color: #3b4456; font-weight: 600; }
.mk-gov .mk-ok { background: #e3f6ec; color: #147a45; }

/* Telefon z powiadomieniem */
.mk-telefon {
  position: relative; height: 100%; max-height: 100%; aspect-ratio: 9 / 18.5;
  border-radius: 34px; padding: 10px; background: #1b1b26;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14), 0 30px 70px -25px rgba(76, 124, 255, 0.6);
}
.mk-ekran {
  height: 100%; border-radius: 26px; overflow: hidden; position: relative; padding: 14% 7% 0;
  background: radial-gradient(120% 70% at 20% 0%, #3d4fb0, transparent 60%), radial-gradient(120% 80% at 90% 100%, #7c3aed, transparent 60%), #10101a;
  display: flex; flex-direction: column; align-items: center; color: #fff;
  font-size: clamp(0.6rem, 1.1vw, 0.8rem);
}
.mk-ekran::before { content: ''; position: absolute; top: 3%; left: 50%; translate: -50% 0; width: 30%; height: 3.2%; border-radius: 99px; background: #000; }
.mk-godzina { font-family: var(--font-heading); font-size: 3.6em; font-weight: 600; line-height: 1; }
.mk-data { margin: 4px 0 18%; opacity: 0.8; }
.mk-powiadomienie {
  width: 100%; padding: 9px 11px; border-radius: 16px; margin-bottom: 7px;
  background: rgba(255, 255, 255, 0.2); text-align: left;
}
.mk-powiadomienie header { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; font-size: 0.85em; opacity: 0.85; }
.mk-powiadomienie header b { font-family: var(--font-heading); }
.mk-powiadomienie header i { font-style: normal; margin-inline-start: auto; }
.mk-powiadomienie p { margin: 0; line-height: 1.35; }
.mk-powiadomienie strong { display: block; }
.mk-ikonka {
  display: inline-grid; place-items: center; width: 18px; height: 18px; border-radius: 5px;
  background: linear-gradient(135deg, #47d6e8, #a855f7); color: #08080f; font-weight: 800; font-size: 0.8em;
}
.mk-scena-telefon { grid-template-columns: auto; }

/* Czat z asystentem */
.mk-czat { flex: 1; display: flex; flex-direction: column; gap: 8px; justify-content: flex-end; min-height: 0; }
.mk-dymek { max-width: 80%; padding: 8px 11px; border-radius: 12px; line-height: 1.45; }
.mk-dymek-ja { align-self: flex-end; background: rgba(124, 140, 255, 0.22); border-bottom-right-radius: 4px; }
.mk-dymek-ai { align-self: flex-start; background: #1b1b28; border: 1px solid rgba(255, 255, 255, 0.08); border-bottom-left-radius: 4px; }
.mk-dymek-ai b { display: block; font-size: 0.85em; color: #a855f7; margin-bottom: 2px; }
.mk-pole { display: flex; gap: 8px; padding: 8px 10px; border-radius: 10px; background: #171722; border: 1px solid rgba(255, 255, 255, 0.08); color: #8b8ba3; }
.mk-pole span { flex: 1; color: #e8e8f0; }
.mk-pole span:empty::before { content: 'Napisz do asystenta…'; color: #6a6a80; }
.mk-kropki { display: inline-flex; gap: 3px; }
.mk-kropki i { width: 5px; height: 5px; border-radius: 50%; background: #8b8ba3; animation: mk-kropka 1s infinite; }
.mk-kropki i:nth-child(2) { animation-delay: 0.15s; }
.mk-kropki i:nth-child(3) { animation-delay: 0.3s; }
@keyframes mk-kropka { 50% { opacity: 0.25; translate: 0 -2px; } }

/* Kalendarz miesiaca */
.mk-kal { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.mk-kal > span { font-size: 0.85em; color: #8b8ba3; text-align: center; }
.mk-dzien { min-height: 3.2em; padding: 4px; border-radius: 7px; background: #161620; border: 1px solid rgba(255, 255, 255, 0.05); font-size: 0.85em; color: #8b8ba3; }
.mk-dzien-tu { border-color: #7c8cff; color: #fff; }
.mk-zdarzenie { display: block; margin-top: 3px; padding: 2px 4px; border-radius: 4px; font-size: 0.85em; color: #fff; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.mk-zd-urlop { background: rgba(251, 191, 36, 0.3); }
.mk-zd-spot { background: linear-gradient(90deg, #3d66e0, #a855f7); }

/* Pismo z e-Doreczen */
.mk-pismo { display: grid; grid-template-columns: auto 1fr auto; gap: 10px; align-items: center; }
.mk-koperta { display: inline-grid; place-items: center; width: 34px; height: 34px; border-radius: 9px; background: rgba(71, 214, 232, 0.14); color: #47d6e8; font-size: 1.1em; }
.mk-nowe { box-shadow: 0 0 0 1px #47d6e8, 0 0 24px -6px rgba(71, 214, 232, 0.7); }
/* Scena z telefonem: telefon i krotkie haslo obok, na telefonie jedno pod drugim. */
.mk-scena.mk-scena-telefon { display: flex; align-items: center; justify-content: center; gap: 6%; padding: 4% 6%; }
.mk-haslo { max-width: 34ch; display: flex; flex-direction: column; gap: 10px; }
.mk-haslo strong { font-family: var(--font-heading); font-size: clamp(1.2rem, 2.6vw, 2rem); line-height: 1.15; }
.mk-haslo span { color: var(--fg-muted); line-height: 1.6; }
@media (max-width: 720px) {
  .mk-scena.mk-scena-telefon { flex-direction: column; gap: 14px; padding: 5%; }
  .mk-scena-telefon .mk-telefon { height: auto; width: 58%; }
  .mk-haslo { text-align: center; }
  .mk-haslo span { display: none; }
}
/* Dymek "pisze..." pokazuje sie na chwile i znika, zanim przyjdzie odpowiedz. */
.mk-chwilowe { opacity: 0; max-height: 0; padding-block: 0; border-width: 0; overflow: hidden; }
.mk-aktywna .mk-chwilowe { animation: mk-chwila 1.05s ease var(--d, 0s) forwards; }
@keyframes mk-chwila {
  0% { opacity: 0; max-height: 0; padding-block: 0; }
  15%, 85% { opacity: 1; max-height: 40px; padding-block: 8px; border-width: 1px; }
  100% { opacity: 0; max-height: 0; padding-block: 0; border-width: 0; }
}
/* Menu: zwykle pozycje oddzielone od wyroznionych cienka kreska. */
.nav-dropdown-menu a.nav-zwykla-pierwsza,
.mobile-nav .mobile-uslugi a.nav-zwykla-pierwsza { margin-top: 6px; padding-top: 14px; border-top: 1px solid var(--border); border-radius: 0 0 10px 10px; }
/* Strona glowna: piec kart, dwie wieksze na gorze i trzy pod nimi. */
@media (min-width: 900px) {
  .glowne-uslugi { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .glowne-uslugi > .glowna-usluga { grid-column: span 2; }
  .glowne-uslugi > .glowna-usluga:nth-child(-n+2) { grid-column: span 3; }
}
.glowna-usluga h3 { margin-top: var(--space-4); }
/* Wizualizacja 3D: wideo w ramce marki. */
.wiz-ramka { position: relative; border-radius: var(--radius-lg); overflow: hidden; border: 1px solid transparent;
  background: linear-gradient(#0b0b12, #0b0b12) padding-box, var(--gradient-brand) border-box; }
.wiz-wideo { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; }
.wiz-etykieta { position: absolute; left: 14px; bottom: 14px; padding: 5px 12px; border-radius: var(--radius-pill);
  background: rgba(8, 8, 15, 0.82); font-size: 0.8rem; font-weight: 600; }
.wiz-figura { max-width: none; }
/* Scalona strona: skroty do sekcji i odstep miedzy trzema czesciami. */
.scal-skroty { display: flex; flex-wrap: wrap; gap: 8px; margin-top: var(--space-4); }
.scal-skroty a { padding: 7px 14px; border-radius: var(--radius-pill); border: 1px solid var(--border-strong); color: var(--fg-muted); font-size: 0.88rem; }
.scal-skroty a:hover { color: var(--fg); border-color: rgba(124, 140, 255, 0.6); }
.scal-sekcja { scroll-margin-top: 90px; padding-top: var(--space-5); border-top: 1px solid var(--border); }
.scal-podtytul { font-family: var(--font-heading); font-size: 1.15rem; margin-block-end: var(--space-3); }
/* Realizacje: zakladki Strony / Panele i karty paneli. */
.real-zakladki { display: flex; gap: 8px; flex-wrap: wrap; margin: var(--space-4) 0 var(--space-5); }
.real-zakladka {
  font: inherit; font-size: 0.95rem; font-weight: 600; cursor: pointer;
  padding: 10px 18px; border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--fg-muted);
}
.real-zakladka[aria-selected="true"] { background: var(--gradient-brand); color: var(--on-gradient); border-color: transparent; }
.real-panele-wstep { margin-bottom: var(--space-5); }
.real-panele { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.real-panel {
  position: relative; display: flex; flex-direction: column; gap: 8px; padding: 12px 12px var(--space-4);
  border-radius: var(--radius-lg); border: 1px solid transparent; color: var(--fg); text-decoration: none;
  background: linear-gradient(var(--bg-elevated), var(--bg-elevated)) padding-box, var(--gradient-brand) border-box;
  transition: translate var(--dur-base) var(--ease-out);
}
.real-panel:hover { translate: 0 -4px; }
.real-panel img { width: 100%; height: auto; border-radius: 14px; aspect-ratio: 16 / 10; object-fit: cover; object-position: top left; }
.real-panel h2 { font-family: var(--font-heading); font-size: 1.15rem; margin: 6px 6px 0; }
.real-panel p { margin: 0 6px; color: var(--fg-muted); font-size: 0.92rem; line-height: 1.6; }
.real-panel-znacznik {
  position: absolute; top: 22px; left: 22px; padding: 4px 10px; border-radius: var(--radius-pill);
  background: var(--gradient-brand); color: var(--on-gradient); font-size: 0.72rem; font-weight: 700;
}
.mk-okno { height: auto; }
@media (max-width: 720px) { .mk-bok { display: none; } }

/* ========== Co robimy: stary uklad obszarow w ramkach z animacjami ==========
   Wiersze dalej ida na zmiane (tekst / ilustracja), ale kazdy stoi w karcie
   z gradientowa ramka marki, a ilustracje to animowane makiety z podstron. */
.obszary.obszary-karty { border-top: none; display: grid; gap: var(--space-5); }
.obszar.obszar-karta {
  border: 1px solid transparent; border-radius: var(--radius-lg);
  padding: var(--space-5);
  background:
    radial-gradient(80% 90% at 0% 0%, rgba(76, 124, 255, 0.1), transparent 60%) padding-box,
    radial-gradient(80% 90% at 100% 100%, rgba(155, 92, 246, 0.1), transparent 60%) padding-box,
    linear-gradient(var(--bg-elevated), var(--bg-elevated)) padding-box,
    var(--gradient-brand) border-box;
  box-shadow: 0 30px 70px -45px rgba(124, 140, 255, 0.6);
}
.obszar.obszar-karta::before { display: none; }
@media (min-width: 860px) {
  .obszar.obszar-karta { padding: var(--space-6); gap: var(--space-6); }
}
.obszar-znacznik {
  display: inline-block; margin-bottom: var(--space-3);
  padding: 4px 11px; border-radius: var(--radius-pill);
  background: var(--gradient-brand); color: var(--on-gradient);
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.04em;
}
/* W wierszu makieta jest mniejsza niz na podstronie, wiec drobniejsza czcionka. */
.obszar-makieta .makieta-mini { width: 100%; max-width: 760px; margin-inline: auto; }
.makieta-mini .mk-kadr { aspect-ratio: 16 / 11; }
.makieta-mini .mk-okno { font-size: clamp(0.55rem, 0.78vw, 0.74rem); }
.makieta-mini .mk-ekran { font-size: clamp(0.55rem, 0.75vw, 0.72rem); }
.makieta-mini .mk-haslo strong { font-size: clamp(1rem, 1.7vw, 1.4rem); }
.makieta-mini .mk-haslo span { font-size: 0.88rem; }
@media (max-width: 720px) { .makieta-mini .mk-kadr { aspect-ratio: 4 / 5; } }
.mk-zrzut { display: block; width: 100%; height: auto; }
.obszar-makieta .wiz-ramka { max-width: 760px; margin-inline: auto; }
.obszar-film { display: grid; place-items: center; }
.obszar-film-wideo { display: block; width: 100%; max-width: 560px; height: auto; }
/* Ilustracje w wierszach maja juz wlasna ramke, bez drugiej obwodki obrazka. */
.obszar-makieta img.mk-zrzut { border: none; border-radius: 0; max-width: none; background: none; }

/* Co robimy, poprawka: ramka z gradientem otacza sam tekst, a ilustracja
   stoi obok we wlasnej, mniejszej ramce. Wiersz sam nie ma juz tla. */
.obszar.obszar-karta { background: none; border: none; box-shadow: none; padding: var(--space-4) 0; }
@media (min-width: 860px) { .obszar.obszar-karta { padding: var(--space-5) 0; gap: var(--space-6); } }
.obszar-karta .obszar-tresc {
  padding: var(--space-5);
  border: 1px solid transparent; border-radius: var(--radius-lg);
  background:
    radial-gradient(90% 90% at 0% 0%, rgba(76, 124, 255, 0.12), transparent 60%) padding-box,
    radial-gradient(90% 90% at 100% 100%, rgba(155, 92, 246, 0.12), transparent 60%) padding-box,
    linear-gradient(var(--bg-elevated), var(--bg-elevated)) padding-box,
    var(--gradient-brand) border-box;
  box-shadow: 0 30px 70px -45px rgba(124, 140, 255, 0.6);
}
@media (min-width: 860px) { .obszar-karta .obszar-tresc { padding: var(--space-6); } }
.obszar-karta .obszar-tresc > p { max-width: none; }
/* Ramki ilustracji: cienkie, mniejsze niz ramka tekstu. */
.obszar-karta .obszar-ilustracja { max-width: 620px; justify-self: center; width: 100%; }
.obszar-karta .makieta-mini .mk-kadr,
.obszar-karta .wiz-ramka { border-radius: var(--radius-md); box-shadow: 0 24px 60px -40px rgba(76, 124, 255, 0.7); }
.obszar-karta .obszar-ilustracja > img {
  border-radius: var(--radius-md); border: 1px solid transparent;
  background: linear-gradient(#0b0b12, #0b0b12) padding-box, var(--gradient-brand) border-box;
}
.obszar-karta .obszar-film {
  border-radius: var(--radius-md); border: 1px solid transparent; padding: var(--space-3);
  background: linear-gradient(#0b0b12, #0b0b12) padding-box, var(--gradient-brand) border-box;
}

/* Co robimy, poprawka 2: animacje obok tekstu bez ramek i bez tla, wieksze. */
@media (min-width: 860px) {
  .obszar.obszar-karta { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); }
}
.obszar-karta .obszar-ilustracja { max-width: none; }
.obszar-karta .obszar-ilustracja::before { display: none; }
.obszar-karta .makieta-mini { max-width: none; }
.obszar-karta .makieta-mini .mk-kadr {
  background: none; border: none; border-radius: 0; box-shadow: none; overflow: visible;
}
.obszar-karta .makieta-mini .mk-scena { padding: 0; }
.obszar-karta .makieta-mini .mk-scena.mk-scena-telefon { padding: 2% 4%; }
.obszar-karta .makieta-mini .mk-okno { max-width: none; }
.obszar-karta .makieta-mini .mk-okno { font-size: clamp(0.6rem, 0.9vw, 0.82rem); }
.obszar-karta .wiz-ramka { background: none; border: none; box-shadow: none; }
.obszar-karta .wiz-wideo { border-radius: var(--radius-md); }
.obszar-karta .obszar-ilustracja > img { border: none; background: none; max-width: none; }
.obszar-karta .obszar-film { border: none; background: none; padding: 0; }
.obszar-karta .obszar-film-wideo { max-width: 680px; }

/* Co robimy, poprawka 3: wieksze animacje. Lista wiersze wychodzi poza
   kontener (do 1400 px), kolumna animacji jest szersza od tekstu, a w
   wierszach parzystych kolumny sa odwrocone, bo tam animacja stoi z lewej. */
@media (min-width: 1100px) {
  .obszary.obszary-karty {
    width: min(1400px, 100vw - 48px);
    margin-inline: calc((100% - min(1400px, 100vw - 48px)) / 2);
  }
}
@media (min-width: 860px) {
  .obszar.obszar-karta { grid-template-columns: minmax(0, 0.72fr) minmax(0, 1.28fr); }
  .obszar.obszar-karta:nth-child(even) { grid-template-columns: minmax(0, 1.28fr) minmax(0, 0.72fr); }
  /* Panel dostaje najwiecej miejsca: zrzuty sa gesta tresc, male sa nieczytelne. */
  .obszar.obszar-karta.obszar-panel:nth-child(even) { grid-template-columns: minmax(0, 1.55fr) minmax(0, 0.6fr); }
  .obszar.obszar-karta.obszar-panel:nth-child(odd) { grid-template-columns: minmax(0, 0.6fr) minmax(0, 1.55fr); }
}
.obszar-karta .makieta-mini .mk-kadr { aspect-ratio: 16 / 10; }
.obszar-panel .makieta-mini .mk-kadr { aspect-ratio: 16 / 10.4; }
.obszar-panel .mk-okno { box-shadow: 0 40px 90px -40px rgba(76, 124, 255, 0.55), 0 30px 70px -40px rgba(155, 92, 246, 0.5); }
.obszar-karta .makieta-mini .mk-okno { font-size: clamp(0.62rem, 0.95vw, 0.88rem); }

/* Kampanie: kafelki wynikow, slupki i rysujaca sie linia. */
.mk-kpi { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
@media (max-width: 720px) { .mk-kpi { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.mk-kpi .mk-karta { display: flex; flex-direction: column; gap: 2px; }
.mk-kpi b { font-family: var(--font-heading); font-size: 1.45em; }
.mk-zmiana { font-size: 0.85em; font-weight: 700; color: #34d399; }
.mk-slupki { display: flex; align-items: flex-end; gap: 6%; height: 7.5em; padding-top: 6px; }
.mk-slupek {
  position: relative; flex: 1; height: var(--h); border-radius: 6px 6px 2px 2px;
  background: linear-gradient(180deg, #a855f7, #7c8cff 55%, #47d6e8);
  transform-origin: bottom; transform: scaleY(0);
}
.mk-slupek i { position: absolute; bottom: -1.5em; left: 0; right: 0; text-align: center; font-style: normal; font-size: 0.8em; color: #8b8ba3; }
.mk-aktywna .mk-slupek { animation: mk-rosnij 0.7s cubic-bezier(0.16, 1, 0.3, 1) var(--d, 0s) forwards; }
@keyframes mk-rosnij { to { transform: scaleY(1); } }
.mk-slupki { margin-bottom: 1.4em; }
.mk-wykres-karta { position: relative; }
.mk-wykres { display: block; width: 100%; height: 9em; }
.mk-linia { stroke-dasharray: 100; stroke-dashoffset: 100; }
.mk-aktywna .mk-linia { animation: mk-rysuj 1.8s ease-out 0.4s forwards; }
@keyframes mk-rysuj { to { stroke-dashoffset: 0; } }
.mk-pole-wykresu { opacity: 0; }
.mk-aktywna .mk-pole-wykresu { animation: mk-pojaw 0.8s ease 1.6s forwards; }
@keyframes mk-pojaw { to { opacity: 1; } }
.mk-dymek-wykresu {
  position: absolute; right: 12px; top: 10px; padding: 4px 10px; border-radius: 99px;
  background: rgba(52, 211, 153, 0.16); color: #34d399; font-weight: 700; font-size: 0.9em;
}
@media (prefers-reduced-motion: reduce) {
  .mk-slupek { transform: none; }
  .mk-aktywna .mk-slupek, .mk-aktywna .mk-linia, .mk-aktywna .mk-pole-wykresu { animation: none; }
  .mk-linia { stroke-dashoffset: 0; }
  .mk-pole-wykresu { opacity: 1; }
}
/* Linia z vector-effect liczy kreski w pikselach ekranu, wiec zapas dlugosci. */
.mk-linia { stroke-dasharray: 3000; stroke-dashoffset: 3000; }
@media (max-width: 720px) {
  .obszar-karta .makieta-mini .mk-kadr { aspect-ratio: 4 / 5; }
  .obszar-panel .makieta-mini .mk-kadr { aspect-ratio: 16 / 12; }
}

/* Podstrony uslug: w naglowku ta sama animacja co w "Co robimy", bez ramki i tla. */
.usluga-figura.figura-animacja { max-width: none; }
.figura-animacja .makieta-mini .mk-kadr { background: none; border: none; border-radius: 0; overflow: visible; aspect-ratio: 16 / 11; }
.figura-animacja .makieta-mini .mk-scena { padding: 0; }
.figura-animacja .makieta-mini .mk-scena.mk-scena-telefon { padding: 2% 4%; }
.figura-animacja .makieta-mini .mk-okno { max-width: none; font-size: clamp(0.6rem, 0.9vw, 0.84rem); }
.figura-animacja .obszar-film-wideo { max-width: 620px; margin-inline: auto; }
@media (max-width: 720px) { .figura-animacja .makieta-mini .mk-kadr { aspect-ratio: 4 / 5; } }

/* Animacja "Strony, rezerwacje i SEO": strona firmy, kalendarz, Google. */
.mk-strona { padding: 12px 14px; display: flex; flex-direction: column; gap: 10px; position: relative; }
.mk-strona-menu { display: flex; align-items: center; gap: 14px; color: #8b8ba3; }
.mk-strona-menu b { color: #fff; margin-right: auto; font-family: var(--font-heading); }
.mk-strona-cta, .mk-strona-przycisk {
  padding: 4px 12px; border-radius: 99px; color: #08080f; font-weight: 700;
  background: linear-gradient(90deg, #47d6e8, #7c8cff, #a855f7);
}
.mk-strona-hero {
  padding: 16px; border-radius: 12px; display: flex; flex-direction: column; gap: 6px; align-items: flex-start;
  background: radial-gradient(90% 120% at 100% 0%, rgba(168, 85, 247, 0.25), transparent 60%), #171722;
}
.mk-strona-hero h4 { margin: 0; font-family: var(--font-heading); font-size: 1.5em; }
.mk-strona-przycisk { margin-top: 4px; }
.mk-strona-karty { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.mk-toast {
  align-self: flex-end; padding: 8px 12px; border-radius: 10px; font-weight: 600;
  background: rgba(52, 211, 153, 0.16); color: #34d399; border: 1px solid rgba(52, 211, 153, 0.35);
}
.mk-kal-rez { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; margin-top: 6px; text-align: center; }
.mk-kal-rez i { font-style: normal; font-size: 0.8em; color: #8b8ba3; }
.mk-dzien-rez { padding: 4px 0; border-radius: 7px; background: #1b1b28; }
.mk-dzien-zajety { opacity: 0.35; text-decoration: line-through; }
.mk-godziny { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
.mk-godz { padding: 4px 9px; border-radius: 99px; border: 1px solid rgba(255, 255, 255, 0.14); }
.mk-aktywna .mk-dzien-wybrany, .mk-aktywna .mk-godz-wybrana { animation: mk-wybierz 0.35s ease var(--d, 0s) forwards; }
@keyframes mk-wybierz { to { background: linear-gradient(135deg, #47d6e8, #a855f7); color: #08080f; font-weight: 700; border-color: transparent; } }
.mk-potwierdzenie { margin-top: 10px; padding: 8px 10px; border-radius: 10px; background: rgba(52, 211, 153, 0.14); color: #34d399; }
.mk-potwierdzenie b { color: #fff; }
.mk-google { padding: 14px 18px; display: flex; flex-direction: column; gap: 10px; }
.mk-google-pasek { display: flex; align-items: center; gap: 12px; }
.mk-google-pasek b { font-size: 1.4em; font-family: var(--font-heading); }
.mk-google-pole { flex: 1; padding: 7px 14px; border-radius: 99px; background: #1b1b28; border: 1px solid rgba(255, 255, 255, 0.12); min-height: 1.3em; }
.mk-wynik { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border-radius: 10px; }
.mk-wynik small { color: #8b8ba3; }
.mk-wynik b { color: #7da0ff; font-weight: 600; }
.mk-wynik span { color: #b8b8c8; }
.mk-wynik-nasz { background: rgba(124, 140, 255, 0.1); box-shadow: 0 0 0 1px rgba(124, 140, 255, 0.5); }
.mk-gwiazdki { font-style: normal; color: #fbbf24; font-size: 0.9em; }
@media (prefers-reduced-motion: reduce) {
  .mk-dzien-wybrany, .mk-godz-wybrana { background: linear-gradient(135deg, #47d6e8, #a855f7); color: #08080f; }
}

/* Film w naglowku strony glownej: lezy na zdjeciu, ktore zostaje jako zapas. */
.hero-bg .hero-film { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: center 30%; }
@media (prefers-reduced-motion: reduce) { .hero-bg .hero-film { display: none; } }

/* Galeria wizualizacji 3D. */
.wiz-galeria { display: grid; gap: var(--space-4); grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: var(--space-4); }
@media (max-width: 820px) { .wiz-galeria { grid-template-columns: minmax(0, 1fr); } }
.wiz-kafel { margin: 0; }
.wiz-kafel-szeroki { grid-column: 1 / -1; }
.wiz-kafel img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--radius-md); }
.wiz-kafel figcaption { margin-top: 8px; color: var(--fg-muted); font-size: 0.9rem; }

/* Okladki bloga: w artykule i na kartach. */
.artykul-okladka { margin: var(--space-4) 0 var(--space-5); }
.artykul-okladka img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--radius-lg); }
.blog-card-okladka { display: block; width: calc(100% + 2px); height: auto; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--radius-md); margin-bottom: var(--space-3); }

/* Naglowek z fala: plotno na zdjeciu zapasowym. Karta escale wylaczona. */
.hero-bg .hero-fala { position: absolute; inset: 0; width: 100%; height: 100%; display: block; opacity: 0; transition: opacity 1.2s ease; }
.hero-bg .hero-fala.fala-dziala { opacity: 1; }
.browser-3d-wrap[hidden] { display: none !important; }
/* Lzejsze przyciemnienie po prawej, zeby fala swiecila, a tekst po lewej zostal czytelny. */
.hero-z-fala .hero-scrim {
  background:
    linear-gradient(90deg, rgba(5,5,7,0.78) 0%, rgba(5,5,7,0.45) 42%, rgba(5,5,7,0) 70%),
    linear-gradient(180deg, rgba(5,5,7,0) 60%, var(--bg-base) 100%);
}
.hero-z-fala .glow { display: none; }
/* Na telefonie tekst zajmuje cala szerokosc, wiec fala schodzi na drugi plan. */
@media (max-width: 720px) {
  .hero-bg .hero-fala.fala-dziala { opacity: 0.42; }
  .hero-z-fala .hero-scrim { background: linear-gradient(180deg, rgba(5,5,7,0.55) 0%, rgba(5,5,7,0.35) 50%, var(--bg-base) 100%); }
}

/* ========== Wydajnosc ==========
   Sekcje daleko pod ekranem przegladarka pomija przy ukladaniu i malowaniu,
   dopoki sie do nich nie zblizy. Rozmiar zastepczy trzyma pasek przewijania
   w miejscu. */
.obszar-karta, #opinie, #blog, #faq, #proces, .realizacje-section {
  content-visibility: auto;
  contain-intrinsic-size: auto 800px;
}
.mk-kadr { contain: layout paint; }
.hero-bg { contain: paint; }

/* Strzalki w naglowku: tylko przesuwanie i przezroczystosc, ktore liczy karta
   graficzna. Rysowanie kreski (stroke-dashoffset) przemalowywalo je w kazdej
   klatce i to ono je scinalo. */
.rise-arrow { will-change: transform, opacity; }
.rise-arrow path { animation: none; stroke-dashoffset: 0; }
/* Naglowek z filmem fali: film na zdjeciu zapasowym, lzejsze przyciemnienie. */
.hero-bg .hero-film { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: right center; }
/* Pionowa fala na telefonie: kadr od dolu, tam jest wstega. */
.hero-bg .hero-film.hero-film-pion { object-position: center bottom; }
@media (max-width: 720px) { .hero-bg .hero-film { opacity: 0.85; } }
/* Na telefonie naglowek jest wyzszy niz ekran: pionowa fala zajmuje pierwszy
   ekran, a nie cala wysokosc naglowka, zeby wstega byla widoczna od razu. */
.hero-bg .hero-film.hero-film-pion { height: 100vh; height: 100svh; bottom: auto; object-position: center bottom; }
.hero-bg { background: #05060a; }

/* Bardzo szerokie ekrany (21:9 i szersze): film 1080p rozciagniety na cala
   szerokosc bylby powiekszony prawie dwa razy i wygladal na rozmyty. Tu film
   trzyma swoje proporcje 16:9 przy prawej krawedzi, a lewa strona plynnie
   przechodzi w tlo, wiec powiekszenie jest niewielkie. */
@media (min-aspect-ratio: 2/1) {
  .hero-bg .hero-film:not(.hero-film-pion) {
    left: auto; right: 0; width: auto; height: 100%; aspect-ratio: 16 / 9; max-width: 100%;
    object-fit: cover; object-position: right center;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 30%);
    mask-image: linear-gradient(90deg, transparent 0, #000 30%);
  }
}
