/* ===================================================================
   SCENE — la navigazione a scene
   ===================================================================
   Il meccanismo che trasforma sette sezioni impilate in sette schermate
   che si scambiano il posto: il palco, i token di fit che adattano ogni
   pannello all'altezza del viewport, le transizioni di entrata e di
   uscita, il footer che sale come un cassetto.

   ATTENZIONE ALL'ORDINE
   Questi quattro file erano un solo css/style.css da 1615 righe, tagliato
   in sequenza il 04/09/2026: nessuna riga e' stata spostata rispetto alle
   altre, ne' riscritta. Nel CSS, a parita' di specificita', vince chi
   arriva dopo — quindi in index.html vanno collegati in QUEST'ORDINE e in
   nessun altro:

       base.css  ->  layout.css  ->  scene.css  ->  responsive.css

   Invertirne due cambia quale regola vince, e rompe la pagina in punti
   difficili da ricollegare alla causa.
   =================================================================== */

/* ===== Scene mode: desktop "game-like" navigation =====
   Off by default — index.html ships every .scene in normal document flow
   (this is also the permanent fallback for no-JS, <981px, and
   prefers-reduced-motion, since js/main.js only ever sets these classes on
   desktop once the wheel nav — body.nav-wheel-ready — is ready).

   Two classes, two different lifetimes:
   - .scene-mode-ready: the STRUCTURAL layout (track/scene sizing, absolute
     stacking, opacity cross-fade). Set once when scene mode initialises and
     never removed for the rest of the session (short of the <981px
     downgrade). Sizing .scene-track to a permanent 100vh here — not only
     while pinned — is what keeps the layout height constant across the
     footer hand-off; the earlier version tied this to the pinned class,
     which meant unpinning also let all six sections revert to their full
     stacked height simultaneously, a jarring jump to wherever that height
     put the scroll position.
   - .scene-pinned: just the wheel-capturing lock (fixed stage, hidden body
     overflow). Toggled on/off every time the state machine hands off to the
     native footer scroll and re-engages from it.

   Each section keeps its own padding exactly as in normal-flow mode
   (including the wheel-lane padding-left from option-wheel.css); only
   .scene-inner scrolls. */
/* Not wrapped in a width media query any more.

   It used to be @media (min-width: 981px), from when scene mode was a desktop
   feature and the phone got the stacked page instead. The gate is now
   body.scene-mode-ready alone — js/main.js adds that class only where scene
   mode actually engages, so the width test was doing the same job twice and
   was the one thing standing between these rules and a phone. Everything
   phone-specific lives in the max-width block further down, which overrides
   from here rather than duplicating. */
/* ===== Scene-fit tokens =====
   Scene mode makes exactly one promise: a section IS a screen. Everything in
   this block is what keeps that promise on a laptop.

   The page's type and spacing were sized in fixed px against a 1080px-tall
   viewport, and on anything shorter two things go wrong at once. The room
   shrinks — 1080 to 768 is 29% of the vertical budget gone — while the
   content GROWS, because the wheel's lane (--wheel-lane, ~215px at 1366)
   narrows the column and the same paragraphs wrap onto more lines. Content
   expanding as its box contracts is what put ~300px of #servizi and ~190px
   of #progetti behind an internal scrollbar, and an internal scrollbar is
   the one thing scene mode exists to replace: the first wheel tick scrolled
   the panel instead of turning to the next one, so a laptop needed two ticks
   per section where a 1080p desktop needed one.

   So every number that feeds a panel's height is a token, and every token is
   a straight line between two anchors: the value it already had at 1080px of
   viewport height — the reference look, unchanged — and a compact value at
   720px. `calc(A*vh + B)` is that line written out; the clamp() bounds hold
   it flat outside the range. Read the middle term as "the value at 1080" and
   the first bound as "and never smaller than this".

   Scoped to scene mode on purpose. In stacked flow the page can simply grow
   taller, so height is not a constraint there and these must not apply. */
