← back to Designer Wallcoverings

onboarding/sangetsu-lilycolor/scripts/lexicon-colorway.py

84 lines

#!/usr/bin/env python3
"""Map a dominant hex -> nearest interior-designer-lexicon colorway NAME.
Local, $0. The lexicon is the working set from ~/.claude/skills/interior-designer/SKILL.md
(colorway vocabulary section). Each named colorway gets an approximate sRGB anchor; we pick
the nearest by weighted-Euclidean distance in RGB. This is a deterministic naming aid that
sits ALONGSIDE the VL's own free-text color name (we keep both)."""
import sys, json

# (name, (r,g,b)) — approximate sRGB anchors for the interior-designer colorway lexicon.
LEXICON = [
    # whites / off-whites
    ("Chalk",(245,244,240)),("Alabaster","#EDEADE"),("Cotton",(252,252,250)),
    ("Swiss Coffee","#EAE3D6"),("Ivory","#FFFFF0"),("Eggshell","#F0EAD6"),
    ("Bone","#E3DAC9"),("Cream","#FFFDD0"),("Linen","#FAF0E6"),("Porcelain","#F5F4F0"),
    ("Antique White","#FAEBD7"),
    # neutrals / greige
    ("Oatmeal","#E0D8C8"),("Flax","#EEDC82"),("Greige","#BEB6A6"),("Mushroom","#BDA697"),
    ("Putty","#C5B9A3"),("Stone","#B6ADA1"),("Wheat","#F5DEB3"),("Sand","#D9C9A8"),
    ("Almond","#EFDECD"),("Taupe","#8B8589"),("Pebble","#CFC8BB"),
    # tans / camels / browns
    ("Buff","#DAA06D"),("Honey","#D9A441"),("Tan","#D2B48C"),("Camel","#C19A6B"),
    ("Khaki","#BDB76B"),("Caramel","#AF6E4D"),("Pecan","#9C6B30"),("Cognac","#9A463D"),
    ("Chestnut","#954535"),("Walnut","#5C4033"),("Mocha","#3D2B1F"),("Espresso","#3B2F2F"),
    # greys
    ("Dove","#D7D2CB"),("Silver","#C0C0C0"),("Ash","#B2BEB5"),("Fog","#D6D6D1"),
    ("Smoke","#A6A6A6"),("Pewter","#8C8C8C"),("Slate","#708090"),("Zinc","#71797E"),
    ("Graphite","#41424C"),("Gunmetal","#2A3439"),("Charcoal","#36454F"),
    # blacks
    ("Ink","#1B1B1E"),("Noir","#0B0B0B"),("Onyx","#0F0F0F"),("Ebony","#1A1110"),
    ("Jet","#0A0A0A"),("Obsidian","#0D0D0D"),
    # greens
    ("Celadon","#ACE1AF"),("Pistachio","#93C572"),("Sage","#9CAF88"),("Eucalyptus","#A2C3A4"),
    ("Sea Glass","#C9E4DE"),("Moss","#8A9A5B"),("Fern","#4F7942"),("Olive","#808000"),
    ("Loden","#4A5D23"),("Juniper","#3A5311"),("Hunter","#355E3B"),("Forest","#228B22"),
    ("Emerald","#50C878"),("Verdigris","#43B3AE"),
    # blues
    ("Powder","#B6D0E2"),("Sky","#87CEEB"),("Wedgwood","#5C8DB8"),("Cornflower","#6495ED"),
    ("French Blue","#0072BB"),("Cerulean","#2A52BE"),("Denim","#1560BD"),("Slate Blue","#6A5ACD"),
    ("Teal","#008080"),("Peacock","#1B6C75"),("Prussian","#003153"),("Indigo","#3F00FF"),
    ("Navy","#1B2A4A"),("Midnight","#191970"),
    # yellows / golds
    ("Buttercream","#FDF1B8"),("Champagne","#F7E7CE"),("Maize","#FBEC5D"),("Citrine","#E4D00A"),
    ("Saffron","#F4C430"),("Amber","#FFBF00"),("Ochre","#CC7722"),("Mustard","#FFDB58"),
    ("Gold","#D4AF37"),("Brass","#B5A642"),
    # oranges / earths
    ("Apricot","#FBCEB1"),("Peach","#FFE5B4"),("Clay","#B66A50"),("Terracotta","#E2725B"),
    ("Persimmon","#EC5800"),("Pumpkin","#FF7518"),("Copper","#B87333"),("Rust","#B7410E"),
    ("Sienna","#882D17"),
    # reds / pinks
    ("Blush","#DE9DAC"),("Rose","#FF007F"),("Dusty Rose","#C09098"),("Coral","#FF7F50"),
    ("Cinnabar","#E34234"),("Brick","#8B3A2F"),("Oxblood","#4A0000"),("Claret","#7F1734"),
    ("Garnet","#733635"),("Burgundy","#800020"),("Crimson","#DC143C"),
    # purples
    ("Lavender","#B57EDC"),("Lilac","#C8A2C8"),("Wisteria","#C9A0DC"),("Orchid","#DA70D6"),
    ("Heather","#9E7E9E"),("Mauve","#E0B0FF"),("Amethyst","#9966CC"),("Plum","#8E4585"),
    ("Aubergine","#3D0734"),
    # metallics / leaf
    ("Platinum","#E5E4E2"),("Gilt","#CBA135"),("Bronze","#CD7F32"),
]

def _rgb(v):
    if isinstance(v, str):
        v = v.lstrip("#")
        return (int(v[0:2],16), int(v[2:4],16), int(v[4:6],16))
    return tuple(v)

ANCHORS = [(name, _rgb(rgb)) for name, rgb in LEXICON]

def nearest(hex_str):
    try:
        r,g,b = _rgb(hex_str)
    except Exception:
        return None
    # weighted euclidean (perceptual-ish: 0.30/0.59/0.11 luminance weights)
    best, bd = None, 1e18
    for name,(ar,ag,ab) in ANCHORS:
        d = 0.30*(r-ar)**2 + 0.59*(g-ag)**2 + 0.11*(b-ab)**2
        if d < bd:
            bd, best = d, name
    return best

if __name__ == "__main__":
    print(json.dumps({"colorway": nearest(sys.argv[1])}))