/*
 * justhonestgaming — page furniture.
 *
 * Tokens, reset, type scale and primitives (.shell .section .eyebrow .mark
 * .btn .card .dot .tag) all come from @thenerdybox/ui, fetched at BUILD time
 * and checksum-verified — see the Dockerfile. This file is only what is
 * specific to THIS site's pages.
 *
 * DO NOT redefine anything the library owns. No new colours, no new radii.
 * If a token needs to change it changes there, and this site picks it up by
 * bumping UI_VER in the Dockerfile.
 *
 * Semantics that are load-bearing, not taste (see @thenerdybox/ui README):
 *   - coral        = "escaping", or "stub / does not exist yet". Never decorative.
 *   - --violet     = text, borders, marks.  --violet-fill = filled surfaces
 *                    carrying a white label. NOT interchangeable; the wrong one
 *                    fails WCAG AA.
 *   - :focus-visible is never removed, for any reason.
 */

/* ---------------------------------------------------------------- a11y */

/* Visible to assistive tech, not to the eye. The library does not ship this —
   checked, it is not in base.css — so it lives here. Used to give the corner
   mark a full accessible name ("…opens in a new window") that survives the
   visible label being hidden on narrow screens. Not `display:none`, which
   would hide it from screen readers too. */
.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;
}

/* ------------------------------------------------------------------ page */

/* .frame and .mark in the library place decoration OUTSIDE their element
   (9px and 5px past the corner — that is the whole idea). At full width that
   overflows the viewport and produces a horizontal scrollbar. The library
   deliberately does not force this on consumers; this site wants it. */
body {
  overflow-x: hidden;
  min-height: 100vh;
}

/* Ambient violet wash from the top. This is the same gesture nerdybox-web
   uses, and it matters here for a second reason: the wordmark image has this
   exact glow baked into its own background, so matching it is what lets the
   image dissolve into the page instead of sitting on it as a rectangle. */
body::before {
  content: "";
  position: fixed;
  inset: 0 0 auto 0;
  height: 70vh;
  background: radial-gradient(
    ellipse 90% 100% at 50% 0%,
    var(--violet-glow),
    transparent 70%
  );
  pointer-events: none;
  z-index: 0;
}

/* The faint grid, also baked into the wordmark artwork. Reproducing it across
   the whole page is the other half of making the image edge invisible — the
   ruling continues past where the picture stops. Kept far below the text
   contrast floor; it is texture, not content. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  background-image: linear-gradient(
      to right,
      rgba(124, 92, 255, 0.045) 1px,
      transparent 1px
    ),
    linear-gradient(to bottom, rgba(124, 92, 255, 0.045) 1px, transparent 1px);
  background-size: 58px 58px;
  pointer-events: none;
  z-index: 0;
}

/* ------------------------------------------------- the way out of the box */

/*
 * TheNerdyBox mark, upper-left, on every page. Clicking it leaves for the
 * parent site in a new window.
 *
 * This is the brand's organising idea used as navigation rather than as
 * decoration: "a container, and something leaving it." The mark is a violet
 * box with a coral block escaping the corner, and this link is the escape.
 * That is also why coral is allowed here at all — the palette reserves it for
 * exactly this meaning.
 */
.out-of-box {
  position: fixed;
  top: clamp(0.85rem, 2.5vw, 1.5rem);
  left: clamp(0.85rem, 2.5vw, 1.5rem);
  z-index: 40;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.35rem 0.75rem 0.35rem 0.4rem;
  border: 1px solid transparent;
  border-radius: 999px;
  text-decoration: none;
  background: rgba(18, 18, 31, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}

.out-of-box:hover,
.out-of-box:focus-visible {
  border-color: var(--violet);
  background: rgba(18, 18, 31, 0.9);
  transform: translateY(-1px);
}

.out-of-box img {
  width: clamp(38px, 8vw, 52px);
  height: auto;
  display: block;
  flex: none;
}

/* The label is the invitation. Hidden on small screens where the mark alone
   has to carry it — the mark is the recognisable thing, the words are the
   bonus. */
.out-of-box span {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mute);
  white-space: nowrap;
  transition: color 0.2s ease;
}

