@font-face {
    font-family: "Figtree";
    src: url("fonts/Figtree.woff2?v=20260702") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
}

:root {
    --fz-html: 62.5%;
    --ff-base: "Figtree", "Helvetica Neue", Helvetica, Arial, sans-serif;

    --c-base: #232633;
    --c-white: #fff;
    --c-snow: #fcfcfc;
    --c-steel: #515667;
    --c-ink: #161821;
    --c-slate: #8e92a0;
    --c-graphite: #363a48;
    --c-haze: #dfe1e8;
    --c-mist: #edeef2;
    --c-frost: #f7f8fa;
    --c-ice: #eaf1f7;

    --header-h: 6.4rem;
    /* breathing room of every full-screen panel (top pairs with --header-h) */
    --panel-gap: clamp(2.4rem, 4vw, 4.8rem);
}

@media (min-width: 767.02px) {
    :root {
        --header-h: 8.8rem;
    }
}

*,
*::before,
*::after {
    padding: 0;
    margin: 0;
    outline: 0;
    box-sizing: border-box;
}

html {
    font-size: var(--fz-html);
    /* iOS tints the toolbar/status-bar area and overscroll from the
       ROOT element's background — keep it on-brand */
    background-color: var(--c-snow);
    -webkit-tap-highlight-color: transparent;
    /* the document never scrolls: .wrapper is the scroller, so iOS
       Safari keeps its toolbar expanded (it only collapses on
       document scroll) */
    height: 100%;
    overflow: hidden;
}

/* no-JS fallback: native anchor jumps animate. The JS animator turns
   this off at boot (inline scroll-behavior:auto) — Chrome smooth-scrolls
   scrollTop writes too, which would trail behind the rAF tween. */
@media (prefers-reduced-motion: no-preference) {
    .wrapper {
        scroll-behavior: smooth;
    }
}

@media (prefers-reduced-motion: reduce) {

    *,
    *::before,
    *::after {
        transition: none !important;
        animation: none !important;
    }
}

body {
    min-width: 320px;
    height: 100%;
    overflow: hidden;
    background-color: var(--c-snow);
    font-family: var(--ff-base);
    font-weight: 400;
    font-size: 1.4rem;
    line-height: 1.6;
    color: var(--c-base);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

@media (min-width: 1023.02px) {
    body {
        font-size: 1.6rem;
    }
}

/* ---- The signature: selecting text blurs it ----
   A visible highlight stays so people who select text as a reading aid
   can still see WHAT is selected, even while the letters blur. */
::selection {
    color: transparent;
    background: var(--c-haze);
    text-shadow: 0 0 .15em var(--c-base);
}

::-moz-selection {
    color: transparent;
    background: var(--c-haze);
    text-shadow: 0 0 .15em var(--c-base);
}

a {
    color: inherit;
    text-decoration: underline;
}

@media (hover: hover) {
    a:where(:hover, :focus-visible) {
        text-decoration: none;
    }
}

/* visually hidden but available to screen readers & crawlers */
.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;
}

h1,
h2,
h3,
h4 {
    font-weight: 400;
    margin-bottom: .8em;
}

h1,
.hero__tagline {
    font-size: 3.2rem;
    letter-spacing: -.03em;
    line-height: 1.2;
}

@media (min-width: 767.02px) {

    h1,
    .hero__tagline {
        font-size: 3.6rem;
    }
}

@media (min-width: 1023.02px) {

    h1,
    .hero__tagline {
        font-size: 4rem;
    }
}

@media (min-width: 1199.02px) {

    h1,
    .hero__tagline {
        font-size: 4.8rem;
    }
}

h2 {
    font-size: 2.4rem;
    letter-spacing: -.02em;
    line-height: 1.35;
}

@media (min-width: 767.02px) {
    h2 {
        font-size: 2.8rem;
    }
}

@media (min-width: 1023.02px) {
    h2 {
        font-size: 3.2rem;
    }
}

@media (min-width: 1199.02px) {
    h2 {
        font-size: 3.6rem;
    }
}

h4,
.modal__head h3 {
    font-size: 2rem;
    letter-spacing: -.01em;
    line-height: 1.45;
}

@media (min-width: 1023.02px) {

    h4,
    .modal__head h3 {
        font-size: 2.2rem;
    }
}

@media (min-width: 1199.02px) {

    h4,
    .modal__head h3 {
        font-size: 2.4rem;
    }
}

*:where(:focus-visible) {
    outline: .2rem solid var(--c-base);
    outline-offset: .2rem;
}

/* ---- Layout shells (match reference widths) ----
   The gutter lives on :root so overlays outside a .shell (the reel
   caption pill, the dot rails) can share the exact edge inset the
   header logo/CTA get. */
:root {
    --shell-gutter: 2.4rem;
}

@media (min-width: 1023.02px) {
    :root {
        --shell-gutter: 3.2rem;
    }
}

@media (min-width: 1199.02px) {
    :root {
        --shell-gutter: 4rem;
    }
}

.shell {
    --shell-max-width: 114rem;
    max-width: calc(var(--shell-max-width) + var(--shell-gutter) * 2);
    padding-inline: var(--shell-gutter);
    margin-inline: auto;
    width: 100%;
}

.shell--sm {
    --shell-max-width: 48rem;
}

@media (min-width: 1023.02px) {
    .shell--sm {
        --shell-max-width: 56rem;
    }
}

.shell--md {
    --shell-max-width: 52rem;
}

@media (min-width: 1023.02px) {
    .shell--md {
        --shell-max-width: 56rem;
    }
}

.shell--fluid {
    max-width: none;
}

.wrapper {
    /* the one and only scroller */
    position: relative;
    height: 100%;
    overflow-y: auto;
    overflow-x: clip;
    scrollbar-gutter: stable;
}

