@khanglvm/relay 0.10.2 → 0.11.0

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/src/ui/blocks.css CHANGED
@@ -545,3 +545,28 @@ body.blk-full-open { overflow: hidden; }
545
545
  /* code blocks reuse the viewer toolbar (comment + full-screen); the pre keeps
546
546
  its own card, the wrapper only hosts the toolbar strip */
547
547
  .blk-codewrap { position: relative; }
548
+
549
+ /* ---------- palette block (swatch cards; hover reveals hex, click copies) ---------- */
550
+ .blk-palette .pal-title { font-size: 11px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin: 2px 0 10px; }
551
+ .blk-palette .pal-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px; }
552
+ .pal-card { border: 1px solid var(--border); border-radius: 12px; overflow: hidden; background: var(--card); }
553
+ .pal-card.pal-spotlight { margin-bottom: 16px; }
554
+ .pal-swatches { display: flex; height: 72px; }
555
+ .pal-spotlight .pal-swatches { height: 104px; }
556
+ .pal-sw { flex: 1; position: relative; cursor: pointer; border: 0; padding: 0; transition: flex .2s var(--ease); }
557
+ .pal-sw:hover { flex: 1.6; }
558
+ .pal-hex { position: absolute; bottom: 7px; left: 50%; transform: translateX(-50%); font: 600 10px/1 var(--mono, ui-monospace, "SF Mono", monospace); background: rgba(0,0,0,.42); color: #fff; padding: 2px 6px; border-radius: 4px; white-space: nowrap; opacity: 0; transition: opacity .15s; pointer-events: none; }
559
+ .pal-sw:hover .pal-hex, .pal-sw.copied .pal-hex { opacity: 1; }
560
+ .pal-sw.copied::after { content: "Copied"; position: absolute; top: 6px; left: 50%; transform: translateX(-50%); font: 600 9px/1 var(--sans); background: var(--ok); color: #fff; padding: 2px 7px; border-radius: 4px; }
561
+ .pal-info { padding: 10px 14px; display: flex; justify-content: space-between; align-items: center; gap: 10px; }
562
+ .pal-spotlight .pal-info { padding: 12px 16px; }
563
+ .pal-name { font-size: 14px; font-weight: 600; color: var(--fg); }
564
+ .pal-spotlight .pal-name { font-size: 15px; }
565
+ .pal-sub { font-size: 12px; color: var(--muted); margin-top: 2px; }
566
+ .pal-tag { flex: none; font-size: 11px; font-weight: 600; padding: 3px 11px; border-radius: 20px; background: var(--bg-sunken); color: var(--fg-2); }
567
+ .pal-tag.tone-warm { background: #FAECE7; color: #993C1D; }
568
+ .pal-tag.tone-cool { background: #E6F1FB; color: #185FA5; }
569
+ .pal-tag.tone-neutral { background: #F1EFE8; color: #5F5E5A; }
570
+ .pal-tag.tone-nature { background: #EAF3DE; color: #3B6D11; }
571
+ .pal-tag.tone-bold { background: #FBEAF0; color: #72243E; }
572
+ .pal-tag.tone-digital { background: #EEEDFE; color: #3C3489; }
package/src/ui/blocks.js CHANGED
@@ -1426,6 +1426,65 @@
1426
1426
  return container;
1427
1427
  }
1428
1428
 
1429
+ // ---------- palette ----------
1430
+ // Color palettes as swatch cards: hover a swatch to reveal its hex, click to
1431
+ // copy it. One palette may be {featured:true} → a larger spotlight row. Lets
1432
+ // an agent present a curated palette with one structured block instead of
1433
+ // hand-writing HTML. Swatches and the whole block are commentable.
1434
+ function copyColor(text, btn) {
1435
+ const done = () => {
1436
+ btn.classList.add('copied');
1437
+ setTimeout(() => btn.classList.remove('copied'), 900);
1438
+ };
1439
+ try {
1440
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1441
+ navigator.clipboard.writeText(text).then(done, () => {});
1442
+ return;
1443
+ }
1444
+ } catch { /* fall through */ }
1445
+ done(); // no clipboard (sandbox) — still flash feedback
1446
+ }
1447
+
1448
+ function paletteCard(p, big, ctx, blockId) {
1449
+ const card = el('div', { class: 'pal-card' + (big ? ' pal-spotlight' : '') });
1450
+ const row = el('div', { class: 'pal-swatches' });
1451
+ for (const c of p.colors) {
1452
+ const sw = el('button', { class: 'pal-sw', type: 'button', style: 'background:' + c, title: 'Copy ' + c });
1453
+ sw.append(el('span', { class: 'pal-hex' }, c));
1454
+ sw.addEventListener('click', () => copyColor(c, sw));
1455
+ if (ctx.annotate) {
1456
+ ctx.annotate.register(sw, {
1457
+ blockId,
1458
+ questionId: ctx.questionId,
1459
+ target: { kind: 'swatch', label: (p.name ? p.name + ' · ' : '') + c },
1460
+ });
1461
+ }
1462
+ row.append(sw);
1463
+ }
1464
+ card.append(row);
1465
+ const info = el('div', { class: 'pal-info' });
1466
+ const meta = el('div', { class: 'pal-meta' });
1467
+ if (p.name) meta.append(el('div', { class: 'pal-name' }, p.name));
1468
+ if (p.sub) meta.append(el('div', { class: 'pal-sub' }, p.sub));
1469
+ info.append(meta);
1470
+ if (p.tag) info.append(el('span', { class: 'pal-tag' + (p.tagTone ? ' tone-' + p.tagTone : '') }, p.tag));
1471
+ if (p.name || p.sub || p.tag) card.append(info);
1472
+ return card;
1473
+ }
1474
+
1475
+ function renderPalette(block, ctx, blockId) {
1476
+ const wrap = el('div', { class: 'blk-palette' });
1477
+ const palettes = Array.isArray(block.palettes) ? block.palettes : [];
1478
+ if (block.title) wrap.append(el('div', { class: 'pal-title' }, block.title));
1479
+ const grid = el('div', { class: 'pal-grid' });
1480
+ for (const p of palettes) {
1481
+ if (p.featured) wrap.append(paletteCard(p, true, ctx, blockId));
1482
+ else grid.append(paletteCard(p, false, ctx, blockId));
1483
+ }
1484
+ if (grid.childNodes.length) wrap.append(grid);
1485
+ return wrap;
1486
+ }
1487
+
1429
1488
  // ---------- viewer controls (zoom / fit / full-screen) ----------
1430
1489
  // Large diagrams get squeezed to the column width; these controls let the
1431
1490
  // user zoom (buttons or cmd/ctrl+wheel) and expand any visual block into a
@@ -1753,6 +1812,10 @@
1753
1812
  inner = renderImage(block, ctx, blockId);
1754
1813
  wrapper.append(inner);
1755
1814
  break;
1815
+ case 'palette':
1816
+ inner = renderPalette(block, ctx, blockId);
1817
+ wrapper.append(inner);
1818
+ break;
1756
1819
  default:
1757
1820
  wrapper.append(el('div', { class: 'blk-error' }, 'Unknown block type: ' + esc(String(block.type))));
1758
1821
  }
package/src/ui/style.css CHANGED
@@ -311,3 +311,14 @@ textarea { min-height: 90px; resize: vertical; }
311
311
  @media (prefers-reduced-motion: reduce) {
312
312
  * { transition: none !important; animation: none !important; }
313
313
  }
314
+
315
+ /* ---------- color picker question (native input + hex + presets) ---------- */
316
+ .colorpick { display: flex; flex-direction: column; gap: 10px; }
317
+ .colorrow { display: flex; align-items: center; gap: 10px; }
318
+ .colorswatch { width: 48px; height: 38px; padding: 0; border: 1px solid var(--border-strong); border-radius: 10px; background: var(--card); cursor: pointer; }
319
+ .colorswatch::-webkit-color-swatch-wrapper { padding: 3px; }
320
+ .colorswatch::-webkit-color-swatch { border: 0; border-radius: 7px; }
321
+ .colorhex { width: 150px; font-family: var(--mono, ui-monospace, "SF Mono", monospace); text-transform: lowercase; }
322
+ .colorpresets { display: flex; flex-wrap: wrap; gap: 8px; }
323
+ .colorpreset { width: 26px; height: 26px; border-radius: 7px; border: 1px solid rgba(0,0,0,.14); cursor: pointer; padding: 0; transition: transform .12s var(--ease); }
324
+ .colorpreset:hover { transform: scale(1.12); }