/* LockIn. Components transcribed from designdocs/, not approximated from them.
 *
 * Where a rule below carries a comment naming an ESS file, the values came from reading that
 * file's inline styles. Changing one of those is a design change and belongs in DESIGN.md
 * first.
 */

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* The `hidden` attribute is only a default of `display: none`, so any class that sets
 * `display` silently beats it. Several components here are flex, which meant elements marked
 * hidden rendered anyway: an empty error banner sat on the login page looking like a failure
 * that had not happened. This restores what the attribute is supposed to mean. */
[hidden] {
  display: none !important;
}

html,
body {
  margin: 0;
  padding: 0;
}

/* ESS Design Doc, canvas rules. */
body {
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-13);
  font-weight: 500;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--brand);
  text-decoration: none;
  font-weight: 600;
}

a:hover {
  color: var(--brand-hover);
  text-decoration: underline;
}

/* Tabular figures so a counting timer does not shift width as it ticks. The ESS stat tiles
 * set font-variant-numeric on their values for the same reason. */
.num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.mono {
  font-family: var(--font-mono);
}

/* ---------- Icons ----------
 * Material Symbols Rounded from Google Fonts, the same source the ESS files use (D-040).
 * Sizing helpers live in fonts.css as .ms-16 through .ms-21; the wrap below is the 34px
 * round container the ESS stat tiles put an 18px glyph inside.
 */

.icon-wrap {
  width: 34px;
  height: 34px;
  flex: 0 0 34px;
  border-radius: var(--r-pill);
  background: var(--canvas-2);
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
}

.tile-attention .icon-wrap {
  background: var(--brand);
  color: var(--brand-ink);
}

/* ---------- Shell ---------- */

.shell {
  display: flex;
  min-height: 100vh;
}

/* ESS Sidebar: 248px fixed, white, hairline right border. */
.sidebar {
  width: var(--sidebar-w);
  flex: 0 0 var(--sidebar-w);
  background: var(--surface);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.brand {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 22px 20px 18px 20px;
}

/* ESS Sidebar: 36px mark, 11px radius, brand fill. */
.brand-mark {
  width: 36px;
  height: 36px;
  flex: 0 0 36px;
  border-radius: var(--r-11);
  background: var(--brand);
  color: var(--brand-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-15);
  font-weight: 800;
  letter-spacing: -0.3px;
}

.brand-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.brand-name {
  font-size: var(--fs-15);
  font-weight: 800;
  color: var(--ink);
  letter-spacing: -0.2px;
  line-height: 1.1;
}

.brand-sub {
  font-size: var(--fs-10-5);
  font-weight: 600;
  color: var(--faint);
  letter-spacing: 0.4px;
}

.nav {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 4px 12px 20px 12px;
}

.nav-group {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* ESS Sidebar group heading. */
.nav-label {
  font-size: var(--fs-9);
  font-weight: 700;
  color: var(--faint-2);
  letter-spacing: 0.9px;
  padding: 8px 10px 6px 10px;
}

/* ESS Sidebar row: 11px gap, 9px/10px padding, 11px radius. */
.nav-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 10px;
  border-radius: var(--r-11);
  color: var(--muted);
  font-size: var(--fs-13);
  font-weight: 600;
  letter-spacing: -0.1px;
  text-decoration: none;
  transition:
    background var(--speed) ease-out,
    color var(--speed) ease-out;
}

.nav-row .ms {
  color: var(--faint);
  flex: 0 0 auto;
}

.nav-row:hover {
  background: var(--canvas-2);
  color: var(--ink);
  text-decoration: none;
}

/* ESS Sidebar active row: brand fill plus the brand shadow. */
.nav-row[aria-current="page"] {
  background: var(--brand);
  color: var(--brand-ink);
  box-shadow: 0 6px 14px -6px rgba(29, 78, 216, 0.55);
}

.nav-row[aria-current="page"] .ms {
  color: var(--brand-ink);
}

.nav-row.is-disabled {
  color: var(--faint-2);
  cursor: default;
}

/* ---------- Build Library ---------- */

.release-gate {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-left: 4px solid var(--warn);
  border-radius: var(--r-11);
  background: var(--surface);
}

.release-gate .card-title,
.release-gate .card-sub {
  margin: 0;
}

.library-filters,
.library-intake-form {
  display: grid;
  grid-template-columns: minmax(220px, 1fr) minmax(160px, 220px) auto;
  gap: 10px;
}

.library-intake-form {
  grid-template-columns: minmax(260px, 1.4fr) minmax(180px, 0.8fr) 190px auto;
}

.library-compact-form {
  display: grid;
  grid-template-columns: minmax(180px, 0.7fr) minmax(240px, 1fr) auto;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}

.library-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px;
}

.library-item {
  appearance: none;
  display: flex;
  flex-direction: column;
  min-height: 160px;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-11);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.library-item-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 14px 16px 16px;
}

/* A fixed aspect ratio keeps the grid on one baseline whatever a capture's own size is. */
.library-thumb {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 10;
  border-bottom: 1px solid var(--line);
  background:
    linear-gradient(135deg, color-mix(in srgb, var(--brand) 8%, transparent), transparent 60%),
    var(--surface-2, var(--surface));
}

.library-thumb-fallback {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.library-thumb.has-image .library-thumb-fallback {
  display: none;
}

.library-thumb-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

/* An SVG is the artwork itself, so show all of it rather than cropping to fill. */
.library-thumb[data-runtime="svg"] .library-thumb-image {
  object-fit: contain;
  padding: 14px;
}

.library-item:hover {
  border-color: var(--brand);
  box-shadow: var(--shadow-sm);
}

.library-preview-frame {
  margin-top: 16px;
  min-height: 320px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-11);
  background: var(--surface-2);
}

.library-preview-frame iframe,
.library-preview-frame img {
  display: block;
  width: 100%;
  min-height: 320px;
  max-height: 560px;
  border: 0;
  object-fit: contain;
}

.library-detail-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}

.library-detail-section {
  margin-top: 20px;
}

.library-detail-section h3 {
  margin: 0 0 8px;
  font-size: 0.9rem;
}

.library-item-head,
.library-source-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.library-source-row {
  padding: 13px 0;
  border-bottom: 1px solid var(--line);
}

.permission-operations {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-8);
}

.permission-operations label {
  display: flex;
  align-items: center;
  gap: 6px;
}

@media (max-width: 760px) {
  .library-filters,
  .library-intake-form,
  .library-compact-form {
    grid-template-columns: 1fr;
  }

  .library-source-row,
  .release-gate {
    align-items: stretch;
    flex-direction: column;
  }
}

.nav-badge {
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  border-radius: var(--r-6);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-10-5);
  font-weight: 700;
  background: var(--brand-tint);
  color: var(--brand);
  margin-left: auto;
}

.nav-row[aria-current="page"] .nav-badge {
  background: rgba(255, 255, 255, 0.22);
  color: var(--brand-ink);
}

.sidebar-foot {
  margin-top: auto;
  padding: 14px 12px 16px 12px;
  border-top: 1px solid var(--line-2);
}

/* ESS Learning screen frame: the content column sits on #F4F7FF beside the white sidebar,
 * with its own padding of 20/24/26 and a 16px gap between the topbar and what follows. The
 * topbar is a component inside that column, not a separate bar with its own background. */