body.scene-mode-ready .scene {
  /* --sc-fit is the last-resort compression, and it is the reason the SIZE
     tokens below are written as max(floor, calc(min(ceiling, line) * fit))
     rather than as a plain clamp(): a clamp cannot be scaled from outside,
     this shape can. js/main.js measures every panel once it is laid out and,
     only if one still does not fit, lowers this for that panel alone until it
     does — see fitScene(). It stays at 1 on every viewport the tokens
     already cover, which today is everything from 1024x640 upward.

     Deliberately applied to spacing and box sizes ONLY. Type keeps its own
     clamp() and is never scaled by the net: a panel that fits because its
     body copy dropped to 11px has not been fitted, it has been broken. When
     the spacing floors are reached and it still does not fit, the panel is
     allowed to scroll — at that point there genuinely is not room.

     Declared on .scene and NOT on <body>, which is the whole reason this
     works at all: a custom property is computed where it is DECLARED and
     inherits as a finished value, so tokens written on <body> would resolve
     var(--sc-fit) against <body> and an override set on a section further
     down could never reach them — the net would run, report success, and
     change nothing. Written here, each panel computes its own set against
     its own --sc-fit, and the inline value js/main.js writes on one section
     is read by that section's tokens and by no other's. */
  --sc-fit: 1;

  /* Breathing room above and below every panel (64px at 1080, 40px at 768) */
  --sc-pad-block: max(18px, calc(min(68px, 7.78vh - 20px) * var(--sc-fit)));

  /* Section headers */
  --sc-eyebrow-gap: max(6px,  calc(min(16px, 1.67vh - 2px) * var(--sc-fit)));
  --sc-header-gap:  max(16px, calc(min(64px, 10.56vh - 50px) * var(--sc-fit)));

  /* Grids and cards */
  --sc-grid-gap:    max(10px, calc(min(24px, 2.22vh) * var(--sc-fit)));
  --sc-card-pad:    max(15px, calc(min(40px, 4.44vh - 8px) * var(--sc-fit)));
  --sc-card-icon:   max(30px, calc(min(52px, 3.33vh + 16px) * var(--sc-fit)));
  --sc-card-gap:    max(6px,  calc(min(12px, 0.83vh + 3px) * var(--sc-fit)));

  /* Progetti */
  --sc-preview-h:   max(90px, calc(min(190px, 16.67vh + 10px) * var(--sc-fit)));
  --sc-preview-gap: max(9px,  calc(min(24px, 2.5vh - 3px) * var(--sc-fit)));
  --sc-tag-gap:     max(8px,  calc(min(20px, 1.94vh - 1px) * var(--sc-fit)));

  /* Chi Sono */
  --sc-col-gap:     max(24px, calc(min(64px, 6.67vh - 8px) * var(--sc-fit)));
  --sc-prose-gap:   max(7px,  calc(min(20px, 2.22vh - 4px) * var(--sc-fit)));

  /* Type. Never scaled by --sc-fit — see the note above. */
  --sc-h2:          clamp(30px, calc(3.89vh + 6px), 48px);
  --sc-card-h3:     clamp(17px, calc(1.39vh + 9px), 24px);
  --sc-card-p:      clamp(13px, calc(0.278vh + 11.5px), 14.5px);
  --sc-proj-h3:     clamp(16px, calc(0.97vh + 10.5px), 21px);
  --sc-lead:        clamp(17px, calc(1.11vh + 10px), 22px);
  --sc-prose:       clamp(13.5px, calc(0.417vh + 11px), 15.5px);
  --sc-hero-title:  clamp(56px, calc(6.67vh + 20px), 92px);
  --sc-cta-title:   clamp(34px, calc(5vh + 4px), 58px);
}

body.scene-mode-ready .scene-track { height: 100vh; }

/* Always the containing block for .scene's position:absolute, pinned or
   not — see .scene-pinned below for the fixed-to-viewport override. */
body.scene-mode-ready .scene-stage {
  position: relative;
  height: 100vh;
  /* Parked scenes sit 9% outside the viewport on either side — clip them
     here so they never widen the page. */
  overflow: hidden;
}

