/* ===== Strumenti: Magic Transform =====

   Vanilla port of React Bits Pro's "Magic Transform", adapted to this site's
   content — the same way css/scroll-expand.css, css/glass-surface.css and
   css/profile-card.css port their originals. The upstream component is a
   React + Tailwind package installed through shadcn against a licensed
   registry; this page has no build step and no React, so the behaviour is
   rebuilt here in plain CSS and js/magic-transform.js. Nothing is imported.

   The original streams anonymous "documents" past a central axis, shreds
   each one there, and emits coloured result chips out the far side. Here the
   documents are the REAL sites from the Progetti scene, and the chips that
   come out are the tools used to build that specific site — so the toolkit
   is no longer a list of logos floating free of anything, it is an answer to
   "with what did you build THAT one".

   Base markup (index.html) is a plain list: three projects, each with its
   tools named underneath. That is what renders with JS off and under
   prefers-reduced-motion — the same information, standing still.

   HOW THE TIMING WORKS
   Every moving part is a CSS animation. js/magic-transform.js only spawns a
   group of elements once per beat and sets a handful of custom properties on
   them; it never touches a style per frame. Everything that has to happen
   AFTER the document reaches the axis (the shred, the chips, the particles)
   is one animation with `animation-delay: var(--mt-travel)`, not a timer —
   which is what makes pausing exact: `animation-play-state: paused` on the
   stage freezes the whole beat mid-flight, delays included, and resuming
   picks it up where it stopped. */

/* Base padding/z-index comes from the shared rule in css/style.css
   (.features, .projects, .stats, .about, .skills) — one number, one place. */

