lerobot/robot-learning-tutorial
508
1---2const rootId = `palettes-${Math.random().toString(36).slice(2)}`;3---4<div class="palettes" id={rootId} style="width:100%; margin: 10px 0;">5 <style is:global>6 .palettes { box-sizing: border-box; }7 .palettes .palettes__grid { display: grid; grid-template-columns: 1fr; gap: 12px; max-width: 100%; }8 .palettes .palette-card { position: relative; display: grid; grid-template-columns: 1fr minmax(0, 220px); align-items: stretch; gap: 12px; border: 1px solid var(--border-color); border-radius: 10px; background: var(--surface-bg); padding: 12px; transition: box-shadow .18s ease, transform .18s ease, border-color .18s ease; min-height: 60px; }9 .palettes .palette-card__preview { width: 48px; height: 48px; border-radius: 999px; flex: 0 0 auto; background-size: cover; background-position: center; }10 .palettes .palette-card__copy { position: absolute; top: 50%; left: 100%; transform: translateY(-50%); z-index: 3; border-left: none; border-top-left-radius: 0; border-bottom-left-radius: 0; }11 .palettes .palette-card__copy svg { width: 18px; height: 18px; fill: currentColor; display: block; color: inherit; }12 .palettes .palette-card__swatches { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); grid-auto-rows: 1fr; gap: 2px; margin: 0; min-height: 60px; }13 .palettes .palette-card__swatches .sw { width: 100%; min-width: 0; height: auto; border-radius: 0; border: 1px solid var(--border-color); }14 .palettes .palette-card__swatches .sw:first-child { border-top-left-radius: 8px; border-bottom-left-radius: 8px; }15 .palettes .palette-card__swatches .sw:last-child { border-top-right-radius: 8px; border-bottom-right-radius: 8px; }16 .palettes .palette-card__content { display: flex; flex-direction: row; align-items: center; justify-content: flex-start; gap: 12px; min-width: 0; padding-right: 12px; }17 .palettes .palette-card__preview { width: 48px; height: 48px; border-radius: 999px; position: relative; flex: 0 0 auto; overflow: hidden; }18 .palettes .palette-card__preview .dot { position: absolute; width: 4px; height: 4px; background: #fff; border-radius: 999px; box-shadow: 0 0 0 1px var(--border-color); }19 .palettes .palette-card__preview .donut-hole { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 24px; height: 24px; border-radius: 999px; background: var(--surface-bg); box-shadow: 0 0 0 1px var(--border-color) inset; }20 21 .palettes .palette-card__content__info { display: flex; flex-direction: column; }22 .palettes .palette-card__title { text-align: left; font-weight: 800; font-size: 15px; }23 .palettes .palette-card__desc { text-align: left; color: var(--muted-color); line-height: 1.5; font-size: 12px; }24 25 .palettes .palettes__select { width: 100%; max-width: 100%; border: 1px solid var(--border-color); background: var(--surface-bg); color: var(--text-color); padding: 8px 10px; border-radius: 8px; }26 .palettes .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 1px, 1px); white-space: nowrap; border: 0; }27 .palettes .palettes__controls { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; margin: 8px 0 14px; }28 .palettes .palettes__field { display: flex; flex-direction: column; gap: 6px; min-width: 0; flex: 1 1 280px; max-width: 100%; }29 .palettes .palettes__label { font-size: 12px; color: var(--muted-color); font-weight: 800; }30 .palettes .palettes__label-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }31 .palettes .ghost-badge { font-size: 11px; padding: 1px 6px; border-radius: 999px; border: 1px solid var(--border-color); color: var(--muted-color); background: transparent; font-variant-numeric: tabular-nums; }32 .palettes .palettes__count { display: flex; align-items: center; gap: 8px; max-width: 100%; }33 .palettes .palettes__count input[type="range"] { width: 100%; }34 .palettes .palettes__count output { min-width: 28px; text-align: center; font-variant-numeric: tabular-nums; font-size: 12px; color: var(--muted-color); }35 .palettes input[type="range"] { -webkit-appearance: none; appearance: none; height: 24px; background: transparent; cursor: pointer; accent-color: var(--primary-color); }36 .palettes input[type="range"]:focus { outline: none; }37 .palettes input[type="range"]::-webkit-slider-runnable-track { height: 6px; background: var(--border-color); border-radius: 999px; }38 .palettes input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; margin-top: -6px; width: 18px; height: 18px; background: var(--primary-color); border: 2px solid var(--surface-bg); border-radius: 50%; }39 .palettes input[type="range"]::-moz-range-track { height: 6px; background: var(--border-color); border: none; border-radius: 999px; }40 .palettes input[type="range"]::-moz-range-progress { height: 6px; background: var(--primary-color); border-radius: 999px; }41 .palettes input[type="range"]::-moz-range-thumb { width: 18px; height: 18px; background: var(--primary-color); border: 2px solid var(--surface-bg); border-radius: 50%; }42 html.cb-grayscale, body.cb-grayscale { filter: grayscale(1) !important; }43 html.cb-protanopia, body.cb-protanopia { filter: url(#cb-protanopia) !important; }44 html.cb-deuteranopia, body.cb-deuteranopia { filter: url(#cb-deuteranopia) !important; }45 html.cb-tritanopia, body.cb-tritanopia { filter: url(#cb-tritanopia) !important; }46 html.cb-achromatopsia, body.cb-achromatopsia { filter: url(#cb-achromatopsia) !important; }47 @media (max-width: 1100px) { .palettes .palette-card { grid-template-columns: 1fr; align-items: stretch; gap: 10px; } .palettes .palette-card__swatches { grid-template-columns: repeat(6, minmax(0, 1fr)); } .palettes .palette-card__content { border-right: none; padding-right: 0; } .palettes .palette-card__copy { display: none; } }48 </style>49 <div class="palettes__controls">50 <div class="palettes__field">51 <label class="palettes__label" for="cb-select">Color vision simulation</label>52 <select id="cb-select" class="palettes__select">53 <option value="none">Normal color vision — typical for most people</option>54 <option value="achromatopsia">Achromatopsia — no color at all</option>55 <option value="protanopia">Protanopia — reduced/absent reds</option>56 <option value="deuteranopia">Deuteranopia — reduced/absent greens</option>57 <option value="tritanopia">Tritanopia — reduced/absent blues</option>58 </select>59 </div>60 <div class="palettes__field">61 <div class="palettes__label-row">62 <label class="palettes__label" for="color-count">Number of colors</label>63 <output id="color-count-out" for="color-count" class="ghost-badge">8</output>64 </div>65 <div class="palettes__count">66 <input id="color-count" type="range" min="6" max="10" step="1" value="8" aria-label="Number of colors" />67 </div>68 </div>69 </div>70 <div class="palettes__grid"></div>71 <div class="palettes__simu" role="group" aria-labelledby="cb-sim-title">72 <svg aria-hidden="true" focusable="false" width="0" height="0" style="position:absolute; left:-9999px; overflow:hidden;">73 <defs>74 <filter id="cb-protanopia"><feColorMatrix type="matrix" values="0.567 0.433 0 0 0 0.558 0.442 0 0 0 0 0.242 0.758 0 0 0 0 0 1 0"/></filter>75 <filter id="cb-deuteranopia"><feColorMatrix type="matrix" values="0.625 0.375 0 0 0 0.7 0.3 0 0 0 0 0.3 0.7 0 0 0 0 0 1 0"/></filter>76 <filter id="cb-tritanopia"><feColorMatrix type="matrix" values="0.95 0.05 0 0 0 0 0.433 0.567 0 0 0 0.475 0.525 0 0 0 0 0 1 0"/></filter>77 <filter id="cb-achromatopsia"><feColorMatrix type="matrix" values="0.299 0.587 0.114 0 0 0.299 0.587 0.114 0 0 0.299 0.587 0.114 0 0 0 0 0 1 0"/></filter>78 </defs>79 </svg>80 </div>81</div>82<script type="module" is:inline>83 import '/scripts/color-palettes.js';84 const ROOT_ID = "{rootId}";85 (() => {86 const cards = [87 { key: 'categorical', title: 'Categorical', desc: 'For <strong>non‑numeric categories</strong>; <strong>visually distinct</strong> colors.' },88 { key: 'sequential', title: 'Sequential', desc: 'For <strong>numeric scales</strong>; gradient from <strong>dark to light</strong>. Ideal for <strong>heatmaps</strong>.' },89 { key: 'diverging', title: 'Diverging', desc: 'For numeric scales with negative and positive; Opposing extremes with smooth contrast around a neutral midpoint.' }90 ];91 const getPaletteColors = (key, count) => {92 const total=Number(count)||6;93 if (window.ColorPalettes && typeof window.ColorPalettes.getColors==='function') {94 return window.ColorPalettes.getColors(key,total) || [];95 }96 return [];97 };98 const render = () => {99 const root = document.getElementById(ROOT_ID) || document.querySelector('.palettes');100 if (!root) return;101 const grid=root.querySelector('.palettes__grid'); if (!grid) return;102 const input=document.getElementById('color-count'); const total=input ? Number(input.value)||6 : 6;103 const html = cards.map(c => {104 const colors=getPaletteColors(c.key,total);105 const swatches=colors.map(col=>`<div class=\"sw\" style=\"background:${col}\"></div>`).join('');106 const baseHex = (window.ColorPalettes && typeof window.ColorPalettes.getPrimary==='function') ? window.ColorPalettes.getPrimary() : (colors[0] || '#FF0000');107 const hueDeg = (()=>{ try { const s=baseHex.replace('#',''); const v=s.length===3?s.split('').map(ch=>ch+ch).join(''):s; const r=parseInt(v.slice(0,2),16)/255, g=parseInt(v.slice(2,4),16)/255, b=parseInt(v.slice(4,6),16)/255; const M=Math.max(r,g,b), m=Math.min(r,g,b), d=M-m; if (d===0) return 0; let h=0; if (M===r) h=((g-b)/d)%6; else if (M===g) h=(b-r)/d+2; else h=(r-g)/d+4; h*=60; if (h<0) h+=360; return h; } catch { return 0; } })();108 const gradient = c.key==='categorical'109 ? (() => {110 const steps = 60; // smooth hue wheel (fixed orientation)111 const wheel = Array.from({ length: steps }, (_, i) => `hsl(${Math.round((i/steps)*360)}, 100%, 50%)`).join(', ');112 return `conic-gradient(${wheel})`;113 })()114 : (colors.length ? `linear-gradient(90deg, ${colors.join(', ')})` : `linear-gradient(90deg, var(--border-color), var(--border-color))`);115 const previewInner = (()=>{116 if (c.key !== 'categorical' || !colors.length) return '';117 const ring = 18; const cx = 24; const cy = 24; const offset = (hueDeg/360) * 2 * Math.PI;118 return colors.map((col,i)=>{119 const angle = offset + (i/colors.length) * 2 * Math.PI;120 const x = cx + ring * Math.cos(angle);121 const y = cy + ring * Math.sin(angle);122 return `<span class=\"dot\" style=\"left:${x-2}px; top:${y-2}px\"></span>`;123 }).join('');124 })();125 const donutHole = (c.key === 'categorical') ? '<span class=\"donut-hole\"></span>' : '';126 return `127 <div class="palette-card" data-colors="${colors.join(',')}">128 <div class="palette-card__content">129 <div class=\"palette-card__preview\" aria-hidden=\"true\" style=\"background:${gradient}\">${previewInner}${donutHole}</div>130 <div class="palette-card__content__info">131 <div class="palette-card__title">${c.title}</div>132 <div class="palette-card__desc">${c.desc}</div>133 </div>134 </div>135 <div class="palette-card__swatches" style="grid-template-columns: repeat(${colors.length}, minmax(0, 1fr));">${swatches}</div>136 <button class="palette-card__copy button--ghost" type="button" aria-label="Copy palette">137 <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M16 1H4c-1.1 0-2 .9-2 2v12h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>138 </button>139 </div>`;140 }).join('');141 grid.innerHTML=html;142 };143 const MODE_TO_CLASS = { protanopia:'cb-protanopia', deuteranopia:'cb-deuteranopia', tritanopia:'cb-tritanopia', achromatopsia:'cb-achromatopsia' };144 const CLEAR_CLASSES = Object.values(MODE_TO_CLASS);145 const clearCbClasses = () => { const rootEl=document.documentElement; CLEAR_CLASSES.forEach(cls=>rootEl.classList.remove(cls)); };146 const applyCbClass = (mode) => { clearCbClasses(); const cls=MODE_TO_CLASS[mode]; if (cls) document.documentElement.classList.add(cls); };147 const currentCbMode = () => { const rootEl=document.documentElement; for (const [mode, cls] of Object.entries(MODE_TO_CLASS)) { if (rootEl.classList.contains(cls)) return mode; } return 'none'; };148 const setupCbSim = () => { const select=document.getElementById('cb-select'); if (!select) return; try { select.value=currentCbMode(); } catch{} select.addEventListener('change', () => applyCbClass(select.value)); };149 const setupCountControl = () => { const input=document.getElementById('color-count'); const out=document.getElementById('color-count-out'); if (!input) return; const clamp=(n,min,max)=>Math.max(min,Math.min(max,n)); const read=()=>clamp(Number(input.value)||6,6,10); const syncOut=()=>{ if (out) out.textContent=String(read()); }; const onChange=()=>{ syncOut(); render(); }; syncOut(); input.addEventListener('input', onChange); document.addEventListener('palettes:updated', () => { syncOut(); render(); }); };150 let copyDelegationSetup=false; const setupCopyDelegation = () => { if (copyDelegationSetup) return; const grid=document.querySelector('.palettes .palettes__grid'); if (!grid) return; grid.addEventListener('click', async (e) => { const btn = e.target.closest ? e.target.closest('.palette-card__copy') : null; if (!btn) return; const card = btn.closest('.palette-card'); if (!card) return; const colors=(card.dataset.colors||'').split(',').filter(Boolean); const json=JSON.stringify(colors,null,2); try { await navigator.clipboard.writeText(json); const old=btn.innerHTML; btn.innerHTML='<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M9 16.2l-3.5-3.5-1.4 1.4L9 19 20.3 7.7l-1.4-1.4z"/></svg>'; setTimeout(()=> btn.innerHTML=old, 900); } catch { window.prompt('Copy palette', json); } }); copyDelegationSetup=true; };151 const bootstrap = () => {152 setupCbSim();153 setupCountControl();154 setupCopyDelegation();155 // Render immediately156 render();157 // Re-render on palette updates158 document.addEventListener('palettes:updated', render);159 // Force an immediate notify after listeners are attached (ensures initial render)160 try {161 if (window.ColorPalettes && typeof window.ColorPalettes.notify === 'function') window.ColorPalettes.notify();162 else if (window.ColorPalettes && typeof window.ColorPalettes.refresh === 'function') window.ColorPalettes.refresh();163 } catch {}164 };165 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', bootstrap, { once: true }); } else { bootstrap(); }166 })();167</script>168 169 170 171 