/* ============ Header (fixed, logo joins it on scroll) ============ */
.header {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 100;
    /* 1.2rem: with the reel clearance equal to the header's height,
       the CTA sits with identical air above and below on mobile */
    padding-block: 1.2rem;
}

@media (min-width: 767.02px) {
    .header {
        padding-block: 2rem;
    }
}

/* Header backdrop: a gradient tint (page colour at the top → transparent
   at the bottom) plus a progressive blur that's strong up top and fades
   to nothing — so content scrolling under the header softens gradually.
   Both layers sit behind the header content and ignore pointer events. */
.header__inner {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 2rem;
}

/* The header logo sits on the left, hidden at the top of the page and
   fading in (in one go) once the page is scrolled past the hero. */
/* The logo height is FIXED across breakpoints (--logo-h), and the full
   "neet" wordmark shows at every size — there's room for it even on the
   narrowest screens alongside the CTA, and it matches the footer logo. */
.header__logo {
    --logo-h: 2.7rem;
    /* single soft ice drop shadow: invisible on the ice background,
       keeps the wordmark legible over dark footage */
    filter: drop-shadow(0 0 2rem color-mix(in srgb, var(--c-ice) 40%, transparent));
    display: block;
    height: var(--logo-h);
    opacity: 0;
    /* visibility keeps the invisible link out of the tab order;
       the delayed 0s visibility transition lets opacity fade out first */
    visibility: hidden;
    transition: opacity .5s var(--ease-out, ease), visibility 0s .5s;
    will-change: opacity;
}

.is-scrolled .header__logo {
    opacity: 1;
    visibility: visible;
    transition-delay: 0s;
}

/* ---- Shared logo styling (header, hero, footer) ----
   All three render the same wordmark: ink fill, eyes that blink in sync.
   The header & footer SVGs share a sizing rule (full wordmark = height ×
   2100/500 = height × 4.2); the hero SVG sizes itself by width. The JS
   blink toggles .blink on the SVG element (which IS .hero__logo for the
   hero, and a child <svg> for header/footer). */
.header__logo svg,
.footer__logo svg {
    height: 100%;
    width: calc(var(--logo-h) * 4.2);
    max-width: none;
    display: block;
    overflow: visible;
}

.header__logo path,
.header__logo circle,
.hero__logo path,
.hero__logo circle,
.footer__logo path,
.footer__logo circle {
    fill: var(--c-base);
}

.header__logo .eye,
.hero__logo .eye,
.footer__logo .eye {
    transition: opacity 60ms linear;
}

.header__logo svg.blink .eye,
.hero__logo.blink .eye,
.footer__logo svg.blink .eye {
    opacity: 0;
}

.header__actions {
    flex: 1;
    display: flex;
    justify-content: flex-end;
}

/* The header CTA mirrors the header logo: hidden at the top of the page,
   it fades in once the hero CTA slides under the header (.is-scrolled) —
   in sync with the logo — and hides again while the footer's own
   "Let's talk" is in view (.footer-cta-visible), so the two are never
   on screen together.
   pointer-events follow opacity so the hidden button isn't clickable. */
.header__cta {
    opacity: 0;
    pointer-events: none;
    visibility: hidden;
    transition: opacity .5s var(--ease-out, ease), visibility 0s .5s;
    will-change: opacity;
}

.is-scrolled .header__cta {
    opacity: 1;
    pointer-events: auto;
    visibility: visible;
    transition-delay: 0s;
}

/* While the footer's own CTA (and its logo) are on screen, the whole
   header — CTA and logo — fades back out, so it's never duplicated
   against the footer at the bottom of the page. */
.is-scrolled.footer-cta-visible .header__cta,
.is-scrolled.footer-cta-visible .header__logo {
    opacity: 0;
    pointer-events: none;
    visibility: hidden;
    transition-delay: 0s, .5s;
}

/* ---- Buttons ---- */
.btn {
    display: inline-flex;
    text-decoration: none;
}

.btn__inner {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 2.2rem;
    font-size: 1.5rem;
    letter-spacing: -.01em;
    line-height: 1;
    font-weight: 600;
    text-decoration: none;
    border-radius: 999rem;
    background-color: var(--c-white);
    color: var(--c-ink);
    border: 2px solid var(--c-white);
    box-shadow: 0 13px 16px -5px rgba(0, 0, 0, .1);
    padding: 1.2rem 1.8rem;
    transition: background-color .3s, border-color .3s, color .3s, box-shadow .3s, transform .3s;
}

@media (min-width: 1023.02px) {
    .btn__inner {
        font-size: 1.6rem;
    }
}

@media (hover: hover) and (pointer: fine) {
    .btn:hover .btn__inner {
        box-shadow: 0 10px 10px -5px rgba(0, 0, 0, .2);
    }
}

.btn--solid .btn__inner {
    background-color: var(--c-base);
    color: var(--c-white);
    border-color: var(--c-base);
    /* the dark pill visually swallows the base .1 shadow — a stronger
       alpha reads as the SAME lift as the white header button */
    box-shadow: 0 13px 16px -5px rgba(0, 0, 0, .22);
}

@media (hover: hover) and (pointer: fine) {
    .btn--solid:hover .btn__inner {
        background-color: var(--c-graphite);
        border-color: var(--c-graphite);
        box-shadow: 0 10px 10px -5px rgba(0, 0, 0, .32);
    }
}

/* the header pill lets a hint of the media behind shine through */
.header__cta .btn__inner {
    background-color: rgba(255, 255, 255, .7);
    /* transparent (not translucent-white): a tinted border stacks on
       the tinted background and reads as a lighter ring */
    border-color: transparent;
}

@media (hover: hover) and (pointer: fine) {
    .header__cta:hover .btn__inner {
        background-color: var(--c-white);
        border-color: var(--c-white);
    }
}

