MADDER
Your first step towards defining your space.
Because color madders.
MADDER
About
The thinking behind Madder
Find Your
Palette
Start with your home's character
How It
Works
What to expect
Designer
Perspectives
Palette worlds from invited designers
About
The thinking
behind Madder

Madder was built by Benjamin Winship, architect, artist, and principal of Winship/Workshop, out of a specific frustration: color decisions are one of the most consequential choices a homeowner makes, and most people make them with almost no support.

The paint chip wall at a hardware store offers thousands of options and zero guidance. Online tools offer endless swatches but no point of view. And hiring a designer (the right answer) isn't accessible to everyone at every stage.

Madder sits in the gap. It is a first step: a curated set of palettes composed with architectural intelligence, historical grounding, and a designer's point of view. It does not replace a designer. It gives you the confidence to approach one with direction, or to proceed on your own with something more than a guess.

Benjamin Winship
Benjamin Winship is an architect and artist based in the United States. As principal of Winship/Workshop, he works across architecture, interiors, and material culture, with a sustained fascination with how color, light, and material behave in real rooms over time.
Madder is a natural extension of that practice: a way to make the design intelligence that informs his own work available to a wider audience. Every palette in the library carries his point of view.
What Madder is, and isn't

Madder is not a visualization tool. It doesn't render your room or simulate paint on a wall. It doesn't replace the judgment of a designer or the irreplaceable act of sampling colors in your specific light.

What it does is give you a starting point that is architecturally coherent. Every palette was composed for a specific house type, considering the proportions, materials, light quality, and regional character of that architecture. The colors were not chosen because they look good on a screen. They were chosen because they belong together in a room of that character.

The goal is confidence. A homeowner who arrives at a paint store or a design consultation with a Madder palette has already made a considered decision. They understand why the colors work together. They know what materials and lighting the palette requires. They have a point of view, and a point of view is the beginning of everything.

Three things Madder gives you
01
Direction
A curated palette composed for your specific architecture: not a generic color suggestion but a considered composition of Lead, Support, Bridge, Ground, and Object colors that work as a system.
02
Understanding
The reasoning behind every decision: compatible woods, materials, lighting requirements, regional context, what to do with existing objects, how to adapt if your space has been renovated. And what will kill the palette if you get it wrong.
03
A deliverable
A printable specification sheet with paint codes, finishes, material notes, and sourced objects, that you can hand to a contractor, a painter, a designer, or use yourself. The information that turns a decision into an action.
How It Works
Simple in.
Specific out.

Two paths in. One outcome: a palette composed for your home, with the reasoning and specification to back it up.

The two paths
Path A: Start with an image
1.Upload a photograph: a room, painting, textile, or landscape you love
2.Madder extracts 6 dominant colors from the image and maps them against the full palette library
3.Results ranked by color compatibility: Strong match, Compatible, or Related
4.A historical color match surfaces: a documented pigment and era that shares the same register
5.Select a palette and unlock the full specification
Path B: Start with your house type
1.Select from 12 house types: Traditional, Craftsman, Cottage, Mid-Century, Contemporary, Farmhouse, Coastal, Madder House, Southern Vernacular, Flemish, Spanish Colonial, or Nordic
2.Madder introduces the palette world for that architecture: its history, character, and what to look for
3.Free palettes are available for each house type. The full library unlocks with a purchase.
4.Select a palette to see its full narrative, color roles, and spec layer
5.Download your specification sheet
Historical grounding

Every palette in the Madder library is grounded in architectural and material history. The 12 house types span the Georgian period through the 20th century, each with a documented lineage of designers, builders, and regional traditions that shaped how color was understood and used.

The palettes are not historical recreations. They are modern interpretations, composed for contemporary use within each architectural character. A Traditional palette draws on Georgian and Federal color logic but is calibrated for today's materials, lighting, and paints. A Craftsman palette understands what Stickley and Morris were doing with earth pigments and botanical color without asking you to live in a museum.

The image upload path surfaces this history directly: after extracting your colors, Madder identifies the historical pigment tradition closest to your palette: a documented era, material, and interior that shares the same color logic.

Using the image tool

Upload any image: a room you love, a painting, a piece of fabric, a travel photograph. The tool works best with images that have clear color character rather than very dark or blown-out photographs.

What happens
The image is processed locally in your browser. It never leaves your device. Madder samples 140140 pixels, groups similar colors, removes near-duplicates, and extracts 6 dominant hues. These are scored against every palette in the library by Euclidean color distance. Results are ranked: Strong match (85%+), Compatible (65%+), or Related.

Note: the image tool surfaces palettes that share the same color temperature and register as your image. It does not guarantee an exact visual match. Color relationships in rooms depend on light, material, and scale in ways no tool can fully predict. Use it as a starting point, not a verdict.

What you get
Free
Free palettes per house type
Access free palettes from every house type: narrative, color roles, and the palette story. Enough to understand the Madder system and decide if a house type world is right for you.
Palette narrative Color roles Image upload tool Historical matching
Standard: one-time purchase
Full palette library for your house type
Unlock all palettes for a selected house type. Each palette includes the complete specification layer: why it works, design intent, spatial strategy, compatible woods, materials, lighting, regional context, what to do with existing objects, renovation logic, and what kills it. Plus a printable paint specification sheet.
Full palette library Complete spec layer Paint specification sheet Material guidance Lighting notes
Curated sourcing package
Everything in Standard, plus The Edit: a curated selection of furniture, lighting, textiles, and objects sourced specifically for your palette. Each item is direct-linked for purchase. The package you hand to a designer, a contractor, or use to transform your space yourself. The difference between knowing what colors and knowing what to buy.
The Edit Sourced furniture Sourced lighting Sourced textiles Direct purchase links
What a full specification looks like
Understanding color roles
Every Madder palette is built from five roles. They are not just names, they are instructions for how much of each color to use, and where.
Lead
The dominant field. Walls, ceiling, largest surfaces. Sets the room's atmosphere before anything else is considered. Typically 50-70% of total color presence.
Support
The secondary color. Upholstery, curtains, or a secondary surface. It repeats enough that the Lead feels intentional. Typically 15-25% of the palette.
Bridge
The mediator. Sits between warm and cool, or between pale and dark. Makes the other colors feel related. Often in rugs, natural materials, or transitional surfaces.
Ground
The deepest note. Shadow, spine, authority. Without a Ground, a palette reads as pretty. With one, it reads as composed. Used sparingly: dark furniture, frames, a painted door.
Object
The accent. Appears once, precisely. A ceramic, a lamp, a piece of hardware. It should appear in one place only. The Object is what gives the room its point of view. Overuse it and the palette dissolves.

She Moves in Her Own Way. Contemporary / Reduced. An example of what Standard unlocks.

