/* =====================================================================
   Hub documentation pages: architecture.html and stack.html
   =====================================================================

   WHY THIS IS A FILE AND NOT A <style> BLOCK. Every other page here is
   self-contained, which is right for pages that share nothing. These two
   share everything: the same chrome, the same node-map system, the same
   card grid. Two copies of six hundred lines would have drifted apart by
   the second edit.

   THE TOKENS BELOW MIRROR index.html'S :root AND MUST MOVE WITH IT. They
   are duplicated rather than imported because the hub keeps its CSS inline
   and refactoring an approved page to import a stylesheet is a bigger
   change than these pages justify. If a brand value changes, it changes in
   both places. Nothing else here is duplicated.

   No build step, no framework, no runtime dependency: these pages are
   plain HTML and this file, the same as the rest of the hub.
   ===================================================================== */

*, *::before, *::after { box-sizing: border-box; }

:root {
  --orange: #FF9300;
  --blue:   #00A1FF;
  --indigo: #3200BE;
  --navy:   #0F0B3D;

  --paper:      #fbfaf7;
  --surface:    #ffffff;
  --ink:        #14112e;
  --muted:      #57536b;
  --faint:      #7a7690;
  --line:       #e6e3da;
  --line-2:     #d8d4c8;
  --accent-ink: #8F4500;

  --d-ink:   #eceaf6;
  --d-muted: #a8a5c0;
  --d-faint: #75728f;
  --d-line:  #262250;

  --mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
  --sans: "Schibsted Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
  --r: 10px;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);

  /* Node-map palette. Deliberately NOT the brand orange for most of it:
     these hues are labels, and a map that colours half its nodes in the
     brand colour reads as emphasis rather than as classification. */
  --n-route:   #00A1FF;
  --n-content: #7C5CFF;
  --n-build:   #14A06B;
  --n-deploy:  #E8792B;
  --n-meta:    #3200BE;
  --n-quiet:   #8b8798;

  /* Connector grey. A step darker than --line-2, which is a hairline for
     borders and disappeared at 1.5px against the map's dotted panel. */
  --wire: #c2bcac;
}

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

.wrap { max-width: 1120px; margin: 0 auto; padding: 0 clamp(22px, 5vw, 48px); }

/* Entrance, matched to the hub: a small rise, staggered by --i, and fully
   disabled for anyone who asked for less motion. */
.rise { opacity: 0; transform: translateY(14px); }
body.on .rise {
  opacity: 1; transform: none;
  transition: opacity .6s var(--ease-out), transform .6s var(--ease-out);
  transition-delay: calc(var(--i, 0) * 90ms);
}
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  .rise { opacity: 1 !important; transform: none !important; }
}
/* Without JS the body never gets .on, so everything above would stay at
   opacity 0. Print has the same problem. */
@media print { .rise { opacity: 1 !important; transform: none !important; } }

/* ---------- cover ---------- */
.cover {
  background: var(--navy);
  color: var(--d-ink);
  padding: clamp(22px, 4vh, 34px) 0 clamp(34px, 6vh, 60px);
}
.brandline {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 18px; margin-bottom: clamp(30px, 6vh, 62px);
}
.de { font-family: var(--mono); font-size: 12px; letter-spacing: .18em; color: #fff; font-weight: 500; }
.de span { color: var(--d-faint); }
.tag { font-family: var(--mono); font-size: 11px; letter-spacing: .14em; color: var(--d-faint); text-align: right; }

/* The way back. Quiet on purpose: someone reading this arrived from the
   hub and knows where they came from; it is a way back, not a menu. */
.back {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--mono); font-size: 11.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--d-faint); text-decoration: none;
  transition: color .15s ease;
}
.back:hover { color: var(--d-ink); }
.back:focus-visible { outline: 2px solid var(--orange); outline-offset: 3px; border-radius: 4px; }
.back svg { width: 13px; height: 13px; }

.kicker {
  font-family: var(--mono); font-size: 12px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--orange); margin: 22px 0 14px;
}
.cover h1 {
  margin: 0; font-size: clamp(28px, 4.4vw, 50px); font-weight: 800;
  letter-spacing: -0.035em; line-height: 1.04; color: #fff; text-wrap: balance;
}
.cover .sub {
  margin: 16px 0 0; max-width: 66ch; color: var(--d-muted);
  font-size: clamp(14.5px, 1.35vw, 17px);
}
.cover .sub b { color: var(--d-ink); font-weight: 600; }

