/* ============================================================================
   Vise — Studio Grafico · site v7
   Rebuilt 1:1 from the Illustrator page designs (Web_V01_*.svg).

   The drawings resolve to one system, measured off the outlines:

     canvas      1440 wide, #111 ground, #fff ink
     grid        9 equal columns, no gutter
                 content 128.7 → 1311.3  (margin 8.9375%, column 131.4)
     type        JetBrains Mono 400 — 15/18 (chrome) · 20/24 (labels, lists)
                 Rowan 600        — 20/24 (running text, display)
     rhythm      every leading is 1.2; paragraph break = one blank line

   Every alignment in the drawings lands on that grid: the nav's right-hand
   items are right-aligned to column edges 6, 7 and 9; the contact panel opens
   on column edge 6; the case-study frame spans columns 3–7.
   ========================================================================== */

@font-face {
  font-family: "JetBrains Mono";
  src: url("assets/fonts/jetbrains-mono-variable.woff2") format("woff2");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Rowan";
  src: url("assets/fonts/rowan-variable.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------- tokens -- */

:root {
  color-scheme: dark;

  --ink: #111;            /* ground */
  --paper: #fff;          /* type */
  --dim: #212121;         /* ground behind the open contact panel */
  --button: #515151;      /* filled button */

  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --serif: "Rowan", "Iowan Old Style", Georgia, serif;

  /* Type — one point size for everything, the bar's. The drawings set page
     text a third larger than the bar (advance 12 against 9, so 20px against
     15px); this holds them equal instead, as asked. Leading still runs at 1.2.
     `--lh-meta` keeps the colophon's own 24px step, which is measured. */
  --fs-chrome: 15px;   --lh-chrome: 18px;   /* nav, meta, drawers */
  --fs-body: 15px;     --lh-body: 18px;     /* labels, lists, running text */
  --fs-drawer: 20px;   --lh-drawer: 24px;   /* serif inside the drawers */
  --lh-meta: 24px;                          /* the colophon's line step */

  /* grid — the content box is a share of the layout width, not of the
     viewport, so the scrollbar can't shift the margins off the drawing */
  --content: min(82.125%, 1400px);               /* 1182.6px at 1440 */
  --pad: clamp(24px, 8.9375vw, 160px);           /* the same margin, as a length */

  /* vertical anchors, measured from the drawings */
  --nav-top: 76.5px;      /* puts the nav cap-line on y = 80 */
  --content-top: 465.5px; /* puts the first content baseline on y = 480 */
  /* STUDIO: the drawings leave 156px between the last baseline of one section
     and the first of the next. That breathing space is the relationship worth
     keeping; the absolute y of the second section was only ever a consequence
     of how tall the first one happened to be. */
  --section-gap: 156.5px;

  /* Contact panel. Its left edge is grid column edge 6 — the same line as
     LAVORI's right edge in the bar — so the width is derived from the grid
     rather than fixed, and the alignment holds at any viewport. Resolves to
     522.9 at the design width, which is what the drawing measures. */
  --panel-w: calc(100% - (100% - var(--content)) / 2 - var(--content) * 6 / 9);
  --panel-pad-l: 71px;

}

/* ------------------------------------------------------------------ base -- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* the gutter is always reserved, so opening the contact panel (which locks
     scrolling) can't shift the grid sideways by a scrollbar width */
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--paper);
  font: 400 var(--fs-body)/var(--lh-body) var(--serif);
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }

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

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
}

:where(a, button):focus-visible {
  outline: 1px solid var(--paper);
  outline-offset: 3px;
}

/* Until the webfonts land the page is laid out on fallback metrics, which wrap
   the running text to a different number of lines — measured: one extra line per
   prose block, which pushed the next section down 17.5px and the colophon 35px.
   So the page is held until the faces are in place. JS caps the wait, so a font
   that never arrives can't leave a blank page, and `visibility` (not `display`)
   keeps the layout — and the scrollbar — stable while it waits. */
