/* ==========================================================================
   FRAMEFLOW — Home Page CSS
   Direction "Kadr" (brief pkt 21, etap 2): condensed Archivo headings,
   viewfinder frames for projects without a cover, sharp corners, no
   gradients or blur. Accent only on CTAs and the active menu link.
   Tokens live in fflow.css.
   ========================================================================== */

/* ==========================================================================
   2. PAGE SHELL
   ========================================================================== */
:root {
    --header-h:         72px;
    --header-h-compact: 60px;
    --gutter:           clamp(16px, 4vw, 56px);
    --content-max:      1360px;
    --col-gap:          32px;
}

body {
    font-size: 1.1875rem; /* 19px */
    line-height: 1.55;
}

@media (prefers-reduced-motion: no-preference) {
    html { scroll-behavior: smooth; }

    /* With the motion layer (ruch.js) smooth anchors come from Lenis.
       CSS smooth scrolling must be off there: ScrollTrigger jumps to the
       top and back while it re-measures (language switch, resize), and a
       smooth jump made it measure mid-way, breaking the frames strip. */
    html.motion { scroll-behavior: auto; }
}

/* Anchor jumps land below the fixed header instead of under it. */
main [id] {
    scroll-margin-top: var(--header-h-compact);
}

/* Set by HeaderController while the mobile menu is open. */
body.is-scroll-locked {
    overflow: hidden;
}

.skip-link {
    position: absolute;
    left: var(--gutter);
    top: -100px;
    z-index: 200;
    padding: 10px 16px;
    background: var(--accent);
    color: var(--on-accent);
    font-weight: 600;
}

.skip-link:focus {
    top: 12px;
}

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

/* ==========================================================================
   3. HEADER + MENU
   Fixed rather than sticky: the bar shrinks after scrolling, and a sticky
   element changing height would shift the whole page under it.
   ========================================================================== */
.site-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 100;
    border-bottom: 1px solid transparent;
    transition: background-color 0.3s ease, border-color 0.3s ease;
}

.site-header.is-scrolled,
.site-header.is-menu-open {
    background: var(--bg);
    border-bottom-color: var(--line);
}

.site-header__bar {
    position: relative;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 40px;
    height: var(--header-h);
    max-width: var(--content-max);
    margin: 0 auto;
    padding: 0 var(--gutter);
    transition: height 0.3s ease;
}

.site-header.is-scrolled .site-header__bar {
    height: var(--header-h-compact);
}

.site-header__logo {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 1.25rem;
    font-stretch: 80%;
    font-weight: 700;
    color: var(--text);
}

.site-header__mark {
    width: 26px;
    height: 26px;
    color: var(--text);
}

.site-nav {
    margin-left: auto;
}

.site-nav__list {
    display: flex;
    gap: 32px;
    list-style: none;
}

.site-nav__link {
    display: inline-block;
    padding: 6px 0;
    font-size: 1rem;
    color: var(--body);
    box-shadow: inset 0 -2px 0 transparent;
    transition: color 0.2s ease, box-shadow 0.2s ease;
}

.site-nav__link:hover {
    color: var(--text);
}

/* The only accent in the menu besides the CTA. */
.site-nav__link.is-active {
    color: var(--text);
    box-shadow: inset 0 -2px 0 var(--accent);
}

.site-header__actions {
    display: flex;
    align-items: center;
    gap: 14px;
}

.site-header__lang,
.site-header__menu-btn {
    padding: 7px 10px;
    border: 1px solid var(--line);
    font-size: 0.875rem;
    color: var(--body);
    transition: color 0.2s ease, border-color 0.2s ease;
}

/* The language switch is a link since stage D; buttons have line-height:
   normal, so the link gets the same to keep its old size. */
a.site-header__lang {
    line-height: normal;
}

.site-header__lang:hover,
.site-header__menu-btn:hover {
    color: var(--text);
    border-color: var(--field-line);
}

.site-header__cta {
    padding: 11px 20px;
    background: var(--accent);
    color: var(--on-accent);
    font-size: 0.9375rem;
    font-weight: 600;
}

.site-header__menu-btn {
    display: none;
}

/* Mobile: the nav turns into a full-screen list under the bar.
   Breakpoint must match MENU_QUERY in main.js. */
@media (max-width: 860px) {
    .site-header__actions {
        margin-left: auto;
    }

    .site-header__menu-btn {
        display: inline-block;
    }

    .site-nav {
        position: fixed;
        inset: 0;
        z-index: 1;
        margin: 0;
        padding: calc(var(--header-h) + 24px) var(--gutter) 40px;
        overflow-y: auto;
        background: var(--bg);
        visibility: hidden;
        opacity: 0;
        transition: opacity 0.2s ease, visibility 0.2s;
    }

    .site-header.is-menu-open .site-nav {
        visibility: visible;
        opacity: 1;
    }

    .site-nav__list {
        flex-direction: column;
        gap: 0;
    }

    .site-nav__link {
        display: block;
        padding: 12px 0;
        border-bottom: 1px solid var(--line);
        font-size: 2.75rem;
        font-stretch: var(--condensed);
        font-weight: 800;
        line-height: 1;
        color: var(--text);
    }

    .site-nav__link.is-active {
        box-shadow: inset 4px 0 0 var(--accent);
        padding-left: 16px;
    }
}

@media (max-width: 480px) {
    /* "Kontakt" in the menu leads to the same place; the bar stays usable at 360px. */
    .site-header__cta {
        display: none;
    }
}

/* ==========================================================================
   4. HERO
   ========================================================================== */
.hero {
    position: relative;
    display: flex;
    align-items: flex-end;
    min-height: 100vh;
    min-height: 100svh;
    overflow: hidden;
}

.hero__canvas {
    position: absolute;
    inset: 0;
}

.ff-canvas {
    position: absolute;
    top: 0;
    left: 0;
    display: block;
}

/* Glow layer of the particle logo: blurred copy of the main canvas
   (filter on the canvas itself, not a backdrop blur). */
.ff-canvas--glow {
    width: 100%;
    height: 100%;
    z-index: 1;
    filter: blur(3px)
            drop-shadow(0 0 5px rgba(255, 230, 0, 1.0))
            drop-shadow(0 0 15px rgba(255, 90, 0, 0.85))
            drop-shadow(0 0 40px rgba(255, 30, 0, 0.5));
    opacity: 0.95;
    mix-blend-mode: screen;
    will-change: filter, transform;
}

/* Static stand-in for the canvas: no JS, phones, reduced motion. */
.hero__mark {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding-right: 8vw;
    pointer-events: none;
}

.hero__mark-svg {
    width: min(56vh, 480px);
    height: auto;
    color: #3A322A;
}

