@font-face{font-family:Geist;src:url('../assets/fonts/geist.woff2') format('woff2');font-weight:100 900;font-display:swap}
@font-face{font-family:'Geist Mono';src:url('../assets/fonts/geist-mono.woff2') format('woff2');font-weight:300 600;font-display:swap}
@font-face{font-family:'Geist Pixel';src:url('../assets/fonts/geist-pixel.woff2') format('woff2');font-weight:400;font-display:swap}
@font-face{font-family:'Source Serif 4';src:url('../assets/fonts/source-serif.woff2') format('woff2');font-weight:400 600;font-display:swap}
@font-face{font-family:Newsreader;src:url('../assets/fonts/newsreader.woff2') format('woff2');font-weight:200 700;font-display:swap}
@font-face{font-family:'Bricolage Grotesque';src:url('../assets/fonts/bricolage.woff2') format('woff2');font-weight:200 800;font-display:swap}
@font-face{font-family:Fraunces;src:url('../assets/fonts/fraunces.woff2') format('woff2');font-weight:100 900;font-display:swap}
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;-webkit-font-smoothing:antialiased;font-family:Geist,sans-serif}button,input,textarea,select{font:inherit}button,a{-webkit-tap-highlight-color:transparent}button{cursor:pointer}a{color:inherit}button:disabled{cursor:default}button:active:not(:disabled){scale:.97}button{transition:background-color .18s,color .18s,transform .18s,scale .12s}img{max-width:100%;display:block}svg.icon{width:16px;height:16px;stroke:currentColor;stroke-width:1.5;fill:none;stroke-linecap:round;stroke-linejoin:round;vertical-align:middle;flex-shrink:0}h1,h2,h3{text-wrap:balance}p{text-wrap:pretty}a:focus-visible,button:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible,summary:focus-visible{outline:2px solid #5270e8;outline-offset:4px}button,a,input,textarea{touch-action:manipulation}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.skip-link{position:fixed;top:12px;left:12px;transform:translateY(-180%);z-index:1000;padding:12px 18px;background:#111;color:white}.skip-link:focus{transform:none}

.mj-dialog{--dlg-bg:#fff;--dlg-raise:#f5f6f2;--dlg-hover:#f0f2ed;--dlg-ink:#191b1a;--dlg-muted:#53584f;--dlg-faint:#6d7069;--dlg-line:#e5e7e2;--dlg-accent:#202820;--dlg-accent-ink:#fff;--dlg-ok:#2f8f5b;--dlg-ok-bg:#edf6e6;--dlg-bad:#b4442c;--dlg-bad-bg:#ffe6e3;--dlg-warn:#d0a02c;--dlg-code-bg:#161d19;--dlg-code-ink:#e2eed8;--dlg-scrim:#f3f3f3b8;--dlg-shadow:0 20px 100px #0000001f;--dlg-focus:#5270e8;--dlg-range:#3f5a34;color:var(--dlg-ink);background:var(--dlg-bg);width: min(820px,calc(100% - 40px));max-height:calc(100dvh - 80px);border:1px solid var(--dlg-line);padding:0;border-radius:16px;box-shadow:var(--dlg-shadow);overscroll-behavior:contain}.mj-dialog::backdrop{background:var(--dlg-scrim);backdrop-filter:blur(12px)}.mj-dialog[open]{animation:mj-enter .3s cubic-bezier(.2,0,0,1)}.mj-dialog .dialog-head{position:sticky;top:0;z-index:2;display:flex;align-items:center;justify-content:space-between;padding:24px 30px;background:var(--dlg-bg);border-bottom:1px solid var(--dlg-line);gap:20px}.mj-dialog .dialog-head h2{font:22px 'Geist Pixel',monospace;margin:0}.mj-dialog .dialog-close{width:40px;height:40px;border:1px solid var(--dlg-line);border-radius:50%;background:none;color:inherit;display:grid;place-items:center}.mj-dialog .dialog-body{padding:30px}.mj-dialog h3{font-size:19px;font-weight:550;margin:0 0 12px;letter-spacing:-.012em}.mj-dialog p{font-size:16.5px;line-height:1.72;color:var(--dlg-muted);margin:0 0 20px;max-width:64ch}.section-intro{font-size:18px;line-height:1.65;max-width:62ch;margin-bottom:30px}.eyebrow,.meta,.source-note{font:12px/1.72 'Geist Mono',monospace;color:var(--dlg-muted)}.eyebrow{text-transform:uppercase;letter-spacing:.09em;margin-bottom:14px}.source-note{border-top:1px solid var(--dlg-line);padding-top:14px;margin-top:20px}.source-note a{color:inherit}.section-grid,.card-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.card,.panel,.project-card,.credential-card{padding:24px;border:1px solid var(--dlg-line);border-radius:9px;background:transparent;text-align:left;color:inherit;min-width:0}.card,.project-card,.credential-card{display:flex;flex-direction:column;gap:8px}.card:has(.card-action):hover,.project-card:hover,.credential-card:hover{background:var(--dlg-hover)}.project-index{font:11.5px 'Geist Mono';color:var(--dlg-faint)}.project-title{font-size:20px}.project-category{font:11px 'Geist Mono';letter-spacing:.07em;text-transform:uppercase;color:var(--dlg-faint)}.project-summary{font-size:15.5px;line-height:1.65;color:var(--dlg-muted)}.tag-list{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0}.tag{font:11.5px/1.5 'Geist Mono';padding:5px 8px;border:1px solid var(--dlg-line);border-radius:4px}.experience-item{padding:25px 0;border-top:1px solid var(--dlg-line)}.experience-date{font:12px 'Geist Mono';color:var(--dlg-faint)}.experience-company{font-size:18px;margin:12px 0 6px;font-weight:600}.experience-role{font-size:16.5px;margin-bottom:12px}.skill-group{margin-bottom:30px}.mj-dialog ul{padding-left:22px;color:var(--dlg-muted);font-size:16.5px;line-height:1.8;max-width:64ch}.mj-dialog ul:not(.tag-list) li+li{margin-top:10px}.text-link{font-size:13.5px;display:inline-flex;align-items:center;gap:8px}.btn,.mj-dialog .choice{min-height:44px;padding:11px 16px;border:1px solid var(--dlg-line);border-radius:7px;background:transparent;color:inherit;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:8px;font-size:13.5px}.btn:hover,.mj-dialog .choice:hover{background:var(--dlg-hover)}.btn.primary{background:var(--dlg-accent);color:var(--dlg-accent-ink);border-color:var(--dlg-accent)}.actions{display:flex;align-items:center;flex-wrap:wrap;gap:9px;margin:20px 0}.empty-mark{font:50px 'Geist Pixel';color:var(--dlg-faint);margin-bottom:24px}.notice{padding:15px 17px;border-radius:7px;background:color-mix(in srgb,var(--dlg-ink) 6%,transparent);color:var(--dlg-muted);font:12.5px/1.7 'Geist Mono',monospace;margin-bottom:24px}.mj-dialog label{font-size:13.5px;display:block;margin-bottom:8px}.mj-dialog input,.mj-dialog textarea{width:100%;border:1px solid var(--dlg-line);background:transparent;color:inherit;padding:13px 15px;border-radius:7px;min-height:46px}.mj-dialog textarea{resize:vertical;line-height:1.6}.mj-dialog .message-list{display:flex;flex-direction:column;gap:14px;max-height:340px;overflow:auto;margin:22px 0}.mj-dialog .message{background:var(--dlg-raise);border-radius:10px;padding:17px;font-size:16px;line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere;max-width:64ch}.mj-dialog .message.user{align-self:flex-end;background:var(--dlg-accent);color:var(--dlg-accent-ink);max-width:90%}.mj-dialog .message small{display:block;font:11.5px 'Geist Mono';opacity:.72;margin-bottom:7px}.mj-dialog .ask-form{display:flex;gap:10px}.mj-dialog .ask-form input{flex:1;min-width:0}.mj-dialog .metric-row{display:flex;gap:36px;margin:20px 0 30px}.mj-dialog .metric{font:32px 'Geist Pixel';font-variant-numeric:tabular-nums}.mj-dialog .metric small{display:block;font:11.5px 'Geist Mono';color:var(--dlg-faint);margin-top:7px}.mj-dialog .choices{display:grid;gap:10px;margin:22px 0}.mj-dialog .choice{text-align:left;justify-content:flex-start;line-height:1.6}.mj-dialog .choice.correct{border-color:var(--dlg-ok);background:var(--dlg-ok-bg)}.mj-dialog .choice.wrong{border-color:var(--dlg-bad);background:var(--dlg-bad-bg)}.mj-dialog .feedback{padding:18px;background:var(--dlg-ok-bg);border:1px solid var(--dlg-ok);border-radius:8px;font-size:16px;line-height:1.75;margin:20px 0}.mj-dialog pre{background:var(--dlg-code-bg);color:var(--dlg-code-ink);border-radius:10px;padding:24px;overflow:auto;font:13px/1.9 'Geist Mono';tab-size:2}.mj-dialog .pipeline-list{display:flex;flex-direction:column;gap:10px}.mj-dialog .pipeline-step{display:flex;align-items:center;gap:14px;border:1px solid var(--dlg-line);border-radius:8px;padding:10px 14px}.mj-dialog .pipeline-step strong{font:12px 'Geist Mono';color:var(--dlg-faint)}.mj-dialog .pipeline-step span{flex:1;font-size:16px}.mj-dialog .pipeline-step button{width:40px;height:40px;background:none;border:1px solid var(--dlg-line);border-radius:5px}.mj-dialog details{padding:16px 0;border-bottom:1px solid var(--dlg-line)}.mj-dialog summary{font-size:16px;cursor:pointer;min-height:44px;line-height:1.7}.resource-row{padding:17px 0;border-top:1px solid var(--dlg-line);display:flex;justify-content:space-between;text-decoration:none;gap:20px}.resource-row small{display:block;color:var(--dlg-faint);font:11.5px 'Geist Mono';margin-top:5px}
@keyframes mj-enter{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
@media(max-width:600px){.mj-dialog{width:calc(100% - 20px);max-height:calc(100dvh - 30px);border-radius:12px}.mj-dialog .dialog-head{padding:15px 18px}.mj-dialog .dialog-head h2{font-size:19px}.mj-dialog .dialog-body{padding:22px 18px}.section-grid{grid-template-columns:1fr}.mj-dialog .metric-row{gap:28px}.mj-dialog .ask-form{flex-wrap:wrap}.mj-dialog .ask-form .btn{width:100%}.mj-dialog .pipeline-step{padding:8px;gap:6px}.mj-dialog .pipeline-step span{font-size:12px}}
/* The press-down was global, so a full-width row of text shrank 3% as a unit — a
   gesture that reads as a button and reads as a glitch on anything wider than one.
   Anything that is really a row opts out. */
button[class*="-entry"]:active:not(:disabled),button[class*="-row"]:active:not(:disabled){scale:1}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}button:active{scale:1}}
@media(max-width:600px){.mj-dialog .dialog-close{width:44px;height:44px}.mj-dialog .pipeline-step button{width:44px;height:44px}}

/* ---- game components added with the shortcut duel, base blitz, regex golf and reaction test ---- */
.mj-dialog .duel-stage{margin:18px 0;min-height:132px;display:grid;place-items:center;border:1px dashed var(--dlg-line);border-radius:14px;background:color-mix(in srgb,var(--dlg-ink) 4%,transparent);cursor:pointer}
.mj-dialog .duel-stage:focus-visible{outline:2px solid var(--dlg-ink);outline-offset:3px}
.mj-dialog .duel-hint{font-size:14px;letter-spacing:.14em;text-transform:uppercase;color:var(--dlg-muted)}
.mj-dialog .duel-key{font-family:ui-monospace,monospace;font-size:clamp(22px,5vw,34px);letter-spacing:.02em;padding:14px 22px;border-radius:12px;border:1px solid var(--dlg-line);border-bottom-width:4px;background:var(--dlg-bg)}
.mj-dialog .duel-key.is-hit{border-color:var(--dlg-ok);color:var(--dlg-ok)}
/* The POST line while it is still asking. */
.bs-detecting{opacity:.55;animation:bs-detect 1s steps(1) infinite}
@keyframes bs-detect{0%,49%{opacity:.55}50%,100%{opacity:.9}}
@media(prefers-reduced-motion:reduce){.bs-detecting{animation:none}}
.mj-dialog .duel-key.is-miss{border-color:var(--dlg-bad);color:var(--dlg-bad)}
.mj-dialog .blitz-card{margin:18px 0;padding:26px 20px;border:1px solid var(--dlg-line);border-radius:14px;text-align:center;background:color-mix(in srgb,var(--dlg-ink) 4%,transparent)}
.mj-dialog .blitz-kind{display:block;font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--dlg-muted);margin-bottom:12px}
.mj-dialog .blitz-value{font-family:ui-monospace,monospace;font-size:clamp(28px,7vw,46px);letter-spacing:.04em;word-break:break-all}
.mj-dialog .golf-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin:18px 0}
.mj-dialog .golf-grid h3{font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--dlg-muted);margin:0 0 8px}
.golf-list{list-style:none;margin:0;padding:0;display:grid;gap:6px}
.golf-list li{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:9px 11px;border:1px solid var(--dlg-line);border-radius:8px;font-size:14.5px}
.golf-list li span{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--dlg-muted)}
.golf-list li.is-ok{border-color:var(--dlg-ok)}
.golf-list li.is-ok span{color:var(--dlg-ok)}
.golf-list li.is-bad{border-color:var(--dlg-bad)}
.golf-list li.is-bad span{color:var(--dlg-bad)}
.mj-dialog .react-pad{width:100%;min-height:168px;margin:18px 0;display:grid;place-items:center;gap:6px;border:1px solid var(--dlg-line);border-radius:16px;background:color-mix(in srgb,var(--dlg-ink) 4%,transparent);color:inherit;font:inherit;cursor:pointer}
.mj-dialog .react-pad span{font-size:clamp(22px,5vw,32px);letter-spacing:-.01em}
.mj-dialog .react-pad small{font-size:12px;color:var(--dlg-muted);letter-spacing:.06em}
.mj-dialog .react-pad[data-state="waiting"]{background:color-mix(in srgb,var(--dlg-bad) 16%,transparent);border-color:var(--dlg-bad)}
.mj-dialog .react-pad[data-state="go"]{background:color-mix(in srgb,var(--dlg-ok) 22%,transparent);border-color:var(--dlg-ok)}
.mj-dialog .react-pad[data-state="early"]{background:color-mix(in srgb,var(--dlg-warn) 18%,transparent);border-color:var(--dlg-warn)}
@media(max-width:600px){.mj-dialog .golf-grid{grid-template-columns:1fr}}


