/* ============================================================
   DAVIDE SEGA — core
   Tokens, reset, primitives.

   Design premise: the site is the frame, not the picture.
   The shell is achromatic on purpose — it owns no hue — so that
   projects with opposite aesthetics can each take over the accent
   without fighting the page around them. Colour enters only from
   the work: a project panel sets --accent, the chrome follows it,
   and everything returns to neutral on the way out.
   ============================================================ */

/* ---------------------------------------------------------- 1. Tokens */

:root {
  /* Achromatic surfaces. No warm or cool cast: an exhibition wall. */
  --bg:          #0a0a0a;
  --bg-deep:     #050505;
  --surface-1:   #101010;
  --surface-2:   #161616;
  --surface-3:   #1e1e1e;

  /* A paper white, not a screen white — one degree off neutral so
     large fields of it don't buzz. */
  /* The steps are set by contrast measurement, not by eye: --fg-2 and
     --fg-3 both clear 4.5:1 on --bg because they carry real text at
     11-14px; --fg-4 clears 3:1 and is reserved for display-size type
     and non-text marks. */
  --fg:          #efeeec;
  --fg-2:        rgb(239 238 236 / 0.62);   /* 6.8:1 */
  --fg-3:        rgb(239 238 236 / 0.56);   /* 5.6:1 — micro-labels */
  --fg-4:        rgb(239 238 236 / 0.38);   /* 3.2:1 — decorative only */

  --hair:        rgb(239 238 236 / 0.09);
  --hair-2:      rgb(239 238 236 / 0.16);
  --hair-3:      rgb(239 238 236 / 0.28);

  /* Accent. Default is the paper white itself — the shell has no
     colour of its own. Project panels override these three. */
  --accent:      #efeeec;
  --accent-ink:  #0a0a0a;
  --accent-soft: rgb(239 238 236 / 0.10);

  /* Type */
  --f-display: "Instrument Sans", ui-sans-serif, system-ui, sans-serif;
  --f-serif:   "Instrument Serif", ui-serif, Georgia, serif;
  --f-mono:    "Geist Mono", ui-monospace, "SF Mono", monospace;

  /* Fluid type scale. Each step is clamp(min, preferred, max). */
  --t-mega:  clamp(3.1rem, 1.1rem + 8.6vw, 9.5rem);
  --t-h1:    clamp(2.6rem, 1.2rem + 6.2vw, 7rem);
  --t-h2:    clamp(2.1rem, 1.2rem + 3.7vw, 4.3rem);
  --t-h3:    clamp(1.5rem, 1.1rem + 1.7vw, 2.4rem);
  --t-h4:    clamp(1.15rem, 1rem + 0.7vw, 1.5rem);
  --t-lead:  clamp(1.05rem, 0.97rem + 0.42vw, 1.32rem);
  --t-body:  clamp(0.95rem, 0.92rem + 0.16vw, 1.05rem);
  --t-small: 0.875rem;
  --t-micro: 0.6875rem;

  /* Space. Sections breathe heavily — this is the main lever that
     separates "editorial" from "landing page". */
  --gutter:  clamp(1.25rem, 0.6rem + 3.2vw, 5rem);
  --maxw:    88rem;
  --measure: 62ch;

  --sp-1: 0.25rem;  --sp-2: 0.5rem;   --sp-3: 0.75rem;
  --sp-4: 1rem;     --sp-5: 1.5rem;   --sp-6: 2rem;
  --sp-7: 3rem;     --sp-8: 4rem;     --sp-9: 6rem;
  /* Every section carries this top AND bottom, so adjacent sections
     always show twice this number. At the old 12.5rem cap that was
     ~480px of empty page between one section and the next — measured,
     not estimated. Halving the cap keeps the layout generous without
     making the visitor scroll through nothing. */
  --sp-section: clamp(4rem, 2.2rem + 4.4vw, 7.5rem);

  /* Radii, kept concentric: an inner core inside an outer shell
     uses --r-in so the two curves stay parallel. */
  --r-out:  22px;
  --r-in:   16px;
  --r-pill: 999px;

  /* Motion. Nothing linear, nothing ease-in-out. */
  --e-glide:  cubic-bezier(0.32, 0.72, 0, 1);
  --e-out:    cubic-bezier(0.16, 1, 0.3, 1);
  --e-inout:  cubic-bezier(0.65, 0, 0.35, 1);
  --e-spring: cubic-bezier(0.34, 1.4, 0.64, 1);

  /* Duration is chosen by what the motion is for, not by how big the
     element is. Interaction feedback has to land inside the moment the
     visitor is still watching their own click; entrances are allowed to
     take their time because nobody is waiting on them. */
  --d-press: 140ms;   /* press feedback — must feel like the click itself */
  --d-hover: 200ms;   /* hover state, hit constantly: short or it drags */
  --d-fast:  220ms;   /* small state changes, colour */
  --d-panel: 420ms;   /* disclosure panels */
  --d-mid:   480ms;
  --d-slow:  820ms;   /* first-time entrances only */

  /* Systemic layers only. No arbitrary z-indexes anywhere else. */
  --z-rail:    20;
  --z-nav:     40;
  --z-overlay: 60;
  --z-grain:   80;

  --nav-h: 76px;

  color-scheme: dark;
}

/* Light inversion. Applied to a project panel that needs it (today
   the Fantasy plate) and to the reading overlays. Every token flips;
   nothing else has to know. */
[data-surface="light"] {
  --bg:          #efeeec;
  --bg-deep:     #e6e5e2;
  --surface-1:   #e9e8e5;
  --surface-2:   #e2e1dd;
  --surface-3:   #dbdad6;

  /* Dark ink on a light field needs more alpha than light ink on a
     dark one to reach the same ratio — these are measured, not
     mirrored from the dark set. */
  --fg:          #0a0a0a;
  --fg-2:        rgb(10 10 10 / 0.66);
  --fg-3:        rgb(10 10 10 / 0.62);
  --fg-4:        rgb(10 10 10 / 0.48);

  --hair:        rgb(10 10 10 / 0.11);
  --hair-2:      rgb(10 10 10 / 0.18);
  --hair-3:      rgb(10 10 10 / 0.30);

  --accent:      #0a0a0a;
  --accent-ink:  #efeeec;
  --accent-soft: rgb(10 10 10 / 0.07);

  color-scheme: light;
}

/* ---------------------------------------------------------- 2. Reset */

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

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The rail and nav are fixed; this keeps #anchors clear of them. */
  scroll-padding-top: calc(var(--nav-h) + 2rem);
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--f-display);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.6;
  letter-spacing: -0.006em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
  transition: background-color var(--d-slow) var(--e-glide),
              color var(--d-slow) var(--e-glide);
}

body.is-locked { overflow: hidden; }

img, picture, svg, video { display: block; max-width: 100%; }
img { height: auto; }

button, input, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }

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

h1, h2, h3, h4 {
  font-weight: 500;
  line-height: 0.98;
  letter-spacing: -0.038em;
  text-wrap: balance;
}

p { text-wrap: pretty; }

ul, ol { list-style: none; padding: 0; }

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

::selection { background: var(--fg); color: var(--bg); }

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

.skip-link {
  position: fixed; top: 0; left: 50%;
  transform: translate(-50%, -120%);
  z-index: calc(var(--z-overlay) + 1);
  padding: 0.85rem 1.5rem;
  background: var(--fg); color: var(--bg);
  border-radius: 0 0 12px 12px;
  font-size: var(--t-small); font-weight: 500;
  transition: transform var(--d-fast) var(--e-out);
}
.skip-link:focus-visible { transform: translate(-50%, 0); }

/* ------------------------------------------------------- 3. Grain */

/* Fixed, non-interactive, outside the scroll flow — a grain layer
   attached to scrolling content repaints on every frame. */
.grain {
  position: fixed;
  inset: -50%;
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: 0.032;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)'/%3E%3C/svg%3E");
  /* steps() means six discrete transforms, not a continuous tween, so
     this is already cheap; a permanent will-change on a layer the size
     of the viewport would cost more than it saves. */
  animation: grain-drift 5.6s steps(6) infinite;
}
@keyframes grain-drift {
  0%   { transform: translate3d(0, 0, 0); }
  16%  { transform: translate3d(-2%, 1%, 0); }
  33%  { transform: translate3d(1%, -2%, 0); }
  50%  { transform: translate3d(-1%, 2%, 0); }
  66%  { transform: translate3d(2%, 1%, 0); }
  83%  { transform: translate3d(-2%, -1%, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* --------------------------------------------------- 4. Layout atoms */

.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--sp-section); position: relative; }

/* Section headings are sized here, once, instead of per section.
   `#servizi` and `#portfolio` had no font-size rule of their own and
   were falling back to the browser default — 25px next to the 69px of
   every other heading on the page, which reads as a broken layout
   rather than as hierarchy. Binding the size to the structure means a
   section added later cannot inherit that hole. */
.section-head h2 { font-size: var(--t-h2); }

/* A section's opening line: eyebrow on the left, hairline running to
   the right edge. Cheap to draw, and it sets the editorial register. */
.section-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-6);
  margin-bottom: clamp(2.5rem, 1.4rem + 3.2vw, 4.5rem);
}
@media (min-width: 900px) {
  /* Three items, two columns, two rows. The eyebrow gets a row of its
     own so the heading and the paragraph beside it start on the same
     line. Nesting the eyebrow with the heading — the obvious markup —
     aligns the paragraph to the *eyebrow* instead, which reads as the
     description floating above the title it belongs to. */
  .section-head {
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    column-gap: var(--sp-8);
    row-gap: 1.35rem;
    align-items: start;
  }
  .section-head > .eyebrow    { grid-column: 1; grid-row: 1; }
  .section-head > h2          { grid-column: 1; grid-row: 2; }
  .section-head > :last-child { grid-column: 2; grid-row: 2; }
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  font-family: var(--f-mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-3);
}
.eyebrow::before {
  content: "";
  width: 1.9em; height: 1px;
  background: currentColor;
  opacity: 0.6;
}
.eyebrow--pill {
  padding: 0.45em 0.95em 0.5em;
  border: 1px solid var(--hair-2);
  border-radius: var(--r-pill);
  color: var(--fg-2);
}
.eyebrow--pill::before { display: none; }

