Single-file component (App.vue) over the shared src/logo.js core. Reactive state via reactive(); v-model drives controls, computed properties produce the live logo style and generated CSS. Keeps the accessibility markup (labels, aria, <details>/<summary>, focus rings); reset restores defaults through the form's @reset handler. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
100 lines
2.5 KiB
JavaScript
100 lines
2.5 KiB
JavaScript
// Framework-agnostic core for the logo generator.
|
|
// State shape (see `defaults`) is shared by every port.
|
|
|
|
export const defaults = {
|
|
text: 'LIBRE\nTECH',
|
|
font: 'Anton',
|
|
fs: 18,
|
|
lh: 1.05,
|
|
ls: 0.01,
|
|
bg: '#362582',
|
|
face: '#f6d939',
|
|
depth: '#ea741e',
|
|
shadow: '#221953',
|
|
angle: 133,
|
|
shadowLen: 30,
|
|
depthLen: 15,
|
|
contourLen: 11,
|
|
rot: -10,
|
|
skew: -8,
|
|
};
|
|
|
|
export const fonts = [
|
|
'Anton',
|
|
'Bowlby One',
|
|
'Bebas Neue',
|
|
'Bungee',
|
|
'Passion One',
|
|
'Ultra',
|
|
'Impact',
|
|
];
|
|
|
|
const round = (n) => Math.round(n * 100) / 100;
|
|
|
|
// Layered text-shadow offsets. `depthColor`/`shadowColor` are substituted
|
|
// so the same geometry serves both the live render (literal hex) and the
|
|
// generated snippet (CSS var()).
|
|
export function shadowLayers(s, depthColor, shadowColor) {
|
|
const rad = (s.angle * Math.PI) / 180;
|
|
const dx = Math.sin(rad);
|
|
const dy = -Math.cos(rad);
|
|
const parts = [];
|
|
// Kontur (front): steps along the light direction, shadow colour.
|
|
for (let i = 1; i <= s.contourLen; i++) {
|
|
parts.push(`${round(dx * i)}px ${round(dy * i)}px 0 ${shadowColor}`);
|
|
}
|
|
// Tiefe (middle): opposite direction.
|
|
for (let i = 1; i <= s.depthLen; i++) {
|
|
parts.push(`${round(-dx * i)}px ${round(-dy * i)}px 0 ${depthColor}`);
|
|
}
|
|
// Schatten (back): long, same direction as Kontur; continue past
|
|
// contourLen so steps do not overlap.
|
|
for (let i = s.contourLen + 1; i <= s.contourLen + s.shadowLen; i++) {
|
|
parts.push(`${round(dx * i)}px ${round(dy * i)}px 0 ${shadowColor}`);
|
|
}
|
|
return parts;
|
|
}
|
|
|
|
export function inlineTextShadow(s) {
|
|
const parts = shadowLayers(s, s.depth, s.shadow);
|
|
return parts.length ? parts.join(', ') : 'none';
|
|
}
|
|
|
|
export function logoStyle(s) {
|
|
return {
|
|
fontFamily: `'${s.font}', 'Bebas Neue', Impact, sans-serif`,
|
|
fontSize: `${s.fs}vw`,
|
|
lineHeight: String(s.lh),
|
|
letterSpacing: `${s.ls}em`,
|
|
color: s.face,
|
|
transform: `rotate(${s.rot}deg) skewX(${s.skew}deg)`,
|
|
textShadow: inlineTextShadow(s),
|
|
};
|
|
}
|
|
|
|
export function cssOutput(s) {
|
|
const lines = shadowLayers(s, 'var(--depth)', 'var(--shadow)').map((l) => ' ' + l);
|
|
return `:root {
|
|
--bg: ${s.bg};
|
|
--face: ${s.face};
|
|
--depth: ${s.depth};
|
|
--shadow: ${s.shadow};
|
|
}
|
|
|
|
body {
|
|
background: var(--bg);
|
|
}
|
|
|
|
.logo {
|
|
font-family: '${s.font}', sans-serif;
|
|
font-size: ${s.fs}vw;
|
|
line-height: ${s.lh};
|
|
letter-spacing: ${s.ls}em;
|
|
color: var(--face);
|
|
transform: rotate(${s.rot}deg) skewX(${s.skew}deg);
|
|
white-space: pre-line;
|
|
text-shadow:
|
|
${lines.join(',\n')};
|
|
}`;
|
|
}
|