.hero--live .hero__mark {
    display: none;
}

.hero__content {
    position: relative;
    z-index: 3;
    width: 100%;
    max-width: var(--content-max);
    margin: 0 auto;
    padding: 0 var(--gutter) clamp(48px, 9vh, 96px);
}

.hero__title {
    max-width: 11ch;
    font-size: clamp(3.5rem, 9vw, 9.25rem);
    font-stretch: var(--condensed);
    font-weight: 800;
    line-height: 0.88;
    color: var(--text);
    /* Dark halo so the title stays readable over the particles. */
    text-shadow: 0 0 18px rgba(15, 12, 10, 0.95),
                 0 0 44px rgba(15, 12, 10, 0.85);
}

/* Optional one-sentence subtitle (HomePage.hero_subtitle); not animated. */
.hero__lead {
    max-width: 46ch;
    margin-top: 24px;
    font-size: clamp(1.125rem, 1.6vw, 1.375rem);
    line-height: 1.45;
    color: var(--body);
    text-shadow: 0 0 16px rgba(15, 12, 10, 0.95);
}

.hero__scroll {
    margin-top: 32px;
}

/* ==========================================================================
   5. STATS BAND — big condensed numbers between vertical rules
   ========================================================================== */
.stats {
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}

.stats__row {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    max-width: var(--content-max);
    margin: 0 auto;
    padding: 0 var(--gutter);
}

.stat {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 36px 0 32px 28px;
    border-left: 1px solid var(--line);
}

.stat:first-child {
    padding-left: 0;
    border-left: 0;
}

.stat__value {
    font-size: clamp(3.5rem, 7vw, 6rem);
    font-stretch: 62%;
    font-weight: 700;
    line-height: 0.9;
    /* equal-width digits: the rolling counter (ruch.js) keeps the width */
    font-variant-numeric: tabular-nums;
    color: var(--text);
}

/* Rolling counter (ruch.js): one masked strip per digit. Each cell is one
   line high, so the band is exactly as tall as with plain text. */
.odo {
    display: inline-block;
    height: 0.9em;
    overflow: hidden;
    vertical-align: top;
}

.odo__strip {
    display: block;
}

.odo__strip > span {
    display: block;
    height: 0.9em;
    line-height: 0.9;
}

.stat__label {
    font-size: 1rem;
    color: var(--body);
}

/* ==========================================================================
   6. SECTIONS — shared label / title / lead; each section sets its own rhythm
   ========================================================================== */
.section {
    padding: clamp(96px, 14vh, 150px) 0;
}

.section__inner {
    max-width: var(--content-max);
    margin: 0 auto;
    padding: 0 var(--gutter);
}

/* The only monospace on the page besides "Kadr NN": small section labels. */
.section__label {
    margin-bottom: 24px;
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    color: var(--quiet);
}

.section__title {
    margin-bottom: 24px;
    font-size: clamp(3rem, 6.4vw, 6.5rem);
    font-stretch: var(--condensed);
    font-weight: 800;
    line-height: 0.9;
    color: var(--text);
}

.section__lead {
    max-width: 44ch;
    font-size: clamp(1.125rem, 1.5vw, 1.3125rem);
    line-height: 1.5;
    color: var(--body);
}

/* ==========================================================================
   7. REALIZACJE — chapters: frame on 8 columns bleeding to the page edge,
   text on 4, sides alternate
   ========================================================================== */
.chapters {
    margin-top: clamp(64px, 9vh, 104px);
}

.chapters__track {
    display: flex;
    flex-direction: column;
    gap: clamp(96px, 14vh, 150px);
}

/* The text side lines up with the menu/content grid at every width
   (same box as .site-header__bar: --content-max wide, --gutter inside);
   the frame side bleeds to the page edge. % padding is relative to the
   .chapters width. */
.chapter {
    --grid-edge: max(var(--gutter), calc((100% - var(--content-max)) / 2 + var(--gutter)));
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--col-gap);
    align-items: end;
    padding-right: var(--grid-edge);
}

.chapter__media {
    grid-column: 1 / 9;
    grid-row: 1;
}

.chapter__text {
    grid-column: 9 / 13;
    grid-row: 1;
    padding-bottom: 4px;
}

.chapter--flip {
    padding-right: 0;
    padding-left: var(--grid-edge);
}

/* Very wide screens: the frame stays inside the grid too, instead of
   running far past the menu. */
@media (min-width: 1801px) {
    .chapter,
    .chapter--flip {
        padding-left: var(--grid-edge);
        padding-right: var(--grid-edge);
    }

    .chapter .chapter__no {
        padding-left: 0;
    }
}

.chapter--flip .chapter__media { grid-column: 5 / 13; }
.chapter--flip .chapter__text  { grid-column: 1 / 5; }

/* "Kadr 01" — frame number above the frame, like on a film strip. */
.chapter__no {
    margin-bottom: 12px;
    padding-left: var(--gutter);
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    color: var(--quiet);
}

.chapter--flip .chapter__no {
    padding-left: 0;
}

/* No cover yet: a designed typographic frame, not an empty box. */
.chapter__frame {
    position: relative;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    background: var(--frame);
    container-type: inline-size;
}

/* Viewfinder corners. Kept on top (z-index) so a later background
   layer inside the frame (pkt 21, krok 2b) sits under them. */
.vf {
    position: absolute;
    z-index: 2;
    width: 28px;
    height: 28px;
    border: 0 solid rgba(243, 236, 224, 0.4);
}

.vf--tl { top: 22px;    left: 22px;  border-top-width: 1px;    border-left-width: 1px; }
.vf--tr { top: 22px;    right: 22px; border-top-width: 1px;    border-right-width: 1px; }
.vf--bl { bottom: 22px; left: 22px;  border-bottom-width: 1px; border-left-width: 1px; }
.vf--br { bottom: 22px; right: 22px; border-bottom-width: 1px; border-right-width: 1px; }

/* Name on the left ~half; the result (only when "Pokaż wynik w kadrze" is
   ticked) sits above it. Both share one clean zone on the left, the
   character motif (§7b) fills the right part. */
.chapter__frame-name {
    position: absolute;
    z-index: 1;
    left: 7cqi;
    bottom: 8cqi;
    max-width: 52cqi;
    font-size: clamp(2.5rem, 9cqi, 7.5rem);
    font-stretch: 62%;
    font-weight: 800;
    line-height: 0.86;
    color: var(--text);
}

.chapter__frame-stat {
    position: absolute;
    z-index: 1;
    left: 7cqi;
    /* --frame-name-h is set by frame-motif.js; without JS the result
       still sits just above the bottom-anchored name block. */
    bottom: calc(8cqi + var(--frame-name-h, 2.2em) + 2.5cqi);
    font-size: clamp(1.75rem, 5.2cqi, 4rem);
    font-stretch: 62%;
    font-weight: 600;
    line-height: 1;
    color: var(--body);
}

/* --- 7a. Frame with a cover: the photo fills the frame; the name and
   the result stay on top of it, on a dark band rising from the bottom
   (a photographic scrim, not a colour gradient) that keeps them readable
   on bright pictures. Layers: photo, motif canvas (§7b, while the frame
   develops), band, name/result (1), corners (2), AF (3). --- */
.chapter__img {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: var(--cover-dim);
}

.chapter__shade {
    position: absolute;
    inset: 0;
    z-index: 0;
    background: linear-gradient(to top,
        rgba(14, 11, 8, 0.86) 0%,
        rgba(14, 11, 8, 0.62) 24%,
        rgba(14, 11, 8, 0.18) 52%,
        rgba(14, 11, 8, 0) 70%);
    box-shadow: var(--cover-edge);
    pointer-events: none;
}

.chapter__frame--cover .chapter__frame-name {
    text-shadow: 0 1px 24px rgba(14, 11, 8, 0.45);
}

.chapter__frame--cover .chapter__frame-stat {
    color: var(--text);
}

/* Corners and the AF tag over a photo: full paper colour with a thin
   dark halo, so they read on white sky and on black alike. */
.chapter__frame--cover .vf {
    border-color: rgba(243, 236, 224, 0.9);
    filter: drop-shadow(0 0 1px rgba(14, 11, 8, 0.85)) drop-shadow(0 0 5px rgba(14, 11, 8, 0.5));
}

.chapter__frame--cover .af {
    background: rgba(14, 11, 8, 0.62);
    filter: drop-shadow(0 0 1px rgba(14, 11, 8, 0.85));
}

/* --- 7b. Character motif (frame-motif.js, krok 2b). No cover: it fills
   the right part of the frame. With a cover: it covers the photo until
   the frame comes into view, builds the picture out of characters and
   dissolves into it, then the canvas is removed. It lies under the
   name/result (z-index 1) and corners (2). --- */
.frame-motif {
    position: absolute;
    inset: 0;
    z-index: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

.chapter__meta {
    margin-bottom: 12px;
    font-size: 0.9375rem;
    color: var(--quiet);
}

.chapter__title {
    margin-bottom: 16px;
    font-size: clamp(1.75rem, 2.4vw, 2.25rem);
    font-stretch: 72%;
    font-weight: 700;
    line-height: 1.02;
    color: var(--text);
}

.chapter__desc {
    margin-bottom: 16px;
    font-size: 1.125rem;
    color: var(--body);
}

.chapter__result {
    margin-bottom: 14px;
    font-size: 1rem;
    color: var(--body);
}

.chapter__result strong {
    font-weight: 600;
    color: var(--text);
}

.chapter__tags {
    margin-bottom: 22px;
    font-size: 0.9375rem;
    color: var(--quiet);
}

/* Three levels of action (ruch v2):
   1. main: yellow, filled: only "Wyceń projekt" and the form's send button;
   2. secondary: outline + arrow: "Zobacz realizację", "Więcej informacji",
      "Zobacz realizacje" in the hero. On hover (ruch.js) they fill from
      the cursor and the arrow moves right;
   3. links inside text: an underline drawn from the left (§17). */
.hero__scroll,
.chapter__more,
.service__more,
.contact-calendar {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    padding: 10px 16px 9px;
    border: 1px solid var(--field-line);
    font-size: 1rem;
    line-height: 1.2;
    color: var(--text);
    transition: border-color 0.2s ease;
}

.hero__scroll:hover,
.chapter__more:hover,
.service__more:hover,
.contact-calendar:hover {
    border-color: var(--text);
}

/* Link to a project's case study page (panel D1), under "Zobacz realizację" */
.chapter__case {
    display: block;
    width: fit-content;
    margin-top: 16px;
    padding-bottom: 3px;
    font-size: 0.9375rem;
    border-bottom: 1px solid var(--field-line);
    color: var(--text);
}

.chapter__case:hover {
    border-bottom-color: var(--text);
}

/* Links under a project (ProjectBlock.links, e.g. credits on IMDb): plain
   underlined text links in a row, next to the frame and in the window. */
.project-links {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 20px;
    margin: 0 0 22px;
    padding: 0;
    list-style: none;
    font-size: 0.9375rem;
}

.project-links a {
    display: inline-block;
    padding-bottom: 3px;
    border-bottom: 1px solid var(--field-line);
    color: var(--text);
    transition: border-color 0.2s ease;
}

.project-links a:hover,
.project-links a:focus-visible {
    border-bottom-color: var(--text);
}

.project-links__out {
    margin-left: 6px;
    color: var(--quiet);
}

.card-modal__links {
    margin: 20px 0 8px;
}

.btn-arrow {
    display: inline-block;
    transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.1, 1);
}

.hero__scroll:hover .btn-arrow,
.chapter__more:hover .btn-arrow,
.service__more:hover .btn-arrow,
.hero__scroll:focus-visible .btn-arrow,
.chapter__more:focus-visible .btn-arrow,
.service__more:focus-visible .btn-arrow,
.contact-calendar:hover .btn-arrow,
.contact-calendar:focus-visible .btn-arrow {
    transform: translate3d(5px, 0, 0);
}

/* "Umów rozmowę": second-level action under the e-mail, only when
   Ustawienia → Integracje has a calendar link (panel stage B). */
.contact-calendar {
    margin-top: 24px;
}

.contact-layout__info .contact-calendar {
    display: flex;
    width: fit-content;
}

/* ==========================================================================
   8. USŁUGI — numbered list with hairlines: number + name | description
   ========================================================================== */
.services-layout {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--col-gap);
    align-items: start;
}

.services-layout__intro {
    grid-column: 1 / 5;
    position: sticky;
    top: calc(var(--header-h-compact) + 32px);
}

.services-layout .section__title {
    font-size: clamp(3rem, 5.4vw, 5.5rem);
}

.services-list {
    position: relative;
    grid-column: 5 / 13;
    list-style: none;
}

.service {
    position: relative;
    display: grid;
    grid-template-columns: 5fr 6fr;
    gap: var(--col-gap);
    padding: 30px 0 34px;
}

/* --- 8a. Service number (service-number.js): the number of the
   hovered / centred service made of characters, like the footer's
   "FrameFlow", under the intro in the sticky column. No frame, no
   background: just the characters. Wide screens only. --- */
