/* ==========================================================================
   App shell — sidebar, topbar, content grid, responsive behaviour
   ========================================================================== */

.app {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  min-height: 100vh;
}

/* --------------------------------------------------------------------------
   Sidebar
   -------------------------------------------------------------------------- */

.sidebar {
  position: sticky;
  top: 0;
  z-index: var(--z-sidebar);
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  height: 100vh;
  padding: var(--sp-5) var(--sp-4);
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.045),
    rgba(255, 255, 255, 0.015)
  );
  backdrop-filter: var(--glass-filter);
  -webkit-backdrop-filter: var(--glass-filter);
  border-right: 1px solid var(--glass-border);
  /* The rail scrolls when brand + nav + donation + status exceed the
     viewport, so navigation is never cut off on a short screen. */
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Fixed regions keep their intrinsic height; only the nav absorbs slack. */
.brand,
.sidebar__footer {
  flex-shrink: 0;
}

/* The donation panel carries its own card border, so the footer's divider
   would read as a second, competing rule directly beneath it. */
.sidebar__footer {
  border-top: none;
  padding-top: 0;
}

/* --------------------------------------------------------------------------
   Brand lockup

   One mark, both themes. The uploaded artwork is a horizontal lockup and
   horizontal is the one shape a 264px rail cannot show at a legible size, so
   it is stacked here: the emblem above, the BTC-SOLO.COM wordmark and its SOLO
   MINING POOL rule beneath, both centred.

   The images are lossless rectangular crops of Design/Logo.png — see
   tools/make-logo-crops.mjs — with the Midnight pair derived from those same
   pixels by tools/make-logo-dark.mjs: neutral ink lightened, the Bitcoin
   orange untouched, the white ground made transparent so the rail's gradient
   shows through. Nothing is re-cut, re-scaled or redrawn, so the two themes
   show the identical mark and differ only in palette. A theme swaps the two
   `background-image`s and nothing else — see themes.css.

   Both are drawn with `contain`, so each keeps its own aspect ratio, and
   neither carries a filter, blend mode, mask or transform.

   The block grows to fit them. That height is absorbed by the rail, which
   already scrolls when brand + nav + donation + status exceed the viewport;
   the nav is `flex: 1 0 auto`, so the links, the support card and the Live
   indicator keep their own sizes and simply sit lower.

   The lockup is an <a> to the dashboard; `color`, `text-decoration` and
   `cursor` only undo what being an anchor would otherwise add. No hover
   treatment: the cursor is the affordance, and the global :focus-visible ring
   covers keyboard use.
   -------------------------------------------------------------------------- */

.brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Compact, as the reference sets it: the wordmark sits directly under the
     emblem rather than floating away from it. */
  gap: var(--sp-1);
  padding: var(--sp-2) var(--sp-2) var(--sp-5);
  border-bottom: 1px solid var(--glass-border);
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

/* 647 x 542 crop. `contain` fits it by height, so it renders 179 x 150 — its
   own ratio, centred, nothing stretched. */
.brand__mark {
  display: grid;
  place-items: center;
  width: 100%;
  height: 150px;
  flex-shrink: 0;
  background: url("../img/logo-emblem-dark.png") center / contain no-repeat;
}

/* The built-in Bitcoin glyph would sit on top of the emblem, which draws its
   own. `opacity` rather than `display: none` so the element keeps its box. */
.brand__mark svg {
  width: var(--icon-2xl);
  height: var(--icon-2xl);
  opacity: 0;
}

/* The wordmark crop carries BTC-SOLO, the vertical .COM and the SOLO MINING
   POOL rule as one piece, exactly as the artwork sets them.
   827 x 156 across the block's 215px content width: 215 / 5.301 = 40.6px. */
.brand__text {
  min-width: 0;
  width: 100%;
  height: 41px;
  background: url("../img/logo-wordmark-dark.png") center / contain no-repeat;
}

/* The artwork carries the wordmark and the tagline, so the app's own are what
   it replaces. They stay in the DOM — the crop has no text for a screen reader
   or a page search to find. */
.brand__name,
.brand__sub {
  opacity: 0;
}

.brand__name {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-heading);
  line-height: var(--lh-display);
}

.brand__sub {
  margin-top: 2px;
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
}

.nav {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  /* Grow into spare space, but never shrink below the links' own height —
     the donation panel must not be able to squeeze the menu. */
  flex: 1 0 auto;
}

