feat: build leuschke.site with pixel design system, vue 3, tailwind
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
This commit is contained in:
12
src/App.vue
Normal file
12
src/App.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<HeaderComp />
|
||||
<main class="min-h-[calc(100vh-120px)] pt-20 pb-16">
|
||||
<router-view />
|
||||
</main>
|
||||
<FooterComp />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import HeaderComp from './components/Header.vue'
|
||||
import FooterComp from './components/Footer.vue'
|
||||
</script>
|
||||
BIN
src/assets/hero.png
Normal file
BIN
src/assets/hero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
32
src/assets/main.css
Normal file
32
src/assets/main.css
Normal file
@@ -0,0 +1,32 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--color-olive: #5B7440;
|
||||
--color-olive-dark: #4A5F34;
|
||||
--color-olive-light: #7A9B57;
|
||||
--color-olive-pale: #A8C686;
|
||||
--color-beige: #E8DCC8;
|
||||
--color-beige-light: #F5EFE0;
|
||||
--color-beige-dark: #C4B89A;
|
||||
--color-orange: #E8813A;
|
||||
--color-orange-light: #F4A460;
|
||||
--color-orange-dark: #C0652A;
|
||||
--font-pixel: "Press Start 2P", sans-serif;
|
||||
--font-body: "Silkscreen", sans-serif;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-pixel);
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
1
src/assets/vite.svg
Normal file
1
src/assets/vite.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.5 KiB |
1
src/assets/vue.svg
Normal file
1
src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
10
src/components/Footer.vue
Normal file
10
src/components/Footer.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<footer class="border-t-4 border-olive py-6 text-center">
|
||||
<p class="text-olive text-[10px] font-pixel">{{ t('footer.copyright') || '© 2025 LeuschkeDigital' }}</p>
|
||||
<p class="text-orange text-[8px] font-pixel mt-2">Pixel-Powered & Made with ♥ by Homér</p>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { t } from '../i18n'
|
||||
</script>
|
||||
106
src/components/Header.vue
Normal file
106
src/components/Header.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<header :class="themeClass">
|
||||
<div class="max-w-[960px] mx-auto px-6 py-5 flex justify-between items-center flex-wrap gap-3">
|
||||
<router-link to="/" class="text-olive font-pixel text-xs tracking-tight hover:text-orange transition-colors">
|
||||
LEUSCHKE<span class="text-orange">.</span>DIGITAL
|
||||
</router-link>
|
||||
|
||||
<!-- Desktop Nav -->
|
||||
<nav class="hidden md:flex items-center gap-3">
|
||||
<router-link v-for="item in navItems" :key="item.path" :to="item.path"
|
||||
class="font-pixel text-[8px] text-olive hover:text-orange transition-colors"
|
||||
:class="{ 'text-orange border-b-2 border-orange': route.path === item.path }">
|
||||
{{ t(item.label) }}
|
||||
</router-link>
|
||||
</nav>
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Mobile menu button -->
|
||||
<button @click="mobileOpen = !mobileOpen" class="md:hidden pixel-btn text-[8px] px-3 py-2">
|
||||
☰
|
||||
</button>
|
||||
<button @click="toggleTheme" class="hidden md:inline-flex pixel-btn text-[8px] px-3 py-2">
|
||||
{{ isDark ? '☀️' : '🌙' }} {{ isDark ? t('nav.light') : t('nav.dark') }}
|
||||
</button>
|
||||
<button @click="toggleLang" class="hidden md:inline-flex pixel-btn text-[8px] px-3 py-2">
|
||||
{{ lang === 'de' ? '🇩🇪 DE' : '🇬🇧 EN' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu -->
|
||||
<div v-if="mobileOpen" class="md:hidden pixel-border mx-3 mb-3 p-3 flex flex-col gap-3">
|
||||
<router-link v-for="item in navItems" :key="item.path" :to="item.path"
|
||||
class="font-pixel text-[8px] text-olive hover:text-orange block px-2 py-1"
|
||||
@click="mobileOpen = false">
|
||||
{{ t(item.label) }}
|
||||
</router-link>
|
||||
<div class="flex gap-2 pt-2">
|
||||
<button @click="toggleTheme" class="pixel-btn text-[8px] px-3 py-2">
|
||||
{{ isDark ? '☀️' : '🌙' }} {{ isDark ? t('nav.light') : t('nav.dark') }}
|
||||
</button>
|
||||
<button @click="toggleLang" class="pixel-btn text-[8px] px-3 py-2">
|
||||
{{ lang === 'de' ? '🇩🇪 DE' : '🇬🇧 EN' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { t, currentLang, type Lang } from '../i18n'
|
||||
|
||||
const route = useRoute()
|
||||
const isDark = ref<boolean>(false)
|
||||
const lang = ref<Lang>('de')
|
||||
const mobileOpen = ref(false)
|
||||
|
||||
const navItems = [
|
||||
{ path: '/', label: 'nav.home' },
|
||||
{ path: '/about', label: 'nav.about' },
|
||||
{ path: '/projects', label: 'nav.projects' },
|
||||
{ path: '/blog', label: 'nav.blog' },
|
||||
{ path: '/contact', label: 'nav.contact' },
|
||||
{ path: '/impressum', label: 'nav.impressum' },
|
||||
]
|
||||
|
||||
const toggleTheme = () => { isDark.value = !isDark.value; applyTheme() }
|
||||
const toggleLang = () => {
|
||||
lang.value = lang.value === 'de' ? 'en' : 'de'
|
||||
currentLang.value = lang.value
|
||||
}
|
||||
const applyTheme = () => {
|
||||
document.documentElement.setAttribute('data-theme', isDark.value ? 'dark' : 'light')
|
||||
document.documentElement.setAttribute('data-lang', lang.value)
|
||||
}
|
||||
|
||||
const themeClass = computed(() => ({
|
||||
'bg-beige-light border-b-4 border-olive': true,
|
||||
'[&[data-theme=dark]]:bg-beige-dark [&[data-theme=dark]]:text-[#d4ccb8] [&[data-theme=dark]]:border-olive-dark': isDark.value,
|
||||
}))
|
||||
|
||||
// Init from localStorage or defaults
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
isDark.value = localStorage.getItem('theme') === 'dark'
|
||||
}
|
||||
lang.value = (localStorage.getItem('lang') as Lang) || 'de'
|
||||
currentLang.value = lang.value
|
||||
applyTheme()
|
||||
|
||||
watch(isDark, (v) => {
|
||||
localStorage.setItem('theme', v ? 'dark' : 'light')
|
||||
applyTheme()
|
||||
})
|
||||
watch(lang, (v) => {
|
||||
localStorage.setItem('lang', v)
|
||||
currentLang.value = v
|
||||
applyTheme()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
header { position: fixed; top: 0; left: 0; right: 0; z-index: 50; }
|
||||
</style>
|
||||
13
src/components/PixelButton.vue
Normal file
13
src/components/PixelButton.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<button class="pixel-btn" :class="{ active: variant === 'primary' }" @click="$emit('click')">
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ variant?: 'primary' | 'secondary' }>()
|
||||
defineEmits(['click'])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
15
src/components/PixelCard.vue
Normal file
15
src/components/PixelCard.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="pixel-card">
|
||||
<div class="border-4 border-olive p-5 bg-beige-light relative">
|
||||
<div class="absolute inset-[4px] border-2 border-beige pointer-events-none -z-10" v-if="showBorder"></div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ showBorder?: boolean }>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
3
src/components/PixelDivider.vue
Normal file
3
src/components/PixelDivider.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div class="h-1 my-6" style="background: repeating-linear-gradient(90deg, var(--color-olive) 0px, var(--color-olive) 8px, transparent 8px, transparent 16px);" />
|
||||
</template>
|
||||
135
src/i18n/index.ts
Normal file
135
src/i18n/index.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export type Lang = 'de' | 'en'
|
||||
export const currentLang = ref<Lang>('de')
|
||||
|
||||
export const t = (key: string): string => {
|
||||
const translations = {
|
||||
de: {
|
||||
// Nav
|
||||
'nav.home': 'Home',
|
||||
'nav.about': 'Über Uns',
|
||||
'nav.projects': 'Projekte',
|
||||
'nav.blog': 'Blog',
|
||||
'nav.contact': 'Kontakt',
|
||||
'nav.impressum': 'Impressum',
|
||||
'nav.light': 'Light',
|
||||
'nav.dark': 'Dark',
|
||||
|
||||
// Home
|
||||
'home.hero.title': 'LeuschkeDigital',
|
||||
'home.hero.subtitle': 'Wir bauen die Zukunft mit KI.',
|
||||
'home.hero.cta': 'Projekte entdecken',
|
||||
'home.about.title': 'Über Uns',
|
||||
'home.about.text': 'LeuschkeDigital ist ein digitales Unternehmen mit Fokus auf KI-gestützte Lösungen für Gründer, Gesundheitstechnologie und moderne Web-Infrastruktur. Wir glauben daran, dass Technologie allen zugänglich sein sollte.',
|
||||
'home.projects.title': 'Unsere Projekte',
|
||||
'home.projects.desc': 'Von Gründer-Plattformen bis HealthTech – wir bauen Tools die etwas bewirken.',
|
||||
'home.projects.roadmap': 'Roadmap',
|
||||
|
||||
// About
|
||||
'about.title': 'Über Uns',
|
||||
'about.subtitle': 'Warum wir Dinge tun',
|
||||
'about.mission.title': 'Unsere Mission',
|
||||
'about.mission.text': 'Wir wollen Technologie demokratisieren. Gründer sollen mit KI-Unterstützung ihre Ideen prüfen und entwickeln. Patienten sollen die Hoheit über ihre Gesundheitsdaten haben. Und Unternehmen sollen moderne, transparente Web-Lösungen nutzen können.',
|
||||
'about.team.title': 'Das Team',
|
||||
'about.team.jan': 'Jan Leuschke – Gründer & CEO',
|
||||
'about.team.homer': 'Homér – Operative Struktur & KI-Infrastruktur',
|
||||
'about.values.title': 'Unsere Werte',
|
||||
'about.values.transparency': 'Transparenz in allem was wir bauen',
|
||||
'about.values.autonomy': 'Daten-Autonomie für den Nutzer',
|
||||
'about.values.openness': 'Open-Source wo immer möglich',
|
||||
|
||||
// Projects
|
||||
'projects.title': 'Unsere Projekte',
|
||||
'projects.subtitle': 'Eine Übersicht unserer aktuellen und geplanten Projekte',
|
||||
'projects.roadmap': 'Roadmap',
|
||||
'projects.leuschke.title': 'LeuschkeDigital Website',
|
||||
'projects.leuschke.desc': 'Diese Corporate Website – Unternehmenspräsentation, Blog & Projektübersicht.',
|
||||
'projects.founderflow.title': 'Founderflow',
|
||||
'projects.founderflow.desc': 'KI-Plattform für Gründer: Geschäftsidee prüfen, Business Model Canvas, Pitch Deck erstellen.',
|
||||
'projects.medvault.title': 'MedVault',
|
||||
'projects.medvault.desc': 'Elektronische Patientenakte mit Datenhoheit beim Patienten (Open Source).',
|
||||
'projects.neuropilot.title': 'NeuroPilot',
|
||||
'projects.neuropilot.desc': 'ADHS-Community und KI-gestützte Selbsthilfe-Plattform.',
|
||||
'projects.consent.title': 'OpenConsent',
|
||||
'projects.consent.desc': 'Patienteneinwilligungen für HealthTech, OIDC4VP-Standard (Open Source).',
|
||||
'projects.status.done': 'Live',
|
||||
'projects.status.active': 'Aktiv',
|
||||
'projects.status.planned': 'Geplant',
|
||||
|
||||
// Contact
|
||||
'contact.title': 'Kontakt',
|
||||
'contact.subtitle': 'Schreib uns eine Nachricht',
|
||||
'contact.name': 'Name',
|
||||
'contact.email': 'E-Mail',
|
||||
'contact.message': 'Nachricht',
|
||||
'contact.send': 'Absenden',
|
||||
'contact.success': 'Nachricht gesendet!',
|
||||
|
||||
// Impressum
|
||||
'impressum.title': 'Impressum',
|
||||
'impressum.info.title': 'Angaben gemäß § 5 TMG',
|
||||
'impressum.name': 'Jan Leuschke',
|
||||
'impressum.company': 'LeuschkeDigital',
|
||||
'impressum.email': 'Kontakt: jan@leuschke.site',
|
||||
},
|
||||
en: {
|
||||
'nav.home': 'Home',
|
||||
'nav.about': 'About',
|
||||
'nav.projects': 'Projects',
|
||||
'nav.blog': 'Blog',
|
||||
'nav.contact': 'Contact',
|
||||
'nav.impressum': 'Imprint',
|
||||
'nav.light': 'Light',
|
||||
'nav.dark': 'Dark',
|
||||
'home.hero.title': 'LeuschkeDigital',
|
||||
'home.hero.subtitle': 'We build the future with AI.',
|
||||
'home.hero.cta': 'Discover Projects',
|
||||
'home.about.title': 'About Us',
|
||||
'home.about.text': 'LeuschkeDigital is a digital company focused on AI-powered solutions for founders, health technology, and modern web infrastructure. We believe technology should be accessible to everyone.',
|
||||
'home.projects.title': 'Our Projects',
|
||||
'home.projects.desc': 'From founder platforms to HealthTech – we build tools that make a difference.',
|
||||
'home.projects.roadmap': 'Roadmap',
|
||||
'about.title': 'About Us',
|
||||
'about.subtitle': 'Why we do things',
|
||||
'about.mission.title': 'Our Mission',
|
||||
'about.mission.text': 'We want to democratize technology. Founders should be able to test and develop their ideas with AI support. Patients should have sovereignty over their health data. And companies should use modern, transparent web solutions.',
|
||||
'about.team.title': 'The Team',
|
||||
'about.team.jan': 'Jan Leuschke – Founder & CEO',
|
||||
'about.team.homer': 'Homér – Operational Structure & AI Infrastructure',
|
||||
'about.values.title': 'Our Values',
|
||||
'about.values.transparency': 'Transparency in everything we build',
|
||||
'about.values.autonomy': 'Data autonomy for the user',
|
||||
'about.values.openness': 'Open-source wherever possible',
|
||||
'projects.title': 'Our Projects',
|
||||
'projects.subtitle': 'An overview of our current and planned projects',
|
||||
'projects.roadmap': 'Roadmap',
|
||||
'projects.leuschke.title': 'LeuschkeDigital Website',
|
||||
'projects.leuschke.desc': 'This corporate website – company presentation, blog & project overview.',
|
||||
'projects.founderflow.title': 'Founderflow',
|
||||
'projects.founderflow.desc': 'AI platform for founders: check business ideas, Business Model Canvas, create pitch decks.',
|
||||
'projects.medvault.title': 'MedVault',
|
||||
'projects.medvault.desc': 'Electronic patient records with data sovereignty at the patient (Open Source).',
|
||||
'projects.neuropilot.title': 'NeuroPilot',
|
||||
'projects.neuropilot.desc': 'ADHD community and AI-powered self-help platform.',
|
||||
'projects.consent.title': 'OpenConsent',
|
||||
'projects.consent.desc': 'Patient consent for HealthTech, OIDC4VP standard (Open Source).',
|
||||
'projects.status.done': 'Live',
|
||||
'projects.status.active': 'Active',
|
||||
'projects.status.planned': 'Planned',
|
||||
'contact.title': 'Contact',
|
||||
'contact.subtitle': 'Send us a message',
|
||||
'contact.name': 'Name',
|
||||
'contact.email': 'Email',
|
||||
'contact.message': 'Message',
|
||||
'contact.send': 'Send',
|
||||
'contact.success': 'Message sent!',
|
||||
'impressum.title': 'Imprint',
|
||||
'impressum.info.title': 'Information according to § 5 TMG',
|
||||
'impressum.name': 'Jan Leuschke',
|
||||
'impressum.company': 'LeuschkeDigital',
|
||||
'impressum.email': 'Contact: jan@leuschke.site',
|
||||
}
|
||||
}
|
||||
return (translations[currentLang.value] as Record<string, string>)?.[key] || key
|
||||
}
|
||||
8
src/main.ts
Normal file
8
src/main.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import './assets/main.css'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
44
src/router/index.ts
Normal file
44
src/router/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: () => import('../views/HomeView.vue')
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'About',
|
||||
component: () => import('../views/AboutView.vue')
|
||||
},
|
||||
{
|
||||
path: '/projects',
|
||||
name: 'Projects',
|
||||
component: () => import('../views/ProjectsView.vue')
|
||||
},
|
||||
{
|
||||
path: '/blog',
|
||||
name: 'Blog',
|
||||
component: () => import('../views/BlogView.vue')
|
||||
},
|
||||
{
|
||||
path: '/contact',
|
||||
name: 'Contact',
|
||||
component: () => import('../views/ContactView.vue')
|
||||
},
|
||||
{
|
||||
path: '/impressum',
|
||||
name: 'Impressum',
|
||||
component: () => import('../views/ImpressumView.vue')
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
scrollBehavior() {
|
||||
return { top: 0 }
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
58
src/views/AboutView.vue
Normal file
58
src/views/AboutView.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<section class="max-w-[960px] mx-auto px-6 pt-24 pb-16">
|
||||
<h1 class="text-2xl text-olive leading-[2] mb-4 font-pixel">{{ t('about.title') }}</h1>
|
||||
<p class="text-orange mb-10">{{ t('about.subtitle') }}</p>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="text-lg text-olive font-pixel">{{ t('about.mission.title') }}</h2>
|
||||
<p class="text-beige-dark mt-4 leading-[2]">{{ t('about.mission.text') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="section mt-10">
|
||||
<h2 class="text-lg text-olive font-pixel">{{ t('about.team.title') }}</h2>
|
||||
<ul class="mt-4 space-y-3">
|
||||
<li class="flex items-center gap-3">
|
||||
<span class="w-2 h-2 bg-orange" />
|
||||
<span class="text-beige-dark">{{ t('about.team.jan') }}</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-3">
|
||||
<span class="w-2 h-2 bg-olive" />
|
||||
<span class="text-beige-dark">{{ t('about.team.homer') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="section mt-10">
|
||||
<h2 class="text-lg text-olive font-pixel">{{ t('about.values.title') }}</h2>
|
||||
<div class="grid md:grid-cols-3 gap-6 mt-6">
|
||||
<div class="border-4 border-olive p-4 bg-beige-light text-center">
|
||||
<span class="text-2xl">🔍</span>
|
||||
<p class="text-sm text-beige-dark mt-2">{{ t('about.values.transparency') }}</p>
|
||||
</div>
|
||||
<div class="border-4 border-olive p-4 bg-beige-light text-center">
|
||||
<span class="text-2xl">🔒</span>
|
||||
<p class="text-sm text-beige-dark mt-2">{{ t('about.values.autonomy') }}</p>
|
||||
</div>
|
||||
<div class="border-4 border-olive p-4 bg-beige-light text-center">
|
||||
<span class="text-2xl">🌐</span>
|
||||
<p class="text-sm text-beige-dark mt-2">{{ t('about.values.openness') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { t } from '../i18n'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.section {
|
||||
border: 4px solid var(--color-olive);
|
||||
padding: 2rem;
|
||||
background: var(--color-beige);
|
||||
position: relative;
|
||||
}
|
||||
[data-theme="dark"] .section { border-color: var(--color-olive); background: var(--color-beige-dark); }
|
||||
[data-theme="dark"] .border-4 { background: var(--color-beige); }
|
||||
</style>
|
||||
25
src/views/BlogView.vue
Normal file
25
src/views/BlogView.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<section class="max-w-[960px] mx-auto px-6 pt-24 pb-16">
|
||||
<h1 class="text-2xl text-olive leading-[2] mb-4 font-pixel">{{ t('nav.blog') }}</h1>
|
||||
<div class="section">
|
||||
<p class="text-beige-dark leading-[2]">Blog kommt bald! Hier erscheinen unsere Updates, Insights und Gedanken rund um unsere Projekte.</p>
|
||||
<div class="mt-6 border-2 border-dashed border-beige-dark p-8 text-center">
|
||||
<span class="text-4xl">📝</span>
|
||||
<p class="text-olive text-sm mt-4 font-pixel">Coming Soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { t } from '../i18n'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.section {
|
||||
border: 4px solid var(--color-olive);
|
||||
padding: 2rem;
|
||||
background: var(--color-beige);
|
||||
}
|
||||
[data-theme="dark"] .section { background: var(--color-beige-dark); }
|
||||
</style>
|
||||
61
src/views/ContactView.vue
Normal file
61
src/views/ContactView.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<section class="max-w-[960px] mx-auto px-6 pt-24 pb-16">
|
||||
<h1 class="text-2xl text-olive leading-[2] mb-4 font-pixel">{{ t('contact.title') }}</h1>
|
||||
<p class="text-orange mb-10">{{ t('contact.subtitle') }}</p>
|
||||
|
||||
<div class="section max-w-xl">
|
||||
<form @submit.prevent="sendForm" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-[8px] text-olive font-pixel mb-1">{{ t('contact.name') }}</label>
|
||||
<input v-model="form.name" required class="pixel-input" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[8px] text-olive font-pixel mb-1">{{ t('contact.email') }}</label>
|
||||
<input v-model="form.email" type="email" required class="pixel-input" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[8px] text-olive font-pixel mb-1">{{ t('contact.message') }}</label>
|
||||
<textarea v-model="form.message" required rows="6" class="pixel-input" />
|
||||
</div>
|
||||
<button type="submit" class="pixel-btn active text-[8px] px-6 py-3">{{ t('contact.send') }}</button>
|
||||
</form>
|
||||
<p v-if="success" class="text-olive text-sm mt-4 font-pixel">✅ {{ t('contact.success') }}</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { t } from '../i18n'
|
||||
|
||||
const form = ref({ name: '', email: '', message: '' })
|
||||
const success = ref(false)
|
||||
|
||||
const sendForm = () => {
|
||||
// TODO: Backend anbindung
|
||||
success.value = true
|
||||
form.value = { name: '', email: '', message: '' }
|
||||
setTimeout(() => success.value = false, 4000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.section {
|
||||
border: 4px solid var(--color-olive);
|
||||
padding: 2rem;
|
||||
background: var(--color-beige);
|
||||
}
|
||||
[data-theme="dark"] .section { background: var(--color-beige-dark); }
|
||||
.pixel-input {
|
||||
font-family: 'Silkscreen', sans-serif;
|
||||
border: 3px solid var(--color-olive);
|
||||
padding: 12px;
|
||||
background: var(--color-beige-light);
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
.pixel-input:focus { border-color: var(--color-orange); box-shadow: 2px 2px 0 var(--color-orange); }
|
||||
[data-theme="dark"] .pixel-input { background: var(--color-beige); }
|
||||
</style>
|
||||
84
src/views/HomeView.vue
Normal file
84
src/views/HomeView.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<!-- HERO -->
|
||||
<section class="max-w-[960px] mx-auto px-6 pt-24 pb-16 text-center">
|
||||
<h1 class="text-2xl md:text-4xl text-olive leading-[2] mb-4 font-pixel">
|
||||
{{ t('home.hero.title') }}
|
||||
</h1>
|
||||
<p class="text-orange text-base md:text-lg mb-10 font-body">{{ t('home.hero.subtitle') }}</p>
|
||||
<router-link to="/projects">
|
||||
<button class="pixel-btn active text-sm px-6 py-3">{{ t('home.hero.cta') }}</button>
|
||||
</router-link>
|
||||
</section>
|
||||
|
||||
<!-- ABOUT TEASER -->
|
||||
<section class="max-w-[960px] mx-auto px-6 py-12">
|
||||
<div class="section">
|
||||
<h2 class="text-lg text-olive font-pixel">{{ t('home.about.title') }}</h2>
|
||||
<p class="text-beige-dark mt-4 leading-[2]">{{ t('home.about.text') }}</p>
|
||||
<router-link to="/about">
|
||||
<button class="pixel-btn text-[8px] px-4 py-3 mt-4">{{ t('nav.about') }} →</button>
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- PROJECTS TEASER -->
|
||||
<section class="max-w-[960px] mx-auto px-6 pb-12">
|
||||
<div class="section">
|
||||
<h2 class="text-lg text-olive font-pixel">{{ t('home.projects.title') }}</h2>
|
||||
<p class="text-beige-dark mt-4 leading-[2]">{{ t('home.projects.desc') }}</p>
|
||||
<div class="grid md:grid-cols-3 gap-6 mt-8">
|
||||
<div class="pixel-card">
|
||||
<div class="border-4 border-olive p-5 bg-beige-light">
|
||||
<h3 class="text-[10px] text-olive font-pixel">{{ t('projects.leuschke.title') }}</h3>
|
||||
<p class="text-sm mt-2 text-beige-dark">{{ t('projects.leuschke.desc') }}</p>
|
||||
<span class="pixel-badge badge-success mt-3 inline-block">{{ t('projects.status.done') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pixel-card">
|
||||
<div class="border-4 border-olive p-5 bg-beige-light">
|
||||
<h3 class="text-[10px] text-olive font-pixel">{{ t('projects.founderflow.title') }}</h3>
|
||||
<p class="text-sm mt-2 text-beige-dark">{{ t('projects.founderflow.desc') }}</p>
|
||||
<span class="pixel-badge badge-warning mt-3 inline-block">{{ t('projects.status.active') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pixel-card">
|
||||
<div class="border-4 border-olive p-5 bg-beige-light">
|
||||
<h3 class="text-[10px] text-olive font-pixel">{{ t('projects.neuropilot.title') }}</h3>
|
||||
<p class="text-sm mt-2 text-beige-dark">{{ t('projects.neuropilot.desc') }}</p>
|
||||
<span class="pixel-badge badge-info mt-3 inline-block">{{ t('projects.status.active') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<router-link to="/projects">
|
||||
<button class="pixel-btn active text-[8px] px-4 py-3 mt-8">{{ t('home.projects.roadmap') }} →</button>
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { t } from '../i18n'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.section {
|
||||
border: 4px solid var(--color-olive);
|
||||
padding: 2rem;
|
||||
background: var(--color-beige);
|
||||
position: relative;
|
||||
}
|
||||
.section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px; left: 6px; right: -6px; bottom: -6px;
|
||||
border: 3px solid var(--color-beige-dark);
|
||||
z-index: 0;
|
||||
}
|
||||
[data-theme="dark"] .section::before { border-color: rgba(255,255,255,0.05); }
|
||||
[data-theme="dark"] .section { border: 4px solid var(--color-olive); background: var(--color-beige-dark); }
|
||||
.pixel-card .border-4 { position: relative; z-index: 1; }
|
||||
[data-theme="dark"] .border-4 { background: var(--color-beige); }
|
||||
.badge-success { font-family: 'Press Start 2P', monospace; font-size: 7px; padding: 4px 8px; border: 2px solid var(--color-olive); color: var(--color-olive); }
|
||||
.badge-warning { font-family: 'Press Start 2P', monospace; font-size: 7px; padding: 4px 8px; border: 2px solid var(--color-orange); color: var(--color-orange); }
|
||||
.badge-info { font-family: 'Press Start 2P', monospace; font-size: 7px; padding: 4px 8px; border: 2px solid var(--color-olive-pale); color: var(--color-olive-pale); }
|
||||
</style>
|
||||
36
src/views/ImpressumView.vue
Normal file
36
src/views/ImpressumView.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<section class="max-w-[960px] mx-auto px-6 pt-24 pb-16">
|
||||
<h1 class="text-2xl text-olive leading-[2] mb-4 font-pixel">{{ t('impressum.title') }}</h1>
|
||||
<div class="section max-w-2xl">
|
||||
<h2 class="text-[10px] text-olive font-pixel mb-4">{{ t('impressum.info.title') }}</h2>
|
||||
<div class="space-y-3 text-sm text-beige-dark leading-[2]">
|
||||
<p><strong class="text-olive">{{ t('impressum.name') }}</strong></p>
|
||||
<p>{{ t('impressum.company') }}</p>
|
||||
<p>{{ t('impressum.email') }}</p>
|
||||
</div>
|
||||
|
||||
<h3 class="text-[8px] text-olive font-pixel mt-8 mb-3">Online-Streitbeilegung</h3>
|
||||
<p class="text-sm text-beige-dark leading-[2]">
|
||||
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung bereit:
|
||||
<a href="https://ec.europa.eu/consumers/odr" class="text-orange underline" target="_blank" rel="noopener">https://ec.europa.eu/consumers/odr</a>
|
||||
</p>
|
||||
<p class="text-sm text-beige-dark leading-[2]">
|
||||
Wir sind nicht verpflichtet und nicht bereit, an einem Streitbeteiligungsverfahren vor einer
|
||||
Verbraucherschlichtungsstelle teilzunehmen.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { t } from '../i18n'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.section {
|
||||
border: 4px solid var(--color-olive);
|
||||
padding: 2rem;
|
||||
background: var(--color-beige);
|
||||
}
|
||||
[data-theme="dark"] .section { background: var(--color-beige-dark); }
|
||||
</style>
|
||||
65
src/views/ProjectsView.vue
Normal file
65
src/views/ProjectsView.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<section class="max-w-[960px] mx-auto px-6 pt-24 pb-16">
|
||||
<h1 class="text-2xl text-olive leading-[2] mb-4 font-pixel">{{ t('projects.title') }}</h1>
|
||||
<p class="text-orange mb-10">{{ t('projects.subtitle') }}</p>
|
||||
|
||||
<!-- Timeline / Roadmap -->
|
||||
<div class="section">
|
||||
<h2 class="text-lg text-olive font-pixel mb-6">{{ t('projects.roadmap') }}</h2>
|
||||
|
||||
<div class="space-y-8">
|
||||
<!-- leuschke.site -->
|
||||
<div class="border-l-4 border-olive pl-6 pb-2">
|
||||
<h3 class="text-[10px] text-olive font-pixel">{{ t('projects.leuschke.title') }}</h3>
|
||||
<p class="text-sm text-beige-dark mt-1">{{ t('projects.leuschke.desc') }}</p>
|
||||
<span class="pixel-badge badge-success mt-2 inline-block">{{ t('projects.status.done') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Founderflow -->
|
||||
<div class="border-l-4 border-orange pl-6 pb-2">
|
||||
<h3 class="text-[10px] text-orange font-pixel">{{ t('projects.founderflow.title') }}</h3>
|
||||
<p class="text-sm text-beige-dark mt-1">{{ t('projects.founderflow.desc') }}</p>
|
||||
<span class="pixel-badge badge-warning mt-2 inline-block">{{ t('projects.status.active') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- MedVault -->
|
||||
<div class="border-l-4 border-beige-dark pl-6 pb-2">
|
||||
<h3 class="text-[10px] text-olive font-pixel">{{ t('projects.medvault.title') }}</h3>
|
||||
<p class="text-sm text-beige-dark mt-1">{{ t('projects.medvault.desc') }}</p>
|
||||
<span class="pixel-badge badge-info mt-2 inline-block">{{ t('projects.status.planned') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Neuropilot -->
|
||||
<div class="border-l-4 border-olive-light pl-6 pb-2">
|
||||
<h3 class="text-[10px] text-olive font-pixel">{{ t('projects.neuropilot.title') }}</h3>
|
||||
<p class="text-sm text-beige-dark mt-1">{{ t('projects.neuropilot.desc') }}</p>
|
||||
<span class="pixel-badge badge-warning mt-2 inline-block">{{ t('projects.status.active') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- OpenConsent -->
|
||||
<div class="border-l-4 border-beige-dark pl-6 pb-2">
|
||||
<h3 class="text-[10px] text-olive font-pixel">{{ t('projects.consent.title') }}</h3>
|
||||
<p class="text-sm text-beige-dark mt-1">{{ t('projects.consent.desc') }}</p>
|
||||
<span class="pixel-badge badge-info mt-2 inline-block">{{ t('projects.status.planned') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { t } from '../i18n'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.section {
|
||||
border: 4px solid var(--color-olive);
|
||||
padding: 2rem;
|
||||
background: var(--color-beige);
|
||||
}
|
||||
[data-theme="dark"] .section { background: var(--color-beige-dark); }
|
||||
.pixel-badge { font-family: 'Press Start 2P', monospace; font-size: 7px; padding: 4px 8px; border: 2px solid; }
|
||||
.badge-success { border-color: var(--color-olive); color: var(--color-olive); }
|
||||
.badge-warning { border-color: var(--color-orange); color: var(--color-orange); }
|
||||
.badge-info { border-color: var(--color-olive-pale); color: var(--color-olive-pale); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user