body.scene-pinned { overflow: hidden; }
body.scene-pinned .scene-stage {
  position: fixed;
  inset: 0;
  overflow: hidden;
}

/* Sections no longer dissolve in place: the outgoing one banks away to the
   LEFT while the incoming one arrives from the RIGHT, pushed back in z and
   swung a few degrees so the swap reads as a camera pan across a row of
   panels. js/main.js mirrors both halves when travelling back up the list,
   and parks each incoming scene on the correct edge (with .scene--instant)
   before releasing it, so an entry never starts from a stale position. */
body.scene-mode-ready .scene {
  position: absolute;
  inset: 0;
  height: 100%;
  opacity: 0;
  pointer-events: none;
  /* Resting place for any scene that hasn't been parked explicitly: off to
     the right, i.e. "not yet reached". */
  transform: perspective(1500px) translate3d(9%, 0, -170px) rotateY(-6deg);
  /* ===== Le due meta' in fila, non sovrapposte =====
     Prima partivano insieme: la scena vecchia usciva in 520ms mentre la nuova
     entrava in 720, e per mezzo secondo si vedevano tutte e due. Luca: "c'e'
     un disallineamento nell'entrata e uscita, prima deve finire di uscire
     quella in atto e poi entra subito quella dopo".

     Ora sono in sequenza: 300ms per uscire, poi 420 per entrare (il ritardo e'
     sulla sola .scene--active, qui sotto). Il totale resta 720ms, cioe'
     esattamente quanto durava prima — scelta di Luca fra le tre proposte, e la
     ragione e' che questo sito cambia scena in continuazione: ogni
     millisecondo aggiunto qui si paga a ogni singolo gesto. */
  transition: opacity 0.42s var(--ease-scene-in), transform 0.42s var(--ease-scene-in);
}

body.scene-mode-ready .scene--from-left {
  transform: perspective(1500px) translate3d(-9%, 0, -170px) rotateY(6deg);
}
body.scene-mode-ready .scene--from-right {
  transform: perspective(1500px) translate3d(9%, 0, -170px) rotateY(-6deg);
}

/* `none`, not an explicit identity list: a settled scene must carry no
   transform at all, or it becomes a stacking context and the hero's
   mix-blend-mode lightning stops compositing against the page. */
body.scene-mode-ready .scene.scene--active {
  opacity: 1;
  pointer-events: auto;
  transform: none;
  /* L'attesa che mette in fila le due meta'. Sta qui e non sulla regola di
     base perche' quella vale anche per la scena che ESCE, che deve partire
     subito: se ritardassero tutte e due non cambierebbe niente. */
  transition-delay: 0.3s;
}

/* A parked scene has to be completely inert, and `pointer-events: none` on
   the section above is not enough on its own: the property is INHERITED, so
   any descendant that sets `auto` for its own reasons opts straight back in
   — and since every scene is stacked at inset: 0, that descendant then sits
   invisibly over whichever section is actually on screen.

   One element does exactly that today: css/button-carousel.css turns hit
   testing back on for its active slide, so the Risultati carousel was
   hovering at 0% opacity over the middle of every other section. It ate the
   hover on the centre project card (which is how it was found), and it had
   been eating clicks on anything else that landed under it for as long as
   both features have existed.

   Specificity is what settles it, not !important: 1 element + 3 classes
   here against the 2 classes of `.button-carousel__slide.is-active`. Any
   future descendant that reaches for `pointer-events: auto` is covered by
   the same rule without knowing about it. */
body.scene-mode-ready .scene:not(.scene--active),
body.scene-mode-ready .scene:not(.scene--active) * {
  pointer-events: none;
}

/* The departing half leaves a little quicker and accelerating, so the eye
   settles on the arriving section instead of tracking the one going away. */