.svc-mark {
    width: 100%;
    height: min(40vh, 340px);
    margin-top: 32px;
}

.svc-mark {
    position: relative;
}

.svc-mark canvas,
.svc-mark img {
    display: block;
    width: 100%;
    height: 100%;
}

.svc-mark img {
    position: absolute;
    inset: 0;
    display: none;
    height: auto;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    filter: var(--cover-dim);
}

.svc-mark.has-img img { display: block; }
.svc-mark.has-img canvas { visibility: hidden; }

@media (max-width: 820px) {
    .svc-mark {
        display: none;
    }
}

/* Hairlines as pseudo-elements (not borders), so §15 can draw them
   from the left; without the motion layer they look like plain borders. */
.service::before,
.services-list::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    height: 1px;
    background: var(--line);
    transform-origin: left center;
}

.service::before { top: 0; }
.services-list::after { bottom: 0; }

.service__head {
    display: flex;
    gap: 20px;
    align-items: baseline;
}

.service__num {
    min-width: 24px;
    font-size: 1rem;
    color: var(--quiet);
    font-variant-numeric: tabular-nums;
}

.service__name {
    font-size: clamp(1.5rem, 2.2vw, 2rem);
    font-stretch: 72%;
    font-weight: 700;
    line-height: 1.05;
    color: var(--text);
}

.service__desc {
    font-size: 1.125rem;
    color: var(--body);
}

.service__more {
    margin-top: 16px;
}

/* ==========================================================================
   9. O MNIE — text layout without a photo, photo + text with one
   ========================================================================== */
.about {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--col-gap);
    row-gap: 40px;
    margin-top: 56px;
}

.about__lead {
    grid-column: 1 / 11;
    font-size: clamp(2.5rem, 4.8vw, 4.75rem);
    font-stretch: 72%;
    font-weight: 700;
    line-height: 1;
    color: var(--text);
}

.about__body {
    grid-column: 5 / 11;
    display: flex;
    flex-direction: column;
    gap: 18px;
    font-size: 1.1875rem;
    color: var(--body);
}

.about__richtext p + p {
    margin-top: 18px;
}

.about__quote {
    margin-top: 12px;
    padding-left: 20px;
    border-left: 1px solid var(--field-line);
    font-size: 1.25rem;
    color: var(--text);
}

.about--photo .about__photo {
    grid-column: 1 / 6;
    grid-row: 1 / span 2;
}

.about__photo img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 5;
    object-fit: cover;
    background: var(--frame);
}

.about--photo .about__lead {
    grid-column: 7 / 13;
    font-size: clamp(2.25rem, 3.6vw, 3.5rem);
}

.about--photo .about__body {
    grid-column: 7 / 12;
}

/* ==========================================================================
   10. KONTAKT — very large heading, info + form below
   ========================================================================== */
.contact-title {
    font-size: clamp(4.5rem, 13vw, 12rem);
    font-stretch: 62%;
    font-weight: 800;
    line-height: 0.85;
    color: var(--text);
}

.contact-layout {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--col-gap);
    margin-top: 56px;
}

.contact-layout__info { grid-column: 1 / 5; }
.contact-layout__form { grid-column: 6 / 13; }

.contact-mail {
    display: inline-block;
    margin-top: 28px;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--field-line);
    font-size: clamp(1.375rem, 2vw, 1.75rem);
    color: var(--text);
}

.contact-mail:hover {
    border-bottom-color: var(--text);
}

/* --- Form: fields are a label + one underline (3.5:1 against the bg) --- */
.contact-form {
    display: flex;
    flex-direction: column;
    gap: 28px;
}

.hp-field {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--col-gap);
}

.form-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.form-label {
    font-size: 0.9375rem;
    color: var(--body);
}

.form-input {
    width: 100%;
    padding: 10px 0;
    border: 0;
    border-bottom: 1px solid var(--field-line);
    border-radius: 0;
    background: transparent;
    font-family: var(--font-main);
    font-size: 1.125rem;
    color: var(--text);
    outline: none;
    appearance: none;
    transition: border-color 0.2s ease;
}

.form-input::placeholder {
    color: var(--quiet);
}

.form-input:focus,
.form-input:focus-visible {
    border-bottom-color: var(--text);
    box-shadow: 0 1px 0 0 var(--text);
}

.form-textarea {
    min-height: 120px;
    resize: vertical;
    line-height: 1.55;
}

.form-select {
    padding-right: 28px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M6 8L0 0h12z' fill='%23CBC2B4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 4px center;
    cursor: pointer;
}

/* Native popup can't be fully styled; background/color on <option> are honored. */
.form-select option {
    background-color: var(--frame);
    color: var(--text);
}

/* Chromium 135+: real CSS for the popup (appearance: base-select). */
@supports selector(::picker(select)) {
    .form-select,
    .form-select::picker(select) {
        appearance: base-select;
    }

    .form-select::picker(select) {
        margin-top: 4px;
        padding: 4px;
        background: var(--frame);
        border: 1px solid var(--field-line);
    }

    .form-select option {
        padding: 10px 12px;
        background: transparent;
        font-family: var(--font-main);
        font-size: 1rem;
        cursor: pointer;
    }

    .form-select option:is(:hover, :focus) {
        background: var(--line);
    }

    .form-select option:checked {
        background: var(--text);
        color: var(--bg);
    }

    .form-select option::checkmark,
    .form-select::picker-icon {
        display: none;
    }
}

.form-submit {
    align-self: flex-start;
    padding: 16px 32px;
    background: var(--accent);
    color: var(--on-accent);
    font-size: 1.0625rem;
    font-weight: 600;
}

.form-submit:disabled {
    opacity: 0.6;
    cursor: progress;
}

.form-success,
.form-error {
    padding: 12px 16px;
    border-left: 2px solid var(--text);
    font-size: 1rem;
    color: var(--text);
}

.form-error {
    border-left-color: #E5533D;
}

/* ==========================================================================
   11. FOOTER
   ========================================================================== */
.site-footer {
    border-top: 1px solid var(--line);
}

.site-footer__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 20px 40px;
    max-width: var(--content-max);
    margin: 0 auto;
    padding: 40px var(--gutter) 48px;
    font-size: 0.9375rem;
    color: var(--quiet);
}

.site-footer__list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 24px;
    list-style: none;
}

.site-footer a,
.site-footer__btn {
    color: var(--body);
    font-size: inherit;
}

.site-footer a:hover,
.site-footer__btn:hover {
    color: var(--text);
}

/* ==========================================================================
   12. CARD DETAIL MODAL (Realizacje + Usługi details)
   Solid panel, no backdrop blur.
   ========================================================================== */
