/*
 * Copyright (C) 2004-2026 Metaphonic Labs
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General
 * Public License along with this program; if not, write to the
 * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * style.css -- the page, on whatever it is being read on.
 *
 * Its own file now rather than a <style> block, because there is enough of
 * it to want reading. Two things it is arranged around:
 *
 * A phone is the interesting case, not the awkward one. The whole page is
 * a thing you play, so what a narrow screen gets is the same page with the
 * source boxes folded away and the keys given the room -- not a cut-down
 * one. The keyboard itself takes no width from here at all; main.js counts
 * the octaves that fit and draws them, so the keys are a finger wide on a
 * phone and a hand wide on a desktop rather than the same proportion of
 * two different things.
 *
 * And a finger is not a mouse. `pointer: coarse' is what says so -- not
 * the width, which a laptop in a small window also has -- so the controls
 * grow to something you can hit only where something will be hitting them.
 */

:root
{
    color-scheme: light dark;

    --fg:        #202020;
    --bg:        #ffffff;
    --dim:       #666666;
    --panel:     #f4f4f4;
    --line:      #dcdcdc;
    --key:       #ffffff;
    --key-edge:  #b0b0b0;
    --key-black: #202020;
    --held:      #5050ff;

    /* Something a person should notice but nothing is broken: a patch
       edited and not written. Not the red an error gets. */
    --warn:      #a05000;
    --key-c:     #eaf7fa;
}

/* What the tiler is drawn in. panes.css reads these names and no others,
   with a fallback on each, so it stands alone; this is the page saying
   which of its own colors each one is. Outside the palette blocks on
   purpose -- it maps names to names, and the names on the right are what
   change with the scheme. */
:root
{
    --pane-fg:    var(--fg);
    --pane-bg:    var(--bg);
    --pane-dim:   var(--dim);
    --pane-panel: var(--panel);
    --pane-line:  var(--line);
    --pane-held:  var(--held);
}

@media (prefers-color-scheme: dark)
{
    :root
    {
        --fg:        #dddddd;
        --bg:        #1b1b1b;
        --dim:       #999999;
        --panel:     #262626;
        --line:      #3a3a3a;
        --key:       #d8d8d8;
        --key-edge:  #555555;
        --key-black: #101010;
        --held:      #6a6aff;
        --warn:      #d09040;
        --key-c:     #2b3a3d;
    }
}

body
{
    font: 15px/1.4 system-ui, sans-serif;
    color: var(--fg);
    background: var(--bg);
    max-width: 60em;
    margin: 2em auto;
    padding: 0 1em;
}

h1 { font-size: 1.6em; margin: 0 0 0.4em; }

textarea, pre
{
    font: 13px/1.35 ui-monospace, monospace;
}

textarea
{
    width: 100%;
    height: 26em;
    box-sizing: border-box;
    background: var(--panel);
    color: var(--fg);
    border: 1px solid var(--line);
    padding: 0.5em;
}

pre
{
    background: var(--panel);
    padding: 0.6em 0.8em;
    margin: 0.8em 0;
    overflow-x: auto;
}

#log { max-height: 10em; overflow: auto; }

.row
{
    display: flex;
    gap: 0.6em;
    align-items: center;
    margin: 0.8em 0;
    flex-wrap: wrap;
}

/* The chrome, which is a row with the title in it rather than a title
   with rows under it. */
#chrome h1 { margin: 0; }

/* A mode's own controls, wrapping as a group rather than one at a time:
   a Rewind on a line of its own, away from the Play it belongs with, is
   not a row wrapping usefully. */
.group
{
    display: flex;
    gap: 0.6em;
    align-items: center;
    flex-wrap: wrap;
}

/* What the page has to say, at the end of the strip rather than on a
   line over it: whatever room the controls left, which on a wide screen
   is none and on a narrow one is a line of its own.
 *
   Under `#chrome' and not on its own, because the room page has a
   status of its own that is a paragraph under a heading and is right to
   be one -- it is the first thing that page says, not the last thing
   this one has to add. */
#chrome #status
{
    margin-left: auto;
    color: var(--dim);
}

kbd
{
    border: 1px solid var(--key-edge);
    border-radius: 3px;
    padding: 0 0.3em;
}

[hidden] { display: none !important; }

summary
{
    cursor: pointer;
    padding: 0.3em 0;
    color: var(--dim);
}

details { margin: 0.8em 0; }

/* ---- the piece ---- */

