feat: app logo

This commit is contained in:
2025-10-09 01:20:36 +02:00
parent 9136f57b8d
commit 79181bf0bd
3 changed files with 253 additions and 124 deletions

View File

@@ -42,5 +42,8 @@
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[vue]": {
"editor.defaultFormatter": "Vue.volar"
}
}

View File

@@ -7,66 +7,41 @@ const { header } = useAppConfig()
</script>
<template>
<UHeader
:ui="{ center: 'flex-1' }"
:to="header?.to || '/'"
>
<UContentSearchButton
v-if="header?.search"
:collapsed="false"
class="w-full"
/>
<UHeader :ui="{ center: 'flex-1' }" :to="header?.to || '/'">
<UContentSearchButton v-if="header?.search" :collapsed="false" class="w-full" />
<template
v-if="header?.logo?.dark || header?.logo?.light || header?.title"
#title
>
<UColorModeImage
v-if="header?.logo?.dark || header?.logo?.light"
:light="header?.logo?.light!"
:dark="header?.logo?.dark!"
:alt="header?.logo?.alt"
class="h-6 w-auto shrink-0"
/>
<template v-if="header?.logo?.dark || header?.logo?.light || header?.title" #title>
<UColorModeImage v-if="header?.logo?.dark || header?.logo?.light" :light="header?.logo?.light!"
:dark="header?.logo?.dark!" :alt="header?.logo?.alt" class="h-6 w-auto shrink-0" />
<span v-else-if="header?.title">
{{ header.title }}
</span>
</template>
<template
v-else
#left
>
<NuxtLink :to="header?.to || '/'">
<AppLogo class="w-auto h-6 shrink-0" />
</NuxtLink>
<template v-else #left>
<div class="flex items-center gap-6">
<NuxtLink :to="header?.to || '/'" >
<AppLogo class="!text-2xl mt-1" />
</NuxtLink>
<TemplateMenu />
<TemplateMenu class="h-6" />
</div>
</template>
<template #right>
<UContentSearchButton
v-if="header?.search"
class="lg:hidden"
/>
<UContentSearchButton v-if="header?.search" class="lg:hidden" />
<UColorModeButton v-if="header?.colorMode" />
<template v-if="header?.links">
<UButton
v-for="(link, index) of header.links"
:key="index"
v-bind="{ color: 'neutral', variant: 'ghost', ...link }"
/>
<UButton v-for="(link, index) of header.links" :key="index"
v-bind="{ color: 'neutral', variant: 'ghost', ...link }" />
</template>
</template>
<template #body>
<UContentNavigation
highlight
:navigation="navigation"
/>
<UContentNavigation highlight :navigation="navigation" />
</template>
</UHeader>
</template>

View File

