Skip to content

Niteshagarwal01/Chromify

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Color Palette Generator β€” Project Guide (Small Build #11)

image

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.

πŸš€ Features

  • 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.

πŸ› οΈ Tech Stack

  • 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)

πŸ“‚ File Structure

  • 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).

βš™οΈ How It Works

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
}

πŸ“ Usage Examples

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();
});

πŸ’» Run Locally

  1. Clone the repository or download the files:
    git clone https://github.com/Niteshagarwal01/basic-projects.git
    cd basic-projects/02-color-palette-generator
  2. Open index.html in your browser.
  3. No Installation Required: Pure vanilla JavaScript - works immediately in any modern browser.
  4. Google Fonts: Automatically loaded via CDN (requires internet connection).
  5. No API Keys: Completely client-side with no external API dependencies.

πŸ”§ Customization

  • Color Scheme: Modify CSS custom properties in :root to 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 */
      }
    }

🎨 UI/UX Features

  • 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)

⚑ Key Functions

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)

πŸ“Š Color Theory Implementation

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/lightness

WCAG 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";

🎯 Features Comparison

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)

πŸ“± Responsive Design

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 */
  }
}

πŸš€ Future Enhancements

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.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors