/* ============================================================================
   BitcoinPuzzles — base layer
   Reset, element defaults, and the shared primitives every surface builds on:
   buttons, panels, state chips, form fields, data tables, machine values.
   Depends on tokens.css. Loaded second, before any surface stylesheet.
   ========================================================================= */

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

/* `color-scheme` is the one declaration that reaches the parts of the window
   no stylesheet can: the overscroll rubber-band, the native scrollbar a
   platform insists on drawing itself, the select popup, the date picker, the
   autofill fill. Without it the browser assumes a light page and lights those
   up white on a #0b0c0e ground. */
html {
  color-scheme: dark;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* The shell. `body` is the flex column that pins the footer, because there is
   nothing else to pin it to: `min-height: 100%` used to sit here and did
   nothing at all — `html` is not sized to the viewport, so a percentage
   resolved against `auto` and every short page left the footer floating
   mid-screen with bare page ground beneath it.

   `dvh` after `vh`: on a phone the URL bar makes `100vh` taller than what is
   actually visible, which would push the footer just below the fold on exactly
   the short pages this rule exists for.

   Views yield their own `<main>`, so the shell cannot wrap the content in a
   growing element without nesting one landmark inside another. It does not need
   to: the footer's `margin-block-start: auto` absorbs the free space instead.
   See the footer block in application.css. */
body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  font-family: var(--font-ui);
  font-size: var(--fs-base);
  line-height: var(--lh-body);
  color: var(--ink-100);
  background: var(--ink-950);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-synthesis-weight: none;
}

body.menu-open,
body.modal-open {
  overflow: hidden;
}

img,
svg,
video {
  max-width: 100%;
  display: block;
}

img,
video {
  height: auto;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  color: var(--ink-050);
  font-weight: 700;
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-tight);
  text-wrap: balance;
}

h1 {
  font-size: var(--fs-3xl);
  letter-spacing: var(--tr-display);
}
h2 {
  font-size: var(--fs-2xl);
}
h3 {
  font-size: var(--fs-lg);
}
h4 {
  font-size: var(--fs-md);
}

p {
  margin: 0;
}

a {
  color: var(--ink-050);
  text-decoration: none;
  transition: color var(--dur-1) var(--ease);
}

a:hover {
  color: var(--accent);
}

/* A URL a visitor typed, promoted to a link inside their own prose. Links are
   undecorated everywhere else on the site, where surrounding layout says what
   is clickable; inside a paragraph there is no such context, so this one has to
   announce itself with an underline and carry it at both text directions. */
.autolink {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  overflow-wrap: anywhere;
}

.autolink:hover {
  color: var(--ink-050);
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

ul,
ol {
  margin: 0;
  padding: 0;
}

hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: var(--sp-6) 0;
}

::selection {
  background: var(--accent);
  color: var(--accent-ink);
}

/* ---------------------------------------------------------------------------
   Scrollbars

   Left to the platform, every scrolled well on the site ends in a bar from
   somebody else's palette: macOS swaps its overlay bar for a light channel
   the moment a mouse is plugged in, Windows draws that channel permanently,
   and on this ground both read as a bright stripe splitting the layout — the
   seam between an alliance transcript and its roster, most visibly.

   So the bar is drawn here instead, once, in the two syntaxes it takes to
   reach every engine.

   The standard properties are the ones current Chromium, Firefox and Safari
   actually use, and they are the better deal by far: when an author sets
   them, the platform keeps its own bar and its own behaviour — including the
   macOS overlay that fades out when nobody is scrolling — and takes only our
   colour. They cost nothing anywhere, so they are not gated.
   ------------------------------------------------------------------------ */

* {
  scrollbar-width: thin;
  scrollbar-color: var(--scroll-thumb) transparent;
}

/* The fallback, for engines too old to read the above: the same slim thumb,
   drawn by hand. Chromium ignores this block entirely once `scrollbar-color`
   is set, which is why the two never fight.

   Fine pointers only. Drawing the bar ourselves means drawing it always —
   there is no overlay fade to inherit — and spending 10px of a phone's
   measure on a gutter nobody can grab would cost a word a line. */
