/* Instruments shared by the home page and the product pages (research/DESIGN.md "Patch", changelog 28 Oct 2026):
   the example-lab topology (with its staged terminal pane, power LEDs and packets), the image finder, the internet
   path figure and the plan table. Everything here is complete without script. */

/* ---------- Topology instrument ---------- */
.topo{container-type:inline-size;margin:0}
.topo__frame{border:1px solid var(--c-line-2);border-radius:var(--r-fig);background:var(--c-bench)}
.topo__bar{display:flex;align-items:center;justify-content:space-between;gap:var(--s-3);padding:10px 14px;border-bottom:1px solid var(--c-line)}
.topo__bar p{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:500 12.5px/1.3 var(--f-mono);color:var(--c-ink-2)}
.topo__bar b{flex:none;font:500 12px/1 var(--f-mono);color:var(--c-ink-3)}
.topo__stage{position:relative;aspect-ratio:880/440;background-image:radial-gradient(circle at 1px 1px,var(--c-line-2) 1px,transparent 1.4px);background-size:20px 20px;background-position:10px 10px;touch-action:pan-y;user-select:none;-webkit-user-select:none}
.topo__svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
.topo__svg--tall{display:none}
.topo .bound{fill:none;stroke:var(--c-line-3);stroke-width:1;stroke-dasharray:4 5}
.topo .bound-label{font:500 11.5px var(--f-mono);fill:var(--c-ink-3)}
.topo .cable{fill:none;stroke:var(--c-cable);stroke-width:1.7;stroke-linecap:round}
.topo .cable--wan{stroke-dasharray:1 5;stroke-width:2}
.topo .cable--virtual{stroke:var(--c-line-3);stroke-dasharray:2 6;stroke-width:1.4}
.topo .ov{fill:none;stroke:var(--c-signal-stroke);stroke-width:2.8;stroke-linecap:round;opacity:0;transition:opacity .18s var(--ease)}
.topo .oob{fill:none;stroke:var(--c-signal-stroke);stroke-width:2;stroke-dasharray:7 5;stroke-linecap:round;opacity:0;transition:opacity .18s var(--ease)}
.topo .tag{font:500 11px var(--f-mono);fill:var(--c-ink-2);paint-order:stroke;stroke:var(--c-bench);stroke-width:4px;stroke-linejoin:round}
.topo .tag--mid{fill:var(--c-ink-3)}
.topo .pulse{fill:var(--c-signal-stroke);opacity:0}
.topo .pulse.is-on{opacity:1}
/* Packets: out of the computer solid, coming back hollow, so direction reads without colour alone. */
/* Packets: HTML marks moved by compositor-run transform keyframes (topology.mjs send); hollow ones come back. */
.topo .pk{position:absolute;left:0;top:0;box-sizing:border-box;translate:-50% -50%;border-radius:1.5px;background:var(--c-signal-stroke);opacity:0;pointer-events:none}
.topo .pk--round{border-radius:50%}
.topo .pk--hollow{background:var(--c-bench);border:1.6px solid var(--c-signal-stroke)}