html.js:not(.fonts-ready) .page,
html.js:not(.fonts-ready) .panel { visibility: hidden; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ------------------------------------------------------------------ grid -- */

/* One 9-column grid, shared by the nav, every page section and the colophon.
   `align-items: baseline` is what keeps a column-1 label sitting on the same
   baseline as the running text in column 3 — as drawn. */
.grid {
  display: grid;
  grid-template-columns: repeat(9, 1fr);
  align-items: baseline;
  width: var(--content);
  margin-inline: auto;
}

/* -------------------------------------------------------------- top bar -- */

/* Clean and simple, and set in `exclusion` so it inverts rather than collides
   wherever it passes over type or the wordmark. */
.topbar {
  position: fixed;
  inset: 0 0 auto;
  z-index: 40;
  padding-top: var(--nav-top);
  padding-bottom: 24px;
  mix-blend-mode: exclusion;
  pointer-events: none;
}

.topbar .grid { align-items: start; }

.topbar a,
.topbar button {
  pointer-events: auto;
  font: 400 var(--fs-chrome)/var(--lh-chrome) var(--mono);
  letter-spacing: 0;
  text-transform: uppercase;
  white-space: nowrap;
}

/* left cluster reads from its column edge, right cluster reads to it */
.nav-home     { grid-column: 1; }
.nav-studio   { grid-column: 3; }
.nav-chisono  { grid-column: 4 / 6; }
.nav-lavori   { grid-column: 6 / 7; text-align: right; }
.nav-servizi  { grid-column: 7 / 8; text-align: right; }
.nav-contatti { grid-column: 9 / 10; text-align: right; }
.nav-menu     { display: none; }   /* the phone's stand-in for the whole bar */

/* The drawings give the current page no special treatment — the bar stays
   flat. `aria-current` is still on the link, for assistive tech. */

/* ------------------------------------------------------------------ page -- */

/* A column, so a page shorter than the screen puts its colophon on the floor
   rather than leaving a void under it — the running text keeps the top. */
.page {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
}

main { flex: 1 0 auto; }

main {
  padding-top: var(--content-top);
  padding-bottom: 200px;
}

main.flush { padding-top: 0; }
main.work  { padding-top: 372px; }   /* the frame, not a label, sets the top */

.section + .section { margin-top: var(--section-gap); }

/* column 1 — the section label */
.label {
  grid-column: 1 / 3;
  font: 400 var(--fs-body)/var(--lh-body) var(--mono);
  text-transform: uppercase;
  white-space: nowrap;
}

/* columns 3–5 — running text, 394px measure, exactly as drawn.
   Wrapping is left greedy on purpose: it reproduces the drawings' line breaks
   line for line, which `pretty`/`balance` would rebalance away. */
.prose {
  grid-column: 3 / 6;
  font: 600 var(--fs-body)/var(--lh-body) var(--serif);
  text-wrap: wrap;
}

.prose p + p { margin-top: var(--lh-body); }   /* one blank line */

/* columns 3–5 — a mono index (LAVORI) */
.index {
  grid-column: 3 / 6;
  font: 400 var(--fs-body)/var(--lh-body) var(--mono);
}

/* LAVORI carries a second pair of columns: the personal projects, kept
   deliberately apart from the client list. Measured off Web_V01_LAVORI.svg —
   label on column 6, list on column 8, both top-aligned with the first pair.
   See LAYOUT-SPEC.md for the numbers and how they were taken.

   The label breaks after «Altri» because the drawing breaks there. In the
   drawing it has no choice — at its 20px the string is wider than one column —
   whereas at the site's 15px it would sit on one line, so the break is written
   explicitly as a <br>. That still works under .label's `white-space: nowrap`:
   nowrap suppresses automatic wrapping, never a forced break. */
.label--altri { grid-column: 6 / 8; }
.index--altri { grid-column: 8 / 10; }

/* columns 7–9 — the secondary column (SERVIZI) */
.aside-list {
  grid-column: 7 / 10;
  font: 400 var(--fs-body)/var(--lh-body) var(--mono);
}

/* column 7 — the logomark on LO STUDIO, exactly one column wide */
.mark-figure {
  grid-column: 7 / 8;
  margin: 0;
  align-self: start;
  padding-top: 8.25px;
}
.mark-figure img { display: block; width: 100%; height: auto; }

/* ---------------------------------------------------------------- foto -- */

/* Le fotografie sono l'unico elemento del sito che non venga dai disegni, e
   restano volutamente spoglie: nessun bordo, nessun filtro, nessuno smusso.
   La cornice a filo di capello di .work-frame appartiene all'anteprima di un
   lavoro, non a una fotografia; qui la regola del sistema è quella di
   .mark-figure, cioè l'immagine e basta. Nessun trattamento di colore: le foto
   restano come consegnate. */
.photo-band img,
.photo-aside img {
  display: block;
  width: 100%;
  height: auto;   /* proporzione intatta, nessun ritaglio */
}

/* A tutta griglia, in chiusura di sezione. Lo stacco sopra è --section-gap,
   lo stesso che separa una sezione dall'altra: la fotografia chiude il
   discorso e sta da sola, alla stessa distanza dal testo che la precede e
   dalla sezione che la segue. */
.photo-band {
  grid-column: 1 / 10;
  margin: var(--section-gap) 0 0;
}

/* Colonne 7-9, accanto al testo: la stessa campata della colonna secondaria di
   SERVIZI (.aside-list). `align-self: start` perché la griglia allinea alla
   linea di base, che per un blocco senza testo cadrebbe sul suo bordo
   inferiore e trascinerebbe giù la riga. */
.photo-aside {
  grid-column: 7 / 10;
  margin: 0;
  align-self: start;
}

/* --------------------------------------------------------------- home -- */

/* The statement sits centred in the first screen, as drawn. */
.hero {
  position: relative;   /* containing block for .clouds, below */
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: 0 var(--pad);
}

.hero h1 {
  position: relative;   /* grid items only stack by z-index when non-auto */
  z-index: 1;            /* keeps the statement above the cloud layer */
  margin: 0;
  font: 600 var(--fs-body)/var(--lh-body) var(--serif);
  text-align: left;
}

/* Cory Arcangel's "Super Mario Clouds" — the game stripped to only its
   scrolling background clouds. Home hero only, decorative, GPU-cheap: the
   whole layer is one compositor-only translate, behind the statement and
   non-interactive.

   Shapes are Alessio's own Illustrator exports (Clouds-12.svg / Clouds-13.svg
   in PROJECT WEBSITE/Export/), cleaned into assets/clouds/ and inlined by
   gen.py, geometry verbatim — no more hand-traced approximation. Both sit on
   one shared pixel lattice: 33x24 and 65x24 sprite px, same resolution, the
   wide cloud genuinely wider rather than a scaled-up small one.

   Colour: #333333 — the previous #191919 lightened by 10, read as Sass's
   lighten() reads it: +10 points of HSL lightness, 9.8% → 19.8%. Still a long
   way off the #fff type, but the clouds now register as shapes on the black
   instead of only just separating from it. */
.clouds {
  position: absolute;
  inset: 0;
  overflow: hidden;   /* the strip below is 480vw wide; this is what keeps that
                          off the page's real scroll width */
  pointer-events: none;

  /* One sprite pixel — the single unit both clouds are sized from, so a step
     of the drift is exactly one pixel of the artwork.

     The vh term caps rendered height at 12vh. Height otherwise scales with
     viewport WIDTH while the lanes are spaced as a share of HEIGHT, so on a
     wide enough window adjacent lanes collide (seen at 1920x700). 12vh always
     leaves >=2vh of air inside the 14% lane gap, and the cap only engages
     above aspect 2.0 — a letterboxed window, not 16:9. (At 10vh it was
     binding at 1280x720 and shrinking the clouds 6% on the commonest desktop
     shape there is.) Where it does bind, a step stops being exactly one sprite
     pixel — the drift stays stepped and stays the right speed, only that
     coincidence lapses. */
  --px: min(0.25vw, 0.5vh);
}

/* The synchronisation fix. Measured off Alessio's recording, the reference
   scrolls as ONE background: cross-correlating frames 36s apart, every cloud
   shifts by the same 470 of the video's 1968px, with no relative drift at all.
   So the clouds here are fixed inside a tile and the tile is what moves —
   a single animation on a single node. They cannot fall out of step with each
   other, rather than being seven animations that merely agree.

   Two identical tiles side by side, translated by exactly one tile width per
   lap, so copy 2 lands precisely where copy 1 was: the loop is seamless by
   construction, with no timing to tune and no off-screen dwell. Every cloud
   sits wholly inside its tile, which is what makes two copies enough to cover
   the viewport at every point in the lap.

   Speed: 470/1968 of the width per 36s = 0.663 vw/s. 240vw / 0.663 = 361.8s.
   The previous 3.0 vw/s was 4.5x too fast — this is the correction.

   steps(960), not linear: 960 = the tile in sprite pixels, so each step
   advances the field exactly one pixel of the artwork. The reference does not
   glide; at native 34.6fps it visibly holds still, then jumps a whole NES
   pixel at once. At this sprite scale that lands at ~0.38s per step. */
.clouds__strip {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 480vw;
  animation: cloud-drift 361.8s steps(960) infinite;
}

.clouds__tile {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 240vw;
}

.clouds__tile:last-child { left: 240vw; }

@keyframes cloud-drift {
  from { transform: translateX(0); }
  to   { transform: translateX(-240vw); }
}

/* --sw/--sh are the sprite's own pixel dimensions, written into the element by
   gen.py; both clouds therefore scale together off one --px and keep their
   true relative size. Explicit height (rather than auto) with
   preserveAspectRatio="none" on the element makes the box exactly
   sprite-sized, so pixel steps land on pixel boundaries. */
.cloud {
  position: absolute;
  top: var(--top);
  left: var(--left);
  width: calc(var(--sw) * var(--px));
  height: calc(var(--sh) * var(--px));
  fill: #333333;
  shape-rendering: crispEdges;   /* belt-and-braces: the outlines are hard-edged
                                     by construction, this only stops the
                                     renderer softening them at odd scales */
}

@media (max-width: 760px) {
  /* Reference: 4-5 clouds visible at once; here, roughly half that, sparse and
     calm. On a phone three of the seven thin further still — the four kept are
     every other one along the tile, so the spread stays even. */
  .cloud--desktop-only { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  /* Calmer to remove them than to leave a frozen mid-drift frame sitting
     behind the statement — nothing drifts, nothing lingers half-arrived. */
  .clouds { display: none; }
}

/* ------------------------------------------------------- selected work -- */

/* Hairline frame spanning columns 3–7, drawn 657 × 1261 at the design width.
   A project can carry more than one preview, so the frames live in a stack and
   it is the stack, not the frame, that holds the grid position.

   The drawn 657/1261 belongs to the EMPTY frame — the shape of the placeholder,
   which is what the drawing actually shows. A frame holding artwork takes the
   artwork's own proportion instead.

   That is not a liberty, it is forced by the material: the first two previews
   were exported to 0.521 to match the frame, but the rest are ordinary
   landscape exports — Asterial 3.15, Blue Breath / Color-Verse / Vise
   wallpapers 1.50, Xbrace 1.33, Giovanni Federigo 0.245. Cropping those into a
   tall portrait would have shown 16% of Asterial's width, cutting its wordmark
   in half. Letting the frame follow the image loses nothing. */
.work-previews {
  grid-column: 3 / 8;
  grid-row: 1;
}

.work-frame {
  margin: 0;
  border: 1px solid var(--paper);
  display: grid;
  place-items: center;
  text-align: center;
}

/* a sequence of previews reads as one group, so the step between them is the
   body line rather than a section gap */
.work-frame + .work-frame { margin-top: var(--lh-body); }

/* only the empty frame is a fixed shape */
.work-frame:not(.work-frame--image) { aspect-ratio: 657 / 1261; }

.work-frame__note {
  font: 400 var(--fs-chrome)/var(--lh-chrome) var(--mono);
}

.work-frame img,
.work-frame video {
  width: 100%;
  height: auto;
  display: block;
}

/* Back to the list. Alessio asked for the drawers' CHIUDI, in style and in
   behaviour, so it borrows .panel__close's type outright — mono chrome,
   uppercase — and sits where CHIUDI sits, top right, on the frame's own top
   edge rather than the page's. It is a plain link, so it works without JS and
   keeps a real URL behind it. */
.work-back {
  grid-column: 8 / 10;
  grid-row: 1;
  align-self: start;
  text-align: right;
  font: 400 var(--fs-chrome)/var(--lh-chrome) var(--mono);
  text-transform: uppercase;
}

/* What the project is, under the frame. No drawing covers this. It is set in
   the same 15px Rowan as every other piece of running text on the site — the
   serif simply reads smaller than the mono beside it, its x-height being about
   10% shorter at the same size, which is a property of the face and not a
   difference in size. On Alessio's instruction the block is squared to the
   frame instead: columns 3–7, the frames' own span, so the text sits under the
   artwork edge to edge rather than to the narrower 394px prose measure. */
.work-about {
  grid-column: 3 / 8;
  grid-row: 2;
  margin-top: var(--lh-body);
  font: 600 var(--fs-body)/var(--lh-body) var(--serif);
}

.work-about p + p { margin-top: var(--lh-body); }

/* the project's own address, set apart from the description */
.work-about__link { font-family: var(--mono); font-weight: 400; }

/* -------------------------------------------------------------- colophon -- */

/* The wordmark and its three meta columns are one locked unit, exactly as
   drawn. The columns are keyed to the *letterforms*, not to the page grid:

       column 1  starts on the left edge of the `s`   (53.162 %)
       column 2  starts on the left edge of the `e`   (76.830 %)
       column 3  ends, at its widest row, on the right edge of the wordmark

   Measured off the exports, those come out at 53.09 %, 76.79 % and 99.89 % —
   so the percentages below are the drawing. Everything is a share of the
   wordmark's own width, which is why the block holds together at any size. */

.colophon {
  padding-bottom: 10px;   /* wordmark baseline sits 10px off the floor */
  /* Held back until the arrival is over, then faded in — the type settling is
     the page's own moment, and this keeps the footer out of anything the effect
     does to the blocks above it. JS caps the hold. */
  transition: opacity .3s ease;
}

html.js:not(.arrived) .colophon { opacity: 0; }

.colophon__block {
  position: relative;
  width: 75.4375%;      /* 1086.3 / 1440 */
  margin-left: 11.0347%;  /*  158.9 / 1440 */
}

.colophon__mark {
  display: block;
  width: 100%;
  height: auto;
}

.colophon__meta {
  position: absolute;
  inset: 0;
  mix-blend-mode: exclusion;
}

.colophon__meta ul {
  position: absolute;
  top: 4.6px;           /* puts the first baseline 22px under the wordmark's top */
  font: 400 var(--fs-chrome)/var(--lh-meta) var(--mono);
  text-transform: uppercase;
  white-space: nowrap;
  /* The columns are pinned to the letterforms, so the gap between column 1 and
     column 2 is a fixed share of the wordmark — it shrinks with the viewport
     while the labels would not. Below ~1150px the labels scale with the mark so
     the three columns keep exactly these positions instead of running together.
     At the design width this resolves to 15px and changes nothing. */
  font-size: min(var(--fs-chrome), 1.3043vw);
}

.meta-a { left: 53.162%; }   /* the `s` */
.meta-b { left: 76.830%; }   /* the `e` */
.meta-c { right: 0; }        /* widest row flush with the wordmark's right edge */

/* --------------------------------------------------------- contact panel -- */

.scrim {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: repeating-linear-gradient(
    to bottom,
    rgb(17 17 17 / .5) 0 9.5px,
    transparent 9.5px 25.5px
  );
  opacity: 0;
  visibility: hidden;
  transition: opacity .28s ease, visibility 0s linear .28s;
}

.panel {
  position: fixed;
  inset: 0 0 0 auto;
  z-index: 60;
  width: min(var(--panel-w), 100%);
  background: var(--ink);
  /* Equal safety space on both sides: CHIUDI keeps the same distance from the
     panel's right edge as CONTATTI keeps from its left. */
  padding: var(--nav-top) var(--panel-pad-l) 48px var(--panel-pad-l);
  overflow-y: auto;
  overscroll-behavior: contain;
  transform: translateX(100%);
  visibility: hidden;
  transition: transform .34s cubic-bezier(.22, 1, .36, 1), visibility 0s linear .34s;
}

/* `.panel-open` is the JS path; `:target` is the same state reached through the
   URL fragment, so the drawer still works with scripting off. */
body.panel-open .scrim,
html:not(.js) body:has(.panel:target) .scrim {
  opacity: 1;
  visibility: visible;
  transition: opacity .28s ease, visibility 0s;
}

.panel.is-open,
html:not(.js) .panel:target {
  transform: translateX(0);
  visibility: visible;
  transition: transform .34s cubic-bezier(.22, 1, .36, 1), visibility 0s;
}

/* the page behind reads as switched off — dimmed ground, 20% content */
body.panel-open .page,
html:not(.js) body:has(.panel:target) .page { background: var(--dim); }

body.panel-open .page > *,
html:not(.js) body:has(.panel:target) .page > * { opacity: .2; }

/* Lock scrolling on the root, not on <body>: an overflow lock on <body>
   propagates to the viewport and takes the reserved gutter with it, which
   would shuffle the whole grid sideways as the drawer opens. */
html:has(body.panel-open),
html:not(.js):has(.panel:target) { overflow: hidden; }

.panel__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font: 400 var(--fs-chrome)/var(--lh-chrome) var(--mono);
  text-transform: uppercase;
}

.panel__title { margin: 0; font: inherit; font-weight: 400; }

.panel__close {
  font: inherit;
  text-transform: uppercase;
}

.panel h2 {
  margin: 86px 0 0;
  font: 600 var(--fs-drawer)/var(--lh-drawer) var(--serif);
}

/* ------------------------------------------------------------ menu drawer -- */

/* Stands in for the nav on a phone, and follows the contact drawer's logic:
   label left, CHIUDI right, then the pages set in Rowan. Never shown on
   desktop, where the bar carries every page itself. */
.panel__list {
  margin-top: 86px;
  font: 600 var(--fs-drawer)/var(--lh-drawer) var(--serif);
}

.panel__foot {
  margin-top: 96px;
  font: 400 var(--fs-chrome)/var(--lh-chrome) var(--mono);
  text-transform: uppercase;
}

.panel__foot p + p { margin-top: var(--lh-chrome); }

.panel__contact {
  margin-top: 58px;
  font: 400 var(--fs-chrome)/var(--lh-chrome) var(--mono);
}

.panel__contact .is-upper { text-transform: uppercase; }

.panel__actions {
  display: flex;
  gap: 10.5px;
  margin-top: 7px;
}

.panel__actions > * {
  flex: 0 0 115.4px;
  height: 30.9px;
  display: grid;
  place-items: center;
  background: var(--button);
  font: 400 var(--fs-chrome)/1 var(--mono);
  text-transform: uppercase;
  text-align: center;
  cursor: pointer;
  white-space: nowrap;
}

.panel__follow {
  margin-top: 277px;
  font: 400 var(--fs-chrome)/var(--lh-chrome) var(--mono);
  text-transform: uppercase;
}

.panel__follow ul { margin-top: var(--lh-chrome); }
.panel__place { margin-top: var(--lh-chrome); }

/* --------------------------------------------------------------- motion -- */

/* Scroll behaviour is deliberately near-absent: type arrives, nothing moves.
   Gated on `html.js` so that if scripting is off — or site.js fails to load —
   the pages are simply complete rather than blank. */
.js .reveal { opacity: 0; }
.js .reveal.is-in { opacity: 1; transition: opacity .18s linear; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js .reveal, .js .reveal.is-in { opacity: 1; transition: none; }
  .scrim, .panel { transition: none; }
}

/* ----------------------------------------------------------- responsive -- */

/* Below the point where nine columns stop being readable the grid folds to
   two, then one. Sizes step down with it; the rhythm stays 1.2. */
/* Down to the fold-to-one-column point the nine columns are kept exactly as
   drawn — only the type steps down, so every alignment in the design survives
   at any width in this range. */
@media (max-width: 1100px) {
  :root {
    --fs-chrome: 14px; --lh-chrome: 17px;
    --fs-body: 14px;   --lh-body: 17px;
    --lh-meta: 22px;
    --content-top: 340px;
    --section-gap: 112px;
    --nav-top: 60px;
  }
  main { padding-bottom: 140px; }
}

/* ---------------------------------------------------------------- phone -- */

/* Drawn at 440 × 956 (iPhone 16 Pro Max logical size, exported at 3×). Read off
   the two mobile drawings:

     margin        23 both sides, content 394
     bar           one line, baseline on y = 68 — only VISE and MENU
     mono          10.89 / 13     (the desktop 15 / 18 at the drawing's 0.726)
     serif         14.51 / 17.5   (the desktop 20 / 24 at the same 0.726)
     drawers       full screen; label left, CHIUDI right; first baseline 221

   Every size above is the desktop value times 0.726 — that one factor is what
   the drawings scale by, and it holds for the type, the leading, the buttons
   and their gap alike. */
@media (max-width: 760px) {
  :root {
    --fs-chrome: 10.89px; --lh-chrome: 13px;
    --fs-body: 10.89px;   --lh-body: 13px;
    --lh-meta: 13px;
    --fs-drawer: 14.51px; --lh-drawer: 17.5px;
    --pad: 23px;
    --content: calc(100% - var(--pad) * 2);   /* 394 at 440 — margins of 23 */
    --nav-top: 58px;      /* puts the bar's baseline on y = 68 */
    --content-top: 96px;
    --section-gap: 72px;
    --panel-pad-l: 23px;
  }

  /* The bar carries only the wordmark and the way into the menu, as drawn.
     Nothing is held over the page at this width, so it scrolls away. */
  .topbar {
    position: static;
    padding-bottom: 0;
    mix-blend-mode: normal;
  }
  .topbar .grid { grid-template-columns: repeat(2, 1fr); }
  .nav-home { grid-column: 1; }
  .nav-menu {
    display: block;
    grid-column: 2;
    text-align: right;
  }
  .nav-studio, .nav-chisono, .nav-lavori, .nav-servizi, .nav-contatti {
    display: none;
  }

  .grid { grid-template-columns: 1fr; }
  .label, .prose, .index, .aside-list, .work-previews,
  .label--altri, .index--altri, .work-back, .work-about,
  .photo-band, .photo-aside,
  .mark-figure, .meta-a, .meta-b, .meta-c { grid-column: 1; }

  /* In colonna singola la foto di CHI SONO scende sotto al testo, dove scende
     anche la colonna secondaria di SERVIZI, e prende lo stesso stacco. La
     banda dello STUDIO non ha bisogno di nulla: il suo --section-gap è già
     responsive e qui vale 72px invece di 156.5. */
  .photo-aside { margin-top: var(--section-gap); }

  /* one column: the previews and the back link must stack, not share row 1 */
  .work-previews, .work-back, .work-about { grid-row: auto; }
  .work-back { margin-bottom: var(--lh-chrome); }

  /* The frame sits high on the desktop drawing — above where running text
     starts — but 372px of it on a phone is most of the screen before anything
     appears. Here it simply starts where every other page's content starts. */
  main.work { padding-top: var(--content-top); }

  /* the personal projects follow the client list rather than sitting beside it */
  .label--altri { margin-top: var(--section-gap); }

  .label { margin-bottom: var(--lh-body); }
  .aside-list { margin-top: var(--section-gap); }
  .mark-figure { width: 96px; margin-top: 40px; padding-top: 0; }
  /* 3/4 is the shape of the *empty* frame on a phone, as drawn. A frame holding
     artwork follows the artwork here too — same reasoning as on desktop. */
  .work-frame:not(.work-frame--image) { aspect-ratio: 3 / 4; }

  main { padding-bottom: 96px; }

  /* Both drawers take the whole screen and share one vertical anchor: the head
     on baseline 68, the first line of content on baseline 221. */
  .panel { width: 100%; }

  /* contact drawer — heading 221, contact block 295, buttons top 316,
     SEGUI 672, socials 698 / 711 / 724, place 750 */
  .panel h2 { margin-top: 136px; }
  .panel__contact { margin-top: 43px; }
  .panel__actions {
    gap: 7.57px;
    margin-top: 5px;
  }
  .panel__actions > * {
    flex: 0 0 83.8px;
    height: 22.47px;
  }
  .panel__follow { margin-top: 323.5px; }

  /* menu drawer — pages 221 → 346 on a 25 step, foot 724 / 750 */
  .panel__list { margin-top: 132.5px; }
  .panel__list ul { line-height: 25px; }
  .panel__foot { margin-top: 360.5px; }

  /* The colophon breaks apart only here: holding the letterform alignment would
     need labels too small to read. */
  .colophon { padding-bottom: 48px; }
  .colophon__block { width: var(--content); margin-inline: auto; }
  /* The three pairs run across on one line, spread to the wordmark's own edges
     so the outer two line up with it. `column-gap` is the floor, so they still
     can't touch on a narrow phone. */
  .colophon__meta {
    position: static;
    margin-top: 20px;
    mix-blend-mode: normal;
    display: flex;
    justify-content: space-between;
    column-gap: 12px;
  }
  .colophon__meta ul {
    position: static;
    left: auto;
    right: auto;
    font-size: var(--fs-chrome);
    line-height: var(--lh-chrome);
  }
}