.link-icon {
    display: inline-flex;
    align-items: center;
    color: var(--c-ink);
    text-decoration: none;
}

/* The hero scroll hint is just the bobbing arrow — no visible label
   (the link's aria-label still names it for assistive tech). */

/* The down arrow eases down and back, hinting "scroll down". A custom
   ease makes it pause gently at the bottom rather than bouncing evenly. */
.link-icon__arrow {
    animation: arrow-bob 2s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}

@keyframes arrow-bob {

    0%,
    100% {
        transform: translateY(0);
    }

    50% {
        transform: translateY(.6rem);
    }
}

/* ============ Hero ============ */
.hero {
    display: grid;
    text-align: center;
    /* exact full-screen panel, aligned with the page panels */
    min-height: 100svh;
}

/* the .shell wrapper must pass the full height down to .hero__inner */
.hero>.shell {
    display: flex;
}

.hero__inner {
    width: 100%;
    display: grid;
    /* header gutter | flexible centred content | actions row */
    grid-template-rows: var(--header-h) 1fr auto;
    row-gap: 2rem;
    padding-bottom: 3.4rem;
}

@media (min-width: 1023.02px) {
    .hero__inner {
        padding-bottom: 4.2rem;
    }
}

/* content cell fills the middle row and centres its own children */
.hero__content {
    grid-row: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.hero__actions {
    grid-row: 3;
}

/* The hero logo is a normal static element that scrolls away with the
   rest of the hero content; the header logo fades in once it's gone. */
.hero__logo {
    width: min(62rem, 82vw);
    height: auto;
    display: block;
    /* the artwork bottoms out exactly at the viewBox edge; the SVG's
       UA default overflow:hidden clips the rounded letter caps when
       iOS rounds the auto height a fraction short (header/footer
       logos already carry the same guard) */
    overflow: visible;
}

/* The hero content (logo, tagline, CTA) does NOT fade on scroll — it
   simply scrolls up and out of view at full opacity. The header logo +
   CTA fade in only once the hero has left the viewport (.is-scrolled,
   driven by an IntersectionObserver on .hero__cta-wrap). */
.hero__cta-wrap {
    margin-top: 4.2rem;
}

@media (min-width: 1023.02px) {
    .hero__cta-wrap {
        margin-top: 4.8rem;
    }
}


/* sizes come from the shared h1 ramp above */
.hero__tagline {
    margin-top: 5.6rem;
    max-width: 24ch;
}

/* ============ Sections ============
   Every section is a full-screen panel (page-panel, reel-sticky or the
   footer); none carries its own block padding in motion mode. */
.page-panel {
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-top: calc(var(--header-h) + var(--panel-gap));
    padding-bottom: var(--panel-gap);
}

.section__head {
    text-align: center;
    margin-bottom: 4.8rem;
}

@media (min-width: 767.02px) {
    .section__head {
        margin-bottom: 6.4rem;
    }
}

@media (min-width: 1023.02px) {
    .section__head {
        margin-bottom: 6.8rem;
    }
}

/* ---- Intro / about ---- */
.section--intro {
    font-size: 2rem;
    letter-spacing: -.01em;
    line-height: 1.6;
    text-align: left;
}

@media (min-width: 1023.02px) {
    .section--intro {
        font-size: 2.2rem;
    }
}

@media (min-width: 1439.02px) {
    .section--intro {
        font-size: 2.4rem;
    }
}

.section--intro p:not(:last-child) {
    margin-bottom: 2rem;
}

/* LinkedIn chip (founder) — sits inline within running text */
.chip {
    display: inline-flex;
    align-items: center;
    gap: .8rem;
    vertical-align: middle;
    border-radius: 999rem;
    box-shadow: 0 3px 8px -2px rgba(0, 0, 0, .14);
    /* tighter top/bottom so the pill hugs the avatar; even left inset, more on the right for the text */
    padding: .25rem 1rem .25rem .4rem;
    text-decoration: none;
    transition: box-shadow .3s, transform .3s;
    color: var(--c-ink);
    position: relative;
    top: -.07em;
}

.chip:hover,
.chip:focus-visible {
    box-shadow: 0 10px 14px -6px rgba(0, 0, 0, .22);
    text-decoration: none;
}

.chip__image {
    width: 2.8rem;
    height: 2.8rem;
    border-radius: 50%;
    object-fit: cover;
    background: var(--c-mist);
    display: block;
    flex-shrink: 0;
}

@media (min-width: 1023.02px) {
    .chip__image {
        width: 3rem;
        height: 3rem;
    }
}

@media (min-width: 1439.02px) {
    .chip__image {
        width: 3.2rem;
        height: 3.2rem;
    }
}

.chip__name {
    font-weight: 400;
}

/* icon reveal is width-only so nothing shifts vertically on hover.
   gap collapses too (negative margin) so a hidden icon adds no width. */
.chip__icon {
    width: 0;
    overflow: hidden;
    flex-shrink: 0;
    color: var(--c-ink);
    display: inline-flex;
    align-items: center;
    margin-left: -.8rem;
    /* cancel the flex gap while hidden */
    transition: width .3s, margin-left .3s;
}

/* viewBox is cropped tight to the arrow ink (no padding), so 2rem is the
   arrow itself; align-items:center on the chip lines it up with the text. */
.chip__icon svg {
    width: 2rem;
    height: 2rem;
    display: block;
}

.chip:hover .chip__icon,
.chip:focus-visible .chip__icon {
    width: 1.5rem;
    margin-left: 0;
    height: 1.5rem;
}

/* ============ Selected-work reel ============
   A sticky full-bleed frame pinned while its section scrolls one screen
   per project; the media covers the whole viewport (crop steered by
   each video's object-position) and runs under the translucent header.
   Testimonials reuse the frame with top clearance instead. */
.reel-sticky {
    /* pins to the viewport while its section scrolls N screens — each
       slide owns a real screen of document, so the scrollbar moves */
    position: sticky;
    top: 0;
    /* SOLID page colour: iOS 26 Safari derives its toolbar tint from
       the background-color of fixed/sticky elements at the top edge
       (theme-color is ignored, transparent elements sample the media
       behind them). The full-bleed frame covers this completely, so
       it is invisible — it exists for Safari's sampler. */
    background-color: var(--c-snow);
    /* svh = the toolbar-expanded viewport: panels are sized ONCE to
       the smallest viewport and never resize with the toolbar */
    height: 100svh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    /* top padding = header height + gap so the image clears the fixed
       header; bottom gets the same gap (so the centred block sits in the
       space BELOW the header rather than tucking under it) */
    /* mobile: clearance == the header's rendered height (6.7rem), so
       the header CTA floats with equal air above and below it */
    padding-top: 6.7rem;
    padding-bottom: var(--panel-gap);
}

@media (min-width: 767.02px) {
    .reel-sticky {
        padding-top: calc(var(--header-h) + var(--panel-gap));
    }
}

/* PROJECT media fills the whole screen at every width and runs under
   the translucent header. Only projects — the testimonial card keeps
   its top clearance so text never tucks under the header. (Motion
   mode only: under reduce the sticky is content-sized and flush
   full-bleed would collapse it.) */
@media (prefers-reduced-motion: no-preference) {
    #projects .reel-sticky {
        padding: 0;
    }

    /* the testimonial card keeps its header clearance but runs to the
       viewport bottom, so its dot rail aligns with the projects' */
    #testimonials .reel-sticky {
        padding-bottom: 0;
    }
}

