/* ===== Design tokens: every colour, font, size, gap, border and timing lives here ===== */
:root {
  --c-bg: #000;
  --c-fg: #fff;
  --c-mid: #808080;

  --font: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --fs-label: 11px;
  --fs-num: 10px;
  --fs-transport: 12px;
  --fs-readout: 16px;
  --letter: 0.04em;

  --app-max: 430px;
  --gutter: 28px;
  --gap: 2px;
  --row-h: 43px;
  --header-h: 64px;
  --bar-row-h: 52px;
  --tap-min: 40px;
  --hit-size: calc(var(--row-h) * 0.5); /* FREE mode hit marker (square; its top edge is the hit time) */
  --c-free-line: rgba(255, 255, 255, 0.14); /* FREE mode crotchet lines: very faint */
  --picker-close: 56px; /* the picker's × : a full-height square in its title row */

  --border: 1px;
  --ghost-border: 2px;
  --step-inset: 14%;   /* gap inside each cell around an on-step */
  --fx-edge-mark: 3px; /* effects cylinder: bar on the side an on-effect is hidden */
  --roll-gap: 10%;     /* gap between a roll's two half-blocks */
  --dot-size: 3px;     /* empty-step marker */
  --playhead-rule: 2px;
  --mute-opacity: 0.35;

  --edge-blur-size: 22%;   /* how far the edge blur reaches into the grid, top and bottom */
  --edge-blur-max: 6px;    /* blur at the very edge (ramps down toward the centre) */
  --pop-ms: 90ms;          /* tap pop: square grows in / shrinks to a dot */
  --cursor-blink-ms: 1060ms;
  --note-pad: 14px;        /* one-off notes (silent switch): band height around the text */

  --c-wave: rgba(255, 255, 255, 0.07); /* loop underlay: deliberately a second, fainter grey */
  --wave-muted-opacity: 0.4;            /* underlay when the loop is muted */
  --loopstrip-h: 22px;

  /* Style pass (M7 E): soft glow on lit things */
  --glow: 0 0 10px rgba(255, 255, 255, 0.55);       /* lit steps, hits, lit buttons */
  --glow-text: 0 0 5px rgba(255, 255, 255, 0.44);   /* all text */
}

/* ===== Font (self-hosted, OFL — fonts/OFL-JetBrainsMono.txt) ===== */
@font-face { font-family: 'JetBrains Mono'; font-weight: 400; font-display: swap; src: url(fonts/jetbrains-mono-latin-400-normal.woff2) format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-weight: 700; font-display: swap; src: url(fonts/jetbrains-mono-latin-700-normal.woff2) format('woff2'); }

/* ===== Base ===== */
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; background: var(--c-bg); color: var(--c-fg); }
body {
  font-family: var(--font);
  text-transform: uppercase;
  letter-spacing: var(--letter);
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}
button, input {
  font: inherit; color: inherit; background: none; border: var(--border) solid var(--c-mid);
  border-radius: 0; letter-spacing: inherit; text-transform: inherit;
}
button { cursor: pointer; touch-action: manipulation; }
button.is-on { background: var(--c-fg); color: var(--c-bg); border-color: var(--c-fg); }
button:disabled { opacity: var(--mute-opacity); cursor: default; }
a { color: inherit; }

/* ===== App frame ===== */
#app {
  display: flex; flex-direction: column;
  height: 100dvh; max-width: var(--app-max); margin: 0 auto;
}
.cols { display: grid; grid-template-columns: var(--gutter) repeat(8, 1fr); gap: var(--gap); }

/* ===== Track header ===== */
#tracks { height: var(--header-h); padding: var(--gap) var(--gap) var(--gap) 0; border-bottom: var(--border) solid var(--c-mid); }
.ig { display: flex; align-items: flex-start; justify-content: center; font-size: var(--fs-num); text-decoration: none; padding-top: 2px; }
.track { display: grid; grid-template-rows: auto 1fr 1fr; grid-template-columns: 1fr 1fr; gap: var(--gap); font-size: var(--fs-label); }
.track .name { grid-column: 1 / 3; text-align: center; }
.track button.name { color: var(--c-fg); padding: 0; }
.track button { border: 0; color: var(--c-mid); }
.track button.vol { grid-column: 1 / 3; color: var(--c-fg); }
.track button.is-on { color: var(--c-bg); }
.track.is-muted .name, .track.is-muted .ghost, .track.is-muted .vol { opacity: var(--mute-opacity); }
.track.is-missing .name { text-decoration: line-through; color: var(--c-mid); }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
#grid:focus-visible { outline: none; }
.cell.is-cursor { outline: var(--border) dashed var(--c-fg); outline-offset: calc(var(--gap) * -1); }

