:root {
  --bg: #0b0b0b;
  --fg: #f5f5f0;
  --orange: #ff8c1a;
  --orange-dim: #cc6f0f;
  --panel: #141414;
  --border: #2a2a2a;
  --error: #ff4d4d;
}

body.light {
  --bg: #ffffff;
  --fg: #1a1a1a;
  --orange: #d9660b;
  --orange-dim: #b3540a;
  --panel: #f7f2ec;
  --border: #e0d5c5;
}

* { box-sizing: border-box; }

body {
  background: var(--bg);
  color: var(--fg);
  font-family: 'Courier New', Courier, monospace;
  margin: 0;
  min-height: 100vh;
  transition: background 0.15s, color 0.15s;
}

.topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--border);
}

.brand {
  color: var(--orange);
  font-weight: bold;
  font-size: 1.2rem;
  letter-spacing: 0.05em;
}

main {
  max-width: 480px;
  margin: 0 auto;
  padding: 1.5rem 1rem 3rem;
}

.label {
  text-transform: uppercase;
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  color: var(--orange-dim);
  margin-bottom: 0.25rem;
}

.tip-panel, .mempool-panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 1.25rem;
  margin-bottom: 1.25rem;
  text-align: center;
}

.tip-amount {
  font-size: 2.2rem;
  font-weight: bold;
  color: var(--orange);
  margin-bottom: 1rem;
}

.progress-bar {
  height: 10px;
  background: var(--border);
  border-radius: 5px;
  overflow: hidden;
  margin-bottom: 1rem;
}

.progress-fill {
  height: 100%;
  background: var(--orange);
  width: 0%;
  transition: width 0.3s;
}

.stats-row {
  display: flex;
  justify-content: space-around;
  margin-bottom: 1.25rem;
  font-size: 1.1rem;
}

.btn-primary {
  background: var(--orange);
  color: #000;
  border: none;
  font-family: inherit;
  font-weight: bold;
  padding: 0.9rem 1.5rem;
  border-radius: 4px;
  cursor: pointer;
  width: 100%;
  font-size: 1rem;
  letter-spacing: 0.03em;
}

.btn-ghost {
  background: none;
  border: 1px solid var(--border);
  color: var(--fg);
  font-family: inherit;
  padding: 0.4rem 0.7rem;
  border-radius: 4px;
  cursor: pointer;
}

.btn-ghost.selected {
  background: var(--orange);
  color: #000;
  border-color: var(--orange);
  font-weight: bold;
}

.chain-tab {
  background: none;
  border: 1px solid var(--border);
  color: var(--orange-dim);
  font-family: inherit;
  padding: 0.4rem 0.9rem;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
  font-size: 0.85rem;
}

.chain-tab.active {
  background: var(--orange);
  color: #000;
  border-color: var(--orange);
  font-weight: bold;
}

.mempool-list {
  list-style: none;
  padding: 0;
  margin: 0.75rem 0 0;
  text-align: left;
}

.mempool-list li {
  display: flex;
  justify-content: space-between;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.95rem;
}

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

.mempool-empty {
  text-align: center;
  color: var(--orange-dim);
  padding: 1rem 0;
}

.footer-links {
  text-align: center;
  font-size: 0.85rem;
}

/* These two entry points are real <button>s, not `<a href="#">` links, for the
   same reason "Join the Stack" and "Submit a Meme" are: an anchor pointing at
   "#" is a navigation the page then has to cancel on every click, and if that
   preventDefault is ever missed the browser rewrites the URL and re-anchors
   the scroll position. A button that opens a dialog is what this actually is,
   so it says so. Styled to look exactly like the links they replaced. */
.footer-links a,
.footer-links .link-button {
  color: var(--orange-dim);
  text-decoration: none;
}

.footer-links .link-button {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}

.footer-links .link-button:hover,
.footer-links .link-button:focus-visible {
  text-decoration: underline;
}

dialog {
  background: var(--panel);
  color: var(--fg);
  border: 1px solid var(--orange);
  border-radius: 6px;
  padding: 1.25rem;
  width: min(90vw, 420px);
  font-family: inherit;
}

dialog::backdrop {
  background: rgba(0,0,0,0.6);
}

dialog h2 {
  color: var(--orange);
  margin-top: 0;
}

dialog label {
  display: block;
  margin-bottom: 1rem;
  font-size: 0.9rem;
}

dialog input[type="text"],
dialog input[type="number"],
dialog input[type="file"] {
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.5rem;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  font-family: inherit;
}