.main {
  flex: 1;
  min-width: 0;
  background: var(--canvas-2);
  padding: 20px 24px 26px 24px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* ---------- Topbar. ESS Topbar, transcribed. ---------- */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.topbar-titles {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.topbar-title {
  font-size: var(--fs-19);
  font-weight: 800;
  color: var(--ink);
  letter-spacing: -0.45px;
  margin: 0;
}

.topbar-sub {
  font-size: var(--fs-12-5);
  font-weight: 500;
  color: var(--muted-2);
  letter-spacing: -0.1px;
  margin: 0;
}

.topbar-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 0 0 auto;
}

/* ESS Topbar: 38px circle, white, hairline border. */
.icon-button {
  width: 38px;
  height: 38px;
  flex: 0 0 38px;
  border-radius: var(--r-pill);
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  position: relative;
  padding: 0;
}

.icon-button:hover {
  background: var(--canvas-2);
}

.icon-button .dot {
  position: absolute;
  top: 8px;
  right: 9px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--danger-dot);
  border: 1.5px solid var(--surface);
}

/* ESS Topbar user pill. */
.user-pill {
  display: flex;
  align-items: center;
  gap: 9px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 4px 12px 4px 4px;
  cursor: pointer;
}

.avatar {
  width: 32px;
  height: 32px;
  flex: 0 0 32px;
  border-radius: 50%;
  background: var(--brand-tint-2);
  color: var(--brand);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-12);
  font-weight: 700;
}

.user-name {
  font-size: var(--fs-12);
  font-weight: 700;
  color: var(--ink);
  line-height: 1.25;
}

.user-role {
  font-size: var(--fs-10);
  font-weight: 500;
  color: var(--faint);
  line-height: 1.25;
}

/* ESS Topbar clock pill, reused here for the deficit and the running timer. */
.status-pill {
  display: flex;
  align-items: center;
  background: var(--brand-tint);
  border: 1px solid var(--brand-line);
  border-radius: var(--r-pill);
  padding: 5px 6px 5px 5px;
}

.status-pill-lead {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 12px 3px 4px;
}

.status-dot {
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  border-radius: 50%;
  background: var(--brand);
  color: var(--brand-ink);
  display: flex;
  align-items: center;
  justify-content: center;
}

.status-dot .ms {
  width: 16px;
  height: 16px;
  flex: 0 0 16px;
}

.status-label {
  font-size: var(--fs-9);
  font-weight: 600;
  color: #7f8aae;
  letter-spacing: 0.2px;
}

.status-value {
  font-size: var(--fs-12);
  font-weight: 700;
  color: var(--deep);
}

.status-value.is-empty {
  color: var(--faint-3);
}

.status-pill-tail {
  display: flex;
  flex-direction: column;
  padding-right: 14px;
}

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

/* The page's own padding lives on .main now, matching the ESS screen frame. */
.page {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* ESS Learning: the dashboard grid is 1.62fr to 1fr. */
/* `stretch`, not `start`. With `start` each column kept its natural height, and once the two
   entry forms moved into dialogs the left column ended halfway up the row and left a block of
   dead canvas beside a full right column. Equal-height columns read as a grid; unequal ones
   read as a mistake. The same fix is on the public board's rows. */
.split {
  display: grid;
  grid-template-columns: 1.62fr 1fr;
  gap: 14px;
  align-items: stretch;
}

/* A column that is a stack of cards has to fill the track it was just told to stretch into,
   or the stack ends short and reopens the gap. */
.split > .stack {
  height: 100%;
}

.split > .stack > .card:last-child,
.split > .card:last-child {
  flex: 1;
}

/* ESS Learning: the stat row is an explicit four columns, not auto-fit. */
.tiles {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
}

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

/* ---------- Cards. ESS Learning: 18px radius, 18px/20px padding, 13px gap. ---------- */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-18);
  padding: 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 13px;
}

.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.card-title {
  font-size: var(--fs-15);
  font-weight: 800;
  color: var(--ink);
  letter-spacing: -0.3px;
  margin: 0;
}

.card-sub {
  font-size: var(--fs-11-5);
  font-weight: 500;
  color: var(--faint);
  padding-top: 2px;
  margin: 0;
}

.card-action {
  font-size: var(--fs-11-5);
  font-weight: 700;
  color: var(--brand);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
}

/* ---------- Stat tiles. ESS Learning learnStats, transcribed. ---------- */

.tile {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-18);
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
}

/* The tinted variant the ESS files use for the one thing needing attention. */
.tile-attention {
  background: var(--brand-tint);
  border-color: var(--brand-tint-2);
}

.tile-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}

.tile-label {
  font-size: var(--fs-12-5);
  font-weight: 700;
  color: var(--ink-2);
}

.tile-body {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding-top: 12px;
}

.tile-value {
  font-size: var(--fs-28);
  font-weight: 800;
  color: var(--ink);
  letter-spacing: -1px;
  font-variant-numeric: tabular-nums;
}

.tile-note {
  font-size: var(--fs-11);
  font-weight: 500;
  color: var(--faint);
  padding-top: 3px;
}

/* ESS pill beside a stat value. */
.pill {
  border-radius: var(--r-6);
  padding: 2px 7px;
  font-size: var(--fs-10-5);
  font-weight: 700;
  background: var(--canvas-2);
  color: var(--muted);
  white-space: nowrap;
}

.tile-attention .pill {
  background: var(--surface);
  color: var(--danger);
}

.pill-good {
  background: var(--success-bg);
  color: var(--success);
}
.pill-warn {
  background: var(--warn-bg);
  color: var(--warn);
}
.pill-bad {
  background: var(--danger-bg);
  color: var(--danger);
}
.pill-brand {
  background: var(--brand-tint);
  color: var(--brand);
}

.is-good {
  color: var(--success);
}
.is-warn {
  color: var(--warn);
}
.is-bad {
  color: var(--danger);
}

/* ---------- Progress bars. ESS: 6px track, 4px radius, overflow hidden. ---------- */

.bar {
  height: 6px;
  background: var(--line-2);
  border-radius: 4px;
  overflow: hidden;
}

.bar-fill {
  height: 100%;
  border-radius: 4px;
  background: var(--brand);
}

.bar-fill.is-good {
  background: var(--success);
}
.bar-fill.is-bad {
  background: var(--danger);
}

/* ---------- Rows. ESS inner card: 16px radius, #EEF1F9 hairline. ---------- */

.rows {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.row {
  display: grid;
  grid-template-columns: 108px 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 11px 13px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-14);
  background: var(--surface);
}

.row-current {
  background: var(--brand-tint);
  border-color: var(--brand-tint-2);
}

.row-dim {
  opacity: 0.5;
}

.row-flag {
  background: var(--warn-bg);
  border-color: var(--warn-bg);
}

.row-time {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-11);
  font-weight: 500;
  color: var(--faint);
}

.row-title {
  font-size: var(--fs-12-5);
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.1px;
  min-width: 0;
}

.row-meta {
  font-size: var(--fs-11);
  font-weight: 500;
  color: var(--faint);
  padding-top: 2px;
}

.row-tail {
  display: flex;
  align-items: center;
  gap: 7px;
}

