/* The look is a phone app first, because that is where this gets used: standing
 * somewhere with one hand free. So the chrome is the system font at system
 * sizes, the targets are thumb-sized, and the one thing worth doing on a screen
 * is the only thing that screen shows.
 *
 * Only the text under review is monospace. It is the thing being weighed word by
 * word, and a proportional face hides the run of a sentence; everything around
 * it reads better as ordinary interface text. */
:root {
  --bg: #000;
  --raised: #1c1c1e;
  --raised-2: #2c2c2e;
  --line: #38383a;
  --ink: #f2f2f7;
  --dim: #8e8e93;
  --tint: #0a84ff;
  --good: #30d158;
  --warn: #ff9f0a;
  --bad: #ff453a;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  --ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif;
  --radius: 12px;
  --pad: 16px;
  /* the home indicator on a phone with no button, so a bottom bar clears it */
  --floor: env(safe-area-inset-bottom, 0px);
  /* how tall the applet's own bars are, which a panel sits under; an applet that
   * measures its bars sets it, and one with none leaves it at nothing */
  --bars: 0px;
}
/* The light set, once: what the system preference picks, and what the toggle
 * picks over it. A chosen theme is `data-theme` on the root and wins either
 * way; with none set the system decides. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: #f2f2f7;
    --raised: #fff;
    --raised-2: #e9e9ec;
    --line: #d1d1d6;
    --ink: #1c1c1e;
    --dim: #6c6c70;
    --tint: #007aff;
    --good: #248a3d;
    --warn: #b25000;
    --bad: #d70015;
  }
}
:root[data-theme="light"] {
  --bg: #f2f2f7;
  --raised: #fff;
  --raised-2: #e9e9ec;
  --line: #d1d1d6;
  --ink: #1c1c1e;
  --dim: #6c6c70;
  --tint: #007aff;
  --good: #248a3d;
  --warn: #b25000;
  --bad: #d70015;
}
* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}
/* Any `display` in a rule below beats the hidden attribute, so a hidden panel
 * stays laid out over the page and swallows every tap behind it, and a hidden
 * pane shows its textarea over whatever replaced it. Both of those shipped, from
 * two different rules, which is what makes this the right place to fix it rather
 * than one selector at a time. */
[hidden] {
  display: none !important;
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.5 var(--ui);
  height: 100dvh;
  display: flex;
  overflow: hidden;
}

/* The frame every applet sits in: the bar across the top and the stage under it.
 *
 * The stage is a row, the left panel, the applet's `main` and the right panel, and
 * `main` takes whatever width the panels leave. Which side a panel sits on is its
 * `data-side` and not where the applet wrote it, so an applet cannot put its list on
 * the wrong edge by writing its markup in another order. Pages and sheets are fixed,
 * over all of it, and take no room in the row. */
body {
  flex-direction: column;
}
#bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  background: var(--bg);
  flex: none;
  min-width: 0;
}
#bar-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 16px;
  font-weight: 600;
  text-align: center;
}
#bar-actions,
#bar-jobs,
#bar-switcher {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}
/* Actions wider than the room the bar has scroll inside their own row at every width and
 * push nothing past the screen; on a phone the row is a line of its own, below. */
#bar-actions {
  flex: 0 1 auto;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
#bar-actions > * {
  flex: none;
}
#bar-actions:empty,
#bar-jobs:empty,
#bar-actions:not(:has(> :not([hidden]))) {
  /* nothing to show, and an applet may hold an action hidden until it applies, so
   * the row goes rather than leaving the gap before it under the bar */
  display: none;
}
/* The title keeps a width of its own however much else the bar carries, and the
 * switcher gives way before it does, its name cut short rather than the title gone. */
#bar-title {
  min-width: 5rem;
}
#bar-switcher {
  flex: 0 1 auto;
  min-width: 0;
}
#bar-switcher .switch,
#bar-switcher .switch-name {
  min-width: 0;
  max-width: 100%;
}
#bar-switcher .switch > span:first-child,
#bar-switcher .switch-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* On a phone one row does not hold the applet's actions beside the rest, so they are
 * a second row of their own under it, scrolling sideways where they do not fit rather
 * than pushing anything past the edge of the screen. The panels sit under both rows,
 * since `--bars` is measured off the bar. */
