:root, :root.light {
  color-scheme: light dark;

  /* default light mode values */
  --body-bg: #fbfbfc;
  --surface-bg: #ffffff;
  --border-soft: rgba(0, 0, 0, 0.18);
  --muted-soft: rgba(0, 0, 0, 0.55);
  --block-stroke: rgba(180, 180, 180, 1);
  --block-top: #e7e9ee;
  --block-side: #c7ccd5;
  --accent: gray;
  /* what blocks from the stratum feed pulse toward. Deliberately not --accent: that
     marks minimum-difficulty blocks, and is orange in dark mode. */
  --mining-pulse: gray;
  --text-color: black;
  --block-to-block-link-color: black;
  --block-description-link-color: gray;
  --block-description-bg: #f8f9fa;
  --block-miner-color: rgb(85, 85, 85);
  --node-indicatior-stroke: black;

  --tip-status-color-active: #79FF00;
  --tip-status-color-invalid: #CC203B;
  --tip-status-color-valid-fork: #3CACB5;
  --tip-status-color-valid-headers: #FF69B4;
  --tip-status-color-headers-only: #FF8800;

  /* deployments a block signals for (BIP 9 version bits). Brightly filled and
     opaque like the tip-status boxes, so the dark chip text stays legible in
     either theme, but in a hue none of those statuses use. */
  --signal-color: #9B8CFF;
}

/* automatic dark mode */
/* keep the rules in sync with the manual dark mode below! */
@media (prefers-color-scheme: dark) {
  :root {
    --body-bg: #18191c;
    --surface-bg: #232427;
    --border-soft: rgba(255, 255, 255, 0.2);
    --muted-soft: rgba(255, 255, 255, 0.6);
    --block-stroke: rgba(255, 255, 255, 0.28);
    --block-top: #34353c;
    --block-side: #15161a;
    --accent: #f7931a;
    --mining-pulse: #555;
    --text-color: white;
    --block-to-block-link-color: white;
    --block-description-link-color: lightgray;
    --block-description-bg: #f8f9fa;
    --node-indicatior-stroke: black;
    --block-miner-color: rgb(172, 172, 172);

    --tip-status-color-active: #79FF00;
    --tip-status-color-invalid: #CC203B;
    --tip-status-color-valid-fork: #3CACB5;
    --tip-status-color-valid-headers: #FF69B4;
    --tip-status-color-headers-only: #FF8800;

    /* deployments a block signals for (BIP 9 version bits). Brightly filled and
       opaque like the tip-status boxes, so the dark chip text stays legible in
       either theme, but in a hue none of those statuses use. */
    --signal-color: #9B8CFF;
  }
  svg.invert, img.invert, .btn-close {
    -webkit-filter: invert(100%); /* safari 6.0 - 9.0 */
    filter: invert(100%);
  }
}

/* manual dark mode */
/* keep the rules in sync with the automatic dark mode above! */
:root.dark {
  --body-bg: #18191c;
  --surface-bg: #232427;
  --border-soft: rgba(255, 255, 255, 0.2);
  --muted-soft: rgba(255, 255, 255, 0.6);
  --block-stroke: rgba(255, 255, 255, 0.28);
  --block-top: #34353c;
  --block-side: #15161a;
  --accent: #f7931a;
  --mining-pulse: #555;
  --text-color: white;
  --block-to-block-link-color: white;
  --block-description-link-color: lightgray;
  --block-description-bg: #f8f9fa;
  --node-indicatior-stroke: black;
  --block-miner-color: rgb(172, 172, 172);

  --tip-status-color-active: #79FF00;
  --tip-status-color-invalid: #CC203B;
  --tip-status-color-valid-fork: #3CACB5;
  --tip-status-color-valid-headers: #FF69B4;
  --tip-status-color-headers-only: #FF8800;

  /* deployments a block signals for (BIP 9 version bits). Brightly filled and
     opaque like the tip-status boxes, so the dark chip text stays legible in
     either theme, but in a hue none of those statuses use. */
  --signal-color: #9B8CFF;
  svg.invert, img.invert, .btn-close {
    -webkit-filter: invert(100%); /* safari 6.0 - 9.0 */
    filter: invert(100%);
  }
}

