/*
 * htp.css — styling for the module shell (tabs + panes) and the keyboard
 * drawer. Loaded AFTER css/styles.css so it can adjust the original trainer
 * layout without editing it.
 */

:root {
	--htp-keyboard-height: 160px;
	--htp-keyboard-bar: 34px;
	/* Acoustic piano proportions: a black key is ~95mm of a white key's ~148mm
	   length and ~13.7mm of its ~23.5mm width. The width ratio is narrowed on a
	   phone, where true proportions leave too little white key to hit. */
	--htp-black-key-height: 64%;
	--htp-black-key-ratio: 0.583;
	--htp-border: #d8d8d8;
	--htp-ink: #222;
	--htp-muted: #767676;
	--htp-accent: #2b6cb0;
	--htp-accent-soft: #d6e6fb;
}

/* ------------------------------------------------------------------ shell */

html { height: 100%; }

body {
	margin: 0;
	min-height: 100vh;
	display: flex;
	flex-direction: column;
	overflow-x: hidden;
	/* Reserve room for the collapsed keyboard bar. */
	padding-bottom: var(--htp-keyboard-bar);
	box-sizing: border-box;
}

body.htp-keyboard-open {
	padding-bottom: calc(var(--htp-keyboard-height) + var(--htp-keyboard-bar));
}

.htp-app {
	flex: 1 1 auto;
	display: flex;
	flex-direction: column;
	min-height: 0;
}

/* ------------------------------------------------------------------- tabs */

.htp-tabbar {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	gap: 0.5em;
	padding: 0 0.75em;
	border-bottom: 1px solid var(--htp-border);
	background: #fafafa;
	min-height: 40px;
}

/* A tab bar with a single module on it is just noise. */
body:not(.htp-multi-module) .htp-tabbar { display: none; }

.htp-tabs { display: flex; gap: 0.25em; flex: 1 1 auto; }

.htp-tab {
	appearance: none;
	border: 1px solid transparent;
	border-bottom: none;
	background: transparent;
	color: var(--htp-muted);
	font: inherit;
	font-size: 14px;
	padding: 0.5em 0.9em;
	border-radius: 4px 4px 0 0;
	cursor: pointer;
}

