Custom Code Experiment •
Colour Palette Generator
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Colour Palette Generator</title>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
font-family: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
line-height: 1.6;
}
body {
background-color: #000;
color: #fff;
min-height: 100vh;
}
.app-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1rem;
display: flex;
flex-direction: column;
align-items: center;
}
.controls {
display: flex;
flex-direction: column;
gap: 2rem;
margin-bottom: 3rem;
width: 100%;
align-items: center;
}
.controls-row {
display: flex;
flex-wrap: wrap;
gap: 2rem;
align-items: flex-start;
width: 100%;
justify-content: center;
}
.colour-picker-section {
display: flex;
align-items: flex-start;
gap: 2rem;
width: 100%;
margin-bottom: 1rem;
justify-content: center;
}
.colour-picker-wrapper {
position: relative;
flex-shrink: 0;
width: 100px;
height: 100px;
}
.colour-picker {
width: 100%;
height: 100%;
border-radius: 50%;
border: 3px solid rgba(255, 255, 255, 0.1);
cursor: pointer;
appearance: none;
-webkit-appearance: none;
padding: 0;
background: transparent;
transition: all 0.3s ease;
overflow: hidden;
}
.colour-picker::-webkit-color-swatch-wrapper {
padding: 0;
border: none;
background: transparent;
}
.colour-picker::-webkit-color-swatch {
border: none;
border-radius: 50%;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
}
.colour-picker::-moz-color-swatch-wrapper {
padding: 0;
border: none;
background: transparent;
}
.colour-picker::-moz-color-swatch {
border: none;
border-radius: 50%;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
}
.colour-picker:hover {
border-color: rgba(255, 255, 255, 0.3);
transform: scale(1.05);
}
.colour-picker-value {
position: absolute;
bottom: -2rem;
left: 50%;
transform: translateX(-50%);
font-family: 'SF Mono', Monaco, monospace;
font-size: 0.875rem;
color: #aaa;
white-space: nowrap;
background: rgba(0, 0, 0, 0.5);
padding: 0.25rem 0.5rem;
border-radius: 4px;
}
.palette-type-selector {
flex: 1;
min-width: 300px;
display: flex;
flex-direction: column;
align-items: center;
}
.palette-type-buttons {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.75rem;
width: 100%;
}
.palette-type-btn,
.btn {
padding: 0.875rem 1.5rem;
font-size: 0.9375rem;
font-weight: 500;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.05);
color: #fff;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
min-height: 48px;
}
.palette-type-btn {
min-height: 44px;
}
.palette-type-btn::before,
.btn::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.palette-type-btn:hover::before,
.btn:hover::before {
opacity: 1;
}
.palette-type-btn:hover,
.btn:hover {
border-color: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
}
.palette-type-btn:active,
.btn:active {
transform: translateY(0);
}
.palette-type-btn.active {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.button-group {
display: flex;
flex-direction: column;
gap: 1rem;
width: 100%;
max-width: 400px;
align-items: center;
}
.btn {
flex: 1;
min-width: 100%;
}
.btn svg {
width: 18px;
height: 18px;
}
.btn-primary {
background: rgba(255, 255, 255, 0.1);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.05);
}
.about-algorithm-btn {
background: rgba(255, 255, 255, 0.05);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.1);
margin: 1.5rem 0 0 0;
padding: 0.75rem 1.5rem;
font-size: 0.9375rem;
font-weight: 500;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
min-height: 48px;
width: auto;
align-self: flex-start;
}
.about-algorithm-btn::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.about-algorithm-btn:hover::before {
opacity: 1;
}
.about-algorithm-btn:hover {
border-color: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
}
.about-algorithm-btn:active {
transform: translateY(0);
}
.palette-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1.5rem;
margin-top: 1rem;
margin-bottom: 1rem;
opacity: 0;
animation: fadeIn 0.6s ease forwards;
width: 100%;
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
.colour-card {
border-radius: 12px;
overflow: hidden;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.05);
opacity: 0;
transform: translateY(20px);
animation: cardSlideUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
transition: all 0.3s ease;
}
.colour-card:hover {
transform: translateY(-4px);
border-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.3);
}
@keyframes cardSlideUp {
to {
opacity: 1;
transform: translateY(0);
}
}
.colour-swatch {
height: 150px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
position: relative;
overflow: hidden;
transition: transform 0.3s ease;
}
.colour-swatch:hover {
transform: scale(1.02);
}
.colour-swatch::after {
content: 'Click to copy';
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 0.5rem;
background: rgba(0, 0, 0, 0.8);
color: #fff;
font-size: 0.75rem;
text-align: center;
transform: translateY(100%);
transition: transform 0.3s ease;
}
.colour-swatch:hover::after {
transform: translateY(0);
}
.colour-value {
background-color: rgba(0, 0, 0, 0.8);
padding: 0.625rem 1.25rem;
border-radius: 20px;
font-weight: 600;
font-family: 'SF Mono', Monaco, monospace;
user-select: all;
color: white;
font-size: 0.875rem;
letter-spacing: 0.5px;
}
.colour-info {
padding: 1rem;
background: rgba(255, 255, 255, 0.02);
border-top: 1px solid rgba(255, 255, 255, 0.05);
}
.colour-name {
font-size: 1rem;
font-weight: 600;
margin-bottom: 0.25rem;
color: #fff;
}
.colour-role {
font-size: 0.8125rem;
color: #888;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(10px);
display: none;
align-items: center;
justify-content: center;
z-index: 3000;
padding: 1rem;
opacity: 0;
transition: opacity 0.3s ease;
}
.modal-overlay.active {
display: flex;
opacity: 1;
}
.modal-content {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 2.5rem;
max-width: 600px;
width: 100%;
max-height: 80vh;
overflow-y: auto;
transform: translateY(20px);
transition: transform 0.3s ease;
position: relative;
}
.modal-overlay.active .modal-content {
transform: translateY(0);
}
.modal-close {
position: absolute;
top: 1rem;
right: 1rem;
background: transparent;
border: none;
color: #aaa;
font-size: 1.5rem;
cursor: pointer;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s ease;
}
.modal-close:hover {
color: #fff;
background: rgba(255, 255, 255, 0.1);
}
.modal-title {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 1.5rem;
color: #fff;
text-align: center;
}
.modal-text {
color: #aaa;
line-height: 1.7;
margin-bottom: 1.5rem;
}
.modal-text p {
margin-bottom: 1rem;
}
.harmony-list {
list-style: none;
margin: 1.5rem 0;
}
.harmony-list li {
padding: 0.75rem 1rem;
margin-bottom: 0.75rem;
background: rgba(255, 255, 255, 0.03);
border-left: 3px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
}
.harmony-list strong {
color: #fff;
font-weight: 600;
display: block;
margin-bottom: 0.25rem;
}
.notification {
position: fixed;
bottom: 2rem;
right: 2rem;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
color: white;
padding: 1rem 1.5rem;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
transform: translateX(100%);
opacity: 0;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 1000;
display: flex;
align-items: center;
gap: 0.75rem;
}
.notification.show {
transform: translateX(0);
opacity: 1;
}
.notification svg {
width: 20px;
height: 20px;
opacity: 0.8;
}
@media (min-width: 769px) {
.controls-row {
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
}
.colour-picker-section {
align-items: flex-start;
width: auto;
margin-bottom: 0;
justify-content: flex-start;
}
.colour-picker-wrapper {
width: 100px;
height: 100px;
}
.palette-type-selector {
margin: 0;
max-width: none;
min-width: 300px;
align-items: flex-start;
}
.palette-type-buttons {
grid-template-columns: repeat(2, 1fr);
}
.button-group {
flex-direction: row;
max-width: none;
width: 100%;
align-items: flex-start;
}
.btn {
min-width: 160px;
}
.about-algorithm-btn {
width: auto;
min-width: 200px;
}
.palette-container {
justify-content: center;
}
}
@media (max-width: 768px) {
.app-container {
padding: 1.5rem 1rem;
align-items: center;
}
.controls {
align-items: center;
max-width: 400px;
}
.controls-row {
justify-content: center;
width: 100%;
}
.colour-picker-section {
flex-direction: column;
align-items: center;
gap: 2rem;
margin-bottom: 2rem;
width: 100%;
}
.colour-picker-wrapper {
width: 120px;
height: 120px;
margin-bottom: 1.5rem;
}
.colour-picker-value {
bottom: -3rem;
font-size: 1rem;
background: transparent;
}
.palette-type-selector {
width: 100%;
align-items: center;
}
.palette-type-buttons {
grid-template-columns: 1fr;
gap: 0.75rem;
width: 100%;
}
.palette-type-btn {
padding: 1rem;
font-size: 1rem;
width: 100%;
}
.button-group {
gap: 0.75rem;
margin-top: 1rem;
width: 100%;
}
.btn {
padding: 1rem;
font-size: 1rem;
border-radius: 10px;
width: 100%;
}
.palette-container {
grid-template-columns: 1fr;
gap: 1rem;
width: 100%;
max-width: 400px;
margin-bottom: 1rem;
}
.colour-swatch {
height: 160px;
}
.about-algorithm-btn {
width: 100%;
max-width: 400px;
margin-top: 1rem;
padding: 1rem;
font-size: 1rem;
align-self: center;
}
.modal-content {
padding: 1.5rem;
margin: 1rem;
}
.notification {
bottom: 1rem;
right: 1rem;
left: 1rem;
text-align: center;
justify-content: center;
max-width: 400px;
margin: 0 auto;
}
}
@media (max-width: 480px) {
.app-container {
padding: 1rem 0.75rem;
}
.colour-picker-wrapper {
width: 100px;
height: 100px;
margin-bottom: 2rem;
}
.colour-picker-value {
bottom: -3.5rem;
}
.colour-swatch {
height: 140px;
}
.colour-value {
font-size: 0.8125rem;
padding: 0.5rem 1rem;
}
}
.loading {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
z-index: 2000;
align-items: center;
justify-content: center;
}
.loading.active {
display: flex;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 3px solid rgba(255, 255, 255, 0.1);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div class="app-container">
<div class="controls">
<div class="controls-row">
<div class="colour-picker-section">
<div class="colour-picker-wrapper">
<input type="color" id="primaryColour" class="colour-picker" value="#ff6936">
<div class="colour-picker-value" id="colourValue">#FF6936</div>
</div>
<div class="palette-type-selector">
<div class="palette-type-buttons">
<button class="palette-type-btn active" data-type="complementary">
Complementary
</button>
<button class="palette-type-btn" data-type="analogous">
Analogous
</button>
<button class="palette-type-btn" data-type="triadic">
Triadic
</button>
<button class="palette-type-btn" data-type="monochromatic">
Monochromatic
</button>
</div>
</div>
</div>
</div>
<div class="button-group">
<button id="generateFromColourBtn" class="btn btn-primary">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 6V3L8 7L12 11V8C15.31 8 18 10.69 18 14C18 17.31 15.31 20 12 20C8.69 20 6 17.31 6 14H4C4 18.42 7.58 22 12 22C16.42 22 20 18.42 20 14C20 9.58 16.42 6 12 6Z" fill="currentColor"/>
</svg>
Generate Palette
</button>
<button id="randomBtn" class="btn btn-secondary">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.59 9.17L5.41 4L4 5.41L9.17 10.62L10.59 9.17ZM14.5 4L16.54 6.04L4 18.59L5.41 20L17.96 7.46L20 9.5V4H14.5ZM14.83 13.41L13.42 14.82L16.55 17.95L14.5 20H20V14.5L17.96 16.54L14.83 13.41Z" fill="currentColor"/>
</svg>
Random Colour
</button>
</div>
</div>
<div class="palette-container" id="paletteContainer"></div>
<button id="aboutAlgorithmBtn" class="about-algorithm-btn">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 7H13V9H11V7ZM11 11H13V17H11V11ZM12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.59 20 4 16.41 4 12C4 7.59 7.59 4 12 4C16.41 4 20 7.59 20 12C20 16.41 16.41 20 12 20Z" fill="currentColor"/>
</svg>
About the Algorithm
</button>
</div>
<!-- Modal Overlay -->
<div class="modal-overlay" id="modalOverlay">
<div class="modal-content">
<button class="modal-close" id="modalClose">×</button>
<h2 class="modal-title">About the Algorithm</h2>
<div class="modal-text">
<p>This colour palette generator uses colour theory algorithms to create harmonious colour schemes based on your selected base colour.</p>
<p>The algorithm converts your chosen colour from RGB to HSL (Hue, Saturation, Lightness) colour space, then calculates related colours based on different harmony rules:</p>
<ul class="harmony-list">
<li>
<strong>Complementary</strong>
Uses colours directly opposite on the colour wheel (180° hue difference). Creates high contrast and vibrant schemes.
</li>
<li>
<strong>Analogous</strong>
Uses colours adjacent to each other on the colour wheel (30° hue difference). Creates serene and comfortable designs.
</li>
<li>
<strong>Triadic</strong>
Uses three colours evenly spaced around the colour wheel (120° hue difference). Creates vibrant yet balanced schemes.
</li>
<li>
<strong>Monochromatic</strong>
Uses variations in saturation and lightness of a single hue. Creates cohesive and elegant designs.
</li>
</ul>
<p>Each generated palette includes five colours: Primary (your selected colour), Secondary, Accent, Background, and Text. The algorithm automatically adjusts saturation and lightness to ensure good contrast and visual appeal.</p>
<p>Click on any colour swatch to copy its hex code to your clipboard for use in your design projects.</p>
</div>
</div>
</div>
<div class="notification" id="notification">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 16.17L4.83 12L3.41 13.41L9 19L21 7L19.59 5.59L9 16.17Z" fill="currentColor"/>
</svg>
Colour copied to clipboard
</div>
<div class="loading" id="loading">
<div class="loading-spinner"></div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const paletteContainer = document.getElementById("paletteContainer");
const generateFromColourBtn = document.getElementById("generateFromColourBtn");
const randomBtn = document.getElementById("randomBtn");
const aboutAlgorithmBtn = document.getElementById("aboutAlgorithmBtn");
const primaryColourInput = document.getElementById("primaryColour");
const colourValueDisplay = document.getElementById("colourValue");
const notification = document.getElementById("notification");
const loading = document.getElementById("loading");
const modalOverlay = document.getElementById("modalOverlay");
const modalClose = document.getElementById("modalClose");
const paletteTypeButtons = document.querySelectorAll('.palette-type-btn');
const colourRoles = ["Primary", "Secondary", "Accent", "Background", "Text"];
let currentHarmonyType = "complementary";
generateRandomPalette();
paletteTypeButtons.forEach(btn => {
btn.addEventListener('click', function() {
paletteTypeButtons.forEach(b => b.classList.remove('active'));
this.classList.add('active');
currentHarmonyType = this.dataset.type;
generateFromColour();
});
});
primaryColourInput.addEventListener("input", function() {
colourValueDisplay.textContent = this.value.toUpperCase();
});
generateFromColourBtn.addEventListener("click", generateFromColour);
randomBtn.addEventListener("click", generateRandomPalette);
aboutAlgorithmBtn.addEventListener("click", () => {
modalOverlay.classList.add("active");
document.body.style.overflow = "hidden";
});
modalClose.addEventListener("click", () => {
modalOverlay.classList.remove("active");
document.body.style.overflow = "";
});
modalOverlay.addEventListener("click", (e) => {
if (e.target === modalOverlay) {
modalOverlay.classList.remove("active");
document.body.style.overflow = "";
}
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && modalOverlay.classList.contains("active")) {
modalOverlay.classList.remove("active");
document.body.style.overflow = "";
}
});
function generateRandomPalette() {
const baseColour = [
Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256)
];
const hexColour = rgbToHex(baseColour[0], baseColour[1], baseColour[2]);
primaryColourInput.value = hexColour;
colourValueDisplay.textContent = hexColour.toUpperCase();
showLoading();
setTimeout(() => {
const palette = generateHarmoniousPalette(baseColour, currentHarmonyType);
displayPalette(palette);
hideLoading();
}, 300);
}
function generateFromColour() {
const hexColour = primaryColourInput.value;
const baseColour = hexToRgb(hexColour);
showLoading();
setTimeout(() => {
const palette = generateHarmoniousPalette(baseColour, currentHarmonyType);
displayPalette(palette);
hideLoading();
}, 300);
}
function generateHarmoniousPalette(baseColour, harmonyType) {
const [r, g, b] = baseColour;
const palette = [baseColour];
const hsl = rgbToHsl(r, g, b);
let [h, s, l] = hsl;
s = Math.max(s, 0.4);
l = Math.max(0.2, Math.min(l, 0.9));
switch(harmonyType) {
case 'complementary':
palette.push(hslToRgb((h + 180) % 360, s * 0.9, l * 0.7));
palette.push(hslToRgb((h + 180) % 360, s * 0.7, l * 0.5));
palette.push(hslToRgb(h, s * 0.5, l * 0.9));
palette.push(hslToRgb(h, s * 0.3, l * 0.2));
break;
case 'analogous':
palette.push(hslToRgb((h + 30) % 360, s * 0.8, l * 0.6));
palette.push(hslToRgb((h + 330) % 360, s * 0.8, l * 0.8));
palette.push(hslToRgb((h + 15) % 360, s * 0.6, l * 0.9));
palette.push(hslToRgb((h + 345) % 360, s * 0.4, l * 0.2));
break;
case 'triadic':
palette.push(hslToRgb((h + 120) % 360, s * 0.9, l * 0.6));
palette.push(hslToRgb((h + 240) % 360, s * 0.9, l * 0.4));
palette.push(hslToRgb((h + 60) % 360, s * 0.7, l * 0.8));
palette.push(hslToRgb((h + 180) % 360, s * 0.5, l * 0.2));
break;
case 'monochromatic':
palette.push(hslToRgb(h, s * 0.9, l * 0.8));
palette.push(hslToRgb(h, s * 0.8, l * 0.6));
palette.push(hslToRgb(h, s * 0.6, l * 0.4));
palette.push(hslToRgb(h, s * 0.4, l * 0.2));
break;
}
return palette;
}
function hexToRgb(hex) {
hex = hex.replace('#', '');
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return [r, g, b];
}
function rgbToHsl(r, g, b) {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h * 360, s, l];
}
function hslToRgb(h, s, l) {
h /= 360;
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toLowerCase();
}
function displayPalette(colours) {
paletteContainer.style.opacity = '0';
paletteContainer.innerHTML = "";
setTimeout(() => {
colours.forEach((colour, index) => {
const hexColour = rgbToHex(colour[0], colour[1], colour[2]);
const colourCard = document.createElement("div");
colourCard.className = "colour-card";
colourCard.style.animationDelay = `${index * 0.1}s`;
colourCard.innerHTML = `
<div class="colour-swatch" style="background-color: ${hexColour};" data-hex="${hexColour}">
<span class="colour-value">${hexColour}</span>
</div>
<div class="colour-info">
<div class="colour-name">${colourRoles[index]}</div>
<div class="colour-role">RGB: ${colour[0]}, ${colour[1]}, ${colour[2]}</div>
</div>
`;
const colourSwatch = colourCard.querySelector(".colour-swatch");
colourSwatch.addEventListener("click", () => {
copyToClipboard(hexColour);
showNotification();
});
paletteContainer.appendChild(colourCard);
});
paletteContainer.style.opacity = '1';
}, 50);
}
function copyToClipboard(text) {
if (navigator.clipboard) {
navigator.clipboard.writeText(text);
} else {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
}
}
function showNotification() {
notification.classList.add("show");
setTimeout(() => {
notification.classList.remove("show");
}, 2000);
}
function showLoading() {
loading.classList.add('active');
}
function hideLoading() {
setTimeout(() => {
loading.classList.remove('active');
}, 300);
}
document.querySelectorAll('button').forEach(button => {
button.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-2px)';
});
button.addEventListener('mouseleave', function() {
this.style.transform = 'translateY(0)';
});
});
});
</script>
</body>
</html>