/* Gigit's only stylesheet. Spec section 10: one hand-written file, no
   framework, no build step, no CDN. Everything a page can show is styled
   here, so a page only ever needs the helpers in html.ts. */

/* ------------------------------------------------------------- the palette
   Every colour in the app is one of these. Dark mode below redefines the
   same names and nothing else, so no rule is ever written twice. */

:root {
  color-scheme: light dark;

  --bg: #f6f8fa;
  --surface: #ffffff;
  --surface-2: #f6f8fa;
  --surface-3: #eaeef2;
  --border: #d0d7de;
  --border-strong: #afb8c1;

  /* The board's work surface. A shade under the page so a column reads as a
     card lying on the board rather than as one more block of the page. */
  --canvas: #eceff3;

  --text: #1f2328;
  --text-muted: #59636e;

  --accent: #0969da;
  --accent-hover: #0860ca;
  --accent-fg: #ffffff;
  --accent-soft: #ddf4ff;

  --success: #1a7f37;
  --success-soft: #dafbe1;
  --danger: #cf222e;
  --danger-soft: #ffebe9;
  --warn: #9a6700;
  --warn-soft: #fff8c5;

  /* Diffs get their own pair so a tweak to the semantic colours above never
     changes how a patch reads. */
  --diff-add: #e6ffec;
  --diff-del: #ffebe9;
  --diff-hunk-bg: #ddf4ff;
  --diff-hunk-fg: #0550ae;

  /* The nav and the deploy log are dark in both themes: one is chrome, the
     other is a terminal. */
  --nav-bg: #24292f;
  --nav-fg: #ffffff;
  --nav-fg-muted: #d0d7de;
  --term-bg: #0d1117;
  --term-fg: #d5dbe3;

  /* The chevron every select draws for itself. It is a colour like any other
     here, so it is a token, and the dark block below restates it rather than
     writing a second rule for the control. */
  --select-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='%2359636e' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5 8 10.5 12 6.5'/%3E%3C/svg%3E");

  --radius: 6px;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0d1117;
    --surface: #151b23;
    --surface-2: #1b2028;
    --surface-3: #262c36;
    --border: #3d444d;
    --border-strong: #4d545e;

    --canvas: #11161e;

    --text: #e6edf3;
    --text-muted: #9198a1;

    --accent: #4493f8;
    --accent-hover: #58a6ff;
    --accent-fg: #ffffff;
    --accent-soft: #121d2f;

    --success: #3fb950;
    --success-soft: #12261e;
    --danger: #f85149;
    --danger-soft: #291415;
    --warn: #d29922;
    --warn-soft: #272115;

    --diff-add: #12261e;
    --diff-del: #291415;
    --diff-hunk-bg: #121d2f;
    --diff-hunk-fg: #4493f8;

    --nav-bg: #151b23;
    --nav-fg: #e6edf3;
    --nav-fg-muted: #9198a1;

    --select-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='%239198a1' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5 8 10.5 12 6.5'/%3E%3C/svg%3E");
  }
}

/* ---------------------------------------------------------------- the page */

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  /* The page column never scrolls sideways; wide things scroll in .scroll-x. */
  overflow-x: hidden;
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

p {
  margin: 0 0 10px;
}

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

ul {
  margin: 6px 0;
  padding-left: 20px;
}

code {
  font-family: var(--mono);
  font-size: 0.92em;
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--surface-3);
  overflow-wrap: anywhere;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

.main {
  max-width: 1120px;
  margin: 0 auto;
  padding: 20px 16px 64px;
}

/* The card page. A document reads at a narrower measure than a file listing,
   and the section tabs above it belong to the same column, so the width is set
   on the page rather than on the card: the header, the tabs and the document
   all end on one line. */
.main-doc {
  max-width: 912px;
}

/* The board page and nothing else. The content column keeps its 1120px and a
   ".full-bleed" child spans the window instead: a named grid, so there is no
   negative margin, no 100vw and no guessing at a scrollbar width. The side
   padding goes because the gutter track is now what provides it. */
.main-wide {
  display: grid;
  grid-template-columns:
    [full-start] minmax(16px, 1fr)
    [content-start] minmax(0, 1120px) [content-end]
    minmax(16px, 1fr) [full-end];
  max-width: none;
  padding: 20px 0 16px;
}

.main-wide > * {
  grid-column: content;
}

.main-wide > .full-bleed {
  grid-column: full;
}

/* The one vertical rhythm in the app: every page is a stack of boxes. */
.stack {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.main > .box {
  margin-bottom: 16px;
}

/* ------------------------------------------------------------------- nav */

.nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
  padding: 0 16px;
  min-height: 48px;
  background: var(--nav-bg);
  color: var(--nav-fg);
}

.nav a {
  color: var(--nav-fg-muted);
}

.nav-brand {
  font-size: 15px;
  font-weight: 600;
  color: var(--nav-fg) !important;
}

.nav a:hover {
  color: var(--nav-fg);
  text-decoration: none;
}

.nav-links {
  display: flex;
  gap: 16px;
}

.nav-spacer {
  flex: 1 1 auto;
}

.nav-user {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* The account menu: a details element dressed as a dropdown, and the panel
   every other menu in the app reuses by class name. nav.js adds what a bare
   details cannot do, which is close on an outside click or on Escape. */
.nav-menu {
  position: relative;
}

.nav-menu > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: var(--radius);
  color: var(--nav-fg-muted);
  cursor: pointer;
  user-select: none;
  list-style: none;
}

.nav-menu > summary::-webkit-details-marker {
  display: none;
}

.nav-menu > summary:hover,
.nav-menu[open] > summary {
  background: rgba(127, 127, 127, 0.25);
  color: var(--nav-fg);
}

/* The caret sits one step under the 16px default so it does not shout next
   to 14px nav text. Two classes because ".icon" is defined further down the
   file and would otherwise win on size. */
.icon.nav-menu-caret {
  width: 14px;
  height: 14px;
}

.nav-menu[open] .nav-menu-caret {
  transform: rotate(180deg);
}

.nav-menu-list {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 20;
  min-width: 180px;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
}

