/* ============================================================
   KLAVIATŪROS PERDANGA — kopija, ne refaktorius.

   Šio failo klavišų medžiaga yra NUKOPIJUOTA iš styles.css
   (tokenai ~976–1005, taisyklės ~1695–2119) — styles.css lieka
   programos, o šis failas — serverio. Jei programa kada pakeis
   klavišų dažus, šitą kopiją reikia atnaujinti ranka; sąmoningas
   mainas už tai, kad serveris niekada neserviruoja viso programos
   stiliaus lapo su jo prielaidomis apie langus.
   Svetainės kopija (assets/tool/keys.css) — iš overlay/keyboard.css be
   html/body taisyklės; atnaujinti kartu.
   ============================================================ */

:root {
  --font: 'Cormorant Infant', Georgia, serif;
  --accent: #E8DBC2;
  --ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
}


:root {
  /* ── KEYBED MATERIAL ────────────────────────────────────────────
     Ivory runs DARK AT THE BACK and light at the front: the back of a
     white key sits between the sharps, in occlusion, while the front
     catches the room. The old gradient ran the other way round, which
     is what made the keyboard read as "drawn in CSS". */
  --ivory-back:  #ece7dc;
  --ivory-mid:   #f6f1e8;
  --ivory-front: #fdfaf4;
  --ebony-crown: #191b1f;
  --ebony-body:  #0a0c0e;
  --ebony-cheek: #2b2f35;   /* lit left bevel — implies an upper-left light */

  /* Kept deliberately low. Real ivory is matte; a strong specular reads as
     wet plastic, and a strong streak on ebony reads as a sticker rather than
     lacquer. Turn these up only if the keybed looks flat on your display. */
  --sheen:   0.16;   /* white-key specular strength */
  --sheen-b: 0.42;   /* black-key gloss strength    */

  /* Pressed-key colour. Defaults matter: applyKeyHighlight*() only runs when
     localStorage has a saved value, so without these a clean install would
     tint pressed keys to `transparent`. */
  --active-key-white: #e6c172;
  --active-key-black: rgb(103,87,51);

  /* Grain tile: authored at 96px, drawn at 48px so it is 1:1 on a 2x display.
     feComponentTransfer squeezes the noise into 0.88-1.00 so `multiply` can
     darken by at most 12%. Drawn at an ABSOLUTE px size, never a percentage —
     so it rasterises once, is shared by all 88 keys, and keeps a constant
     physical scale from an 18px key to a 57px one, the way real material does. */
  --key-grain: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='96' height='96'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/><feComponentTransfer><feFuncR type='linear' slope='0.12' intercept='0.88'/><feFuncG type='linear' slope='0.12' intercept='0.88'/><feFuncB type='linear' slope='0.12' intercept='0.88'/><feFuncA type='linear' slope='0' intercept='1'/></feComponentTransfer></filter><rect width='96' height='96' filter='url(%23n)'/></svg>");
}

#piano-container {
  overflow-x: auto;
  overflow-y: visible;
  /* Just enough for the pressed-note labels and no more — every pixel here
     is a pixel the keys do not get, and 76px top + 14px bottom was costing
     the keybed about 8% of its height. Horizontal padding is set later, from
     the black-key width. */
  padding-top: 69px;   /* = styles.css #piano-container — the two surfaces must agree */
  padding-bottom: 0;
  box-shadow: inset 0 12px 20px -9px rgba(0,0,0,0.92);   /* fallboard shadow */
  text-align: center;
  position: relative;
}

/* Punching cloth: the felt strip the keys rest against at the back. Scrolls
   with the keybed because it hangs off #piano-keys, not the container. The
   accent tint is what ties the keyboard to the rest of the theme without
   introducing a colour the app does not already own. */
#piano-keys::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -5px;
  height: 5px;
  pointer-events: none;
  background: linear-gradient(180deg,
    #000 0%,
    #0c0c0e 26%,
    color-mix(in srgb, var(--accent) 18%, #241318) 58%,
    #170d10 84%,
    rgba(0,0,0,0.92) 100%);
  box-shadow: 0 2px 5px rgba(0,0,0,0.85);
}

