@@ -19,6 +19,18 @@ const pastProjects = [
1919 link: " https://github.com/duykhanh09103/ytdlp-web-api" ,
2020 },
2121];
22+
23+ const COLORS = [
24+ " #0a0a0a" ,
25+ " #c41e3a" ,
26+ " #e5e5e5" ,
27+ " #404040" ,
28+ " #3b82f6" ,
29+ " #22c55e" ,
30+ " #eab308" ,
31+ " #a855f7" ,
32+ " #f97316" ,
33+ ];
2234---
2335
2436<Layout >
@@ -120,7 +132,34 @@ const pastProjects = [
120132
121133 <section class =" info-section canvas-section" >
122134 <canvas id =" pixel-canvas" ></canvas >
123- <div class =" palette" id =" palette" ></div >
135+ <div class =" palette" id =" palette" >
136+ {
137+ COLORS .map ((color ) => (
138+ <div
139+ class = " swatch"
140+ style = { { background: color }}
141+ data-color = { color }
142+ title = { color === " #0a0a0a" ? " erase" : color }
143+ />
144+ ))
145+ }
146+ <div class =" swatch add-btn" title =" custom color" >
147+ <svg
148+ width =" 12"
149+ height =" 12"
150+ viewBox =" 0 0 24 24"
151+ fill =" none"
152+ stroke =" currentColor"
153+ stroke-width =" 3"
154+ stroke-linecap =" round"
155+ stroke-linejoin =" round"
156+ >
157+ <line x1 =" 12" y1 =" 5" x2 =" 12" y2 =" 19" />
158+ <line x1 =" 5" y1 =" 12" x2 =" 19" y2 =" 12" />
159+ </svg >
160+ <input type =" color" class =" picker-input" />
161+ </div >
162+ </div >
124163 <pre class =" curl-example" id =" curl-example" ></pre >
125164 </section >
126165 </main >
@@ -406,23 +445,52 @@ const pastProjects = [
406445 justify-content: center;
407446 }
408447
409- :global( .swatch) {
448+ .swatch {
410449 width: 20px;
411450 height: 20px;
412451 border: 1px solid #262626;
413452 cursor: pointer;
414453 transition: outline 0.1s;
415454 }
416455
417- :global( .swatch[data-color="#0a0a0a"]) {
456+ .swatch[data-color="#0a0a0a"] {
418457 border-color: #404040;
419458 }
420459
421- :global( .swatch.active) {
460+ .swatch.active {
422461 outline: 2px solid #e5e5e5;
423462 outline-offset: 1px;
424463 }
425464
465+ .swatch.add-btn {
466+ position: relative;
467+ background: var(--custom-color, #111);
468+ color: #737373;
469+ display: flex;
470+ align-items: center;
471+ justify-content: center;
472+ border: 1px dashed #404040;
473+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
474+ }
475+
476+ .picker-input {
477+ position: absolute;
478+ inset: -1px;
479+ width: calc(100% + 2px);
480+ height: calc(100% + 2px);
481+ opacity: 0;
482+ cursor: pointer;
483+ padding: 0;
484+ border: none;
485+ }
486+
487+ .swatch.add-btn:hover {
488+ color: #fff;
489+ border-color: #e5e5e5;
490+ background: var(--custom-color, rgba(255, 255, 255, 0.15));
491+ filter: brightness(1.2);
492+ }
493+
426494 .curl-example {
427495 font-family: monospace;
428496 font-size: 0.75rem;
@@ -538,17 +606,6 @@ const pastProjects = [
538606 const HEIGHT = 32;
539607 const POLL_MS = 3000;
540608
541- const COLORS = [
542- "#0a0a0a",
543- "#c41e3a",
544- "#e5e5e5",
545- "#404040",
546- "#3b82f6",
547- "#22c55e",
548- "#eab308",
549- "#a855f7",
550- "#f97316",
551- ];
552609
553610 const canvas = document.getElementById("pixel-canvas") as HTMLCanvasElement;
554611 if (!canvas) throw new Error("canvas not found");
@@ -582,13 +639,11 @@ const pastProjects = [
582639 curlEl.textContent = curlCommand(x, y);
583640 }
584641
585- const palette = document.getElementById("palette")!;
586- for (const color of COLORS) {
587- const swatch = document.createElement("div");
588- swatch.className = "swatch" + (color === selectedColor ? " active" : "");
589- swatch.style.background = color;
590- swatch.dataset.color = color;
591- swatch.title = color === "#0a0a0a" ? "erase" : color;
642+ const swatches = document.querySelectorAll(".swatch:not(.add-btn)");
643+ swatches.forEach((swatch) => {
644+ const color = (swatch as HTMLElement).dataset.color!;
645+ if (color === selectedColor) swatch.classList.add("active");
646+
592647 swatch.addEventListener("click", () => {
593648 selectedColor = color;
594649 document
@@ -598,8 +653,23 @@ const pastProjects = [
598653 render();
599654 updateCurl(selectedCell?.x ?? null, selectedCell?.y ?? null);
600655 });
601- palette.appendChild(swatch);
602- }
656+ });
657+
658+ const picker = document.querySelector(".picker-input") as HTMLInputElement;
659+ picker.oninput = () => {
660+ selectedColor = picker.value;
661+
662+ const addBtn = picker.parentElement as HTMLElement;
663+ addBtn.style.setProperty("--custom-color", selectedColor);
664+ addBtn.style.borderStyle = "solid";
665+ addBtn.style.borderColor = "rgba(255,255,255,0.1)";
666+
667+ document
668+ .querySelectorAll(".swatch")
669+ .forEach((s) => s.classList.remove("active"));
670+ updateCurl(selectedCell?.x ?? null, selectedCell?.y ?? null);
671+ render();
672+ };
603673
604674 function render() {
605675 for (let y = 0; y < HEIGHT; y++) {
0 commit comments