/* Panopticon Rising — the wall.
 *
 * A wall is a composition you stand in front of, so the layout goal is a dense,
 * ragged, deliberately uneven field of cards. Cards differ in height and that
 * difference is the texture; nothing here tries to make them match.
 *
 * MASONRY VIA CSS COLUMNS, not grid and not a JS library.
 * The trade is real and worth stating: columns lay cards out top-to-bottom in
 * each column, so the DOM order runs down column one, then down column two.
 * A grid would read left-to-right but leaves ragged gaps at the bottom of every
 * row unless a script measures and repositions. For a wall — where the order is
 * composed rather than chronological, and no card depends on the one before it
 * — reading order carries little meaning and density carries a lot. Columns
 * also cost zero JavaScript and reflow correctly at any width for free.
 *
 * If the wall ever becomes chronological, this is the decision to revisit
 * first: down-the-column order would then be actively wrong.
 */

/* --------------------------------------------------------------------------
   Tokens. Light is the base; dark redefines only the values.

   Every colour is defined here on bare :root, so no colour has its only
   definition inside a media query. The dark block is guarded with
   :not([data-theme="light"]) so an explicit light choice still wins.
   -------------------------------------------------------------------------- */
:root {
  color-scheme: light dark;

  --bg:            #ffffff;
  --card-bg:       #f1f1ef;
  --card-bg-hover: #eaeae7;
  --ink:           #16181d;
  --ink-soft:      #5c6068;
  --ink-faint:     #8b8f97;
  --rule:          #d8d8d4;
  --chip-border:   #bcbcb6;
  --accent:        #16181d;
  --focus:         #2b6cb0;
  --overlay:       linear-gradient(to top, rgba(0, 0, 0, 0.88) 0%, rgba(0, 0, 0, 0.62) 42%, rgba(0, 0, 0, 0) 100%);
  --on-media:      #ffffff;
  --on-media-soft: #d6d6d2;

  --mono: ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  --gap: 12px;
}

:root:not([data-theme="light"]) {
  @media (prefers-color-scheme: dark) {
    --bg:            #0d0e10;
    --card-bg:       #17191d;
    --card-bg-hover: #1d2026;
    --ink:           #e8e6e1;
    --ink-soft:      #a2a6ad;
    --ink-faint:     #6e737b;
    --rule:          #2a2d33;
    --chip-border:   #3a3e45;
    --accent:        #e8e6e1;
    --focus:         #7aa7d9;
  }
}

/* Nested @media inside a selector is recent syntax. Repeat it flat so older
   browsers get dark mode too — the duplication is deliberate, not an oversight. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:            #0d0e10;
    --card-bg:       #17191d;
    --card-bg-hover: #1d2026;
    --ink:           #e8e6e1;
    --ink-soft:      #a2a6ad;
    --ink-faint:     #6e737b;
    --rule:          #2a2d33;
    --chip-border:   #3a3e45;
    --accent:        #e8e6e1;
    --focus:         #7aa7d9;
  }
}

:root[data-theme="dark"] {
  --bg:            #0d0e10;
  --card-bg:       #17191d;
  --card-bg-hover: #1d2026;
  --ink:           #e8e6e1;
  --ink-soft:      #a2a6ad;
  --ink-faint:     #6e737b;
  --rule:          #2a2d33;
  --chip-border:   #3a3e45;
  --accent:        #e8e6e1;
  --focus:         #7aa7d9;
}

/* --------------------------------------------------------------------------
   Reset and page
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

/* MUST come before any rule that sets `display` on something we later hide.
 * `[hidden] { display: none }` is a USER-AGENT rule, so any author `display`
 * declaration beats it on specificity grounds — `.card { display: flow-root }`
 * below silently defeated the filter, which sets `hidden` and expects the card
 * to disappear. This one line is what makes `el.hidden = true` mean anything. */
[hidden] { display: none !important; }

body {
  margin: 0;
  padding-block: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

:where(a):focus-visible,
:where(button):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.u-visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Visible only once focused, so a keyboard reader can jump the masthead. */
.u-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  padding: 0.75rem 1rem;
  background: var(--card-bg);
  color: var(--ink);
  text-decoration: none;
}
.u-skip:focus { left: 16px; top: 16px; }

/* --------------------------------------------------------------------------
   Masthead
   -------------------------------------------------------------------------- */
.masthead {
  padding-block: clamp(2.5rem, 7vw, 4.5rem) 1.75rem;
  padding-inline: 16px;
  max-width: 46rem;
  margin-inline: auto;
  text-align: center;
}