/* The frame simply FILLS its panel at every width — no aspect-ratio
   juggling. The media handles the viewport's shape itself: object-fit
   cover crops to whatever the frame is, and each video's
   object-position picks which part of the shot survives the crop. */
.reel-frame {
    position: relative;
    width: 100%;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    background-color: var(--c-mist);
}

/* stacked layers that SLIDE vertically in place: below the active
   slide they wait at +100%, above it they rest at -100% */
.project {
    position: absolute;
    inset: 0;
    transform: translateY(100%);
    transition: transform .7s cubic-bezier(.65, .05, .36, 1);
    pointer-events: none;
}

.project.is-active {
    transform: none;
    pointer-events: auto;
}

.project.is-above {
    transform: translateY(-100%);
}

/* per-figure captions feed the shared pill; only shown when the
   reduced-motion layout stacks the projects as a plain list */
figcaption.project__caption {
    display: none;
}

.project__media {
    width: 100%;
    height: 100%;
}

.project__media :is(img, video) {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    -webkit-user-select: none;
    user-select: none;
    object-position: center center;
}

/* Vertical crop (frame WIDER than the image): tritanium and docmalik
   keep the centred default; optc anchors top (crops the bottom). */
.project--optc .project__media :is(img, video) {
    object-position: center top;
}

/* Horizontal crop (frame TALLER than the image, e.g. portrait viewport):
   keep the existing left/right nudges for the hand-held shots. */
@media (max-aspect-ratio: 4 / 5) {
    .project--docmalik .project__media :is(img, video) {
        object-position: 65% center;
    }

    .project--tritanium .project__media :is(img, video) {
        object-position: 20% center;
    }

    .project--optc .project__media :is(img, video) {
        object-position: center center;
    }
}

/* Caption geometry, shared by the per-figure figcaptions (shown only in
   reduced-motion/print) and the floating #reel-caption pill that the JS
   swaps as slides change. */
.project__caption {
    position: absolute;
    bottom: 2rem;
    left: var(--shell-gutter);   /* same edge inset as the header logo */
    margin: 0;
    font-size: 1.3rem;
    letter-spacing: -.01em;
    color: var(--c-snow);
    text-align: left;
    /* .72 keeps the 13px text ≥4.5:1 even over white video frames */
    background: rgba(22, 24, 33, .72);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    /* same corner radius as the dot rail (its 24px-wide capsule
       clamps to a 12px radius) */
    border-radius: 1.2rem;
    /* same lift as the header "Let's talk" button — stronger alpha
       because a .1 shadow disappears against busy video pixels */
    box-shadow: 0 13px 16px -5px rgba(0, 0, 0, .3);
    padding: .7rem 1.6rem;
    /* width adapts to the content; the cap leaves room for the dot
       rail on the right so the two never collide */
    width: max-content;
    max-width: calc(100% - 8rem);
    line-height: 1.45;
    pointer-events: none;
    /* the pill stays put across slide changes: text fades, width glides */
    white-space: nowrap;
    overflow: hidden;
    transition: width .35s var(--ease-out, ease);
}

.project__caption>* {
    transition: opacity .18s ease;
}

.project__caption.is-swapping>* {
    opacity: 0;
}

@media (min-width: 1023.02px) {
    .project__caption {
        font-size: 1.4rem;
        bottom: 2.4rem;
    }
}

/* two stacked lines: client name on top, services underneath */
.project__caption b {
    display: block;
    font-weight: 600;
    color: var(--c-white);
}

.project__caption-services {
    display: block;
    font-size: .925em;
    opacity: .92;
}

/* progress dots */
/* Vertical dot rail overlaid on the right edge — mirrors the scroll
   direction, and dressed like the caption pill. Tapping a dot scrolls
   to that project/testimonial (buttons wired up in slider()). */
.reel-dots {
    position: absolute;
    right: var(--shell-gutter);   /* same edge inset as the header CTA */
    /* bottom-aligned with the caption pill */
    bottom: 2rem;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* 3px block / 2px inline padding + 14px buttons around 9px dots */
    padding: 3px 2px;
    background: rgba(22, 24, 33, .72);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border-radius: 999rem;
    /* same lift as the header "Let's talk" button — stronger alpha
       because a .1 shadow disappears against busy video pixels */
    box-shadow: 0 13px 16px -5px rgba(0, 0, 0, .3);
}

