feat: add color conversion and target/sets configuration for logos

- Updated Makefile to include a new target for converting logo colors format.
- Added a new script `convertColorsFormat.js` to convert colors from array to object format in `logos.json`.
- Modified `logos.json` structure to use an object for colors and added targets and sets for SVG logos.
- Updated `scanLogos.js` to set default colorConfig, targets, and sets for SVG logos.
- Enhanced Svelte components (`Grid.svelte`, `List.svelte`, `Preview.svelte`, `InlineSvg.svelte`) to support new targets, sets, and colors structure.
- Updated color theme utility functions to handle the new colors object format.
- Removed deprecated `mono_white.svg` logo file.
This commit is contained in:
sHa
2025-05-12 20:52:07 +03:00
parent 29383bb6a1
commit bd4c8dca76
11 changed files with 669 additions and 255 deletions

View File

@@ -6,7 +6,7 @@ CONTAINER_NAME = logo-gallery
DEV_PORT = 5006
# Main targets
.PHONY: all build start stop restart logs clean scan-logos dev rebuild favicon deps-favicon build-with-favicons sync-packages
.PHONY: all build start stop restart logs clean scan-logos dev rebuild favicon deps-favicon build-with-favicons sync-packages convert-colors
all: build start
@@ -50,6 +50,12 @@ scan-logos:
$(DOCKER_COMPOSE) -f compose.dev.yml run --rm slogos-dev npm run scan-logos
@echo "Logos have been updated - refresh the browser to see changes"
# Convert logo colors format from array to object
convert-colors:
@echo "Converting logo colors format in logos.json..."
$(DOCKER_COMPOSE) -f compose.dev.yml run --rm slogos-dev npm run convert-colors
@echo "Color format conversion complete"
# Clean up build artifacts and temporary files
clean:
@echo "Cleaning up build artifacts and temporary files..."

View File

@@ -7,7 +7,8 @@
"dev": "rollup -c -w",
"start": "sirv public --host 0.0.0.0 --dev --single",
"scan-logos": "node scripts/scanLogos.js",
"generate-favicons": "node scripts/generateFavicons.js"
"generate-favicons": "node scripts/generateFavicons.js",
"convert-colors": "node scripts/convertColorsFormat.js"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^17.0.0",

View File