.masthead__title {
  margin: 0;
  font-size: clamp(1.5rem, 5vw, 2.25rem);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.masthead__tagline {
  margin: 0.9rem 0 0;
  color: var(--ink-soft);
  font-size: 0.8125rem;
  line-height: 1.6;
}

/* --------------------------------------------------------------------------
   Filter bar
   -------------------------------------------------------------------------- */
.filters {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  padding-block: 0 1.75rem;
  padding-inline: 16px;
  max-width: 62rem;
  margin-inline: auto;
}

.filters__btn {
  font: inherit;
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.7rem;
  background: transparent;
  color: var(--ink-soft);
  border: 1px solid var(--chip-border);
  border-radius: 0;
  cursor: pointer;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}

.filters__btn:hover { color: var(--ink); border-color: var(--ink-faint); }

.filters__btn.is-active {
  color: var(--bg);
  background: var(--accent);
  border-color: var(--accent);
}

/* The bar carries icon and name together, which is what makes the icon alone
   readable on a card. It is the legend as well as the control. */
.filters__glyph {
  width: 13px;
  height: 13px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex: none;
}

/* The flex gap already spaces this; the old margin would double it. */
.filters__count { opacity: 0.55; margin-left: 0; }

/* --------------------------------------------------------------------------
   The wall
   -------------------------------------------------------------------------- */
.wall {
  padding-inline: 16px;
  padding-block: 0 3rem;
  max-width: 96rem;
  margin-inline: auto;

  columns: 4;
  column-gap: var(--gap);
}

@media (max-width: 1400px) { .wall { columns: 3; } }
@media (max-width: 1000px) { .wall { columns: 2; } }
@media (max-width: 620px)  { .wall { columns: 1; } }

.wall__empty,
.wall__none {
  color: var(--ink-soft);
  text-align: center;
  padding-block: 3rem;
}

/* --------------------------------------------------------------------------
   Cards

   `break-inside: avoid` is what stops a card being sliced across a column
   boundary — the single most load-bearing line in this file. `display: flow-root`
   goes with it: without a block formatting context, some engines still split
   the card's own margins across the break.
   -------------------------------------------------------------------------- */
.card {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
  display: flow-root;

  margin-bottom: var(--gap);
  background: var(--card-bg);
  position: relative;
  transition: background 0.12s;
}

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

.card__body { padding: 0.9rem 1rem 1rem; }

/* Corner glyph, top right, out of the text flow. */
.card__source {
  position: absolute;
  top: 0.75rem;
  right: 0.85rem;
  line-height: 0;
  color: var(--ink-faint);
  z-index: 2;
}

.card__link { color: inherit; }
.card__link:hover { color: var(--ink); }

.card__glyph {
  width: 15px;
  height: 15px;
  fill: currentColor;
  display: block;
}

/* --------------------------------------------------------------------------
   Whole-card link

   One anchor, stretched over the card, instead of a link on the corner glyph
   alone — that glyph was a 15px target on a card the size of a postcard. The
   overlay carries the href and the accessible name; the glyph is decoration.

   Done in CSS rather than a click handler so the wall still works with scripts
   off, which is the same reason the cards are rendered server-side. The cost is
   that text inside a linked card cannot be selected: on a wall whose single
   action is "go to the source" that is worth paying, and every quote is in
   data/wall.json for anyone who wants to copy one.
   -------------------------------------------------------------------------- */
.card__hit {
  position: absolute;
  inset: 0;
  z-index: 3;
}

.card--linked { cursor: pointer; }

/* The ring belongs on the card. Drawn on the overlay it would trace a
   zero-height box and land in the right place only by accident. */
.card:focus-within {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.card__hit:focus-visible { outline: none; }

/* --------------------------------------------------------------------------
   Subject marks

   Stroked, where the source glyphs are filled. The two sit on the same card and
   answer different questions — what this is about, and where it came from — so
   they are drawn as different kinds of mark while staying monochrome.
   -------------------------------------------------------------------------- */
/* --ink, not --ink-faint: the mark reads white on the dark theme, which is what
   was asked for, and stays legible rather than going white-on-white when the
   light theme is in play. A literal #fff would vanish on the light card. */
.card__mark {
  display: flex;
  justify-content: center;
  line-height: 0;
  margin: 0.15rem 0 0.7rem;
  color: var(--ink);
}

/* Sized in `em` against the card's own font-size so the mark keeps its
   proportion to the text if that ever changes, and capped at 42% of the card so
   a one-column phone layout does not hand it half the screen. */
.card__glyph--tag {
  width: 2.15em;
  height: 2.15em;
  max-width: 42%;
  fill: none;
  stroke: currentColor;

  /* The stroke scales WITH the glyph, so this moves opposite to the size: it
     was 0.55 while the mark was 6.4em, and comes back up as the mark shrinks.
     At 2.15em (~30px) this lands near 1.5px on screen. Change one without the
     other and the marks go spidery or turn to blobs. */
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Over media the card's text is always light, so the mark follows it there
   rather than following the theme — --ink would go near-black on the light
   theme and disappear into the image. */
.card--media .card__mark { color: var(--on-media); }

/* The mark sits at full --ink now, so there is nothing for hover to brighten —
   the old rule dimmed it on hover, which read as the card going inactive. */

.card__lead {
  margin: 0;
  padding-right: 1.5rem;   /* clears the glyph */
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink);
}

.card__sub,
.card__role,
.card__place {
  margin: 0.1rem 0 0;
  font-size: 0.75rem;
  color: var(--ink-soft);
}

.card__place { color: var(--ink-faint); }

.card__quote {
  margin: 0.7rem 0 0;
  padding: 0;
  border: 0;
}

.card__quote p {
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--ink);
}

/* Curly quotes come from the stylesheet so the JSON holds clean prose and
   nobody has to remember to type them. */
.card__quote p::before { content: "\201C"; }
.card__quote p::after  { content: "\201D"; }

.card__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.85rem 0 0;
}