/* the testimonial rail sits on the light page background — style it
   like the header "Let's talk" button (white pill, dark dots) */
#testi-dots {
    background: var(--c-white);
    /* on the flat light page the button's own .1 shadow reads fine */
    box-shadow: 0 13px 16px -5px rgba(0, 0, 0, .1);
}

#testi-dots .reel-dot::before {
    /* ≥3:1 against the white rail (WCAG 1.4.11 non-text contrast) */
    background: rgba(35, 38, 51, .55);
}

#testi-dots .reel-dot.is-active::before {
    background: var(--c-ink);
}

#testi-dots .reel-dot:focus-visible {
    outline-color: var(--c-base);
}

@media (min-width: 1023.02px) {
    .reel-dots {
        bottom: 2.4rem;
    }
}

/* each dot is a large invisible tap target (the whole rail is
   clickable); the visible 9px dot is drawn by ::before */
.reel-dot {
    /* 24×24px buttons — WCAG 2.5.8 minimum target size — around the
       9px dot; the rail's 2px inline padding makes a 28px-wide pill */
    width: 24px;
    height: 24px;
    border: 0;
    padding: 0;
    background: none;
    cursor: pointer;
    display: grid;
    place-items: center;
}

.reel-dot::before {
    content: "";
    width: 9px;
    height: 9px;
    border-radius: 50%;
    /* ≥3:1 against the rail even when bright video lightens it
       (WCAG 1.4.11 non-text contrast) */
    background: rgba(255, 255, 255, .6);
    transition: background-color .3s;
}

.reel-dot.is-active::before {
    background: var(--c-white);
}

.reel-dot:focus-visible {
    outline: .2rem solid var(--c-white);
    outline-offset: -.2rem;
    border-radius: 999rem;
}

@media (prefers-reduced-motion: reduce) {

    /* Under reduced motion the JS marks every project active — lay
       them out as a plain stacked list so all three are visible and
       reachable. */
    .reel-sticky {
        position: static;
        height: auto;
    }

    .reel-frame {
        width: min(100%, 100vw);
        overflow: visible;
        background: none;
    }

    .project {
        position: relative;
        inset: auto;
        transform: none;
        pointer-events: auto;
        /* same ratio the video frame would have had (squarish default;
           orientation overrides below) with the same cover-crop */
        aspect-ratio: 1 / 1;
        overflow: hidden;
    }

    /* stacked list: every project shows its own caption again */
    figcaption.project__caption {
        display: block;
    }

    #reel-caption {
        display: none;
    }

    .project+.project {
        margin-top: 2.4rem;
    }

    #reel-dots,
    #testi-dots {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) and (min-aspect-ratio: 5 / 4) {
    .project {
        aspect-ratio: 16 / 10;
    }
}

@media (prefers-reduced-motion: reduce) and (max-aspect-ratio: 4 / 5) {
    .project {
        aspect-ratio: 10 / 16;
    }
}

/* ---- Services (label | copy rows) ---- */
.services__item {
    display: flex;
    align-items: flex-start;
    gap: 1.2rem;
    border-top: 1px solid var(--c-mist);
    padding-block: 1.5rem;
    color: var(--c-steel);
}

@media (min-width: 1023.02px) {
    .services__item {
        padding-block: 2rem;
    }
}

.services h3 {
    font-size: 1.4rem;
    font-weight: 600;
    flex: 0 0 38%;
    color: var(--c-ink);
    margin-bottom: 0;
}

@media (min-width: 767.02px) {
    .services h3 {
        flex: 0 0 25%;
    }
}

@media (min-width: 1023.02px) {
    .services h3 {
        font-size: 1.6rem;
        flex: 0 0 23.3%;
    }
}

.services p {
    margin-bottom: 0;
}

/* Under reduced motion the reveal machinery (whose wrapper span
   normally collapses each row into title-over-text) never runs —
   force the same stacked layout it would have produced. */
@media (prefers-reduced-motion: reduce) {
    .services__item {
        display: block;
    }
}

/* ---- Testimonials ---- */
.testimonial {
    text-align: center;
    max-width: 56rem;
    margin-inline: auto;
    /* the card is full-bleed on phones (no shell) — keep the text off
       the screen edges */
    padding-inline: 2.4rem;
}

.testimonial__images {
    display: flex;
    justify-content: center;
    margin-bottom: 3rem;
}

@media (min-width: 1023.02px) {
    .testimonial__images {
        margin-bottom: 3.6rem;
    }
}

.testimonial__image {
    width: 6.4rem;
    height: 6.4rem;
    border-radius: 50%;
    overflow: hidden;
    background: var(--c-mist);
}

.testimonial__image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.testimonial blockquote {
    font-size: 2rem;
    letter-spacing: -.01em;
    margin-bottom: 2rem;
}

@media (min-width: 1023.02px) {
    .testimonial blockquote {
        font-size: 2.2rem;
        margin-bottom: 2.3rem;
    }
}

@media (min-width: 1439.02px) {
    .testimonial blockquote {
        font-size: 2.4rem;
    }
}

.testimonial__author {
    font-size: 1.2rem;
    color: var(--c-steel);
}

@media (min-width: 1023.02px) {
    .testimonial__author {
        font-size: 1.3rem;
    }
}

/* Testimonials reuse the reel's sticky + frame verbatim (identical
   sizing, paddings and rail placement in every viewport); this
   modifier just strips the media chrome and centres the content. */
