/* ============================================================
   BUDDY TERMINAL — green-phosphor CRT theme
   Design notes:
     * Green-on-black, monospace. Moderate retro: soft text glow +
       a subtle static scanline overlay. NO flicker, NO blocking
       boot animation. Readability first.
     * Only two motions exist: a blinking block cursor and the
       phosphor-bar meters filling once on load. Both are optional
       eye-candy and never gate reading.
   ============================================================ */

:root {
  --phosphor:        #33ff66;   /* primary text */
  --phosphor-dim:    #1f9c43;   /* labels, secondary */
  --phosphor-faint:  #0f5a26;   /* rules, empty meter cells */
  --amber-alert:     #ffb000;   /* low-battery / alert accents */
  --bg:              #050805;   /* near-black with a green cast */
  --bg-panel:        #060d06;
  --glow: 0 0 2px rgba(51, 255, 102, 0.55), 0 0 6px rgba(51, 255, 102, 0.30);

  /* Width of the screen itself. Wide enough that a desktop isn't mostly black
     margin, capped so an ultrawide monitor still shows a centred terminal
     rather than one stretched wall-to-wall. */
  --shell-max: 1140px;
  /* Longest comfortable prose line, in characters. The panel may be wide; a
     paragraph inside it still wraps at a readable column, the way a terminal
     wraps at 80. */
  --prose-max: 76ch;
  --photo-gap: 14px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--phosphor);
  font-family: "Share Tech Mono", "VT323", "Courier New", Consolas, monospace;
  font-size: 18px;
  line-height: 1.5;
  text-shadow: var(--glow);
  /* Gentle radial vignette — the corners of a CRT are a touch darker. */
  background-image: radial-gradient(ellipse at center, rgba(20, 60, 30, 0.12) 0%, rgba(0, 0, 0, 0) 60%);
  -webkit-font-smoothing: none;
}

/* ---- Scanline overlay (fixed, non-interactive) ---- */
.crt-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  pointer-events: none;
  /* Thin dark lines every 3px = subtle scanlines. */
  background:
    repeating-linear-gradient(
      to bottom,
      rgba(0, 0, 0, 0) 0,
      rgba(0, 0, 0, 0) 2px,
      rgba(0, 0, 0, 0.22) 3px
    );
  /* A faint phosphor wash over the whole screen. */
  box-shadow: inset 0 0 120px rgba(10, 40, 20, 0.55);
  mix-blend-mode: multiply;
  opacity: 0.85;
}

/* ---- Layout shell ---- */
.crt {
  max-width: var(--shell-max);
  margin: 0 auto;
  padding: 28px 26px 40px;
  min-height: 100vh;
}

.panel {
  border: 1px solid var(--phosphor-faint);
  background: var(--bg-panel);
  padding: 14px 18px 18px;
  margin-bottom: 22px;
  position: relative;
}

.panel-title {
  color: var(--phosphor);
  letter-spacing: 0.08em;
  font-size: 1.05rem;
  border-bottom: 1px dashed var(--phosphor-faint);
  padding-bottom: 8px;
  margin-bottom: 14px;
  text-transform: uppercase;
}

.prompt { color: var(--phosphor-dim); }