@@ -1,86 +1,237 @@
<template>
<svg
width="280"
height="80"
viewBox="0 0 280 80"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<!-- Soft shadow filter -->
<filter id="softShadow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
<feOffset dx="0" dy="3" result="offsetblur"/>
<feComponentTransfer>
<feFuncA type="linear" slope="0.3"/>
</feComponentTransfer>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<!-- Subtle glow for extra depth -->
<filter id="glow">
<feGaussianBlur stdDeviation="2" result="coloredBlur"/>
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<script setup>
import { ref, computed } from 'vue'
<g filter="url(#softShadow)">
<!-- K -->
<path
d="M 20,20 L 20,60 M 20,40 L 48,20 M 20,40 L 48,60"
stroke="currentColor"
stroke-width="12"
stroke-linecap="round"
stroke-linejoin="round"
filter="url(#glow)"
/>
const props = defineProps({
size: {
type: String,
default: '42px' // Can be: '24px', '32px', '42px', '56px', etc.
}
})
const isHovered = ref(false)
// Load Google Font
if (typeof document !== 'undefined') {
const link = document.createElement('link')
link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@800;900&display=swap'
link.rel = 'stylesheet'
document.head.appendChild(link)
}
</script>
<template>
<div
class="logo-wrapper"
:style="{ fontSize: size }"
@mouseenter="isHovered = true"
@mouseleave="isHovered = false"
>
<div class="logo-container">
<!-- Main brand text -->
<span class="brand-text">kompose</span>
<!-- M -->
<path
d="M 68,60 L 68,20 L 86,42 L 104,20 L 104,60"
stroke="currentColor"
stroke-width="12"
stroke-linecap="round"
stroke-linejoin="round"
filter="url(#glow)"
/>
<!-- P -->
<path
d="M 124,60 L 124,20 L 148,20 C 156,20 162,26 162,34 C 162,42 156,48 148,48 L 124,48"
stroke="currentColor"
stroke-width="12"
stroke-linecap="round"
stroke-linejoin="round"
filter="url(#glow)"
/>
<!-- S -->
<path
d="M 196,24 C 192,20 186,20 182,24 C 178,28 178,32 182,36 C 186,39 190,39 194,42 C 198,45 202,45 206,49 C 210,53 210,57 206,61 C 202,65 196,65 192,61"
stroke="currentColor"
stroke-width="12"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
filter="url(#glow)"
/>
<!-- E -->
<path
d="M 260,20 L 226,20 L 226,60 L 260,60 M 226,40 L 254,40"
stroke="currentColor"
stroke-width="12"
stroke-linecap="round"
stroke-linejoin="round"
filter="url(#glow)"
/>
</g>
</svg>
<!-- Tech domain extension with animated dot -->
<span class="domain-wrapper">
<span class="animated-dot-wrapper">
<span class="animated-dot" :class="{ active: isHovered }"></span>
</span>
<span class="domain-text-wrapper">
<span class="domain-text">sh</span>
<span class="underline" :class="{ active: isHovered }"></span>
</span>
</span>
</div>
<!-- Floating particles on hover -->
<transition name="fade">
<div v-if="isHovered" class="particles">
<span v-for="i in 3" :key="i" class="particle" :style="{ animationDelay: `${i * 0.15}s` }"></span>
</div>
</transition>
</div>
</template>
<style scoped>
.logo-wrapper {
position: relative;
display: inline-block;
cursor: pointer;
font-size: 42px; /* Default size, can be overridden */
}
.logo-container {
position: relative;
display: inline-flex;
align-items: center;
gap: 0;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-weight: 900;
font-size: 1em; /* Relative to wrapper */
letter-spacing: -0.05em;
color: currentColor;
text-shadow: 0 0.07em 0.19em rgba(0, 0, 0, 0.25);
line-height: 1;
user-select: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: transform 0.3s ease;
}
.logo-wrapper:hover .logo-container {
transform: translateY(-0.05em);
}
.brand-text {
position: relative;
z-index: 2;
}
.domain-wrapper {
display: inline-flex;
align-items: center;
margin-left: -0.02em;
gap: 0;
}
/* Animated pulsing dot (replacing the regular dot) */
.animated-dot-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
width: 0.3em;
height: 1em;
position: relative;
}
.animated-dot {
width: 0.15em;
height: 0.15em;
border-radius: 50%;
background: var(--ui-primary, #00DC82);
opacity: 0.7;
transition: all 0.3s ease;
animation: pulse 2s ease-in-out infinite;
position: relative;
top: 0.15em; /* Align as a period baseline */
}
.animated-dot.active {
opacity: 1;
box-shadow: 0 0 0.3em var(--ui-primary, #00DC82);
animation: pulse-fast 1s ease-in-out infinite;
}
.domain-text-wrapper {
position: relative;
display: inline-block;
}
.domain-text {
position: relative;
z-index: 2;
color: var(--ui-primary, #00DC82);
opacity: 0.8;
font-weight: 800;
transition: opacity 0.3s ease;
}
.logo-wrapper:hover .domain-text {
opacity: 1;
}
/* Animated underline - only under "sh" */
.underline {
position: absolute;
bottom: -0.1em;
left: 0;
width: 0%;
height: 0.07em;
background: var(--ui-primary, #00DC82);
opacity: 0.8;
border-radius: 0.05em;
transition: width 0.4s ease;
}
.underline.active {
width: 100%;
}
/* Floating particles */
.particles {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
pointer-events: none;
}
.particle {
position: absolute;
width: 0.1em;
height: 0.1em;
background: var(--ui-primary, #00DC82);
border-radius: 50%;
opacity: 0.5;
animation: float-particle 1.5s ease-out forwards;
box-shadow: 0 0 0.2em var(--ui-primary, #00DC82);
}
.particle:nth-child(1) {
left: 20%;
top: 30%;
}
.particle:nth-child(2) {
left: 50%;
top: 20%;
}
.particle:nth-child(3) {
left: 80%;
top: 40%;
}
/* Animations */
@keyframes pulse {
0%, 100% {
transform: scale(1);
opacity: 0.7;
}
50% {
transform: scale(1.3);
opacity: 1;
}
}
@keyframes pulse-fast {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.5);
opacity: 0.8;
}
}
@keyframes float-particle {
0% {
transform: translateY(0) scale(1);
opacity: 0.5;
}
100% {
transform: translateY(-0.7em) scale(0);
opacity: 0;
}
}
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
</style>