/* ---- pets: shared shell (prototypes/shared/pets.js) ----
   The wrapper is the physics body and carries the translate; the sprite inside
   carries rotation and squash, so the two never fight over one transform. */
.mj-pet{position:fixed;top:0;left:0;z-index:20;cursor:grab;will-change:transform;touch-action:none;
  user-select:none;-webkit-user-select:none}
.mj-pet[data-state="dragging"]{cursor:grabbing}
/* At the light it is not something you pick up — one touch and it goes in. */
.mj-pet[data-state="lured"]{cursor:pointer}
.mj-pet[data-state="entering"]{pointer-events:none}

/* Wings. The beat is driven by what the pet is doing: quick while it drifts, slow
   while it holds station, frantic at the light. transform-box keeps the origin on
   the body rather than the SVG's own bounding box, which moves as the wings do. */
.mj-pet [class$="-moth-wing"]{transform-origin:32px 32px;transform-box:view-box;
  animation:mj-flap 260ms ease-in-out infinite alternate}
.mj-pet[data-state="pausing"] [class$="-moth-wing"]{animation-duration:900ms}
.mj-pet[data-state="lured"] [class$="-moth-wing"]{animation-duration:130ms}
.mj-pet[data-state="dragging"] [class$="-moth-wing"]{animation-duration:110ms}
@keyframes mj-flap{from{transform:scaleX(1)}to{transform:scaleX(.34)}}
.mj-pet-sprite{width:100%;height:100%;will-change:transform;pointer-events:none}
.mj-pet svg{display:block;overflow:visible;width:100%;height:100%}
.mj-pet-toggle{font:inherit;color:inherit;background:none;border:0;cursor:pointer;padding:0;text-align:left}