/* Blinking block cursor — the ONLY looping animation, and it's tiny. */
.cursor {
  display: inline-block;
  animation: blink 1.1s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

/* Users who prefer reduced motion get a steady cursor, no blink. */
@media (prefers-reduced-motion: reduce) {
  .cursor { animation: none; }
  .meter-fill { transition: none !important; }
}

/* ======================= STATUS HEADER ======================= */
.status .unit-line {
  font-size: 1.5rem;
  margin-bottom: 4px;
  line-height: 1.2;
}
.status .unit-line .handle { color: var(--phosphor-dim); font-size: 1rem; }

.status .bio {
  color: var(--phosphor);
  opacity: 0.92;
  margin: 6px 0 16px;
  font-style: italic;
}

/* Readout rows: LABEL [meter] value, on a fixed grid so values align.
   Capped independently of the panel: the meters are drawn out of block
   characters, so on a wide screen an uncapped row would strand the value far
   off to the right of the bar it belongs to. */
.readout {
  display: grid;
  grid-template-columns: 8.5em 1fr auto;
  gap: 6px 12px;
  align-items: center;
  max-width: 640px;
}
.readout .label { color: var(--phosphor-dim); text-transform: uppercase; letter-spacing: 0.05em; }
.readout .value { color: var(--phosphor); text-align: right; white-space: nowrap; }

.meter {
  font-variant-ligatures: none;
  letter-spacing: 1px;
  color: var(--phosphor);
  white-space: nowrap;
  overflow: hidden;
}
.meter .empty { color: var(--phosphor-faint); }

.readout .value.alert,
.meter.alert { color: var(--amber-alert); text-shadow: 0 0 3px rgba(255,176,0,0.6); }

/* Visitor counter: a fixed-width odometer readout. Tabular figures keep the
   digits from shifting as the number climbs, and the dashes of an unavailable
   count occupy exactly the same width as a real one — the layout never moves,
   and a missing count is visibly missing rather than quietly absent. */
.readout .value.visitors {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: 0.14em;
  color: var(--phosphor-dim);          /* dim while unknown */
}
/* Brightened only once a REAL number has landed. */
.readout .value.visitors.live {
  color: var(--phosphor);
  text-shadow: 0 0 3px rgba(51, 255, 102, 0.85), 0 0 9px rgba(51, 255, 102, 0.45);
}

.mode-badge {
  border: 1px solid var(--phosphor-dim);
  padding: 0 8px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.mode-badge.follow { color: var(--amber-alert); border-color: var(--amber-alert); }
/* A mode nobody has confirmed lately. Dimmed to the faint phosphor and stripped of
   the follow accent, so a stale FOLLOW can never read as "he's moving right now". */
.mode-badge.stale {
  color: var(--phosphor-faint);
  border-color: var(--phosphor-faint);
  border-style: dashed;
  text-shadow: none;
}
.readout .last-known {
  color: var(--phosphor-faint);
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-shadow: none;
}

/* Uptime readout. Same tabular treatment as the visitor counter so the digits don't
   jitter as the clock ticks, and the dashes of an unknown uptime occupy the same
   width as a real one — a missing value is visibly missing, never quietly absent. */
.readout .value.uptime {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: 0.08em;
  color: var(--phosphor-dim);          /* dim while unknown */
}
.readout .value.uptime.live {
  color: var(--phosphor);
  text-shadow: 0 0 3px rgba(51, 255, 102, 0.85), 0 0 9px rgba(51, 255, 102, 0.45);
}
/* Stale: amber, the same accent the rest of the panel uses for "pay attention".
   It is a timestamp, not a running clock, and it should not look like one. */
.readout .value.uptime.offline {
  color: var(--amber-alert);
  text-shadow: 0 0 3px rgba(255, 176, 0, 0.5);
}

/* The pulse next to the unit name: the one bit of the page that says "now".
   Green + breathing while the heartbeat is fresh, flat amber once it isn't,
   hollow when the file can't tell us. */
.live-dot {
  display: inline-block;
  width: 0.58em;
  height: 0.58em;
  margin-left: 0.35em;
  border-radius: 50%;
  vertical-align: 0.08em;
  background: transparent;
  border: 1px solid var(--phosphor-faint);
}
.live-dot.on {
  background: var(--phosphor);
  border-color: var(--phosphor);
  box-shadow: 0 0 4px rgba(51, 255, 102, 0.9), 0 0 10px rgba(51, 255, 102, 0.5);
  animation: pulse 2s ease-in-out infinite;
}
.live-dot.off {
  background: var(--amber-alert);
  border-color: var(--amber-alert);
  box-shadow: 0 0 4px rgba(255, 176, 0, 0.6);
}
.live-dot.unknown { background: transparent; border-color: var(--phosphor-faint); }

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}
/* The CRT theme already respects this for the meters; the heartbeat dot is the
   most motion on the page, so it must honour it too. */
@media (prefers-reduced-motion: reduce) {
  .live-dot.on { animation: none; }
}

/* ======================= VISUAL ID =======================
   The fixed unit gallery. It borrows the whole .photo-strip layout from the feed
   (those rules are global, not scoped to .log-entry), so the columns, centring
   and breakpoints here are the same code the log entries use — nothing about the
   grid is duplicated below. */
.identity[hidden] { display: none; }   /* beats .panel's own display, always */

.identity-intro {
  color: var(--phosphor-dim);
  font-size: 0.92rem;
  margin: 10px 0 2px;
  max-width: var(--prose-max);
}
.identity-intro::before { content: "// "; opacity: 0.75; }

/* The strip's top margin already spaces it from the intro; drop the extra so the
   panel doesn't sit taller than the status header above it. */
.identity-body .photo-strip { margin-top: 10px; }

/* Shown on a log entry whose photographs are all pinned in the gallery above. The
   entry keeps its data and its words; only the duplicate images are left undrawn,
   and this says so instead of letting them look like they failed to load. */
.photo-elsewhere {
  color: var(--phosphor-faint);
  font-size: 0.85rem;
  margin: 12px 0 2px;
  letter-spacing: 0.03em;
}
.photo-elsewhere::before { content: "// "; opacity: 0.75; }

/* ======================= LIDAR SCOPE ======================= */
.scope[hidden] { display: none; }

.scope-body { display: flex; flex-wrap: wrap; gap: 20px; align-items: flex-start; }

/* The scope holds a square aspect and shrinks on narrow screens rather than
   forcing the panel to scroll sideways. */
.scope-wrap { flex: 0 0 auto; width: min(340px, 100%); }
.scope-svg { display: block; width: 100%; height: auto; }

.scope-ring   { fill: none; stroke: var(--phosphor-faint); stroke-width: 1; }
.scope-spoke  { stroke: var(--phosphor-faint); stroke-width: 1; opacity: 0.55; }
.scope-ringlbl,
.scope-brg {
  fill: var(--phosphor-dim);
  font-family: inherit;
  font-size: 11px;
  text-anchor: middle;
}
.scope-ringlbl { text-anchor: start; opacity: 0.8; }

/* One measurement = one dot. There is deliberately no polygon or outline joining
   them: a line between two returns would draw a wall that nothing measured. */
.scope-blip {
  fill: var(--phosphor);
  filter: drop-shadow(0 0 2px rgba(51, 255, 102, 0.85));
}
.scope-origin { fill: var(--amber-alert); }

.scope-meta { flex: 1 1 260px; min-width: 0; }
.scope-fact {
  display: flex;
  gap: 10px;
  margin-bottom: 6px;
  font-size: 0.92rem;
  line-height: 1.45;
}
.scope-fact .k {
  flex: 0 0 8.5em;
  color: var(--phosphor-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.scope-fact .v { flex: 1 1 auto; min-width: 0; color: var(--phosphor); }
.scope-note {
  color: var(--phosphor-faint);
  font-size: 0.82rem;
  margin: 8px 0 12px;
  max-width: var(--prose-max);
}
.scope-note::before { content: "// "; opacity: 0.75; }

/* NO SCAN: amber, wordy, and unmistakably not a picture of an empty room. */
.scope-none { padding: 6px 0 2px; }
.scope-none-tag {
  display: inline-block;
  border: 1px dashed var(--amber-alert);
  color: var(--amber-alert);
  padding: 2px 10px;
  letter-spacing: 0.14em;
  margin-bottom: 8px;
}
.scope-none-why {
  color: var(--phosphor-dim);
  font-size: 0.9rem;
  max-width: var(--prose-max);
}

@media (max-width: 560px) {
  .scope-body { gap: 14px; }
  .scope-fact { display: block; }
  .scope-fact .k { display: block; margin-bottom: 1px; }
}

/* ======================= LOG FEED ======================= */
.log-entry {
  border-left: 2px solid var(--phosphor-faint);
  padding: 4px 0 14px 14px;
  margin-bottom: 20px;
}
.log-entry .meta {
  color: var(--phosphor-dim);
  font-size: 0.9rem;
  letter-spacing: 0.04em;
  margin-bottom: 4px;
}
.log-entry .title {
  color: var(--phosphor);
  font-size: 1.25rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 2px 0 8px;
}
/* Prose keeps a readable measure even though the panel around it is wide.
   Photos below are deliberately NOT capped — they use the full panel. */
.log-entry .body { max-width: var(--prose-max); }
.log-entry .body p { margin: 0 0 10px; }
.log-entry .body p:last-child { margin-bottom: 0; }

/* ======================= ATTACHED PHOTOS =======================
   Real photographs, framed like something pulled up on the terminal:
   a phosphor border, corner brackets, and the same scanlines as the
   screen laid over the image. The photo itself is only lightly tinted
   so it still reads as a real photo — hover/focus clears the tint
   entirely, and tapping opens the untouched original. */
/* The strip is a wrapping flex row rather than a grid, for one reason: a grid
   left-aligns a partial last row, which is what left 7 photos looking like
   3+3+1. Flex centres it. Because a FULL row sums to exactly 100% wide,
   justify-content can only ever move the final short row — complete rows stay
   edge-to-edge and look identical to the old grid. */
.photo-strip {
  --photo-cols: 3;                 /* replaced per post by the [data-cols] rules */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: var(--photo-gap);
  margin: 14px 0 4px;
}

/* One photo = one column. The 0.5px shaved off each basis absorbs sub-pixel
   rounding, so a row that should hold N photos can never drop the Nth onto a
   line of its own. Half a pixel across a whole row is not visible. */
.photo {
  margin: 0;
  min-width: 0;
  flex: 0 0 calc(
    (100% - (var(--photo-cols) - 1) * var(--photo-gap)) / var(--photo-cols) - 0.5px
  );
}

/* Column count per post, chosen in js/app.js so odd counts land in balanced
   rows: 7 -> 4+3 centred, 5 -> 3+2 centred, 4 -> 2+2, 6 -> 3+3. */
.photo-strip[data-cols="1"] { --photo-cols: 1; }
.photo-strip[data-cols="2"] { --photo-cols: 2; }
.photo-strip[data-cols="3"] { --photo-cols: 3; }
.photo-strip[data-cols="4"] { --photo-cols: 4; }

/* A lone photo shouldn't stretch across the whole panel — but it should still
   shrink below 420px on a narrow screen instead of overflowing. */
.photo-strip.single .photo { flex: 0 1 100%; max-width: 420px; }

.photo-frame {
  position: relative;
  display: block;
  border: 1px solid var(--phosphor-faint);
  background: #000;
  padding: 5px;
  overflow: hidden;
  line-height: 0;                 /* no descender gap under the img */
}
/* Corner brackets — drawn with two gradients, no extra markup. */
.photo-frame::before,
.photo-frame::after {
  content: "";
  position: absolute;
  width: 12px; height: 12px;
  border-color: var(--phosphor-dim);
  border-style: solid;
  pointer-events: none;
}
.photo-frame::before { top: 3px;    left: 3px;  border-width: 1px 0 0 1px; }
.photo-frame::after  { bottom: 3px; right: 3px; border-width: 0 1px 1px 0; }

.photo-frame img {
  display: block;
  width: 100%;
  height: auto;
  /* Mild green cast so photos sit in the phosphor world; NOT a heavy filter —
     the picture must stay honest and legible. */
  filter: saturate(0.72) contrast(1.05) sepia(0.18) hue-rotate(58deg);
  transition: filter 160ms linear;
}
.photo-frame:hover img,
.photo-frame:focus-visible img { filter: none; }

/* Scanlines over the image, matching the screen overlay's rhythm. */
.photo-scan {
  position: absolute;
  inset: 5px;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0) 0,
    rgba(0, 0, 0, 0) 2px,
    rgba(0, 0, 0, 0.28) 3px
  );
  box-shadow: inset 0 0 26px rgba(10, 40, 20, 0.75);
}

.photo-tag {
  position: absolute;
  right: 8px; bottom: 8px;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--phosphor);
  background: rgba(0, 0, 0, 0.62);
  border: 1px solid var(--phosphor-faint);
  padding: 0 5px;
  line-height: 1.5;
}

