rshow 0.0.5 → 0.0.7

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.
Files changed (41) hide show
  1. package/dist/main.js +15258 -631
  2. package/package.json +2 -1
  3. package/skills/rshow/SKILL.md +7 -0
  4. package/skills/rshow/reference/mockups.md +1 -0
  5. package/skills/rshow/reference/reports.md +5 -1
  6. package/skills/rshow-slides/SKILL.md +9 -0
  7. package/skills/rshow-slides/reference/layouts.md +15 -0
  8. package/themes/default/deck.css +325 -1
  9. package/themes/default/deck.js +255 -5
  10. package/themes/default/example/index.html +19 -2
  11. package/themes/default/example/report.html +17 -10
  12. package/themes/default/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
  13. package/themes/default/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
  14. package/themes/default/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
  15. package/themes/default/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
  16. package/themes/default/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
  17. package/themes/default/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
  18. package/themes/default/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
  19. package/themes/default/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
  20. package/themes/default/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
  21. package/themes/default/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
  22. package/themes/default/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
  23. package/themes/default/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
  24. package/themes/default/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
  25. package/themes/default/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
  26. package/themes/default/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
  27. package/themes/default/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
  28. package/themes/default/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
  29. package/themes/default/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
  30. package/themes/default/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
  31. package/themes/default/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
  32. package/themes/default/fonts/katex/LICENSE +21 -0
  33. package/themes/default/math.css +46 -0
  34. package/themes/default/math.js +58 -0
  35. package/themes/default/mockup.css +105 -2
  36. package/themes/default/mockup.js +342 -18
  37. package/themes/default/report.css +15 -6
  38. package/themes/default/terminal.css +8 -0
  39. package/themes/default/terminal.js +70 -13
  40. package/themes/default/theme.json +7 -0
  41. package/worker/index.js +15 -15
@@ -388,24 +388,30 @@ tbody tr:last-child td {
388
388
  font-variant-numeric: tabular-nums;
389
389
  }
390
390
 
391
- /* An equation is one line of mono with its number at the right end of the text
392
- column, so the number lines up with the right edge of the prose. The box
393
- itself stays in the body face, which is what makes --measure resolve to the
394
- same width here as in a paragraph. */
391
+ /* An equation is a .math box in KaTeX's own face with its number at the right
392
+ end of the text column, so the number lines up with the right edge of the
393
+ prose. A tall equation centres its number instead of sticking to the first
394
+ line. The box itself stays in the body face, which is what makes --measure
395
+ resolve to the same width here as in a paragraph. */
395
396
  .equation {
396
397
  display: grid;
397
398
  grid-template-columns: minmax(0, 1fr) auto;
398
- align-items: baseline;
399
+ align-items: center;
399
400
  gap: var(--space-05);
400
401
  margin: var(--space-06) 0;
401
402
  line-height: 1.5;
402
403
  }
403
404
 
