from pathlib import Path out = Path("/mnt/data/mp-color-lab") out.mkdir(exist_ok=True) index_html = r''' MP Color Lab – Free Online Color Tools, Picker, Converter & Palette Generator
MP Color Labby MP Digital Hub
Explore Tools
FREE • BROWSER-BASED • DESIGNER FRIENDLY

Your Complete Online Color Toolkit

Pick, convert, generate, compare and create beautiful colors — all in one place.

✓ No installation✓ Client-side tools✓ Free to use
Color workspace● ● ●
#6C63FFHEX
108, 99, 255RGB
Advertisement placeholder
QUICK TOOL

Color Picker

Choose a color and instantly get production-ready values.

HEX#6C63FF
RGBrgb(108, 99, 255)
HSLhsl(243, 100%, 69%)
HSVhsv(243, 58%, 100%)
HSBhsv(243, 58%, 100%)
CMYK (approx.)58%, 61%, 0%, 0%
CONVERT ANY FORMAT

Universal Color Converter

Enter HEX, RGB, HSL or HSV and get every useful format.

CREATE COMBINATIONS

Color Palette Generator

Build harmonious palettes from one base color.

CSS READY

Gradient Generator

Create linear, radial and conic gradients with live CSS.

ACCESSIBILITY

Contrast Checker

Check WCAG contrast for text and backgrounds.

16.00:1Contrast Ratio
The quick brown foxjumps over the lazy dog.Accessible color preview
100% LOCAL PROCESSING

Image Color Tools

Analyze an image without uploading it to a server.

Extract Dominant Colors

Your image is processed locally in your browser and is not uploaded to a server.

#—RGB —
TOOLS DIRECTORY

Explore Color Tools

Everything you need for digital color work.

🎨

Color Picker

Pick and copy professional color formats.

Use Tool →

HEX / RGB Converter

Convert popular color formats instantly.

Use Tool →

Palette Generator

Generate complementary, analogous and more.

Use Tool →

CSS Gradient Generator

Build beautiful CSS gradients.

Use Tool →

Contrast Checker

Check AA and AAA accessibility levels.

Use Tool →

Image Color Extractor

Extract dominant colors locally.

Use Tool →

Shades & Tints

Use the picker and generate color variations.

Use Tool →
</>

CSS Color Tools

Copy ready-to-use values and CSS.

Use Tool →
FOR CREATORS

Tools for Designers & Developers

✦ Brand Color GeneratorBuild a polished brand palette.
▣ Website Color PaletteCreate consistent UI colors.
▰ Button Color GeneratorFind strong CTA combinations.
◒ Background GeneratorExplore gradient backgrounds.
A Text Color GeneratorTest readable foreground colors.
</> CSS Variable GeneratorTurn colors into reusable variables.
WHY MP COLOR LAB?

Built to make color work easier.

100% Browser-BasedMany tools run entirely on your device.
Fast & EasyNo complicated setup or account.
Free Online ToolsUseful utilities available at no cost.
No InstallationOpen the site and start creating.
Mobile FriendlyDesigned for phones, tablets and desktops.
Made for Designers & DevelopersPractical formats and CSS-ready outputs.
FAQ

Frequently Asked Questions

What is MP Color Lab?

MP Color Lab is a free collection of browser-based color utilities for designers, developers, creators, students and anyone working with colors.

Is MP Color Lab free?

Yes. The tools are designed to be free to use in your browser.

Can I convert HEX to RGB?

Yes. Use the Universal Color Converter to convert HEX, RGB, HSL and HSV values.

Can I generate color palettes?

Yes. The palette generator creates several common harmony families from a base color.

Can I create CSS gradients?

Yes. The Gradient Generator provides live previews and copyable CSS.

Can I extract colors from an image?

Yes. Upload an image to the Image Color Extractor. Processing happens locally in the browser.

Are uploaded images sent to a server?

The image tools on this static site process selected images locally using browser APIs.

Can I save favorite colors?

The site is prepared for browser-local favorites/history using localStorage; no account is required.

NEED HELP?

Have feedback or a color question?

Reach MP Digital Hub through the support channels below.

Copied!
''' style_css = r''':root{--bg:#f7f8fc;--surface:#fff;--surface2:#eef1f8;--text:#12182a;--muted:#667085;--border:#e3e7f0;--primary:#6c63ff;--cyan:#00c2ff;--pink:#ff4ecd;--shadow:0 18px 55px rgba(30,35,70,.10);--radius:22px}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:var(--text);background:var(--bg);line-height:1.6}body.dark{--bg:#080d1b;--surface:#10172a;--surface2:#151e34;--text:#f4f6ff;--muted:#aab2c5;--border:#27314a;--shadow:0 20px 60px rgba(0,0,0,.35)}a{color:inherit;text-decoration:none}button,input,select{font:inherit}.progress{position:fixed;top:0;left:0;height:3px;width:0;background:linear-gradient(90deg,var(--pink),var(--primary),var(--cyan));z-index:1000}.header{height:74px;position:sticky;top:0;z-index:100;background:color-mix(in srgb,var(--surface) 86%,transparent);backdrop-filter:blur(18px);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:24px;padding:0 clamp(16px,4vw,56px)}.brand{display:flex;align-items:center;gap:10px;min-width:max-content}.brand .logo{width:42px;height:42px;display:grid;place-items:center}.logo svg{width:100%;height:100%}.brand strong{display:block;font-size:16px;letter-spacing:-.3px}.brand small{display:block;color:var(--muted);font-size:10px}.header nav{display:flex;gap:20px;margin-left:auto;font-size:13px;color:var(--muted)}.header nav a:hover{color:var(--primary)}.header-actions{display:flex;align-items:center;gap:8px}.icon-btn,.menu-btn{border:1px solid var(--border);background:var(--surface);color:var(--text);width:40px;height:40px;border-radius:12px;cursor:pointer}.menu-btn{display:none}.btn{border:0;border-radius:13px;padding:11px 17px;font-weight:750;cursor:pointer;display:inline-flex;gap:9px;align-items:center;justify-content:center;transition:.2s transform,.2s box-shadow}.btn:hover{transform:translateY(-2px);box-shadow:0 10px 25px rgba(60,70,150,.16)}.btn-primary{color:#fff;background:linear-gradient(135deg,var(--primary),var(--pink))}.btn-ghost{color:var(--text);background:var(--surface);border:1px solid var(--border)}.section{max-width:1180px;margin:auto;padding:78px 24px}.hero{min-height:640px;display:grid;grid-template-columns:1.02fr .98fr;align-items:center;gap:40px;padding-top:85px}.eyebrow{font-size:11px;font-weight:850;letter-spacing:1.7px;color:var(--primary)}h1{font-size:clamp(45px,6vw,78px);line-height:1.03;letter-spacing:-4px;margin:16px 0}.hero h1 span{background:linear-gradient(90deg,var(--primary),var(--cyan),var(--pink));-webkit-background-clip:text;color:transparent}.hero-copy>p{font-size:19px;color:var(--muted);max-width:600px}.hero-actions{display:flex;gap:12px;flex-wrap:wrap;margin:28px 0}.hero-trust{display:flex;gap:18px;flex-wrap:wrap;color:var(--muted);font-size:12px}.hero-art{min-height:420px;position:relative;display:grid;place-items:center}.showcase-card{position:relative;width:min(440px,90%);padding:18px;border:1px solid rgba(255,255,255,.35);background:color-mix(in srgb,var(--surface) 82%,transparent);border-radius:28px;box-shadow:var(--shadow);backdrop-filter:blur(20px);transform:rotate(2deg)}.showcase-top{display:flex;justify-content:space-between;color:var(--muted);font-size:11px}.mini-dot{width:8px;height:8px;border-radius:50%;background:var(--pink);margin-right:auto}.showcase-gradient{height:230px;border-radius:20px;margin:15px 0;background:linear-gradient(135deg,#6c63ff,#00c2ff,#ff4ecd)}.showcase-values{display:flex;gap:35px}.showcase-values b{font-size:18px}.showcase-values small{display:block;color:var(--muted);font-size:9px}.mini-palette{display:flex;gap:7px;margin-top:15px}.mini-palette i{height:35px;flex:1;border-radius:9px;background:#6c63ff}.mini-palette i:nth-child(2){background:#00c2ff}.mini-palette i:nth-child(3){background:#ff4ecd}.mini-palette i:nth-child(4){background:#ffb547}.mini-palette i:nth-child(5){background:#2ed47a}.orb{position:absolute;border-radius:50%;filter:blur(2px);opacity:.65}.orb-a{width:180px;height:180px;background:#ff4ecd;right:0;top:30px}.orb-b{width:130px;height:130px;background:#00c2ff;left:0;bottom:25px}.ad-slot{max-width:900px;height:72px;border:1px dashed var(--border);border-radius:16px;margin:0 auto 20px;display:grid;place-items:center;color:var(--muted);font-size:11px}.section-heading{display:flex;align-items:end;justify-content:space-between;gap:20px;margin-bottom:25px}.section-heading h2{font-size:clamp(30px,4vw,46px);letter-spacing:-2px;line-height:1.1;margin:8px 0}.section-heading p{color:var(--muted);margin:0}.centered{justify-content:center;text-align:center}.tool-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);padding:24px}.picker-grid{display:grid;grid-template-columns:300px 1fr;gap:25px}.picker-preview{min-height:310px;border-radius:18px;background:#6c63ff;display:grid;place-items:center}.picker-preview input{width:120px;height:120px;border:0;background:none;cursor:pointer}.value-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}.value-box{background:var(--surface2);border:1px solid var(--border);border-radius:14px;padding:13px;position:relative}.value-box span{font-size:10px;color:var(--muted);font-weight:800}.value-box strong{display:block;margin:4px 0 0;font-size:13px;word-break:break-word}.value-box button,.code-box button{position:absolute;right:9px;top:9px;border:0;border-radius:8px;background:var(--surface);color:var(--primary);padding:5px 8px;font-size:10px;font-weight:800;cursor:pointer}.quick-actions{grid-column:1/-1;display:flex;gap:10px;align-items:center}.converter-layout{display:grid;grid-template-columns:280px 1fr;gap:28px}.converter-inputs label,.gradient-controls label{display:block;font-size:12px;font-weight:750;margin-bottom:14px}.converter-inputs input,.converter-inputs select,.gradient-controls select,.tool-search{width:100%;padding:12px;border:1px solid var(--border);border-radius:11px;background:var(--surface2);color:var(--text);margin-top:6px;outline:none}.converter-results{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}.result-box{padding:17px;background:var(--surface2);border-radius:14px;border:1px solid var(--border)}.result-box span{font-size:10px;color:var(--muted)}.result-box b{display:block;margin-top:4px}.result-box button{margin-top:9px;border:0;background:transparent;color:var(--primary);font-size:11px;font-weight:800;cursor:pointer}.error{color:#ef4444;font-size:12px}.tool-toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:22px}.tool-toolbar label{font-size:12px;font-weight:800;display:flex;align-items:center;gap:8px}.tool-toolbar input[type=color]{width:48px;height:40px;border:0;background:none}.palette-groups{display:grid;grid-template-columns:repeat(2,1fr);gap:18px}.palette-group{border:1px solid var(--border);border-radius:16px;padding:15px}.palette-group h3{font-size:14px;margin:0 0 10px}.swatches{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}.swatch{min-height:100px;border-radius:11px;display:flex;align-items:end;padding:7px;color:#fff;text-shadow:0 1px 3px #000;font-size:9px;cursor:pointer}.gradient-layout{display:grid;grid-template-columns:1.2fr .8fr;gap:28px}.gradient-preview{min-height:430px;border-radius:20px;background:linear-gradient(135deg,#ff4ecd 0%,#00c2ff 50%,#6c63ff 100%)}.gradient-controls input[type=color]{width:100%;height:42px;border:0;background:var(--surface2);border-radius:9px}.gradient-controls input[type=range]{width:calc(100% - 55px);vertical-align:middle}.gradient-controls output{display:inline-block;width:45px;text-align:right}.code-box{position:relative;background:#0d1324;color:#fff;border-radius:13px;padding:16px;margin:16px 0;min-height:70px}.code-box code{display:block;font-size:12px;line-height:1.5;padding-right:65px;word-break:break-word}.code-box button{background:#fff}.contrast-layout{display:grid;grid-template-columns:360px 1fr;gap:25px}.contrast-controls{display:grid;grid-template-columns:1fr 1fr;gap:12px}.contrast-controls label{background:var(--surface2);padding:12px;border-radius:13px;font-size:12px;font-weight:800}.contrast-controls input{display:block;width:100%;height:42px;border:0;background:none;margin-top:4px}.contrast-ratio{grid-column:1/-1;padding:18px;border-radius:14px;background:linear-gradient(135deg,#eef0ff,#e7fbff)}.contrast-ratio strong{display:block;font-size:30px}.contrast-ratio span{font-size:11px;color:#667085}.checks{grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr;gap:8px}.check{padding:9px;border-radius:10px;font-size:11px;font-weight:800;background:var(--surface2)}.contrast-demo{min-height:300px;border-radius:18px;padding:50px;display:flex;flex-direction:column;justify-content:center;gap:7px;font-size:24px}.contrast-demo small{font-size:11px;opacity:.7}.image-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px}.upload-card{min-height:390px;text-align:center;position:relative}.upload-card input[type=file]{width:100%;padding:15px;border:1px dashed var(--border);border-radius:13px}.upload-icon{font-size:40px;margin:22px}.dominant-colors{display:flex;flex-wrap:wrap;gap:8px;margin:18px 0;justify-content:center}.dominant{width:70px;height:70px;border-radius:12px;display:flex;align-items:end;padding:5px;color:#fff;font-size:8px;text-shadow:0 1px 3px #000;cursor:pointer}.image-picker-card{overflow:hidden}.image-picker-card canvas{width:100%;max-height:340px;object-fit:contain;background:var(--surface2);border-radius:14px;display:block}.image-pick-row{display:flex;align-items:center;gap:12px;margin-top:12px}.image-pick-row input{max-width:180px}.image-pick-row span{width:45px;height:45px;border-radius:10px;background:#ddd}.image-pick-row small{display:block;color:var(--muted)}.tool-search{max-width:280px}.tool-directory{display:grid;grid-template-columns:repeat(4,1fr);gap:15px}.tool-tile{background:var(--surface);border:1px solid var(--border);border-radius:18px;padding:20px;box-shadow:0 8px 25px rgba(30,35,70,.05);transition:.2s}.tool-tile:hover{transform:translateY(-4px);box-shadow:var(--shadow)}.tool-tile>span{font-size:28px}.tool-tile h3{font-size:16px;margin:10px 0 5px}.tool-tile p{color:var(--muted);font-size:12px;min-height:42px}.tool-tile a{font-size:11px;font-weight:850;color:var(--primary)}.feature-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}.feature-grid>div{padding:21px;border-radius:17px;background:var(--surface);border:1px solid var(--border)}.feature-grid b{display:block}.feature-grid span{display:block;color:var(--muted);font-size:12px;margin-top:6px}.why{background:linear-gradient(135deg,color-mix(in srgb,var(--primary) 9%,var(--bg)),color-mix(in srgb,var(--cyan) 7%,var(--bg)));max-width:none}.why>.section-heading,.why>.feature-grid{max-width:1132px;margin-left:auto;margin-right:auto}.faq{max-width:900px;margin:auto}.faq details{background:var(--surface);border:1px solid var(--border);border-radius:13px;margin:9px 0;padding:17px 20px}.faq summary{cursor:pointer;font-weight:800}.faq p{color:var(--muted);font-size:13px;margin:12px 0 0}.support-card{border-radius:26px;padding:38px;display:flex;justify-content:space-between;gap:25px;align-items:center;background:linear-gradient(135deg,#151b36,#27235a);color:#fff}.support-card p{color:#c6cce0}.support-actions{display:flex;gap:10px}.support-card .btn-ghost{color:#fff;background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.2)}.legal{display:grid;grid-template-columns:1.3fr 1fr 1fr 1fr;gap:25px}.legal h2{margin-top:7px}.legal h3{font-size:16px}.legal p{font-size:12px;color:var(--muted)}.footer{background:#080d1b;color:#dfe4f5;padding:55px max(24px,calc((100% - 1132px)/2)) 20px}.footer-main{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:40px}.footer-brand{margin-bottom:12px}.footer p,.footer a{color:#8e98b2;font-size:12px}.footer h4{font-size:12px;text-transform:uppercase;letter-spacing:1px;color:#fff}.footer-main>div>a:not(.brand){display:block;margin:7px 0}.footer-bottom{border-top:1px solid #1d263a;margin-top:40px;padding-top:18px;display:flex;justify-content:space-between;font-size:11px;color:#727d96}.footer-bottom a{color:#aeb7cb}.toast{position:fixed;bottom:25px;left:50%;transform:translate(-50%,20px);opacity:0;background:#111827;color:#fff;padding:10px 15px;border-radius:10px;font-size:12px;z-index:1001;transition:.25s}.toast.show{opacity:1;transform:translate(-50%,0)}.backtop{position:fixed;right:20px;bottom:20px;width:42px;height:42px;border-radius:50%;border:1px solid var(--border);background:var(--surface);color:var(--text);box-shadow:var(--shadow);cursor:pointer;display:none}.backtop.show{display:block}@media(max-width:1050px){.header nav{display:none}.menu-btn{display:block}.nav-cta{display:none}.header{justify-content:space-between}.header nav.open{display:flex;position:absolute;top:74px;left:0;right:0;background:var(--surface);padding:18px 24px;flex-direction:column;border-bottom:1px solid var(--border)}.hero{grid-template-columns:1fr}.hero-art{min-height:340px}.tool-directory{grid-template-columns:repeat(2,1fr)}.legal{grid-template-columns:1fr 1fr}}@media(max-width:720px){.section{padding:58px 17px}.hero{padding-top:55px}.hero h1{letter-spacing:-2.5px}.picker-grid,.converter-layout,.gradient-layout,.contrast-layout,.image-grid{grid-template-columns:1fr}.picker-preview{min-height:220px}.value-grid,.converter-results,.palette-groups{grid-template-columns:1fr}.tool-directory,.feature-grid{grid-template-columns:1fr}.support-card{flex-direction:column;align-items:flex-start}.legal{grid-template-columns:1fr}.footer-main{grid-template-columns:1fr 1fr}.footer-bottom{flex-direction:column;gap:8px}.contrast-controls{grid-template-columns:1fr}.swatches{grid-template-columns:repeat(5,1fr)}.swatch{min-height:75px}.hero-art{min-height:300px}.showcase-gradient{height:180px}.tool-search{max-width:100%}.section-heading{align-items:flex-start;flex-direction:column}}''' script_js = r'''(() => { "use strict"; const $=s=>document.querySelector(s), $$=s=>[...document.querySelectorAll(s)]; const toast=(msg="Copied!")=>{const t=$("#toast");t.textContent=msg;t.classList.add("show");clearTimeout(window._toast);window._toast=setTimeout(()=>t.classList.remove("show"),1600)}; const clamp=(n,a,b)=>Math.min(b,Math.max(a,n)); const hexToRgb=hex=>{hex=hex.replace("#","").trim();if(hex.length===3)hex=hex.split("").map(x=>x+x).join("");if(!/^[0-9a-f]{6}$/i.test(hex))throw Error("Invalid HEX");return {r:parseInt(hex.slice(0,2),16),g:parseInt(hex.slice(2,4),16),b:parseInt(hex.slice(4,6),16)}}; const rgbToHex=({r,g,b})=>"#"+[r,g,b].map(x=>clamp(Math.round(x),0,255).toString(16).padStart(2,"0")).join("").toUpperCase(); function rgbToHsl({r,g,b}){r/=255;g/=255;b/=255;let max=Math.max(r,g,b),min=Math.min(r,g,b),h,s,l=(max+min)/2;if(max===min)h=s=0;else{let d=max-min;s=l>.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g{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};if(s===0)return {r:l*255,g:l*255,b:l*255};let q=l<.5?l*(1+s):l+s-l*s,p=2*l-q;return {r:hue2rgb(p,q,h+1/3)*255,g:hue2rgb(p,q,h)*255,b:hue2rgb(p,q,h-1/3)*255}} function rgbToHsv({r,g,b}){r/=255;g/=255;b/=255;let max=Math.max(r,g,b),min=Math.min(r,g,b),d=max-min,h=max===min?0:max===r?((g-b)/d+(g`rgb(${Math.round(c.r)}, ${Math.round(c.g)}, ${Math.round(c.b)})`; const fmtHsl=c=>{let x=rgbToHsl(c);return `hsl(${x.h}, ${x.s}%, ${x.l}%)`}; const fmtHsv=c=>{let x=rgbToHsv(c);return `hsv(${x.h}, ${x.s}%, ${x.v}%)`}; const cmyk=c=>{let r=c.r/255,g=c.g/255,b=c.b/255,k=1-Math.max(r,g,b);if(k>=.999)return "0%, 0%, 0%, 100%";return [((1-r-k)/(1-k)*100),((1-g-k)/(1-k)*100),((1-b-k)/(1-k)*100),k*100].map(x=>Math.round(x)+"%").join(", ")}; const randomHex=()=>rgbToHex({r:Math.random()*256,g:Math.random()*256,b:Math.random()*256}); function updateQuick(hex){let c=hexToRgb(hex),h=rgbToHsl(c),v=rgbToHsv(c),css=rgbToHex(c);$("#quickColor").value=css;$("#quickPreview").style.background=css;$("#qHex").textContent=css;$("#qRgb").textContent=fmtRgb(c);$("#qHsl").textContent=fmtHsl(c);$("#qHsv").textContent=fmtHsv(c);$("#qHsb").textContent=fmtHsv(c);$("#qCmyk").textContent=cmyk(c);$("#heroHex").textContent=css;$("#heroGradient").style.background=`linear-gradient(135deg,${css},#00c2ff,#ff4ecd)`;localStorage.setItem("mcl:lastColor",css);addHistory(css)} function addHistory(hex){let a=JSON.parse(localStorage.getItem("mcl:history")||"[]");a=[hex,...a.filter(x=>x!==hex)].slice(0,20);localStorage.setItem("mcl:history",JSON.stringify(a))} $("#quickColor").addEventListener("input",e=>updateQuick(e.target.value));$("#randomQuick").onclick=()=>updateQuick(randomHex());$("#resetQuick").onclick=()=>updateQuick("#6c63ff"); $$("[data-copy]").forEach(b=>b.addEventListener("click",()=>{const el=$("#"+b.dataset.copy);copy(el.textContent)})); async function copy(text){try{await navigator.clipboard.writeText(text);toast()}catch{toast("Copy failed")}} function converter(){try{let c=parseColor($("#inputFormat").value,$("#converterInput").value),items=[["HEX",rgbToHex(c)],["RGB",fmtRgb(c)],["HSL",fmtHsl(c)],["HSV",fmtHsv(c)],["HSB",fmtHsv(c)],["CMYK",cmyk(c)]];$("#convertError").textContent="";$("#converterResults").innerHTML=items.map(([k,v])=>`
${k}${v}
`).join("");$$(".result-box button").forEach(x=>x.onclick=()=>copy(x.dataset.val))}catch(e){$("#convertError").textContent="Please enter a valid color value."}} $("#convertBtn").onclick=converter;$("#converterInput").addEventListener("input",converter);$("#inputFormat").addEventListener("change",converter); function palette(base){let c=rgbToHsl(hexToRgb(base)),H=c.h,S=c.s,L=c.l;const h=(x)=>hslToRgb((x+360)%360,S,L);const mono=[-28,-14,0,14,28].map(d=>hslToRgb(H,S,clamp(L+d,8,92)));return { "Complementary":[h(H),h(H+180),h(H+180)], "Analogous":[h(H-30),h(H),h(H+30),h(H+60),h(H-60)], "Monochromatic":mono, "Triadic":[h(H),h(H+120),h(H+240)], "Split Complementary":[h(H),h(H+150),h(H+210)], "Tetradic":[h(H),h(H+90),h(H+180),h(H+270)], "Square":[h(H),h(H+90),h(H+180),h(H+270)], "Warm Palette":[h(15),h(35),h(50),h(5),h(25)], "Cool Palette":[h(180),h(205),h(220),h(245),h(160)], "Pastel Palette":[hslToRgb(H,55,88),hslToRgb(H+35,55,88),hslToRgb(H+70,55,88),hslToRgb(H+150,55,88),hslToRgb(H+210,55,88)] }} function renderPalette(){let base=$("#paletteBase").value,p=palette(base);$("#paletteGroups").innerHTML=Object.entries(p).map(([name,arr])=>`

${name}

${arr.map(c=>{let x=rgbToHex(c);return `
${x}
`}).join("")}
`).join("");$$(".swatch").forEach(s=>s.onclick=()=>copy(s.dataset.hex))} $("#generatePalette").onclick=renderPalette;$("#paletteBase").oninput=renderPalette;$("#randomPalette").onclick=()=>{$("#paletteBase").value=randomHex();renderPalette()};$("#copyPalette").onclick=()=>{let a=[...document.querySelectorAll(".swatch")].map(x=>x.dataset.hex);copy(a.join(", "))}; function gradient(){let t=$("#gradType").value,a=$("#grad1").value,b=$("#grad2").value,c=$("#grad3").value,ang=$("#gradAngle").value,stop=$("#gradStop").value,stops=`${a} 0%, ${b} ${stop}%, ${c} 100%`,css=t==="linear"?`linear-gradient(${ang}deg, ${stops})`:t==="radial"?`radial-gradient(circle, ${stops})`:`conic-gradient(from ${ang}deg, ${stops})`;$("#gradientPreview").style.background=css;$("#gradientCss").textContent=`background: ${css};`;$("#angleOut").value=ang+"°";$("#angleOut").textContent=ang+"°";$("#stopOut").value=stop+"%";$("#stopOut").textContent=stop+"%"} ["gradType","grad1","grad2","grad3","gradAngle","gradStop"].forEach(id=>$("#"+id).addEventListener("input",gradient));$("#randomGradient").onclick=()=>{$("#grad1").value=randomHex();$("#grad2").value=randomHex();$("#grad3").value=randomHex();$("#gradAngle").value=Math.floor(Math.random()*361);$("#gradStop").value=25+Math.floor(Math.random()*51);gradient()};$("#resetGradient").onclick=()=>{$("#grad1").value="#ff4ecd";$("#grad2").value="#00c2ff";$("#grad3").value="#6c63ff";$("#gradAngle").value=135;$("#gradStop").value=50;gradient()}; function luminance(c){let a=[c.r,c.g,c.b].map(x=>{x/=255;return x<=.03928?x/12.92:Math.pow((x+.055)/1.055,2.4)});return .2126*a[0]+.7152*a[1]+.0722*a[2]} function contrast(){let f=hexToRgb($("#fg").value),b=hexToRgb($("#bg").value),r=(Math.max(luminance(f),luminance(b))+.05)/(Math.min(luminance(f),luminance(b))+.05);$("#ratio").textContent=r.toFixed(2)+":1";$("#contrastDemo").style.color=$("#fg").value;$("#contrastDemo").style.background=$("#bg").value;const pass=(x)=>r>=x?"✓ Pass":"✕ Fail";$("#checks").innerHTML=`
Normal AA: ${pass(4.5)}
Normal AAA: ${pass(7)}
Large AA: ${pass(3)}
Large AAA: ${pass(4.5)}
`} ["fg","bg"].forEach(id=>$("#"+id).addEventListener("input",contrast)); function imageAnalyze(file){if(!file)return;let img=new Image, url=URL.createObjectURL(file);img.onload=()=>{let c=$("#imageCanvas"),ctx=c.getContext("2d"),w=500,h=Math.round(img.height/img.width*w);c.width=w;c.height=h;ctx.drawImage(img,0,0,w,h);let data=ctx.getImageData(0,0,w,h).data,map=new Map();for(let i=0;ib[1]-a[1]).slice(0,10);$("#dominantColors").innerHTML=top.map(([k])=>{let [r,g,b]=k.split(",").map(Number),x=rgbToHex({r,g,b});return `
${x}
`}).join("");$$(".dominant").forEach(x=>x.onclick=()=>copy(x.dataset.hex));$("#downloadPalette").onclick=()=>download("mp-color-palette.txt",top.map(([k])=>rgbToHex(Object.fromEntries(["r","g","b"].map((n,i)=>[n,+k.split(",")[i]])))).join("\n"));URL.revokeObjectURL(url)};img.src=url} $("#imageInput").onchange=e=>imageAnalyze(e.target.files[0]);$("#downloadPalette").onclick=()=>toast("Upload an image first"); function imagePick(file){if(!file)return;let img=new Image,url=URL.createObjectURL(file);img.onload=()=>{let c=$("#imageCanvas"),ctx=c.getContext("2d"),w=600,h=Math.round(img.height/img.width*w);c.width=w;c.height=h;ctx.drawImage(img,0,0,w,h);c.onclick=e=>{let r=c.getBoundingClientRect(),x=Math.floor((e.clientX-r.left)*c.width/r.width),y=Math.floor((e.clientY-r.top)*c.height/r.height),p=ctx.getImageData(x,y,1,1).data,hex=rgbToHex({r:p[0],g:p[1],b:p[2]});$("#pickedSwatch").style.background=hex;$("#pickedHex").textContent=hex;$("#pickedRgb").textContent=fmtRgb({r:p[0],g:p[1],b:p[2]})};URL.revokeObjectURL(url)};img.src=url} $("#pickImageInput").onchange=e=>imagePick(e.target.files[0]); function download(name,text){let a=document.createElement("a");a.href=URL.createObjectURL(new Blob([text],{type:"text/plain"}));a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),500)} $("#toolSearch").addEventListener("input",e=>{let q=e.target.value.toLowerCase();$$(".tool-tile").forEach(x=>x.hidden=!x.dataset.name.includes(q))}); $("#menuBtn").onclick=()=>$("#nav").classList.toggle("open");$$(".header nav a").forEach(a=>a.onclick=()=>$("#nav").classList.remove("open")); $("#themeBtn").onclick=()=>{document.body.classList.toggle("dark");localStorage.setItem("mcl:theme",document.body.classList.contains("dark")?"dark":"light");$("#themeBtn").textContent=document.body.classList.contains("dark")?"☀":"☾"};if(localStorage.getItem("mcl:theme")==="dark"){$("body").classList.add("dark");$("#themeBtn").textContent="☀"} window.addEventListener("scroll",()=>{let h=document.documentElement.scrollHeight-innerHeight,$=(scrollY/h)*100;$("#progress").style.width=Math.min(100,$)+"%";$("#backTop").classList.toggle("show",scrollY>500)}); $("#backTop").onclick=()=>scrollTo({top:0,behavior:"smooth"}); updateQuick(localStorage.getItem("mcl:lastColor")||"#6c63ff");converter();renderPalette();gradient();contrast(); })();''' (out/"index.html").write_text(index_html, encoding="utf-8") (out/"style.css").write_text(style_css, encoding="utf-8") (out/"script.js").write_text(script_js, encoding="utf-8") # Zip the complete GitHub Pages-ready project import zipfile zip_path = Path("/mnt/data/MP_Color_Lab_GitHub_Pages.zip") with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as z: for p in out.iterdir(): z.write(p, arcname=p.name) print(f"Created: {zip_path}") print("Files:", ", ".join(p.name for p in out.iterdir()))