.reel-frame--bare {
    background: none;
    /* clips the sliding testimonial layers */
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.testi-slides {
    position: relative;
    width: 100%;
    align-self: stretch;
    flex: 1;
}

/* full-card layers that slide vertically, like the projects */
.testi-slide {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    transform: translateY(100%);
    transition: transform .7s cubic-bezier(.65, .05, .36, 1);
    pointer-events: none;
}

.testi-slide.is-active {
    transform: none;
    pointer-events: auto;
}

.testi-slide.is-above {
    transform: translateY(-100%);
}

/* one-paint suppression for instant slide switches (deep links) */
.no-anim .project,
.no-anim .testi-slide {
    transition: none !important;
}

/* the card's rows fade + rise in row by row, exactly like the services
   list — same rise, duration and 70ms-per-row stagger (delays are set
   inline by reveals(), which also splits the quote into line rows).
   Re-triggered each time a slide becomes active; on first arrival the
   section's .is-in releases the active slide. Rows never fade OUT:
   the hidden state returns instantly, delayed until the card has
   fully slid away (.7s). The hidden state only exists once the
   slider has booted (.has-reveals), so no-JS, reduced motion and
   print all show full cards. */
.has-reveals .t-row {
    opacity: 0;
    transform: translateY(var(--rise));
    transition: opacity 0s .75s, transform 0s .75s;
}

.has-reveals span.t-row {
    display: block;
    /* quote line rows lay out like reveal-line */
}

.has-reveals.is-in .testi-slide.is-active .t-row {
    opacity: 1;
    transform: none;
    transition: opacity var(--dur) var(--ease-out) var(--reveal-delay, 0ms),
        transform var(--dur) var(--ease-out) var(--reveal-delay, 0ms);
}

@media (prefers-reduced-motion: reduce) {
    #projects,
    #testimonials {
        height: auto !important;
    }

    /* both slides are shown at once — stack them in normal flow
       instead of superimposing the absolutely-positioned boxes
       (the shared .reel-sticky/.reel-frame reduce rules above handle
       the container) */
    .testi-slide {
        position: static;
        transform: none !important;
    }

    .testi-slide+.testi-slide {
        margin-top: 8rem;
    }
}

/* ---- Accordion (single open; grid-rows trick) ---- */
.accordion__section {
    border-top: 1px solid var(--c-mist);
}

.accordion__h {
    margin: 0;
}

.accordion__head {
    /* real <button> so the FAQ works from the keyboard.
       Horizontal padding (offset by negative margins so the text keeps
       its alignment) gives the focus ring breathing room around the
       question and the +/- icon. */
    appearance: none;
    width: calc(100% + 2.4rem);
    margin-inline: -1.2rem;
    padding-inline: 1.2rem;
    background: none;
    border: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 2rem;
    padding-block: 1.6rem;
    cursor: pointer;
}

@media (min-width: 1023.02px) {
    .accordion__head {
        padding-block: 2rem;
    }
}

.accordion__head:hover .accordion__icon {
    transform: rotate(180deg);
}

.accordion__q {
    font-size: 1.4rem;
    font-weight: 600;
    color: var(--c-ink);
}

@media (min-width: 1023.02px) {
    .accordion__q {
        font-size: 1.6rem;
    }
}

.accordion__icon {
    display: inline-block;
    width: 1.6rem;
    height: 1.6rem;
    position: relative;
    flex-shrink: 0;
    transition: transform .3s;
}

.accordion__icon::before,
.accordion__icon::after {
    content: "";
    position: absolute;
    background-color: var(--c-slate);
    border-radius: 999rem;
    transition: opacity .3s;
}

.accordion__icon::before {
    top: 50%;
    left: 0;
    width: 100%;
    height: 1px;
    transform: translateY(-50%);
}

.accordion__icon::after {
    top: 0;
    left: 50%;
    width: 1px;
    height: 100%;
    transform: translateX(-50%);
}

.accordion__body {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows .3s, padding .3s;
    color: var(--c-steel);
    font-size: 1.4rem;
    opacity: .85;
}

@media (min-width: 1023.02px) {
    .accordion__body {
        font-size: 1.6rem;
        padding-right: 4rem;
    }
}

.accordion__body-inner {
    overflow: hidden;
    /* air between the focused question's ring and the answer text */
    padding-top: .8rem;
}

.accordion__section.is-active .accordion__body {
    grid-template-rows: 1fr;
    padding-bottom: 1.4rem;
}

@media (min-width: 1023.02px) {
    .accordion__section.is-active .accordion__body {
        padding-bottom: 2rem;
    }
}

.accordion__section.is-active .accordion__icon::after {
    opacity: 0;
}

/* ============ Footer ============ */
.footer {
    position: relative;   /* anchors the 25%-line CTA block */
    background-color: var(--c-snow);
    text-align: center;
    /* full-screen closing panel: CTA + logo centred, foot row at the
       bottom; grows if content ever exceeds the viewport */
    display: flex;
    flex-direction: column;
    min-height: 100svh;
    padding-top: calc(var(--header-h) + var(--panel-gap));
    padding-bottom: 3.2rem;
}

.footer>.shell {
    /* one cell: the CTA block sits against the panel's top padding —
       the same line every other section's heading starts on — and the
       logo + copyright block anchors to the panel bottom */
    flex: 1;
    display: grid;
}

.footer>.shell>* {
    grid-area: 1 / 1;
}

.footer__inner {
    align-self: start;
}

.footer__bottom {
    align-self: end;
}

.footer h2 {
    margin-bottom: 3.4rem;
}

@media (min-width: 1023.02px) {
    .footer h2 {
        margin-bottom: 4.2rem;
    }
}

/* Footer logo: the FULL Neet wordmark, at the same fixed height as the
   header logo (--logo-h). Centred above the legal line. */
.footer__brand {
    display: flex;
    justify-content: center;
    margin-bottom: 3.2rem;
}

@media (min-width: 767.02px) {
    .footer__brand {
        margin-bottom: 4rem;
    }
}

.footer__logo {
    --logo-h: 2.7rem;
    display: block;
    height: var(--logo-h);
    line-height: 0;
}

