/* ── Metty interface prototype ──────────────────────────────
   Braun-style: black ground, white geometry, no gradients and
   no shadows. Depth is carried by line weight and spacing only.
   Desktop (>768px): pinned to a centred square deck.
   Mobile: bowl above; display and control band at the bottom.
   ───────────────────────────────────────────────────────── */

:root{
  --pad: 3.5vh;
  --bowl-size: min(58vw, 58vh);
  --dial: min(24.7vh, 19.5vw);           /* program dial == screen height */

  /* Clear air between the bowl and the top of the control panel. */
  --clearance: calc(var(--dial) * 0.40);

  /* The two secondary dials. Their stack is the height of the display
     beside them, and the first label hangs above it, so what the pair
     has to share is one --dial less the label that sits between them
     and its two gaps (1.8em above + 1em tall + 1.2em below). Splitting
     that budget rather than sizing each dial on its own is what keeps
     stages on the display's top edge and time on its bottom one. */
  --dial-pair:   calc(var(--dial) - var(--label) * 4);
  --dial-stages:   calc(var(--dial-pair) * 0.35);
  --dial-time:   calc(var(--dial-pair) * 0.65);

  --page:  #171716;
  --deck:  #000;
  --white: #f2f2ef;
  --grey:  #8c8c8c;
  --dim:   #3a3a3a;
  --hair:  #5a5a5a;
  --lcd:     #d7d8d0;
  --lcd-ink: #0d0f0c;

  /* Label type. Held as variables so the gaps around a label can be
     stated as multiples of its own size. */
  --label:  clamp(.5rem,.72vw,.62rem);
  --plabel: clamp(.5rem,.75vw,.64rem);
}

*{ box-sizing: border-box; margin:0; padding:0; }
[hidden]{ display: none !important; }

html,body{
  height:100%;
  overflow:hidden;
  background: var(--page);
  color: var(--white);
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

/* dvh, not vh: on phones 100vh is the *large* viewport — it excludes the
   URL bar and bottom toolbar. The mobile layout is anchored to the deck's
   bottom edge, so a vh-tall deck puts the control band underneath the
   browser chrome, where it is unreadable and untappable. vh first as the
   fallback for browsers without dvh. */
#deck{
  position: relative;
  width:100vw;
  height:100vh;
  height:100dvh;
  background: var(--deck);
}

/* ── Bowl ────────────────────────────────────────────────── */
#bowl-zone{
  position:absolute;
  top: var(--pad);
  left: var(--pad);
  width: var(--bowl-size);
  height: var(--bowl-size);
}
#bowl{
  position:absolute; inset:0;
  border-radius:50%;
  border:1px solid var(--grey);
  transition: border-color .1s linear;
}
#bowl.hit{ border-color: var(--white); }
.ring{
  position:absolute; border-radius:50%;
  border:1px solid var(--dim);
  transition: border-color .1s linear;
}
.r1{ inset:7%; border-color: var(--hair); }
.r2{ inset:38%; }
.r3{ display:none; }
#bowl.hit .ring{ border-color: var(--grey); }
#bowl-centre{
  position:absolute; inset:47.5%;
  border-radius:50%;
  background: var(--white);
}

/* ── Striker (tonearm) ───────────────────────────────────── */
#striker{
  position:absolute;
  top: var(--pad);
  right: var(--pad);
  height: var(--bowl-size);
  width: 1.1rem;
  /* column-reverse anchors the pivot at the bottom of the column, so
     the arm rises from the bottom-right corner of the bowl area. */
  display:flex; flex-direction:column-reverse; align-items:center;
}
#striker-pivot{
  width:1.1rem; height:1.1rem; border-radius:50%;
  border:1px solid var(--hair);
  flex:none; z-index:2;
}
#striker-arm{
  width:1px; flex:1;
  background: var(--hair);
  transform-origin: bottom center;
  transform: rotate(0deg);
  transition: transform .5s cubic-bezier(.22,.9,.3,1);
  position:relative;
}
/* Negative angle swings the free (top) end left, towards the bowl. */
#striker.strike #striker-arm{
  transform: rotate(-11deg);
  transition: transform .09s cubic-bezier(.3,0,.7,1);
}
#striker-head{
  position:absolute; top:0; left:50%;
  transform: translateX(-50%);
  width:0.55rem; height:0.55rem;
  border-radius:50%;
  background: var(--white);
}