.out-of-box:hover span,
.out-of-box:focus-visible span {
  color: var(--ink);
}

/* A GIF cannot be paused with CSS, so reduced-motion gets a real static frame
   rather than a promise. Both files ship; exactly one is ever displayed.
   NOTE the selector specificity: `.out-of-box img` above is (0,1,1) and would
   beat a bare `.mark-static` at (0,1,0), leaving BOTH marks visible. Scope
   these the same way or the rule silently loses. */
.out-of-box .mark-static { display: none; }

@media (prefers-reduced-motion: reduce) {
  .out-of-box .mark-animated { display: none; }
  .out-of-box .mark-static { display: block; }
  .out-of-box:hover,
  .out-of-box:focus-visible { transform: none; }
}

@media (max-width: 620px) {
  .out-of-box span { display: none; }
  .out-of-box { padding: 0.35rem; }
}

/* -------------------------------------------------------------- wordmark */

/* Top padding is smaller than it looks it should be because .sitenav above it
   already clears the fixed corner mark. */
.hero {
  position: relative;
  z-index: 1;
  padding: clamp(1.5rem, 4vw, 2.5rem) 0 clamp(0.5rem, 2vw, 1rem);
  text-align: center;
}

.wordmark {
  display: block;
  width: 100%;
  max-width: 660px;
  height: auto;
  margin: 0 auto;

  /*
   * THE SEAMLESS BIT — two mechanisms, because one was not enough.
   *
   * 1. `screen` blend. The artwork carries its own near-black background
   *    (sampled #0c0a15 / #0e0c19, within a few values of --void #0a0a12).
   *    Screen leaves the backdrop untouched wherever the source is black, so
   *    the artwork's background drops out entirely and only the bright parts —
   *    the wordmark, the mark, the coral block, the glow — survive. The page's
   *    own glow and grid then read straight through it.
   *
   *    Cheaper than a transparent PNG and better: knocking out the background
   *    would have taken the internal glow gradient with it, and that gradient
   *    is half of why the artwork looks like it belongs here.
   *
   * 2. An edge mask. Screen alone still leaves the artwork's faint internal
   *    grid brightening a hard-edged rectangle, and it does not align with the
   *    page grid. Fading the edges hides where one stops and the other starts.
   *
   * `isolation: isolate` is deliberately NOT set anywhere up the tree — that
   * would trap the blend in its own stacking context and it would compose
   * against nothing.
   */
  mix-blend-mode: screen;

  -webkit-mask-image: radial-gradient(
    ellipse 62% 74% at 50% 50%,
    #000 42%,
    rgba(0, 0, 0, 0.6) 70%,
    transparent 100%
  );
  mask-image: radial-gradient(
    ellipse 62% 74% at 50% 50%,
    #000 42%,
    rgba(0, 0, 0, 0.6) 70%,
    transparent 100%
  );
}

.hero-lede {
  margin: clamp(0.25rem, 1vw, 0.75rem) auto 0;
  max-width: 46ch;
  color: var(--mute);
  font-size: clamp(0.98rem, 1.6vw, 1.08rem);
  text-wrap: balance;
}

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

.links {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 0.7rem;
  max-width: 560px;
  margin: clamp(2rem, 5vw, 3rem) auto 0;
  padding: 0;
  list-style: none;
}

/*
 * A link row. Built on the library's surface + border + radius rather than a
 * new component: same 1px line, same --radius, same elevation model (no
 * box-shadow anywhere in this system — depth is a border, a surface step and
 * a 1px lift).
 */
.link {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 1rem 1.15rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--ink);
  text-decoration: none;
  font-size: 0.98rem;
  font-weight: 500;
  transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}

/* Hover BRIGHTENS the border. The marketing site originally went
   --line -> --line-soft here, which is darker — the border became less visible
   on hover. That was caught as a defect and fixed in the library; do not
   reintroduce it. */
.link:hover,
.link:focus-visible {
  border-color: var(--violet);
  background: var(--surface-2);
  transform: translateY(-2px);
}

