/*
 * Pairette.
 *
 * The load-bearing idea is in "The page" below: the whole screen is one grid of
 * two columns, and the contrast matrix is one table placed across both of them.
 * Its first column is exactly the rail width, so the row headers - the palette
 * colours - land in the rail while the cells fill the main area. The table is
 * never taken apart to achieve this: no display:contents on table parts, no
 * reconstructed ARIA, and the header association stays the native one.
 */

/* Tokens ---------------------------------------------------------------- */

:root {
  --ground: #ffffff;
  --ground-sunken: #f2f2f1;

  /*
   * The ground of the brand block: one step past the rail, away from the page.
   * It sets the application's own name apart from everything the page is about
   * without becoming a colour of its own - 1.11:1 against the rail, which is a
   * step the eye takes and no contrast the ink has to survive; the mark on it
   * still stands at 14:1.
   */
  --ground-brand: #e7e7e4;
  --ink: #1a1a18;
  --ink-muted: #5c5c58;
  --line: #d3d3cf;
  --line-strong: #9a9a94;
  --accent: #0b57d0;

  /*
   * What a focused field is outlined in: pure black, and pure white in the dark
   * scheme. Not the ink of the text, which is a shade off both - a field that has
   * the focus has to be findable at a glance across a page full of fields, and
   * the one colour nothing else on the page uses is what finds it.
   */
  --focus: #000000;

  /* Width of the rail, and with it the first column of every table. */
  --rail: 19rem;

  /* Edge length of a matrix cell. Square by construction. */
  --cell: 10rem;

  /*
   * Four sizes for text and two for headings, and nothing else. A size invented
   * for one element is a size a reader has to place, and this application has
   * four places for text: prose, an annotation beside it, the dense figures of a
   * cell, and the marks inside them.
   */
  --text: 1rem;
  --text-small: 0.875rem;
  --text-dense: 0.75rem;
  --text-mark: 0.6875rem;
  --text-heading: 1.125rem;
  --text-title: 2rem;

  /*
   * Height of the strip across the top: the brand block on the left and the tab
   * bar beside it are one band, so both are told the same height rather than one
   * of them being padded until it happens to match.
   */
  --bar: 2.75rem;

  --gutter: 1rem;

  /*
   * The vertical step between two blocks of a rail section: a field and the
   * button that applies it, a bar of switches and the next one, the store and
   * the note about it. One number for all of them, because a rail whose gaps
   * differ per section reads as several rails - and the gap is declared here
   * rather than as a margin per block, so that nothing can drift.
   */
  --stack: 0.75rem;

  /*
   * The horizontal inset shared by the rail and the row labels of the table.
   * Everything in the left column starts on one line - a section heading beside
   * the table and a colour name inside it - and one line is what makes a column
   * a column. Stated once, or the two would be two numbers that happen to agree
   * until somebody changes one of them.
   */
  --inset: 0.6rem;
  --radius: 0.25rem;
  /* Height of every single-line control: input, select, switch bar, button. */
  --control: 2.25rem;
  /* How wide running text may get before the eye loses the next line. */
  --measure: 44rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ground: #17171a;
    --ground-sunken: #202024;
    --ground-brand: #2a2a30;
    --ink: #eceCe8;
    --ink-muted: #a0a09a;
    --line: #3a3a40;
    --line-strong: #5e5e66;
    --accent: #8ab4f8;
    --focus: #ffffff;
  }
}

/*
 * The three sizes, and what the smallest of them has to hold: the sample line,
 * four measurements - one per procedure - and the space between them. That comes
 * to 106 pixels at the default root size, so the smallest cell is 112 and no
 * procedure falls off the bottom of it any more.
 *
 * The steps are a constant ratio of about 1.4 apart, which is what makes a change
 * of size read as the same view at another scale rather than as a different
 * layout. The largest carries the conformance badges as well: with every
 * procedure on and their badges wrapping onto a second row, that comes to some
 * 210 pixels, and 224 leaves the room a wrapping row needs without being asked
 * for it.
 */
.page--cell-s {
  --cell: 7rem;
}

.page--cell-m {
  --cell: 10rem;
}

.page--cell-l {
  --cell: 14rem;
}

* {
  box-sizing: border-box;
}

/*
 * An author display beats the attribute, and buttons get one below. Without
 * this the palette store would show controls it has nothing to put in yet.
 */
[hidden] {
  display: none !important;
}

/*
 * The root keeps whatever size the browser was set to, and everything below is
 * stated as a multiple of it: font sizes, cell edges, the rail, every gutter and
 * radius. A reader who sets 20 pixels gets the whole application at 20 - the
 * matrix included, which is why the cells are in rem and not in pixels.
 *
 * Written out rather than left implied, because the one line that would break
 * the promise - a size on :root - is the one somebody adds to "fix" a layout.
 */
html {
  font-size: 100%;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: system-ui, sans-serif;
  font-size: var(--text);
  line-height: 1.45;
}

a {
  color: var(--accent);
}

/* The page ------------------------------------------------------------- */

/*
 * Two columns, four rows. The table of row two spans both columns; everything
 * else sits in one of them.
 *
 * The width is stated rather than derived: the number of columns is known on
 * the server, so the page can say how wide it is instead of leaving it to
 * intrinsic sizing across a spanning table. Wider than the viewport means the
 * page scrolls - neither region scrolls on its own.
 */
.page {
  --cols: 0;

  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  grid-template-rows: auto auto auto 1fr;
  width: max(100%, calc(var(--rail) + var(--cols) * var(--cell)));
  min-height: 100vh;
}

/*
 * The top left of the page: the application's name, as high as the tab bar beside
 * it and on a ground of its own. Stretched rather than fixed, so a notice beside
 * it that makes the band taller cannot tear a hole into the column below - the
 * name itself stays on the first line of it.
 */
.brand {
  display: flex;
  grid-column: 1;
  grid-row: 1;
  min-height: var(--bar);
  align-items: center;
  padding: 0 var(--inset);
  border-right: 1px solid var(--line);
  background: var(--ground-brand);
}

.masthead {
  display: flex;
  flex-direction: column;
  grid-column: 2;
  grid-row: 1;
}

/*
 * What follows the strip needs the gutter the strip must not have. Only the
 * sides and the distance from the strip are set here; whatever stands there
 * keeps its own spacing below, so several notices still stack as they should.
 */
.masthead > :not(.tabs) {
  margin-right: var(--gutter);
  margin-left: var(--gutter);
}

.masthead > .tabs + * {
  margin-top: var(--gutter);
}

/*
 * The spanning table, and the only thing that couples the two columns: its own
 * first column is the rail width, so a cell stands under its colour because of
 * where it is.
 */
.canvas {
  grid-column: 1 / -1;
  grid-row: 2;
}

/*
 * A view without colours has no such table and therefore nothing to couple. It
 * moves down beside the rail, leaving the spanning row empty, or its height
 * would push the rail down and tear a hole in the column's ground - a gap as
 * tall as the text happens to be, and visible as a break in the background.
 */
/*
 * What the grid has to say about its own measurements: in the column beside the
 * rail, above the figure it qualifies. The column brings its own gutter and
 * measure, so all this needs is the distance from the table over it - the notice
 * inside carries the space to the sentence below.
 */
.footnotes {
  margin-top: var(--gutter);
}

