/* ============================================================================
   try crystal - the console's clothes.

   THE ART DIRECTION LIVES IN THE TWO TOKEN BLOCKS AT THE TOP. Everything
   below them is structure: what a transcript line is, where the entry row
   sits, how focus is shown. Redirecting the look means changing the tokens
   and leaving the rest alone.

   The design is deliberately not a default AI look: no purple, no gradient,
   no glass blur, no rounded-everything, no emoji. A console panel set on a
   mineral paper ground, cut corners on one surface only, IBM Plex because
   the sibling crystalshards.org sites are set in it. The console panel is
   dark in BOTH color schemes because it is a terminal: terminals have a
   ground color of their own, and the page around it carries the scheme.

   Accessibility decisions that are load bearing, do not undo them casually:
   - --focus is a 2px ring at >= 3:1 against every ground it appears on.
     Never set outline: none without an equivalent replacement.
   - --console-err is the ONLY signal for failed output and carries >= 4.5:1
     on the console ground; color never carries meaning alone, the reaction
     text says what happened.
   - Interactive targets are >= 24px tall (WCAG 2.2).
   ========================================================================= */

:root {
  /* ---- page ground and ink (scheme carries here) ---------------------- */
  --paper:        #f4f6f6;
  --ink:          #0e1416;
  --ink-muted:    #55666b;
  --edge:         #d8dfe0;

  /* ---- accent: glacial teal, this site's own hue in the family --------- */
  --accent:       #0c6f5e;   /* 5.6:1 on --paper */
  --accent-deep:  #09544a;   /* 7.6:1 on --paper */

  /* ---- focus ------------------------------------------------------------ */
  --focus:        #0c6f5e;
  --focus-ring:   0 0 0 2px var(--paper), 0 0 0 4px var(--focus);

  /* ---- type ------------------------------------------------------------ */
  --sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --cond: "IBM Plex Sans Condensed", var(--sans);
  --mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --step--1: 0.8125rem;
  --step-0:  0.9375rem;
  --step-1:  1.0625rem;
  --step-2:  clamp(1.3rem, 1.1rem + 0.8vw, 1.7rem);

  /* ---- space and geometry ---------------------------------------------- */
  --gap:   1.125rem;
  --pad:   1.375rem;
  --shell: 58rem;
  --facet: 10px;   /* the one cut corner, on the console panel */
  --tap:   24px;   /* WCAG 2.2 target minimum */
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper:        #070a0b;
    --ink:          #e9f1f0;
    --ink-muted:    #9fb2b3;
    --edge:         #222e30;

    --accent:       #4fd8c0;   /* 9.6:1 on --paper */
    --accent-deep:  #35a893;
  }
}

/* ---- the console panel has its own ground in both schemes -------------- */
:root {
  --console-ground:  #0c1210;
  --console-raised:  #101a17;
  --console-edge:    #223731;
  --console-ink:     #d7e4df;  /* 13.9:1 on --console-ground */
  --console-ink-dim: #8fa8a0;  /* 6.9:1 on --console-ground */
  --console-echo:    #f2f7f5;
  --console-accent:  #4fd8c0;  /* 9.9:1 on --console-ground */
  --console-err:     #ff9c88;  /* 7.4:1 on --console-ground */
  --console-meta:    #6d857e;  /* 4.0:1, decorative timing only */
  --console-focus:   #4fd8c0;
}

/* ==========================================================================
   base
   ========================================================================= */

* {
  box-sizing: border-box;
}

html {
  height: 100%;
}

body {
  margin: 0;
  min-height: 100%;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--accent-deep);
  text-underline-offset: 0.2em;
}

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--paper);
  color: var(--ink);
  padding: 0.5rem 1rem;
  z-index: 10;
}