.link-label { flex: 1; min-width: 0; }

/* The trailing glyph is drawn, not typed, so a screen reader never reads it
   out as punctuation and it rotates/shifts cleanly on hover. */
.link-go {
  flex: none;
  width: 0.42rem;
  height: 0.42rem;
  border-right: 2px solid var(--faint);
  border-top: 2px solid var(--faint);
  transform: rotate(45deg);
  transition: border-color 0.2s ease, transform 0.2s ease;
}

.link:hover .link-go,
.link:focus-visible .link-go {
  border-color: var(--violet);
  transform: rotate(45deg) translate(2px, -2px);
}

/* Small mono tag on the left of each row, e.g. the platform name. */
.link-kind {
  flex: none;
  width: 4.5rem;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  transition: color 0.2s ease;
}

.link:hover .link-kind,
.link:focus-visible .link-kind { color: var(--violet-soft); }

/*
 * "Does not exist yet."
 *
 * Coral is reserved in this system for the escaping block and for stubs, and
 * this is the stub case. Dashed border, no lift, no pointer — three separate
 * signals that there is nothing to press. Being honest about an empty slot is
 * better than a live-looking row that goes nowhere.
 */
.link-stub {
  border-style: dashed;
  color: var(--faint);
  cursor: default;
  background: transparent;
}

.link-stub:hover {
  border-color: var(--line);
  background: transparent;
  transform: none;
}

.link-stub:hover .link-kind { color: var(--faint); }
.link-stub .link-go { display: none; }

/* Reuses the library's .tag, which is already coral-on-coral and mono. */
.link-soon { flex: none; }

/* ------------------------------------------------------------ live badge */

/*
 * Shown on the home page's Streams row only while the channel is actually
 * live, from /api/live.json. Hidden by default in the markup, so a viewer with
 * no JS, no network, or an unconfigured poller sees a normal row rather than a
 * broken or lying one.
 */
.live-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  flex: none;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--live);
}

.live-badge[hidden] { display: none; }

/* ------------------------------------------------------ copy-to-clipboard */

/*
 * The Discord row is a <button>, not an <a>, because it performs an action
 * rather than going somewhere — and a link to nowhere would be a lie. Buttons
 * bring their own UA styling, so this resets it back onto the .link surface.
 *
 * It must be a real <button>: that is what gives keyboard activation, focus
 * order and the right role for free. A div with a click handler gives none of
 * those and would have to fake all three.
 */
.link-copy {
  width: 100%;
  font-family: inherit;
  font-size: 0.98rem;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

.link-copy-hint {
  flex: none;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  transition: color 0.2s ease;
}

.link-copy:hover .link-copy-hint,
.link-copy:focus-visible .link-copy-hint { color: var(--violet-soft); }

/*
 * Confirmation uses --violet-soft, NOT --live.
 *
 * --live means "a server is up" and nothing else — the library says so in as
 * many words: "not 'saved', not 'valid', not 'success'." A copy confirmation is
 * exactly the kind of borrowing that rule exists to stop, and green here would
 * quietly teach the wrong meaning across the brand.
 *
 * There is no --success token, deliberately (see brand-assets.md — it is a
 * known gap, not an oversight). The brand accent is the honest substitute: it
 * reads as "acknowledged" without claiming a status the system does not define.
 */
.link-copy.is-copied { border-color: var(--violet-soft); }
.link-copy.is-copied .link-copy-hint { color: var(--violet-soft); }

/* ------------------------------------------------------------------- nav */

/* Only appears once there is more than one page to move between. Sits below
   the fixed corner mark rather than beside it, so the two never collide on a
   narrow screen. */
.sitenav {
  position: relative;
  z-index: 1;
  display: flex;
  justify-content: center;
  gap: 1.75rem;
  padding: clamp(4.5rem, 11vw, 6rem) 0 0;
}

.sitenav a {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--mute);
  text-decoration: none;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid transparent;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.sitenav a:hover { color: var(--ink); }

/* The current page is marked with aria-current in the markup, so the styling
   hangs off that rather than a separate class — one source of truth, and it
   stays correct for screen readers by construction. */
.sitenav a[aria-current="page"] {
  color: var(--ink);
  border-bottom-color: var(--violet);
}

/* --------------------------------------------------------- inner pages */

/* Wider than --shell, because a 16:9 player beside a chat column needs it.
   Everything else on the site stays at the library's 1180px. */
.shell-wide { max-width: 1320px; }

.page-head {
  position: relative;
  z-index: 1;
  padding: clamp(2rem, 5vw, 3rem) 0 clamp(1.5rem, 4vw, 2.25rem);
  text-align: center;
}

.page-head h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2.1rem, 6vw, 3.2rem);
  font-weight: 800;
  line-height: 1.04;
  letter-spacing: -0.035em;
}