@media (max-width: 820px) {
  #bar {
    flex-wrap: wrap;
    row-gap: 8px;
  }
  /* A wrapping row is broken where its items stop fitting at their base widths,
   * before any of them shrinks, so the switcher is measured from nothing and grows
   * to its name's width at most, as the title already does into the rest. The first
   * row then holds, and only the actions wrap. */
  #bar-switcher {
    flex: 1 1 0;
    max-width: max-content;
  }
  #bar-actions {
    order: 1;
    flex: 1 0 100%;
  }
}
#stage {
  flex: 1;
  display: flex;
  min-height: 0;
  min-width: 0;
}
#stage > main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  /* Without this a flex child refuses to shrink below its content, so a long page
   * made `main` taller than the screen and the overflow never reached the element
   * that would have scrolled it. */
  min-height: 0;
  background: var(--bg);
}
#stage > .panel[data-side="left"] {
  order: -1;
}
#stage > .panel[data-side="right"] {
  order: 1;
}

/* Controls.
 *
 * 44px is apple's own minimum target, and it is the difference between a tool
 * you can use walking and one you have to stop and aim at. */
input,
button,
select,
textarea {
  font: inherit;
  font-family: var(--ui);
  color: var(--ink);
  background: var(--raised-2);
  border: none;
  border-radius: 10px;
  padding: 10px 14px;
  min-height: 44px;
}
textarea {
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1.55;
  width: 100%;
  border: 1px solid var(--line);
  background: var(--raised);
  padding: 12px;
  resize: vertical;
}
button {
  cursor: pointer;
  font-weight: 500;
  transition: opacity .12s;
}
button:active {
  opacity: .55;
}
button.primary {
  background: var(--tint);
  color: #fff;
}
button.quiet {
  background: none;
  color: var(--tint);
  padding-inline: 6px;
}
button.small {
  padding: 8px 12px;
  min-height: 36px;
  font-size: 15px;
}
/* Beside the thing it copies rather than in a bar, and quiet, since copying is
   never the answer to what the page is asking. */
button.copy {
  background: none;
  color: var(--tint);
  padding: 8px 12px;
  min-height: 36px;
  font-size: 15px;
}
/* Two positions and one meaning each. Left is always the files. Right is always
 * whatever belongs to what is on screen. Nothing else lives on the bar, so the
 * name in the middle has the room it needs.
 *
 * The glyphs are drawn rather than typed: an emoji renders at whatever size and
 * weight the platform feels like, which is how a folder ended up twice the height
 * of the text beside it. */
