This commit is contained in:
2026-02-14 10:51:48 +01:00
commit 0688bca82d
21 changed files with 12426 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
22

17
Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
FROM node:22-alpine
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY . .
RUN apk add curl
RUN npm cache clear --force && npm ci
RUN npm run build
ENV NUXT_HOST=0.0.0.0
ENV NUXT_PORT=3000
EXPOSE 3000
ENTRYPOINT ["node", "/usr/src/app/.output/server/index.mjs"]

75
README.md Normal file
View File

@@ -0,0 +1,75 @@
# Nuxt Minimal Starter
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

5
app.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>

33
assets/css/main.css Normal file
View File

@@ -0,0 +1,33 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html {
@apply bg-black text-white antialiased;
scroll-behavior: smooth;
}
::selection {
@apply bg-white text-black;
}
}
/* Page transitions */
.page-enter-active,
.page-leave-active {
transition: opacity 0.3s ease;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
}
/* Staggered animation delays */
.delay-100 { animation-delay: 0.1s; }
.delay-200 { animation-delay: 0.2s; }
.delay-300 { animation-delay: 0.3s; }
.delay-400 { animation-delay: 0.4s; }
.delay-500 { animation-delay: 0.5s; }
.delay-600 { animation-delay: 0.6s; }

2
build-and-push.sh Executable file
View File

@@ -0,0 +1,2 @@
docker build -t git.bga.sh/bennetgallein/bg-it-solutions.de:latest .
docker push git.bga.sh/bennetgallein/bg-it-solutions.de:latest

View File

@@ -0,0 +1,36 @@
<template>
<a
:href="url"
target="_blank"
rel="noopener noreferrer"
class="group block border border-white/10 rounded-lg p-6 transition-all duration-300 hover:border-white/30 hover:bg-white/[0.03]"
>
<div class="flex items-start justify-between gap-4">
<div>
<h3 class="font-mono font-medium text-lg text-white group-hover:text-white transition-colors">
{{ title }}
</h3>
<p class="mt-1 text-sm text-white/50 font-mono">{{ domain }}</p>
<p class="mt-3 text-white/60 leading-relaxed">{{ description }}</p>
</div>
<svg
class="w-5 h-5 text-white/20 group-hover:text-white/60 transition-all duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5 shrink-0 mt-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="1.5"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25" />
</svg>
</div>
</a>
</template>
<script setup lang="ts">
defineProps<{
title: string
domain: string
url: string
description: string
}>()
</script>

19
layouts/default.vue Normal file
View File

@@ -0,0 +1,19 @@
<template>
<div class="min-h-screen bg-black text-white font-sans flex flex-col">
<slot />
<footer class="mt-auto border-t border-white/10 py-8 px-6">
<div class="max-w-4xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-4 text-sm text-white/40 font-mono">
<span>&copy; {{ new Date().getFullYear() }} Bennet Gallein IT Solutions</span>
<nav class="flex gap-6">
<NuxtLink to="/legal-notice" class="hover:text-white transition-colors duration-200">
Legal Notice
</NuxtLink>
<NuxtLink to="/privacy-policy" class="hover:text-white transition-colors duration-200">
Privacy Policy
</NuxtLink>
</nav>
</div>
</footer>
</div>
</template>

34
nuxt.config.ts Normal file
View File

@@ -0,0 +1,34 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
modules: [
'@nuxtjs/tailwindcss',
'@nuxtjs/google-fonts',
],
googleFonts: {
families: {
'JetBrains Mono': [400, 500, 700],
'DM Sans': [400, 500, 700],
},
display: 'swap',
},
app: {
head: {
title: 'Bennet Gallein IT Solutions',
htmlAttrs: { lang: 'en' },
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ name: 'description', content: 'Bennet Gallein IT Solutions Software, Infrastructure & APIs' },
],
link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
],
},
pageTransition: { name: 'page', mode: 'out-in' },
},
})

11792
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

19
package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "bg-it-solutions-de",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@nuxtjs/google-fonts": "^3.2.0",
"@nuxtjs/tailwindcss": "^6.14.0",
"nuxt": "^3.21.1",
"vue": "^3.5.28",
"vue-router": "^4.6.4"
}
}