.card-modal {
    position: fixed;
    inset: 0;
    z-index: 10000; /* above the fixed header (100) */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 0.2s ease, visibility 0.2s;
}

.card-modal.is-active {
    opacity: 1;
    visibility: visible;
    pointer-events: all;
}

.card-modal__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 12, 10, 0.88);
}

.card-modal__panel {
    position: relative;
    width: 100%;
    max-width: 720px;
    max-height: 86vh;
    overflow-y: auto;
    padding: clamp(28px, 5vw, 48px);
    background: var(--bg);
    border: 1px solid var(--field-line);
}

.card-modal__close {
    position: absolute;
    top: 14px;
    right: 14px;
    width: 40px;
    height: 40px;
    border: 1px solid var(--line);
    font-size: 1.5rem;
    line-height: 1;
    color: var(--text);
}

.card-modal__close:hover {
    border-color: var(--field-line);
}

.card-modal__cover-frame {
    position: relative;
    margin-bottom: 24px;
}

.card-modal__cover {
    display: block;
    width: 100%;
    height: auto;
    filter: var(--cover-dim);
}

.card-modal__cover-frame::after {
    content: '';
    position: absolute;
    inset: 0;
    box-shadow: var(--cover-edge);
    pointer-events: none;
}

.card-modal__meta {
    margin-bottom: 10px;
    padding-right: 48px;
    font-size: 0.9375rem;
    color: var(--quiet);
}

.card-modal__title {
    margin-bottom: 20px;
    padding-right: 48px;
    font-size: clamp(2rem, 4vw, 2.75rem);
    font-stretch: 72%;
    font-weight: 700;
    line-height: 1.02;
    color: var(--text);
}

.card-modal__lead {
    margin-bottom: 20px;
    font-size: 1.125rem;
    color: var(--body);
    white-space: pre-line;
}

.card-modal__case {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 10px 20px;
    margin-bottom: 24px;
    padding: 18px 0;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}

.card-modal__case dt {
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
}

.card-modal__case dd {
    font-size: 1rem;
    color: var(--body);
}

.card-modal__result {
    margin-bottom: 24px;
}

.card-modal__result-value {
    display: block;
    margin-bottom: 6px;
    font-size: clamp(2.5rem, 6vw, 3.5rem);
    font-stretch: 62%;
    font-weight: 700;
    line-height: 1;
    color: var(--text);
}

.card-modal__result-desc {
    font-size: 1rem;
    color: var(--body);
}

/* Before/after bars from the result_chart tag — neutral, no accent. */
.card-modal__result-chart {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 16px;
}

.result-chart__row {
    display: grid;
    grid-template-columns: 56px 1fr;
    align-items: center;
    gap: 12px;
}

.result-chart__label {
    font-size: 0.875rem;
    color: var(--quiet);
}

.result-chart__track {
    height: 8px;
    background: var(--line);
}