.canvas--plain {
  grid-column: 2;
  grid-row: 3 / 5;
  max-width: calc(var(--measure) + 2 * var(--gutter));
  padding: 0 var(--gutter) var(--gutter);
}

.rail {
  grid-column: 1;
  grid-row: 3;
  border-right: 1px solid var(--line);
  background: var(--ground-sunken);
}

/*
 * Down to the foot of the page rather than into the rail's row alone. An auto
 * row takes the height of its tallest occupant, so sharing one with the main
 * column let a long text push the rail - and the footer under it - as far down
 * as that text happened to reach. Spanning into the last row keeps the two
 * columns apart: a track is not sized by an item that spans a flexible one, so
 * the rail's row is now measured by the rail and by nothing else.
 */
.aside {
  grid-column: 2;
  grid-row: 3 / 5;
  max-width: calc(var(--measure) + 2 * var(--gutter));
  padding: 0 var(--gutter);
}

/*
 * The footer belongs to the rail: it is the foot of the column that carries the
 * palette, not of the view beside it.
 */
.footer {
  grid-column: 1;
  grid-row: 4;
  padding: var(--gutter) var(--inset);
  border-right: 1px solid var(--line);
  background: var(--ground-sunken);
  color: var(--ink-muted);
  font-size: var(--text-small);
}

.footer p {
  margin: 0 0 0.25rem;
}

/* Brand ----------------------------------------------------------------- */

.brand__title {
  margin: 0;
  font-size: var(--text-heading);
  font-weight: 700;
  letter-spacing: 0.01em;
}

.brand__title a {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: inherit;
  text-decoration: none;
}

.brand__mark {
  width: 1.75rem;
  height: 1.75rem;
  flex: none;
}

.brand__mark--back {
  fill: var(--ink);
}

.brand__mark--front {
  fill: var(--ground);
  stroke: var(--ink);
  stroke-width: 2.5;
}

/* Tabs ------------------------------------------------------------------ */

/*
 * Tabs to look at, links to work with. There is deliberately no tablist role:
 * every entry is a page of its own that carries the palette in its address.
 */
/*
 * A strip across the top of the main column, set off from what follows by one
 * line. The tabs sit flush in its top left corner: no padding, no gap, no
 * rounding - they are divisions of the strip rather than objects on it, and a
 * hairline between neighbours is all that separates them, as in a switch bar.
 */
.tabs {
  border-bottom: 1px solid var(--line);
  background: var(--ground-sunken);
}

/*
 * Pulled down onto that line, so that the current tab can cover it: a parent's
 * border is painted before its descendants, and the one tab carrying a ground
 * of its own therefore paints over the single pixel beneath it. The others are
 * transparent and leave the line standing.
 */
.tabs ul {
  display: flex;
  margin: 0 0 -1px;
  padding: 0;
  list-style: none;
}

.tabs li {
  display: flex;
}

.tabs li + li {
  border-left: 1px solid var(--line);
}

.tabs__tab {
  display: flex;
  height: var(--bar);
  align-items: center;
  padding: 0 0.9rem;
  color: var(--ink-muted);
  text-decoration: none;
}

/*
 * The page you are on carries the ground of the page, and nothing divides it
 * from the content below - which is the whole statement a tab strip makes.
 */
.tabs__tab[aria-current="page"] {
  background: var(--ground);
  color: var(--ink);
  font-weight: 600;
}

.tabs__tab:hover,
.tabs__tab:focus-visible {
  color: var(--ink);
}

/* The spanning table ---------------------------------------------------- */

.grid {
  width: calc(var(--rail) + var(--cols) * var(--cell));
  border-collapse: collapse;
  table-layout: fixed;
}

/*
 * A band across the page: the line that says how the table is read, and under it
 * the name of the palette, which is the name of the table. A caption is centred by
 * a browser and has no inset of its own, so both are said here, and a band starts
 * where everything in the left column starts - that is the line the page is read
 * from.
 *
 * As high as the strip across the top of the page and set in the text of the page,
 * because it is a name rather than a heading: the band it forms continues the one
 * the application's own name sits in, and the line reads at the size everything
 * else is read at.
 *
 * Centred by its line height, not by a flex box. A caption given an inner display
 * of its own stops being a caption box: the table then wraps it in an anonymous
 * row of its own making, and the first real row slides up behind it. One line is
 * all there is here anyway - a name too long for the table is cut with an
 * ellipsis rather than wrapped into a band that would have to grow.
 *
 * A caption spans the whole table, so it crosses the rail column as well and cut
 * a band out of it. It carries the column on instead: the ground of the rail for
 * the rail's own width and the hairline that closes it, both painted rather than
 * bordered, because one box cannot have a border a fraction of the way along.
 * The line goes on the last pixel of that width, which is where the border of the
 * rail and the right edge of every row label sit.
 */