/* ---------- Chips ---------- */

.chip {
  display: inline-flex;
  align-items: center;
  height: 19px;
  padding: 0 7px;
  border-radius: var(--r-6);
  font-size: var(--fs-10-5);
  font-weight: 700;
  background: var(--canvas-2);
  color: var(--muted);
  white-space: nowrap;
}

.chip-brand {
  background: var(--brand-tint-2);
  color: var(--brand);
}
.chip-good {
  background: var(--success-bg);
  color: var(--success);
}
.chip-warn {
  background: var(--warn-bg);
  color: var(--warn);
}
.chip-bad {
  background: var(--danger-bg);
  color: var(--danger);
}
.chip-accent {
  background: var(--accent-bg);
  color: var(--accent);
}

/* Session tags keep a fixed colour so the timetable reads at a glance. */
.chip-build,
.chip-solve,
.chip-debug {
  background: var(--success-bg);
  color: var(--success);
}
.chip-structured {
  background: var(--brand-tint);
  color: var(--brand);
}
.chip-explore {
  background: var(--warn-bg);
  color: var(--warn);
}
.chip-passive,
.chip-log,
.chip-free {
  background: var(--canvas-2);
  color: var(--faint);
}

/* Monospace section marker, from the ESS design doc headings. */
.marker {
  font-family: var(--font-mono);
  font-size: var(--fs-11);
  font-weight: 500;
  color: var(--surface);
  background: var(--ink);
  border-radius: var(--r-6);
  padding: 3px 7px;
}

/* ---------- Timer ---------- */

/* IBM Plex Mono sets every glyph on the same advance, so a colon carries as much side
 * bearing as a digit and the readout reads as "00 : 00 : 00". Pulling the tracking in
 * closes the gap without touching the digits, which still align because tabular-nums keeps
 * their advance fixed. */
.timer-readout {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 44px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -3px;
  line-height: 1.05;
}

.timer-meta {
  font-size: var(--fs-11-5);
  font-weight: 500;
  color: var(--faint);
}

.timer-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* ---------- Buttons. ESS Topbar action button. ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  appearance: none;
  border: 1px solid var(--line-4);
  background: var(--surface);
  color: var(--ink);
  font-family: inherit;
  font-size: var(--fs-12-5);
  font-weight: 700;
  letter-spacing: -0.1px;
  padding: 10px 15px;
  border-radius: var(--r-10);
  cursor: pointer;
  /* A button label that wraps stops looking like a button. If it does not fit, the layout is
     wrong, not the label. */
  white-space: nowrap;
  transition:
    background var(--speed) ease-out,
    border-color var(--speed) ease-out;
}

.btn:hover {
  background: var(--canvas-2);
}

.btn-primary {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
  box-shadow: var(--shadow-brand);
}

.btn-primary:hover {
  background: var(--brand);
  filter: brightness(1.06);
}

.btn-danger {
  background: var(--danger);
  border-color: var(--danger);
  color: #ffffff;
}

.btn-small {
  padding: 6px 11px;
  font-size: var(--fs-11-5);
  border-radius: var(--r-9);
}

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
}

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

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.field-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

label {
  font-size: var(--fs-11-5);
  font-weight: 700;
  color: var(--ink-2);
}

input,
select,
textarea {
  font-family: inherit;
  font-size: var(--fs-12-5);
  font-weight: 500;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-4);
  border-radius: var(--r-10);
  padding: 10px 13px;
  width: 100%;
}

input[type="checkbox"] {
  width: auto;
}

input:focus,
select:focus,
textarea:focus,
.btn:focus-visible,
.icon-button:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

textarea {
  min-height: 76px;
  resize: vertical;
}

.form-inline {
  display: flex;
  gap: 8px;
}

.form-inline input {
  flex: 1;
}

@media (max-width: 520px) {
  .form-grid {
    grid-template-columns: 1fr;
  }
}

/* ---------- Banners ---------- */

.banner {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  border-radius: var(--r-12);
  padding: 11px 14px;
  font-size: var(--fs-12-5);
  font-weight: 600;
}

.banner-warn {
  background: var(--warn-bg);
  color: var(--warn);
}
.banner-bad {
  background: var(--danger-bg);
  color: var(--danger);
}
.banner-good {
  background: var(--success-bg);
  color: var(--success);
}
.banner-info {
  background: var(--brand-tint);
  color: var(--brand);
}

.muted {
  color: var(--muted);
}
.small {
  font-size: var(--fs-11-5);
}
.empty {
  font-size: var(--fs-11-5);
  color: var(--faint);
  padding: 6px 0;
}

/* ---------- Meters ---------- */

.meter {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 13px 0;
  border-bottom: 1px solid var(--line-2);
}

.meter:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.meter:first-child {
  padding-top: 0;
}

.meter-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.meter-name {
  font-size: var(--fs-12-5);
  font-weight: 700;
  color: var(--ink-2);
}

.meter-pct {
  font-size: var(--fs-17);
  font-weight: 800;
  color: var(--ink);
  letter-spacing: -0.5px;
  font-variant-numeric: tabular-nums;
}

