/* =============================================================================
   Page 1 — the fader game.  Silent-first, phone-first, no instructions.
   The page is his own collage: a turquoise wall with the fader panel on it,
   the strips typed on paper, and the notes. Everything that moves was cut out
   of that one picture by tools/game-art-2.py, so the page and the picture are
   the same thing. The old beige-paper design is parked in "vecchio design".
   ============================================================================= */

/* His own typewriter, the one on the paper strips. Special Elite, by
   Astigmatic, under the Apache 2.0 licence; the file lives in the site so the
   page owes nothing to a font service and works offline. */
@font-face {
  font-family: "Special Elite";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/special-elite-latin.woff2") format("woff2");
}

:root {
  --font-ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, Monaco, "Cascadia Code", "Courier New", monospace;

  --ink: #2b2823;
  --ink-soft: #6d675d;
  --accent: #cf5340;                    /* the coral from his CV, muted for paper */

  color-scheme: light;
}

* { box-sizing: border-box; }
a:visited { color: inherit; }     /* a link already opened must look the same */
html, body { height: 100%; }
html { -webkit-text-size-adjust: 100%; -moz-text-size-adjust: none; text-size-adjust: 100%; }   /* no text enlarged by a phone's browser */

body.game {
  margin: 0;
  font-family: var(--font-ui);
  color: var(--ink);
  /* The wall is drawn at the height of the window, never cropped top or
     bottom, so the panel and the notes always stand where he glued them. On a
     window wider than the picture the plain turquoise of its own edges carries
     on to the sides, which is why the colour below is that very turquoise. */
  background: #15b2c4 url("../assets/game-2/wall.jpg?v=2") center center / auto 100% no-repeat fixed;
  overflow: hidden;
  overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

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

/* the abstract layers are painted here, over the paper */
.stage {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

/* ---------------------------------------------------------------- skip --- */
/* A torn strip of paper with the words drawn on it (tools/game-art.py), so the
   link carries them again in text, for screen readers and for search. */
.skip {
  position: fixed;
  top: max(10px, env(safe-area-inset-top));
  right: max(8px, env(safe-area-inset-right));
  z-index: 4;
  display: block;
  width: clamp(178px, 27vw, 430px);
  line-height: 0;
  transition: transform .16s cubic-bezier(.2, .8, .3, 1.2), filter .18s ease;
  /* the last thing to come, the first time: from twenty seconds in, once
     the notes have had the stage, it surfaces over four, steadily; until it
     begins it cannot be clicked either */
  animation: skip-in 4s linear 20s both;
}
@keyframes skip-in {
  from { opacity: 0; visibility: hidden; }
  to   { opacity: 1; visibility: visible; }
}
/* the pen ring round it, drawn in one stroke just after it has come */
.skip__ring {
  position: absolute;
  left: -5%;
  top: 4%;
  width: 110%;
  height: 92%;
  overflow: visible;
  pointer-events: none;
}
.skip__ring path {
  fill: none;
  stroke: #13282e;
  stroke-width: 1.05;              /* in the ring's own units: about 2px on a computer */
  stroke-linecap: round;
  stroke-opacity: .85;
  stroke-dasharray: 1 2;          /* one stroke, and a gap longer than the ring */
  animation: ring-draw 1s ease-in-out 24s both;    /* once it is all there */
}
/* not a dot of it shows before the pen starts: the round end of a line
   would otherwise sit there as a fleck while the strip comes */
@keyframes ring-draw {
  from { stroke-dashoffset: 1; stroke-opacity: 0; }
  1%   { stroke-opacity: .85; }
  to   { stroke-dashoffset: 0; stroke-opacity: .85; }
}
.skip img { display: block; width: 100%; height: auto; }
.skip:hover { transform: translateY(-2px) rotate(-.4deg) scale(1.02); }
.skip:active { transform: scale(.99); }

/* words that are already drawn into a picture, kept for screen readers */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ------------------------------------------------------------- console --- */
/* The panel is the one he made, with its drawn knobs taken off. The rack is
   its fixed holder: on a wide screen JS puts it exactly where it stands in the
   picture, and on a phone low on the page, leaving the wall above it, where
   the notes are, in view. */
/* On a first visit the panel rises out of the blue of the wall over three
   seconds — the time the first of the sound takes to arrive — and the notes
   surface after it at their own pace. Whoever comes back finds it all there
   at once (.is-back, set in the page's head from what the browser remembers). */
.rack {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  animation: rack-in 3s ease-in-out both;
}
@keyframes rack-in { from { opacity: 0; } to { opacity: 1; } }
/* someone coming back: everything at once, in half a second */
.is-back .rack, .is-back .skip, .is-back .notes, .is-back .cue, .is-back .staff, .is-back .song {
  animation: rack-in .5s ease-out both;
}
.is-back .skip__ring path { animation: none; }

/* Until the first touch only the wall and the way in are there; nothing else
   has started its entrance yet, so it all begins from that touch. */
.is-waiting .rack, .is-waiting .skip, .is-waiting .skip__ring path, .is-waiting .notes,
.is-waiting .cue, .is-waiting .staff, .is-waiting .song, .is-waiting .loading {
  animation: none;
  opacity: 0;
  visibility: hidden;
}

/* ------------------------------------------------------------ the way in -- */
/* One line typed on a strip of torn paper, like his own strips, alone on the
   wall. The whole window is the button. */
.start {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* after the tap it all goes in a second, while the notes begin to surface
     where the sign stood */
  transition: opacity 1s ease, visibility 0s linear 1s;
}
/* His "volume up" sign, in the place the notes will take (JS sets where,
   from the same measure as the notes); the line goes under it. */
.start__volume {
  position: absolute;
  left: var(--vol-x, 50%);
  top: var(--vol-y, 38%);
  width: var(--vol-size, min(44vw, 30vh));
  height: auto;
  transform: translate(-50%, -44%);       /* the drawing sits a little high in its square */
  user-select: none;
  -webkit-user-drag: none;
  animation: start-in .9s ease-out both;
}
.start__place {
  position: absolute;
  left: 50%;
  top: var(--strip-y, 58%);
  transform: translateX(-50%);
}
.start[hidden] { display: none; }
.start.is-gone { opacity: 0; visibility: hidden; pointer-events: none; }
.start:focus-visible { outline: none; }
.start:focus-visible .start__shade { outline: 2px solid #13282e; outline-offset: 6px; }
/* the shadow and the tilt are the strip's holder's, so the torn edge cuts the
   paper and not its shadow */
.start__shade {
  display: block;
  filter: drop-shadow(2px 4px 0 rgba(8, 52, 58, .35));
  transform: rotate(-1.6deg);
  animation: start-in .9s ease-out both, call 3.2s ease-in-out 1.5s infinite;
}
.start__strip {
  display: block;
  padding: .55em 1.1em .5em;
  font-family: "Special Elite", var(--font-mono);
  font-size: clamp(22px, 6.2vw, 46px);
  line-height: 1.1;
  white-space: nowrap;
  color: #13282e;
  /* paper: its colour, a grain, and a little light from the top */
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' seed='3'/><feColorMatrix values='0 0 0 0 .42  0 0 0 0 .33  0 0 0 0 .2  0 0 0 .55 0'/></filter><rect width='160' height='160' filter='url(%23n)'/></svg>"),
    linear-gradient(#efdcb6, #e3c79a);
  /* torn along the top and bottom, cut straight-ish at the ends */
  clip-path: polygon(0% 9%, 4% 3%, 9% 8%, 15% 2%, 21% 7%, 27% 1%, 34% 6%, 40% 2%, 47% 8%, 53% 3%, 60% 7%,
                     66% 1%, 72% 6%, 78% 2%, 85% 8%, 91% 3%, 96% 7%, 100% 2%, 99.4% 50%, 100% 97%,
                     95% 92%, 89% 98%, 83% 93%, 76% 99%, 70% 94%, 63% 98%, 57% 92%, 50% 97%, 44% 93%,
                     37% 99%, 31% 94%, 24% 98%, 18% 92%, 11% 97%, 5% 93%, 0% 98%, .6% 50%);
}
.start__click { display: none; }
@media (hover: hover) and (pointer: fine) {
  .start__touch { display: none; }
  .start__click { display: inline; }
}
@keyframes start-in { from { opacity: 0; } to { opacity: 1; } }
/* it waits, but not dead still: a slow breath */
@keyframes call { 0%, 100% { transform: rotate(-1.6deg) scale(1); } 50% { transform: rotate(-1.6deg) scale(1.025); } }
@media (prefers-reduced-motion: reduce) { .start__shade { animation: start-in .9s ease-out both; } }

.console {
  --w: min(46vw, 780px);                /* how wide the panel is drawn; JS sets it */
  --knob-w: calc(var(--w) * .0509);     /* the knob, as a part of the panel */
  --knob-h: calc(var(--knob-w) * 55 / 43);
  position: relative;
  width: var(--w);
  min-width: 280px;
}
.console__paper {                       /* its shadow on the wall is in the picture */
  display: block;
  width: 100%;
  height: auto;
  user-select: none;
  -webkit-user-drag: none;
}

/* A fader is the stretch of its printed slot that the knob's middle can
   travel — panel-sized, from tools/game-art-2.py — and the knob rides it.
   The box to take hold of reaches half a knob past each end of that stretch,
   so a knob sitting at the bottom or the top is grabbable all over. */
.fader {
  position: absolute;
  left: var(--slot-x);
  top: calc(var(--track-top) - var(--knob-h) / 2);
  height: calc(var(--track-h) + var(--knob-h));
  width: max(34px, calc(var(--w) * .095));
  transform: translateX(-50%);
  padding: 0;
  border: 0;
  background: none;
  cursor: grab;
  touch-action: none;               /* we handle the drag ourselves */
  user-select: none;
}
.fader:active { cursor: grabbing; }

.fader__slot {                      /* the travel itself, inside that box */
  position: absolute;
  left: 0;
  right: 0;
  top: calc(var(--knob-h) / 2);
  bottom: calc(var(--knob-h) / 2);
  display: block;
}
/* The slot is printed on the panel, so nothing is drawn over it. */

/* the knob, cut off its slot in the picture, with the shadow it used to cast */
.fader__cap {
  position: absolute;
  left: 50%;
  bottom: 0;
  width: var(--knob-w);
  aspect-ratio: 43 / 55;
  transform: translate(-50%, 50%);
  background: url("../assets/game-2/knob.png") center / 100% 100% no-repeat;
  filter: drop-shadow(1px 4px 4px rgba(38, 28, 16, .5));
}
.fader.is-open .fader__cap {
  filter: drop-shadow(1px 4px 4px rgba(38, 28, 16, .5))
          drop-shadow(0 0 2px rgba(207, 83, 64, .9));
}

/* ---------------------------------------------------------------- song --- */
/* What is playing, typed under the panel like the caption under a picture
   (his pick, "D"). js/game.js sets its place and size with the panel's, so it
   keeps the same proportions on any screen, and it surfaces with the notes —
   the panel alone is there from the first frame. */
/* While the stems and the notes are still coming down, a note in the corner
   breathes, in the ink of the credit; once enough is in to play, it goes. */
.loading {
  position: fixed;
  left: max(18px, env(safe-area-inset-left));
  bottom: max(14px, env(safe-area-inset-bottom));
  z-index: 4;
  margin: 0;
  font-family: "Special Elite", var(--font-mono);
  font-size: 26px;
  line-height: 1;
  color: #13282e;
  pointer-events: none;
  animation: breathe 1.6s ease-in-out infinite;
  transition: opacity 1.2s ease;
}
.loading.is-done { opacity: 0; animation: none; }
@keyframes breathe { 0%, 100% { opacity: .25; } 50% { opacity: .85; } }
@media (prefers-reduced-motion: reduce) { .loading { animation: none; opacity: .6; } }

.song {
  animation: notes-in 20s cubic-bezier(.11, 0, .5, 0) both;
  position: fixed;
  left: 50%;
  top: var(--song-y, 50%);
  z-index: 4;
  margin: 0;
  transform: translateX(-50%);
  font-family: "Special Elite", var(--font-mono);
  font-size: var(--song-size, 19px);
  line-height: 1.2;
  white-space: nowrap;
  color: #13282e;
  pointer-events: none;
  user-select: none;
}

/* --------------------------------------------------------------- notes --- */
/* Nine whole notes on the paper. JS places them and sets --note-w (the width
   of a note's picture) and --tilt.
   They surface by themselves once the page opens: slowly at first, fully there
   after 20 s, and then they stay. The curve is an ease-in, since ink on paper
   already reads as nearly there long before full opacity. Nothing drives it
   from JS, so no stalled frame can hold it back.
   They stay rendered throughout and only the opacity moves, from a true zero:
   switching them from hidden to shown, even at a faint 3%, made a visible
   step. Until they can be seen they just don't take touches (and JS keeps
   them inert, out of the tab order). */
.notes {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  animation: notes-in 20s cubic-bezier(.11, 0, .5, 0) both;
}
.notes:not(.is-live) .note { pointer-events: none; }
@keyframes notes-in { from { opacity: 0; } to { opacity: 1; } }

/* The staff is the ground the notes stand on, so it surfaces with them, on
   the same 20 s curve, and lies under everything else on the wall. */
.staff {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  pointer-events: none;
  animation: notes-in 20s cubic-bezier(.11, 0, .5, 0) both;
}

/* "(hold the circles)" and its arrow: they belong to the notes, so they
   surface with them. JS puts them beside the notes it has just dealt. */
.cue {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  animation: notes-in 20s cubic-bezier(.11, 0, .5, 0) both;
}
.cue__strip {                       /* its shadow on the wall is in the picture */
  position: absolute;
  height: auto;
}

/* The button is the design itself, not its whole picture: the picture has
   transparent margins, and JS keeps the designs 8px apart, so each hit area
   reaches 4px beyond its ink and no two of them overlap. */
.note {
  --ring: rgba(207, 83, 64, .55);  /* the console's coral: this can be played */
  position: absolute;
  width: calc(var(--note-w, 90px) * .84);
  height: calc(var(--note-w, 90px) * .84);
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  transform: translate(-50%, -50%) rotate(var(--tilt, 0deg));
  cursor: pointer;
  pointer-events: auto;
  -webkit-tap-highlight-color: transparent;
  /* a note is held down to play: no scrolling, zooming, selecting or the
     phone's press-and-hold menu may take the touch over */
  touch-action: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
.note img {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--note-w, 90px);
  height: auto;
  max-width: none;
  transform: translate(-50%, -50%);
  pointer-events: none;
  -webkit-user-drag: none;
  user-select: none;
  filter: drop-shadow(0 3px 5px rgba(46, 38, 28, .28));
  transition: transform .18s cubic-bezier(.2, .8, .3, 1.3);
}
@media (hover: hover) {           /* on touch a hover state would stick after the tap */
  .note:hover img { transform: translate(-50%, -50%) scale(1.12); }
  .note:hover::before { animation-duration: 1.6s; }
}
/* held down: it stays up and keeps calling faster, for as long as it sounds */
.note.is-held img { transform: translate(-50%, -50%) scale(1.12); }
.note.is-held::before { animation-duration: .9s; }
.note:focus-visible { outline-offset: 2px; }

/* A slow ring keeps rising out of every note, each on its own beat, so they
   read as things to touch without a word of instruction. It hugs the design,
   not the transparent margins of its picture. */
.note::before,
.note::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--note-w, 90px) * .86);
  height: calc(var(--note-w, 90px) * .86);
  border: 1.5px solid var(--ring);
  border-radius: 50%;
  transform: translate(-50%, -50%) scale(.8);
  opacity: 0;
  pointer-events: none;
}
.note::before { animation: note-call 3.2s ease-out infinite var(--call-delay, 0s); }
@keyframes note-call {
  0%   { opacity: 0;   transform: translate(-50%, -50%) scale(.8); }
  15%  { opacity: .9; }
  60%  { opacity: 0;   transform: translate(-50%, -50%) scale(1.45); }
  100% { opacity: 0;   transform: translate(-50%, -50%) scale(1.45); }
}

/* hit: a wider ring and a small jump of the note */
.note.is-hit::after { border-width: 2px; animation: note-hit .6s cubic-bezier(.15, .7, .3, 1) forwards; }
.note.is-hit img { animation: note-pop .3s ease-out; }
@keyframes note-hit {
  from { opacity: 1; transform: translate(-50%, -50%) scale(.9); }
  to   { opacity: 0; transform: translate(-50%, -50%) scale(2.1); }
}
@keyframes note-pop {
  35% { transform: translate(-50%, -50%) scale(.86); }
  70% { transform: translate(-50%, -50%) scale(1.1); }
}

@media (prefers-reduced-motion: reduce) {
  /* the notes' fade stays: it changes nothing's position */
  .note img { transition: none; }
  .note::before { animation: none; opacity: .5; transform: translate(-50%, -50%) scale(1.1); }
  .note.is-hit::after, .note.is-hit img { animation: none; }
  .note.is-hit::before { opacity: 1; }
}

/* ---------------------------------------------------------- responsive --- */
@media (max-width: 720px) {
  .skip { width: clamp(150px, 44vw, 240px); }
  .skip__ring path { stroke-width: 1.7; }
}
/* on a wide screen the corner is far off: half as far from the middle */
@media (min-width: 1280px) {
  .skip { right: calc(25vw + 4px - clamp(178px, 27vw, 430px) / 4); }
}

@media (prefers-reduced-motion: reduce) {
  .skip { transition: none; }
}