.nav__label {
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--text-muted);
}

.nav__link {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  /* 37px rows rather than 45px: six links plus the donation panel have to
     share the rail, and the extra height read as slack, not generosity. */
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  font-weight: var(--fw-medium);
  transition:
    background-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.nav__link:hover {
  background: var(--brand-dim);
  color: var(--brand-ink);
}

.nav__link:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: -2px;
}

.nav__link[aria-current="page"] {
  background: var(--brand-dim);
  color: var(--brand-ink);
}

/* A hover rail that grows into the active rail, so the two states feel
   like one control rather than two unrelated treatments. */
.nav__link::before {
  content: "";
  position: absolute;
  left: calc(var(--sp-4) * -1 - 1px);
  top: 50%;
  width: 3px;
  height: 0;
  border-radius: 0 var(--radius-pill) var(--radius-pill) 0;
  background: var(--brand);
  transform: translateY(-50%);
  transition: height var(--dur-fast) var(--ease-out);
}

.nav__link:hover::before {
  height: 12px;
}

/* Active rail marker */
.nav__link[aria-current="page"]::before {
  height: 20px;
}

.nav__link svg {
  width: var(--icon-lg);
  height: var(--icon-lg);
}

.sidebar__footer {
  padding: var(--sp-3);
  border-top: 1px solid var(--glass-border);
}

/* --------------------------------------------------------------------------
   Main column
   -------------------------------------------------------------------------- */

.main {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: var(--z-topbar);
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  min-height: var(--topbar-h);
  padding: var(--sp-3) var(--sp-6);
  background: rgba(10, 14, 20, 0.72);
  backdrop-filter: var(--glass-filter);
  -webkit-backdrop-filter: var(--glass-filter);
  border-bottom: 1px solid var(--glass-border);
}

.topbar__titles {
  min-width: 0;
  flex: 1;
}

/* Both lines truncate rather than wrap: the topbar is a fixed-height band
   and a long subtitle must not push it taller on a narrow screen. */
.topbar__title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar__subtitle {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-shrink: 0;
}

/* Tabular figures: the clock reruns every poll and must not shift the
   controls beside it as digits change width. */
.shell-updated__time {
  font-variant-numeric: tabular-nums;
}

.content {
  flex: 1;
  width: 100%;
  max-width: var(--content-max);
  margin-inline: auto;
  padding: var(--sp-6);
}

/* --------------------------------------------------------------------------
   Grids
   -------------------------------------------------------------------------- */

.grid {
  display: grid;
  gap: var(--sp-5);
}

/* Auto-fitting stat row: never fewer than 220px per tile. */
.grid--stats {
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.grid--halves {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr));
}


/* Two-column split favouring the primary panel.
   
   Grid stretches both columns to the row height, so the two column BOXES
   already align top and bottom. What regressed is what happens *inside* the
   shorter column: a .stack is a flex container, and stretching the container
   does not stretch its children — so whichever column has less content ends
   up with dead space below its last card.
   
   The two rules below close that from both directions, so the row stays
   flush regardless of which side is naturally taller. */
.grid--split {
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
}

/* Right column of the Dashboard's hero row: Current Progress + Last Block.
   The selector is deliberately narrow — .stack is used inside cards and as
   the page container elsewhere, and neither should start growing. */
.grid--split > .stack {
  min-height: 0;
}

/* Both cards share the surplus equally, so their combined height is exactly
   the row height and there is no gap above, between or below them. */
.grid--split > .stack > .card {
  flex: 1 1 auto;
  min-height: 0;
}

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

/* Views reserve a slot for the data-source banner. When no banner is
   showing the slot is an empty div, which a flex gap still spaces around —
   leaving a 20px hole above the first row of every page. */
.view.stack > div:empty {
  display: none;
}

/* --------------------------------------------------------------------------
   Mobile navigation
   -------------------------------------------------------------------------- */

/* The drawer's close button exists only for the off-canvas layout, where it is
   the way back out. `.btn` in components.css loads later at equal specificity,
   so a bare `.sidebar__close` here loses to it and the control has been showing
   on desktop, where clicking it does nothing at all. Raising the specificity
   makes the intended rule the one that applies. */
.btn.sidebar__close {
  display: none;
}