/* ---------- section rhythm ---------- */
main { padding-bottom: clamp(40px, 7vh, 80px); }
section { padding: clamp(38px, 6vh, 70px) 0 0; }
.eyebrow {
  font-size: 12.5px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--faint); margin-bottom: 14px;
}
h2 {
  font-size: clamp(22px, 2.6vw, 32px); font-weight: 700;
  letter-spacing: -0.03em; line-height: 1.1; margin: 0; text-wrap: balance;
}
.lede {
  margin-top: 12px; font-size: clamp(14.5px, 1.35vw, 17px);
  color: var(--muted); max-width: 68ch;
}
.lede b { color: var(--ink); font-weight: 650; }
.note {
  margin-top: 14px; font-size: 13.5px; color: var(--faint); max-width: 74ch;
}
.note a, .lede a { color: var(--ink); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }

/* =====================================================================
   THE NODE MAP
   =====================================================================
   A tree drawn with CSS grid and two pseudo-elements, no SVG and no
   layout JS.

   HOW THE CONNECTORS WORK, because it is not obvious and it is easy to
   break. Every tier is a grid of equal columns, so the centre of the
   first column sits at 100%/(2N) from the left and the last at the same
   distance from the right, where N is the column count. That is exactly
   the inset the horizontal bus needs, so the bus is drawn with those two
   values and lands on the middle of the outermost cards at any width.
   Each tier carries its own --cols; get that number wrong and the bus
   overshoots or falls short. A one-column tier collapses the bus to zero
   width, which is correct: a single child needs a stem, not a bus.
   ===================================================================== */