61
pages/index.vue Normal file
View File

@@ -0,0 +1,61 @@
<template>
<main class="flex-1 flex flex-col justify-center px-6 py-24">
<div class="max-w-4xl mx-auto w-full">
<!-- Hero -->
<div class="mb-20 opacity-0 animate-fade-in">
<h1
class="font-mono font-bold text-4xl sm:text-5xl lg:text-6xl tracking-tight leading-[1.1]"
>
Bennet Gallein IT Solutions<span
class="animate-blink text-white/60"
>_</span
>
</h1>
<p class="mt-6 text-white/50 text-lg max-w-xl leading-relaxed">
Software, Infrastructure &amp; APIs.
</p>
</div>
<!-- Projects -->
<section>
<h2
class="font-mono text-xs text-white/30 uppercase tracking-widest mb-6 opacity-0 animate-fade-in delay-200"
>
Projects
</h2>
<div class="grid gap-4">
<div class="opacity-0 animate-fade-in-up delay-300">
<ProjectCard
title="Software Shop"
domain="bennetgallein.de"
url="https://bennetgallein.de"
description="Custom software solutions and digital products."
/>
</div>
<div class="opacity-0 animate-fade-in-up delay-400">
<ProjectCard
title="Remote Backups"
domain="remote-backups.com"
url="https://remote-backups.com"
description="Infrastructure as a Service secure, managed backup solutions."
/>
</div>
<div class="opacity-0 animate-fade-in-up delay-500">
<ProjectCard
title="VAT API"
domain="vat-api.eu"
url="https://vat-api.eu"
description="EU VAT validation and calculation as an API."
/>
</div>
</div>
</section>
</div>
</main>
</template>
<script setup lang="ts">
useHead({
title: "Bennet Gallein IT Solutions",
});
</script>

88
pages/legal-notice.vue Normal file
View File

@@ -0,0 +1,88 @@
<template>
<main class="flex-1 px-6 py-24">
<div class="max-w-3xl mx-auto w-full">
<NuxtLink
to="/"
class="inline-flex items-center gap-2 font-mono text-sm text-white/40 hover:text-white transition-colors duration-200 mb-12"
>
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" />
</svg>
Back
</NuxtLink>
<article class="opacity-0 animate-fade-in prose-legal">
<h1 class="font-mono font-bold text-3xl sm:text-4xl mb-12">Legal Notice</h1>
<section class="mb-10">
<h2 class="font-mono font-medium text-lg mb-3 text-white/80">Information pursuant to § 5 TMG</h2>
<div class="text-white/60 leading-relaxed space-y-1">
<p>Bennet Gallein</p>
<p>Bennet Gallein IT Solutions</p>
<p>Elbinger Str. 18</p>
<p>21339 Lüneburg</p>
</div>
</section>
<section class="mb-10">
<h2 class="font-mono font-medium text-lg mb-3 text-white/80">Contact</h2>
<div class="text-white/60 leading-relaxed space-y-1">
<p>Phone: +49 1772489624</p>
<p>Email: me@bennetgallein.de</p>
</div>
</section>
<section class="mb-10">
<h2 class="font-mono font-medium text-lg mb-3 text-white/80">VAT ID</h2>
<div class="text-white/60 leading-relaxed space-y-1">
<p>
VAT identification number pursuant to § 27a of the German VAT Act:<br />
DE320525917
</p>
</div>
</section>
<section class="mb-10">
<h2 class="font-mono font-medium text-lg mb-3 text-white/80">Responsible for content pursuant to § 55 para. 2 RStV</h2>
<div class="text-white/60 leading-relaxed space-y-1">
<p>Bennet Gallein</p>
<p>Elbinger Str. 18</p>
<p>21339 Lüneburg</p>
</div>
</section>
<section class="mb-10">
<h2 class="font-mono font-medium text-lg mb-3 text-white/80">EU Dispute Resolution</h2>
<div class="text-white/60 leading-relaxed">
<p>
The European Commission provides a platform for online dispute resolution (ODR):
<a
href="https://ec.europa.eu/consumers/odr/"
target="_blank"
rel="noopener noreferrer"
class="text-white/80 underline underline-offset-4 hover:text-white transition-colors"
>https://ec.europa.eu/consumers/odr/</a>.
</p>
<p class="mt-2">Our email address can be found above in the legal notice.</p>
</div>
</section>
<section class="mb-10">
<h2 class="font-mono font-medium text-lg mb-3 text-white/80">Consumer Dispute Resolution</h2>
<div class="text-white/60 leading-relaxed">
<p>
We are not willing or obliged to participate in dispute resolution proceedings
before a consumer arbitration board.
</p>
</div>
</section>
</article>
</div>
</main>
</template>
<script setup lang="ts">
useHead({
title: 'Legal Notice Bennet Gallein IT Solutions',
})
</script>