404
- .equation > span:not(.eq-num) {
405
+ .equation > span:not(.eq-num):not(.math) {
405
406
  font-family: var(--font-mono);
406
407
  font-size: 0.95em;
407
408
  }
408
409
 
410
+ .equation > .math {
411
+ margin: 0;
412
+ min-width: 0;
413
+ }
414
+
409
415
  .eq-num {
410
416
  color: var(--muted);
411
417
  font-variant-numeric: tabular-nums;
@@ -539,6 +545,9 @@ tbody tr:last-child td {
539
545
 
540
546
  .refs li {
541
547
  margin: 0 0 var(--space-04);
548
+ /* A cited address is longer than a phone is wide and has no space to break
549
+ at, so it wraps mid path rather than widening the page. */
550
+ overflow-wrap: anywhere;
542
551
  }
543
552
 
544
553
  .refs li::marker {
@@ -241,6 +241,14 @@
241
241
  tab-size: 8;
242
242
  }
243
243
 
244
+ /* A recording the width cannot hold keeps the grid it was taken on and scrolls
245
+ here, rather than being fitted down to a font nobody can read. The player
246
+ clips its own box, so the scroll has to be said again one class deeper. */
247
+ .terminal .terminal-body .ap-player {
248
+ overflow-x: auto;
249
+ overscroll-behavior-x: contain;
250
+ }
251
+
244
252
  /* asciinema-player declares a dark palette of its own on .ap-player, which
245
253
  would win over anything inherited from the figure. Hand it the figure's
246
254
  palette instead, so a recording and a capture of the same program agree. */
@@ -228,6 +228,23 @@
228
228
  return anchor;
229
229
  }
230
230
 
231
+ // One cell of the theme's monospace font at the size the theme sets, measured
232
+ // on the page. A recording is fitted to the width only while its text stays at
233
+ // MIN_FONT; below that the frame keeps the page width and scrolls instead.
234
+ var MIN_FONT = 9;
235
+
236
+ function cellWidth(host) {
237
+ var probe = element('span', 'terminal-probe');
238
+ probe.style.cssText = 'position: absolute; top: 0; left: 0; visibility: hidden;' +
239
+ 'white-space: pre; font-family: ' + property(host, '--font-mono', 'monospace') +
240
+ '; font-size: ' + fontSize(host) + 'px';
241
+ probe.textContent = '0000000000';
242
+ (document.body || document.documentElement).appendChild(probe);
243
+ var width = probe.getBoundingClientRect().width / 10;
244
+ (document.body || document.documentElement).removeChild(probe);
245
+ return width > 0 ? width : 7.8;
246
+ }
247
+
231
248
  function build(host) {
232
249
  var src = text(host.getAttribute('data-src'));
233
250
  var title = text(host.getAttribute('data-title')) || (src === '' ? 'terminal' : fileName(src));
@@ -264,8 +281,9 @@
264
281
 
265
282
  // ----------------------------------------------------------------- ansi ---
266
283
 
267
- // A terminal window is as wide as its grid: the capture was taken at cols by
268
- // rows, so the frame is stretched to that width and no further.
284
+ // A terminal window is as wide as its grid, and never wider than the page: on a
285
+ // phone the frame keeps the width it is given and the grid scrolls inside the
286
+ // body, which is what terminal.css leaves overflow-x for.
269
287
  function hug(shell, body, term) {
270
288
  if (!term.element) {
271
289
  return;
@@ -276,7 +294,9 @@
276
294
  }
277
295
  var box = window.getComputedStyle(body);
278
296
  var side = (parseFloat(box.paddingLeft) || 0) + (parseFloat(box.paddingRight) || 0);
279
- shell.style.width = Math.ceil(width + side + 2) + 'px';
297
+ var wanted = Math.ceil(width + side + 2);
298
+ var room = shell.parentNode ? shell.parentNode.clientWidth : 0;
299
+ shell.style.width = (room > 0 && wanted > room ? room : wanted) + 'px';
280
300
  }
281
301
 
282
302
  function ansi(host, body, shell, src) {
@@ -339,7 +359,32 @@
339
359
 
340
360
  // ----------------------------------------------------------------- cast ---
341
361
 
362
+ // The grid a recording ran on: the figure says so, or the first line of the
363
+ // asciicast does, one JSON object with its width and height.
364
+ function castGrid(host, src, done) {
365
+ var cols = count(host.getAttribute('data-cols'), 0);
366
+ var rows = count(host.getAttribute('data-rows'), 0);
367
+ if (cols > 0 && rows > 0) {
368
+ done(cols, rows);
369
+ return;
370
+ }
371
+ window.fetch(src, { credentials: 'same-origin' }).then(function (reply) {
372
+ return reply.ok ? reply.text() : '';
373
+ }).then(function (cast) {
374
+ var head = {};
375
+ try {
376
+ head = JSON.parse(cast.split('\n')[0]) || {};
377
+ } catch (err) {
378
+ head = {};
379
+ }
380
+ done(count(head.width, 0), count(head.height, 0));
381
+ }, function () {
382
+ done(0, 0);
383
+ });
384
+ }
385
+
342
386
  function cast(host, body, src) {
387
+ var size = fontSize(host);
343
388
  var settings = {
344
389
  fit: 'width',
345
390
  controls: true,
@@ -348,19 +393,12 @@
348
393
  // rectangle: the prompt and the command being typed.
349
394
  poster: 'npt:0:02',
350
395
  terminalFontFamily: property(host, '--font-mono', 'monospace'),
351
- terminalFontSize: fontSize(host) + 'px',
396
+ terminalFontSize: size + 'px',
352
397
  terminalLineHeight: 1.4
353
398
  };
354
- var cols = count(host.getAttribute('data-cols'), 0);
355
- var rows = count(host.getAttribute('data-rows'), 0);
356
- if (cols > 0) {
357
- settings.cols = cols;
358
- }
359
- if (rows > 0) {
360
- settings.rows = rows;
361
- }
362
399
 
363
400
  var player = null;
401
+ var ready = false;
364
402
 
365
403
  function start(at) {
366
404
  var options = {};
@@ -378,11 +416,30 @@
378
416
  }
379
417
 
380
418
  loadPlayer().then(function () {
381
- start(0);
419
+ castGrid(host, src, function (cols, rows) {
420
+ if (cols > 0) {
421
+ settings.cols = cols;
422
+ }
423
+ if (rows > 0) {
424
+ settings.rows = rows;
425
+ }
426
+ // Fitting 100 columns into a phone asks for a 5px font. So the recording
427
+ // is fitted only while the fit keeps its text at MIN_FONT; below that
428
+ // fit is off at the theme's size and the body scrolls sideways.
429
+ if (cols > 0 && body.clientWidth > 0 &&
430
+ body.clientWidth / (cols * cellWidth(host)) * size < MIN_FONT) {
431
+ settings.fit = false;
432
+ }
433
+ ready = true;
434
+ start(0);
435
+ });
382
436
  // The player reads the palette once, when it mounts, so a mode change
383
437
  // means a new player, put back where the old one was.
384
438
  document.addEventListener('rshowmodechange', function () {
385
439
  var at = 0;
440
+ if (!ready) {
441
+ return;
442
+ }
386
443
  try {
387
444
  at = player && player.getCurrentTime ? player.getCurrentTime() : 0;
388
445
  } catch (err) {
@@ -55,6 +55,13 @@
55
55
  "example": "example/tui.html"
56
56
  }
57
57
  },
58
+ "addons": {
59
+ "math": {
60
+ "css": ["math.css"],
61
+ "js": ["math.js"],
62
+ "example": "example/report.html"
63
+ }
64
+ },
58
65
  "tokens": {
59
66
  "--font-sans": "\"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", sans-serif",
60
67
  "--font-mono": "\"IBM Plex Mono\", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
package/worker/index.js CHANGED
@@ -4497,8 +4497,8 @@ function registerApi(app) {
4497
4497
  __name(registerApi, "registerApi");
4498
4498
 
4499
4499
  // src/static.generated.ts
4500
- var APP_CSS = '/* IBM Carbon Design System v11, g10 theme (light) and g100 theme (dark) */\n:root{--bg:#f4f4f4;--layer-01:#ffffff;--layer-02:#f4f4f4;--layer-hover:#e8e8e8;--layer-active:#d1d1d1;--border-subtle:#e0e0e0;--border-strong:#8d8d8d;--text-primary:#161616;--text-secondary:#525252;--link:#0f62fe;--link-hover:#0043ce;--focus:#0f62fe;--support-error:#da1e28;--support-success:#24a148;--error-bg:#fff1f1;--tag-gray:#e0e0e0;--tag-blue:#d0e2ff;--tag-blue-ink:#0043ce;--tag-teal:#9ef0f0;--tag-teal-ink:#004144;--btn-primary:#0f62fe;--btn-primary-hover:#0043ce;--btn-primary-ink:#ffffff;--btn-secondary:#393939;--btn-secondary-hover:#4c4c4c;--tag-live-bg:#a7f0ba;--tag-live-ink:#044317;--code-bg:#161616;--code-ink:#f4f4f4;--shell:#161616;--shell-fg:#f4f4f4;--shell-muted:#c6c6c6;--shell-field:#393939;--nav-selected:#e8e8e8;--segment-on-bg:#161616;--segment-on-fg:#ffffff;--font-sans:"IBM Plex Sans",system-ui,-apple-system,sans-serif;--font-mono:"IBM Plex Mono",ui-monospace,monospace}\n@media (prefers-color-scheme: dark){:root:not([data-mode="light"]){--bg:#161616;--layer-01:#262626;--layer-02:#393939;--layer-hover:#333333;--layer-active:#525252;--border-subtle:#393939;--border-strong:#6f6f6f;--text-primary:#f4f4f4;--text-secondary:#c6c6c6;--link:#78a9ff;--link-hover:#a6c8ff;--focus:#ffffff;--support-error:#fa4d56;--support-success:#42be65;--error-bg:#2d0709;--tag-gray:#393939;--tag-blue:#002d9c;--tag-blue-ink:#d0e2ff;--tag-teal:#004144;--tag-teal-ink:#9ef0f0;--btn-primary:#0f62fe;--btn-primary-hover:#0353e9;--btn-primary-ink:#ffffff;--btn-secondary:#6f6f6f;--btn-secondary-hover:#525252;--tag-live-bg:#044317;--tag-live-ink:#a7f0ba;--code-bg:#262626;--code-ink:#f4f4f4;--shell:#161616;--shell-fg:#f4f4f4;--shell-muted:#c6c6c6;--shell-field:#262626;--nav-selected:#393939;--segment-on-bg:#f4f4f4;--segment-on-fg:#161616}}\n:root[data-mode="dark"]{--bg:#161616;--layer-01:#262626;--layer-02:#393939;--layer-hover:#333333;--layer-active:#525252;--border-subtle:#393939;--border-strong:#6f6f6f;--text-primary:#f4f4f4;--text-secondary:#c6c6c6;--link:#78a9ff;--link-hover:#a6c8ff;--focus:#ffffff;--support-error:#fa4d56;--support-success:#42be65;--error-bg:#2d0709;--tag-gray:#393939;--tag-blue:#002d9c;--tag-blue-ink:#d0e2ff;--tag-teal:#004144;--tag-teal-ink:#9ef0f0;--btn-primary:#0f62fe;--btn-primary-hover:#0353e9;--btn-primary-ink:#ffffff;--btn-secondary:#6f6f6f;--btn-secondary-hover:#525252;--tag-live-bg:#044317;--tag-live-ink:#a7f0ba;--code-bg:#262626;--code-ink:#f4f4f4;--shell:#161616;--shell-fg:#f4f4f4;--shell-muted:#c6c6c6;--shell-field:#262626;--nav-selected:#393939;--segment-on-bg:#f4f4f4;--segment-on-fg:#161616}\n*{box-sizing:border-box}\nhtml,body{margin:0;padding:0}\nbody{background:var(--bg);color:var(--text-primary);font:400 14px/1.43 var(--font-sans);-webkit-font-smoothing:antialiased}\na{color:var(--link);text-decoration:none}\na:hover{color:var(--link-hover);text-decoration:underline}\na:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,summary:focus-visible,.segment:focus-within{outline:2px solid var(--focus);outline-offset:-2px}\nh1,h2,h3,p{margin:0}\nh1{font-size:28px;font-weight:400;line-height:1.29;letter-spacing:0}\nh2{font-size:16px;font-weight:600;line-height:1.375;letter-spacing:0}\n.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}\n.muted{color:var(--text-secondary)}\n.mono{font-family:var(--font-mono);font-size:12px;line-height:1.33}\n.empty{padding:24px 16px;margin:0;color:var(--text-secondary)}\n.app{min-height:100vh;display:flex;flex-direction:column;background:var(--bg);color:var(--text-primary)}\n.topbar{height:48px;flex:0 0 48px;display:flex;align-items:center;gap:16px;padding:0 16px;background:var(--shell);color:var(--shell-fg)}\n.brand{display:inline-flex;align-items:baseline;gap:8px;color:var(--shell-fg);font-size:14px;font-weight:600;white-space:nowrap}\n.brand:hover{color:var(--shell-fg);text-decoration:none}\n.brand-sub{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--shell-muted)}\n.search{flex:1 1 240px;min-width:0;max-width:520px;height:32px;display:flex;align-items:center;gap:8px;padding:0 12px;background:var(--shell-field);color:var(--shell-fg);margin-left:8px}\n.search-icon{flex:0 0 auto;color:var(--shell-muted);font-size:16px;line-height:1}\n.search input{flex:1;min-width:0;height:32px;padding:0;border:0;background:transparent;color:var(--shell-fg);font:400 14px/1.29 var(--font-sans)}\n.search input::placeholder{color:var(--shell-muted)}\n.search input:focus{outline:2px solid var(--focus);outline-offset:-2px}\n.top-spacer{flex:1}\n.topbar-user{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--font-mono);font-size:12px;color:var(--shell-muted)}\nbutton.mode-toggle{height:48px;padding:0 12px;display:inline-flex;align-items:center;gap:8px;color:var(--shell-fg);background:transparent;border:0;border-radius:0;white-space:nowrap}\nbutton.mode-toggle:hover{background:var(--shell-field);color:var(--shell-fg)}\n.shell{flex:1;display:flex;align-items:stretch;min-height:0}\n.sidenav{flex:0 0 240px;width:240px;padding:16px 0;background:var(--layer-01);border-right:1px solid var(--border-subtle)}\n.nav-item{display:flex;align-items:center;gap:8px;min-height:40px;padding:8px 16px;border-left:3px solid transparent;color:var(--text-primary);font-size:14px;line-height:1.29}\n.nav-item:hover{background:var(--layer-hover);color:var(--text-primary);text-decoration:none}\n.nav-item.is-active{background:var(--nav-selected);border-left-color:var(--link);font-weight:600}\n.nav-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.nav-count{margin-left:auto;color:var(--text-secondary);font-weight:400;font-family:var(--font-mono);font-size:12px}\n.nav-heading{margin:24px 16px 8px;font-size:12px;line-height:1.33;color:var(--text-secondary);letter-spacing:.32px}\n.main{flex:999 1 640px;min-width:0;padding:32px}\n.page{width:100%;max-width:1376px;margin:0 auto;padding:24px 32px 48px}\n.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:24px}\n.detail-head{align-items:flex-start}\n.detail-head h1,.signin-card h1{font-size:32px;font-weight:300;line-height:1.25}\n.title-block{min-width:0;flex:1 1 480px}\n.page-sub{margin:4px 0 0;color:var(--text-secondary);max-width:80ch}\n.project-description{margin-top:12px;color:var(--text-primary);font-size:16px;line-height:1.5}\n.meta-line{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin:8px 0 0;color:var(--text-secondary);font-size:14px}\n.meta-line a{color:var(--text-primary)}\n.meta-line a.mono:hover{color:var(--link);text-decoration:none}\n.meta-line .pill{vertical-align:middle}\n.breadcrumbs{margin:0 0 12px;font-size:14px;line-height:1.29;color:var(--text-secondary)}\n.breadcrumbs a{color:var(--link)}\n.crumb-sep{margin:0 4px;color:var(--text-secondary)}\n.button-row{display:flex;align-items:center;gap:1px;flex-wrap:wrap}\n.btn{display:inline-flex;align-items:center;justify-content:center;height:48px;padding:0 16px;border:1px solid transparent;border-radius:0;background:var(--btn-secondary);color:#fff;font:400 14px/1.29 var(--font-sans);white-space:nowrap;cursor:pointer}\n.btn:hover{background:var(--btn-secondary-hover);color:#fff;text-decoration:none}\n.btn-primary{background:var(--btn-primary);color:var(--btn-primary-ink)}\n.btn-primary:hover{background:var(--btn-primary-hover);color:var(--btn-primary-ink)}\n.btn-tertiary{background:transparent;border-color:var(--border-strong);color:var(--text-primary)}\n.btn-tertiary:hover{background:var(--layer-hover);color:var(--text-primary)}\n.btn-danger{background:transparent;border-color:var(--support-error);color:var(--support-error)}\n.btn-danger:hover{background:var(--error-bg);color:var(--support-error)}\n.btn-sm{height:40px;padding:0 12px}\n.btn-link{display:inline-flex;align-items:center;height:32px;padding:0 8px;border:0;background:transparent;color:var(--link);font:400 14px/1.29 var(--font-sans);cursor:pointer}\n.btn-link:hover{background:var(--layer-hover);color:var(--link-hover);text-decoration:none}\nbutton{height:40px;padding:0 16px;border:1px solid transparent;border-radius:0;background:var(--btn-secondary);color:#fff;font:400 14px/1.29 var(--font-sans);cursor:pointer}\nbutton:hover{background:var(--btn-secondary-hover)}\nbutton.primary{background:var(--btn-primary);color:var(--btn-primary-ink)}\nbutton.primary:hover{background:var(--btn-primary-hover)}\nbutton.small{height:40px;padding:0 12px;border-color:transparent;background:transparent;color:var(--link)}\nbutton.small:hover{background:var(--layer-hover);color:var(--link-hover)}\nbutton.small.danger{color:var(--support-error)}\nbutton.danger{background:transparent;border-color:transparent;color:var(--support-error)}\nbutton.danger:hover{background:var(--error-bg);color:var(--support-error)}\n.icon-btn{width:40px;height:40px;padding:0;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:0;color:var(--text-secondary);font-size:20px;line-height:1}\n.icon-btn:hover{background:var(--layer-hover);color:var(--text-primary)}\n.tagbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:16px;min-height:32px}\n.tagbar-label{margin-right:4px;color:var(--text-secondary);font-size:14px}\n.tagbar-spacer{flex:1}\n.tag-chip{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border-radius:16px;border:0;background:var(--tag-gray);color:var(--text-primary);font:400 12px/1.33 var(--font-sans);text-decoration:none;white-space:nowrap;cursor:pointer}\n.tag-chip:hover{background:var(--layer-active);color:var(--text-primary);text-decoration:none}\n.tag-chip.is-on{background:var(--btn-primary);color:var(--btn-primary-ink)}\n.tag-count{font-family:var(--font-mono);opacity:.72}\n.tag-chip-static{cursor:default;padding-right:6px}\n.tag-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-height:32px}\n.tag-remove{width:20px;height:20px;padding:0;display:inline-flex;align-items:center;justify-content:center;border:0;background:transparent;color:inherit;font-size:16px;line-height:1}\n.tag-remove:hover{background:var(--layer-active);color:var(--text-primary)}\n.sharing-filter{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.inline-label{display:inline-flex;align-items:center;gap:8px;color:var(--text-secondary);font-size:14px;white-space:nowrap}\ninput,select,textarea{height:40px;padding:0 12px;border:0;border-bottom:1px solid var(--border-strong);border-radius:0;background:var(--layer-02);color:var(--text-primary);font:400 14px/1.29 var(--font-sans)}\nselect{padding-right:32px;cursor:pointer}\ntextarea{height:auto;min-height:80px;padding:8px 12px;resize:vertical}\ninput::placeholder,textarea::placeholder{color:var(--text-secondary)}\ninput:focus,select:focus,textarea:focus{outline:2px solid var(--focus);outline-offset:-2px}\ninput:disabled,select:disabled,textarea:disabled{color:var(--text-secondary);cursor:not-allowed}\n.field-row{display:flex;align-items:stretch;gap:1px;flex-wrap:wrap}\n.field-row input,.field-row select{flex:1 1 160px;min-width:0}\n.field-row .btn{height:40px}\n.field-label{display:block;margin-bottom:8px;color:var(--text-secondary);font-size:12px;line-height:1.33;letter-spacing:.32px}\n.field-hint{color:var(--text-secondary);font-size:12px;line-height:1.33}\n.form-stack{display:flex;flex-direction:column;gap:24px}\n.form-field{display:flex;flex-direction:column;gap:8px;max-width:640px}\n.form-field input,.form-field textarea{width:100%}\n.form-card{max-width:720px}\n.form-actions{display:flex;gap:1px;flex-wrap:wrap}\n.detail-block{display:flex;flex-direction:column;gap:8px;margin-bottom:24px}\n.detail-block:last-child{margin-bottom:0}\n.tag-form{margin-top:12px;max-width:420px}\n.table-box{width:100%;overflow-x:auto;background:var(--layer-01);border-radius:0}\n.data-table{width:100%;border-collapse:collapse;font-size:14px;line-height:1.29}\n.gallery-table{min-width:860px}\n.project-table{min-width:620px}\n.token-table{min-width:640px}\n.data-table th,.data-table td{height:48px;padding:0 16px;text-align:left;vertical-align:middle;white-space:nowrap;border-bottom:1px solid var(--border-subtle)}\n.data-table th{background:var(--border-subtle);color:var(--text-primary);font-weight:600}\n.data-table tbody tr:hover td{background:var(--layer-hover)}\n.data-table tr:last-child td{border-bottom:0}\n.cell-name{min-width:220px}\n.cell-name .row-title{display:block;color:var(--text-primary);font-weight:400;max-width:360px;overflow:hidden;text-overflow:ellipsis}\n.cell-name .row-title:hover{color:var(--link);text-decoration:none}\n.cell-name .slug-link{display:block;margin-top:2px;color:var(--text-secondary)}\n.cell-name .slug-link:hover{color:var(--link);text-decoration:none}\n.empty-row td{height:auto;padding:32px 16px;white-space:normal;color:var(--text-secondary);text-align:center}\n.pill{display:inline-flex;align-items:center;height:24px;padding:0 8px;border-radius:12px;background:var(--tag-gray);color:var(--text-primary);font-size:12px;font-weight:400;line-height:1;white-space:nowrap}\n.pill-private{background:var(--tag-gray);color:var(--text-primary)}\n.pill-people{background:var(--tag-blue);color:var(--tag-blue-ink)}\n.pill-public{background:var(--tag-teal);color:var(--tag-teal-ink)}\n.pill-live{background:var(--tag-live-bg);color:var(--tag-live-ink)}\na.pill:hover{text-decoration:none;color:var(--text-primary)}\n.panel{margin-bottom:24px;background:var(--layer-01);border-radius:0}\n.panel-head{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;min-height:48px;padding:8px 16px;border-bottom:1px solid var(--border-subtle)}\n.panel-head h2{font-size:16px;font-weight:600}\n.panel-head .muted{font-size:14px}\n.panel-body{padding:16px}\n.detail-layout{display:flex;align-items:flex-start;gap:24px;flex-wrap:wrap}\n.detail-main{flex:999 1 640px;min-width:0}\n.detail-side{flex:1 1 380px;min-width:0;display:flex;flex-direction:column;gap:20px;padding:16px;background:var(--layer-01)}\n.detail-side h2{font-size:16px;font-weight:600}\n.side-text{margin:4px 0 0;color:var(--text-secondary);line-height:1.43}\n.side-hint{margin:4px 0 0;color:var(--text-secondary);font-size:12px;line-height:1.33}\n.side-section{display:flex;flex-direction:column;gap:12px}\n.preview-picker{display:inline-flex;align-items:center;gap:8px}\n.preview-body{padding:16px}\n.preview-frame{display:block;width:100%;aspect-ratio:16/9;border:1px solid var(--border-subtle);background:#ffffff}\n.preview-empty{display:flex;align-items:center;justify-content:center;min-height:200px;padding:24px;border:1px solid var(--border-subtle);background:var(--layer-02);color:var(--text-secondary);text-align:center}\n.version-list{list-style:none;margin:0;padding:0}\n.version-row{display:flex;align-items:center;gap:16px;flex-wrap:wrap;min-height:56px;padding:8px 16px;border-bottom:1px solid var(--border-subtle)}\n.version-row:last-child{border-bottom:0}\n.version-row.is-served{background:var(--layer-02);border-left:3px solid var(--support-success);padding-left:13px}\n.version-n{flex:0 0 36px}\n.version-main{flex:1 1 220px;min-width:0}\n.version-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.version-meta{color:var(--text-secondary);font-size:12px;line-height:1.33}\n.version-actions{padding:12px 16px;border-top:1px solid var(--border-subtle)}\n.segmented{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;margin:0;background:var(--border-strong)}\n.segment{position:relative;display:flex;align-items:center;justify-content:center;height:40px;background:var(--layer-01);color:var(--text-primary);font-size:14px;cursor:pointer}\n.segment:hover{background:var(--layer-hover)}\n.segment.is-on{background:var(--segment-on-bg);color:var(--segment-on-fg)}\n.segment-input{position:absolute;width:1px;height:1px;padding:0;margin:0;overflow:hidden;clip:rect(0,0,0,0);border:0;opacity:0}\n.people-list{list-style:none;margin:0;padding:0}\n.person-row{display:flex;align-items:center;gap:8px;min-height:48px;padding:4px 0;border-bottom:1px solid var(--border-subtle)}\n.person-row:last-child{border-bottom:0}\n.person-email{flex:1;min-width:0;overflow-wrap:anywhere}\n.person-role{color:var(--text-secondary);font-size:12px;white-space:nowrap}\n.info-box{padding:12px 16px;background:var(--layer-02)}\n.info-title{font-weight:600}\n.info-text{margin-top:4px;color:var(--text-secondary);font-size:14px}\n.info-box a{display:inline-block;margin-top:8px}\n.danger-zone{padding-top:16px;border-top:1px solid var(--border-subtle)}\n.danger-zone h2{margin-bottom:8px;font-size:14px;font-weight:600;color:var(--support-error)}\n.theme-list{list-style:none;margin:0;padding:0}\n.theme-row{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:48px;padding:0 16px;border-bottom:1px solid var(--border-subtle)}\n.theme-row:last-child{border-bottom:0}\n.token-box{margin-top:16px;padding:16px;background:var(--code-bg);color:var(--code-ink)}\n.token-box code{display:block;margin-top:8px;font-family:var(--font-mono);font-size:12px;word-break:break-all}\n.token-box .muted{color:var(--code-ink);opacity:.78}\n.token-error{margin-top:12px;color:var(--support-error)}\n.signin{display:flex;align-items:flex-start;justify-content:center;padding-top:80px}\n.signin-card{width:100%;max-width:560px}\n.signin-card .btn{margin-top:8px}\n@media(max-width:900px){\n.shell{display:block}\n.sidenav{width:auto;display:flex;flex-wrap:wrap;padding:8px 0;border-right:0;border-bottom:1px solid var(--border-subtle)}\n.nav-heading{width:100%;margin:12px 16px 4px}\n.nav-item{width:auto;flex:0 0 auto}\n.main{padding:24px 16px 48px}\n.page{padding:16px 16px 40px}\n.detail-side{flex-basis:100%}\n}\n@media(max-width:620px){\n.topbar{height:auto;min-height:48px;flex-wrap:wrap;gap:8px;padding:8px 16px}\n.search{order:3;flex:1 1 100%;max-width:none;margin-left:0}\n.brand-sub{display:none}\n.topbar-user{max-width:100%}\nbutton.mode-toggle{height:40px}\nh1,.detail-head h1,.signin-card h1{font-size:24px}\n.page-head,.detail-head{align-items:flex-start}\n.button-row{width:100%}\n.button-row .btn{flex:1 1 auto}\n.field-row input,.field-row select{flex:1 1 140px}\n.panel-head{align-items:flex-start}\n.version-row{gap:8px}\n.preview-frame{aspect-ratio:4/3}\n}\n';
4501
- var APP_JS = '"use strict";\n(function () {\n function api(method, path, body) {\n var init = { method: method, credentials: "same-origin", headers: {} };\n if (method !== "GET") init.headers["Content-Type"] = "application/json";\n if (body !== undefined) init.body = JSON.stringify(body);\n return fetch(path, init).then(function (res) {\n if (res.status === 204) return null;\n return res.text().then(function (text) {\n var data = null;\n if (text) { try { data = JSON.parse(text); } catch (err) { data = null; } }\n if (!res.ok) {\n var msg = data && data.error ? data.error : "Request failed with status " + res.status;\n throw new Error(msg);\n }\n return data;\n });\n });\n }\n\n function fail(err) {\n window.alert(err && err.message ? err.message : String(err));\n }\n\n function reload() {\n window.location.reload();\n }\n\n function copyText(value) {\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(value).catch(fail);\n return;\n }\n var input = document.createElement("textarea");\n input.value = value;\n document.body.appendChild(input);\n input.select();\n try { document.execCommand("copy"); } catch (err) { fail(err); }\n document.body.removeChild(input);\n }\n\n function propsOf(el) {\n var raw = el.getAttribute("data-props");\n if (!raw) return {};\n try { return JSON.parse(raw) || {}; } catch (err) { return {}; }\n }\n\n function closest(event, selector) {\n var target = event.target;\n if (!target || !target.closest) return null;\n return target.closest(selector);\n }\n\n function flash(el, text) {\n var label = el.textContent;\n el.textContent = text;\n window.setTimeout(function () { el.textContent = label; }, 1200);\n }\n\n function field(form, name) {\n var el = form.querySelector("[name=" + name + "]");\n return el && typeof el.value === "string" ? el.value : "";\n }\n\n var islands = {\n copy: function (root, props) {\n root.addEventListener("click", function (event) {\n event.preventDefault();\n copyText(String(props.text || ""));\n flash(root, "Copied");\n });\n },\n delete: function (root, props) {\n root.addEventListener("click", function (event) {\n event.preventDefault();\n if (!window.confirm("Delete this artifact, all versions and all shares?")) return;\n api("DELETE", "/api/artifacts/" + encodeURIComponent(props.slug)).then(function () {\n window.location.assign("/");\n }, fail);\n });\n },\n versions: function (root, props) {\n root.addEventListener("click", function (event) {\n var el = closest(event, "[data-action]");\n if (!el || !root.contains(el)) return;\n var action = el.getAttribute("data-action");\n if (action !== "pin" && action !== "set-live" && action !== "newest") return;\n event.preventDefault();\n if (action === "newest") {\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), { pinned: null }).then(reload, fail);\n } else {\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n pinned: Number(el.getAttribute("data-n"))\n }).then(reload, fail);\n }\n });\n },\n share: function (root, props) {\n root.addEventListener("click", function (event) {\n var removed = closest(event, "[data-action=remove-share]");\n if (removed && root.contains(removed)) {\n event.preventDefault();\n api(\n "DELETE",\n "/api/artifacts/" + encodeURIComponent(props.slug) + "/shares/" + encodeURIComponent(removed.getAttribute("data-email"))\n ).then(reload, fail);\n return;\n }\n var tag = closest(event, "[data-action=remove-tag]");\n if (tag && root.contains(tag)) {\n event.preventDefault();\n var tags = Array.isArray(props.tags) ? props.tags.filter(function (value) {\n return value !== tag.getAttribute("data-tag");\n }) : [];\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), { tags: tags }).then(reload, fail);\n }\n });\n root.addEventListener("submit", function (event) {\n var form = event.target;\n var kind = form.getAttribute("data-form");\n if (!kind) return;\n event.preventDefault();\n if (kind === "mode") {\n var checked = form.querySelector("[name=mode]:checked") || form.querySelector("[name=mode]");\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n mode: checked ? checked.value : "private"\n }).then(reload, fail);\n } else if (kind === "title") {\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n title: field(form, "title").trim()\n }).then(reload, fail);\n } else if (kind === "share") {\n var email = field(form, "email").trim();\n if (email === "") return;\n api(\n "PUT",\n "/api/artifacts/" + encodeURIComponent(props.slug) + "/shares/" + encodeURIComponent(email),\n { role: field(form, "role") }\n ).then(reload, fail);\n } else if (kind === "tags") {\n var tagInput = field(form, "tag").trim().toLowerCase();\n if (tagInput === "") return;\n var next = Array.isArray(props.tags) ? props.tags.slice() : [];\n if (next.indexOf(tagInput) === -1) next.push(tagInput);\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), { tags: next }).then(reload, fail);\n } else if (kind === "project") {\n var project = field(form, "project");\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n project: project === "" ? null : project\n }).then(reload, fail);\n }\n });\n },\n project: function (root, props) {\n root.addEventListener("click", function (event) {\n var el = closest(event, "[data-action=remove-member]");\n if (!el || !root.contains(el)) return;\n event.preventDefault();\n api(\n "DELETE",\n "/api/projects/" + encodeURIComponent(props.slug) + "/members/" + encodeURIComponent(el.getAttribute("data-email"))\n ).then(reload, fail);\n });\n root.addEventListener("submit", function (event) {\n var form = event.target;\n var kind = form.getAttribute("data-form");\n if (!kind) return;\n event.preventDefault();\n if (kind === "project-new") {\n var slug = field(form, "slug").trim().toLowerCase();\n if (slug === "") return;\n var body = { slug: slug };\n var title = field(form, "title").trim();\n var description = field(form, "description").trim();\n if (title !== "") body.title = title;\n if (description !== "") body.description = description;\n api("POST", "/api/projects", body).then(function (data) {\n window.location.assign("/p/" + encodeURIComponent(data.slug));\n }, fail);\n } else if (kind === "description") {\n api("PATCH", "/api/projects/" + encodeURIComponent(props.slug), {\n description: field(form, "description").trim()\n }).then(reload, fail);\n } else if (kind === "add-artifact") {\n var artifact = field(form, "artifact");\n if (artifact === "") return;\n api("PATCH", "/api/artifacts/" + encodeURIComponent(artifact), { project: props.slug }).then(reload, fail);\n } else if (kind === "member") {\n var email = field(form, "email").trim();\n if (email === "") return;\n api(\n "PUT",\n "/api/projects/" + encodeURIComponent(props.slug) + "/members/" + encodeURIComponent(email),\n { role: field(form, "role") }\n ).then(reload, fail);\n }\n });\n },\n tokens: function (root) {\n root.addEventListener("click", function (event) {\n var copy = closest(event, "[data-action=copy]");\n if (copy && root.contains(copy)) {\n event.preventDefault();\n copyText(copy.getAttribute("data-copy") || "");\n flash(copy, "Copied");\n return;\n }\n var revoke = closest(event, "[data-action=revoke], [data-action=revoke-token]");\n if (!revoke || !root.contains(revoke)) return;\n event.preventDefault();\n if (!window.confirm("Revoke this token? The CLI using it will stop working.")) return;\n api("DELETE", "/api/tokens/" + encodeURIComponent(revoke.getAttribute("data-id"))).then(function () {\n var row = revoke.closest("tr");\n if (row) row.remove();\n }, fail);\n });\n root.addEventListener("submit", function (event) {\n var form = event.target;\n if (form.getAttribute("data-form") !== "token") return;\n event.preventDefault();\n var name = field(form, "name").trim();\n var scope = field(form, "scope");\n api("POST", "/api/tokens", { name: name, scope: scope }).then(function (data) {\n var box = document.getElementById("token-box");\n var code = document.getElementById("token-value");\n var error = document.getElementById("token-error");\n if (box && code) {\n code.textContent = data.token;\n box.hidden = false;\n }\n if (error) error.hidden = true;\n var body = document.getElementById("token-rows");\n if (body) {\n var tr = document.createElement("tr");\n var cells = [data.name, data.scope.charAt(0).toUpperCase() + data.scope.slice(1), String(data.created_at).slice(0, 10), "Never"];\n for (var i = 0; i < cells.length; i += 1) {\n var td = document.createElement("td");\n td.textContent = cells[i];\n tr.appendChild(td);\n }\n var last = document.createElement("td");\n var button = document.createElement("button");\n button.type = "button";\n button.className = "btn-link";\n button.setAttribute("data-action", "revoke");\n button.setAttribute("data-id", data.id);\n button.textContent = "Revoke";\n last.appendChild(button);\n tr.appendChild(last);\n body.appendChild(tr);\n }\n form.reset();\n }, function (err) {\n var box = document.getElementById("token-error");\n if (box) {\n box.textContent = err && err.message ? err.message : String(err);\n box.hidden = false;\n return;\n }\n fail(err);\n });\n });\n }\n };\n\n var MODE_KEY = "rshow-mode";\n var MODES = ["system", "light", "dark"];\n var MODE_LABELS = { system: "System", light: "Light", dark: "Dark" };\n\n function normalizeMode(value) {\n return value === "light" || value === "dark" ? value : "system";\n }\n\n function readMode() {\n try { return normalizeMode(window.localStorage.getItem(MODE_KEY)); } catch (err) { return "system"; }\n }\n\n function applyMode(mode) {\n var root = document.documentElement;\n if (mode === "light" || mode === "dark") root.dataset.mode = mode;\n else delete root.dataset.mode;\n }\n\n function saveMode(mode) {\n try { window.localStorage.setItem(MODE_KEY, mode); } catch (err) { /* storage may be blocked */ }\n applyMode(mode);\n }\n\n function paintMode(toggle) {\n var mode = readMode();\n toggle.textContent = MODE_LABELS[mode];\n toggle.setAttribute("aria-label", "Color mode: " + MODE_LABELS[mode] + ". Activate to cycle System, Light, Dark.");\n }\n\n var modeToggle = document.getElementById("mode-toggle");\n if (modeToggle) {\n paintMode(modeToggle);\n modeToggle.addEventListener("click", function () {\n var next = MODES[(MODES.indexOf(readMode()) + 1) % MODES.length];\n saveMode(next);\n paintMode(modeToggle);\n });\n }\n\n document.addEventListener("change", function (event) {\n var el = event.target;\n if (!el || !el.matches || !el.matches("[data-submit-on-change]")) return;\n if (el.form) el.form.requestSubmit();\n });\n\n var nodes = document.querySelectorAll("[data-island]");\n for (var i = 0; i < nodes.length; i += 1) {\n var kind = nodes[i].getAttribute("data-island");\n var mount = islands[kind];\n if (mount) mount(nodes[i], propsOf(nodes[i]));\n }\n})();\n';
4500
+ var APP_CSS = '/* IBM Carbon Design System v11, g10 theme (light) and g100 theme (dark) */\n:root{--bg:#f4f4f4;--layer-01:#ffffff;--layer-02:#f4f4f4;--layer-hover:#e8e8e8;--layer-active:#d1d1d1;--border-subtle:#e0e0e0;--border-strong:#8d8d8d;--text-primary:#161616;--text-secondary:#525252;--link:#0f62fe;--link-hover:#0043ce;--focus:#0f62fe;--support-error:#da1e28;--support-success:#24a148;--error-bg:#fff1f1;--tag-gray:#e0e0e0;--tag-blue:#d0e2ff;--tag-blue-ink:#0043ce;--tag-teal:#9ef0f0;--tag-teal-ink:#004144;--btn-primary:#0f62fe;--btn-primary-hover:#0043ce;--btn-primary-ink:#ffffff;--btn-secondary:#393939;--btn-secondary-hover:#4c4c4c;--tag-live-bg:#a7f0ba;--tag-live-ink:#044317;--code-bg:#161616;--code-ink:#f4f4f4;--shell:#161616;--shell-fg:#f4f4f4;--shell-muted:#c6c6c6;--shell-field:#393939;--nav-selected:#e8e8e8;--segment-on-bg:#161616;--segment-on-fg:#ffffff;--font-sans:"IBM Plex Sans",system-ui,-apple-system,sans-serif;--font-mono:"IBM Plex Mono",ui-monospace,monospace}\n@media (prefers-color-scheme: dark){:root:not([data-mode="light"]){--bg:#161616;--layer-01:#262626;--layer-02:#393939;--layer-hover:#333333;--layer-active:#525252;--border-subtle:#393939;--border-strong:#6f6f6f;--text-primary:#f4f4f4;--text-secondary:#c6c6c6;--link:#78a9ff;--link-hover:#a6c8ff;--focus:#ffffff;--support-error:#fa4d56;--support-success:#42be65;--error-bg:#2d0709;--tag-gray:#393939;--tag-blue:#002d9c;--tag-blue-ink:#d0e2ff;--tag-teal:#004144;--tag-teal-ink:#9ef0f0;--btn-primary:#0f62fe;--btn-primary-hover:#0353e9;--btn-primary-ink:#ffffff;--btn-secondary:#6f6f6f;--btn-secondary-hover:#525252;--tag-live-bg:#044317;--tag-live-ink:#a7f0ba;--code-bg:#262626;--code-ink:#f4f4f4;--shell:#161616;--shell-fg:#f4f4f4;--shell-muted:#c6c6c6;--shell-field:#262626;--nav-selected:#393939;--segment-on-bg:#f4f4f4;--segment-on-fg:#161616}}\n:root[data-mode="dark"]{--bg:#161616;--layer-01:#262626;--layer-02:#393939;--layer-hover:#333333;--layer-active:#525252;--border-subtle:#393939;--border-strong:#6f6f6f;--text-primary:#f4f4f4;--text-secondary:#c6c6c6;--link:#78a9ff;--link-hover:#a6c8ff;--focus:#ffffff;--support-error:#fa4d56;--support-success:#42be65;--error-bg:#2d0709;--tag-gray:#393939;--tag-blue:#002d9c;--tag-blue-ink:#d0e2ff;--tag-teal:#004144;--tag-teal-ink:#9ef0f0;--btn-primary:#0f62fe;--btn-primary-hover:#0353e9;--btn-primary-ink:#ffffff;--btn-secondary:#6f6f6f;--btn-secondary-hover:#525252;--tag-live-bg:#044317;--tag-live-ink:#a7f0ba;--code-bg:#262626;--code-ink:#f4f4f4;--shell:#161616;--shell-fg:#f4f4f4;--shell-muted:#c6c6c6;--shell-field:#262626;--nav-selected:#393939;--segment-on-bg:#f4f4f4;--segment-on-fg:#161616}\n*{box-sizing:border-box}\nhtml,body{margin:0;padding:0}\nbody{background:var(--bg);color:var(--text-primary);font:400 14px/1.43 var(--font-sans);-webkit-font-smoothing:antialiased}\na{color:var(--link);text-decoration:none}\na:hover{color:var(--link-hover);text-decoration:underline}\na:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,summary:focus-visible,.segment:focus-within{outline:2px solid var(--focus);outline-offset:-2px}\nh1,h2,h3,p{margin:0}\nh1{font-size:28px;font-weight:400;line-height:1.29;letter-spacing:0}\nh2{font-size:16px;font-weight:600;line-height:1.375;letter-spacing:0}\n.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}\n.muted{color:var(--text-secondary)}\n.mono{font-family:var(--font-mono);font-size:12px;line-height:1.33;overflow-wrap:anywhere}\n.empty{padding:24px 16px;margin:0;color:var(--text-secondary)}\n.app{min-height:100vh;display:flex;flex-direction:column;background:var(--bg);color:var(--text-primary)}\n.topbar{height:48px;flex:0 0 auto;display:flex;align-items:center;gap:16px;padding:0 16px;background:var(--shell);color:var(--shell-fg)}\n.brand{display:inline-flex;align-items:baseline;gap:8px;color:var(--shell-fg);font-size:14px;font-weight:600;white-space:nowrap}\n.brand:hover{color:var(--shell-fg);text-decoration:none}\n.brand-sub{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--shell-muted)}\n.search{flex:1 1 240px;min-width:0;max-width:520px;height:32px;display:flex;align-items:center;gap:8px;padding:0 12px;background:var(--shell-field);color:var(--shell-fg);margin-left:8px}\n.search-icon{flex:0 0 auto;color:var(--shell-muted);font-size:16px;line-height:1}\n.search input{flex:1;min-width:0;height:32px;padding:0;border:0;background:transparent;color:var(--shell-fg);font:400 14px/1.29 var(--font-sans)}\n.search input::placeholder{color:var(--shell-muted)}\n.search input:focus{outline:2px solid var(--focus);outline-offset:-2px}\n.top-spacer{flex:1}\n.topbar-user{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--font-mono);font-size:12px;color:var(--shell-muted)}\nbutton.mode-toggle{height:48px;padding:0 12px;display:inline-flex;align-items:center;gap:8px;color:var(--shell-fg);background:transparent;border:0;border-radius:0;white-space:nowrap}\nbutton.mode-toggle:hover{background:var(--shell-field);color:var(--shell-fg)}\n.shell{flex:1;display:flex;align-items:stretch;min-height:0}\n.sidenav{flex:0 0 240px;width:240px;padding:16px 0;background:var(--layer-01);border-right:1px solid var(--border-subtle)}\n.nav-item{display:flex;align-items:center;gap:8px;min-height:40px;padding:8px 16px;border-left:3px solid transparent;color:var(--text-primary);font-size:14px;line-height:1.29}\n.nav-item:hover{background:var(--layer-hover);color:var(--text-primary);text-decoration:none}\n.nav-item.is-active{background:var(--nav-selected);border-left-color:var(--link);font-weight:600}\n.nav-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.nav-count{margin-left:auto;color:var(--text-secondary);font-weight:400;font-family:var(--font-mono);font-size:12px}\n.nav-heading{margin:24px 16px 8px;font-size:12px;line-height:1.33;color:var(--text-secondary);letter-spacing:.32px}\n.main{flex:999 1 640px;min-width:0;padding:32px}\n.page{width:100%;max-width:1376px;margin:0 auto;padding:24px 32px 48px}\n.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:24px}\n.detail-head{align-items:flex-start}\n.detail-head h1,.signin-card h1{font-size:32px;font-weight:300;line-height:1.25}\n.title-block{min-width:0;flex:1 1 480px;overflow-wrap:anywhere}\n.page-sub{margin:4px 0 0;color:var(--text-secondary);max-width:80ch}\n.project-description{margin-top:12px;color:var(--text-primary);font-size:16px;line-height:1.5}\n.meta-line{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin:8px 0 0;color:var(--text-secondary);font-size:14px;overflow-wrap:anywhere}\n.meta-line a{color:var(--text-primary)}\n.meta-line a.mono:hover{color:var(--link);text-decoration:none}\n.meta-line .pill{vertical-align:middle}\n.breadcrumbs{margin:0 0 12px;font-size:14px;line-height:1.29;color:var(--text-secondary);overflow-wrap:anywhere}\n.breadcrumbs a{color:var(--link)}\n.crumb-sep{margin:0 4px;color:var(--text-secondary)}\n.button-row{display:flex;align-items:center;gap:1px;flex-wrap:wrap}\n.btn{display:inline-flex;align-items:center;justify-content:center;height:48px;padding:0 16px;border:1px solid transparent;border-radius:0;background:var(--btn-secondary);color:#fff;font:400 14px/1.29 var(--font-sans);white-space:nowrap;cursor:pointer}\n.btn:hover{background:var(--btn-secondary-hover);color:#fff;text-decoration:none}\n.btn-primary{background:var(--btn-primary);color:var(--btn-primary-ink)}\n.btn-primary:hover{background:var(--btn-primary-hover);color:var(--btn-primary-ink)}\n.btn-tertiary{background:transparent;border-color:var(--border-strong);color:var(--text-primary)}\n.btn-tertiary:hover{background:var(--layer-hover);color:var(--text-primary)}\n.btn-danger{background:transparent;border-color:var(--support-error);color:var(--support-error)}\n.btn-danger:hover{background:var(--error-bg);color:var(--support-error)}\n.btn-sm{height:40px;padding:0 12px}\n.btn-link{display:inline-flex;align-items:center;height:32px;padding:0 8px;border:0;background:transparent;color:var(--link);font:400 14px/1.29 var(--font-sans);cursor:pointer}\n.btn-link:hover{background:var(--layer-hover);color:var(--link-hover);text-decoration:none}\nbutton{height:40px;padding:0 16px;border:1px solid transparent;border-radius:0;background:var(--btn-secondary);color:#fff;font:400 14px/1.29 var(--font-sans);cursor:pointer}\nbutton:hover{background:var(--btn-secondary-hover)}\nbutton.primary{background:var(--btn-primary);color:var(--btn-primary-ink)}\nbutton.primary:hover{background:var(--btn-primary-hover)}\nbutton.small{height:40px;padding:0 12px;border-color:transparent;background:transparent;color:var(--link)}\nbutton.small:hover{background:var(--layer-hover);color:var(--link-hover)}\nbutton.small.danger{color:var(--support-error)}\nbutton.danger{background:transparent;border-color:transparent;color:var(--support-error)}\nbutton.danger:hover{background:var(--error-bg);color:var(--support-error)}\n.icon-btn{width:40px;height:40px;padding:0;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:0;color:var(--text-secondary);font-size:20px;line-height:1}\n.icon-btn:hover{background:var(--layer-hover);color:var(--text-primary)}\n.tagbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:16px;min-height:32px}\n.tagbar-label{margin-right:4px;color:var(--text-secondary);font-size:14px}\n.tagbar-spacer{flex:1}\n.tag-chip{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border-radius:16px;border:0;background:var(--tag-gray);color:var(--text-primary);font:400 12px/1.33 var(--font-sans);text-decoration:none;white-space:nowrap;cursor:pointer}\n.tag-chip:hover{background:var(--layer-active);color:var(--text-primary);text-decoration:none}\n.tag-chip.is-on{background:var(--btn-primary);color:var(--btn-primary-ink)}\n.tag-count{font-family:var(--font-mono);opacity:.72}\n.tag-chip-static{cursor:default;padding-right:6px}\n.tag-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-height:32px}\n.tag-remove{width:20px;height:20px;padding:0;display:inline-flex;align-items:center;justify-content:center;border:0;background:transparent;color:inherit;font-size:16px;line-height:1}\n.tag-remove:hover{background:var(--layer-active);color:var(--text-primary)}\n.sharing-filter{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.inline-label{display:inline-flex;align-items:center;gap:8px;color:var(--text-secondary);font-size:14px;white-space:nowrap}\ninput,select,textarea{height:40px;padding:0 12px;border:0;border-bottom:1px solid var(--border-strong);border-radius:0;background:var(--layer-02);color:var(--text-primary);font:400 14px/1.29 var(--font-sans)}\nselect{padding-right:32px;cursor:pointer}\ntextarea{height:auto;min-height:80px;padding:8px 12px;resize:vertical}\ninput::placeholder,textarea::placeholder{color:var(--text-secondary)}\ninput:focus,select:focus,textarea:focus{outline:2px solid var(--focus);outline-offset:-2px}\ninput:disabled,select:disabled,textarea:disabled{color:var(--text-secondary);cursor:not-allowed}\n.field-row{display:flex;align-items:stretch;gap:1px;flex-wrap:wrap}\n.field-row input,.field-row select{flex:1 1 160px;min-width:0}\n.field-row .btn{height:40px}\n.field-label{display:block;margin-bottom:8px;color:var(--text-secondary);font-size:12px;line-height:1.33;letter-spacing:.32px}\n.field-hint{color:var(--text-secondary);font-size:12px;line-height:1.33}\n.form-stack{display:flex;flex-direction:column;gap:24px}\n.form-field{display:flex;flex-direction:column;gap:8px;max-width:640px}\n.form-field input,.form-field textarea{width:100%}\n.form-card{max-width:720px}\n.form-actions{display:flex;gap:1px;flex-wrap:wrap}\n.detail-block{display:flex;flex-direction:column;gap:8px;margin-bottom:24px}\n.detail-block:last-child{margin-bottom:0}\n.tag-form{margin-top:12px;max-width:420px}\n.table-box{width:100%;overflow-x:auto;background:var(--layer-01);border-radius:0}\n.data-table{width:100%;border-collapse:collapse;font-size:14px;line-height:1.29}\n.gallery-table{min-width:860px}\n.project-table{min-width:620px}\n.token-table{min-width:640px}\n.data-table th,.data-table td{height:48px;padding:0 16px;text-align:left;vertical-align:middle;white-space:nowrap;border-bottom:1px solid var(--border-subtle)}\n.data-table th{background:var(--border-subtle);color:var(--text-primary);font-weight:600}\n.data-table tbody tr:hover td{background:var(--layer-hover)}\n.data-table tr:last-child td{border-bottom:0}\n.cell-name{min-width:220px}\n.cell-name .row-title{display:block;color:var(--text-primary);font-weight:400;max-width:360px;overflow:hidden;text-overflow:ellipsis}\n.cell-name .row-title:hover{color:var(--link);text-decoration:none}\n.cell-name .slug-link{display:block;margin-top:2px;color:var(--text-secondary)}\n.cell-name .slug-link:hover{color:var(--link);text-decoration:none}\n.empty-row td{height:auto;padding:32px 16px;white-space:normal;color:var(--text-secondary);text-align:center}\n.pill{display:inline-flex;align-items:center;height:24px;padding:0 8px;border-radius:12px;background:var(--tag-gray);color:var(--text-primary);font-size:12px;font-weight:400;line-height:1;white-space:nowrap}\n.pill-private{background:var(--tag-gray);color:var(--text-primary)}\n.pill-people{background:var(--tag-blue);color:var(--tag-blue-ink)}\n.pill-public{background:var(--tag-teal);color:var(--tag-teal-ink)}\n.pill-live{background:var(--tag-live-bg);color:var(--tag-live-ink)}\na.pill:hover{text-decoration:none;color:var(--text-primary)}\n.panel{margin-bottom:24px;background:var(--layer-01);border-radius:0}\n.panel-head{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;min-height:48px;padding:8px 16px;border-bottom:1px solid var(--border-subtle)}\n.panel-head h2{font-size:16px;font-weight:600}\n.panel-head .muted{font-size:14px}\n.panel-body{padding:16px}\n.detail-layout{display:flex;align-items:flex-start;gap:24px;flex-wrap:wrap}\n.detail-main{flex:999 1 640px;min-width:0}\n.detail-side{flex:1 1 380px;min-width:0;display:flex;flex-direction:column;gap:20px;padding:16px;background:var(--layer-01)}\n.detail-side h2{font-size:16px;font-weight:600}\n.side-text{margin:4px 0 0;color:var(--text-secondary);line-height:1.43}\n.side-hint{margin:4px 0 0;color:var(--text-secondary);font-size:12px;line-height:1.33}\n.side-section{display:flex;flex-direction:column;gap:12px}\n.preview-picker{display:inline-flex;align-items:center;gap:8px}\n.preview-body{padding:16px}\n.preview-frame{display:block;width:100%;aspect-ratio:16/9;border:1px solid var(--border-subtle);background:#ffffff}\n.preview-empty{display:flex;align-items:center;justify-content:center;min-height:200px;padding:24px;border:1px solid var(--border-subtle);background:var(--layer-02);color:var(--text-secondary);text-align:center}\n.version-list{list-style:none;margin:0;padding:0}\n.version-row{display:flex;align-items:center;gap:16px;flex-wrap:wrap;min-height:56px;padding:8px 16px;border-bottom:1px solid var(--border-subtle)}\n.version-row:last-child{border-bottom:0}\n.version-row.is-served{background:var(--layer-02);border-left:3px solid var(--support-success);padding-left:13px}\n.version-n{flex:0 0 36px}\n.version-main{flex:1 1 220px;min-width:0}\n.version-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.version-meta{color:var(--text-secondary);font-size:12px;line-height:1.33}\n.version-actions{padding:12px 16px;border-top:1px solid var(--border-subtle)}\n.segmented{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;margin:0;background:var(--border-strong)}\n.segment{position:relative;display:flex;align-items:center;justify-content:center;height:40px;background:var(--layer-01);color:var(--text-primary);font-size:14px;cursor:pointer}\n.segment:hover{background:var(--layer-hover)}\n.segment.is-on{background:var(--segment-on-bg);color:var(--segment-on-fg)}\n.segment-input{position:absolute;width:1px;height:1px;padding:0;margin:0;overflow:hidden;clip:rect(0,0,0,0);border:0;opacity:0}\n.people-list{list-style:none;margin:0;padding:0}\n.person-row{display:flex;align-items:center;gap:8px;min-height:48px;padding:4px 0;border-bottom:1px solid var(--border-subtle)}\n.person-row:last-child{border-bottom:0}\n.person-email{flex:1;min-width:0;overflow-wrap:anywhere}\n.person-role{color:var(--text-secondary);font-size:12px;white-space:nowrap}\n.info-box{padding:12px 16px;background:var(--layer-02)}\n.info-title{font-weight:600}\n.info-text{margin-top:4px;color:var(--text-secondary);font-size:14px}\n.info-box a{display:inline-block;margin-top:8px}\n.danger-zone{padding-top:16px;border-top:1px solid var(--border-subtle)}\n.danger-zone h2{margin-bottom:8px;font-size:14px;font-weight:600;color:var(--support-error)}\n.theme-list{list-style:none;margin:0;padding:0}\n.theme-row{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:48px;padding:0 16px;border-bottom:1px solid var(--border-subtle)}\n.theme-row:last-child{border-bottom:0}\n.token-box{margin-top:16px;padding:16px;background:var(--code-bg);color:var(--code-ink)}\n.token-box code{display:block;margin-top:8px;font-family:var(--font-mono);font-size:12px;word-break:break-all}\n.token-box .muted{color:var(--code-ink);opacity:.78}\n.token-error{margin-top:12px;color:var(--support-error)}\n.signin{display:flex;align-items:flex-start;justify-content:center;padding-top:80px}\n.signin-card{width:100%;max-width:560px}\n.signin-card .btn{margin-top:8px}\n@media(max-width:900px){\n.shell{display:block}\n.sidenav{width:auto;display:flex;flex-wrap:nowrap;align-items:stretch;overflow-x:auto;overflow-y:hidden;padding:0;border-right:0;border-bottom:1px solid var(--border-subtle);scrollbar-width:thin}\n.nav-item{width:auto;flex:0 0 auto;min-height:48px;padding:0 16px;border-left:0;border-bottom:3px solid transparent}\n.nav-item.is-active{border-left-color:transparent;border-bottom-color:var(--link)}\n.nav-heading{display:none}\n.data-table{min-width:0}\n.data-table th,.data-table td{white-space:normal;overflow-wrap:anywhere}\n.data-table th{overflow-wrap:normal}\n.main{padding:24px 16px 48px}\n.page{padding:16px 16px 40px}\n.detail-side{flex-basis:100%}\n}\n@media(max-width:620px){\n.topbar{height:auto;min-height:48px;flex-wrap:wrap;gap:8px;padding:8px 16px}\n.search{order:3;flex:1 1 100%;max-width:none;height:40px;margin-left:0}\n.search input{height:40px}\n.brand-sub{display:none}\n.topbar-user{max-width:100%}\nbutton.mode-toggle{height:40px}\nh1,.detail-head h1,.signin-card h1{font-size:24px}\n.page-head,.detail-head{align-items:flex-start}\n.button-row{width:100%}\n.button-row .btn{flex:1 1 auto}\n.field-row input,.field-row select{flex:1 1 140px}\n.panel-head{align-items:flex-start}\n.version-row{gap:8px}\n.preview-frame{aspect-ratio:4/3}\n.data-table,.data-table tbody,.data-table tr,.data-table td{display:block;width:100%;min-width:0}\n.data-table thead{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.data-table tr{padding:12px 16px;border-bottom:1px solid var(--border-subtle);background:var(--layer-01)}\n.data-table tr:hover{background:var(--layer-hover)}\n.data-table tbody tr:hover td{background:transparent}\n.data-table tr:last-child{border-bottom:0}\n.data-table td,.data-table th{height:auto;padding:0;border:0;text-align:left;white-space:normal;overflow-wrap:anywhere;background:transparent}\n.data-table tr>td:first-child{padding-bottom:8px}\n.data-table td[data-label]{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;min-height:32px;padding:2px 0}\n.data-table td[data-label]::before{content:attr(data-label) ": ";flex:0 0 auto;min-width:96px;color:var(--text-secondary);font-family:var(--font-sans);font-weight:400;font-size:12px;line-height:1.33}\n.data-table .btn-link{height:40px;padding:0}\n.data-table .icon-btn{height:40px}\n.data-table .empty-row td{padding:24px 0;text-align:center;color:var(--text-secondary)}\n.cell-name .row-title{max-width:none;overflow:visible;white-space:normal}\n}\n';
4501
+ var APP_JS = '"use strict";\n(function () {\n function api(method, path, body) {\n var init = { method: method, credentials: "same-origin", headers: {} };\n if (method !== "GET") init.headers["Content-Type"] = "application/json";\n if (body !== undefined) init.body = JSON.stringify(body);\n return fetch(path, init).then(function (res) {\n if (res.status === 204) return null;\n return res.text().then(function (text) {\n var data = null;\n if (text) { try { data = JSON.parse(text); } catch (err) { data = null; } }\n if (!res.ok) {\n var msg = data && data.error ? data.error : "Request failed with status " + res.status;\n throw new Error(msg);\n }\n return data;\n });\n });\n }\n\n function fail(err) {\n window.alert(err && err.message ? err.message : String(err));\n }\n\n function reload() {\n window.location.reload();\n }\n\n function copyText(value) {\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(value).catch(fail);\n return;\n }\n var input = document.createElement("textarea");\n input.value = value;\n document.body.appendChild(input);\n input.select();\n try { document.execCommand("copy"); } catch (err) { fail(err); }\n document.body.removeChild(input);\n }\n\n function propsOf(el) {\n var raw = el.getAttribute("data-props");\n if (!raw) return {};\n try { return JSON.parse(raw) || {}; } catch (err) { return {}; }\n }\n\n function closest(event, selector) {\n var target = event.target;\n if (!target || !target.closest) return null;\n return target.closest(selector);\n }\n\n function flash(el, text) {\n var label = el.textContent;\n el.textContent = text;\n window.setTimeout(function () { el.textContent = label; }, 1200);\n }\n\n function field(form, name) {\n var el = form.querySelector("[name=" + name + "]");\n return el && typeof el.value === "string" ? el.value : "";\n }\n\n var islands = {\n copy: function (root, props) {\n root.addEventListener("click", function (event) {\n event.preventDefault();\n copyText(String(props.text || ""));\n flash(root, "Copied");\n });\n },\n delete: function (root, props) {\n root.addEventListener("click", function (event) {\n event.preventDefault();\n if (!window.confirm("Delete this artifact, all versions and all shares?")) return;\n api("DELETE", "/api/artifacts/" + encodeURIComponent(props.slug)).then(function () {\n window.location.assign("/");\n }, fail);\n });\n },\n versions: function (root, props) {\n root.addEventListener("click", function (event) {\n var el = closest(event, "[data-action]");\n if (!el || !root.contains(el)) return;\n var action = el.getAttribute("data-action");\n if (action !== "pin" && action !== "set-live" && action !== "newest") return;\n event.preventDefault();\n if (action === "newest") {\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), { pinned: null }).then(reload, fail);\n } else {\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n pinned: Number(el.getAttribute("data-n"))\n }).then(reload, fail);\n }\n });\n },\n share: function (root, props) {\n root.addEventListener("click", function (event) {\n var removed = closest(event, "[data-action=remove-share]");\n if (removed && root.contains(removed)) {\n event.preventDefault();\n api(\n "DELETE",\n "/api/artifacts/" + encodeURIComponent(props.slug) + "/shares/" + encodeURIComponent(removed.getAttribute("data-email"))\n ).then(reload, fail);\n return;\n }\n var tag = closest(event, "[data-action=remove-tag]");\n if (tag && root.contains(tag)) {\n event.preventDefault();\n var tags = Array.isArray(props.tags) ? props.tags.filter(function (value) {\n return value !== tag.getAttribute("data-tag");\n }) : [];\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), { tags: tags }).then(reload, fail);\n }\n });\n root.addEventListener("submit", function (event) {\n var form = event.target;\n var kind = form.getAttribute("data-form");\n if (!kind) return;\n event.preventDefault();\n if (kind === "mode") {\n var checked = form.querySelector("[name=mode]:checked") || form.querySelector("[name=mode]");\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n mode: checked ? checked.value : "private"\n }).then(reload, fail);\n } else if (kind === "title") {\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n title: field(form, "title").trim()\n }).then(reload, fail);\n } else if (kind === "share") {\n var email = field(form, "email").trim();\n if (email === "") return;\n api(\n "PUT",\n "/api/artifacts/" + encodeURIComponent(props.slug) + "/shares/" + encodeURIComponent(email),\n { role: field(form, "role") }\n ).then(reload, fail);\n } else if (kind === "tags") {\n var tagInput = field(form, "tag").trim().toLowerCase();\n if (tagInput === "") return;\n var next = Array.isArray(props.tags) ? props.tags.slice() : [];\n if (next.indexOf(tagInput) === -1) next.push(tagInput);\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), { tags: next }).then(reload, fail);\n } else if (kind === "project") {\n var project = field(form, "project");\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n project: project === "" ? null : project\n }).then(reload, fail);\n }\n });\n },\n project: function (root, props) {\n root.addEventListener("click", function (event) {\n var el = closest(event, "[data-action=remove-member]");\n if (!el || !root.contains(el)) return;\n event.preventDefault();\n api(\n "DELETE",\n "/api/projects/" + encodeURIComponent(props.slug) + "/members/" + encodeURIComponent(el.getAttribute("data-email"))\n ).then(reload, fail);\n });\n root.addEventListener("submit", function (event) {\n var form = event.target;\n var kind = form.getAttribute("data-form");\n if (!kind) return;\n event.preventDefault();\n if (kind === "project-new") {\n var slug = field(form, "slug").trim().toLowerCase();\n if (slug === "") return;\n var body = { slug: slug };\n var title = field(form, "title").trim();\n var description = field(form, "description").trim();\n if (title !== "") body.title = title;\n if (description !== "") body.description = description;\n api("POST", "/api/projects", body).then(function (data) {\n window.location.assign("/p/" + encodeURIComponent(data.slug));\n }, fail);\n } else if (kind === "description") {\n api("PATCH", "/api/projects/" + encodeURIComponent(props.slug), {\n description: field(form, "description").trim()\n }).then(reload, fail);\n } else if (kind === "add-artifact") {\n var artifact = field(form, "artifact");\n if (artifact === "") return;\n api("PATCH", "/api/artifacts/" + encodeURIComponent(artifact), { project: props.slug }).then(reload, fail);\n } else if (kind === "member") {\n var email = field(form, "email").trim();\n if (email === "") return;\n api(\n "PUT",\n "/api/projects/" + encodeURIComponent(props.slug) + "/members/" + encodeURIComponent(email),\n { role: field(form, "role") }\n ).then(reload, fail);\n }\n });\n },\n tokens: function (root) {\n root.addEventListener("click", function (event) {\n var copy = closest(event, "[data-action=copy]");\n if (copy && root.contains(copy)) {\n event.preventDefault();\n copyText(copy.getAttribute("data-copy") || "");\n flash(copy, "Copied");\n return;\n }\n var revoke = closest(event, "[data-action=revoke], [data-action=revoke-token]");\n if (!revoke || !root.contains(revoke)) return;\n event.preventDefault();\n if (!window.confirm("Revoke this token? The CLI using it will stop working.")) return;\n api("DELETE", "/api/tokens/" + encodeURIComponent(revoke.getAttribute("data-id"))).then(function () {\n var row = revoke.closest("tr");\n if (row) row.remove();\n }, fail);\n });\n root.addEventListener("submit", function (event) {\n var form = event.target;\n if (form.getAttribute("data-form") !== "token") return;\n event.preventDefault();\n var name = field(form, "name").trim();\n var scope = field(form, "scope");\n api("POST", "/api/tokens", { name: name, scope: scope }).then(function (data) {\n var box = document.getElementById("token-box");\n var code = document.getElementById("token-value");\n var error = document.getElementById("token-error");\n if (box && code) {\n code.textContent = data.token;\n box.hidden = false;\n }\n if (error) error.hidden = true;\n var body = document.getElementById("token-rows");\n if (body) {\n var tr = document.createElement("tr");\n var cells = [data.name, data.scope.charAt(0).toUpperCase() + data.scope.slice(1), String(data.created_at).slice(0, 10), "Never"];\n var labels = [null, "Scope", "Created", "Last used"];\n for (var i = 0; i < cells.length; i += 1) {\n var td = document.createElement("td");\n td.textContent = cells[i];\n if (labels[i]) td.setAttribute("data-label", labels[i]);\n tr.appendChild(td);\n }\n var last = document.createElement("td");\n last.setAttribute("data-label", "Actions");\n var button = document.createElement("button");\n button.type = "button";\n button.className = "btn-link";\n button.setAttribute("data-action", "revoke");\n button.setAttribute("data-id", data.id);\n button.textContent = "Revoke";\n last.appendChild(button);\n tr.appendChild(last);\n body.appendChild(tr);\n }\n form.reset();\n }, function (err) {\n var box = document.getElementById("token-error");\n if (box) {\n box.textContent = err && err.message ? err.message : String(err);\n box.hidden = false;\n return;\n }\n fail(err);\n });\n });\n }\n };\n\n var MODE_KEY = "rshow-mode";\n var MODES = ["system", "light", "dark"];\n var MODE_LABELS = { system: "System", light: "Light", dark: "Dark" };\n\n function normalizeMode(value) {\n return value === "light" || value === "dark" ? value : "system";\n }\n\n function readMode() {\n try { return normalizeMode(window.localStorage.getItem(MODE_KEY)); } catch (err) { return "system"; }\n }\n\n function applyMode(mode) {\n var root = document.documentElement;\n if (mode === "light" || mode === "dark") root.dataset.mode = mode;\n else delete root.dataset.mode;\n }\n\n function saveMode(mode) {\n try { window.localStorage.setItem(MODE_KEY, mode); } catch (err) { /* storage may be blocked */ }\n applyMode(mode);\n }\n\n function paintMode(toggle) {\n var mode = readMode();\n toggle.textContent = MODE_LABELS[mode];\n toggle.setAttribute("aria-label", "Color mode: " + MODE_LABELS[mode] + ". Activate to cycle System, Light, Dark.");\n }\n\n var modeToggle = document.getElementById("mode-toggle");\n if (modeToggle) {\n paintMode(modeToggle);\n modeToggle.addEventListener("click", function () {\n var next = MODES[(MODES.indexOf(readMode()) + 1) % MODES.length];\n saveMode(next);\n paintMode(modeToggle);\n });\n }\n\n document.addEventListener("change", function (event) {\n var el = event.target;\n if (!el || !el.matches || !el.matches("[data-submit-on-change]")) return;\n if (el.form) el.form.requestSubmit();\n });\n\n var navBar = document.querySelector(".sidenav");\n var activeNav = navBar ? navBar.querySelector(".nav-item.is-active") : null;\n if (navBar && activeNav && navBar.scrollWidth > navBar.clientWidth) {\n var delta = activeNav.getBoundingClientRect().left - navBar.getBoundingClientRect().left;\n if (delta < 0 || delta + activeNav.offsetWidth > navBar.clientWidth) {\n navBar.scrollLeft = Math.max(0, navBar.scrollLeft + delta - 16);\n }\n }\n\n var nodes = document.querySelectorAll("[data-island]");\n for (var i = 0; i < nodes.length; i += 1) {\n var kind = nodes[i].getAttribute("data-island");\n var mount = islands[kind];\n if (mount) mount(nodes[i], propsOf(nodes[i]));\n }\n})();\n';
4502
4502
  var MODE_JS = '"use strict";\n(function () {\n var KEY = "rshow-mode";\n var root = document.documentElement;\n var mode = null;\n try { mode = window.localStorage.getItem(KEY); } catch (err) { mode = null; }\n if (mode === "light" || mode === "dark") root.dataset.mode = mode;\n else delete root.dataset.mode;\n})();\n';
4503
4503
 
4504
4504
  // src/static.ts
@@ -5676,11 +5676,11 @@ function ArtifactRow(props) {
5676
5676
  }
5677
5677
  )
5678
5678
  ] }),
5679
- /* @__PURE__ */ jsxDEV("td", { class: "muted", children: artifact.project ? /* @__PURE__ */ jsxDEV("a", { href: `/p/${artifact.project}`, children: props.projectTitle ?? artifact.project }) : /* @__PURE__ */ jsxDEV("span", { class: "muted", children: "No project" }) }),
5680
- /* @__PURE__ */ jsxDEV("td", { class: "muted", children: artifact.tags.length === 0 ? "No tags" : artifact.tags.join(", ") }),
5681
- /* @__PURE__ */ jsxDEV("td", { children: /* @__PURE__ */ jsxDEV(Pill, { mode: artifact.mode }) }),
5682
- /* @__PURE__ */ jsxDEV("td", { class: "mono", children: artifact.served === null ? "No version" : `v${artifact.served}` }),
5683
- /* @__PURE__ */ jsxDEV("td", { class: "muted", children: /* @__PURE__ */ jsxDEV("time", { datetime: artifact.updated_at, children: shortDate(artifact.updated_at) }) })
5679
+ /* @__PURE__ */ jsxDEV("td", { class: "muted", "data-label": "Project", children: artifact.project ? /* @__PURE__ */ jsxDEV("a", { href: `/p/${artifact.project}`, children: props.projectTitle ?? artifact.project }) : /* @__PURE__ */ jsxDEV("span", { class: "muted", children: "No project" }) }),
5680
+ /* @__PURE__ */ jsxDEV("td", { class: "muted", "data-label": "Tags", children: artifact.tags.length === 0 ? "No tags" : artifact.tags.join(", ") }),
5681
+ /* @__PURE__ */ jsxDEV("td", { "data-label": "Sharing", children: /* @__PURE__ */ jsxDEV(Pill, { mode: artifact.mode }) }),
5682
+ /* @__PURE__ */ jsxDEV("td", { class: "mono", "data-label": "Live version", children: artifact.served === null ? "No version" : `v${artifact.served}` }),
5683
+ /* @__PURE__ */ jsxDEV("td", { class: "muted", "data-label": "Updated", children: /* @__PURE__ */ jsxDEV("time", { datetime: artifact.updated_at, children: shortDate(artifact.updated_at) }) })
5684
5684
  ] });
5685
5685
  }
5686
5686
  __name(ArtifactRow, "ArtifactRow");
@@ -5916,10 +5916,10 @@ function projectPage(env, email, project, members, artifacts, ownArtifacts) {
5916
5916
  }
5917
5917
  )
5918
5918
  ] }),