.rule { height: 1px; background: var(--hair); border: 0; }

.serif-em {
  font-family: var(--f-serif);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.015em;
  /* Instrument Serif sits optically small next to the grotesk. */
  font-size: 1.06em;
}

.lead {
  font-size: var(--t-lead);
  line-height: 1.5;
  color: var(--fg-2);
  max-width: var(--measure);
}

/* Numbering sits next to real content (contact steps, case-study
   kickers), so it is held to the label tier, not the decorative one. */
.num {
  font-family: var(--f-mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--fg-3);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------ 5. Buttons */

.btn {
  --btn-pad: 0.72rem;
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.9rem;
  padding: var(--btn-pad) var(--btn-pad) var(--btn-pad) 1.5rem;
  border-radius: var(--r-pill);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: -0.01em;
  white-space: nowrap;
  /* Asymmetric on purpose: colour can ease across 220ms, but the press
     has to arrive with the finger. 220ms on the depress reads as lag. */
  transition: background-color var(--d-fast) ease,
              color var(--d-fast) ease,
              border-color var(--d-fast) ease,
              transform var(--d-press) var(--e-out);
}
.btn:active { transform: scale(0.978); }

/* The trailing glyph never sits naked beside the label — it lives in
   its own well, flush with the button's inner padding. */
.btn__ico {
  display: grid;
  place-items: center;
  width: 2rem; height: 2rem;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--accent-soft);
  /* 480ms of spring on a hover state is decoration the visitor has to
     wait through every single time. Same curve, a third of the length. */
  transition: transform var(--d-hover) var(--e-spring),
              background-color var(--d-fast) ease;
}
.btn__ico svg { width: 13px; height: 13px; }

.btn--primary {
  background: var(--accent);
  color: var(--accent-ink);
}
.btn--primary .btn__ico { background: rgb(0 0 0 / 0.10); }
.btn--primary:hover { background: color-mix(in oklab, var(--accent) 88%, var(--bg)); }
.btn--primary:hover .btn__ico { background: rgb(0 0 0 / 0.18); }

.btn--ghost {
  border: 1px solid var(--hair-2);
  color: var(--fg);
}
.btn--ghost:hover { border-color: var(--hair-3); background: var(--accent-soft); }

/* Every pressable surface gets the same depress. Without it the ones
   that aren't `.btn` — the accordion rows, the glossary card, the
   overlay close — feel dead next to the ones that are.
   Elements that already declare a `transition` shorthand later in the
   cascade carry `transform` inside that declaration instead; adding it
   here would simply be overwritten. */
.svc__head, .link { transition: transform var(--d-press) var(--e-out); }

.svc__head:active,
.glossary-cue:active,
.ov__close:active { transform: scale(0.992); }

.lang button:active,
.nav__link:active,
.link:active { transform: scale(0.96); }

/* Hover motion, gated.
   A touch device fires :hover on tap and then leaves the element stuck
   in it — the arrow stays nudged, the underline stays drawn, and the
   user has no way to undo it. Colour changes are harmless that way;
   movement is not. So the movement lives here and the colour doesn't. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover .btn__ico { transform: translate(2px, -2px) scale(1.06); }
  .link:hover::after   { transform: scaleX(1); }
  .link:hover svg      { transform: translate(2px, -2px); }
}

.btn--lg { --btn-pad: 0.85rem; font-size: var(--t-body); padding-left: 1.75rem; }
.btn--lg .btn__ico { width: 2.3rem; height: 2.3rem; }

/* A text link that draws its own underline on hover, left to right. */
.link {
  position: relative;
  display: inline-flex; align-items: center; gap: 0.5em;
  font-size: var(--t-small); font-weight: 500;
  color: var(--fg);
}
.link::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: -3px;
  height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--d-fast) var(--e-glide);
}
.link svg { width: 11px; height: 11px; transition: transform var(--d-hover) var(--e-spring); }