.magic-transform {
  position: relative;
  width: 100%;
  max-width: 1000px;
  margin-inline: auto;
  /* .scene-inner is a flex column: a child whose content is wider than the
     box (the stage's belt is, by design) would otherwise floor the box at
     its content width and push the whole panel into horizontal overflow.
     Same release valve, and same reason, as .skills-marquee. */
  min-width: 0;

  /* One place for every duration and size the component uses. The stage
     reads them; js/magic-transform.js reads --mt-beat back off the element
     so the JS beat and the CSS delays can never drift apart. */
  --mt-beat: 4200ms;      /* one document in, one document shredded */
  --mt-travel: 10080ms;   /* left edge -> axis (2.4 beats: ~3 docs in flight) */
  /* How long the belt takes to eat one document, expressed as the fraction
     of the trip that happens past the band. Not a token: it falls out of the
     geometry, and js/magic-transform.js writes --mt-hit (the moment a page's
     leading edge reaches the beam) from the same measurement. */
  /* Longer than a beat on purpose: a mark is meant to be looked at, and the
     bursts overlapping slightly is what keeps the far side of the stage
     alive between one document and the next. */
  --mt-chip: 3800ms;
  --mt-stagger: 140ms;
  --mt-particle: 950ms;

  /* The page's shared --ease is an expo-out: it covers most of its distance
     in the first fifth of the time. Exactly right for something arriving and
     settling (the core's pulse still uses it), and wrong for both halves of
     this machine — a chip on it was at the far edge of the stage before it
     had begun to fade, so what should read as "flies out and dissipates"
     read as "appears at the edge, then dims". These two are gentler on
     purpose: a quadratic ease-out for anything travelling, and a symmetric
     ease-in-out for the shred, so a page is seen coming apart rather than
     found already in pieces. */
  --mt-ease-fly: cubic-bezier(0.25, 0.46, 0.45, 0.94);

  /* Capped on BOTH axes. 16vw alone is the right measure for a page held
     upright, and the wrong one the moment the phone is turned: at 844x390 it
     asked for a 135px document — 192px tall once the 1.42 ratio is applied —
     inside a stage that only had 390px of screen to sit in, and the labels
     fell off the bottom. min() takes whichever axis is scarcer, so every
     portrait and desktop size is untouched and landscape stops asking for
     the impossible. */
  --mt-doc-w: clamp(104px, min(16vw, 24vh), 176px);
  --mt-doc-h: calc(var(--mt-doc-w) * 1.42);

  /* Air between one page and the next, as a share of a page's own width.
     js/magic-transform.js reads this back and turns it into the belt's
     speed — spacing is what sets how fast a page has to travel to still
     arrive one per beat, so widening the gap here quickens the belt rather
     than thinning it out.

     0.27 is the original's proportion (60px at 220px wide) and is right on
     a wide stage. It is NOT right on a phone: the same 27% of a 104px page
     is 28px, and with a belt only 170px across that put two documents on
     screen almost touching. See the phone override at the foot of this
     file. */
  --mt-gap-ratio: 0.27;

  /* How wide the disintegration is, measured back from the beam. */
  --mt-band: clamp(64px, 11vw, 132px);

  /* The dissolve itself: a field of small squares whose coverage falls off
     from solid at the left of the band to nothing at the beam, generated
     once and pasted here as a data URI. It is a MASK, not a picture — what
     survives inside each surviving square is the screenshot's own pixels, so
     a page really does come apart into bits of itself rather than into some
     generic confetti laid over the top of it. */
  --mt-dither: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='120'%20height='384'%20viewBox='0%200%20120%20384'%3E%3Cpath%20fill='%23fff'%20fill-opacity='.5'%20d='M0%200h18v6h-18zM30%200h12v6h-12zM54%200h6v6h-6zM90%200h6v6h-6zM0%206h6v6h-6zM18%206h24v6h-24zM48%206h6v6h-6zM60%206h6v6h-6zM84%206h12v6h-12zM0%2012h24v6h-24zM30%2012h18v6h-18zM54%2012h6v6h-6zM72%2012h12v6h-12zM90%2012h12v6h-12zM0%2018h36v6h-36zM42%2018h6v6h-6zM54%2018h18v6h-18zM78%2018h6v6h-6zM108%2018h6v6h-6zM0%2024h48v6h-48zM54%2024h6v6h-6zM0%2030h36v6h-36zM42%2030h18v6h-18zM66%2030h12v6h-12zM84%2030h6v6h-6zM0%2036h12v6h-12zM18%2036h24v6h-24zM66%2036h12v6h-12zM102%2036h12v6h-12zM0%2042h24v6h-24zM30%2042h6v6h-6zM54%2042h18v6h-18zM84%2042h6v6h-6zM0%2048h36v6h-36zM48%2048h6v6h-6zM66%2048h6v6h-6zM0%2054h42v6h-42zM54%2054h6v6h-6zM66%2054h6v6h-6zM78%2054h18v6h-18zM102%2054h6v6h-6zM0%2060h18v6h-18zM24%2060h6v6h-6zM54%2060h6v6h-6zM72%2060h6v6h-6zM84%2060h6v6h-6zM102%2060h6v6h-6zM0%2066h24v6h-24zM30%2066h18v6h-18zM54%2066h12v6h-12zM84%2066h6v6h-6zM0%2072h36v6h-36zM48%2072h12v6h-12zM72%2072h6v6h-6zM0%2078h6v6h-6zM12%2078h6v6h-6zM24%2078h6v6h-6zM36%2078h30v6h-30zM84%2078h6v6h-6zM0%2084h36v6h-36zM54%2084h6v6h-6zM66%2084h18v6h-18zM0%2090h24v6h-24zM30%2090h12v6h-12zM48%2090h6v6h-6zM66%2090h12v6h-12zM84%2090h6v6h-6zM96%2090h6v6h-6zM108%2090h6v6h-6zM0%2096h24v6h-24zM30%2096h12v6h-12zM48%2096h6v6h-6zM60%2096h6v6h-6zM84%2096h6v6h-6zM102%2096h6v6h-6zM0%20102h36v6h-36zM42%20102h18v6h-18zM66%20102h24v6h-24zM96%20102h6v6h-6zM0%20108h60v6h-60zM66%20108h6v6h-6zM78%20108h6v6h-6zM0%20114h42v6h-42zM48%20114h6v6h-6zM66%20114h6v6h-6zM6%20120h18v6h-18zM36%20120h6v6h-6zM54%20120h12v6h-12zM78%20120h6v6h-6zM96%20120h6v6h-6zM0%20126h6v6h-6zM12%20126h36v6h-36zM54%20126h6v6h-6zM66%20126h6v6h-6zM78%20126h6v6h-6zM0%20132h12v6h-12zM18%20132h12v6h-12zM36%20132h6v6h-6zM66%20132h12v6h-12zM84%20132h6v6h-6zM0%20138h6v6h-6zM12%20138h6v6h-6zM24%20138h18v6h-18zM48%20138h6v6h-6zM66%20138h12v6h-12zM0%20144h48v6h-48zM54%20144h6v6h-6zM102%20144h6v6h-6zM0%20150h24v6h-24zM60%20150h6v6h-6zM90%20150h6v6h-6zM0%20156h42v6h-42zM54%20156h6v6h-6zM102%20156h6v6h-6zM0%20162h18v6h-18zM24%20162h42v6h-42zM90%20162h18v6h-18zM0%20168h30v6h-30zM36%20168h12v6h-12zM60%20168h6v6h-6zM78%20168h12v6h-12zM102%20168h6v6h-6zM0%20174h54v6h-54zM66%20174h6v6h-6zM78%20174h6v6h-6zM0%20180h24v6h-24zM30%20180h6v6h-6zM60%20180h12v6h-12zM102%20180h6v6h-6zM0%20186h48v6h-48zM72%20186h12v6h-12zM102%20186h6v6h-6zM0%20192h12v6h-12zM18%20192h12v6h-12zM42%20192h6v6h-6zM54%20192h12v6h-12zM0%20198h66v6h-66zM72%20198h6v6h-6zM0%20204h24v6h-24zM30%20204h6v6h-6zM42%20204h12v6h-12zM60%20204h6v6h-6zM102%20204h12v6h-12zM0%20210h48v6h-48zM60%20210h6v6h-6zM78%20210h6v6h-6zM90%20210h6v6h-6zM0%20216h42v6h-42zM48%20216h6v6h-6zM66%20216h6v6h-6zM90%20216h12v6h-12zM0%20222h36v6h-36zM42%20222h18v6h-18zM66%20222h6v6h-6zM78%20222h6v6h-6zM0%20228h30v6h-30zM36%20228h12v6h-12zM60%20228h12v6h-12zM78%20228h6v6h-6zM0%20234h30v6h-30zM36%20234h18v6h-18zM60%20234h6v6h-6zM84%20234h6v6h-6zM114%20234h6v6h-6zM0%20240h18v6h-18zM24%20240h18v6h-18zM48%20240h24v6h-24zM78%20240h18v6h-18zM0%20246h18v6h-18zM24%20246h6v6h-6zM54%20246h12v6h-12zM84%20246h6v6h-6zM0%20252h48v6h-48zM54%20252h12v6h-12zM72%20252h6v6h-6zM90%20252h6v6h-6zM0%20258h24v6h-24zM36%20258h6v6h-6zM48%20258h6v6h-6zM60%20258h6v6h-6zM72%20258h12v6h-12zM0%20264h18v6h-18zM24%20264h42v6h-42zM72%20264h12v6h-12zM0%20270h30v6h-30zM36%20270h18v6h-18zM66%20270h12v6h-12zM0%20276h6v6h-6zM18%20276h6v6h-6zM30%20276h6v6h-6zM42%20276h12v6h-12zM60%20276h6v6h-6zM0%20282h24v6h-24zM30%20282h6v6h-6zM48%20282h18v6h-18zM72%20282h6v6h-6zM0%20288h72v6h-72zM96%20288h6v6h-6zM0%20294h18v6h-18zM24%20294h18v6h-18zM48%20294h12v6h-12zM66%20294h6v6h-6zM78%20294h12v6h-12zM0%20300h30v6h-30zM42%20300h12v6h-12zM66%20300h12v6h-12zM102%20300h6v6h-6zM0%20306h36v6h-36zM42%20306h6v6h-6zM54%20306h6v6h-6zM66%20306h24v6h-24zM0%20312h18v6h-18zM24%20312h18v6h-18zM48%20312h6v6h-6zM66%20312h12v6h-12zM108%20312h6v6h-6zM0%20318h12v6h-12zM18%20318h18v6h-18zM54%20318h18v6h-18zM0%20324h36v6h-36zM54%20324h6v6h-6zM0%20330h36v6h-36zM48%20330h12v6h-12zM66%20330h6v6h-6zM84%20330h6v6h-6zM0%20336h6v6h-6zM12%20336h42v6h-42zM66%20336h6v6h-6zM78%20336h6v6h-6zM0%20342h30v6h-30zM48%20342h6v6h-6zM60%20342h6v6h-6zM84%20342h12v6h-12zM0%20348h42v6h-42zM48%20348h6v6h-6zM60%20348h6v6h-6zM78%20348h6v6h-6zM96%20348h6v6h-6zM0%20354h30v6h-30zM36%20354h30v6h-30zM72%20354h6v6h-6zM96%20354h12v6h-12zM0%20360h24v6h-24zM30%20360h18v6h-18zM54%20360h6v6h-6zM0%20366h42v6h-42zM48%20366h6v6h-6zM84%20366h6v6h-6zM0%20372h42v6h-42zM54%20372h24v6h-24zM0%20378h12v6h-12zM18%20378h30v6h-30zM60%20378h18v6h-18z'/%3E%3C/svg%3E");
}

