Add Vue + Vite port of the logo generator
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>
This commit is contained in:
+99
@@ -0,0 +1,99 @@
|
||||
// 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')};
|
||||
}`;
|
||||
}
|
||||
Reference in New Issue
Block a user