.photo figcaption {
  color: var(--phosphor-dim);
  font-size: 0.86rem;
  line-height: 1.35;
  margin-top: 6px;
  padding-left: 2px;
}
.photo figcaption::before { content: "// "; opacity: 0.75; }

/* Honour reduced-motion: no tint transition to animate. */
@media (prefers-reduced-motion: reduce) {
  .photo-frame img { transition: none; }
}

/* ======================= TRANSMISSIONS (comments) =======================
   Visitor comments, backed by GitHub Discussions via giscus. The comments
   themselves render inside a cross-origin IFRAME, so nothing in this file can
   reach them — their styling lives in css/giscus.css, which giscus loads. What
   these rules own is the FRAME: the panel, the header, the intro, and the two
   honest failure states.

   The panel is deliberately the same .panel as the log feed above it, so the
   comments read as another section of the same terminal rather than a widget
   parked underneath one. */
/* Buddy's greeting, sitting directly above the factual blurb. Full-brightness
   phosphor and, deliberately, NO "// " prefix: that prefix marks machine
   narration, and this line is the robot himself talking. The visual split is an
   honesty split — his voice on top, the system's statement of fact underneath —
   which is also why the greeting lives here as page copy rather than being
   posted into Discussions, where GitHub would sign it with a human's name. */