/* The bubble is positioned above the body and never intercepts the pointer, so it
   cannot block a grab. */
.mj-pet-bubble{position:absolute;bottom:100%;left:50%;transform:translateX(-50%);margin-bottom:12px;
  width:max-content;max-width:220px;padding:7px 11px;pointer-events:none;
  transition:opacity .2s ease,transform .2s cubic-bezier(.2,0,0,1)}
.mj-pet-bubble[data-state="in"]{opacity:1}
.mj-pet-bubble[data-state="out"]{opacity:0;transform:translateX(-50%) translateY(4px)}
.mj-pet-bubble[hidden]{display:none}

@media(prefers-reduced-motion:reduce){.mj-pet{display:none!important}}
/* The pet wanders on a phone too. It was switched off below 600px, which is where most
   people will meet this page — and it is the site's own mark, the thing the ask panel
   wears and the way through to Bootsector.

   It already clamps every move to innerWidth and innerHeight, so it cannot leave a small
   screen; what it needed was to be smaller relative to one, and to keep further clear of
   the text, because a 44px insect on a 390px column covers a great deal more of it than
   the same insect on a 1440px page. Its bubble also has to fit. */
/* The sprite's size is set in pets.js, because the same number drives the bounds maths
   and a CSS override would leave the geometry believing something the element is not.
   What belongs here is the speech bubble, which has to fit a narrow screen. */