.result-chart__bar {
    width: 0;
    height: 100%;
    animation: ff-bar-grow 0.9s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.result-chart__bar--before {
    background: var(--quiet);
    animation-delay: 0.05s;
}

.result-chart__bar--after {
    background: var(--text);
    animation-delay: 0.2s;
}

@keyframes ff-bar-grow {
    from { width: 0; }
    to   { width: var(--bar-width); }
}

.card-modal__richtext {
    font-size: 1.0625rem;
    color: var(--body);
}

.card-modal__richtext p,
.card-modal__richtext ul,
.card-modal__richtext ol {
    margin-bottom: 14px;
}

.card-modal__richtext ul,
.card-modal__richtext ol {
    padding-left: 20px;
}

.card-modal__video-link {
    display: inline-block;
    margin-top: 8px;
    padding-bottom: 3px;
    border-bottom: 1px solid var(--text);
    color: var(--text);
}

/* Video facade (pkt 2a): poster + play button, iframe only after the click. */
.video-facade {
    position: relative;
    aspect-ratio: 16 / 9;
    margin-top: 12px;
    overflow: hidden;
    background: var(--frame);
}

.video-facade__poster {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.video-facade__poster--frame {
    background: var(--frame);
}

.video-facade__play {
    position: absolute;
    left: 50%;
    top: 50%;
    z-index: 3;
    display: inline-flex;
    align-items: center;
    gap: 12px;
    padding: 14px 22px;
    transform: translate(-50%, -50%);
    background: var(--bg);
    border: 1px solid var(--field-line);
    font-size: 1rem;
    color: var(--text);
    white-space: nowrap;
}

.video-facade__play:hover {
    border-color: var(--text);
}

.video-facade__icon {
    width: 0;
    height: 0;
    border-top: 7px solid transparent;
    border-bottom: 7px solid transparent;
    border-left: 12px solid currentColor;
}

.card-modal__video-embed {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    margin-top: 12px;
    background: #000;
}

.card-modal__video-embed iframe,
.card-modal__video-embed video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* ==========================================================================
   13. NARROW SCREENS
   ========================================================================== */
@media (max-width: 820px) {
    body {
        font-size: 1.125rem;
    }

    /* Mark above the headline instead of behind it. */
    .hero__mark {
        align-items: flex-start;
        justify-content: center;
        padding: calc(var(--header-h) + 32px) var(--gutter) 0;
    }

    .hero__mark-svg {
        width: min(64vw, 38vh);
    }

    /* Up to 3 figures: one per row (value beside its label), so a third
       one never ends up alone in a half-empty grid row. */
    .stats__row {
        grid-template-columns: 1fr;
    }

    .stat,
    .stat:first-child {
        display: grid;
        grid-template-columns: 46% 1fr;
        align-items: baseline;
        gap: 16px;
        padding: 20px 0;
        border-left: 0;
    }

    .stat + .stat {
        border-top: 1px solid var(--line);
    }

    .stat__value {
        font-size: 2.75rem;
    }

    /* Chapters: frame full width, text under it. */
    .chapter,
    .chapter--flip {
        display: block;
        padding: 0;
    }

    .chapter__no,
    .chapter--flip .chapter__no {
        padding-left: var(--gutter);
    }

    .chapter__text {
        padding: 24px var(--gutter) 0;
    }

    .vf { width: 16px; height: 16px; }
    .vf--tl, .vf--tr { top: 14px; }
    .vf--bl, .vf--br { bottom: 14px; }
    .vf--tl, .vf--bl { left: 14px; }
    .vf--tr, .vf--br { right: 14px; }

    .services-layout,
    .about,
    .contact-layout {
        display: block;
    }

    .services-layout__intro {
        position: static;
    }

    .services-list {
        margin-top: 48px;
    }

    .service {
        grid-template-columns: 1fr;
        gap: 12px;
    }

    .about__photo {
        margin-bottom: 32px;
    }

    .about__body {
        margin-top: 28px;
    }

    .contact-layout__form {
        margin-top: 48px;
    }

    .form-row {
        grid-template-columns: 1fr;
        gap: 28px;
    }

    .card-modal {
        padding: 12px;
    }

    .card-modal__panel {
        max-height: 90vh;
    }
}

/* ==========================================================================
   15. HEADLINE REVEALS (motion.js, paczka 2)
   Start states apply only under html.motion (set by an inline head script,
   never with prefers-reduced-motion), so without JS everything is visible.
   Only transform and opacity are animated; each headline once.
   ========================================================================== */
.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;
}

/* Headlines: hidden until split into lines (needs the web font), then
   each line slides up from under its mask. */
.motion [data-reveal="lines"]:not(.is-split) {
    opacity: 0;
}

/* The split part is a flex column so the masks' negative margins don't
   collapse between siblings: each line then takes exactly its line-height,
   the same as the plain text before and after the reveal (no layout shift). */
.mlines {
    display: flex;
    flex-direction: column;
}

.mline {
    display: block;
    overflow: hidden;
    /* room for Polish accents and descenders inside the mask */
    padding: 0.12em 0 0.14em;
    margin: -0.12em 0 -0.14em;
}

/* block (not inline-block): an inline-block sits on the baseline and makes
   each masked line a little taller than the plain text, which shifted the
   bottom-anchored hero when the lines were split and restored (CLS). */
.mline__inner {
    display: block;
    transform: translate3d(0, 110%, 0);
    transition: transform 0.7s cubic-bezier(0.2, 0.7, 0.1, 1);
}

.is-in .mline__inner {
    transform: none;
}

/* ==========================================================================
   16. RUCH V2 (ruch.js): camera HUD, frames strip ("taśma"), AF lock
   Everything here exists only once ruch.js has run (HUD is [hidden] until
   then, .tape is a class it sets), so no JS / reduced motion = none of it.
   ========================================================================== */

/* --- Lenis (smooth mouse wheel, ruch.js): its stylesheet, inlined. --- */
html.lenis,
html.lenis body {
    height: auto;
}

.lenis:not(.lenis-autoToggle).lenis-stopped {
    overflow: clip;
}

.lenis [data-lenis-prevent] {
    overscroll-behavior: contain;
}

.lenis.lenis-smooth iframe {
    pointer-events: none;
}

/* --- HUD: a small camera readout in the menu bar, right after the logo,
   so it never covers any text. Not a control (pointer-events: none),
   hidden from screen readers. Narrower bars drop parts of it (below). --- */
.hud {
    display: flex;
    align-items: center;
    gap: 14px;
    font-family: var(--font-mono);
    font-size: 0.75rem;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    color: var(--quiet);
    pointer-events: none;
}

.hud[hidden] {
    display: none;
}

.hud__rec {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

/* Hollow while standing by, filled red while recording (= scrolling). */
.hud__dot {
    position: relative;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    box-shadow: inset 0 0 0 1px var(--quiet);
}

.hud__dot::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: var(--rec);
    opacity: 0;
    transition: opacity 0.15s ease;
}

.hud.is-rec .hud__dot::after {
    opacity: 1;
}

.hud__state {
    min-width: 4ch; /* REC and STBY take the same room */
}

.hud.is-rec .hud__state,
.hud__tc,
.hud__frame {
    color: var(--body);
}

.hud__frame {
    display: none;
}

.hud.is-work .hud__frame {
    display: inline;
}

/* --- Frames strip (desktop): the page stops for a short stretch and
   the track slides left. .chapters gets the height of that stretch from
   ruch.js; the track is sticky inside it (no JS pinning, no jump). Every
   project is one panel: frame + its text beside it; the panels arrive at
   the centre one by one. --- */
.tape .chapters {
    margin-top: clamp(32px, 5vh, 64px);
    overflow-x: clip; /* not hidden: that would make a scroll box and break sticky */
}

.tape .chapters__track {
    /* frame as large as the screen height allows, max 56% of the width */
    --frame-w: min(56vw, calc((100vh - var(--header-h-compact) - 150px) * 16 / 9));
    --text-w: 320px;
    --panel-gap: 40px;
    --panel-w: calc(var(--frame-w) + var(--panel-gap) + var(--text-w));
    position: sticky;
    top: 0;
    flex-direction: row;
    align-items: flex-end;
    gap: 5vw;
    width: max-content;
    height: 100vh;
    /* sides: first and last panel can both sit in the middle of the
       screen; bottom: "Kadr NN" + frame centred under the menu bar, so
       every frame stops at the same height whatever its text */
    padding: 0 calc((100% - var(--panel-w)) / 2)
             calc((100vh - var(--header-h-compact) - var(--frame-w) * 9 / 16 - 34px) / 2);
    will-change: transform;
}

.tape .chapter,
.tape .chapter--flip {
    display: flex;
    flex: none;
    align-items: flex-end;
    gap: var(--panel-gap);
    width: var(--panel-w);
    padding: 0;
}

.tape .chapter__media {
    position: relative;
    flex: none;
    width: var(--frame-w);
}

.tape .chapter__text {
    flex: none;
    width: var(--text-w);
    padding-bottom: 0;
}

.tape .chapter__no,
.tape .chapter--flip .chapter__no {
    padding-left: 0;
}

/* --- AF lock: corners turn full white, a short flash, "AF" blinks
   twice and stays while the frame is centred. --- */
.tape .vf::after {
    content: '';
    position: absolute;
    inset: -1px;
    border-style: solid;
    border-color: var(--text);
    border-width: inherit;
    opacity: 0;
    transition: opacity 0.2s ease;
}

.tape .is-af .vf::after {
    opacity: 1;
}

.tape .chapter__frame::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 2;
    background: var(--text);
    opacity: 0;
    pointer-events: none;
}

.tape .chapter__frame.is-af::after {
    animation: af-flash 0.45s ease-out 1;
}

.af {
    position: absolute;
    z-index: 3;
    top: 22px;
    left: 62px;
    padding: 3px 5px 2px;
    border: 1px solid var(--text);
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    line-height: 1;
    color: var(--text);
    opacity: 0;
    transition: opacity 0.2s ease;
}

.is-af .af {
    opacity: 1;
    animation: af-blink 0.36s steps(1, end) 1;
}

@keyframes af-flash {
    0%   { opacity: 0.07; }
    100% { opacity: 0; }
}

@keyframes af-blink {
    0%   { opacity: 1; }
    25%  { opacity: 0; }
    50%  { opacity: 1; }
    75%  { opacity: 0; }
    100% { opacity: 1; }
}

