/* integration-patterns explainer
   Direction: a signal diagram, not a marketing page. The palette IS the
   subject: jade for healthy flow, coral for failure, ochre for waiting. The
   display face is monospace because the topic is wire protocols and logs, and
   because it keeps a family resemblance to the chess-engine demo without
   repeating it.

   Token names describe a role, not a colour, so the light theme overrides
   cleanly. */

:root {
  color-scheme: dark;

  --ground:   #171A2B;
  --surface:  #1F2338;
  --surface-2:#262B44;
  --line:     #333A57;
  --text:     #E4E6F0;
  --dim:      #8B90A8;

  --flow:     #6BC5A8;   /* healthy path      */
  --fail:     #E5675C;   /* failure           */
  --hold:     #D4A537;   /* waiting, retrying */

  --glow:     rgba(107, 197, 168, 0.07);
  --shadow:   rgba(0, 0, 0, 0.35);

  /* Glass. Three parts, and it reads as flat grey paint if any one is missing:
     a translucent fill, a blur that keeps saturation so the field behind stays
     coloured rather than washing out, and a hairline of light along the top
     edge where a real pane would catch it.

     Opacity is the readability dial rather than a style choice. Below about
     55% the body copy starts fighting whatever is behind it.

     A blur over a flat colour returns the flat colour, so `body` owes the
     panels a field with structure in it. See the field block below. */
  --glass:      color-mix(in srgb, var(--surface) 72%, transparent);
  --glass-thin: color-mix(in srgb, var(--surface) 55%, transparent);
  --glass-blur: saturate(180%) blur(20px);
  --glass-edge: rgba(255, 255, 255, 0.07);

  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --sans: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
  --r: 5px;
  --band: 62rem;
}

:root[data-theme="light"] {
  color-scheme: light;

  --ground:   #F4F5FA;
  --surface:  #FFFFFF;
  --surface-2:#EDEFF7;
  --line:     #D6DAE8;
  --text:     #191D2B;
  --dim:      #5F6479;

  --flow:     #17805F;
  --fail:     #BE3427;
  --hold:     #96690C;

  --glow:     rgba(23, 128, 95, 0.06);
  --shadow:   rgba(30, 34, 55, 0.12);

  /* Daylight needs a denser pane. The field behind is brighter than the panel
     rather than darker, so a thin fill lets it read straight through the text. */
  --glass:      color-mix(in srgb, var(--surface) 68%, transparent);
  --glass-thin: color-mix(in srgb, var(--surface) 50%, transparent);
  --glass-edge: rgba(255, 255, 255, 0.6);
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html { scroll-behavior: smooth; }

/* The field the glass panels blur.

   A blur over a flat colour gives back the flat colour, so glass on a plain
   background is an expensive way to draw nothing. The panels need structure
   behind them, and on a page about signals crossing a wire the honest
   structure is the wire: fine horizontal rules at 24px, which is the lane
   pitch used by the scenes, plus a jade pool where the healthy path runs and
   an ochre one where things wait.

   Horizontal only, unlike the pg-outbox grid. This page is about sequence
   along a line rather than position on a plan, and a square grid would say
   the wrong thing about it.

   `background-attachment: fixed` holds the field still while the page scrolls
   over it, which is what makes the panels read as sitting on it.

   Painted on `body` rather than a fixed `::before` at `z-index: -1`. That
   pattern needs the whole ancestor chain free of stacking contexts, and the
   first `opacity` or `transform` added above it swallows the layer silently.
   This page animates opacity in several places, so that trap is live here. */
body {
  margin: 0;
  background: var(--ground);
  background-image:
    radial-gradient(ellipse 62% 45% at 18% -5%, color-mix(in srgb, var(--flow) 13%, transparent), transparent 70%),
    radial-gradient(ellipse 55% 40% at 88% 20%, color-mix(in srgb, var(--hold) 9%, transparent), transparent 72%),
    repeating-linear-gradient(
      to bottom,
      transparent 0 23px,
      color-mix(in srgb, var(--line) 26%, transparent) 23px 24px
    );
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

:root[data-theme="light"] body {
  background-image:
    radial-gradient(ellipse 62% 45% at 18% -5%, color-mix(in srgb, var(--flow) 9%, transparent), transparent 70%),
    radial-gradient(ellipse 55% 40% at 88% 20%, color-mix(in srgb, var(--hold) 8%, transparent), transparent 72%),
    repeating-linear-gradient(
      to bottom,
      transparent 0 23px,
      color-mix(in srgb, var(--line) 34%, transparent) 23px 24px
    );
}

/* ------------------------------------------------------------------ header */

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem clamp(1rem, 4vw, 2.5rem);
  background: color-mix(in srgb, var(--ground) 72%, transparent);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid var(--line);
  box-shadow: inset 0 1px 0 var(--glass-edge);
}

.wordmark {
  font-family: var(--mono);
  font-weight: 800;
  font-size: 0.95rem;
  letter-spacing: -0.02em;
  color: var(--text);
  text-decoration: none;
}
.wordmark .dim { color: var(--dim); }

.topnav { display: flex; align-items: center; gap: 0.9rem; }
.topnav a {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--dim);
  text-decoration: none;
  transition: color 0.15s;
}
.topnav a:hover, .topnav a:focus-visible { color: var(--flow); }

