/* Copyright 2026 G&G Technologies S.r.l. — SPDX-License-Identifier: Apache-2.0 */

/* The palette is redeclared here rather than pulled from assets/site.css. That sheet is generated
   by the site build and versioned with a query string that changes on every rebuild: an app that
   leans on it breaks later, when nobody is looking. Five values copied are the cheaper mistake.
   When a second app exists this file moves to _lib/ and the duplication ends. */

:root {
  color-scheme: dark;
  --bg: #0d1220;
  --panel: #172136;
  --panel-2: #1e2a43;
  --border: rgba(255, 255, 255, 0.09);
  --border-strong: rgba(255, 255, 255, 0.17);
  --text: #eef1f8;
  --muted: #aab3c9;
  --faint: #8f98ad;
  --trace: #6b7690;
  --accent: #34d399;
  --accent-text: #6ee7b7;
  --accent-soft: rgba(52, 211, 153, 0.10);
  --btn-text: #05221a;
  --radius: 16px;
  --radius-sm: 8px;
  --font: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-display: ui-serif, Georgia, 'Times New Roman', Times, serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

html[data-theme="light"] {
  color-scheme: light;
  --bg: #f6f8fc;
  --panel: #ffffff;
  --panel-2: #eef2fa;
  --border: rgba(13, 18, 32, 0.10);
  --border-strong: rgba(13, 18, 32, 0.18);
  --text: #0d1220;
  --muted: #4a5266;
  --faint: #5b6377;
  --trace: #6d7689;
  --accent: #059669;
  --accent-text: #047857;
  --accent-soft: rgba(5, 150, 105, 0.10);
  --btn-text: #ffffff;
}

@media (prefers-color-scheme: light) {
  html:not([data-theme]) {
    color-scheme: light;
    --bg: #f6f8fc;
    --panel: #ffffff;
    --panel-2: #eef2fa;
    --border: rgba(13, 18, 32, 0.10);
    --border-strong: rgba(13, 18, 32, 0.18);
    --text: #0d1220;
    --muted: #4a5266;
    --faint: #5b6377;
    --trace: #6d7689;
    --accent: #059669;
    --accent-text: #047857;
    --accent-soft: rgba(5, 150, 105, 0.10);
    --btn-text: #ffffff;
  }
}

* { box-sizing: border-box; }

/* An author rule beats the browser's own `[hidden] { display: none }`, so a section styled with
   `display: flex` stays on screen while carrying the attribute. That is how the selection bar of a
   file nobody had opened yet ended up under the drop area on the very first run. */
[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.5;
  display: flex;
  flex-direction: column;
  -webkit-text-size-adjust: 100%;
}

/* The stretch has to be handed down: `flex: 1` on the drop area does nothing while its parent is
   an ordinary block, and the footer floats up under the text instead of sitting at the bottom. */
main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 6px 11px;
  cursor: pointer;
}

button:hover:not(:disabled) { border-color: var(--border-strong); }

/* A control that cannot do anything has to look like it. Several already switched themselves off —
   zooming out with the whole file on screen, playing something that has nowhere to scroll — and
   without this rule they went on looking exactly as clickable as the rest, so the only feedback
   for pressing them was that nothing happened. Kept hoverable, because the tooltip explains why. */
button:disabled, input:disabled {
  opacity: 0.38;
  cursor: default;
}

/* Focus has to be visible: the selection can be driven entirely from the keyboard, and an
   invisible focus ring makes that path unusable rather than merely awkward. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The stack is a control, so it takes focus and has to show it. The ring is drawn inside its own
   edges: offset outwards it ran along the whole width in the accent colour and read as part of the
   selection rather than as "this has the keyboard". */
.rows:focus-visible {
  outline-offset: -2px;
  outline-style: dashed;
}

.primary {
  background: var(--accent);
  color: var(--btn-text);
  border-color: transparent;
  font-weight: 500;
}

.ghost { border-color: transparent; padding: 6px 8px; }

.ghost.small { color: var(--faint); font-size: 12.5px; padding: 5px 8px; }

.ghost.small:hover:not(:disabled) { color: var(--text); background: rgba(127, 140, 170, 0.10); }

.ghost.icon { display: inline-flex; align-items: center; padding: 5px 7px; }

/* The export is the goal of the app, but the accent already means "selected" everywhere else. An
   outline says "this is the action" without teaching a second meaning for the same green. */
.accent {
  border-color: var(--accent);
  color: var(--accent-text);
  font-weight: 500;
}

.accent:hover { background: var(--accent-soft); border-color: var(--accent); }

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

/* ---------------------------------------------------------------------------------------------
   bars
   --------------------------------------------------------------------------------------------- */