.meter-gaps {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

/* ---------- Skills ---------- */

.skill-group {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.skill-heading {
  font-size: var(--fs-9);
  font-weight: 700;
  color: var(--faint-2);
  letter-spacing: 0.9px;
  text-transform: uppercase;
}

.skill-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* Four named states, four treatments. Never a bar (D-022). */
.skill-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  font-size: var(--fs-11-5);
  font-weight: 700;
  border: 1px solid var(--line-4);
  background: var(--canvas-2);
  color: var(--muted);
}

.skill-chip.level-1 {
  background: var(--canvas-2);
  border-color: var(--line-4);
  color: var(--muted);
}
.skill-chip.level-2 {
  background: var(--brand-tint);
  border-color: var(--brand-tint-2);
  color: var(--brand);
}
.skill-chip.level-3 {
  background: var(--success-bg);
  border-color: var(--success-bg-2);
  color: var(--success);
}
.skill-chip.level-4 {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
  box-shadow: var(--shadow-brand);
}

/* Brand marks come in every colour their owner chose, so they are muted to sit inside a chip
   rather than shout over the name beside them. Full colour on hover, which turns the wall of
   chips into something worth reading across. */
.skill-logo {
  width: 15px;
  height: 15px;
  flex: none;
  object-fit: contain;
  opacity: 0.82;
  filter: saturate(0.85);
  transition:
    opacity var(--speed) ease-out,
    filter var(--speed) ease-out;
}

.skill-chip:hover .skill-logo {
  opacity: 1;
  filter: none;
}

/* Level 4 fills the chip with solid brand blue, which several dark marks disappear into. The
   white disc is the smallest thing that keeps them readable without special-casing each logo. */
.skill-chip.level-4 .skill-logo {
  background: #ffffff;
  border-radius: 50%;
  padding: 1.5px;
  box-sizing: content-box;
  opacity: 1;
}

.skill-chip.is-blocked {
  border-color: var(--warn);
}

.skill-chip .flag {
  font-size: var(--fs-10);
  font-weight: 700;
  color: var(--warn);
}

/* ---------- Login ---------- */

.centre {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background: var(--canvas);
}

.login-card {
  width: 100%;
  max-width: 400px;
  background: var(--surface);
  border: 1px solid var(--line-3);
  border-radius: var(--r-22);
  box-shadow: var(--shadow-card);
  padding: 30px 32px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* ---------- Day/night theme toggle ---------- */

/* Lives here rather than in public.css because both surfaces load app.css and there must
   be exactly one definition of it. */

/* Adapted from Steve Walson's pure-CSS day/night toggle,
   https://codepen.io/steve_walson/pen/oNprgre

   Two changes from the source, both deliberate:

   1. Every length is in `em` against the one `font-size` on `.toggle`. The original is 200x80,
      which would be twice the height of everything else in this nav. The scene's proportions
      are unchanged; only the base changes.

   2. The original hides the checkbox with `display: none`, which also removes it from the tab
      order. It is the only theme control on the page, so it is clipped instead of hidden and
      it draws a focus ring on the scene.

   Everything is scoped under `.daynight` because `.cloud`, `.star` and `.sea` are names this
   stylesheet would otherwise collide with the moment anything else needs a sky. */

.daynight {
  display: block;
  cursor: pointer;
  flex: none;
}

/* Clipped, not `display: none`, so it stays focusable. */
.daynight-input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.daynight .toggle {
  font-size: 34px;
  display: block;
  height: 1em;
  width: 2.5em;
  border-radius: 9999px;
  background: linear-gradient(to bottom, #6ec8ff, #1f73a7);
  position: relative;
  overflow: hidden;
  box-shadow: 0.075em 0.075em 0.125em #0000003d;
}

.daynight-input:focus-visible + .toggle {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.daynight .mountains {
  height: 100%;
  width: 100%;
  position: absolute;
  z-index: 3;
  background-color: #1d7371;
  transition: all 0.5s ease-in-out;
  clip-path: polygon(
    0% 80%,
    25% 50%,
    40% 70%,
    60% 35%,
    65% 45%,
    80% 20%,
    100% 80%,
    100% 100%,
    0% 100%
  );
}

.daynight .cloud {
  height: 0.1em;
  width: 0.225em;
  border-radius: 9999px;
  background-color: rgb(255, 255, 255);
  position: absolute;
  top: 20%;
  left: 10%;
  filter: opacity(0.5);
}

.daynight .cloud::before {
  content: "";
  height: 0.125em;
  width: 0.125em;
  border-radius: 50%;
  background-color: rgb(255, 255, 255);
  position: absolute;
  top: -50%;
  right: 50%;
  transform: translateX(50%);
}

.daynight .sea {
  height: 2.5em;
  width: 300%;
  border-radius: 50%;
  position: absolute;
  right: 50%;
  bottom: -500%;
  transform: translateX(50%);
  transition: all 0.5s ease-in-out;
  background: linear-gradient(
    to bottom,
    #3f75cc,
    #06063b,
    #06063b,
    #06063b,
    #06063b
  );
}

.daynight .sea::before {
  content: "";
  height: 0.3125em;
  width: 0.1875em;
  background-color: rgba(254, 254, 254, 0.34);
  filter: blur(0.0625em);
  position: absolute;
  top: 2%;
  left: 43%;
}

/* The one measurement that does not scale proportionally. The source's 2px star at 80px tall
   works out at 0.85px here, which is sub-pixel and renders as a smudge. Held at roughly 1.7px
   instead, because a star you cannot see is not a star. */
.daynight .star {
  height: 0.05em;
  width: 0.05em;
  position: absolute;
  top: -50%;
  right: 15%;
  background-color: #fff;
  transition: all 0.5s ease-in-out;
  filter: blur(0.015em);
  border-radius: 50%;
}

.daynight .star::before {
  content: "";
  height: 100%;
  width: 100%;
  position: absolute;
  top: -50%;
  right: 0.25em;
  background-color: #fff;
  transition: all 0.5s ease-in-out;
  border-radius: 50%;
}

.daynight .star::after {
  content: "";
  height: 100%;
  width: 100%;
  position: absolute;
  top: 0.125em;
  right: -0.25em;
  background-color: #fff;
  transition: all 0.5s ease-in-out;
  border-radius: 50%;
}

/* The sun. */
.daynight .toggle::before {
  content: "";
  height: 0.5em;
  width: 0.5em;
  position: absolute;
  top: 10%;
  right: 30%;
  border-radius: 50%;
  background-color: #ffd34d;
  transition: all 0.5s ease-in-out;
  box-shadow: 0 0 0.1875em #fef95fc3;
}

/* The moon, parked off the left edge until it is night. */
.daynight .toggle::after {
  content: "";
  height: 0.4375em;
  width: 0.4375em;
  position: absolute;
  top: 10%;
  left: -60%;
  border-radius: 50%;
  background-color: #ffffff;
  transition: all 0.5s ease-in-out;
  box-shadow: 0 0 0.125em #ffffff88;
}

.daynight-input:checked + .toggle {
  background: linear-gradient(to bottom, #036daf, #003d63);
}

.daynight-input:checked + .toggle::before {
  transform: translateX(280%);
}

.daynight-input:checked + .toggle::after {
  transform: translateX(480%);
}

.daynight-input:checked + .toggle .mountains {
  background-color: #05021a;
  transform: translateY(100%);
}

.daynight-input:checked + .toggle .sea {
  background-color: #05021a;
  bottom: -210%;
}

.daynight-input:checked + .toggle .star {
  transform: rotate(10deg);
  top: 20%;
}

@media (prefers-reduced-motion: reduce) {
  .daynight .toggle::before,
  .daynight .toggle::after,
  .daynight .mountains,
  .daynight .sea,
  .daynight .star,
  .daynight .star::before,
  .daynight .star::after {
    transition: none;
  }
}

/* ---------- Device pairing ---------- */

.token-box {
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 14px 15px;
  border-radius: var(--r-12);
  background: var(--warn-bg);
  border: 1px solid var(--warn);
  margin-bottom: 14px;
}

.token-label {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  font-size: var(--fs-12);
  font-weight: 700;
  color: var(--warn);
}

/* Breaks anywhere, because a 43-character token in a narrow dialog would otherwise either
   overflow or need a scrollbar to read a value you have to copy exactly. */
.token-value {
  display: block;
  padding: 10px 12px;
  border-radius: var(--r-9);
  background: var(--surface);
  border: 1px solid var(--line-3);
  font-size: var(--fs-12);
  color: var(--ink);
  word-break: break-all;
  user-select: all;
}

.steps {
  margin: 16px 0 0 0;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: var(--fs-12);
  color: var(--muted);
}

.steps code {
  padding: 1px 5px;
  border-radius: var(--r-6);
  background: var(--canvas-2);
  border: 1px solid var(--line);
  font-size: var(--fs-11-5);
  color: var(--ink-2);
}

/* ---------- Page tabs ---------- */

/* Named page-tab rather than tab, because the public board already owns `.tab` for its top
   nav and it loads this stylesheet too. */
.page-tabs {
  display: flex;
  align-items: center;
  gap: 2px;
  background: var(--canvas-3);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 4px;
  overflow-x: auto;
  scrollbar-width: none;
  align-self: flex-start;
  max-width: 100%;
}

.page-tabs::-webkit-scrollbar {
  display: none;
}

.page-tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  appearance: none;
  border: none;
  background: transparent;
  font-family: inherit;
  padding: 8px 15px;
  border-radius: var(--r-pill);
  font-size: var(--fs-12-5);
  font-weight: 700;
  color: var(--muted);
  white-space: nowrap;
  cursor: pointer;
  transition:
    background var(--speed) ease-out,
    color var(--speed) ease-out;
}

.page-tab .ms {
  color: var(--faint);
}

.page-tab:hover {
  background: var(--surface);
  color: var(--ink);
}

.page-tab.is-on,
.page-tab.is-on:hover {
  background: var(--brand);
  color: var(--brand-ink);
  box-shadow: var(--shadow-brand);
}

.page-tab.is-on .ms {
  color: var(--brand-ink);
}

/* The count of what is inside, so a tab still says something while it is closed. That is the
   thing a tab costs you: what you cannot see, you forget. */
.page-tab .tab-count {
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  border-radius: var(--r-6);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-10-5);
  font-weight: 700;
  background: var(--brand-tint);
  color: var(--brand);
}

.page-tab.is-on .tab-count {
  background: rgba(255, 255, 255, 0.22);
  color: var(--brand-ink);
}

@media (max-width: 620px) {
  .page-tab {
    padding: 8px 12px;
  }
}

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

/* A native <dialog>. `::backdrop` and the top layer come with the element, so there is no
   z-index to lose an argument with. */
.sheet {
  width: min(560px, calc(100vw - 32px));
  max-height: min(86dvh, 760px);
  padding: 22px 24px 24px 24px;
  border: 1px solid var(--line);
  border-radius: var(--r-22);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-card);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.sheet::backdrop {
  background: rgba(16, 21, 40, 0.5);
  backdrop-filter: blur(2px);
}

.sheet-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 16px;
}

.sheet-title {
  font-size: var(--fs-17);
  font-weight: 800;
  color: var(--ink);
  letter-spacing: -0.3px;
  margin: 0;
}

.sheet-sub {
  font-size: var(--fs-12-5);
  font-weight: 500;
  color: var(--muted-2);
  margin: 2px 0 0 0;
}

/* On a phone a centred box with margins wastes the width it needs most. This is the standard
   bottom sheet: full width, square top corners only, sitting on the bottom edge. */
@media (max-width: 620px) {
  .sheet {
    width: 100vw;
    max-width: 100vw;
    max-height: 92dvh;
    margin: auto auto 0 auto;
    border-radius: var(--r-22) var(--r-22) 0 0;
    border-bottom: none;
    padding: 20px 16px 24px 16px;
  }
}

/* ---------- Topbar action buttons ---------- */

.btn-quiet {
  background: var(--surface);
  border-color: var(--line);
  color: var(--ink);
}

.btn-quiet:hover {
  background: var(--canvas-2);
}

/* Below the point where the two labels stop fitting beside the title, the buttons keep their
   icons and lose their words rather than wrapping the topbar onto three lines. */
@media (max-width: 900px) {
  .topbar-do-text {
    display: none;
  }

  .topbar-do {
    width: 38px;
    height: 38px;
    flex: 0 0 38px;
    padding: 0;
    justify-content: center;
    border-radius: var(--r-pill);
  }

  .topbar-actions {
    gap: 8px;
  }
}

/* ---------- Sidebar: sticky, collapsible, and a drawer on small screens ---------- */

/* Sticky rather than scrolling away with the page. It is the only navigation in the app, and
   a nav you have to scroll back up to find is a nav that stops getting used. `height: 100dvh`
   rather than `100vh` because mobile browsers shrink the visual viewport when their toolbars
   appear, and `vh` does not notice. */
.sidebar {
  position: sticky;
  top: 0;
  align-self: flex-start;
  height: 100dvh;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  transition:
    width var(--speed) ease-out,
    flex-basis var(--speed) ease-out;
}

/* The collapse control. Sits with the brand so it is in the same place whichever state the
   sidebar is in. */
.sidebar-toggle {
  margin-left: auto;
  width: 30px;
  height: 30px;
  flex: 0 0 30px;
  border-radius: var(--r-9);
  background: transparent;
  border: 1px solid transparent;
  color: var(--faint);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  transition:
    background var(--speed) ease-out,
    color var(--speed) ease-out;
}

.sidebar-toggle:hover {
  background: var(--canvas-2);
  border-color: var(--line);
  color: var(--ink);
}

/* ---------- Collapsed ---------- */

.shell.is-collapsed .sidebar {
  width: var(--sidebar-w-collapsed);
  flex-basis: var(--sidebar-w-collapsed);
}

/* Group headings, row labels, the wordmark and the footer note all go. The icon stays, and
   `title` on each row carries the label, so a collapsed sidebar is still navigable rather than
   a column of unlabelled glyphs. */
.shell.is-collapsed .brand-text,
.shell.is-collapsed .nav-label,
.shell.is-collapsed .nav-row > span:not(.ms),
.shell.is-collapsed .nav-badge,
.shell.is-collapsed .sidebar-foot {
  display: none;
}

/* Stacked rather than side by side. At 72px there is no room beside the mark, and floating
   the button into the corner put it on top of the mark. */
.shell.is-collapsed .brand {
  flex-direction: column;
  gap: 10px;
  padding: 16px 0 12px 0;
  justify-content: center;
}

.shell.is-collapsed .sidebar-toggle {
  margin-left: 0;
}

.shell.is-collapsed .nav {
  padding: 4px 10px 20px 10px;
}

.shell.is-collapsed .nav-row {
  justify-content: center;
  padding: 10px 0;
}

/* Groups still need separating once their headings are gone, or thirteen icons read as one
   undifferentiated column. */
.shell.is-collapsed .nav-group + .nav-group {
  border-top: 1px solid var(--line-2);
  padding-top: 10px;
}

/* ---------- Mobile: off-canvas drawer ---------- */

.sidebar-scrim {
  display: none;
}

.topbar-menu {
  display: none;
}

@media (max-width: 900px) {
  .shell {
    flex-direction: row;
  }

  /* Off-canvas rather than the old full-width strip of wrapped icons. Thirteen nav rows across
     the top of every page pushed the actual content below the fold on a phone. */
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 60;
    width: min(280px, 84vw);
    flex: none;
    height: 100dvh;
    border-right: 1px solid var(--line);
    transform: translateX(-100%);
    transition: transform 220ms ease-out;
    box-shadow: 0 20px 50px -20px rgba(16, 21, 40, 0.4);
  }

  .shell.is-drawer-open .sidebar {
    transform: translateX(0);
  }

  /* Collapse is a desktop idea. On a phone the drawer is either open or it is not. */
  .shell.is-collapsed .sidebar {
    width: min(280px, 84vw);
    flex-basis: auto;
  }

  .shell.is-collapsed .brand-text,
  .shell.is-collapsed .nav-label,
  .shell.is-collapsed .nav-row > span:not(.ms),
  .shell.is-collapsed .sidebar-foot {
    display: revert;
  }

  .shell.is-collapsed .brand {
    padding: 22px 20px 18px 20px;
    justify-content: flex-start;
  }

  .shell.is-collapsed .nav-row {
    justify-content: flex-start;
    padding: 9px 10px;
  }

  .shell.is-collapsed .sidebar-toggle {
    position: static;
    margin-left: auto;
  }

  .shell.is-collapsed .nav-group + .nav-group {
    border-top: none;
    padding-top: 0;
  }

  .sidebar-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 50;
    background: rgba(16, 21, 40, 0.45);
    opacity: 0;
    visibility: hidden;
    transition:
      opacity 220ms ease-out,
      visibility 220ms;
  }

  .shell.is-drawer-open .sidebar-scrim {
    opacity: 1;
    visibility: visible;
  }

  .topbar-menu {
    display: flex;
  }

  .main {
    padding: 14px 14px 22px 14px;
  }

  .topbar {
    gap: 10px;
  }

  /* Without this the title centres itself: space-between with three children puts the middle
     one in the middle. */
  .topbar-titles {
    flex: 1;
  }

  .topbar-title {
    font-size: var(--fs-17);
  }

  /* The deficit and streak pill is the one thing worth keeping in a phone topbar, but it needs
     to stop being a wide two-part pill first. */
  .status-pill {
    padding: 5px 10px;
    gap: 10px;
  }

  .status-pill-lead .status-dot {
    display: none;
  }

  /* The name is already on the sign-out control's tooltip and on the avatar. */
  .user-pill > span:not(.avatar):not(.ms) {
    display: none;
  }

  .user-pill {
    padding: 4px;
  }

  .user-pill .ms {
    display: none;
  }
}