body {
  background: var(--body-bg);
  color: var(--text-color);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

.card {
  background: var(--surface-bg);
}

h1, h2, h3, h4, h5, h6, p, label, span, hr, a,
.h1, .h2, .h3, .h4, .h5, .h6 {
  color: var(--text-color);
}

.border {
  border-color: var(--border-soft) !important;
}

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

/* let native-styled selects adopt the surface colors */
.form-select {
  background-color: var(--surface-bg);
  color: var(--text-color);
  border-color: var(--border-soft);
  border-radius: 0;
}

.text-color-dark {
  color: black;
}

.link {
  fill: none;
}

path.link.link-block-block {
  stroke: var(--block-to-block-link-color);
  stroke-width: 2px;
  stroke-linejoin: round;
}

path.link.link-block-description {
  stroke: var(--block-description-link-color);
  stroke-width: 1px;
}

text.text-blocks-not-shown {
 fill: var(--text-color);
 text-anchor: middle;
 font-size: 12px;
}

line.countdown-line {
  stroke: var(--text-color);
  stroke-width: 1px;
  stroke-dasharray: 8 6;
}

text.countdown-label {
  fill: var(--text-color);
  font-size: 14px;
}

.countdown-display {
  position: absolute;
  top: 1rem;
  left: 1rem;
  font-size: 1.75rem;
  color: var(--text-color);
  pointer-events: none;
  z-index: 10;
}

rect.block-background {
 fill: var(--surface-bg);
}

/* 3D cube faces behind the front square (fill only; edges drawn once by .block-edges) */
polygon.block-face-top {
 fill: var(--block-top);
}

polygon.block-face-side {
 fill: var(--block-side);
}

/* the cube's top/side/back edges, drawn once so shared edges don't double up */
path.block-edges {
 fill: none;
 stroke: var(--block-stroke);
 stroke-width: 1px;
 stroke-linejoin: round;
}

text.block-text {
 fill: var(--text-color);
 text-anchor: middle;
 font-size: 11px;
}

/* "being mined" blocks (from the stratum jobs feed): a solid block whose whole cube
   (front + both 3D faces + edges) and its incoming link pulse in unison toward a
   neutral grey to read as "in progress". Colours (not opacity) are animated so the
   block stays opaque and the dashed link never shows through it. All parts share the
   same duration so the pulse looks like one motion. */
.block.being-mined rect.block-background {
 animation: mining-pulse-front 2.4s ease-in-out infinite;
}

.block-back.being-mined polygon.block-face-top {
 animation: mining-pulse-top 2.4s ease-in-out infinite;
}

.block-back.being-mined polygon.block-face-side {
 animation: mining-pulse-side 2.4s ease-in-out infinite;
}

.block-back.being-mined path.block-edges {
 animation: mining-pulse-stroke 2.4s ease-in-out infinite;
}

path.link.link-block-block.being-mined {
 stroke-opacity: 0.6;
 animation: mining-pulse-link 2.4s ease-in-out infinite;
}

@keyframes mining-pulse-front {
 0%, 100% { fill: var(--surface-bg); }
 50% { fill: color-mix(in srgb, var(--mining-pulse) 32%, var(--surface-bg)); }
}

@keyframes mining-pulse-top {
 0%, 100% { fill: var(--block-top); }
 50% { fill: color-mix(in srgb, var(--mining-pulse) 45%, var(--block-top)); }
}

@keyframes mining-pulse-side {
 0%, 100% { fill: var(--block-side); }
 50% { fill: color-mix(in srgb, var(--mining-pulse) 45%, var(--block-side)); }
}

@keyframes mining-pulse-stroke {
 0%, 100% { stroke: var(--block-stroke); }
 50% { stroke: var(--mining-pulse); }
}

@keyframes mining-pulse-link {
 0%, 100% { stroke: var(--block-to-block-link-color); }
 50% { stroke: var(--mining-pulse); }
}

/* status tag above a block that comes from the stratum feed. Unlike the tip-status
   boxes - whose fill is a bright per-status colour, so they can carry dark text in
   either theme - this one has no colour of its own, so it takes the same surface and
   muted label colours as the pool names it sits among. */
.mining-tag-bg {
 fill: var(--surface-bg);
 stroke: var(--block-stroke);
 stroke-width: 0.5px;
}

.mining-tag-text {
 fill: var(--block-miner-color);
 font-size: 8px;
}

/* force-positioned pool-name labels around a being-mined block */
.mining-pool-link {
 stroke: var(--block-stroke);
 stroke-width: 1px;
 opacity: 0.5;
}

.mining-pool-bg {
 fill: var(--surface-bg);
 stroke: var(--block-stroke);
 stroke-width: 0.5px;
}

.mining-pool-text {
 fill: var(--block-miner-color);
 font-size: 8px;
}

text.block-miner {
 fill: var(--block-miner-color);
 text-anchor: middle;
 font-size: 10px;
}

rect.block-miner-bg {
 fill: var(--surface-bg);
}

.block {
  cursor: pointer;
}

.block-description {
  cursor: grab;
}

.block-description-background {
  fill: var(--block-description-bg);
  stroke: var(--text-color);
}

/* tip info: stack of colored "Nx status" boxes next to a tip block */
.tip-info-bg {
 stroke: var(--block-stroke);
 stroke-width: 0px;
}

.tip-info-text {
 fill: #1a1a1a;
 font-size: 8px;
 cursor: default;
}

/* chip in the corner of a block face, marking a deployment the block signals for */
.signal-chip-bg {
 fill: var(--signal-color);
}

.signal-chip-text {
 fill: #1a1a1a;
 font-size: 7px;
 cursor: default;
}

.legend-item {
  color: black;
  padding: 1px 6px;
}

.tip-status-color-background-active { background-color: var(--tip-status-color-active); }
.tip-status-color-background-invalid { background-color: var(--tip-status-color-invalid); color: }
.tip-status-color-background-valid-fork { background-color: var(--tip-status-color-valid-fork); }
.tip-status-color-background-valid-headers { background-color: var(--tip-status-color-valid-headers); }
.tip-status-color-background-headers-only { background-color: var(--tip-status-color-headers-only); }


.tip-status-color-fill-active { fill: var(--tip-status-color-active); color: var(--tip-status-color-active); }
.tip-status-color-fill-invalid { fill: var(--tip-status-color-invalid); color: var(--tip-status-color-invalid); }
.tip-status-color-fill-valid-fork { fill: var(--tip-status-color-valid-fork); color: var(--tip-status-color-valid-fork); }
.tip-status-color-fill-valid-headers { fill: var(--tip-status-color-valid-headers); color: var(--tip-status-color-valid-headers); }
.tip-status-color-fill-headers-only { fill: var(--tip-status-color-headers-only); color: var(--tip-status-color-headers-only); }

#connection-status {
  animation: pulse infinite 5s;
  color: gray;
  vertical-align: text-bottom;
}