/* Two altitudes. The app bar is chrome: it never changes, so it is small, grey and low-contrast.
   The document bar is the one being used, so it gets the panel background and the only strong
   text on the screen. Before, both lived on one row and read as equally important. */

.appbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 14px;
  border-bottom: 1px solid var(--border);
  min-height: 34px;
}

.appbar .brand {
  font-family: var(--font-display);
  font-size: 13.5px;
  color: var(--muted);
}

.appbar .meta { font-size: 11.5px; color: var(--faint); }

.docbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--panel);
  flex-wrap: wrap;
}

.doc-mark { width: 16px; height: 16px; color: var(--faint); flex: none; }

.docbar .file { font-size: 13.5px; color: var(--text); }

.docbar .meta { font-size: 11.5px; color: var(--faint); }

.docbar .views { margin-left: 4px; }

.spacer { flex: 1 1 auto; }

.hint {
  width: 100%;
  font-size: 12px;
  color: var(--faint);
  border-bottom: 1px solid var(--border);
  padding: 7px 14px;
  cursor: pointer;
}

/* ---------------------------------------------------------------------------------------------
   drop
   --------------------------------------------------------------------------------------------- */

/* The whole window is the target, and there is no dashed box at rest: the site has no dashed
   boxes. The dash appears only while a file is over the window, where it does its actual job —
   saying "you can let go now" instead of decorating. */

/* `flex: 1` while it is alone, `0 1 auto` once the history is under it. Left growing, the drop area
   pushed the list to the bottom of the window with a hand's width of nothing in between, and the
   two read as unrelated panels rather than as one opening screen. */
.drop:has(+ .history:not([hidden])) { flex: 0 1 auto; padding-bottom: 20px; }

.drop {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  text-align: center;
  padding: 32px 20px;
}

.drop h1 {
  font-family: var(--font-display);
  font-size: clamp(20px, 4vw, 26px);
  font-weight: 400;
  margin: 10px 0 2px;
}

.drop p { margin: 0; color: var(--muted); font-size: 14px; }

.drop .small { color: var(--faint); font-size: 12.5px; margin-top: 14px; }

.drop .mark { width: 26px; height: 26px; color: var(--accent); }

.linkish {
  background: none;
  border: 0;
  padding: 0;
  color: var(--accent-text);
  text-decoration: underline;
  cursor: pointer;
}

body.dragging .drop::after {
  content: "";
  position: absolute;
  inset: 10px;
  border: 2px dashed var(--accent);
  border-radius: var(--radius);
  background: var(--accent-soft);
  pointer-events: none;
}

body.dragging .drop { position: relative; }

/* ---------------------------------------------------------------------------------------------
   channels
   --------------------------------------------------------------------------------------------- */

/* The three columns are declared once, here, because four things have to agree on them: the
   channel rows, the time axis, the overlay carrying the selection handles, and the code that turns
   a pointer position into a row index. They did not agree on the first run, and the handles landed
   a hundred pixels away from the band they were supposed to bound. */
.sheet {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  --col-name: 236px;
  --pad: 14px;
  --gap: 12px;
}

/* What the app could not use. Not an error and not styled as one: a file of records has more text
   than numbers, and the app has to say so rather than drop eleven columns without a word. */
.notice {
  margin: 0;
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  color: var(--faint);
}

.notice b { color: var(--muted); font-weight: 500; }

/* The stack takes the room it is given, and the strips share it. A ceiling on each row seemed
   prudent and left a hole under two channels on a tall window; the floor is what actually matters,
   because below it a trace is a scribble. Past that many channels, the stack scrolls. */
.rows {
  position: relative;
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
}

/* Name above, statistics under it, the trace beside both. The statistics used to sit at the far
   right: on a wide window the eye travelled the whole screen to tie a channel to its numbers. */
.row {
  display: grid;
  grid-template-columns: var(--col-name) minmax(0, 1fr);
  /* The second track takes the slack, so the name and its numbers stay together at the top of the
     row instead of drifting apart as the row grows. Centred in two auto tracks they ended up
     eighty pixels away from each other on a tall strip. */
  grid-template-rows: auto 1fr;
  gap: 2px var(--gap);
  padding: 6px var(--pad);
  border-bottom: 1px solid var(--border);
  flex: 1 1 auto;
  min-height: 56px;
}

.row .name { grid-area: 1 / 1; align-self: start; }

.row .stats { grid-area: 2 / 1; align-self: start; }

.row .plot { grid-area: 1 / 2 / 3 / 3; align-self: center; height: 100%; }

.row.off .strip, .row.off .stats { display: none; }

.row.off .name { color: var(--muted); }

/* The whole label is the switch. A 13px square met no target-size guideline at all — WCAG 2.2
   asks for 24 as an absolute minimum and a finger wants 44 — and it looked like a colour legend
   while being a control. Now the eye is the control's mark and the row is its target. */