/* ------------------------------------------- 6. Double-bezel plate */

/* Nothing premium sits flat on the background. An outer tray holds an
   inner core; the radii are concentric and the core carries a 1px
   top highlight so it reads as a plate seated in a frame. */
.plate {
  padding: 6px;
  border: 1px solid var(--hair);
  border-radius: var(--r-out);
  background: linear-gradient(180deg, rgb(239 238 236 / 0.045), rgb(239 238 236 / 0.012));
}
.plate__core {
  position: relative;
  border-radius: var(--r-in);
  background: var(--surface-1);
  box-shadow: inset 0 1px 0 rgb(239 238 236 / 0.06);
  overflow: hidden;
}
[data-surface="light"] .plate {
  background: linear-gradient(180deg, rgb(10 10 10 / 0.04), rgb(10 10 10 / 0.01));
}
[data-surface="light"] .plate__core {
  background: var(--surface-1);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.6);
}

/* ------------------------------------------------- 7. Scroll reveal */

/* Failsafe by construction. Content is visible by default; the hidden
   state only exists while <html> carries .reveal-armed, which JS adds
   after it has confirmed IntersectionObserver works, and a watchdog
   strips again if anything stalls. A broken observer can never leave
   the page blank — the worst case is no animation. */
.reveal { --reveal-y: 26px; }

.reveal-armed .reveal:not(.is-in) {
  opacity: 0;
  transform: translate3d(0, var(--reveal-y), 0);
  filter: blur(6px);
}
.reveal-armed .reveal {
  transition: opacity var(--d-slow) var(--e-out),
              transform var(--d-slow) var(--e-out),
              filter var(--d-slow) var(--e-out);
  transition-delay: var(--reveal-delay, 0ms);
}
.reveal-armed .reveal.is-in {
  opacity: 1;
  transform: none;
  filter: none;
}

/* Word-level headline reveal. Same failsafe contract: the mask is only
   armed under .reveal-armed, and at rest the word sits at y=0. The old
   build shipped the inverse and lost its H1 in production. */
.hw { display: inline-block; overflow: hidden; vertical-align: bottom; padding-bottom: 0.06em; }
.hw > span { display: inline-block; }

/* will-change promotes each word to its own layer, so it is scoped to
   the moment the words are actually moving. Left on permanently it
   holds a dozen composited layers for a headline that finishes
   animating in the first second and then never moves again. */
.reveal-armed .headline:not(.is-in) .hw > span { will-change: transform; }

.reveal-armed .headline:not(.is-in) .hw > span { transform: translate3d(0, 108%, 0); }
.reveal-armed .headline .hw > span {
  transition: transform 1s var(--e-glide);
  transition-delay: calc(var(--i, 0) * 55ms);
}
.reveal-armed .headline.is-in .hw > span { transform: none; }

/* Reduced motion means *less* movement, not no feedback.
   The blanket `transition-duration: 0.01ms !important` this used to
   carry is the usual mistake: it also kills the hover, the press and
   the open/close fades, so every state change teleports and the
   interface stops confirming that it heard you. What actually causes
   trouble is displacement — things flying in, parallax, drift. That is
   what gets removed here; opacity and colour stay. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  /* Ambient, non-communicative motion: gone. */
  .grain { animation: none; }
  .vitals__dot { animation: none; }

  /* Entrances keep their fade, lose their travel and their blur. */
  .reveal-armed .reveal:not(.is-in) {
    opacity: 0;
    transform: none;
    filter: none;
  }
  .reveal-armed .reveal { transition: opacity var(--d-panel) ease; }
  .reveal-armed .headline:not(.is-in) .hw > span { transform: none; }

  /* No element travels on hover; the colour change still answers. */
  .btn:hover .btn__ico,
  .link:hover svg,
  .svc__item:hover .svc__title,
  .work__media:hover picture { transform: none; }

  /* Panels open instantly rather than sliding their own height. */
  .svc__panel { transition: none; }
  .svc__inner { transition: opacity var(--d-panel) ease; }

  /* Anything scroll-linked is pure displacement by definition. */
  .work__media img,
  .footer__mark span,
  .scroll-progress span { animation: none !important; }
}