/* --- Narrower bars: the HUD gives up the REC/STBY word and the hours
   first, then (with the mobile menu) the frame counter replaces the
   timecode while Realizacje is on screen. --- */
@media (max-width: 1279px) {
    .hud__state,
    .hud__hh {
        display: none;
    }

    .hud {
        gap: 10px;
    }
}

@media (max-width: 1099px) {
    .hud.is-work .hud__tc {
        display: none;
    }
}

@media (max-width: 860px) {
    .site-header__bar {
        gap: 12px;
    }

    .hud {
        margin: 0 auto;
        gap: 8px;
        font-size: 0.6875rem;
    }

    .site-header.is-menu-open .hud {
        display: none;
    }
}

/* ==========================================================================
   17. RUCH V2, step 2: services strip, service preview, cursor,
   micro-interactions, footer mark. Like §16: the moving parts only exist
   once ruch.js / footer-mark.js have run; without them it is still text.
   ========================================================================== */

/* --- 5. Services strip between Realizacje and Usługi. Without JS one
   still line, cut at the right edge. --- */
.marquee {
    overflow: hidden;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}

.marquee__track {
    display: flex;
    width: max-content;
    will-change: transform;
}

.marquee__group {
    flex: none;
    padding: 0.14em 0 0.08em;
    font-size: clamp(3rem, 8vw, 7.5rem);
    font-stretch: 62%;
    font-weight: 800;
    line-height: 1;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--text);
}

.marquee__group b {
    padding: 0 0.32em;
    font-weight: 400;
    color: var(--quiet);
}

/* --- 6. Services: the hovered row opens up (its name moves away from the
   number), the others step back; a frame follows the cursor. --- */
.service__name,
.service__body {
    transition: transform 0.45s cubic-bezier(0.2, 0.7, 0.1, 1), opacity 0.3s ease;
}

.services-list.is-hovering .service:not(.is-hover) .service__name,
.services-list.is-hovering .service:not(.is-hover) .service__body,
.services-list.is-hovering .service:not(.is-hover) .service__num {
    opacity: 0.4;
}

.service.is-hover .service__name {
    transform: translate3d(18px, 0, 0);
}

.service.is-hover .service__body {
    transform: translate3d(10px, 0, 0);
}

.service__num {
    transition: opacity 0.3s ease;
}

/* --- 7. Cursor: viewfinder crosshair (mouse only, ruch.js). Only the
   crosshair is visible: the system cursor is hidden everywhere (any
   element's own cursor: pointer included), except in form fields, where
   the text cursor is needed and the crosshair steps aside. --- */
.has-cursor,
.has-cursor * {
    cursor: none !important;
}

.has-cursor input,
.has-cursor textarea,
.has-cursor select,
.has-cursor [contenteditable] {
    cursor: auto !important;
}

.cursor {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 10001; /* over the cookie banner and the modal */
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.2s ease;
}

.cursor.is-on {
    opacity: 1;
}

.cursor.is-text {
    opacity: 0;
}

/* Four short strokes around an open centre, like a focus mark. */
.cursor__cross {
    position: absolute;
    left: -14px;
    top: -14px;
    width: 28px;
    height: 28px;
    transition: transform 0.3s cubic-bezier(0.2, 0.7, 0.1, 1);
}

.cursor__cross i {
    position: absolute;
    background: var(--text);
}

.cursor__cross i:nth-child(1) { left: 13.5px; top: 0;      width: 1px;  height: 9px; }
.cursor__cross i:nth-child(2) { left: 13.5px; bottom: 0;   width: 1px;  height: 9px; }
.cursor__cross i:nth-child(3) { top: 13.5px;  left: 0;     width: 9px;  height: 1px; }
.cursor__cross i:nth-child(4) { top: 13.5px;  right: 0;    width: 9px;  height: 1px; }

/* Over the yellow accent: dark strokes and label (on --accent the dark
   is 10.5:1; the light crosshair would disappear). */
.cursor.is-dark .cursor__cross i {
    background: var(--on-accent);
}

.cursor.is-dark .cursor__label {
    background: var(--on-accent);
    color: var(--text);
}

.cursor.is-link .cursor__cross {
    transform: scale(0.7);
}

.cursor.is-down .cursor__cross {
    transform: scale(0.4);
}

.cursor.is-label .cursor__cross {
    transform: scale(1.5);
}

.cursor__label {
    position: absolute;
    left: 18px;
    top: 14px;
    padding: 3px 6px 2px;
    background: var(--text);
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    line-height: 1.2;
    color: var(--on-accent);
    white-space: nowrap;
    opacity: 0;
    transform: translate3d(-6px, 0, 0);
    transition: opacity 0.2s ease, transform 0.25s cubic-bezier(0.2, 0.7, 0.1, 1);
}

.cursor.is-label .cursor__label {
    opacity: 1;
    transform: none;
}

/* --- 8a. Buttons fill from where the cursor came in. Solid buttons get
   the fill under their text (dark text stays dark); outline buttons get a
   paper fill in difference mode, so their light text turns dark exactly
   where the fill is: only transform is animated. --- */
.fx {
    position: relative;
    overflow: hidden;
    isolation: isolate;
}

.fx::before {
    content: '';
    position: absolute;
    left: var(--fx-x, 50%);
    top: var(--fx-y, 50%);
    z-index: -1;
    width: var(--fx-d, 200px);
    height: var(--fx-d, 200px);
    margin: calc(var(--fx-d, 200px) / -2) 0 0 calc(var(--fx-d, 200px) / -2);
    border-radius: 50%;
    background: var(--text);
    transform: scale(0);
    transition: transform 0.5s cubic-bezier(0.2, 0.7, 0.1, 1);
    pointer-events: none;
}

.fx.is-fill::before {
    transform: scale(1);
}

.fx--invert::before {
    z-index: 1;
    mix-blend-mode: difference;
}

/* --- 8c. Links inside text: the underline is drawn from the left. The
   e-mail address keeps its line and redraws it (out to the right, back in
   from the left); footer links draw one on hover. --- */
.contact-mail {
    position: relative;
    border-bottom-color: transparent !important;
}

.contact-mail::after,
.site-footer a::after,
.site-footer__btn::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;
    height: 1px;
    background: currentColor;
    transform-origin: left center;
    pointer-events: none;
}

.contact-mail::after {
    background: var(--field-line);
}

.site-footer a,
.site-footer__btn {
    position: relative;
}

.site-footer a::after,
.site-footer__btn::after {
    transform: scaleX(0);
    transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.1, 1);
}