/* `.nav-toggle` is deliberately not hidden here. It used to be, but `.btn` in
   components.css loads later at equal specificity and won, so the control has
   always rendered on desktop — where it opened a drawer that does not exist
   there and left nothing but the scrim's fade. It now has a real job at every
   width (see "Sidebar collapse" below), so it stays visible on purpose. */

.scrim {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-sidebar) - 1);
  background: rgba(4, 6, 10, 0.6);
  backdrop-filter: blur(2px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out);
}

.scrim[data-open="true"] {
  opacity: 1;
  pointer-events: auto;
}

/* --------------------------------------------------------------------------
   Sidebar collapse (docked layout only)

   The topbar toggle hides the rail and hands its width to the content. Purely
   structural — both themes get the same behaviour from these rules, and the
   default state is `expanded`, so nothing moves until the button is pressed.

   Every duration below is `--dur-base`, which variables.css already zeroes
   under `prefers-reduced-motion`, so the rail arrives instantly for users who
   ask for less motion without losing the control.
   -------------------------------------------------------------------------- */

@media (min-width: 861px) {
  /* The drawer's scrim belongs to the off-canvas layout. Nothing sets it here,
     but stating it makes the guarantee structural: collapsing can never dim
     the page. */
  .scrim {
    display: none;
  }

  .app {
    transition: grid-template-columns var(--dur-base) var(--ease-out);
  }

  .app[data-nav="collapsed"] {
    grid-template-columns: 0 minmax(0, 1fr);
  }

  /* Squeezing the track to zero would reflow the rail's contents on the way
     out — labels wrapping, the logo shrinking — so the sidebar keeps its own
     width and slides instead, while the track closes underneath it. */
  .sidebar {
    width: var(--sidebar-w);
    transition:
      transform var(--dur-base) var(--ease-out),
      visibility 0s;
  }

  /* `visibility` is held until the slide finishes: the links stay painted
     while they travel, then leave the tab order once they are off-screen. */
  .app[data-nav="collapsed"] .sidebar {
    transform: translateX(-100%);
    visibility: hidden;
    transition:
      transform var(--dur-base) var(--ease-out),
      visibility 0s linear var(--dur-base);
  }
}

/* --- Tablet ------------------------------------------------------------- */

@media (max-width: 1100px) {
  .grid--split {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- Mobile / small tablet ---------------------------------------------- */

@media (max-width: 860px) {
  .app {
    grid-template-columns: minmax(0, 1fr);
  }

  .sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    width: min(84vw, var(--sidebar-w));
    background: rgba(15, 20, 28, 0.94);
    transform: translateX(-102%);
    transition: transform var(--dur-base) var(--ease-out);
    box-shadow: var(--shadow-lg);
  }

  .sidebar[data-open="true"] {
    transform: translateX(0);
  }

  /* Sticky rather than absolute: the drawer scrolls once the donation
     panel is in it, and a close button that scrolls away strands the user.
     Matches the specificity of the base rule it is overriding. */
  .btn.sidebar__close {
    display: grid;
    position: sticky;
    top: 0;
    z-index: 1;
    align-self: flex-end;
    margin-bottom: calc(var(--sp-5) * -1);
  }

  .topbar {
    padding-inline: var(--sp-4);
  }

  .content {
    padding: var(--sp-4);
  }

  .topbar__subtitle {
    display: none;
  }
}

@media (max-width: 520px) {
  .grid--stats {
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--sp-3);
  }

  .grid,
  .stack {
    gap: var(--sp-4);
  }

  .card {
    --card-pad: var(--sp-4);
  }

  .content {
    padding: var(--sp-3);
  }

  /* The freshness timestamp is how a user confirms automatic refresh is
     working, so it stays visible even here — only the "Updated" prefix is
     dropped, leaving the clock itself. */
  #shell-updated .shell-updated__label {
    display: none;
  }

  /* `.stat__value { font-size: var(--fs-xl) }` used to sit here and never once
     applied: components.css declares `.stat__value` at the same specificity and
     loads later, so the base 32px won at every width and the figures broke
     mid-digit on every phone. A media query adds no specificity. The reduction
     now lives beside the rule it overrides, driven by the card's own width —
     see the container queries in components.css. */

  /* The block-height display is the largest type in the app; at 40px it
     overran the card on the narrowest screens. */
  .hero-block__height {
    font-size: var(--fs-2xl);
  }
}