.map {
  position: relative;
  margin-top: clamp(24px, 3.5vh, 36px);
  padding: clamp(22px, 3.2vw, 40px) clamp(16px, 2.6vw, 34px) clamp(18px, 2.6vw, 28px);
  border: 1px solid var(--line);
  border-radius: 14px;
  background-color: #f7f6f2;
  /* The dotted field. Two layers: the dots, then a soft wash so the
     panel does not read as flat grey behind the cards. */
  background-image:
    radial-gradient(var(--line-2) 1px, transparent 1px),
    linear-gradient(180deg, #faf9f6, #f4f3ee);
  background-size: 22px 22px, 100% 100%;
}
/* THE SCROLL LIVES HERE, NOT ON .map. Setting overflow-x on the panel
   itself computes overflow-y to auto as well, which is a CSS rule that is
   easy to forget: the nested tiers were then clipped and the panel grew a
   scrollbar down its right edge. This wrapper holds only the diagram, so
   the same computed overflow-y is harmless. */
.map-scroll { overflow-x: auto; padding-bottom: 2px; }
/* The map is the one thing on these pages that cannot reflow below a
   certain width without becoming unreadable, so it scrolls inside its own
   box rather than forcing the page to. */
.map-inner { min-width: 780px; }
@media (max-width: 860px) { .map-inner { min-width: 620px; } }

.root {
  position: relative;
  width: max-content; max-width: 100%;
  margin: 0 auto;
  padding: 13px 22px;
  background: #14112e;
  border-radius: 12px;
  box-shadow: 0 18px 34px -22px rgba(15, 11, 61, .8);
}
.root b { display: block; color: #fff; font-size: 15.5px; font-weight: 700; letter-spacing: -0.01em; }
.root span { display: block; margin-top: 2px; font-family: var(--mono); font-size: 11.5px; color: #9c98b4; }

/* The stem out of the bottom of a node, into the bus below it. */
.root::after,
.node::before,
.tier::before { content: ""; position: absolute; background: var(--wire); }
.root::after { top: 100%; left: 50%; width: 1.5px; height: 22px; transform: translateX(-50%); }

.tier {
  position: relative;
  display: grid;
  gap: clamp(10px, 1.2vw, 16px);
  margin-top: 44px;
  grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));
  align-items: start;
}
.tier::before {
  top: -22px; height: 1.5px;
  left: calc(100% / (2 * var(--cols, 1)));
  right: calc(100% / (2 * var(--cols, 1)));
}
.node { position: relative; }
.node::before { top: -22px; left: 50%; width: 1.5px; height: 22px; transform: translateX(-50%); }

/* A tier nested under a single parent card: shorter drop, no bus. */
.tier.sub { margin-top: 30px; }
.tier.sub::before { display: none; }
.tier.sub > .node::before { top: -30px; height: 30px; }

.card {
  padding: 12px 14px 13px;
  background: var(--surface);
  border: 1.5px solid var(--c, var(--line-2));
  border-radius: 11px;
  box-shadow: 0 10px 22px -18px rgba(15, 11, 61, .45);
}
.card .lab {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .11em;
  text-transform: uppercase; color: var(--faint); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.card .ttl {
  margin-top: 5px; font-size: 14.5px; font-weight: 700;
  letter-spacing: -0.015em; color: var(--c, var(--ink)); line-height: 1.2;
}
.card .path {
  margin-top: 4px; font-family: var(--mono); font-size: 10.5px;
  color: var(--faint); line-height: 1.4; word-break: break-word;
}
.card .badges { margin-top: 9px; display: flex; flex-wrap: wrap; gap: 5px; }

/* Type badges. The shape carries the meaning as well as the colour, so
   the classification still reads in greyscale and for anyone who cannot
   separate these hues: solid pill, outlined pill, italic serif-ish. */
.b {
  display: inline-block; padding: 3px 7px; border-radius: 5px;
  font-family: var(--mono); font-size: 9px; font-weight: 600;
  letter-spacing: .09em; text-transform: uppercase; white-space: nowrap;
}
.b.static  { background: #eceaf0; color: #5a5670; }
.b.archive { background: rgba(0, 161, 255, .13); color: #0069a8; }
.b.tmpl    { background: rgba(20, 160, 107, .14); color: #0b7a4f; font-style: italic; text-transform: none; letter-spacing: .04em; }
.b.conv    { background: rgba(50, 0, 190, .1); color: #3200BE; }
.b.gen     { background: rgba(232, 121, 43, .14); color: #9a4a11; }
.b.count   { background: #eceaf0; color: #5a5670; }

/* A group of small nodes that share one parent slot. */
.cluster {
  display: grid; gap: 8px;
  grid-template-columns: repeat(var(--sub, 3), minmax(0, 1fr));
  padding: 9px;
  border: 1px dashed var(--line-2);
  border-radius: 11px;
  background: rgba(255, 255, 255, .5);
}
.cluster .card { box-shadow: none; padding: 10px 11px 11px; }
.cluster .card .ttl { font-size: 13px; }
/* The label above a stacked cluster, naming what the group is. */
.cluster .chd {
  grid-column: 1 / -1;
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .11em;
  text-transform: uppercase; color: var(--faint); padding: 1px 2px 3px;
}

/* Body copy inside a map card, for the tiers that explain rather than
   enumerate. Sits below .path and above .badges if both are present. */
.card .cbody {
  margin: 8px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--muted);
}
/* A tier whose nodes hang off nothing: used where the three cards are a
   set rather than children of a parent above them. */
.node[style*="--noline"]::before { display: none; }

/* ---------- the pipeline flow ----------
   Left to right at desk width, top to bottom on a phone. The arrow between
   steps is a pseudo-element on the step itself rather than a separate
   element, so the markup stays a plain list of steps. */
.flow {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 26px);
}
.step { position: relative; display: flex; flex-direction: column; }
.step + .step::before {
  content: "";
  position: absolute; top: 46px;
  left: calc(-1 * clamp(16px, 2vw, 26px)); width: clamp(16px, 2vw, 26px);
  height: 1.5px; background: var(--wire);
}
/* The arrowhead, drawn as a rotated corner so it needs no extra markup. */
.step + .step::after {
  content: "";
  position: absolute; top: 42.5px; left: -5px;
  width: 7px; height: 7px;
  border-top: 1.5px solid var(--wire); border-right: 1.5px solid var(--wire);
  transform: rotate(45deg);
}
.step .when {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--accent-ink);
  margin-bottom: 8px; height: 14px;
}
.step .card { flex: 1; }
.step .card p {
  margin: 8px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--muted);
}

@media (max-width: 900px) {
  .flow { grid-template-columns: 1fr; gap: 26px; }
  .step + .step::before {
    top: -26px; left: 50%; width: 1.5px; height: 26px;
  }
  .step + .step::after { top: -8px; left: calc(50% - 3px); transform: rotate(135deg); }
}

.map-note {
  display: flex; gap: 8px; align-items: flex-start;
  margin-top: 22px; padding-top: 14px;
  border-top: 1px dashed var(--line-2);
  font-size: 12px; color: var(--faint); line-height: 1.55;
}
.map-note svg { width: 13px; height: 13px; flex: none; margin-top: 2px; color: var(--accent-ink); }
.map-note code { font-family: var(--mono); font-size: 11px; color: var(--muted); }

/* ---------- legend ---------- */
.legend {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px clamp(12px, 1.6vw, 22px);
  margin-top: 16px; font-size: 12px; color: var(--faint);
}
.legend .grp { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.legend .hd { font-weight: 600; color: var(--muted); }
.legend .sw { display: inline-flex; align-items: center; gap: 6px; }
.legend .sw i { width: 11px; height: 11px; border-radius: 3px; background: var(--c); flex: none; }

/* =====================================================================
   THE STACK GRID
   ===================================================================== */
.groupline {
  display: flex; align-items: baseline; gap: 12px;
  margin-top: clamp(30px, 4.5vh, 48px);
}
.groupline h3 {
  margin: 0; font-size: 17px; font-weight: 700; letter-spacing: -0.02em;
}
.groupline .rule { flex: 1; height: 1px; background: var(--line); }
.groupline .cnt { font-family: var(--mono); font-size: 11px; color: var(--faint); }

.stack { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(12px, 1.6vw, 18px); margin-top: 16px; }
.tech {
  display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 14px;
  padding: 16px 18px 17px;
  background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--r);
  box-shadow: 0 14px 30px -26px rgba(15, 11, 61, .5);
}
.tech .mark {
  width: 40px; height: 40px; border-radius: 9px;
  display: grid; place-items: center;
  background: #f6f5f1; border: 1px solid var(--line);
}
.tech .mark svg { width: 22px; height: 22px; display: block; }
/* Wordmark tiles, for the libraries whose real logo is type rather than a
   shape. Drawing an approximation of someone else's logotype is worse than
   setting their name honestly, so these are set, not traced. The size is
   whatever fits the word in a 40px tile, which is why it varies. */
.tech .mark .wm { font-weight: 700; letter-spacing: -0.02em; line-height: 1; color: var(--ink); }
.tech .mark .wm.xs { font-size: 8.5px; }
.tech .mark .wm.sm { font-size: 9.5px; }
.tech .mark .wm.md { font-size: 10.5px; }
.tech .mark .wm.lg { font-size: 11.5px; font-weight: 800; letter-spacing: -0.03em; }
.tech .mark.dark { background: #111; border-color: #000; }
.tech .mark.dark .wm { color: #fff; }
.tech .mark.deep { background: #0F0B3D; border-color: #0F0B3D; }
.tech .mark.deep .wm { color: #fff; }
.tech .mark.lime { background: #0AE448; border-color: #0ac93f; }
.tech .mark.lime .wm { color: #0F0B3D; }
.tech .mark .none { font-size: 15px; color: var(--faint); line-height: 1; }
.tech h4 {
  margin: 0; font-size: 16.5px; font-weight: 700; letter-spacing: -0.02em;
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.tech .ver { font-family: var(--mono); font-size: 10.5px; font-weight: 500; color: var(--faint); }
/* A label, not a subheading: uppercase and tracked so it reads as a
   different kind of thing from the sentence below it rather than as body
   copy that happens to be a point and a half smaller. */
.tech .role {
  margin-top: 5px; font-size: 10.5px; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--accent-ink);
}
.tech p { margin: 7px 0 0; font-size: 13.5px; color: var(--muted); line-height: 1.55; }
.tech p + p { margin-top: 6px; }
.tech .tell {
  margin-top: 11px; padding-top: 10px; border-top: 1px dashed var(--line);
  font-size: 12.5px; color: var(--faint); line-height: 1.5;
}
.tech .tell b { color: var(--muted); font-weight: 600; }

/* A row that is explicitly about something NOT used. */
.tech.absent { background: transparent; border-style: dashed; box-shadow: none; }
.tech.absent .mark { background: transparent; border-style: dashed; }

/* THE ORPHAN. A group with an odd number of cards left the last one alone
   in the left column with a card's worth of empty space beside it, which is
   the same "unfinished" read a half-empty section always gives. The last
   card of an odd group takes the full width instead, and its prose runs in
   two columns so the extra width is used rather than turned into a
   sixteen-word measure. The heading spans both columns so it still reads as
   one card and not as two.

   :last-child:nth-child(odd) is the whole condition: a card that is both
   the last one and in an odd position can only be the odd one out. */
.stack > .tech:last-child:nth-child(odd) { grid-column: 1 / -1; }
@media (min-width: 941px) {
  .stack > .tech:last-child:nth-child(odd) > div:last-child {
    column-count: 2; column-gap: clamp(20px, 2.4vw, 34px);
  }
  .stack > .tech:last-child:nth-child(odd) h4,
  .stack > .tech:last-child:nth-child(odd) .role { column-span: all; }
  /* Without this the first paragraph inherits the top margin that only
     makes sense when it follows the label in a single column. */
  .stack > .tech:last-child:nth-child(odd) .role { margin-bottom: 9px; }
  .stack > .tech:last-child:nth-child(odd) p:first-of-type { margin-top: 0; }
  /* A rule across a column boundary reads as a divider between the two
     columns rather than as the top of the note, so it becomes a lead-in. */
  .stack > .tech:last-child:nth-child(odd) .tell {
    break-inside: avoid; border-top: none; padding-top: 0; margin-top: 10px;
  }
}

/* ---------- facts strip ---------- */
.facts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; margin-top: 22px; background: var(--line); border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
.facts .f { background: var(--paper); padding: 15px 16px; }
.facts .n { font-size: clamp(19px, 2.2vw, 26px); font-weight: 800; letter-spacing: -0.03em; line-height: 1; }
.facts .k { margin-top: 6px; font-size: 12px; color: var(--faint); line-height: 1.4; }

/* ---------- next / cross-link ---------- */
.next {
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  margin-top: clamp(34px, 5vh, 56px); padding: 18px 20px;
  border: 1px solid var(--line-2); border-radius: var(--r);
  background: var(--surface); text-decoration: none;
  transition: transform .18s var(--ease-out), box-shadow .18s var(--ease-out);
}
.next:hover { transform: translateY(-2px); box-shadow: 0 22px 40px -30px rgba(15, 11, 61, .5); }
.next:focus-visible { outline: 2px solid var(--indigo); outline-offset: 3px; }
.next .k { font-family: var(--mono); font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase; color: var(--faint); }
.next .t { margin-top: 4px; font-size: 16px; font-weight: 700; letter-spacing: -0.02em; }
.next .arrow { flex: none; color: var(--accent-ink); }
.next .arrow svg { width: 20px; height: 20px; display: block; }

/* ---------- footer ---------- */
footer { background: var(--navy); color: var(--d-muted); margin-top: clamp(40px, 7vh, 76px); padding: clamp(30px, 5vh, 52px) 0; }
footer .contact { font-size: 13.5px; color: var(--d-muted); display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; }
footer .contact b { color: var(--d-ink); }
footer .contact a { color: var(--d-muted); text-decoration: underline; text-underline-offset: 3px; }
footer .contact a:hover { color: var(--d-ink); }
footer .sep { color: var(--d-faint); }
footer .copy { margin-top: 14px; font-family: var(--mono); font-size: 11px; letter-spacing: .06em; color: var(--d-faint); }

/* ---------- responsive ---------- */
@media (max-width: 940px) {
  .stack { grid-template-columns: 1fr; }
  .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .brandline { flex-wrap: wrap; gap: 6px 18px; }
  .tag { text-align: left; }
}
@media (max-width: 560px) {
  .facts { grid-template-columns: 1fr; }
  .tech { grid-template-columns: 1fr; gap: 10px; }
}