/* Footer nav: the crawlable path links, styled like the legal line */
.footer__nav {
    font-size: 1.2rem;
    color: var(--c-steel);
    margin-bottom: 1.6rem;
}

.footer__nav .footer__links {
    flex-wrap: wrap;
    gap: .4rem 1.6rem;
}

/* no dot separators between the nav links at any width (the
   first-child selector outweighs the desktop rule that re-enables
   the leading dot for the legal row) */
.footer__nav .footer__links li::before,
.footer__nav .footer__links li:first-child::before {
    content: none;
}

@media (min-width: 1023.02px) {
    .footer__nav {
        font-size: 1.3rem;
    }
}

.footer__foot {
    font-size: 1.2rem;
    color: var(--c-steel);
}

@media (min-width: 767.02px) {
    .footer__foot {
        display: flex;
        justify-content: center;
    }
}

@media (min-width: 1023.02px) {
    .footer__foot {
        font-size: 1.3rem;
    }
}

.footer__foot p {
    margin-bottom: .4rem;
}

.footer__links {
    list-style: none;
    display: flex;
    justify-content: center;
}

.footer__links a {
    text-underline-offset: 3px;
}

/* One dot separator, shared: the "Neet · Sweden" line (.dot-sep span) and
   the footer links (li::before) use the same small square dot. */
.dot-sep,
.footer__links li::before {
    content: "";
    display: inline-block;
    width: .2rem;
    height: .2rem;
    background-color: currentColor;
    margin-inline: .8rem;
    vertical-align: middle;
}

.footer__links li:first-child::before {
    content: none;
}

@media (min-width: 767.02px) {
    .footer__links li:first-child::before {
        content: "";
    }
}

/* footer links sit quiet by default; underline only on interaction */
.footer__nav a,
.footer__foot a {
    text-decoration: none;
}

.footer__nav a:hover,
.footer__nav a:focus-visible,
.footer__foot a:hover,
.footer__foot a:focus-visible {
    text-decoration: underline;
}

/* ============ Modal (contact + privacy popups) ============ */
/* Staged open/close: the backdrop darkens + blurs IN first, the panel
   fades/rises in right behind it; on close the panel leaves first and
   the blur releases after. The stagger lives entirely in the
   transition-delays of the two states below. */
.modal {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: none;
    align-items: flex-start;
    overflow-x: hidden;
    overflow-y: auto;
    pointer-events: none;
    padding: 2.4rem;
}

.modal.is-mounted {
    display: flex;
}

.modal.is-open {
    pointer-events: all;
}

/* closed / closing: blur releases AFTER the panel has faded (.18s).
   The tint + blur are constant and the whole overlay fades via
   opacity — Safari doesn't render backdrop-filter transitions (the
   blur never ramps in and snaps off on close), opacity stages the
   same choreography in every engine. */
.modal__overlay {
    position: fixed;
    inset: 0;
    background-color: rgba(35, 38, 51, .28);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    opacity: 0;
    transition: opacity .35s ease .18s;
}

/* opening: backdrop starts immediately */
.modal.is-open .modal__overlay {
    opacity: 1;
    transition: opacity .4s ease;
}

/* closed / closing: panel leaves first, quickly */
.modal__inner {
    position: relative;
    /* the dialog receives programmatic focus on open — no ring on it */
    outline: none;
    background-color: var(--c-white);
    width: 100%;
    max-width: 48rem;
    margin: auto;
    padding: 3.2rem;
    border-radius: 1.2rem;
    box-shadow: 0 24px 60px -20px rgba(0, 0, 0, .35);
    opacity: 0;
    transform: translateY(1.2rem);
    transition: opacity .2s ease, transform .2s ease;
}

/* opening: panel fades in once the blur is mostly there */
.modal.is-open .modal__inner {
    opacity: 1;
    transform: none;
    transition: opacity .35s var(--ease-out, ease) .22s,
        transform .35s var(--ease-out, ease) .22s;
}

/* No-JS fallback: the privacy trigger links to #modal-privacy, so
   :target opens the modal without any script (JS preventDefaults the
   click, so this path never fires when the script is running). */
.modal:target {
    display: flex;
    pointer-events: all;
}

.modal:target .modal__overlay {
    opacity: 1;
}

.modal:target .modal__inner {
    opacity: 1;
    transform: none;
}

/* no-JS close: clearing the hash un-targets the modal. Hidden when
   the script is running (JS close buttons take over). */
.modal__nojs-close {
    display: none;
    position: absolute;
    top: 1rem;
    right: 1rem;
    width: 3.6rem;
    height: 3.6rem;
    align-items: center;
    justify-content: center;
    border-radius: 999rem;
    color: var(--c-steel);
    text-decoration: none;
    font-size: 2rem;
}

.modal:target .modal__nojs-close {
    display: flex;
}

.modal.is-mounted .modal__nojs-close {
    display: none;
}

@media (min-width: 767.02px) {
    .modal__inner {
        padding: 4rem;
    }
}

.modal__inner--wide {
    max-width: 60rem;
}

.modal__head {
    text-align: center;
    margin-bottom: 2.4rem;
    line-height: 1.45;
}

@media (min-width: 767.02px) {
    .modal__head {
        margin-bottom: 4rem;
    }
}

.modal__head--left {
    text-align: left;
}

.modal__btn-close {
    position: absolute;
    top: 1rem;
    right: 1rem;
    width: 3.6rem;
    height: 3.6rem;
    border: none;
    background: none;
    border-radius: 999rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--c-steel);
    transition: transform .3s, background-color .3s;
}

.modal__btn-close:hover {
    transform: translateY(-2px);
    background-color: var(--c-mist);
}