@media (max-width: 620px) {
  .tiles {
    grid-template-columns: 1fr 1fr;
  }

  .topbar-sub {
    display: none;
  }

  /* At this width the pill costs more than it tells you. The same two numbers are the first
     two stat tiles, twenty pixels below. */
  .status-pill {
    display: none;
  }
}

@media (max-width: 420px) {
  .tiles {
    grid-template-columns: 1fr;
  }

  .main {
    padding: 12px 11px 20px 11px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sidebar,
  .sidebar-scrim {
    transition: none;
  }
}

/* ---------- Responsive ---------- */

@media (max-width: 1240px) {
  .tiles {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 1080px) {
  .split {
    grid-template-columns: 1fr;
  }
}

/* The old rule here turned the sidebar into a full-width strip of wrapped icons above the
   content. It is replaced by the drawer above. What remains is the content itself. */
@media (max-width: 900px) {
  .row {
    grid-template-columns: 88px 1fr;
  }
  .row-tail {
    grid-column: 1 / -1;
  }
}

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

/* ---------- Loader ----------
 *
 * Sits over the page until the first render finishes, so nobody sees a half-built dashboard
 * or an empty board that reads as zero progress.
 *
 * Present in the markup rather than injected, so it paints on the very first frame. Injecting
 * it from JavaScript would mean a flash of empty page first, which is the thing it exists to
 * prevent.
 */

.loader {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  background: var(--canvas);
  transition:
    opacity 260ms ease-out,
    visibility 260ms;
}

.loader img {
  width: 80px;
  height: 80px;
  display: block;
}

.loader-text {
  font-size: var(--fs-12-5);
  font-weight: 600;
  color: var(--faint);
  letter-spacing: -0.1px;
}

/* `visibility` as well as `opacity`, or the finished overlay keeps swallowing clicks. */
.loader.is-done {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .loader {
    transition: none;
  }
}

/* ---------- Programme, practice and digest pages ----------
 *
 * Built from the same primitives as the rest: card, row, chip, bar. The additions below are
 * only the shapes those primitives cannot make, and each one is used on more than one page.
 */

/* A block of the year. The left rule is the state, so thirteen of these read as a timeline
 * without needing a timeline widget. */
.block-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
  gap: 12px;
}

.block-card {
  border: 1px solid var(--line-2);
  border-left: 3px solid var(--line-3);
  border-radius: var(--r-14);
  background: var(--surface);
  padding: 13px 15px;
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.block-card.is-current {
  border-left-color: var(--brand);
  background: var(--brand-tint);
}

.block-card.is-past {
  border-left-color: var(--success);
}

.block-card.is-future {
  opacity: 0.82;
}

.block-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.block-number {
  font-family: var(--font-mono);
  font-size: var(--fs-10-5);
  font-weight: 500;
  color: var(--mono-label);
  letter-spacing: 0.4px;
}

.block-title {
  font-size: var(--fs-15);
  font-weight: 700;
  letter-spacing: -0.2px;
}

.block-dates {
  font-family: var(--font-mono);
  font-size: var(--fs-10-5);
  color: var(--faint);
}

.block-focus {
  font-size: var(--fs-12);
  color: var(--ink-3);
}

.block-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}

/* Checklists: the build status, and any list of done and not-done. The marker carries the
 * state so the text never has to say "done" twice. */
.check-list {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.check {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: 9px;
  align-items: start;
  padding: 5px 2px;
  font-size: var(--fs-12-5);
  line-height: 1.45;
}

.check .ms {
  color: var(--faint-2);
  margin-top: 1px;
}

.check.is-done {
  color: var(--muted);
}

.check.is-done .ms {
  color: var(--success);
}

.phase-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 6px;
}

.phase-title {
  font-size: var(--fs-12-5);
  font-weight: 700;
  letter-spacing: -0.1px;
}

/* An exam paper. One section per band, points in the corner, rubric folded away until it is
 * time to mark: reading the rubric before answering is reading the answer. */
.paper-section {
  border: 1px solid var(--line-2);
  border-radius: var(--r-14);
  background: var(--surface);
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.paper-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.paper-name {
  font-family: var(--font-mono);
  font-size: var(--fs-10-5);
  font-weight: 500;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--mono-label);
}

.paper-points {
  font-family: var(--font-mono);
  font-size: var(--fs-11);
  color: var(--faint);
}

.paper-prompt {
  font-size: var(--fs-13);
  font-weight: 600;
  line-height: 1.55;
  white-space: pre-wrap;
}

.paper-instruction {
  font-size: var(--fs-11-5);
  color: var(--muted);
}

details.rubric > summary {
  cursor: pointer;
  font-size: var(--fs-11-5);
  font-weight: 600;
  color: var(--brand);
  list-style: none;
}

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

details.rubric > summary::before {
  content: "expand_more";
  font-family: "Material Symbols Rounded";
  font-size: 16px;
  vertical-align: -3px;
  margin-right: 3px;
}

details.rubric[open] > summary::before {
  content: "expand_less";
}

.rubric-copy {
  white-space: pre-wrap;
}

details.rubric > div {
  margin-top: 7px;
  padding: 10px 12px;
  border-radius: var(--r-11);
  background: var(--canvas-2);
  border: 1px solid var(--line-2);
  font-size: var(--fs-12);
  color: var(--ink-2);
  line-height: 1.55;
}

/* A prompt pack. The body is the product, so it gets the monospace block and a copy button
 * that reports what it did rather than hoping you noticed. */
.pack-body {
  font-family: var(--font-mono);
  font-size: var(--fs-11-5);
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 320px;
  overflow: auto;
  padding: 12px 14px;
  border-radius: var(--r-12);
  border: 1px solid var(--line-2);
  background: var(--canvas-3);
  color: var(--ink-2);
  margin: 0;
}

.pack-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 9px;
}

