/* ═══════════════════════════════════════════════════════════════════════
   bkchou.com

   One console panel, not a page with a header and a footer. Everything
   lives inside a single drawn frame; the labels sit on the frame itself
   the way silkscreen sits on an instrument.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  --mono: "Berkeley Mono", "SFMono-Regular", ui-monospace, "JetBrains Mono",
          "IBM Plex Mono", Menlo, Consolas, monospace;

  /* Deep saturated ink. Surfaces stay low-luminance; the hue does the work. */
  --bg:      #05070F;
  --bg-2:    #090D1A;
  --bg-3:    #10162A;
  --rule:    #1B2440;
  --rule-2:  #2C3860;
  --fg:      #E5E8F2;
  --dim:     #7A85A8;
  --dimmer:  #4A5478;

  /* Console indicators — saturated, legible on ink. */
  --accent:  #FF5C2B;
  --amber:   #FFB020;
  --cyan:    #22C3E6;
  --violet:  #9B7BFF;
  --rose:    #FF4D8D;
  --ok:      #2FD07E;

  --scan:    rgba(0,0,0,.46);
  --pitch:   3px;
  --noise:   .04;
  --lift:    rgba(255,255,255,.05);

  --pad:     clamp(18px, 4vw, 56px);
  --inset:   clamp(10px, 2.4vw, 28px);
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1.6;
  font-variant-ligatures: none;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

/* ── CRT overlay ─────────────────────────────────────────────────────── */
/* ── CRT ─────────────────────────────────────────────────────────────────
   Three layers, all fixed so nothing shimmers against a scrolling page.

   What tires the eye is contrast at the line edge, not the number of lines.
   So each line is a soft ramp rather than a hard band — but a ramp needs room
   to be a ramp. Below about three CSS pixels of pitch the dark and light ends
   average into a flat wash and the lines stop reading as lines at all, which
   is what happened when this halved the pitch on 2× displays.

   So: 3px throughout, and the contrast spent on a bright line as well as a
   dark one. A lift above each trough doubles the perceived depth of the line
   without making the page any darker overall. */
.crt { position: fixed; inset: 0; pointer-events: none; z-index: 100; }

/* Aperture grille — vertical phosphor triads. Barely there, and it lifts
   rather than darkens, which keeps the density from muddying the page. */
.crt {
  background-image: linear-gradient(90deg,
    rgba(255,90,90,.022) 0 33.33%,
    rgba(90,255,140,.022) 33.33% 66.66%,
    rgba(100,140,255,.022) 66.66% 100%);
  background-size: 3px 100%;
}

.crt::before {
  content: ""; position: absolute; inset: 0;
  background-image: linear-gradient(to bottom,
    var(--lift) 0,
    transparent 26%,
    var(--scan) 48%, var(--scan) 64%,
    transparent 92%);
  background-size: 100% var(--pitch);
}
.crt::after {
  content: ""; position: absolute; inset: -20%;
  background:
    radial-gradient(120% 90% at 50% 40%, transparent 52%, rgba(2,4,12,.72) 100%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='.55'/%3E%3C/svg%3E");
  opacity: var(--noise);
}

/* ── the frame ───────────────────────────────────────────────────────── */
.console {
  position: relative;
  max-width: 1180px;
  margin: var(--inset) auto;
  border: 1px solid var(--rule-2);
  padding: clamp(56px, 9vw, 104px) 0 0;
}

/* Silkscreen labels, punched through the frame line. */
.tag {
  position: absolute;
  display: flex; align-items: center; gap: 9px;
  padding: 0 12px;
  background: var(--bg);
  font-size: 11.5px; letter-spacing: .17em; text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
  max-width: calc(100% - 2 * var(--pad));
  overflow: hidden; text-overflow: ellipsis;
}
.tag-tl { top: 0; left: calc(var(--pad) - 12px); transform: translateY(-50%); color: var(--fg); }
.tag-tr { top: 0; right: calc(var(--pad) - 12px); transform: translateY(-50%); }
.tag-bl { bottom: 0; left: calc(var(--pad) - 12px); transform: translateY(50%); }

.led {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent);
  flex: none;
  animation: pulse 3.2s ease-in-out infinite;
}
.ok .led { background: var(--ok); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ok) 22%, transparent); }
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .35 } }

/* ── head ───────────────────────────────────────────────────────────── */
.head { padding: 0 var(--pad) clamp(48px, 7vw, 76px); container-type: inline-size; }