@media(max-width:600px){
  .mj-pet-bubble{font-size:11.5px;padding:5px 9px;max-width:min(62vw,220px)}
}
/* Coarse pointers get a larger grab area than the sprite, without a larger sprite. */
@media(pointer:coarse){
  .mj-pet{padding:8px;margin:-8px}
}

/* ---- contrast check, complexity order and kerning eye ---- */
.mj-dialog .contrast-swatch{margin:18px 0;padding:30px 24px;border-radius:14px;border:1px solid var(--dlg-line);display:grid;gap:10px}
.mj-dialog .contrast-swatch strong{font-size:16px;line-height:1.6;font-weight:500;color:inherit}
.mj-dialog .contrast-swatch span{font-family:ui-monospace,monospace;font-size:12px;letter-spacing:.06em;opacity:.85}
.mj-dialog [data-contrast].correct{border-color:var(--dlg-ok);color:var(--dlg-ok)}
.mj-dialog [data-contrast].wrong{border-color:var(--dlg-bad);color:var(--dlg-bad)}
.mj-dialog .kern-stage{margin:20px 0;padding:26px 20px;border:1px solid var(--dlg-line);border-radius:14px;display:grid;gap:14px;justify-items:center;background:color-mix(in srgb,var(--dlg-ink) 3%,transparent)}
.mj-dialog .kern-row{font-family:'Geist Mono',ui-monospace,monospace;font-size:clamp(38px,9vw,64px);line-height:1}
.mj-dialog .kern-rule{width:100%;height:1px;background:var(--dlg-line)}
.mj-dialog input[type=range]{width:100%;min-height:44px;accent-color:var(--dlg-range);padding:0;border:0;background:none}
.mj-dialog output{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums}

/* ---- command palette (shared dialog surface) ---- */
.mj-dialog[data-mode="palette"] .dialog-body{padding:22px 24px 24px}
.mj-dialog #palette-input{width:100%;font-size:17px;padding:14px 16px;border:1px solid var(--dlg-line);border-radius:9px;background:transparent;color:inherit}
.mj-dialog #palette-help{margin:10px 2px 14px}
.mj-dialog .palette-list{list-style:none;margin:0;padding:0;max-height:min(52vh,420px);overflow:auto;display:flex;flex-direction:column;gap:2px}
.mj-dialog .palette-row{display:grid;grid-template-columns:1fr auto;grid-template-areas:"name group" "hint hint";gap:2px 14px;width:100%;text-align:left;padding:11px 13px;border:0;border-radius:8px;background:none;color:inherit;min-height:44px}
.mj-dialog .palette-row:hover,.mj-dialog .palette-row.is-active{background:color-mix(in srgb,var(--dlg-ink) 8%,transparent)}
.mj-dialog .palette-name{grid-area:name;font-size:15.5px}
.mj-dialog .palette-group{grid-area:group;font:10px 'Geist Mono',monospace;text-transform:uppercase;letter-spacing:.09em;color:var(--dlg-muted);align-self:center}
.mj-dialog .palette-hint{grid-area:hint;font-size:12px;color:var(--dlg-muted)}
.mj-dialog .palette-empty{padding:16px 13px;font-size:14.5px;color:var(--dlg-muted)}