/* One row shape for every menu in the app, which is what makes the account
   menu, a column menu and the card menu read as the same control: a flex row,
   the icon a fixed box on the left, the label the part that grows, and both
   sitting on the row's centre line rather than on a text baseline. The logout
   row has to be a form post, so its button is stripped back to look like the
   link above it. */
.nav-menu-list a,
.nav-menu-list button {
  display: flex;
  align-items: center;
  /* Stated, not left to the default: the base button rule centres its content,
     and specificity is settled per property, so a rule that never mentions
     justify-content does not take it back. */
  justify-content: flex-start;
  gap: 8px;
  width: 100%;
  min-height: 30px;
  padding: 5px 10px;
  border: none;
  border-radius: 4px;
  background: none;
  color: var(--text) !important;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.nav-menu-list a:hover,
.nav-menu-list button:hover {
  background: var(--surface-3);
  text-decoration: none;
}

.nav-menu-list a:active,
.nav-menu-list button:active {
  background: var(--border);
}

/* Inset, because the row is flush with the edge of the panel and a ring drawn
   outside it would be clipped by the panel's own radius. */
.nav-menu-list a:focus-visible,
.nav-menu-list button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* An icon button that drops the panel above. It started as the column menu and
   is now every menu in the app that is not the account one: a column, a card,
   the ref switcher, the clone url. It lives here, beside the panel it opens,
   rather than in the board section, so that the two places which restyle its
   summary can simply come later in the file instead of out-specifying it. Only
   the summary and the danger entry differ from the nav's, because that panel
   was written for the dark nav strip where every row is the same colour. */
.col-menu {
  position: relative;
}

.col-menu > summary {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius);
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
  list-style: none;
}

.col-menu > summary::-webkit-details-marker {
  display: none;
}

.col-menu > summary:hover,
.col-menu[open] > summary {
  background: var(--surface-3);
  color: var(--text);
}

.col-menu > summary:active {
  background: var(--border);
}

/* ".nav-menu-list button" wins its colour with !important, so Delete has to
   take it back the same way, hover included. */
.col-menu .nav-menu-list button.danger {
  color: var(--danger) !important;
}

.col-menu .nav-menu-list button.danger:hover:not(:disabled) {
  color: var(--accent-fg) !important;
}

/* ------------------------------------------------------------------ boxes
   win() in html.ts renders one of these for every titled region. */

.box {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.box-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: var(--radius) var(--radius) 0 0;
}

.box-title {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.box-actions {
  display: flex;
  gap: 4px;
}

.box-actions button {
  min-width: 26px;
  padding: 2px 6px;
  font-size: 12px;
}

.box-body {
  padding: 14px;
}

/* Where the status bar used to be: one muted line, and the region kanban.js
   and log.js write progress and errors into. */
.box-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  padding: 8px 14px;
  border-top: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 12px;
}

.box-foot-item {
  margin: 0;
}

/* A plain bordered container. sunken() wraps anything that needs an edge of
   its own inside a box: a table, a diff, a blob. */
.frame {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.scroll-x {
  overflow-x: auto;
}

/* --------------------------------------------------------------- tables */

.grid {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.grid th {
  padding: 8px 12px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  color: var(--text-muted);
  font-weight: 600;
  text-align: left;
  white-space: nowrap;
}

.grid td {
  padding: 7px 12px;
  border-top: 1px solid var(--border);
  vertical-align: top;
}

/* The first row sits against the frame edge or the header, never both. */
.grid tbody tr:first-child td {
  border-top: none;
}

.grid thead + tbody tr:first-child td {
  border-top: 1px solid var(--border);
}

/* Each action in a table cell is its own little post form; they belong on
   one line, not stacked. */
.grid td > form {
  display: inline-block;
}

.grid td > form + form {
  margin-left: 6px;
}

/* Headerless two column tables: commit metadata, run metadata, account. */
.kv td:first-child {
  width: 1%;
  white-space: nowrap;
  color: var(--text-muted);
}

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

button,
input,
select,
textarea {
  font: inherit;
  color: var(--text);
}

input[type="text"],
input[type="password"],
input[type="search"],
input[type="file"],
select,
textarea {
  padding: 5px 10px;
  min-height: 32px;
  max-width: 100%;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
}

textarea {
  width: 100%;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.5;
  resize: vertical;
}

input[readonly] {
  background: var(--surface-2);
  color: var(--text-muted);
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}

/* The combo box, drawn rather than left to the platform. The native control
   paints a different arrow on every operating system and sizes its own box to
   suit, which made it the one control on a form that never matched the inputs
   beside it. Appearance goes, the chevron becomes the token at the top of this
   file, and the right padding is what keeps a long option off it. */
select {
  appearance: none;
  padding-right: 30px;
  background-image: var(--select-caret);
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 16px 16px;
  cursor: pointer;
}

select:hover:not(:disabled) {
  border-color: var(--accent);
}

/* The option list belongs to the platform, so the closed control is the only
   surface left to say the press landed. */
select:active:not(:disabled) {
  background-color: var(--surface-3);
}

input:disabled,
select:disabled,
textarea:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

label {
  color: var(--text-muted);
  font-weight: 500;
}

button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 5px 14px;
  min-height: 32px;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  font-weight: 500;
  cursor: pointer;
}

button:hover:not(:disabled) {
  background: var(--surface-3);
}

button:active:not(:disabled) {
  background: var(--border);
}

button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* One primary per form at most: the thing the page is for. */
button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-fg);
}

button.primary:hover:not(:disabled) {
  background: var(--accent-hover);
}

button.danger {
  color: var(--danger);
  border-color: var(--border-strong);
}

button.danger:hover:not(:disabled) {
  background: var(--danger);
  border-color: var(--danger);
  color: #ffffff;
}

/* Two shapes only. ".field-row" is a row of controls side by side, which is
   now always buttons or links; every labelled control is a stacked row, with
   the label on its own line above its input. */
.field-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.field-row + .field-row,
.field-row-stacked + .field-row,
.field-row + .field-row-stacked,
.field-row-stacked + .field-row-stacked {
  margin-top: 12px;
}

/* Inside a stack the gap already spaces them; the adjacency rule above is for
   the plain form bodies that are not one. */
.stack > .field-row,
.stack > .field-row-stacked {
  margin-top: 0;
}