@-webkit-keyframes pulse {
  0% {
    opacity: 1;
  }
  40% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
  60% {
    opacity: 1;
  }
  100% {
    opacity: 1;
  }
}

/* ---------------------------------------------------------------------------
   Modern layout
   --------------------------------------------------------------------------- */

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

/* compact, sticky top bar */
.app-header {
  position: sticky;
  top: 0;
  z-index: 20;
  margin-bottom: 1rem;
  background: color-mix(in srgb, var(--body-bg) 88%, transparent);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border-soft);
}

.app-header .navbar-brand span {
  font-size: 1.25rem;
}

/* toolbar above the visualization: title/description on the left, controls right */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
  margin-bottom: 0.75rem;
}

.toolbar-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
}

/* small RSS feed pills */
.feed-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.feed-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.15rem 0.6rem;
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--muted-soft);
  border: 1px solid var(--border-soft);
  text-decoration: none;
  transition: border-color 0.15s ease, color 0.15s ease;
}

/* square corners everywhere to match the block theme */
.badge {
  border-radius: 0 !important;
}

.feed-pill:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.feed-pill img {
  opacity: 0.7;
}

/* the visualization is the centerpiece: keep it large */
.viz {
  display: block;
  width: 100%;
  height: 78vh;
  background: var(--surface-bg);
}