.band {
  height: var(--bar);
  padding: 0 var(--inset);
  overflow: hidden;
  background-image:
    linear-gradient(var(--line), var(--line)),
    linear-gradient(var(--ground-sunken), var(--ground-sunken));
  background-position: calc(var(--rail) - 1px) 0, 0 0;
  background-repeat: no-repeat;
  background-size: 1px 100%, var(--rail) 100%;
  line-height: var(--bar);
  text-align: left;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * The name holds the width of the rail column and nothing more, so that the line
 * beside it begins over the table - where the columns are - and is read as being
 * about them. Both stay on the one line the band is: the name gives way with an
 * ellipsis, the sentence is cut by the band's own overflow where a table is
 * narrower than the sentence is long.
 *
 * Aligned by its top edge rather than by its baseline, and that is not a matter of
 * taste: an inline block whose overflow is anything but visible has its bottom
 * margin edge for a baseline, so the name sat a line's descent away from the
 * sentence beside it. Both carry the line height of the band, so their tops are
 * where their text is.
 */
.grid__name {
  display: inline-block;
  width: calc(var(--rail) - var(--inset));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: top;
}

/*
 * An aside about the table, set apart as one: the grey of the sentence that
 * counts the pairs under the table, and italic, because it is the one line on the
 * page that talks about the page rather than saying something measured.
 */
.grid__lead {
  padding-left: var(--inset);
  color: var(--ink-muted);
  font-style: italic;
}

.grid__rail {
  width: var(--rail);
}

.grid__col {
  width: var(--cell);
}

/*
 * One line per division, drawn by exactly one cell: every cell states its right
 * and bottom edge and nothing else, and the head row its top edge. With a border
 * on all four sides each line is stated twice over, and two claims on one grid
 * line are how it comes out twice as thick.
 *
 * It also settles what used to be two exceptions: there are no left borders at
 * all, so the rail runs into the screen edge because nothing draws a line there
 * rather than because two rules take one away.
 */
.grid th,
.grid td {
  padding: 0;
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}

/*
 * The corner, above the rail column and beside the column headers. It names
 * what stands beneath it rather than explaining the table - the caption does
 * that, and saying it twice would only make a screen reader read it twice.
 */
.grid__corner {
  background: var(--ground-sunken);
}

.grid thead th {
  height: auto;
  border-top: 1px solid var(--line);
}

.grid tbody th,
.grid tbody td {
  height: var(--cell);
}

/*
 * A colour label sits on the colour itself. Which of black and white is legible
 * on it is decided on the server, because it follows from the colour and not
 * from the theme.
 */
.swatch {
  display: grid;
  position: relative;
  font-weight: 400;
  grid-template-columns: minmax(0, 1fr) auto;
  height: var(--cell);
  align-content: start;
  padding: 0.4rem var(--inset);
  overflow: hidden;
  background: var(--swatch-bg, var(--ground-sunken));
  color: var(--swatch-fg, var(--ink));
  gap: 0.15rem 0.4rem;
}

.swatch--head {
  height: auto;
  min-height: 2.5rem;
  align-content: end;
}

/*
 * Top left, with the controls beside it and the details below: the line is placed
 * rather than left to the grid, because the button that opens the colour claims
 * the same cell and would otherwise push the name into a row of its own.
 *
 * The name and the mark that says there is something to read share it, so the
 * mark follows the name rather than the edge of the cell. Only the name gives
 * way when the room runs out: a truncated name is recoverable from the title and
 * from what is read out, while a mark cut in half says nothing at all.
 */
.swatch__title {
  display: flex;
  grid-row: 1;
  grid-column: 1;
  min-width: 0;
  align-items: center;
  gap: 0.25rem;
}

.swatch__name {
  overflow: hidden;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * Drawn in the ink of the swatch, like everything else written on it. No colour
 * of its own: a mark that said "warning" in red would say it only to those who
 * see red, and what it means is in the label of the button it stands on.
 */
.swatch__warning {
  display: flex;
  flex: none;
}

/*
 * The luminance a column aims at, under the step it belongs to. Pulled to the
 * start: there is one line here and nothing to line it up with, so the two parts
 * stand together rather than pushed apart to the edges of the cell.
 */
/*
 * The luminance a column aims at, on the line of the step it belongs to rather
 * than under it. A column header says two things about one step and they are one
 * statement: the step on the left, what it aims at on the right. Two lines made
 * the band twice as tall for nothing.
 *
 * The second cell of the swatch grid, which is where the controls of a row label
 * sit - a column header has none, so the room is free.
 */
.swatch--head .values {
  grid-row: 1;
  grid-column: 2;
  grid-template-columns: auto auto;
  align-items: center;
  justify-content: end;
}

/*
 * Both halves of the line sit in the middle of the cell. The step is set at the
 * body size and its luminance at the dense one, so the two boxes are of
 * different height - aligning the row to an edge left the smaller of them
 * hanging off it, and a baseline lined the letters up while the line as a whole
 * still sat where the taller half put it.
 *
 * Only where there is a second half to centre against. A column header of the
 * matrix carries a name and nothing else, and the row it stands in is the whole
 * of what it has.
 */
.swatch--head:has(.values) {
  align-content: center;
}

.swatch__note {
  grid-column: 1 / -1;
  font-size: var(--text-dense);
  opacity: 0.85;
}

/*
 * Beside the name, not beneath it: the two controls act on the colour the row
 * is named after, so they belong to the line that names it. The name takes the
 * width that is left and is cut with an ellipsis - the controls keep their size
 * however long a colour is called.
 */
.swatch__actions {
  display: flex;
  grid-row: 1;
  grid-column: 2;
  gap: 0.25rem;
}

/*
 * The button that opens the colour, and the area it claims.
 *
 * The label inside it is set aside the way every hidden label here is; the button
 * itself may not be, because those properties end in clip-path and that would clip
 * away the very pseudo-element the control consists of.
 *
 * So the button is a pixel with nothing in it, placed in the cell the name already
 * occupies rather than left to the grid, which would otherwise give it a row of its
 * own and make the swatch a pixel taller than the cell. Nothing is painted: no
 * border, no ground, no room taken.
 *
 * Static, deliberately: a positioned button would become the containing block of
 * its own pseudo-element and the area would be one pixel square. This way the
 * swatch is the containing block, and the area is the cell.
 */
.swatch__edit {
  display: block;
  grid-row: 1;
  grid-column: 1;
  width: 1px;
  height: 1px;
  min-height: 0;
  padding: 0;
  border: none;
  background: none;
  font: inherit;
}

/*
 * The marker belongs to the cell, not to the pixel. A browser draws its own around
 * the button, and a ring around a single pixel in the corner of a colour field says
 * nothing about what has the focus.
 */
.swatch__edit:focus,
.swatch__edit:focus-visible {
  outline: none;
}

/*
 * Below the remove link in the stacking order, which is what the z-index on that
 * link is for: the two overlap across the whole cell, and whichever lies on top
 * answers the click. Removing a colour must not be something that opens a dialog
 * instead.
 */
.swatch__edit::after {
  position: absolute;
  z-index: 0;
  content: "";
  inset: 0;
}

/*
 * The cell is what is clicked, so the cell is what says so - on hover for the
 * pointer, wider on focus for the keyboard. In the ink the swatch is legible in,
 * because no theme colour knows which colour it lands on, and inside the edge
 * rather than around it: an outline outside the cell is covered by the cell next
 * to it.
 */
.swatch__edit:hover::after,
.swatch__edit:focus-visible::after {
  outline: 2px solid var(--swatch-fg, var(--ink));
  outline-offset: -3px;
}

.swatch__edit:focus-visible::after {
  outline-width: 3px;
}

/*
 * An icon control: the mark is the whole of it. The button rules hand out a
 * bordered block and a muted ink on hover, and both are wrong here.
 *
 * It serves wherever an icon is the control - removing a colour from a swatch,
 * copying a value out of a dialog - and the geometry is the same in every one of
 * them: the same square, the same radius, the same mark inside it. Only the colour
 * changes with the place, because on a swatch it has to be the ink that colour is
 * legible in and elsewhere the ink of the theme.
 *
 * The radius and the decoration are stated here rather than left to the element:
 * one of these controls is a button and gets both from the button rules, the other
 * is a link and gets neither, and two controls that differ by which tag they
 * happen to be are two kinds of thing to a reader.
 *
 * The square is 24 pixels at the default root size and the mark inside it 16: an
 * icon stays the size of an icon, and the area that answers a click is larger
 * than what it is drawn on - which is what a pointer, and a finger above all,
 * needs. In rem rather than in pixels, so that the target grows with the text
 * instead of shrinking against it.
 */
.iconbutton {
  display: flex;
  position: relative;
  z-index: 1;
  width: 1.5rem;
  height: 1.5rem;
  flex: none;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: var(--radius);
  background: none;
  color: inherit;
  text-decoration: none;
}

/*
 * Hover and focus are one state: the control inverts, taking the ink it was
 * written in as its ground and the counterpart for the mark. On a swatch both
 * values come from the server, because which of black and white is legible
 * follows from the palette colour and not from the theme; elsewhere the fallbacks
 * are the theme's own pair.
 *
 * A copy that went through wears the same state for a moment. It is the only
 * answer the control can give: the clipboard is not a place the page can show.
 *
 * No outline with it. The default one is drawn in a colour that knows nothing
 * about the colour it lands on, and the inversion is the stronger signal of the
 * two - it fills the whole of the target rather than tracing its edge.
 */
.iconbutton:hover,
.iconbutton:focus-visible {
  background: var(--swatch-fg, var(--ink));
  color: var(--swatch-fg-counter, var(--ground));
  outline: none;
}

/*
 * What the colour is, medium by medium, directly under the name: the
 * medium names its line and the value follows it. Two columns, so that the values
 * line up under one another however long the name of a medium is - a list that
 * stepped in and out again would be read as a list of unrelated things.
 *
 * The name column is a flat width rather than as wide as the names of the moment:
 * "Spot color" is the longest of the three, and it appears in the labels of the
 * colours that carry a measurement and nowhere else. Sized by what is there, the
 * values of one row would start further in than the values of the next, and a
 * column of them would step in and out down the rail.
 *
 * Which notation the value is in is not written down: a hex value and four ink
 * percentages are recognised by their shape, and the shape is in plain sight. What
 * a shape cannot say is said where it is read out.
 */
.values {
  display: grid;
  margin: 0.1rem 0 0;
  grid-column: 1 / -1;
  /*
   * The term takes what it needs and no more. A fixed measure lined "Screen" up
   * with "Print" down the whole rail and cost every swatch the difference - and
   * in a column header, where one line stands alone, it cut the widest value of
   * all off at the edge.
   */
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.4rem;
  row-gap: 0.05rem;
}

.values__medium {
  font-size: var(--text-dense);
  line-height: 1.3;
}

.values__value {
  margin: 0;
  font-size: var(--text-dense);
  font-variant-numeric: tabular-nums;
  line-height: 1.3;
}

/* Icons ----------------------------------------------------------------- */

/*
 * A copy that went through says so by becoming a tick for a few seconds. It is the
 * only answer the control can give - the clipboard is not a place the page can
 * show - and it is a change of mark rather than of colour, which is read at a
 * glance and needs no second look to interpret.
 */
.iconbutton .icon--check {
  display: none;
}

.iconbutton[data-copied] .icon--copy {
  display: none;
}

.iconbutton[data-copied] .icon--check {
  display: block;
}

/*
 * Lucide draws on a 24 unit grid with a stroke of two, which is a stroke of one
 * device pixel at sixteen. The size is set here rather than in the markup so
 * that the icon follows the text it stands beside.
 */
.icon {
  display: block;
  width: 1rem;
  height: 1rem;
}

/* Cells ----------------------------------------------------------------- */

/*
 * Everything read in a cell is black or white, whichever stands further from
 * the ground it is read on. Setting the figures in the colour being examined
 * would lose them precisely on the pairs worth looking at: a pair that fails
 * is a pair whose two colours are close together.
 */
/*
 * The pair fills its cell, and the height says so outright rather than as a
 * percentage: a percentage height inside a table cell resolves against a height
 * a browser is free to treat as automatic, and the colour then stopped short of
 * the cell's own edge. What showed through underneath was the page, a band of it
 * above every line - which reads as a line twice as thick between two rows.
 *
 * Definite and clipped, like the swatch of a row label: the content of a cell can
 * be taller than the cell, and a cell that grew would take its whole row with it
 * and stop the cells being square.
 */
.cell {
  display: flex;
  /*
   * The containing block for what covers it: a step of a tone scale carries a
   * link over its whole area, the way a swatch carries the button that opens
   * its colour.
   */
  position: relative;
  height: var(--cell);
  flex-direction: column;
  padding: 0.3rem 0.4rem;
  overflow: hidden;
  background: var(--cell-bg);
  color: var(--cell-ink, var(--cell-fg));
  gap: 0.1rem;
}

/*
 * The one thing in the cell that is looked at rather than read: the pair.
 *
 * Both weights, because they are not the same case. A bold face puts more ink on
 * the ground and survives a contrast a regular one gives up at - which is the
 * distinction every one of these procedures draws somewhere in its own
 * thresholds, and the only one of them a cell can simply show.
 *
 * No emphasis is meant by the bold half, and none is heard: the line carries
 * aria-hidden, because what it demonstrates cannot be read out.
 */
.cell__sample {
  margin: 0;
  color: var(--cell-fg);
  font-weight: 400;
}

.cell__sample b {
  font-weight: 700;
}

.cell__empty {
  margin: 0;
  font-size: var(--text-dense);
  opacity: 0.8;
}

.grid__cell--diagonal {
  background: repeating-linear-gradient(
    -45deg,
    transparent,
    transparent 5px,
    var(--line) 5px,
    var(--line) 6px
  );
}

/*
 * What a cell found, procedure by procedure: two columns, the name of the
 * procedure and everything it has to say. The columns belong to the list rather
 * than to a row, so the value column starts at the same place in every row - and
 * a line of badges that wraps starts there too, instead of running on under the
 * label.
 *
 * The name column is auto: as wide as the widest of four short names and not a
 * millimetre more, and empty rather than wide where the names are set aside.
 */
.metrics {
  display: grid;
  margin: auto 0 0;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: baseline;
  column-gap: 0.4rem;
  row-gap: 0.2rem;
  font-size: var(--text-dense);
  line-height: 1.3;
}

/*
 * The name gives way, never the measurement. A truncated "16…" is a number the
 * reader cannot use, while a truncated name is recoverable from the row it
 * stands in, from the title and from the text that is read aloud.
 */
.metric__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * The measurement, the grade and the badges in one wrapping line, all of them
 * siblings: the first line fills up before anything moves down, which is what a
 * badge pushed onto a line of its own by a box around it could not do.
 *
 * Against the right edge, and every line of it: the numbers of a cell end where
 * the cell ends, so they can be compared down the column rather than read one at
 * a time. The names on the left are what the eye returns to; between the two the
 * line is ragged, which is the side that may be.
 *
 * The items sit on their baselines, and so do the rows of the list around them -
 * a grade in a bordered chip and a number beside it are one line of text, and
 * without saying so the chip's box lines its edge up with the number instead.
 */
.metric__values {
  display: flex;
  margin: 0;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: flex-end;
  gap: 0.2rem 0.25rem;
}

.metric__figure {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/*
 * The grade names itself - "AA", "fail" - so nothing here is carried by colour
 * alone. The border keeps the chip legible on any cell background, which is the
 * one thing a stylesheet cannot know in advance.
 */
/*
 * A pair that has no colour in the medium the cells are painted in. It shows
 * its figures on the page's own ground rather than on an invented grey, which
 * would be a colour pair the palette does not contain.
 */
.cell--unpainted {
  border: 1px dashed var(--line);
}

/*
 * Air between the procedures, once each of them is two lines rather than one.
 * The gap inside a procedure - between its measurement and the badges it was
 * read off - stays the smaller of the two, or the reader has to work out which
 * badge line belongs to which name. Grouping is the ratio of the two gaps and
 * nothing else; there is no rule drawn between them.
 */
.page--cell-l .metrics {
  row-gap: 0.7rem;
}

/*
 * A line of their own, under the measurement and the rung they belong to, and
 * against the same right edge. Two statements of different rank: the rung is the
 * answer, the badges are what it was read off. Side by side they competed for
 * the same line - and WCAG 2 names its rungs after its own levels, so "AA" in
 * the chip and "AA" at the head of the badges read as one thing said twice.
 *
 * flex-basis fills the line the wrapper starts on, which is what pushes it onto
 * a line of its own without a rule saying where anything goes. The gaps repeat
 * the row's own, so the badges keep the rhythm of what stands above them.
 *
 * The tone is the second half of the same statement. A badge is read after the
 * rung, so it is set in a mix of the cell's ink and its ground rather than in
 * the ink itself: near enough to be read at a glance, far enough that the eye
 * reaches the rung first. Mixed from both, so it holds on a dark cell exactly as
 * it does on a light one. The declaration before it is what a browser without
 * color-mix keeps, and that browser sees the full ink - weaker as a hierarchy,
 * never as a statement.
 */
.page--cell-l .badges {
  /*
   * Every property that sets the wrapper aside, taken back. At the smaller sizes
   * it generates no box at all and they had nothing to act on; a real box is
   * clipped to a pixel by them.
   */
  position: static;
  display: flex;
  width: auto;
  height: auto;
  margin: 0;
  overflow: visible;
  flex-basis: 100%;
  flex-wrap: nowrap;
  justify-content: flex-end;
  gap: 0.2rem 0.25rem;
  clip-path: none;
  opacity: 0.68;
  white-space: normal;
}

/*
 * The line never breaks. Where it is longer than the column it runs on to the
 * left, under the name of the procedure - which is empty there anyway, and the
 * alternative was a second row of badges under the first, two ranks deep under a
 * rung that is one statement.
 *
 * That is what flex-end does with what does not fit: it packs against the right
 * edge and lets the excess out on the left. The marks keep their size while it
 * happens, or the line would be kept whole by squeezing the words out of it.
 */
.page--cell-l .badges > .badge {
  flex: none;
}

/*
 * At the smallest size the name has to go - from the eye. The order of the rows
 * is the same in every cell, so nothing is lost by looking; but display:none
 * took the name out of the accessibility tree as well, title and all, which is
 * the one place it could not be got at from. It is set aside the way every
 * other invisible label here is.
 */
/*
 * The smallest cell has no room for the names: they are set aside, and the column
 * they stood in goes with them - one column, and its gap gone with it, or every
 * value would sit a gap's width from an edge with nothing in front of it.
 *
 * The single column is what keeps one measurement to a line. A name set aside is
 * positioned absolutely and is therefore no longer a grid item at all, so the
 * values were placed into whatever cells were free - and landed two to a row.
 */
.page--cell-s .metrics {
  grid-template-columns: minmax(0, 1fr);
  column-gap: 0;
}

/* Marks -------------------------------------------------------------------- */

/*
 * Every mark this application sets is one thing, whatever it marks: a conformance
 * aspect, the rung a pair reached, a value nobody decided. One set of parameters
 * for all of them - the same hairline, the same radius, the same size, the same
 * weight - because three marks built three ways read as three vocabularies, and a
 * reader then has to learn each of them.
 *
 * Nothing is dimmed and nothing is emboldened. A mark at three quarters of the ink
 * reads as a mark three quarters meant, and the weight belongs to the text around
 * it - inside a row header, which a browser sets bold, that means saying so.
 */
.badge,
.chip,
.origin {
  padding: 0 0.2rem;
  border: 1px solid currentcolor;
  border-radius: var(--radius);
  font-size: var(--text-mark);
  font-weight: 400;
  letter-spacing: 0.03em;
  line-height: 1.45;
  white-space: nowrap;
}

/* Behind the value it belongs to, at a distance that is not a word space. */
.origin {
  margin-left: 0.35rem;
}

/*
 * The state is said by the fill and by the line, and by nothing else - never by a
 * colour on its own, so that it survives a grey print and a reader who sees no
 * red:
 *
 *   reached                     the cell inverted
 *   missed, a requirement       its outline alone
 *   missed, a recommendation    its outline dashed
 *
 * Inverted is the ink of the cell as the ground and the ground as the ink. Both
 * come from the cell, so the mark carries the contrast the cell was legible in to
 * begin with. Not currentcolor for the ground: it resolves against this element's
 * own colour, which the next line replaces, and the mark would come out in one
 * colour. The border stays where it cannot be seen - a pixel on each side, without
 * which a row of marks would step in and out as the verdicts change.
 */
.badge--pass,
.chip {
  border-color: var(--mark-ink, var(--cell-ink, var(--ink)));
  background: var(--mark-ink, var(--cell-ink, var(--ink)));
  color: var(--cell-bg, var(--ground));
}

.badge--fail,
.chip--fail {
  border-color: currentcolor;
  border-style: solid;
  background: none;
  color: inherit;
}

/*
 * Dashed rather than dotted. A dotted hairline beside a solid one is a
 * difference nobody finds without being told to look for it, and this is the
 * mark that has to carry the distinction between what a standard demands and
 * what it merely prefers - APCA's Lc 90 is the only aspect in the application
 * that can ever wear it.
 */
.badge--advisory {
  border-style: dashed;
}

/*
 * On a page of running text a mark is read rather than glanced at, and the size it
 * has in a cell of a few centimetres is not one for a line of prose. The size is
 * the only thing that changes: it is the same mark, only legible.
 */
.canvas--plain .badge {
  font-size: var(--text-small);
}

/* Rail sections --------------------------------------------------------- */

.section {
  border-bottom: 1px solid var(--line);
}

.section > summary {
  margin: 0;
  padding: 0.6rem var(--inset);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/*
 * The blocks of a section stand one step apart, and the step is the container's
 * rather than each block's own bottom margin. A form directly inside the body is
 * such a container too: the section is one column of blocks whether or not a
 * form happens to be wrapped around some of them, and the reader sees no seam
 * where one begins.
 *
 * Which is also why the blocks themselves are stripped of their vertical
 * margins here. A margin that survived would be added to the gap, and the one
 * gap the rail promises would hold everywhere except next to whatever carried
 * one.
 */
.section__body,
.section__body > form {
  display: flex;
  flex-direction: column;
  gap: var(--stack);
}

.section__body {
  padding: 0 var(--inset) var(--gutter);
}

.section__body > *,
.section__body > form > * {
  margin-block: 0;
}

/*
 * The buttons of the rail span it. One fills the width, two share it - which is
 * what the palette section needs, where a pair sits side by side.
 */
.actions,
.options__actions {
  display: flex;
  gap: 0.4rem;
}

.options__actions {
  margin: 0;
}

.actions {
  padding: var(--gutter) var(--inset);
}

/*
 * Only ever above the first button, and only until there is a colour.
 *
 * Italic, the way a placeholder is: this is the application talking about
 * itself, not a label on anything in the rail, and the one thing that stands
 * where controls normally do has to read as something other than a control.
 */
.intro {
  margin: 0;
  padding: var(--gutter) var(--inset) 0;
  color: var(--ink-muted);
  font-style: italic;
}

.intro + .intro {
  padding-top: 0.5rem;
}

.actions > button,
.actions > .button,
.options__actions > button,
.options__actions > .button {
  flex: 1;
}

/* Forms ----------------------------------------------------------------- */

.field {
  margin: 0 0 0.5rem;
}

.field label {
  display: block;
  margin-bottom: 0.25rem;
}

/*
 * Italic, so that a suggestion is not read as an entry. Several of these fields
 * say what would happen if they were left empty - "calculated", "measured" -
 * and a placeholder set in the same face as a value invites the reader to take
 * it for one.
 */
::placeholder {
  font-style: italic;
}

.field input[type="text"],
.field input[type="number"],
.field select,
.readout__field {
  width: 100%;
  height: var(--control);
  padding: 0 0.4rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--ground);
  color: inherit;
  font: inherit;
  font-variant-numeric: tabular-nums;
}

/*
 * A field with the focus says so itself: its edge turns the one pure colour on the
 * page and thickens to two pixels. Half of that is the border it already has,
 * turned to the new colour, and half is an outline of a single pixel laid flush
 * against it - an outline takes part in no layout, so the text inside stays
 * exactly where it was while a wider border would have moved it by a pixel. Both
 * follow the radius of the field by themselves.
 *
 * On :focus rather than :focus-visible. A text field that was clicked into has
 * the focus as much as one that was tabbed into, and a select that a pointer
 * opened is exactly the control that needs to say where the focus went.
 */
.field input[type="text"]:focus,
.field input[type="number"]:focus,
.field input[type="color"]:focus,
.field select:focus,
.readout__field:focus,
.import textarea:focus {
  border-color: var(--focus);
  outline: 1px solid var(--focus);
  outline-offset: 0;
}

/*
 * A field that is read keeps saying so while it has the focus. Its border is
 * dashed because nobody fills it in, and an outline laid solid against it would
 * close the dashes back up into a line - the edge would say "type here" at
 * exactly the moment somebody is looking at it.
 */
.readout__field:focus {
  outline-style: dashed;
}

fieldset {
  margin: 0 0 0.75rem;
  padding: 0;
  border: none;
}

legend {
  padding: 0;
}

/*
 * The hand cursor belongs to links and to nothing else. Anchors wearing
 * .button keep it from the user agent, which is correct - they navigate.
 * Buttons, summaries and labels do not, and none of them ask for one.
 */
button,
summary,
label {
  cursor: default;
}

/*
 * The same edge as a field, the ground it stands on instead of a field's bright
 * inset, and the muted ink a resting switch is written in - so a button sits at
 * the weight of a switch that is off and never above one that is on. Its label is
 * set in the text of the page for the same reason: a bold label shouts a button
 * that is one of several equals.
 *
 * Not the border colour itself, tempting as the symmetry is: that is 2.5:1
 * against this ground, and a tool that measures contrast cannot label its own
 * buttons below the threshold it reports on. The muted ink is 6:1.
 */
button,
.button {
  display: inline-flex;
  height: var(--control);
  align-items: center;
  justify-content: center;
  padding: 0 0.75rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--ground-sunken);
  color: var(--ink-muted);
  font: inherit;
  /*
   * The line height carries the centring, not the flex box alone: `font: inherit`
   * hands the button the line height of the text around it, and a line of 23
   * pixels in a box of 36 sits where the browser's own idea of a button's
   * baseline puts it. As tall as the control, one line is centred in it by
   * arithmetic - and it has to come after the shorthand, which would reset it.
   */
  line-height: var(--control);
}

/*
 * A button answers the pointer and the keyboard the way a field does: its border
 * turns the same pure colour and a single pixel of outline lies flush against it,
 * which comes to the same two pixel edge without the label inside moving. One
 * signal for both, because they mean the same thing - this is what the next thing
 * you do will happen to.
 *
 * Icon controls keep their own answer, which is to invert: their own rule is the
 * more specific of the two, and it takes the outline away again.
 */
button:hover,
.button:hover,
button:focus-visible,
.button:focus-visible {
  border-color: var(--focus);
  outline: 1px solid var(--focus);
  outline-offset: 0;
  color: var(--ink);
}

/* Notices --------------------------------------------------------------- */

.notice {
  margin: 0 0 0.75rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--ground-sunken);
  font-size: var(--text);
}

.notice ul {
  margin: 0;
  padding-left: 1.25rem;
}

.notice--warning {
  border-width: 2px;
}

.notice h2 {
  margin: 0 0 0.25rem;
  font-size: var(--text);
}

/* Utilities ------------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: -9999px;
}

.skip-link:focus {
  position: static;
  display: inline-block;
  padding: 0.5rem;
}

/*
 * For assistive technology only. Not display:none, which would remove the text
 * there as well - which is the whole promise a smaller cell makes: what the size
 * changes is what is visible, never what is there.
 *
 * Stated once, for every place that makes it. Copied out by hand it was copied
 * out incompletely: each copy left off the padding and the border, and an
 * element carrying either is not set aside but shrunk to a sliver of itself.
 *
 *   .badges                     the conformance detail, which only fits to the
 *                               eye once the cells are large
 *   .metric__label              the name of a procedure, where a cell has room
 *                               for the figure and not for both
 *   .values__medium             the word beside a luminance, which together are
 *                               wider than a cell of seven rems
 */
.u-visually-hidden,
.badges,
.page--cell-s .metric__label,
.page--cell-s .swatch--head .values__medium,
.page--cell-m .swatch--head .values__medium {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  border: 0;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Dialogs --------------------------------------------------------------- */

/*
 * Two ways in, one appearance. The invoker command opens the dialog modally;
 * ?edit=2 renders it with the open attribute, which is not modal and would
 * otherwise sit inline at the end of the page. Placing it the same way in both
 * cases is what makes the address a genuine second route rather than a
 * different feature.
 */
.dialog {
  width: min(26rem, calc(100vw - 2rem));
  max-height: calc(100vh - 4rem);
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--ground);
  color: var(--ink);
}

/*
 * Centred, and the two lines that look superfluous are the ones doing it. A
 * dialog comes with inset: 0 and margin: auto from the browser; overriding only
 * top and left leaves right and bottom at 0, and the automatic margins then
 * centre the box inside the bottom right quarter of the window rather than
 * inside the window. The shift that follows is half the box, not half the
 * quarter, so it lands low and to the right.
 */
.dialog[open] {
  position: fixed;
  z-index: 10;
  top: 50%;
  right: auto;
  bottom: auto;
  left: 50%;
  overflow: auto;
  margin: 0;
  transform: translate(-50%, -50%);
}

/*
 * What lies behind an open dialog. Written out rather than taken from a token:
 * ::backdrop does not reliably inherit, and a custom property in it falls back to
 * no ground at all in the browsers where it does not.
 *
 * Darker in the dark scheme than in the light one, which sounds backwards and is
 * not: dimming takes brightness away, and a dark page has little of it to take -
 * what separates a dialog from a white page at 55 percent needs three quarters
 * against a near-black one.
 */
.dialog::backdrop {
  background: rgb(0 0 0 / 55%);
}

@media (prefers-color-scheme: dark) {
  .dialog::backdrop {
    background: rgb(0 0 0 / 75%);
  }
}

/*
 * The inside of a dialog, whichever kind it is. The box itself carries none, so
 * that a scrollable body can reach its edges; what stands in it does. A dialog
 * with fields is a form, one that only reads is not - and both are inside.
 */
.dialog__form,
.dialog__body {
  padding: var(--gutter);
}

.dialog__title {
  margin: 0 0 0.75rem;
}

.dialog__actions {
  display: flex;
  margin: var(--gutter) 0 0;
  align-items: center;
  gap: 0.5rem;
}

/*
 * Filling in and taking away are two things, and they stand side by side rather
 * than one after the other: a column of fields and a column of conversions. Mixed
 * into one list, a reader has to work out which of the numbers they are meant to
 * edit - and the answer would be "the ones with a box around them", which is not
 * an answer a layout should make anybody look for.
 *
 * Only a dialog that has something to read is widened. The one an added colour is
 * typed into has no values yet and stays the narrow column it always was.
 */
.dialog--paired {
  width: min(46rem, calc(100vw - 2rem));
}

.dialog__columns--paired {
  display: grid;
  gap: var(--gutter);
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

/*
 * A line between the halves, drawn by the second of them: filling in and taking
 * away are two things, and the line is what says they are not one list.
 */
.dialog__columns--paired > .dialog__column + .dialog__column {
  padding-left: var(--gutter);
  border-left: 1px solid var(--line);
}

/*
 * The colour written out, medium by medium: a heading for each, and under it the
 * notations as terms with their values beside them. Every value stands in a field
 * of its own - see the partial, which says why - and the field takes the width
 * that is left, so a long oklch() is not cut where a short hex value would fit.
 */
.readout + .readout {
  margin-top: 1.25rem;
}

.readout__medium {
  display: flex;
  margin: 0 0 0.35rem;
  align-items: baseline;
  font-size: var(--text-small);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.readout__values {
  display: grid;
  margin: 0;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  align-items: center;
  column-gap: 0.4rem;
  row-gap: 0.25rem;
}

/*
 * What is to be said about the colour, under the last of its values. The bullets
 * stand in the left edge of the heading above them and the lines that wrap sit
 * under the text rather than under the bullet, which is what the padding buys:
 * the marker is drawn outside the box, so the box starts one marker further in.
 */
.readout__notes {
  margin: 0;
  padding-left: 1.15em;
  font-size: var(--text-small);
  line-height: 1.4;
}

.readout__term {
  color: var(--ink-muted);
  font-size: var(--text-small);
}

/*
 * The value and the control that copies it, side by side: the field takes the
 * width that is left, the control keeps its square.
 */
.readout__value {
  display: flex;
  margin: 0;
  align-items: center;
  gap: 0.25rem;
}

/*
 * A field in every respect but one: it is read, not filled in, and its outline
 * says so by not being drawn through. Everything else it shares with the fields in
 * the other half, because it is the same thing to a click and to a keystroke.
 */
.readout__field {
  border-style: dashed;
}

.button {
  text-decoration: none;
}

/* Forms, continued ------------------------------------------------------ */

.field__pair {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.field__pair input[type="text"] {
  flex: 1 1 auto;
  min-width: 0;
}

/*
 * The picker speaks hex and nothing else, so it can only ever be an addition to
 * the field beside it. It ships hidden and is revealed by the script that keeps
 * the two in step - without that script it would be a second field writing a
 * value the first one does not know about.
 */
.field__picker {
  width: 2.25rem;
  height: var(--control);
  flex: none;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: none;
}

/*
 * Built like every other field and nothing else: the same hairline, the same
 * radius, the same height, and the focus of the group it belongs to. What a
 * browser adds of its own - a bezel around the swatch, an inset, a shadow - is
 * taken off, or the one control that shows a colour would be the one control that
 * looks like a different application.
 *
 * The swatch keeps a radius a pixel tighter than the field's, which is the radius
 * of what it sits inside: the same arithmetic as any other border of a border.
 */
.field__picker::-webkit-color-swatch-wrapper {
  padding: 0;
}

.field__picker::-webkit-color-swatch {
  border: none;
  border-radius: calc(var(--radius) - 1px);
  box-shadow: none;
}

.field__picker::-moz-color-swatch {
  border: none;
  border-radius: calc(var(--radius) - 1px);
  box-shadow: none;
}

/*
 * A value typed in parts: the letters that name them and the fields they stand
 * in, across the full width of the row. Equal columns, because the four
 * percentages of a colour are four of a kind and one of them being wider would
 * suggest it mattered more - and the letter takes what it needs while the field
 * takes the rest.
 */
.parts {
  margin: 0 0 0.75rem;
  padding: 0;
  border: none;
}

.parts__legend {
  padding: 0 0 0.25rem;
}

/*
 * The parts in their own row inside the group, because a legend in a flex
 * container is a special case and this way it stays an ordinary one.
 */
.parts__row {
  display: flex;
  gap: 0.3rem;
}

/*
 * Equal shares of the row: four percentages of a colour are four of a kind, and
 * one of them being wider would suggest it mattered more. The letter takes what
 * it needs, the field takes the rest.
 */
.parts__part {
  display: flex;
  flex: 1 1 0;
  min-width: 0;
  align-items: center;
  gap: 0.3rem;
}

.parts__letter {
  flex: none;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

/*
 * The field gives way, never the letter. A number input brings a width of its own
 * that has nothing to do with the row it stands in, and min-width is what lets it
 * shrink inside a grid column at all.
 */
.parts__input {
  width: 100%;
  min-width: 0;
}

.field__hint {
  margin: 0 0 0.75rem;
  color: var(--ink-muted);
  font-size: var(--text-small);
}

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

/*
 * A checkbox or radio group as one horizontal bar, filling the width and never
 * wrapping. Four grades used to cost four lines of rail; they cost one now,
 * which is what keeps the matrix in view beside its own filters.
 *
 * The inputs are really there - see partial/switchbar.php - only moved out of
 * sight, so everything below styles the label and nothing here invents a
 * control. The fieldset around it and its legend are styled globally; the bar
 * is the only new part.
 */
.switchbar__bar {
  display: flex;
  flex-wrap: nowrap;
  width: 100%;
  height: var(--control);
  margin-top: 0.25rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}

/* Equal shares of whatever width there is, and none of them wider than that. */
.switchbar__option {
  display: flex;
  flex: 1 1 0;
  min-width: 0;
}

/* One hairline between neighbours, and none at the ends - the bar has an edge. */
.switchbar__option + .switchbar__option {
  border-left: 1px solid var(--line-strong);
}

.switchbar__label {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
  min-width: 0;
  padding: 0 0.25rem;
  background: var(--ground);
  color: var(--ink-muted);
}

/* Inside the frame, so one pixel less than the frame's own rounding. */
.switchbar__option:first-child .switchbar__label {
  border-radius: calc(var(--radius) - 1px) 0 0 calc(var(--radius) - 1px);
}

.switchbar__option:last-child .switchbar__label {
  border-radius: 0 calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0;
}

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

/*
 * The same step a field leaves above the note that belongs to it, and nothing
 * below it: what follows the bar is a block of the section, and the gap between
 * blocks is the section's to give.
 */
.switchbar .field__hint {
  margin: 0.5rem 0 0;
}

/*
 * Ground and writing trade places, and that is the whole mark. A difference in
 * brightness rather than in hue, so it survives greyscale and every colour
 * vision deficiency - no tick and no bold beside it.
 */
.switchbar__input:checked + .switchbar__label {
  background: var(--accent);
  color: var(--ground);
}

.switchbar__label:hover {
  color: var(--ink);
}

.switchbar__input:checked + .switchbar__label:hover {
  color: var(--ground);
}

/*
 * The input cannot show a focus ring from where it is, so the label wears it -
 * lifted above the neighbour it shares an edge with, or that neighbour would
 * cut the ring in half. Drawn in ink rather than in the accent, which would
 * disappear into the background of a switch that is on.
 */
.switchbar__input:focus-visible + .switchbar__label {
  position: relative;
  z-index: 1;
  outline: 2px solid var(--ink);
  outline-offset: 1px;
}

/*
 * Forced colours discards the palette above. The system highlight is the one
 * pair that still means "selected" there.
 */
@media (forced-colors: active) {
  .switchbar__input:checked + .switchbar__label {
    background: Highlight;
    color: HighlightText;
  }

  /*
   * The marks say their state by fill and line, and forced colors replaces both
   * with the system palette - after which a reached aspect and a missed one are
   * the same ground and the same border, and a cell's five WCAG badges render
   * identically. In an accessibility tool that is the regression that matters
   * most, so the construction is restated in system colors, where it is honoured:
   * inverted stays inverted, an outline stays an outline.
   *
   * The dashed line of a recommendation needs nothing here. Only colors are
   * forced, and border-style comes through untouched.
   */
  .badge--pass,
  .chip {
    border-color: CanvasText;
    background: CanvasText;
    color: Canvas;
  }

  .badge--fail,
  .chip--fail {
    border-color: CanvasText;
    background: Canvas;
    color: CanvasText;
  }
}

/* The legend of the procedures ------------------------------------------ */

/*
 * Badge and meaning, side by side: the badge is drawn exactly as it appears in
 * a cell, because a legend that redraws its subject explains something else.
 */
.legend {
  display: grid;
  margin: 0.5rem 0 1rem;
  gap: 0.3rem 0.75rem;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: baseline;
}

.legend dt {
  grid-column: 1;
}

.legend dd {
  grid-column: 2;
  margin: 0;
  color: var(--ink-muted);
}

/* Page heading ---------------------------------------------------------- */

/*
 * The title of the view, at the head of the content. It carries the heading
 * rank because the view is what the page is about - the application's name in
 * the corner is a logo and stands on every page alike.
 */
/*
 * The one heading of the page. Room above it of about three quarters of its own
 * size and a little less below: a heading flush against the strip over it reads
 * as part of that strip, and the space is what makes it the beginning of
 * something - as much as that and no more, or it reads as detached from what
 * follows it.
 *
 * Only where it is seen. On the matrix and the tone scales it is set aside once
 * there are colours - the table says which view this is better than a line of
 * text - and a margin on a box taken out of the flow does nothing either way.
 */
.canvas__title,
.dialog__title {
  font-size: var(--text-title);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
}

.canvas__title {
  margin: 1.5rem 0 1.25rem;
}

/* Plain pages ----------------------------------------------------------- */

.canvas--plain h2 {
  margin: 1.5rem 0 0.5rem;
  font-size: var(--text-heading);
}

/*
 * A table of a plain page takes the column it stands in, whatever it holds. Left
 * to shrink-to-fit it takes the width of its widest cell, which makes the same
 * table narrow on one page and wide on another and leaves a rule under a row
 * ending in the middle of the text above it. The measure the column is capped at
 * is what keeps the lines readable; the table has no reason to stop short of it.
 *
 * The matrix and the tone scales are not these tables: they are sized by the
 * cell, span the rail and carry their own class.
 */
.table {
  width: 100%;
  border-collapse: collapse;
}

.table th,
.table td {
  padding: 0.3rem 0.75rem 0.3rem 0;
  border-bottom: 1px solid var(--line);
  text-align: left;
}

/*
 * A distance and a ratio are single tokens: "50 points" broken after the number
 * reads as two figures, and "4.58:1" broken at the colon reads as neither. The
 * prose column is the one with room to wrap.
 */
.table--figures :is(th, td):nth-child(-n + 2) {
  white-space: nowrap;
}

.empty {
  margin: var(--gutter) 0;
  color: var(--ink-muted);
}

.summary {
  margin: 0.75rem 0;
  color: var(--ink-muted);
}

.canvas--plain .summary {
  margin: 0.5rem 0;
}

/* Palette section ------------------------------------------------------- */

.links {
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
}

.links li {
  margin-bottom: 0.3rem;
}

/* Import ---------------------------------------------------------------- */

/*
 * The file field in the application's own construction, as far as a file field
 * can be taken: the frame is the frame every other control has, and the button
 * the browser puts inside it is the button drawn everywhere else. Those two
 * parts are all there is to reach - the file name beside the button is the
 * browser's own text and stays with it, which is the price of keeping the
 * native control and the picker it opens. A label and a hidden input would buy
 * the last few pixels and lose the one thing the control has to do.
 *
 * Taller than a single-line control by the padding around the button, so the
 * button inside keeps the height every other button has.
 */
.field__file {
  width: 100%;
  max-width: var(--measure);
  padding: 0.4rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--ground);
  color: inherit;
  font: inherit;
}

.field__file::file-selector-button {
  height: var(--control);
  margin-right: 0.6rem;
  padding: 0 0.75rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--ground-sunken);
  color: var(--ink-muted);
  font: inherit;
  /* The same arithmetic that centres the label of an ordinary button. */
  line-height: var(--control);
}

/*
 * The pointer is over the button whenever it is over the field, near enough:
 * there is no selector for the button alone. Which is the honest answer anyway,
 * because clicking anywhere in a file field opens the picker.
 */
.field__file:hover::file-selector-button {
  border-color: var(--focus);
  color: var(--ink);
}

/*
 * The frame answers the focus, not the button inside it: the browser gives the
 * focus to the input, and an outline drawn around the button would put the
 * signal somewhere the focus is not.
 */
.field__file:focus {
  border-color: var(--focus);
  outline: 1px solid var(--focus);
  outline-offset: 0;
}

.import textarea {
  width: 100%;
  max-width: var(--measure);
  padding: 0.4rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--ground);
  color: inherit;
  font-family: ui-monospace, monospace;
}

.prose {
  max-width: var(--measure);
}

/*
 * A step of a tone scale opens its own values, and the whole cell is what opens
 * them. A link rather than a button: the dialog is not in this markup, so the
 * address fetches it - see ToneViewing for the weight that decides it.
 *
 * Built like the button on a swatch, and for the same reason: an interactive
 * element may not sit inside another, so the area is carried on a pseudo-element
 * while the link itself is a pixel with its label set aside.
 */
.cell__open {
  display: block;
  position: static;
  z-index: 1;
  width: 1px;
  height: 1px;
  margin: 0;
  color: inherit;
  text-decoration: none;
}

.cell__open::after {
  position: absolute;
  z-index: 0;
  content: "";
  inset: 0;
}

.cell__open:hover::after,
.cell__open:focus-visible::after {
  outline: 2px solid var(--cell-ink, var(--ink));
  outline-offset: -3px;
}

.cell__open:focus-visible::after {
  outline-width: 3px;
}

.cell__open:focus-visible {
  outline: none;
}

/*
 * The mark that says a step has something to answer for, ahead of its values the
 * way it stands ahead of a colour's name. It takes the ink of the cell, like
 * everything else written there.
 */
/*
 * That a step has something to answer for, in the corner it starts reading from.
 * Not beside a value: the values stand against the right edge, where a mark
 * among them shifts with the width of whatever it happens to stand next to, and
 * a step has one dialog rather than one per number.
 *
 * The corner is free. A cell of a tone scale carries no sample line, and the
 * values sit at its foot.
 */
.cell__warning {
  display: flex;
  position: absolute;
  z-index: 1;
  top: 0.3rem;
  left: 0.4rem;
}

/*
 * A row of values that has no badges behind it does not wrap: a mark and the
 * value it belongs to are one statement, and at the smallest cell size the two
 * were landing on lines of their own. Recognised by the cell carrying a link to
 * its own values - the matrix has none, and there the wrap is what lets a line
 * of badges fill up before anything moves down.
 */
.cell:has(.cell__open) .metric__values {
  flex-wrap: nowrap;
}

