/* Andrew Tabb - personal one-page portfolio (tabblabs.net/andrew).
   DISTINCT from the Tabb Labs company site (../index.html, dark v4). This is a LIGHT, rich
   multi-hue gradient FIELD per the accepted recipe: ~14 soft, big, OVERLAPPING radial blooms
   (transparent ~74%) on a tinted base, cool top -> violet/green mid -> one warm amber accent low.
   No line art. Clean type, generous spacing, white cards floating over the field.
   Field passes kit/check-gradient-presence.cjs (>=8 blooms, strong blooms, tinted base). */

:root {
  --ink: #171423;
  --ink-soft: #453f59;
  /* 2026-07-28 WCAG AA fix (Andrew-directed via pendo work-order). Was #7b7492, which fails AA for
     small text on EVERY surface this page paints it on: 4.42:1 on a white card and 3.50-3.78:1 on the
     four gradient-field stops. It carries the .mono eyebrows, .idx, .legend, .fn-head and .foot, all
     small and several of them bold, so none of them qualify for the large-text allowance (bold only
     counts as large at 18.66px+). #655e7d measures 6.09:1 on the card and 4.83:1 at the worst field
     stop. It is also the disabled-CTA background, which improves 4.42 -> 6.09 for its white label.
     Measured against the real CSS values, not the config brand tokens - which is exactly why no gate
     caught this: see failure-archive/2026-07-28_tabblabs-andrew_inherited-contrast-failure-*.md */
  --ink-faint: #655e7d;
  --line: rgba(23, 20, 35, 0.10);
  --line-soft: rgba(23, 20, 35, 0.07);
  --card: rgba(255, 255, 255, 0.74);
  --card-solid: rgba(255, 255, 255, 0.86);
  --accent: #5b3df5;
  --accent2: #e0487f;
  --accent3: #37b6d6;
  --ok: #0f8a4e;
  --sans: 'Segoe UI', system-ui, -apple-system, Roboto, 'Helvetica Neue', Arial, sans-serif;
  --mono: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
  --maxw: 1080px;
  --r: 22px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
  font-family: var(--sans); color: var(--ink); line-height: 1.6;
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
  background: #efe9f8; position: relative; overflow-x: hidden;
}

/* ---- the light rich multi-hue gradient FIELD (14 overlapping blooms + tinted base) ---- */
/* horizontal inset kept at 0 so the field never widens the page (an -8% h-bleed added ~8%
   horizontal scroll: 115px @1440 / 31px @390).
   2026-07-29: the VERTICAL bleed is now px, not %. It was `-6% 0 -4%`, and the note above that a vertical
   bleed "is safe" was wrong in one direction: this is an absolutely positioned child, so it extends the
   body's SCROLLABLE area. -4% of this document is 433px at 1280 and 357px at 390 — dead empty gradient
   after the footer that the reader has to scroll through. A px bleed does not scale with document length.
   Verified safe for the scroll features before changing: at max scroll every track already finishes
   780px+ past the viewport top, so none of them was using that space as runway, and the progress bar
   (docH = scrollHeight - vh) now reaches 100% at the true end of the content instead of short of it. */
