/* Nomo editor.
 *
 * Print styles are here from the start rather than added later. Engineering
 * worksheets get printed, signed and filed, and EngineeringPaper.xyz treats
 * correct printing as a hard requirement for the same reason. Retrofitting it
 * means rebuilding the layout around it; keeping it costs a media query.
 */

:root {
  color-scheme: light dark;
  --ink: #16191d;
  --paper: #ffffff;
  --chrome: #f4f5f7;
  --rule: #d8dbe0;
  --muted: #6b7280;
  --error: #b00020;
  --warning: #9a6400;
  --good: #1a7f4b;
  --unit: #0b6b8f;
  --keyword: #7c3aed;
  --function: #1a5fb4;
  --constant: #9a4f00;
  --number: #0d7060;
  --string: #8a3c78;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #e6e8ea;
    --paper: #16181c;
    --chrome: #1e2126;
    --rule: #333840;
    --muted: #9aa3ae;
    --error: #ff6b6b;
    --warning: #e0b050;
    --good: #5dd39e;
    --unit: #6fc3e0;
    --keyword: #c4a7f7;
    --function: #7db1f0;
    --constant: #e5a663;
    --number: #5fc9b4;
    --string: #dda0c8;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 100dvh;
  display: grid;
  /* Four rows, and every child is placed explicitly, because the import panel
     is `hidden` most of the time. A hidden element is not a grid item at all,
     so an implicit placement would slide `main` up a row and give the editor
     `auto` height whenever the panel was absent. */
  grid-template-rows: auto auto 1fr auto;
  background: var(--paper);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
}

header {
  grid-row: 1;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--rule);
  background: var(--chrome);
}

header h1 { font-size: 1rem; font-weight: 600; margin: 0; letter-spacing: 0.02em; }

header nav { display: flex; gap: 0.35rem; }

header button {
  font: inherit;
  font-size: 0.82rem;
  padding: 0.2rem 0.7rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 0.3rem;
  cursor: pointer;
}
header button:hover:not(:disabled) { border-color: var(--muted); }
header button:disabled { opacity: 0.4; cursor: default; }
header button[hidden] { display: none; }

/* The name of the document, with a dot when it has unsaved changes. Pushed
   right so the status line stays at the far edge where it is easy to find. */
#file-name {
  margin: 0 auto 0 0.5rem;
  font-size: 0.85rem;
  color: var(--muted);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}

#status { margin: 0; font-size: 0.85rem; color: var(--muted); }
#status.good { color: var(--good); }
#status.warn { color: var(--warning); }
#status.bad  { color: var(--error); }

/* The import report.

   It is capped and scrolls rather than growing with the worksheet: a large
   import can produce a hundred notes, and a panel that pushed the editor off
   the screen would be reporting a translation nobody could then look at. */
#import {
  grid-row: 2;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.4rem 1rem;
  align-items: start;
  max-height: 30vh;
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--rule);
  background: var(--chrome);
  font-size: 0.82rem;
  overflow: auto;
}
#import[hidden] { display: none; }

#import-summary { margin: 0; font-weight: 600; }
#import-summary.clean { color: var(--good); }

#import-dismiss {
  font: inherit;
  font-size: 0.78rem;
  padding: 0.15rem 0.6rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 0.3rem;
  cursor: pointer;
  grid-row: 1;
  grid-column: 2;
}
#import-dismiss:hover { border-color: var(--muted); }

#import-list {
  grid-column: 1 / -1;
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.15rem;
}
#import-list:empty { display: none; }

#import-list li {
  display: grid;
  grid-template-columns: max-content max-content minmax(0, 1fr);
  gap: 0.6rem;
  align-items: baseline;
}

/* The line number is a button because it does something: it takes the reader to
   the marker in the source. A marker nobody is pointed at is only half of the
   rule that nothing is ever silently dropped. */