body.scene-mode-ready .scene--exiting {
  transition-duration: 0.3s;
  transition-timing-function: var(--ease-scene-out);
  /* Esce per prima: nessuna attesa, e va detto esplicitamente perche' una
     scena che esce ha addosso anche .scene--active per un istante. */
  transition-delay: 0s;
}

/* One un-animated frame, used by js/main.js to snap an incoming scene onto
   its starting edge before the entry transition runs. */
body.scene-mode-ready .scene--instant { transition: none; }

/* Every panel gets the same vertical breathing room, so the box its content
   is centred inside is identical from scene to scene. The stacked-flow
   paddings (140/60 for most sections, 160/180 for the CTA, 60/60 for the
   hero) were sized for a continuously scrolling page; as full-viewport
   panels they only made each scene's content start at a different height.
   The extra element in the selector is deliberate: it has to outrank
   option-wheel.css's `body.nav-wheel-ready .hero`, which sets its own
   padding-top and is loaded after this file. */
body.scene-mode-ready main .scene {
  padding-block: var(--sc-pad-block);
}

body.scene-mode-ready .scene-inner {
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: none; /* keep the game-like feel: no visible scrollbar chrome */
  /* Vertically centre the panel. The hero used to be the only scene doing
     this, which is why the others sat high or low depending on how much
     content they happened to carry.

     `safe` is what makes this correct for a panel taller than its box: it
     falls back to top alignment instead of overflowing equally in both
     directions, which would push the head of the content above the scroll
     origin where no amount of scrolling can reach it. Browsers without
     `safe` drop the declaration and land back on top alignment, which is
     exactly the old behaviour. */
  display: flex;
  flex-direction: column;
  justify-content: safe center;
}
body.scene-mode-ready .scene-inner::-webkit-scrollbar { display: none; }

/* Flex items shrink by default; a tall panel has to keep its natural height
   and let .scene-inner scroll, not get squashed to fit. */
body.scene-mode-ready .scene-inner > * { flex-shrink: 0; }

/* ===== Token application =====
   One rule per number the tokens above replace. Nothing here changes what a
   panel CONTAINS or how it is arranged — it only re-sizes what is already
   there. The arrangement changes live in the max-height block further down,
   which is a separate decision and takes effect at a separate threshold. */

/* Headers */
body.scene-mode-ready .section-header { margin-bottom: var(--sc-header-gap); }
body.scene-mode-ready .section-header.align-left { margin-bottom: 0; }
body.scene-mode-ready .section-header h2 { font-size: var(--sc-h2); }
body.scene-mode-ready .eyebrow { margin-bottom: var(--sc-eyebrow-gap); }

/* Hero */
body.scene-mode-ready .hero-title { font-size: var(--sc-hero-title); }
body.scene-mode-ready .tech-label { margin-bottom: var(--sc-header-gap); }

/* Cards, both grids */
body.scene-mode-ready .feature-grid,
body.scene-mode-ready .projects-grid { gap: var(--sc-grid-gap); }
body.scene-mode-ready .spotlight-card { padding: var(--sc-card-pad); }
body.scene-mode-ready .spotlight-card h3 {
  font-size: var(--sc-card-h3);
  margin-bottom: var(--sc-card-gap);
}
body.scene-mode-ready .spotlight-card p { font-size: var(--sc-card-p); }
body.scene-mode-ready .card-icon {
  width: var(--sc-card-icon);
  height: var(--sc-card-icon);
  margin-bottom: var(--sc-card-gap);
  /* Corner radius tracks the box, or a 36px chip keeps a 16px radius meant
     for a 52px one and stops reading as a squircle. */
  border-radius: calc(var(--sc-card-icon) * 0.31);
}
body.scene-mode-ready .card-icon svg {
  width: calc(var(--sc-card-icon) * 0.42);
  height: calc(var(--sc-card-icon) * 0.42);
}

