:root {
  /* Everything sits on the same white. Structure comes from black rules, not
     from tinted panels or shadows. */
  --paper: #ffffff;
  --ink: #000000;
  --muted: #565656;
  --faint: #7a7a7a;
  --rule: #000000;
  --hairline: #d4d4d4;
  --hover: #f2f2f2;
  --ok: #0a6b34;
  --err: #b3001b;
  --warn: #8a5000;
  --radius: 4px;
}

* {
  box-sizing: border-box;
}

/* An explicit `display` in author CSS beats the UA stylesheet's [hidden] rule,
   which silently breaks hiding on anything styled display:flex. */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 15px/1.55 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

.layout {
  max-width: 1220px;
  margin: 0 auto;
  padding: 0 20px 60px;
  display: grid;
  grid-template-columns: minmax(420px, 520px) 1fr;
  gap: 16px;
  align-items: start;
  width: 100%;
}

.col {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

/* Wide screens get an app shell: the page itself never scrolls, and each
   column scrolls inside its own track. That is what lets the stage summary sit
   on the bottom edge of the screen rather than wherever the list happens to
   end - and it keeps the settings and progress in view while you scroll a few
   hundred links. */
@media (min-width: 901px) {
  body {
    height: 100vh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  .topbar {
    flex: none;
    width: 100%;
  }

  .layout {
    flex: 1 1 auto;
    min-height: 0;
    padding-bottom: 16px;
    align-items: stretch;
  }

  .col-left {
    overflow-y: auto;
    padding-right: 4px;
  }

  .col-right {
    min-height: 0;
  }

  .col-right #step-links {
    /* Takes the leftover height, which is what pushes the summary panel down
       to the bottom edge instead of leaving it under a short list. */
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
  }
}

/* Narrow screens stack and scroll normally; a capped list keeps the page from
   becoming one enormous scroll. */
@media (max-width: 900px) {
  .layout {
    grid-template-columns: 1fr;
  }

  .col-right #step-links .links {
    max-height: 60vh;
  }
}

/* ---------- top bar ---------- */

.topbar {
  max-width: 1220px;
  margin: 0 auto;
  padding: 28px 20px 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.tagline {
  margin: 0;
  color: var(--ink);
  font-size: 15px;
}

/* Keeps a top-level heading in the document outline for screen readers now
   that the visible wordmark is gone. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.status {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  color: var(--ink);
  padding: 5px 11px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  white-space: nowrap;
}

.status .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--faint);
  flex: none;
}

.status[data-state='loading'] .dot {
  background: var(--warn);
  animation: pulse 1.4s ease-in-out infinite;
}

.status[data-state='ready'] .dot {
  background: var(--ok);
}

.status[data-state='error'] .dot {
  background: var(--err);
}

@keyframes pulse {
  50% {
    opacity: 0.25;
  }
}

/* ---------- cards ---------- */

.card {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 18px;
}

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

.card-head h2 {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 8px;
}

.count {
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0 7px;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.card-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  /* The left column is narrow, so the download buttons wrap rather than
     spilling out of the card. */
  flex-wrap: wrap;
}

/* ---------- dropzone ---------- */

.dropzone {
  border: 1px dashed var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  padding: 44px 20px;
  text-align: center;
  cursor: pointer;
  transition: background 0.12s;
}

.dropzone:hover,
.dropzone:focus-visible {
  background: var(--hover);
  outline: none;
}

.dropzone.dragging {
  background: var(--hover);
  border-style: solid;
}

.dropzone.busy {
  opacity: 0.5;
  pointer-events: none;
}

.drop-icon {
  width: 32px;
  height: 32px;
  color: var(--ink);
  margin-bottom: 10px;
}

.drop-title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
}

.drop-sub {
  margin: 3px 0 0;
  color: var(--muted);
  font-size: 14px;
}