.field-row-stacked {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 5px;
}

.field-row-stacked > label {
  font-size: 13px;
}

/* A stacked control fills its box, which is right for a card title or a body,
   but a select holds one short line, so it gets a column width instead. */
.field-row-stacked > select {
  max-width: 420px;
}

/* Same reason, for a form that is nothing but short fields: without this the
   inputs stretch the full width of the page and read as banners. */
.narrow-form {
  max-width: 420px;
}

/* ------------------------------------------------------------------ icons
   Lucide 1.38.0 (https://lucide.dev), ISC. The geometry is one <symbol>
   sprite printed by the page shell (src/icons.ts); every icon anywhere is a
   <use> pointing at it, so a glyph exists once per document however often it
   is drawn, and kanban.js builds one carrying no path data at all.

   A <use> renders into a shadow tree, so no rule may reach the shapes:
   ".icon path {...}" matches nothing and fails silently. Fill and stroke are
   inherited properties, so setting them here on the referencing <svg> is
   what crosses the boundary. That is also why no <symbol> carries a
   presentation attribute of its own: one there could not be overridden. */

.icon-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.icon {
  display: inline-block;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  /* Sits the 16px box on the baseline of the 14px text beside it. */
  vertical-align: -0.125em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* A control whose whole label is its icon: the name lives in aria-label and
   title instead. Square, so a row of them reads as a row of them. This block
   must stay after ".box-actions button" and "button.danger", which are the
   same specificity and set the padding and rest colour it is overriding. */
button.icon-button {
  width: 28px;
  min-width: 28px;
  min-height: 28px;
  padding: 0;
  color: var(--text-muted);
}

button.icon-button:hover:not(:disabled) {
  color: var(--text);
}

button.icon-button.danger:hover:not(:disabled) {
  color: var(--accent-fg);
}

/* -------------------------------------------------------- repo header */

.repo-header {
  margin-bottom: 16px;
}

/* Inside a stack the gap already separates it. */
.stack > .repo-header {
  margin-bottom: 0;
}

.repo-title {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.repo-desc {
  margin: 4px 0 0;
  color: var(--text-muted);
}

/* One row under the repo name: the section tabs on the left, and on the right
   the controls that act on the repo. They share a line and a rule because they
   are one bar, not a tab strip with a toolbar bolted under it. The rule lives
   here rather than on .tabs so it runs the full width behind both halves. */
.repo-bar {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  margin-top: 14px;
  border-bottom: 1px solid var(--border);
}

/* The tabs give first: on a narrow window they scroll sideways and the
   controls keep their size, because a squeezed Clone button is unusable while
   a scrolled tab is not. */
.repo-tools {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  gap: 8px;
  padding-bottom: 6px;
}

/* The section tabs. The active one carries aria-current, which is both the
   accessible answer and the styling hook. The underline is an inset shadow
   rather than a border so it cannot shift the row when a tab activates, and
   so the row still scrolls cleanly on a narrow screen. */
.tabs {
  display: flex;
  flex: 1 1 auto;
  gap: 2px;
  min-width: 0;
  overflow-x: auto;
}

.tabs a {
  padding: 8px 14px;
  border-radius: var(--radius) var(--radius) 0 0;
  color: var(--text);
  white-space: nowrap;
}

.tabs a:hover {
  background: var(--surface-3);
  text-decoration: none;
}

.tabs a[aria-current="page"] {
  box-shadow: inset 0 -2px var(--accent);
  font-weight: 600;
}

/* The two disclosures in the repo bar. Both reuse the column menu's summary
   and the account menu's panel, so a menu is a menu everywhere in the app; what
   they add is a label beside the icon, which a 26px square summary has no room
   for. */
.ref-menu > summary,
.clone-menu > summary {
  gap: 6px;
  width: auto;
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--text);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
}

.ref-menu > summary:hover,
.clone-menu > summary:hover,
.ref-menu[open] > summary,
.clone-menu[open] > summary {
  background: var(--surface-3);
}

/* The branch name is the one part of the control that can be long, so it is
   the only part allowed to be cut. */
.ref-current {
  display: inline-block;
  max-width: 160px;
  overflow: hidden;
  font-family: var(--mono);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ref-menu[open] .nav-menu-caret,
.clone-menu[open] .nav-menu-caret {
  transform: rotate(180deg);
}

/* Branches and tags are one panel with two headings rather than two panels: a
   ref is a ref, and which kind it is matters less than which one you want. */
.menu-head {
  margin: 6px 10px 2px;
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.menu-head:first-child {
  margin-top: 2px;
}

.ref-list {
  left: 0;
  right: auto;
  max-height: 380px;
  min-width: 260px;
  overflow-y: auto;
}

/* The tick holds its column on every row, so the names line up whether or not
   the row is the one being shown. */
.ref-list .ref-tick {
  visibility: hidden;
}

.ref-list a[aria-current="true"] .ref-tick {
  visibility: visible;
}

.ref-name {
  flex: 1 1 auto;
  overflow: hidden;
  font-family: var(--mono);
  font-size: 13px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* When the tip last moved. It closes every row, so a branch nobody has pushed
   to in months is visible from the control you switch with, which is what the
   branches table used to be for. */
.ref-when {
  flex: 0 0 auto;
  color: var(--text-muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.ref-note {
  flex: 0 0 auto;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-muted);
  font-size: 11px;
}

.clone-panel {
  min-width: 340px;
  padding: 4px 10px 10px;
}

.clone-panel .field-row {
  gap: 6px;
  margin-top: 0;
}

.clone-panel input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 30px;
  font-family: var(--mono);
  font-size: 12px;
}

/* ------------------------------------------------------------- go to file */

/* The field and the panel it drops. finder.js clears the hidden attribute on
   the wrapper, so with no script none of this is on the page at all. */
.finder {
  position: relative;
}

.finder-field {
  display: flex;
  align-items: center;
  position: relative;
}

.finder-field > .icon {
  position: absolute;
  left: 9px;
  color: var(--text-muted);
  pointer-events: none;
}

.finder-field input[type="search"] {
  width: 190px;
  min-height: 28px;
  padding: 3px 10px 3px 30px;
  font-size: 13px;
}

/* Wider than the field it hangs off, because a path is longer than a query and
   truncating every row would defeat the control. */
.finder-panel {
  max-height: 360px;
  min-width: 380px;
  overflow-y: auto;
}

.finder-panel a {
  font-family: var(--mono);
  font-size: 12px;
  overflow-wrap: anywhere;
}

/* The keyboard's place in the list. It is an attribute rather than the focus,
   so it needs its own fill: the caret stays in the field while the arrows walk
   the results. */
.finder-panel a[aria-selected="true"] {
  background: var(--accent-soft);
}

.finder-note {
  margin: 0;
  padding: 6px 10px;
  color: var(--text-muted);
  font-size: 12px;
}

/* ------------------------------------------------------------ breadcrumb */

.breadcrumb {
  margin-bottom: 12px;
  font-family: var(--mono);
  font-size: 13px;
  overflow-wrap: anywhere;
}

.breadcrumb .sep {
  color: var(--text-muted);
  padding: 0 2px;
}

/* ----------------------------------------------------------- file listing */

/* The icon and the name are one cell, and the icon holds its own column, so a
   long name wraps under itself rather than under the glyph. */
.tree-name {
  display: flex;
  align-items: baseline;
  gap: 8px;
  overflow-wrap: anywhere;
}

.tree-name > .icon {
  color: var(--text-muted);
}

/* What the file is, rather than what git calls it. Set back from the name it
   describes, because the name is what the reader is scanning for. */
.tree-kind {
  color: var(--text-muted);
  white-space: nowrap;
}

/* Directories read before files: heavier, and the row they lead to is the
   common case, so give them the whole cell width. */
.tree-dir a {
  font-weight: 600;
}

/* SHAs, refs and paths are all fixed width wherever they appear. */
.mono,
.sha {
  font-family: var(--mono);
  font-size: 12.5px;
}

/* ------------------------------------------------------------- activity */

.event {
  padding: 12px 14px;
}

.event-head {
  overflow-wrap: anywhere;
}

/* The commit leads the line instead of trailing the item on a row of its own,
   which is one row less per event and puts the link where the eye starts. The
   chip is what keeps it from reading as the first word of the sentence. */
.event-sha {
  display: inline-block;
  margin-right: 6px;
  padding: 0 6px;
  background: var(--surface-3);
  border-radius: 4px;
}

.event ul {
  margin: 6px 0 0;
  color: var(--text-muted);
}

/* --------------------------------------------------------------- pills */

.pill {
  display: inline-block;
  padding: 1px 9px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}

.pill-queued {
  background: var(--surface-3);
  border-color: var(--border);
  color: var(--text-muted);
}

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

.pill-success {
  background: var(--success-soft);
  border-color: var(--success);
  color: var(--success);
}

.pill-failed {
  background: var(--danger-soft);
  border-color: var(--danger);
  color: var(--danger);
}

.pill-error,
.pill-unknown {
  background: var(--warn-soft);
  border-color: var(--warn);
  color: var(--warn);
}

/* ----------------------------------------------------------- code views */

.plain {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-family: var(--mono);
  font-size: 13px;
}

/* preText inside a box body wants an edge of its own, like a code block. */
.box-body > .plain {
  padding: 10px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.diff,
.blob {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.45;
}

.diff td,
.blob td {
  padding: 0 10px;
  vertical-align: top;
  white-space: pre;
}

/* The gutter is scenery, not content: it must never land in a copied
   selection of the file. */
.diff .lineno,
.blob .lineno {
  width: 1%;
  padding: 0 8px;
  text-align: right;
  background: var(--surface-2);
  border-right: 1px solid var(--border);
  color: var(--text-muted);
  user-select: none;
}

.diff .add {
  background: var(--diff-add);
}

.diff .del {
  background: var(--diff-del);
}

.diff .hunk {
  background: var(--diff-hunk-bg);
  color: var(--diff-hunk-fg);
}

.diff-file {
  margin: 16px 0 6px;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.diff-file:first-child {
  margin-top: 0;
}

/* A terminal, so it stays dark in both themes. log.js appends to it and
   keeps it pinned to the bottom. */
.log-output {
  height: 60vh;
  margin: 0;
  padding: 10px 12px;
  overflow: auto;
  background: var(--term-bg);
  color: var(--term-fg);
  border-radius: var(--radius);
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------- board */

/* The board is a region, not a box in a stack: it owns the width of the
   window and the rest of its height, so every column ends on the same line
   and scrolls inside itself rather than each one growing the page.
   "--board-top" is where the region starts. kanban.js measures it, because
   the repo header above it wraps at narrow widths; this value is what a
   reader with no javascript gets, and it is deliberately generous. dvh, not
   vh, so the last card is not under the browser chrome on a phone.

   The floor is not decoration. At 320x256 CSS px, which is 400% zoom on a
   1280x1024 screen and the reflow viewport WCAG 2.1 asks a page to survive,
   everything above the region wraps until it is taller than the window: the
   subtraction goes negative, a negative height clamps to zero, and the board
   is a zero-height scroll container that no amount of page scrolling can
   bring back. Past that point the region gives up owning the viewport and
   becomes an ordinary block the page scrolls to. kanban.js clamps the
   measured value to the same 320. */
/* The region runs the full width of the window while the header above it stops
   at the content column, and without something drawn under it that difference
   reads as a mistake rather than as a decision. The tint and the rule at the
   top are that something: above the line is the page, below it is the board,
   and a white column now lies on a surface instead of floating on the page. */
.board-region {
  --board-top: 260px;
  display: flex;
  flex-direction: column;
  height: max(320px, calc(100dvh - var(--board-top)));
  padding: 12px 16px 0;
  background: var(--canvas);
  border-top: 1px solid var(--border);
}

/* One row: the filter on the left, what the page is doing on the right, one
   control height for all of it. */
.board-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 10px;
}

.board-bar button,
.board-bar select,
.board-bar input[type="search"] {
  min-height: 28px;
  padding: 3px 10px;
}

/* Shorter than the form default, so the chevron moves in with the box. */
.board-bar select {
  padding-right: 26px;
  background-position: right 7px center;
}

/* The label wraps the field so the magnifier sits inside the frame and is
   part of the click target. */
.board-search {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.board-search > .icon {
  position: absolute;
  left: 9px;
  color: var(--text-muted);
  pointer-events: none;
}

.board-search input[type="search"] {
  width: 220px;
  padding-left: 30px;
}

/* The status line is the spacer as well, so a message appears in the gap
   instead of moving the controls on either side of it. */
.board-status {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  color: var(--text-muted);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.board-live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  color: var(--text-muted);
  font-size: 12px;
}

.board-live-dot {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--text-muted);
}

/* The dot is the state, so the state is what colours it: attached, trying, or
   not connected at all. kanban.js writes both the attribute and the word. */
.board-live[data-state="live"] .board-live-dot {
  background: var(--success);
}

.board-live[data-state="reconnecting"] .board-live-dot {
  background: var(--warn);
}

.board-live[data-state="offline"] .board-live-dot {
  background: var(--border-strong);
}

.board {
  display: flex;
  flex: 1 1 auto;
  align-items: stretch;
  gap: 12px;
  min-height: 0;
  overflow-x: auto;
  padding-bottom: 8px;
}

.board-column {
  display: flex;
  flex: 0 0 280px;
  flex-direction: column;
  max-width: 280px;
  max-height: 100%;
}

/* The head is where a column is picked up, and on a touch screen it is the
   only place: panning is off here and nowhere else on the column, so a
   finger anywhere below still scrolls the board sideways. */
.board-column > .box-head {
  cursor: grab;
  touch-action: none;
}

.board-column .box-body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
  padding: 10px;
}

.board-cards {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 8px;
  min-height: 48px;
  overflow-y: auto;
}

.board-card {
  /* Grip in the first column, title and meta stacked in the second. */
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 6px;
  padding: 10px;
  background: var(--surface-2);
  cursor: grab;
}

/* The filter hides cards rather than removing them, and an author "display"
   beats the "[hidden]" rule in the browser's own stylesheet. */
.board-card[hidden] {
  display: none;
}

/* The whole card is what drags under a mouse, so this only makes visible the
   affordance "cursor: grab" already implied. Under a finger it is the drag
   itself: panning is off here and on nowhere else on the card, so a swipe
   anywhere but the grip still scrolls the column. */
.board-card-grip {
  grid-column: 1;
  grid-row: 1;
  align-self: start;
  margin-top: 2px;
  color: var(--border);
  touch-action: none;
}

.board-card:hover .board-card-grip {
  color: var(--text-muted);
}

.board-card:hover {
  border-color: var(--border-strong);
}

/* An empty column says "No cards yet" right up until something is about to
   land in it. */
.board-cards > .drag-placeholder + .empty {
  display: none;
}

/* The card title fills its card so the whole block reads as one target. */
.board-card-title {
  display: block;
  grid-column: 2;
  color: var(--text);
  font-weight: 500;
  overflow-wrap: anywhere;
}

.board-card-meta {
  display: flex;
  grid-column: 2;
  justify-content: space-between;
  gap: 8px;
  margin-top: 6px;
  color: var(--text-muted);
  font-size: 12px;
}

/* Each half is an icon plus its word, and the assignee half is what has to
   give when the name is long. */
.board-card-meta > span {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}

.drop-line {
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
  pointer-events: none;
}

/* Only ever the inline row the bar's Add column opens, parked at the tail of
   the column row; it removes itself when the row closes. */
.board-add {
  flex: 0 0 240px;
  align-self: flex-start;
}

/* The inline add-card and rename-column rows kanban.js builds. */
.inline-edit input[type="text"] {
  flex: 1 1 100%;
  min-width: 0;
}

/* Inside a column both of those land in a flex column that is already full
   height, and the row is content: the card list is what gives. */
.board-cards > .inline-edit,
.board-column .box-body > .inline-edit {
  flex: 0 0 auto;
}

/* ----------------------------------------------------------------- drag */

/* src/static/drag.js, and the only motion in this file. The node being
   dragged leaves the flow for a fixed layer above everything else, a
   placeholder of the same size holds its slot, and the siblings shuffling
   around it are moved with a transform and nothing else. drag.js reads these
   durations back out of the computed style rather than keeping its own copy,
   so the reduced-motion block at the end of the section turns the animation
   off in the module as well as in the browser. */

.drag-layer {
  position: fixed;
  z-index: 40;
  inset: 0;
  pointer-events: none;
}

/* The node in the air. Its size and its origin are set inline because they
   are measurements; the tilt is what makes it read as picked up rather than
   as a copy that happens to be moving. */
.drag-lift {
  position: fixed;
  margin: 0;
  transform: translate(var(--drag-x, 0px), var(--drag-y, 0px)) rotate(3deg) scale(1.03);
  box-shadow: 0 18px 38px rgba(0, 0, 0, 0.3);
  cursor: grabbing;
  pointer-events: none;
}

/* The flight into the slot at the end of a drag, or back to where the drag
   began when it is abandoned. The tilt comes out as it lands. */
.drag-land {
  transform: translate(var(--drag-x, 0px), var(--drag-y, 0px)) rotate(0deg) scale(1);
  box-shadow: none;
  transition:
    transform 160ms ease-out,
    box-shadow 160ms ease-out;
}

/* The hole the lifted node left, which is also the drop target: the box it
   had, so nothing else moved when it went. */
.drag-placeholder {
  flex: 0 0 auto;
  background: var(--surface-2);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
}

/* Play, the last quarter of a FLIP: drag.js has put a sibling back where it
   used to be with a transform and is now taking that transform away. */
.drag-slide {
  transition: transform 160ms ease-out;
}

/* A drag is a drag and not a selection, wherever the pointer ends up. */
body.drag-live {
  cursor: grabbing;
  user-select: none;
}

/* A canvas frame swallows every pointer event that happens over it, and a
   drag that crosses one must not lose the pointer. drag.js captures the
   pointer, which is already enough; this is the belt to that pair of braces. */
body.drag-live .ce-canvas-frame {
  pointer-events: none;
}

/* The drag still works, it just stops moving. Nothing else in this file
   animates, so this block is the whole of it. */
@media (prefers-reduced-motion: reduce) {
  .drag-land,
  .drag-slide {
    transition: none;
  }
}

/* ----------------------------------------------------------- attachments */

.attachment {
  padding: 10px;
}

.attachment-name {
  display: flex;
  align-items: center;
  gap: 6px;
  overflow-wrap: anywhere;
}

.thumb {
  max-width: 240px;
  height: auto;
  border-radius: 4px;
}

/* ----------------------------------------------------------- card page */

/* The card reads as a document and not as a form (plan section 5): a heading,
   a panel of properties, then the body flowing straight down. The left padding
   is the gutter the block grips live in and it sits on the whole stack, not on
   the card alone, so the tabs above start on the same line the title does and
   a grip still never leaves the page. */

.card-stack {
  padding-left: 26px;
}

.card-page {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* The conflict and retry bars insert themselves at the top of the page and
   the gap above already spaces them. */
.card-page > .field-row {
  margin-top: 0;
}

.card-head {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}

/* The page's subject, sized like one, and editable in place. The min-height
   keeps a title the reader has just emptied clickable. */
.card-title {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 1.3em;
  /* Pulled back by its own padding so the heading's first letter sits on the
     same line as the first letter of the document below it. */
  margin: 0 0 0 -6px;
  padding: 2px 6px;
  border-radius: var(--radius);
  font-size: 24px;
  font-weight: 600;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

/* Nothing on the page says the heading is typeable, so the pointer has to: the
   title takes a fill under the cursor the way a menu row does, which is the
   only affordance a contenteditable heading can carry without turning back
   into the labelled input this page replaced. */
.card-title:hover {
  background: var(--surface-3);
}

/* Inside a document the caret is the indicator, so neither the title nor the
   body takes the page's focus ring. The title still marks itself out while it
   is being typed in, because it is the one block that is not part of the
   document below it. */
.card-title:focus,
.ce:focus {
  outline: none;
}

.card-title:focus {
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--border-strong);
}

/* A card whose title has just been emptied is still a click target and still
   says what it is waiting for. */
.card-title:empty::before {
  content: "Untitled";
  color: var(--text-muted);
}

.card-menu {
  flex: 0 0 auto;
}

/* Properties, not form fields. The document below draws no frame at all, so
   without one here the two would run together into one flat column; the panel
   is the whole reason the page has a top and a body rather than a single
   undifferentiated surface. One layer only: it lifts off the page, and nothing
   inside it is boxed again. */
.card-props {
  display: grid;
  gap: 1px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
  font-size: 13px;
}

/* A name and its value, the names on one line so the values read as a column
   of answers rather than as a run of labelled fields. */
.card-prop {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  align-items: center;
  gap: 8px;
  min-height: 30px;
  margin: 0;
  padding: 0 6px;
  border-radius: 4px;
}

.card-prop-name {
  color: var(--text-muted);
}

/* The row is the hit target for the control it holds, so the row is what
   lights up under the pointer. */
.card-prop:hover {
  background: var(--surface-2);
}

/* The house style for a property: the value, and nothing drawn around it. The
   frame and the chevron arrive together, and only once the pointer or the
   keyboard has said the value is about to be changed. The room the chevron
   needs is held open the whole time, so arriving does not move the text. */
.card-prop select {
  width: 100%;
  min-height: 26px;
  padding: 2px 24px 2px 6px;
  background-color: transparent;
  background-image: none;
  background-position: right 5px center;
  border-color: transparent;
}

.card-prop:hover select,
.card-prop select:focus {
  background-color: var(--surface);
  background-image: var(--select-caret);
  border-color: var(--border-strong);
}

/* Files this card owns that no block points at any more. The server renders
   it only when it counted one, and kanban.js removes it again when the last
   one is deleted: it is what is left of the attachments box, and it is a
   repair surface rather than a feature. */
.card-unused {
  font-size: 13px;
}

/* ".fold" is defined further down the file and carries a margin of its own,
   which the gap on .card-page has already provided. */
.card-page > .card-unused {
  margin-top: 0;
}

/* The page's tail: when the card was made, when it last changed, and the line
   every status message on this page lands in. All three are the page talking
   about itself rather than the card's content, which is why they sit under the
   document instead of in the property panel above it. */
.card-foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 16px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 12px;
}

.card-foot p {
  margin: 0;
}

/* The spacer as well as the status target, so a message grows into the gap at
   the end of the line instead of moving what is beside it. */
.card-status {
  flex: 1 1 120px;
  overflow: hidden;
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-unused > p {
  margin: 8px 0 0;
}

.card-unused > .stack {
  margin-top: 10px;
}

/* The only Save button on the page, and only after a save has actually
   failed. Danger rather than warn: unlike a conflict, this is the page
   holding changes that are not anywhere else yet. */
.card-retry {
  padding: 8px 12px;
  background: var(--danger-soft);
  border: 1px solid var(--danger);
  border-radius: var(--radius);
}

.card-retry p {
  flex: 1 1 240px;
  margin: 0;
}

.card-retry[hidden] {
  display: none;
}

/* --------------------------------------------------------- card editor */

/* The block document on the card page. It draws no frame of its own: the
   document is the page (plan section 5), not a field on it, so what is left
   is the page's sans stack at a reading size and line height. The min-height
   is the click target below the last block. The negative margin and the
   matching padding take the region out over the gutter .card-page left open,
   so the text still starts where the heading does and a grip in the gutter is
   inside this element rather than beside it. Spec section 10c. */

.ce-wrap {
  position: relative;
}

.ce {
  margin-left: -26px;
  padding: 4px 0 40px 26px;
  min-height: 240px;
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.7;
}

/* The newline decision, applied uniformly: a soft break is a newline in the
   document, in the projection and here. There is no <br> anywhere. */
.ce-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  min-height: 1.6em;
}

/* Positioned, because the gutter grip is an absolutely placed child of
   whichever block the pointer is over. */
.ce-block {
  position: relative;
  margin: 2px 0;
}

.ce-block + .ce-block {
  margin-top: 6px;
}

.ce-h1 > .ce-text,
.ce-h2 > .ce-text,
.ce-h3 > .ce-text {
  font-weight: 600;
  line-height: 1.3;
}

.ce-h1 > .ce-text {
  font-size: 24px;
}

.ce-h2 > .ce-text {
  font-size: 19px;
}

.ce-h3 > .ce-text {
  font-size: 16px;
}

.ce-h1,
.ce-h2,
.ce-h3 {
  margin-top: 16px;
}

.ce-li {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}

.ce-li > .ce-text {
  flex: 1 1 auto;
  min-width: 0;
}

.ce-bullet,
.ce-marker {
  flex: 0 0 auto;
  min-width: 18px;
  text-align: right;
  color: var(--text-muted);
  user-select: none;
}

/* The checkbox sits on the first line of the item, not on the box's own
   baseline, which is what makes a wrapped to-do read straight. */
.ce-check {
  flex: 0 0 auto;
  margin: 5px 0 0;
}

.ce-todo[data-done="1"] > .ce-text {
  color: var(--text-muted);
  text-decoration: line-through;
}

.ce-block[data-indent="1"] {
  margin-left: 24px;
}

.ce-block[data-indent="2"] {
  margin-left: 48px;
}

.ce-block[data-indent="3"] {
  margin-left: 72px;
}

.ce-quote {
  padding-left: 12px;
  border-left: 3px solid var(--border-strong);
  color: var(--text-muted);
}

/* Code reads like the .plain blocks elsewhere on the site. */
.ce-code > .ce-text {
  padding: 8px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.5;
}

.ce code {
  padding: 1px 4px;
  background: var(--surface-3);
  border-radius: 4px;
  font-family: var(--mono);
  font-size: 13px;
}

.ce-divider {
  padding: 6px 0;
}

.ce-divider hr {
  margin: 0;
  border: 0;
  border-top: 1px solid var(--border-strong);
}

/* .thumb's 240px cap is a list treatment; an image in the body is content. */
.ce-img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: 4px;
}

.ce-caption {
  padding-top: 4px;
  color: var(--text-muted);
  font-size: 13px;
}

.ce-file {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 8px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.ce-missing {
  padding: 8px 10px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
}

/* The canvas block: an author's own html document in a frame that cannot
   reach this page. The padding is not decoration, it is the only part of the
   block a pointer can land on, because a frame swallows everything inside
   itself; the chrome below lives in it. */
.ce-canvas {
  padding: 8px 8px 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* The height is the frame's own height attribute, written by the resize
   handle and printed by both renderers. Nothing here may set one: an author
   rule would beat the attribute and the handle would stop working. */
.ce-canvas-frame {
  display: block;
  width: 100%;
  background: var(--surface);
  border: 0;
  border-radius: 4px;
}

/* The way into the block menu, in the top right of the padding. One of these
   exists; editor.js moves it into whichever canvas the pointer is over, the
   same way it moves the gutter grip. */
.ce-canvas-bar {
  position: absolute;
  top: 4px;
  right: 4px;
}

.ce-canvas-bar button {
  background: var(--surface);
}

/* The bottom edge, which drag.js turns into the height. */
.ce-canvas-grip {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  height: 14px;
  cursor: ns-resize;
  touch-action: none;
}

.ce-canvas-grip::after {
  content: "";
  display: block;
  width: 40px;
  height: 3px;
  margin: 5px auto;
  border-radius: 2px;
  background: var(--border-strong);
}

/* An upload still in flight. It carries data-pending, so it is also the one
   kind of block that can never be serialised into a save. */
.ce-pending {
  opacity: 0.55;
}

.ce-block[data-empty="1"] > .ce-text::before {
  content: attr(data-placeholder);
  color: var(--text-muted);
  pointer-events: none;
}

.ce-file-input {
  display: none;
}

/* The drag handle, in the gutter at the left of the document. One of these
   exists; editor.js moves it into the block under the pointer, so it is only
   ever visible where a drag would start, and it sits inside .ce's own left
   padding so reaching for it is not leaving the region. */
.ce-grip {
  position: absolute;
  top: 2px;
  left: -26px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 4px;
  color: var(--border-strong);
  cursor: grab;
  user-select: none;
  touch-action: none;
}

.ce-grip:hover {
  background: var(--surface-3);
  color: var(--text);
}

/* The image or file block whose menu is open. */
.ce-picked {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Download, Replace and Delete for the file behind a block, on the same
   dropdown panel the column menu reuses. editor.js measures it against
   .ce-wrap and writes top and right inline. */
.ce-block-menu {
  min-width: 150px;
}

.ce-block-menu[hidden] {
  display: none;
}

.ce-block-menu a,
.ce-block-menu button {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* ".nav-menu-list button" wins its colour with !important, so Delete has to
   take it back the same way, hover included. */
.ce-block-menu button.danger {
  color: var(--danger) !important;
}

.ce-block-menu button.danger:hover:not(:disabled) {
  color: var(--accent-fg) !important;
}

/* Source and Preview for a canvas. The panel covers the block it belongs to,
   so the two read as one block showing two faces; editor.js measures it
   against .ce-wrap and writes the box the way the menus write theirs. */
.ce-source {
  position: absolute;
  z-index: 6;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 220px;
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
}

.ce-source[hidden] {
  display: none;
}

/* No highlighting of any kind (plan section 6). The panel decides how tall it
   is, so the corner grab a textarea normally gets would only fight it. */
.ce-source-text {
  flex: 1 1 auto;
  min-height: 0;
  resize: none;
}

/* The slash menu, absolutely placed inside .ce-wrap and never inside the
   editable region. Not a modal: it closes on Escape and on blur. */
.ce-menu {
  position: absolute;
  z-index: 5;
  min-width: 220px;
  max-height: 280px;
  overflow-y: auto;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
}

.ce-menu[hidden] {
  display: none;
}

.ce-menu-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 5px 8px;
  border-radius: 4px;
  cursor: pointer;
}

.ce-menu-item.is-active {
  background: var(--accent-soft);
}

.ce-menu-hint {
  color: var(--text-muted);
  font-family: var(--mono);
  font-size: 12px;
}

.ce-link-bar {
  margin-top: 6px;
}

.ce-link-bar[hidden] {
  display: none;
}

.ce-link-bar input[type="text"] {
  flex: 1 1 220px;
}

/* The card moved on under this page and the editor has stopped saving. Warn
   rather than danger: nothing is broken and nothing is lost yet, there is
   just a newer version to take. */
.card-conflict {
  padding: 8px 12px;
  background: var(--warn-soft);
  border: 1px solid var(--warn);
  border-radius: var(--radius);
}

.card-conflict p {
  flex: 1 1 240px;
  margin: 0;
}

/* ----------------------------------------------------------- login page */

.login-box {
  max-width: 360px;
  margin: 48px auto 0;
}

.field-row.login-actions {
  margin-top: 18px;
}

.form-error {
  margin-top: 12px;
  color: var(--danger);
}

.login-actions button {
  width: 100%;
}

/* The token field and its reveal button are one control. */
.input-reveal {
  display: flex;
  align-items: stretch;
  gap: 6px;
}

.input-reveal input {
  flex: 1 1 auto;
  min-width: 0;
}

.reveal {
  flex: 0 0 auto;
  padding: 0 10px;
  color: var(--text-muted);
}

.reveal .icon-eye-off,
.reveal[aria-pressed="true"] .icon-eye {
  display: none;
}

.reveal[aria-pressed="true"] .icon-eye-off {
  display: inline-block;
}

/* ------------------------------------------------- credentials and copying */

/* The copy button lives in a box header, so it inherits ".box-actions button"
   for its size. Only its two transient states are its own. */
.copy-button[data-state="done"] {
  color: var(--success);
  border-color: var(--success);
}

.copy-button[data-state="failed"] {
  color: var(--danger);
  border-color: var(--danger);
}

/* A copy button standing on its own in the page stack, with the text it copies
   never painted at all. It is the whole feature, so it is a pill rather than
   another rectangle: nothing else on the page is this shape, which is what
   stops one small control reading as a broken box. */
.pill-row {
  display: flex;
  justify-content: flex-end;
}

/* Laid out explicitly rather than by inheritance from "button". This control
   is one icon beside one word and nothing else, so the row, the no-wrap and
   the centring are stated here where they can be read, instead of being
   assembled out of three rules further up the file. */
button.copy-button.pill {
  display: inline-flex;
  flex-flow: row nowrap;
  align-items: center;
  justify-content: center;
  /* The icon is a 16px box with the glyph drawn inside it, so the optical gap
     is always a little tighter than the number. 10px reads as the 8px it
     needs to be. */
  gap: 10px;
  padding: 5px 18px;
  min-height: 30px;
  border-radius: 999px;
  color: var(--text-muted);
  font-size: 13px;
  /* The label is a flex item, so its box is centred against the icon's; with
     the inherited leading still on it, the glyphs sit low inside that box and
     the two read as misaligned. Collapsing the line box to the text height
     makes centring the boxes the same thing as centring what you can see. */
  line-height: 1;
}

button.copy-button.pill > .icon {
  flex: 0 0 auto;
  margin: 0;
  /* Only meaningful when an icon is not a flex item, and here it always is. */
  vertical-align: 0;
}

button.copy-button.pill > .copy-label {
  display: block;
  white-space: nowrap;
}

button.pill:hover:not(:disabled) {
  color: var(--text);
  border-color: var(--accent);
}

/* The done and failed colours above set only the text and the border, which on
   a pill leaves the fill flat. Tint it too, so a click that landed is obvious
   without reading the label. */
button.pill[data-state="done"] {
  background: var(--success-soft);
}

button.pill[data-state="failed"] {
  background: var(--danger-soft);
}

/* A link that stands in a row of buttons: the admin's per-user "Setup" is a
   GET, so it must be an anchor, but it has to line up with the two forms
   beside it. Everything here is copied off "button" on purpose; the two must
   not drift apart. */
.button-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 5px 14px;
  min-height: 32px;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text);
  font-weight: 500;
  text-decoration: none;
}

.button-link:hover {
  background: var(--surface-3);
  color: var(--text);
  text-decoration: none;
}

/* A long block that is worth keeping on the page but not worth reading every
   visit. The summary is the only affordance; the block itself is a .plain. */
.fold {
  margin-top: 12px;
}

.fold > summary {
  cursor: pointer;
  color: var(--text-muted);
  font-weight: 500;
  list-style-position: inside;
}

.fold > summary:hover {
  color: var(--accent);
}

.fold > .plain {
  margin-top: 10px;
  max-height: 60vh;
  overflow-y: auto;
  padding: 10px 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* ---------------------------------------------------------- access grid */

.access-grid th:not(:first-child),
.access-grid td:not(:first-child) {
  text-align: center;
}

.access-grid input[type="checkbox"] {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

/* The boxes carry no visible label, so the label is only there to widen the
   hit area around its checkbox. */
.access-grid td label {
  display: inline-block;
  padding: 6px;
  cursor: pointer;
}

/* ----------------------------------------------------------------- misc */

.muted {
  color: var(--text-muted);
}

.right {
  display: block;
  text-align: right;
}

/* Never a blank box: an empty region says what would fill it. */
.empty {
  margin: 0;
  padding: 20px 4px;
  color: var(--text-muted);
  text-align: center;
}

.pager {
  margin-top: 14px;
  justify-content: center;
  color: var(--text-muted);
}

/* ---------------------------------------------------------------- phone */

@media (max-width: 640px) {
  .main {
    padding: 14px 10px 48px;
  }

  /* The gutter track still provides the side padding, and the bottom one
     stays at 16px because kanban.js measures the board against it. */
  .main-wide {
    padding: 14px 0 16px;
  }

  .nav {
    padding: 6px 10px;
    gap: 10px;
  }

  .box-body {
    padding: 12px;
  }

  .repo-title {
    font-size: 18px;
  }

  /* A narrower gutter, and the document and the grips that live in it move
     with it. */
  .card-stack {
    padding-left: 22px;
  }

  .ce {
    margin-left: -22px;
    padding-left: 22px;
  }

  .ce-grip {
    left: -22px;
  }

  .card-title {
    font-size: 20px;
  }

  .board-region {
    padding: 10px 10px 0;
  }

  /* The bar wraps to two rows on a phone rather than squeezing the field. */
  .board-bar {
    flex-wrap: wrap;
  }

  .board-column,
  .board-add {
    flex-basis: 84vw;
    max-width: 84vw;
  }
}
