/*
 * Diminuendo — "manuscript".
 *
 * Understated on purpose. This is a utility, and the thing that earns trust is
 * the before/after crop on the result screen, not the chrome around it. Every
 * decision here is trying to stay out of the way of that crop.
 *
 * No build step, no framework, no webfonts. The type is a system serif stack
 * for the wordmark and headings and system sans for everything else: PRODUCT.md
 * asked for self-hosted fonts rather than CDN ones, and the stack that ships
 * with the machine is the same idea taken further — nothing to download,
 * nothing to break offline, and no bytes added to a 584 KB payload whose size
 * is part of the pitch.
 *
 * All styling lives here rather than in style attributes, which is what lets
 * _headers drop 'unsafe-inline' from style-src.
 */

:root {
  /* Paper and ink. The accent is the colour of a pencil marking in a score. */
  --paper: #faf7f0;
  --surface: #fffdf8;
  --ink: #1a1a1a;
  --ink-muted: #5f5a52;
  --rule: #ddd6c8;
  --rule-strong: #c4bba8;
  --accent: #a2342a;
  --accent-soft: #f2e4e1;
  --good: #2f6b4f;
  --shadow: 0 1px 2px rgb(26 26 26 / 6%), 0 6px 20px rgb(26 26 26 / 6%);

  --serif: 'Iowan Old Style', 'Palatino Linotype', Palatino, 'Book Antiqua',
           Georgia, 'Times New Roman', serif;
  --sans: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --radius: 6px;
}

/*
 * Dark mode follows the system. There is no in-app toggle, and adding one would
 * mean storing a preference — this app currently writes two keys to
 * localStorage, both of them measurements it needs to avoid crashing, and "no
 * accounts, no cookies" reads better the fewer exceptions it has.
 */
@media (prefers-color-scheme: dark) {
  :root {
    --paper: #171614;
    --surface: #201e1b;
    --ink: #ece7dc;
    --ink-muted: #9d968a;
    --rule: #34302a;
    --rule-strong: #4b463e;
    --accent: #e08c7c;
    --accent-soft: #33231f;
    --good: #7fbf9b;
    --shadow: 0 1px 2px rgb(0 0 0 / 40%), 0 6px 20px rgb(0 0 0 / 30%);
  }
}

* { box-sizing: border-box; }

/*
 * `hidden` is a UA-stylesheet `display: none`, which any author `display` beats.
 * Every component here that sets its own display — buttons, the screens, the
 * queue — therefore ignored it, and the batch result kept showing a Download
 * button it had explicitly hidden. One rule instead of remembering per class.
 */
[hidden] { display: none !important; }

html { color-scheme: light dark; }

body {
  margin: 0;
  padding: 0;
  background: var(--paper);
  color: var(--ink);
  font: 16px/1.6 var(--sans);
  -webkit-text-size-adjust: 100%;
}

h1, h2, h3 {
  font-family: var(--serif);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  margin: 0 0 0.5em;
}

h1 { font-size: clamp(1.9rem, 1.4rem + 2.2vw, 2.9rem); }
h2 { font-size: 1.35rem; }
h3 { font-size: 1.05rem; }

p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

a { color: var(--accent); text-underline-offset: 2px; }
a:hover { text-decoration-thickness: 2px; }

/* Everything focusable gets the same visible ring, including the drop zone. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

 /*
  * padding-inline rather than the padding shorthand, so a section can set its
  * own vertical rhythm without either of them clobbering the other. As
  * shorthands they collided invisibly: `.wrap` out-specifies a bare `main`, so
  * `main`'s vertical padding never applied, while `.masthead` came later than
  * `.wrap` and zeroed the gutter instead.
  */
.wrap {
  width: 100%;
  max-width: 46rem;
  margin: 0 auto;
  padding-inline: 1.25rem;
}

/* ---------------------------------------------------------------- masthead */

.masthead {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding-block: 1.5rem 0;
}

/*
 * The mark is icon.svg used as a mask rather than as an <img>, so there is one
 * copy of it — the favicon, the masthead and both themes all read the same
 * file — and so it can take its colour from the theme. An <img> could not:
 * currentColor does not cross into an externally referenced document.
 */
.mark {
  width: 30px;
  height: 30px;
  flex: none;
  background: var(--accent);
  -webkit-mask: url(./icon.svg) center / contain no-repeat;
  mask: url(./icon.svg) center / contain no-repeat;
}

.wordmark {
  font-family: var(--serif);
  font-size: 1.3rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  margin: 0;
}

/*
 * The one branded device: five staff lines as a rule. Says "sheet music"
 * without a clip-art clef, and it is a background image rather than five
 * elements so it can sit on anything.
 */