/* A digest entry. The matched keywords are the reason it is here, so they sit under the
 * title rather than behind a tooltip. */
.feed-item {
  border: 1px solid var(--line-2);
  border-radius: var(--r-14);
  background: var(--surface);
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.feed-title {
  font-size: var(--fs-13);
  font-weight: 700;
  letter-spacing: -0.15px;
  color: var(--ink);
  text-decoration: none;
}

.feed-title:hover {
  color: var(--brand);
}

.feed-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-11);
  color: var(--faint);
}

.feed-summary {
  font-size: var(--fs-12);
  color: var(--ink-3);
  line-height: 1.5;
}

/* The day strip on the curriculum page: 28 squares, one per day of the block. */
.day-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.day-cell {
  width: 30px;
  height: 30px;
  border-radius: var(--r-8);
  border: 1px solid var(--line-3);
  background: var(--canvas-2);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: var(--fs-10);
  color: var(--muted);
}

.day-cell.is-past {
  background: var(--brand-tint-2);
  border-color: var(--brand-line);
  color: var(--ink-2);
}

.day-cell.is-today {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
  font-weight: 700;
}

.day-cell.is-sunday {
  border-style: dashed;
}

/* A wide table that must never make the page scroll sideways. */
.scroll-x {
  overflow-x: auto;
}