/* Brand marks inherit the link colour, so they theme themselves and light up
   with the label on hover rather than sitting there as static artwork. */
.ico { width: 1rem; height: 1rem; flex: none; fill: currentColor; }

/* On narrow screens the labels go and the marks carry the meaning. */
@media (max-width: 680px) {
  .topnav a span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .topnav a { gap: 0; }
  .ico { width: 1.15rem; height: 1.15rem; }
}

/* In the topbar, which already blurs. Fill only, no second blur. */
.pill, .icon-btn {
  font-family: var(--sans);
  color: var(--dim);
  background: var(--glass-thin);
  box-shadow: inset 0 1px 0 var(--glass-edge);
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.pill { font-size: 0.74rem; letter-spacing: 0.06em; text-transform: uppercase; padding: 0.3rem 0.7rem; }
.icon-btn { display: grid; place-items: center; width: 1.9rem; height: 1.9rem; padding: 0; border-radius: var(--r); }
.pill:hover, .icon-btn:hover, .pill:focus-visible, .icon-btn:focus-visible { color: var(--flow); border-color: var(--flow); }
.icon-btn svg { width: 0.95rem; height: 0.95rem; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.icon-btn .moon { display: none; }
:root[data-theme="light"] .icon-btn .moon { display: block; }
:root[data-theme="light"] .icon-btn .sun { display: none; }

/* ------------------------------------------------------------------ layout */

main { display: block; }

.hero, .band {
  max-width: var(--band);
  margin: 0 auto;
  padding: clamp(3rem, 8vw, 5.5rem) clamp(1rem, 4vw, 2.5rem);
}
.band { border-top: 1px solid var(--line); }

h2 {
  margin: 0 0 0.9rem;
  font-family: var(--mono);
  font-size: clamp(1.5rem, 3.6vw, 2.1rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.15;
}

.eyebrow {
  margin: 0 0 0.6rem;
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--flow);
}

.body { margin: 0 0 1.8rem; max-width: 62ch; color: var(--text); }
.dim-body { color: var(--dim); }
.caption { margin: 1rem 0 0; font-size: 0.86rem; color: var(--dim); max-width: 62ch; }

/* ------------------------------------------------------------------ hero */

.hero { position: relative; padding-top: clamp(3.5rem, 10vw, 7rem); }

.kicker {
  margin: 0 0 1rem;
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
}

.hero-title {
  margin: 0 0 1.3rem;
  font-family: var(--mono);
  font-size: clamp(1.9rem, 5.6vw, 3.5rem);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.04em;
}

.lede { margin: 0 0 2rem; max-width: 60ch; font-size: 1.05rem; color: var(--dim); }

.hero-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.9rem; }

.btn {
  font-family: var(--sans);
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.6rem 1.1rem;
  color: var(--text);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r);
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s, border-color 0.15s, color 0.15s;
}
.btn:hover, .btn:focus-visible { border-color: var(--flow); color: var(--flow); }
.btn-primary { background: var(--flow); border-color: var(--flow); color: var(--ground); }
.btn-primary:hover, .btn-primary:focus-visible {
  background: color-mix(in srgb, var(--flow) 84%, white);
  border-color: transparent;
  color: var(--ground);
}