.page-lede {
  margin: 0.9rem auto 0;
  max-width: 52ch;
  color: var(--mute);
  font-size: clamp(0.98rem, 1.6vw, 1.06rem);
  text-wrap: balance;
}

/* --------------------------------------------------------------- streams */

.streams {
  position: relative;
  z-index: 1;
  display: grid;
  gap: clamp(1.5rem, 4vw, 2.5rem);
}

.stream {
  padding: clamp(1rem, 2.5vw, 1.4rem);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.stream-head {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-bottom: 0.9rem;
}

.stream-head h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -0.015em;
}

/*
 * Status dot. Driven ONLY by real ONLINE/OFFLINE events from Twitch's player
 * SDK — never set optimistically, never guessed. It starts `unknown`, which is
 * the library's own --faint default, and stays there if no event ever arrives.
 *
 * ON USING --live HERE: the library reserves it for "a server is up. Not
 * 'saved', not 'valid', not 'success'." A stream that is broadcasting right now
 * is the same *kind* of fact — a real-time up/down state reported by the thing
 * itself — rather than a borrowed "success" meaning, which is what that rule
 * exists to stop. Flagging it as a judgement call: if the ui repo's owners
 * read the token more narrowly than that, this is the place to change.
 */
.dot[data-state="live"] {
  --dot: var(--live);
  animation: stream-pulse 3s ease-in-out infinite;
}

.dot[data-state="offline"] { --dot: var(--faint); }
.dot[data-state="unknown"] { --dot: var(--faint); }

@keyframes stream-pulse {
  0%, 100% { box-shadow: 0 0 7px -1px var(--dot); }
  50%      { box-shadow: 0 0 14px 1px var(--dot); }
}

/* A pulsing dot is decoration; a stationary one still carries the colour and
   the adjacent word. */
@media (prefers-reduced-motion: reduce) {
  .dot[data-state="live"] { animation: none; }
}

/* The word beside the dot. Colour alone is never the only signal — that is
   unreadable for anyone who cannot distinguish it, and meaningless to a screen
   reader. role="status" on this element announces the change when it lands. */
.stream-state {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}

.stream[data-live="true"] .stream-state { color: var(--live); }

.stream-out {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  text-decoration: none;
  transition: color 0.2s ease;
}

.stream-out:hover { color: var(--violet-soft); }

/* Player and chat side by side. 340px is above Twitch's own 400x300 minimum
   for the player once the grid gives the player the larger share. */
.stream-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: 0.9rem;
}

/*
 * aspect-ratio does the work an old padding-top hack used to.
 *
 * NO min-height here, deliberately. `min-height: 300px` (added to respect
 * Twitch's suggested 400x300 minimum) combines with `aspect-ratio: 16/9` to
 * force a MINIMUM WIDTH of 533px — which blew straight through the card and
 * off the side of a 390px phone. Aspect-ratio derives one axis from the other,
 * so constraining the short axis constrains the long one too. Let the width
 * lead and the height follow.
 */
.stream-player {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--void);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.stream-chat {
  background: var(--void);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  overflow: hidden;
  min-height: 420px;
}

.stream-player iframe,
.stream-chat iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Below this the two columns stop being readable side by side — the chat
   column squeezes the player under Twitch's own minimum. Stack instead. */