/* ── split-flap board ────────────────────────────────────────────────────
   One departure board, fourteen cells across: the name on the left, the
   date and time in the right column. Everything is sized off a single
   character width so both fields sit on the same grid, and the gutter is
   just a wider version of the gap between cells — a column on one board,
   not a second board. */
.board {
  /* Solve the cell width so the board comes out exactly as wide as the
     container, rather than sizing cells off the viewport and leaving whatever
     is left over stranded on the right.

     Across the board: 14 cells, 13 gaps between them, 6 more gaps of housing
     padding, 2px of border. Gap is 7% of a cell but never under 2px, so there
     are two regimes and the smaller answer is the safe one.

     cqw, not vw — it measures the container's content box, so it is not
     thrown off by the scrollbar. */
  --cw: min((100cqw - 3px) / 15.33, (100cqw - 41px) / 14);
  --ch: calc(var(--cw) * 1.44);
  --gap: max(2px, calc(var(--cw) * .07));
  --fr: 2px;
  margin: 0 0 clamp(40px, 5.5vw, 64px);

  /* A housing. Without one the cells only group by proximity, and any gutter
     wider than the gap between cells splits them into two boards. So: one
     enclosure, and the same gap everywhere inside it. */
  display: flex; align-items: flex-start;
  gap: var(--gap);
  padding: calc(var(--gap) * 3);
  background: var(--bg-2);
  border: 1px solid var(--rule);
  border-radius: 4px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.03),
              0 1px 0 rgba(0,0,0,.4);
  font-weight: 400;
}
.field { display: flex; flex-direction: column; gap: var(--gap); }
.line { display: flex; gap: var(--gap); }

/* ── one flap cell ───────────────────────────────────────────────────── */
.cell {
  position: relative;
  display: block;
  width: var(--cw); height: var(--ch);
  perspective: calc(var(--ch) * 4);
  font-size: calc(var(--ch) * .62);
  line-height: var(--ch);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}
.cell .card {
  position: absolute; left: 0; width: 100%; height: 50%;
  overflow: hidden;
  background: var(--bg-3);
  backface-visibility: hidden;
}
.cell .card b {
  display: block; width: 100%; height: var(--ch);
  line-height: var(--ch); text-align: center;
  font-weight: 400; color: var(--amber);
}
/* The name is the headline; the date and time sit behind it. */
[data-field] .cell .card b { color: var(--dim); }
.cell .top    { top: 0; border-radius: var(--fr) var(--fr) 0 0;
                box-shadow: inset 0 1px 0 rgba(255,255,255,.05); }
.cell .bottom { bottom: 0; border-radius: 0 0 var(--fr) var(--fr);
                background: linear-gradient(rgba(0,0,0,.18), transparent 60%), var(--bg-3); }
.cell .bottom b { transform: translateY(-50%); }
.cell .top::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 1px;
  background: var(--bg);
}
.cell .ftop, .cell .fbot { z-index: 2; }
.cell .ftop { top: 0; transform-origin: 50% 100%; border-radius: var(--fr) var(--fr) 0 0; }
.cell .fbot { bottom: 0; transform-origin: 50% 0; border-radius: 0 0 var(--fr) var(--fr);
              transform: rotateX(90deg);
              background: linear-gradient(rgba(0,0,0,.18), transparent 60%), var(--bg-3); }
.cell .fbot b { transform: translateY(-50%); }

/* 55ms up, 55ms down — the card itself falls fast. What makes the board feel
   mechanical is the gap between characters, not a slow flap. See STEP in app.js. */
.cell.go .ftop { animation: flap-top .055s cubic-bezier(.4,0,.9,.5) forwards; }
.cell.go .fbot { animation: flap-bot .055s cubic-bezier(.1,.5,.6,1) .055s forwards; }
@keyframes flap-top { to { transform: rotateX(-90deg); } }
@keyframes flap-bot { to { transform: rotateX(0deg); } }

/* ── disciplines ─────────────────────────────────────────────────────── */
.disciplines {
  list-style: none;
  margin: 0 0 clamp(44px, 6vw, 68px); padding: 0;
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 6px 18px;
  font-size: clamp(17px, 1.9vw, 21px);
  letter-spacing: -.01em;
}
/* The rule belongs to the item that follows it, so a wrap carries the
   rule down with it instead of leaving one stranded at the line end. */
.disciplines li + li::before {
  content: ""; display: inline-block;
  width: 1px; height: .92em;
  margin-right: 18px;
  vertical-align: -.1em;
  background: var(--rule-2);
}

