@kitlangton/knobs 0.4.0-next.7 → 0.4.0-next.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui-react.js CHANGED
@@ -12702,6 +12702,171 @@ var init_compare = __esm(() => {
12702
12702
  };
12703
12703
  });
12704
12704
 
12705
+ // src/ui/player.ts
12706
+ function player(container, options) {
12707
+ const doc = container.ownerDocument;
12708
+ const el = (tag, cls) => {
12709
+ const n = doc.createElement(tag);
12710
+ n.className = cls;
12711
+ return n;
12712
+ };
12713
+ const look = {
12714
+ ...options.preset ? { preset: options.preset } : {},
12715
+ ...options.theme ? { theme: options.theme } : {},
12716
+ ...options.appearance ? { appearance: options.appearance } : {}
12717
+ };
12718
+ const root = el("div", "knobs-player");
12719
+ root.setAttribute("role", "group");
12720
+ root.setAttribute("aria-label", options.label ?? "Video");
12721
+ applyLook2(root, options);
12722
+ const frame = el("div", "knobs-player-frame");
12723
+ frame.style.setProperty("--kp-aspect", String(options.aspect ?? 16 / 9));
12724
+ const v = el("video", "knobs-player-media");
12725
+ v.muted = options.muted ?? true;
12726
+ v.defaultMuted = v.muted;
12727
+ v.playsInline = true;
12728
+ v.preload = "metadata";
12729
+ v.setAttribute("playsinline", "");
12730
+ if (options.poster)
12731
+ v.poster = options.poster;
12732
+ if (options.captions) {
12733
+ const track = doc.createElement("track");
12734
+ track.kind = "captions";
12735
+ track.label = "Captions";
12736
+ track.srclang = doc.documentElement.lang || "en";
12737
+ track.default = true;
12738
+ track.src = options.captions;
12739
+ v.append(track);
12740
+ }
12741
+ frame.append(v);
12742
+ const host = el("div", "knobs-player-line");
12743
+ root.append(frame, host);
12744
+ container.append(root);
12745
+ const markers = (options.chapters ?? []).map((c, i) => ({
12746
+ id: c.id ?? `c${i}`,
12747
+ time: c.start,
12748
+ label: c.label
12749
+ }));
12750
+ const tl = timeline(host, {
12751
+ ...look,
12752
+ variant: "line",
12753
+ duration: 1,
12754
+ fps: options.fps ?? 30,
12755
+ markers,
12756
+ repeat: options.loop ?? true,
12757
+ arrows: { step: 5, shift: 10 },
12758
+ label: options.label ? `${options.label} time` : "Video time",
12759
+ ...options.hints === false ? { hints: false } : {}
12760
+ });
12761
+ const onFrame = () => root.setAttribute("data-ready", "");
12762
+ let pending = null;
12763
+ const seekVideo = (t) => {
12764
+ if (v.readyState < 1)
12765
+ return;
12766
+ if (v.seeking)
12767
+ pending = t;
12768
+ else
12769
+ v.currentTime = t;
12770
+ };
12771
+ let reported = 0;
12772
+ const onSeeked = () => {
12773
+ if (pending !== null) {
12774
+ const t = pending;
12775
+ pending = null;
12776
+ v.currentTime = t;
12777
+ return;
12778
+ }
12779
+ if (!v.paused || tl.playing || Math.abs(v.currentTime - reported) < 0.1)
12780
+ return;
12781
+ reported = v.currentTime;
12782
+ options.onSeek?.(reported);
12783
+ };
12784
+ const onMeta = () => {
12785
+ onFrame();
12786
+ if (v.videoWidth)
12787
+ frame.style.setProperty("--kp-aspect", String(v.videoWidth / v.videoHeight));
12788
+ if (Number.isFinite(v.duration))
12789
+ tl.update({ duration: v.duration });
12790
+ };
12791
+ v.addEventListener("loadedmetadata", onMeta);
12792
+ v.addEventListener("loadeddata", onFrame);
12793
+ v.addEventListener("seeked", onSeeked);
12794
+ const unlisten = tl.listen({
12795
+ onSeek(t, source) {
12796
+ if (source === "play") {
12797
+ if (Math.abs(v.currentTime - t) > 0.25)
12798
+ v.currentTime = t;
12799
+ return;
12800
+ }
12801
+ if (Math.abs(v.currentTime - t) < 1 / (options.fps ?? 30))
12802
+ return;
12803
+ seekVideo(t);
12804
+ },
12805
+ onPlay(on) {
12806
+ if (on)
12807
+ v.play().catch(() => tl.pause());
12808
+ else {
12809
+ v.pause();
12810
+ reported = tl.time;
12811
+ }
12812
+ root.toggleAttribute("data-playing", on);
12813
+ options.onPlay?.(on, tl.time);
12814
+ },
12815
+ onScrub(on) {
12816
+ if (on)
12817
+ v.pause();
12818
+ }
12819
+ });
12820
+ tl.drive(() => {
12821
+ if (v.readyState < 1)
12822
+ return Number.NaN;
12823
+ if (v.seeking)
12824
+ return tl.time;
12825
+ return v.ended ? Number.POSITIVE_INFINITY : v.currentTime;
12826
+ });
12827
+ const onClick = () => tl.focus();
12828
+ frame.addEventListener("click", onClick);
12829
+ v.src = options.src;
12830
+ const api = {
12831
+ element: root,
12832
+ video: v,
12833
+ timeline: tl,
12834
+ get time() {
12835
+ return tl.time;
12836
+ },
12837
+ get playing() {
12838
+ return tl.playing;
12839
+ },
12840
+ get duration() {
12841
+ return Number.isFinite(v.duration) ? v.duration : 0;
12842
+ },
12843
+ play: () => tl.play(),
12844
+ pause: () => tl.pause(),
12845
+ toggle: () => tl.toggle(),
12846
+ seek: (t, o) => tl.seek(t, o?.glide ? { glide: true } : undefined),
12847
+ seekBy: (d) => tl.seek(Math.min(tl.duration, Math.max(0, tl.time + d)), { glide: true }),
12848
+ focus: () => tl.focus(),
12849
+ dispose() {
12850
+ unlisten();
12851
+ tl.drive(null);
12852
+ tl.dispose();
12853
+ v.pause();
12854
+ frame.removeEventListener("click", onClick);
12855
+ v.removeEventListener("loadedmetadata", onMeta);
12856
+ v.removeEventListener("loadeddata", onFrame);
12857
+ v.removeEventListener("seeked", onSeeked);
12858
+ v.removeAttribute("src");
12859
+ v.load();
12860
+ root.remove();
12861
+ }
12862
+ };
12863
+ return api;
12864
+ }
12865
+ var init_player = __esm(() => {
12866
+ init_look();
12867
+ init_timeline();
12868
+ });
12869
+
12705
12870
  // src/ui/react.tsx
12706
12871
  import {
12707
12872
  Children,
@@ -18184,25 +18349,63 @@ function normalizeWalkthrough(doc) {
18184
18349
  })),
18185
18350
  sections: withKickers(sections, doc.sections, doc.kickers),
18186
18351
  steps,
18187
- prs: knownPRs(doc, sections),
18352
+ prs: knownPRs(doc, sections, steps),
18188
18353
  ...repoOf(doc) ? { repo: repoOf(doc) } : {}
18189
18354
  };
18190
18355
  }