.scale-name-overlay {
  position: absolute;
  top: 8px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font);   /* dropped a 'Cormorant' fallback — no such family ships here */
  font-size: 22px;
  font-weight: 400;
  letter-spacing: 0.07em;
  color: rgba(220,220,220,0.85);
  text-shadow: 0 1px 2px rgba(0,0,0,0.6);
  pointer-events: none;
  z-index: 10;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.scale-name-overlay.visible { opacity: 1; }

/* The keybed only ever scrolls sideways, and only when the window is too
   narrow for 52 keys at the 18px minimum. `width` is set as well as `height`
   because a container with overflow-x:auto also gets overflow-y:auto (the
   `visible` on the other axis is not allowed to stand), so a vertical bar can
   appear here too — and unstyled it comes back at the platform default width.

   The track is no longer #000. On the black theme nobody could tell, but OBS
   mode paints the case white and a black track is then a hard black rule down
   the side of the keyboard — the one thing on stream that is not the app. */
#piano-container::-webkit-scrollbar { height: 3px; width: 3px; }
#piano-container::-webkit-scrollbar-track { background: transparent; }
#piano-container::-webkit-scrollbar-thumb { background: #2a2a2a; border-radius: 2px; }
#piano-container::-webkit-scrollbar-thumb:hover { background: #444; }
#piano-container::-webkit-scrollbar-corner { background: transparent; }

#piano-keys {
  position: relative;
  display: inline-block;
  height: var(--key-height, 120px);
  flex-shrink: 0;
  font-size: 0; /* prevent inline-block whitespace gaps */
  /* An inline-block sits ON the text baseline, so the line box adds the
     container font's descender UNDER the keybed — 5px of nothing that the
     container counted as overflow. That handed the well a vertical scrollbar
     it had no scrolling to do, the bar took 6px off the width, and losing
     those 6px pushed the keys into horizontal overflow as well, so a second
     bar appeared under them. Two scrollbars, both drawn for a phantom.
     `font-size: 0` above does not help: the strut comes from the CONTAINER. */
  vertical-align: top;
  /* Bound invalidation to the keybed: a class flip on one key must not make
     the browser reconsider the rest of the window. Promoted from panel.html so
     the main window gets it too. No will-change — promoting 88 layers for a
     70ms transform costs far more than it saves. */
  contain: layout style;
  will-change: auto;
}

/* ────────────────────────────────────────────────────────────────
   KEY STRUCTURE

   .piano-key-*        geometry, hit area, cast shadow        STATIC
     ::before          note/degree label while pressed        text only
     .key-face         the material, front chamfer included   STATIC
        ::before       specular                               STATIC
        ::after        every state tint + press shading       opacity only
     ::after           finger dot

   All bevel/slot stops are in ABSOLUTE px, never percentages: `3%` is 0.54px
   on an 18px key and 1.7px on a 57px one, so a percentage bevel visibly
   changes character across the keyboard. Absolute stops give every key the
   same crisp edge at every width.
   ──────────────────────────────────────────────────────────────── */

.piano-key-white {
  position: relative;
  display: inline-block;
  vertical-align: top;
  width: var(--key-width, 24px);
  height: var(--key-height, 120px);
  background: none;
  border: 0;                      /* borders became inset shadows: at the 18px
                                     minimum, 2px of border was 11% of the key */
  border-radius: 0 0 2px 2px;
  cursor: pointer;
  z-index: 1;
  pointer-events: auto;
  /* Cast onto the keybed. Static — never animated, never transitioned. */
  box-shadow:
    0 6px 10px -3px rgba(0,0,0,0.45),
    0 2px 3px rgba(0,0,0,0.28);
}