/* ---- table of contents (each design paints it, this is the skeleton) ---- */
.toc-label{margin:0 0 10px}
.toc-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:1px}
.toc-list a{display:flex;gap:10px;align-items:baseline;padding:5px 0;text-decoration:none;color:inherit}
.toc-index{flex-shrink:0;opacity:.55;font-variant-numeric:tabular-nums}
.toc-text{min-width:0}

/* ---- expanded content: about, stack context, note synopses ---- */
.about-para p,.about-para p{margin-bottom:12px}
.about-para,.about-para{margin-bottom:26px}
.skill-context,.skill-context{font-size:16.5px;line-height:1.72;margin:0 0 14px;max-width:64ch}
.note-synopsis,.note-synopsis{font-size:16.5px;line-height:1.75;max-width:64ch}


/* ---- the shared component vocabulary, as it appears inside a dialog ---- */
.card{margin:0}
.card-title{font-size:19px;font-weight:550;margin:0;letter-spacing:-.012em}
.card-action{display:flex;align-items:baseline;justify-content:space-between;gap:12px;width:100%;
  padding:0;border:0;background:none;color:inherit;font:inherit;text-align:left}
.card-arrow{font-size:12px;opacity:.5}
.card:hover .card-arrow{opacity:1}
.card-meta{font:10px 'Geist Mono',monospace;letter-spacing:.08em;text-transform:uppercase;color:var(--dlg-muted);margin:0}
.card-summary{font-size:16px;line-height:1.68;color:var(--dlg-muted);margin:0}
.card-role-title{font-size:16px;margin:0}
.card-points{padding-left:18px;margin:4px 0 0}
.tag-list{list-style:none;padding:0}
.spec{margin:6px 0 0;display:grid;gap:1px;background:var(--dlg-line);border:1px solid var(--dlg-line)}
.spec-row{display:grid;grid-template-columns:96px minmax(0,1fr);gap:12px;background:var(--dlg-bg);padding:8px 11px}
.spec dt{font:10px 'Geist Mono',monospace;letter-spacing:.08em;text-transform:uppercase;color:var(--dlg-muted)}
.spec dd{margin:0;font-size:16px;line-height:1.6}
.spec .tag-list{margin:0}
.pull-quote{margin:0 0 22px;padding:0}
.pull-quote blockquote{margin:0;font:24px/1.4 'Source Serif 4',Georgia,serif}
.pull-quote figcaption{font:10px 'Geist Mono',monospace;letter-spacing:.08em;text-transform:uppercase;color:var(--dlg-muted);margin-top:10px}
.resource-list{list-style:none;margin:0;padding:0}
.resource-name{display:block}
.section-subhead{font-size:16px;margin:30px 0 12px}
.section-note{font-size:15px;line-height:1.68;color:var(--dlg-muted);max-width:64ch}
.note-title{display:block;margin-top:4px}
.mj-dialog time{font-variant-numeric:tabular-nums}
@media(max-width:600px){.card-grid{grid-template-columns:1fr}.spec-row{grid-template-columns:1fr;gap:2px}}

/* ---- the runner ---- */
.mj-dialog .run-canvas{display:block;width:100%;margin:18px 0;border:1px solid var(--dlg-line);
  border-radius:10px;background:color-mix(in srgb,var(--dlg-ink) 3%,transparent);touch-action:manipulation}

/* ---- the entrance meter (prototypes/shared/stage.js) ----
   Injected into whatever overlay a design uses, so a long entrance always says how
   far along it is and always says it can be skipped. */
.mj-stage-meter{position:absolute;left:0;right:0;bottom:0;padding:0 0 22px;display:grid;
  justify-items:center;gap:11px;pointer-events:none}
.mj-stage-track{width:min(340px,52vw);height:2px;overflow:hidden}
.mj-stage-track i{display:block;width:100%;height:100%;transform:scaleX(0);transform-origin:0 50%}
.mj-stage-skip{margin:0}
@media(max-width:600px){.mj-stage-meter{padding-bottom:16px}}

/* ---- tag list ----
   A shared component has to carry its own layout, not borrow the dialog's. Without
   this the same markup renders as a bulleted column anywhere else it is used. */
.tag-list{display:flex;flex-wrap:wrap;align-items:flex-start;gap:6px;list-style:none;padding:0;margin:10px 0}
.tag-list > li{margin:0;display:block}

/* ---- exposure: what the browser gives away ----------------------------------
   Built entirely on the dialog tokens, so it inherits whichever design and theme it is
   opened in without a second palette to keep in step. */