.htp-tab:hover { color: var(--htp-ink); background: #f0f0f0; }

.htp-tab.is-active {
	color: var(--htp-ink);
	background: #fff;
	border-color: var(--htp-border);
	box-shadow: inset 0 2px 0 var(--htp-accent);
}

.htp-iconbtn {
	appearance: none;
	border: 1px solid var(--htp-border);
	background: #fff;
	color: var(--htp-muted);
	font: inherit;
	font-size: 13px;
	padding: 0.35em 0.7em;
	border-radius: 4px;
	cursor: pointer;
}

.htp-iconbtn:hover { color: var(--htp-ink); }
.htp-iconbtn.is-active { color: #fff; background: var(--htp-accent); border-color: var(--htp-accent); }

/* ------------------------------------------------------------------ panes */

.htp-panes {
	flex: 1 1 auto;
	display: flex;
	flex-direction: column;
	min-height: 0;
}

.htp-pane { display: none; }

.htp-pane.is-visible {
	display: block;
	position: relative;      /* anchors .buttonsContainer inside its own pane */
	flex: 1 1 0;
	min-height: 0;
	overflow: auto;
}

/* In split view the panes share the height and get a divider. */
.htp-panes[data-layout="split"] .htp-pane.is-visible + .htp-pane.is-visible {
	border-top: 1px solid var(--htp-border);
}

/* The original stylesheet pins the controls absolutely at the top of the page.
   Inside a pane, with extra option checkboxes below them, they are better in
   normal flow — then they can never overlap the staff. */
div.buttonsContainer { position: static; padding: 0.75em 1em 0; }
/* The whole staff — clefs, noteheads, markers, spacing — is sized in em off
   this one value, so changing it zooms the notation as a unit. Set from
   js/htp-panes.js; browser zoom still applies on top of it.

   The staff always spans the full width, so this size governs its HEIGHT and
   the size of its glyphs, not how wide it is. The viewport cap is therefore
   only a backstop for genuinely narrow screens, where a tall staff would crowd
   out the keyboard — it does not kick in on an ordinary phone. */
div.staffsContainer {
	font-size: min(var(--htp-staff-size, 75px), 20vw);
	margin-top: 0.8em;
}

.htp-panes[data-layout="split"] div.staffsContainer { margin-top: 0.4em; }

/* The options are app chrome, not trainer chrome: they drive the staff markings
   and the keyboard, so they stay put and keep their state as you move between
   panes. */
.htp-optionsbar {
	flex: 0 0 auto;
	padding: 0.5em 1em;
	background: #fff;
	border-bottom: 1px solid var(--htp-border);
	/* A drawer: max-height animates where height:auto cannot. The cap is well
	   clear of the two rows the options wrap to on a narrow window. */
	max-height: 240px;
	overflow: hidden;
	transition: max-height 0.18s ease, padding 0.18s ease;
	/* A flex item's automatic minimum size refuses to shrink below its content,
	   which beats max-height and leaves the drawer stuck open. `overflow:hidden`
	   is supposed to zero that automatically; Safari does not always agree, so
	   say it explicitly. */
	min-height: 0;
}

.htp-optionsbar[data-open="false"] {
	max-height: 0;
	padding-top: 0;
	padding-bottom: 0;
	border-bottom-width: 0;
}

/* The drawer handle, in the tab bar. Same chevron as the keyboard drawer. */
.htp-iconbtn--drawer {
	display: inline-flex;
	align-items: center;
	gap: 0.4em;
}

.htp-drawer-chevron {
	display: inline-block;
	width: 0;
	height: 0;
	border-left: 5px solid transparent;
	border-right: 5px solid transparent;
	border-top: 6px solid currentColor;
	transition: transform 0.15s ease;
}

.htp-iconbtn--drawer[aria-expanded="false"] .htp-drawer-chevron {
	transform: rotate(180deg);
}

.htp-options {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0.4em 1.4em;
	font-size: 13px;
	color: var(--htp-muted);
}

.htp-options label {
	font-weight: normal;
	margin: 0;
	cursor: pointer;
	display: flex;
	align-items: center;
	gap: 0.35em;
}

.htp-options input { margin: 0; }

/* ----------------------------------------------------------------- footer */

.htp-footer {
	flex: 0 0 auto;
	padding: 0.6em 1em;
	font-size: 13px;
	color: var(--htp-muted);
	border-top: 1px solid var(--htp-border);
}

/* Override the original absolute positioning — the keyboard drawer now owns
   the bottom of the screen. */
div.shareContainer { position: static; }

/* --------------------------------------------------------- keyboard drawer */

.htp-keyboard {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 1000;
	background: #fff;
	border-top: 1px solid var(--htp-border);
	box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.08);
}

.htp-keyboard__bar {
	display: flex;
	align-items: center;
	gap: 1em;
	height: var(--htp-keyboard-bar);
	padding: 0 0.75em;
	font-size: 13px;
	color: var(--htp-muted);
}

.htp-keyboard__toggle {
	appearance: none;
	border: none;
	background: transparent;
	font: inherit;
	color: var(--htp-ink);
	cursor: pointer;
	display: flex;
	align-items: center;
	gap: 0.45em;
	padding: 0;
}

.htp-keyboard__chevron {
	display: inline-block;
	width: 0;
	height: 0;
	border-left: 5px solid transparent;
	border-right: 5px solid transparent;
	border-top: 6px solid var(--htp-ink);
	transition: transform 0.15s ease;
}

.htp-keyboard[data-open="false"] .htp-keyboard__chevron { transform: rotate(180deg); }

.htp-keyboard__group { display: flex; align-items: center; gap: 0.4em; }
.htp-keyboard__grouplabel { color: var(--htp-muted); }
.htp-keyboard__value { min-width: 4.2em; text-align: center; color: var(--htp-ink); }

.htp-keyboard__btn {
	appearance: none;
	border: 1px solid var(--htp-border);
	background: #fff;
	color: var(--htp-ink);
	font: inherit;
	line-height: 1;
	width: 22px;
	height: 22px;
	border-radius: 3px;
	cursor: pointer;
}

.htp-keyboard__status {
	margin-left: auto;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.htp-keyboard__body {
	overflow-x: auto;
	overflow-y: hidden;
	height: var(--htp-keyboard-height);
	transition: height 0.18s ease;
}

.htp-keyboard[data-open="false"] .htp-keyboard__body { height: 0; }

.htp-keys {
	position: relative;
	height: var(--htp-keyboard-height);
	/* min-width and max-width are set per range in js/htp-keyboard.js: narrow
	   ranges stay a sane key size and centre, wide ones scroll. */
	margin: 0 auto;
	touch-action: none;
}

.htp-key {
	position: absolute;
	top: 0;
	box-sizing: border-box;
	cursor: pointer;
	user-select: none;
	-webkit-user-select: none;
	-webkit-touch-callout: none;   /* no long-press menu over a key on iOS */
}

.htp-key--white {
	width: var(--htp-white-key-width);
	height: 100%;
	background: #fff;
	border: 1px solid #555;
	border-top: none;
	border-radius: 0 0 3px 3px;
	z-index: 1;
}

.htp-key--black {
	/* Centred on the seam its `left` names, so the ratio can change without JS
	   recomputing any offsets. */
	width: calc(var(--htp-white-key-width) * var(--htp-black-key-ratio));
	transform: translateX(-50%);
	height: var(--htp-black-key-height);
	background: #1c1c1c;
	border: 1px solid #000;
	border-top: none;
	border-radius: 0 0 3px 3px;
	z-index: 2;
}

.htp-key--white.is-active { background: var(--htp-accent-soft); }
.htp-key--black.is-active { background: var(--htp-accent); }

/* A landmark key highlights in its own colour rather than the generic blue, so
   pressing it reads as the same landmark you see marked on the staff. Every
   landmark (C, F, G) is a white key, hence only the white rule. */
.htp-key--white.is-landmark.is-active { background: var(--htp-key-tint-soft); }

.htp-key__label {
	position: absolute;
	bottom: 4px;
	left: 0;
	right: 0;
	text-align: center;
	font-size: 9px;
	color: var(--htp-muted);
	pointer-events: none;
}

/* ----------------------------------------------------------- midi monitor */

.htp-monitor { padding: 1em 1.2em; font-size: 14px; }
.htp-monitor__head { display: flex; align-items: center; gap: 1em; }
.htp-monitor__title { font-size: 16px; margin: 0; flex: 1 1 auto; }
.htp-monitor__empty { color: var(--htp-muted); }

.htp-monitor__list {
	list-style: none;
	margin: 0.8em 0 0;
	padding: 0;
	font-family: Menlo, Consolas, monospace;
	font-size: 12px;
}

.htp-monitor__row {
	display: flex;
	gap: 1em;
	padding: 0.28em 0.5em;
	border-bottom: 1px solid #f0f0f0;
}

.htp-monitor__row--on { background: #f4f9ff; }
.htp-monitor__label { min-width: 6em; color: var(--htp-ink); }
.htp-monitor__detail { flex: 1 1 auto; color: var(--htp-muted); }
.htp-monitor__source { color: #aaa; }

/* ------------------------------------------------------- landmark markings */

/*
 * The C / F / G landmark system, following the piano-roll project. Parity
 * decides the shape: a landmark on a staff line gets a dashed rule along it, a
 * landmark in a space gets a filled band covering that space. Geometry and
 * colours come from js/code.js (applyLineMarkers) and HTP.landmarks.
 */
.htp-markers {
	position: absolute;
	left: 0;
	right: 0;
	top: 0;
	bottom: 0;
	/* Above the clef's opaque background (10) so the rules and bands carry across
	   it, but below the staff lines (100). The staff lines already draw over
	   noteheads in this design, so markers crossing a notehead is consistent. */
	z-index: 11;
	pointer-events: none;
	overflow: hidden;
}

.htp-marker { position: absolute; left: 0; right: 0; }

/* Dash pattern and weight follow the piano-roll renderer: a 2.6mm dash with a
   2.2mm gap, drawn about 1.9x the weight of a staff line. */
.htp-marker--rule {
	height: 2px;
	background-image: repeating-linear-gradient(
		to right,
		currentColor 0, currentColor 6px,
		transparent 6px, transparent 11px
	);
}

.htp-marker--band { opacity: 0.16; }

/* Landmark keys on the on-screen keyboard, tinted to match. */
.htp-key--white.is-landmark::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 26%;
	background: var(--htp-key-tint);
	opacity: 0.85;
	border-radius: 0 0 2px 2px;
}

.htp-key__label { z-index: 1; }
.htp-key--white.is-landmark .htp-key__label { color: #fff; }

/* -------------------------------------------------------- played-note ghost */

/*
 * The note you actually played, drawn on top of the target note so a wrong
 * answer shows the interval you missed by. Appended by showPlayedNote() in
 * js/code.js to the target note's own symbol, so it travels with it.
 */
div.staff div.symbol.note svg.played { visibility: visible; opacity: 0.9; }
div.staff div.symbol.note svg.played path { fill: var(--htp-played-colour, #c0392b); }
div.staff div.symbol.note svg.played line { stroke: var(--htp-played-colour, #c0392b); }
div.staff div.symbol.note svg.played.wrong { --htp-played-colour: #c0392b; }
div.staff div.symbol.note svg.played.correct { --htp-played-colour: #1e9e5a; }

/* ------------------------------------------------------------- name readout */

.htp-readout {
	min-height: 3.2em;
	margin: 0.4em 0 0.8em;
	text-align: center;
	color: var(--htp-ink);
}

/* The note names themselves, large. What they add up to sits underneath. */
.htp-readout__primary {
	font-size: 30px;
	font-weight: 600;
	line-height: 1.15;
	letter-spacing: 0.01em;
}

.htp-readout__secondary {
	font-size: 15px;
	color: var(--htp-muted);
}

.htp-readout__played {
	font-size: 13px;
	margin-top: 0.25em;
}

.htp-readout__played--wrong { color: #c0392b; }
.htp-readout__played--correct { color: #1e9e5a; }

.htp-options__group {
	display: flex;
	align-items: center;
	gap: 0.6em;
}

.htp-options__grouplabel { color: var(--htp-muted); }

/* The clef keeps the original stylesheet's solid white background: it is what
   masks notes — and an outgoing clef — as they scroll behind it. Making it
   transparent showed two clefs overlapping during a clef change. The landmark
   layer is lifted above it instead (see .htp-markers), so the rules and bands
   still read across the clef. */

/* ------------------------------------------------------------ free practice */

.htp-free { padding-top: 0.5em; }

/* Its own staves, laid out like the trainer's but without the scroll animation. */
.htp-free__staves { margin-top: 0.4em; }

/*
 * A free-practice notehead group: one per staff, holding every sounding note on
 * that staff. Unlike the trainer's notes it never scrolls, so it sits at a fixed
 * x and does not clip — a sharp or flat glyph is wider than a bare notehead.
 */

.htp-options__value {
	min-width: 2em;
	text-align: center;
	color: var(--htp-ink);
}

/* Free practice has no scrolling notes and no clef changes, so its clefs need
   no opaque backdrop — and without one the landmark rules and bands read across
   them. The trainer keeps its opaque clef, which is what masks notes and an
   outgoing clef as they scroll past. */
.htp-free div.staff div.symbol.clef { background: transparent; }

/*
 * Free practice puts the note you are playing dead centre.
 *
 * Every note glyph right-aligns its notehead inside the symbol box (the original
 * stylesheet sets `right: 0` with a 0.1em right margin), so accidentals grow
 * leftwards and the notehead always lands in the same place whatever the
 * spelling. Working back from a notehead centred on 50%, the box's `left` is:
 *
 *     50%  - 2.4em    box width
 *          + 0.1em    the glyph's right margin
 *          + 0.132em  half a notehead (141.012 of 1066.201 viewBox units,
 *                     rendered 2em tall, so 0.2645em wide)
 *          - 0.1em    .symbol.note's own margin-left, which offsets the box
 *     = 50% - 2.268em
 *
 * Verified in the browser: notehead centre lands within a pixel of staff centre.
 */
div.staff div.symbol.note.htp-free-note {
	width: 2.4em;
	left: calc(50% - 2.268em);
	overflow: visible;
}

/*
 * Ledger lines for a free-practice note. The original stylesheet stretches them
 * across the whole symbol box (`left: 0; right: 0`), which is right for the
 * trainer, where the box is measured to fit its glyphs. Here the box is a fixed
 * 2.4em so accidentals have room, and a full-width ledger line would trail far
 * off to the left of the notehead.
 *
 * Right-aligning them at 0.464em wide centres them on the notehead: the
 * notehead's centre sits 0.232em in from the box's right edge, so a line of
 * twice that width, right-aligned, shares the same centre.
 */
div.staff div.symbol.note.htp-free-note svg.line {
	left: auto;
	right: 0;
	width: 0.464em;
}

/*
 * The original stylesheet clips a note symbol to its own box, which the trainer
 * needs — its boxes are measured to fit and its notes scroll in from the edge of
 * the staff. Here the box is a fixed width and the note is parked in the middle,
 * so the clip has nothing useful to do and everything to lose: the ledger line
 * sits flush against the right edge, where clipping shaves its last pixel and
 * the note reads as cropped.
 */
div.staff div.symbol.note.htp-free-note {
	overflow: visible;
}

/* --------------------------------------------------------------- staff lines */

/*
 * The five staff lines are drawn as elements by renderStaffLines() in
 * js/code.js, at the same computed positions the notes and landmark markers
 * use. The original stylesheet tiles them as a background SVG, which the
 * browser rounds — so at some sizes and zoom levels the painted lines no longer
 * matched where the notes and markers were placed. One formula for all three
 * removes that whole class of drift.
 */
div.staff div.lines {
	background-image: none;
}

div.staff div.lines .htp-staffline {
	position: absolute;
	left: 0;
	right: 0;
	height: 0;
	border-top: 1px solid #000;
}

/* Free practice grows its staff container when a note falls outside the staff.
   Deliberately NOT transitioned: the room has to be there the instant the note
   is drawn, and an eased padding also reports its starting value to any script
   measuring it mid-transition, which makes the behaviour hard to verify. */

/* Turning a clef off hides its whole staff, which js/code.js and the modules
   that draw their own staves each handle — there is nothing to do in CSS. */

/* ------------------------------------------------------------------ phones */

/*
 * The app is a fixed stack of horizontal bands — tabs, options, staff,
 * keyboard — and on a narrow, short screen each one has to give up some room.
 */
@media (max-width: 700px) {
	:root {
		--htp-keyboard-height: 108px;
		--htp-keyboard-bar: 30px;
	}

	/* Three tabs plus three buttons: let them wrap rather than squash, and
	   shrink them so they usually do not have to. */
	.htp-tabbar {
		flex-wrap: wrap;
		gap: 0.3em;
		padding: 0.25em 0.5em;
		min-height: 0;
	}

	.htp-tab { font-size: 13px; padding: 0.4em 0.6em; }
	.htp-iconbtn { font-size: 12px; padding: 0.3em 0.5em; }

	/* Two panes side by side on a phone would leave neither readable, and the
	   button costs a row in a bar that is already wrapping. */
	#htpSplit { display: none; }

	.htp-optionsbar { padding: 0.4em 0.6em; max-height: 420px; }
	.htp-options { gap: 0.35em 0.9em; font-size: 12px; }

	/* The trainer's own controls, sized by the original stylesheet for a
	   desktop window. */
	div.buttonsContainer { padding: 0.5em 0.6em 0; }
	div.levelsContainer { margin-top: 0.6em; font-size: 13px; }
	div.levelsContainer select { max-width: 60vw; }

	.htp-readout { min-height: 2.6em; margin: 0.3em 0 0.5em; }
	.htp-readout__primary { font-size: 24px; }
	.htp-readout__secondary { font-size: 13px; }

	.htp-footer { padding: 0.4em 0.6em; font-size: 12px; }

	.htp-keyboard__bar { gap: 0.6em; padding: 0 0.5em; font-size: 12px; }
	.htp-keyboard__grouplabel { display: none; }   /* the buttons speak for themselves */
	.htp-keyboard__status { font-size: 11px; }

	.htp-monitor { padding: 0.7em; font-size: 13px; }
}

/* Portrait is the tight one: the keyboard is at its narrowest, so a true-width
   black key leaves too little white key either side to hit with a thumb. */
@media (max-width: 700px) and (orientation: portrait) {
	:root { --htp-black-key-ratio: 0.44; }
}

/* Landscape on a phone is all width and no height; give the chrome back. */
@media (max-height: 480px) {
	:root {
		--htp-keyboard-height: 84px;
		--htp-keyboard-bar: 28px;
	}
	.htp-footer { display: none; }
	div.staffsContainer { margin-top: 0.3em; }
}

/* ------------------------------------------------- installed / standalone */

/*
 * With viewport-fit=cover the page runs edge to edge, which is what a Home
 * Screen launch wants — but the notch and the home indicator then sit on top of
 * the app. These insets are zero in an ordinary browser window, so the rules
 * cost nothing there.
 */
.htp-tabbar {
	padding-top: env(safe-area-inset-top);
}

.htp-keyboard {
	padding-bottom: env(safe-area-inset-bottom);
}

.htp-app {
	padding-left: env(safe-area-inset-left);
	padding-right: env(safe-area-inset-right);
}

/* A control that cannot apply right now, rather than one that is missing. */
.htp-options label.is-disabled {
	opacity: 0.45;
	cursor: not-allowed;
}

/* Shown only where full screen is impossible and the app is not already
   installed — it replaces the button rather than sitting beside it. */
.htp-installhint {
	font-size: 11px;
	color: var(--htp-muted);
	white-space: nowrap;
}

@media (max-width: 700px) {
	.htp-installhint { font-size: 10px; }
}

/* ------------------------------------------------ notehead line alignment */

/*
 * Nudge noteheads up by a hair so they sit exactly on their line.
 *
 * The staff lines, the ledger lines and the landmark markings are all placed at
 * markerTopEm(), which corresponds to 0.88164em down a symbol's 2em box — that
 * is where the line artwork draws its stroke. The notehead artwork's painted
 * centre falls at 0.88558em inside the same box, so every notehead rendered a
 * constant 0.00394em low: a third of a pixel at the default size, invisible on
 * its own, but the lines use shape-rendering:crispEdges and snap to the device
 * pixel grid while an antialiased notehead does not — so on a high-DPI screen
 * the two can land a whole pixel apart and the note reads as off its line.
 *
 * The 100em here is the original stylesheet's positioning trick, not a value of
 * its own; this only subtracts the artwork's own bias from it.
 */
div.staff div.symbol.note svg:not(.line) {
	margin-top: calc(100em - 0.00394em);
}