.mt-lede {
  max-width: 54ch;
  /* The pull upward has to be measured against the thing it cancels. The
     heading's own bottom margin is --sc-header-gap, which is fluid: 64px on
     a desktop, and down at its 16px floor on a landscape phone. A flat -44px
     was fine against 64 and 28px MORE than there was against 16, which is
     why the lede climbed on top of "Cosa so usare" when the phone was
     turned. Taking 70% of whatever the gap currently is can never exceed it;
     the max() keeps the desktop figure at exactly the -44px it was tuned to.

     The bottom margin gives way on a short screen for the same reason the
     rest of the panel does. */
  margin: max(calc(var(--sc-header-gap, 64px) * -0.7), -44px) auto clamp(18px, 4.6vh, 40px);
  text-align: center;
  font-size: 15px;
  color: var(--text-50);
}

/* The sprite holds every tool mark once, as <symbol>s. The chips the JS
   builds and the static list below both point at the same definitions with
   <use>, so a mark is described in the markup exactly one time no matter how
   many times it appears on screen. */
.mt-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* ===== Base: the plain list =====
   Three projects, their tools spelled out. Ships with JS off, and stays put
   under prefers-reduced-motion — js/magic-transform.js checks the same media
   query and simply does nothing when it is set. Not a fallback: it is the
   readable version of the same claim. */
.mt-source {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 28px;
  list-style: none;
}

.mt-item {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px;
  border: 1px solid var(--hairline);
  border-radius: 16px;
  background: var(--glass-bg);
}

.mt-item__shot {
  width: 100%;
  height: 150px;
  object-fit: cover;
  object-position: top center;
  border-radius: 10px;
  border: 1px solid var(--glass-border);
}

.mt-item__tag {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
}