/* 16px is a HARD FLOOR on anything you can type into, and it is not a taste
   decision. iOS Safari zooms the entire page in when a text field smaller
   than 16px receives focus, and it does not zoom back out when the field —
   or the dialog containing it — goes away. The page is simply left rendered
   at the zoomed-in scale afterwards, which is exactly the "cancelling out
   leaves the page rendered wrong" bug reported against the two request
   forms. Those two hit it every single time because text inputs are the only
   thing in them; the join and meme forms open on a checkbox and offer file
   pickers and quick-amount chips, so it's possible to complete them without
   ever focusing a text field, which is why they seemed fine.
   Browsers' default form-control font-size is ~13.3px, which is under the
   line, so this has to be stated explicitly rather than inherited.
   The other well-known "fix" — user-scalable=no / maximum-scale=1 in the
   viewport meta — is deliberately NOT used here: it stops the zoom by taking
   pinch-zoom away from everyone, including people who need it to read. */
dialog input[type="text"],
dialog input[type="number"],
dialog input[type="email"],
dialog input[type="url"],
dialog input[type="tel"],
dialog input[type="search"],
dialog input[type="password"],
dialog textarea,
dialog select {
  font-size: 16px;
}

.privacy-note {
  font-size: 0.8rem;
  color: var(--orange-dim);
  border-left: 2px solid var(--orange);
  padding-left: 0.6rem;
}

.redact-wrap {
  margin-bottom: 1rem;
}

#redact-canvas {
  width: 100%;
  max-height: 300px;
  object-fit: contain;
  background: #000;
  border: 1px dashed var(--orange);
  border-radius: 4px;
  cursor: crosshair;
  touch-action: none;
  display: block;
}

.redact-toolbar {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.redact-toolbar .btn-ghost {
  flex: 1;
  font-size: 0.8rem;
  padding: 0.4rem;
}

.nothing-to-redact {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  margin-top: 0.6rem;
}

.redact-status {
  font-size: 0.78rem;
  color: var(--orange-dim);
  margin-top: 0.4rem;
  min-height: 1.1em;
}

.form-error {
  color: var(--error);
  font-size: 0.85rem;
  min-height: 1.2em;
  margin-bottom: 0.5rem;
}

.modal-actions {
  display: flex;
  gap: 0.75rem;
}

.modal-actions .btn-ghost { flex: 1; }
.modal-actions .btn-primary { flex: 2; }

/* ---- "Block Being Built" visualizer ---------------------------------- */
/* The frame is the block: its full area stands for 100% of the current tip,
   so an empty block genuinely looks empty and a full one is visibly full.
   Squares are absolutely positioned inside #block-viz-stage and moved with
   transforms, which keeps every animation on the compositor. */

.block-viz-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 20 / 11;
  background:
    linear-gradient(rgba(255, 140, 26, 0.05) 1px, transparent 1px) 0 0 / 100% 12.5%,
    linear-gradient(90deg, rgba(255, 140, 26, 0.05) 1px, transparent 1px) 0 0 / 12.5% 100%,
    #000;
  border: 1px solid var(--border);
  border-radius: 4px;
  overflow: hidden;
  touch-action: manipulation;
}

body.light .block-viz-frame {
  background:
    linear-gradient(rgba(217, 102, 11, 0.08) 1px, transparent 1px) 0 0 / 100% 12.5%,
    linear-gradient(90deg, rgba(217, 102, 11, 0.08) 1px, transparent 1px) 0 0 / 12.5% 100%,
    #17110c;
}

.block-viz-stage {
  position: absolute;
  inset: 0;
}

/* `display: flex` below would otherwise beat the browser's own
   `[hidden] { display: none }`, leaving this message sitting behind the
   squares forever — the same trap the meme form and the Magazine checkbox
   both hit. Re-assert it here so `.hidden = true` actually hides it. */
.block-viz-empty[hidden] {
  display: none;
}

.block-viz-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--orange-dim);
  font-size: 0.85rem;
  text-align: center;
  padding: 0 1rem;
}

/* One brick. Positioned entirely by transform so moving it never triggers
   layout — the size transition is the only thing that does, and it only ever
   runs when the packing actually changes. */
.viz-block {
  position: absolute;
  top: 0;
  left: 0;
  padding: 0;
  border: 0;
  border-radius: 1px;
  cursor: pointer;
  will-change: transform;
  transform-origin: top left;
  /* A single dark inset hairline is the mortar line between neighbouring
     tiles — flat, like a block explorer's grid, not the raised bevel these
     used to have. The old inset white highlight read as a button. */
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.55);
  /* 1050ms must stay in sync with VIZ_FALL_MS in app.js — that constant is
     what times the landing flash, and if the two drift the flash fires while
     the block is still in the air. The curve starts slow and accelerates,
     which is what falling looks like; the old ease-out curve (fast start,
     slow coast) read as a slide no matter how long it ran. */
  transition:
    transform 1050ms cubic-bezier(0.45, 0.03, 0.4, 1),
    width 1050ms cubic-bezier(0.45, 0.03, 0.4, 1),
    height 1050ms cubic-bezier(0.45, 0.03, 0.4, 1),
    opacity 500ms ease,
    filter 160ms ease;
}