.transmissions-greeting {
  color: var(--phosphor);
  font-size: 1rem;
  margin: 0 0 8px;
  max-width: var(--prose-max);
}

.transmissions-intro {
  color: var(--phosphor-dim);
  font-size: 0.92rem;
  margin: 0 0 14px;
  max-width: var(--prose-max);
}
.transmissions-intro::before { content: "// "; opacity: 0.75; }

/* The iframe giscus injects. Transparent background so the panel shows through
   (the theme's canvas is transparent), and no border of its own — the panel is
   already a frame, and a second one would double up. */
.transmissions-body .giscus,
.transmissions-body .giscus-frame {
  width: 100%;
  border: none;
  color-scheme: normal;   /* stops a white flash before the theme lands */
}

/* ---- Honest failure ----
   Same treatment as the scope's NO SCAN: an amber dashed tag and a sentence
   saying what is actually wrong. A comment section that fails must never be an
   empty bordered box, which reads as "nobody has ever written anything". */
.tx-none { padding: 2px 0; }
.tx-none-tag {
  display: inline-block;
  border: 1px dashed var(--amber-alert);
  color: var(--amber-alert);
  padding: 2px 10px;
  letter-spacing: 0.14em;
  margin-bottom: 8px;
}
.tx-none-why {
  color: var(--phosphor-dim);
  font-size: 0.9rem;
  max-width: var(--prose-max);
}
/* The dash carries the same meaning as the dashes in the status readout: a value
   we do not have, shown as visibly missing rather than quietly absent. */