body::before {
  content: ""; position: absolute; inset: -60px 0 -40px 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(1300px 1180px at 16% 3%,  rgba(120, 92, 255, 0.34), transparent 74%),
    radial-gradient(1200px 1100px at 84% 6%,  rgba(90, 190, 235, 0.30), transparent 74%),
    radial-gradient(1150px 1050px at 46% 12%, rgba(150, 120, 255, 0.26), transparent 74%),
    radial-gradient(1250px 1150px at 92% 20%, rgba(255, 120, 175, 0.24), transparent 74%),
    radial-gradient(1200px 1100px at 8%  27%, rgba(120, 150, 255, 0.26), transparent 74%),
    radial-gradient(1350px 1240px at 62% 32%, rgba(150, 205, 235, 0.24), transparent 74%),
    radial-gradient(1200px 1100px at 30% 40%, rgba(170, 130, 255, 0.24), transparent 74%),
    radial-gradient(1250px 1150px at 86% 47%, rgba(120, 220, 200, 0.22), transparent 74%),
    radial-gradient(1350px 1230px at 12% 55%, rgba(150, 122, 250, 0.27), transparent 74%),
    radial-gradient(1300px 1200px at 58% 62%, rgba(115, 205, 200, 0.26), transparent 74%),
    radial-gradient(1550px 1420px at 86% 70%, rgba(255, 165, 110, 0.31), transparent 76%),
    radial-gradient(1500px 1380px at 20% 74%, rgba(150, 118, 255, 0.31), transparent 76%),
    radial-gradient(1300px 1200px at 68% 84%, rgba(255, 140, 185, 0.28), transparent 74%),
    radial-gradient(1600px 1460px at 82% 88%, rgba(110, 195, 238, 0.29), transparent 76%),
    radial-gradient(1550px 1420px at 40% 92%, rgba(255, 158, 120, 0.30), transparent 76%),
    radial-gradient(1300px 1200px at 30% 96%, rgba(180, 135, 255, 0.26), transparent 74%),
    linear-gradient(168deg, #ece6f7 0%, #e6eefb 38%, #e9e2f4 70%, #efe6f2 100%);
  background-repeat: no-repeat;
}

.wrap { width: min(var(--maxw), 92vw); margin: 0 auto; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
.mono {
  font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.15em;
  font-size: 0.7rem; font-weight: 700; color: var(--ink-faint);
}

.skip { position: absolute; left: -999px; top: 0; background: var(--ink); color: #fff;
  padding: 10px 16px; border-radius: 0 0 10px 0; z-index: 60; font-weight: 700; }
.skip:focus { left: 0; }
:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; border-radius: 6px; }

/* ---- header ---- */
header { position: sticky; top: 0; z-index: 30; padding: 16px 0;
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  background: rgba(243, 239, 251, 0.6); border-bottom: 1px solid var(--line); }
.bar { display: flex; align-items: center; justify-content: space-between; gap: 18px; }
.brand { display: flex; align-items: center; gap: 11px; font-weight: 800; font-size: 1.05rem; letter-spacing: -0.02em; }
.brand .spark { width: 26px; height: 26px; border-radius: 8px; flex: none;
  background: conic-gradient(from 210deg, var(--accent), var(--accent2), var(--accent3), var(--accent));
  box-shadow: 0 4px 14px rgba(91, 61, 245, 0.35); }
nav ul { display: flex; gap: 24px; list-style: none; font-weight: 600; font-size: 0.9rem; }
nav a { color: var(--ink-soft); } nav a:hover { color: var(--ink); }
.pill { padding: 9px 16px; border-radius: 999px; background: var(--ink); color: #fff;
  font-weight: 700; font-size: 0.86rem; box-shadow: 0 8px 20px rgba(23, 20, 35, 0.22); }

/* ---- hero ---- */
.hero { padding: 92px 0 40px; text-align: center; }
.hero .eyebrow { display: inline-flex; align-items: center; gap: 10px; padding: 7px 15px;
  border-radius: 999px; background: rgba(255, 255, 255, 0.62); border: 1px solid var(--line); margin-bottom: 26px; }
.hero h1 { font-size: clamp(2.7rem, 7vw, 5rem); line-height: 1.0; letter-spacing: -0.035em; font-weight: 800; }
.hero h1 .grad { background: linear-gradient(120deg, var(--accent), var(--accent2) 62%, var(--accent3));
  -webkit-background-clip: text; background-clip: text; color: transparent; }
.hero .role { max-width: 640px; margin: 22px auto 0; font-size: 1.16rem; font-weight: 600; color: var(--ink-soft); }
.hero .lede { max-width: 620px; margin: 14px auto 0; font-size: 1.08rem; color: var(--ink-soft); font-style: italic; }
.hero .cta { display: flex; gap: 14px; justify-content: center; margin-top: 34px; flex-wrap: wrap; }

.btn { padding: 13px 26px; border-radius: 14px; font-weight: 700; font-size: 0.98rem;
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; }
.btn-solid { background: var(--ink); color: #fff; box-shadow: 0 14px 30px rgba(23, 20, 35, 0.25); }
.btn-solid:hover { transform: translateY(-2px); }
.btn-ghost { background: rgba(255, 255, 255, 0.7); border: 1px solid var(--line); color: var(--ink); }
.btn-ghost:hover { transform: translateY(-2px); border-color: rgba(91, 61, 245, 0.32); }

/* ---- generic section ---- */
section.block { padding: 40px 0; }
.sec-head { display: flex; align-items: baseline; gap: 16px; margin-bottom: 24px; flex-wrap: wrap; }
.sec-head h2 { font-size: clamp(1.7rem, 3.4vw, 2.4rem); letter-spacing: -0.03em; font-weight: 800; }
.sec-head .mono { margin-left: auto; }
.lead-line { max-width: 62ch; color: var(--ink-soft); font-size: 1.05rem; margin: -8px 0 26px; }

.card { background: var(--card); border: 1px solid var(--line); border-radius: var(--r);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  box-shadow: 0 20px 46px rgba(60, 40, 120, 0.12); }

/* ---- pendo proof (the differentiator) ---- */
.pendo .card { padding: 40px 44px; }
.pendo .kicker { display: inline-flex; align-items: center; gap: 9px; padding: 6px 13px; border-radius: 999px;
  background: linear-gradient(120deg, rgba(91, 61, 245, 0.12), rgba(224, 72, 127, 0.12));
  border: 1px solid var(--line); font-weight: 700; font-size: 0.8rem; color: var(--accent); margin-bottom: 18px; }
.proof-list { display: grid; gap: 18px; margin-top: 8px; }
.proof-item { display: flex; gap: 16px; align-items: flex-start; }
.proof-item .k { width: 30px; height: 30px; border-radius: 9px; flex: none; display: grid; place-items: center;
  background: linear-gradient(135deg, var(--accent), var(--accent2)); color: #fff; font-weight: 800; font-size: 0.82rem;
  box-shadow: 0 6px 16px rgba(91, 61, 245, 0.28); }
.proof-item .txt { font-size: 1.02rem; color: var(--ink-soft); }
.proof-item .txt b { color: var(--ink); font-weight: 750; }
.proof-item-cert { align-items: center; }
.cert-badge { width: 96px; height: auto; flex: none; margin-left: auto; filter: drop-shadow(0 8px 16px rgba(60,40,120,.22)); }
@media (max-width: 620px){ .cert-badge { width: 76px; } }

/* ---- live products ---- */
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
.pcard { position: relative; display: block; overflow: hidden;
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; }
a.pcard:hover { transform: translateY(-6px); box-shadow: 0 34px 64px rgba(60, 40, 120, 0.18); border-color: rgba(91, 61, 245, 0.3); }
.shot { aspect-ratio: 16 / 10; overflow: hidden; border-bottom: 1px solid var(--line); background: #ecebf4; }
.shot img { width: 100%; height: 100%; object-fit: cover; object-position: top center; }
.logohero { display: grid; place-items: center; }
.lh-hs { background: radial-gradient(120% 120% at 50% 18%, #2a1030, #140720); }
.lh-hs .word { font-weight: 900; font-size: clamp(1.8rem, 4vw, 2.6rem); letter-spacing: -0.02em;
  background: linear-gradient(100deg, #ff6a8f, #c56bff); -webkit-background-clip: text; background-clip: text;
  color: transparent; text-shadow: 0 0 40px rgba(200, 90, 220, 0.3); }
.pmeta { padding: 24px 26px 26px; }
.prow { display: flex; align-items: center; gap: 14px; }
.tile { width: 44px; height: 44px; border-radius: 12px; flex: none; box-shadow: 0 6px 16px rgba(40, 30, 90, 0.18); }
.mono-tile { display: grid; place-items: center; font-family: var(--mono); font-weight: 800; font-size: 0.9rem; color: #fff; }
.t-hs { background: linear-gradient(135deg, #e04f7a, #7a2ea8); }
.idx { font-family: var(--mono); font-size: 0.72rem; color: var(--ink-faint); letter-spacing: 0.12em; }
.prow h3 { font-size: 1.34rem; letter-spacing: -0.02em; font-weight: 800; }
.what { margin-top: 13px; color: var(--ink-soft); font-size: 0.98rem; }
.go { margin-top: 16px; display: inline-flex; gap: 8px; font-weight: 700; font-size: 0.92rem; color: var(--accent); }
.badge { position: absolute; top: 16px; right: 16px; font-family: var(--mono); font-size: 0.62rem; font-weight: 700;
  letter-spacing: 0.13em; padding: 6px 11px; border-radius: 999px; display: inline-flex; align-items: center; gap: 7px;
  background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(6px); box-shadow: 0 4px 12px rgba(40, 30, 90, 0.16); }
.badge::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
.badge { color: var(--ok); }

/* in-progress product group: quieter, honestly not-yet-shipped */
.grp-head { margin-top: 40px; }
.grp-head h2 { color: var(--ink-soft); }
.pcard.muted { opacity: 0.9; filter: saturate(0.7); background: rgba(255, 255, 255, 0.6); }
.pcard.muted .prow h3 { color: var(--ink-soft); }
.pcard.muted:hover { transform: none; box-shadow: 0 20px 46px rgba(60, 40, 120, 0.12); }
.t-tb { background: linear-gradient(135deg, #8a8298, #5f5872); }
.t-dive { background: linear-gradient(135deg, #7d8aa6, #59647e); }
.badge.dev { color: var(--ink-faint); }
.badge.dev::before { background: var(--ink-faint); }

/* ---- the system I built: connected ARCHITECTURE CHART (pinned, scroll-assembled) ---- */
.sys-head { text-align: center; margin-bottom: 26px; }
.sys-head h2 { font-size: clamp(1.7rem, 3.4vw, 2.4rem); letter-spacing: -0.03em; font-weight: 800; margin-top: 8px; }
.sys-sub { max-width: 60ch; margin: 12px auto 0; color: var(--ink-soft); font-size: 1.05rem; }
.sys-stats { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 10px; margin-top: 16px; }
.sys-stats span { padding: 5px 13px; border-radius: 999px; background: rgba(255, 255, 255, 0.62);
  font-size: 0.82rem; font-weight: 600; color: var(--ink-soft); }
.sys-stats b { font-weight: 800; background: linear-gradient(120deg, var(--accent), var(--accent2));
  -webkit-background-clip: text; background-clip: text; color: transparent; }

.chart { max-width: 960px; margin: 0 auto; }

/* --- layer stack strung on a vertical backbone (the spine), each layer branching to its sub-nodes --- */
.arch { position: relative; display: flex; flex-direction: column; gap: 14px; }
.arch-spine { position: absolute; left: 27px; top: 26px; bottom: 26px; width: 3px; z-index: 0;
  background: linear-gradient(180deg, var(--accent), var(--accent3)); border-radius: 3px;
  transform-origin: top center; }

.layer { position: relative; display: grid; grid-template-columns: 320px 40px minmax(0, 1fr); align-items: start; }
.layer-node { position: relative; z-index: 2; display: flex; flex-direction: column; gap: 8px;
  padding: 12px 16px 14px 12px; border-radius: 14px;
  background: linear-gradient(120deg, rgba(91, 61, 245, 0.14), rgba(224, 72, 127, 0.09)), var(--card-solid);
  border: 1px solid rgba(91, 61, 245, 0.22); box-shadow: 0 12px 30px rgba(60, 40, 120, 0.12); }
.layer-head { display: flex; align-items: center; gap: 11px; }
.layer-idx { width: 30px; height: 30px; border-radius: 9px; flex: none; display: grid; place-items: center;
  color: #fff; font-weight: 800; font-size: 0.82rem;
  background: linear-gradient(135deg, var(--accent), var(--accent2)); box-shadow: 0 6px 15px rgba(91, 61, 245, 0.30); }
.layer-name { font-weight: 780; font-size: 1.02rem; letter-spacing: -0.01em; color: var(--ink); }
.layer-desc { color: var(--ink-soft); font-size: 0.82rem; line-height: 1.46; }

/* horizontal branch connector: layer node -> its sub-node cluster (aligned to the layer-idx center) */
.branch { position: relative; align-self: stretch; z-index: 1; transform-origin: left center; }
.branch::before { content: ""; position: absolute; top: 27px; left: -8px; right: -2px; height: 3px; border-radius: 3px;
  background: linear-gradient(90deg, var(--accent), rgba(91, 61, 245, 0.32)); }

/* sub-node cluster: a distributor rail feeding the 4 sub-nodes */
.subs { position: relative; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 7px 0 9px 16px;
  align-content: center; }
.subs .rail { position: absolute; left: 0; top: 10px; bottom: 12px; width: 3px; border-radius: 3px; z-index: 0;
  background: linear-gradient(180deg, rgba(91, 61, 245, 0.5), rgba(55, 182, 214, 0.42)); transform-origin: top center; }
.node.sub { position: relative; z-index: 1; padding: 8px 13px; border-radius: 11px; font-size: 0.8rem; font-weight: 640;
  color: var(--ink-soft); background: var(--card); border: 1px solid var(--line);
  box-shadow: 0 6px 16px rgba(60, 40, 120, 0.07); display: flex; align-items: center; }

/* --- three key flows: connected pipelines / a cycle --- */
.flows { margin-top: 30px; display: flex; flex-direction: column; gap: 22px; }
.flow-title { display: block; margin-bottom: 12px; }
/* nowrap: a pipeline that wraps strands its last node under a connector pointing at nothing.
   Nodes hold their natural width; only a node marked .elastic gives up room (min-width:0 lets it
   wrap its own text). Letting every node shrink instead breaks short labels mid-word. */
.chain { display: flex; flex-wrap: nowrap; align-items: center; gap: 9px; }
.node.fnode { padding: 9px 15px; border-radius: 12px; font-size: 0.82rem; font-weight: 700; color: var(--ink);
  background: var(--card-solid); border: 1px solid var(--line); box-shadow: 0 8px 20px rgba(60, 40, 120, 0.10);
  flex: 0 0 auto; }
.node.fnode.elastic { flex: 0 1 auto; min-width: 0; }
.node.fnode.gate { background: linear-gradient(120deg, rgba(55, 182, 214, 0.16), rgba(91, 61, 245, 0.10)), var(--card-solid);
  border-color: rgba(55, 182, 214, 0.36); }
.node.fnode.approve { background: linear-gradient(120deg, rgba(15, 138, 78, 0.16), rgba(91, 61, 245, 0.08)), var(--card-solid);
  border-color: rgba(15, 138, 78, 0.34); color: var(--ink); }
/* directional connector line + arrowhead between flow nodes */
.link { position: relative; flex: 0 0 30px; height: 3px; border-radius: 3px; align-self: center;
  background: var(--accent); transform-origin: left center; }
.link::after { content: ""; position: absolute; right: -1px; top: 50%; transform: translateY(-50%);
  border-left: 8px solid var(--accent); border-top: 5px solid transparent; border-bottom: 5px solid transparent; }

/* self-improvement CYCLE: a return path from the last node back to the first */
.chain.cycle { position: relative; padding-bottom: 40px; }
.return { position: absolute; left: 14px; right: 40px; bottom: 8px; height: 22px; z-index: 0;
  border: 3px solid rgba(91, 61, 245, 0.4); border-top: none; border-radius: 0 0 14px 14px; }
.return::before { content: ""; position: absolute; left: -6px; top: -10px;
  border-bottom: 10px solid var(--accent); border-left: 6px solid transparent; border-right: 6px solid transparent; }
.return-label { position: absolute; left: 50%; bottom: -10px; transform: translateX(-50%);
  padding: 2px 10px; background: #efe9f8; color: var(--accent); border-radius: 999px; }

/* legend */
.legend { display: flex; flex-wrap: wrap; gap: 8px 18px; justify-content: center; margin-top: 26px;
  padding-top: 18px; border-top: 1px solid var(--line-soft); }
.legend .lg { display: inline-flex; align-items: center; gap: 8px; font-size: 0.76rem; font-weight: 640; color: var(--ink-faint); }
.lg-swatch { width: 15px; height: 15px; border-radius: 5px; flex: none; }
.lg-swatch.layer-sw { background: linear-gradient(135deg, var(--accent), var(--accent2)); }
.lg-swatch.sub-sw { background: var(--card); border: 1px solid var(--line); }
.lg-swatch.gate-sw { background: linear-gradient(135deg, var(--accent3), var(--accent)); }
.lg-swatch.link-sw { height: 4px; border-radius: 3px; background: var(--accent); }

/* ---- resume ---- */
.resume .card { padding: 30px 32px 32px; }
.resume-top { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 22px; }
.resume-top .lbl { font-weight: 700; color: var(--ink-soft); font-size: 0.98rem; }
.dl { display: inline-flex; align-items: center; gap: 9px; padding: 12px 22px; border-radius: 12px;
  background: var(--ink); color: #fff; font-weight: 700; font-size: 0.94rem; box-shadow: 0 12px 26px rgba(23, 20, 35, 0.22);
  transition: transform 0.2s ease, opacity 0.2s ease; }
.dl:hover { transform: translateY(-2px); }
.dl[aria-disabled="true"] { background: var(--ink-faint); box-shadow: none; cursor: default; pointer-events: none; }
.resume-frame { width: 100%; height: 620px; border: 1px solid var(--line); border-radius: 14px; background: #fff; }
.resume-holder { border: 1px dashed rgba(91, 61, 245, 0.4); border-radius: 14px; padding: 54px 30px; text-align: center;
  background: linear-gradient(140deg, rgba(255,255,255,0.6), rgba(240,236,252,0.6)); }
.resume-holder .tag { display: inline-block; font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.14em;
  color: var(--accent); background: rgba(91, 61, 245, 0.1); padding: 6px 12px; border-radius: 999px; margin-bottom: 16px; }
.resume-holder h3 { font-size: 1.4rem; letter-spacing: -0.02em; margin-bottom: 8px; }
.resume-holder p { color: var(--ink-soft); max-width: 46ch; margin: 0 auto; }
/* the phone resume card is OFF on desktop, where the inline <object> embed works; the phone
   rules that switch the two live in the <=780px block. Desktop rendering is unchanged. */
.resume-mobile { display: none; }
.hide { display: none !important; }

/* ---- contact ---- */
.contact { text-align: center; padding: 56px 0 26px; }
.contact h2 { font-size: clamp(1.8rem, 4vw, 2.6rem); letter-spacing: -0.03em; }
.contact .sub { color: var(--ink-soft); margin: 12px 0 26px; }
.links { display: flex; flex-direction: column; gap: 12px; align-items: center; }
.links .primary { font-size: clamp(1.2rem, 3vw, 1.7rem); font-weight: 800;
  background: linear-gradient(120deg, var(--accent), var(--accent2)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.links .row { display: flex; gap: 10px 26px; flex-wrap: wrap; justify-content: center; color: var(--ink-soft); font-weight: 600; }
.links .row a:hover { color: var(--accent); }
.links .row .ph { color: var(--ink-faint); }

footer { padding: 30px 0 46px; border-top: 1px solid var(--line); margin-top: 34px; }
.foot { display: flex; justify-content: space-between; align-items: center; color: var(--ink-faint);
  font-size: 0.88rem; flex-wrap: wrap; gap: 12px; }
.foot a { color: var(--ink-soft); font-weight: 600; margin-left: 18px; }

/* ---- responsive ---- */
@media (max-width: 780px) {
  .grid { grid-template-columns: 1fr; }
  /* nav was `display:none` here, which left a phone visitor with NO navigation at all
     (and no way to reach the five in-page anchors). It is restored as a real two-row
     header in the mobile pass block below. */
  .pendo .card { padding: 30px 26px; }
  .resume-frame { height: 480px; }
  /* Feature C mobile: kill the Z-depth perspective (heavy 3D induces discomfort + overflow on narrow
     screens). JS also drops to a lite scale/fade here - no translateZ / rotateX / blur. */
  .depth-stage { perspective: none; }

  /* --- chart stacks cleanly on narrow screens: node on top, a short vertical connector, subs below --- */
  .arch { gap: 18px; }
  .arch-spine { display: none; }
  .layer { grid-template-columns: 1fr; gap: 0; }
  .layer-node { min-height: 0; }
  .branch { height: 18px; align-self: center; margin-left: 26px; }
  .branch::before { top: 0; bottom: 0; left: 0; right: auto; width: 3px; height: auto;
    background: linear-gradient(180deg, var(--accent), rgba(91, 61, 245, 0.32)); }
  .subs { grid-template-columns: 1fr; padding: 0 0 0 26px; gap: 8px; }
  .subs .rail { top: 4px; bottom: 4px; }

  /* flows become vertical pipelines with downward arrows */
  .chain { flex-direction: column; align-items: stretch; gap: 8px; }
  .node.fnode { text-align: center; }
  .link { flex: 0 0 20px; width: 3px; height: 20px; align-self: center; }
  .link::after { right: auto; left: 50%; top: auto; bottom: -1px; transform: translateX(-50%);
    border-left: 5px solid transparent; border-right: 5px solid transparent;
    border-top: 8px solid var(--accent); border-bottom: none; }
  .chain.cycle { padding-bottom: 0; }
  .return { position: static; height: auto; border: none; margin: 8px auto 0; display: inline-flex; justify-content: center; }
  .return::before { display: none; }
  .return-label { position: static; transform: none; display: inline-block;
    border: 1px solid rgba(91, 61, 245, 0.4); }
}
/* ============================================================
   Scroll interactions (motherduck-FEEL, Andrew's soft-bloom aesthetic).
   ALL initial-hidden state is gated on html.anim, which JS adds ONLY when
   motion is allowed. No .anim (JS off / reduced-motion) => everything visible.
   Transform + opacity only; GPU-friendly; no strokes, no line art.
   ============================================================ */

/* reveal-on-scroll: fade + rise, index-staggered. One-shot transition, so NO permanent
   will-change (it would pin a compositor layer on every revealed element forever). */
html.anim [data-reveal] {
  opacity: 0; transform: translateY(16px);
  transition: opacity 0.45s ease, transform 0.45s cubic-bezier(0.22, 0.61, 0.36, 1);
  transition-delay: calc(var(--i, 0) * 90ms);
}
html.anim [data-reveal].is-in { opacity: 1; transform: none; }

/* Pendo number badges: subtle pop as their row reveals */
html.anim .proof-item[data-reveal] .k {
  transform: scale(0.5); opacity: 0;
  transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 0.12s, opacity 0.3s ease 0.12s;
}
html.anim .proof-item[data-reveal].is-in .k { transform: scale(1); opacity: 1; }

/* parallax field: soft FILLED blooms (no border/stroke) drifting on scroll. GPU layer comes from the
   translate3d JS writes, not a permanent will-change. */
.parallax-layer { position: absolute; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }
.parallax-bloom { position: absolute; border-radius: 50%; }

/* thin scroll-progress accent line, palette gradient (compositor layer via translateZ in the transform) */
.scroll-progress {
  position: fixed; top: 0; left: 0; height: 3px; width: 100%; z-index: 100;
  background: linear-gradient(90deg, var(--accent), var(--accent2), var(--accent3));
  transform: scaleX(0); transform-origin: left center;
}

/* ============================================================
   Scroll-SCRUBBED features (MotherDuck-style: state driven by scroll POSITION).
   DEFAULT CSS = the COMPLETED state (system stack assembled + lit, funnel full), so JS-off AND
   reduced-motion render finished, never blank/stuck. JS drives it from 0->1 when
   scrubbing is active. Soft FILLED shapes only, no strokes/line art.
   ============================================================ */

/* -- Feature A: pinned ARCHITECTURE CHART. Tall track + sticky pin ONLY when JS arms .scrub (desktop).
   DEFAULT (no .scrub / JS-off / reduced-motion) = the COMPLETE chart, assembled + drawn. .scrub adds the
   hidden initial state; JS toggles per-element .in classes as the pinned track scrolls, and CSS TRANSITIONS
   do the draw (connectors grow, nodes light) - no per-frame style writes for the chart itself. -- */
.scrub-track { position: relative; }
html.anim .scrub-system.scrub .scrub-track { height: 460vh; }
/* the chart is taller than the viewport, so the pinned frame is a fixed 100vh window and the inner .wrap
   PANS upward as the section scroll-builds. will-change:transform is added by JS only while active. */
html.anim .scrub-system.scrub .scrub-pin {
  position: sticky; top: 0; height: 100vh; overflow: hidden;
  display: flex; align-items: flex-start; justify-content: center; padding-top: 3vh;
}
/* smooth draw/light transitions on the chart primitives */
html.anim .scrub-system.scrub .arch-spine,
html.anim .scrub-system.scrub .branch,
html.anim .scrub-system.scrub .subs .rail,
html.anim .scrub-system.scrub .link {
  transition: transform 0.5s cubic-bezier(0.22, 0.61, 0.36, 1);
}
html.anim .scrub-system.scrub .layer-node,
html.anim .scrub-system.scrub .node,
html.anim .scrub-system.scrub .flow-title,
html.anim .scrub-system.scrub .return,
html.anim .scrub-system.scrub .legend {
  transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* initial (unassembled) states while scrubbing */
html.anim .scrub-system.scrub .arch-spine { transform: scaleY(0); }
html.anim .scrub-system.scrub .branch { transform: scaleX(0); }
html.anim .scrub-system.scrub .subs .rail { transform: scaleY(0); }
html.anim .scrub-system.scrub .link { transform: scaleX(0); }
html.anim .scrub-system.scrub .layer-node,
html.anim .scrub-system.scrub .node,
html.anim .scrub-system.scrub .flow-title,
html.anim .scrub-system.scrub .return { opacity: 0; transform: translateY(14px) scale(0.97); }
html.anim .scrub-system.scrub .legend { opacity: 0; }
/* assembled states (JS adds .in as each element's threshold is crossed) */
html.anim .scrub-system.scrub .arch-spine.in { transform: scaleY(1); }
html.anim .scrub-system.scrub .branch.in { transform: scaleX(1); }
html.anim .scrub-system.scrub .subs .rail.in { transform: scaleY(1); }
html.anim .scrub-system.scrub .link.in { transform: scaleX(1); }
html.anim .scrub-system.scrub .layer-node.in,
html.anim .scrub-system.scrub .node.in,
html.anim .scrub-system.scrub .flow-title.in,
html.anim .scrub-system.scrub .return.in { opacity: 1; transform: none; }
html.anim .scrub-system.scrub .legend.in { opacity: 1; }

/* -- Feature B: Pendo Add Task -> Done funnel (fills with section scroll) -- */
.funnel { margin-top: 28px; padding-top: 24px; border-top: 1px solid var(--line); }
.fn-head { font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.12em; font-size: 0.68rem; font-weight: 700; color: var(--ink-faint); margin-bottom: 18px; }
.fn-bars { display: flex; flex-direction: column; gap: 12px; }
.fn-step { display: flex; align-items: center; gap: 14px; }
.fn-cap { flex: none; width: 100px; font-size: 0.86rem; font-weight: 650; color: var(--ink-soft); }
.fn-track { flex: 1 1 auto; }
.fn-bar { position: relative; display: block; height: 24px; border-radius: 999px; background: rgba(91, 61, 245, 0.10); overflow: hidden; }
.fn-bar > i { position: absolute; inset: 0; transform-origin: left center; transform: scaleX(1); border-radius: 999px;
  background: linear-gradient(90deg, var(--accent), var(--accent2)); }

/* ============================================================
   Feature C: Live products ARRIVE FROM DEPTH. A perspective stage; the two cards start far
   (small, pushed back in Z, tilted back, dim, blurred) and RUSH to full size/facing on scroll
   (accelerating ease-in). DEFAULT (JS-off / reduced-motion) = completed: full size, facing, sharp.
   JS adds .depth3d and drives inline transform/opacity/filter; on arrival it clears inline so :hover works.
   ============================================================ */
.depth-stage { perspective: 1100px; perspective-origin: 50% 42%; }
.depth-grid { transform-style: preserve-3d; }
/* NO permanent will-change; JS adds it only while the products section is active (add on enter, drop on
   leave). Depth "far" softness is a STATIC blur applied via .depth-far, never an animated per-frame filter. */
.depth-card { backface-visibility: hidden; transform-origin: center 60%; }
html.anim .depth-card.depth-far { filter: blur(4px); }

/* ============================================================
   Feature E: 3D CARD DECK (vertical ring / rolodex). DEFAULT = a clean static fallback grid, so
   JS-off / reduced-motion / mobile always read the skills as a simple list (never a broken ring).
   JS adds .deck-on (desktop + motion only) which shows the ring, pins the section, and rotates it.
   One empty slot (slot 0) is the GAP/seam that marks first+last.
   ============================================================ */
.deck-stage { display: none; }
.deck-fallback { list-style: none; display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin-top: 22px; }
.deck-fallback li { padding: 16px 18px; border-radius: 14px; background: var(--card);
  box-shadow: 0 12px 30px rgba(60, 40, 120, 0.10); font-weight: 650; color: var(--ink-soft); }

html.anim .scrub-deck.deck-on .deck-stage { display: block; position: relative; height: 360px; margin-top: 30px;
  perspective: 1150px; perspective-origin: 50% 50%; }
html.anim .scrub-deck.deck-on .deck-fallback { display: none; }
html.anim .scrub-deck.deck-on .scrub-track { height: 320vh; }
html.anim .scrub-deck.deck-on .scrub-pin { position: sticky; top: 0; min-height: 100vh; display: flex; align-items: center; }

/* NO permanent will-change on the ring/cards; JS adds it to the ring only while the deck is active. */
.deck-ring { position: absolute; inset: 0; transform-style: preserve-3d; }
.deck-card { position: absolute; top: 50%; left: 50%; width: min(420px, 80%); height: 118px;
  margin-left: calc(min(420px, 80%) / -2); margin-top: -59px;
  display: flex; align-items: center; justify-content: center; text-align: center; padding: 0 26px;
  border-radius: 18px; background: var(--card-solid); box-shadow: 0 18px 40px rgba(60, 40, 120, 0.14);
  backface-visibility: hidden; transform-origin: center center; }
.deck-card h3 { font-size: 1.12rem; font-weight: 750; letter-spacing: -0.01em; color: var(--ink); line-height: 1.3; }
.deck-card.is-front { box-shadow: 0 28px 64px rgba(60, 40, 120, 0.24); }

/* ============================================================
   Perf: skip rendering + layout of the heaviest offscreen section (the resume embeds a PDF viewer) while
   it is far from the viewport; contain-intrinsic-size reserves its height so scroll offsets stay stable.
   Applied to a non-measured static section only - the JS-measured sections (products depth stage, the
   pinned chart + deck) are made free-when-offscreen by the IntersectionObserver active-gate instead, which
   avoids any content-visibility measurement/anchor jump on a rect the loop reads. */
.resume { content-visibility: auto; contain-intrinsic-size: auto 760px; }

@media (max-width: 620px) {
  .fn-cap { width: 78px; font-size: 0.78rem; }
}

/* ============================================================
   MOBILE PASS (2026-07-21) - legibility + touch + collision fixes.
   EVERY rule below lives inside a max-width media query, so the desktop
   rendering is byte-for-byte unchanged. Nothing is removed or hidden; the
   fixes reflow, resize and re-stack only.

   Measured with kit/mobile-audit.cjs at 320 / 390 / 414. Baseline defects:
     - 12 text runs rendering under 12px (.mono .idx .badge .fn-head) - the
       "I have to pinch-zoom to read it" complaint.
     - 7 links under the 44x44 touch minimum (brand, header pill, the contact
       email + link row, the footer links).
     - the absolutely-positioned status badge on the two in-progress cards
       landing on top of the card's index line and clipping it ("operations
       plat|" under "In development") - a real collision, not just tight.
     - text sitting ~13px from the screen edge at 320 (92vw gutter).
   ============================================================ */
@media (max-width: 780px) {

  /* -- gutter: a fixed edge margin reads better than a percentage that
        collapses to 13px on the narrowest phones -- */
  .wrap { width: min(var(--maxw), 100vw - 32px); }

  /* -- header: brand + CTA on row 1 (both clear 44x44), the REAL nav on row 2.
        Same two-row solution the tabblabs.net root page uses, so the two pages
        navigate identically on a phone.
        The two rows cost ~110px of a 844px screen, so the header also SCROLLS AWAY
        on a downward scroll and comes back the instant you scroll up or reach the
        top (app.js toggles html.nav-away, and only on gradual scrolling, never on
        an in-page jump). With JS off nothing hides: the nav simply stays put. -- */
  header { padding: 10px 0; transition: transform 0.26s ease; }
  html.nav-away header { transform: translateY(-100%); }
  .bar { gap: 0 12px; flex-wrap: wrap; }
  .brand { min-height: 44px; flex: 1 1 auto; }
  .pill { display: inline-flex; align-items: center; min-height: 44px; padding: 11px 18px; flex: none; }
  nav { display: block; order: 3; flex: 1 1 100%; border-top: 1px solid var(--line); }
  nav ul { gap: 2px; justify-content: space-between; font-size: 0.84rem; margin: 0 -6px; }
  /* -6px cancels the link padding so the row's text edges line up with the wrap */
  nav a { display: flex; align-items: center; min-height: 44px; padding: 0 6px;
    white-space: nowrap; color: var(--ink); }

  /* -- anchor jumps (nav links, hero CTAs, the skip link) land BELOW the taller
        two-row sticky bar instead of under it -- */
  #pendo, #products, #skills, #resume, #contact { scroll-margin-top: 118px; }

  /* -- hero: less dead space above the fold, tidier line breaks -- */
  .hero { padding: 46px 0 30px; }
  .hero h1, .hero .role, .hero .lede { text-wrap: balance; }
  .hero .role { margin-top: 18px; font-size: 1.08rem; }
  .hero .lede { font-size: 1.02rem; }
  .hero .cta { margin-top: 26px; gap: 12px; }
  .cta .btn { flex: 1 1 auto; text-align: center; padding: 14px 20px; }

  /* -- type scale: nothing renders under 12px on a phone -- */
  .mono { font-size: 0.79rem; letter-spacing: 0.11em; }
  .idx { font-size: 0.79rem; letter-spacing: 0.09em; }
  .badge { font-size: 0.78rem; letter-spacing: 0.09em; padding: 6px 12px; }
  .fn-head { font-size: 0.79rem; letter-spacing: 0.1em; }
  .legend .lg { font-size: 0.8rem; }
  .resume-holder .tag { font-size: 0.79rem; }

  /* -- product cards: the status badge gets its own line on the in-progress
        cards instead of overlapping the index line beneath it -- */
  .pcard.muted .badge { position: static; display: inline-flex; margin: 18px 0 0 24px; }
  .pcard.muted .pmeta { padding-top: 14px; }
  .prow h3 { font-size: 1.28rem; }
  .what { overflow-wrap: anywhere; }

  /* long unbreakable tokens (the pendo.validateInstall() call) wrap instead of
     running into the card edge at 320 */
  .proof-item .txt, .lead-line, .layer-desc { overflow-wrap: break-word; }

  /* -- "In progress" group heading: label under the heading, not squeezed
        against the right edge -- */
  .grp-head { gap: 4px 14px; }
  .grp-head .mono { margin-left: 0; }

  /* -- resume: the inline PDF embed is unreadable at this width (its body text
        renders at ~4px, and iOS will not render an <object> PDF at all), so the
        phone gets the preview card instead: a page-1 image rendered from the real
        PDF, plus two full-width controls that open it in the system PDF viewer,
        which zooms properly. Nothing is removed; the embed returns above 780px. -- */
  .resume-top { flex-direction: column; align-items: stretch; gap: 12px; }
  .resume-top .lbl { text-align: left; }
  .dl { justify-content: center; }
  #resume-live { display: none; }
  .resume-mobile { display: block; }
  .resume-preview { display: block; border: 1px solid var(--line); border-radius: 14px;
    background: #fff; overflow: hidden; box-shadow: 0 10px 26px rgba(60, 40, 120, 0.10); }
  .resume-preview img { width: 100%; height: auto; }
  .dl-open { display: flex; justify-content: center; margin-top: 14px; width: 100%;
    min-height: 52px; font-size: 1rem; }

  /* -- contact: every link is a real 44px touch target, and the address
        wraps instead of running off a 320px screen -- */
  .contact { padding: 44px 0 20px; }
  .links { gap: 6px; }
  .links .primary { display: inline-block; min-height: 44px; padding: 7px 8px;
    overflow-wrap: anywhere; line-height: 1.5; }
  .links .row { gap: 2px 6px; }
  .links .row a { display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px; padding: 8px 14px; border-radius: 12px; }

  /* -- footer: stack the two halves, links become touch targets -- */
  .foot { flex-direction: column; align-items: flex-start; gap: 2px; }
  .foot a { display: inline-flex; align-items: center; min-height: 44px;
    margin: 0 14px 0 0; padding: 0 4px; }
}

@media (max-width: 620px) {
  /* the Pendo certification badge drops BELOW its paragraph instead of stealing
     half the text column (it was squeezing item 3 into a ~150px ribbon) */
  .proof-item-cert { display: grid; grid-template-columns: 30px minmax(0, 1fr);
    column-gap: 16px; row-gap: 14px; align-items: start; }
  .proof-item-cert .k { grid-column: 1; grid-row: 1; }
  .proof-item-cert .txt { grid-column: 2; grid-row: 1; }
  .cert-badge { grid-column: 2; grid-row: 2; width: 104px; margin: 0; justify-self: center; }
}

@media (max-width: 480px) {
  /* narrowest phones: give the text back the room the card padding was taking */
  .wrap { width: min(var(--maxw), 100vw - 28px); }
  .pendo .card { padding: 24px 18px 26px; }
  .resume .card { padding: 22px 16px 24px; }
  .pmeta { padding: 18px 18px 20px; }
  .pcard.muted .badge { margin-left: 18px; }
  .layer-node { padding: 12px 14px 14px 12px; }
  .node.sub { padding: 9px 12px; }
  .node.fnode { padding: 10px 12px; }
  .deck-fallback li { padding: 15px 16px; }
}

/* ============================================================
   LANDSCAPE PHONE (2026-07-21) - the same phone, turned sideways.

   Held sideways an iPhone is 844x390, which is WIDER than the 780px block above, so every
   rule of the mobile pass switched off and the page handed a thumb its desktop self again:
   header targets back to ~19px tall, eleven labels back under the 12px pinch-zoom floor,
   and the flow pipelines (.chain is flex-wrap:nowrap BY DESIGN - a wrapped pipeline strands
   its last node under an arrow pointing at nothing) back to a 918px row inside a 776px
   column. That is what made this page PAN SIDEWAYS by 108px, and it squeezed the
   "Work-order to another project's inbox" node into a one-word-per-line sliver.

   Split by what the constraint actually is:
   - LAYOUT stays keyed to WIDTH. Measured at 844 the header needs 588px of the 776px
     available, so the real horizontal nav fits and reads well; forcing the two-row phone
     header would spend a quarter of a 390px-TALL screen on chrome, and the products grid
     is better 2-up there than stacked.
   - TOUCH and the type floor follow the POINTER, because a 44px target is a property of
     the finger, not of the viewport. `pointer: coarse` is false for a mouse and the 900px
     cap means a desktop can never reach this block - structurally, not merely unlikely.
   ============================================================ */
@media (max-width: 780px), (max-width: 900px) and (pointer: coarse) {

  /* -- every interactive target clears 44x44, header to footer -- */
  .brand { min-height: 44px; }
  nav a { display: flex; align-items: center; min-height: 44px; white-space: nowrap; }
  .pill { display: inline-flex; align-items: center; min-height: 44px; }
  .links .primary { display: inline-block; min-height: 44px; padding: 7px 8px;
    overflow-wrap: anywhere; line-height: 1.5; }
  .links .row { gap: 2px 6px; }
  .links .row a { display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px; padding: 8px 14px; border-radius: 12px; }
  .foot a { display: inline-flex; align-items: center; min-height: 44px; }

  /* -- nothing renders below the pinch-zoom threshold on a touch screen -- */
  .mono { font-size: 0.79rem; letter-spacing: 0.11em; }
  .idx { font-size: 0.79rem; letter-spacing: 0.09em; }
  .badge { font-size: 0.78rem; letter-spacing: 0.09em; padding: 6px 12px; }
  .fn-head { font-size: 0.79rem; letter-spacing: 0.1em; }
  .legend .lg { font-size: 0.8rem; }
  .resume-holder .tag { font-size: 0.79rem; }

  /* -- the three flow pipelines run vertically, the same treatment the portrait phone
        already gets. Nothing is dropped: every node, arrow and the loop-back label stay,
        they just read top-to-bottom instead of overflowing the column. -- */
  .chain { flex-direction: column; align-items: stretch; gap: 8px; }
  .node.fnode { text-align: center; }
  .link { flex: 0 0 20px; width: 3px; height: 20px; align-self: center; }
  .link::after { right: auto; left: 50%; top: auto; bottom: -1px; transform: translateX(-50%);
    border-left: 5px solid transparent; border-right: 5px solid transparent;
    border-top: 8px solid var(--accent); border-bottom: none; }
  .chain.cycle { padding-bottom: 0; }
  .return { position: static; height: auto; border: none; margin: 8px auto 0;
    display: inline-flex; justify-content: center; }
  .return::before { display: none; }
  .return-label { position: static; transform: none; display: inline-block;
    border: 1px solid rgba(91, 61, 245, 0.4); }

  /* -- resume: a touch device gets the page-1 preview card, not the <object> embed.
        Safari on iOS/iPadOS does not render an <object> PDF inline at all (blank frame),
        and a 620px-tall frame is taller than the whole 390px landscape screen anyway.
        Nothing is removed - the Download control is untouched and the embed returns the
        moment a mouse is present. -- */
  .resume-top { flex-direction: column; align-items: stretch; gap: 12px; }
  .dl { justify-content: center; }
  #resume-live { display: none; }
  .resume-mobile { display: block; }
  .resume-preview { display: block; border: 1px solid var(--line); border-radius: 14px;
    background: #fff; overflow: hidden; box-shadow: 0 10px 26px rgba(60, 40, 120, 0.10); }
  .resume-preview img { width: 100%; height: auto; }
  .dl-open { display: flex; justify-content: center; margin-top: 14px; width: 100%;
    min-height: 52px; font-size: 1rem; }

  /* -- ROOT CAUSE of the 640x360 collision between "View resume (PDF)" and the contact
        block. `.resume` carries `content-visibility: auto; contain-intrinsic-size: auto
        760px`. While the section is off-screen the browser skips its contents AND applies
        SIZE containment, so the section occupies the 760px PLACEHOLDER rather than its own
        height. On a touch device the card is the preview image, not the embed, and at 640px
        wide that card measures 938px - 178px taller than the reservation. Everything after
        it is therefore laid out ~200px too high: the resume card paints over "One inbox
        reaches me directly." and the email address, and the page jumps when the section
        finally renders. The reservation exists to keep the heavy PDF viewer off the main
        thread while it is far away; where the viewer is display:none there is nothing to
        defer and nothing to guess, so the section measures itself and the estimate cannot
        be wrong. (Fixing the estimate instead would only move the error to another width.) -- */
  .resume { content-visibility: visible; contain-intrinsic-size: none; }
}

/* landscape-only tail: once the bar's rows are 44px tall it no longer needs the desktop
   padding, and a 390px-tall screen cannot spare it - this holds the header near its
   original height instead of paying for the bigger targets in screen real estate. The
   anchor offset follows the header it has to clear. Portrait keeps its own values above. */
@media (min-width: 781px) and (max-width: 900px) and (pointer: coarse) {
  header { padding: 6px 0; }
  #pendo, #products, #skills, #resume, #contact { scroll-margin-top: 72px; }
  /* the "In development" badge takes its own line on the in-progress cards instead of
     landing on top of the index line beneath it. At 844 the badge was already clipping
     "operations platfo|" before this pass, and a badge widened to clear the 12px type
     floor covers more of it. The audit cannot see this one: .badge is position:absolute
     and the OVERLAP check deliberately skips absolutely-positioned boxes, so it took
     looking at the screenshot. Landscape-only: portrait already has its own version of
     this rule (with its own narrow-phone margin), and re-stating it here would override it. */
  .pcard.muted .badge { position: static; display: inline-flex; margin: 18px 0 0 24px; }
  .pcard.muted .pmeta { padding-top: 14px; }

  /* the vertical pipeline's nodes size to their labels here instead of stretching: a
     776px-wide pill around the word "QA" reads as a button, not as a step in a flow.
     (A 358px portrait column is narrow enough that stretching still reads correctly,
     so portrait keeps align-items:stretch.) */
  .chain { align-items: center; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
  /* belt-and-suspenders: never leave content hidden under reduced-motion - show the COMPLETE chart */
  html.anim [data-reveal], html.anim .proof-item[data-reveal] .k { opacity: 1 !important; transform: none !important; }
  html.anim .scrub-system.scrub .layer-node,
  html.anim .scrub-system.scrub .node,
  html.anim .scrub-system.scrub .flow-title,
  html.anim .scrub-system.scrub .return,
  html.anim .scrub-system.scrub .legend { opacity: 1 !important; transform: none !important; }
  html.anim .scrub-system.scrub .arch-spine,
  html.anim .scrub-system.scrub .branch,
  html.anim .scrub-system.scrub .subs .rail,
  html.anim .scrub-system.scrub .link { transform: none !important; }
  .parallax-layer, .scroll-progress { display: none !important; }
  /* scrubbed features show COMPLETED (defaults already do; no pin) */
  .scrub-system.scrub .scrub-track { height: auto !important; }
  .scrub-system.scrub .scrub-pin { position: static !important; min-height: 0 !important; display: block !important; }
}