/* Progetti */
body.scene-mode-ready .project-card h3 { font-size: var(--sc-proj-h3); }
body.scene-mode-ready .project-tag { margin-bottom: var(--sc-tag-gap); }
body.scene-mode-ready .tag-row { margin-top: var(--sc-tag-gap); }
body.scene-mode-ready .project-preview { margin-bottom: var(--sc-preview-gap); }
/* Outranks the flat 190px in css/scroll-expand.css (2 classes to its 1), so
   load order between the two files stays irrelevant. js/scroll-expand.js
   measures this well with getBoundingClientRect at the moment the stage
   opens, so a height that moves with the viewport hands off just as cleanly
   as the fixed one did. */
body.scene-mode-ready .project-preview--shot { height: var(--sc-preview-h); }

/* Chi Sono */
body.scene-mode-ready .about-grid { gap: var(--sc-col-gap); }
body.scene-mode-ready .about-side { gap: var(--sc-header-gap); }
body.scene-mode-ready .about-lead {
  font-size: var(--sc-lead);
  margin-bottom: var(--sc-prose-gap) !important; /* matches the base rule's own !important */
}
body.scene-mode-ready .about-content p {
  font-size: var(--sc-prose);
  margin-bottom: var(--sc-prose-gap);
}
body.scene-mode-ready .about-facts {
  gap: var(--sc-prose-gap);
  margin-top: var(--sc-header-gap);
  padding-top: var(--sc-header-gap);
}

/* CTA finale */
body.scene-mode-ready .gradient-text { font-size: var(--sc-cta-title); }
body.scene-mode-ready .final-cta p { margin-bottom: var(--sc-header-gap); }

/* ===== Footer as a slide-up drawer =====
   Out of flow entirely, parked just below the viewport and raised over the
   stage once the last scene is exhausted. This replaces the old hand-off,
   where the footer sat after the stage in normal flow and the page unpinned
   to scroll down to it — in scene mode the page now never scrolls natively
   at all, so there is no mode switch, no dead tick, and no way to strand
   the wheel outside the scenes.

   `translate` rather than `transform` on purpose: js/nav-dodge.js writes
   `transform` on .footer-inner, and the two must not fight over one
   property (same reason .reveal uses it). */
body.scene-mode-ready .footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 95; /* over the nav rail (90), which the drawer covers */
  background: var(--bg);
  border-top: 1px solid var(--glass-border);
  box-shadow: 0 -28px 60px -18px rgba(0, 0, 0, 0.92);
  translate: 0 100%;
  transition: translate 0.62s var(--ease-scene-in);
}
body.scene-mode-ready.footer-open .footer { translate: 0 0; }

/* LaserFlow layer: the closing section's beam.

   Component CSS upstream is just `width/height: 100%; pointer-events: none`
   on the container; everything below is this site's placement of it.

   It belongs to the Contatti scene, so it fades in with that scene rather
   than with the drawer. Then, when the drawer rises, the whole layer rides
   up with it by exactly the panel's height: the layer's bottom edge stays
   welded to the panel's top edge in both states, which is what keeps the
   beam landing on the footer instead of drifting off it. Both halves use
   the drawer's own duration so they read as one movement.

   Clipping the layer at the panel edge costs nothing, because the panel is
   opaque and covers everything below that line anyway.

   Stacked between the scene stage and the drawer (z-index 94 against the
   drawer's 95) so the beam passes behind the panel rather than over its
   text. `screen` is what makes an opaque canvas usable here: the shader
   clears to black, and screen blending drops black to nothing while keeping
   the beam and its haze additive over the page. Same approach as
   .lightning-bg in the hero. */
body.scene-mode-ready .footer-laser {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* Full height, not a strip at the bottom. The shader derives its unit
     scale from the canvas resolution, so the layer's height also sets how
     far the beam reaches: at 62vh it faded out barely half way up the
     screen and read as a stub sitting on the floor rather than a beam
     crossing the section. */
  height: 100vh;
  z-index: 94;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0;
  translate: 0 0;
  transition: opacity 0.75s var(--ease-scene-in), translate 0.62s var(--ease-scene-in);
}

body.scene-mode-ready.scene-contatti .footer-laser { opacity: 1; }

