/* ============================================================
   VISIOMINDS V2 — shared design language
   Loaded by every page AFTER home.css, so it wins on equal
   specificity without home.css having to be rewritten.

   Thesis: the work is the interface. Type is set large enough to
   be cropped by the viewport, imagery carries the argument, and
   the accent only ever marks state.

   Motion is hand-rolled in v2.js. No GSAP, no Lenis.
   ============================================================ */

:root {
    --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

    /* velocity skew, written by v2.js */
    --vel: 0deg;

    /* v2 rhythm — tighter than home.css, the page earns its length
       from imagery rather than from padding */
    --v2-pad:   clamp(72px, 11vh, 150px);
    --v2-pad-s: clamp(48px, 7vh, 92px);
}

/* ------------------------------------------------------------
   1. DISPLAY TYPE — set to crop, not to fit
   home.css caps every hero around 168px and keeps it inside the
   gutter. The winners run type off both edges. These clamps push
   the ceiling up and let the line break where it wants.
   ------------------------------------------------------------ */
.hero__display,
.studio-hero__display,
.contact__display {
    font-size: clamp(52px, 13.5vw, 260px);
    line-height: 0.86;
    letter-spacing: -0.05em;
    max-width: none;
}

.plans-hero__title,
.work-archive-hero__title,
.services-page-hero__title,
.faq-hero__title,
.notfound__code {
    font-size: clamp(44px, 11vw, 190px);
    line-height: 0.88;
    letter-spacing: -0.045em;
    max-width: none;
}

.about__statement,
.work__title,
.services__title,
.process__title,
.team__title,
.manifesto__title,
.recognition__title,
.locale__title,
.studio-teaser__title,
.process-preview__title,
.pricing-ref__title {
    font-size: clamp(34px, 7vw, 108px);
    line-height: 0.94;
    letter-spacing: -0.04em;
}

/* ------------------------------------------------------------
   2. SYSTEM BAR
   The nav becomes a thin strip of live studio data rather than a
   chrome bar. Monospace, uppercase, low contrast until hovered.
   ------------------------------------------------------------ */
.nav__meta,
.nav__avail,
.nav__menu,
.nav__cta,
.section-eyebrow,
.eyebrow,
.work-card__index,
.work-card__year,
.clients__label,
.foot__legal,
.foot__bottom {
    font-family: var(--mono);
    letter-spacing: 0.08em;
}

.nav__menu { text-transform: uppercase; font-size: 11px; }
.nav__cta  { text-transform: uppercase; font-size: 11px; letter-spacing: 0.1em; }

.section-eyebrow,
.eyebrow {
    text-transform: uppercase;
    letter-spacing: 0.14em;
}

/* ------------------------------------------------------------
   3. REVEAL CHOREOGRAPHY
   Four behaviours instead of one. v2.js tags elements with
   data-rv and flips .is-shown when they enter.
   Everything starts visible and is only hidden once JS confirms
   it can animate — no-JS never leaves a blank page.
   ------------------------------------------------------------ */
/* will-change only while the element is still waiting to reveal. Leaving it
   on permanently promotes a compositor layer per element for the life of the
   page, and a promoted layer stops updating if the compositor stalls — which
   is exactly how content ends up stuck invisible. */
.v2-ready [data-rv]:not(.is-shown) { will-change: transform, opacity, clip-path; }

/* mask — a line wipes up from behind its own edge */
.v2-ready [data-rv="mask"] {
    clip-path: inset(0 0 100% 0);
    transform: translateY(0.18em);
    transition: clip-path .9s var(--ease-out), transform .9s var(--ease-out);
}
.v2-ready [data-rv="mask"].is-shown {
    clip-path: inset(0 0 -20% 0);
    transform: none;
}

/* rise — the workhorse, but with real distance and a longer tail */
.v2-ready [data-rv="rise"] {
    opacity: 0;
    transform: translateY(34px);
    transition: opacity .7s ease, transform .95s var(--ease-out);
}
.v2-ready [data-rv="rise"].is-shown { opacity: 1; transform: none; }

/* sweep — a horizontal wipe, for rules, images and full-bleed media */
.v2-ready [data-rv="sweep"] {
    clip-path: inset(0 100% 0 0);
    transition: clip-path 1.15s var(--ease-out);
}
.v2-ready [data-rv="sweep"].is-shown { clip-path: inset(0 0 0 0); }