/* The piano roll: a canvasview (below) whose drawing is always exactly
 * its view, so the two rules it adds to that one are a fixed height and
 * no scrolling.
 *
 * Fixed and not `min-height' to a content-sized box, which is the trap
 * .canvasview's own comment describes: the drawing here is sized to the
 * box, so a box sized to the drawing would be a box fitting into its own
 * last answer. And `hidden' rather than a scrollbar because there is
 * nothing to scroll to -- the roll's x axis is time and a scroller
 * around it would be a second, silent answer to where "now" is. */
#roll
{
    height: 180px;
    min-height: 0;
    max-height: none;
    overflow: hidden;
    background: var(--panel);
    margin: 0.8em 0;
}

/* The knobs a piece declared, drawn by panel.js the way the channel's
   parameters are -- same rows, same wrapping, same column pairs. All this
   has to say is that a knob is a knob and gets a little more room than a
   parameter, since a piece declares a handful where a patch declares
   thirty. */
#knobs { margin: 0.8em 0; }

/* One line per channel the piece touches: its number, then what is on it.
   The numbers are a column of their own so the row reads down as a list of
   channels rather than across as a sentence, and monospaced so 1 and 10
   line up under each other. */
#channels { margin: 0.8em 0; }

#channels .row { margin: 0.3em 0; }

#channels .chan
{
    font: 13px ui-monospace, monospace;
    color: var(--dim);
    min-width: 2em;
    text-align: right;
}

/* A channel the piece aimed itself. Nothing to choose, so nothing that
   looks like a control. */
#channels .own { color: var(--dim); }

/* A patch that has been changed since it was read. Beside the menu rather
   than in it: the menu says what was chosen and this says what has happened
   to it since, and a menu row that changed its own text under somebody
   would be the wrong place to say so. */
#channels .edited
{
    margin-left: 0.5em;
    font: 12px ui-monospace, monospace;
    color: var(--warn);
}

#channels .save { margin-left: 0.5em; font-size: 12px; }

/* ---- the sequencer ----
 *
 * One block per track: a heading that says which channel it is heard on
 * and what is on it, and under that the grid's own picture at the pane's
 * full width. The canvas is sized by the script, in both dimensions --
 * its width is what the element gets from here and its height is the
 * grid's own row count -- because the pointer's coordinates and the
 * picture's have to be the same numbers (seqview.js).
 */
#tracks { margin: 0.4em 0; }

.track { margin: 0 0 0.6em; }

.trackhead { margin: 0 0 0.2em; align-items: baseline; }

.trackhead .chan
{
    font: 13px ui-monospace, monospace;
    color: var(--dim);
    min-width: 2em;
    text-align: right;
}

.trackhead .what { color: var(--dim); font-size: 0.9em; }

/* What plays this track. Narrow enough that four of them down a pane do
   not read as a form, wide enough for a drawer's name and a patch's. */
.trackpick { max-width: 18em; font-size: 0.9em; }

.trackgrid
{
    display: block;
    width: 100%;
    border: 1px solid var(--line);
    border-radius: 4px;
    background: #14161a;
    touch-action: none;
    cursor: crosshair;
}

.trackgrid:focus-visible
{
    outline: 2px solid var(--held);
    outline-offset: -2px;
}

#about { margin: 0.4em 0; color: var(--dim); }

#clock { font: 13px ui-monospace, monospace; }

/* Four digits and the spinner, which is all a bpm ever needs: left to
   itself a number input takes the width of a text one and pushes the
   clock off the end of a narrow strip. */
#tempo { width: 5em; }

/* A control that cannot reach this piece still says what it would be.
   Dimmed rather than gone, because a tempo box that appears and
   disappears as pieces load is a strip that jumps. */
#tempolabel:has(#tempo:disabled),
#speedlabel:has(#speed:disabled) { color: var(--dim); }

/* Short, because it shares a strip with everything else, and with its
   reading in a fixed width beside it so that the row does not shuffle
   sideways between 1.00 and 0.85. */
#speed { width: 7em; vertical-align: middle; }

#speedis
{
    display: inline-block;
    width: 3.4em;
    font: 13px ui-monospace, monospace;
}

/* ---- the keyboard ---- */

/* The keys take whatever width there is and are drawn to fit it: the
   viewBox is in key units and the element is 100% wide, so one <svg> is a
   two-octave keyboard on a phone and a four-octave one on a desktop
   without a second set of measurements anywhere. keyboard.js counts the
   octaves that fit and writes the viewBox, height and all -- there is no
   preserveAspectRatio here, and that file says why not.

   touch-action: none, because a finger dragging across the keys is a
   glissando and not a scroll, and the browser's default is to decide that
   for itself after a delay -- which eats the first note of every phrase. */
