UNPKG

poline

Version:

color palette generator mico-lib

439 lines (379 loc) 12.8 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Poline Picker Example</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin=""> <link href="https://fonts.googleapis.com/css2?family=Aboreto&amp;family=Work+Sans:wght@300;400&amp;display=swap" rel="stylesheet"> <style> :root { --light: #fff; --dark: #202124; --bg: var(--light); --onBg: var(--dark); background: var(--bg); color: var(--onBg); font-family: 'Work Sans', sans-serif; font-weight: 300; font-size: 0.9rem; accent-color: var(--c0); } body { font-family: 'Work Sans', sans-serif; margin: 0; padding: 2rem; background: #f0f0f0; } .container { display: flex; flex-direction: column; align-items: center; gap: 20px; } label { display: flex; margin: 0; font-size: .8rem; justify-content: space-between; border: 1px solid #e0e0e0; padding: .5rem 1.5rem; background-color: var(--bg); } label:first-of-type { padding-top: 1.5rem; } label + label { border-top: none; margin-top: -1px; background-color: var(--bg); } label:last-of-type { padding-bottom: 1.5rem; } label + button { margin-top: 1.5rem; } label i { text-align: right; font-style: normal; } label .t { display: block; margin: 0; font-size: 1rem; flex: 0 1 auto; font-family: 'Aboreto', cursive; } select { display: block; padding: 0; border: none; font: inherit; font-family: inherit; font-size: 1rem; text-decoration: underline; background: transparent; text-align: right; color: var(--onBg); } button { font-family: 'Aboreto', cursive; background-color: var(--onBg); color: var(--bg); padding: 1em 1.75em; border: none; cursor: pointer; width: 100%; margin-top: 1.5rem; } .controls { margin-top: 2rem; margin-bottom: 2rem; width: 360px; } poline-picker { width: 300px; height: 300px; --poline-picker-line-color: #333; --poline-picker-bg-color: #fff; } #colors, #colors-oklch, #colors-lch { display: flex; margin-top: .2rem; width: 300px; height: 10px; gap: 0.1em; } .color-swatch { flex: 1 0 auto; } h1 { color: #333; font-family: 'Aboreto', cursive; font-size: 2.5rem; margin: 0; padding: 0; font-weight: normal; letter-spacing: -0.05em; margin-left: -0.06em; } h2 { font-family: 'Aboreto', cursive; font-size: 1rem; margin: 1rem 0 0 0; font-weight: normal; } .instructions { color: #666; max-width: 400px; line-height: 1.5; text-align: center; } .key { display: inline-block; background: #f0f0f0; border: 1px solid #ccc; border-radius: 3px; padding: 2px 6px; font-family: monospace; font-size: 0.85em; } .hide { display: none; } @media (prefers-color-scheme: dark) { :root { --bg: var(--dark); --onBg: var(--light); } body { background: #1a1a1a; } .instructions { color: #ccc; } h1, h2 { color: var(--light); } .key { background: #333; border-color: #555; color: var(--light); } } </style> </head> <body> <div class="container"> <h1>Poline Picker</h1> <p class="instructions"> Experience the magic of "<strong>poline</strong>" for yourself, dear color explorer. Drag existing anchor points to adjust colors. Press <span class="key">P</span> to add a new point at cursor position, and press <span class="key"></span> to remove the last selected anchor point. Try using the <span class="key"></span> and <span class="key"></span> keys to change the hue of all colors </p> <div class="controls"> <label> <span class="t">Steps</span> <i><input type="range" min="1" max="15" value="5" id="steps"></i> </label> <label> <span class="t">Invert Lightness</span> <i><input type="checkbox" id="invertedLightness"></i> </label> <label> <span class="t">Closed Loop</span> <i><input type="checkbox" id="closedLoop"></i> </label> <label class="hide"> <span class="t">Allow Add Points</span> <i><input type="checkbox" id="allowAddPoints"></i> </label> <label> <span><span class="t">Position fn X</span> (Hue / Light)</span> <i><select id="positionFunctionX"> </select></i> </label> <label> <span><span class="t">Position fn Y</span> (Hue / Light)</span> <i><select id="positionFunctionY"> </select></i> </label> <label> <span><span class="t">Position fn Z</span> (Saturation)</span> <i><select id="positionFunctionZ"> </select></i> </label> <button id="randomize">Randomize Anchors</button> </div> <poline-picker interactive></poline-picker> <article aria-label="Color Swatches"> <h2>HSL</h2> <div id="colors"></div> <h2>OKLch</h2> <div id="colors-oklch"></div> <h2>Lch</h2> <div id="colors-lch"></div> </article> </div> <script type="module"> import { Poline, positionFunctions } from "./picker.mjs"; const picker = document.querySelector("poline-picker"); const colorsContainer = document.getElementById("colors"); const colorsOklchContainer = document.getElementById("colors-oklch"); const colorsLchContainer = document.getElementById("colors-lch"); // Control elements const stepsInput = document.getElementById("steps"); const allowAddPointsCheckbox = document.getElementById("allowAddPoints"); const invertedLightnessCheckbox = document.getElementById("invertedLightness"); const closedLoopCheckbox = document.getElementById("closedLoop"); const positionFunctionXSelect = document.getElementById("positionFunctionX"); const positionFunctionYSelect = document.getElementById("positionFunctionY"); const positionFunctionZSelect = document.getElementById("positionFunctionZ"); const randomizeButton = document.getElementById("randomize"); // Initialize poline const poline = new Poline({ anchorColors: [ [0, 1, 0.5], [180, 1, 0.5], ], numPoints: 5, positionFunctionX: positionFunctions.smoothStepPosition, positionFunctionY: positionFunctions.smoothStepPosition, positionFunctionZ: positionFunctions.linearPosition, closedLoop: false, invertedLightness: false, }); // Populate position function selects const positionSelects = [positionFunctionXSelect, positionFunctionYSelect, positionFunctionZSelect]; const functionNames = Object.keys(positionFunctions); positionSelects.forEach(select => { functionNames.forEach(name => { const option = document.createElement('option'); option.value = name; option.textContent = name; select.appendChild(option); }); }); // Set initial values positionFunctionXSelect.value = 'smoothStepPosition'; positionFunctionYSelect.value = 'smoothStepPosition'; positionFunctionZSelect.value = 'linearPosition'; picker.setPoline(poline); let lastMousePosition = { x: 0, y: 0 }; let lastSelectedPoint = null; function createColorSwatches(container, colors) { container.innerHTML = ""; colors.forEach(color => { const swatch = document.createElement("div"); swatch.className = "color-swatch"; swatch.style.backgroundColor = color; swatch.title = color; container.appendChild(swatch); }); } function updatePolineAndColors() { picker.setPoline(poline); updateColors(); } function updateColors() { createColorSwatches(colorsContainer, poline.colorsCSS); createColorSwatches(colorsOklchContainer, poline.colorsCSSoklch); createColorSwatches(colorsLchContainer, poline.colorsCSSlch); } // Track mouse position and selected points picker.addEventListener("pointermove", (e) => { const rect = picker.getBoundingClientRect(); lastMousePosition = { x: e.clientX - rect.left, y: e.clientY - rect.top, }; }); picker.addEventListener("pointerdown", (e) => { const rect = picker.getBoundingClientRect(); const x = (e.clientX - rect.left) / rect.width; const y = (e.clientY - rect.top) / rect.height; const closestAnchor = poline.getClosestAnchorPoint({ xyz: [x, y, null], maxDistance: 0.1, }); if (closestAnchor) { lastSelectedPoint = closestAnchor; } }); // Listen for the custom event from the picker picker.addEventListener("poline-change", updateColors); // Control event listeners stepsInput.addEventListener("input", (e) => { poline.numPoints = parseInt(e.target.value); updatePolineAndColors(); }); allowAddPointsCheckbox.addEventListener("change", (e) => { picker.setAllowAddPoints(e.target.checked); }); invertedLightnessCheckbox.addEventListener("change", (e) => { poline.invertedLightness = e.target.checked; updatePolineAndColors(); }); closedLoopCheckbox.addEventListener("change", (e) => { poline.closedLoop = e.target.checked; updatePolineAndColors(); }); // Position function handlers with shared logic const createPositionFunctionHandler = (property) => (e) => { poline[property] = positionFunctions[e.target.value]; updatePolineAndColors(); }; positionFunctionXSelect.addEventListener("change", createPositionFunctionHandler('positionFunctionX')); positionFunctionYSelect.addEventListener("change", createPositionFunctionHandler('positionFunctionY')); positionFunctionZSelect.addEventListener("change", createPositionFunctionHandler('positionFunctionZ')); randomizeButton.addEventListener("click", () => { poline.anchorPoints.forEach(anchor => { anchor.hsl = [ (anchor.color[0] + (-90 + Math.random() * 180)) % 360, Math.random(), anchor.color[2] + (-.05 + Math.random() * .1), ]; }); poline.updateAnchorPairs(); updatePolineAndColors(); }); // Keyboard shortcuts document.addEventListener("keydown", (e) => { if (e.key === "p" || e.key === "P") { const newPoint = picker.addPointAtPosition(lastMousePosition.x, lastMousePosition.y); if (newPoint) { lastSelectedPoint = newPoint; } updateColors(); } if (e.key === "Backspace" || e.key === "Delete") { if (lastSelectedPoint && poline.anchorPoints.length > 2) { try { poline.removeAnchorPoint({ point: lastSelectedPoint }); lastSelectedPoint = null; updatePolineAndColors(); } catch (error) { console.log("Cannot remove anchor point:", error.message); } } } if (e.key === "ArrowLeft") { poline.shiftHue(-4); updatePolineAndColors(); } if (e.key === "ArrowRight") { poline.shiftHue(4); updatePolineAndColors(); } }); // Initial color update updateColors(); </script> </body> </html>