.import-line {
  font: inherit;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 0.78rem;
  padding: 0;
  color: var(--function);
  background: none;
  border: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.import-kind { color: var(--muted); }
.import-detail { color: var(--ink); min-width: 0; }

/* Only two kinds are coloured, and they are the two that mean something is
   wrong with the *result*: a construct that did not come across, and an answer
   that came across differently from SMath's. Everything else — a rename, a
   figure carried without being drawn — is information, not a warning. */
.import-unsupported .import-kind { color: var(--warning); }
.import-disagreed .import-kind { color: var(--error); }

main {
  grid-row: 3;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  min-height: 0;
}

#pane-editor { border-right: 1px solid var(--rule); min-width: 0; overflow: auto; }
#pane-output { min-width: 0; overflow: auto; padding: 1rem 1.5rem; }

footer {
  grid-row: 4;
  padding: 0.5rem 1rem;
  border-top: 1px solid var(--rule);
  background: var(--chrome);
  font-size: 0.78rem;
  color: var(--muted);
}
footer p { margin: 0; }

/* One column on a narrow screen. The constraint is EP's: worksheets have to
   work on a phone, not only on a desk. */
@media (max-width: 46rem) {
  main { grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; }
  #pane-editor { border-right: 0; border-bottom: 1px solid var(--rule); }
  /* A phone has no room for three columns of note, so each one stacks. The
     panel also takes less of the screen here, where there is less of it. */
  #import { max-height: 40vh; }
  #import-list li { grid-template-columns: 1fr; gap: 0; }
  #import-list li + li { margin-top: 0.4rem; }
}

/* ---- the editor ------------------------------------------------------- */

.cm-editor { height: 100%; font-size: 0.92rem; }
.cm-editor.cm-focused { outline: none; }
.cm-scroller {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  line-height: 1.65;
}
.cm-gutters { background: var(--chrome); border-right: 1px solid var(--rule); color: var(--muted); }

/* Token classes come from the engine; see web/src/highlight.js. */
.tok-comment    { color: var(--muted); font-style: italic; }
.tok-number     { color: var(--number); }
.tok-keyword    { color: var(--keyword); font-weight: 600; }
.tok-unit       { color: var(--unit); }
.tok-function   { color: var(--function); }
.tok-constant   { color: var(--constant); }
.tok-text       { color: var(--string); }
.tok-variable   { color: inherit; }
.tok-operator,
.tok-bracket,
.tok-separator  { color: var(--muted); }
/* A name that resolves to nothing. Marked rather than left plain, because it is
   almost always a typo and this shows it before the diagnostic arrives. */
.tok-unresolved { color: var(--error); text-decoration: underline wavy currentColor; text-underline-offset: 0.25em; }

/* ---- results ---------------------------------------------------------- */

#typeset-label {
  font-size: 0.85rem;
  opacity: 0.8;
  display: flex;
  align-items: center;
  gap: 0.3rem;
  white-space: nowrap;
}

/* The math font this application ships, rather than one it hopes to find.
   MathML layout reads the fraction bar thickness, the axis height, the script
   shifts and the stretchy bracket recipes from an OpenType MATH table, and
   without one the browser guesses all of them from ordinary text metrics —
   which is what a reader with no math font installed used to get.

   A subset of STIX Two Math, built by `web/font.mjs` from a file
   `scripts/fetch-font.sh` obtains and hash-verifies. It is not committed and
   it is not fetched at run time from anyone else's server: it is served from
   this origin, precached by the service worker, and works offline like the
   rest of the application.

   `swap` because the fallback stack below is a real math font on most
   machines: a reader should see the formula immediately in whatever they have
   and get the shipped face when it arrives, rather than looking at nothing. */
/* The text companion to the math font, and the reason it is here: a worksheet
   is a document, and a document whose prose is set in the machine's UI sans
   beside mathematics set in a book face looks like two things stapled together.
   STIX Two Text is the face STIX Two Math was drawn against, so the prose and
   the formulae share a design rather than merely coexisting.

   Two files, both *variable*: one carries the whole 400–700 weight axis, which
   is smaller than shipping Regular and SemiBold separately and covers the 700
   the verdict asks for. Italic is a second file because italic is a separate
   design, not a slant — no axis produces it. `font-display: swap` for the same
   reason as the math font: read the text now in whatever is at hand, and get
   this when it arrives. */