#keys
{
    display: block;
    width: 100%;
    height: 132px;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
    margin: 0.4em 0;
}

#keys .white { fill: var(--key); stroke: var(--key-edge); stroke-width: 0.02; }
#keys .black { fill: var(--key-black); stroke: var(--key-edge);
               stroke-width: 0.02; }

/* Every C, not just middle C, and faintly: two octaves on a phone is not
   much to find your way around by, and the octave lines are the way. */
#keys .c     { fill: var(--key-c); }
#keys .held  { fill: var(--held); }

/* Outlined, because the key under a note name changes colour when it is
   held and the name has to stay readable on either. paint-order puts the
   stroke behind the fill, which is what makes it a halo rather than a
   thickening. */
#keys text
{
    font-family: system-ui, sans-serif;
    fill: var(--fg);
    stroke: var(--key);
    stroke-width: 0.05;
    paint-order: stroke;
    text-anchor: middle;
    pointer-events: none;
}

#range
{
    font: 13px ui-monospace, monospace;
    color: var(--dim);
    min-width: 7em;
}

.hint { color: var(--dim); font-size: 0.9em; margin: 0.4em 0; }

/* ---- a finger rather than a mouse ---- */

@media (pointer: coarse)
{
    button, select
    {
        min-height: 44px;
        font-size: 16px;
    }

    /* 16px or iOS zooms the page in when the box takes focus, and then
       the page is the wrong size and stays that way. */
    textarea, input { font-size: 16px; }

    #keys { height: 160px; }

    /* A slider a thumb can find, in the knob strip and in the parameter
       panel alike -- both are panel.js's rows now. */
    #knobs input[type="range"], #params input[type="range"] { height: 44px; }
}

/* ---- a narrow screen ---- */

@media (max-width: 40em)
{
    body { margin: 0.8em auto; padding: 0 0.6em; }

    h1 { font-size: 1.3em; }

    textarea { height: 14em; }

    #roll { height: 120px; }

    .hint { display: none; }
}

/* A phone held sideways is the playing position, and it is also the shape
   with no room: everything this page has, stacked, is most of two screens
   at 360 pixels tall. So the keys are pinned to the bottom of the viewport
   instead of taking their turn in the stack -- the roll, the knobs and the
   source scroll past above them and the keys stay under the thumbs.
   Which is what a keyboard is, on a device you hold. */
@media (max-height: 30em) and (orientation: landscape)
{
    body { margin: 0.3em auto; }

    h1, #about, .hint { display: none; }

    #roll { height: 80px; }

    #keyboard
    {
        position: sticky;
        bottom: 0;
        z-index: 1;
        background: var(--bg);
        padding-bottom: 0.3em;

        /* Sticky and opaque, so the page has to be told where it ends. */
        box-shadow: 0 -0.5em 0.5em -0.5em rgb(0 0 0 / 0.3);
    }

    #keyboard .row { margin: 0.3em 0; }

    #keys { height: 38vh; margin: 0.2em 0; }
}

/* ---- the room page ---- */

.dim { color: var(--dim); font-weight: normal; }

.peers { display: flex; gap: 0.8em; flex-wrap: wrap; }

.peers .peer
{
    background: var(--panel);
    border: 1px solid var(--line);
    padding: 0.2em 0.6em;
    border-radius: 0.3em;
}

.peers .peer.me { border-color: var(--held); }
.peers .path { color: var(--dim); font-size: 0.85em; margin-left: 0.4em; }

.tabs { display: flex; gap: 0.3em; flex-wrap: wrap; }

.tab
{
    background: var(--panel);
    border: 1px solid var(--line);
    padding: 0.2em 0.7em;
    font: inherit;
    color: var(--fg);
}

.tab.active { border-color: var(--held); font-weight: bold; }

#editor .cm-editor
{
    height: 26em;
    border: 1px solid var(--line);
    background: var(--panel);
    font: 13px/1.35 ui-monospace, monospace;
}

#editor .cm-scroller { overflow: auto; }

#numbers { min-height: 8em; }

/*
 * A canvas whose drawing is done in wasm (canvasview.js). The element is
 * as big as the drawing and this scrolls it, which is the shape a Gtk
 * scrolled window gives the same canvas on the desktop; the height is a
 * view's worth, so a piece with twenty chains does not push the keyboard
 * off the screen.
 */