.drop-sub .link {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.drop-formats {
  margin: 12px 0 0;
  color: var(--faint);
  font-size: 12.5px;
}

/* ---------- paste box ---------- */

.paste {
  margin-top: 12px;
  font-size: 13.5px;
}

.paste summary {
  cursor: pointer;
  color: var(--muted);
  padding: 4px 0;
  width: fit-content;
}

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

.paste textarea {
  width: 100%;
  margin-top: 8px;
  padding: 10px 12px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  resize: vertical;
}

textarea:focus,
select:focus,
input[type='search']:focus {
  outline: 2px solid var(--ink);
  outline-offset: -1px;
}

.paste .btn {
  margin-top: 8px;
}

/* ---------- file list ---------- */

.filelist {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.filelist li {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 4px 11px;
  font-size: 13px;
}

.filelist .fname {
  font-weight: 500;
  max-width: 240px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.filelist .fmeta {
  color: var(--muted);
}

.filelist .fmeta.bad {
  color: var(--err);
}

/* ---------- links list ---------- */

.links {
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}

/* Three columns, and nothing crosses between them.
   Flex could not hold this line: the status column needs `min-width: 0` to stop
   long messages shoving the link off the row, but that also lets it collapse
   narrower than the dots it contains, which then spill out sideways and land on
   top of the file size. A grid track sized `minmax(min-content, …)` cannot go
   below the dots and cannot grow past its share, so both failures are ruled out
   by the layout rather than patched afterwards. */
.links li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(min-content, 290px) auto;
  align-items: center;
  column-gap: 14px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--hairline);
  font-size: 13.5px;
}

.links li[data-status='done'] .u-host {
  font-weight: 600;
}

.links li:last-child {
  border-bottom: none;
}

.links li:hover {
  background: var(--hover);
}

.links li.hidden {
  display: none;
}

.links .r-link {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* The title goes here once a page has one, and titles run long. Without a
   truncation rule it sets the row's width and drives the dots off the edge. */
.links .u-host {
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.links .u-rest {
  color: var(--muted);
  font-size: 12.5px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.empty {
  color: var(--muted);
  font-size: 13.5px;
  text-align: center;
  padding: 18px;
  margin: 0;
}

/* ---------- options ---------- */

.options summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-weight: 600;
  font-size: 14px;
  padding: 2px 0;
  list-style: none;
}

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

.summary-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.chevron {
  transition: transform 0.15s ease;
}

.options[open] > summary .chevron {
  transform: rotate(180deg);
}

.summary-hint {
  font-weight: 400;
  font-size: 12.5px;
  color: var(--muted);
}

.option-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin: 16px 0 4px;
}

.option-grid label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 12.5px;
  color: var(--muted);
}

select {
  padding: 6px 9px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  font-size: 13.5px;
  font-family: inherit;
}

.option-checks {
  display: flex;
  flex-direction: column;
  gap: 9px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--hairline);
}

.option-checks label {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13.5px;
  cursor: pointer;
}

.option-checks input {
  accent-color: var(--ink);
  width: 15px;
  height: 15px;
}

/* An option the current renderer has no use for: still readable, clearly not
   in play, rather than vanishing and making the panel jump. */
.option-grid label.muted,
.option-checks label.muted {
  opacity: 0.4;
  cursor: default;
}

.option-checks em {
  font-style: normal;
  color: var(--faint);
  font-size: 12.5px;
}

.go {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--hairline);
  flex-wrap: wrap;
}

/* ---------- buttons ---------- */

.btn {
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  padding: 7px 14px;
  border-radius: var(--radius);
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
  transition: background 0.12s;
  white-space: nowrap;
}

.btn:hover:not(:disabled) {
  background: var(--hover);
}

.btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

.btn-primary {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
  font-weight: 600;
  padding: 8px 18px;
}

.btn-primary:hover:not(:disabled) {
  background: #262626;
}

.btn-quiet {
  border-color: transparent;
  background: transparent;
  color: var(--muted);
  padding: 6px 10px;
}

.btn-quiet:hover:not(:disabled) {
  background: var(--hover);
  color: var(--ink);
}

input[type='search'] {
  padding: 6px 11px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  width: 150px;
}

/* ---------- progress ---------- */

.progress {
  height: 10px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}

.progress-bar {
  height: 100%;
  width: 0;
  background: var(--ink);
  transition: width 0.25s ease;
}

.progress-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 8px;
}

.progress-text,
.progress-eta {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}

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

/* ---------- stage dots ---------- */

/* The status column: dots, then whatever this row has to say, then its size and
   timing. One stack, right-aligned, entirely inside its own grid track. */
.r-track {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
}

.stages {
  flex: none;
  display: inline-flex;
  align-items: center;
}

.stages b {
  position: relative;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  background: var(--paper);
  color: var(--faint);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
}

.stages b svg {
  width: 11px;
  height: 11px;
  display: block;
}

.stages b + b {
  margin-left: 13px;
}

/* The connector belongs to the dot it leads into, so it can be filled by the
   state of the dot before it. */
.stages b + b::before {
  content: '';
  position: absolute;
  right: 100%;
  top: 50%;
  width: 13px;
  height: 1px;
  background: var(--hairline);
  transform: translateY(-50%);
}

/* "skipped" fills exactly like "done": a page that was already English had
   nothing to translate, which is a success, not a gap. The summary percentages
   count it the same way. */