.install {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--dim);
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 1px 0 var(--glass-edge);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 0.55rem 0.8rem;
}

/* A rule under the hero, with a single marker at the origin. Static on purpose:
   the animated scenes below are the ones making an argument, and ambient drift
   up here would only compete with them. */
.wire { position: relative; height: 2px; margin-top: 3.5rem; }
.wire-line { position: absolute; inset: 0; background: var(--line); }
.wire::before {
  content: "";
  position: absolute;
  left: 0; top: -2px;
  width: 34px; height: 6px;
  border-radius: 3px;
  background: var(--flow);
}

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

.split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: 1rem;
}

.card, .lane {
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 1.2rem;
  box-shadow: inset 0 1px 0 var(--glass-edge), 0 18px 40px -30px var(--shadow);
}
.card { margin: 0; }
.card figcaption { display: grid; gap: 0.25rem; margin-top: 1rem; }
.card figcaption strong { font-size: 0.92rem; }
.card figcaption span { font-size: 0.86rem; color: var(--dim); }

/* ------------------------------------------------------------ mini scenes */

.mini-scene, .flow {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 0.6rem;
}

.node {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  padding: 0.4rem 0.55rem;
  color: var(--dim);
  /* Inside a lane, which already blurs. A backdrop-filter within a
     backdrop-filter samples the parent pane rather than the page and comes out
     muddy, so every nested surface on this page takes a fill and no blur. */
  background: color-mix(in srgb, var(--surface-2) 62%, transparent);
  border: 1px solid var(--line);
  border-radius: var(--r);
  white-space: nowrap;
}
.node.done { color: var(--flow); border-color: var(--flow); }

.track {
  position: relative;
  height: 2px;
  background: var(--line);
  border-radius: 1px;
}

.dot {
  position: absolute;
  top: -4px;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--flow);
  opacity: 0;
}

/* The two indistinguishable failures from the problem section */
.dot.fail-out   { animation: failOut 3s ease-in-out infinite; background: var(--fail); }
.dot.ok-out     { animation: okOut 3s ease-in-out infinite; }
.dot.fail-back  { animation: failBack 3s ease-in-out infinite; background: var(--fail); }

@keyframes failOut {
  0%        { left: 0;   opacity: 0; }
  10%       { left: 0;   opacity: 1; }
  45%       { left: 45%; opacity: 1; }
  60%, 100% { left: 45%; opacity: 0; }
}
@keyframes okOut {
  0%        { left: 0;    opacity: 0; }
  8%        { left: 0;    opacity: 1; }
  35%       { left: 100%; opacity: 1; }
  40%, 100% { left: 100%; opacity: 0; }
}
@keyframes failBack {
  0%, 42%   { left: 100%; opacity: 0; }
  48%       { left: 100%; opacity: 1; }
  75%       { left: 55%;  opacity: 1; }
  85%, 100% { left: 55%;  opacity: 0; }
}

/* ------------------------------------------------------------------ scenes */

.scene { margin-top: 0.5rem; }
.scene-head { display: flex; justify-content: flex-end; margin-bottom: 0.9rem; }

.lane-title {
  margin: 0 0 1rem;
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim);
}
.lane-title.mono { font-family: var(--mono); text-transform: none; letter-spacing: 0; font-size: 0.8rem; }
.lane-bad  { border-color: color-mix(in srgb, var(--fail) 35%, var(--line)); }
.lane-good { border-color: color-mix(in srgb, var(--flow) 35%, var(--line)); }
.lane-bad  .lane-title { color: var(--fail); }
.lane-good .lane-title { color: var(--flow); }
.lane-note { margin: 1rem 0 0; font-size: 0.85rem; color: var(--dim); }

/* Idempotency scene: two deliveries, one shield */
.flow .dot { opacity: 0; }
.scene.playing .flow .dot.d1 { animation: deliver 1.1s ease-in-out 0.2s forwards; }
.scene.playing .lane-bad  .flow .dot.d2 { animation: deliver 1.1s ease-in-out 2.2s forwards; }
.scene.playing .lane-good .flow .dot.d2 { animation: blocked 1.4s ease-in-out 2.2s forwards; }