@media (pointer: fine) {
  ::-webkit-scrollbar {
    width: 10px;
    height: 10px;
  }

  /* No track and no corner: the well's own fill runs to its edge, and the
     thumb floats on it the way the macOS overlay bar does. */
  ::-webkit-scrollbar-track,
  ::-webkit-scrollbar-corner {
    background: transparent;
  }

  /* Inset by a transparent border rather than by a narrower bar — the thumb
     reads as 6px and stays a 10px target, because a hairline you have to hit
     exactly is a hairline nobody drags. */
  ::-webkit-scrollbar-thumb {
    background-color: var(--scroll-thumb);
    background-clip: padding-box;
    border: 2px solid transparent;
    border-radius: var(--r-full);
  }

  /* Reach and press. `scrollbar-color` has no state syntax, so on the branch
     above the platform handles this itself; here it is ours to draw. */
  ::-webkit-scrollbar-thumb:hover {
    background-color: var(--scroll-thumb-hi);
  }

  ::-webkit-scrollbar-thumb:active {
    background-color: var(--scroll-thumb-hard);
  }
}

/* One focus treatment site-wide. Never removed, only shaped. */
:focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: var(--r-sm);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

/* The site-wide hide utility: fifty-odd views and a dozen Stimulus controllers
   add and remove this class. It was declared at the top level of `puzzles.css`
   — a surface sheet that happened to load on every page — which meant a
   drawer, a modal and a tab all depended on the create-challenge stylesheet
   being present. It belongs here, with the other utilities. One of the two
   sanctioned uses of !important in the system: it must beat whatever display
   value the element it is thrown at already carries. */
.hidden {
  display: none !important;
}

/* ---------------------------------------------------------------------------
   Layout containers
   ------------------------------------------------------------------------ */

.container {
  width: 100%;
  max-width: var(--w-page);
  margin-inline: auto;
  padding-inline: var(--sp-4);
}

@media (min-width: 768px) {
  .container {
    padding-inline: var(--sp-6);
  }
}

.container--narrow {
  max-width: var(--w-narrow);
}

.prose {
  max-width: var(--w-prose);
  font-size: var(--fs-md);
  color: var(--ink-200);
}

.prose p + p {
  margin-top: var(--sp-4);
}

/* ---------------------------------------------------------------------------
   Machine values — addresses, prizes, ranges, counts, dates.
   Monospace here is for data alignment, not for a technical mood.
   ------------------------------------------------------------------------ */

.mono,
.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* A bitcoin address, a key range and a hash are identifiers, not prose: they
   read left-to-right in every locale. Without this the bidi algorithm
   reorders the head and tail of a truncated address under `dir="rtl"`, which
   silently shows a different string than the one stored. */
.mono,
.addr {
  direction: ltr;
  unicode-bidi: isolate;
}

.num {
  letter-spacing: -0.01em;
}

/* A bitcoin address keeps its head and its tail: the tail is what a hunter
   verifies against. Never truncate to a trailing ellipsis alone. */
.addr {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink-200);
  display: inline-flex;
  align-items: baseline;
  min-width: 0;
  max-width: 100%;
}

.addr__head {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 3ch;
}

.addr__tail {
  flex: none;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   Panels — the one surface primitive. Flat fill plus a hairline.
   Never nest a panel inside a panel; use .panel__section and a rule instead.
   ------------------------------------------------------------------------ */

.panel {
  background: var(--ink-850);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
}

.panel--sunken {
  background: var(--ink-900);
}

.panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--line);
}

.panel__title {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink-200);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
}

.panel__body {
  padding: var(--sp-5);
}

.panel__section + .panel__section {
  border-top: 1px solid var(--line);
}

/* ---------------------------------------------------------------------------
   Micro label — the small uppercase key above a value in a data pair.
   ------------------------------------------------------------------------ */

.label {
  display: block;
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink-400);
}

/* ---------------------------------------------------------------------------
   Buttons
   ------------------------------------------------------------------------ */

.btn {
  --btn-py: 0.5rem;
  --btn-px: 0.875rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--btn-py) var(--btn-px);
  font-family: var(--font-ui);
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  cursor: pointer;
  text-decoration: none;
  transition: background-color var(--dur-1) var(--ease),
    border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease),
    transform var(--dur-1) var(--ease);
}