button.bar-icon {
  background: none;
  border: none;
  width: 34px;
  min-width: 34px;
  min-height: 34px;
  padding: 0;
  color: var(--dim);
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
button.bar-icon svg {
  width: 19px;
  height: 19px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
button.bar-icon:active {
  color: var(--ink);
}
/* A thumb's target on a phone is 44px, apple's own minimum; the glyph keeps its size inside it. */
@media (max-width: 820px), (pointer: coarse) {
  button.bar-icon {
    width: 44px;
    min-width: 44px;
    min-height: 44px;
  }
}
button.push {
  background: var(--tint);
  color: #fff;
  border-radius: 16px;
  font-weight: 600;
  font-size: 14px;
  min-height: 32px;
  padding: 0 14px;
  flex: none;
}
button.wide {
  width: 100%;
  background: var(--raised-2);
}
/* This one opens a sheet over the page rather than swapping what is under the
 * bar, so it does not read as one of the view buttons beside it. The caret is
 * the part that says so: it points at where the sheet comes from. */
button.opens-sheet {
  background: var(--raised-2);
  color: var(--ink);
  padding: 8px 12px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-right: 4px;
}
button.opens-sheet::after {
  content: "\25B4";
  font-size: 11px;
  color: var(--dim);
}
button.opens-sheet[aria-expanded="true"]::after {
  content: "\25BE";
}
select {
  appearance: none;
  padding-right: 30px;
}

/* The side panel, one primitive on both sides.
 *
 * It is an overlay: fixed under the bars, slid in from its own edge, dimming the
 * page behind it. From 821px up, where it carries `data-dock`, it is a column in
 * the flow beside the reading pane instead, collapsed by its button rather than
 * slid away; one without `data-dock` stays an overlay at every width. Which
 * side it is on is the `data-side` attribute and nothing else: no rule below is
 * keyed on which panel it is, and the two carry the same header, the same
 * close, the same swipe out. What each holds is content, styled as content. */
.panel {
  position: fixed;
  top: var(--bars);
  bottom: 0;
  width: min(340px, 86vw);
  z-index: 30;
  background: var(--raised);
  display: flex;
  flex-direction: column;
  transition: transform .24s cubic-bezier(.32, .72, 0, 1);
  padding-bottom: var(--floor);
}
.panel[data-side="left"] {
  left: 0;
  right: auto;
  border-right: 1px solid var(--line);
  box-shadow: 10px 0 40px rgb(0 0 0 / .5);
  transform: translateX(-100%);
}
.panel[data-side="right"] {
  right: 0;
  left: auto;
  border-left: 1px solid var(--line);
  box-shadow: -10px 0 40px rgb(0 0 0 / .5);
  transform: translateX(100%);
}
.panel.in {
  transform: translateX(0);
}
.panel > header {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: var(--pad);
  border-bottom: 1px solid var(--line);
  flex: none;
  touch-action: none;
}
@media (min-width: 821px) {
  .panel[data-dock],
  .panel[data-dock][data-side="left"],
  .panel[data-dock][data-side="right"] {
    position: static;
    transform: none;
    width: 320px;
    flex: none;
    height: auto;
    z-index: auto;
    box-shadow: none;
    padding-bottom: 0;
    background: var(--bg);
  }
  .panel[data-dock].collapsed {
    display: none;
  }
}

/* Belongs to the panels rather than to the narrow layout: both of them are
 * overlays at every width, and the right one had no backdrop on a wide screen. */
#backdrop {
  position: fixed;
  inset: var(--bars) 0 0 0;
  z-index: 20;
  background: rgb(0 0 0 / .45);
  opacity: 0;
  pointer-events: none;
  transition: opacity .24s;
}
#backdrop.on {
  opacity: 1;
  pointer-events: auto;
}
.panel-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.panel-bar strong {
  font-size: 17px;
}

/* Two conventions, and they are meant to feel different.
 *
 * A PAGE is somewhere you went. It slides in from the right, fills the screen,
 * is opaque, has no backdrop, and gives itself back through a Back chevron on
 * the left. Drills and Notes are pages.
 *
 * A SHEET is something in front of what you were doing. It comes up from the
 * bottom, stops short of the top, has a grab handle and a dimmed backdrop, and
 * closes on the right. The file list is a sheet.
 *
 * Making both of them a full-screen overlay with a button in the corner was the
 * thing that read as arbitrary: same shape, two names, no way to guess which one
 * takes you somewhere and which one is a detour. */