.stave {
  height: 13px;
  border: 0;
  /* Centred explicitly. This element also carries .wrap, whose `margin: 0 auto`
     a `margin: 2.5rem 0` here would silently override — which is exactly what
     it did, leaving the one branded element on the page hanging off the left. */
  margin: 2.5rem auto;
  background-image: repeating-linear-gradient(
    to bottom,
    var(--rule) 0 1px,
    transparent 1px 3px
  );
  background-size: 100% 13px;
  background-repeat: no-repeat;
}

/* ------------------------------------------------------------------ layout */

main { padding-block: 2rem; }

.lede {
  font-size: 1.15rem;
  color: var(--ink-muted);
  margin-bottom: 2rem;
}

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

.drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  min-height: 15rem;
  padding: 2.5rem 1.5rem;
  text-align: center;
  background: var(--surface);
  border: 2px dashed var(--rule-strong);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color 120ms, background-color 120ms;
}

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

/* Set while a file is over the window, so the target is the whole zone rather
   than whatever pixel the pointer happens to be on. */
.drop.is-over {
  border-color: var(--accent);
  border-style: solid;
  background: var(--accent-soft);
}

.drop-title {
  font-family: var(--serif);
  font-size: 1.3rem;
  font-weight: 600;
}

.drop-sub { color: var(--ink-muted); font-size: 0.95rem; }

.drop-privacy {
  margin-top: 1rem;
  font-size: 0.9rem;
  color: var(--ink-muted);
}

.drop-privacy strong { color: var(--ink); font-weight: 600; }

/* The file input is the real control; the drop zone is its label. Hidden
   visually but reachable, so keyboard and screen-reader users get the ordinary
   file picker rather than a div pretending to be one. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------------------------------------------------------------- controls */

.btn {
  font: inherit;
  font-weight: 600;
  padding: 0.65rem 1.25rem;
  min-height: 44px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

.btn:hover { filter: brightness(1.08); }
.btn:disabled { opacity: 0.5; cursor: default; filter: none; }

.btn-quiet {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule-strong);
}

.btn-quiet:hover { border-color: var(--ink-muted); filter: none; }

/*
 * Dark ink on the accent fill, which is light in dark mode. Scoped away from
 * .btn-quiet: that shares the .btn class at equal specificity, so an unscoped
 * rule here won and painted dark text on a dark transparent background —
 * a button with no visible label at all.
 */
@media (prefers-color-scheme: dark) {
  .btn:not(.btn-quiet) { color: #1b1512; }
}

/* ------------------------------------------------------------------ notice */

.notice {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  padding: 1.25rem 1.4rem;
}

.notice h2 { margin-bottom: 0.4rem; }
.notice.is-good { border-left-color: var(--good); }

/* ------------------------------------------------------------------- panel */

.panel {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1.4rem;
  box-shadow: var(--shadow);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin: 1.25rem 0 0;
}

.hint { color: var(--ink-muted); font-size: 0.9rem; margin-top: 1.25rem; }
.hint:empty { display: none; }

/* ----------------------------------------------------------------- working */

.meter {
  height: 10px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 999px;
  overflow: hidden;
}

.meter-fill {
  height: 100%;
  width: 0;
  background: var(--accent);
  border-radius: 999px;
  transition: width 200ms linear;
}

/*
 * Before the first page comes back there is no proportion to show, so the bar
 * says "something is happening" rather than inventing a number.
 */
.meter-fill.is-indeterminate {
  width: 35%;
  animation: sweep 1.4s ease-in-out infinite;
}

@keyframes sweep {
  0% { margin-left: -35%; }
  100% { margin-left: 100%; }
}

.meter-label {
  margin: 0.75rem 0 0;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------ result */

.eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--ink-muted);
  margin: 0 0 0.35rem;
}

/* The number, big. It is the whole point of the screen. */
.headline {
  font-size: clamp(2.1rem, 1.3rem + 3.6vw, 3.4rem);
  margin-bottom: 0.4rem;
}

.headline em {
  font-style: normal;
  color: var(--accent);
}

/* ---------------------------------------------------- before/after compare */

.compare {
  margin: 2.5rem 0 0;
  padding: 0;
}

/*
 * Caption on the left, the escape hatch on the right. They share a line because
 * the button is about the caption — which passage this is — rather than about
 * the result as a whole, and putting it under the crops would read as another
 * step in the flow.
 */
.compare-caption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  color: var(--ink-muted);
  font-size: 0.9rem;
  margin-top: 0.6rem;
}

/*
 * A text button: no fill and no border, because it sits inside a caption and a
 * second filled button next to Download would compete with it. Still 44px of
 * touch target — the padding carries it, and the negative inline margin keeps
 * the label optically aligned with the edge despite that padding.
 */