176
pages/privacy-policy.vue Normal file
View File

@@ -0,0 +1,176 @@
<template>
<main class="flex-1 px-6 py-24">
<div class="max-w-3xl mx-auto w-full">
<NuxtLink
to="/"
class="inline-flex items-center gap-2 font-mono text-sm text-white/40 hover:text-white transition-colors duration-200 mb-12"
>
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" />
</svg>
Back
</NuxtLink>
<article class="opacity-0 animate-fade-in prose-legal">
<h1 class="font-mono font-bold text-3xl sm:text-4xl mb-12">Privacy Policy</h1>
<section class="mb-10">
<div class="text-white/60 leading-relaxed space-y-3">
<p>
Unless stated otherwise below, providing your personal data is neither legally
nor contractually required. You are not obligated to provide data, and there are
no consequences for not doing so.
</p>
<p>
"Personal data" refers to all information relating to an identified or identifiable
natural person.
</p>
</div>
</section>
<section class="mb-10">
<h2 class="font-mono font-medium text-lg mb-3 text-white/80">Server Log Files</h2>
<div class="text-white/60 leading-relaxed space-y-3">
<p>
You can visit this website without providing any personal information. Each time
this website is accessed, usage data is transmitted by your internet browser and
stored in log files (server log files). This stored data includes, for example:
</p>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>Name of the page accessed</li>
<li>Date and time of access</li>
<li>IP address</li>
<li>Amount of data transferred</li>
<li>Requesting provider</li>
</ul>
<p>
Processing is carried out pursuant to Art. 6 para. 1 lit. f GDPR based on our
legitimate interest in ensuring uninterrupted operation of the website and improving
our services.
</p>
</div>
</section>
<section class="mb-10">
<h2 class="font-mono font-medium text-lg mb-3 text-white/80">Contact</h2>
<div class="text-white/60 leading-relaxed space-y-3">
<h3 class="font-mono text-sm font-medium text-white/60 mb-2 mt-4">Data Controller</h3>
<div class="space-y-1">
<p>Bennet Gallein</p>
<p>Bennet Gallein IT Solutions</p>
<p>Elbinger Str. 18</p>
<p>21339 Lüneburg</p>
<p class="mt-2">Phone: +49 1772489624</p>
<p>Email: me@bennetgallein.de</p>
</div>
<h3 class="font-mono text-sm font-medium text-white/60 mb-2 mt-6">Contact via Email</h3>
<p>
When you contact us via email, we only collect the personal data you provide
(name, email address, message text). Processing serves to handle and respond
to your inquiry.
</p>
<p>
If the contact relates to pre-contractual measures or an existing contract,
processing is carried out pursuant to Art. 6 para. 1 lit. b GDPR. In all other
cases, processing is based on Art. 6 para. 1 lit. f GDPR. You have the right to
object to this processing based on your particular situation.
</p>
<p>
Your email address is used exclusively for processing your inquiry. Data is
deleted after statutory retention periods expire, unless you have consented to
further processing.
</p>
</div>
</section>
<section class="mb-10">
<h2 class="font-mono font-medium text-lg mb-3 text-white/80">Cookies</h2>
<div class="text-white/60 leading-relaxed space-y-3">
<p>
Our website uses cookies small text files that are stored by your browser
on your device. Cookies contain characteristic strings that enable browser
identification upon revisiting the site.
</p>
<p>
We use only technically necessary cookies to make our website user-friendly,
effective, and secure. Some functions of the website require the browser to be
recognized across page changes.
</p>
<p>
Cookie usage is based on § 25 para. 2 TTDSG. Processing of personal data
occurs pursuant to Art. 6 para. 1 lit. f GDPR, based on our legitimate interest
in ensuring website functionality and an effective service design.
</p>
<p>
You have the right to object to this processing based on your particular
situation. You can manage or delete cookies at any time through your browser
settings, though this may limit website functionality.
</p>
</div>
</section>
<section class="mb-10">
<h2 class="font-mono font-medium text-lg mb-3 text-white/80">Data Subject Rights &amp; Storage Duration</h2>
<h3 class="font-mono text-sm font-medium text-white/60 mb-2 mt-4">Storage Duration</h3>
<div class="text-white/60 leading-relaxed space-y-3">
<p>
After complete fulfillment of the purpose, data is retained considering legal,
tax, and commercial retention requirements, then deleted upon expiration of
those periods, unless you have consented to further processing.
</p>
</div>
<h3 class="font-mono text-sm font-medium text-white/60 mb-2 mt-6">Your Rights</h3>
<div class="text-white/60 leading-relaxed space-y-3">
<p>
Where the legal requirements are met, you have the following rights under
Art. 1520 GDPR: right to access, right to correction, right to deletion,
right to restriction of processing, and right to data portability.
</p>
<p>
Additionally, under Art. 21 para. 1 GDPR, you have the right to object to
processing based on Art. 6 para. 1 lit. f GDPR, as well as to processing for
direct marketing purposes.
</p>
</div>
<h3 class="font-mono text-sm font-medium text-white/60 mb-2 mt-6">Right to Lodge a Complaint</h3>
<div class="text-white/60 leading-relaxed space-y-3">
<p>
Under Art. 77 GDPR, you have the right to lodge a complaint with the supervisory
authority if you believe that the processing of your personal data violates the law.
</p>
<div class="mt-2 space-y-1">
<p class="text-white/80 font-medium">Competent Supervisory Authority:</p>
<p>Landesbeauftragte für den Datenschutz Niedersachsen</p>
<p>Prinzenstraße 5</p>
<p>30159 Hannover</p>
<p class="mt-2">Phone: +49 511 1204500</p>
<p>Fax: +49 511 1204599</p>
<p>Email: poststelle@lfd.niedersachsen.de</p>
</div>
</div>
<h3 class="font-mono text-sm font-medium text-white/60 mb-2 mt-6">Right to Object</h3>
<div class="text-white/60 leading-relaxed">
<p>
Where processing is based on Art. 6 para. 1 lit. f GDPR, you have the right to
object at any time on grounds relating to your particular situation. Following
your objection, processing will cease unless there are compelling legitimate
grounds that override your interests, or the processing serves to assert,
exercise, or defend legal claims.
</p>
</div>
</section>
</article>
</div>
</main>
</template>
<script setup lang="ts">
useHead({
title: 'Privacy Policy Bennet Gallein IT Solutions',
})
</script>

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

4
public/favicon.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#000"/>
<text x="4" y="23" font-family="monospace" font-size="20" font-weight="bold" fill="#fff">BG</text>
</svg>

After

Width:  |  Height:  |  Size: 221 B

2
public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-Agent: *
Disallow:

3
server/tsconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

31
tailwind.config.ts Normal file
View File

@@ -0,0 +1,31 @@
import type { Config } from 'tailwindcss'
export default {
theme: {
extend: {
fontFamily: {
mono: ['"JetBrains Mono"', 'monospace'],
sans: ['"DM Sans"', 'sans-serif'],
},
animation: {
'fade-in': 'fadeIn 0.8s ease-out forwards',
'fade-in-up': 'fadeInUp 0.8s ease-out forwards',
'blink': 'blink 1s step-end infinite',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
fadeInUp: {
'0%': { opacity: '0', transform: 'translateY(16px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
blink: {
'0%, 100%': { opacity: '1' },
'50%': { opacity: '0' },
},
},
},
},
} satisfies Config

4
tsconfig.json Normal file
View File

@@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}