@media (hover: hover) {
    .contact-mail:hover::after {
        background: var(--text);
        animation: ff-redraw 0.6s cubic-bezier(0.2, 0.7, 0.1, 1);
    }

    .site-footer a:hover::after,
    .site-footer__btn:hover::after {
        transform: scaleX(1);
    }
}

@keyframes ff-redraw {
    0%   { transform: scaleX(1); transform-origin: right center; }
    40%  { transform: scaleX(0); transform-origin: right center; }
    41%  { transform: scaleX(0); transform-origin: left center; }
    100% { transform: scaleX(1); transform-origin: left center; }
}

/* --- 9. Footer: big "FrameFlow". Plain text until footer-mark.js draws
   it in characters on a canvas over the same box (the text then stays
   as an invisible placeholder, so the layout never moves). --- */
.footer-mark {
    position: relative;
    max-width: var(--content-max);
    margin: 0 auto;
    padding: clamp(48px, 8vh, 96px) var(--gutter) 0;
}

.footer-mark__text {
    display: block;
    width: max-content;
    font-size: 26.4cqi;
    font-stretch: 62%;
    font-weight: 800;
    line-height: 0.82;
    white-space: nowrap;
    color: var(--text);
}

.footer-mark {
    container-type: inline-size;
}

.footer-mark__text.is-replaced {
    visibility: hidden;
}

.footer-mark__canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

/* ==========================================================================
   18. RUCH V2, step 3: one main motion per section, the rest accents.
   As in §16-17, every hidden start state hangs on a class that only
   ruch.js sets (svc-enter, tape) or on inline styles it writes, so
   without the script all of this is plain, visible content.
   ========================================================================== */

/* --- Menu: text roll. The label slides up and out, its copy (data-text)
   comes in from below. Desktop bar only; the full-screen phone menu
   keeps plain links. --- */
@media (hover: hover) and (min-width: 861px) {
    /* The mask: clips the label as it rolls up. Its own class: without a
       mask the label floats above the bar. */
    .roll-mask {
        display: inline-block;
        overflow: hidden;
        vertical-align: top;
        padding-bottom: 0.12em;
        margin-bottom: -0.12em;
    }

    .roll {
        position: relative;
        display: inline-block;
        transition: transform 0.4s cubic-bezier(0.2, 0.7, 0.1, 1);
    }

    .roll::after {
        content: attr(data-text);
        position: absolute;
        left: 0;
        top: calc(100% + 0.12em);
    }

    .site-nav__link:hover .roll,
    .site-nav__link:focus-visible .roll {
        transform: translate3d(0, calc(-100% - 0.12em), 0);
    }
}

/* --- Realizacje: the big name in the frame, letter by letter (ruch.js
   sets opacity/transform per letter). Words never break inside. --- */
.nm-word {
    display: inline-block;
    white-space: nowrap;
}

.nm-l {
    display: inline-block;
}

/* In the strip the text slides out from behind its frame. */
.tape .chapter__media {
    z-index: 1;
}

.tape .chapter__text {
    position: relative;
    z-index: 0;
}

/* --- Usługi: rows in one after another: number, name, hairline; the
   description only fades a little. --- */
.svc-enter .service__num,
.svc-enter .service__name {
    opacity: 0;
    transform: translate3d(0, 14px, 0);
    transition: opacity 0.5s ease, transform 0.6s cubic-bezier(0.2, 0.7, 0.1, 1);
}

.svc-enter .service__num  { transition-delay: calc(var(--i, 0) * 140ms); }
.svc-enter .service__name { transition-delay: calc(var(--i, 0) * 140ms + 80ms); }

.svc-enter .service::before {
    transform: scaleX(0);
    transition: transform 0.7s cubic-bezier(0.2, 0.7, 0.1, 1) calc(var(--i, 0) * 140ms + 160ms);
}

.svc-enter .service__body {
    opacity: 0.35;
    transition: opacity 0.8s ease calc(var(--i, 0) * 140ms + 200ms), transform 0.45s cubic-bezier(0.2, 0.7, 0.1, 1);
}

.svc-enter.is-in .service__num,
.svc-enter.is-in .service__name,
.svc-enter.is-in .service__body {
    opacity: 1;
    transform: none;
}

.svc-enter.is-in .service::before {
    transform: none;
}

/* the closing hairline comes last */
.svc-enter.services-list::after {
    transform: scaleX(0);
    transition: transform 0.7s cubic-bezier(0.2, 0.7, 0.1, 1) 0.9s;
}

.svc-enter.is-in.services-list::after {
    transform: none;
}

/* hover (§17) must not wait for the entrance delays */
.svc-enter.is-in .service.is-hover .service__name,
.svc-enter.is-in .service.is-hover .service__body,
.svc-enter.is-in.is-hovering .service__name,
.svc-enter.is-in.is-hovering .service__body,
.svc-enter.is-in.is-hovering .service__num {
    transition-delay: 0s;
}

.svc-enter.is-in .service.is-hover .service__name {
    transform: translate3d(18px, 0, 0);
}

.svc-enter.is-in .service.is-hover .service__body {
    transform: translate3d(10px, 0, 0);
}

.svc-enter.is-in.is-hovering .service:not(.is-hover) .service__name,
.svc-enter.is-in.is-hovering .service:not(.is-hover) .service__body,
.svc-enter.is-in.is-hovering .service:not(.is-hover) .service__num {
    opacity: 0.4;
}

/* --- O mnie: HUD-style metadata line; photo in a viewfinder frame. --- */
.about__meta {
    margin-top: 28px;
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    line-height: 1.6;
    color: var(--quiet);
}

.about__photo {
    position: relative;
}

.about__photo .vf {
    width: 22px;
    height: 22px;
    border-color: rgba(243, 236, 224, 0.75);
}

.about__photo .vf--tl { top: 14px;    left: 14px; }
.about__photo .vf--tr { top: 14px;    right: 14px; }
.about__photo .vf--bl { bottom: 14px; left: 14px; }
.about__photo .vf--br { bottom: 14px; right: 14px; }

/* --- Kontakt: the line under a field is drawn from the left when it
   gets focus (keyboard or mouse); that line is the focus indicator. --- */
.form-group {
    position: relative;
}

.form-group::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2px;
    background: var(--text);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 0.45s cubic-bezier(0.2, 0.7, 0.1, 1);
    pointer-events: none;
}

.form-group:focus-within::after {
    transform: scaleX(1);
}

.form-input:focus,
.form-input:focus-visible {
    box-shadow: none;
}

/* ==========================================================================
   14. REDUCED MOTION — no animations, static mark in the hero
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
    .ff-canvas { display: none !important; }
    .hero--live .hero__mark { display: flex; }
}