.page {
  position: fixed;
  inset: 0;
  z-index: 10;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform .26s cubic-bezier(.32, .72, 0, 1);
}
.page.in {
  transform: translateX(0);
}
.page > header {
  padding: 10px 12px;
  display: flex;
  gap: 8px;
  align-items: center;
  border-bottom: 1px solid var(--line);
  flex: none;
}
.page > header strong {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 16px;
  font-weight: 600;
  text-align: center;
}
.page > header .back {
  background: none;
  color: var(--tint);
  padding: 0 6px 0 0;
  min-height: 44px;
  font-size: 17px;
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
.page > header .back::before {
  content: "\2039";
  font-size: 26px;
  line-height: 1;
}

/* The parts a page and a panel are built from, shared by every one of them.
 * A page is a header, a body and an action bar; a panel is a header, a body and
 * a foot. Nothing below is keyed on which page or panel it is in; a variant is a
 * data attribute on the element. */
.page-body {
  flex: 1;
  overflow: auto;
  padding: var(--pad);
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* `safe` because plain centring in a scrolling flex column puts the overflow
   above the scroll origin, where nothing can reach it: the compare deck grew
   from three cards to four and the first one would have gone off the top with
   no way to scroll back to it. Safe centring falls back to flex-start the
   moment the content is taller than the box. */
/* A page's content is a centred column of at most 60rem, so a line does not run the
 * width of a laptop; on a phone the padding is the page's own and the column is the
 * screen. The percentage is the page's width, the body's containing block. */
.page > .page-body {
  padding-inline: max(var(--pad), calc((100% - 60rem) / 2));
}
.page-body[data-align="centre"] {
  justify-content: safe center;
}
/* One row per setting: the key and its control on a line, the sentence and the
 * source under it. A select where the kind is a closed set, a field otherwise;
 * which is a data attribute, and nothing is keyed on the setting's name. */
.setting {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 12px;
  align-items: center;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.setting > code {
  font-size: 15px;
}
.setting > small {
  grid-column: 1 / -1;
  color: var(--dim);
}
.setting > small[data-source="user"] {
  color: var(--tint);
}
.setting > input,
.setting > select {
  min-width: 140px;
  max-width: 50vw;
  font: inherit;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg);
  color: inherit;
}
.setting[data-scope="repo"] > input,
.setting[data-scope="repo"] > select {
  opacity: .6;
}
/* One row per answer: what it was and when, the files it touched under it. The
 * same shape as a setting row and deliberately not the same class, since the two
 * would then drift into each other. */
.moment {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px 12px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}
.moment > b {
  font-weight: 500;
}
.moment > time,
.moment > small {
  color: var(--dim);
}
.moment > small {
  grid-column: 1 / -1;
  word-break: break-all;
}
.moment[data-sent="no"] > b::before {
  content: "\00b7\00a0";
  color: var(--tint);
}
#answers-state {
  color: var(--dim);
}
#answers-state[data-unsent="some"] {
  color: var(--tint);
}
.page-actions,
.panel-foot {
  flex: none;
  display: flex;
  gap: 8px;
  padding: 10px var(--pad) calc(10px + var(--floor));
  border-top: 1px solid var(--line);
  background: var(--bg);
}
.panel-body {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 0 var(--pad) var(--pad);
}
/* a line of small dim text: a count in a header, where a card came from, what
 * the last answer did */
.meta {
  color: var(--dim);
  font-size: 13px;
  margin: 0;
  flex: none;
  font-variant-numeric: tabular-nums;
}
.meta:empty {
  display: none;
}
.meta[data-keep] {
  min-height: 1.2em;
}
/* a block of text on a raised surface: what he wrote, the paragraph, the rewrite */
.card {
  margin: 0;
  padding: 14px;
  background: var(--raised);
  border-radius: var(--radius);
  white-space: pre-wrap;
  font: 15px/1.55 var(--mono);
}
.card[data-tone="prose"] {
  padding: 18px;
  font: 17px/1.6 var(--sans, inherit);
}
/* his version of something, typed */
.draft {
  min-height: 8em;
  font: 15px/1.55 var(--mono);
}

/* Feedback. Every write says so, in the middle of the screen, briefly.
 *
 * The old note line was 12px and dim at the bottom of a column, which on a phone
 * is off screen: the save worked every time and looked like it had done nothing. */
#toast {
  position: fixed;
  left: 50%;
  bottom: calc(24px + var(--floor));
  --above-actions: calc(80px + var(--floor));
  transform: translateX(-50%) translateY(8px);
  z-index: 40;
  background: var(--raised-2);
  border: 1px solid var(--line);
  border-radius: 22px;
  padding: 11px 18px;
  font-size: 15px;
  max-width: min(90vw, 34rem);
  box-shadow: 0 8px 30px rgb(0 0 0 / .4);
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s, transform .18s;
}
#toast.on {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  /* without this the toast is unclickable and Undo is decoration: the pointer
   * goes straight through to whatever sits behind it */
  pointer-events: auto;
}
#toast {
  display: flex;
  gap: 14px;
  align-items: center;
}
.undo {
  background: none;
  color: var(--tint);
  font-weight: 600;
  padding: 0;
  min-height: 0;
  flex: none;
}
#toast.bad {
  border-color: var(--bad);
  color: var(--bad);
}