/* scale — media settles out of an over-scale rather than fading */
.v2-ready [data-rv="scale"] { overflow: hidden; }
/* descendant, not child: case media is figure > img, work cards are div > img */
.v2-ready [data-rv="scale"] img,
.v2-ready [data-rv="scale"] video {
    transform: scale(1.14);
    transition: transform 1.5s var(--ease-out);
}
.v2-ready [data-rv="scale"].is-shown img,
.v2-ready [data-rv="scale"].is-shown video { transform: none; }

/* word-level sweep for body copy, driven by --i on each word */
.v2-ready [data-rv="read"] .rv-w {
    opacity: 0.18;
    transition: opacity .5s ease;
    transition-delay: calc(var(--i) * 14ms);
}
.v2-ready [data-rv="read"].is-shown .rv-w { opacity: 1; }

/* ------------------------------------------------------------
   4. VELOCITY SKEW
   The whole canvas leans very slightly into the scroll. Small
   enough to read as weight rather than as an effect.
   ------------------------------------------------------------ */
.v2-skew { transform: skewY(var(--vel)); transform-origin: 50% 0; will-change: transform; }

/* ------------------------------------------------------------
   5. ACCENT DISCIPLINE
   Red marks state and nothing else. It is never decoration.
   ------------------------------------------------------------ */
.section-eyebrow__num,
.work-card__year,
.nav__avail { color: var(--ink-faint); }

/* ------------------------------------------------------------
   6. FULL-BLEED MEDIA
   Case imagery runs edge to edge and the caption sits under it in
   mono, the way a plate caption would.
   ------------------------------------------------------------ */
.case-block__cap,
.work-card__tagline { font-family: var(--sans); }
.case-block__cap {
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-faint);
}

/* ------------------------------------------------------------
   7. PAGE TRANSITION
   A single sheet wipes across on navigation. v2.js drives it.
   ------------------------------------------------------------ */
.v2-wipe {
    position: fixed;
    inset: 0;
    z-index: 999;
    background: var(--bg);
    transform: scaleY(0);
    transform-origin: 50% 100%;
    pointer-events: none;
}
.v2-wipe.is-covering {
    transform: scaleY(1);
    transition: transform .55s cubic-bezier(0.7, 0, 0.3, 1);
}
.v2-wipe.is-clearing {
    transform: scaleY(0);
    transform-origin: 50% 0;
    transition: transform .6s cubic-bezier(0.7, 0, 0.3, 1);
}

/* ------------------------------------------------------------
   8. REDUCED MOTION
   Strip every behaviour, keep every composition.
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
    .v2-ready [data-rv] {
        opacity: 1 !important;
        transform: none !important;
        clip-path: none !important;
        transition: none !important;
    }
    .v2-ready [data-rv="scale"] img,
    .v2-ready [data-rv="scale"] video { transform: none !important; }
    .v2-ready [data-rv="read"] .rv-w { opacity: 1 !important; }
    .v2-skew { transform: none !important; }
    .v2-wipe { display: none; }
}

/* ------------------------------------------------------------
   10. FAILSAFE
   v2.js stamps .rv-done once an element's reveal has finished or
   timed out. Nothing after this point may hide it. Content being
   permanently invisible is far worse than a missed animation, and
   every image-pipeline bug found building this site had that same
   shape: a transition or callback that silently never fired.
   ------------------------------------------------------------ */
.v2-ready [data-rv].rv-done,
.v2-ready [data-rv].rv-done .rv-w {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
    will-change: auto !important;
}

/* ============================================================
   11. HERO — statement left, live work right

   Sized off the winners rather than off instinct: TRIONN's h1 is
   84px, Ciao Energy's is 64px, and both carry real media in the
   first screen. Earlier passes ran the type at 132-260px with no
   work on screen, which is the wrong trade for a studio whose case
   imagery is the argument.

   Fully theme-aware. An earlier version hardcoded this section to
   #0A0A0A with white text, which meant the theme toggle did nothing
   on the homepage. Everything here reads from the theme tokens.
   ============================================================ */
.vhero {
    position: relative;
    min-height: 100svh;
    display: grid;
    align-content: center;
    padding: 128px var(--gutter) 108px;
    background: var(--bg);
    overflow: hidden;
}
.vhero__grid {
    width: 100%;
    max-width: 1500px;
    margin: 0 auto;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr);
    gap: clamp(32px, 5vw, 88px);
    align-items: center;
}

.vhero__eyebrow {
    font-family: var(--mono);
    font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
    color: var(--ink-faint);
    margin-bottom: clamp(20px, 3vh, 34px);
}
.vhero__eyebrow i { font-style: normal; color: var(--red); margin: 0 6px; }