@@ -30,18 +30,23 @@
"tags": [
"tech"
],
"colors": [
{
"label": "Silver",
"value": "#999"
},
{
"label": "Dark Grey",
"value": "#666"
}
],
"colors": {
"silver": "#999",
"dark_grey": "#666"
},
"colorConfig": {
"target": "path"
},
"targets": {
"main": "path"
},
"sets": {
"set_1": {
"main": "silver"
},
"set_2": {
"main": "dark_grey"
}
}
},
{
@@ -100,16 +105,21 @@
"tags": [
"tech"
],
"colors": [
{
"label": "Dark Blue",
"value": "#080225"
}
],
"colors": {
"dark_blue": "#080225"
},
"colorConfig": {
"target": "g"
},
"brand": "arm"
"brand": "arm",
"targets": {
"main": "g"
},
"sets": {
"set_1": {
"main": "dark_blue"
}
}
},
{
"name": "ATB",
@@ -200,12 +210,9 @@
"garage"
],
"brand": "Dalnoboy Service",
"colors": [
{
"label": "Orange",
"value": "#ee7800"
}
]
"colors": {
"orange": "#ee7800"
}
},
{
"name": "Debian",
@@ -241,14 +248,19 @@
"container"
],
"brand": "Docker",
"colors": [
{
"label": "Blue",
"value": "#1d63ed"
}
],
"colors": {
"blue": "#1d63ed"
},
"colorConfig": {
"target": "path"
},
"targets": {
"main": "path"
},
"sets": {
"set_1": {
"main": "blue"
}
}
},
{
@@ -261,19 +273,23 @@
"automobile",
"transport"
],
"colors": [
{
"label": "Grey",
"value": "#2b2a29",
"theme": "light"
},
{
"label": "Red",
"value": "#e22b28"
}
],
"colors": {
"grey": "#2b2a29",
"red": "#e22b28"
},
"colorConfig": {
"selector": "#text"
},
"targets": {
"selector_1": "#text"
},
"sets": {
"set_1": {
"selector_1": "grey"
},
"set_2": {
"selector_1": "red"
}
}
},
{
@@ -353,14 +369,19 @@
"software"
],
"brand": "GitHub",
"colors": [
{
"label": "Dark Grey",
"value": "#24292f"
}
],
"colors": {
"dark_grey": "#24292f"
},
"colorConfig": {
"target": "path"
},
"targets": {
"main": "path"
},
"sets": {
"set_1": {
"main": "dark_grey"
}
}
},
{
@@ -373,14 +394,19 @@
"software"
],
"brand": "GitHub",
"colors": [
{
"label": "Dark Grey",
"value": "#24292f"
}
],
"colors": {
"dark_grey": "#24292f"
},
"colorConfig": {
"target": "path"
},
"targets": {
"main": "path"
},
"sets": {
"set_1": {
"main": "dark_grey"
}
}
},
{
@@ -404,30 +430,35 @@
"language"
],
"brand": "Go",
"colors": [
{
"label": "Aqua",
"value": "#2DBCAF"
},
{
"label": "Blue",
"value": "#00ACD7"
},
{
"label": "Fuchsia",
"value": "#CE3262"
},
{
"label": "LightBlue",
"value": "#5DC9E1"
},
{
"label": "Yellow",
"value": "#FDDD00"
}
],
"colors": {
"aqua": "#2DBCAF",
"blue": "#00ACD7",
"fuchsia": "#CE3262",
"lightblue": "#5DC9E1",
"yellow": "#FDDD00"
},
"colorConfig": {
"target": "g"
},
"targets": {
"main": "g"
},
"sets": {
"set_1": {
"main": "aqua"
},
"set_2": {
"main": "blue"
},
"set_3": {
"main": "fuchsia"
},
"set_4": {
"main": "lightblue"
},
"set_5": {
"main": "yellow"
}
}
},
{
@@ -560,14 +591,19 @@
"automobile",
"transport"
],
"colors": [
{
"label": "Green",
"value": "#424d07"
}
],
"colors": {
"green": "#424d07"
},
"colorConfig": {
"target": "g"
},
"targets": {
"main": "g"
},
"sets": {
"set_1": {
"main": "green"
}
}
},
{
@@ -602,14 +638,19 @@
"appliances"
],
"brand": "LG",
"colors": [
{
"label": "Grey",
"value": "#6b6c6b"
}
],
"colors": {
"grey": "#6b6c6b"
},
"colorConfig": {
"target": "#text"
},
"targets": {
"main": "#text"
},
"sets": {
"set_1": {
"main": "grey"
}
}
},
{
@@ -644,16 +685,21 @@
"software",
"messaging"
],
"colors": [
{
"label": "Denim",
"value": "#1e325c"
}
],
"colors": {
"denim": "#1e325c"
},
"colorConfig": {
"target": "g"
},
"brand": "Mattermost"
"brand": "Mattermost",
"targets": {
"main": "g"
},
"sets": {
"set_1": {
"main": "denim"
}
}
},
{
"name": "McDonald's",
@@ -739,31 +785,33 @@
"bank",
"finance"
],
"colors": [
{
"label": "Black",
"value": "#000"
},
{
"label": "Yellow",
"value": "#f2bd2b"
}
],
"colors": {
"black": "#000",
"yellow": "#f2bd2b",
"white": "#fff"
},
"colorConfig": {
"target": "#background"
},
"targets": {
"background": "#background",
"text": "#text"
},
"sets": {
"black": {
"background": "black",
"text": "white"
},
"white": {
"background": "white",
"text": "black"
},
"yellow": {
"background": "yellow",
"text": "white"
}
}
},
{
"name": "Mono White",
"path": "logos/mono_white.svg",
"format": "SVG",
"disable": false,
"brand": "Monobank",
"tags": [
"bank",
"finance"
]
},
{
"name": "Monobank paw",
"path": "logos/monobank_paw.svg",
@@ -1035,14 +1083,19 @@
"database"
],
"brand": "Redis",
"colors": [
{
"label": "Grey",
"value": "#636466"
}
],
"colors": {
"grey": "#636466"
},
"colorConfig": {
"selector": "#text"
},
"targets": {
"selector_1": "#text"
},
"sets": {
"set_1": {
"selector_1": "grey"
}
}
},
{
@@ -1089,21 +1142,25 @@
"tags": [
"furniture"
],
"colors": [
{
"label": "Brown",
"value": "#2b1c13",
"theme": "light"
},
{
"label": "Green",
"value": "#859310"
}
],
"colors": {
"brown": "#2b1c13",
"green": "#859310"
},
"colorConfig": {
"target": "#text"
},
"brand": "RoomerIN"
"brand": "RoomerIN",
"targets": {
"main": "#text"
},
"sets": {
"set_1": {
"main": "brown"
},
"set_2": {
"main": "green"
}
}
},
{
"name": "Sainsbury's",
@@ -1200,14 +1257,19 @@
"communication"
],
"brand": "Signal",
"colors": [
{
"label": "Ultramarine",
"value": "#3b45fd"
}
],
"colors": {
"ultramarine": "#3b45fd"
},
"colorConfig": {
"target": "path"
},
"targets": {
"main": "path"
},
"sets": {
"set_1": {
"main": "ultramarine"
}
}
},
{
@@ -1220,14 +1282,19 @@
"communication"
],
"brand": "Signal",
"colors": [
{
"label": "Ultramarine",
"value": "#3b45fd"
}
],
"colors": {
"ultramarine": "#3b45fd"
},
"colorConfig": {
"target": "path"
},
"targets": {
"main": "path"
},
"sets": {
"set_1": {
"main": "ultramarine"
}
}
},
{
@@ -1252,14 +1319,21 @@
"footwear"
],
"brand": "Skechers",
"colors": [
{
"label": "Deep Blue",
"value": "#062849"
}
],
"colors": {
"deep_blue": "#062849"
},
"colorConfig": {
"selector": "#text, #logo_int"
},
"targets": {
"selector_1": "#text",
"selector_2": "#logo_int"
},
"sets": {
"set_1": {
"selector_1": "deep_blue",
"selector_2": "deep_blue"
}
}
},
{
@@ -1317,14 +1391,19 @@
"transport"
],
"brand": "Tesla",
"colors": [
{
"label": "Red",
"value": "#e82127"
}
],
"colors": {
"red": "#e82127"
},
"colorConfig": {
"target": "path"
},
"targets": {
"main": "path"
},
"sets": {
"set_1": {
"main": "red"
}
}
},
{
@@ -1348,14 +1427,19 @@
"transport"
],
"brand": "Toyota",
"colors": [
{
"label": "Red",
"value": "#EB0A1E"
}
],
"colors": {
"red": "#EB0A1E"
},
"colorConfig": {
"target": "path"
},
"targets": {
"main": "path"
},
"sets": {
"set_1": {
"main": "red"
}
}
},
{
@@ -1368,14 +1452,19 @@
"transport"
],
"brand": "Toyota",
"colors": [
{
"label": "Red",
"value": "#EB0A1E"
}
],
"colors": {
"red": "#EB0A1E"
},
"colorConfig": {
"target": "path"
},
"targets": {
"main": "path"
},
"sets": {
"set_1": {
"main": "red"
}
}
},
{

View File

@@ -1,14 +0,0 @@
<svg width="500" height="500" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
<path id="background" fill="#ffffff" fill-rule="evenodd" stroke="none"
d="M 0 470 C -0 486.568542 13.431458 500 30 500 L 470 500 C 486.568542 500 500 486.568542 500 470 L 500 30 C 500 13.431458 486.568542 0 470 0 L 30 0 C 13.431458 0 0 13.431458 0 30 Z" />
<g id="text" fill="#000000" stroke="none">
<path
d="M 165.832596 291.28302 L 165.832596 233.147736 C 165.832596 216.656921 156.9505 208.828003 142.036041 208.828003 C 129.634674 208.828003 118.909729 216.157349 114.21714 223.486694 C 111.200958 214.325195 103.65937 208.828003 91.761093 208.828003 C 79.359734 208.828003 68.634781 216.490143 65.282707 221.321045 L 65.282707 210.826935 L 43.999985 210.826935 L 43.999985 291.28302 L 65.282707 291.28302 L 65.282707 237.145599 C 68.466835 232.648193 74.667145 227.651276 82.040787 227.651276 C 90.755676 227.651276 94.107018 232.98172 94.107018 240.477112 L 94.107018 291.28302 L 115.557655 291.28302 L 115.557655 236.979553 C 118.573837 232.648193 124.774879 227.651276 132.315735 227.651276 C 141.030624 227.651276 144.381958 232.98172 144.381958 240.477112 L 144.381958 291.28302 L 165.832596 291.28302 Z" />
<path
d="M 220.610077 291.28302 C 247.422882 291.28302 263.511414 272.417511 263.511414 249.974457 C 263.511414 227.693481 247.422882 208.828003 220.610077 208.828003 C 193.963791 208.828003 177.875992 227.693481 177.875992 249.974457 C 177.875992 272.417511 193.963791 291.28302 220.610077 291.28302 Z M 220.610077 272.905243 C 207.37056 272.905243 199.996887 262.334595 199.996887 249.974457 C 199.996887 237.776398 207.37056 227.20578 220.610077 227.20578 C 233.848938 227.20578 241.38974 237.776398 241.38974 249.974457 C 241.38974 262.334595 233.848938 272.905243 220.610077 272.905243 Z" />
<path
d="M 356.887146 291.28302 L 356.887146 234.480347 C 356.887146 218.822571 348.340912 208.828003 330.576721 208.828003 C 317.33786 208.828003 307.450409 215.157532 302.255493 221.321045 L 302.255493 210.826935 L 280.972015 210.826935 L 280.972015 291.28302 L 302.255493 291.28302 L 302.255493 237.145599 C 305.77475 232.31543 312.310181 227.651276 320.689209 227.651276 C 329.738495 227.651276 335.604401 231.482361 335.604401 242.642761 L 335.604401 291.28302 L 356.887146 291.28302 Z" />
<path
d="M 412.862396 291.28302 C 439.675873 291.28302 455.762207 272.417511 455.762207 249.974457 C 455.762207 227.693481 439.675873 208.828003 412.862396 208.828003 C 386.21756 208.828003 370.128998 227.693481 370.128998 249.974457 C 370.128998 272.417511 386.21756 291.28302 412.862396 291.28302 Z M 412.862396 272.905243 C 399.624268 272.905243 392.249939 262.334595 392.249939 249.974457 C 392.249939 237.776398 399.624268 227.20578 412.862396 227.20578 C 426.103394 227.20578 433.643524 237.776398 433.643524 249.974457 C 433.643524 262.334595 426.103394 272.905243 412.862396 272.905243 Z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// Path to logos.json
const logosJsonPath = path.join(__dirname, '../public/data/logos.json');
// Read the current logos.json file
console.log('Reading logos.json...');
const logosData = JSON.parse(fs.readFileSync(logosJsonPath, 'utf8'));
// Convert the colors format and create targets & sets
console.log('Converting logos format...');
let convertedColorsCount = 0;
let convertedConfigCount = 0;
for (const logo of logosData) {
// 1. Convert colors array to object if needed
if (logo.colors && Array.isArray(logo.colors)) {
// Convert array format to object format
const newColors = {};
for (const colorObj of logo.colors) {
// Convert label to lowercase and replace spaces with underscores
const key = colorObj.label.toLowerCase().replace(/\s+/g, '_');
newColors[key] = colorObj.value;
}
logo.colors = newColors;
convertedColorsCount++;
}
// 2. Convert colorConfig to targets and sets
if (logo.colorConfig && !logo.targets) {
// Create targets object
logo.targets = {};
// Handle selector or target from colorConfig
if (logo.colorConfig.selector) {
// Split multiple selectors (e.g., "#text, #logo_int")
const selectors = logo.colorConfig.selector.split(',').map(s => s.trim());
// Create a target for each selector
selectors.forEach((selector, index) => {
logo.targets[`selector_${index + 1}`] = selector;
});
// Create sets for each color
if (logo.colors && Object.keys(logo.colors).length > 0) {
logo.sets = {};
let setIndex = 1;
// Create a set for each color
for (const [colorName, colorValue] of Object.entries(logo.colors)) {
const setName = `set_${setIndex}`;
logo.sets[setName] = {};
// Apply this color to all targets in this set
Object.keys(logo.targets).forEach(targetName => {
logo.sets[setName][targetName] = colorName;
});
setIndex++;
}
}
} else if (logo.colorConfig.target) {
logo.targets.main = logo.colorConfig.target;
// Create sets for each color
if (logo.colors && Object.keys(logo.colors).length > 0) {
logo.sets = {};
let setIndex = 1;
// Create a set for each color
for (const [colorName, colorValue] of Object.entries(logo.colors)) {
const setName = `set_${setIndex}`;
logo.sets[setName] = {
main: colorName
};
setIndex++;
}
}
}
convertedConfigCount++;
// Keep the original colorConfig for backward compatibility
}
}
// Write the updated data back to logos.json
console.log('Writing updated logos.json...');
fs.writeFileSync(logosJsonPath, JSON.stringify(logosData, null, 2));
console.log(`Conversion complete! Updated colors for ${convertedColorsCount} logos and created targets/sets for ${convertedConfigCount} logos.`);

View File

@@ -171,6 +171,52 @@ function scanLogos() {
if (!logoObj.brand) logoObj.brand = logoObj.name;
if (!Array.isArray(logoObj.tags)) logoObj.tags = [];
if (typeof logoObj.disable !== 'boolean') logoObj.disable = false;
// Set default colorConfig, targets, and sets for SVGs
if (logoObj.format.toLowerCase() === 'svg') {
// Maintain backward compatibility
if (!logoObj.colorConfig) {
logoObj.colorConfig = { target: 'path', attribute: 'fill' };
}
// Add new format targets if not already present
if (!logoObj.targets && (logoObj.colorConfig.target || logoObj.colorConfig.selector)) {
logoObj.targets = {};
if (logoObj.colorConfig.selector) {
// Split multiple selectors (e.g., "#text, #logo_int")
const selectors = logoObj.colorConfig.selector.split(',').map(s => s.trim());
// Create a target for each selector
selectors.forEach((selector, index) => {
logoObj.targets[`selector_${index + 1}`] = selector;
});
} else if (logoObj.colorConfig.target) {
logoObj.targets.main = logoObj.colorConfig.target;
} else {
logoObj.targets.main = 'path';
}
}
// Create sets if there are colors but no sets
if (logoObj.colors && !logoObj.sets) {
logoObj.sets = {};
let setIndex = 1;
// Create a set for each color
for (const [colorName, colorValue] of Object.entries(logoObj.colors)) {
const setName = `set_${setIndex}`;
logoObj.sets[setName] = {};
// Apply this color to all targets
Object.keys(logoObj.targets || {}).forEach(targetName => {
logoObj.sets[setName][targetName] = colorName;
});
setIndex++;
}
}
}
}
return logos;

View File

@@ -62,6 +62,10 @@ $: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
path={logo.path}
color={logo.colors ? (logo._activeColor || getLogoThemeColor(logo)) : undefined}
colorConfig={logo.colors ? logo.colorConfig : undefined}
targets={logo.targets}
sets={logo.sets}
colors={logo.colors}
activeSet={logo._activeSet}
alt={logo.name}
/>
{/key}
@@ -105,17 +109,40 @@ $: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
<path d="M682.843,117.843l-565.686,565.685c-156.209,-156.21 -156.209,-409.476 0,-565.685c156.21,-156.21 409.476,-156.21 565.686,-0Z" style="fill:#33363f;"/>
</svg>
</span>
{#each logo.colors as colorObj}
<span
class="color-circle"
title={colorObj.label}
style="background:{colorObj.value}"
tabindex="0"
role="button"
on:click|stopPropagation={() => logo._activeColor = colorObj.value}
on:keydown|stopPropagation={(e) => (e.key === 'Enter' || e.key === ' ') && (logo._activeColor = colorObj.value)}
></span>
{/each}
{#if logo.sets}
{#each Object.entries(logo.sets) as [setName, setConfig], i}
<span
class="color-circle set-circle"
title={`Color Set ${i + 1}`}
tabindex="0"
role="button"
on:click|stopPropagation={() => {
logo._activeColor = Object.values(logo.colors)[i % Object.keys(logo.colors).length];
logo._activeSet = setName;
}}
on:keydown|stopPropagation={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
logo._activeColor = Object.values(logo.colors)[i % Object.keys(logo.colors).length];
logo._activeSet = setName;
}
}}
>
{i + 1}
</span>
{/each}
{:else}
{#each Object.entries(logo.colors) as [colorName, colorValue], i}
<span
class="color-circle"
title={colorName.replace('_', ' ')}
style="background:{colorValue}"
tabindex="0"
role="button"
on:click|stopPropagation={() => logo._activeColor = colorValue}
on:keydown|stopPropagation={(e) => (e.key === 'Enter' || e.key === ' ') && (logo._activeColor = colorValue)}
></span>
{/each}
{/if}
</div>
{/if}
</div>
@@ -189,4 +216,20 @@ $: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
position: absolute;
bottom: 0;
}
.set-circle {
background: var(--color-border);
color: var(--color-text);
font-size: 10px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
/* Dark theme variation */
:global(.dark-theme) .set-circle {
background: #444;
color: #eee;
}
</style>

View File

@@ -3,6 +3,10 @@
export let path;
export let color;
export let colorConfig = { target: "path", attribute: "fill" };
export let targets = null;
export let sets = null;
export let activeSet = null;
export let colors = null; // Add colors object for access to all color values
export const alt = "";
let svgHtml = "";
@@ -54,36 +58,70 @@
});
// Remove all <style> elements
styleEls.forEach((styleEl) => styleEl.remove());
let targets;
if (colorConfig.selector) {
targets = doc.querySelectorAll(colorConfig.selector);
} else if (colorConfig.target) {
targets = doc.querySelectorAll(colorConfig.target);
} else {
targets = [];
}
targets.forEach((el) => {
if (colorConfig.attribute) {
// Legacy: force a single attribute
el.setAttribute(colorConfig.attribute, color);
} else {
// Always override fill and stroke unless they are 'none'
if (el.hasAttribute("fill") && el.getAttribute("fill") !== "none") {
el.setAttribute("fill", color);
}
if (el.hasAttribute("stroke") && el.getAttribute("stroke") !== "none") {
el.setAttribute("stroke", color);
}
if (!el.hasAttribute("fill") && !el.hasAttribute("stroke")) {
// If neither, prefer fill
el.setAttribute("fill", color);
// Handle the new format with targets and sets if available
if (targets && sets && activeSet && sets[activeSet]) {
// Get the color assignments from the active set
const colorAssignments = sets[activeSet];
// Apply each target-color pair
for (const [targetName, colorName] of Object.entries(colorAssignments)) {
if (targets[targetName] && colors && colors[colorName]) {
// Get the selector for this target
const selector = targets[targetName];
const targetElements = doc.querySelectorAll(selector);
// Get the actual color value for this target
const targetColor = colors[colorName];
// Apply the color to all elements matching this selector
targetElements.forEach(el => {
// Always override fill and stroke unless they are 'none'
if (el.hasAttribute("fill") && el.getAttribute("fill") !== "none") {
el.setAttribute("fill", targetColor);
}
if (el.hasAttribute("stroke") && el.getAttribute("stroke") !== "none") {
el.setAttribute("stroke", targetColor);
}
if (!el.hasAttribute("fill") && !el.hasAttribute("stroke")) {
// If neither, prefer fill
el.setAttribute("fill", targetColor);
}
});
}
}
});
}
// Otherwise, use the legacy format for backward compatibility
else {
let targetElements;
if (colorConfig.selector) {
targetElements = doc.querySelectorAll(colorConfig.selector);
} else if (colorConfig.target) {
targetElements = doc.querySelectorAll(colorConfig.target);
} else {
targetElements = [];
}
targetElements.forEach((el) => {
if (colorConfig.attribute) {
// Legacy: force a single attribute
el.setAttribute(colorConfig.attribute, color);
} else {
// Always override fill and stroke unless they are 'none'
if (el.hasAttribute("fill") && el.getAttribute("fill") !== "none") {
el.setAttribute("fill", color);
}
if (el.hasAttribute("stroke") && el.getAttribute("stroke") !== "none") {
el.setAttribute("stroke", color);
}
if (!el.hasAttribute("fill") && !el.hasAttribute("stroke")) {
// If neither, prefer fill
el.setAttribute("fill", color);
}
}
});
}
svgHtml = doc.documentElement.outerHTML;
}
$: path, color, colorConfig, fetchAndColorSvg();
$: path, color, colorConfig, targets, sets, activeSet, colors, fetchAndColorSvg();
</script>
{@html svgHtml}

View File

@@ -98,6 +98,10 @@
? logo._activeColor || getLogoThemeColor(logo)
: undefined}
colorConfig={logo.colors ? logo.colorConfig : undefined}
targets={logo.targets}
sets={logo.sets}
colors={logo.colors}
activeSet={logo._activeSet}
alt={logo.name}
/>
{:else}
@@ -142,20 +146,43 @@
/>
</svg>
</span>
{#each logo.colors as colorObj}
<span
class="color-circle"
title={colorObj.label}
style={`background:${colorObj.value}`}
tabindex="0"
role="button"
on:click|stopPropagation={() =>
(logo._activeColor = colorObj.value)}
on:keydown|stopPropagation={(e) =>
(e.key === "Enter" || e.key === " ") &&
(logo._activeColor = colorObj.value)}
></span>
{/each}
{#if logo.sets}
{#each Object.entries(logo.sets) as [setName, setConfig], i}
<span
class="color-circle set-circle"
title={`Color Set ${i + 1}`}
tabindex="0"
role="button"
on:click|stopPropagation={() => {
logo._activeColor = Object.values(logo.colors)[i % Object.keys(logo.colors).length];
logo._activeSet = setName;
}}
on:keydown|stopPropagation={(e) => {
if (e.key === "Enter" || e.key === " ") {
logo._activeColor = Object.values(logo.colors)[i % Object.keys(logo.colors).length];
logo._activeSet = setName;
}
}}
>
{i + 1}
</span>
{/each}
{:else}
{#each Object.entries(logo.colors) as [colorName, colorValue]}
<span
class="color-circle"
title={colorName.replace('_', ' ')}
style={`background:${colorValue}`}
tabindex="0"
role="button"
on:click|stopPropagation={() =>
(logo._activeColor = colorValue)}
on:keydown|stopPropagation={(e) =>
(e.key === "Enter" || e.key === " ") &&
(logo._activeColor = colorValue)}
></span>
{/each}
{/if}
</div>
{/if}
</div>
@@ -271,4 +298,19 @@
justify-content: space-between;
gap: 0.5em;
}
.set-circle {
background: var(--color-border);
color: var(--color-text);
font-size: 10px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
/* Dark theme variation */
:global(.dark-theme) .set-circle {
background: #444;
color: #eee;
}
</style>

View File

@@ -141,6 +141,10 @@
path={logo.path}
color={logo.colors ? (logo._activeColor || getLogoThemeColor(logo)) : undefined}
colorConfig={validColorConfig}
targets={logo.targets}
sets={logo.sets}
colors={logo.colors}
activeSet={logo._activeSet}
alt={logo.name}
/>
{:else}
@@ -166,17 +170,40 @@
<path d="M682.843,117.843l-565.686,565.685c-156.209,-156.21 -156.209,-409.476 0,-565.685c156.21,-156.21 409.476,-156.21 565.686,-0Z" style="fill:#33363f;"/>
</svg>
</span>
{#each logo.colors as colorObj}
<span
class="color-circle"
title={colorObj.label}
style={`background:${colorObj.value}`}
tabindex="0"
role="button"
on:click|stopPropagation={() => logo._activeColor = colorObj.value}
on:keydown|stopPropagation={(e) => (e.key === 'Enter' || e.key === ' ') && (logo._activeColor = colorObj.value)}
></span>
{/each}
{#if logo.sets}
{#each Object.entries(logo.sets) as [setName, setConfig], i}
<span
class="color-circle set-circle"
title={`Color Set ${i + 1}`}
tabindex="0"
role="button"
on:click|stopPropagation={() => {
logo._activeColor = Object.values(logo.colors)[i % Object.keys(logo.colors).length];
logo._activeSet = setName;
}}
on:keydown|stopPropagation={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
logo._activeColor = Object.values(logo.colors)[i % Object.keys(logo.colors).length];
logo._activeSet = setName;
}
}}
>
{i + 1}
</span>
{/each}
{:else}
{#each Object.entries(logo.colors) as [colorName, colorValue]}
<span
class="color-circle"
title={colorName.replace('_', ' ')}
style={`background:${colorValue}`}
tabindex="0"
role="button"
on:click|stopPropagation={() => logo._activeColor = colorValue}
on:keydown|stopPropagation={(e) => (e.key === 'Enter' || e.key === ' ') && (logo._activeColor = colorValue)}
></span>
{/each}
{/if}
</div>
{/if}
{#if logo.brand}
@@ -198,6 +225,21 @@
</div>
<style>
.set-circle {
background: var(--color-border);
color: var(--color-text);
font-size: 10px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
:global(.dark-theme) .set-circle {
background: #444;
color: #eee;
}
.modal-backdrop.fullscreen {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
@@ -316,6 +358,22 @@
flex-wrap: wrap;
gap: 0.5rem;
}
.set-circle {
background: var(--color-border);
color: var(--color-text);
font-size: 10px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
/* Dark theme variation */
:global(.dark-theme) .set-circle {
background: #444;
color: #eee;
}
@media (max-width: 900px) {
.modal-body.fullscreen-body {
flex-direction: column;

View File

@@ -1,19 +1,31 @@
// Utility to pick the logo color for the current theme using the "theme" key only
export function getDefaultLogoColor(colors, theme = 'light') {
if (!colors || colors.length === 0) return undefined;
// Use the color with the matching theme key if present
const match = colors.find(c => c.theme === theme);
if (match) return match.value;
if (!colors || Object.keys(colors).length === 0) return undefined;
// Look through all colors to find one with a theme property
for (const [colorName, colorValue] of Object.entries(colors)) {
// If color value is an object with theme property matching current theme
if (typeof colorValue === 'object' && colorValue.theme === theme) {
return colorValue.value;
}
}
// Fallback: do not colorize (undefined)
return undefined;
}
// Utility to select the color with the matching theme key from the colors array
// Utility to select the color with the matching theme key from the colors object
export function getThemeColor(colors, theme = 'light') {
if (!colors || colors.length === 0) return undefined;
// Try to find a color with the matching theme key
const match = colors.find(c => c.theme === theme);
if (match) return match.value;
if (!colors || Object.keys(colors).length === 0) return undefined;
// Look through all colors to find one with a theme property
for (const [colorName, colorValue] of Object.entries(colors)) {
// If color value is an object with theme property matching current theme
if (typeof colorValue === 'object' && colorValue.theme === theme) {
return colorValue.value;
}
}
// Fallback: pick the first color
return colors[0].value;
return Object.values(colors)[0];
}