/* wraps the SVG so the recenter button can float over its corner */
.viz-wrap {
  position: relative;
}

/* shown over the SVG until the first batch of blocks has been drawn. Uses the
   same cube geometry as a real block (see below) so the loading state reads as
   "a block, but not resolved yet" rather than an unrelated spinner. */
.viz-loading {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  pointer-events: none;
  background: var(--surface-bg);
  opacity: 1;
  transition: opacity 0.25s ease;
}

.viz-loading-hidden {
  opacity: 0;
}

.viz-loading-text {
  font-size: 0.85rem;
  color: var(--muted-soft);
}

/* window onto the endless scrolling chain below: fixed size, and fades its own
   left/right edges so blocks entering/leaving read as a smooth flow rather than
   a hard pop-in/pop-out at the boundary. */
.viz-loading-strip {
  width: 240px;
  height: 90px;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent 0%, black 15%, black 85%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0%, black 15%, black 85%, transparent 100%);
}

/* one period (80px = one block spacing) of an otherwise-identical, infinitely
   repeating chain: sliding it exactly one period to the left loops seamlessly,
   since what scrolls off on the left is indistinguishable from what was already
   waiting off-screen on the right. */
.viz-loading-track {
  animation: viz-loading-scroll 2.4s linear infinite;
}

@keyframes viz-loading-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-80px); }
}

/* a subtler version of the being-mined pulse (see above) for the loading
   indicator's blocks: same idea, but toned down since it plays continuously
   for as long as loading takes, rather than for one real, momentary event. */
.viz-loading-pulse.block rect.block-background {
  animation: viz-loading-pulse-front 2.4s ease-in-out infinite;
}

.viz-loading-pulse.block-back polygon.block-face-top {
  animation: viz-loading-pulse-top 2.4s ease-in-out infinite;
}

.viz-loading-pulse.block-back polygon.block-face-side {
  animation: viz-loading-pulse-side 2.4s ease-in-out infinite;
}

.viz-loading-pulse.block-back path.block-edges {
  animation: viz-loading-pulse-stroke 2.4s ease-in-out infinite;
}

path.link.link-block-block.viz-loading-pulse {
  animation: viz-loading-pulse-link 2.4s ease-in-out infinite;
}

@keyframes viz-loading-pulse-front {
  0%, 100% { fill: var(--surface-bg); }
  50% { fill: color-mix(in srgb, var(--mining-pulse) 14%, var(--surface-bg)); }
}

@keyframes viz-loading-pulse-top {
  0%, 100% { fill: var(--block-top); }
  50% { fill: color-mix(in srgb, var(--mining-pulse) 20%, var(--block-top)); }
}

@keyframes viz-loading-pulse-side {
  0%, 100% { fill: var(--block-side); }
  50% { fill: color-mix(in srgb, var(--mining-pulse) 20%, var(--block-side)); }
}

@keyframes viz-loading-pulse-stroke {
  0%, 100% { stroke: var(--block-stroke); }
  50% { stroke: color-mix(in srgb, var(--mining-pulse) 40%, var(--block-stroke)); }
}

@keyframes viz-loading-pulse-link {
  0%, 100% { stroke: var(--block-to-block-link-color); }
  50% { stroke: color-mix(in srgb, var(--mining-pulse) 40%, var(--block-to-block-link-color)); }
}

.viz-recenter {
  position: absolute;
  top: 10px;
  right: 10px;
  padding: 4px 10px;
  font-size: 0.8rem;
  line-height: 1.2;
  color: var(--accent);
  background: var(--surface-bg);
  border: 1px solid var(--block-stroke);
  cursor: pointer;
}

.viz-recenter:hover {
  border-color: var(--accent);
}