/* ── Controls band ───────────────────────────────────────── */
#controls{
  position:absolute;
  left: var(--pad);
  right: var(--pad);
  top: calc(var(--pad) + var(--bowl-size) + 2.5vh);
  bottom: var(--pad);
  display:flex;
  align-items:center;
  gap: clamp(1rem, 2.2vw, 2.4rem);
}
.group{ display:flex; flex-direction:column; align-items:center; flex:none; }
.mini-name{
  font-size: var(--label);
  letter-spacing:.2em;
  font-weight:500;
  color: var(--grey);
  line-height:1;
}

/* ── Dials ───────────────────────────────────────────────── */
.dial{
  position:relative;
  border-radius:50%;
  cursor: grab;
  touch-action:none;
  outline:none;
  flex:none;
}
.dial:active{ cursor: grabbing; }
.dial-face{
  position:absolute; inset:0;
  border-radius:50%;
  border:1px solid var(--hair);
  transition: border-color .15s ease;
}
.dial:hover .dial-face,
.dial:focus-visible .dial-face{ border-color: var(--white); }

/* Pointer: a radial line from the rim inward, not a floating mark */
.dial-pointer{
  position:absolute; inset:0;
  transform: rotate(0deg);
  pointer-events:none;
}
.dial-pointer::before{
  content:"";
  position:absolute;
  left:50%; top:0;
  transform: translateX(-50%);
  width:1px; height:34%;
  background: var(--white);
}

/* Tick rings. Fine and many == continuous; few and heavy == counted. */
.ticks{ position:absolute; inset:0; pointer-events:none; }
.tick{ position:absolute; inset:0; }
.tick::before{
  content:"";
  position:absolute;
  left:50%; top:0;
  transform: translateX(-50%);
  width:1px; height:9%;
  background: var(--dim);
}
.prog-dial .tick::before{ height:14%; background: var(--hair); }

.prog-dial{ width: var(--dial); height: var(--dial); }
.prog-dial .dial-pointer{ transition: transform .22s cubic-bezier(.3,1.4,.5,1); }
.time-dial{ width: var(--dial-time); height: var(--dial-time); }
.stages-dial{ width: var(--dial-stages); height: var(--dial-stages); }

/* Program wrapper + labels around the outside */
.prog-wrap{
  position:relative;
  width: calc(var(--dial) * 1.5);
  height: calc(var(--dial) * 1.5);
  display:grid; place-items:center;
}
.prog-wrap .prog-dial{ position:absolute; }
.prog-label{
  position:absolute;
  transform: translate(-50%,-50%);
  font-size: var(--plabel);
  letter-spacing:.16em;
  font-weight:500;
  color: var(--dim);
  cursor:pointer;
  white-space:nowrap;
  transition: color .15s ease;
  user-select:none;
}
.prog-label:hover{ color: var(--grey); }
.prog-label.active{ color: var(--white); }
.pos-nw{ left:20%;  top:20%; }
.pos-ne{ left:80%;  top:20%; }
.pos-se{ left:80%;  top:80%; }
.pos-sw{ left:20%;  top:80%; }

/* Phosphor-geometry icons: 256 grid, 16 stroke, round ends. They take
   the label's colour, so they light up with selection like the words. */
.prog-label .ico{
  display:block;
  width:1.9em; height:1.9em;
  fill:none;
  stroke: currentColor;
  stroke-width:16;
  stroke-linecap:round;
  stroke-linejoin:round;
}

/* The dial's own name, centred above it like every other label.
   83.33% is the dial's top edge inside the 1.5x wrapper. */
.prog-name{
  position:absolute;
  left:50%; bottom: calc(83.333% + var(--label) * 1.2);
  transform: translateX(-50%);
  font-size: var(--label);
  letter-spacing:.2em;
  font-weight:500;
  color: var(--grey);
  line-height:1;
  white-space:nowrap;
}

/* Stack: TIME / dial / STAGES / dial. Every label is centred
   above the dial it names — 1.2 label-heights above its own dial, and
   1.8 below the unrelated dial before it, so each pair reads as one. */
.stack{
  display:flex; flex-direction:column;
  align-items:center;
  height: var(--dial);
  justify-content: flex-start;
}
.pair{ display:flex; flex-direction:column; align-items:center; }
.pair .mini-name{ margin-bottom: calc(var(--label) * 1.2); }
.pair + .pair{ margin-top: calc(var(--label) * 1.8); }
/* The stack box is the screen's box, so the first dial's top edge meets
   the top of the display; its label hangs in the air above the line. */
.stack > .pair:first-child .mini-name{ margin-top: calc(var(--label) * -2.2); }
.stack-play{ justify-content: flex-end; }