.mt-item__name {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: 22px;
  line-height: 1.2;
}

.mt-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 12px;
  list-style: none;
  margin-top: auto;
}

/* Bare marks here too, for the same reason as on the stage — the still
   version of the section should look like the moving one stopped, not like a
   different design. */
.mt-tool {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 58px;
}

.mt-tool__icon { width: 30px; height: 30px; flex-shrink: 0; }

.mt-tool__name {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-50);
  text-align: center;
  line-height: 1.3;
}

/* Once the stage is up, the list stops being the presentation and becomes
   the accessible text behind it: still in the accessibility tree and still
   in the page's text, just not drawn. The stage itself is aria-hidden, so a
   screen reader hears this list and never the choreography.
   The thumbnails go entirely — the stage paints the same files as CSS
   backgrounds, and a clipped <img> nobody can see is dead weight. */
.mt-source--upgraded {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.mt-source--upgraded .mt-item__shot { display: none; }

/* ===== Live: the stage ===== */
.mt-stage {
  position: relative;
  /* Nearer the original's proportions: it gives the machine 560px and the
     room is most of what makes it read as a machine rather than a strip. */
  /* The floor is what a landscape phone runs into: 46vh of 390px is 179, so
     the clamp held the stage at 260px on a screen that had no 260px to give
     and the panel overflowed. Only viewports under ~452px tall ever reach
     this bound, so changing it touches landscape and nothing else.

     208 rather than the 196 that merely fit: the tallest document is the one
     whose name wraps onto a second line, and at 196 it cleared the stage by
     a single pixel top and bottom — true today and false the moment a
     project gets a longer name. The panel had the room to spare. */
  height: clamp(208px, 46vh, 430px);
  overflow: hidden;
  border-radius: 20px;
  border: 1px solid var(--hairline);
  background:
    radial-gradient(120% 90% at 50% 50%, rgba(219, 107, 43, 0.07), transparent 62%),
    rgba(255, 255, 255, 0.015);
  /* The stage fades at both edges so a document is never seen to pop into
     existence — it arrives already faint, exactly like the marquee's mask.
     Scaled rather than fixed: 56px is a soft hem on a 1000px stage and a
     third of a phone's, where it was eating the chips whole. */
  --mt-hem: clamp(20px, 4vw, 52px);
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--mt-hem), #000 calc(100% - var(--mt-hem)), transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 var(--mt-hem), #000 calc(100% - var(--mt-hem)), transparent 100%);
}

/* One brake, one class. .is-idle is put on by js/magic-transform.js only for
   the reasons the machine should not be running at all: the reader is on
   another scene, the panel is off screen, or the tab is in the background.

   The pointer is deliberately NOT one of them. The marquee this section
   replaced paused under the cursor because it was a strip of names to read
   and stopping it let you read one; a machine that runs is not asking to be
   read, and freezing it mid-shred every time the mouse crosses the panel
   made it look broken rather than considerate.

   It stays a class rather than a `:hover`-style rule, because pausing has to
   stop the CSS and the spawn clock at the same instant: freezing only the
   animations would leave the clock piling up documents that never move and
   never get cleared, then let go of them all at once. */
.mt-stage.is-idle * { animation-play-state: paused !important; }

/* ===== The axis ===== */
.mt-axis {
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 0;
  z-index: 3;
  pointer-events: none;
}