.viz-block:hover,
.viz-block:focus-visible {
  filter: brightness(1.35);
  outline: 1px solid var(--fg);
  outline-offset: 1px;
  z-index: 5;
}

/* Pending stacks pulse their opacity rather than their colour. The hue now
   carries the stack's SIZE (the orange ramp), so tinting pending tiles a
   different shade would collide with that reading — two meanings on one
   channel. Fading is a free channel. */
.viz-block.pending {
  animation: viz-breathe 2.6s ease-in-out infinite;
}

@keyframes viz-breathe {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 0.95; }
}

/* The flash as a brick lands in the block. */
.viz-block.landing {
  animation: viz-land 600ms ease-out;
}

@keyframes viz-land {
  0%   { box-shadow: 0 0 0 0 rgba(255, 200, 120, 0.9), inset 0 0 14px rgba(255, 255, 255, 0.9); }
  100% { box-shadow: 0 0 0 10px rgba(255, 200, 120, 0), inset 0 1px 0 rgba(255, 255, 255, 0.35); }
}

/* A CSS animation outranks inline styles, so the pending pulse would keep
   re-asserting opacity over the fade-out. Stop it explicitly on the way out. */
.viz-block.leaving {
  animation: none;
  opacity: 0;
  filter: brightness(2);
  pointer-events: none;
}

/* Anyone who has asked their OS for less motion gets the layout without the
   flying, breathing and flashing — the visualizer still works, it just
   doesn't move. */
@media (prefers-reduced-motion: reduce) {
  .viz-block {
    transition: opacity 200ms ease, filter 160ms ease;
  }
  .viz-block.pending,
  .viz-block.landing {
    animation: none;
  }
}

.block-viz-caption {
  font-size: 0.75rem;
  text-align: center;
  margin-top: 0.45rem;
  color: var(--orange-dim);
}

.block-viz-legend {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.9rem;
  font-size: 0.72rem;
  margin-top: 0.35rem;
  color: var(--orange-dim);
}

/* ---- Overflow: stacks past the current tip ---- */
.block-viz-overflow-note {
  margin-top: 0.6rem;
  padding: 0.6rem;
  border: 1px dashed var(--orange-dim);
  border-radius: 4px;
  background: rgba(255, 140, 26, 0.04);
}

.block-viz-overflow-label {
  font-size: 0.78rem;
  color: var(--orange);
  margin-bottom: 0.45rem;
}

.block-viz-overflow {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
  justify-content: center;
}

.viz-overflow-block {
  width: 16px;
  height: 16px;
  border: 0;
  border-radius: 2px;
  padding: 0;
  opacity: 0.55;
  cursor: pointer;
  transition: opacity 160ms ease, transform 160ms ease;
}

.viz-overflow-block:hover,
.viz-overflow-block:focus-visible {
  opacity: 1;
  transform: scale(1.2);
}

.block-viz-overflow-sub {
  font-size: 0.7rem;
  color: var(--orange-dim);
  margin-top: 0.45rem;
  text-align: center;
}

/* ---- Tap-to-reveal detail card ---- */
.stack-detail-modal {
  border: 1px solid var(--orange);
  border-radius: 6px;
  background: var(--panel);
  color: var(--fg);
  max-width: 420px;
  width: calc(100% - 2rem);
  padding: 1.1rem;
}

.stack-detail-modal::backdrop {
  background: rgba(0, 0, 0, 0.7);
}

.stack-detail-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin-bottom: 0.2rem;
}

.stack-detail-name {
  font-weight: bold;
  word-break: break-all;
}

.stack-detail-amount {
  color: var(--orange);
  font-size: 1.3rem;
  font-weight: bold;
  white-space: nowrap;
}

.stack-detail-status {
  font-size: 0.8rem;
  color: var(--orange-dim);
  margin-bottom: 0.7rem;
}

.stack-detail-img {
  width: 100%;
  border-radius: 4px;
  border: 1px solid var(--border);
  margin-bottom: 0.5rem;
}

.stack-detail-modal .modal-actions {
  margin-top: 0.9rem;
}