@keyframes deliver {
  0%   { left: 0;    opacity: 1; }
  90%  { left: 100%; opacity: 1; }
  100% { left: 100%; opacity: 0; }
}
@keyframes blocked {
  0%   { left: 0;   opacity: 1; background: var(--flow); }
  55%  { left: 62%; opacity: 1; background: var(--flow); }
  70%  { left: 62%; opacity: 1; background: var(--hold); }
  100% { left: 62%; opacity: 0; background: var(--hold); }
}

.shield {
  position: absolute;
  left: 62%;
  top: -11px;
  width: 3px; height: 24px;
  border-radius: 2px;
  background: var(--hold);
  opacity: 0;
  transform: scaleY(0.3);
  transition: opacity 0.2s, transform 0.2s;
}
.scene.playing .lane-good .shield { animation: shieldUp 1.2s ease-out 2.5s forwards; }
@keyframes shieldUp {
  0%        { opacity: 0; transform: scaleY(0.3); }
  25%       { opacity: 1; transform: scaleY(1); }
  75%       { opacity: 1; transform: scaleY(1); }
  100%      { opacity: 0.45; transform: scaleY(1); }
}

.counter { margin: 1.1rem 0 0; display: flex; align-items: baseline; gap: 0.45rem; }
.count {
  font-family: var(--mono);
  font-size: 1.9rem;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.lane-bad  .count { color: var(--fail); }
.lane-good .count { color: var(--flow); }
.count.bump { animation: bump 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes bump { 0% { transform: scale(1); } 45% { transform: scale(1.35); } 100% { transform: scale(1); } }
.count-label { font-size: 0.78rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--dim); }

/* Step list, revealed as the scene runs */
.steps { margin: 1.5rem 0 0; padding: 0; list-style: none; counter-reset: s; display: grid; gap: 0.5rem; }
.steps li {
  counter-increment: s;
  display: grid;
  grid-template-columns: 1.6rem 1fr;
  gap: 0.6rem;
  font-size: 0.88rem;
  color: var(--dim);
  opacity: 0.35;
  transition: opacity 0.3s, color 0.3s;
}
.steps li::before {
  content: counter(s);
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--dim);
  border: 1px solid var(--line);
  border-radius: var(--r);
  text-align: center;
  line-height: 1.5rem;
  height: 1.5rem;
}
.steps li.on { opacity: 1; color: var(--text); }
.steps li.on::before { color: var(--flow); border-color: var(--flow); }
.steps li[data-step="4"] { display: none; }
.steps li[data-step="4"].on { display: grid; }
/* Step 4 carries two outcomes. Both are direct grid children, so they must be
   pinned to the text column or the second one drops into the number column. */
.steps li .bad, .steps li .good { display: block; grid-column: 2; }
.steps li .bad  { color: var(--fail); }
.steps li .good { color: var(--flow); }

/* Retry scene: eight clients returning */
.herd {
  position: relative;
  height: 5.5rem;
  background: color-mix(in srgb, var(--surface-2) 62%, transparent);
  border-radius: var(--r);
  overflow: hidden;
}
.herd .client {
  position: absolute;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--hold);
  left: 0;
  opacity: 0;
  transition: left 1.4s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.3s;
}
.scene.playing .herd .client { opacity: 1; }
.herd[data-herd="fixed"]  .client { background: var(--fail); }
.herd[data-herd="jitter"] .client { background: var(--flow); }
/* The spike a synchronised herd creates, drawn only in the fixed lane */
.herd .spike {
  position: absolute;
  bottom: 0;
  width: 3px;
  background: var(--fail);
  opacity: 0;
  border-radius: 2px 2px 0 0;
  transition: opacity 0.4s, height 0.6s;
}
.axis {
  display: flex;
  justify-content: space-between;
  margin-top: 0.4rem;
  font-family: var(--mono);
  font-size: 0.65rem;
  color: var(--dim);
}