5919
- /* @__PURE__ */ jsxDEV("td", { class: "muted", children: kindOf(artifact.tags) }),
5920
- /* @__PURE__ */ jsxDEV("td", { children: /* @__PURE__ */ jsxDEV(Pill, { mode: artifact.mode }) }),
5921
- /* @__PURE__ */ jsxDEV("td", { class: "mono", children: artifact.served === null ? "No version" : `v${artifact.served}` }),
5922
- /* @__PURE__ */ jsxDEV("td", { class: "muted", children: /* @__PURE__ */ jsxDEV("time", { datetime: artifact.updated_at, children: shortDate(artifact.updated_at) }) })
5919
+ /* @__PURE__ */ jsxDEV("td", { class: "muted", "data-label": "Kind", children: kindOf(artifact.tags) }),
5920
+ /* @__PURE__ */ jsxDEV("td", { "data-label": "Sharing", children: /* @__PURE__ */ jsxDEV(Pill, { mode: artifact.mode }) }),
5921
+ /* @__PURE__ */ jsxDEV("td", { class: "mono", "data-label": "Live", children: artifact.served === null ? "No version" : `v${artifact.served}` }),
5922
+ /* @__PURE__ */ jsxDEV("td", { class: "muted", "data-label": "Updated", children: /* @__PURE__ */ jsxDEV("time", { datetime: artifact.updated_at, children: shortDate(artifact.updated_at) }) })
5923
5923
  ] })) })
