/*! loum.io · © 2025–2026 Louie Sellers · All rights reserved · Original at https://loum.io */
/* Pewter kite over Halvergate. */
/* Sampler v1 */
:root {
    --bg: #121211; --raise: #1a1a19; --raise-2: #222220; --line: #2a2a28; --line-strong: #3a3a37;
    --text: #e9e7e2; --muted: #9a978f; --faint: #6b6963; --accent: #ff7a3d; --accent-dim: rgba(255, 122, 61, 0.14); --accent-soft: rgba(255, 122, 61, 0.35);
    --font: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
    --spring: cubic-bezier(0.34, 1.4, 0.64, 1);
    --radius: 6px;
}
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { margin: 0; background: var(--bg); color: var(--text); font: 13px/1.4 var(--font); -webkit-tap-highlight-color: transparent; }
main { width: 100%; max-width: 640px; margin: 0 auto; padding: 16px; display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr); }
h1 { margin: 0; font-size: 16px; font-weight: 500; }
button { font: inherit; color: inherit; cursor: pointer; }
button:focus-visible, input:focus-visible, summary:focus-visible, a:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
p { margin: 0; }

.top { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.ctl { height: 40px; padding: 0 12px; background: var(--raise); border: 1px solid var(--line); border-radius: var(--radius); user-select: none; -webkit-user-select: none; }
.ctl[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--bg); }
.ctl:disabled { color: var(--faint); cursor: default; }

.sliders { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 16px; }
.sliders label { display: grid; gap: 4px; color: var(--muted); }
.sliders label > span { display: flex; justify-content: space-between; }
.sliders output { color: var(--text); }
input[type=range] { width: 100%; height: 24px; margin: 0; accent-color: var(--accent); }