@media (max-width: 640px) {
  .block-grid {
    grid-template-columns: 1fr;
  }
}

/* ---------- Small utilities, because the CSP forbids inline styles ----------
 *
 * `style-src 'self'` with no 'unsafe-inline' blocks the style ATTRIBUTE as well as a <style>
 * block. Every `style="gap: 10px"` in the markup was being dropped by the browser and logging
 * a CSP violation, which is why the logout icon was the wrong colour and dialog forms sat at
 * the default gap. It had been that way since the policy was written and nothing noticed,
 * because a blocked style looks like a style nobody wrote.
 *
 * Adding 'unsafe-inline' would have fixed it in one line and given up the directive that stops
 * an injected style attribute doing damage. These four classes cost less.
 */
.gap-10 {
  gap: 10px;
}

.gap-12 {
  gap: 12px;
}

.icon-faint {
  color: var(--faint-3);
}

.field-wide {
  min-width: 220px;
}

/* ---------- Text that came from somewhere else ----------
 *
 * A feed title is arbitrary text from an arbitrary site. One long unbroken token, which for a
 * feed usually means a URL, makes its card wider than the phone and the whole page scrolls
 * sideways. Measured: 472px of content in a 390px viewport, from one title.
 *
 * `min-width: 0` is the half that is easy to miss. A flex item's default minimum size is its
 * content, so the wrapping rules below do nothing until that floor is removed.
 */
.feed-item,
.feed-title,
.feed-summary,
.row-title,
.paper-prompt {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Row titles sit in a grid track that must be allowed to shrink for the same reason. */
.row > div {
  min-width: 0;
}

/* ---------- Focus: labels, slots and checkpoints ----------
 *
 * D-062. A library of 101 excellent resources reads as though every entry is equally urgent,
 * which is how a year gets spent perpetually preparing. The label is the defence, so it has
 * to be readable at a glance rather than buried in a tooltip.
 */
.label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  font-size: var(--fs-10);
  font-weight: 700;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  border: 1px solid transparent;
  white-space: nowrap;
}

.label-core {
  background: var(--brand-tint-2);
  color: var(--brand-hover);
  border-color: var(--brand-line);
}

.label-practice {
  background: var(--success-bg);
  color: var(--success);
}

.label-supplement {
  background: var(--accent-bg);
  color: var(--accent);
}

/* Reference and later are deliberately quiet. They are the two that must not compete for
 * attention with the thing you are supposed to be doing today. */
.label-reference {
  background: var(--canvas-2);
  color: var(--muted);
  border-color: var(--line-3);
}

.label-later,
.label-parked {
  background: transparent;
  color: var(--faint);
  border-color: var(--line-3);
}

/* The five slots. An empty slot is drawn as an empty slot, not hidden, because the whole
 * point is seeing how much room is left. */
.slot-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 9px;
}

.slot {
  border: 1px solid var(--line-2);
  border-radius: var(--r-12);
  padding: 10px 12px;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.slot.is-free {
  border-style: dashed;
  background: var(--canvas-3);
}

.slot-name {
  font-family: var(--font-mono);
  font-size: var(--fs-10);
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--mono-label);
}

.slot-occupant {
  font-size: var(--fs-12-5);
  font-weight: 700;
  letter-spacing: -0.1px;
  overflow-wrap: anywhere;
}

.slot.is-free .slot-occupant {
  font-weight: 500;
  color: var(--faint);
}

.slot-rule {
  font-size: var(--fs-11);
  color: var(--muted);
  line-height: 1.45;
}

/* A parked topic. Reads as settled rather than as a to-do, which is the entire function. */
.parked-item {
  border: 1px dashed var(--line-3);
  border-radius: var(--r-11);
  padding: 9px 12px;
  background: var(--canvas-3);
}

.parked-name {
  font-size: var(--fs-12-5);
  font-weight: 700;
  color: var(--muted);
  text-decoration: line-through;
  text-decoration-color: var(--faint-3);
}

.parked-reason {
  font-size: var(--fs-11-5);
  color: var(--ink-3);
  line-height: 1.5;
  margin-top: 3px;
}

/* ---------- Score bands ----------
 *
 * Five named buttons instead of "a number out of 100" typed into a prompt box. A hundred-way
 * choice with no anchors, made at 23:00, is not a more precise judgement than five bands. It
 * is the same judgement with arithmetic bolted on.
 */

.score-choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px;
}

.score-choice {
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px;
  cursor: pointer;
  text-align: left;
  transition:
    border-color 140ms ease,
    background 140ms ease;
}

.score-choice:hover {
  border-color: var(--brand);
  background: var(--brand-tint);
}

.score-choice.is-on {
  border-color: var(--brand);
  background: var(--brand-tint);
}

/* Only the next level up is offered, so most of these are disabled most of the time. Without
 * this they looked live, and the page read as one that ignores clicks rather than one that is
 * telling you a level cannot be skipped. */
.score-choice:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  background: var(--canvas-2);
}

.score-choice:disabled:hover {
  border-color: var(--line);
  background: var(--canvas-2);
}

.score-choice small {
  display: block;
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-3);
  margin-top: 2px;
}

/* The live word counter on the cannot-explain kill. Turning green at forty is the whole
 * feedback loop the prompt() box could not give. */
.is-enough {
  color: var(--success);
  font-weight: 600;
}

/* ---------- Progress rail ----------
 *
 * One pip per step, filled as they are done. Shared by the nightly wrap-up and the sixteen
 * interrogation questions: both are sequences where the end has to be visible from the start,
 * because "how much more of this is there" is the question that ends a session early.
 */

.wrap-rail {
  display: flex;
  gap: 5px;
}

.wrap-rail span {
  height: 4px;
  flex: 1;
  border-radius: 999px;
  background: var(--line);
  transition: background 160ms ease;
}

.wrap-rail span.is-done {
  background: var(--brand);
}

.wrap-rail span.is-here {
  background: var(--brand);
  opacity: 0.55;
}