.canvasview {
    overflow: auto;

    /* A height of its own, which is the whole point of a viewport: the
       box used to be as tall as whatever was drawn in it, so a canvas
       that scaled itself to fit the box was fitting into its own last
       answer -- each redraw a little smaller than the one before. The
       minimum stops that; the maximum stops a tall drawing from pushing
       the keyboard off the screen. */
    min-height: 16rem;
    max-height: 60vh;
    border: 1px solid var(--line);
    border-radius: 4px;
    background: #14161a;
}

.canvasview canvas {
    display: block;
    touch-action: none;         /* a drag paints; it does not pan */
}

.canvasview canvas:focus-visible {
    outline: 2px solid var(--held);
    outline-offset: -2px;
}

/*
 * The instrument's parameters: a row per control, wrapping into as many
 * columns as the width allows.
 *
 * A flow rather than a fixed column count, for the reason the desktop's
 * panel uses a FlowBox (src/gui/PanelView.cpp): a count guessed from how
 * many rows there are is a guess at the width, made before anything has
 * been given one, and it is wrong in both directions. Each row has a
 * minimum of its own, the wrapping falls out of how many of those fit, and
 * a narrow window gets one column rather than sliders squeezed to a nub.
 */
#params { margin: 0.5rem 0; font-size: 0.9rem; }

/* One grid for the whole block, and each row a subgrid of it.
 *
 * Not a grid per row. A row that sizes its own name column starts its
 * slider wherever its own label happens to end, so a panel of them is a
 * ragged left edge and no columns at all -- which is what the desktop's two
 * SizeGroups exist to stop (src/gui/PanelView.h). `max-content' over a
 * subgrid is the same answer by the same means: the width is measured off
 * the widest name actually there rather than guessed at in rem.
 *
 * Column *pairs*, because that is what a name and a control are, and how
 * many pairs there are is the wrapping. A media query rather than auto-fill:
 * auto-fill cannot repeat a pair and keep the subgrid, and a panel is tens
 * of rows, so two widths and a phone is the whole of it.
 *
 * Each block is its own grid, so a group's names line up with each other and
 * not with the loose rows above them. The desktop's size groups do cross its
 * expanders; CSS has no way to make two grids agree, and the alternative --
 * a width in pixels chosen here -- is the guess this is avoiding. */
.panelrows
{
    display: grid;
    grid-template-columns: max-content minmax(12rem, 1fr);
    gap: 0.3rem 1.5rem;
    align-items: center;
}

@media (min-width: 60em)
{
    .panelrows
    {
        grid-template-columns: max-content minmax(12rem, 1fr)
                               max-content minmax(12rem, 1fr);
    }
}

@media (min-width: 100em)
{
    .panelrows
    {
        grid-template-columns: max-content minmax(12rem, 1fr)
                               max-content minmax(12rem, 1fr)
                               max-content minmax(12rem, 1fr);
    }
}

.panelrow
{
    display: grid;
    grid-template-columns: subgrid;
    grid-column: span 2;
    align-items: center;
    gap: 0.5rem;
}

/* Right-aligned, so the names make a column down the panel rather than
   trailing off into the sliders. */
.panelrow label
{
    text-align: right;
    white-space: nowrap;
}

/* The slider and its number box, which share the row's second column. */
.panelcontrol
{
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

/* A slider narrower than this is not draggable in any useful way -- the
   handle is most of it -- and asking for the width is also what tells the
   grid how much a row costs, so it is the number the wrapping is decided
   by. The same 140 pixels the desktop's panel asks for. */
.panelcontrol input[type="range"]
{
    flex: 1 1 140px;
    min-width: 140px;
}

.panelrow input[type="number"], .panelrow .value
{
    font: 13px ui-monospace, monospace;
    text-align: right;
}

/* A group -- an envelope's four controls, a filter's two -- as its own
   foldable block, open to begin with: a parameter you cannot see is a
   parameter you will not remember the patch has. */
.panelgroup { margin: 0.5rem 0; }

.panelgroup > summary
{
    cursor: pointer;
    font-weight: 600;
}

@media (max-width: 30em)
{
    /* Label over control: a name column and a slider in a phone's width
       leave the slider too short to aim at. One column of the outer grid
       and one row of the subgrid each, so the name sits above the thing it
       names. */
    .panelrows { grid-template-columns: 1fr; }

    .panelrow
    {
        grid-column: span 1;
        grid-template-columns: subgrid;
        grid-template-rows: auto auto;
        gap: 0.1rem;
        margin-bottom: 0.4rem;
    }

    .panelrow label { text-align: left; }
    .panelrow > * { grid-column: 1; }
}

/*
 * The node editor's params panel: a row per arg, wrapping into columns on
 * anything wider than a phone.
 */
.params {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem 1rem;
    margin-top: 0.5rem;
}

.paramrow {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    font-size: 0.9rem;
}

.paramrow input {
    width: 7rem;
}

/* What a parameter is when it is not a number to type in: an output the
   plugin writes, or a value something else drives. */
.paramwhat {
    color: var(--dim);
    font-style: italic;
}

/*
 * A little menu or panel that sits over the page beside whatever asked
 * for it: the node canvas's port menu, and the composer canvas's params.
 * Placed by the script, which is the only thing that knows where the
 * canvas put the thing it belongs to.
 */
.popover {
    position: absolute;
    z-index: 10;
    min-width: 12rem;
    max-width: 22rem;
    padding: 0.4rem;
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--panel);
    box-shadow: 0 4px 16px #0006;
    font-size: 0.9rem;
}