.mt-axis__beam {
  position: absolute;
  left: 50%;
  top: 8%;
  bottom: 8%;
  width: 2px;
  translate: -50% 0;
  border-radius: 2px;
  background: linear-gradient(180deg, transparent, var(--accent) 18%, #ffd9bd 50%, var(--accent) 82%, transparent);
  opacity: 0.6;
  animation: mt-beam var(--mt-beat) linear infinite;
  animation-delay: var(--mt-phase, 0ms);
}

@keyframes mt-beam {
  0%   { opacity: 1;   box-shadow: 0 0 22px 4px rgba(219, 107, 43, 0.5); }
  22%  { opacity: 0.6; box-shadow: 0 0 10px 1px rgba(219, 107, 43, 0.18); }
  100% { opacity: 0.6; box-shadow: 0 0 10px 1px rgba(219, 107, 43, 0.18); }
}

.mt-core {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  display: grid;
  place-items: center;
  /* A rounded tile on the line, the way the original has it — not a
     medallion. A circle read as a planet the pages flew past; a tile reads as
     the head of the machine they go into. */
  width: clamp(44px, 6.5vw, 60px);
  aspect-ratio: 1;
  border-radius: 26%;
  border: 1px solid rgba(219, 107, 43, 0.5);
  background: linear-gradient(180deg, rgba(30, 18, 12, 0.96), rgba(8, 6, 5, 0.96));
  backdrop-filter: blur(6px);
  animation: mt-core-beat var(--mt-beat) var(--ease) infinite;
  animation-delay: var(--mt-phase, 0ms);
}

.mt-core__glyph {
  width: 44%;
  fill: var(--accent);
}

@keyframes mt-core-beat {
  0%   { scale: 1.14; border-color: rgba(255, 255, 255, 0.8); box-shadow: 0 0 30px 7px rgba(219, 107, 43, 0.38); }
  30%  { scale: 1;    border-color: rgba(219, 107, 43, 0.5);  box-shadow: 0 0 18px 2px rgba(219, 107, 43, 0.16); }
  100% { scale: 1;    border-color: rgba(219, 107, 43, 0.5);  box-shadow: 0 0 18px 2px rgba(219, 107, 43, 0.16); }
}

/* The beam bleeds sideways onto whatever is under it. In the original this is
   what sells the line as something that DOES the disintegrating rather than
   just marking where it happens: the last centimetre of every page is lit by
   it on the way in. Screen blend so it lifts the screenshot rather than
   washing a flat colour over it. */
.mt-axis__bleed {
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  width: calc(var(--mt-band) * 1.5);
  translate: -1px 0;
  /* An ellipse anchored ON the beam, not a strip beside it. As a linear
     gradient across the band this was a hard-edged orange rectangle standing
     in the middle of the panel whether or not anything was passing through
     it; falling off in both directions it reads as light coming off the line,
     which is what it is. Screen blend, so it lifts the surviving pixels of a
     page instead of painting flat colour over them — and lifts nothing at all
     where the stage is empty and black. */
  background: radial-gradient(58% 46% at 100% 50%, rgba(219, 107, 43, 0.34), rgba(219, 107, 43, 0.10) 45%, transparent 72%);
  mix-blend-mode: screen;
  pointer-events: none;
}

/* ===== In: the documents =====

   THE BELT IS ONLY THE LEFT HALF OF THE STAGE, and that is the whole trick.
   Its right edge IS the beam, so `overflow: hidden` alone gives the hard line
   the pages are eaten at — no clip-path to keep in sync, no per-element
   arithmetic. A page does not stop at the axis and break apart: it keeps
   travelling at the speed it arrived with, and simply stops existing past the
   line, leading edge first, until there is none of it left.

   The mask on top is the disintegration. Two layers, unioned (which is what
   mask-composite does by default):
     1. the dither, pinned to the belt's right edge and --mt-band wide, so it
        sits in the strip immediately before the beam;
     2. a gradient that is opaque everywhere BEFORE that strip and then FADES
        OUT across the first third of it. The fade is not decoration: with a
        hard stop there, a page went from whole to dithered in one pixel and
        the step read as a drawn vertical line down the middle of it — which
        is exactly what it was. Fading, the two layers hand over to each other
        and there is no edge to see.
   Left of the band a page is whole (layer 2). Inside it, only the surviving
   squares of layer 1 show. At the beam neither layer has anything left. The
   mask belongs to the STAGE, not to a page, which is why every page comes
   apart at the same place and none of them has to be told when.

   The marks come out under the axis too, so they are born from behind the
   core rather than sliding across the top of it. */
.mt-belt {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 50%;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
  -webkit-mask-image: var(--mt-dither),
    linear-gradient(90deg, #000 calc(100% - var(--mt-band)), transparent calc(100% - var(--mt-band) * 0.62));
          mask-image: var(--mt-dither),
    linear-gradient(90deg, #000 calc(100% - var(--mt-band)), transparent calc(100% - var(--mt-band) * 0.62));
  -webkit-mask-size: var(--mt-band) 100%, 100% 100%;
          mask-size: var(--mt-band) 100%, 100% 100%;
  -webkit-mask-position: right top, left top;
          mask-position: right top, left top;
  -webkit-mask-repeat: no-repeat, no-repeat;
          mask-repeat: no-repeat, no-repeat;
}

.mt-out {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.mt-doc {
  position: absolute;
  /* left: 100% of the BELT, and the belt ends at the beam — so this is the
     axis, written without a single hard-coded number. */
  left: 100%;
  top: 50%;
  width: var(--mt-doc-w);
  /* --mt-run (how far left of the beam a page starts) is written by
     js/magic-transform.js off the stage's measured width: a distance in
     pixels cannot be expressed here without knowing how wide the panel
     turned out to be. Everything else about the trip is static. */
  animation: mt-doc-in var(--mt-travel) linear both;
  animation-delay: calc(-1 * var(--mt-off, 0ms));
}

/* One constant-speed trip, from fully off the left to fully past the beam.
   No rotation, no stop, no separate "and now come apart" step: the page is
   eaten because it keeps going and the belt ends. Ending at translateX(0)
   puts the page's LEFT edge on the axis, which is the moment there is
   nothing left of it. */
@keyframes mt-doc-in {
  from { transform: translate(calc(-100% - var(--mt-run, 520px)), -50%); }
  to   { transform: translate(0, -50%); }
}

/* One element, one screenshot. There is nothing to cut it into any more —
   the mask on the belt does the coming-apart, and it does it to whatever
   happens to be passing, so a page is just a page. */
.mt-doc__paper {
  width: 100%;
  height: var(--mt-doc-h);
  border-radius: 10px;
  border: 1px solid var(--glass-border);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
  background-image: var(--shot);
  background-repeat: no-repeat;
  background-size: 100% auto;
  background-position: center top;
}

.mt-doc__label {
  margin-top: 12px;
  text-align: center;
}

.mt-doc__tag {
  display: block;
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
}

/* Wraps inside the document's own width rather than running past it: at a
   phone's document size "Pellicceria La Rosa Dei Venti" set on one line was
   half again as wide as the page it belonged to, and reached across into the
   caption of the document behind it. */
.mt-doc__name {
  display: block;
  font-family: var(--font-sans);
  font-size: 12px;
  line-height: 1.25;
  color: var(--text-50);
  text-wrap: balance;
}

/* ===== Out: the tools that built it ===== */
/* What leaves the machine is the tool's own mark, at a size where it can be
   read as a logo — not a lozenge with a word in it. The mark carries the
   brand's colour by itself; a tinted pill around it only put a second,
   competing shape between the reader and the thing they are meant to
   recognise. Same conclusion the old logo strip reached: a logo strip reads
   as a logo strip precisely because nothing is competing with the marks.

   The name stays, small and quiet, under the mark. That is what keeps a mark
   somebody does not recognise from being wasted — React's atom needs no
   caption to a developer and gets one anyway, for the reader who isn't one. */
.mt-chip {
  position: absolute;
  left: 50%;
  top: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: max-content;
  /* Each mark takes its own horizontal lane, centred on the axis: with --n
     tools, lanes 0..n-1 spread symmetrically above and below the middle. The
     spacing clears a whole column (mark plus its caption), so a burst of
     five never has one mark sitting on another's name.

     --l is the lane, and it is NOT --i: js/magic-transform.js reshuffles
     which tool gets which lane on every beat, and adds the jitter below.
     Tied to the emission order, the marks left in a tidy descending ladder,
     the same ladder every single time the same project came round — which is
     a diagram of a burst, not a burst. Order of appearance still follows the
     markup (that is --i, on the delay); only where they land is scattered.

     --jy nudges the lane, --jx varies how far the mark gets before it fades,
     --jscale its size and --jd its moment.

     The scatter is deliberately spent on the axis that has room for it. --jy
     stays small and the lanes wide, because two lanes can only close by
     twice the jitter and a column is 57px tall: at +/-14px on a 66px lane
     that left 38px, and marks started landing on each other's captions.
     --jx is where the randomness actually lives — sideways there is half a
     stage of room, and two marks on neighbouring lanes that stop at
     different distances never meet at all. */
  --lane: calc((var(--l, 0) - (var(--n) - 1) / 2) * 72px + var(--jy, 0px));
  animation: mt-chip-out var(--mt-chip) var(--mt-ease-fly) both;
  animation-delay: calc(var(--mt-hit) + var(--i) * var(--mt-stagger) + var(--jd, 0ms) - var(--mt-off, 0ms));
}

/* A soft aura in the tool's own colour, behind the mark and shaped like
   nothing in particular: enough to lift a dark logo (CapCut's black tile,
   Claude's warm mark) off a near-black stage without drawing an edge of its
   own. A gradient rather than a filter — it composites, and there are up to
   five of these moving at once. */
.mt-chip__mark {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  background: radial-gradient(circle at 50% 46%,
    color-mix(in srgb, var(--tool) 42%, transparent),
    color-mix(in srgb, var(--tool) 10%, transparent) 52%,
    transparent 72%);
}

.mt-chip__mark svg { width: 34px; height: 34px; }

/* Deliberately small and dim. In flight nobody reads it — the mark is what
   is being read — but a caption that is wider than its own mark is what
   makes a fanned-out burst look tangled, so it is sized down until a column
   clears the next lane. The full names are in the list behind the stage,
   which is where they are actually legible. */
.mt-chip__name {
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-50);
  white-space: nowrap;
}

/* The fan opens almost at once and the flight is what takes the time. Held
   closed for the first third (the lane only reached 35% by 16%), four marks
   spent that third stacked on each other a few pixels apart, and what should
   read as a burst read as a clump drifting right. */
@keyframes mt-chip-out {
  0%   { transform: translate(-50%, -50%) translate(0, calc(var(--lane) * 0.15)) scale(0.25); opacity: 0; }
  15%  { transform: translate(-50%, -50%)
                    translate(calc(var(--mt-fly, 320px) * 0.17 * var(--jx, 1)), calc(var(--lane) * 0.8))
                    scale(calc(1.06 * var(--jscale, 1))); opacity: 1;
         /* Eased out of the core, then LINEAR the rest of the way. The pop is
            what makes it read as ejected; the drift after it is just travel,
            and on the shared ease-out a longer flight only bought more time
            parked at the far edge instead of more time crossing the stage.
            Per-keyframe timing, so one animation can be both. */
         animation-timing-function: linear; }
  /* Gone before the stage's edge mask reaches it: a mark that is still fully
     opaque when it hits the fade reads as clipped rather than as something
     that flew off. */
  50%  { opacity: 1; animation-timing-function: linear; }
  100% { transform: translate(-50%, -50%)
                    translate(calc(var(--mt-fly, 320px) * var(--jx, 1)), var(--lane))
                    scale(calc(0.96 * var(--jscale, 1))); opacity: 0; }
}

/* Squares, not dots, and tumbling: the original throws little coloured chips
   of the document it just ate, and a chip has corners. */
.mt-particle {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 7px;
  height: 7px;
  border-radius: 1.5px;
  background: var(--tool);
  box-shadow: 0 0 9px color-mix(in srgb, var(--tool) 60%, transparent);
  animation: mt-particle-out var(--mt-particle) var(--mt-ease-fly) both;
  animation-delay: calc(var(--mt-hit) + var(--d) - var(--mt-off, 0ms));
}

@keyframes mt-particle-out {
  0%   { transform: translate(-50%, -50%) scale(0.2) rotate(0deg); opacity: 1; }
  100% { transform: translate(-50%, -50%) translate(var(--px), var(--py)) scale(var(--ps, 1)) rotate(var(--pr, 90deg));
         opacity: 0; }
}

/* A no-op animation used purely as a clock. js/magic-transform.js removes a
   beat's elements when this ends rather than on a setTimeout, because a
   timeout keeps running while the stage is paused and would sweep away a
   document frozen mid-shred under the reader's cursor. An animation pauses
   with everything else, so the group lives exactly as long as it is on
   screen. */
@keyframes mt-life { from, to { opacity: 1; } }
.mt-beat {
  position: absolute;
  inset: 0;
  animation: mt-life var(--mt-life) linear both;
  animation-delay: calc(-1 * var(--mt-off, 0ms));
}

@media (max-width: 720px) {
  /* Two documents at 27% of their own width, on a belt 170px across, left
     28px of air between them and read as one clump rather than as a queue.
     At 0.58 the spacing is about a belt-width, so a page has the machine to
     itself while it is being eaten and the next is only just entering — the
     same rhythm the wide stage has, at the size a phone actually shows. */
  .magic-transform { --mt-gap-ratio: 0.58; }

  .mt-lede { font-size: 14px; }
  .mt-chip { gap: 5px; --lane: calc((var(--l, 0) - (var(--n) - 1) / 2) * 60px + var(--jy, 0px)); }
  .mt-chip__mark { width: 34px; height: 34px; }
  .mt-chip__mark svg { width: 28px; height: 28px; }
  .mt-chip__name { font-size: 8px; letter-spacing: 0.06em; }
}

/* ===== A phone on its side =====
   The one shape where the stage cannot simply be given more room: 390px of
   screen has to hold the bar, the heading, the lede AND the machine. The
   document itself already gives way (--mt-doc-w is capped against the height
   as well as the width), and this is the other half — the caption under it.

   It matters because the caption is the part that GROWS when the document
   shrinks: "Pellicceria La Rosa Dei Venti" fits on one line under a 135px
   page and wraps onto two under a 104px one, and two lines at the full size
   put the document 5px past a stage that had nothing left to give. Smaller
   type and a tighter gap buy back more than the second line costs.

   Same 980x500 bound the rest of the page uses for landscape, so a section
   changes shape at one threshold and not at several of its own. */
@media (max-width: 980px) and (max-height: 500px) {
  .mt-doc__label { margin-top: 8px; }
  .mt-doc__tag { font-size: 8px; letter-spacing: 0.16em; }
  .mt-doc__name { font-size: 10.5px; line-height: 1.2; }
}

@media (prefers-reduced-motion: reduce) {
  .mt-stage * { animation: none !important; }
}

/* ===================================================================
   IL BERSAGLIO SULLA STELLINA, E LA FINESTRA CHE APRE
   ===================================================================
   La stellina al centro della linea si preme e mostra tutti gli strumenti
   insieme, invece che uno alla volta mentre passano.

   Il bersaglio e' un <button> separato steso sopra di lei, non la stellina
   stessa, e non e' un vezzo: .mt-axis ha pointer-events:none, e tutto il
   palco e' aria-hidden per far sentire a chi naviga a voce l'elenco dei
   progetti invece della coreografia. Un comando li' dentro sarebbe un
   comando che esiste per chi vede e non per chi ascolta. Vedi la nota
   estesa in js/magic-transform.js.
   =================================================================== */

.mt-core-hit {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  /* Un filo piu' grande del disegno: la stellina e' 44-60px, e qualche
     pixel di margine e' la differenza fra un tocco preso e uno mancato. */
  width: clamp(56px, 8vw, 72px);
  aspect-ratio: 1;
  z-index: 4;
  background: none;
  border: 0;
  padding: 0;
  border-radius: 26%;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Nessun disegno suo: quello che si vede e' la stellina sotto. Il bordo
   compare solo col fuoco da tastiera, dove serve davvero — chi naviga col
   tab deve sapere dove si trova, e li' non c'e' un cursore a dirglielo. */
.mt-core-hit:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

/* Al passaggio del mouse la stellina reagisce: e' l'unico modo che ha di
   dire "sono premibile" senza aggiungere un disegno in piu'. Solo dove un
   puntatore esiste davvero, come per tutto il resto del sito. */
@media (hover: hover) {
  .mt-core-hit:hover ~ .mt-stage .mt-core {
    border-color: rgba(255, 255, 255, 0.85);
    box-shadow: 0 0 34px 8px rgba(219, 107, 43, 0.45);
  }
}

/* ===== La finestra ===== */
.mt-tools {
  position: fixed;
  inset: 0;
  /* Sopra la pagina (scroll-expand e text-peek arrivano a 200) e sotto il
     menu di navigazione (210), che deve poter coprire tutto. */
  z-index: 205;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  /* Pieno, non velato: su WebKit backdrop-filter puo' ridisegnare il fondo
     SOPRA il colore invece che sotto, e un pannello resta trasparente per
     quanto opaco lo si faccia. Vedi la nota in css/nav-sheet.css. */
  background: rgba(6, 6, 6, 0.94);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.32s var(--ease), visibility 0.32s var(--ease);
}

.mt-tools.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* ===== Il pannello: lo stesso vetro delle card =====
   Aveva un fondo e un bordo suoi, dipinti a mano. Ora porta la classe
   liquid-glass (js/magic-transform.js) e prende il materiale della pagina:
   il bordo conico che si accende in alto, il riflesso, l'ombra a strati, la
   grana. Fondo e bordo di prima sono spariti da qui, e non e' una pulizia
   facoltativa: css/magic-transform.css e' caricato DOPO css/liquid-glass.css,
   quindi finche' restavano scritti qui vincevano loro e del materiale non si
   sarebbe visto niente.

   Il velo dietro resta pieno, per scelta: su WebKit backdrop-filter puo'
   ridisegnare il fondo SOPRA il colore, e un pannello semitrasparente su un
   velo semitrasparente e' proprio la combinazione che su questo sito ha gia'
   dato problemi (vedi la nota qui sopra e in css/nav-sheet.css). Del
   materiale si vede tutto tranne la sfocatura di quel che sta dietro — che
   dietro a un velo pieno non avrebbe comunque niente da sfocare. */
.mt-tools__panel {
  width: min(560px, 100%);
  max-height: 100%;
  /* Lo scorrimento se ne va alla griglia qui sotto. Le rifiniture del
     materiale — il bordo che si accende, il riflesso — sono figli in
     posizione assoluta ancorati al pannello, e dentro a un contenitore che
     scorre scorrerebbero via insieme al contenuto, lasciando la cornice a
     meta' strada. Col pannello fermo e la sola lista che scorre, la cornice
     resta dov'e'. */
  display: flex;
  flex-direction: column;
  padding: 20px;
  translate: 0 10px;
  transition: translate 0.32s var(--ease);
  /* ===== Il fondo sotto al materiale =====
     liquid-glass tiene il proprio fondo quasi trasparente — 4.5% di bianco —
     perche' sulla pagina ha sotto di se' il campo colorato che deve filtrare.
     Qui sotto non c'e' niente da filtrare, e il risultato era che il testo
     della sezione si leggeva ATTRAVERSO il pannello, sotto le icone.

     Questo colore va sotto: css/magic-transform.css e' caricato dopo, quindi
     sostituisce il background-color del materiale ma non la sua
     background-image — la grana e il gradiente restano, appoggiati su un
     fondo pieno invece che sul vuoto. Del materiale si perde solo quello che
     qui non aveva niente da mostrare.

     Il tono e' quello caldo del pannello di prima: la macchina degli
     strumenti e' ambra, e un vetro grigio-blu in mezzo a lei stonava. */
  background-color: rgb(16 12 10 / 0.985);
}
.mt-tools.is-open .mt-tools__panel { translate: 0 0; }

.mt-tools__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 20px;
}

.mt-tools__eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
}

.mt-tools__close {
  width: 44px;
  height: 44px;
  margin: -8px -10px -8px 0;
  position: relative;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--text);
  flex-shrink: 0;
}
.mt-tools__close::before,
.mt-tools__close::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 18px;
  height: 1px;
  margin: 0 0 0 -9px;
  background: currentColor;
}
.mt-tools__close::before { transform: rotate(45deg); }
.mt-tools__close::after  { transform: rotate(-45deg); }

.mt-tools__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* Si dispone da sola: quattro colonne dove c'e' spazio, due su un
     telefono. Nessun numero di colonne scritto per larghezza. */
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 12px;
  /* Scorre lei, non il pannello: vedi la nota li' sopra. min-height serve
     perche' un figlio flex non scende sotto il proprio contenuto senza. */
  overflow-y: auto;
  min-height: 0;
}

.mt-tools__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 16px 10px;
  border-radius: 14px;
  border: 1px solid var(--hairline);
  background: rgb(var(--lg-tint, 255 255 255) / 0.03);
}

.mt-tools__icon {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
}
.mt-tools__icon svg { width: 100%; height: 100%; }

.mt-tools__name {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-align: center;
  /* Il colore del marchio, lo stesso che la pastiglia usa quando vola fuori
     dalla macchina: il nome e il segno si riconoscono come la stessa cosa. */
  color: var(--tool, var(--text));
}

@media (prefers-reduced-motion: reduce) {
  .mt-tools,
  .mt-tools__panel { transition: none; }
}