/* Atomicity scene */
.race { display: grid; gap: 0.5rem; }
.worker, .db {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.55rem 0.7rem;
  background: color-mix(in srgb, var(--surface-2) 62%, transparent);
  border: 1px solid var(--line);
  border-radius: var(--r);
  font-family: var(--mono);
  font-size: 0.74rem;
}
.db { justify-content: center; color: var(--dim); }
.tag { color: var(--dim); }
.verdict { font-weight: 700; opacity: 0; transition: opacity 0.3s; }
.verdict.show { opacity: 1; }
.verdict.won  { color: var(--flow); }
.verdict.lost { color: var(--dim); }
.worker.flash { border-color: var(--hold); }
.lane-bad .verdict.won { color: var(--fail); }

/* ------------------------------------------------------------------ notes */

/* No one-sided accent bar. A 3px stripe down the left edge is a stock
   admonition from every documentation theme, and on a page whose whole
   vocabulary is jade for flow and coral for failure it also spends a colour
   without saying anything with it.

   The kind is carried the way the lanes already carry it: the border is tinted
   all the way round, and the heading takes the colour. That is one rule for
   the page rather than two, and it survives a glass panel, where a hard stripe
   against a blurred edge reads as a rendering fault. */
.note {
  margin-top: 1.6rem;
  padding: 1.1rem 1.2rem;
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid color-mix(in srgb, var(--flow) 35%, var(--line));
  border-radius: var(--r);
  box-shadow: inset 0 1px 0 var(--glass-edge), 0 18px 40px -30px var(--shadow);
}
.note strong {
  display: block;
  margin-bottom: 0.5rem;
  font-size: 0.95rem;
  color: var(--flow);
}
.note-warn { border-color: color-mix(in srgb, var(--hold) 35%, var(--line)); }
.note-warn strong { color: var(--hold); }
.note p { margin: 0; font-size: 0.9rem; color: var(--dim); max-width: 62ch; }

/* Sits inside cards and notes, so no blur of its own. */
.code {
  margin: 0.7rem 0;
  padding: 0.7rem 0.85rem;
  background: color-mix(in srgb, var(--ground) 62%, transparent);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow-x: auto;
}
.code code { font-family: var(--mono); font-size: 0.78rem; color: var(--flow); }

/* ------------------------------------------------------------------ ledger */

.ledger { margin: 0 0 1.8rem; padding: 0; list-style: none; display: grid; gap: 0.1rem; max-width: 34rem; }
.ledger li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 0;
  border-bottom: 1px dotted var(--line);
  font-size: 0.92rem;
  color: var(--dim);
}
.ledger li em {
  font-style: normal;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.ledger li.is-done { color: var(--text); }
.ledger li.is-done em { color: var(--flow); }

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

.foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6rem;
  padding: 1.6rem 1rem 2.2rem;
  border-top: 1px solid var(--line);
  font-size: 0.8rem;
  color: var(--dim);
}
.foot .sep { opacity: 0.5; }
.foot a { color: var(--text); text-decoration: none; border-bottom: 1px solid var(--line); }
.foot a:hover, .foot a:focus-visible { color: var(--flow); border-bottom-color: var(--flow); }

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

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

/* Glass is a readability cost somebody may have already told their machine
   they cannot pay. macOS "Reduce transparency" and its Windows equivalent both
   report here, and the honest response is to stop rather than to blur a little
   less: every panel goes solid, every blur comes off, the field goes. Nothing
   moves and nothing reflows, because the fills are the same colours at full
   strength. */
@media (prefers-reduced-transparency: reduce) {
  :root {
    --glass:      var(--surface);
    --glass-thin: var(--surface);
    --glass-edge: transparent;
  }
  .topbar, .card, .lane, .note, .install, .code, .pill, .icon-btn {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .topbar { background: var(--ground); }
  .code { background: var(--ground); }
  .node, .herd, .worker, .db { background: var(--surface-2); }
  body { background-image: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  /* With motion off the scenes must still make their point, so every step is
     shown at once rather than revealed over time. */
  .steps li { opacity: 1; color: var(--text); }
  .steps li[data-step="4"] { display: grid; }
  .verdict { opacity: 1; }
}