@media (max-width: 940px) {
  .stream-body { grid-template-columns: minmax(0, 1fr); }
  .stream-chat { min-height: 360px; }
}

/* ------------------------------------------------------- open-in-app ask */

/*
 * Hidden by default and revealed only on narrow screens — on a desktop the
 * embed is the better experience and the question would be noise.
 *
 * `display: none` here loses to nothing, because the `hidden` attribute is
 * what the script toggles and [hidden] is handled separately below. Keeping
 * the two mechanisms distinct means dismissal works at every width.
 */
.app-prompt { display: none; }

.app-prompt[hidden] { display: none !important; }

@media (max-width: 940px) {
  .app-prompt {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem 1rem;
    margin-bottom: 0.9rem;
    padding: 0.9rem 1rem;
    background: var(--surface-2);
    border: 1px dashed var(--line);
    border-radius: var(--radius-sm);
  }

  .app-prompt p {
    flex: 1 1 14rem;
    margin: 0;
    color: var(--mute);
    font-size: 0.9rem;
    line-height: 1.4;
  }

  .app-prompt-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    flex: 0 0 auto;
  }

  /* Two buttons squeezed onto one line with the text is how you get a 28px
     tap target. Let them take the full width and stay pressable. */
  .app-prompt-actions .btn {
    flex: 1 1 auto;
    justify-content: center;
    padding: 0.7rem 1rem;
    font-size: 0.88rem;
  }
}

.stream-fallback {
  position: relative;
  z-index: 1;
  padding: 1.4rem 1.6rem;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  color: var(--mute);
}

.stream-fallback ul { margin: 1rem 0 0; padding: 0; list-style: none; }

/* --------------------------------------------------------------- the exit */

/* The same destination as the corner mark, offered again at the end of the
   page — someone who has read to the bottom is the person most likely to
   want it, and the corner badge is easy to stop seeing. */
.exit {
  position: relative;
  z-index: 1;
  max-width: 560px;
  margin: clamp(2rem, 5vw, 3rem) auto 0;
  padding: clamp(1.4rem, 4vw, 2rem);
  background: linear-gradient(150deg, rgba(124, 92, 255, 0.14), transparent 65%);
  border: 1px solid rgba(124, 92, 255, 0.3);
  border-radius: var(--radius);
  text-align: center;
}

.exit h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 3.5vw, 1.6rem);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.exit p {
  margin: 0.6rem auto 1.25rem;
  max-width: 40ch;
  color: var(--mute);
  font-size: 0.94rem;
}

/* ---------------------------------------------------------------- donate */

/*
 * Parked until Javier wants it. The markup is in index.html behind
 * `hidden` — remove that attribute to ship it, no CSS change needed.
 */
.donate {
  display: flex;
  justify-content: center;
  margin: clamp(1.5rem, 4vw, 2.25rem) auto 0;
}

.donate[hidden] { display: none; }

.donate a {
  display: inline-block;
  border-radius: 14px;
  transition: transform 0.2s ease, filter 0.2s ease;
}

.donate a:hover,
.donate a:focus-visible {
  transform: translateY(-1px);
  filter: brightness(1.08);
}

.donate img { display: block; width: 260px; height: auto; }

/* ---------------------------------------------------------------- footer */

.footer {
  position: relative;
  z-index: 1;
  max-width: 560px;
  margin: clamp(2.5rem, 6vw, 4rem) auto 0;
  padding: 1.5rem 0 2.5rem;
  border-top: 1px solid var(--line-soft);
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}

.footer a {
  color: var(--mute);
  text-decoration: none;
  transition: color 0.2s ease;
}

.footer a:hover { color: var(--ink); }

/* --------------------------------------------------------------- motion */

/* The library ships a reduced-motion block of its own; this covers what is
   added here. Transforms go, colour transitions stay — they carry state. */
@media (prefers-reduced-motion: reduce) {
  .link:hover,
  .link:focus-visible { transform: none; }
  .link:hover .link-go,
  .link:focus-visible .link-go { transform: rotate(45deg); }
  .donate a:hover,
  .donate a:focus-visible { transform: none; }
}