/* ── Screen ──────────────────────────────────────────────── */
#group-screen{ flex:1 1 auto; min-width:0; }
#screen{
  width:100%;
  height: var(--dial);
  background: var(--lcd);
  color: var(--lcd-ink);
  padding: calc(var(--dial) * 0.08) calc(var(--dial) * 0.1);
  display:flex; flex-direction:column;
  justify-content: space-between;
  overflow:hidden;
}
.scr-top{
  height:30%;
  display:flex; align-items:center; justify-content:space-between;
  gap:1rem;
}
.scr-bottom{
  height:60%;
  display:flex; align-items:center; justify-content:flex-end;
}
.seg-box{ height:100%; min-width:0; }
.scr-top .seg-box svg{ height:100%; width:auto; max-width:100%; display:block; }
.scr-bottom .seg-box svg{ height:100%; width:auto; max-width:100%; display:block; }

/* Unlit segments stay faintly visible, as on a real reflective LCD */
.seg{ fill: currentColor; opacity:.07; }
.seg.on{ opacity:1; }

/* Two hard inversions of the whole display — the only acknowledgement
   the device gives that a preset has been written. No glow, no fade:
   a reflective LCD either reflects or it doesn't. */
#screen.pulse{ animation: lcd-pulse .48s steps(1, end) 2; }
@keyframes lcd-pulse{
  0%,  100%{ background: var(--lcd);     color: var(--lcd-ink); }
  50%      { background: var(--lcd-ink); color: var(--lcd);     }
}

/* ── Play ────────────────────────────────────────────────── */
.btn{
  /* Matched to the stages dial, the smallest of the round controls.
     The one filled control on the deck: white face, black glyph. */
  width: var(--dial-stages); height: var(--dial-stages);
  border-radius:50%;
  border:1px solid var(--white);
  background: var(--white);
  color: var(--deck);
  cursor:pointer;
  display:grid; place-items:center;
  flex:none;
  transition: opacity .15s ease;
  /* Held for three seconds to store a preset — the touch callout and
     selection that a long press would otherwise raise are in the way. */
  touch-action:manipulation;
  user-select:none;
  -webkit-touch-callout:none;
}
.btn:hover:not([disabled]){ opacity:.85; }
.btn[disabled]{ opacity:.35; cursor:not-allowed; }
.btn svg{ width:38%; height:38%; fill: currentColor; }

/* ── Mobile: bowl above, then display and control band ───────
   The display and the band below it are both --band tall and both
   anchored to the bottom edge; the bowl takes whatever is left.
   The band is one row — program, time, stages, play — with a fixed
   24px gutter; the dials take whatever width is left over.
   ───────────────────────────────────────────────────────── */
/* Width OR height: a phone in landscape is wider than 768px but far too
   short for the square deck, which would otherwise squeeze the controls
   down to 14px. Either dimension being small means the stacked layout. */
@media (max-width: 768px), (max-height: 600px){
  :root{
    /* Two bands stacked at the bottom. Capped in dvh so a short viewport
     shrinks them rather than squeezing the bowl above out of existence. */
  --band: min(160px, 26dvh);
    --edge: 6vw;
    --gutter: 24px;
    --play: 48px;
    /* The three dials take whatever the play button and the gutters
       leave: the program wrapper is 1.5 dials wide, the other two are
       one each, so the row is 3.5 dials plus the fixed parts. */
    --ctl: min(
      calc((100vw - var(--edge) * 2 - var(--play) - var(--gutter) * 3) / 3.5),
      calc(var(--band) * 0.6)
    );
    --dial: var(--ctl);
    --dial-time: var(--ctl);
    --dial-stages: var(--ctl);
    --label:  .55rem;
    --plabel: .55rem;
    /* Whatever the two bottom bands leave. */
    --bowl-size: max(0px, min(70vw, calc(100dvh - var(--band) * 2 - 8dvh)));
  }

  #bowl-zone{ top: 3dvh; left: var(--edge); }
  #striker{ top: 3dvh; right: var(--edge); height: var(--bowl-size); }

  #controls{
    top: auto;
    left: 0; right: 0;
    /* Clear the iOS home indicator when the page is rendered edge to edge;
       0 everywhere else. The screen rides on `bottom: 100%` of this box, so
       it follows the band up. */
    bottom: env(safe-area-inset-bottom, 0px);
    height: var(--band);
    padding: 0 var(--edge);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--gutter);
  }

  /* The stack is a desktop arrangement: on mobile its two dials join
     the band as columns of their own. */
  #group-stack, .stack{ display: contents; }
  .pair + .pair{ margin-top: 0; }
  /* Each label is lifted out of the flow so a column's box is its dial
     alone: with the boxes centred in the band, every dial lands on the
     same centre line whether or not it is named. PROGRAM already floats
     this way inside its wrapper, at the same height above the rim. */
  .pair{ position: relative; }
  .pair .mini-name{
    position: absolute;
    left: 50%;
    bottom: calc(100% + var(--label) * 1.4);
    transform: translateX(-50%);
    margin: 0;
    white-space: nowrap;
  }

  /* TIME comes after STAGES in the DOM; the band reads left to right. */
  #group-program{ order: 1; }
  #pair-time   { order: 2; }
  #pair-stages { order: 3; }
  #group-play  { order: 4; }

  /* The play button keeps a fixed 48px and sits on the dials' bottom
     line: everything is centred in the band, so dropping it by half
     the difference in size puts the two bottom edges together. */
  .btn{ width: var(--play); height: var(--play); }
  #group-play{ margin-top: calc(var(--ctl) - var(--play)); }

  /* The display rides directly on top of the band, same height. */
  #group-screen{
    position: absolute;
    left: var(--edge); right: var(--edge);
    bottom: 100%;
    height: var(--band);
  }
  #screen{ height: 100%; }

  .prog-wrap{
    width: calc(var(--dial) * 1.5);
    height: calc(var(--dial) * 1.5);
  }
  /* PROGRAM sits on the same line as the other three labels. */
  .prog-name{ bottom: calc(83.333% + var(--label) * 1.4); }
}

