/*
 * This is a manifest file that'll be compiled into application.css.
 *
 * With Propshaft, assets are served efficiently without preprocessing steps. You can still include
 * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard
 * cascading order, meaning styles declared later in the document or manifest will override earlier ones,
 * depending on specificity.
 *
 * Consider organizing styles into separate files for maintainability.
 */

/* Markdown editor (Write/Preview tabs + toolbar) — module description field. */
.md-tab { background: transparent; color: var(--pulsers-muted); }
.md-tab[data-active="true"] { background: var(--pulsers-ground); color: var(--pulsers-text); }
/* Scoped to the editor only — a global `.hidden { !important }` would override the
   inline `display` that other panels (properties, specs, modals) rely on. */
.md-hidden { display: none; }

/* Module editor tabs (Build / Description / Démos): real pill buttons. Inactive
   = outlined pill, muted text; active = solid accent; hover lifts the text. */
.designer-tab {
  background: var(--pulsers-surface);
  border: 1px solid var(--pulsers-line);
  border-radius: 6px;
  color: var(--pulsers-muted);
  padding: 8px 18px;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.03em;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.designer-tab:hover { color: var(--pulsers-text); border-color: var(--pulsers-faint); }
.designer-tab[data-active="true"] {
  background: var(--pulsers-accent);
  border-color: var(--pulsers-accent);
  color: var(--pulsers-ground);
  font-weight: 700;
}

/* Rendered Markdown (preview pane and module show). */
.markdown-body { word-wrap: break-word; }
.markdown-body h1, .markdown-body h2, .markdown-body h3 { color: var(--pulsers-text); font-weight: 700; margin: 12px 0 6px; }
.markdown-body h1 { font-size: 18px; }
.markdown-body h2 { font-size: 16px; }
.markdown-body h3 { font-size: 14px; }
.markdown-body p { margin: 0 0 10px; }
.markdown-body ul, .markdown-body ol { margin: 0 0 10px; padding-left: 20px; }
.markdown-body li { margin: 2px 0; }
.markdown-body a { color: var(--pulsers-accent); text-decoration: underline; }
.markdown-body code { background: var(--pulsers-surface); border: 1px solid var(--pulsers-line); border-radius: 3px; padding: 1px 4px; font-family: var(--font-mono); font-size: 0.9em; }
.markdown-body blockquote { border-left: 3px solid var(--pulsers-line); margin: 0 0 10px; padding-left: 12px; color: var(--pulsers-muted); }

/* ── Realistic rack chrome (rails, screw holes, HP ruler) — read-only shows ── */
.rack-body { position: relative; padding: 7px 0; border-radius: 4px; background-color: #161616; }

/* Brushed-metal rails top & bottom, with screw holes punched every 1 HP. The
   holes are a single repeating radial-gradient, not one element per screw. */
.rack-rail {
  position: relative;
  height: 7px;
  background:
    radial-gradient(circle at calc(var(--hp-px) / 2) 50%, #0b0b0b 0 1.1px, transparent 1.4px) repeat-x,
    linear-gradient(180deg, #3d3d3d 0%, #262626 45%, #1a1a1a 100%);
  background-size: var(--hp-px) 100%, auto;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.07), inset 0 -1px 0 rgba(0,0,0,.6);
}
.rack-rail--top { border-radius: 3px 3px 0 0; margin-bottom: 2px; }
.rack-rail--bottom { border-radius: 0 0 3px 3px; margin-top: 2px; }

/* Aluminium HP ruler. */
.rack-ruler { background: linear-gradient(180deg, #2a2a2a, #202020); border-radius: 3px 3px 0 0; overflow: hidden; margin-bottom: 2px; }
.rack-ruler-tick { position: absolute; top: 0; width: 1px; height: 6px; background: #4a4a4a; }
.rack-ruler-tick--major { height: 11px; background: #6b7f88; }
.rack-ruler-num { position: absolute; bottom: 1px; transform: translateX(2px); font-family: 'IBM Plex Mono', monospace; font-size: 9px; color: #8a9aa0; }

/* Module link + hover "View ↗" overlay (show rack only). */
.rack-module-link { cursor: pointer; }
.rack-module-overlay {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 4px;
  background: rgba(13,13,13,.55); border: 1.5px solid var(--pulsers-accent); border-radius: 4px;
  box-shadow: 0 0 12px color-mix(in srgb, var(--pulsers-accent) 35%, transparent);
  opacity: 0; transition: opacity 120ms ease; pointer-events: none;
}
.rack-module-link:hover .rack-module-overlay,
.rack-module-link:focus-visible .rack-module-overlay { opacity: 1; }
.rack-module-overlay-label { font-family: var(--font-mono); font-size: 11px; font-weight: 700; color: var(--pulsers-accent); }
.rack-module-overlay-name { font-family: var(--font-mono); font-size: 9px; color: var(--pulsers-text); text-align: center; padding: 0 4px; }

/* Read-only rack/patch shows: components are not draggable, so no move cursor.
   Overrides the editor's `.placed-component { cursor: move }` by specificity. */
.rack-body .placed-component,
.rack-body .placed-component * { cursor: default; }

/* Read-only module designer (module show): components are inert → default cursor. */
[data-module-designer-readonly-value="true"] .placed-component,
[data-module-designer-readonly-value="true"] .placed-component * { cursor: default; }

/* Knob/slider ViewComponents set `cursor:grab` inline for editor dragging; in a
   read-only show that drag is disabled, so force the default cursor. Scoped
   !important is required to beat the inline style, and only applies in shows. */
.rack-body .placed-component svg,
[data-module-designer-readonly-value="true"] .placed-component svg { cursor: default !important; }

/* ── Module show: panel | characteristics, then the rest under the panel ───── */
/* Two rows on purpose. The panel shares row 1 with the characteristics card so
   the two blocks align top and bottom; the caption and the description sit in
   row 2, in the panel's column. Nesting them with the panel would have made the
   left column taller than the card, and the two would never line up. */
.module-show-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 288px;
  grid-template-rows: auto auto;
  gap: 24px;
  margin-bottom: 40px;
}
.module-show-panel { grid-column: 1; grid-row: 1; }
.module-show-cols > .module-specs { grid-column: 2; grid-row: 1; align-self: start; }
.module-show-under-panel { grid-column: 1; grid-row: 2; }

@media (max-width: 1000px) {
  .module-show-cols { grid-template-columns: minmax(0, 1fr); }
  /* Stacked, the description would push the figures far down the page, so the
     card comes first — the numbers are what a reader came for. */
  .module-show-cols > .module-specs { grid-column: 1; grid-row: 2; }
  .module-show-under-panel { grid-row: 3; }
}

/* What matters is that this block, not the module, is what holds a width: it
   always fills its column, and the module is centred on it whatever its size.
   Tying the layout to the panel's width is what went wrong twice — a fixed
   column clipped the wide panels, a flexible one left a 4 HP module floating in
   570px of nothing. min-width:0 lets a wide panel scroll rather than stretch. */
.module-show-panel { min-width: 0; }

/* The frame takes the row's height and aligns with the characteristics card; the
   module is laid inside it at the scale the server rendered it (3.6 px/mm) and
   centred, never stretched.

   Scaling the SVG to the row is what this used to do, and it was wrong twice
   over: it showed the module at a size it does not have, and it showed it
   differently from the editor — where the panel is traced against the reference
   photo. A 3U module is always 128.5mm tall, so the gap to fill was a constant
   30px, and deforming every panel to close it bought nothing.

   Centring is `align-items`, not an auto margin: the partial writes
   `margin: 0 auto` inline for the horizontal axis, which an outside rule cannot
   override — but zero vertical margins leave align-items free to do its work. */
.module-show-panel .module-panel {
  height: 100%;
  align-items: center;
  padding: 13px;
  background-color: var(--pulsers-thumb-bg);
  border: 1px solid var(--pulsers-line);
  border-radius: 2px;
}

/* Millimetres, because that is the measurement that says whether the module
   fits a rack. The scroll hint is hidden until the panel actually overflows —
   announcing a gesture that does nothing is worse than not announcing it. */
.module-show-panel-hint {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin: 10px 0 0;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--pulsers-muted);
}
.module-show-panel-scroll { display: none; color: var(--pulsers-accent); }
/* The attribute sits on the scroller inside the panel block, and the hint is
   further down the grid — `~`, not `+`: making the panel and the card share a
   row moved the caption into its own grid item, so the two stopped being
   adjacent siblings and the hint silently never showed again. */
.module-show-panel:has(.module-panel[data-overflow-start]) ~ .module-show-under-panel .module-show-panel-scroll,
.module-show-panel:has(.module-panel[data-overflow-end]) ~ .module-show-under-panel .module-show-panel-scroll {
  display: inline;
}

/* ── Module panel: scroll affordance ───────────────────────────────────────────
   A wide panel scrolls rather than being clipped, but the native scrollbar stays
   hidden on macOS until the user scrolls, so a panel wider than its column just
   looked cut. The edge fades away while content remains on that side; the
   panel-overflow controller sets the data attributes, since CSS alone can't tell
   whether anything is hidden. Fades only, no layout shift. */
.module-panel[data-overflow-end] {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 56px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 56px), transparent);
}
.module-panel[data-overflow-start] {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 56px);
  mask-image: linear-gradient(to right, transparent, #000 56px);
}
.module-panel[data-overflow-start][data-overflow-end] {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 56px, #000 calc(100% - 56px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 56px, #000 calc(100% - 56px), transparent);
}

/* ── Demo thumbnails grid ──────────────────────────────────────────────────── */
/* Two big tiles per row rather than a strip of stamps: a demo is the point of
   the page for a reader deciding whether they want the module. minmax() gets
   there without a breakpoint — one column when the room for two is gone. */
.demo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 16px; }

/* The stage holds the thumbnail and, once playing, the iframe that replaces it.
   Its ratio is fixed so starting a demo does not shift the rest of the page. */
.demo-tile-stage { position: relative; width: 100%; aspect-ratio: 16 / 9; }
.demo-tile-stage--audio { aspect-ratio: auto; height: 166px; }
.demo-tile-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; border-radius: 6px; }
.demo-tile-stage--audio .demo-tile-frame { position: static; height: 166px; }
.demo-tile-stage[data-playing] .demo-tile { display: none; }
.demo-tile {
  position: absolute; inset: 0; display: block; width: 100%; height: 100%;
  padding: 0; border: 1px solid var(--pulsers-line); border-radius: 6px; overflow: hidden;
  background: var(--pulsers-thumb-bg); cursor: pointer; transition: border-color 120ms ease;
}
.demo-tile:hover, .demo-tile:focus-visible { border-color: var(--pulsers-accent); }
.demo-tile-thumb { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.demo-tile-fallback {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 13px; letter-spacing: 0.14em; color: var(--pulsers-muted);
}
.demo-tile-play { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
.demo-tile-figure { margin: 0; }
.demo-tile-caption {
  margin-top: 8px; color: var(--pulsers-muted); font-family: var(--font-mono); font-size: 12px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── Toolbar: theme and locale, same control in every layout ───────────────── */
.toolbar { display: flex; align-items: center; gap: 8px; }
.toolbar-button {
  display: inline-flex; align-items: center; gap: 7px;
  height: 30px; padding: 0 12px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.08em;
  text-transform: uppercase; white-space: nowrap;
  color: var(--pulsers-muted);
  background: var(--pulsers-surface);
  border: 1px solid var(--pulsers-line);
  border-radius: 2px;
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease;
}
.toolbar-button:hover { color: var(--pulsers-text); border-color: var(--pulsers-faint); }
.toolbar-button:focus-visible { outline: 2px solid var(--pulsers-accent); outline-offset: 2px; }
/* Every control in the bar is one of these, so they share a single height. The
   user button used to be a bare 20px icon next to 30px buttons, which is what
   made the row look misaligned. */
a.toolbar-button { text-decoration: none; }
.toolbar-button--accent {
  color: var(--pulsers-on-accent);
  background: var(--pulsers-accent);
  border-color: var(--pulsers-accent);
  font-weight: 700;
}
.toolbar-button--accent:hover { color: var(--pulsers-on-accent); border-color: var(--pulsers-accent); }

/* The burger is mobile-only. Tailwind's `md:hidden` would fight `.toolbar-button`
   over `display` and win or lose depending on which sheet loads last, so the
   rule lives here where there is nothing to arbitrate. */
.toolbar-button--burger { display: none; }
@media (max-width: 767px) { .toolbar-button--burger { display: inline-flex; } }

/* ── Rack listing: one row per rack ────────────────────────────────────────── */
/* The row wraps rather than squeezing: below its threshold the thumbnail and the
   identity stay on the first line and the figures drop underneath, instead of
   the name being crushed between two blocks — which is what it did on a phone. */
.rack-row {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 13px 15px;
  background: var(--pulsers-surface);
  border: 1px solid var(--pulsers-line);
  border-radius: 2px;
  text-decoration: none;
  transition: border-color 120ms ease;
}
.rack-row:hover { border-color: var(--pulsers-accent); }

/* Framed, so an empty rack still reads as a rack rather than as blank space. */
.rack-row-thumb {
  flex: 0 0 auto; width: 142px; overflow: hidden;
  background: var(--pulsers-thumb-bg);
  border: 1px solid var(--pulsers-line);
  border-radius: 2px;
  padding: 5px;
}

/* How full the rack is — the figure that decides whether another module fits,
   and which the listing showed nowhere although the database held every part of
   it. The dot carries the same reading as the colour, for anyone who cannot use
   the colour alone. */
.rack-row-fill {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 4px 0 0;
  font-family: var(--font-mono); font-size: 11px;
  color: var(--pulsers-ok);
}
.rack-row-fill::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; flex: 0 0 auto;
}
.rack-row-fill--full { color: var(--pulsers-warn); }

/* The same proportion as a length, read without doing the subtraction. */
.rack-row-meter {
  display: block; height: 4px; width: 126px; margin: 6px 0 0 auto;
  background: var(--pulsers-line); border-radius: 1px; overflow: hidden;
}
.rack-row-meter i { display: block; height: 100%; background: var(--pulsers-accent); }

@media (max-width: 640px) {
  .rack-row > div:last-child { flex: 1 1 100%; text-align: left; }
  .rack-row-meter { margin-left: 0; }
}

/* ── Synth listing: what you can play, then what is coming ─────────────────── */
/* Contact form — the reason chips.

   Same shape and same values as FilterChipComponent, which the listings use to
   filter. That one is a link, because filtering is a GET whose state belongs in
   the URL; these are buttons, because choosing a reason navigates nowhere and
   only rewrites the question below. Written here rather than as Tailwind classes
   so the controller can toggle one modifier instead of swapping six utilities. */
.contact-chip {
  font-family: var(--font-mono); font-size: 12px;
  padding: 6px 12px;
  border: 1px solid var(--pulsers-line);
  border-radius: 2px;
  background: var(--pulsers-surface);
  color: var(--pulsers-muted);
  cursor: pointer;
  transition: border-color .15s, color .15s;
}
.contact-chip:hover { border-color: var(--pulsers-accent); color: var(--pulsers-text); }
.contact-chip:focus-visible { outline: 2px solid var(--pulsers-accent); outline-offset: 2px; }
.contact-chip--on {
  background: var(--pulsers-accent);
  border-color: var(--pulsers-accent);
  color: var(--pulsers-on-accent);
}
.contact-chip--on:hover { color: var(--pulsers-on-accent); }

/* Field shapes, shared by every form on the site. The forms used to spell these
   out inline, each with its own radius — 4px on the rack and module creation
   pages, 6px elsewhere — while the system has one, at 2px. */
.form-field {
  width: 100%;
  background: var(--pulsers-field-bg);
  border: 1px solid var(--pulsers-line);
  border-radius: 2px;
  color: var(--pulsers-text);
  font-family: var(--font-mono);
  font-size: 13px;
  padding: 9px 11px;
}
.form-field:focus { outline: none; border-color: var(--pulsers-accent); }
.form-field--multiline { min-height: 150px; resize: vertical; line-height: 1.55; }
/* A field holding two digits should not span the page. Declared here rather
   than with a `w-28` utility, which loses to `.form-field { width: 100% }` —
   same specificity, and this file is loaded last. */
.form-field--narrow { width: 7rem; }

/* Same rule-to-the-right label as the catalogue's result count: it separates two
   groups without a heading that would compete with the page title. */
.synths-label {
  display: flex; align-items: center; gap: 12px; margin: 0 0 13px;
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--pulsers-muted);
}
.synths-label::after { content: ""; flex: 1; height: 1px; background: var(--pulsers-line); }

.synths-announced {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 8px;
}
/* An announced synth has a page too — it states what the instrument is and that
   its panel is not drawn yet. The row is the frame, the link inside it is what
   fills and carries the layout, so the whole tile is clickable. */
.synths-announced li {
  background: var(--pulsers-surface);
  border: 1px solid var(--pulsers-line);
  border-radius: 2px;
  transition: border-color .2s;
}
.synths-announced li:hover { border-color: var(--pulsers-accent); }
.synths-announced-link {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 13px;
  color: inherit; text-decoration: none;
}
.synths-announced-name { display: block; font-family: var(--font-mono); font-size: 12px; font-weight: 700; }
.synths-announced-brand { display: block; font-family: var(--font-mono); font-size: 11px; color: var(--pulsers-muted); }
.synths-announced-state {
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--pulsers-faint);
}

/* Module show — header & body text. Every colour here is a token: the page used
   to paint itself var(--pulsers-ground) on var(--pulsers-text) whatever the theme, which is why the
   characteristics card went white while its values stayed near-white on it. */
.module-show { background-color: var(--pulsers-ground); color: var(--pulsers-text); }
.module-show-inner { max-width: 1080px; margin: 0 auto; padding: 24px 16px; }
/* Graph paper under the content: this is a page about a physical object with
   millimetre dimensions, and the grid says so without taking any room. One
   container carries it, so the ruling stays aligned across the whole body — set
   per block, each background would restart at its own origin. The header stays
   on plain ground above it. */
.module-show-body,
.module-index-body {
  padding: 22px;
  background-image:
    linear-gradient(to right, var(--pulsers-grid) 1px, transparent 1px),
    linear-gradient(to bottom, var(--pulsers-grid) 1px, transparent 1px);
  background-size: 22px 22px;
}
/* The catalogue stacks its own blocks; the module page positions its two
   columns itself, so only this one gets a gap. */
.module-index-body { display: grid; gap: 24px; }

/* A count with a rule running off to the right: it separates the controls from
   the results without a heading that would compete with the page title. */
.module-index-results {
  display: flex; align-items: center; gap: 12px; margin: 0 0 -8px;
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--pulsers-muted);
}
.module-index-results::after { content: ""; flex: 1; height: 1px; background: var(--pulsers-line); }
/* The header carries the same 22px inset as the body below it, so the title and
   the panel frame line up while the ruled area still runs to the container's
   edge — the grid needs room around the content, not around the page. */
.module-show-header {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
  padding: 4px 22px 18px;
  border-bottom: 1px solid var(--pulsers-line);
}
.module-show-name {
  font-family: var(--font-mono); color: var(--pulsers-text);
  font-size: 20px; font-weight: 700; letter-spacing: 0.05em; margin: 0 0 5px;
}
.module-show-meta { font-family: var(--font-mono); color: var(--pulsers-muted); font-size: 12px; margin: 0; }
.module-show-edit {
  font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  background: var(--pulsers-accent); color: var(--pulsers-on-accent);
  border-radius: 2px; padding: 8px 16px; text-decoration: none; white-space: nowrap;
}
.module-show-h2 { font-family: var(--font-mono); color: var(--pulsers-text); font-size: 12px; text-transform: uppercase; letter-spacing: 0.16em; margin: 0; }
.module-show-desc-block { margin-top: 22px; }
.module-show-desc-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.module-show-fallback-badge {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--pulsers-warn); border: 1px solid var(--pulsers-warn);
  border-radius: 2px; padding: 2px 8px;
}
.module-show-desc { color: var(--pulsers-muted); font-size: 13.5px; line-height: 1.7; max-width: 66ch; }
.module-show-manufacturer { margin: 14px 0 0; }
.module-show-link { color: var(--pulsers-accent); font-family: var(--font-mono); font-size: 12px; text-decoration: none; }
.module-show-link:hover { text-decoration: underline; }
.module-show-demos { margin-top: 32px; }
.demo-link { color: var(--pulsers-accent); font-family: var(--font-mono); font-size: 13px; text-decoration: none; word-break: break-all; }

/* ── Shared app modal (module show opened from a rack via turbo-frame) ──────── */
/* The overlay never scrolls the page: it centres a height-capped dialog whose
   OWN content scrolls when it overflows. */
.app-modal-overlay {
  position: fixed; inset: 0; z-index: 90; display: none;
  align-items: center; justify-content: center; padding: 24px 16px;
  background: rgba(0,0,0,.78);
}
.app-modal-overlay.is-open { display: flex; }
.app-modal-dialog {
  position: relative; width: 100%; max-width: 1120px;
  max-height: calc(100vh - 48px); overflow: hidden;
  display: flex; flex-direction: column;
  background: var(--pulsers-ground); border: 1px solid var(--pulsers-line); border-radius: 10px;
}
.app-modal-content { overflow-y: auto; }
.app-modal-close {
  position: absolute; top: 12px; right: 14px; z-index: 2;
  background: transparent; border: none; color: var(--pulsers-muted); font-size: 18px; cursor: pointer; line-height: 1;
}
.app-modal-close:hover { color: var(--pulsers-text); }