.name {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  min-height: 32px;
  width: 100%;
  padding: 4px 6px 4px 0;
  border: 0;
  background: none;
  border-radius: var(--radius-sm);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  color: var(--text);
}

.name:hover { background: rgba(127, 140, 170, 0.10); }

.name .eye { width: 17px; height: 17px; flex: none; color: var(--accent); }

.row.off .name { color: var(--muted); }

.row.off .name .eye { color: var(--faint); }

.name span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.stats {
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
  padding-left: 25px;
}

.stats b { color: var(--muted); font-weight: 500; }

/* While the pointer is over the stack the statistics give way to the value under it. Reading a
   number off a trace is what a scope is for, and the app had no way to do it. */
.stats.reading { color: var(--accent-text); }

.stats.reading b { color: var(--accent-text); font-weight: 600; }

.plot { position: relative; min-width: 0; }

.strip { display: block; width: 100%; height: 100%; min-height: 40px; }

.strip .trace { fill: none; stroke: var(--trace); stroke-width: 1.6; }

.strip .trace.picked { stroke: var(--accent); stroke-width: 2; }

.strip .guide { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; }

.hidden-note { font-size: 12px; color: var(--faint); }

/* The two handles sit above the whole stack, so the selection reads as one range across every
   channel rather than as a coincidence repeated on each strip. */
/* The right edge is the padding and nothing else. It used to also subtract a column of statistics,
   from back when those sat at the far right of the row; they moved under the channel name and this
   did not follow. Nothing broke loudly — clicking still landed on the right sample, because that is
   measured on .plot — but the band and its two handles were drawn in a space 208px narrower than
   the trace they belong to: on the example the coloured stretch ran from 601 to 903 while the
   handles stood at 551 and 797. Two coordinate systems for one range is the mistake this whole
   block of variables exists to prevent. */
.overlay {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--pad) + var(--col-name) + var(--gap));
  right: var(--pad);
  pointer-events: none;
}

.overlay .band {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--accent-soft);
}

/* The cursor is a hairline, not a handle: it says "here is where you are reading", and must not
   be mistaken for one of the two edges of the selection. */
.cursor {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--accent-text);
  opacity: 0.55;
  pointer-events: none;
}

.handle {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: var(--accent);
}

/* ---------------------------------------------------------------------------------------------
   history — the files opened before, on the opening screen only
   --------------------------------------------------------------------------------------------- */

/* The width of the drop area above it, so the two read as one column and not as two panels that
   happen to be stacked. */
.history {
  width: min(680px, 100%);
  margin: 0 auto 28px;
  padding: 0 20px;
}

.history-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }

.history-head h2 { font-size: 0.95rem; font-weight: 600; margin: 0; }

/* `.small` is declared under `.drop`, so out here the note came out at body size and outshouted
   the heading above it. Same values, not a new size. */
.history .small { color: var(--faint); font-size: 12.5px; margin: 0; }

.history-list {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.history-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}

.history-row:last-child { border-bottom: 0; }

/* The name takes the room and the numbers keep theirs: a long name should be cut, not push the
   figures off the end of the row. */
.history-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.history-meta {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

.history-empty { padding: 12px; font-size: 13px; color: var(--faint); }

/* ---------------------------------------------------------------------------------------------
   scrub — where you are in the file, and how much of it you are seeing
   --------------------------------------------------------------------------------------------- */

/* Controls, then the track, then how much of the file is on screen.
   The track lined up with the plot column while there were three controls, and that was worth
   having. With seven it is not a choice any more: aligned, the track would have been shorter than
   the row of buttons beside it. The extent goes last so that its width changing — "40 di 900.000"
   is wider than "tutto" — moves nothing but itself. */
.scrub {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--gap);
  padding: 8px var(--pad);
  border-top: 1px solid var(--border);
}

.scrub-tools { display: flex; align-items: center; gap: 6px; min-width: 0; }

/* How much of the file is on screen, in words. The thumb says the same thing in a shape; a number
   is what you repeat to somebody else. */
.scrub-extent {
  font-size: 11px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-align: right;
}

/* The playing state is on the icon, not on a colour: two shapes that mean start and stop are read
   the same by everybody, and the button keeps the same size so the row does not shuffle. */
#play .i-stop { display: none; }

#play[aria-pressed="true"] .i-play { display: none; }

#play[aria-pressed="true"] .i-stop { display: inline; color: var(--accent-text); }

.scrub-speed {
  width: 92px;
  accent-color: var(--accent);
  cursor: pointer;
}

.scrub-speed:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Fixed width and tabular figures: the label sits between the slider and the track, so a number
   that changes width would nudge the track left and right as you drag. */