@font-face {
  font-family: "STIX Two Text Subset";
  src: url("fonts/stix-two-text-subset.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "STIX Two Text Subset";
  src: url("fonts/stix-two-text-italic-subset.woff2") format("woff2");
  font-weight: 400 700;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "STIX Two Math Subset";
  src: url("fonts/stix-two-math-subset.woff2") format("woff2");
  font-display: swap;
}

/* A formula sits taller than a line of text, so the rows need room to breathe
   when typesetting is on. The stack keeps every name it had: the shipped subset
   first, then whatever the platform offers for anything outside it — a name in
   an alphabet the subset does not carry falls back rather than showing a box. */
#output math {
  font-size: 1.05em;
  font-family: "STIX Two Math Subset", "Latin Modern Math", "STIX Two Math",
               "TeX Gyre Pagella Math", "Cambria Math", math;
}

/* The results pane is a document, so it is set as one. Everything around it —
   the header, the buttons, the file name — is an application and stays in the
   machine's own UI face, because a control that looks like prose is a control
   that reads as prose. `.step` keeps its monospace: an untypeset line is linear
   text whose columns line up, which is what monospace is for. */
#output {
  font-family: "STIX Two Text Subset", "STIX Two Text", Georgia,
               "Times New Roman", serif;
}

#output .prose { margin: 1.1rem 0 0.4rem; color: #999; }
/* A code span in prose. Monospace at nine tenths, because a monospace face set
   at the same size as a book face reads a size larger; the faint ground is what
   makes a name inside a sentence findable without a border round it. */
#output code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 0.9em;
  background: rgba(127, 127, 127, 0.12);
  padding: 0.05em 0.3em;
  border-radius: 3px;
}
/* Set by the engine when the mathematics is typeset, and it takes the whole
   line rather than only the `<math>` in it — the result, the `=` between the
   columns and the words `check` and `pass` are part of the same statement.
   Leaving them in monospace beside a fraction in a book face is a line that
   looks half converted. */
#output .step.typeset {
  font-family: "STIX Two Math Subset", "Latin Modern Math", "STIX Two Math",
               "TeX Gyre Pagella Math", "Cambria Math", math;
  font-size: 0.95rem;
}

/* No card round a worked line. Each one used to sit in a bordered, rounded,
   grey-backgrounded box with a blue rule down its left edge, and
   `render/html.rs` never drew one — so the editor and the standalone document
   disagreed about what a worksheet looks like, which is the thing the two
   comments further down this file say they must not do. A box also reads as a
   *code block* now that the prose around it is a book face and the mathematics
   is typeset, and a worked line is neither code nor an aside: it is the
   document. */
#output .step {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 0.9rem;
  margin: 0.5rem 0;
  overflow-x: auto;
  padding: 0.5rem;
}
/* A conditional, drawn as mathematics draws one: a brace over its cases. The
   rows are laid out by CSS because MathML Core removed `columnalign` and most
   of `mtable`'s other attributes — an `<mtd>` is a table cell and `text-align`
   reaches it, which the attribute no longer does. Left in both columns: the
   reader is looking down the values, and a centred column of them wanders. */
#output mtable.cases > mtr > mtd { text-align: left; padding: 0.1em 0; }
#output mtable.cases > mtr > mtd:first-child { padding-right: 1em; }

#output .name   { font-weight: 600; }
/* A typeset name and a typeset result are deliberately not bold. The two columns
   are bold in a text worksheet so the definitions scan down the left edge and
   the eye lands on the answer — but bold has a *meaning* in mathematics, where
   a bold σ is a tensor rather than a stress, so a symbol that has become
   mathematics must not inherit it. MathML Core's own stylesheet already resets
   the weight; this says so out loud, because a column that stopped being bold
   otherwise reads as a bug worth fixing. The typeset page does not need the
   weight: a result is the last thing on its line and the only thing after the
   final `=`. */
#output .name math, #output .result math { font-weight: normal; }
#output .eq     { opacity: 0.45; padding: 0 0.35rem; }
#output .subst  { opacity: 0.7; }
#output .result { font-weight: 600; }
#output .error  { color: var(--error); font-weight: 600; }
/* A verdict, in the results pane. The word carries it as well as the colour:
   the same markup prints, and a printed worksheet is often monochrome. */
#output .verdict { font-weight: 700; margin-left: 0.6rem; }
#output .check .name { margin-right: 0.5rem; }
#output .check.pass .verdict { color: #1a7f37; }
#output .check.fail .verdict { color: var(--error); }
#output .check.undecided .verdict { color: #8a6d00; }
/* Only the tint, now that a worked line has no left rule to colour. The
   standalone document has drawn a failed check this way and no other all along
   — `render/html.rs` has `.check.fail { background }` and never had a border —
   so this is the two panes agreeing again rather than a second decision. */