/* ===== LOOP strip (above the track header) ===== */
.loopstrip {
  height: var(--loopstrip-h); align-items: stretch; font-size: var(--fs-label);
  padding: 0 var(--gap) 0 0; border-bottom: var(--border) solid var(--c-mid);
  grid-template-columns: var(--gutter) 1fr auto auto auto;
}
.loopstrip button.tempo { color: var(--c-fg); white-space: nowrap; }
.loopstrip button[hidden] { display: none; }
.loopstrip .lbl { font-size: var(--fs-num); color: var(--c-mid); display: flex; align-items: center; justify-content: center; }
.loopstrip button { border: 0; color: var(--c-mid); padding: 0 8px; }
.loopstrip button.name { text-align: left; color: var(--c-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.loopstrip:not(.has-loop) button.name { color: var(--c-mid); }
.loopstrip button.vol { color: var(--c-fg); }
.loopstrip button.is-on { color: var(--c-bg); }
.loopstrip.is-muted button.name, .loopstrip.is-muted .vol { opacity: var(--mute-opacity); }

/* ===== Grid ===== */
.grid-wrap { flex: 1; position: relative; min-height: 0; }
#grid {
  height: 100%; position: relative; overflow-y: auto; padding-right: var(--gap);
  overscroll-behavior: contain; touch-action: pan-y; scrollbar-width: none;
  overflow-anchor: none; /* grid.js owns the scroll position (re-centre, detent); no browser nudges */
}
#grid::-webkit-scrollbar { display: none; }
.row { height: var(--row-h); margin-top: var(--gap); position: relative; transform-origin: 50% 50%; }
/* Rows well outside the drum's reach skip rendering entirely (grid.js decides, since the warp pulls
   rows in from beyond the layout viewport). Keeps the repeated copies cheap. */
.row.is-far { content-visibility: hidden; }
/* Beat / bar rules sit in the gap above the row so every row keeps the same pitch. */
.row.beat::before, .row.bar::before {
  content: ''; position: absolute; left: 0; right: 0; top: calc(var(--gap) / -2);
  border-top: var(--border) solid var(--c-mid); pointer-events: none;
}
.row.bar::before { top: calc(var(--gap) / -2 - var(--border)); border-top: calc(var(--border) * 3) double var(--c-fg); }
.wave {
  position: absolute; left: var(--gutter); right: 0; pointer-events: none;
  top: calc(var(--gap) / -2); bottom: calc(var(--gap) / -2); /* bridge the row gaps */
  background-size: 100% 100%; background-repeat: no-repeat;
}
.loop-muted .wave { opacity: var(--wave-muted-opacity); }
.num { font-size: var(--fs-num); color: var(--c-mid); display: flex; align-items: center; justify-content: center; }
.row.on-beat .num { color: var(--c-fg); }
.cell { position: relative; }
/* Off = small dot, on = inset square. Explicit geometry both ways so the tap pop can transition. */
.cell::before {
  content: ''; position: absolute; left: 50%; top: 50%; width: var(--dot-size); height: var(--dot-size);
  transform: translate(-50%, -50%); background: var(--c-mid); border: 0 solid var(--c-fg);
  transition: left var(--pop-ms) ease-out, top var(--pop-ms) ease-out, width var(--pop-ms) ease-out,
    height var(--pop-ms) ease-out, transform var(--pop-ms) ease-out, background-color var(--pop-ms) ease-out;
}
.cell.on::before {
  left: var(--step-inset); top: var(--step-inset);
  width: calc(100% - 2 * var(--step-inset)); height: calc(100% - 2 * var(--step-inset));
  transform: none; background: var(--c-fg);
}
.cell.on.col-ghost::before { background: transparent; border-width: var(--ghost-border); }
/* Roll (M7): two half-blocks, one above the other, as the two notes fall in time. */
.cell.roll::before { height: calc(50% - var(--step-inset) - var(--roll-gap) / 2); }
.cell.roll::after {
  content: ''; position: absolute; left: var(--step-inset); bottom: var(--step-inset);
  width: calc(100% - 2 * var(--step-inset)); height: calc(50% - var(--step-inset) - var(--roll-gap) / 2);
  background: var(--c-fg); border: 0 solid var(--c-fg);
}
.cell.roll.col-ghost::after { background: transparent; border-width: var(--ghost-border); }
.cell.col-muted { opacity: var(--mute-opacity); } /* whole column dims: dots, squares, ghosts */
.row.is-playhead .num { background: var(--c-fg); color: var(--c-bg); }
.row.is-playhead::after {
  content: ''; position: absolute; left: var(--gutter); right: 0; top: 50%;
  height: var(--playhead-rule); background: var(--c-fg); pointer-events: none;
}
.cell.flash { outline: var(--ghost-border) solid var(--c-fg); outline-offset: var(--gap); }

/* FREE mode (M6): no step dots/squares, a faint line every crotchet, numbers only on crotchets. */
#grid.is-free .cell::before { display: none; }
#grid.is-free .row.beat::before, #grid.is-free .row.bar::before { top: calc(var(--gap) / -2); border-top: var(--border) solid var(--c-free-line); }
#grid.is-free .row:not(.on-beat):not(.is-playhead) .num { visibility: hidden; }
.hit {
  position: absolute; left: 50%; width: var(--hit-size); height: var(--hit-size); margin-left: calc(var(--hit-size) / -2);
  background: var(--c-fg); touch-action: none; z-index: 1;
}
.cell.col-ghost .hit { background: transparent; border: var(--ghost-border) solid var(--c-fg); }
.hit.flash { outline: var(--ghost-border) solid var(--c-fg); outline-offset: var(--gap); }
#grid.is-dragging { cursor: grabbing; }
#grid.is-dragging .hit { pointer-events: none; } /* find the row under the finger, not the held hit */

/* Speed blur overlay; grid.js sets its backdrop blur from scroll speed. */
.speed-blur { position: absolute; inset: 0; pointer-events: none; }

/* Progressive edge blur: three stacked backdrop blurs, each masked closer to the edge. */
.edge-blur { position: absolute; left: 0; right: 0; height: var(--edge-blur-size); pointer-events: none; }
.edge-blur.top { top: 0; --dir: to bottom; }
.edge-blur.bottom { bottom: 0; --dir: to top; }
.edge-blur i { position: absolute; inset: 0; }
.edge-blur i:nth-child(1) {
  backdrop-filter: blur(calc(var(--edge-blur-max) / 4)); -webkit-backdrop-filter: blur(calc(var(--edge-blur-max) / 4));
  mask-image: linear-gradient(var(--dir), #000, transparent); -webkit-mask-image: linear-gradient(var(--dir), #000, transparent);
}
.edge-blur i:nth-child(2) {
  backdrop-filter: blur(calc(var(--edge-blur-max) / 2)); -webkit-backdrop-filter: blur(calc(var(--edge-blur-max) / 2));
  mask-image: linear-gradient(var(--dir), #000, transparent 60%); -webkit-mask-image: linear-gradient(var(--dir), #000, transparent 60%);
}
.edge-blur i:nth-child(3) {
  backdrop-filter: blur(var(--edge-blur-max)); -webkit-backdrop-filter: blur(var(--edge-blur-max));
  mask-image: linear-gradient(var(--dir), #000, transparent 30%); -webkit-mask-image: linear-gradient(var(--dir), #000, transparent 30%);
}

/* First-load hint: blinking block cursor until the first tap. */
.hint {
  position: absolute; left: var(--gutter); right: 0; top: 50%; transform: translateY(-50%);
  text-align: center; font-size: var(--fs-transport); pointer-events: none;
}
.hint[hidden] { display: none; }
.hint .cursor { display: inline-block; width: 0.6em; height: 1.1em; vertical-align: -0.2em; background: var(--c-fg); animation: blink var(--cursor-blink-ms) steps(1) infinite; }
.hint span:last-child { background: var(--c-bg); padding: 0 0.4em; }
@keyframes blink { 50% { opacity: 0; } }
.hint.is-note .cursor { display: none; } /* one-off notes (silent switch) don't blink */
.hint.is-note { left: 0; padding: var(--note-pad) 0; background: var(--c-bg); border-block: var(--border) solid var(--c-fg); }

/* Phones in landscape: portrait-only layout, so cover it with a rotate hint. */
.rotate { display: none; }
@media (orientation: landscape) and (max-height: 500px) and (pointer: coarse) {
  #app { visibility: hidden; }
  .rotate {
    display: flex; position: fixed; inset: 0; align-items: center; justify-content: center;
    background: var(--c-bg); color: var(--c-fg); font-size: var(--fs-transport);
  }
}

/* Sound picker: opens over the grid, terminal-style list. */
.picker { position: absolute; inset: 0; background: var(--c-bg); display: flex; flex-direction: column; font-size: var(--fs-transport); }
.picker[hidden] { display: none; }
/* Loop confirm card (M7): along the bottom of the grid, so the drums stay in view while it plays. */
.loopcard {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; display: grid; gap: var(--gap); padding: var(--gap);
  grid-template-columns: 1fr var(--tap-min) var(--tap-min) calc(var(--tap-min) * 1.5);
  background: var(--c-bg); border-top: var(--border) solid var(--c-fg); font-size: var(--fs-transport);
}
.loopcard[hidden] { display: none; }
.loopcard button { min-height: var(--tap-min); }
.lc-text { display: flex; flex-direction: column; justify-content: center; padding-left: 8px; }
.lc-note { color: var(--c-mid); font-size: var(--fs-num); }
.p-title { min-height: var(--picker-close); display: flex; align-items: center; padding: 0 var(--picker-close) 0 12px; border-bottom: var(--border) solid var(--c-mid); }
.p-list { flex: 1; overflow-y: auto; padding: 6px 0; }
.p-sec { color: var(--c-mid); font-size: var(--fs-num); padding: 14px 12px 4px; }
.p-empty { color: var(--c-mid); padding: 8px 24px; }
.p-item { display: block; width: 100%; min-height: var(--tap-min); text-align: left; padding: 0 12px 0 24px; border: 0; }
.p-item.is-on { background: var(--c-fg); color: var(--c-bg); }
.p-msg { min-height: 1.5em; padding: 6px 12px; color: var(--c-fg); }
.p-close { position: absolute; top: 0; right: 0; width: var(--picker-close); height: var(--picker-close); border: 0; border-left: var(--border) solid var(--c-mid); font-size: 24px; }

/* ===== Transport ===== */
#transport { padding-bottom: env(safe-area-inset-bottom); border-top: var(--border) solid var(--c-mid); }
.bar-row { display: grid; gap: var(--gap); height: var(--bar-row-h); padding: var(--gap); font-size: var(--fs-transport); }
.bar-row.r1 { grid-template-columns: 2fr 2fr 2fr 1fr 1fr; } /* PLAY · BPM · SWING/SNAP · FREE · FOLLOW */
.bar-row.r2 { grid-template-columns: 1fr 1fr 2fr 1fr; } /* RANDOM · CLEAR · effects · EXPORT */
/* Effects cylinder (M7, fx.js): buttons stacked at the centre, placed on the drum by transform. */
.fx { position: relative; overflow: hidden; touch-action: none; user-select: none; -webkit-user-select: none; }
.fx button {
  position: absolute; top: 0; left: 50%; height: 100%;
  width: var(--fx-item-w); margin-left: calc(var(--fx-item-w) / -2); transform-origin: center;
}
.fx button.is-behind { pointer-events: none; }
/* An effect that's on is round the left / right of the drum: a bright bar on that edge. */
.fx::before, .fx::after {
  content: ''; position: absolute; top: 0; bottom: 0; width: var(--fx-edge-mark); z-index: 1;
  background: var(--c-fg); pointer-events: none; display: none;
}
.fx::before { left: 0; } .fx::after { right: 0; }
.fx.on-left::before, .fx.on-right::after { display: block; }
.bar-row.r2-menu { grid-template-columns: 1fr 1fr 1fr var(--tap-min); }
.bar-row[hidden] { display: none; }
.bar-row > [hidden] { display: none; } /* SWING ↔ SNAP swap */
button.is-busy { border-color: var(--c-fg); }
.readout {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  border: var(--border) solid var(--c-mid); touch-action: none; user-select: none; -webkit-user-select: none; cursor: ns-resize;
}
.readout .lbl { font-size: var(--fs-num); color: var(--c-mid); }
.readout .val { font-size: var(--fs-readout); }
.readout input { border: 0; width: 100%; text-align: center; font-size: var(--fs-readout); outline: 0; }
.readout.is-dragging, .readout.is-editing { border-color: var(--c-fg); }

/* ===== Style pass (M7 E): glow. Static (no animation). Scan lines tried and dropped by Olun ===== */
body, button { text-shadow: var(--glow-text); }
.cell.on::before, .cell.roll::after, .hit, button.is-on { box-shadow: var(--glow); }