/* ---- Merkle, pacing the header ---------------------------------------- */
/* A 4-frame walk cycle generated from the mascot art (see the sprite sheet
   at assets/merkle-walk.png: feet alternate lifting, with a body bob and a
   1px weight shift toward the planted foot).

   Two animations, deliberately split across two elements:
     .merkle-runner  travels the width of the track, via transform only, so
                     it stays on the compositor instead of triggering layout
                     on every frame of a permanently-running animation. A
                     percentage in translateX resolves against the element's
                     OWN width — the runner is stretched to the full track,
                     so `100% - 32px` lands him flush against the far end.
     .merkle-sprite  runs the frame-stepping and the turn-around flip.
   Splitting them matters: both effects want `transform`, and a single
   element can only be animated by one `transform` keyframe set at a time. */

.merkle-walk {
  flex: 1;
  min-width: 0;
  position: relative;
  height: 32px;
  margin: 0 0.6rem;
  overflow: hidden;
}

.merkle-runner {
  position: absolute;
  inset: 0;
  animation: merkle-stroll 16s ease-in-out infinite;
}

.merkle-sprite {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 32px;
  height: 32px;
  background-image: url('assets/merkle-walk.png');
  background-repeat: no-repeat;
  background-size: 128px 32px; /* 4 frames across, each shown at 32px */
  image-rendering: pixelated;
  animation:
    merkle-step 0.5s steps(4) infinite,
    merkle-face 16s ease-in-out infinite;
}

/* Steps through the 4 frames. jump-end means it lands on 0/-32/-64/-96 and
   never actually reaches -128, which is exactly the four frames. */
@keyframes merkle-step {
  from { background-position-x: 0; }
  to   { background-position-x: -128px; }
}

@keyframes merkle-stroll {
  0%,   3%   { transform: translateX(0); }
  47%,  53%  { transform: translateX(calc(100% - 32px)); }
  97%, 100%  { transform: translateX(0); }
}

/* Flips during the pause at each end, so he turns around rather than
   moonwalking back. The art's own orientation faces LEFT, so the mirrored
   state (scaleX(-1)) is the one that goes with the rightward leg of the
   stroll — getting this backwards is exactly what moonwalking looks like. */
@keyframes merkle-face {
  0%,  49%   { transform: scaleX(-1); } /* walking right */
  50%, 99%   { transform: scaleX(1); }  /* walking left */
  100%       { transform: scaleX(-1); }
}

@media (prefers-reduced-motion: reduce) {
  .merkle-runner,
  .merkle-sprite {
    animation: none;
  }
  .merkle-sprite { background-position-x: -32px; } /* both feet down */
}


/* ---- Block stepper ---------------------------------------------------- */
.block-viz-pager {
  display: flex;
  align-items: stretch;
  gap: 0.4rem;
  margin-top: 0.5rem;
}

.block-viz-pager[hidden] { display: none; }

.pager-arrow {
  flex: 0 0 auto;
  min-width: 2.4rem;
  background: none;
  border: 1px solid var(--border);
  color: var(--orange);
  border-radius: 4px;
  font-family: inherit;
  font-size: 1.2rem;
  line-height: 1;
  padding: 0.45rem 0.6rem;
  cursor: pointer;
}

.pager-arrow:hover,
.pager-arrow:focus-visible {
  border-color: var(--orange);
  background: rgba(255, 140, 26, 0.08);
}

.pager-arrow:active { background: rgba(255, 140, 26, 0.18); }

.pager-readout {
  flex: 1;
  min-width: 0;
  background: none;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--fg);
  font-family: inherit;
  padding: 0.35rem 0.5rem;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  align-items: center;
  justify-content: center;
}

.pager-readout:hover,
.pager-readout:focus-visible { border-color: var(--orange); }

.viz-readout-name {
  font-size: 0.85rem;
  color: var(--orange);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.viz-readout-meta {
  font-size: 0.68rem;
  color: var(--orange-dim);
}

/* The currently-stepped-to block, called out in the picture itself so the
   arrows and the visualization stay tied together. */
.viz-block.selected {
  outline: 2px solid var(--fg);
  outline-offset: -1px;
  z-index: 6;
}

/* The selected block must not keep breathing — the pending pulse animates
   `filter`, which would otherwise fight the selection and wash the colour
   out to yellow while you're stepping through. */
.viz-block.selected.pending {
  animation: none;
}

.viz-overflow-block.selected {
  opacity: 1;
  outline: 2px solid var(--fg);
  outline-offset: 1px;
}


/* The legend's colour ramp, drawn straight from ORANGE_RAMP in app.js. */
.viz-ramp {
  display: inline-block;
  width: 66px;
  height: 9px;
  border-radius: 2px;
  vertical-align: middle;
  background: linear-gradient(to right,
    #6b3403, #8a4504, #a85705, #c56d08, #df830f, #f59a1f, #ffb04a);
}
