:root {
  color-scheme: light;
  --ink: #102033;
  --paper: #edf5fb;
  --glow-a: rgba(78, 168, 255, 0.16);
  --glow-b: rgba(141, 120, 255, 0.1);
  font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

html {
  /* One source of truth for proportions: the rack is a fixed width
     (--kv-rack-width) and the type scale is a fixed base, so the tab and
     the installed app render at identical ratios regardless of window
     size. (Was a viewport-based clamp, which made the two contexts —
     differently sized windows — scale differently.) */
  font-size: 16px;
  width: 100%;
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
  /* WebKit may promote a full-screen WebGL canvas into its own compositor
     layer. Isolate the page and give the rack stage an explicit sibling layer
     so the canvas cannot be composited above it on iPhone. */
  isolation: isolate;
}

html[data-theme="dark"] {
  color-scheme: dark;
  --ink: #f4f7fb;
  --paper: #07111f;
  --glow-a: rgba(31, 183, 230, 0.15);
  --glow-b: rgba(141, 120, 255, 0.14);
}

* { box-sizing: border-box; }

body {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100dvh;
  margin: 0;
  min-height: 0;
  overflow: hidden;
  overscroll-behavior: none;
  color: var(--ink);
  background:
    radial-gradient(circle at 78% 9%, var(--glow-a), transparent 34rem),
    radial-gradient(circle at 12% 52%, var(--glow-b), transparent 30rem),
    var(--paper);
}
html.is-theme-transitioning body {
  transition: color 380ms cubic-bezier(.22, .72, .24, 1),
              background-color 380ms cubic-bezier(.22, .72, .24, 1);
}
@media (prefers-reduced-motion: reduce) {
  html.is-theme-transitioning body { transition-duration: 0s; }
}

/* Winamp-style: the page is only a stage for the racks — no wrapper card,
   no heading, no controls of its own. */
main {
  position: relative;
  z-index: 1;
  width: min(1420px, calc(100% - 24px));
  margin: 12px auto;
  transform: translateZ(0);
}

.workspace {
  height: calc(100dvh - 24px);
  min-height: 0;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.instrument { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) min(var(--kv-rack-width, 760px), 100%) minmax(0, 1fr); gap: 0; align-items: stretch; }
.player-stage { grid-column: 2; grid-row: 1; display: flex; min-width: 0; max-height: 100%; align-items: center; }
.instrument[data-player-placement="left"] .player-stage { grid-column: 1; }
.instrument[data-player-placement="center"] .player-stage { grid-column: 2; }
.instrument[data-player-placement="right"] .player-stage { grid-column: 3; }
.player-host { min-width: 0; min-height: 104px; max-height: 100%; display: flex; align-items: center; justify-content: center; padding: 12px 0; overflow: auto; background: transparent; border: 0; transition: grid-column .22s ease; }
.player-host .enh-gpiano.is-docked,
.player-host .enh-gpiano.is-docked > .enh-gpiano-pop { max-height: 100%; }

/* The standalone page opens the amp immediately, so it needs neither a
   launcher nor a close action. Keep the launcher's layout box as the initial
   floating-position anchor, but make both controls unavailable and invisible. */
.player-host .enh-gpiano-play {
  visibility: hidden;
  pointer-events: none;
}
/* This was scoped to docked layout only; a floating standalone rack (an
   ordinary browser tab, not the installed/desktop app) kept its close
   button live while the launcher above stays permanently hidden — closing
   it left no way to reopen the player at all. Same "needs neither" intent
   as the launcher above, so hide it unconditionally here too. */
.player-host .enh-gpiano-pop > .enh-gpiano-floatbar .enh-gpiano-close { display: none; }

@media (max-width: 760px) {
  main { width: min(100% - 16px, 1180px); margin: 8px auto; }
  .workspace { height: calc(100dvh - 16px); }
  .instrument, .instrument[data-player-placement] { grid-template-columns: 1fr; }
  .instrument .player-stage, .instrument[data-player-placement] .player-stage { grid-column: 1; grid-row: 1; }
}

@media (max-height: 420px) and (orientation: landscape) {
  html, body { height: 100dvh; overflow: hidden; }
  main { margin-block: 0; }
  .workspace { min-height: 100dvh; }
  .player-host { padding-block: 0; }
}

/* ============ Frameless app window ============
   Only when Klangvev runs as an installed app (manifest display:
   standalone — Chrome's "Open in app"): the macOS window IS the outer
   border, no page margins, no inset card, rack edge to edge. In a normal
   browser tab the centered card layout above stays in effect. */
@media (display-mode: standalone), (display-mode: window-controls-overlay) {
  main { width: 100%; margin: 0; }
  .workspace { min-height: 100dvh; }
  .instrument, .instrument[data-player-placement] { grid-template-columns: 1fr; }
  .instrument .player-stage, .instrument[data-player-placement] .player-stage { grid-column: 1; }
  .player-host { min-height: 0; height: 100dvh; padding: 0; align-items: stretch; }

  /* In landscape, center the rack instead of stretching it full-width. */
  @media (orientation: landscape) {
    .instrument, .instrument[data-player-placement] { grid-template-columns: minmax(0, 1fr) min(var(--kv-rack-width, 760px), 100%) minmax(0, 1fr); }
    .instrument .player-stage, .instrument[data-player-placement] .player-stage { grid-column: 2; }
  }

  .player-host { justify-content: center; }
  /* The installed window is the rack. Do not retain the normal webpage's
     centered 760px cap, which leaves empty canvas on wider app windows. */
  .enh-gpiano.is-docked {
    width: min(100%, 1040px);
    min-width: 0;
    max-width: 1040px;
    margin: 0 auto;
  }
  .enh-gpiano.is-docked > .enh-gpiano-pop {
    /* Same as the native shell: drop the 760px webpage minimum so a small or
       zoomed installed window compacts instead of clipping. */
    min-width: 0 !important;
    /* Fit the visible app viewport exactly. A 100vh minimum combined with the
       rack's own padding could leave the final pixels beneath the window edge.
       The max() pairs keep desktop identical (insets are 0) while an iPhone
       home-screen install clears the sensor housing and home indicator. */
    height: 100dvh;
    min-height: 0;
    max-height: none;
    padding-top: max(10px, env(safe-area-inset-top));
    padding-right: max(12px, env(safe-area-inset-right));
    padding-bottom: env(safe-area-inset-bottom);
    padding-left: max(12px, env(safe-area-inset-left));
    border-radius: 0;
    box-shadow: none;
    resize: none;
    background: transparent;
  }
  /* An active Winamp skin still paints its own panel background. */
  html.klangvev-textured .enh-gpiano.is-docked > .enh-gpiano-pop {
    background: var(--klangvev-bg, #0d1727);
  }

  /* window.resizeTo() (standalone.js's fitInstalledWindowToPlayer) is a
     silent no-op for an already-open installed-app window on some platforms
     (observed on ChromeOS) — the OS window is left at whatever size it
     already had, often much taller than the rack's own natural footprint.
     The rules above assume that resize succeeded and the window IS the
     rack's size, so forcing height: 100dvh just stretches the compact rack
     to fill that oversized window, leaving a huge gap behind the controls.
     Once standalone.js gives up retrying the resize, fall back to centering
     the rack at its own natural height instead of stretching it — the same
     "insets are 0 so desktop looks identical" desktop case still lands here
     unaffected, since its window was already the right size either way. */
  html.klangvev-app-resize-failed .player-host {
    height: 100dvh;
    align-items: center;
  }
  html.klangvev-app-resize-failed .enh-gpiano.is-docked > .enh-gpiano-pop {
    height: auto;
    max-height: 100dvh;
  }

  /* The background visual's ⋯/× controls pin to the viewport's top-left and,
     on touch devices, are always visible (no hover to reveal them). In an
     installed app drawing under the status bar they must start below the
     sensor housing. The extra `section` keeps this heavier than the
     player-ui.css rules, which load later. */
  section.enh-gpiano-visual-window.is-background > header {
    padding-top: max(6px, env(safe-area-inset-top));
    padding-left: max(12px, env(safe-area-inset-left));
  }
}

/* Installed Chromium PWAs that support Window Controls Overlay remove the
   browser-supplied title strip. The real OS controls remain functional while
   Klangvev's own Loom/Winamp bar becomes the draggable window titlebar. */
@media (display-mode: window-controls-overlay) {
  body {
    min-height: 100dvh;
    overflow: hidden;
  }

  .enh-gpiano.is-docked > .enh-gpiano-pop {
    height: 100dvh;
  }

  .enh-gpiano.is-docked .enh-gpiano-floatbar {
    position: relative;
    z-index: 20;
    box-sizing: border-box;
    width: 100%;
    min-height: max(40px, env(titlebar-area-height, 40px));
    margin: 0 0 7px;
    padding-top: 2px;
    padding-right: max(8px, calc(100vw - env(titlebar-area-x, 0px) - env(titlebar-area-width, 100vw) + 8px));
    padding-bottom: 5px;
    padding-left: max(8px, calc(env(titlebar-area-x, 0px) + 8px));
    -webkit-app-region: drag;
    app-region: drag;
    user-select: none;
  }

  .enh-gpiano.is-docked .enh-gpiano-floatbar button,
  .enh-gpiano.is-docked .enh-gpiano-floatbar select,
  .enh-gpiano.is-docked .enh-gpiano-floatbar input,
  .enh-gpiano.is-docked .enh-gpiano-floatbar a {
    -webkit-app-region: no-drag;
    app-region: no-drag;
  }

  /* The installed app uses the native overlay controls; it must not paint a
     second HTML close button beside them. */
  .player-host .enh-gpiano.is-docked > .enh-gpiano-pop > .enh-gpiano-floatbar .enh-gpiano-close { display: none; }
}

/* Native desktop shell: the player stays shared; only its outer frame changes. */
html.klangvev-desktop,
html.klangvev-desktop body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #071625;
}

html.klangvev-desktop main { width: 100%; margin: 0; }
html.klangvev-desktop .instrument,
html.klangvev-desktop .instrument[data-player-placement] {
  grid-template-columns: minmax(0, 1fr) !important;
}
html.klangvev-desktop .instrument .player-stage {
  grid-column: 1 !important;
  width: 100%;
}
html.klangvev-desktop .player-host {
  width: 100%;
  min-height: 100vh;
  padding: 0;
  /* Do not stretch the dock wrapper to a manually enlarged native window.
     The amp retains its web-sized box; any app canvas outside it stays plain
     instead of becoming an empty extension of the rack. Parked at the bottom
     (not the top) when AutoZoom is off, matching the camera's placement
     under the library. */
  align-items: flex-end;
  justify-content: center;
}
html.klangvev-desktop .enh-gpiano.is-docked,
html.klangvev-desktop .enh-gpiano.is-docked > .enh-gpiano-pop {
  width: 100% !important;
  /* Without this the shared rack keeps its 760px webpage minimum and clips
     both edges in a smaller (or zoomed) native window instead of letting the
     compact layout take over. The window is the rack; it must never overflow. */
  min-width: 0 !important;
  max-width: none;
  max-height: none !important;
  /* Native chrome differs, but the player body follows the same content-led
     height as the web rack. A viewport-height body created a large dead area
     and forced the transport away from its web position. */
  height: auto !important;
  /* Content-led, same as the web amp: a 270px floor left an empty strip
     under the transport, and AutoZoom enlarged that strip with the rest. */
  min-height: 0 !important;
  /* The whole window, not 85% of it: the macOS window is sized to the amp's
     own height, so an 85vh cap clipped the amp and let the sticky transport
     row ride up over the mixer (the old 270px floor used to mask this). */
  max-height: min(100vh, 900px) !important;
  box-sizing: border-box;
}
html.klangvev-desktop .enh-gpiano.is-docked { align-self: flex-end; }
/* The amp never stretches past the web amp's own proportions (560 wide at its
   ~218px natural height). A wide native window used to spread the controls
   to 1400px across. This caps the untransformed box; AutoZoom scales that box
   uniformly, so width and height grow together and the ratio holds either way. */
html.klangvev-desktop .enh-gpiano.is-docked {
  max-width: var(--kv-amp-max-width, 560px) !important;
  margin-inline: auto;
}
/* A taller native window is deliberate breathing room below the transport,
   not a cue to enlarge the now-playing card. Keep the source row at the web
   rack's compact artwork-safe height: when it grew to 180px the timeline was
   visually stranded in a second, empty-looking row below the song details. */
html.klangvev-desktop:not(.klangvev-desktop-presentation) .enh-gpiano-front > .enh-gpiano-source-row {
  /* A flexible 150px basis spent any native window slack inside the title
     card, making the cover 98px and putting the landscape/timer one visual
     row too low. Keep the same 88px song header as the web rack. */
  flex: 0 0 88px;
  min-height: 88px;
  max-height: 88px;
}
html.klangvev-desktop .enh-gpiano.is-docked > .enh-gpiano-pop {
  border: 0;
  border-radius: 0;
  box-shadow: none;
  /* The web amp's vertical padding; the shared docked 10px/18px left extra
     empty space above the titlebar and below the transport. */
  padding-top: 8px;
  padding-bottom: 12px;
}
html.klangvev-desktop .enh-gpiano.is-docked .enh-gpiano-floatbar {
  min-height: 48px;
  padding-left: 10px;
  -webkit-app-region: drag;
  app-region: drag;
  user-select: none;
}
/* Compact title bar (the web default) stays compact in the native shell. */
html.klangvev-desktop .enh-gpiano.is-docked.has-compact-titlebar .enh-gpiano-floatbar {
  min-height: 28px;
}
/* Every visible rack header can move its native macOS window through empty
   titlebar space. Complete control islands remain interactive, including
   wrappers around navigation, tabs, searches, and right-aligned actions. */
html.klangvev-desktop :is(.enh-gpiano-floatbar, .enh-gpiano-modland-bar, .enh-gpiano-score-window-bar) {
  -webkit-app-region: drag !important;
  app-region: drag !important;
  user-select: none;
}
html.klangvev-desktop :is(.enh-gpiano-floatbar, .enh-gpiano-modland-bar, .enh-gpiano-score-window-bar) > :is(
  button, input, select, textarea, a, label, summary,
  .enh-gpiano-modland-history,
  .enh-gpiano-modland-tabs,
  .enh-gpiano-modland-search,
  .enh-gpiano-float-actions,
  .enh-gpiano-native-rack-controls
),
html.klangvev-desktop :is(.enh-gpiano-floatbar, .enh-gpiano-modland-bar, .enh-gpiano-score-window-bar) :is(
  button, input, select, textarea, a, label, summary, [contenteditable="true"], [role="button"]
) {
  -webkit-app-region: no-drag !important;
  app-region: no-drag !important;
  pointer-events: auto;
}
/* In the shared-window mode this is an in-page rack, not the native window
   titlebar. Let its existing magnetic pointer drag receive the gesture. */
html.klangvev-desktop .enh-gpiano-visual-config > .enh-gpiano-rack-titlebar {
  -webkit-app-region: no-drag !important;
  app-region: no-drag !important;
}
html.klangvev-desktop .enh-gpiano.is-docked > .enh-gpiano-pop > .enh-gpiano-floatbar .enh-gpiano-close { display: none !important; }
html.klangvev-desktop .enh-gpiano.is-docked > .enh-gpiano-pop > .enh-gpiano-floatbar [data-gp="miniplayer"] { display: none !important; }
html.klangvev-desktop.klangvev-native-window-shell .enh-gpiano.is-docked > .enh-gpiano-pop > .enh-gpiano-floatbar .enh-gpiano-window-control {
  display: inline-grid;
  place-items: center;
}
html.klangvev-desktop .enh-gpiano-window-close:hover,
html.klangvev-desktop .enh-gpiano-window-close:focus-visible {
  color: #fff;
  background: rgba(226, 74, 74, .82);
}
html.klangvev-desktop.klangvev-native-window-controls .enh-gpiano.is-docked .enh-gpiano-floatbar {
  padding-left: 78px;
}
html.klangvev-desktop.klangvev-native-window-controls .enh-gpiano.is-docked > .enh-gpiano-pop > .enh-gpiano-floatbar .enh-gpiano-window-control {
  display: none;
}
.enh-gpiano-native-window-setting { display: none; }
html.klangvev-desktop .enh-gpiano-native-window-setting { display: inline-flex; }
html.klangvev-desktop .enh-gpiano-native-window-setting > span {
  /* Three drawn dots. The ● glyph's ink is wider than its advance in the
     system font, so three of them spilled past the 28px switch. */
  width: 21px;
  height: 6px;
  font-size: 0;
  background: radial-gradient(circle at center, currentColor 2.6px, transparent 3.1px) 0 0 / 7px 6px repeat-x;
}

/* Native fullscreen is a presentation stage, not a stretched version of the
   resizable amp window. The rack returns to its authored proportions while a
   quiet Klangvev night field supplies the same sense of place as web/iOS. */
html.klangvev-desktop.klangvev-desktop-presentation,
html.klangvev-desktop.klangvev-desktop-presentation body {
  background:
    radial-gradient(ellipse at 50% 92%, rgba(25, 115, 135, .2), transparent 36%),
    radial-gradient(ellipse at 50% 58%, rgba(36, 47, 116, .18), transparent 52%),
    linear-gradient(180deg, #09072f 0%, #07172a 58%, #020a12 100%);
}
html.klangvev-desktop.klangvev-desktop-presentation main {
  width: 100%;
  height: 100%;
  margin: 0;
}
html.klangvev-desktop.klangvev-desktop-presentation .workspace,
html.klangvev-desktop.klangvev-desktop-presentation .instrument,
html.klangvev-desktop.klangvev-desktop-presentation .player-stage {
  width: 100%;
  height: 100dvh;
}
html.klangvev-desktop.klangvev-desktop-presentation .player-host {
  width: 100%;
  height: 100dvh;
  min-height: 0;
  padding: 32px;
  place-items: center;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
/* URL presentation parks the amp along the bottom of the stage. */
html.klangvev-desktop.klangvev-desktop-presentation.klangvev-url-presentation .player-host {
  align-items: flex-end;
  padding-bottom: var(--kv-presentation-bottom-margin, 0px);
}
html.klangvev-desktop.klangvev-desktop-presentation .enh-gpiano.is-docked {
  width: min(var(--kv-amp-max-width, 560px), calc(100vw - 48px)) !important;
  min-width: 0 !important;
  max-width: var(--kv-amp-max-width, 560px) !important;
  height: auto !important;
  min-height: 0 !important;
  margin: 0 auto;
}
html.klangvev-desktop.klangvev-desktop-presentation .enh-gpiano.is-docked > .enh-gpiano-pop {
  width: 100% !important;
  min-width: 0 !important;
  max-width: none !important;
  height: auto !important;
  min-height: 0 !important;
  max-height: calc(100dvh - 64px) !important;
  padding: 10px 12px 0;
  overflow: hidden auto;
  resize: none;
  border: 1px solid var(--klangvev-primary, rgba(110, 86, 207, .3));
  border-radius: 18px;
  box-shadow: 0 28px 90px rgba(0, 0, 0, .52), 0 0 44px rgba(40, 170, 180, .08);
}
html.klangvev-desktop.klangvev-desktop-presentation .enh-gpiano.is-docked .enh-gpiano-floatbar {
  min-height: 48px;
  padding-left: 10px;
  -webkit-app-region: no-drag;
}
html.klangvev-desktop.klangvev-desktop-presentation .enh-gpiano.is-docked.has-compact-titlebar .enh-gpiano-floatbar {
  height: 28px;
  min-height: 28px;
  margin: -8px -5px 4px;
}
/* Presentation mode previously let the flexible source row absorb all spare
   rack height. Its portrait grew, while the title and progress offsets stayed
   calibrated for the normal 60px card, so copy and the elapsed clock landed
   underneath the image. Keep one deliberate title-card height and reserve
   independent horizontal lanes for artwork, copy, clock, and progress. */
html.klangvev-desktop.klangvev-desktop-presentation .enh-gpiano-front > .enh-gpiano-source-row {
  flex: 0 0 88px;
  min-height: 88px;
}
html.klangvev-desktop.klangvev-desktop-presentation
  .enh-gpiano.has-combined-title-progress
  .enh-gpiano-now-playing:has(.enh-gpiano-now-art:not([hidden]))
  .enh-gpiano-now-copy {
  margin-left: 88px;
  transform: translateY(-9px);
}
html.klangvev-desktop.klangvev-desktop-presentation
  .enh-gpiano.has-combined-title-progress
  .enh-gpiano-now-playing:has(.enh-gpiano-now-art:not([hidden]))
  > .enh-gpiano-progress {
  --kv-progress-inline-start: 118px;
}

/* Drop presentation gutters once the compact 560px rack no longer fits. */
@media (max-width: 560px), (max-height: 480px) {
  html.klangvev-desktop.klangvev-desktop-presentation.klangvev-url-presentation .player-host {
    padding-bottom: 0;
  }
  html.klangvev-desktop.klangvev-desktop-presentation.klangvev-url-presentation .enh-gpiano.is-docked > .enh-gpiano-pop {
    max-height: 100dvh !important;
    padding-bottom: env(safe-area-inset-bottom);
  }
}
@media (max-width: 560px) {
  html.klangvev-desktop.klangvev-desktop-presentation.klangvev-url-presentation .player-host {
    padding-left: 0;
    padding-right: 0;
  }
  html.klangvev-desktop.klangvev-desktop-presentation.klangvev-url-presentation .enh-gpiano.is-docked {
    width: 100vw !important;
    max-width: none !important;
  }
  html.klangvev-desktop.klangvev-desktop-presentation.klangvev-url-presentation .enh-gpiano.is-docked > .enh-gpiano-pop {
    border-radius: 0;
  }
}

html.klangvev-desktop.klangvev-url-presentation .enh-gpiano.is-docked { align-self: flex-end; }

/* The fixed presentation header gives the cover the same 4px inset at
   its top and bottom, independent of the progress footer's text padding. */
html.klangvev-desktop.klangvev-desktop-presentation .enh-gpiano.has-combined-title-progress .enh-gpiano-now-playing {
  --kv-now-art-size: 80px;
}

/* Ease the presentation gutters as the viewport crosses compact limits. */
html.klangvev-desktop.klangvev-desktop-presentation.klangvev-url-presentation .player-host {
  transition: padding 240ms ease;
}
html.klangvev-desktop.klangvev-desktop-presentation.klangvev-url-presentation .enh-gpiano.is-docked {
  transition: width 240ms ease, max-width 240ms ease;
}
html.klangvev-desktop.klangvev-desktop-presentation.klangvev-url-presentation .enh-gpiano.is-docked > .enh-gpiano-pop {
  transition: border-radius 240ms ease;
}
@media (prefers-reduced-motion: reduce) {
  html.klangvev-desktop.klangvev-desktop-presentation.klangvev-url-presentation .player-host,
  html.klangvev-desktop.klangvev-desktop-presentation.klangvev-url-presentation .enh-gpiano.is-docked,
  html.klangvev-desktop.klangvev-desktop-presentation.klangvev-url-presentation .enh-gpiano.is-docked > .enh-gpiano-pop {
    transition: none;
  }
}