.xp-lede{font-size:17px;line-height:1.65;color:var(--dlg-ink);max-width:62ch;margin-bottom:26px}
.xp-sub{font-size:15px;line-height:1.6;margin:0 0 16px;max-width:64ch}
.xp-loading{color:var(--dlg-faint);font:12.5px/1.7 'Geist Mono',monospace;padding:20px 0}
.mj-dialog .xp h3,.mj-dialog .dialog-body > h3{margin-top:34px}
.xp-table{border-top:1px solid var(--dlg-line);margin-bottom:26px}
.xp-row{display:grid;grid-template-columns:150px minmax(0,1fr);gap:18px;
  padding:13px 0;border-bottom:1px solid var(--dlg-line);align-items:baseline}
.xp-key{font:11.5px/1.7 'Geist Mono',monospace;letter-spacing:.07em;
  text-transform:uppercase;color:var(--dlg-faint)}
.xp-val{font:14px/1.6 'Geist Mono',monospace;color:var(--dlg-ink);overflow-wrap:anywhere}
.xp-note{font-size:14px;line-height:1.6;color:var(--dlg-muted);margin-top:5px;max-width:58ch}
.xp-fine{font-size:13px;line-height:1.6;color:var(--dlg-faint);max-width:60ch}

/* The hash is the moment the argument lands, so it is the one thing given weight. */
.xp-signature{border:1px solid var(--dlg-line);background:var(--dlg-raise);
  border-radius:9px;padding:24px;margin:8px 0 6px}
.xp-sig-label{font:11.5px/1.7 'Geist Mono',monospace;letter-spacing:.08em;
  text-transform:uppercase;color:var(--dlg-faint)}
.xp-sig-hash{font:600 clamp(22px,4.4vw,32px)/1.25 'Geist Mono',monospace;
  color:var(--dlg-ink);letter-spacing:.04em;overflow-wrap:anywhere;margin:8px 0 16px}
.xp-signature p{margin-bottom:12px}
.xp-signature p:last-child{margin-bottom:0}

.xp-asks{display:flex;flex-wrap:wrap;gap:8px;padding:0;margin:0 0 8px;list-style:none;max-width:none}
.xp-asks li{font:11.5px/1.5 'Geist Mono',monospace;padding:7px 11px;
  border:1px solid var(--dlg-line);border-radius:4px;color:var(--dlg-muted)}
.xp-ip{border:1px solid var(--dlg-line);border-radius:9px;padding:20px;margin-bottom:6px}
.xp-ip .xp-fine{margin:14px 0 0}
.xp-ip .xp-table{margin-bottom:0}
.xp-tips li{margin-bottom:14px}
.xp-tips b{color:var(--dlg-ink)}
.mj-dialog code{font:13px 'Geist Mono',monospace;background:var(--dlg-raise);padding:2px 5px;border-radius:4px}
@media(max-width:600px){
  .xp-row{grid-template-columns:1fr;gap:2px}
  .xp-signature,.xp-ip{padding:16px}
}

/* ---- reveal: the entrance reads the connection back to you -------------------
   Sits inside whichever cover is already playing, so it inherits that design's ground
   and needs no palette of its own. Pointer-transparent and aria-hidden: the page
   underneath is rendered and readable the whole time. */
.rv{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none;
  z-index:5;opacity:0;transition:opacity .5s ease}
.rv[data-state]{opacity:1}
.rv[data-state="out"]{opacity:0}
.rv-inner{width:min(560px,86vw);text-align:left}
.rv-eyebrow{font:500 11px/1.6 'Geist Mono',ui-monospace,monospace;letter-spacing:.22em;
  text-transform:uppercase;opacity:.55;margin:0 0 22px}
.rv-inner{width:min(600px,88vw)}
.rv-lines{display:flex;flex-direction:column;gap:11px;min-height:210px}
.rv-line{display:grid;grid-template-columns:132px minmax(0,1fr);gap:16px;margin:0;
  opacity:0;transform:translateY(10px);
  transition:opacity .5s ease,transform .5s cubic-bezier(.2,0,0,1)}
.rv-line.is-in{opacity:1;transform:none}
.rv-label{font:500 11px/1.7 'Geist Mono',ui-monospace,monospace;letter-spacing:.14em;
  text-transform:uppercase;opacity:.5}
.rv-value{font:500 15px/1.7 'Geist Mono',ui-monospace,monospace;overflow-wrap:anywhere;min-height:1.7em}
/* A caret while a value is being typed, so a half-written line reads as in progress
   rather than as a rendering fault. */
.rv-value.is-typing::after{content:"";display:inline-block;width:.55em;height:1.05em;
  margin-left:.06em;background:currentColor;vertical-align:-.16em;animation:rv-caret .9s steps(1) infinite}
@keyframes rv-caret{0%,49%{opacity:1}50%,100%{opacity:0}}
.rv-tag{margin:30px 0 0;padding-top:22px;position:relative;
  font:500 16.5px/1.55 'Geist Mono',ui-monospace,monospace;letter-spacing:-.01em;
  opacity:0;transform:translateY(8px);transition:opacity .6s ease .15s,transform .6s cubic-bezier(.2,0,0,1) .15s}