.skip-link:focus {
  left: 0.5rem;
  top: 0.5rem;
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

:focus-visible {
  outline: 2px solid var(--console-focus);
  outline-offset: 2px;
}

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

/* ==========================================================================
   the workspace

   A developer's screen, used. The page itself does not scroll: the masthead
   and colophon are fixed height, and the three panes take the rest and
   scroll independently. Reading the lesson and writing code happen at the
   same time, so they get their own surfaces instead of taking turns in one
   column.

   Deliberately NOT a max-width column. --shell is gone from this layout; a
   58rem measure is right for prose and wrong for a tool, and the whole point
   of this revision is the real estate.

   100dvh, not 100vh: on mobile the dynamic viewport unit is the one that
   accounts for the browser's own chrome, and 100vh there produces a page
   that is always slightly taller than the screen.
   ========================================================================= */

.shell {
  height: 100dvh;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  padding: 0;
  margin: 0;
  max-width: none;
  overflow: hidden;
}

.masthead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gap);
  padding: 0.7rem var(--pad);
  border-bottom: 1px solid var(--edge);
  min-height: 0;
}

/* ---- brand and progress -------------------------------------------------
   These were lost once already. Rewriting the layout block replaced the old
   .shell and masthead section wholesale, and took the brand and progress
   rules with it: the mark rendered in body ink instead of the accent, the
   wordmark lost its condensed face, and .step is a <span>, so with its
   width and height gone it measured 0x0 and the progress indicator was
   invisible on the page while still being in the DOM. Caught by measuring
   the rendered geometry, not by reading the file. */

.brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.mark {
  color: var(--accent);
  flex: none;
}

.wordmark {
  font-family: var(--cond);
  font-weight: 700;
  font-size: var(--step-1);
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.tagline {
  margin: 0;
  color: var(--ink-muted);
  font-family: var(--mono);
  font-size: var(--step--1);
}

.progress {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.progress-label {
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--ink-muted);
  white-space: nowrap;
}

.steps {
  display: flex;
  gap: 0.4rem;
}

/* Small facets, echoing the mark. A flex item, so width and height apply to
   what is otherwise an inline span. */
.step {
  width: 10px;
  height: 10px;
  flex: none;
  border: 1.5px solid var(--ink-muted);
  transform: rotate(45deg);
}

.step--current {
  background: var(--accent);
  border-color: var(--accent);
}

.step--done {
  background: var(--ink-muted);
  border-color: var(--ink-muted);
}

/* The two columns. minmax(0, ...) on both tracks is load bearing: without
   the zero minimum a long unbroken line of output makes a grid track refuse
   to shrink, and the whole layout grows a horizontal scrollbar instead of
   the pane that owns the content. */
.workspace {
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 27rem) minmax(0, 1fr);
  gap: 1px;
  background: var(--edge);
}

/* Past a very wide viewport the lesson column stops growing: prose has a
   comfortable measure and an 80rem-wide paragraph is not it. The editor and
   output take the surplus, which is where a wide screen actually helps. */
@media (min-width: 100rem) {
  .workspace {
    grid-template-columns: minmax(0, 32rem) minmax(0, 1fr);
  }
}

.workbench {
  min-width: 0;
  min-height: 0;
  display: grid;
  grid-template-rows: minmax(0, 2fr) minmax(0, 3fr);
  gap: 1px;
  background: var(--edge);
}

/* ---- a pane ------------------------------------------------------------- */

.pane {
  min-width: 0;
  min-height: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  background: var(--paper);
}

.pane--editor,
.pane--output {
  background: var(--console-ground);
  color: var(--console-ink);
}

.pane-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.4rem 0.9rem;
  border-bottom: 1px solid var(--edge);
  min-height: calc(var(--tap) + 0.5rem);
}

.pane--editor .pane-head,
.pane--output .pane-head {
  border-bottom-color: var(--console-edge);
  background: var(--console-raised);
}

.pane-title {
  font-family: var(--cond);
  font-weight: 700;
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.pane--editor .pane-title,
.pane--output .pane-title {
  color: var(--console-ink-dim);
}

.pane-hint {
  margin-left: auto;
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--console-meta);
  white-space: nowrap;
}

.pane-body {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--pad);
}

/* ---- the lesson pane ---------------------------------------------------- */

.lesson-text {
  margin: 0 0 1.1rem;
  font-size: var(--step-1);
  line-height: 1.55;
  max-width: 42ch;
  white-space: pre-wrap;
}

.example {
  display: grid;
  gap: 0.5rem;
  justify-items: start;
  margin-bottom: 1.25rem;
}

