CoolFace
Apppublic

syanthan/stylesync-outfit-wizard

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
script.js380 linesDownload Raw Back to root
1 2document.addEventListener('DOMContentLoaded', function() {3    // Enhanced clothing data with categories4    const clothingData = {5        tops: [6            { id: 1, name: 'White Kurta', category: 'ethnic', image: 'http://static.photos/white/320x240/1', colors: ['#FFFFFF', '#F3F4F6'] },7            { id: 2, name: 'Blue Crop Top', category: 'casual', image: 'http://static.photos/blue/320x240/2', colors: ['#3B82F6', '#93C5FD'] },8            { id: 3, name: 'Black Hoodie', category: 'casual', image: 'http://static.photos/black/320x240/3', colors: ['#1F2937', '#4B5563'] },9            { id: 4, name: 'Red Shirt', category: 'formal', image: 'http://static.photos/red/320x240/4', colors: ['#EF4444', '#FCA5A5'] },10            { id: 5, name: 'Green Blouse', category: 'ethnic', image: 'http://static.photos/green/320x240/5', colors: ['#10B981', '#6EE7B7'] },11            { id: 6, name: 'Yellow T-shirt', category: 'casual', image: 'http://static.photos/yellow/320x240/6', colors: ['#F59E0B', '#FCD34D'] }12],13        bottoms: [14            { id: 1, name: 'Black Jeans', category: 'casual', image: 'http://static.photos/black/320x240/7', colors: ['#1F2937', '#4B5563'] },15            { id: 2, name: 'Blue Cargos', category: 'casual', image: 'http://static.photos/blue/320x240/8', colors: ['#3B82F6', '#93C5FD'] },16            { id: 3, name: 'White Leggings', category: 'casual', image: 'http://static.photos/white/320x240/9', colors: ['#FFFFFF', '#F3F4F6'] },17            { id: 4, name: 'Red Palazzos', category: 'ethnic', image: 'http://static.photos/red/320x240/10', colors: ['#EF4444', '#FCA5A5'] },18            { id: 5, name: 'Green Skirt', category: 'casual', image: 'http://static.photos/green/320x240/11', colors: ['#10B981', '#6EE7B7'] },19            { id: 6, name: 'Denim Shorts', category: 'casual', image: 'http://static.photos/blue/320x240/12', colors: ['#1E40AF', '#3B82F6'] }20]21    };22 23    // Initialize Swiper carousels24    const topsSwiper = new Swiper('#tops-container', {25        slidesPerView: 'auto',26        spaceBetween: 20,27        navigation: {28            nextEl: '.swiper-button-next',29            prevEl: '.swiper-button-prev',30        },31    });32 33    const bottomsSwiper = new Swiper('#bottoms-container', {34        slidesPerView: 'auto',35        spaceBetween: 20,36        navigation: {37            nextEl: '.swiper-button-next',38            prevEl: '.swiper-button-prev',39        },40    });41// State management42    let state = {43        selectedTop: null,44        selectedBottom: null,45        selectedTopColor: null,46        selectedBottomColor: null,47        suggestedColors: []48    };49 50    // DOM elements51    const topsContainer = document.getElementById('tops-container');52    const bottomsContainer = document.getElementById('bottoms-container');53    const outfitPreview = document.querySelector('custom-outfit-preview');54 55    // Render clothing items56    function renderClothingItems() {57        // Clear containers58        topsContainer.innerHTML = '';59        bottomsContainer.innerHTML = '';60 61        // Render tops62        clothingData.tops.forEach(top => {63            const topElement = document.createElement('div');64            topElement.className = `clothing-item rounded-lg overflow-hidden ${state.selectedTop?.id === top.id ? 'selected' : ''}`;65            topElement.innerHTML = `66                <div class="relative pb-[125%] bg-gray-100 rounded-lg">67                    <img src="${top.image}" alt="${top.name}" class="absolute h-full w-full object-cover">68                </div>69                <div class="mt-2">70                    <h3 class="font-medium text-gray-800">${top.name}</h3>71                    <div class="mt-1 flex">72                        ${top.colors.map(color => `73                            <div class="color-swatch ${state.selectedTopColor === color && state.selectedTop?.id === top.id ? 'active' : ''}" 74                                 style="background-color: ${color}" 75                                 data-item-id="${top.id}" 76                                 data-color="${color}" 77                                 data-type="top"></div>78                        `).join('')}79                    </div>80                </div>81            `;82            topsContainer.appendChild(topElement);83        });84 85        // Render bottoms86        clothingData.bottoms.forEach(bottom => {87            const bottomElement = document.createElement('div');88            bottomElement.className = `clothing-item rounded-lg overflow-hidden ${state.selectedBottom?.id === bottom.id ? 'selected' : ''}`;89            bottomElement.innerHTML = `90                <div class="relative pb-[125%] bg-gray-100 rounded-lg">91                    <img src="${bottom.image}" alt="${bottom.name}" class="absolute h-full w-full object-cover">92                </div>93                <div class="mt-2">94                    <h3 class="font-medium text-gray-800">${bottom.name}</h3>95                    <div class="mt-1 flex">96                        ${bottom.colors.map(color => `97                            <div class="color-swatch ${state.selectedBottomColor === color && state.selectedBottom?.id === bottom.id ? 'active' : ''}" 98                                 style="background-color: ${color}" 99                                 data-item-id="${bottom.id}" 100                                 data-color="${color}" 101                                 data-type="bottom"></div>102                        `).join('')}103                    </div>104                </div>105            `;106            bottomsContainer.appendChild(bottomElement);107        });108 109        // Add event listeners110        document.querySelectorAll('.clothing-item').forEach(item => {111            item.addEventListener('click', function(e) {112                if (e.target.classList.contains('color-swatch')) return;113                114                const type = this.querySelector('.color-swatch')?.dataset.type;115                const id = parseInt(this.querySelector('.color-swatch')?.dataset.itemId);116                117                if (type === 'top') {118                    const selectedTop = clothingData.tops.find(t => t.id === id);119                    state.selectedTop = selectedTop;120                    state.selectedTopColor = selectedTop.colors[0];121                } else if (type === 'bottom') {122                    const selectedBottom = clothingData.bottoms.find(b => b.id === id);123                    state.selectedBottom = selectedBottom;124                    state.selectedBottomColor = selectedBottom.colors[0];125                }126                127                updateColorSuggestions();128                renderClothingItems();129                updateOutfitPreview();130            });131        });132 133        document.querySelectorAll('.color-swatch').forEach(swatch => {134            swatch.addEventListener('click', function(e) {135                e.stopPropagation();136                137                const type = this.dataset.type;138                const color = this.dataset.color;139                const id = parseInt(this.dataset.itemId);140                141                if (type === 'top') {142                    state.selectedTop = clothingData.tops.find(t => t.id === id);143                    state.selectedTopColor = color;144                } else if (type === 'bottom') {145                    state.selectedBottom = clothingData.bottoms.find(b => b.id === id);146                    state.selectedBottomColor = color;147                }148                149                updateColorSuggestions();150                renderClothingItems();151                updateOutfitPreview();152            });153        });154    }155// Enhanced color matching with FITBOARD algorithm156function getColorSuggestions(hex) {157    try {158        const {r,g,b} = hexToRgb(hex);159        const {h,s,l} = rgbToHsl(r,g,b);160        161        const tonal = [162            {label:'Lighter', hex:hslToHex(h, Math.max(0.02,s*0.6), Math.min(0.96,l+0.18))},163            {label:'Darker', hex:hslToHex(h, Math.max(0.02,s*0.85), Math.max(0.04,l-0.24))},164            {label:'Muted', hex:hslToHex(h, Math.max(0.02,s*0.4), Math.min(0.95,l+0.04))}165        ];166        167        const neutrals = [168            {label:'White', hex:'#ffffff'},169            {label:'Light Gray', hex:'#d1d5db'},170            {label:'Navy', hex:'#243447'},171            {label:'Beige', hex:'#d6c5a7'},172            {label:'Black', hex:'#0b0b0b'}173        ];174 175        const hue = ((h%360)+360)%360;176        let curated = [];177        178        if(hue >= 330 || hue < 30) {179            curated = [180                {label:'Navy', hex:'#243447'},181                {label:'Beige', hex:'#d6c5a7'},182                {label:'Denim', hex:'#1f3250'}183            ];184        } else if(hue >= 30 && hue < 90) {185            curated = [186                {label:'Navy', hex:'#243447'},187                {label:'Khaki', hex:'#8a7d4e'},188                {label:'Brown', hex:'#4b3426'}189            ];190        } else if(hue >= 90 && hue < 170) {191            curated = [192                {label:'Tan', hex:'#a57c4b'},193                {label:'Navy', hex:'#243447'},194                {label:'Cream', hex:'#f5efe6'}195            ];196        } else if(hue >= 170 && hue < 260) {197            curated = [198                {label:'White', hex:'#ffffff'},199                {label:'Khaki', hex:'#d6c5a7'},200                {label:'Burgundy', hex:'#6b2737'}201            ];202        } else {203            curated = [204                {label:'Gray', hex:'#9aa4b2'},205                {label:'Black', hex:'#0b0b0b'},206                {label:'Navy', hex:'#243447'}207            ];208        }209 210        const combined = [];211        tonal.forEach(t => combined.push({label:t.label, hex:t.hex.toLowerCase()}));212        curated.forEach(c => combined.push({label:c.label, hex:c.hex.toLowerCase()}));213        neutrals.forEach(n => combined.push({label:n.label, hex:n.hex.toLowerCase()}));214 215        const seen = new Set();216        return combined.filter(i => {217            if(!i.hex) return false;218            if(seen.has(i.hex)) return false;219            seen.add(i.hex);220            return true;221        });222    } catch(e) {223        return [];224    }225}226 227// Helper functions228function hexToRgb(hex) {229    hex = (hex || '').replace('#','');230    if(hex.length === 3) hex = hex.split('').map(c => c+c).join('');231    const int = parseInt(hex, 16) || 0;232    return {233        r: (int >> 16) & 255,234        g: (int >> 8) & 255,235        b: int & 255236    };237}238 239function rgbToHsl(r, g, b) {240    r /= 255; g /= 255; b /= 255;241    const max = Math.max(r,g,b), min = Math.min(r,g,b);242    let h = 0, s = 0, l = (max + min) / 2;243 244    if(max !== min) {245        const d = max - min;246        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);247        switch(max) {248            case r: h = (g - b) / d + (g < b ? 6 : 0); break;249            case g: h = (b - r) / d + 2; break;250            case b: h = (r - g) / d + 4; break;251        }252        h = Math.round(h * 60);253    }254    return {h: s === 0 ? 0 : h, s: +(s.toFixed(3)), l: +(l.toFixed(3))};255}256 257function hslToRgb(h, s, l) {258    h = ((h % 360) + 360) % 360;259    s = Math.max(0, Math.min(1, s));260    l = Math.max(0, Math.min(1, l));261    262    if(s === 0) {263        const v = Math.round(l * 255);264        return {r: v, g: v, b: v};265    }266    267    const q = l < 0.5 ? l * (1 + s) : l + s - l * s;268    const p = 2 * l - q;269    const hk = h / 360;270    271    const tfunc = tt => {272        if(tt < 0) tt += 1;273        if(tt > 1) tt -= 1;274        if(tt < 1/6) return p + (q - p) * 6 * tt;275        if(tt < 1/2) return q;276        if(tt < 2/3) return p + (q - p) * (2/3 - tt) * 6;277        return p;278    };279    280    return {281        r: Math.round(tfunc(hk + 1/3) * 255),282        g: Math.round(tfunc(hk) * 255),283        b: Math.round(tfunc(hk - 1/3) * 255)284    };285}286 287function hslToHex(h, s, l) {288    const {r,g,b} = hslToRgb(h,s,l);289    return rgbToHex(r,g,b);290}291 292function rgbToHex(r, g, b) {293    const toHex = v => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0');294    return '#' + toHex(r) + toHex(g) + toHex(b);295}296// Update color suggestions with compatibility scoring297function updateColorSuggestions() {298    if (state.selectedTop && state.selectedTopColor) {299        state.suggestedColors = getColorSuggestions(state.selectedTopColor);300        301        // Update color palette component with enhanced suggestions302        const colorPalette = document.querySelector('custom-color-palette');303        if (colorPalette) {304            const enhancedSuggestions = state.suggestedColors.map(color => {305                const score = computeCompatibility(state.selectedTopColor, color.hex);306                return {307                    ...color,308                    score: score309                };310            }).sort((a, b) => b.score - a.score);311            312            colorPalette.setAttribute('colors', JSON.stringify({313                base: state.selectedTopColor,314                suggestions: enhancedSuggestions315            }));316        }317    } else if (state.selectedBottom && state.selectedBottomColor) {318        state.suggestedColors = getColorSuggestions(state.selectedBottomColor);319        320        // Update color palette component321        const colorPalette = document.querySelector('custom-color-palette');322        if (colorPalette) {323            const enhancedSuggestions = state.suggestedColors.map(color => {324                const score = computeCompatibility(state.selectedBottomColor, color.hex);325                return {326                    ...color,327                    score: score328                };329            }).sort((a, b) => b.score - a.score);330            331            colorPalette.setAttribute('colors', JSON.stringify({332                base: state.selectedBottomColor,333                suggestions: enhancedSuggestions334            }));335        }336    }337}338 339// Compatibility scoring function340function computeCompatibility(hexA, hexB) {341    try {342        const a = rgbToHsl(...Object.values(hexToRgb(hexA)));343        const b = rgbToHsl(...Object.values(hexToRgb(hexB)));344        345        const hueDist = Math.min(Math.abs(a.h - b.h), 360 - Math.abs(a.h - b.h));346        const toneSim = +(1 - (hueDist / 180)).toFixed(3);347        const satSim = +(1 - Math.abs(a.s - b.s)).toFixed(3);348        const lightSim = +(1 - Math.abs(a.l - b.l)).toFixed(3);349        350        const weights = {351            hue: 0.55,352            sat: 0.225,353            light: 0.225354        };355        356        const base = +(weights.hue * toneSim + weights.sat * satSim + weights.light * lightSim).toFixed(3);357        const neutralBoost = (a.s < 0.08 || b.s < 0.08) ? 0.12 : 0;358        const compBonus = (Math.abs(hueDist - 180) < 12) ? 0.07 : 0;359        360        let raw = base + neutralBoost + compBonus;361        raw = Math.max(0, Math.min(1, raw));362        363        return Math.round(raw * 100);364    } catch(e) {365        return 0;366    }367}368// Update outfit preview369    function updateOutfitPreview() {370        if (outfitPreview) {371            outfitPreview.setAttribute('top-image', state.selectedTop?.image || '');372            outfitPreview.setAttribute('bottom-image', state.selectedBottom?.image || '');373            outfitPreview.setAttribute('top-color', state.selectedTopColor || '');374            outfitPreview.setAttribute('bottom-color', state.selectedBottomColor || '');375        }376    }377 378    // Initialize379    renderClothingItems();380});