.popover button {
    display: block;
    width: 100%;
    margin: 0.1rem 0;
    text-align: left;
}

.popover .menutitle {
    display: block;
    padding: 0.1rem 0.3rem 0.3rem;
    color: var(--dim);
    font-family: monospace;
}

/* The capture meter beside the Live in button. Monospace because it is ten
   characters of bar and a number, and a proportional font makes the bar
   change width as it fills. */
.level {
    font-family: monospace;
    white-space: pre;
}

/*
 * ---- what this page puts in a pane ----
 *
 * Everything below applies only with the tiler on, and every rule in it
 * names something of this page's: which of these boxes takes the room a
 * pane has, and which keeps the height it asks for. panes.css cannot
 * answer that -- a layout knows it has a box to fill and not what is in
 * it -- so the answer is here, beside the rules that lay the same boxes
 * out as a document.
 */

body.tiled h1 { font-size: 1.2em; margin: 0 0 0.2em; }
body.tiled #about { margin: 0.2em 0; }
body.tiled .row { margin: 0.4em 0; }

/* The room page keeps everything but the join row in a section of its
   own, so the column goes through it. */
body.tiled #roompanel
{
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* The boxes that are meant to take the room there is. The canvas view's
   maximum goes with them: 60vh is a share of the window, and in a pane
   the share that matters is the pane's. */
body.tiled .panebody .canvasview
{
    flex: 1 1 8rem;
    min-height: 6rem;
    max-height: none;
}

body.tiled .panebody textarea
{
    flex: 1 1 8rem;
    height: auto;
    min-height: 4em;
}

body.tiled .panebody #roll { flex: 1 1 auto; min-height: 4rem; }

body.tiled .panebody #editor { flex: 1 1 8rem; min-height: 0; }
body.tiled .panebody #editor .cm-editor { height: 100%; }

/* And the ones that are not: a row of controls is as tall as its
   controls, and the keys say how tall they are themselves. */
body.tiled .panebody > .row,
body.tiled .panebody .row,
body.tiled .panebody .hint,
body.tiled .panebody #keys { flex: 0 0 auto; }

/*
 * A panel wraps into as many column pairs as there is room for, and in a
 * pane the room there is is the pane's rather than the window's.
 *
 * style.css asks the window, because in a document those are the same
 * question. Tiled they are not, and a panel laid out three pairs wide in
 * a 300-pixel pane is the answer to the wrong one -- names over their own
 * sliders and a row that runs off the edge. So the same wrapping again,
 * asked of the box the rows are actually in. It is the desktop's FlowBox
 * either way (src/gui/PanelView.cpp): how many fit, rather than how many
 * were guessed at.
 */
body.tiled .panelrows
{
    grid-template-columns: max-content minmax(12rem, 1fr);
}

@container (min-width: 44em)
{
    body.tiled .panelrows
    {
        grid-template-columns: max-content minmax(12rem, 1fr)
                               max-content minmax(12rem, 1fr);
    }
}

@container (min-width: 70em)
{
    body.tiled .panelrows
    {
        grid-template-columns: max-content minmax(12rem, 1fr)
                               max-content minmax(12rem, 1fr)
                               max-content minmax(12rem, 1fr);
    }
}

/* And a pane narrow enough that a name column would leave the slider too
   short to aim at: the name over the thing it names, as a phone gets. */
@container (max-width: 26em)
{
    body.tiled .panelrows { grid-template-columns: 1fr; }

    body.tiled .panelrow
    {
        grid-column: span 1;
        grid-template-columns: subgrid;
        grid-template-rows: auto auto;
        gap: 0.1rem;
        margin-bottom: 0.4rem;
    }

    body.tiled .panelrow label { text-align: left; }
    body.tiled .panelrow > * { grid-column: 1; }
}