.btn-inline {
  font: inherit;
  font-weight: 600;
  color: var(--accent);
  background: none;
  border: 0;
  border-radius: 4px;
  padding: 0.6rem 0.4rem;
  margin-inline: -0.4rem;
  min-height: 44px;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 3px;
  transition: text-decoration-color 120ms;
}

.btn-inline:hover { text-decoration-color: currentColor; }
.btn-inline[disabled] { color: var(--ink-muted); cursor: default; text-decoration-color: transparent; }

.compare-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

/* One column on a phone: side by side at 100% zoom would show a sliver of each
   and prove nothing. */
@media (max-width: 34rem) {
  .compare-pair { grid-template-columns: 1fr; }
}

.compare-cell { min-width: 0; }

.compare-label {
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-muted);
  margin: 0 0 0.35rem;
}

/*
 * The crop scrolls inside its own box rather than being scaled to fit. Fitting
 * it would resample the pixels and defeat the entire purpose — the claim is
 * that the engraving survived at full resolution, so it has to be shown at full
 * resolution.
 */
.compare-cell canvas {
  display: block;
  width: auto;
  max-width: none;
  background: #fff;
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  image-rendering: pixelated;
}

.compare-cell {
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* -------------------------------------------------------- threshold slider */

.tune {
  margin-top: 2rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 0.9rem 1.2rem;
}

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

.tune-body { padding-top: 1rem; }

.slider-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin: 1rem 0 0.5rem;
}

.slider-end {
  font-size: 0.85rem;
  color: var(--ink-muted);
  white-space: nowrap;
}

@media (max-width: 30rem) {
  .slider-row { flex-wrap: wrap; }
  .slider-row input[type=range] { order: -1; flex-basis: 100%; }
}

/* 44px of touch target, whatever the track looks like. */
input[type=range] {
  flex: 1;
  min-width: 8rem;
  height: 44px;
  margin: 0;
  background: transparent;
  accent-color: var(--accent);
  cursor: pointer;
}

.slider-value {
  font-family: var(--mono);
  font-size: 0.9rem;
  color: var(--ink);
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.slider-help {
  font-size: 0.85rem;
  color: var(--ink-muted);
  margin: 0.5rem 0 0;
}

/* ---------------------------------------------------------------- warnings */

.warn {
  margin-top: 1.5rem;
  padding: 0.9rem 1.1rem;
  background: var(--accent-soft);
  border-radius: var(--radius);
  font-size: 0.92rem;
}

.warn p { margin: 0; }
.warn p + p { margin-top: 0.4rem; }

/* ---------------------------------------------------------------- advanced */

.advanced {
  margin-top: 2.5rem;
  border-top: 1px solid var(--rule);
  padding-top: 1rem;
  font-size: 0.9rem;
}

.advanced > summary {
  cursor: pointer;
  color: var(--ink-muted);
  font-weight: 600;
}

.advanced > summary:hover { color: var(--ink); }

.field-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 1.5rem;
  margin: 1rem 0;
}

.field { display: flex; flex-direction: column; gap: 0.25rem; }
.field > span { color: var(--ink-muted); font-size: 0.85rem; }

.field input, .field select {
  font: inherit;
  padding: 0.4rem 0.5rem;
  min-height: 36px;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--rule-strong);
  border-radius: 4px;
}

.field input[type=number] { width: 6.5rem; }
.field input[type=text] { width: 9rem; }

.log, .bar {
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-x: auto;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 0.75rem;
  margin: 0.75rem 0 0;
}

.bar { color: var(--accent); }
.bar:empty, .log:empty { display: none; }

.previews { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 0.75rem; }
.previews figure { margin: 0; display: inline-block; text-align: center; }
.previews canvas {
  max-width: 100%;
  border: 1px solid var(--rule-strong);
  background: #fff;
}
.previews figcaption { font-size: 12px; color: var(--ink-muted); }

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

.colophon {
  padding-block: 0 3rem;
  font-size: 0.9rem;
  color: var(--ink-muted);
}

.colophon h2 { font-size: 1.1rem; color: var(--ink); }

.build {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink-muted);
}

/* -------------------------------------------------- "why one bit" disclosure */

.explain {
  margin: 1.25rem 0 1.75rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 0.8rem 1.1rem;
}

.explain > summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--ink);
}

.explain-body { padding-top: 1rem; }

/*
 * The histogram. Sixteen bars over a black-to-white ramp: the bar says how many
 * pixels, the ramp underneath says which shade. Nothing is measured off it, so
 * there is no scale — the U is the entire message.
 */
.hist { margin: 1.25rem 0 1.5rem; }

.hist-bars {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 84px;
}

.hist-bars > span {
  flex: 1;
  background: var(--ink-muted);
  border-radius: 2px 2px 0 0;
}

/*
 * Roughly the shape a 600 dpi scan of printed music actually makes: a tall
 * spike at paper, a shorter one at ink, and a floor between them that is not
 * quite zero — it is the antialiased edge of every glyph, one pixel wide.
 */
.hist-bars > span:nth-child(1)  { height: 60%; }
.hist-bars > span:nth-child(2)  { height: 25%; }
.hist-bars > span:nth-child(3)  { height: 9%; }
.hist-bars > span:nth-child(4)  { height: 5%; }
.hist-bars > span:nth-child(5)  { height: 3%; }
.hist-bars > span:nth-child(6)  { height: 2%; }
.hist-bars > span:nth-child(7)  { height: 2%; }
.hist-bars > span:nth-child(8)  { height: 2%; }
.hist-bars > span:nth-child(9)  { height: 2%; }
.hist-bars > span:nth-child(10) { height: 2%; }
.hist-bars > span:nth-child(11) { height: 3%; }
.hist-bars > span:nth-child(12) { height: 4%; }
.hist-bars > span:nth-child(13) { height: 7%; }
.hist-bars > span:nth-child(14) { height: 17%; }
.hist-bars > span:nth-child(15) { height: 44%; }
.hist-bars > span:nth-child(16) { height: 100%; }

/*
 * Literal greys, so they are the same in both themes — like the crop canvases,
 * which also refuse to be re-themed. The border is what keeps the black end
 * from dissolving into a dark page and the white end from glowing on a light
 * one.
 */
.hist-ramp {
  height: 12px;
  margin-top: 4px;
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
  background: linear-gradient(to right, #000, #fff);
}

.hist-axis {
  display: flex;
  justify-content: space-between;
  margin: 0.4rem 0 0;
  font-size: 0.8rem;
  color: var(--ink-muted);
}

/*
 * Eight boxes against one. Both rows start at the same left edge and the labels
 * share a column, so the comparison is made by the eye before either line is
 * read.
 */
.bits {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 0.6rem 1rem;
  margin: 1.25rem 0 1.5rem;
}

.bit-boxes { display: flex; gap: 3px; }

.bit-boxes i {
  width: 10px;
  height: 15px;
  border-radius: 2px;
  background: var(--accent);
}

.bits strong { color: var(--ink); font-weight: 600; }

/* The footer's own paragraphs are spaced by `p`; this one closes the box. */
.explain-last { margin-bottom: 0; }

/*
 * Stacking is a last resort, not a phone style: the whole point of these two
 * rows is that eight boxes sit directly above one, and text between them breaks
 * that. The label wraps to two or three lines long before this fires.
 */
@media (max-width: 20rem) {
  .bits { grid-template-columns: minmax(0, 1fr); gap: 0.3rem; }
  .bits span:nth-child(2) { margin-bottom: 0.6rem; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ------------------------------------------------------------------- queue */

/*
 * One row per file, filled in as the queue advances. The same list serves the
 * working screen and the result, so nothing is rebuilt when the last file
 * lands — it simply stops changing.
 */
.files {
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
  border-top: 1px solid var(--rule);
}

.file-row {
  display: grid;
  grid-template-columns: 1.5rem minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0 0.75rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--rule);
}

.file-status {
  font-family: var(--mono);
  color: var(--ink-muted);
  text-align: center;
}

.file-row[data-state=done] .file-status,
.file-row[data-state=warned] .file-status { color: var(--good); }
.file-row[data-state=failed] .file-status { color: var(--accent); }
.file-row[data-state=working] .file-status { color: var(--accent); }

.file-name {
  min-width: 0;
  overflow-wrap: anywhere;
}

.file-row[data-state=pending] .file-name { color: var(--ink-muted); }

.file-note {
  grid-column: 2;
  font-size: 0.88rem;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

.file-note:empty { display: none; }

/* The download sits on the first row of the grid, against the right edge. */
.file-row .btn-inline {
  grid-column: 3;
  grid-row: 1;
  padding-block: 0.2rem;
  min-height: 0;
}

@media (max-width: 30rem) {
  .file-row { grid-template-columns: 1.5rem minmax(0, 1fr); }
  .file-row .btn-inline { grid-column: 2; grid-row: auto; justify-self: start; }
}

/*
 * Attribution, and the last thing on the page. Quiet on purpose: this is a
 * utility somebody came to for a reason, and a byline that competed with the
 * result would be the wrong end of that.
 */
.byline {
  margin-top: 2rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
  font-size: 0.9rem;
  color: var(--ink-muted);
}