.tx-none-why .dash {
  color: var(--phosphor-faint);
  letter-spacing: 0.14em;
}

.screen-footer {
  color: var(--phosphor-dim);
  font-size: 0.82rem;
  text-align: center;
  opacity: 0.7;
  letter-spacing: 0.06em;
  padding-top: 6px;
}

.loading { color: var(--phosphor-dim); opacity: 0.8; }
.error {
  color: var(--amber-alert);
  text-shadow: 0 0 3px rgba(255,176,0,0.6);
}
.error code {
  background: rgba(255,176,0,0.08);
  padding: 1px 5px;
  border: 1px solid rgba(255,176,0,0.35);
}

/* ======================= RESPONSIVE =======================
   The per-post column count is a DESKTOP maximum. As the screen narrows it is
   capped down so photos never shrink into thumbnails. Centring of the trailing
   row is handled by the flex row itself, so every one of these caps stays
   balanced without any per-breakpoint arithmetic. */
@media (max-width: 1023px) {
  .photo-strip[data-cols="4"] { --photo-cols: 3; }
}
@media (max-width: 759px) {
  .photo-strip[data-cols="3"],
  .photo-strip[data-cols="4"] { --photo-cols: 2; }
}

@media (max-width: 560px) {
  html, body { font-size: 16px; }
  .crt { padding: 18px 13px 30px; }
  .panel { padding: 12px 13px 14px; }
  .status .unit-line { font-size: 1.25rem; }
  /* Stack the readout: label above, meter + value below, full width. */
  .readout { grid-template-columns: 1fr auto; row-gap: 2px; }
  .readout .label { grid-column: 1 / -1; }
  .meter { font-size: 0.95rem; }
  /* Photos go single-column and full-width — a two-up row on a phone makes
     both images too small to read. */
  .photo-strip { --photo-gap: 12px; }
  .photo-strip[data-cols] { --photo-cols: 1; }
  .photo figcaption { font-size: 0.82rem; }
  .photo-tag { font-size: 0.66rem; }
  /* css/giscus.css steps its own type down at this same 560px breakpoint, so the
     comments and the page around them stay in proportion. */
  .transmissions-greeting { font-size: 0.94rem; }
  .transmissions-intro { font-size: 0.88rem; }
}