/* --footer-h is measured and published by js/main.js. */
body.scene-mode-ready.footer-open .footer-laser {
  translate: 0 calc(-1 * var(--footer-h, 0px));
}

.footer-laser canvas { display: block; }

/* ...and they stretch across the cross axis, which would blow the CTA's
   inline-level children — its button most visibly — out to the full column
   width. Keep those at their natural size. */
body.scene-mode-ready .scene-inner > .btn,
body.scene-mode-ready .scene-inner > .eyebrow {
  align-self: center;
}


/* ===== Scene mode: recomposition on a short viewport =====
   The tokens above re-size what a panel already is. This block is the other
   half of the fit: below ~980px of viewport height, two panels stop being able
   to hold their reference arrangement no matter how small the numbers get, and
   they change SHAPE instead of shrinking further.

   Why a second threshold rather than folding this into the tokens: shrinking is
   continuous and invisible, restructuring is a jump. Putting the jump at 980px
   of height keeps every 1050px-and-up display — the whole 1080p/1440p desktop
   range, which is the design's reference — on the arrangement the page was
   drawn with, and asks only laptops to trade it for one that fits.

   Still gated on min-width so this never reaches the stacked/mobile layout,
   where the page is free to grow and neither change is wanted. */
@media (min-width: 981px) and (max-height: 980px) {

  /* Servizi: five cards in a 3+2 grid is the tallest panel on the site, and the
     icon is what makes each card tall — a 52px chip on its own row, plus its
     gap, costs ~76px of card height while using none of the card's width.

     Laid beside the title instead, the two share one row: the chip disappears
     into height the heading was spending anyway, and five cards give back
     ~55px each. That is most of the deficit recovered from a single change,
     with nothing removed and nothing made smaller than the tokens already say.

     Grid rather than flex on the title row: the icon must not shrink and the
     heading must be free to wrap to two lines under its own column, which is
     exactly what `auto 1fr` plus a full-width body row expresses. */
  body.scene-mode-ready .feature-grid .spotlight-card {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "icon title"
      "body body";
    column-gap: calc(var(--sc-card-gap) + 4px);
    align-items: center;
  }
  body.scene-mode-ready .feature-grid .spotlight-card .card-icon {
    grid-area: icon;
    margin-bottom: 0;
  }
  body.scene-mode-ready .feature-grid .spotlight-card h3 {
    grid-area: title;
    margin-bottom: 0;
    /* Tightened from the base 1.6: at two lines beside a chip the default
       leading is what pushes the row past the icon's height again. */
    line-height: 1.2;
  }
  body.scene-mode-ready .feature-grid .spotlight-card p {
    grid-area: body;
    margin-top: calc(var(--sc-card-gap) + 2px);
  }

  /* Progetti: the descriptions run five to seven lines in a column this narrow,
     and they are the last thing in the card — so what fell off the bottom of
     the panel was the tag row, i.e. the part that is scannable.

     Clamped to three lines here. The text is NOT removed: it stays in the DOM
     for search engines and assistive tech, and the reader gets all of it in the
     full-screen stage that opens on click, where there is room for it. Above
     980px of height the clamp does not apply at all and the cards read exactly
     as they always have. */
  body.scene-mode-ready .project-card > p {
    /* Clamped with max-height and not with -webkit-line-clamp, which draws an
     ellipsis of its own at the truncation point. There is already one there:
     js/text-peek.js puts an amber one at the end of the line as the control
     that opens the full text, and the browser's grey one landed a few words
     to its left — two ellipses on one line, which reads as a mistake. This
     caps the same number of lines and draws nothing. */
    /* Cinque righe e non piu' tre, per lo stesso motivo del telefono: le tre
       pastiglie che rubavano quello spazio non ci sono piu'. La nota qui sopra
       dice che il taglio nacque proprio perche' la riga di etichette cadeva
       fuori dal pannello — tolte quelle, il motivo si e' ristretto. */
    max-height: calc(5 * 1lh);
    overflow: hidden;
  }
}