.piano-key-white .key-face {
  position: absolute;
  inset: 0;
  border-radius: 0 0 2px 2px;
  transform-origin: 50% 0;        /* pivot at the back — the balance rail */
  contain: layout paint style;    /* invalidation boundary + blend isolation */
  background-image:
    var(--key-grain),
    linear-gradient(rgba(255,250,238,var(--tone-a,0)), rgba(255,250,238,var(--tone-a,0))),
    /* slot between neighbours */
    linear-gradient(90deg,
      rgba(20,17,12,0.20) 0, rgba(20,17,12,0.05) 1.5px, rgba(20,17,12,0) 5px,
      rgba(20,17,12,0) calc(100% - 5px), rgba(20,17,12,0.08) calc(100% - 1.5px),
      rgba(20,17,12,0.26) 100%),
    /* ambient occlusion: fallboard shadow at the back, deep between the sharps */
    linear-gradient(180deg,
      rgba(16,13,9,0.20) 0,
      rgba(16,13,9,0.10) 5px,
      rgba(16,13,9,0.05) 15%,
      rgba(16,13,9,0.015) 46%,
      rgba(16,13,9,0) 63%),
    /* ivory body — the front chamfer is the tail of THIS gradient, so there is
       no element seam at the bottom of the key to read as a tone step */
    linear-gradient(180deg,
      var(--ivory-back)   0%,
      #e9e2d6            17%,
      var(--ivory-mid)   43%,
      var(--ivory-front) 74%,
      #f8f3ea            90%,
      #efe8dc            97%,
      #e0d7c7           100%);
  background-size: 48px 48px, auto, auto, auto, auto;
  background-position: var(--grain-x, 0) var(--grain-y, 0), 0 0, 0 0, 0 0, 0 0;
  /* multiply only on the STATIC grain layer — Blink pays for it once at raster
     time. The old rules blended on :hover/.active too, i.e. on every repaint. */
  background-blend-mode: multiply, normal, normal, normal, normal;
  box-shadow:
    inset  1px 0 0 rgba(255,255,255,0.72),
    inset -1px 0 0 rgba(0,0,0,0.16),
    inset  0 -1px 0 rgba(60,50,36,0.22);   /* crisp 1px front edge, not a band */
}

/* Raked sheen, not a symmetric shine */
.piano-key-white .key-face::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(97deg,
    rgba(255,255,255,0) 16%,
    rgba(255,255,255,0.50) 32%,
    rgba(255,255,255,0.10) 45%,
    rgba(255,255,255,0) 60%);
  opacity: var(--sheen, 0.30);
}

.piano-key-black {
  position: absolute;
  /* Ratios come from KEYBED_GEOMETRY via src/lib/keybed.js — the same numbers
     that decide where the key is placed. Do not hardcode them here. */
  width:  calc(var(--key-width, 24px)  * var(--black-w, 0.57));
  height: calc(var(--key-height, 120px) * var(--black-h, 0.62));
  background: none;
  border: 0;
  border-radius: 0 0 2px 2px;
  cursor: pointer;
  z-index: 3;
  pointer-events: auto;
  /* Biased right, so the light reads as upper-left. Negative spread keeps the
     shadow inside the tapered face. */
  box-shadow:
    1px 1px 0 rgba(0,0,0,0.55),
    2px 8px 10px -4px rgba(0,0,0,0.62),
    0 2px 3px rgba(0,0,0,0.40);
}

