• 01 - Critter Models 02
  • 02 - VantaJS Shaders
  • 03 - App Bundle 02
  • 04 - Cathedrals
  • 05 - App Bundle 01
  • 06 - Retroscopic
  • 07 - Games
  • 08 - Voronoi
  • 09 - Terrain
  • 10 - Critter Models 01
  • 11 - Chromatophores
  • 12 - Conformal Maps
  • 13 - Echo Logix
  • 14 - Music
  • 15 - Colour Theory
MIKE'S NOTEBOOK
Notes
Number:
Project:
13Echo Logix
└ 01 Price of Sound└ 02 The Resonance Room└ 03 Residuals└ 04 Terms of Silence└ 05 The Voice Vault└ 06 The Echo Market└ 07 Fragments of Finn└ 08 Signal to Noise└ 09 A Prayer
14Music
└ Launch App
15Colour Theory
└ ColorMind└ CSS Gradient Lab└ Colour Palette└ Color API
03 / 03

COLORMIND - THREEJS PROCEDURAL ANIMATION SOURCE CODE

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ColorMind APi</title>
  <style>
    *,
    *::before,
    *::after {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    html,
    body {
      height: 100%;
      font-family: "Inter", system-ui, sans-serif;
      line-height: 1.6;
    }

    body {
      background: #000000;
      color: #ffffff;
      min-height: 100vh;
      padding-bottom: 2rem;
    }

    .blog-content {
      max-width: 1200px;
      margin: 0 auto;
      padding: 2rem;
    }

    .page-header {
      margin-bottom: 2rem;
      padding: 1rem 0;
      text-align: left;
    }

    .page-header .tagline {
      color: #ffffff;
      font-size: clamp(1.4rem, 2vw, 1.8rem);
      opacity: 0.9;
      font-weight: 400;
      margin-top: 0.5rem;
    }

    .intro-text {
      text-align: left;
      margin-bottom: 2rem;
      max-width: 800px;
    }

    .intro-text p {
      margin-bottom: 1rem;
      font-size: 1.1rem;
      opacity: 0.8;
      line-height: 1.7;
    }

    .intro-text a {
      color: #ffffff;
      text-decoration: none;
      border-bottom: 1px solid #666;
      transition: all 0.3s ease;
    }

    .intro-text a:hover {
      border-bottom: 1px solid #ffffff;
    }

    .controls-container {
      display: flex;
      justify-content: flex-start;
      gap: 1rem;
      margin-bottom: 2rem;
      padding: 1.5rem;
      background: rgba(255, 255, 255, 0.05);
      border-radius: 12px;
      border: 1px solid rgba(255, 255, 255, 0.1);
    }

    .button-group {
      display: flex;
      gap: 1rem;
      flex-wrap: wrap;
    }

    .btn {
      padding: 0.85rem 1.75rem;
      font-size: 0.95rem;
      border-radius: 8px;
      background: #000000;
      color: #ffffff;
      border: 1px solid #ffffff;
      cursor: pointer;
      transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
      font-family: "Inter", system-ui;
      font-weight: 500;
      letter-spacing: 0.5px;
      position: relative;
      overflow: hidden;
      min-width: 180px;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 10px;
    }

    .btn::before {
      content: '';
      position: absolute;
      top: 50%;
      left: 50%;
      width: 0;
      height: 0;
      border-radius: 50%;
      background: rgba(255, 255, 255, 0.1);
      transform: translate(-50%, -50%);
      transition: width 0.6s, height 0.6s;
    }

    .btn:hover::before {
      width: 300px;
      height: 300px;
    }

    .btn:hover {
      background: #ffffff;
      color: #000000;
      border-color: #ffffff;
      transform: translateY(-2px);
      box-shadow: 0 8px 20px rgba(255, 255, 255, 0.1);
    }

    .btn:active {
      transform: translateY(0);
    }

    .btn:disabled {
      opacity: 0.4;
      cursor: not-allowed;
      transform: none;
    }

    .btn:disabled:hover::before {
      width: 0;
      height: 0;
    }

    .btn:disabled:hover {
      background: #000000;
      color: #ffffff;
      border-color: #ffffff;
      box-shadow: none;
    }

    .btn-secondary {
      background: #000000;
      border: 1px solid #666;
    }

    .btn-secondary:hover {
      background: #333;
      color: #ffffff;
      border-color: #666;
    }

    .palette-container {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 20px;
      margin-bottom: 40px;
    }

    .color-card {
      border-radius: 12px;
      overflow: hidden;
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
      transition: all 0.3s ease;
      background: rgba(255, 255, 255, 0.05);
      border: 1px solid rgba(255, 255, 255, 0.1);
      opacity: 0;
      transform: translateY(30px);
    }

    .color-card:hover {
      transform: translateY(-5px);
      box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
      border-color: rgba(255, 255, 255, 0.2);
    }

    .color-swatch {
      height: 150px;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
      cursor: pointer;
      transition: transform 0.2s ease;
    }

    .color-value {
      background-color: rgba(0, 0, 0, 0.8);
      padding: 10px 18px;
      border-radius: 6px;
      font-weight: 500;
      font-family: 'Monaco', 'Consolas', monospace;
      user-select: all;
      color: white;
      font-size: 0.9rem;
      border: 1px solid rgba(255, 255, 255, 0.1);
      letter-spacing: 0.5px;
    }

    .color-info {
      padding: 1.2rem;
      background: transparent;
    }

    .color-name {
      font-weight: 600;
      margin-bottom: 8px;
      color: #ffffff;
      font-size: 1.1rem;
    }

    .color-role {
      font-size: 0.85rem;
      color: #aaa;
      letter-spacing: 0.3px;
    }

    .notification {
      position: fixed;
      bottom: 20px;
      right: 20px;
      background: rgba(255, 255, 255, 0.95);
      color: #000000;
      padding: 12px 24px;
      border-radius: 8px;
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
      transform: translateY(100px);
      opacity: 0;
      transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
      border: 1px solid rgba(0, 0, 0, 0.1);
      z-index: 1000;
      font-weight: 500;
    }

    .notification.show {
      transform: translateY(0);
      opacity: 1;
    }

    .loader {
      display: none;
      text-align: left;
      margin: 30px 0;
    }

    .loader.show {
      display: block;
    }

    .spinner {
      border: 3px solid rgba(255, 255, 255, 0.1);
      border-left-color: #ffffff;
      border-radius: 50%;
      width: 40px;
      height: 40px;
      animation: spin 1s linear infinite;
      margin: 0 0 1rem 0;
    }

    @keyframes spin {
      to {
        transform: rotate(360deg);
      }
    }

    footer {
      text-align: left;
      margin-top: 40px;
      padding: 20px 0;
      color: #888;
      font-size: 0.9rem;
    }

    footer a {
      color: #ffffff;
      text-decoration: none;
      border-bottom: 1px solid #666;
      transition: all 0.3s ease;
    }

    footer a:hover {
      border-bottom: 1px solid #ffffff;
    }

    @media (max-width: 768px) {
      .palette-container {
        grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
        gap: 15px;
      }

      .blog-content {
        padding: 1.5rem;
      }

      .controls-container {
        margin-bottom: 1.5rem;
        padding: 1rem;
      }

      .button-group {
        flex-direction: column;
        width: 100%;
      }

      .btn {
        min-width: 100%;
      }
    }

    @media (max-width: 480px) {
      .blog-content {
        padding: 1rem;
      }

      .page-header .tagline {
        font-size: 1.3rem;
      }

      .color-card {
        border-radius: 10px;
      }
    }
  </style>
</head>

<body>
  <div class="blog-content">
    <header class="page-header">
      <p class="tagline">Create random palettes with the ColorMind API. Click on any colour to copy its hex value.</p>
    </header>

    <div class="intro-text">
      <p>This tool generates harmonious color palettes using artificial intelligence. Each palette consists of five complementary colors suitable for design systems, UI projects, and branding.</p>
      <p>Click "Generate Palette" for AI-curated colors or "Random Colors" for completely random combinations.</p>
    </div>

    <div class="controls-container">
      <div class="button-group">
        <button id="generateBtn" class="btn">
          <svg width="16" height="16" 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="16" height="16" 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 Colors
        </button>
      </div>
    </div>

    <div class="loader" id="loader">
      <div class="spinner"></div>
      <p>Generating your palette...</p>
    </div>

    <div class="palette-container" id="paletteContainer">
    </div>

    <footer>
      <p>Powered by the <a href="http://colormind.io/api-access/" target="_blank">Colormind API</a></p>
    </footer>
  </div>

  <div class="notification" id="notification">Color copied to clipboard!</div>

  <script>
    document.addEventListener("DOMContentLoaded", function () {
      const paletteContainer = document.getElementById("paletteContainer");
      const generateBtn = document.getElementById("generateBtn");
      const randomBtn = document.getElementById("randomBtn");
      const loader = document.getElementById("loader");
      const notification = document.getElementById("notification");

      const colorRoles = ["Primary", "Secondary", "Accent", "Background", "Text"];

      generatePalette();
      generateBtn.addEventListener("click", generatePalette);
      randomBtn.addEventListener("click", generateRandomPalette);

      async function generatePalette() {
        loader.classList.add("show");
        generateBtn.disabled = true;
        randomBtn.disabled = true;

        try {
          const response = await fetch("http://colormind.io/api/", {
            method: "POST",
            body: JSON.stringify({
              model: "default"
            })
          });

          if (!response.ok) {
            throw new Error("Failed to fetch color palette");
          }

          const data = await response.json();
          displayPalette(data.result);
        } catch (error) {
          console.error("Error fetching palette:", error);

          const fallbackPalette = [
            [255, 105, 54],
            [59, 29, 34],
            [39, 22, 30],
            [38, 14, 14],
            [233, 233, 240]
          ];
          displayPalette(fallbackPalette);
        } finally {
          loader.classList.remove("show");
          generateBtn.disabled = false;
          randomBtn.disabled = false;
        }
      }

      function generateRandomPalette() {
        const randomPalette = [];

        for (let i = 0; i < 5; i++) {
          const randomColor = [
            Math.floor(Math.random() * 256),
            Math.floor(Math.random() * 256),
            Math.floor(Math.random() * 256)
          ];
          randomPalette.push(randomColor);
        }

        displayPalette(randomPalette);
      }

      function displayPalette(colors) {
        paletteContainer.innerHTML = "";

        colors.forEach((color, index) => {
          const hexColor = rgbToHex(color[0], color[1], color[2]);

          const colorCard = document.createElement("div");
          colorCard.className = "color-card";
          colorCard.style.opacity = "0";
          colorCard.style.transform = "translateY(30px)";

          colorCard.innerHTML = \`
            <div class="color-swatch" style="background-color: \${hexColor};" data-hex="\${hexColor}">
              <span class="color-value">\${hexColor}</span>
            </div>
            <div class="color-info">
              <div class="color-name">\${colorRoles[index]}</div>
              <div class="color-role">RGB: \${color[0]}, \${color[1]}, \${color[2]}</div>
            </div>
          \`;

          const colorSwatch = colorCard.querySelector(".color-swatch");
          colorSwatch.addEventListener("click", function () {
            copyToClipboard(hexColor);
            showNotification();

            colorSwatch.style.transform = "scale(0.95)";
            setTimeout(() => {
              colorSwatch.style.transform = "scale(1)";
            }, 150);
          });

          paletteContainer.appendChild(colorCard);

          setTimeout(() => {
            colorCard.style.transition =
              "all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)";
            colorCard.style.opacity = "1";
            colorCard.style.transform = "translateY(0)";
          }, index * 100);
        });
      }

      function rgbToHex(r, g, b) {
        return (
          "#" +
          ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()
        );
      }

      function copyToClipboard(text) {
        const textarea = document.createElement("textarea");
        textarea.value = text;
        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);
      }
    });
  </script>
</body>
</html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.