.vhero__line {
    font-family: var(--display);
    font-weight: 700;
    font-size: clamp(38px, 5.1vw, 84px);
    line-height: 1.02;
    letter-spacing: -0.035em;
    color: var(--ink);
    margin: 0;
    max-width: 15ch;
}
.vhero__line em {
    font-family: var(--serif);
    font-style: italic;
    font-weight: 400;
    letter-spacing: -0.01em;
    color: var(--ink-mute);
}

.vhero__lede {
    margin-top: clamp(22px, 3vh, 34px);
    max-width: 44ch;
    font-size: clamp(15px, 1.15vw, 17px);
    line-height: 1.68;
    color: var(--ink-mute);
}

.vhero__actions {
    margin-top: clamp(28px, 4vh, 44px);
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
}
.vhero__cta {
    display: inline-flex; align-items: center; gap: 12px;
    min-height: 48px; padding: 0 26px;
    border-radius: 999px;
    background: var(--ink); color: var(--bg);
    font-size: 13.5px; font-weight: 500;
    text-decoration: none;
    transition: background .3s ease, color .3s ease, gap .3s var(--ease-out);
}
.vhero__cta i { font-style: normal; }
.vhero__cta:hover { background: var(--red); color: var(--on-red, #fff); gap: 18px; }
.vhero__ghost-cta {
    display: inline-flex; align-items: center;
    min-height: 48px; padding: 0 22px;
    border: 1px solid var(--line-2);
    border-radius: 999px;
    color: var(--ink);
    font-size: 13.5px; text-decoration: none;
    transition: border-color .3s ease;
}
.vhero__ghost-cta:hover { border-color: var(--ink); }

/* ---- the work cluster ----

   Four layers, each on its own clock, composed by nesting rather than
   fought over one property:

     .vtile          slot        discrete, slow, CSS eased + staggered
     .vtile__inner   drift/float/scroll   continuous, per-frame, no transition
     .vtile__media   hover push  CSS eased
     img             ken-burns   CSS keyframes

   The previous version put the slot AND the pointer drift into a single
   transform on a single element carrying transition: transform .85s. The
   parallax therefore lagged most of a second behind the cursor, and the
   reshuffle could not be slow without the drift becoming sluggish too.
   Splitting them is the whole fix. The base state is correct with no JS
   running, so a stalled frame loop costs motion, never visibility.
   ---- */
.vhero__stack {
    position: relative;
    height: clamp(360px, 58vh, 620px);
}

.vtile {
    position: absolute;
    left: 0; top: 0;
    width: 52%;
    aspect-ratio: 3 / 4;
    display: block;
    text-decoration: none;
    transform-origin: 50% 50%;
    transform: translate3d(var(--sx, 0%), var(--sy, 0%), 0) scale(var(--sc, 1));
    transition: transform .9s var(--ease-out), opacity .5s ease, clip-path 1.05s var(--ease-out);
    transition-delay: var(--sd, 0ms), var(--sd, 0ms), calc(var(--n, 0) * 120ms);
    clip-path: inset(0 0 100% 0);          /* entry: uncovers from the bottom */
}
.v2-ready .vhero.is-live .vtile { clip-path: inset(0 0 0 0); }

/* the per-frame layer — deliberately carries no transition so it tracks
   the pointer 1:1; the smoothing is a lerp in the frame loop */
.vtile__inner {
    position: absolute;
    inset: 0;
    display: block;
    will-change: transform;
}

.vtile__media {
    position: absolute;
    inset: 0;
    overflow: hidden;
    border-radius: 3px;
    background: var(--bg-2);
    box-shadow: 0 30px 70px -30px rgba(0,0,0,.55);
    transition: transform .7s var(--ease-out), box-shadow .5s ease;
}
.vtile:hover .vtile__media {
    transform: scale(1.03);
    box-shadow: 0 44px 92px -30px rgba(0,0,0,.7);
}

.vtile__media img,
.vtile__media video {
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
    transform: scale(1.04);
    animation: tileKen 26s ease-in-out infinite alternate;
    animation-delay: calc(var(--n, 0) * -7s);   /* desynchronise the tiles */
}
/* Footage already moves; a ken-burns on top of it fights the shot. Video tiles
   hold still and let the clip do the work. */
.vtile__media video { animation: none; transform: scale(1.02); }
/* the poster frame sits under the video, so a clip that never starts — iOS low
   power, Save-Data, a failed file — still shows the project rather than a hole */
.vtile__media .vtile__poster {
    position: absolute; inset: 0;
    z-index: 0;
}
.vtile__media video { position: absolute; inset: 0; z-index: 1; opacity: 0; transition: opacity .6s ease; }
.vtile__media video.is-playing { opacity: 1; }
@keyframes tileKen {
    from { transform: scale(1.04) translate3d(0, 0, 0); }
    to   { transform: scale(1.10) translate3d(-1.6%, -1.1%, 0); }
}

.vtile__cap {
    position: absolute; left: 12px; right: 12px; bottom: 11px;
    font-family: var(--mono);
    font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
    color: #fff;
    text-shadow: 0 1px 12px rgba(0,0,0,.95);
    opacity: 0; transform: translateY(8px);
    transition: opacity .4s ease, transform .45s var(--ease-out);
    pointer-events: none;
}
.vtile:hover .vtile__cap { opacity: 1; transform: none; }

/* three slots, deliberately uneven — a grid of equals reads as a contact sheet */
.vtile--1 { --sx: 0%;   --sy: 6%;  --sc: 1;   z-index: 3; opacity: 1; }
.vtile--2 { --sx: 62%;  --sy: 32%; --sc: .82; z-index: 2; opacity: 1; }
.vtile--3 { --sx: 105%; --sy: -4%; --sc: .74; z-index: 1; opacity: 1; }

/* Two off-states, so the cluster reads as a strip moving leftward rather than
   everything collapsing to one point: what leaves sinks away to the left,
   what is queued waits off to the right and slides in. */
.vtile.is-off { --sx: 160%; --sy: 6%;  --sc: .66; opacity: 0; pointer-events: none; z-index: 0; }
.vtile.is-out { --sx: -46%; --sy: 22%; --sc: .62; opacity: 0; pointer-events: none; z-index: 0; }

/* ---- foot ---- */
.vhero__foot {
    position: absolute;
    left: var(--gutter); right: var(--gutter); bottom: 28px;
    display: flex; align-items: center; justify-content: space-between; gap: 20px;
    /* clear the fixed WhatsApp button, which sits bottom-right on every page */
    padding-right: 76px;
}
.vhero__index { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }
.vhero__index-label {
    font-family: var(--mono);
    font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
    color: var(--ink-faint);
}
.vhero__index ul { display: flex; gap: 20px; flex-wrap: wrap; list-style: none; margin: 0; padding: 0; }
.vhero__index a {
    position: relative;
    font-family: var(--mono);
    font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
    color: var(--ink-faint);
    text-decoration: none;
    display: inline-flex; align-items: center; min-height: 44px;
    transition: color .3s ease;
}
.vhero__index a::after {
    content: ''; position: absolute; left: 0; right: 0; bottom: 11px;
    height: 1px; background: var(--red);
    transform: scaleX(0); transform-origin: left;
    transition: transform .4s var(--ease-out);
}
.vhero__index a:hover, .vhero__index a:focus-visible, .vhero__index a.is-active { color: var(--ink); }
.vhero__index a:hover::after, .vhero__index a:focus-visible::after, .vhero__index a.is-active::after { transform: scaleX(1); }

.vhero__scroll {
    font-family: var(--mono);
    font-size: 10px; letter-spacing: .18em; text-transform: uppercase;
    color: var(--ink-faint);
    animation: scrollHint 2.6s ease-in-out infinite;
}
@keyframes scrollHint { 0%,100% { opacity:.45 } 50% { opacity:1 } }

@media (max-width: 1000px) {
    .vhero { padding-top: 112px; padding-bottom: 116px; min-height: auto; }
    .vhero__grid { grid-template-columns: 1fr; gap: 40px; }
    .vhero__line { font-size: clamp(36px, 8.6vw, 62px); max-width: 13ch; }
    .vhero__stack { height: clamp(280px, 42vh, 380px); }
    .vtile { width: 50%; }
    .vtile--2 { --sx: 62%; }
    .vtile--3 { --sx: 105%; }
    .vhero__foot { position: static; margin-top: 40px; flex-wrap: wrap; padding-right: 0; }
    .vhero__scroll { display: none; }
}
@media (max-width: 620px) {
    .vhero__index-label { display: none; }
    .vhero__index ul { gap: 0 16px; }
    .vtile { width: 56%; }
    .vtile--2 { --sx: 58%; --sc: .78; }
    .vtile--3 { --sx: 100%; --sc: .7; }
}

@media (prefers-reduced-motion: reduce) {
    .vtile { clip-path: none !important; transition: opacity .2s linear; }
    .vhero__scroll { animation: none; }
}