5924
5924
  ] }) })
5925
5925
  ] }) }),
@@ -6135,10 +6135,10 @@ function tokensPage(env, email, tokens, projects) {
6135
6135
  ] }) }),
6136
6136
  /* @__PURE__ */ jsxDEV("tbody", { id: "token-rows", children: tokens.map((token) => /* @__PURE__ */ jsxDEV("tr", { "data-token-id": token.id, children: [
6137
6137
  /* @__PURE__ */ jsxDEV("td", { children: token.name }),
6138
- /* @__PURE__ */ jsxDEV("td", { class: "muted", children: titleCase(token.scope) }),
6139
- /* @__PURE__ */ jsxDEV("td", { class: "muted", children: /* @__PURE__ */ jsxDEV("time", { datetime: token.created_at, children: formatDate(token.created_at) }) }),
6140
- /* @__PURE__ */ jsxDEV("td", { class: "muted", children: token.last_used_at === null ? "Never" : /* @__PURE__ */ jsxDEV("time", { datetime: token.last_used_at, children: formatDate(token.last_used_at) }) }),
6141
- /* @__PURE__ */ jsxDEV("td", { children: /* @__PURE__ */ jsxDEV("button", { type: "button", class: "btn-link", "data-action": "revoke", "data-id": token.id, children: "Revoke" }) })
6138
+ /* @__PURE__ */ jsxDEV("td", { class: "muted", "data-label": "Scope", children: titleCase(token.scope) }),
6139
+ /* @__PURE__ */ jsxDEV("td", { class: "muted", "data-label": "Created", children: /* @__PURE__ */ jsxDEV("time", { datetime: token.created_at, children: formatDate(token.created_at) }) }),
6140
+ /* @__PURE__ */ jsxDEV("td", { class: "muted", "data-label": "Last used", children: token.last_used_at === null ? "Never" : /* @__PURE__ */ jsxDEV("time", { datetime: token.last_used_at, children: formatDate(token.last_used_at) }) }),
6141
+ /* @__PURE__ */ jsxDEV("td", { "data-label": "Actions", children: /* @__PURE__ */ jsxDEV("button", { type: "button", class: "btn-link", "data-action": "revoke", "data-id": token.id, children: "Revoke" }) })
6142
6142
  ] })) })
6143
6143
  ] }) })
6144
6144
  ] })