/* Narrowest phones: at 320px the four columns and three 24px gutters add
   up to the whole viewport with nothing to spare, so the play button sits
   on the clipping edge. Tightening the gutter buys the row its slack back. */
@media (max-width: 380px){
  :root{ --gutter: 16px; }
}

/* ── Desktop: square deck ────────────────────────────────────
   Above 768px the interface is pinned to a square, centred in
   the viewport with at least 24px clear on every side.

   Interior metrics are re-derived from --side rather than from
   vw/vh: inside a square container the viewport units no longer
   describe the box the layout actually lives in, so leaving them
   would let the deck's proportions drift with window aspect.
   ───────────────────────────────────────────────────────── */
@media (min-width: 769px) and (min-height: 601px){
  :root{
    --side: min(calc(100vw - 48px), calc(100vh - 48px));
    --pad:        calc(var(--side) * 0.038);
    /* As large as will fit while keeping --clearance of clear space
       between the bowl and the top of the control panel. */
    --bowl-size:  calc(var(--side) - (var(--pad) * 2) - var(--dial) - var(--clearance));
    --dial:       calc(var(--side) * 0.195);
  }

  body{ display: grid; place-items: center; }

  #deck{
    width: var(--side);
    height: var(--side);
    border-radius: 24px;
  }

  /* The band is 1.5x the dial to make room for the program labels,
     but its content is only 1 dial tall and centred. Pulling it down
     by that 0.25 margin puts the dials, screen and play button on the
     same padding line as the bowl above and the edges either side. */
  #controls{
    top: auto;
    bottom: calc(var(--pad) - var(--dial) * 0.25);
    height: calc(var(--dial) * 1.5);
    gap: var(--band-gap);
  }

  /* Line the program dial's rim up with the left edge of the bowl above:
     its label wrapper is a quarter-dial wider on that side. */
  #group-program{ margin-left: calc(var(--dial) * -0.25); }

  /* The dials belong to the program selector, not the display: pull them
     left so the air on their program side is the smaller of the two. The
     matched right margin keeps the play button on its padding line. */
  #group-stack{
    margin-left:  calc(var(--shift) * -1);
    margin-right: var(--shift);
  }
  /* The display sits so that the play button has the same air on both
     sides: --pad to the deck's edge on its right, the same to the display
     on its left. The two margins add up to the same total as before, so
     the display keeps its width and shifts rather than stretching, and
     the space it leaves behind widens the gap back to the dials. */
  #group-screen{
    margin-left:  calc(var(--shift) - var(--pad) + var(--band-gap));
    margin-right: calc(var(--pad) - var(--band-gap));
  }

  /* Type scaled from --side for the same reason as the metrics. */
  :root{
    --label:  calc(var(--side) * 0.0098);
    --plabel: calc(var(--side) * 0.0105);
    /* The band's own gap between neighbours, and the extra added to it
       around the display. */
    --band-gap: calc(var(--side) * 0.022);
    --shift:    calc(var(--side) * 0.036);
  }
}