/* The rule draws itself across before the sentence arrives. */
.rv-tag::before{content:"";position:absolute;left:0;top:0;height:1px;width:100%;
  background:currentColor;opacity:.28;transform:scaleX(0);transform-origin:left;
  transition:transform .7s cubic-bezier(.2,0,0,1)}
.rv-tag span{display:block;font-size:12.5px;opacity:.6;margin-top:9px;letter-spacing:.03em;line-height:1.6}
.rv[data-state="tag"] .rv-tag::before,.rv[data-state="out"] .rv-tag::before{transform:scaleX(1)}
.rv[data-state="tag"] .rv-tag,.rv[data-state="out"] .rv-tag{opacity:1;transform:none}
@media(max-width:600px){
  .rv-line{grid-template-columns:1fr;gap:1px}
  .rv-value{font-size:14px}
  .rv-lines{min-height:230px}
}
@media(prefers-reduced-motion:reduce){.rv{display:none}}
@media(prefers-reduced-motion:reduce){.rv-value.is-typing::after{animation:none}}

/* ---- community dock: live count, total, and the way into the room ---- */
.cm-dock{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.cm-dock-live{display:flex;align-items:center;gap:8px}
.cm-pips{display:inline-flex;align-items:center}
.cm-pips i{width:13px;height:13px;border-radius:50%;display:block;
  background:currentColor;opacity:.9;margin-left:-4px;
  box-shadow:0 0 0 2px var(--cm-ground,transparent);
  animation:cm-pip .32s cubic-bezier(.2,0,0,1) both;animation-delay:calc(var(--i) * 60ms)}
.cm-pips i:first-child{margin-left:0}
@keyframes cm-pip{from{transform:scale(.4);opacity:0}to{transform:scale(1);opacity:.9}}
.cm-dock-now,.cm-dock-total{font:500 11.5px/1.6 'Geist Mono',ui-monospace,monospace;letter-spacing:.06em}
.cm-dock-now b,.cm-dock-total b{font-weight:600}
.cm-dock-off{opacity:.6}
.cm-dock-chat{display:inline-flex;align-items:center;gap:8px;min-height:32px;padding:5px 11px;
  border:1px solid currentColor;border-radius:4px;background:none;color:inherit;cursor:pointer;
  font:500 11.5px/1.6 'Geist Mono',ui-monospace,monospace;letter-spacing:.07em;opacity:.8;
  transition:opacity .2s,background-color .2s}
.cm-dock-chat:hover{opacity:1}
.cm-dock-icon{flex-shrink:0;opacity:.75}
.cm-dock-chat:hover .cm-dock-icon{opacity:1}
@media(prefers-reduced-motion:reduce){.cm-pips i{animation:none}}

/* ---- the room itself ---- */
.cm-intro,.cm-offline{font-size:15px;line-height:1.62;max-width:62ch}
.cm-log{display:flex;flex-direction:column;gap:14px;max-height:min(46vh,380px);
  overflow:auto;overscroll-behavior:contain;padding:4px 2px;margin:20px 0}
.cm-empty{color:var(--dlg-faint);font:12.5px/1.7 'Geist Mono',monospace;margin:0;padding:24px 0}
.cm-msg{background:var(--dlg-raise);border-radius:10px;padding:14px 16px}
.cm-msg-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  font:11.5px/1.6 'Geist Mono',monospace;color:var(--dlg-faint);margin-bottom:6px}
.cm-msg-head b{color:var(--dlg-ink);font-weight:600}
.cm-msg-head{position:relative}
.cm-del{margin-left:auto;padding:2px 8px;min-height:24px;border:1px solid var(--dlg-line);
  border-radius:4px;background:none;color:var(--dlg-faint);cursor:pointer;
  font:10.5px/1.5 'Geist Mono',ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;
  transition:color .18s,border-color .18s}
.cm-del:hover:not(:disabled){color:var(--dlg-bad);border-color:var(--dlg-bad)}
.cm-del:disabled{opacity:.5;cursor:default}
.cm-admin{font:11.5px/1.7 'Geist Mono',ui-monospace,monospace;color:var(--dlg-accent);
  letter-spacing:.05em;margin:-8px 0 18px}
.cm-msg p{margin:0;font-size:15.5px;line-height:1.65;color:var(--dlg-ink);
  white-space:pre-wrap;overflow-wrap:anywhere;max-width:62ch}
.cm-form{display:flex;gap:9px;flex-wrap:wrap}
.cm-name{flex:0 0 150px;min-width:0}
.cm-text{flex:1;min-width:0}
.cm-status{margin-top:12px}
@media(max-width:600px){
  .cm-name{flex:1 1 100%}
  .cm-form .btn{width:100%}
}

/* ---- writing: the index, and one piece read in full ---- */
.post-list{border-top:1px solid var(--dlg-line)}
.post-row{display:block;width:100%;text-align:left;background:none;border:0;
  border-bottom:1px solid var(--dlg-line);padding:20px 0;color:inherit;cursor:pointer;
  transition:padding-left .2s}