.piano-key-black .key-face {
  position: absolute;
  inset: 0;
  border-radius: 0 0 2px 2px;
  transform-origin: 50% 0;
  contain: layout paint style;
  clip-path: polygon(0 0, 100% 0, 94% 100%, 6% 100%);   /* sharps taper forward */
  background-image:
    var(--key-grain),
    /* side bevels: lit left cheek, dark right cheek */
    linear-gradient(90deg,
      var(--ebony-cheek) 0, #23262b 6%, #131519 15%,
      var(--ebony-body) 50%,
      #090b0d 85%, #050607 95%, #000 100%),
    /* matte crown → gloss → chamfer catch-light → lip */
    linear-gradient(180deg,
      var(--ebony-crown) 0%, #16181c 6%,
      #121417 30%, #0e1013 56%,
      #090b0d 79%,
      #333a44 90%,
      #0a0c0f 96%,
      #040507 100%);
  background-size: 48px 48px, auto, auto;
  background-position: var(--grain-x, 0) var(--grain-y, 0), 0 0, 0 0;
  background-blend-mode: multiply, normal, normal;
}

/* Polished ebony: crown highlight + a narrow raked reflection. It is the
   streak, not overall brightening, that reads as lacquer. */
.piano-key-black .key-face::before {
  content: '';
  position: absolute;
  left: 7%; right: 7%; top: 0; bottom: 6%;
  pointer-events: none;
  background:
    linear-gradient(101deg,
      rgba(255,255,255,0) 26%,
      rgba(255,255,255,0.14) 34%,
      rgba(255,255,255,0.03) 40%,
      rgba(255,255,255,0) 46%),
    linear-gradient(180deg,
      rgba(255,255,255,0.22) 0%,
      rgba(255,255,255,0.06) 13%,
      rgba(255,255,255,0.015) 40%,
      rgba(255,255,255,0.008) 68%,
      rgba(255,255,255,0.06) 88%,
      rgba(255,255,255,0) 96%);
  opacity: var(--sheen-b, 0.42);
}

/* ── STATE ──────────────────────────────────────────────────────
   One overlay drives pressed, search and scale highlighting. Only its
   colour and opacity change, so a note event never invalidates a
   background — and because the tint MULTIPLIES over ivory (and SCREENS
   over ebony) rather than replacing it, the gradient, sheen and grain
   survive whatever colour the user picks. Plain alpha would wash the
   material out on white and make dark colours invisible on black. */
.piano-key-white .key-face::after,
.piano-key-black .key-face::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: var(--key-state-color, transparent);
  opacity: var(--key-state-alpha, 0);
  transition: opacity 70ms var(--ease-out, ease-out);
}

.piano-key-white.active { --key-state-color: var(--active-key-white); --key-state-alpha: 1; }
.piano-key-white.active .key-face::after {
  mix-blend-mode: multiply;
  box-shadow:
    inset 0 7px 11px -5px rgba(0,0,0,0.46),   /* the key dips: back edge occludes */
    inset 0 -3px 6px -2px rgba(0,0,0,0.20);
}
.piano-key-black.active { --key-state-color: var(--active-key-black); --key-state-alpha: 1; }
.piano-key-black.active .key-face::after {
  mix-blend-mode: screen;
  box-shadow: inset 0 5px 9px -4px rgba(0,0,0,0.60);
}

/* mix-blend-mode only ever applies to the handful of pressed keys, never all 88 */
/* A searched chord or scale lights the keys it would sound — so it lights
   them the way sounding them does, in the pressed-key colour the player
   picked. Each used to have a colour of its own: a fixed brown for a
   chord, and raw --accent for a scale, which with a pale accent painted
   white onto ivory and showed nothing at all. What still separates a
   preview from a real press is the dip and the note labels, and those
   stay with .active. */
.piano-key-white.search-highlight,
.piano-key-white.scale-highlight { --key-state-color: var(--active-key-white); --key-state-alpha: 1; }
.piano-key-black.search-highlight,
.piano-key-black.scale-highlight { --key-state-color: var(--active-key-black); --key-state-alpha: 1; }
.piano-key-white.search-highlight .key-face::after,
.piano-key-white.scale-highlight  .key-face::after { mix-blend-mode: multiply; }
.piano-key-black.search-highlight .key-face::after,
.piano-key-black.scale-highlight  .key-face::after { mix-blend-mode: screen; }

/* ── THE PRESS ──────────────────────────────────────────────────
   A back-pivoted dip, not a slab translate — the transform sits on
   .key-face, not the key root, because on the root it would tilt the note
   label (which lives above the key) and give it a perspective parent.

   A single transition can only ease from A to B, which is why the old press
   read as a binary on/off. A real key accelerates down, hits the felt
   punching, and settles back a hair — so the keyframes overshoot past the
   resting angle at 48% and recover. Release is slower than the strike
   (the return spring is weaker than the finger) and gets its own easing.

   Compositor-only: transform and opacity, no layout, no paint. */
@keyframes key-dip-white {
  0%   { transform: perspective(760px) rotateX(0)       translateY(0); }
  48%  { transform: perspective(760px) rotateX(1.28deg) translateY(0.85px); }
  74%  { transform: perspective(760px) rotateX(0.80deg) translateY(0.34px); }
  100% { transform: perspective(760px) rotateX(0.95deg) translateY(0.5px); }
}
@keyframes key-dip-black {
  0%   { transform: perspective(620px) rotateX(0)       translateY(0); }
  46%  { transform: perspective(620px) rotateX(1.95deg) translateY(1.5px); }
  73%  { transform: perspective(620px) rotateX(1.30deg) translateY(0.8px); }
  100% { transform: perspective(620px) rotateX(1.55deg) translateY(1px); }
}
.piano-key-white.active .key-face {
  transform: perspective(760px) rotateX(0.95deg) translateY(0.5px);
  animation: key-dip-white 150ms cubic-bezier(0.25, 0.9, 0.3, 1);
}
.piano-key-black.active .key-face {
  transform: perspective(620px) rotateX(1.55deg) translateY(1px);
  animation: key-dip-black 130ms cubic-bezier(0.25, 0.9, 0.3, 1);
}
/* Release: slower than the strike, with a whisper of spring-back. */
.piano-key-white .key-face,
.piano-key-black .key-face {
  transition: transform 105ms cubic-bezier(0.34, 1.16, 0.64, 1);
}

@media (prefers-reduced-motion: reduce) {
  .piano-key-white.active .key-face,
  .piano-key-black.active .key-face { animation: none; }
}

/* ── PER-KEY VARIATION ──────────────────────────────────────────
   Prime cycles keep the pattern from reading as a repeat. The grain offsets
   matter most: without them the noise tile lines up continuously across all
   52 keys and reads as a background texture instead of per-key material.
   (nth-of-type counts div siblings: whites are 1-52, blacks 53-88.) */
.piano-key-white:nth-of-type(3n)    { --tone-a: 0.015; --grain-x: 13px; --grain-y:  7px; }
.piano-key-white:nth-of-type(7n+2)  { --tone-a: 0;     --grain-x: 31px; --grain-y: 19px; }
.piano-key-white:nth-of-type(11n+5) { --tone-a: 0.028; --grain-x:  5px; --grain-y: 37px; }
.piano-key-white:nth-of-type(5n+3)  {                  --grain-x: 22px; --grain-y: 44px; }
.piano-key-black:nth-of-type(4n)    { --grain-x: 17px; --grain-y: 11px; }
.piano-key-black:nth-of-type(7n+3)  { --grain-x: 39px; --grain-y: 29px; }

/* ── FINGER DOT ─────────────────────────────────────────────────
   Scaled: a fixed 16px dot does not fit on an 18px key. */
.piano-key-white.active::after,
.piano-key-white.search-highlight::after,
.piano-key-white.scale-highlight::after {
  content: '';
  position: absolute;
  bottom: 30%;
  left: 50%;
  transform: translateX(-50%);
  width:  min(16px, calc(var(--key-width, 24px) * 0.5));
  height: min(16px, calc(var(--key-width, 24px) * 0.5));
  border-radius: 50%;
  background: #111;
  border: 2px solid #555;
  box-shadow: 0 0 8px rgba(0,0,0,0.6);
  pointer-events: none;
  z-index: 5;
}

.piano-key-black.active::after,
.piano-key-black.search-highlight::after,
.piano-key-black.scale-highlight::after {
  content: '';
  position: absolute;
  bottom: 15%;
  left: 50%;
  transform: translateX(-50%);
  width:  min(14px, calc(var(--key-width, 24px) * 0.42));
  height: min(14px, calc(var(--key-width, 24px) * 0.42));
  border-radius: 50%;
  background: #eee;
  border: 2px solid #999;
  box-shadow: 0 0 8px rgba(255,255,255,0.6);
  pointer-events: none;
  z-index: 5;
}

/* Hide QWERTY shortcut labels */
.key-label { display: none; }

/* Note name + solfège labels ABOVE the keys on dark background */
.piano-key-white.active::before,
.piano-key-black.active::before {
  content: attr(data-degree) '\A' attr(data-solfege) '\A' attr(data-notename);
  white-space: pre;
  position: absolute;
  top: -69px;
  left: 50%;
  transform: translateX(-50%);
  text-align: center;
  /* Not --fs-caption, and not free to grow. This label is three stacked
     lines (degree / solfège / note name) hanging in the fixed 56px gap
     above the keybed — 3 × 1.3 line-height, so anything over ~14px runs
     out of gap and the bottom line lands on the keys themselves. It was
     briefly on the caption step, and when that step moved to 17px this
     is exactly what happened. Its size is bound by the gap above, the
     way the dock's is bound by the width beside it. */
  font-size: 14px;
  line-height: 1.3;
  color: #e0e0e0;
  font-family: var(--font);
  font-weight: 400;
  pointer-events: none;
  z-index: 10;
  letter-spacing: -0.02em;
  width: max-content;
}