/* The sheet, the second of the two conventions: in front of what you were doing,
 * up from the bottom, short of the top, with a grab handle and the backdrop. */
.sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 86dvh;
  z-index: 30;
  background: var(--raised);
  border-radius: 14px 14px 0 0;
  box-shadow: 0 -10px 40px rgb(0 0 0 / .5);
  display: flex;
  flex-direction: column;
  padding: 0 var(--pad) calc(var(--pad) + var(--floor));
  transform: translateY(100%);
  transition: transform .24s cubic-bezier(.32, .72, 0, 1);
}
.sheet.in {
  transform: translateY(0);
}
.sheet-grip {
  width: 36px;
  height: 5px;
  border-radius: 3px;
  background: var(--line);
  margin: 8px auto 12px;
  flex: none;
}
@media (min-width: 821px) {
  .sheet {
    left: 50%;
    right: auto;
    width: min(480px, 90vw);
    transform: translate(-50%, 100%);
  }
  .sheet.in {
    transform: translate(-50%, 0);
  }
}

/* The applets the instance serves, one row each, and the bar's switcher that opens
 * them. With one applet served there is nowhere to switch to, and the switcher is the
 * applet's name alone. */
#switcher-list a {
  display: block;
  padding: 13px 14px;
  border-radius: 10px;
  background: var(--raised-2);
  color: var(--ink);
  text-decoration: none;
  margin-bottom: 8px;
}
#switcher-list a[aria-current="page"] {
  outline: 2px solid var(--tint);
}
button.switch {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--raised-2);
  color: var(--ink);
  padding: 6px 10px 6px 12px;
  min-height: 32px;
  border-radius: 16px;
  font-size: 14px;
  font-weight: 600;
}
/* the mark that says it opens a list, rather than naming where you are */
button.switch .switch-mark {
  color: var(--tint);
  font-size: 12px;
}
.switch-name {
  font-size: 14px;
  font-weight: 600;
}

/* the deck: one card, its four grades under it, and the take back */
[data-deck] {
  display: grid;
  gap: 12px;
  max-width: 36rem;
  margin: 0 auto;
  padding: var(--pad);
}
.deck-count {
  margin: 0;
  font-size: 0.85rem;
  color: var(--dim);
}
.deck-card {
  display: grid;
  gap: 10px;
  min-height: 10rem;
  padding: 20px;
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  transition: transform .18s ease, opacity .18s ease;
}
.deck-card:disabled {
  cursor: default;
}
.deck-front {
  font-size: 1.1rem;
}
.deck-back {
  padding-top: 10px;
  border-top: 1px solid var(--line);
}
.deck-source {
  margin: 0;
  font-size: 0.8rem;
  color: var(--dim);
}
.deck-card.left {
  transform: translateX(-40px) rotate(-3deg);
  opacity: 0;
}
.deck-card.right {
  transform: translateX(40px) rotate(3deg);
  opacity: 0;
}
.deck-grades {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
.deck-grade.again {
  border-color: var(--bad);
}
.deck-grade.easy {
  border-color: var(--good);
}
.deck-undo {
  justify-self: start;
}

/* The job page: one row per job, its kind, its state and when it got there, opening to
 * what it was asked and what came of it. The state's colour is the one thing to read
 * at a glance, so it is the only colour on the row. */
.job {
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}
.job > summary {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 12px;
  align-items: baseline;
  cursor: pointer;
}
.job > summary > b {
  font-weight: 500;
}
.job > summary > time,
.job-brief {
  color: var(--dim);
}
.job-state {
  color: var(--dim);
}
.job[data-state="queued"] .job-state,
.job[data-state="claimed"] .job-state {
  color: var(--tint);
}
.job[data-state="finished"] .job-state {
  color: var(--good);
}
.job[data-state="failed"] .job-state,
.job[data-state="lapsed"] .job-state {
  color: var(--bad);
}
.job-result {
  font-family: var(--mono);
  font-size: 13px;
  white-space: pre-wrap;
  word-break: break-word;
}
.job-needs {
  color: var(--warn);
}
