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

@@ -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];
}