.card__tag {
  font-size: 0.625rem;
  letter-spacing: 0.03em;
  padding: 0.2rem 0.4rem;
  border: 1px solid var(--chip-border);
  color: var(--ink-soft);
}

.card__date {
  font-size: 0.625rem;
  color: var(--ink-faint);
}

/* --------------------------------------------------------------------------
   Media cards

   The image or video fills the card and the text sits over it in a gradient.
   No fixed aspect-ratio: the media keeps its own proportions, which is part of
   what makes the columns ragged.
   -------------------------------------------------------------------------- */
.card--media { background: #000; }
.card--media:hover { background: #000; }

.card__media { line-height: 0; }

.card__img,
.card__video {
  display: block;
  width: 100%;
  height: auto;
  max-width: 100%;
}

.card--media .card__body {
  position: absolute;
  inset: auto 0 0 0;
  padding: 2.5rem 1rem 1rem;
  background: var(--overlay);
  pointer-events: none;   /* let clicks reach the video controls underneath */
}

/* ...except the things that are themselves interactive. */
.card--media .card__source,
.card--media .card__link { pointer-events: auto; }

.card--media .card__lead,
.card--media .card__quote p { color: var(--on-media); }

.card--media .card__sub,
.card--media .card__role,
.card--media .card__place,
.card--media .card__date { color: var(--on-media-soft); }

.card--media .card__source { color: var(--on-media); }

.card--media .card__tag {
  color: var(--on-media);
  border-color: rgba(255, 255, 255, 0.45);
}

/* A video card's controls sit at the bottom, exactly where the overlay text is.
   Push the text up so the two do not fight. */
.card--media:has(.card__video) .card__body { padding-bottom: 2.75rem; }

/* --------------------------------------------------------------------------
   Colophon
   -------------------------------------------------------------------------- */
.colophon {
  padding-block: 2rem 3.5rem;
  padding-inline: 16px;
  text-align: center;
  border-top: 1px solid var(--rule);
  max-width: 62rem;
  margin-inline: auto;
}

.colophon p {
  margin: 0;
  font-size: 0.6875rem;
  color: var(--ink-faint);
  letter-spacing: 0.03em;
}

/* --------------------------------------------------------------------------
   The player

   A YouTube card opens here instead of sending the reader to YouTube. Built to
   sit on the wall rather than on top of it: same flat surfaces, same square
   corners, no shadow and no bounce.

   The panel is dark in BOTH themes, because what it frames is a video. A light
   chrome around a black picture is a halo, and the video is the thing being
   looked at.
   -------------------------------------------------------------------------- */
.player {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.player__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.82);
}

.player__panel {
  position: relative;
  z-index: 1;
  width: min(92vw, 62rem);
  background: #101114;
  border: 1px solid #2a2d33;
}

/* A Short is portrait. In a 16:9 frame it plays as a stripe with black either
   side, so the panel narrows to match the video instead. */
.player__panel[data-shape="short"] { width: min(92vw, 24rem); }

.player__bar {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.7rem 0.8rem;
}

.player__title {
  margin: 0;
  flex: 1;
  font-size: 0.75rem;
  line-height: 1.45;
  color: #e8e6e1;
}

.player__close {
  flex: none;
  padding: 0.2rem;
  background: transparent;
  border: 0;
  color: #8b8f97;
  cursor: pointer;
  line-height: 0;
}

.player__close:hover { color: #e8e6e1; }

.player__x {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
}

/* aspect-ratio rather than the padding-top trick: the frame is empty until the
   iframe is built, and an empty box with no ratio would collapse the panel. */
.player__frame {
  aspect-ratio: 16 / 9;
  background: #000;
}

.player__panel[data-shape="short"] .player__frame { aspect-ratio: 9 / 16; }

.player__iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.player__foot {
  margin: 0;
  padding: 0.6rem 0.8rem;
  font-size: 0.6875rem;
}

.player__out {
  color: #8b8f97;
  text-decoration: none;
  border-bottom: 1px solid #2a2d33;
}

.player__out:hover { color: #e8e6e1; }

/* The wall must not scroll behind an open player. */
.is-playing, .is-playing body { overflow: hidden; }

@media (max-width: 620px) {
  .player { padding: 8px; }
  .player__panel,
  .player__panel[data-shape="short"] { width: 100%; }
}

/* --------------------------------------------------------------------------
   Reduced motion
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}