/* fields in the block info card / table that copy to the clipboard on click */
.copyable {
  cursor: pointer;
}

.copyable:hover {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

/* transient confirmation toast (clipboard copy) */
.toast-msg {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%) translateY(10px);
  padding: 8px 16px;
  font-size: 0.85rem;
  color: var(--surface-bg);
  background: var(--accent);
  border: 1px solid var(--accent);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.18s ease, transform 0.18s ease;
  z-index: 1080;
}

.toast-msg.toast-show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* horizontal scroll keeps the node table usable on narrow screens */
.node-table-wrap {
  margin-top: 0.6rem;
  overflow-x: auto;
  border: 1px solid var(--border-soft);
}

@media (max-width: 575.98px) {
  .toolbar-controls {
    width: 100%;
  }
  .viz {
    height: 70vh;
  }
}

/* node table: one node per row */
.node-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface-bg);
  font-size: 0.95rem;
}

.node-table thead th {
  position: sticky;
  top: 0;
  background: var(--surface-bg);
  text-align: left;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--muted-soft);
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--border-soft);
  white-space: nowrap;
}

.node-table td {
  padding: 0.5rem 0.75rem;
  vertical-align: middle;
  border-bottom: 1px solid var(--border-soft);
}

.node-table tbody tr:last-child td {
  border-bottom: none;
}

.node-row {
  transition: background-color 0.15s ease;
}

.node-row:hover {
  background: color-mix(in srgb, var(--accent) 7%, var(--surface-bg));
}

.nt-name {
  min-width: 12rem;
}

.node-status-dot {
  display: inline-block;
  width: 0.55rem;
  height: 0.55rem;
  margin-right: 0.4rem;
  vertical-align: middle;
}

.node-status-dot.is-up {
  background: var(--tip-status-color-active);
}

.node-status-dot.is-down {
  background: var(--tip-status-color-invalid);
}

.nt-name-text {
  font-weight: 600;
  vertical-align: middle;
}

/* marks a node imported from another fork-observer instance */
.nt-via {
  margin-left: 0.35rem;
  padding: 0.05rem 0.3rem;
  font-size: 0.75rem;
  color: var(--muted-soft);
  border: 1px solid var(--border-soft);
  vertical-align: middle;
  white-space: nowrap;
  cursor: help;
}

.nt-desc {
  color: var(--muted-soft);
  font-size: 0.82rem;
  margin-top: 0.15rem;
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}

.nt-desc.nt-desc-open {
  white-space: normal;
}

.nt-impl {
  white-space: nowrap;
}

.nt-time {
  white-space: nowrap;
}

.nt-num {
  text-align: left;
}

/* solid color chips: same height -> same height-chip color; same tip -> same
   hash-chip color. hues come from the --height-hue / --hash-hue vars on the row. */
.height-chip,
.hash-chip {
  display: inline-block;
  padding: 0.12rem 0.5rem;
  color: #1a1a1a;
  white-space: nowrap;
}

.height-chip {
  background: hsl(var(--height-hue, 0) 68% 74%);
  font-variant-numeric: tabular-nums;
}

.hash-chip {
  background: hsl(var(--hash-hue, 0) 68% 74%);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  cursor: pointer;
}

.hash-chip:hover {
  filter: brightness(0.95);
}

/* the open block-header info card in the visualization */
.block-info-card {
  border-radius: 0 !important;
  overflow: hidden;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.28);
}

.block-info-card .card-header {
  background: var(--body-bg);
  font-weight: 600;
}

/* footer */
.app-footer {
  border-top: 1px solid var(--border-soft);
}

.footer-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--text-color);
}

.footer-link:hover {
  color: var(--accent);
}

/* ---------------------------------------------------------------------------
   Activity log pages (activity.html, playback.html)
   --------------------------------------------------------------------------- */

/* an unadorned button that matches the pill/chip look of the rest of the UI */
.btn-plain {
  padding: 0.15rem 0.6rem;
  font-size: 0.85rem;
  color: var(--muted-soft);
  background: var(--surface-bg);
  border: 1px solid var(--border-soft);
  transition: border-color 0.15s ease, color 0.15s ease;
}