.btn:active {
  transform: translateY(1px);
}

/* Fading an accent button to 45% puts its label at ~2.5:1. A money control
   must stay readable while it is unavailable, so the fill changes instead. */
.btn[disabled],
.btn[aria-disabled="true"] {
  cursor: not-allowed;
  background: var(--ink-750);
  border-color: var(--line);
  color: var(--ink-400);
  box-shadow: none;
  transform: none;
}

.btn--primary {
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--sh-1);
}

.btn--primary:hover {
  background: var(--accent-hi);
  color: var(--accent-ink);
}

.btn--primary:active {
  background: var(--accent-lo);
}

.btn--secondary {
  background: var(--ink-800);
  border-color: var(--line-hi);
  color: var(--ink-050);
}

.btn--secondary:hover {
  background: var(--ink-750);
  border-color: var(--line-hard);
  color: var(--ink-050);
}

.btn--ghost {
  background: transparent;
  color: var(--ink-200);
}

.btn--ghost:hover {
  background: var(--ink-800);
  color: var(--ink-050);
}

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

.btn--danger:hover {
  background: var(--danger);
  color: var(--ink-950);
}

.btn--lg {
  --btn-py: 0.75rem;
  --btn-px: 1.25rem;
  font-size: var(--fs-md);
}

.btn--sm {
  --btn-py: 0.3125rem;
  --btn-px: 0.625rem;
  font-size: var(--fs-sm);
}

.btn--block {
  width: 100%;
}

.btn .icon {
  flex: none;
}

/* ---------------------------------------------------------------------------
   State chips — always paired with a word, never colour alone.
   ------------------------------------------------------------------------ */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 0.1875rem 0.5rem;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: var(--ink-800);
  color: var(--ink-300);
}

.chip--open {
  background: var(--open-wash);
  border-color: rgba(62, 201, 192, 0.3);
  color: var(--open);
}

.chip--solved {
  background: var(--solved-wash);
  border-color: rgba(91, 196, 122, 0.3);
  color: var(--solved);
}

.chip--locked {
  background: var(--locked-wash);
  border-color: rgba(155, 140, 240, 0.3);
  color: var(--locked);
}

.chip--accent {
  background: var(--accent-wash);
  border-color: rgba(247, 147, 26, 0.3);
  color: var(--accent);
}

.chip--danger {
  background: var(--danger-wash);
  border-color: rgba(242, 85, 90, 0.3);
  color: var(--danger);
}

/* A live challenge gets one small pulsing dot — the only ambient motion
   in the system, and it stops for reduced-motion users. */
.chip__dot {
  width: 5px;
  height: 5px;
  border-radius: var(--r-full);
  background: currentColor;
  flex: none;
}

.chip--open .chip__dot {
  animation: chip-pulse 2.4s var(--ease) infinite;
}

@keyframes chip-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

/* ---------------------------------------------------------------------------
   Form fields
   ------------------------------------------------------------------------ */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.field > label,
.field__label {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink-200);
}

.input,
.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field input[type="number"],
.field input[type="url"],
.field input[type="search"],
.field textarea,
.field select {
  width: 100%;
  padding: 0.625rem var(--sp-3);
  font-size: var(--fs-base);
  color: var(--ink-050);
  background: var(--ink-800);
  border: 1px solid var(--line-hi);
  border-radius: var(--r-md);
  transition: border-color var(--dur-1) var(--ease),
    background-color var(--dur-1) var(--ease);
}

.input:hover,
.field input:hover,
.field textarea:hover,
.field select:hover {
  border-color: var(--line-hard);
}

.input:focus,
.field input:focus,
.field textarea:focus,
.field select:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--ink-850);
  box-shadow: var(--ring);
}

/* --ink-400 is 5.0:1 on the page ground but only 4.31:1 on the input fill
   these sit on, so placeholders use the next step up. */
.input::placeholder,
.field input::placeholder,
.field textarea::placeholder {
  color: var(--ink-300);
}