/* Which overlay shows for which method: pure CSS, so the picker works before (and without) the script. */
.topo:not([data-js]):has(#m-ssh:checked) [data-m~=ssh],
.topo:not([data-js]):has(#m-browser:checked) [data-m~=browser],
.topo:not([data-js]):has(#m-rdp:checked) [data-m~=rdp],
.topo:not([data-js]):has(#m-vnc:checked) [data-m~=vnc],
.topo:not([data-js]):has(#m-web:checked) [data-m~=web],
.topo:not([data-js]):has(#m-internet:checked) [data-m~=internet],
.topo[data-js] .is-lit{opacity:1}

/* Devices: real buttons over the drawing, at the lab file's positions. Lab devices carry a power LED. */
.node{position:absolute;left:var(--x);top:var(--y);display:grid;justify-items:center;gap:3px;width:max-content;max-width:170px;padding:0;border:0;border-radius:8px;background:none;color:var(--c-ink);text-align:center;transform:translate(-50%,-23px);cursor:pointer;-webkit-tap-highlight-color:transparent}
.node__plate{position:relative;display:grid;place-items:center;width:46px;height:46px;border:1px solid var(--c-line-3);border-radius:8px;background:var(--c-surface);color:var(--c-ink);transition:border-color .14s,box-shadow .14s,color .14s,background-color .14s,opacity .3s}
.node--outside .node__plate{border-style:dashed;background:var(--c-bench)}
.node svg{width:26px;height:26px;stroke:currentColor;fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
.node__name{font-weight:600;font-size:13.5px;line-height:1.2;padding:1px 4px;border-radius:3px;background:color-mix(in srgb,var(--c-bench) 88%,transparent)}
.node__kind{font:450 11px/1.2 var(--f-mono);color:var(--c-ink-3);padding:0 4px;background:color-mix(in srgb,var(--c-bench) 88%,transparent)}
.node:hover .node__plate{border-color:var(--c-ink-2)}
.node:focus-visible{outline:2px solid var(--c-focus);outline-offset:4px}
.node[aria-pressed=true] .node__plate{border-color:var(--c-signal-stroke);box-shadow:0 0 0 4px var(--c-signal-wash);color:var(--c-signal)}
.node.is-target .node__plate{border-color:var(--c-signal-stroke);color:var(--c-signal)}
.node.is-dragging{cursor:grabbing;z-index:3}
.node.is-dragging .node__plate{box-shadow:0 10px 24px -10px light-dark(rgba(14,18,23,.45),rgba(0,0,0,.9))}
.topo[data-js] .node{touch-action:none}
.led{position:absolute;top:5px;right:5px;width:6px;height:6px;border-radius:50%;background:var(--c-up);box-shadow:0 0 0 2px color-mix(in srgb,var(--c-up) 22%,transparent);transition:background-color .2s,box-shadow .35s}
/* Boot (only when the inline head script found motion allowed): devices start dark and power on in order.
   A CSS fallback powers everything on after 3.5 s even if the script never runs. */
.boot .topo--staged .node:not(.is-on) .node__plate{opacity:.45;animation:boot-fallback 0s 3.5s forwards}
.boot .topo--staged .node:not(.is-on) .led{background:var(--c-ink-3);box-shadow:none;animation:led-fallback 0s 3.5s forwards}
@keyframes boot-fallback{to{opacity:1}}
@keyframes led-fallback{to{background:var(--c-up)}}

/* Picker, recording pane and explanation */
.topo__controls{display:grid;gap:var(--s-4);padding:var(--s-4);border-top:1px solid var(--c-line)}
.topo__picker{display:flex;align-items:center;gap:var(--s-3);min-width:0}
.topo__picker .seg{flex-wrap:nowrap;overflow-x:auto;max-width:100%;scrollbar-width:none}
.topo__picker .seg::-webkit-scrollbar{display:none}
.topo__panel{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr);gap:var(--s-5);min-height:150px}
.how{display:none;gap:var(--s-3);align-content:start}
/* Without the script, the checked radio picks the panel; with it, a data attribute does (cheaper to restyle). */
.topo:not([data-js]):has(#m-ssh:checked) .how[data-method=ssh],
.topo:not([data-js]):has(#m-browser:checked) .how[data-method=browser],
.topo:not([data-js]):has(#m-rdp:checked) .how[data-method=rdp],
.topo:not([data-js]):has(#m-vnc:checked) .how[data-method=vnc],
.topo:not([data-js]):has(#m-web:checked) .how[data-method=web],
.topo:not([data-js]):has(#m-internet:checked) .how[data-method=internet],
.topo[data-method=ssh] .how[data-method=ssh],
.topo[data-method=browser] .how[data-method=browser],
.topo[data-method=rdp] .how[data-method=rdp],
.topo[data-method=vnc] .how[data-method=vnc],
.topo[data-method=web] .how[data-method=web],
.topo[data-method=internet] .how[data-method=internet]{display:grid}
.how p{font-size:15px;line-height:1.5;color:var(--c-ink-2)}
.steps{display:grid;gap:6px}
.steps>span{font-size:13.5px;color:var(--c-ink-3)}
.steps>span code{font-size:12.5px}
.device{display:grid;gap:6px;align-content:start;padding-left:var(--s-5);border-left:1px solid var(--c-line)}
.device__title{font-size:16px;font-weight:600;color:var(--c-ink)}
.device .t-meta{margin-top:-2px}
.device ul{list-style:none;display:grid;gap:4px;font-size:14px;color:var(--c-ink-2)}
.device dl{display:grid;grid-template-columns:auto 1fr;gap:2px 10px;margin:4px 0 0;font-size:13.5px}
.device dt{color:var(--c-ink-3)}
.device dd{margin:0;color:var(--c-ink)}
.topo__table{margin-top:var(--s-3)}
.topo__table summary{cursor:pointer;font-size:14px;font-weight:550;color:var(--c-ink-2)}
.topo__table .table-wrap{margin-top:var(--s-3)}
.topo__table td code{white-space:normal;word-break:break-word}
/* The staged pane: a short terminal window that replays the real recording for the chosen way in. */
.term--pane{container-type:inline-size;border-radius:8px;view-transition-name:stage-pane}
.term--pane .term__screen{min-height:calc(9 * 1.42em + 28px);height:calc(9 * 1.42em + 28px);contain:strict;font-size:clamp(10px,calc(100cqi / var(--cols,96) * 1.6),13px);overflow:hidden;padding:12px 14px 14px}
.term--pane .term__title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.stage__note{margin:0;padding:8px 14px 10px;border-top:1px solid var(--c-term-line);font-size:12.5px;color:var(--c-term-dim)}

@container (max-width:600px){
 .topo__stage{aspect-ratio:420/720}
 .topo__svg--wide{display:none}
 .topo__svg--tall{display:block}
 .node{left:var(--tx);top:var(--ty)}
 .node__plate{width:44px;height:44px}
 .node__name{font-size:12.5px}
 .node__kind{display:none}
 .topo__panel{grid-template-columns:1fr}
 .topo__picker .seg{flex-wrap:wrap;overflow:visible}
 .topo__picker .seg label{height:34px;padding:0 11px;font-size:14px}
 .device{padding:var(--s-4) 0 0;border-left:0;border-top:1px solid var(--c-line)}
 /* Phones: the pane keeps about 62 columns at a readable size and fades out what runs past the edge. */
 .term--pane .term__screen{font-size:clamp(9.5px,calc(100cqi / 62 * 1.6),12px);-webkit-mask-image:linear-gradient(90deg,#000 88%,transparent);mask-image:linear-gradient(90deg,#000 88%,transparent)}
}

/* ---------- Image finder (finder.mjs; markup and index: build.mjs finderHTML). Search by vendor, product, version or
   name; matches are grouped by product, each exact name with a Copy button. Without script: a few names and a link. ---------- */
.finder{container-type:inline-size;display:grid;gap:var(--s-4);min-width:0;padding:var(--s-5);border:1px solid var(--c-line);border-radius:var(--r-card);background:var(--c-surface);box-shadow:var(--shadow-card)}
.finder__head{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:var(--s-2)}
.finder__big{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px}
.finder__big strong{font-family:var(--f-display);font-size:clamp(36px,4vw,56px);line-height:1;font-weight:600;font-stretch:87.5%;letter-spacing:-.012em;font-variant-numeric:tabular-nums;color:var(--c-ink)}
.finder__big span{font-size:15px;color:var(--c-ink-2)}
.roll{display:inline-block;animation:roll .22s var(--ease-out)}
@keyframes roll{from{transform:translateY(.35em);opacity:.2}}
.finder__search{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;gap:8px}
.finder__search svg{position:absolute;left:14px;top:26px;transform:translateY(-50%);color:var(--c-ink-3);pointer-events:none}
.finder__search .input{flex:1 1 220px;min-width:0;height:52px;padding-left:42px;font-size:16px}
.finder__preset{flex:none;min-height:40px;padding:0 14px;border:1px solid var(--c-line-2);border-radius:var(--r-ctl);background:var(--c-surface);color:var(--c-ink-2);font:500 14px/1.2 var(--f-sans);cursor:pointer}
.finder__preset:hover{border-color:var(--c-ink-3);color:var(--c-ink)}
.finder__preset[aria-pressed=true]{border-color:var(--c-ink);background:var(--c-hover);color:var(--c-ink);box-shadow:inset 0 0 0 1px var(--c-ink)}
.finder__meta{display:grid;gap:4px;margin-top:-6px}
.finder__help{font-size:13.5px;line-height:1.5;color:var(--c-ink-3)}
.finder__summary{font-size:14px;line-height:1.5;color:var(--c-ink-2)}
.finder__groups{display:grid;align-content:start;gap:var(--s-5);max-height:560px;overflow:auto;overscroll-behavior:contain;margin:0 -6px;padding:6px;scrollbar-width:thin}
.finder__groups:focus-visible{outline-offset:-2px}
.finder__index{display:flex;flex-wrap:wrap;gap:6px}
.finder__index button{min-height:32px;padding:0 12px;border:1px solid var(--c-line-2);border-radius:var(--r-ctl);background:var(--c-surface);color:var(--c-ink);font:500 13.5px/1 var(--f-sans);cursor:pointer}
.finder__index button.is-code{font:500 13px/1 var(--f-mono)}
.finder__index button:hover{border-color:var(--c-ink-3);background:var(--c-hover)}
.finder__group{display:grid;gap:2px;min-width:0}
.finder__gh{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 10px;padding-bottom:6px;border-bottom:1px solid var(--c-line-2)}
.finder__prod{font:600 15px/1.35 var(--f-sans);color:var(--c-ink);text-decoration:none}
.finder__prod.is-code{font:600 14px/1.35 var(--f-mono)}
a.finder__prod{text-decoration:underline;text-decoration-color:var(--c-line-3);text-underline-offset:3px}
a.finder__prod:hover{text-decoration-color:currentColor}
.finder__vendor{font-size:13.5px;color:var(--c-ink-3)}
.finder__n{margin-left:auto;font:500 12.5px/1.35 var(--f-mono);color:var(--c-ink-3);font-variant-numeric:tabular-nums;white-space:nowrap}
.finder__names{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:var(--s-5);margin:0;padding:0;list-style:none}
.finder__names li{min-width:0;border-bottom:1px solid var(--c-line)}
.finder__names .cmd{gap:8px;padding:4px 0;border:0;border-radius:0;background:none}
.finder__names code{padding:3px 0;font-size:13px;line-height:1.4;color:var(--c-ink-2)}
.finder__names .copy{height:26px;padding:0 9px;font-size:12.5px}
.finder mark{border-radius:2px;background:var(--c-signal-wash);color:var(--c-ink)}
.finder__all{justify-self:start;margin-top:2px;padding:6px 0;border:0;background:none;color:var(--c-ink);font:600 13.5px/1.2 var(--f-sans);text-decoration:underline;text-decoration-color:var(--c-line-3);text-underline-offset:3px;cursor:pointer}
.finder__all:hover{text-decoration-color:currentColor}
.finder__empty{display:grid;gap:var(--s-3);justify-items:start;font-size:15px;line-height:1.5;color:var(--c-ink-2)}
.finder__more,.finder__nojs{font-size:14px;line-height:1.5;color:var(--c-ink-2)}
html.js .finder__nojs,html:not(.js) .finder__search,html:not(.js) .finder__meta,html:not(.js) .finder__index,html:not(.js) .finder .copy{display:none}
@container (max-width:600px){.finder__names{grid-template-columns:minmax(0,1fr)}}
@media (max-width:520px){.finder{padding:var(--s-4)}.finder__groups{max-height:480px}}
@media (forced-colors:active){.finder__preset[aria-pressed=true]{border:2px solid Highlight}.finder mark{forced-color-adjust:none;background:Mark;color:MarkText}}

/* ---------- Internet path figure: the path draws as it scrolls in (the static end state is complete) ---------- */
.inet{display:grid;gap:var(--s-7)}
.inet__text{display:grid;gap:var(--s-5);max-width:66ch}
.inet__frame{container-type:inline-size;padding:var(--s-4);background:var(--c-bench)}
.inet__svg{display:block;width:100%;height:auto;overflow:visible}
.inet__svg--tall{display:none}
.inet__fig .bound{fill:none;stroke:var(--c-line-3);stroke-width:1;stroke-dasharray:4 5}
.inet__fig .bound-label{font:500 13px var(--f-mono);fill:var(--c-ink-3)}
.inet__fig .cable{fill:none;stroke:var(--c-cable);stroke-width:1.8;stroke-linecap:round}
.inet__path{fill:none;stroke:var(--c-signal-stroke);stroke-width:3;stroke-linecap:round}
.hop rect{fill:var(--c-surface);stroke:var(--c-line-3)}
.hop--out rect{fill:var(--c-bench);stroke-dasharray:4 3}
.hop svg{fill:none;stroke:var(--c-ink);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
.hop__name{font:600 15px var(--f-sans);fill:var(--c-ink)}
.hop__detail{font:450 12.5px var(--f-mono);fill:var(--c-ink-3)}
@container (max-width:640px){.inet__svg--wide{display:none}.inet__svg--tall{display:block;max-width:420px}}
@supports (animation-timeline:view()){
 @media (prefers-reduced-motion:no-preference){
  .inet__path{stroke-dasharray:1 1;animation:inet-draw linear both;animation-timeline:view();animation-range:entry 20% cover 55%}
  @keyframes inet-draw{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}
 }
}

@media (forced-colors:active){
 .topo .cable{stroke:CanvasText}
 .topo .ov,.topo .oob{stroke:Highlight}
 .topo .pk{background:Highlight}
 .topo .pk--hollow{background:Canvas;border-color:Highlight}
 .node__plate{border:1px solid CanvasText}
 .node[aria-pressed=true] .node__plate{outline:2px solid Highlight}
 .led{forced-color-adjust:none;background:CanvasText}
}

/* ---------- Plan table (build.mjs planTableHTML; options caption="…", example=column) ---------- */
.plan-table .table th[scope=row]{font-size:16px;font-weight:600}
.plan-ex{display:block;margin-top:2px;font-size:13.5px;font-weight:450;color:var(--c-ink-3)}
.plan-table td.n{font-size:16px}
.plan-table td.plan-ex-col{font-size:14.5px;color:var(--c-ink-2)}
.plan-table__cap{caption-side:bottom;text-align:left;padding:var(--s-3) var(--s-4);font-size:13.5px;color:var(--c-ink-3);border-top:1px solid var(--c-line)}
/* Stacks by the width of its own column (a container), not the screen: a plan per card, labels above values. */
.plan-stack{container-type:inline-size;min-width:0}
@container (max-width:600px){
 .plan-table{border:0;background:none;overflow:visible}
 .plan-table table,.plan-table tbody,.plan-table tr,.plan-table th,.plan-table td{display:block}
 .plan-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
 .plan-table tr{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px 12px;padding:var(--s-4);margin-bottom:var(--s-3);border:1px solid var(--c-line-2);border-radius:var(--r-fig);background:var(--c-surface)}
 .plan-table th[scope=row]{grid-column:1/-1;padding:0 0 var(--s-2);border:0}
 .plan-table td,.plan-table td.n{padding:0;border:0;text-align:left}
 .plan-table td.plan-ex-col{grid-column:1/-1}
 .plan-table td::before{content:attr(data-label);display:block;font-size:12.5px;color:var(--c-ink-3)}
 .plan-table__cap{display:block;padding:0 0 var(--s-3);border:0}
}
@container (max-width:380px){.plan-table tr{grid-template-columns:1fr 1fr}}

/* ---------- Copy failure line ---------- */
.copy-fail{margin-top:6px;color:var(--c-ink-2)}