.example-label,
.hint-label {
  font-family: var(--cond);
  font-weight: 700;
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* ==========================================================================
   compiler colours

   The compiler writes its diagnostics in ANSI, and the server parses those
   escapes into segments carrying these class names. Every value is checked
   against --console-ground, because a compiler error is the text a beginner
   most needs to read and the one they are most likely to be upset by.

   Contrast on --console-ground (#0c1210):
     red #ff9c88 7.4:1   green #7ee0b8 11.0:1   yellow #f0c674 10.6:1
     blue #8ab4f8 8.3:1  magenta #e3a7e0 8.9:1  cyan #7fd6d0 10.3:1
     white #d7e4df 13.9:1  black, remapped, #8fa8a0 6.9:1

   ansi-black is deliberately NOT black. A terminal's "black" on a dark ground
   is invisible, and the compiler uses it for de-emphasis, so it maps to the
   dim ink that already carries that meaning here.
   ========================================================================= */

.ansi-bold {
  font-weight: 600;
}

/* Dim, but never below 4.5:1. opacity would take the text under it. */
.ansi-dim {
  color: var(--console-ink-dim);
}

.ansi-black   { color: #8fa8a0; }
.ansi-red     { color: #ff9c88; }
.ansi-green   { color: #7ee0b8; }
.ansi-yellow  { color: #f0c674; }
.ansi-blue    { color: #8ab4f8; }
.ansi-magenta { color: #e3a7e0; }
.ansi-cyan    { color: #7fd6d0; }
.ansi-white   { color: #d7e4df; }

/* A colour beats dim when the compiler asks for both: the escape sequence's
   own intent is the colour, and dim is the de-emphasis it pairs with. */
.ansi-dim.ansi-black   { color: #7b918a; }
.ansi-dim.ansi-red     { color: #d98878; }
.ansi-dim.ansi-green   { color: #6bbd9c; }
.ansi-dim.ansi-yellow  { color: #ccaa64; }
.ansi-dim.ansi-blue    { color: #7599cf; }
.ansi-dim.ansi-magenta { color: #bf8dbd; }
.ansi-dim.ansi-cyan    { color: #6bb4af; }
.ansi-dim.ansi-white   { color: #b4bfbb; }

/* ==========================================================================
   scrollbars

   The default bar is a chunky light gutter that reads as a UI element sitting
   on top of the console, which it is not: it is a hint about how much more
   there is. Thin, in the surface's own edge colour, and only where a pane can
   actually scroll.

   Two properties on purpose. scrollbar-width and scrollbar-color are the
   standard ones, honoured by Firefox and now by Chromium; the ::-webkit rules
   cover Safari and older Chromium, which ignore them. Neither hides the bar
   entirely: a scroll region with no visible affordance is a usability bug, not
   a clean design.
   ========================================================================= */

.pane-body,
.pane--editor .input,
.example-code {
  scrollbar-width: thin;
  scrollbar-color: var(--console-edge) transparent;
}

.pane--lesson .pane-body {
  scrollbar-color: var(--edge) transparent;
}

.pane-body::-webkit-scrollbar,
.pane--editor .input::-webkit-scrollbar,
.example-code::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

.pane-body::-webkit-scrollbar-track,
.pane--editor .input::-webkit-scrollbar-track,
.example-code::-webkit-scrollbar-track {
  background: transparent;
}

.pane-body::-webkit-scrollbar-thumb,
.pane--editor .input::-webkit-scrollbar-thumb,
.example-code::-webkit-scrollbar-thumb {
  background: var(--console-edge);
  border-radius: 4px;
}

.pane--lesson .pane-body::-webkit-scrollbar-thumb {
  background: var(--edge);
}

/* The corner where two bars meet renders as a pale square otherwise. */
.pane-body::-webkit-scrollbar-corner,
.pane--editor .input::-webkit-scrollbar-corner {
  background: transparent;
}

.example-code {
  margin: 0;
  align-self: stretch;
  font-family: var(--mono);
  font-size: var(--step-0);
  background: var(--console-ground);
  color: var(--console-echo);
  border-radius: var(--facet) 0 var(--facet) 0;
  padding: 0.7rem 0.9rem;
  overflow-x: auto;
  white-space: pre;
}

.ghost {
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--accent-deep);
  background: transparent;
  border: 1px solid var(--edge);
  border-radius: 4px;
  min-height: var(--tap);
  padding: 0.15rem 0.7rem;
  cursor: pointer;
}

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

.lesson-hint {
  margin: 0;
  color: var(--ink-muted);
  font-size: var(--step-0);
  max-width: 42ch;
}

/* ---- the editor pane --------------------------------------------------- */
/* The textarea IS the pane body, so it fills the pane and scrolls itself.
   wrap="off" plus overflow auto keeps a long line a long line, which is what
   a code editor does; soft wrapping code changes what the author sees. */

.pane--editor .input {
  min-height: 0;
  width: 100%;
  font-family: var(--mono);
  font-size: var(--step-0);
  line-height: 1.6;
  color: var(--console-echo);
  background: var(--console-ground);
  border: 0;
  padding: var(--pad);
  resize: none;
  overflow: auto;
  tab-size: 2;
}

.pane--editor .input:focus {
  outline: 2px solid var(--console-focus);
  outline-offset: -2px;
}

.transcript {
  font-family: var(--mono);
  font-size: var(--step-0);
  line-height: 1.55;
}

/* Every line is textContent only; the kinds differ by weight, size and
   color, never by markup. white-space preserves the copy's own line breaks,
   including the code sample on its own line. */
.line {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin: 0 0 0.55rem;
}

.line--system {
  color: var(--console-ink);
  max-width: 46rem;
}

.line--echo {
  color: var(--console-echo);
  font-weight: 500;
  margin-top: 1.1rem;
}

.line--echo::before,
.line--out::before,
.line--err::before,
.line--value::before {
  content: "";
  display: block;
  height: 0.25rem;
}

.line--out {
  color: var(--console-ink);
}

.line--err {
  color: var(--console-err);
}

.line--value {
  color: var(--console-accent);
  font-weight: 600;
}

.line--reaction {
  color: var(--console-ink-dim);
  font-style: italic;
  max-width: 46rem;
  margin-top: 0.4rem;
}

.line--meta {
  color: var(--console-meta);
  font-size: var(--step--1);
  text-align: right;
}

.line--final {
  color: var(--console-accent);
  font-weight: 500;
  max-width: 46rem;
}

/* ---- the run control ---------------------------------------------------- */

.run {
  margin-left: 0;
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--console-ground);
  background: var(--console-accent);
  border: 0;
  border-radius: 4px;
  min-height: var(--tap);
  padding: 0.2rem 1.1rem;
  cursor: pointer;
}

.run:disabled {
  opacity: 0.55;
  cursor: default;
}

/* The editor pane dims its own title while a submission is out, so the
   waiting state reads on the surface that is waiting rather than as a
   spinner bolted somewhere else. */
.pane--editor[data-pending] .pane-title {
  animation: breathe 1.2s ease-in-out infinite;
}

@keyframes breathe {
  50% {
    opacity: 0.35;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pane--editor[data-pending] .pane-title {
    animation: none;
    opacity: 0.55;
  }
}

noscript {
  display: block;
  padding: 0.75rem var(--pad);
  border-top: 1px solid var(--console-edge);
  color: var(--console-ink-dim);
  font-family: var(--sans);
  font-size: var(--step--1);
}

/* ==========================================================================
   colophon
   ========================================================================= */

.colophon {
  padding: 0.5rem var(--pad);
  border-top: 1px solid var(--edge);
  color: var(--ink-muted);
  font-size: var(--step--1);
}

/* ==========================================================================
   narrow screens

   Below the point where two columns each stop being usable, the panes stack
   and the PAGE scrolls instead of each pane scrolling inside a fixed height.
   Three independently scrolling boxes stacked inside one screen height is
   the worst of both: nothing has room, and every gesture is ambiguous.
   ========================================================================= */

@media (max-width: 60rem) {
  .shell {
    height: auto;
    min-height: 100dvh;
    overflow: visible;
  }

  .workspace,
  .workbench {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto;
  }

  .pane {
    grid-template-rows: auto auto;
  }

  .pane-body {
    overflow-y: visible;
  }

  /* An editor still needs a real height when nothing is constraining it. */
  .pane--editor .input {
    min-height: 11rem;
  }

  .pane--output .pane-body {
    min-height: 14rem;
  }
}

@media (max-width: 40rem) {
  .masthead {
    align-items: flex-start;
    flex-direction: column;
  }

  .progress {
    margin-left: 0;
  }
}