Contemporary / Reduced. Standard
She Moves in Her Own Way
She woke before the second alarm, the black cat already settled at the foot of the bed. The flat was small and not quite square, in a former factory building with plaster walls the color of milk and floorboards that still held the memory of heavier work. Outside, the city had been rinsed overnight, a cool shine on the pavement and a thin spring light working its way between buildings. The mirrored closet opened with a soft click. First the cream blouse, plain but exact, the kind that made her look more certain than she felt. Then the brown trousers, pulled on while sitting at the edge of the bed, one foot bare, one foot searching for the floor. The green coat came last, though it changed everything. It made the whole morning gather itself around her. She did not think any of this was style. She thought style was louder, richer, more public. This was only how she preferred things: the leather strap worn smooth by her hand, the red shoes by the door, the dark wool beret she nearly forgot and then chose without looking. A palette for rooms where taste arrived before vocabulary.
Color roles
Plaster Milk
LEAD
Bone Linen
SUPPORT
Limestone
SUPPORT
Warm Taupe
BRIDGE
Olive Smoke
GROUND
Flemish Red
OBJECT
Graphite Brown
GROUND
Why this works
Plaster Milk and Bone Linen sit close together in value. The room builds atmosphere through shadow and texture rather than contrast. Olive Smoke contains enough grey and brown to absorb into the wall field rather than read as a color statement. Flemish Red works because it appears once.
Spatial strategy
70% Plaster Milk and Bone Linen field 15% Limestone and Warm Taupe textile and stone bridge 10% Olive Smoke as primary character 4% Graphite Brown as grounding 1% Flemish Red as object accent. Matte finish throughout; no sheen above eggshell.
Compatible woods
Smoked oak, cerused oak, walnut, chestnut, aged pine. Medium-dark woods connect Warm Taupe to Graphite Brown. Avoid raw pale oak, which pulls the palette too Nordic. Avoid orange red oak without dark furniture to balance.
Lighting
24002700K only. Shaded table lamps, one indirect uplight. The palette fails under cool LEDs. The plaster goes flat and the olive turns grey. Needs shadow, pools of light, and uneven illumination.
What kills it
Bright white trim. Cool grey floors. Polished chrome. Glossy walls. Blonde wood everywhere. Making the red large. Failure symptoms: room feels flat, reads as beige rather than atmospheric, furniture floats, lighting feels clinical.
If renovated
If smooth drywall has replaced plaster: limewash paint is the minimum. In open-plan spaces: keep Plaster Milk consistent throughout, ground through furniture mass rather than color change.
Paint specification
Role
Color
Code
Finish
LEAD
Plaster Milk
See brand source
See spec
SUPPORT
Bone Linen
See brand source
See spec
SUPPORT
Limestone
See brand source
See spec
BRIDGE
Warm Taupe
See brand source
See spec
GROUND
Olive Smoke
See brand source
See spec
OBJECT
Flemish Red
See brand source
See spec
GROUND
Graphite Brown
See brand source
See spec
Design intent, materials, lighting, regional context, lifestyle compatibility, art compatibility, and more.
Unlock the full spec with Standard
This is a Standard palette specification. Advanced tier also includes anchor objects with direct sourcing links: furniture, lighting, textiles, and accessories selected specifically for this palette.
Find Your Palette
Begin with your home.
Madder works from the inside out, starting with the character of your home rather than a color card. Select your house type to find palettes built for your architecture.
Or: start with an image
Have a room, painting, or textile you love?
Upload any photograph. Madder extracts the dominant colors and surfaces the palettes that share the same DNA.
Select your house type
Begin with your architecture
Know your architecture? Choose from 12 house types: Traditional, Craftsman, Coastal, Flemish, and more. Each type has been assigned a curated world of palettes composed specifically for its materials, proportions, light quality, and regional history.
Best for: when you know your house type and want the full curated experience: narrative, material guidance, spec sheet, and The Edit.
Select below
What kind of house
is this, really?
The moment you choose, the field narrows. We'll guide you toward a curated color world shaped for your architecture, mood, and way of living.
Selected
,
Uploaded image
Extracted colors
Your extracted palette
Untitled Palette
Color world
Color roles
Historical reference
MADDER
Step 2: Your Palette World
The Traditional World.
Traditional homes carry history easily: framed openings, proper trim, rooms with identity, and a natural sense of ceremony. The palettes below are composed for that architecture. Three are yours to explore freely. The full Madder-curated library unlocks with access.
Unlock the full Madder world. All curated palettes for your house type, plus material guidance, room direction, a considered shopping edit, and your specification sheet.
Your selection
,
,
Madder Full Access
Unlock the full
Madder world.
  • All Madder-curated color palettes for your house type
  • Full material and finish guidance
  • Room-by-room direction and palette logic
  • The Edit: furniture, lighting, textiles, objects
  • Trusted brands, sourcing, and specification sheet