#output .check.fail { background: #fff4f4; }
#output .note   { opacity: 0.6; font-style: italic; }
/* A figure carries the browser's default 40px side margin, which would inset
   every image relative to the mathematics it belongs to. The standalone render
   sets the same three properties in `render/html.rs`; the two panes are meant to
   show a worksheet the same way.

   `max-width` and `height: auto` are what make the `width`/`height` attributes
   the renderer writes a *requested* size rather than a fixed one: the figure is
   drawn at the width the worksheet asks for, in the image's own proportions,
   and a pane narrower than that shrinks it whole. It is never cropped and never
   stretched — a reader shown two-thirds of a diagram, or a squashed one, with
   nothing to say so, is worse off than one shown a small diagram.
   `scripts/check-figures.mjs` holds all three properties to that in Chrome. */
#output figure  { margin: 1.2rem 0; }
#output img     { max-width: 100%; height: auto; display: block; }

/* A plot arrives as markup the engine generated, carrying classes and no
   colours of its own — so without these rules it renders as an invisible grid
   and a black blob. `render/html.rs` sets the same properties for the
   standalone file, for the reason above: the two panes show a worksheet the
   same way. Structure is `currentColor` so it follows the theme; the curves
   carry accents instead, which have to read on both grounds and against each
   other. */
#output figure.plot svg { width: 100%; max-width: 42rem; height: auto; display: block; }
#output .plot-grid  { stroke: currentColor; stroke-width: 0.5; opacity: 0.15; }
#output .plot-axis  { stroke: currentColor; stroke-width: 1; opacity: 0.5; }
#output .plot-curve { fill: none; stroke: #0072b2; stroke-width: 1.8; stroke-linejoin: round; }
/* Six curve colours, the Okabe-Ito colourblind-safe palette without its yellow
   and its black: a curve cannot take the page's colour the way the structure
   does, because curves have to differ from each other as well as from the
   ground. `render/plot.rs` cycles through them and gives a legend swatch the
   same class as the curve it names. */
#output .plot-curve-1 { stroke: #0072b2; }
#output .plot-curve-2 { stroke: #d55e00; }
#output .plot-curve-3 { stroke: #009e73; }
#output .plot-curve-4 { stroke: #cc79a7; }
#output .plot-curve-5 { stroke: #e69f00; }
#output .plot-curve-6 { stroke: #56b4e9; }
/* A measured point is drawn as an open ring in its series' own colour,
   which is why it takes the curve's class rather than one of its own. */
#output .plot-mark { stroke-width: 1.3; }
#output .plot-label { fill: currentColor; opacity: 0.6; font-size: 11px; }
#output .plot-unit  { fill: currentColor; opacity: 0.75; font-size: 11px; font-style: italic; }
#output .plot-x       { text-anchor: middle; }
#output .plot-y       { text-anchor: end; }
#output .plot-start   { text-anchor: start; }
#output .plot-end     { text-anchor: end; }
#output .plot-y-title { text-anchor: start; }
#output .plot-axis-label { text-anchor: middle; opacity: 0.8; }
#output .plot-legend   { text-anchor: start; opacity: 0.75; }

/* ---- print ------------------------------------------------------------ */

@media print {
  /* The worksheet is the document. The editor is a tool for making it, and has
     no business on paper. */
  header, footer, #pane-editor, #import { display: none; }

  body { height: auto; display: block; background: #fff; color: #000; }
  main { display: block; }
  #pane-output { overflow: visible; padding: 0; }

  #output .step {
    /* Long expressions must wrap rather than be clipped at the page edge: a
       scrollbar is not a thing paper has. */
    overflow-x: visible;
    white-space: pre-wrap;
    font-size: 10pt;
  }
  #output .prose { font-size: 10pt; }
  /* Do not break a worked line across pages. */
  #output .step, #output .prose { break-inside: avoid; }
  #output .error { color: #000; text-decoration: underline; }
}

/* The footer's links out of the editor — the language reference and the worked
   examples. They are the only links on the page, so they need to look like
   links rather than inherit the footer's muted prose. */
footer a {
  margin-right: 1.2rem;
  color: inherit;
}