/* ── daylight strip ──────────────────────────────────────────────────────
   72 columns of one day over New York. Night dots, twilight dashes, daylight
   bars, and a caret that walks across as the day goes. */
.ascii {
  margin: 0;
  font-size: clamp(9px, 1.15vw, 14px); line-height: 1.5;
  color: var(--dimmer);
  white-space: pre;
  overflow-x: auto;
  scrollbar-width: none;
}
.ascii::-webkit-scrollbar { display: none; }
.ascii i  { color: color-mix(in srgb, var(--cyan) 55%, var(--dimmer)); font-style: normal; }
.ascii em { color: color-mix(in srgb, var(--amber) 62%, var(--dimmer)); font-style: normal; }
.ascii b  { color: var(--accent); font-weight: 400; }

/* ── section bands ───────────────────────────────────────────────────── */
#sites     { --sec: var(--cyan); }
#writing   { --sec: var(--amber); }
#profiles  { --sec: var(--violet); }
#elsewhere { --sec: var(--rose); }

/* A labelled register: the band spans the full frame, tinted by its own
   indicator colour, with a hairline of that colour down the left edge. */
.shead {
  display: flex; align-items: center; gap: 16px;
  margin: 0;
  padding: 10px var(--pad);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  background: color-mix(in srgb, var(--sec) 8%, transparent);
  box-shadow: inset 3px 0 0 var(--sec);
  font-size: 13px; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase;
}
.shead .idx { color: var(--sec); }
.shead .ct { margin-left: auto; font-size: 12px; color: var(--dim); }

.rows { list-style: none; margin: 0; padding: 0; }
.rows li + li { border-top: 1px solid var(--rule); }

.row {
  display: grid;
  grid-template-columns: 44px minmax(0, 380px) minmax(0, 1fr) 26px;
  align-items: baseline;
  gap: 20px;
  padding: 21px var(--pad);
  text-decoration: none; color: inherit;
  position: relative;
  transition: background-color .18s, box-shadow .18s;
}
.row:hover, .row:focus-visible {
  background: color-mix(in srgb, var(--sec) 7%, var(--bg-2));
  box-shadow: inset 3px 0 0 var(--sec);
  outline: none;
}

.row-n { font-size: 12px; color: var(--dimmer); letter-spacing: .1em; }
.row-t { font-size: 18px; }
.row-d { font-size: 14px; color: var(--dim); }
.row-x { font-size: 15px; color: var(--dimmer); text-align: right;
         transition: color .18s, transform .18s; }
.row:hover .row-n { color: var(--sec); }
.row:hover .row-x { color: var(--sec); transform: translateX(3px); }
.row:focus-visible .row-x { color: var(--sec); }

/* The last band needs room above the frame's bottom label. */
#elsewhere .rows { padding-bottom: clamp(28px, 4vw, 44px); }

/* ── entrance ────────────────────────────────────────────────────────── */
/* Pure CSS, no scripting gate: `backwards` holds the from-state during the
   delay, so the content can never be stranded invisible by a failed script. */
.rise {
  animation: rise .6s cubic-bezier(.2,.7,.3,1) backwards;
  animation-delay: calc(var(--i) * 70ms + 60ms);
}
@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* ── responsive ──────────────────────────────────────────────────────── */
@media (max-width: 900px) {
  .tag-tr { display: none; }
}

/* Below this the two fields no longer fit side by side. */
@media (max-width: 620px) {
  .row {
    grid-template-columns: 34px minmax(0, 1fr) 20px;
    gap: 4px 14px;
    padding: 17px var(--pad);
  }
  .row-n { grid-column: 1; grid-row: 1; }
  .row-t { grid-column: 2; grid-row: 1; }
  .row-x { grid-column: 3; grid-row: 1; }
  .row-d { grid-column: 2 / 4; grid-row: 2; }
  .tag { font-size: 10.5px; letter-spacing: .14em; }
}

/* ── a11y ────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .rise { opacity: 1; transform: none; animation: none !important; }
  .cell.go .ftop, .cell.go .fbot { animation: none !important; }
  .cell .ftop, .cell .fbot { display: none; }
  .led { animation: none; }
  .crt::before { display: none; }
  * { transition-duration: .01ms !important; }
}

@media print {
  .crt, .ascii { display: none; }
  body { background: #fff; color: #000; }
  .console { border: none; }
}