.btn-plain:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.btn-plain:disabled {
  opacity: 0.5;
}

/* ---------------------------------------------------------------------------
   Activity log list
   --------------------------------------------------------------------------- */

.activity-search {
  width: 16rem;
  max-width: 100%;
  background: var(--surface-bg);
  color: var(--text-color);
  border: 1px solid var(--border-soft);
  border-radius: 0;
}

.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.5rem;
  padding: 0.5rem 0.75rem;
  margin-bottom: 0.75rem;
  background: var(--surface-bg);
  border: 1px solid var(--border-soft);
}

.filter-group {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  min-width: 0;
}

.filter-label {
  font-size: 0.78rem;
  color: var(--muted-soft);
  min-width: 3rem;
}

.filter-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

/* a filter toggle. On (the default) it carries the color of what it selects,
   off it fades into the background. */
.filter-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.08rem 0.5rem;
  font-size: 0.78rem;
  color: var(--text-color);
  background: transparent;
  border: 1px solid var(--pill-color, var(--border-soft));
  border-left-width: 4px;
  transition: opacity 0.15s ease;
}

.filter-pill.is-off {
  color: var(--muted-soft);
  border-color: var(--border-soft);
  opacity: 0.55;
  text-decoration: line-through;
}

.activity-status {
  margin-bottom: 0.35rem;
}

.activity-list {
  border: 1px solid var(--border-soft);
  background: var(--surface-bg);
}

.activity-day {
  padding: 0.3rem 0.75rem;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--muted-soft);
  background: var(--body-bg);
  border-bottom: 1px solid var(--border-soft);
  position: sticky;
  top: 0;
  z-index: 1;
}

.activity-row {
  display: grid;
  grid-template-columns: 7.5rem 9.5rem 12rem minmax(0, 1fr) 4rem;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.35rem 0.75rem;
  border-bottom: 1px solid var(--border-soft);
  border-left: 4px solid var(--event-color, transparent);
  font-size: 0.9rem;
}

.activity-row:last-child {
  border-bottom: none;
}

.activity-row:hover {
  background: color-mix(in srgb, var(--accent) 6%, var(--surface-bg));
}

/* events that arrived while the page was open */
.activity-row.is-fresh {
  animation: activity-fresh 4s ease-out;
}

@keyframes activity-fresh {
  from { background: color-mix(in srgb, var(--event-color) 35%, var(--surface-bg)); }
  to { background: transparent; }
}

.ev-time {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
  white-space: nowrap;
}

.ev-clock {
  font-variant-numeric: tabular-nums;
}

.ev-ago {
  font-size: 0.75rem;
  color: var(--muted-soft);
}

.ev-badge {
  display: inline-block;
  padding: 0.08rem 0.45rem;
  font-size: 0.78rem;
  color: #1a1a1a;
  background: var(--event-color, var(--muted-soft));
  white-space: nowrap;
}