.grid-head { display: flex; align-items: center; gap: 8px; color: var(--muted); margin-bottom: 8px; min-height: 24px; }
.grid-head .name { margin-right: auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.grid-head b { font-weight: 500; color: var(--text); }
.plain { background: none; border: 0; padding: 0; color: var(--muted); text-decoration: underline; text-underline-offset: 3px; white-space: nowrap; }
.plain[aria-pressed="true"] { color: var(--accent); }

.row { display: none; }
.row.sel { display: block; }
.row-name { display: none; }
.cells { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 3px; }
.cell { height: 40px; padding: 0; background: var(--raise); border: 1px solid var(--line); border-radius: 3px; }
.cell.beat { background: var(--raise-2); border-color: var(--line-strong); }
.cell.on { background: var(--accent); border-color: var(--accent); }
.cell.now { box-shadow: inset 0 0 0 2px var(--text); }

.pads { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px; touch-action: none; }
.pad { position: relative; aspect-ratio: 1; padding: 8px; display: flex; align-items: flex-end; text-align: left; overflow: hidden; background: var(--raise-2); border: 1px solid var(--line-strong); border-bottom-width: 3px; border-radius: var(--radius); color: var(--text); touch-action: none; user-select: none; -webkit-user-select: none; transition: transform 160ms var(--spring), background-color 160ms ease-out; }
.pad .t .chop, .row-name .chop { color: var(--muted); }
.pad .t { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; overflow-wrap: anywhere; line-height: 1.25; }
.pad.empty { background: var(--raise); border-color: var(--line); color: var(--faint); }
.pad.sel { border-color: var(--muted); }
/* A locked pad wears a small padlock. While locking, the unlocked ones show it open and faint */
.pad.held::before, .pads.locking .pad:not(.empty)::before { content: ""; position: absolute; top: 9px; left: 8px; width: 10px; height: 12px; background: var(--muted); -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 12'%3E%3Cpath d='M2.5 5.5V3.5a2.5 2.5 0 0 1 5 0v2' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Crect x='0.5' y='5.5' width='9' height='6' rx='1'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 12'%3E%3Cpath d='M2.5 5.5V3.5a2.5 2.5 0 0 1 5 0v2' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Crect x='0.5' y='5.5' width='9' height='6' rx='1'/%3E%3C/svg%3E") center / contain no-repeat; }
.pads.locking .pad:not(.empty):not(.held)::before { background: var(--faint); -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 12'%3E%3Cpath d='M2.5 5.5V3.5a2.5 2.5 0 0 1 5 0' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Crect x='0.5' y='5.5' width='9' height='6' rx='1'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 12'%3E%3Cpath d='M2.5 5.5V3.5a2.5 2.5 0 0 1 5 0' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Crect x='0.5' y='5.5' width='9' height='6' rx='1'/%3E%3C/svg%3E"); }
.pads.locking .pad.held::before { background: var(--accent); }
.pad.hit::before { background: var(--bg) !important; }
.pad.busy { color: var(--faint); animation: dig 900ms ease-in-out infinite alternate; }
@keyframes dig { from { background: var(--raise); } to { background: var(--accent-dim); } }
.pad.hit { background: var(--accent); border-color: var(--accent); color: var(--bg); transform: translateY(2px); transition: none; }
.pad.drop { border-color: var(--accent); }
.pad kbd { position: absolute; top: 8px; right: 8px; font: inherit; color: var(--faint); }
.pad.hit kbd { color: var(--bg); }
@media (hover: none) { .pad kbd { display: none; } }

.panel { display: grid; gap: 8px; }
.panel .src { min-height: 36px; }
.panel .src p:first-child { overflow-wrap: anywhere; }
.panel .src p + p { color: var(--muted); }
.panel .acts { display: flex; flex-wrap: wrap; gap: 8px; }
.panel .acts .ctl { flex: 1 1 0; min-width: 0; padding: 0 4px; }
#p-mic { touch-action: none; font-variant-numeric: tabular-nums; }

details { border-top: 1px solid var(--line); padding-top: 8px; }
summary { cursor: pointer; color: var(--muted); padding: 4px 0; }
summary b { font-weight: 400; color: var(--text); }
.list { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 12px; }
.list li { display: flex; align-items: baseline; gap: 12px; }
.list .what { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.list .what small { display: block; font-size: inherit; color: var(--muted); }
.list .what .flag { color: var(--accent); }
#credits .at { color: var(--muted); }
.list a { color: inherit; }
.none { color: var(--muted); margin-top: 8px; }

.read { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 16px; color: var(--faint); }
.read b { font-weight: 400; color: var(--muted); }
/* Credit line, as on the home page and the other apps: under the content, at the foot of the
   screen when the page is short, lined up with the page's left edge */
body { display: flex; flex-direction: column; min-height: 100vh; min-height: 100svh; }
main { flex: 0 0 auto; }
.credit { width: 100%; max-width: 640px; margin: auto auto 0; padding: 48px 16px 24px; color: var(--faint); font-size: 12px; }
.credit a { color: inherit; text-decoration: underline; text-decoration-color: transparent; text-underline-offset: 2px; transition: color 160ms ease-out, text-decoration-color 160ms ease-out; }
@media (hover: hover) { .credit a:hover { color: var(--muted); text-decoration-color: currentColor; } }
@media (min-width: 960px) { .credit { max-width: 1200px; } }

.toast { position: fixed; left: 16px; right: 16px; bottom: calc(16px + env(safe-area-inset-bottom)); max-width: 400px; margin: 0 auto; padding: 12px 16px; background: var(--raise-2); border: 1px solid var(--line-strong); border-radius: var(--radius); opacity: 0; transform: translateY(8px); transition: opacity 160ms ease-out, transform 240ms var(--spring); pointer-events: none; }
.toast.on { opacity: 1; transform: none; }


.padbar { display: flex; align-items: center; gap: 8px; }
.oct { display: flex; align-items: center; gap: 8px; }
.oct[hidden] { display: none; }
.oct span { min-width: 24px; text-align: center; }
.pads.keys .pad { align-items: center; justify-content: center; }
.pads.keys .pad.root { color: var(--accent); }
.pads.keys .pad.root.hit { color: var(--bg); }
.pads.picking .pad { border-color: var(--accent); }
.wave { display: block; width: 100%; height: 72px; background: var(--raise); border: 1px solid var(--line); border-radius: var(--radius); touch-action: pan-y; cursor: ew-resize; }
.wave.none { cursor: default; }
.drawer { margin: 8px 0; }
.wide { width: 100%; margin-bottom: 8px; }
.choke { display: flex; align-items: center; gap: 12px; color: var(--muted); }
#chokes { flex: 1; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 4px; }
#chokes .ctl { padding: 0; color: var(--text); }
#chokes .ctl[aria-pressed="true"] { color: var(--bg); }
.cell { font-size: 12px; line-height: 1; color: var(--bg); }

@media (min-width: 600px) { .cells { grid-template-columns: repeat(16, minmax(0, 1fr)); } }

/* Wide: pads on the left, every pad's row on the right */
@media (min-width: 960px) {
    main { max-width: 1200px; grid-template-columns: minmax(0, 420px) minmax(0, 1fr); gap: 16px 32px; align-items: start; }
    .header-section, .top, .sliders { grid-column: 1 / -1; }
    .sliders { grid-template-columns: repeat(2, minmax(0, 320px)); }
    /* One fixed column each: a long pad name is cut short, it never widens the steps */
    .left, .right { display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr); min-width: 0; }
    .row, .row.sel { display: grid; grid-template-columns: 120px minmax(0, 1fr); align-items: center; gap: 8px; }
    .rows { display: grid; gap: 3px; }
    .row-name { display: block; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: none; border: 0; padding: 0; text-align: left; }
    .row.sel .row-name { color: var(--text); }
    .cell { height: 24px; }
}
@media (max-width: 959px) { .left, .right { display: contents; } .gridbox { order: 1; } .padbar { order: 2; } .pads { order: 3; } .panel { order: 4; } .below { order: 5; display: grid; gap: 8px; } .read { order: 6; } }

.patbar { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.patbar .grow { flex: 1; }
#pats { display: flex; gap: 4px; }
#pats .ctl { width: 40px; padding: 0; color: var(--faint); }
#pats .ctl.has { color: var(--text); }
#pats .ctl[aria-pressed="true"] { color: var(--bg); }
#pats .ctl.queued { border: 1px dashed var(--accent); color: var(--accent); }
.padbar #accent { margin-left: auto; }
.cell.acc { background: var(--text); border-color: var(--text); }
.two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-bottom: 8px; }

@media (prefers-reduced-motion: reduce) { .pad, .toast { transition: none; } .pad.busy { animation: none; background: var(--accent-dim); } }

.none a { color: inherit; }

[hidden] { display: none !important; }

/* Dig's switch. It sits outside the drawer, so it shows and works open or shut */
.sw-wrap { position: relative; }
.sw-wrap summary { padding-right: 46px; }
.switch { position: absolute; top: 8px; right: 0; height: 26px; display: inline-flex; align-items: center; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.toggle { position: relative; flex: none; width: 30px; height: 18px; border-radius: 9px; border: 1px solid var(--line-strong); transition: background-color 200ms ease-out, border-color 200ms ease-out; }
.toggle::after { content: ""; position: absolute; top: 3px; left: 3px; width: 10px; height: 10px; border-radius: 50%; background: var(--muted); transition: transform 280ms var(--spring), background-color 200ms ease-out; }
.switch input:checked + .toggle { background: var(--accent-dim); border-color: var(--accent); }
.switch input:checked + .toggle::after { transform: translateX(12px); background: var(--accent); }
.switch input:focus-visible + .toggle { outline: 2px solid var(--text); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .toggle, .toggle::after { transition: none; } }

.padbar #shuffle { margin-left: auto; }
#tap { touch-action: none; }
.ui-tiles #tap { display: none; }

input[type=range]:disabled { opacity: 0.35; }

/* Tiles (?ui=tiles): one grid of values instead of sliders and drawers */
.ui-tiles main > .sliders, .ui-tiles .panel > .sliders, .ui-tiles .panel > details, .ui-tiles .sw-wrap, .ui-tiles .choke { display: none; }
.tile { position: relative; height: 48px; padding: 5px 8px; display: flex; flex-direction: column; justify-content: space-between; overflow: hidden; background: var(--raise); border: 1px solid var(--line); border-radius: var(--radius); color: var(--muted); line-height: 1.2; text-align: left; font: inherit; user-select: none; -webkit-user-select: none; touch-action: none; cursor: ns-resize; transition: border-color 160ms ease-out, background-color 160ms ease-out; }
.tile span { line-height: 1.2; }
.tile b { display: block; font-size: 16px; font-weight: 400; color: var(--text); line-height: 1; white-space: nowrap; }
.tile::after { content: ""; position: absolute; left: 0; bottom: 0; height: 3px; width: calc(var(--v, 0) * 100%); background: var(--accent); border-radius: 0 2px 0 0; }
.tile.c::after { left: 50%; width: calc(var(--v, 0) * 50%); }
.tile.c.neg::after { left: auto; right: 50%; border-radius: 2px 0 0 0; }
.tile.drag { border-color: var(--accent); }
.tile.off { opacity: 0.4; }
.tile.flag, .tile.more { cursor: pointer; touch-action: manipulation; }
.tile.flag::after, .tile.more::after, .tile.blank::after { display: none; }
.tile.flag.set { color: var(--text); border-color: var(--line-strong); background: var(--raise-2); }
.tile.flag.set b { color: var(--accent); }
.tile.more { color: var(--text); background: var(--raise-2); border-color: var(--line-strong); }
.tile.more b { color: var(--muted); font-size: 13px; line-height: 16px; }
.tile.blank { background: none; border-color: transparent; cursor: default; }
.tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4px; }
.tilebox[data-page="sound"] .p-more, .tilebox[data-page="more"] .p-sound { display: none; }
.ui-tiles .top { gap: 4px; }
.ui-tiles .top .tile { flex: 1 1 0; min-width: 0; max-width: 208px; }
.ui-tiles .top .ctl { height: 48px; flex: none; }
.ui-tiles .top { justify-content: flex-start; }
.ui-tiles .top #auto { margin-left: auto; }
.ui-tiles .wave { height: 56px; }
@media (prefers-reduced-motion: reduce) { .tile { transition: none; } }

/* The loum app header, as on Dig and the rest */
.header-section { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; padding-bottom: 20px; margin-bottom: 12px; border-bottom: 1px solid var(--line); }
.crumbs { display: flex; align-items: baseline; gap: 10px; }
.home-link { color: var(--muted); text-decoration: none; transition: color 160ms ease-out; }
.home-link:hover { color: var(--text); }
.crumb-sep { color: var(--faint); }
.header-section h1 { margin: 0; font-size: 13px; font-weight: 500; }
.header-note { margin: 0; color: var(--faint); }

@media (max-width: 360px) {
    .header-note { display: none; }
    .patbar, .padbar { flex-wrap: wrap; }
    .grid-head { flex-wrap: wrap; }
}

.header-right { display: flex; align-items: baseline; gap: 16px; }
.plain:disabled { color: var(--faint); text-decoration: none; cursor: default; }
@media (max-width: 640px) { .header-note { display: none; } }

.prj { display: flex; gap: 8px; margin-top: 8px; }
.prj input { flex: 1; min-width: 0; height: 40px; padding: 0 12px; background: var(--raise); border: 1px solid var(--line); border-radius: var(--radius); color: var(--text); font: 16px var(--font); }
.prj input::placeholder { color: var(--faint); }
.list .rename { width: 100%; height: 40px; padding: 0 12px; background: var(--raise); border: 1px solid var(--line-strong); border-radius: var(--radius); color: var(--text); font: 16px var(--font); }

/* An instrument, not a page: holding and dragging never selects text or pops up the iOS menu.
   Only the name field still takes a selection. */
body { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
button, .tile, .cell, .wave { touch-action: manipulation; }
.pad, .tile:not(.flag):not(.more) { touch-action: none; }
.wave { touch-action: pan-y; }
input[type=text] { -webkit-user-select: text; user-select: text; -webkit-touch-callout: default; }

/* The pad's crate: one line, scrolling sideways when it doesn't fit, fading at the edge */
.crate-line { display: flex; align-items: center; gap: 12px; color: var(--muted); min-width: 0; }
.crate-line > span { flex: none; }
.crate-row { flex: 1; min-width: 0; display: flex; gap: 4px; overflow-x: auto; scrollbar-width: none; touch-action: pan-x; -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); }
.crate-row::-webkit-scrollbar { display: none; }
.chip { flex: none; height: 32px; padding: 0 12px; background: var(--raise); border: 1px solid var(--line); border-radius: var(--radius); color: var(--muted); }
.chip[aria-pressed="true"] { background: var(--accent-dim); border-color: var(--accent); color: var(--text); }
.chip:disabled { opacity: 0.4; cursor: default; }

/* Patterns: as many as you add, scrolling sideways past what fits, the + at the end */
.patpick { display: flex; gap: 4px; min-width: 0; flex: 0 1 auto; }
#pats { display: flex; gap: 4px; min-width: 0; overflow-x: auto; scrollbar-width: none; touch-action: pan-x; }
#pats::-webkit-scrollbar { display: none; }
#pats .ctl { flex: none; }
#pat-add { width: 40px; padding: 0; flex: none; }
@media (max-width: 599px) {
    .patbar { flex-wrap: wrap; row-gap: 8px; }
    .patpick { flex: 1 0 100%; }
}

/* Lengths: a row shows its own bars. Stacked, each bar's first line sits a beat below the last;
   two a line, bars 2 and 4 start after a gap, and a four-bar row is two lines */
.cell.out { display: none; }
.rows.stack .cell.later.half { margin-top: 8px; }
@media (min-width: 600px) { .rows.stack .cell.later { margin-top: 8px; } }
.rows.both .cells { grid-template-columns: repeat(16, minmax(0, 1fr)) 4px repeat(16, minmax(0, 1fr)); }
.rows.both .cell.mid { grid-column-start: 18; }
@media (min-width: 960px) {
    .row.long { align-items: start; }
    .row.long .row-name { line-height: 24px; }
    .rows.both .row.long { margin-top: 4px; margin-bottom: 4px; }
}
.lens { display: flex; align-items: center; gap: 16px; flex: none; }

/* Focus rings are for the keyboard: after a tap or a click there's none, on tiles or anywhere */
html.by-pointer :focus, html.by-pointer :focus-visible { outline: none; }
html.by-pointer .switch input:focus-visible + .toggle { outline: none; }

.tile.idle { opacity: 0.4; }
.tile.idle.drag { opacity: 1; }

/* Phones: the transport stays at the top of the screen, so play and stop are always in reach.
   It runs edge to edge over the page, with a line under it once the page scrolls beneath. */
@media (max-width: 959px) {
    .top { position: sticky; top: 0; z-index: 5; margin: -8px -16px 0; padding: 8px 16px; background: var(--bg); border-bottom: 1px solid transparent; transition: border-color 160ms ease-out; }
    .top.stuck { border-bottom-color: var(--line); }
}

.prj-files { display: flex; gap: 16px; margin-top: 12px; }

/* ---------- The UI audit ---------- */

/* 1 · The pad panel's three tiers on one grid: one 4px gutter, the actions as 56px keys so their
   middle gap meets the tiles', "dig from" on tile column 1, the chips as Dig's crates */
#p-title { font-weight: 500; }
.panel .acts { gap: 4px; }
/* The pad's settings, then a beat, then what you do to it and where its sounds come from */
.ui-tiles #tiles + .acts { margin-top: 8px; }
.ui-tiles #tiles { margin-top: 8px; }   /* the same beat after the waveform as before the actions */
.ui-tiles .acts + .crate-line { margin-top: -4px; }
.crate-line { gap: 4px; }
.crate-line > span { flex: 0 0 calc((100% - 12px) / 4); }
.chip { height: 40px; background: none; }
.chip[aria-pressed="true"] { background: var(--raise-2); border-color: var(--text); color: var(--text); }
.chip:disabled { opacity: 1; color: var(--faint); }

/* 3 · Orange means sound. Solid: running or sounding (play, rec, steps, hits, value bars).
   Orange ink on a raised key: a setting that's on. A light border: the thing you're looking at. */
#keys[aria-pressed="true"], #auto[aria-pressed="true"], #accent[aria-pressed="true"], #p-lock[aria-pressed="true"],
#p-copy[aria-pressed="true"], #p-rev[aria-pressed="true"], #p-loop[aria-pressed="true"], #p-gate[aria-pressed="true"] {
    background: var(--raise-2); border-color: var(--line-strong); color: var(--accent);
}
#pats .ctl[aria-pressed="true"], #chokes .ctl[aria-pressed="true"] { background: var(--raise-2); border-color: var(--text); color: var(--text); }
#pats .ctl.queued[aria-pressed="false"] { color: var(--accent); }

/* 4 · Spacing in three steps, and the step cells on the grid */
.cells { gap: 4px; }
.cell { border-radius: 4px; }
@media (max-width: 959px) {
    .gridbox { margin-bottom: 8px; }
    .padbar { margin-bottom: -8px; }
    .panel { margin-bottom: 8px; }
}

/* 5 · Family details from Dig: quiet underlines, a chevron that turns with weight, hover and press */
.plain { text-decoration-color: var(--line-strong); }
summary { list-style: none; display: flex; align-items: baseline; gap: 4px; }
summary::-webkit-details-marker { display: none; }
summary::before { content: ""; flex: none; align-self: center; width: 6px; height: 6px; margin: 0 8px 0 2px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); transition: transform 280ms var(--spring); }
details[open] > summary::before { transform: rotate(45deg); }
@media (hover: hover) {
    .ctl:hover:not(:disabled), .chip:hover:not(:disabled) { border-color: var(--line-strong); }
    .plain:hover:not(:disabled) { color: var(--text); }
}
.ctl:active:not(:disabled) { transform: translateY(1px); }

/* 6 · Empty and busy: silence is a flat line; on touch screens the pads carry their numbers */
.wave.none { background: linear-gradient(var(--line-strong), var(--line-strong)) center / 100% 1px no-repeat, var(--raise); }
@media (hover: none) {
    .pad kbd { display: block; font-size: 0; }
    .pad kbd::after { content: attr(data-n); font-size: 13px; }
}
.tile.more b { font-size: 16px; line-height: 1; color: var(--text); }

/* Disabled is one system: colour, not opacity, so a greyed tile keeps its edge */
.tile.idle, .tile.off { opacity: 1; color: var(--faint); }
.tile.idle b, .tile.off b { color: var(--faint); }
.tile.idle::after, .tile.off::after { background: var(--line-strong); }
.tile.idle.drag { color: var(--muted); }
.tile.idle.drag b { color: var(--text); }

/* 2 · The pinned bar on phones: at the top it's the full row; once it sticks it closes to 48px,
   undo · redo on the left, tempo and swing fade out, click, rec and play become 32px keys */
.stuck-undo { display: none; }
@media (max-width: 959px) {
    .ui-tiles .top { height: 64px; transition: height 200ms cubic-bezier(0.22, 1, 0.36, 1), border-color 160ms ease-out; }
    .ui-tiles .top .tile { flex: 0 0 calc(25% - 3px); transition: opacity 120ms ease-out, flex-basis 200ms cubic-bezier(0.22, 1, 0.36, 1); }
    .ui-tiles .top .ctl { min-width: 56px; padding: 0 8px; transition: height 200ms cubic-bezier(0.22, 1, 0.36, 1); }
    .stuck-undo { display: flex; gap: 16px; align-items: center; max-width: 0; opacity: 0; overflow: hidden; transition: opacity 120ms ease-out; }
    .ui-tiles .top.stuck { height: 48px; }
    .ui-tiles .top.stuck .tile { opacity: 0; flex-basis: 0; min-width: 0; padding: 0; border-width: 0; margin-right: -4px; pointer-events: none; }
    .ui-tiles .top.stuck .ctl { height: 32px; }
    .ui-tiles .top.stuck .stuck-undo { max-width: 160px; opacity: 1; transition-delay: 80ms; }
}
@media (prefers-reduced-motion: reduce) {
    .ui-tiles .top, .ui-tiles .top .tile, .ui-tiles .top .ctl, .stuck-undo, summary::before { transition: none; }
}

/* The header as on the other apps: the same smoothing (so weights match), the same width on a
   desktop (so it doesn't jump when you go between apps) and the same spacing around its line */
body { -webkit-font-smoothing: antialiased; }
@media (min-width: 960px) {
    main { max-width: 1120px; padding: 20px 32px 16px; }
    .credit { max-width: 1120px; padding-left: 32px; padding-right: 32px; }
}
@media (max-width: 700px) {
    .header-section { padding-bottom: 16px; margin-bottom: 4px; }
}

html { scrollbar-gutter: stable; }

/* The header's line as on the other apps (1.5), so it's the same height to the pixel */
.header-section { line-height: 1.5; }

/* A setting tile greyed by its switch stays grey, even when it holds a value like hall */
.tile.flag.idle, .tile.flag.set.idle { color: var(--faint); background: var(--raise); border-color: var(--line); }
.tile.flag.set.idle b { color: var(--faint); }

/* The pattern row: the patterns as keys with + and − after them, then one line of links, the
   pattern's own on the left (bars, repeats, copy, export), the playing switches on the right */
.patbar { display: block; margin-bottom: 16px; }
.patrow { display: flex; align-items: center; gap: 8px; min-width: 0; }
.patrow .patpick { flex: 0 1 auto; min-width: 0; }
.patpm { display: flex; gap: 4px; flex: none; }
/* Add and delete (and the keyboard's octave up and down) wear a quieter key: no fill, a faint
   edge, a grey sign */
.patpm .ctl, .oct .ctl { width: 40px; padding: 0; background: transparent; border-color: var(--line); color: var(--muted); }
@media (hover: hover) { .patpm .ctl:hover:not(:disabled), .oct .ctl:hover:not(:disabled) { color: var(--text); border-color: var(--line-strong); } }
.patpm .ctl:disabled, .oct .ctl:disabled { color: var(--faint); opacity: 0.5; }
.patlinks { display: flex; justify-content: space-between; align-items: center; gap: 16px; min-height: 24px; margin-top: 8px; }
.patgroup { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
@media (max-width: 599px) { .patbar .patpick { flex: 0 1 auto; } }

/* An open drawer gets room under its contents before the next line */
details[open] { padding-bottom: 16px; }

/* Every row of links is spaced the same, 16px; a label sits 8px from the links it introduces */
.grid-head { gap: 16px; }
.head-main { flex: 1; min-width: 0; display: flex; align-items: center; gap: 16px; }
.head-main .name { margin-right: auto; }
.clears { display: flex; align-items: center; gap: 16px; flex: none; }
.clear-word { color: var(--muted); margin-right: -8px; }
.rowlabel { display: none; }

/* Desktop: the three rows over the steps hang on the grid's name column, so the pattern keys,
   the pattern's links and the steps heading all start on the step cells' left edge */
@media (min-width: 960px) {
    .patrow, .patlinks, .grid-head { display: grid; column-gap: 8px; align-items: center; }
    .patrow { grid-template-columns: 120px minmax(0, max-content) auto; justify-content: start; }
    .patlinks { grid-template-columns: 120px minmax(0, 1fr) auto; }
    .grid-head { grid-template-columns: 120px minmax(0, 1fr); }
    .rowlabel { display: block; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .steps-word { display: none; }
}

/* "dig from" at its own width, 8px from its crates, like the other labels */
.crate-line > span { flex: none; margin-right: 4px; }

/* Armed: every pad shows it will record; the one being held pulses, with its time */
.pads.armed .pad { border-color: var(--accent); }
.pads.armed .pad.empty { color: var(--muted); }
.pad.recording { background: var(--accent-dim); border-color: var(--accent); color: var(--text); animation: rec-ring 900ms ease-in-out infinite alternate; font-variant-numeric: tabular-nums; }
@keyframes rec-ring { from { box-shadow: 0 0 0 0 rgba(255, 122, 61, 0.55); } to { box-shadow: 0 0 0 4px rgba(255, 122, 61, 0); } }
@media (prefers-reduced-motion: reduce) { .pad.recording { animation: none; box-shadow: 0 0 0 2px var(--accent); } }

/* The armed bar: fixed to the foot of a phone screen, on the page's gutter, above the home bar */
.armbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; color: var(--muted); }
.armbar[hidden] { display: none; }
.armbar .ctl { flex: none; min-width: 56px; }

/* Fades only where there's more to scroll to */
.crate-row { -webkit-mask-image: none; mask-image: none; }
.crate-row.more-right { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); }
.crate-row.more-left { -webkit-mask-image: linear-gradient(to right, transparent, #000 24px); mask-image: linear-gradient(to right, transparent, #000 24px); }
.crate-row.more-left.more-right { -webkit-mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent); mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent); }

/* Desktop: the global row is the phone row, one module throughout. Tempo and swing a pad column
   each, click, rec and play sharing the other two, the row over the pads it belongs to, and the
   right column rising so the patterns sit level with the transport */
@media (min-width: 960px) {
    .ui-tiles .top { grid-column: 1; grid-row: 2; justify-content: flex-start; gap: 4px; }
    .ui-tiles .left { grid-column: 1; grid-row: 3; }
    .ui-tiles .right { grid-column: 2; grid-row: 2 / span 2; }
    /* When the right column grows (a drawer opened), the growth goes below the pads, never above */
    .ui-tiles main { grid-template-rows: auto auto 1fr; }
    .ui-tiles .top .tile { flex: 0 0 calc(25% - 3px); max-width: none; }
    .ui-tiles .top .ctl { flex: 1 1 0; min-width: 0; padding: 0; }
    .ui-tiles .top #auto { margin-left: 0; }
}
.patrow #pat-copy { flex: none; }

@media (min-width: 960px) { .patrow { grid-template-columns: 120px minmax(0, max-content) auto auto; } .patrow #pat-copy { margin-left: 8px; } }

/* The three keys share what the tiles leave, so the row always fits its column */
.ui-tiles .top .ctl { flex: 1 1 0; min-width: 0; padding: 0; }
.ui-tiles .top.stuck .ctl { flex: 0 0 56px; }

/* A nudged step: the orange block slides towards the side it's nudged to, a quarter at a time */
.cell { touch-action: pan-y; }
.cell.on.nudged { background-color: var(--raise); border-color: var(--accent); background-image: linear-gradient(var(--accent), var(--accent)); background-repeat: no-repeat; background-size: 50% 100%; background-position: calc((var(--o) + 0.5) * 100%) 0; }

/* The grid is drawn on with a drag, so a drag over it never scrolls the page */
.cell { touch-action: none; }
/* On touch screens there's no drawing: a swipe that starts on a blank step scrolls the page. Steps that are on keep the drag for nudge and pitch. */
@media (pointer: coarse) { .cell:not(.on) { touch-action: pan-y; } }

/* Your press squashes the pad from the top, its foot planted; a sequencer hit only tints it */
.pad { transform-origin: bottom; }
.pad.hit { transform: scaleY(0.96); }
.pad.seq:not(.hit) { background: var(--accent-dim); border-color: var(--accent); color: var(--text); transition: none; }
@media (prefers-reduced-motion: reduce) { .pad.hit { transform: none; } }

/* Desktop: the selected pad's row carries a faint band across its name and steps, so the name
   above the grid isn't needed (phones keep it: there's no name column there) */
@media (min-width: 960px) {
    .head-main .name { visibility: hidden; }
    .row.sel { background: rgba(233, 231, 226, 0.045); border-radius: 6px; margin: 0 -6px; padding: 0 6px; box-shadow: 0 0 0 3px rgba(233, 231, 226, 0.045); }
}

/* A drawer, open or shut, has the same room under its label as over it; open ones get more under their contents */
.below { gap: 0 !important; }
details { padding-bottom: 8px; }
details[open] { padding-bottom: 16px; }

/* Desktop: the selected row in the grid's own language. Its name becomes a key, like the selected
   pattern, and its empty cells take one step more edge. No band, nothing competing with orange */
@media (min-width: 960px) {
    .row.sel { background: none; box-shadow: none; margin: 0; padding: 0; border-radius: 0; }
    .row-name { box-sizing: border-box; height: 24px; padding: 0 8px; margin-left: -8px; line-height: 22px; border: 1px solid transparent; border-radius: 4px; transition: border-color 160ms ease-out, background-color 160ms ease-out; }
    .row.sel .row-name { background: var(--raise-2); border-color: var(--text); }
    .row.sel .cell { border-color: var(--line-strong); }
    .row.sel .cell.beat { border-color: var(--faint); }
    .row.sel .cell.on { border-color: var(--accent); }
    .rows { gap: 4px; }
}

/* Softer: the selected name is a quiet key, no white edge; a press squashes only a little */
@media (min-width: 960px) { .row.sel .row-name { border-color: var(--line-strong); } }
.pad.hit { transform: scaleY(0.98); }
@media (prefers-reduced-motion: reduce) { .pad.hit { transform: none; } }

/* The waveform sits the same distance under the title whether the pad is empty or not:
   the title and its line are one line each, cut short with an ellipsis */
.wave.none { margin-top: 0; }
.panel .src { min-height: 0; }
.panel .src p { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* The panel is one column the width of its box: a long title is cut short, it never widens the settings */
.panel { grid-template-columns: minmax(0, 1fr); }
.panel > * { min-width: 0; }

/* A nudged step slides along the row by its offset (half a step is half a cell's pitch), leaving
   its empty slot behind, so where it belongs and when it plays show at once */
.cell { position: relative; transition: transform 120ms var(--spring); }
.cell.on.nudged { z-index: 1; background: transparent; background-image: none; border-color: transparent; transform: translateX(calc(var(--o) * (100% + 4px))); }
.cell.on.nudged::before, .cell.on.nudged::after { content: ""; position: absolute; inset: -1px; border-radius: 4px; z-index: -1; }
.cell.on.nudged::before { background: var(--raise); box-shadow: inset 0 0 0 1px var(--line); transform: translateX(calc(var(--o) * -1 * (100% + 2px))); }
.cell.beat.on.nudged::before { background: var(--raise-2); box-shadow: inset 0 0 0 1px var(--line-strong); }
.cell.on.nudged::after { background: var(--accent); }
.cell.acc.nudged::after { background: var(--text); }
.cell.now.nudged { box-shadow: none; }
.cell.now.nudged::after { box-shadow: inset 0 0 0 2px var(--text); }
@media (prefers-reduced-motion: reduce) { .cell { transition: none; } }

/* Every lit step is built to slide: its orange face and its empty slot are always there, at zero
   offset on the grid, so a nudge or a quantise animates smoothly and nothing switches mid-move */
.cell.on { z-index: 1; background: transparent; background-image: none; border-color: transparent; transform: translateX(calc(var(--o, 0) * (100% + 4px))); }
.cell.on::before, .cell.on::after { content: ""; position: absolute; inset: -1px; border-radius: 4px; z-index: -1; }
.cell.on::before { background: var(--raise); box-shadow: inset 0 0 0 1px var(--line); transform: translateX(calc(var(--o, 0) * -1 * (100% + 2px))); transition: transform 120ms var(--spring); }
.cell.beat.on::before { background: var(--raise-2); box-shadow: inset 0 0 0 1px var(--line-strong); }
.cell.on::after { background: var(--accent); }
.cell.acc::after { background: var(--text); }
.cell.on.now { box-shadow: none; }
.cell.on.now::after { box-shadow: inset 0 0 0 2px var(--text); }
@media (min-width: 960px) { .row.sel .cell.on { border-color: transparent; } }
@media (prefers-reduced-motion: reduce) { .cell.on::before { transition: none; } }

/* A gate note's length: a tail behind the steps it sounds through, capped where it ends, and
   fainter where it has come round from the end of the row. At rest only notes that ring on have
   one; shorter ones are plain steps. Blank steps under a tail still take a tap. */
.cells { position: relative; }
.tails { position: absolute; inset: 0; pointer-events: none; }
.tail { position: absolute; background: rgba(255, 122, 61, 0.26); }
.tail.cap { border-radius: 0 4px 4px 0; box-shadow: inset -2px 0 0 rgba(255, 122, 61, 0.75); }
.tail.ghost { opacity: 0.5; }
.row.quiet .tail { opacity: 0.4; }
/* Only while it's held: a note shorter than its step has a face that much narrower, its slot showing beside it */
.cell.on.short { color: var(--text); font-weight: 500; text-shadow: 0 0 2px var(--bg), 0 0 2px var(--bg); }
.cell.on.short::after { background: linear-gradient(90deg, var(--accent) 0 var(--g), transparent var(--g) 100%); }
.cell.acc.short::after { background: linear-gradient(90deg, var(--text) 0 var(--g), transparent var(--g) 100%); }
/* Held, its length in your hand: a light ring, and the step reads out its length */
.cell.on.stretching { z-index: 3; }
.cell.on.stretching::after { box-shadow: 0 0 0 2px var(--text); }
/* With a mouse, the end of a tail is a handle */
.grip { display: none; position: absolute; z-index: 2; }
@media (hover: hover) and (pointer: fine) {
    .grip { display: block; pointer-events: auto; cursor: ew-resize; touch-action: none; }
    .grip::after { content: ""; position: absolute; top: 4px; bottom: 4px; right: 0; width: 3px; border-radius: 0 2px 2px 0; background: var(--text); opacity: 0; transition: opacity 160ms cubic-bezier(0.22, 1, 0.36, 1); }
    .grip:hover::after { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .grip::after { transition: none; } }

/* Keys mode: pads or keyboard */
.kbview { display: flex; gap: 12px; align-items: center; }
.kbview[hidden] { display: none; }
.kbview .plain[aria-pressed="true"] { color: var(--text); text-decoration-color: var(--text); }

/* The keyboard, as on Dig: two octaves, white keys in a row, black keys over their gaps */
.keyboard { position: relative; display: grid; grid-template-columns: repeat(15, minmax(0, 1fr)); gap: 3px; height: 148px; padding: 8px; border: 1px solid var(--line); border-radius: 8px; background: #141413; touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.keyboard[hidden] { display: none; }
.key { position: relative; display: flex; align-items: flex-end; justify-content: center; margin: 0; padding: 0 0 8px; border: 1px solid #e9e7e2; border-radius: 0 0 5px 5px; background: #e9e7e2; box-shadow: 0 2px 0 #0d0d0c; color: #a3a099; font: 500 11px/1 var(--font); text-transform: uppercase; cursor: pointer; touch-action: none; transition: transform 90ms ease-out, box-shadow 90ms ease-out, background-color 120ms ease-out, border-color 120ms ease-out, color 120ms ease-out; }
.key.black { position: absolute; top: 8px; height: 58%; width: calc((100% - 16px - 14 * 3px) / 15 * 0.62); padding-bottom: 6px; background: #222220; border-color: #3a3a37; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 2px 0 #0d0d0c; color: #6b6963; z-index: 1; left: calc(8px + var(--i) * ((100% - 16px - 14 * 3px) / 15 + 3px) - (100% - 16px - 14 * 3px) / 15 * 0.31); }
.key.hit { transform: translateY(2px); box-shadow: 0 0 0 #0d0d0c; background: var(--accent); border-color: var(--accent); color: #121211; }
.key.black.hit { box-shadow: 0 -3px 0 #222220; }
.key.seq:not(.hit) { background: #f6d8c8; border-color: var(--accent); }
.key.black.seq:not(.hit) { background: #4a2e20; }
.key.is-root::after { content: ""; position: absolute; left: 50%; bottom: 22px; width: 4px; height: 4px; margin-left: -2px; border-radius: 50%; background: var(--accent); }
.key.hit.is-root::after { background: #121211; }
@media (hover: none) { .keyboard .key { color: transparent; } }
@media (prefers-reduced-motion: reduce) { .key { transition: none; } .key.hit { transform: none; } }

.padbar .oct { margin-left: auto; }

@media (max-width: 959px) { .keyboard { order: 3; } }

/* Mute and solo. Any pad the sequencer won't play greys back a little (still playable by hand);
   a muted one's name is also struck through, and a soloed pad keeps an orange name. In a mode
   every pad gets a faint orange edge (tap to set) and the set ones a full one; the selected pad
   gets a lighter face instead of a white edge, so white never reads as a state. On the rows:
   struck names for muted, orange for soloed, and a skipped row's steps go hollow. The keys hold their width so the count doesn't move the row. */
.padbar #mute, .padbar #solo, .padbar #lock, .padbar #pick { min-width: 80px; color: var(--muted); font-variant-numeric: tabular-nums; }
.padbar #mute.set, .padbar #solo.set, .padbar #lock.set { color: var(--text); }
.padbar #mute[aria-pressed="true"], .padbar #solo[aria-pressed="true"], .padbar #lock[aria-pressed="true"], .padbar #pick[aria-pressed="true"] { background: var(--raise-2); border-color: var(--accent); color: var(--accent); }
.padbar #mute[hidden], .padbar #solo[hidden], .padbar #lock[hidden], .padbar #pick[hidden], .padbar #mute-clear[hidden] { display: none; }
.pad { transition-property: transform, background-color, border-color, color, opacity; }
/* Only what's on the pad fades, so its edge still says what a tap does and what's set */
.pad.quiet:not(.hit) > *, .pad.quiet:not(.hit)::before { opacity: 0.45; }
.pad.muted:not(.hit) .t { color: var(--faint); text-decoration: line-through; text-decoration-thickness: 1px; text-decoration-color: var(--muted); }
.pad.soloed:not(.hit) .t { color: var(--accent); }
.pads.muting .pad:not(.hit) { border-color: var(--accent-soft); }
.pads.muting .pad.set:not(.hit) { border-color: var(--accent); }
/* In a mode the selected pad's face is a step lighter; its edge stays free to say what's set */
.pads.muting .pad.sel:not(.hit) { background: #2c2c2a; }
/* Sequenced hits still show in a mode, the selected pad's included, a step stronger since every edge is already orange */
.pads.muting .pad.seq:not(.hit) { background: rgba(255, 122, 61, 0.3); border-color: var(--accent); }
.padbar #mute-clear:not(:disabled) { border-color: var(--accent); }
.row.quiet .row-name { color: var(--faint); }
.row.quiet .cell.on:not(.now) { background: var(--raise); border-color: rgba(255, 122, 61, 0.5); }
.row.muted .row-name { text-decoration: line-through; text-decoration-thickness: 1px; text-decoration-color: var(--muted); }
.row.soloed .row-name { color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .pad { transition-property: background-color, border-color, color, opacity; } }

/* The pad bar: keys, mute, solo, lock and shuffle share the pads' width in five equal keys,
   with the pads' 4px gap */
.padbar { gap: 4px; }
.padbar #keys, .padbar #mute, .padbar #solo, .padbar #lock, .padbar #pick { flex: 1 1 0; min-width: 0; padding: 0; }
/* With keys on, the other four make way: keys keeps its fifth rather than taking their room */
.padbar #keys[aria-pressed="true"] { flex: 0 0 calc((100% - 20px) / 6); }
.padbar #shuffle, .padbar #mute-clear { flex: 1 1 0; min-width: 0; padding: 0; white-space: nowrap; }
.padbar .kbview { margin-left: 8px; }

/* Projects: each row is the project as a key; tap to open. The open one is a pressed key with
   "· open" after its name. Rename and delete wait at the right, shown on hover, on focus, or
   when the row is swiped left on a phone, where the row slides aside for them. */
#prj-list { gap: 4px; }
#prj-list .prow { position: relative; display: block; padding: 7px 8px; margin: 0 -8px; border: 1px solid transparent; border-radius: var(--radius); overflow: hidden; touch-action: pan-y; transition: background-color 160ms ease-out, border-color 160ms ease-out; }
#prj-list .go { display: block; width: 100%; min-width: 0; padding: 0; background: none; border: 0; text-align: left; color: inherit; font: inherit; cursor: pointer; }
#prj-list .nm { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#prj-list .mt { display: block; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#prj-list .prow.on { background: var(--raise-2); border-color: var(--line-strong); }
#prj-list .prow.on .go { cursor: default; }
#prj-list .prow.on .nm::after { content: " · open"; color: var(--faint); }
#prj-list .acts { position: absolute; top: 7px; right: 8px; display: flex; gap: 12px; align-items: baseline; padding-left: 8px; background: var(--bg); opacity: 0; transition: opacity 160ms ease-out; }
#prj-list .prow.on .acts { background: var(--raise-2); }
/* Hidden actions can't be pressed: a tap there opens the project. The open one always shows its own;
   the rest on a swipe, a hover, or the keyboard reaching them */
#prj-list .acts { pointer-events: none; }
#prj-list .prow:has(:focus-visible) .acts, #prj-list .prow.swiped .acts, #prj-list .prow.on .acts { opacity: 1; pointer-events: auto; }
#prj-list .prow.swiped .nm, #prj-list .prow:has(:focus-visible) .nm, #prj-list .prow.on .nm { padding-right: 136px; }
#prj-list .prow.renaming { padding: 0; border-color: transparent; background: none; }
@media (hover: hover) {
    #prj-list .prow:hover .acts { opacity: 1; pointer-events: auto; }
    #prj-list .prow:hover .nm { padding-right: 136px; }
    #prj-list .prow:not(.on):hover { border-color: var(--line); }
}
#prj-more { margin-top: 8px; }
#prj-more[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) {
    #prj-list .prow, #prj-list .go, #prj-list .acts { transition: none; }
}

/* The toast, inverted: the page's ink as paper, in the same spot, springing up. Nothing else on
   the screen is light, so it can't be missed. A problem leads with the orange square a locked pad
   wears. A tap puts it away. */
.toast { z-index: 7; background: var(--text); border-color: var(--text); color: var(--bg); transform: translateY(12px); transition: opacity 160ms ease-out, transform 160ms ease-out, bottom 200ms cubic-bezier(0.22, 1, 0.36, 1); pointer-events: none; cursor: pointer; }
.toast.on { pointer-events: auto; transform: none; transition: opacity 120ms ease-out, transform 320ms var(--spring), bottom 200ms cubic-bezier(0.22, 1, 0.36, 1); }
.toast.problem::before { content: ""; display: inline-block; width: 8px; height: 8px; margin-right: 10px; background: var(--accent); vertical-align: 1px; }
@media (prefers-reduced-motion: reduce) { .toast, .toast.on { transform: none; transition: opacity 120ms ease-out; } }

/* Copy: the bar says what each tap carries. On a phone it sits at the foot like the mic's armed
   bar, since the pads are up the page from the copy key; on a desktop it opens under the action
   row. The pad being copied is solid orange with an orange name; the pads it can go onto get a
   dashed edge, as queued patterns do, so it reads as a direction, not as mute or the mic. */
.copybar { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; animation: copybar-in 200ms cubic-bezier(0.22, 1, 0.36, 1); }
.copybar[hidden] { display: none; }
.copybar .lbl { margin-right: 4px; color: var(--muted); white-space: nowrap; }
.copybar .pick { padding: 0 8px; color: var(--muted); }
.copybar .pick[aria-pressed="true"] { background: var(--raise-2); border-color: var(--text); color: var(--text); }
.copybar #copy-done { margin-left: auto; }
@keyframes copybar-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.pads.copying .pad.from:not(.hit) { border-color: var(--accent); }
.pads.copying .pad.from:not(.hit) .t { color: var(--accent); }
.pads.copying .pad:not(.from):not(.hit) { border-style: dashed; border-bottom-style: solid; border-color: rgba(255, 122, 61, 0.5); }
@media (prefers-reduced-motion: reduce) { .copybar { animation: none; } }
/* On a phone the label steps aside until there's room for all of it beside done: the pad being
   copied is already marked orange */
@media (max-width: 519px) { .copybar .lbl { display: none; } }

/* ---------- The bars on a phone, as one system ----------
   The pinned transport at the top; copy and the mic at the foot. Each is 48px with its line, on
   the page's column and gutter, with 32px keys 4px apart (a 48px touch area each) and the key
   that ends it at the right edge. One line always, so the height never moves: what doesn't fit
   steps aside or fades. They slide in on the transport's curve and leave a little faster. The
   toast sits 16px above whichever is up, and follows it. The keys sit on whole pixels, 8px from
   the screen's edge and 7px from the bar's line, so their words are drawn the same in every browser. */
@media (max-width: 959px) {
    .ui-tiles .top.stuck { padding-top: 8px; padding-bottom: 7px; }
    .armbar, .copybar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 6; width: 100%; max-width: 640px; height: calc(48px + env(safe-area-inset-bottom)); margin: 0 auto; padding: 7px 16px calc(8px + env(safe-area-inset-bottom)); flex-wrap: nowrap; background: var(--bg); border-top: 1px solid var(--line); animation: none; transition: transform 200ms cubic-bezier(0.22, 1, 0.36, 1), visibility 0s; }
    .armbar[hidden], .copybar[hidden] { display: flex !important; visibility: hidden; transform: translateY(100%); transition: transform 160ms cubic-bezier(0.5, 0, 0.75, 0), visibility 0s 160ms; }
    .armbar .ctl, .copybar .ctl, .copybar #copy-done { position: relative; flex: none; height: 32px; }
    .armbar .ctl::after, .copybar .ctl::after, .copybar #copy-done::after { content: ""; position: absolute; inset: -9px -3px; }
    .copybar #copy-done { min-width: 56px; padding: 1px 8px 0; line-height: 18px; background: var(--raise); border: 1px solid var(--line); border-radius: var(--radius); color: var(--text); text-decoration: none; user-select: none; -webkit-user-select: none; }
    .copybar #copy-done:active { transform: translateY(1px); }
    .armbar > span, .copybar .lbl { flex: 0 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; padding-right: 16px; margin-right: -12px; -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 16px), transparent); mask-image: linear-gradient(90deg, #000 calc(100% - 16px), transparent); }
    body.armbar-on, body.copybar-on { padding-bottom: calc(48px + env(safe-area-inset-bottom)); }
    body.armbar-on .toast, body.copybar-on .toast { bottom: calc(64px + env(safe-area-inset-bottom)); }
}
@media (max-width: 959px) and (hover: hover) { .copybar #copy-done:hover { border-color: var(--line-strong); } }
/* Narrower phones: the copy keys close up a step at a time so the bar stays one line. Under 360px
   the words that can go, go: "pad + steps" is "+ steps", the mic's line starts at "hold" */
@media (max-width: 379px) { .copybar .pick { padding: 0 6px; } }
@media (max-width: 362px) { .copybar #copy-done { min-width: 0; } }
@media (max-width: 359px) {
    .if-room { display: none; }
    /* The header fits the screen there too, so nothing fixed is measured against a wider page */
    .header-section { gap: 8px; }
    .header-right { gap: 8px; }
    .crumbs { gap: 6px; }
    .copybar .pick { padding: 0 4px; }
}
@media (prefers-reduced-motion: reduce) { .armbar, .copybar, .armbar[hidden], .copybar[hidden] { transform: none; transition: none; } }
/* A pad with its own length reads brighter than one following the pattern */
#pad-bars.own { color: var(--text); }
/* Phones: the pad's name gets its own line, its length and the clears under it */
@media (max-width: 599px) {
    .head-main { flex-wrap: wrap; row-gap: 8px; }
    .head-main .name { flex: 1 0 100%; }
    .clears { margin-left: auto; }
}
.clears .slash { color: var(--faint); margin: 0 -8px; }

/* ---------- The first-load intro (g by default, static/js/intro.js). Safe to delete with that file. ---------- */
/* Held back until the script takes over; the lights are a layer over the page, never the controls themselves */
.intro-wait main > :not(.header-section), .intro-wait .credit { opacity: 0; }
.intro-layer { position: absolute; top: 0; left: 0; width: 0; height: 0; z-index: 6; pointer-events: none; }
.intro-lamp { position: absolute; opacity: 0; pointer-events: none; will-change: opacity; }
/* Orange: lit from under the pad, a little hotter low down where the label sits */
.intro-lamp.accent { background: radial-gradient(120% 120% at 50% 100%, rgba(255, 122, 61, 0.34), rgba(255, 122, 61, 0.12) 70%); box-shadow: inset 0 0 0 1px rgba(255, 122, 61, 0.8); }
/* White: the face of the control lifts a step, as if catching light */
.intro-lamp.lift { background: rgba(233, 231, 226, 0.1); box-shadow: inset 0 0 0 1px rgba(233, 231, 226, 0.42); }

/* Random cut sits at the end of the source line, above the waveform. The credit gives way to it */
.panel .src { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 16px; align-items: baseline; }
.panel .src #p-title { grid-column: 1 / -1; }
#p-cut { position: relative; }
#p-cut::before { content: ""; position: absolute; inset: -14px -8px; }

/* Export asks: the row's links give way to the question (this pattern or the chain, then mix or
   stems), and the question to the progress, a line filling under it */
.patlinks.asking .patgroup { display: none; }
.xask, .xprog { flex: 1; min-width: 0; display: flex; align-items: center; gap: 16px; animation: x-in 160ms ease-out both; }
.xask[hidden], .xprog[hidden] { display: none; }
.xwhat { display: flex; align-items: center; gap: 8px; }
.xor { color: var(--faint); }
#x-pat:disabled { color: var(--muted); }
.xcancel { margin-left: auto; }
.xprog { position: relative; height: 24px; color: var(--muted); white-space: nowrap; }
#xprog-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
#xprog-t b { font-weight: inherit; color: var(--text); }
.xtrack { position: absolute; left: 0; right: 0; bottom: 0; height: 2px; border-radius: 1px; background: var(--line); overflow: hidden; }
#xprog-fill { display: block; width: 0; height: 100%; background: var(--accent); transition: width 320ms cubic-bezier(0.22, 1, 0.36, 1); }
@media (min-width: 960px) { .xask, .xprog { grid-column: 2 / -1; } }
@keyframes x-in { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .xask, .xprog { animation: none; } #xprog-fill { transition: none; } }

/* Text links on touch screens: the same look, with an unseen touch area around each word so it's
   at least 40px tall; neighbours meet half way */
@media (pointer: coarse) {
    .plain { position: relative; }
    .plain::after { content: ""; position: absolute; inset: -11px -8px; }
}

/* A turned phone: the pads fit the screen's height, centred, rather than two rows at a time; the
   header note steps aside so the header stays one line */
@media (orientation: landscape) and (max-height: 500px) {
    .pads { width: 100%; max-width: max(240px, calc(100dvh - 72px)); margin-inline: auto; }
    .header-note { display: none; }
}

/* Save goes grey while the open project matches its saved copy (it still works). The header's
   link looks like undo with nothing to undo: faint, no underline */
#prj-save.kept { color: var(--muted); }
#save.kept { color: var(--faint); text-decoration: none; }

/* Rec counting in: lit, and blinking until the downbeat */
#rec.counting { animation: count-in 500ms steps(2, jump-none) infinite; }
@keyframes count-in { from { opacity: 1; } to { opacity: 0.4; } }
@media (prefers-reduced-motion: reduce) { #rec.counting { animation: none; } }

/* The cut's keyboard handles: unseen on the waveform's white lines until focused from the keys */
.cut-handle { position: absolute; width: 12px; margin-left: -6px; pointer-events: none; border-radius: 2px; }
.cut-handle:focus-visible { outline: 2px solid var(--text); outline-offset: 0; }
.cut-handle[hidden] { display: none; }

/* On a phone the footer line sits centred */
@media (max-width: 640px) { .credit { text-align: center; } }

/* Automation. The auto key: armed is a setting that's on (orange ink, rule 3); while a move is
   being written it's running, so it fills like rec */
#auto.writing { background: var(--accent); border-color: var(--accent); color: var(--bg); }
/* A tile's dot, top right. A ring: armed, this setting can be recorded. Orange: its automation is
   playing. Beating: being written now. Grey: there, but overridden (two taps bring it back). */
.tile .adot { position: absolute; top: 6px; right: 6px; width: 8px; height: 8px; border-radius: 50%; display: none; z-index: 1; }
.tile.a-can .adot { display: block; border: 1px solid var(--accent-soft); }
.tile.a-on .adot, .tile.a-writing .adot { display: block; background: var(--accent); }
.tile.a-off .adot { display: block; background: var(--faint); }
.tile.a-writing .adot { animation: auto-write 400ms steps(2, jump-none) infinite; }
@keyframes auto-write { from { opacity: 1; } to { opacity: 0.4; } }
.tile.a-off::after { background: var(--line-strong); }
/* The lane, faint behind the value, and where the pattern is along it */
.tile .alane { position: absolute; inset: 0; width: 100%; height: 100%; display: none; pointer-events: none; }
.tile.a-on .alane, .tile.a-off .alane, .tile.a-writing .alane { display: block; }
.tile .alane path { fill: none; stroke: var(--accent); stroke-width: 1; opacity: 0.4; vector-effect: non-scaling-stroke; }
.tile.a-off .alane path { stroke: var(--faint); }
.tile .alane line { stroke: var(--line-strong); stroke-width: 1; vector-effect: non-scaling-stroke; }
.tile.a-off .alane line { display: none; }
.tile > span, .tile > b { position: relative; }
@media (prefers-reduced-motion: reduce) { .tile.a-writing .adot { animation: none; } }
/* Under the tiles: the settings with automation on this pad, each cleared on its own */
.auto-line { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 16px; min-height: 24px; color: var(--muted); }
.auto-line[hidden] { display: none; }
.auto-line .plain.off { color: var(--faint); }
.ui-tiles #tiles + .auto-line + .acts { margin-top: 8px; }
.ui-tiles #tiles + .auto-line:not([hidden]) { margin-top: 8px; }
.ui-tiles #tiles + .auto-line:not([hidden]) + .acts { margin-top: 0; }


/* Moving a pad. On a wide screen a row is picked up by its name, after a hold: nothing says so at
   rest. While it's held the name presses in, so the lift doesn't come from nowhere; once lifted,
   three short lines sit at the end of the name, which fades out under them. The held row
   sits on a raised plate, the others make room with a little weight, and a dashed slot shows where
   it will rest (dashes already mean "this can go here" on the pads). The pad it came from goes
   orange and the one it will become wears the same dashes, so the grid says which key it lands on. */
.slot { display: none; }
.pad.move-from:not(.hit) { border-color: var(--accent); }
.pad.move-from:not(.hit) .t { color: var(--accent); }
.pad.move-to:not(.hit) { border-style: dashed; border-bottom-style: solid; border-color: rgba(255, 122, 61, 0.5); }
/* Swap: the sounds cross the grid; a key stays with its slot, so it sits the trip out */
.pad.swapping { z-index: 2; transition: transform 340ms var(--spring); }
.pad.swapping kbd { opacity: 0; }
.copybar .pick[hidden] { display: none; }
@media (min-width: 960px) {
    .rows { position: relative; }
    .row, .row.sel { position: relative; }
    .row-name { position: relative; user-select: none; -webkit-user-select: none; transform-origin: 20px 50%; transition: border-color 160ms ease-out, background-color 160ms ease-out, transform 220ms var(--spring); }
    /* The press waits 150 ms before it shows, so a plain click never shrinks, then takes the rest of the hold */
    .row.holding .row-name { transform: scale(0.95); transition: border-color 160ms ease-out, background-color 160ms ease-out, transform 400ms cubic-bezier(0.3, 0, 0.5, 1) 150ms; }
    .row-name::after { content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 40px; opacity: 0; transition: opacity 160ms cubic-bezier(0.22, 1, 0.36, 1);
        background:
            linear-gradient(var(--muted), var(--muted)) right 8px top 7px / 8px 1px no-repeat,
            linear-gradient(var(--muted), var(--muted)) right 8px top 11px / 8px 1px no-repeat,
            linear-gradient(var(--muted), var(--muted)) right 8px top 15px / 8px 1px no-repeat,
            linear-gradient(90deg, transparent, var(--bg) 45%); }
    .row.sel .row-name::after { background:
            linear-gradient(var(--text), var(--text)) right 8px top 7px / 8px 1px no-repeat,
            linear-gradient(var(--text), var(--text)) right 8px top 11px / 8px 1px no-repeat,
            linear-gradient(var(--text), var(--text)) right 8px top 15px / 8px 1px no-repeat,
            linear-gradient(90deg, transparent, var(--raise-2) 45%); }
    .row.lift .row-name::after { opacity: 1; }
    .rows.lifting .row, .rows.settling .row { transition: transform 260ms cubic-bezier(0.3, 1.25, 0.5, 1); }
    .rows.lifting .row.lift, .rows.still .row { transition: none; }
    .rows.lifting, .rows.lifting * { cursor: grabbing !important; }
    .row.lift { z-index: 4; }
    .row.lift::before { content: ""; position: absolute; inset: -4px -8px; border-radius: 6px; background: var(--raise); border: 1px solid var(--line-strong); box-shadow: 0 12px 28px -8px rgba(0, 0, 0, 0.7), 0 2px 6px rgba(0, 0, 0, 0.4); z-index: -1; }
    .row.lift .row-name { color: var(--text); }
    .row.landing { transition: transform 300ms var(--spring) !important; }
    .row.landing::before { opacity: 0; transition: opacity 240ms ease-out 80ms; }
    .slot { position: absolute; left: -8px; right: -8px; top: 0; border: 1px dashed rgba(255, 122, 61, 0.5); border-radius: 6px; pointer-events: none; transition: transform 260ms cubic-bezier(0.3, 1.25, 0.5, 1); }
    .rows.lifting .slot { display: block; }
    .rows.still .slot { transition: none; }
}
@media (prefers-reduced-motion: reduce) {
    .pad.swapping, .slot, .row-name::after { transition: none; }
    .row-name, .row.holding .row-name { transform: none; transition: border-color 160ms ease-out, background-color 160ms ease-out; }
    .row.holding .row-name { border-color: var(--line-strong); }
    .rows.lifting .row, .rows.settling .row, .row.landing { transition: none !important; }
}

/* Moving a pattern: its key is picked up after a hold, like a row's name. It presses in while
   held, then lifts onto a shadow; the others make room, and the same dashed slot shows where it
   will rest. The strip gets room above and below (it clips, being a scroller) for the lift. */
#pats { position: relative; padding: 8px 4px; margin: -8px -4px; }
#pats .ctl { position: relative; transition: scale 220ms var(--spring); }
#pats .ctl.holding { scale: 0.92; transition: scale 400ms cubic-bezier(0.3, 0, 0.5, 1) 150ms; }
#pats.lifting .ctl, #pats.settling .ctl { transition: translate 260ms cubic-bezier(0.3, 1.25, 0.5, 1), scale 220ms var(--spring); }
#pats.lifting, #pats.lifting .ctl { cursor: grabbing; touch-action: none; }
#pats .ctl.lift { z-index: 2; scale: 1.08; background: var(--raise); color: var(--text); box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.7), 0 2px 4px rgba(0, 0, 0, 0.4); transition: scale 220ms var(--spring); }
#pats .ctl.lift:not([aria-pressed="true"]):not(.queued) { border-color: var(--line-strong); }
#pats .ctl.landing { z-index: 2; transition: translate 300ms var(--spring), scale 220ms var(--spring), box-shadow 240ms ease-out; }
#pats.lifting::after { content: ""; position: absolute; top: 8px; bottom: 8px; left: 0; width: var(--slot-w, 40px); transform: translateX(var(--slot-x, 0)); border: 1px dashed rgba(255, 122, 61, 0.5); border-radius: var(--radius); pointer-events: none; transition: transform 260ms cubic-bezier(0.3, 1.25, 0.5, 1); }
#pats.still .ctl, #pats.still::after { transition: none !important; }
@media (prefers-reduced-motion: reduce) {
    #pats .ctl, #pats .ctl.holding, #pats .ctl.lift, #pats .ctl.landing, #pats.lifting .ctl, #pats.settling .ctl, #pats.lifting::after { scale: none; transition: none; }
    #pats .ctl.holding { border-color: var(--line-strong); }
}

/* A key's word, centred by eye. Most labels are small letters, and centring the tall ones (as the
   browser nearly does) leaves the small letters' band most of a pixel low, which reads as low. A
   whole-pixel line box lifted half a pixel puts the capitals a touch high and the small letters
   on the middle, the same on every key, phone or desktop. */
.ctl, .chip { line-height: 18px; padding-top: 0 !important; padding-bottom: 1px !important; }
.copybar #copy-done { padding-top: 0; padding-bottom: 1px; }

/* Picking: the pad bar itself says what to do with the picked pads. clear takes keys' place and
   dig takes shuffle's (shuffle is a dig for every pad), so the six keys stay where they are.
   mute, solo and lock between them act on the picks too. The two wake once something's picked. */
.padbar #pick-clear, .padbar #pick-dig { flex: 1 1 0; min-width: 0; padding: 0; white-space: nowrap; }
.padbar #pick-clear[hidden], .padbar #pick-dig[hidden], .padbar #keys[hidden] { display: none; }
.padbar #pick-clear:not(:disabled), .padbar #pick-dig:not(:disabled) { border-color: var(--accent); }
.padbar #pick-dig { margin-left: auto; }
.padbar.picked #mute, .padbar.picked #solo, .padbar.picked #lock { color: var(--text); border-color: var(--accent-soft); }
/* On a phone six keys are too tight: lock waits until you select, then locks what's selected.
   Locked pads still wear their lock, and the count is there again on wider screens. */
@media (max-width: 479px) { .padbar:not(.selecting) #lock { display: none; } }