18191
18356
  var prNumber = (href) => /\/(?:pull|merge_requests)\/(\d+)(?:[/?#]|$)/.exec(href)?.[1] ?? null;
18192
18357
  var repoOf = (doc) => doc.repo?.trim().replace(/^https?:\/\/github\.com\//, "").replace(/\/+$/, "") || undefined;
18193
- function knownPRs(doc, sections) {
18358
+ var PR_STATES = ["merged", "open", "draft", "closed"];
18359
+ function prStatus(status) {
18360
+ const words = (status ?? "").trim().toLowerCase();
18361
+ const first = /^[a-z]+/.exec(words)?.[0] ?? "";
18362
+ const state = PR_STATES.includes(first) ? first : first === "shipped" || first === "done" ? "merged" : null;
18363
+ return { state, checks: /\b(green|passing|passed|ci|checks? pass)/.test(words) };
18364
+ }
18365
+ function knownPRs(doc, sections, steps = []) {
18194
18366
  const out = {};
18195
18367
  const repo = repoOf(doc);
18196
- for (const s of sections) {
18197
- const n = s.link && prNumber(s.link.href);
18198
- if (n)
18199
- out[n] = { href: s.link.href.replace(/(\/pull\/\d+).*$/, "$1") };
18200
- }
18368
+ const add = (n, pr) => {
18369
+ const known = out[n] ?? {};
18370
+ const next = { ...known };
18371
+ for (const [k, v] of Object.entries(pr))
18372
+ if (v !== undefined && next[k] === undefined)
18373
+ next[k] = v;
18374
+ out[n] = next;
18375
+ };
18201
18376
  for (const [k, pr] of Object.entries(doc.prs ?? {})) {
18202
18377
  const n = k.replace(/^#/, "");
18203
18378
  if (!/^\d+$/.test(n))
18204
18379
  continue;
18205
- out[n] = { ...out[n], ...pr };
18380
+ add(n, pr);
18381
+ }
18382
+ for (const step of steps)
18383
+ for (const m of step.media) {
18384
+ if (m.kind !== "stack")
18385
+ continue;
18386
+ for (const it of m.items) {
18387
+ const n = it.pr.replace(/^#/, "");
18388
+ if (!/^\d+$/.test(n))
18389
+ continue;
18390
+ const said = prStatus(it.status);
18391
+ add(n, {
18392
+ ...said.state ? { state: said.state } : {},
18393
+ ...said.state && said.checks ? { checks: true } : {},
18394
+ ...it.title ? { title: plainMarkdown(it.title) } : {},
18395
+ ...it.href ? { href: it.href } : {}
18396
+ });
18397
+ }
18398
+ }
18399
+ for (const s of sections) {
18400
+ const n = s.link && prNumber(s.link.href);
18401
+ if (!n)
18402
+ continue;
18403
+ const said = prStatus(s.status);
18404
+ add(n, {
18405
+ href: s.link.href.replace(/(\/pull\/\d+).*$/, "$1"),
18406
+ ...said.state ? { state: said.state } : {},
18407
+ title: plainMarkdown(s.title)
18408
+ });
18206
18409
  }
18207
18410
  if (repo) {
18208
18411
  for (const n of Object.keys(out))
@@ -18211,13 +18414,17 @@ function knownPRs(doc, sections) {
18211
18414
  }
18212
18415
  return out;
18213
18416
  }
18214
- function findPR(prs, n, repo) {
18215
- const known = prs[n];
18216
- const href = repo ? `https://github.com/${repo}/pull/${n}` : undefined;
18417
+ function findPR(prs, n, repo, kind = "pr") {
18418
+ const entry = prs[n];
18419
+ const known = entry && (entry.kind ?? "pr") === kind ? entry : undefined;
18420
+ const path = kind === "issue" ? "issues" : "pull";
18421
+ const href = repo ? `https://github.com/${repo}/${path}/${n}` : undefined;
18422
+ const base = kind === "issue" ? { kind } : {};
18217
18423
  if (known)
18218
- return known.href || !href ? known : { ...known, href };
18219
- return href ? { href } : null;
18424
+ return known.href || !href ? { ...base, ...known } : { ...base, ...known, href };
18425
+ return href ? { ...base, href } : kind === "issue" ? { kind } : null;
18220
18426
  }
18427
+ var issueNumber = (href) => /^https?:\/\/[^/]+\/.+\/issues\/(\d+)\/?(?:[?#].*)?$/.exec(href)?.[1] ?? null;
18221
18428
  var isPullRequest = (href) => /\/(?:pull|merge_requests)\/\d+(?:[/?#]|$)/.test(href);
18222
18429
  function withKickers(sections, source, kickers = "pr") {
18223
18430
  if (kickers === false)
@@ -19578,12 +19785,7 @@ var CHECK3 = "M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11
19578
19785
  var svg12 = (d, evenodd = false) => `<svg class="knobs-icon" viewBox="0 0 256 256" width="12" height="12" fill="currentColor"${evenodd ? ' fill-rule="evenodd"' : ""} aria-hidden="true" focusable="false"><path d="${d}"/></svg>`;
19579
19786
  var prGlyph = (state) => state === "merged" ? svg12(PR_PATHS.merge) : state === "draft" ? svg12(DRAFT, true) : state === "closed" ? svg12(X) : svg12(PR_PATHS.pull);
19580
19787
  var PR_STATE = { merged: "Merged", open: "Open", draft: "Draft", closed: "Closed" };
19581
- function prStatus(status) {
19582
- const words = (status ?? "").trim().toLowerCase();
19583
- const first = /^[a-z]+/.exec(words)?.[0] ?? "";
19584
- const state = first in PR_STATE ? first : null;
19585
- return { state, checks: /\b(green|passing|passed|ci|checks? pass)/.test(words) };
19586
- }
19788
+ var ISSUE = "M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-88a16,16,0,1,1-16-16A16,16,0,0,1,144,128Z";
19587
19789
  function prStateLabel(doc, state, checks, title) {
19588
19790
  const s = el2(doc, "span", "knobs-wt-prstate");
19589
19791
  s.dataset.state = state;
@@ -19607,27 +19809,55 @@ function prChip(doc, n, pr) {
19607
19809
  chip.setAttribute("target", "_blank");
19608
19810
  chip.setAttribute("rel", "noreferrer");
19609
19811
  }
19812
+ const issue = pr.kind === "issue";
19813
+ if (issue)
19814
+ chip.dataset.kind = "issue";
19610
19815
  if (pr.state)
19611
19816
  chip.dataset.state = pr.state;
19612
- chip.innerHTML = prGlyph(pr.state);
19817
+ chip.innerHTML = issue ? svg12(ISSUE) : prGlyph(pr.state);
19613
19818
  chip.append(text2(doc, "span", "", `#${n}`));
19614
- const tip = [`#${n}`, pr.title, pr.state ? PR_STATE[pr.state] : ""].filter(Boolean).join(" · ");
19615
- chip.title = tip;
19616
- chip.setAttribute("aria-label", `Pull request ${tip}`);
19819
+ if (pr.checks) {
19820
+ const c = el2(doc, "span", "knobs-wt-prchecks");
19821
+ c.innerHTML = svg12(CHECK3);
19822
+ chip.append(c);
19823
+ }
19824
+ const words = [
19825
+ `${issue ? "Issue" : "Pull request"} #${n}`,
19826
+ pr.title,
19827
+ pr.state ? PR_STATE[pr.state] : "",
19828
+ pr.checks ? "checks pass" : ""
19829
+ ].filter(Boolean);
19830
+ chip.title = words.slice(1).length ? `#${n} · ${words.slice(1).join(" · ")}` : `#${n}`;
19831
+ chip.setAttribute("aria-label", words.join(" · "));
19617
19832
  return chip;
19618
19833
  }
19619
19834
  var PR_URL = /^https?:\/\/[^/]+\/.+\/(?:pull|merge_requests)\/(\d+)\/?(?:[?#].*)?$/;
19620
- var PR_REF = /(^|[^\w&/#])#(\d{3,7})\b/g;
19835
+ var ISSUE_URL = /^https?:\/\/[^/]+\/.+\/issues\/(\d+)\/?(?:[?#].*)?$/;
19836
+ var REF = /(^|[^\w&/#:])(issue:)?#(\d{3,7})\b/gi;
19837
+ var WHOLE_REF = /^(issue:)?#(\d{1,7})$/i;
19838
+ var KEEP = 'a,pre,button,.knobs-wt-pr,.knobs-wt-slot:not([data-kind="table"]),.knobs-wt-stack';
19621
19839
  function linkPRs(root, find) {
19622
19840
  const doc = root.ownerDocument;
19841
+ const chip = (n, kind, href) => prChip(doc, n, { ...kind === "issue" ? { kind } : {}, ...find(n, kind), ...href ? { href } : {} });
19623
19842
  for (const a of Array.from(root.querySelectorAll("a[href]"))) {
19624
- if (a.classList.contains("knobs-wt-pr") || a.closest(".knobs-wt-slot,button"))
19843
+ if (a.classList.contains("knobs-wt-pr") || a.parentElement?.closest(KEEP))
19844
+ continue;
19845
+ const url = a.getAttribute("href") ?? "";
19846
+ const pr = PR_URL.exec(url)?.[1];
19847
+ const n = pr ?? ISSUE_URL.exec(url)?.[1];
19848
+ if (!n)
19625
19849
  continue;
19626
- const n = PR_URL.exec(a.getAttribute("href") ?? "")?.[1];
19627
- const words = a.textContent?.trim() ?? "";
19628
- if (!n || !(words === `#${n}` || words === n || words === a.getAttribute("href")))
19850
+ const words = (a.textContent ?? "").trim().replace(/^issue:/i, "");
19851
+ if (!(words === `#${n}` || words === n || words === url))
19629
19852
  continue;
19630
- a.replaceWith(prChip(doc, n, { href: a.href, ...find(n) }));
19853
+ a.replaceWith(chip(n, pr ? "pr" : "issue", a.href));
19854
+ }
19855
+ for (const c of Array.from(root.querySelectorAll("code"))) {
19856
+ if (c.closest("pre") || c.parentElement?.closest(KEEP))
19857
+ continue;
19858
+ const m = WHOLE_REF.exec((c.textContent ?? "").trim());
19859
+ if (m)
19860
+ c.replaceWith(chip(m[2], m[1] ? "issue" : "pr"));
19631
19861
  }
19632
19862
  const walker = doc.createTreeWalker(root, 5);
19633
19863
  const hits = [];
@@ -19635,29 +19865,22 @@ function linkPRs(root, find) {
19635
19865
  if (t.nodeType !== 3)
19636
19866
  continue;
19637
19867
  const p = t.parentElement;
19638
- if (!p || p.closest("a,code,pre,button,.knobs-wt-slot,.knobs-wt-pr"))
19868
+ if (!p || p.closest(`${KEEP},code`))
19639
19869
  continue;
19640
- PR_REF.lastIndex = 0;
19641
- if (PR_REF.test(t.nodeValue ?? ""))
19870
+ REF.lastIndex = 0;
19871
+ if (REF.test(t.nodeValue ?? ""))
19642
19872
  hits.push(t);
19643
19873
  }
19644
19874
  for (const t of hits) {
19645
19875
  const value = t.nodeValue ?? "";
19646
19876
  const frag = doc.createDocumentFragment();
19647
19877
  let last = 0;
19648
- let changed = false;
19649
- PR_REF.lastIndex = 0;
19650
- for (let m = PR_REF.exec(value);m; m = PR_REF.exec(value)) {
19651
- const pr = find(m[2]);
19652
- if (!pr)
19653
- continue;
19878
+ REF.lastIndex = 0;
19879
+ for (let m = REF.exec(value);m; m = REF.exec(value)) {
19654
19880
  const at = m.index + m[1].length;
19655
- frag.append(value.slice(last, at), prChip(doc, m[2], pr));
19656
- last = at + m[2].length + 1;
19657
- changed = true;
19881
+ frag.append(value.slice(last, at), chip(m[3], m[2] ? "issue" : "pr"));
19882
+ last = m.index + m[0].length;
19658
19883
  }
19659
- if (!changed)
19660
- continue;
19661
19884
  frag.append(value.slice(last));
19662
19885
  t.replaceWith(frag);
19663
19886
  }
@@ -19676,7 +19899,7 @@ var snappy3 = springCss(SPRINGS.snappy);
19676
19899
  var calm = springCss(SPRINGS.calm);
19677
19900
  var state = springCss(SPRINGS.default);
19678
19901
  var quick = springCss(FADE);
19679
- var fade2 = "-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 1.5em),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 1.5em),transparent)";
19902
+ var fade2 = "-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 16px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 16px),transparent)";
19680
19903
  var walkthroughCss = `
19681
19904
  :where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
19682
19905
  .knobs-ui:is(.knobs-walkthrough,.knobs-wt-slot){--k-glass:0;--k-glow:0;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms}
@@ -19688,7 +19911,7 @@ var walkthroughCss = `
19688
19911
  .knobs-walkthrough [hidden]{display:none!important}
19689
19912
  /* (0,0,1): a host's element rules stop here, and every class rule below wins over it. */
19690
19913
  :where(.knobs-walkthrough) :is(h1,h2,h3,p,ol,ul,dl,dd){margin:0;font:inherit;letter-spacing:inherit}
19691
- .knobs-walkthrough a{color:inherit;text-decoration:underline;text-decoration-color:color-mix(in srgb,currentColor 35%,transparent);text-underline-offset:2px;transition:text-decoration-color .15s}
19914
+ .knobs-walkthrough a{cursor:default;color:inherit;text-decoration:underline;text-decoration-color:color-mix(in srgb,currentColor 35%,transparent);text-underline-offset:2px;transition:text-decoration-color calc(var(--_k-hover-out) * 1ms)}
19692
19915
  .knobs-walkthrough a:hover{text-decoration-color:currentColor}
19693
19916
  .knobs-walkthrough code,.knobs-wt-slot code{font:var(--_k-type-label) var(--_k-mono);padding:1px 5px;border-radius:calc(var(--_k-radius) / 2);background:var(--_k-fill)}
19694
19917
  :where(.knobs-ui) :is(.knobs-wt-btn,.knobs-wt-pick,.knobs-wt-toc-row,button.knobs-wt-stitle,.knobs-wt-chapter,.knobs-wt-disc-toggle){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;
@@ -19716,14 +19939,17 @@ var walkthroughCss = `
19716
19939
  .knobs-walkthrough .knobs-wt-metaline a:not(.knobs-wt-pr){display:inline-flex;align-items:baseline;color:inherit;text-decoration:none;transition:color calc(var(--_k-hover-out) * 1ms)}
19717
19940
  .knobs-walkthrough .knobs-wt-metaline a:not(.knobs-wt-pr):hover{color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
19718
19941
  .knobs-wt-metaline svg{align-self:center;width:12px;height:12px;margin-left:2px}
19719
- /* A pull request: a small chip, its state's icon and number in a quiet tint of the state (open
19720
- the success hue, merged the violet series, draft and closed neutral), its title on hover. */
19721
- .knobs-ui .knobs-wt-pr{--kw-pr:var(--_k-fg-2);display:inline-flex;align-items:center;gap:3px;height:18px;padding:0 5px 0 4px;margin:0 1px;border-radius:calc(var(--_k-radius) / 2);vertical-align:1px;
19942
+ /* A pull request or issue: a capsule, its state's icon and number in a quiet tint of the state
19943
+ (open the success hue, merged the violet series, draft and closed faint, unknown neutral; a
19944
+ closed issue is done, so violet), a faint check where its checks pass, its title on hover. */
19945
+ .knobs-ui .knobs-wt-pr{--kw-pr:var(--_k-fg-2);display:inline-flex;align-items:center;gap:3px;height:18px;padding:0 6px 0 5px;margin:0 1px;border-radius:9px;vertical-align:1px;
19722
19946
  background:color-mix(in oklch,var(--kw-pr) 12%,transparent);color:var(--kw-pr);font:var(--_k-weight-label) var(--_k-type-small)/1 var(--_k-font);font-variant-numeric:tabular-nums;white-space:nowrap;text-decoration:none;
19723
19947
  transition:background calc(var(--_k-hover-out) * 1ms)}
19724
19948
  .knobs-ui .knobs-wt-pr[data-state="open"]{--kw-pr:color-mix(in oklch,var(--_k-ok) 60%,var(--_k-fg-2))}
19725
19949
  .knobs-ui .knobs-wt-pr[data-state="merged"]{--kw-pr:color-mix(in oklch,var(--_k-series-5) 60%,var(--_k-fg-2))}
19726
19950
  .knobs-ui .knobs-wt-pr:is([data-state="draft"],[data-state="closed"]){--kw-pr:var(--_k-fg-3)}
19951
+ .knobs-ui .knobs-wt-pr[data-kind="issue"][data-state="closed"]{--kw-pr:color-mix(in oklch,var(--_k-series-5) 60%,var(--_k-fg-2))}
19952
+ .knobs-ui .knobs-wt-pr .knobs-wt-prchecks{margin-left:0}
19727
19953
  .knobs-ui a.knobs-wt-pr:is(:hover,:focus-visible){background:color-mix(in oklch,var(--kw-pr) 22%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
19728
19954
  .knobs-wt-pr svg{flex:none}
19729
19955
  .knobs-wt-metaline .knobs-wt-pr{align-self:center;margin:0}
@@ -19782,14 +20008,14 @@ var walkthroughCss = `
19782
20008
  .knobs-wt-fi-ask{max-width:var(--kw-measure);color:var(--_k-fg);font-weight:var(--kw-strong);text-wrap:pretty}
19783
20009
  .knobs-wt-fi-text{max-width:var(--kw-measure);color:var(--_k-fg);text-wrap:pretty}
19784
20010
  .knobs-wt-fi-answer{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
19785
- :where(.knobs-ui) .knobs-wt-fi-step{all:revert;box-sizing:border-box;font:inherit;color:inherit;background:transparent;border:0;margin:0;padding:0;cursor:pointer;outline:none;
20011
+ :where(.knobs-ui) .knobs-wt-fi-step{all:revert;box-sizing:border-box;font:inherit;color:inherit;background:transparent;border:0;margin:0;padding:0;cursor:default;outline:none;
19786
20012
  transition:color calc(var(--_k-hover-out) * 1ms)}
19787
20013
  .knobs-ui .knobs-wt-fi-step:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
19788
20014
 
19789
20015
  /* ——— a disclosure: a small faint toggle with a caret, no fill. The body's height springs (in
19790
20016
  script) and its content fades in place, top-anchored, under a soft bottom mask ——— */
19791
20017
  .knobs-wt-disc{display:flex;flex-direction:column;min-width:0}
19792
- .knobs-ui .knobs-wt-disc-toggle{display:inline-flex;align-items:center;gap:var(--_k-space-inside);width:max-content;height:20px;color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);cursor:pointer;
20018
+ .knobs-ui .knobs-wt-disc-toggle{display:inline-flex;align-items:center;gap:var(--_k-space-inside);width:max-content;height:20px;color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);cursor:default;
19793
20019
  border-radius:calc(var(--_k-radius) / 2);transition:color calc(var(--_k-hover-out) * 1ms)}
19794
20020
  .knobs-ui .knobs-wt-disc-toggle:is(:hover,:focus-visible),.knobs-wt-disc[data-open]>.knobs-wt-disc-toggle{color:var(--_k-fg-2);transition-duration:calc(var(--_k-hover-in) * 1ms)}
19795
20021
  .knobs-ui .knobs-wt-disc-toggle:focus-visible{box-shadow:0 0 0 2px var(--_k-focus)}
@@ -19816,7 +20042,7 @@ var walkthroughCss = `
19816
20042
  .knobs-wt-n[data-leaving]{opacity:0;filter:blur(3px);padding-block:0}
19817
20043
  .knobs-wt-n+.knobs-wt-n{box-shadow:inset 0 1px 0 var(--_k-border-subtle)}
19818
20044
  .knobs-wt-n-meta{flex:none;min-width:44px;color:var(--_k-fg-3);font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-body) var(--_k-mono)}
19819
- .knobs-wt-n-meta[data-seek]{cursor:pointer}
20045
+ .knobs-wt-n-meta[data-seek]{cursor:default}
19820
20046
  .knobs-wt-n-meta[data-seek]:hover{color:var(--_k-fg)}
19821
20047
  .knobs-wt-n-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
19822
20048
  .knobs-wt-n-voice{height:30px;max-width:320px}
@@ -19833,7 +20059,7 @@ var walkthroughCss = `
19833
20059
  /* Collapsed: the launcher and the bar swap in place, a quick fade with a little scale and blur
19834
20060
  from the launcher's corner; the exit is the shorter. */
19835
20061
  :where(.knobs-ui) .knobs-wt-launch{all:revert;box-sizing:border-box;justify-self:end;align-self:end;display:inline-flex;align-items:center;gap:6px;height:32px;margin:0;padding:0 10px 0 9px;border:0;border-radius:var(--_k-round);
19836
- background:var(--_k-bg);color:var(--_k-fg-2);box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay);font:var(--kw-strong) var(--_k-type-small)/1 var(--_k-font);cursor:pointer;outline:none;transform-origin:100% 100%;
20062
+ background:var(--_k-bg);color:var(--_k-fg-2);box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay);font:var(--kw-strong) var(--_k-type-small)/1 var(--_k-font);cursor:default;outline:none;transform-origin:100% 100%;
19837
20063
  transition:color calc(var(--_k-hover-out) * 1ms),opacity var(--kw-quick-ms) var(--kw-quick),scale var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
19838
20064
  .knobs-ui .knobs-wt-launch:is(:hover,:focus-visible){color:var(--_k-fg)}
19839
20065
  .knobs-ui .knobs-wt-launch:focus-visible{box-shadow:var(--_k-focus-ring)}
@@ -19853,11 +20079,11 @@ var walkthroughCss = `
19853
20079
  .knobs-wt-attach{display:flex;gap:4px;align-items:center;align-self:center}
19854
20080
  .knobs-wt-attach:empty{display:none}
19855
20081
  .knobs-wt-chip-a{display:inline-flex;align-items:center;gap:2px;height:24px;padding:0 2px 0 9px;border-radius:var(--_k-round);background:var(--_k-fill-selected);font-size:var(--_k-type-small);white-space:nowrap}
19856
- .knobs-ui .knobs-wt-x{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;color:var(--_k-fg-3);cursor:pointer;transition:background .15s,color .15s}
20082
+ .knobs-ui .knobs-wt-x{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;color:var(--_k-fg-3);cursor:default;transition:background .15s,color .15s}
19857
20083
  .knobs-ui .knobs-wt-x:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
19858
20084
  .knobs-wt-x svg{width:12px;height:12px}
19859
20085
  .knobs-ui .knobs-wt-tool{flex:none;display:inline-flex;align-items:center;justify-content:center;gap:5px;min-width:32px;height:32px;padding:0 8px;border-radius:calc(var(--_k-radius) + 2px);
19860
- color:var(--_k-fg-2);cursor:pointer;transition:background .15s,color .15s}
20086
+ color:var(--_k-fg-2);cursor:default;transition:background .15s,color .15s}
19861
20087
  .knobs-ui .knobs-wt-tool:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
19862
20088
  .knobs-ui .knobs-wt-tool[aria-pressed="true"]{background:var(--_k-fill-selected);color:var(--_k-fg)}
19863
20089
  .knobs-wt-tool svg{width:16px;height:16px}
@@ -19867,7 +20093,7 @@ var walkthroughCss = `
19867
20093
  .knobs-ui .knobs-wt-tool[data-send][data-ready]{background:var(--_k-accent);color:var(--_k-bg)}
19868
20094
  /* Back: in place over the bar for a few seconds after a note moves the page on. */
19869
20095
  .knobs-ui .knobs-wt-back{position:absolute;left:8px;bottom:calc(100% + 8px);display:inline-flex;align-items:center;gap:var(--_k-space-inside);max-width:min(360px,calc(100% - 16px));height:28px;padding:0 12px 0 9px;
19870
- border-radius:var(--_k-round);background:var(--_k-bg);color:var(--_k-fg);box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay);font-size:var(--_k-type-small);font-weight:var(--kw-strong);cursor:pointer;
20096
+ border-radius:var(--_k-round);background:var(--_k-bg);color:var(--_k-fg);box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay);font-size:var(--_k-type-small);font-weight:var(--kw-strong);cursor:default;
19871
20097
  opacity:0;scale:.96;filter:blur(3px);pointer-events:none;transition:opacity .11s cubic-bezier(.4,0,1,1),scale .11s cubic-bezier(.4,0,1,1),filter .11s cubic-bezier(.4,0,1,1)}
19872
20098
  .knobs-ui .knobs-wt-back[data-on]{opacity:1;scale:1;filter:none;pointer-events:auto;transition:opacity .15s cubic-bezier(.2,.8,.2,1),scale .15s cubic-bezier(.2,.8,.2,1),filter .15s cubic-bezier(.2,.8,.2,1)}
19873
20099
  .knobs-ui .knobs-wt-back:hover{background:var(--_k-fill-selected)}
@@ -19894,6 +20120,9 @@ var walkthroughCss = `
19894
20120
  .knobs-wt-step[data-full] .knobs-wt-body{flex:1;min-height:0}
19895
20121
  .knobs-wt-step[data-full] .knobs-wt-fit{height:100%;transition:none}
19896
20122
  .knobs-wt-step[data-full] .knobs-wt-frame{height:100%;aspect-ratio:auto}
20123
+ .knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"]>.knobs-wt-body{display:flex;flex-direction:column}
20124
+ .knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"] .knobs-player{flex:1;min-height:0}
20125
+ .knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"] .knobs-player-frame{flex:1;min-height:0;aspect-ratio:auto}
19897
20126
 
19898
20127
  /* ——— a taste test: the options side by side, Kit's pick checked and the rest stepping back. On
19899
20128
  the step's card, so no fill of their own ——— */
@@ -19902,7 +20131,7 @@ var walkthroughCss = `
19902
20131
  .knobs-wt-option{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0}
19903
20132
  .knobs-wt-option>:not(.knobs-wt-pick){transition:opacity var(--kw-snappy-ms) var(--kw-snappy)}
19904
20133
  .knobs-wt-options[data-has-pick] .knobs-wt-option:not([data-picked])>:not(.knobs-wt-pick){opacity:.55}
19905
- .knobs-ui .knobs-wt-pick{display:flex;align-items:center;gap:8px;width:100%;min-height:28px;margin:0 -6px;width:calc(100% + 12px);padding:0 6px;border-radius:var(--_k-radius);cursor:pointer;color:var(--_k-fg-2);text-align:left;
20134
+ .knobs-ui .knobs-wt-pick{display:flex;align-items:center;gap:8px;width:100%;min-height:28px;margin:0 -6px;width:calc(100% + 12px);padding:0 6px;border-radius:var(--_k-radius);cursor:default;color:var(--_k-fg-2);text-align:left;
19906
20135
  transition:background calc(var(--_k-hover-out) * 1ms)}
19907
20136
  .knobs-ui .knobs-wt-pick:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_k-fill);transition-duration:calc(var(--_k-hover-in) * 1ms)}
19908
20137
  .knobs-wt-letter{flex:none;font-weight:var(--kw-strong);color:var(--_k-fg)}
@@ -19925,17 +20154,18 @@ var walkthroughCss = `
19925
20154
  .knobs-wt-bar{position:relative;height:4px;border-radius:2px;overflow:hidden;background:var(--_k-fill)}
19926
20155
  .knobs-wt-fill{position:absolute;inset:0;border-radius:inherit;background:var(--_k-accent);transform-origin:0 50%;transform:scaleX(var(--kw-done,0))}
19927
20156
  .knobs-walkthrough[data-settled] .knobs-wt-fill{transition:transform var(--kw-calm-ms) var(--kw-calm)}
19928
- /* The contents: every row on one text edge and in one colour, title rows (32 high, the control
19929
- radius) and the steps under them (20 high, half of it), bled out so their text sits on the
19930
- rail's edge; the selection pill slides to the current step, and base rows never brighten. */
19931
- .knobs-wt-toc{position:relative;flex:1 1 auto;min-height:0;overflow:auto;scrollbar-width:none;display:flex;flex-direction:column;gap:var(--_k-space-sibling);margin:0 calc(var(--_k-space-sibling) * -1);padding:0 0 8px}
20157
+ /* The contents: every row on one text edge and in one colour, title rows (28 high, the control
20158
+ radius) and the steps under them (20 high, half of it), bled 6 px out so their text sits on the
20159
+ rail's edge and the first title's words on the column's top line; a narrow fade where a title
20160
+ runs out of room; the selection pill slides to the current step, and base rows never brighten. */
20161
+ .knobs-wt-toc{position:relative;flex:1 1 auto;min-height:0;overflow:auto;scrollbar-width:none;display:flex;flex-direction:column;gap:var(--_k-space-sibling);margin:-4px -6px 0;padding:0 0 8px}
19932
20162
  .knobs-wt-toc::-webkit-scrollbar{display:none}
19933
20163
  .knobs-wt-toc-group{display:flex;flex-direction:column;min-width:0}
19934
- .knobs-ui .knobs-wt-toc-row{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:var(--_k-space-sibling);align-items:center;flex:none;width:100%;height:32px;
19935
- padding:0 var(--_k-space-sibling);border-radius:var(--_k-radius);color:var(--_k-fg-2);font-size:var(--_k-type-body);line-height:var(--_k-leading-body);font-weight:var(--_k-weight);text-align:left;white-space:nowrap;cursor:pointer;
19936
- transition:color calc(var(--_k-hover-out) * 1ms)}
20164
+ .knobs-ui .knobs-wt-toc-row{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:var(--_k-space-sibling);align-items:center;flex:none;width:100%;height:28px;
20165
+ padding:0 6px;border-radius:var(--_k-radius);color:var(--_k-fg-2);font-size:var(--_k-type-body);line-height:var(--_k-leading-body);font-weight:var(--_k-weight);text-align:left;white-space:nowrap;cursor:default;
20166
+ transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
19937
20167
  .knobs-ui .knobs-wt-toc-sub{height:20px;border-radius:calc(var(--_k-radius) / 2);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);font-weight:var(--_k-weight)}
19938
- .knobs-ui .knobs-wt-toc-row:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20168
+ .knobs-ui .knobs-wt-toc-row:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
19939
20169
  .knobs-ui .knobs-wt-toc-row:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
19940
20170
  .knobs-wt-toc-end{display:flex;align-items:center;font-size:var(--_k-type-small);line-height:var(--_k-leading-small);font-weight:var(--_k-weight);color:var(--_k-fg-3)}
19941
20171
  .knobs-wt-toc-label{min-width:0;overflow:hidden;${fade2}}
@@ -19947,7 +20177,7 @@ var walkthroughCss = `
19947
20177
  .knobs-wt-toc-glyph[data-done]{background:var(--kw-tone)}
19948
20178
  .knobs-wt-toc-glyph[data-tone="none"]{box-shadow:inset 0 0 0 1px var(--_k-border)}
19949
20179
  .knobs-wt-actions{display:flex;flex-wrap:wrap;gap:4px}
19950
- .knobs-ui .knobs-wt-btn{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 9px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:pointer;white-space:nowrap;
20180
+ .knobs-ui .knobs-wt-btn{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 9px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:default;white-space:nowrap;
19951
20181
  transition:background .15s,color .15s}
19952
20182
  .knobs-ui .knobs-wt-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
19953
20183
  .knobs-ui .knobs-wt-btn[data-primary]{background:var(--_k-fill-selected);color:var(--_k-fg)}
@@ -19981,11 +20211,10 @@ var walkthroughCss = `
19981
20211
  /* One frame for every slot that has one (a picture, a video, a code block, a compare's stage, a
19982
20212
  slot still loading): the panel radius and a quiet fill, no ring; concentric on a card. */
19983
20213
  .knobs-wt-slot .knobs-cmp-stage{border-radius:var(--kw-frame-r)}
19984
- /* A compare is exactly as tall as its pictures at this width, so there's no letterbox: the two
19985
- sides edge to edge (side) or one (overlaid), plus its mode bar, the timeline and their two gaps
20214
+ /* A compare is as tall as its two pictures side by side at this width, in every mode (Wipe fuses
20215
+ them at one side's size, so nothing grows or re-measures), plus its mode bar, the line and gaps
19986
20216
  (92 px), and the frame strip with its gap (158 px). */
19987
20217
  .knobs-wt-fit{width:100%;height:calc(100cqw / 2 / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
19988
- .knobs-wt-fit[data-overlay]{height:calc(100cqw / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
19989
20218
  .knobs-wt-fit[data-strip]{--kw-chrome:158px}
19990
20219
  .knobs-wt-fit[data-settled]{transition:height var(--k-wt-calm-ms,${calm.duration}ms) ${calm.easing}}
19991
20220
  .knobs-wt-frame{position:relative;width:100%;overflow:hidden;border-radius:var(--kw-frame-r);background:var(--_k-fill);aspect-ratio:var(--kw-aspect,16/9)}
@@ -19994,8 +20223,12 @@ var walkthroughCss = `
19994
20223
  .knobs-wt-frame[data-natural]>img{position:static;height:auto}
19995
20224
  /* Diff, code and terminal: one plain container (no glass): a flat inset fill, a hairline ring, the
19996
20225
  control radius and room inside, so a diff's buttons and the code under them can breathe. */
19997
- .knobs-wt-slot[data-kind="diff"]>.knobs-wt-body{padding:var(--_k-space-sibling) var(--_k-pad-card) var(--_k-pad-card);border-radius:var(--_k-radius);background:var(--_k-bg-inset);box-shadow:inset 0 0 0 1px var(--_k-border-subtle)}
19998
- .knobs-wt-code{margin:0;padding:var(--_k-pad-card);max-height:420px;overflow:auto;border-radius:var(--_k-radius);background:var(--_k-bg-inset);box-shadow:inset 0 0 0 1px var(--_k-border-subtle);color:var(--_k-fg);
20226
+ .knobs-wt-slot[data-kind="diff"]>.knobs-wt-body{padding:var(--_k-pad-card);border-radius:var(--_k-radius-panel);background:var(--_k-bg-inset);box-shadow:inset 0 0 0 1px var(--_k-border-subtle)}
20227
+ /* The diff inside it: its header row 28 high on the container's padding, a hairline under it, and
20228
+ no line of its own under the code (the container's ring closes it). */
20229
+ .knobs-wt-slot[data-kind="diff"] .knobs-ui.knobs-diff:not([data-on="pane"]){--kd-head:28px;--kd-x:0px}
20230
+ .knobs-wt-slot[data-kind="diff"] .knobs-ui.knobs-diff:not([data-on="pane"])::after{display:none}
20231
+ .knobs-wt-code{margin:0;padding:var(--_k-pad-card);max-height:420px;overflow:auto;border-radius:var(--_k-radius-panel);background:var(--_k-bg-inset);box-shadow:inset 0 0 0 1px var(--_k-border-subtle);color:var(--_k-fg);
19999
20232
  font:var(--_k-type-label)/var(--_k-leading-body) var(--_k-mono);white-space:pre;tab-size:2;scrollbar-width:thin}
20000
20233
  .knobs-surface :is(.knobs-wt-code,.knobs-wt-slot[data-kind="diff"]>.knobs-wt-body){padding-inline:0;border-radius:0;background:transparent;box-shadow:inset 0 1px 0 var(--_k-border-subtle),inset 0 -1px 0 var(--_k-border-subtle)}
20001
20234
  .knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
@@ -20041,7 +20274,7 @@ var walkthroughCss = `
20041
20274
  .knobs-wt-mark:is([data-shape="dot"],[data-shape="arrow"]) .knobs-wt-mark-label{left:12px;bottom:auto;top:10px}
20042
20275
  .knobs-wt-mark[data-shape="arrow"] .knobs-wt-mark-label{left:auto;right:30px;top:auto;bottom:30px}
20043
20276
  .knobs-ui .knobs-wt-anno-toggle{all:revert;box-sizing:border-box;position:absolute;top:8px;right:8px;display:grid;place-items:center;width:28px;height:28px;padding:0;border:0;margin:0;
20044
- border-radius:var(--_k-radius);background:color-mix(in srgb,var(--_k-bg) 72%,transparent);color:var(--_k-fg-2);cursor:pointer;opacity:.6;outline:none;
20277
+ border-radius:var(--_k-radius);background:color-mix(in srgb,var(--_k-bg) 72%,transparent);color:var(--_k-fg-2);cursor:default;opacity:.6;outline:none;
20045
20278
  transition:opacity calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
20046
20279
  .knobs-ui .knobs-wt-anno-toggle:is(:hover,:focus-visible){opacity:1;color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20047
20280
  .knobs-ui .knobs-wt-anno-toggle:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
@@ -20062,7 +20295,7 @@ var walkthroughCss = `
20062
20295
  .knobs-wt-srow{display:grid;grid-template-columns:auto minmax(0,1fr) auto;column-gap:10px;align-items:start;margin:0 -8px;padding:var(--kw-spt) 8px var(--kw-spb);
20063
20296
  border-radius:max(0px,calc(var(--_k-radius-panel) - 8px));transition:background calc(var(--_k-hover-out) * 1ms)}
20064
20297
  .knobs-wt-srow[data-trunk]{--kw-spb:0px;grid-template-columns:auto minmax(0,1fr)}
20065
- .knobs-wt-srow[data-go]{cursor:pointer}
20298
+ .knobs-wt-srow[data-go]{cursor:default}
20066
20299
  .knobs-wt-srow[data-go]:hover{background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20067
20300
  .knobs-wt-lanes{display:flex;align-self:stretch;margin-block:calc(var(--kw-spt) * -1) calc(var(--kw-spb) * -1)}
20068
20301
  .knobs-wt-lane{position:relative;flex:none;width:16px}
@@ -20078,7 +20311,7 @@ var walkthroughCss = `
20078
20311
  .knobs-ui .knobs-wt-spr{font:var(--_k-weight-label) var(--_k-type-label)/var(--kw-line) var(--_k-mono);color:var(--_k-fg-2);text-decoration:none;white-space:nowrap}
20079
20312
  .knobs-ui a.knobs-wt-spr:is(:hover,:focus-visible){color:var(--_k-fg);text-decoration:underline}
20080
20313
  .knobs-ui .knobs-wt-stitle{font-weight:var(--_k-weight-label);line-height:var(--kw-line);color:var(--_k-fg);text-align:left}
20081
- .knobs-ui button.knobs-wt-stitle{cursor:pointer;text-decoration:underline;text-decoration-color:transparent;text-underline-offset:3px;transition:text-decoration-color calc(var(--_k-hover-out) * 1ms)}
20314
+ .knobs-ui button.knobs-wt-stitle{cursor:default;text-decoration:underline;text-decoration-color:transparent;text-underline-offset:3px;transition:text-decoration-color calc(var(--_k-hover-out) * 1ms)}
20082
20315
  .knobs-ui :is(.knobs-wt-srow[data-go]:hover button.knobs-wt-stitle,button.knobs-wt-stitle:focus-visible){text-decoration-color:color-mix(in oklch,currentColor 45%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20083
20316
  .knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
20084
20317
  .knobs-wt-smeta{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 12px;font:var(--_k-weight) var(--_k-type-small)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-3)}
@@ -20110,7 +20343,7 @@ var walkthroughCss = `
20110
20343
  .knobs-wt-tablebox{min-width:0}
20111
20344
  /* A row with a step goes there: the whole row, a wash under the pointer, no arrow, so rows with and
20112
20345
  without one line up. */
20113
- .knobs-wt-tablebox [data-go]{cursor:pointer;outline:none}
20346
+ .knobs-wt-tablebox [data-go]{cursor:default;outline:none}
20114
20347
  .knobs-wt-tablebox tr[data-go]>td,.knobs-wt-trow[data-go]{transition:background calc(var(--_k-hover-out) * 1ms)}
20115
20348
  .knobs-wt-tablebox tr[data-go]:is(:hover,:focus-visible)>td,.knobs-wt-trow[data-go]:is(:hover,:focus-visible){background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20116
20349
  .knobs-wt-tablebox [data-go]:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
@@ -20166,13 +20399,18 @@ var walkthroughCss = `
20166
20399
  .knobs-wt-body:has(>.knobs-wt-chapters){display:flex;flex-direction:column;gap:var(--_k-space-title)}
20167
20400
  .knobs-wt-chapters{position:relative;display:flex;flex-direction:column;margin:0 calc(var(--_k-space-sibling) * -1)}
20168
20401
  .knobs-ui .knobs-wt-chapter{position:relative;display:grid;grid-template-columns:2ch minmax(0,1fr) auto;column-gap:var(--_k-space-sibling);align-items:center;height:32px;padding:0 var(--_k-space-sibling);
20169
- border-radius:var(--_k-radius);color:var(--_k-fg-2);text-align:left;white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
20170
- .knobs-ui .knobs-wt-chapter:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20402
+ border-radius:var(--_k-radius);color:var(--_k-fg-2);text-align:left;white-space:nowrap;cursor:default;transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
20403
+ .knobs-ui .knobs-wt-chapter:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20171
20404
  .knobs-ui .knobs-wt-chapter:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
20172
20405
  .knobs-wt-chapter-n{font-size:var(--_k-type-small);color:var(--_k-fg-3)}
20173
20406
  .knobs-wt-chapter-label{min-width:0;overflow:hidden;${fade2}}
20174
20407
  .knobs-wt-chapter-time{font:var(--_k-weight) var(--_k-type-small)/1 var(--_k-mono);color:var(--_k-fg-3)}
20175
20408
  .knobs-pick-copy :is(.knobs-wt-chapter-n,.knobs-wt-chapter-time){color:var(--_k-fg-2)}
20409
+ /* Every hover in the walkthrough lands at once (hover-in, 0 ms) and eases out (hover-out): links,
20410
+ buttons, chips, rail, chapter, table and stack rows (its own; the pieces inside keep theirs). */
20411
+ :is(.knobs-walkthrough,.knobs-wt-slot) :is(a,button,[data-go])[class*="knobs-wt-"]:is(:hover,:focus-visible),
20412
+ .knobs-walkthrough a:not([class]):hover,
20413
+ :is(.knobs-walkthrough,.knobs-wt-slot) :is(.knobs-wt-srow,tr)[data-go]:is(:hover,:focus-visible) :is(td,.knobs-wt-stitle){transition-duration:calc(var(--_k-hover-in) * 1ms)}
20176
20414
  @media(prefers-reduced-motion:reduce){:is(.knobs-walkthrough,.knobs-wt-slot) :where(*,::before,::after){transition-duration:.15s!important;transition-property:opacity,background,color!important}}
20177
20415
  @media(prefers-reduced-motion:reduce){.knobs-wt-disc-content{transition:opacity .15s}}
20178
20416
  `;
@@ -20273,7 +20511,6 @@ async function mountCompare(host, m, look, alive, onEvent) {
20273
20511
  fit.style.setProperty("--kw-aspect", String(known ?? DEFAULT_ASPECT));
20274
20512
  const width = host.getBoundingClientRect().width;
20275
20513
  const mode = m.mode ?? (width && width < 560 ? "wipe" : "side");
20276
- fit.toggleAttribute("data-overlay", mode !== "side");
20277
20514
  host.append(fit);
20278
20515
  const [aspect, lib] = await Promise.all([
20279
20516
  known ? Promise.resolve(known) : probeAspect(doc, after.src),
@@ -20297,7 +20534,6 @@ async function mountCompare(host, m, look, alive, onEvent) {
20297
20534
  ...aspect ? { aspect } : {},
20298
20535
  ...m.strip ? { strip: true } : {},
20299
20536
  onMode: (next) => {
20300
- fit.toggleAttribute("data-overlay", next !== "side");
20301
20537
  onEvent?.({ what: "mode", detail: next });
20302
20538
  const layer = marks?.element;
20303
20539
  if (layer) {
@@ -20321,6 +20557,12 @@ async function mountCompare(host, m, look, alive, onEvent) {
20321
20557
  }
20322
20558
  });
20323
20559
  const stage = fit.querySelector(".knobs-cmp-stage");
20560
+ if (stage)
20561
+ doc.defaultView?.requestAnimationFrame(() => {
20562
+ const chrome = fit.clientHeight - stage.clientHeight;
20563
+ if (alive() && stage.clientHeight > 0 && chrome > 0)
20564
+ fit.style.setProperty("--kw-chrome", `${Math.round(chrome)}px`);
20565
+ });
20324
20566
  if (m.annotations?.length && stage) {
20325
20567
  const shape = { w: aspect ?? DEFAULT_ASPECT, h: 1 };
20326
20568
  marks = annotationLayer(stage, m.annotations, () => {
@@ -20396,45 +20638,38 @@ async function mountCompare(host, m, look, alive, onEvent) {
20396
20638
  var round2 = (v) => Math.round(v * 100) / 100;
20397
20639
  var MARK_GLIDE = 450;
20398
20640
  var zoomedIn = (z) => z.w < 0.999 || z.h < 0.999;
20399
- function mountVideo(host, m, onEvent) {
20641
+ async function mountVideo(host, m, look, alive, onEvent) {
20400
20642
  const doc = host.ownerDocument;
20401
- const frame = div(doc, "knobs-wt-frame");
20643
+ await Promise.resolve().then(() => init_player());
20644
+ if (!alive())
20645
+ return { dispose() {} };
20402
20646
  const aspect = parseAspect(m.aspect);
20403
- if (aspect)
20404
- frame.style.setProperty("--kw-aspect", String(aspect));
20405
- const v = doc.createElement("video");
20406
- v.controls = true;
20407
- v.loop = true;
20408
- v.muted = true;
20409
- v.playsInline = true;
20410
- v.preload = "metadata";
20411
- if (m.poster)
20412
- v.poster = m.poster;
20413
- if (!aspect)
20414
- v.addEventListener("loadedmetadata", () => v.videoWidth && frame.style.setProperty("--kw-aspect", String(v.videoWidth / v.videoHeight)), { once: true });
20415
- if (m.captions) {
20416
- const track = doc.createElement("track");
20417
- track.kind = "captions";
20418
- track.label = "Captions";
20419
- track.srclang = doc.documentElement.lang || "en";
20420
- track.default = true;
20421
- track.src = m.captions;
20422
- v.append(track);
20423
- }
20424
- v.src = m.src;
20425
- v.addEventListener("play", () => onEvent?.({ what: "play", time: round2(v.currentTime) }));
20426
- v.addEventListener("pause", () => onEvent?.({ what: "pause", time: round2(v.currentTime) }));
20427
- v.addEventListener("seeked", () => {
20428
- if (v.paused)
20429
- onEvent?.({ what: "seek", time: round2(v.currentTime) });
20647
+ let pills = null;
20648
+ let marking = null;
20649
+ const p = player(host, {
20650
+ ...look,
20651
+ src: m.src,
20652
+ ...m.poster ? { poster: m.poster } : {},
20653
+ ...m.captions ? { captions: m.captions } : {},
20654
+ ...aspect ? { aspect } : {},
20655
+ ...m.label ? { label: m.label } : {},
20656
+ ...m.chapters?.length ? { chapters: m.chapters.map((c) => ({ label: c.label, start: c.start, ...c.id ? { id: c.id } : {} })) } : {},
20657
+ onPlay: (on, time) => {
20658
+ marking?.kick();
20659
+ onEvent?.({ what: on ? "play" : "pause", time: round2(time) });
20660
+ },
20661
+ onSeek: (time) => {
20662
+ pills?.sync();
20663
+ marking?.kick();
20664
+ onEvent?.({ what: "seek", time: round2(time) });
20665
+ }
20430
20666
  });
20431
- frame.append(v);
20432
- host.append(frame);
20433
- const pills = m.chapters?.length ? chapterPills(host, m.chapters, {
20434
- now: () => v.currentTime,
20667
+ const v = p.video;
20668
+ pills = m.chapters?.length ? chapterPills(host, m.chapters, {
20669
+ now: () => p.time,
20435
20670
  seek: (t) => {
20436
- v.currentTime = t;
20437
- v.play().catch(() => {});
20671
+ p.seek(t, { glide: true });
20672
+ p.play();
20438
20673
  },
20439
20674
  onEvent
20440
20675
  }) : null;
@@ -20442,36 +20677,33 @@ function mountVideo(host, m, onEvent) {
20442
20677
  if (pills)
20443
20678
  for (const type of ["timeupdate", "seeking", "seeked"])
20444
20679
  v.addEventListener(type, sync);
20680
+ const frame = p.element.querySelector(".knobs-player-frame") ?? p.element;
20445
20681
  const marks = m.annotations?.length ? annotationLayer(frame, m.annotations, () => ({
20446
20682
  rects: [
20447
20683
  contain({ w: frame.clientWidth, h: frame.clientHeight }, { w: v.videoWidth, h: v.videoHeight })
20448
20684
  ],
20449
- time: v.currentTime
20685
+ time: p.time
20450
20686
  })) : null;
20451
- const marking = marks ? follow(doc.defaultView, () => !v.paused, () => marks.update()) : null;
20452
- if (marking)
20453
- for (const type of ["play", "seeked", "loadedmetadata", "timeupdate"])
20454
- v.addEventListener(type, () => marking.kick());
20687
+ if (marks) {
20688
+ marking = follow(doc.defaultView, () => p.playing, () => marks.update());
20689
+ for (const type of ["seeked", "loadedmetadata", "timeupdate"])
20690
+ v.addEventListener(type, () => marking?.kick());
20691
+ }
20455
20692
  return {
20456
- pause: () => v.pause(),
20457
- seekBy: (d) => {
20458
- v.currentTime = Math.max(0, Math.min(v.duration || Infinity, v.currentTime + d));
20459
- },
20693
+ pause: () => p.pause(),
20694
+ seekBy: (d) => p.seekBy(d),
20460
20695
  context: () => ({
20461
20696
  kind: "video",
20462
- time: round2(v.currentTime),
20463
- ...Number.isFinite(v.duration) ? { duration: round2(v.duration) } : {},
20464
- playing: !v.paused,
20697
+ time: round2(p.time),
20698
+ ...p.duration ? { duration: round2(p.duration) } : {},
20699
+ playing: p.playing,
20465
20700
  ...pills?.current ? { chapter: pills.current.label } : {}
20466
20701
  }),
20467
20702
  dispose: () => {
20468
20703
  marking?.stop();
20469
20704
  marks?.dispose();
20470
20705
  pills?.dispose();
20471
- v.pause();
20472
- v.removeAttribute("src");
20473
- v.load();
20474
- frame.remove();
20706
+ p.dispose();
20475
20707
  }
20476
20708
  };
20477
20709
  }
@@ -20732,7 +20964,7 @@ function mediaSlot(container, options) {
20732
20964
  case "diff":
20733
20965
  return void mountDiff(host, m, look, alive, options.onEvent).then(done, fail("diff"));
20734
20966
  case "video":
20735
- return done(mountVideo(host, m, options.onEvent));
20967
+ return void mountVideo(host, m, look, alive, options.onEvent).then(done, fail("video"));
20736
20968
  case "image":
20737
20969
  return done(mountImage(host, m));
20738
20970
  case "code":
@@ -21715,11 +21947,12 @@ function walkthrough(container, options) {
21715
21947
  line.append(s);
21716
21948
  return s;
21717
21949
  };
21718
- const findPRIn = (n) => findPR(page.prs, n, page.repo);
21950
+ const findPRIn = (n, kind = "pr") => findPR(page.prs, n, page.repo, kind);
21719
21951
  const metaLink = (line, text, href) => {
21720
21952
  const n = prNumber(href);
21721
- if (n) {
21722
- const chip = prChip(doc, n, { href, ...findPRIn(n) });
21953
+ const issue = n ? null : issueNumber(href);
21954
+ if (n || issue) {
21955
+ const chip = n ? prChip(doc, n, { ...findPRIn(n), href }) : prChip(doc, issue, { ...findPRIn(issue, "issue"), href });
21723
21956
  line.append(chip);
21724
21957
  return chip;
21725
21958
  }