/* Email field with copy button */
.email-field {
    display: flex;
    align-items: center;
    background-color: var(--c-frost);
    border-radius: .8rem;
    box-shadow: inset 0 1px 3px -1px rgba(0, 0, 0, .18);
    padding: 1.3rem 1.4rem 1.3rem 1.6rem;
}

@media (min-width: 767.02px) {
    .email-field {
        padding: 1.6rem 1.8rem 1.6rem 2rem;
    }
}

.email-field__addr {
    flex: 1;
    min-width: 0;
    font-size: 1.6rem;
    letter-spacing: -.01em;
    color: var(--c-ink);
    text-decoration: none;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.email-field__addr:hover,
.email-field__addr:focus-visible {
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* copy glyph sits inside the field — flat, embossed grey, no button chrome */
.email-field__copy {
    flex: none;
    margin-left: 1rem;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--c-steel);
    /* engraved/embossed look: dark icon + a faint light edge below */
    filter: drop-shadow(0 1px 0 rgba(255, 255, 255, .9));
    transition: color .2s;
}

.email-field__copy:hover,
.email-field__copy:focus-visible {
    color: var(--c-ink);
}

.email-field__copy .ic-check {
    display: none;
    color: #2e9e6b;
}

.email-field__copy.is-copied .ic-copy {
    display: none;
}

.email-field__copy.is-copied .ic-check {
    display: block;
}

.modal__legal {
    color: var(--c-steel);
    font-size: 1.45rem;
    line-height: 1.65;
}

.modal__legal h4 {
    color: var(--c-ink);
    font-weight: 600;
    font-size: 1.5rem;
    margin: 2rem 0 .6rem;
}

.modal__legal p+p {
    margin-top: 1rem;
}

/* ============================================================
   Motion system
   ------------------------------------------------------------
   One easing + one distance for everything, so the page feels
   coherent. Two families:

     1) LOAD-IN  (above the fold) — runs once on first paint,
        sequenced by --enter-delay:
          hero logo → tagline → CTA → "Learn more".
     2) SCROLL-IN (below the fold) — each section reveals as it
        enters the viewport, its own lines/items staggered by
        --reveal-delay.

   Both use the same .7s ease-out + 1.2rem rise, so a load-in and
   a scroll-in look like the same gesture at different times.
   ============================================================ */
:root {
    --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
    --rise: 1.2rem;
    --dur: .7s;
}

/* ---- LOAD-IN: only while the page is still "entering" ----
   opacity stays a hair above 0 so the browser records LCP at first
   paint instead of when the entrance choreography finishes. */
.is-entering .enter {
    opacity: 0.001;
    transform: translateY(var(--rise));
}

.enter {
    transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
    transition-delay: var(--enter-delay, 0ms);
}

/* the hero logo fades in without a rise — a transform would fight the
   blink animation's transform-origin */
.is-entering .enter--logo {
    opacity: 0;
}

.enter--logo {
    transition: opacity var(--dur) var(--ease-out);
    transition-delay: var(--enter-delay, 0ms);
}

/* ---- SCROLL-IN: line-by-line + block reveals ---- */
.reveal-line {
    display: block;
    opacity: 0;
    transform: translateY(var(--rise));
    transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
    transition-delay: var(--reveal-delay, 0ms);
}

.is-revealed .reveal-line {
    opacity: 1;
    transform: none;
}

.reveal-up {
    opacity: 0;
    transform: translateY(var(--rise));
    transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
    transition-delay: var(--reveal-delay, 0ms);
}

.reveal-up.is-revealed {
    opacity: 1;
    transform: none;
}

@media (prefers-reduced-motion: reduce) {

    .enter,
    .enter--logo,
    .reveal-line,
    .reveal-up {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
    }
}

/* ---- Reduced motion: no full-page rhythm at all — plain stacked
   sections with even spacing and regular scrolling (the slide engine
   is off in this mode) ---- */
@media (prefers-reduced-motion: reduce) and (min-width: 767.02px) {

    /* .wrapper qualifier outweighs the base rule below (which comes
       later in source order) */
    .wrapper .section,
    .wrapper .footer {
        scroll-margin-top: 10rem;
    }
}

@media (prefers-reduced-motion: reduce) {

    /* anchor jumps (header 6.7rem mobile / 8.4rem desktop is FIXED)
       must land content below it — sections here have no built-in
       clearance, unlike the full-page panels */
    .section,
    .footer {
        scroll-margin-top: 9rem;
    }

    .section,
    .section.section--page,
    .section--reel,
    .section--testimonials {
        padding-block: 6.4rem;
    }

    .hero {
        min-height: 0;
        padding-top: calc(var(--header-h) + 9.6rem);
        padding-bottom: 6.4rem;
    }

    .page-panel {
        min-height: 0;
        padding: 0;
        display: block;
    }

    .footer {
        min-height: 0;
        padding-top: 6.4rem;
    }

    /* undo the single-cell grid — blocks flow normally instead of
       overlapping at natural height */
    .footer>.shell {
        display: block;
    }

    .footer__bottom {
        margin-top: 9.6rem;
    }
}

/* ---- Print / save-as-PDF: everything visible, no scroll-lock voids ---- */
@media print {

    html,
    body,
    .wrapper {
        height: auto;
        overflow: visible;
    }


    .enter,
    .reveal-line,
    .reveal-up,
    .project,
    .testi-slide,
    .t-row {
        opacity: 1 !important;
        transform: none !important;
    }

    .reel-sticky {
        position: static;
        height: auto;
    }

    .section--reel,
    #testimonials {
        height: auto !important;
    }

    .project,
    .testi-slide {
        position: static;
    }

    figcaption.project__caption {
        display: block;
    }

    #reel-caption {
        display: none;
    }

    .reel-frame {
        overflow: visible;
    }

    .page-panel {
        min-height: 0;
    }

    .header,
    #reel-dots,
    #testi-dots {
        display: none;
    }
}
    