/* A row of decisions, each big enough to hit without aiming. */
.wrap-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.wrap-actions .btn {
  min-height: 46px;
}

/* ---------- Attaching evidence ----------
 *
 * A screenshot of the thing working IS the evidence, which is what shrinks the typing to one
 * line. Drop, paste or pick: paste matters most, because a screenshot on Windows goes to the
 * clipboard and forcing it to disk first turns two seconds into a file dialog.
 */

.attach {
  position: relative;
  border: 1px dashed var(--line-3);
  border-radius: 12px;
  padding: 14px;
  background: var(--canvas-3);
  transition:
    border-color 140ms ease,
    background 140ms ease;
}

.attach.is-over {
  border-color: var(--brand);
  background: var(--brand-tint);
}

.attach.is-busy {
  opacity: 0.6;
  pointer-events: none;
}

.attach-prompt {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--ink-3);
  pointer-events: none;
}

/* Covers the whole zone so the entire panel is the click target, not just a small button. */
.attach-input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}

.attach-preview {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.attach-thumb {
  width: 56px;
  height: 56px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--canvas);
}

.attach-meta {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}

.attach-name {
  font-size: 13px;
  font-weight: 600;
  /* A long filename must not widen a dialog on a phone. */
  min-width: 0;
  overflow-wrap: anywhere;
}

.attach-size {
  font-size: 11px;
  color: var(--ink-3);
}

.attach-status {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--ink-3);
}

.attach-status.is-bad {
  color: var(--danger);
}

/* ---------- The reading archive (D-069) ---------- */

.archive-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-14);
  background: var(--surface);
}

.archive-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  flex: 1;
}

.archive-title {
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-align: left;
  /* A page title from the open web can be any length at all. */
  min-width: 0;
  overflow-wrap: anywhere;
}

.archive-title:hover {
  color: var(--brand);
}

.archive-snippet {
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-2);
  margin: 2px 0 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The stored text, shown as-is. `pre-wrap` keeps the paragraph breaks that survived
 * extraction without letting a long line push the dialog wider than the screen. */
.archive-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink-2);
  background: var(--canvas-3);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 14px;
  max-height: 50vh;
  overflow-y: auto;
  margin: 0;
}

/* ---------- Chips ----------
 *
 * A tappable one-word answer. Used by the wrap-up's check-in cards, the ledger field picker,
 * and the archive's "never read at all" list, so it lives here rather than in one page's
 * stylesheet. The rail and the action row moved for the same reason.
 */

.wrap-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  /* A flex child does not shrink below its content unless told to, so a chip row nested in
   * another flex container overflows the page instead of wrapping. */
  min-width: 0;
}

.wrap-chip {
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 9px 14px;
  cursor: pointer;
  min-width: 0;
  overflow-wrap: anywhere;
  text-align: left;
}

.wrap-chip:hover {
  border-color: var(--brand);
  background: var(--brand-tint);
}

/* The selected one. Without this the default choice looked identical to every other, so the
 * page appeared to be asking a question it had already answered. */
.wrap-chip.is-on {
  border-color: var(--brand);
  background: var(--brand);
  color: var(--brand-ink);
  font-weight: 600;
}

/* The full plan remains visible, but this one card owns the visual start of the day (D-075). */
.next-action {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  gap: 14px;
  align-items: start;
  border-color: color-mix(in srgb, var(--brand) 42%, var(--line));
  box-shadow: var(--shadow-brand);
}

.next-action[hidden] {
  display: none;
}

.next-action-mark {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  color: var(--brand-ink);
  background: var(--brand);
}

.next-action-title {
  margin: 4px 0 6px;
  max-width: 900px;
  font-size: clamp(17px, 2vw, 22px);
  line-height: 1.35;
}

.next-action-body {
  min-width: 0;
}

.next-action-objective {
  margin: 5px 0 0;
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 600;
}

.next-action-facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin: 14px 0;
}

.next-action-facts > div {
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--canvas-2);
}

.next-action-facts > div:nth-child(5) {
  grid-column: 1 / -1;
}

.next-action-facts dt {
  margin-bottom: 4px;
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.next-action-facts dd {
  margin: 0;
  color: var(--ink-2);
  font-size: 13px;
  line-height: 1.5;
}

.stuck-steps {
  display: grid;
  grid-template-columns: repeat(7, minmax(72px, 1fr));
  gap: 6px;
  padding: 0;
  margin: 4px 0 16px;
  list-style: none;
  overflow-x: auto;
}

.stuck-steps li {
  padding: 7px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
  text-align: center;
  white-space: nowrap;
}

.stuck-steps li.is-complete {
  border-color: var(--success-bg-2);
  color: var(--success);
  background: var(--success-bg);
}

.stuck-steps li.is-current {
  border-color: var(--brand);
  color: var(--deep);
  background: var(--brand-tint);
}

.stuck-current {
  margin-bottom: 14px;
}

.stuck-hint {
  margin-top: 12px;
  padding: 12px;
  border-left: 4px solid var(--brand);
  color: var(--deep);
  background: var(--brand-tint);
  line-height: 1.5;
}

.stuck-choice {
  display: grid;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
}

.stuck-choice label {
  display: flex;
  gap: 8px;
  align-items: center;
}

@media (max-width: 620px) {
  .next-action-facts {
    grid-template-columns: 1fr;
  }

  .next-action-facts > div:nth-child(5) {
    grid-column: auto;
  }
}

.recovery-screen {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  gap: 18px;
  align-items: start;
  border-color: var(--danger);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--danger) 20%, transparent);
}

.recovery-screen[hidden] {
  display: none;
}

.recovery-mark {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 14px;
  color: var(--surface);
  background: var(--danger);
}

.recovery-title {
  margin: 4px 0 6px;
  font-size: clamp(22px, 3vw, 34px);
  line-height: 1.2;
}

.recovery-progress {
  width: fit-content;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-8, 8px);
  font-variant-numeric: tabular-nums;
  font-weight: 650;
  background: var(--surface-2);
}

.library-command {
  flex: 1;
  overflow-x: auto;
  padding: 8px 10px;
  border-radius: var(--r-8, 8px);
  background: var(--code-bg, rgba(127, 127, 127, 0.12));
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  white-space: nowrap;
}
.pwa-control {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.pwa-control summary {
  color: var(--ink);
  cursor: pointer;
  font-size: 12px;
  font-weight: 700;
}

.pwa-control p {
  margin: 8px 0 10px;
  line-height: 1.5;
}

.pwa-control-button {
  width: 100%;
}

.pwa-control-button + .pwa-control-button {
  margin-top: 8px;
}

.shell.is-collapsed .pwa-control {
  display: none;
}

.nav-more > summary,
#additional-resources > summary {
  cursor: pointer;
  font-weight: 600;
  padding: 12px 10px;
}
.nav-more[open] > .nav-group { margin-top: 12px; }
#additional-resources { margin-top: 16px; border-top: 1px solid var(--line); }


@media (max-width: 620px) {
  .topbar { flex-wrap: wrap; }
  .topbar-titles { min-width: 140px; }
  .topbar-actions { margin-left: auto; max-width: 100%; flex-wrap: wrap; }
  .tabpanel[data-tab="resources"] > .card-head {
    flex-direction: column;
    align-items: stretch;
  }
}
