A professional brand identity generator featuring dynamic color palettes, typography combinations, and comprehensive design guidelines. Built with vanilla JavaScript and advanced color theory algorithms for creating cohesive brand systems with customizable controls.
- Dynamic Color Palettes: Generate 3-5 color harmonies using advanced color theory algorithms.
- 5 Harmony Types: Random, Monochromatic, Analogous, Complementary, and Triadic color schemes.
- Smart Typography: 6 professional font pairings (script + bold) with automatic brand color application.
- User Controls: Customize color count (3-5), font pairs (1-3), and harmony type via dropdowns.
- WCAG Contrast: Advanced luminance calculations ensure accessible color combinations.
- Color Locking: Lock favorite colors while generating new palettes.
- Dynamic Guidelines: Auto-generated color roles, type scale, and spacing system.
- Color Overlay: Hover to preview how colors look over brand palette.
- Copy to Clipboard: One-click copy for HEX, RGB, and HSL color codes.
- Google Fonts: 12 premium fonts (Playfair Display, Inter, Lora, Poppins, Merriweather, Open Sans, Cormorant Garamond, Montserrat, Crimson Text, Raleway, Spectral, Karla).
- Responsive Design: Auto-fit grid layout adapts seamlessly from 3-5 color boxes.
- Yellow/Black/White Theme: Professional brand colors with gradient backgrounds.
- No Dependencies: Pure vanilla JavaScript with memoization caching for performance.
- Frontend: HTML5, CSS3
- Logic: Vanilla JavaScript (ES6+) with Map-based memoization, Set for uniqueness, document fragments.
- Styling: CSS Custom Properties with auto-fit grid (repeat(auto-fit, minmax(200px, 1fr))), optimized to 480 lines.
- Icons: Remix Icons 4.0
- Fonts: Google Fonts API (12 font families)
- Color Theory: HSL/RGB/HEX conversions, WCAG luminance formula (0.299r + 0.587g + 0.114*b)
- Architecture: MVC-like separation with state management (palette[], fontPairs[], locked[], colorCount, fontPairCount)
index.html- DOM structure with 3 control dropdowns, palette container, font pairing showcase, and guidelines grid.style.css- Modern styling with auto-fit grid, CSS variables, animations, and mobile-first responsive design (480 lines).script.js- Core logic for color harmony generation, typography rendering, WCAG contrast, and dynamic guidelines (~420 lines).
The application manages state with the following structure:
// State Management
let palette = []; // Array of color objects { hsl, hex, rgb, locked }
let fontPairs = []; // Array of { script: "Playfair Display", bold: "Inter" }
let locked = []; // Boolean array for locked color states
let colorCount = 5; // User-selected count (3-5)
let fontPairCount = 3; // User-selected font pairs (1-3)
// Color Object Structure
{
hsl: [217, 91, 60], // HSL values [hue, saturation, lightness]
hex: "#1e90ff", // HEX color code
rgb: [30, 144, 255], // RGB values [red, green, blue]
locked: false // Lock state
}
// Font Pair Structure
{
script: "Playfair Display", // Decorative/script font
bold: "Inter" // Body/sans-serif font
}Generating Color Harmonies:
// Random Harmony (3-5 colors)
function genRand(count) {
return Array(count)
.fill()
.map(() => {
const hue = Math.floor(Math.random() * 360);
const sat = Math.floor(Math.random() * 40) + 60; // 60-100%
const lit = Math.floor(Math.random() * 40) + 30; // 30-70%
return hsl2obj([hue, sat, lit]);
});
}
// Monochromatic Harmony
function genMono(count) {
const base = Math.floor(Math.random() * 360);
const sat = Math.floor(Math.random() * 30) + 60; // 60-90%
return Array(count)
.fill()
.map((_, i) => {
const lit = 20 + (i * (70 / (count - 1))); // Evenly distributed lightness
return hsl2obj([base, sat, lit]);
});
}
// Analogous Harmony
function genAnalog(count) {
const base = Math.floor(Math.random() * 360);
return Array(count)
.fill()
.map((_, i) => {
const hue = (base + (i * (60 / (count - 1)))) % 360; // 60Β° spread
const sat = 65 + Math.random() * 20;
const lit = 40 + Math.random() * 30;
return hsl2obj([hue, sat, lit]);
});
}
// Complementary Harmony
function genComp(count) {
const base = Math.floor(Math.random() * 360);
const comp = (base + 180) % 360;
const colors = [];
const primary = Math.ceil(count / 2);
// Primary color variants
for (let i = 0; i < primary; i++) {
const sat = 65 + Math.random() * 25;
const lit = 35 + (i * 20);
colors.push(hsl2obj([base, sat, lit]));
}
// Complementary variants
for (let i = 0; i < count - primary; i++) {
const sat = 65 + Math.random() * 25;
const lit = 35 + (i * 20);
colors.push(hsl2obj([comp, sat, lit]));
}
return colors;
}
// Triadic Harmony
function genTriad(count) {
const base = Math.floor(Math.random() * 360);
const hues = [base, (base + 120) % 360, (base + 240) % 360];
const colors = [];
let hueIdx = 0;
for (let i = 0; i < count; i++) {
const hue = hues[hueIdx];
const sat = 65 + Math.random() * 25;
const lit = 40 + Math.random() * 30;
colors.push(hsl2obj([hue, sat, lit]));
hueIdx = (hueIdx + 1) % 3;
}
return colors;
}WCAG Contrast Calculation:
// Calculate relative luminance using WCAG formula
function getContrastColor(hsl) {
const rgb = hsl2rgb(hsl);
const [r, g, b] = rgb.map((val) => {
val /= 255;
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
});
// WCAG luminance formula
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
// Return black or white based on luminance threshold
return luminance > 0.5 ? "#000000" : "#FFFFFF";
}
// Smart color adjustment for readability
function getReadableColor(baseHsl, bgColor) {
const contrastColor = getContrastColor(baseHsl);
// If background is dark, lighten the color
if (bgColor === "#FFFFFF") {
return `hsl(${baseHsl[0]}, ${baseHsl[1]}%, ${Math.max(baseHsl[2] - 20, 20)}%)`;
}
// If background is light, ensure sufficient darkness
return `hsl(${baseHsl[0]}, ${baseHsl[1]}%, ${Math.min(baseHsl[2], 50)}%)`;
}Dynamic Font Pair Generation:
const FONTS = [
{ script: "Playfair Display", bold: "Inter" },
{ script: "Lora", bold: "Poppins" },
{ script: "Merriweather", bold: "Open Sans" },
{ script: "Cormorant Garamond", bold: "Montserrat" },
{ script: "Crimson Text", bold: "Raleway" },
{ script: "Spectral", bold: "Karla" },
];
// Generate random unique font pairs
function generate() {
const harmonyType = harmonySelect.value;
colorCount = parseInt(colorCountSelect.value, 10);
fontPairCount = parseInt(fontCountSelect.value, 10);
// Generate color palette
if (harmonyType === "random") palette = genRand(colorCount);
else if (harmonyType === "mono") palette = genMono(colorCount);
else if (harmonyType === "analogous") palette = genAnalog(colorCount);
else if (harmonyType === "complementary") palette = genComp(colorCount);
else if (harmonyType === "triadic") palette = genTriad(colorCount);
// Preserve locked colors
palette = palette.map((color, i) => (locked[i] ? palette[i] : color));
// Generate unique font pairs
const available = [...FONTS];
fontPairs = [];
for (let i = 0; i < fontPairCount && available.length > 0; i++) {
const idx = Math.floor(Math.random() * available.length);
fontPairs.push(available.splice(idx, 1)[0]);
}
render();
updateFont();
updateGuide();
}Dynamic Typography with Brand Colors:
function updateFont() {
const container = document.querySelector(".font-pairing");
if (!container) return;
container.innerHTML = "";
fontPairs.forEach((pair, index) => {
const card = document.createElement("div");
card.className = "font-pair-card";
// Use palette colors for styling
const primaryColor = palette[index % palette.length];
const accentColor = palette[(index + 1) % palette.length];
const bgColor = getContrastColor(primaryColor.hsl);
card.innerHTML = `
<div class="font-header" style="border-left: 4px solid ${primaryColor.hex}">
<h3 style="color: ${primaryColor.hex}">${pair.script} + ${pair.bold}</h3>
</div>
<div class="font-preview">
<h1 style="font-family: '${pair.script}', serif; color: ${getReadableColor(primaryColor.hsl, bgColor)}">
Your Brand Story
</h1>
<p style="font-family: '${pair.bold}', sans-serif; color: ${getReadableColor(accentColor.hsl, bgColor)}">
${NAMES[Math.floor(Math.random() * NAMES.length)]} β
Every great brand needs a powerful narrative that connects with its audience.
</p>
</div>
<div class="font-details">
<span class="font-tag" style="background: ${primaryColor.hex}20; color: ${primaryColor.hex}">
Display: ${pair.script}
</span>
<span class="font-tag" style="background: ${accentColor.hex}20; color: ${accentColor.hex}">
Body: ${pair.bold}
</span>
</div>
`;
container.appendChild(card);
});
}Dynamic Design Guidelines:
const TYPE_SCALE = [
{ name: "Display", size: "48px", use: "Hero headlines" },
{ name: "Heading 1", size: "36px", use: "Page titles" },
{ name: "Heading 2", size: "28px", use: "Section headers" },
{ name: "Heading 3", size: "20px", use: "Sub-sections" },
{ name: "Body", size: "16px", use: "Main content" },
{ name: "Small", size: "14px", use: "Captions" },
];
const SPACING = [
{ name: "XXS", value: "4px", use: "Tight spacing" },
{ name: "XS", value: "8px", use: "Small gaps" },
{ name: "SM", value: "16px", use: "Default spacing" },
{ name: "MD", value: "24px", use: "Medium gaps" },
{ name: "LG", value: "32px", use: "Section spacing" },
{ name: "XL", value: "48px", use: "Large sections" },
];
function updateGuide() {
const container = document.querySelector(".guidelines-grid");
if (!container) return;
container.innerHTML = "";
// Color Roles Section
const colorSection = document.createElement("div");
colorSection.className = "guideline-section";
colorSection.innerHTML = `
<h3 style="color: ${palette[0].hex}">
<i class="ri-palette-line"></i> Color Roles
</h3>
<div class="guideline-items">
${palette
.map(
(color, i) => `
<div class="guideline-item" style="border-left: 3px solid ${color.hex}">
<span class="guide-label" style="color: ${color.hex}">
${["Primary", "Secondary", "Accent", "Neutral", "Highlight"][i] || `Color ${i + 1}`}
</span>
<code style="background: ${color.hex}20; color: ${color.hex}">${color.hex}</code>
</div>
`
)
.join("")}
</div>
`;
// Typography Scale Section
const typeSection = document.createElement("div");
typeSection.className = "guideline-section";
typeSection.innerHTML = `
<h3 style="color: ${palette[1]?.hex || palette[0].hex}">
<i class="ri-text"></i> Type Scale
</h3>
<div class="guideline-items">
${TYPE_SCALE.map(
(type, i) => `
<div class="guideline-item" style="border-left: 3px solid ${palette[i % palette.length].hex}">
<span class="guide-label" style="color: ${palette[i % palette.length].hex}">
${type.name}
</span>
<code style="background: ${palette[i % palette.length].hex}20; color: ${palette[i % palette.length].hex}">
${type.size} β ${type.use}
</code>
</div>
`
).join("")}
</div>
`;
// Spacing System Section
const spacingSection = document.createElement("div");
spacingSection.className = "guideline-section";
spacingSection.innerHTML = `
<h3 style="color: ${palette[2]?.hex || palette[0].hex}">
<i class="ri-space"></i> Spacing System
</h3>
<div class="guideline-items">
${SPACING.map(
(space, i) => `
<div class="guideline-item" style="border-left: 3px solid ${palette[i % palette.length].hex}">
<span class="guide-label" style="color: ${palette[i % palette.length].hex}">
${space.name}
</span>
<code style="background: ${palette[i % palette.length].hex}20; color: ${palette[i % palette.length].hex}">
${space.value} β ${space.use}
</code>
</div>
`
).join("")}
</div>
`;
container.appendChild(colorSection);
container.appendChild(typeSection);
container.appendChild(spacingSection);
}Color Rendering with Lock & Overlay:
function render() {
const container = document.querySelector(".palette-container");
if (!container) return;
container.innerHTML = "";
palette.forEach((color, index) => {
const colorBox = document.createElement("div");
colorBox.className = "color-box";
colorBox.style.background = color.hex;
const contrastColor = getContrastColor(color.hsl);
colorBox.style.color = contrastColor;
colorBox.innerHTML = `
<button class="lock-btn ${locked[index] ? "locked" : ""}" data-index="${index}">
<i class="ri-${locked[index] ? "lock" : "lock-unlock"}-line"></i>
</button>
<div class="color-info">
<div class="color-values">
<div class="color-value">
<span class="label">HEX</span>
<code>${color.hex}</code>
</div>
<div class="color-value">
<span class="label">RGB</span>
<code>rgb(${color.rgb.join(", ")})</code>
</div>
<div class="color-value">
<span class="label">HSL</span>
<code>hsl(${color.hsl[0]}, ${color.hsl[1]}%, ${color.hsl[2]}%)</code>
</div>
</div>
<button class="copy-btn" data-color="${color.hex}">
<i class="ri-file-copy-line"></i> Copy HEX
</button>
</div>
<!-- Color Overlay Preview -->
<div class="color-overlay">
${palette
.filter((_, i) => i !== index)
.map(
(overlayColor) => `
<div class="overlay-sample"
style="background: ${overlayColor.hex}; color: ${getContrastColor(overlayColor.hsl)}">
Aa
</div>
`
)
.join("")}
</div>
`;
container.appendChild(colorBox);
});
// Add event listeners for lock buttons
document.querySelectorAll(".lock-btn").forEach((btn) => {
btn.addEventListener("click", (e) => {
const idx = parseInt(e.currentTarget.dataset.index, 10);
locked[idx] = !locked[idx];
render();
});
});
// Add event listeners for copy buttons
document.querySelectorAll(".copy-btn").forEach((btn) => {
btn.addEventListener("click", async (e) => {
const color = e.currentTarget.dataset.color;
await navigator.clipboard.writeText(color);
const icon = e.currentTarget.querySelector("i");
icon.className = "ri-check-line";
setTimeout(() => {
icon.className = "ri-file-copy-line";
}, 2000);
});
});
}User Controls Integration:
// Event listeners for dynamic controls
document.addEventListener("DOMContentLoaded", () => {
const colorCountSelect = document.getElementById("color-count");
const harmonySelect = document.getElementById("harmony-select");
const fontCountSelect = document.getElementById("font-count");
const generateBtn = document.getElementById("generate-btn");
// Regenerate on control changes
colorCountSelect.addEventListener("change", generate);
harmonySelect.addEventListener("change", generate);
fontCountSelect.addEventListener("change", generate);
generateBtn.addEventListener("click", generate);
// Initial generation
generate();
});- Clone the repository or download the files:
git clone https://github.com/Niteshagarwal01/basic-projects.git cd basic-projects/02-color-palette-generator - Open
index.htmlin your browser. - No Installation Required: Pure vanilla JavaScript - works immediately in any modern browser.
- Google Fonts: Automatically loaded via CDN (requires internet connection).
- No API Keys: Completely client-side with no external API dependencies.
-
Color Scheme: Modify CSS custom properties in
:rootto change the theme::root { --bg-dark: #1a1a1a; /* Main background */ --bg-card: #2a2a2a; /* Card background */ --primary: #fbbf24; /* Yellow accent */ --text-light: #ffffff; /* Primary text */ --text-muted: #9ca3af; /* Secondary text */ --border: #374151; /* Border color */ --shadow: rgba(0, 0, 0, 0.3); /* Shadow color */ }
-
Font Pairings: Add custom font combinations in
script.js:const FONTS = [ { script: "Playfair Display", bold: "Inter" }, { script: "Bebas Neue", bold: "Roboto" }, // Add your custom pair // ... more pairs ];
-
Color Count Range: Modify dropdown options in
index.html:<select id="color-count"> <option value="3">3 Colors</option> <option value="4">4 Colors</option> <option value="5" selected>5 Colors</option> <option value="6">6 Colors</option> <!-- Add more options --> </select>
-
Custom Harmony Algorithm: Add new color generation function:
function genSplitComp(count) { const base = Math.floor(Math.random() * 360); const comp1 = (base + 150) % 360; const comp2 = (base + 210) % 360; // Your custom logic here // Return array of color objects } // Add to generate() function else if (harmonyType === "split-complementary") palette = genSplitComp(colorCount);
-
Type Scale Values: Customize font sizes in
script.js:const TYPE_SCALE = [ { name: "Display", size: "64px", use: "Hero headlines" }, // Larger { name: "Heading 1", size: "48px", use: "Page titles" }, // ... modify sizes as needed ];
-
Grid Layout: Adjust responsive breakpoints in
style.css:.palette-container { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); /* Wider boxes */ } @media (max-width: 768px) { .palette-container { grid-template-columns: 1fr; /* Stack on tablets */ } }
- Auto-Fit Grid: Color boxes automatically resize from 3-5 columns using
repeat(auto-fit, minmax(200px, 1fr)) - Gradient Background: Dark theme with yellow accent and subtle gradients
- Color Overlay Preview: Hover over color box to see how it looks over other palette colors
- Lock Mechanism: Click lock icon to preserve colors during regeneration
- Copy Feedback: Check icon animation on successful clipboard copy
- Dropdown Controls: Intuitive selects for color count, harmony type, and font pairs
- Font Preview Cards: Live typography samples with brand colors applied
- Dynamic Guidelines: Color-coded design system with palette-matched styling
- Smooth Transitions: CSS animations on all interactive elements
- Responsive Layout: Grid adapts from desktop (5 columns) to mobile (1 column)
- Icon Integration: Remix Icons throughout for visual clarity
- Memoization Cache: Map-based caching for color conversions (performance optimization)
| Function | Purpose |
|---|---|
generate() |
Main generation function - reads user controls, generates palette and fonts |
genRand(count) |
Generates random color harmony (3-5 colors) |
genMono(count) |
Creates monochromatic palette with varying lightness |
genAnalog(count) |
Generates analogous colors (60Β° hue spread) |
genComp(count) |
Creates complementary palette (180Β° opposite hues) |
genTriad(count) |
Generates triadic harmony (120Β° intervals) |
render() |
Renders color boxes with lock buttons, copy buttons, and overlay |
updateFont() |
Creates font-pair-card elements with palette colors |
updateGuide() |
Dynamically generates color roles, type scale, and spacing guidelines |
getContrastColor(hsl) |
Calculates WCAG-compliant text color (black/white) for backgrounds |
getReadableColor(baseHsl, bgColor) |
Adjusts color lightness for optimal readability |
hsl2hex(hsl) |
Converts HSL to HEX with memoization |
hsl2rgb(hsl) |
Converts HSL to RGB with memoization |
hsl2obj(hsl) |
Creates color object with all formats (HSL, HEX, RGB) |
The app implements professional color theory principles:
Harmony Types:
// Random: Completely random hues with controlled saturation/lightness
genRand() β Random hues, 60-100% saturation, 30-70% lightness
// Monochromatic: Single hue with varying lightness
genMono() β Same hue, 60-90% saturation, lightness from 20% to 90%
// Analogous: Adjacent hues on color wheel
genAnalog() β 60Β° hue spread, varied saturation/lightness
// Complementary: Opposite hues (180Β° apart)
genComp() β Base hue + 180Β° complement, multiple shades of each
// Triadic: Three hues 120Β° apart
genTriad() β Base, +120Β°, +240Β° hues, varied saturation/lightnessWCAG Contrast Formula:
// Calculate relative luminance
L = 0.2126 * R + 0.7152 * G + 0.0722 * B
// Where R, G, B are linearized:
if (val <= 0.03928) {
val = val / 12.92;
} else {
val = Math.pow((val + 0.055) / 1.055, 2.4);
}
// Choose text color based on luminance threshold
textColor = luminance > 0.5 ? "#000000" : "#FFFFFF";| Feature | Basic Generator | Brand Identity Generator β¨ |
|---|---|---|
| Color Generation | β Random only | β 5 harmony algorithms |
| Color Count | β Fixed (5) | β User control (3-5) |
| Typography | β None | β 6 font pairings (1-3 shown) |
| Design Guidelines | β None | β Dynamic (colors, type, spacing) |
| Color Locking | β | β Lock individual colors |
| WCAG Contrast | β | β Advanced luminance calc |
| Color Overlay | β | β Hover preview |
| Copy to Clipboard | β HEX only | β HEX, RGB, HSL |
| User Controls | β | β 3 dropdowns (count, harmony, fonts) |
| Google Fonts | β | β 12 premium fonts |
| Responsive Grid | β Fixed | β Auto-fit (3-5 columns) |
| Performance | Basic | β Memoization cache |
| Code Quality | Basic | β Optimized (480 lines CSS) |
The app adapts seamlessly across devices:
Desktop (>768px):
- Auto-fit grid: 3-5 color boxes (200px minimum width)
- Side-by-side font pair cards
- Full-width guidelines grid (3 columns)
Tablet (481px-768px):
- Auto-fit grid: 2-3 color boxes
- Stacked font pair cards
- 2-column guidelines grid
Mobile (<480px):
- Single-column color boxes (stacked)
- Single-column font pairs
- Single-column guidelines
- Reduced padding and font sizes
/* Auto-fit grid adjusts automatically */
.palette-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
@media (max-width: 768px) {
.controls {
flex-direction: column; /* Stack controls */
}
.font-pairing {
grid-template-columns: 1fr; /* Single column */
}
}
@media (max-width: 480px) {
.palette-container {
grid-template-columns: 1fr; /* Force single column */
}
.color-box {
min-height: 250px; /* Taller boxes for mobile */
}
}Potential features to add:
- Export Functionality: Download palette as CSS variables, SCSS, JSON, or image
- Accessibility Scores: Display WCAG AA/AAA ratings for color pairs
- Save Presets: LocalStorage for favorite color palettes
- Brand Name Generator: AI-powered brand names based on palette mood
- Color Picker: Manual color selection with harmony auto-completion
- Gradient Generator: Create CSS gradients from palette colors
- Dark/Light Mode: Toggle between themes with preference persistence
- Animation Presets: Show palette in action with animated UI components
- Color Blindness Simulator: Preview palette for different vision types
- Social Share: Generate shareable palette cards for social media
Contributions welcome β feel free to open issues or create PRs to add features like export functionality, accessibility scoring, or color picker integration.