.post-row:hover{padding-left:6px}
.post-row:hover .post-title{color:var(--dlg-accent)}
.post-meta{display:block;font:11.5px/1.7 'Geist Mono',monospace;letter-spacing:.07em;
  text-transform:uppercase;color:var(--dlg-faint)}
.post-title{display:block;font-size:20px;font-weight:550;letter-spacing:-.012em;
  margin:6px 0 8px;color:var(--dlg-ink);transition:color .2s}
.post-standfirst{display:block;font-size:15.5px;line-height:1.62;color:var(--dlg-muted);max-width:64ch}
.post .post-standfirst{font-size:19px;line-height:1.55;color:var(--dlg-ink);
  margin:14px 0 28px;max-width:60ch}
.post p{font-size:17px;line-height:1.75;color:var(--dlg-muted);max-width:62ch;margin:0 0 1.15em}
.post .post-meta{margin-bottom:0}
@media(max-width:600px){
  .post p{font-size:16px}
  .post .post-standfirst{font-size:17.5px}
}

/* ---- the credential wall ----------------------------------------------------
   Eight credentials as stacked definition lists was a page nobody reads. A tile carries
   the three things that matter — who issued it, what it is, and how to check it — and a
   grid of them can be scanned in one pass. */
.cred-wall{display:grid;grid-template-columns:repeat(auto-fill,minmax(178px,1fr));
  gap:12px;margin:6px 0 26px}
.cred{display:flex;flex-direction:column;align-items:flex-start;gap:0;
  padding:20px 18px 18px;border:1px solid var(--dlg-line);border-radius:10px;
  text-decoration:none;color:inherit;background:transparent;position:relative;
  transition:border-color .2s ease,background-color .2s ease,transform .2s cubic-bezier(.2,0,0,1)}
a.cred:hover,a.cred:focus-visible{border-color:var(--dlg-accent);background:var(--dlg-raise);transform:translateY(-2px)}
.cred-plain{opacity:.72}

.cred-face{display:grid;place-items:center;width:42px;height:42px;margin-bottom:14px;
  border:1px solid var(--dlg-line);border-radius:9px;background:var(--dlg-raise);flex-shrink:0}
.cred-face img{width:26px;height:26px;object-fit:contain}
.cred-monogram{font:600 13px/1 'Geist Mono',ui-monospace,monospace;letter-spacing:.06em;
  color:var(--dlg-faint)}
a.cred:hover .cred-face{border-color:var(--dlg-accent)}
a.cred:hover .cred-monogram{color:var(--dlg-accent)}

.cred-title{font-size:15.5px;font-weight:550;line-height:1.35;letter-spacing:-.008em;
  color:var(--dlg-ink);margin-bottom:7px}
.cred-issuer{font:11.5px/1.55 'Geist Mono',ui-monospace,monospace;color:var(--dlg-faint);
  letter-spacing:.03em;margin-bottom:12px}
.cred-verify{margin-top:auto;font:11px/1.5 'Geist Mono',ui-monospace,monospace;
  letter-spacing:.05em;text-transform:uppercase;color:var(--dlg-faint);
  display:inline-flex;align-items:center;gap:6px;transition:color .2s}
.cred-verify .icon{width:12px;height:12px}
a.cred:hover .cred-verify{color:var(--dlg-accent)}
.cred-plain .cred-verify{text-transform:none;letter-spacing:.02em}

@media(max-width:600px){
  .cred-wall{grid-template-columns:repeat(auto-fill,minmax(148px,1fr));gap:10px}
  .cred{padding:16px 14px 14px}
}
@media(prefers-reduced-motion:reduce){.cred{transition:none}a.cred:hover{transform:none}}

/* ---- ask the moth ---- */
.ask-head{display:flex;align-items:flex-start;gap:16px;margin-bottom:22px}
.ask-face{flex:0 0 auto;width:46px;height:46px;display:grid;place-items:center;
  border:1px solid var(--dlg-line);border-radius:10px;background:var(--dlg-raise);
  color:var(--dlg-ink)}
.ask-face svg{width:30px;height:30px}
.ask-face-dot{width:10px;height:10px;border-radius:50%;background:currentColor}
.ask-blurb{font-size:15px;line-height:1.6;color:var(--dlg-muted);margin:0;max-width:52ch}
.ask-status{margin-top:12px;min-height:1.4em}

/* Three dots while it thinks. Without one, a slow model reads as a broken panel. */
.ask-thinking{display:flex;align-items:center;gap:12px}
.ask-dots{display:inline-flex;gap:5px}
.ask-dots i{width:6px;height:6px;border-radius:50%;background:var(--dlg-faint);
  animation:ask-blink 1.1s ease-in-out infinite}
.ask-dots i:nth-child(2){animation-delay:.16s}
.ask-dots i:nth-child(3){animation-delay:.32s}
@keyframes ask-blink{0%,80%,100%{opacity:.25}40%{opacity:1}}
@media(prefers-reduced-motion:reduce){.ask-dots i{animation:none;opacity:.6}}
@media(max-width:600px){
  .ask-head{gap:12px}
  .ask-face{width:40px;height:40px}
  .ask-face svg{width:26px;height:26px}
}