.scrub-rate {
  min-width: 11ch;                      /* «×0,71 reale», the widest it gets */
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The track is the whole file and the thumb is the window. Thin, because it is a place indicator
   first and a control second: at full zoom out the thumb fills it and there is nothing to do. */
.scrub-track {
  position: relative;
  height: 22px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border);
  cursor: pointer;
  touch-action: none;                   /* the drag is ours; without this the page pans instead */
}

.scrub-track:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.scrub-thumb {
  position: absolute;
  top: 2px;
  bottom: 2px;
  border-radius: 999px;
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  min-width: 10px;                      /* on an hour of ECG the window would otherwise vanish */
  pointer-events: none;
}

/* Nothing to scroll: the control stays put rather than disappearing, because a row that comes and
   goes makes the whole stack jump by its own height every time you zoom out. */
.scrub.whole .scrub-track { cursor: default; }

.scrub.whole .scrub-thumb { background: rgba(255, 255, 255, 0.05); border-color: var(--border); }

.axis {
  display: grid;
  grid-template-columns: var(--col-name) minmax(0, 1fr);
  align-items: center;
  gap: var(--gap);
  padding: 7px var(--pad);
  border-top: 1px solid var(--border);
  font-size: 11px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

.axis-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.axis-line { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-width: 0; }

.axis-sel { display: flex; align-items: center; gap: 8px; }

.axis-sel strong { color: var(--accent-text); font-weight: 500; }

/* In the table there is no time axis to label, but the range still applies: the ends go, the
   selection stays. */
.axis.no-ticks .tick { visibility: hidden; }

/* ---------------------------------------------------------------------------------------------
   table
   --------------------------------------------------------------------------------------------- */

/* The scroller is given the height a hundred thousand rows would take, and about forty are drawn
   inside it. Nested scrolling, which the app avoids everywhere else, is the point here: the
   headings have to stay put while the body moves under them. */

.views { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }

.views button {
  border: 0;
  border-radius: 0;
  padding: 6px 12px;
  font-size: 13px;
  color: var(--muted);
}

.views button[aria-pressed="true"] { background: var(--accent); color: var(--btn-text); font-weight: 500; }

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

.tv-head {
  position: sticky;
  top: 0;
  z-index: 1;
  border-bottom: 1px solid var(--border-strong);
  background: var(--panel);
}

.tv-scroll { flex: 1 1 auto; overflow: auto; min-height: 0; }

.tv-spacer { position: relative; }

.tv-rows { position: absolute; top: 0; left: 0; right: 0; }

.tv-row {
  display: grid;
  grid-template-columns: var(--tv-cols);
  align-items: center;
  gap: 0 14px;
  height: 30px;
  padding: 0 14px;
  font-size: 12.5px;
  border-bottom: 1px solid var(--border);
}

.tv-head .tv-row { border-bottom: 0; color: var(--faint); font-weight: 500; height: 34px; }

.tv-row > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tv-index { color: var(--faint); text-align: right; font-variant-numeric: tabular-nums; }

.tv-num { text-align: right; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------------------------
   error
   --------------------------------------------------------------------------------------------- */

.error {
  margin: 24px auto;
  max-width: 520px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  padding: 18px 20px;
}

.error h2 { font-family: var(--font-display); font-weight: 400; font-size: 18px; margin: 0 0 6px; }

.error p { margin: 0 0 14px; color: var(--muted); font-size: 14px; }

/* ---------------------------------------------------------------------------------------------
   foot
   --------------------------------------------------------------------------------------------- */

.foot {
  display: flex;
  gap: 14px;
  align-items: center;
  padding: 10px 14px;
  border-top: 1px solid var(--border);
  font-size: 12px;
  color: var(--faint);
  flex-wrap: wrap;
}

.foot a { color: var(--faint); }

@media (max-width: 760px) {
  /* One column: name and statistics on their own lines, the trace under them. Reusing the wide
     grid here put the statistics on top of the trace and pushed the axis under the footer. */
  .sheet { --col-name: 0px; --gap: 0px; }
  .row {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto;
    gap: 2px;
    min-height: 120px;
  }
  .row .name { grid-area: auto; }
  .row .stats { grid-area: auto; padding-left: 0; }
  .row .plot { grid-area: auto; min-height: 56px; }
  .axis { grid-template-columns: minmax(0, 1fr); }
  .axis .ticks { width: 100%; }
  /* Stacked, not squeezed: the controls need a finger-sized target and the track needs the width.
     Side by side on a phone the track ends up shorter than the thumb it has to hold. */
  .scrub { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  .scrub-track { height: 28px; }
  .scrub-tools { flex-wrap: wrap; }
  .scrub-extent { text-align: left; }
  .stats { padding-left: 0; }
  .bar { gap: 8px; }
  .sep { display: none; }
}