.ev-node {
  color: var(--muted-soft);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ev-detail {
  min-width: 0;
}

.ev-id {
  font-size: 0.75rem;
  color: var(--muted-soft);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.ev-num {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.ev-mono,
.ev-hash {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.82rem;
}

.ev-block {
  white-space: nowrap;
}

.ev-hash {
  margin-left: 0.25rem;
  padding: 0 0.25rem;
  color: var(--muted-soft);
  background: color-mix(in srgb, var(--muted-soft) 12%, transparent);
}

.status-chip {
  padding: 0 0.35rem;
  color: #1a1a1a;
  font-size: 0.82rem;
  white-space: nowrap;
}

.activity-empty {
  padding: 1.5rem 0.75rem;
  margin: 0;
}

.activity-more {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.75rem;
}

@media (max-width: 991.98px) {
  .activity-row {
    grid-template-columns: 7.5rem minmax(0, 1fr);
    row-gap: 0.15rem;
  }
  .ev-id {
    display: none;
  }
}

/* ---------------------------------------------------------------------------
   Activity playback
   --------------------------------------------------------------------------- */

.playback-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  padding: 0.5rem 0.75rem;
  background: var(--surface-bg);
  border: 1px solid var(--border-soft);
}

.playback-buttons {
  display: flex;
  gap: 0.25rem;
}

.playback-buttons .btn-plain {
  min-width: 2.2rem;
}

.btn-play.is-playing {
  color: var(--accent);
  border-color: var(--accent);
}

.playback-slider {
  flex: 1 1 12rem;
  accent-color: var(--accent);
}

.playback-now {
  padding: 0.4rem 0.75rem;
  margin-top: -1px;
  background: var(--surface-bg);
  border: 1px solid var(--border-soft);
  font-size: 0.9rem;
}

.pb-now-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 0.75rem;
  border-left: 4px solid var(--event-color, transparent);
  padding-left: 0.5rem;
}

.pb-now-node {
  font-weight: 600;
}

.pb-now-detail {
  flex: 1 1 20rem;
}

.pb-now-time {
  font-size: 0.8rem;
  white-space: nowrap;
}

/* The moment the replay is showing, over the tree - that is where you are
   looking while it plays, not at the event line above it. Same corner and size
   as the countdown display, which a replay never has. */
.playback-timestamp {
  position: absolute;
  top: 1rem;
  left: 1rem;
  pointer-events: none;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
  z-index: 10;
}

.pb-timestamp-time {
  display: block;
  font-size: 1.75rem;
}

.pb-timestamp-date {
  display: block;
  font-size: 0.85rem;
  color: var(--muted-soft);
}

.playback-stage {
  display: flex;
  align-items: stretch;
  gap: 0.75rem;
  margin-top: 0.75rem;
}

.playback-viz {
  flex: 1 1 auto;
  min-width: 0;
}

/* the events of the replay, next to the tree, current one marked */
.playback-events {
  /* the offset parent the current event is scrolled to the middle against */
  position: relative;
  flex: 0 0 24rem;
  max-height: 78vh;
  overflow-y: auto;
  background: var(--surface-bg);
  border: 1px solid var(--border-soft);
}

.pb-event {
  display: grid;
  grid-template-columns: 8rem minmax(0, 1fr);
  gap: 0 0.4rem;
  width: 100%;
  padding: 0.3rem 0.5rem;
  text-align: left;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border-soft);
  border-left: 4px solid var(--event-color, transparent);
  font-size: 0.82rem;
  color: var(--text-color);
}

.pb-event:hover {
  background: color-mix(in srgb, var(--accent) 8%, var(--surface-bg));
}

/* events that haven't been replayed yet are dimmed: they are the future */
.pb-event.is-future {
  opacity: 0.4;
}

.pb-event.is-current {
  background: color-mix(in srgb, var(--event-color) 22%, var(--surface-bg));
}

.pb-event-kind {
  color: var(--muted-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

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

.pb-event-detail {
  grid-column: 2;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* the panel is narrow, so a "height …hash" pair may have to break across
   lines rather than run out of the panel */
.pb-event .ev-block {
  white-space: normal;
}

.pb-event .ev-hash {
  overflow-wrap: anywhere;
}

@media (max-width: 1199.98px) {
  .playback-stage {
    flex-direction: column;
  }
  .playback-events {
    flex: 1 1 auto;
    max-height: 24rem;
  }
}

/* Blocks that data.json doesn't carry (anymore) and that the playback put back
   from the activity log. Only their hash and height are known, so they get a
   flat grey face and a muted height and open no block info box - an ordinary
   cube otherwise, which is what they stand for. */
.block.playback-reconstructed rect.block-background {
  fill: var(--block-top);
}

.block.playback-reconstructed {
  pointer-events: none;
}

.block.playback-reconstructed text.block-text {
  fill: var(--muted-soft);
}

/* sample of a reconstructed block's face, used in the playback legend */
.legend-reconstructed {
  padding: 0 0.35rem;
  color: var(--muted-soft);
  background: var(--block-top);
  border: 1px solid var(--block-stroke);
}