.input[disabled],
.field input[disabled],
.field textarea[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.field__hint {
  font-size: var(--fs-sm);
  color: var(--ink-400);
}

.field__error {
  font-size: var(--fs-sm);
  color: var(--danger);
}

.field--invalid .input,
.field--invalid input,
.field--invalid textarea {
  border-color: var(--danger);
}

/* ---------------------------------------------------------------------------
   Avatars
   ------------------------------------------------------------------------ */

.avatar {
  --avatar-size: 28px;
  width: var(--avatar-size);
  height: var(--avatar-size);
  flex: none;
  border-radius: var(--r-full);
  object-fit: cover;
  background: var(--ink-750);
  border: 1px solid var(--line-hi);
}

.avatar--placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(var(--avatar-size) * 0.42);
  font-weight: 700;
  color: var(--ink-200);
  text-transform: uppercase;
  line-height: 1;
}

.avatar--lg {
  --avatar-size: 48px;
}
.avatar--sm {
  --avatar-size: 22px;
}

/* ---------------------------------------------------------------------------
   Media bands — the identicon plate

   Every band that carries a stored picture draws the record's own mark
   underneath it. The plate is the band's floor: it is what shows while the
   request is in flight and what stays if the request never lands, so a slow
   CDN and a dead URL both resolve to the state an imageless record already
   has, and no band is ever an empty rectangle.

   Sizing is the band's business, not the plate's — a 64px crest and a 16:9
   card want very different marks — so each surface sets `--identicon-size` on
   the element it puts the plate in.
   ------------------------------------------------------------------------ */

.identicon-plate {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: var(--ink-900);
}

.identicon-plate .identicon {
  display: block;
  inline-size: var(--identicon-size, 50%);
  block-size: auto;
  opacity: 0.9;
}

/* The picture itself sits over the plate and covers it. Sized off the band
   rather than off its own intrinsic box, which the `width`/`height` attributes
   are there to reserve rather than to impose. */
.media-fill {
  position: relative;
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* ---------------------------------------------------------------------------
   Icons — drawn, one stroke weight, sized by context. No emoji, ever.
   ------------------------------------------------------------------------ */

.icon {
  width: 16px;
  height: 16px;
  flex: none;
  stroke-width: 1.5;
  vertical-align: middle;
}

.icon--sm {
  width: 14px;
  height: 14px;
}
.icon--lg {
  width: 20px;
  height: 20px;
}
.icon--xl {
  width: 28px;
  height: 28px;
}

/* A third-party brand mark — the same call shape as `.icon`, the opposite
   rules. A trademark ships from its owner's kit as a filled silhouette, so it
   takes a fill and no stroke and cannot be redrawn on this system's 1.5-stroke
   grid. One size: these appear in exactly one place. */
.brand-mark {
  width: 18px;
  height: 18px;
  flex: none;
  fill: currentColor;
}

/* ---------------------------------------------------------------------------
   Empty states — the one primitive. A surface reporting that it holds nothing
   is still a surface, so `.empty` is composed with `.panel` rather than left
   floating on the page ground: `.panel` carries no padding of its own, which
   is why `class="panel empty"` on one element is the whole composition and no
   surface needs a bespoke panel clone beside it.
   ------------------------------------------------------------------------ */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-16) var(--sp-6);
  text-align: center;
  color: var(--ink-300);
}

/* For an empty state that is already inside a small box — a scrolling
   transcript, the words rail, the hero's stake board — where the full 4rem
   band would push its own title out of the well it is reporting on. */
.empty--compact {
  padding-block: var(--sp-8);
}

.empty .icon {
  color: var(--ink-400);
}

.empty__title {
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--ink-100);
}

.empty__body {
  max-width: 44ch;
  color: var(--ink-400);
}

/* ---------------------------------------------------------------------------
   Section heading — more space above than below, per the spacing rule.
   ------------------------------------------------------------------------ */

.section {
  padding-block: var(--sp-16);
}

.section__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-6);
}

.section__title {
  font-size: var(--fs-2xl);
}

.section__link {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink-300);
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  white-space: nowrap;
}

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

/* ---------------------------------------------------------------------------
   Motion preferences
   ------------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