Your House Type: Full Library
,
Explore the full Madder-curated library for your house type.
A considered selection of pieces
that belong in this world.
1015 items. Tightly edited. Grouped by role, not category. Images coming as the library grows.
"Intentional choices. Lasting world."
Sample physically before committing. This specification is a starting point.
'; } // Historical reference var histHtml = ''; if (window._imgpalHistMatch) { var h = window._imgpalHistMatch; var swatchBar = (h.colors||[]).map(function(c){ return c.hex; }).join(', '); var gradH = 'linear-gradient(to right,' + (h.colors||[]).map(function(c,i,a){ var p = Math.round(i/a.length*100); var p2 = Math.round((i+1)/a.length*100); return c.hex+' '+p+'% '+p2+'%'; }).join(',') + ')'; histHtml = '
' + '
Historical reference
' + '
' + '
' + h.era + '
' + '
' + h.title + '
' + '
' + h.pigment + '
' + '
' + h.body + '
' + '
'; } html = html.replace('', houseMatchesHtml + histHtml + ''); var w = window.open('', '_blank'); if (w) { w.document.write(html); w.document.close(); setTimeout(function(){ w.print(); }, 400); } } function switchVariant(id) { document.querySelectorAll('.imgpal-var-tab').forEach(function(t) { t.classList.toggle('active', t.textContent.trim().toLowerCase().replace(' ','') === id.replace('v1','full').replace('v2','reduced').replace('v3','leadswap').replace('v4', t.textContent.trim().toLowerCase().replace(' ',''))); }); // Simpler: match by onclick attr document.querySelectorAll('.imgpal-var-tab').forEach(function(t) { t.classList.remove('active'); if (t.getAttribute('onclick') && t.getAttribute('onclick').indexOf(id) !== -1) t.classList.add('active'); }); document.querySelectorAll('.imgpal-var-panel').forEach(function(p) { p.classList.toggle('active', p.id === 'vp-' + id); }); } function unlockImageSpec() { // Remove blur and hide overlay var gated = document.getElementById('imgpalGatedContent'); var overlay = document.getElementById('imgpalGateOverlay'); if (gated) gated.classList.remove('imgpal-gate-blur'); if (overlay) overlay.style.display = 'none'; // Payment gate — unlock for beta } function saveCustomPalette() { var nameEl = document.getElementById('imgpalNameInput'); var narrativeEl = document.getElementById('imgpalNarrative'); var statusEl = document.getElementById('imgpalSaveStatus'); var paletteName = (nameEl && nameEl.value.trim()) || 'Untitled Palette'; var narrative = (narrativeEl && narrativeEl.value.trim()) || ''; // Build the email message var roles = document.querySelectorAll('.imgpal-role'); // Build comprehensive email with all 4 variants var lines = [ 'MADDER — USER IMAGE PALETTE SUBMISSION', '======================================', 'Name: ' + paletteName, 'Generated: ' + new Date().toISOString(), '', 'RAW EXTRACTED COLORS:', IMG_EXTRACTED.join(', '), '' ]; // Add each variant var variantData = window._imgpalVariants || []; variantData.forEach(function(v) { lines.push('--- ' + v.label.toUpperCase() + ' ---'); v.colors.forEach(function(c) { lines.push(' ' + c.role.padEnd(8) + ' ' + c.hex.toUpperCase() + (c.name && c.name !== c.hex.toUpperCase() ? ' (' + c.name + ')' : '') + (c.src ? ' [' + c.src + ']' : '')); }); lines.push(''); }); // Historical reference if (window._imgpalHistMatch) { var h = window._imgpalHistMatch; lines.push('HISTORICAL REFERENCE: ' + h.title); lines.push('Era: ' + h.era); lines.push('Pigment: ' + h.pigment); lines.push(''); } if (narrative) { lines.push('DESIGN STORY:'); lines.push(narrative); lines.push(''); } var msg = lines.join('\n'); if (EMAILJS_SERVICE_ID === 'YOUR_SERVICE_ID') { console.log('EmailJS not configured. Custom palette:\n', msg); if (statusEl) { statusEl.textContent = '✓ Palette "' + paletteName + '" saved. Add EmailJS credentials to enable email.'; statusEl.style.color = 'var(--soot)'; } return; } emailjs.init(EMAILJS_PUBLIC_KEY); emailjs.send(EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_ID, { to_email: 'hello.madder.house@gmail.com', subject: 'Madder custom palette: ' + paletteName, message: msg }).then(function() { if (statusEl) { statusEl.textContent = '✓ "' + paletteName + '" sent to Madder for review.'; statusEl.style.color = 'var(--soot)'; } }).catch(function(e) { console.error('EmailJS:', e); if (statusEl) statusEl.textContent = 'Saved locally — email send failed.'; }); } function proceedWithImagePalette() { var colors = IMG_EXTRACTED; if (!colors || !colors.length) return; // Assign roles by percentage (based on position = frequency rank from extraction) // Weights approximate: lead ~40%, ground ~25%, support ~15%, bridge ~12%, object ~8% var roleDefs = [ { role: 'Lead', pct: 40 }, { role: 'Ground', pct: 25 }, { role: 'Support', pct: 15 }, { role: 'Bridge', pct: 12 }, { role: 'Object', pct: 8 }, ]; var assigned = colors.slice(0, 5).map(function(hex, i) { var rd = roleDefs[i] || { role: 'Accent', pct: 5 }; return { hex: hex, role: rd.role, pct: rd.pct }; }); // If fewer than 5 colors, redistribute percentages var total = assigned.reduce(function(s, c) { return s + c.pct; }, 0); assigned.forEach(function(c) { c.pct = Math.round(c.pct / total * 100); }); // Build Wada grid using buildWada with assigned colors var wadaColors = assigned.map(function(c) { return { hex: c.hex, role: c.role, name: c.hex.toUpperCase() }; }); var wada = buildWada(wadaColors, true); var wadaEl = document.getElementById('imgpalWada'); if (wadaEl) { // Apply grid styles from buildWada var gsStr = wada.gs; var gsMatch = gsStr.match(/grid-template-columns:([^;]+)/); var grMatch = gsStr.match(/grid-template-rows:([^;]+)/); if (gsMatch) wadaEl.style.gridTemplateColumns = gsMatch[1].trim(); if (grMatch) wadaEl.style.gridTemplateRows = grMatch[1].trim(); else wadaEl.style.gridTemplateRows = '1fr'; wadaEl.innerHTML = wada.cells; } // Roles table var ROLE_DEFS = { 'Lead': 'The dominant color. Covers the most surface — walls, large pieces, flooring. Sets the room\u2019s fundamental register.', 'Ground': 'The dark anchor. Furniture, frames, hardware, painted woodwork. Gives the palette weight.', 'Support': 'The secondary field. Appears in secondary upholstery, drapery lining, or a painted surface. Extends the palette without competing.', 'Bridge': 'The mediating tone. Sits between Lead and Ground, resolving tension. Often a mid-toned textile, rug, or secondary wall.', 'Object': 'The accent. Used once, precisely — a ceramic, a cushion, a single painted detail. The color that gives the palette its personality.', }; var rolesTableEl = document.getElementById('imgpalRolesTable'); if (rolesTableEl) { var rows = '
Color roles & application
'; assigned.forEach(function(c) { var roleColor = c.role === 'Lead' ? '#C4A44A' : 'var(--mid)'; rows += '
' + '
' + '
' + '
' + c.role.toUpperCase() + '
' + '
' + c.pct + '%
' + '
' + '
' + '
' + c.hex.toUpperCase() + '
' + '
' + (ROLE_DEFS[c.role] || '') + '
' + '
' + '
'; }); rolesTableEl.innerHTML = rows; } // Color strip summary var swRow = document.getElementById('imgpalSwRow'); var swNames = document.getElementById('imgpalSwNames'); if (swRow) swRow.innerHTML = assigned.map(function(c) { return '
'; }).join(''); if (swNames) swNames.innerHTML = assigned.map(function(c) { return '
' + c.hex.toUpperCase() + '
'; }).join(''); // Title stays as typed name or default var titleEl = document.getElementById('imgpalTitle'); if (titleEl) titleEl.textContent = 'Untitled Palette'; // Store assigned globally for PDF window._imgpalAssigned = assigned; // ── HOUSE TYPE MATCHING ────────────────────────────────────────────────── // Score each house type's colorPool against extracted colors function hexDist(h1, h2) { var r1=parseInt(h1.slice(1,3),16), g1=parseInt(h1.slice(3,5),16), b1=parseInt(h1.slice(5,7),16); var r2=parseInt(h2.slice(1,3),16), g2=parseInt(h2.slice(3,5),16), b2=parseInt(h2.slice(5,7),16); return Math.sqrt((r1-r2)*(r1-r2)+(g1-g2)*(g1-g2)+(b1-b2)*(b1-b2)); } var houseScores = HOUSE_TYPES.map(function(ht) { // For each extracted color, find closest color in this house type's pool var totalScore = 0; var weights = [3,2,1.5,1,1,0.5]; colors.forEach(function(extHex, i) { var w = weights[i] || 0.5; var minDist = Math.min.apply(null, (ht.colorPool||[]).map(function(poolHex) { return hexDist(extHex, poolHex); })); // Convert distance to score (0-1, lower distance = higher score) var score = Math.max(0, 1 - minDist/300); totalScore += score * w; }); return { ht: ht, score: totalScore }; }); houseScores.sort(function(a,b){ return b.score-a.score; }); // Top 3 house types var topHouses = houseScores.slice(0,1); var maxHouseScore = topHouses[0].score || 1; // Historical anecdote → house type mapping var HIST_HOUSE_MAP = { "The Stone Blue Drawing Room": ["traditional","historic-coastal"], "The Four-Color Palette": ["traditional","southern-vernacular"], "The Falun Mine Color": ["scandinavian","farmhouse"], "Morris's Palette": ["craftsman","cottage"], "The Riad Color Logic": ["spanish-colonial","madder-house"], "The Compressed Flemish Interior": ["flemish","contemporary"], "Milk Paint and Earth Pigments": ["traditional","southern-vernacular"], "The Gustavian Blue-Grey": ["scandinavian","contemporary"] }; // ── SCORE HISTORICAL PIGMENTS AGAINST DOMINANT EXTRACTED COLOR ────────── // Uses HISTORICAL_PIGMENTS master library (26 documented pigments with sources) // Matches by hue proximity to dominant extracted color — not palette anecdotes // Helper: hue distance in degrees (0-180) function hueOf(hex) { var r=parseInt(hex.slice(1,3),16)/255, g=parseInt(hex.slice(3,5),16)/255, b=parseInt(hex.slice(5,7),16)/255; var mx=Math.max(r,g,b), mn=Math.min(r,g,b), d=mx-mn, hue=0; if(d>0.001){ if(mx===r) hue=((g-b)/d+6)%6; else if(mx===g) hue=(b-r)/d+2; else hue=(r-g)/d+4; hue*=60; } return hue; } function hueDist(h1,h2){ var d=Math.abs(hueOf(h1)-hueOf(h2)); return Math.min(d,360-d); } var dominant = colors[0]; var pigmentScores = HISTORICAL_PIGMENTS.map(function(p) { var hd = hueDist(dominant, p.hex); var hueScore = Math.max(0, 1 - hd / 180); var rgbScore = Math.max(0, 1 - hexDist(dominant, p.hex) / 250); return { p: p, score: hueScore * 0.65 + rgbScore * 0.35 }; }); pigmentScores.sort(function(a,b){ return b.score - a.score; }); // Pick top 2 from different house type families var pickedPigments = []; var usedFamilies = {}; pigmentScores.forEach(function(ps) { if (pickedPigments.length >= 2) return; var familyKey = ps.p.houses[0] || ps.p.id; if (!usedFamilies[familyKey]) { pickedPigments.push(ps.p); usedFamilies[familyKey] = true; } }); // Format pickedHist to match the render format (house types via HOUSE_TYPES) var pickedHist = pickedPigments.map(function(p) { return { title: p.name, era: p.era, pigment: p.pigment, body: p.body, hex: p.hex, source: p.source, houses: p.houses, colors: [{ hex: p.hex, role: p.role, name: p.name }] }; }); var bestHist = pickedHist[0] ? { title: pickedHist[0].title, era: pickedHist[0].era, pigment: pickedHist[0].pigment, body: pickedHist[0].body, hex: pickedHist[0].hex, colors: pickedHist[0].colors } : null; // ── RENDER HISTORICAL REFERENCE CARDS ──────────────────────────────────── var histWrap = document.getElementById('imgpalHistWrap'); if (histWrap && pickedHist.length) { histWrap.style.display = 'block'; histWrap.innerHTML = pickedHist.map(function(h, hi) { // Find the closest single color in this anecdote to the dominant extracted color var palColors = h.colors || [{hex:h.hex, name:h.title}]; var closestColor = palColors.reduce(function(best, c) { var d = hexDist(colors[0], c.hex); return d < best.d ? {c:c, d:d} : best; }, {c:palColors[0], d:9999}).c; var swatchLum = lumOf(closestColor.hex); var swatchTc = swatchLum > 0.45 ? '#383028' : '#F0EAE0'; var houseTypes = HIST_HOUSE_MAP[h.title] || []; // Find missing colors: what colors does this tradition have that the user doesn't? var suggestions = palColors.filter(function(pc) { var tooClose = colors.some(function(ext) { return hexDist(ext, pc.hex) < 80; }); return !tooClose && pc.role !== 'Lead'; // don't suggest what they already have }).slice(0, 2); var suggHtml = suggestions.length ? '
' + '
Colors from this tradition not in your palette
' + '
' + suggestions.map(function(s) { var lum = lumOf(s.hex); var tc = lum > 0.45 ? '#383028' : '#F0EAE0'; return '
' + '
' + '
' + s.role + '
' + '
' + s.hex.toUpperCase() + '
' + '
'; }).join('') + '
' : ''; // Build house type blocks var htBlocks = houseTypes.map(function(id) { var ht = HOUSE_TYPES.find(function(t){ return t.id===id; }); if (!ht) return ''; return '
' + '
' + ht.name + ' — ' + ht.period + '
' + '
' + (ht.desc||'') + '
' + '
In this tradition, color followed material logic: the dominant pigment covered walls and large surfaces, darker earth tones appeared in woodwork, furniture frames, and hardware, mid-tones lived in soft furnishings and secondary surfaces, and one concentrated note — often the most expensive pigment — was used once and precisely. The palette was rarely decorative. It was structural.
' + '
'; }).join(''); return '
' + '
' + '
Historical pigment
' + '
' + '
' + '
' + '
' + h.era + '
' + '
' + h.pigment + '
' + '
' + closestColor.hex.toUpperCase() + '
' + '
' + '
' + '
' + h.body + '
' + (h.source ? '
Source: ' + h.source + '
' : '') + '
' + htBlocks + '
'; }).join(''); } window._imgpalHistMatch = bestHist; // COLOR_NOTES no longer used — single swatch display var COLOR_NOTES_UNUSED = { 'Lead': 'The dominant surface. Apply to walls or the largest furniture piece. This is the color the room lives inside.', 'Ground': 'The dark anchor. Use in painted woodwork, furniture frames, hardware, or one dominant dark piece.', 'Support': 'The secondary field. Curtains, secondary upholstery, or a painted piece that is not the focal point.', 'Bridge': 'The mediating tone. Found in rugs, mid-toned textiles, or a floor tone that connects light and dark.', 'Object': 'The single accent. One ceramic, one cushion, one painted detail. Appears once and is not repeated.', }; var histNotesEl = document.getElementById('imgpalHistColorNotes'); if (histNotesEl && bestHist && bestHist.colors) { var hd = '
How to use ' + bestHist.title + '
'; var colorCells = '
'; bestHist.colors.forEach(function(c) { var lum = (parseInt(c.hex.slice(1,3),16)*299 + parseInt(c.hex.slice(3,5),16)*587 + parseInt(c.hex.slice(5,7),16)*114) / 255000; var tc = lum > 0.45 ? 'var(--soot)' : 'var(--bone)'; var roleColor = c.role === 'Lead' ? '#C4A44A' : 'var(--mid)'; colorCells += '
' + '
' + '
' + c.role.toUpperCase() + '
' + '
' + c.name + '
' + '
' + c.hex.toUpperCase() + '
' + '
' + (COLOR_NOTES[c.role] || '') + '
' + '
'; }); colorCells += '
'; histNotesEl.innerHTML = hd + colorCells; histNotesEl.style.display = 'block'; } // ── ADJACENT COLORWAYS ─────────────────────────────────────────────────── // Analyze what the extracted palette is missing and suggest additions function rgbFromHex(h) { return [parseInt(h.slice(1,3),16), parseInt(h.slice(3,5),16), parseInt(h.slice(5,7),16)]; } function getLumFromHex(h) { var c = rgbFromHex(h); return (c[0]*299 + c[1]*587 + c[2]*114) / 255000; } function getSat(h) { var c = rgbFromHex(h); var max = Math.max.apply(null,c), min = Math.min.apply(null,c); return max === 0 ? 0 : (max-min)/max; } var lums = colors.map(getLumFromHex); var avgLum = lums.reduce(function(a,b){return a+b;},0)/lums.length; var hasDark = lums.some(function(l){ return l < 0.25; }); var hasLight = lums.some(function(l){ return l > 0.70; }); var hasMid = lums.some(function(l){ return l >= 0.35 && l <= 0.65; }); var avgSat = colors.map(getSat).reduce(function(a,b){return a+b;},0)/colors.length; var adjacent = []; // Missing light/cream? if (!hasLight) { adjacent.push({ hex: '#EDE8DC', name: 'Bone White', role: 'Ground or trim', note: 'Your palette has no light field. A warm off-white here would give the eye somewhere to rest and make the darker tones read more deliberately.' }); } // Missing dark anchor? if (!hasDark) { adjacent.push({ hex: '#2A2620', name: 'Near Black', role: 'Anchor', note: 'Without a dark anchor the palette can feel ungrounded. A near-black in furniture or woodwork will give it weight and definition.' }); } // Very saturated — suggest a neutral bridge if (avgSat > 0.4 && !hasMid) { adjacent.push({ hex: '#B8AA98', name: 'Warm Stone', role: 'Bridge', note: 'Your colors are vivid. A warm mid-toned stone or putty between the saturated notes will prevent the palette from feeling restless.' }); } // If has earth tones — suggest a botanical/green var hasGreen = colors.some(function(h){ var c=rgbFromHex(h); return c[1]>c[0]&&c[1]>c[2]&&c[1]>80; }); var hasRed = colors.some(function(h){ var c=rgbFromHex(h); return c[0]>c[1]+40&&c[0]>c[2]+40; }); if (hasRed && !hasGreen) { adjacent.push({ hex: '#7B8461', name: 'Sage', role: 'Botanical note', note: 'A desaturated green would cool and balance the warm tones without competing. Think of it as the garden brought inside.' }); } // Suggest using the historical lead color as a reference if (bestHist && bestHist.colors && bestHist.colors.length) { var histLead = bestHist.colors.find(function(c){ return c.role==='Lead'; }) || bestHist.colors[0]; var alreadyClose = colors.some(function(h){ return hexDist(h,histLead.hex)<80; }); if (!alreadyClose) { adjacent.push({ hex: histLead.hex, name: histLead.name + ' (historical lead)', role: 'Historical reference', note: 'The closest historical palette used ' + histLead.name + ' as its dominant. Incorporating this tone anchors your palette in a documented color tradition.' }); } } // If very few warm tones, suggest a warm accent var hasWarm = colors.some(function(h){ var c=rgbFromHex(h); return c[0]>c[2]+30&&getLumFromHex(h)>0.25&&getLumFromHex(h)<0.7; }); if (!hasWarm && colors.length > 1) { adjacent.push({ hex: '#C4943A', name: 'Yellow Ochre', role: 'Warm accent', note: 'A warm ochre or amber note would introduce light and life without disrupting the palette character. Used once — a lamp, a ceramic, a textile.' }); } // ── COMPOSED PALETTE ───────────────────────────────────────────────────── // ALL extracted colors + ALL adjacent suggestions merged into one palette. // No color is dropped. Roles assigned by luminosity + saturation profile. var ROLE_USAGE = { 'Lead': { pct: 0, why: 'The dominant field. This color covers the most surface area — walls, or the largest furniture piece. It sets the register that everything else responds to.' }, 'Ground': { pct: 0, why: 'The dark anchor. Applied in painted woodwork, furniture frames, hardware, or one dominant dark piece. Without it the palette floats.' }, 'Support': { pct: 0, why: 'The secondary field. Appears in curtains, a secondary upholstered piece, or a painted surface that is present but not the focal point.' }, 'Bridge': { pct: 0, why: 'The mediating tone. Found in rugs, mid-toned textiles, or a floor tone. It connects the light and dark registers and prevents visual tension.' }, 'Object': { pct: 0, why: 'The single accent. Used once and precisely — one ceramic, one cushion, one painted detail. It is the color that gives the palette its character.' }, 'Accent': { pct: 0, why: 'An additional accent tone. Introduce it sparingly through a textile, object, or painted detail.' }, }; function lumOf(h) { var r=parseInt(h.slice(1,3),16), g=parseInt(h.slice(3,5),16), b=parseInt(h.slice(5,7),16); return (r*299+g*587+b*114)/255000; } function satOf(h) { var r=parseInt(h.slice(1,3),16), g=parseInt(h.slice(3,5),16), b=parseInt(h.slice(5,7),16); var max=Math.max(r,g,b), min=Math.min(r,g,b); return max===0 ? 0 : (max-min)/max; } // Build merged pool: all extracted + all adjacent, deduped by proximity var pool = []; var allHexes = colors.slice(); adjacent.forEach(function(a){ allHexes.push(a.hex); }); allHexes.forEach(function(hex) { var tooClose = pool.some(function(p){ var dr=parseInt(hex.slice(1,3),16)-parseInt(p.hex.slice(1,3),16); var dg=parseInt(hex.slice(3,5),16)-parseInt(p.hex.slice(3,5),16); var db=parseInt(hex.slice(5,7),16)-parseInt(p.hex.slice(5,7),16); return Math.sqrt(dr*dr+dg*dg+db*db) < 30; }); if (!tooClose) { // Find name from extracted or adjacent var adjMatch = adjacent.find(function(a){ return a.hex===hex; }); var name = adjMatch ? adjMatch.name : hex.toUpperCase(); var src = adjMatch ? 'suggested' : 'extracted'; pool.push({ hex:hex, name:name, src:src, lum:lumOf(hex), sat:satOf(hex) }); } }); // Historical lead — always included, tagged var histLead = bestHist && bestHist.colors ? (bestHist.colors.find(function(c){ return c.role==='Lead'; }) || bestHist.colors[0]) : null; if (histLead) { var alreadyIn = pool.some(function(p){ var dr=parseInt(histLead.hex.slice(1,3),16)-parseInt(p.hex.slice(1,3),16); var dg=parseInt(histLead.hex.slice(3,5),16)-parseInt(p.hex.slice(3,5),16); var db=parseInt(histLead.hex.slice(5,7),16)-parseInt(p.hex.slice(5,7),16); return Math.sqrt(dr*dr+dg*dg+db*db) < 40; }); if (!alreadyIn) { pool.unshift({ hex:histLead.hex, name:histLead.name, src:'historical', lum:lumOf(histLead.hex), sat:satOf(histLead.hex) }); } else { // Mark closest existing as historical var closest = pool.reduce(function(best,p){ var dr=parseInt(histLead.hex.slice(1,3),16)-parseInt(p.hex.slice(1,3),16); var dg=parseInt(histLead.hex.slice(3,5),16)-parseInt(p.hex.slice(3,5),16); var db=parseInt(histLead.hex.slice(5,7),16)-parseInt(p.hex.slice(5,7),16); var d=Math.sqrt(dr*dr+dg*dg+db*db); return d < best.d ? {p:p, d:d} : best; }, {p:null, d:999}).p; if (closest) { closest.name = histLead.name; closest.src = 'historical'; } } } // Sort pool: darkest first (for role assignment) var sorted = pool.slice().sort(function(a,b){ return a.lum - b.lum; }); // Assign roles based on luminosity bands across the full set var roleOrder = ['Lead','Ground','Support','Bridge','Object','Accent','Accent','Accent']; // Lead = most visually dominant (first extracted, or historical if present) // Ground = darkest // Support, Bridge = mid tones // Object = most saturated remaining // Extra colors get Accent var composedColors = []; var used = {}; // Lead: historical lead first, else most frequent (first extracted) var leadEntry = pool.find(function(p){ return p.src==='historical'; }) || pool[0]; if (leadEntry) { composedColors.push({ hex:leadEntry.hex, name:leadEntry.name, role:'Lead', src:leadEntry.src }); used[leadEntry.hex] = true; } // Ground: darkest unused var groundEntry = sorted.find(function(p){ return !used[p.hex]; }); if (groundEntry) { composedColors.push({ hex:groundEntry.hex, name:groundEntry.name, role:'Ground', src:groundEntry.src }); used[groundEntry.hex] = true; } // Object: most saturated unused (before filling mid-tones, so accent gets a slot) var bysat = pool.slice().sort(function(a,b){ return b.sat-a.sat; }); var objEntry = bysat.find(function(p){ return !used[p.hex]; }); if (objEntry) { composedColors.push({ hex:objEntry.hex, name:objEntry.name, role:'Object', src:objEntry.src }); used[objEntry.hex] = true; } // Bridge: lightest unused (cream/neutral if present) var bylight = pool.slice().sort(function(a,b){ return b.lum-a.lum; }); var bridgeEntry = bylight.find(function(p){ return !used[p.hex]; }); if (bridgeEntry) { composedColors.push({ hex:bridgeEntry.hex, name:bridgeEntry.name, role:'Bridge', src:bridgeEntry.src }); used[bridgeEntry.hex] = true; } // Support: first mid-lum unused var midEntry = pool.find(function(p){ return !used[p.hex] && p.lum>=0.25 && p.lum<=0.65; }) || pool.find(function(p){ return !used[p.hex]; }); if (midEntry) { composedColors.push({ hex:midEntry.hex, name:midEntry.name, role:'Support', src:midEntry.src }); used[midEntry.hex] = true; } // All remaining get Accent role (preserves every color) pool.forEach(function(p) { if (!used[p.hex]) { composedColors.push({ hex:p.hex, name:p.name, role:'Accent', src:p.src }); used[p.hex] = true; } }); // Recalculate percentages based on role (Lead biggest, tailing off) var rolePcts = { 'Lead':35, 'Ground':20, 'Support':16, 'Bridge':13, 'Object':10, 'Accent':6 }; var totalPct = composedColors.reduce(function(s,c){ return s+(rolePcts[c.role]||5); }, 0); composedColors.forEach(function(c) { c.pct = Math.round((rolePcts[c.role]||5)/totalPct*100); if (!ROLE_USAGE[c.role]) ROLE_USAGE[c.role] = ROLE_USAGE['Accent']; }); // ── BUILD VARIATION PALETTES ────────────────────────────────────────────── // Helper: assign roles to any array of color entries, returning new array function assignRoles(entries) { var order = ['Lead','Ground','Object','Bridge','Support','Accent','Accent','Accent','Accent']; // Lead = first (highest priority) // Ground = darkest remaining // Object = most saturated remaining // Bridge = lightest remaining // Support = best mid remaining // Accent = everything else var pool = entries.map(function(e){ return Object.assign({},e); }); var result = []; var used = {}; // Lead: first entry (historical or most frequent) result.push(Object.assign({}, pool[0], {role:'Lead'})); used[pool[0].hex] = true; // Ground: darkest unused var byDark = pool.filter(function(p){ return !used[p.hex]; }).sort(function(a,b){ return a.lum-b.lum; }); if (byDark.length) { result.push(Object.assign({},byDark[0],{role:'Ground'})); used[byDark[0].hex]=true; } // Object: most saturated unused var bySat = pool.filter(function(p){ return !used[p.hex]; }).sort(function(a,b){ return b.sat-a.sat; }); if (bySat.length) { result.push(Object.assign({},bySat[0],{role:'Object'})); used[bySat[0].hex]=true; } // Bridge: lightest unused var byLight = pool.filter(function(p){ return !used[p.hex]; }).sort(function(a,b){ return b.lum-a.lum; }); if (byLight.length) { result.push(Object.assign({},byLight[0],{role:'Bridge'})); used[byLight[0].hex]=true; } // Support: mid-lum unused var midPool = pool.filter(function(p){ return !used[p.hex] && p.lum>=0.25 && p.lum<=0.65; }); if (!midPool.length) midPool = pool.filter(function(p){ return !used[p.hex]; }); if (midPool.length) { result.push(Object.assign({},midPool[0],{role:'Support'})); used[midPool[0].hex]=true; } // Accents: all remaining pool.forEach(function(p){ if(!used[p.hex]){ result.push(Object.assign({},p,{role:'Accent'})); used[p.hex]=true; } }); // Assign percentages var rolePcts = { Lead:35, Ground:20, Support:16, Bridge:13, Object:10, Accent:6 }; var total = result.reduce(function(s,c){ return s+(rolePcts[c.role]||5); }, 0); result.forEach(function(c){ c.pct = Math.round((rolePcts[c.role]||5)/total*100); }); return result; } // ROLE_USAGE descriptions var ROLE_WHY = { Lead: 'The dominant field. Covers walls or the largest furniture piece. Sets the register everything else responds to.', Ground: 'The dark anchor. In painted woodwork, furniture frames, hardware, or one dominant dark piece. Without it the palette floats.', Support: 'The secondary field. In curtains, secondary upholstery, or a painted surface present but not focal.', Bridge: 'The mediating tone. In rugs, mid-toned textiles, or a floor tone connecting the light and dark registers.', Object: 'The single accent. Used once precisely — one ceramic, one cushion, one painted detail.', Accent: 'An additional tone. Introduce sparingly through a textile, object, or painted detail.', }; function buildVariantPanel(varColors, noteTitle, noteBody) { // Wada var wadaInput = varColors.map(function(c){ return { hex:c.hex, role:c.role, name:c.hex.toUpperCase() }; }); var cw = buildWada(wadaInput, true); var gsMatch = cw.gs.match(/grid-template-columns:([^;]+)/); var grMatch = cw.gs.match(/grid-template-rows:([^;]+)/); var wadaStyle = ''; if (gsMatch) wadaStyle += 'grid-template-columns:' + gsMatch[1].trim() + ';'; if (grMatch) wadaStyle += 'grid-template-rows:' + grMatch[1].trim() + ';'; // Color strip var strip = varColors.map(function(c){ return '
'; }).join(''); var names = varColors.map(function(c){ return '
'+c.hex.toUpperCase()+'
'; }).join(''); // Role table var rows = varColors.map(function(c) { var roleColor = c.role === 'Lead' ? '#C4A44A' : 'var(--mid)'; var srcLabel = c.src==='historical' ? 'Historical reference' : c.src==='suggested' ? 'Suggested addition' : 'From your image'; return '
' + '
' + '
' + '
'+c.role.toUpperCase()+'
' + '
'+c.pct+'%
' + '
' + '
' + '
'+c.hex.toUpperCase()+'
' + (c.name && c.name !== c.hex.toUpperCase() ? '
'+c.name+'
' : '') + '
'+(ROLE_WHY[c.role]||'')+'
' + '
'+srcLabel+'
' + '
' + '
'; }).join(''); return '
' + '
'+noteTitle+'
' + '
'+noteBody+'
' + '
' + '
'+cw.cells+'
' + '
'+strip+'
' + '
'+names+'
' + '
Color roles & how to use them
'+rows+'
'; } // ── VARIANT 1: Full — all colors, historical lead anchors ─────────────────── // Ensure histLead is genuinely at the front of v1pool var histEntry = pool.find(function(p){ return p.src==='historical'; }); var v1pool = histEntry ? [histEntry].concat(pool.filter(function(p){ return p.hex !== histEntry.hex; })) : pool.slice(); var v1 = assignRoles(v1pool); var v1Note = [ 'Full — historical lead', 'All colors from your image plus suggested additions, anchored by the closest historical pigment. ' + 'The historical lead sets the room register. Everything else — your extracted colors and additions — fills the supporting roles. ' + 'This version has the most tonal range and suits rooms with complex light or multiple zones.' ]; // ── VARIANT 2: Reduced — extracted colors only, no historical injection ───── var ranked = colors.map(function(hex, i) { var minDist = colors.reduce(function(min, other, j) { if (i===j) return min; return Math.min(min, hexDist(hex, other)); }, 999); return { hex:hex, lum:lumOf(hex), sat:satOf(hex), src:'extracted', name:hex.toUpperCase(), score: minDist * 1.5 + satOf(hex) * 60 + Math.abs(lumOf(hex)-0.5) * 50 }; }); ranked.sort(function(a,b){ return b.score - a.score; }); var v2pool = ranked.slice(0, Math.min(4, ranked.length)); var v2 = assignRoles(v2pool); var v2Note = [ 'Reduced — image only', 'The four most tonally distinct colors from your photograph. No historical injection, no additions — ' + 'just what the image contains, stripped to its essential relationships. ' + 'Good for rooms where the palette should feel drawn directly from a specific moment or place.' ]; // ── VARIANT 3: Lead swap — dominant EXTRACTED color leads, not historical ─── var extractedColors = pool.filter(function(p){ return p.src==='extracted'; }); var extractedFirst = extractedColors[0]; // most frequent/dominant from image var v3pool; if (extractedFirst && histEntry && extractedFirst.hex !== histEntry.hex) { // Swap: extracted dominant first, then all others, historical pushed to end v3pool = [extractedFirst] .concat(pool.filter(function(p){ return p.hex!==extractedFirst.hex && p.src!=='historical'; })) .concat(pool.filter(function(p){ return p.src==='historical'; })); } else { // No meaningful swap possible — use second extracted as lead var secondExtracted = extractedColors[1] || extractedColors[0]; v3pool = secondExtracted ? [secondExtracted].concat(pool.filter(function(p){ return p.hex!==secondExtracted.hex; })) : pool.slice(); } var v3 = assignRoles(v3pool); var v3LeadHex = v3pool[0] ? v3pool[0].hex.toUpperCase() : ''; var v3LeadName = histLead ? histLead.name : 'the historical color'; var v3Note = [ 'Lead swap — your image leads', v3LeadHex + ' — the dominant color from your photograph — takes the Lead role. ' + 'The historical reference moves to a supporting position. ' + 'The room feels grounded in what you actually photographed, not in a historical tradition. ' + 'Choose this if the historical lead reads as too neutral or too different from the image that drew you here.' ]; // ── VARIANT 4: Warm/Cool shift depending on palette character ───────────── var avgHue = (function() { var hues = pool.map(function(p) { var r=parseInt(p.hex.slice(1,3),16)/255, g=parseInt(p.hex.slice(3,5),16)/255, b=parseInt(p.hex.slice(5,7),16)/255; var max=Math.max(r,g,b), min=Math.min(r,g,b), d=max-min, h=0; if(d>0){ if(max===r) h=((g-b)/d+6)%6; else if(max===g) h=(b-r)/d+2; else h=(r-g)/d+4; h*=60; } return h; }); return hues.reduce(function(a,b){return a+b;},0)/hues.length; })(); var isCool = avgHue > 150 && avgHue < 300; // blue/green/purple range var v4pool = pool.slice(); var v4label, v4body, v4addHex, v4addName; if (isCool) { // Suggest a warm note injection v4addHex = '#C4943A'; v4addName = 'Yellow Ochre (warm injection)'; v4label = 'Warm shift — inject warmth'; v4body = 'Your palette is predominantly cool. Adding Yellow Ochre as Object introduces warmth without disrupting the cool field. ' + 'The room will feel less austere and more inviting, particularly under warm artificial light in the evening. ' + 'Use it in one ceramic, a lamp shade, or a single textile accent.'; } else { // Suggest a cool note injection v4addHex = '#6A80A0'; v4addName = 'Smalt Blue (cool injection)'; v4label = 'Cool shift — add a distance note'; v4body = 'Your palette runs warm. Adding a muted blue-grey as Object introduces visual distance and stops the room from feeling heavy. ' + 'It reads as shadow, coolness, or the colour seen through a window — not a decorating decision, but a material one. ' + 'Use it once: a ceramic, a textile, or a painted interior detail.'; } // Replace or add the shift color var alreadyIn = v4pool.some(function(p){ var dr=parseInt(p.hex.slice(1,3),16)-parseInt(v4addHex.slice(1,3),16); var dg=parseInt(p.hex.slice(3,5),16)-parseInt(v4addHex.slice(3,5),16); var db=parseInt(p.hex.slice(5,7),16)-parseInt(v4addHex.slice(5,7),16); return Math.sqrt(dr*dr+dg*dg+db*db) < 60; }); if (!alreadyIn) { // Swap out least distinct accent, or add v4pool = v4pool.filter(function(p){ return p.role !== 'Accent'; }).slice(0, 4); v4pool.push({ hex:v4addHex, name:v4addName, src:'suggested', lum:lumOf(v4addHex), sat:satOf(v4addHex) }); } var v4 = assignRoles(v4pool); var v4Note = [v4label, v4body]; // ── RENDER TABS + PANELS ────────────────────────────────────────────────── var variants = [ { id:'v1', label:'Full', colors:v1, note:v1Note }, { id:'v2', label:'Reduced', colors:v2, note:v2Note }, { id:'v3', label:'Lead swap', colors:v3, note:v3Note }, { id:'v4', label: isCool ? 'Warm shift' : 'Cool shift', colors:v4, note:v4Note }, ]; var tabsEl = document.getElementById('imgpalVarTabs'); var panelsEl = document.getElementById('imgpalVarPanels'); if (tabsEl && panelsEl) { tabsEl.innerHTML = variants.map(function(v, i) { return ''; }).join(''); panelsEl.innerHTML = variants.map(function(v, i) { return '
' + buildVariantPanel(v.colors, v.note[0], v.note[1]) + '
'; }).join(''); } // Intro var compIntro = document.getElementById('imgpalComposedIntro'); if (compIntro) { compIntro.textContent = 'Four variations built from your image colors and the suggested additions. Each reorganizes the same palette with a different intention. Switch between them to see how the room changes.'; } var composedWrap = document.getElementById('imgpalComposedWrap'); if (composedWrap) composedWrap.style.display = 'block'; window._imgpalComposed = v1; // Add missing-from-tradition colors to adjacent suggestions if (pickedHist.length) { pickedHist.forEach(function(h) { var palColors = h.colors || []; palColors.forEach(function(pc) { if (pc.role === 'Lead') return; // skip lead — already in historical reference var tooClose = colors.some(function(ext) { return hexDist(ext, pc.hex) < 80; }); var alreadyAdj = adjacent.some(function(a) { return hexDist(a.hex, pc.hex) < 60; }); if (!tooClose && !alreadyAdj) { adjacent.push({ hex: pc.hex, name: pc.name, role: pc.role + ' (from ' + h.era.split(',')[0] + ')', note: 'This color appears in ' + h.title + ' as ' + pc.role.toLowerCase() + '. It is absent from your extracted palette and worth considering as an addition from this tradition.' }); } }); }); } var adjEl = document.getElementById('imgpalAdjacentGrid'); var adjIntroEl = document.getElementById('imgpalAdjacentIntro'); var adjWrap = document.getElementById('imgpalAdjacentWrap'); if (adjEl && adjacent.length) { if (adjIntroEl) adjIntroEl.textContent = 'Based on what your palette contains and what is missing, here are colors worth considering. Each one addresses a specific gap — a light field, a dark anchor, a warm note, a cooling bridge.'; adjEl.innerHTML = adjacent.map(function(a) { var lum = getLumFromHex(a.hex); var tc = lum > 0.45 ? 'var(--soot)' : 'var(--bone)'; return '
' + '
' + '
' + '
' + a.role + '
' + '
' + a.hex.toUpperCase() + '
' + '
' + a.note + '
' + '
' + '
'; }).join(''); if (adjWrap) adjWrap.style.display = 'block'; } // Fire EmailJS with the extracted + role data var lines = ['MADDER — IMAGE PALETTE SUBMISSION', '']; assigned.forEach(function(c) { lines.push(c.role.padEnd(10) + ' ' + c.hex.toUpperCase() + ' (' + c.pct + '%)'); }); lines.push(''); lines.push('All extracted colors: ' + colors.join(', ')); lines.push('Generated: ' + new Date().toISOString()); var msg = lines.join('\n'); if (EMAILJS_SERVICE_ID === 'YOUR_SERVICE_ID') { console.log('EmailJS not configured. Palette submission:\n', msg); } else { emailjs.init(EMAILJS_PUBLIC_KEY); emailjs.send(EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_ID, { to_email: 'hello.madder.house@gmail.com', subject: 'Madder image palette: ' + (colors[0] || '').toUpperCase(), message: msg }).catch(function(e) { console.error('EmailJS:', e); }); } // Clear rename field var ni = document.getElementById('imgpalNameInput'); if (ni) ni.value = ''; var si = document.getElementById('imgpalSaveStatus'); if (si) si.textContent = ''; goTo('screen-img-palette'); } function selectImgrSwatch(hex, chipEl) { // Update centered swatch var swatch = document.getElementById('imgrDominantSwatch'); if (swatch) swatch.style.background = hex; var hexEl = document.getElementById('imgrDominantHex'); if (hexEl) { hexEl.textContent = hex.toUpperCase(); hexEl.style.color = textOnBg(hex); } // Update selected state on strip document.querySelectorAll('.imgr-chip').forEach(function(c) { c.classList.remove('selected'); }); if (chipEl) chipEl.classList.add('selected'); } function imgPickPalette(paletteId) { // Find house type var foundHouse = null; HOUSE_TYPES.forEach(function(ht) { if ((ALL_PALETTES[ht.id]||[]).find(function(p){ return p.id === paletteId; })) foundHouse = ht; }); if (foundHouse) STATE.selectedHouse = foundHouse.id; STATE.selectedPalette = paletteId; RETURN_TO_IMG_RESULTS = true; goTo('screen-palettes'); } var RETURN_TO_IMG_RESULTS = false;