.stages b[data-s='done'],
.stages b[data-s='skipped'] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.stages b[data-s='done'] + b::before,
.stages b[data-s='skipped'] + b::before {
  background: var(--ink);
}

.stages b[data-s='running'] {
  border-color: var(--ink);
  border-width: 2px;
  color: var(--ink);
  animation: pulse 1.1s ease-in-out infinite;
}

.stages b[data-s='failed'] {
  background: var(--err);
  border-color: var(--err);
  color: var(--paper);
}

/* ---------- stage summary, pinned under the list ---------- */

.stage-summary {
  flex: none;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  padding: 14px 18px;
}

.stage-summary-dots {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
}

.sum-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  flex: 1 1 0;
  min-width: 0;
  position: relative;
}

/* Connector runs between the big discs, matching the rows above. */
.sum-stage + .sum-stage::before {
  content: '';
  position: absolute;
  right: 50%;
  left: -50%;
  top: 20px;
  height: 1px;
  background: var(--hairline);
  z-index: 0;
}

.sum-stage[data-lit='true']::before {
  background: var(--ink);
}

/* The disc fills from the bottom to its percentage. Two stacked copies of the
   same icon do the work: the lower layer is a black icon on white, the upper is
   a white icon on black, clipped to just the filled portion. Anything under the
   fill line therefore reads inverted - background and icon together - without
   needing to know anything about the icon's shape. */
.sum-disc {
  position: relative;
  z-index: 1;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: var(--paper);
  overflow: hidden;
  display: block;
}

.sum-disc > span {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.disc-base {
  color: var(--ink);
}

.disc-fill {
  color: var(--paper);
  background: var(--ink);
  clip-path: inset(calc(100% - var(--fill, 0%)) 0 0 0);
  transition: clip-path 0.45s ease;
}

.sum-disc svg {
  width: 20px;
  height: 20px;
}

.sum-pct {
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.sum-label {
  font-size: 11.5px;
  color: var(--muted);
  text-align: center;
}

.sum-count {
  font-size: 11px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

.r-meta {
  color: var(--faint);
  font-size: 11px;
  line-height: 1.35;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Anything the row has to say, under the dots and set as plain text. Only the
   colour separates a failure from a note, so they never have to be hunted for
   in two different places. */
.r-tags {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: baseline;
  gap: 0 8px;
  min-width: 0;
  max-width: 100%;
}

/* Wraps to two lines before giving up and clipping. A failure explains itself
   in its second half - "and no Wayback Machine snapshot exists" is the part
   worth reading - so a single ellipsised line would hide the useful bit. The
   full text is on the element's title either way. */
.r-tag {
  font-size: 11px;
  line-height: 1.35;
  color: var(--muted);
  max-width: 100%;
  text-align: right;
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow-wrap: anywhere;
}

/* The same red the response code uses. */
.r-tag.bad {
  color: var(--err);
}

/* Not finished, not failed - a retry counting down. */
.r-tag.pending {
  color: var(--warn);
}

.r-dl {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 26px;
  color: var(--ink);
  text-decoration: none;
  border-radius: var(--radius);
}

.r-dl:hover {
  background: var(--hover);
}

.r-dl[aria-disabled='true'] {
  color: var(--hairline);
  pointer-events: none;
}

.r-dl svg {
  display: block;
}

/* ---------- toast ---------- */

/* Sits at the top: the bottom edge now belongs to the stage summary panel,
   and a toast there would cover it every time. */
.toast {
  position: fixed;
  left: 50%;
  top: 14px;
  transform: translateX(-50%);
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--rule);
  padding: 10px 16px;
  border-radius: var(--radius);
  font-size: 13.5px;
  margin: 0;
  max-width: min(560px, calc(100vw - 40px));
  z-index: 10;
}

@media (max-width: 560px) {
  .card-head {
    align-items: stretch;
  }

  input[type='search'] {
    width: 100%;
  }

  .go .btn {
    flex: 1;
  }
}

/* ---------- sign-in gate ---------- */

.gate {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: var(--paper);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.gate-card {
  width: min(360px, 100%);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 28px 26px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.gate-title {
  margin: 0;
  font-size: 20px;
  letter-spacing: -0.01em;
}

.gate-sub {
  margin: 0;
  color: var(--muted);
  font-size: 13.5px;
}

.gate-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
  color: var(--muted);
}

.gate-field input {
  font: inherit;
  font-size: 14px;
  padding: 7px 11px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
}

.gate-error {
  margin: 0;
  color: var(--err);
  font-size: 13px;
}

.gate-submit {
  margin-top: 4px;
}
