This commit is contained in:
Clemens Hering
2025-11-03 07:11:48 +01:00
parent 0c0c4d7185
commit 45606723ea
32 changed files with 879 additions and 0 deletions

View File

@@ -0,0 +1 @@
export default new Map();

View File

@@ -0,0 +1 @@
export default new Map();

199
.astro/content.d.ts vendored Normal file
View File

@@ -0,0 +1,199 @@
declare module 'astro:content' {
export interface RenderResult {
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}
interface Render {
'.md': Promise<RenderResult>;
}
export interface RenderedContent {
html: string;
metadata?: {
imagePaths: Array<string>;
[key: string]: unknown;
};
}
}
declare module 'astro:content' {
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof AnyEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
export type ContentCollectionKey = keyof ContentEntryMap;
export type DataCollectionKey = keyof DataEntryMap;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
ContentEntryMap[C]
>['slug'];
export type ReferenceDataEntry<
C extends CollectionKey,
E extends keyof DataEntryMap[C] = string,
> = {
collection: C;
id: E;
};
export type ReferenceContentEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}) = string,
> = {
collection: C;
slug: E;
};
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
collection: C;
id: string;
};
/** @deprecated Use `getEntry` instead. */
export function getEntryBySlug<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
// Note that this has to accept a regular string too, for SSR
entrySlug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
/** @deprecated Use `getEntry` instead. */
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
collection: C,
entryId: E,
): Promise<CollectionEntry<C>>;
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E,
): Promise<E[]>;
export function getCollection<C extends keyof AnyEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown,
): Promise<CollectionEntry<C>[]>;
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter?: LiveLoaderCollectionFilterType<C>,
): Promise<
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
entry: ReferenceContentEntry<C, E>,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
entry: ReferenceDataEntry<C, E>,
): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
slug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E,
): E extends keyof DataEntryMap[C]
? string extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]> | undefined
: Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter: string | LiveLoaderEntryFilterType<C>,
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof ContentEntryMap>(
entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[],
): Promise<CollectionEntry<C>[]>;
export function getEntries<C extends keyof DataEntryMap>(
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
): Promise<CollectionEntry<C>[]>;
export function render<C extends keyof AnyEntryMap>(
entry: AnyEntryMap[C][string],
): Promise<RenderResult>;
export function reference<C extends keyof AnyEntryMap>(
collection: C,
): import('astro/zod').ZodEffects<
import('astro/zod').ZodString,
C extends keyof ContentEntryMap
? ReferenceContentEntry<C, ValidContentEntrySlug<C>>
: ReferenceDataEntry<C, keyof DataEntryMap[C]>
>;
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
export function reference<C extends string>(
collection: C,
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ContentEntryMap = {
};
type DataEntryMap = {
};
type AnyEntryMap = ContentEntryMap & DataEntryMap;
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
infer TData,
infer TEntryFilter,
infer TCollectionFilter,
infer TError
>
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
: { data: never; entryFilter: never; collectionFilter: never; error: never };
type ExtractDataType<T> = ExtractLoaderTypes<T>['data'];
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
LiveContentConfig['collections'][C]['schema'] extends undefined
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
: import('astro/zod').infer<
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
>;
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
LiveContentConfig['collections'][C]['loader']
>;
export type ContentConfig = typeof import("../src/content.config.mjs");
export type LiveContentConfig = never;
}

1
.astro/data-store.json Normal file
View File

@@ -0,0 +1 @@
[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.15.3","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"site\":\"https://valtrix.systems\",\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[]},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false},\"legacy\":{\"collections\":false}}"]

5
.astro/settings.json Normal file
View File

@@ -0,0 +1,5 @@
{
"_variables": {
"lastUpdateCheck": 1762086631503
}
}

1
.astro/types.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="astro/client" />

9
.containerignore Normal file
View File

@@ -0,0 +1,9 @@
node_modules
.astro
.vscode
.git
npm-debug.log*
yarn-error.log*
.DS_Store
# We rebuild inside the image, no need to send dist
/dist

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
node_modules
package-lock.json
dist
.env
.DS_Store
.vscode

7
.stylelintrc.json Normal file
View File

@@ -0,0 +1,7 @@
{
"rules": {
"at-rule-no-unknown": [true, {
"ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"]
}]
}
}

25
Containerfile Normal file
View File

@@ -0,0 +1,25 @@
# Multi-stage build for Astro static site
FROM node:22-alpine AS builder
WORKDIR /app
# Install deps
COPY package*.json ./
RUN npm ci || npm install
# Copy sources and build static output
COPY . .
RUN npm run build
# ---- Runtime stage ----
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV WEB_ROOT=/app/dist
ENV TZ=Europe/Berlin
COPY --from=builder /app/dist /app/dist
COPY server.mjs /app/server.mjs
# Drop root: use the pre-created node user
USER node
#EXPOSE 3000
CMD ["node", "/app/server.mjs"]

37
README.md Normal file
View File

@@ -0,0 +1,37 @@
# Valtrix Corporate Website (Astro + Tailwind) v4
- Dark Hero standard, Case Studies integriert
- Logo: `public/logo-valtrix.png`
## Quickstart
```bash
npm ci
npm run dev
npm run build
```
## Container (Podman)
Container bauen und starten:
```bash
# Image bauen (nutzt Containerfile im Repo)
podman build -t valtrix-site .
# Container starten (localhost:8080 → Container:3000)
podman run --rm -p 8080:3000 valtrix-site
```
Optional: als Pod via `podman play kube` (setzt lokales Image `localhost/valtrix-site:latest` voraus):
```bash
# Taggen für den lokalen Registry-Namespace
podman tag valtrix-site localhost/valtrix-site:latest
# Pod aus YAML starten
podman play kube deploy/podman-kube.yaml
```
Hinweise:
- Das Image ist zweistufig: Build in Node 22 Alpine, Runtime in NGINX Alpine.
- Die Seite ist rein statisch (Astro output: static) und benötigt keinen ServerSideCode.

2
astro.config.mjs Normal file
View File

@@ -0,0 +1,2 @@
import { defineConfig } from 'astro/config';
export default defineConfig({ site: 'https://valtrix.systems' });

18
deploy/podman-kube.yaml Normal file
View File

@@ -0,0 +1,18 @@
apiVersion: v1
kind: Pod
metadata:
name: valtrix-site
spec:
containers:
- name: web
image: localhost/valtrix-site:latest
ports:
- containerPort: 3000
resources: {}
# Read-only root filesystem is fine for static serving
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
restartPolicy: Never

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "valtrix-site",
"version": "0.4.0",
"private": true,
"type": "module",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"dependencies": {
"astro": "^5.15.3"
},
"devDependencies": {
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.14"
},
"engines": {
"node": ">=22"
}
}

1
postcss.config.js Normal file
View File

@@ -0,0 +1 @@
export default { plugins: { tailwindcss: {}, autoprefixer: {} } };

1
public/favicon.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><circle cx="32" cy="32" r="28" fill="#0D47A1"/><path d="M18 40 L32 24 L46 34" stroke="#00B8D9" stroke-width="6" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 238 B

BIN
public/logo-valtrix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

1
public/styles.css Normal file
View File

@@ -0,0 +1 @@
@import "/_astro/src/styles/globals.css";

98
server.mjs Normal file
View File

@@ -0,0 +1,98 @@
import http from 'http';
import fs from 'fs';
import path from 'path';
import url from 'url';
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
const ROOT = process.env.WEB_ROOT || path.resolve(__dirname, 'dist');
const PORT = Number(process.env.PORT || 3000);
const MIME = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.mjs': 'application/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.webp': 'image/webp',
'.gif': 'image/gif',
'.ico': 'image/x-icon',
'.txt': 'text/plain; charset=utf-8',
'.map': 'application/json; charset=utf-8',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.otf': 'font/otf',
'.eot': 'application/vnd.ms-fontobject',
'.mp4': 'video/mp4',
'.webm': 'video/webm',
'.wasm': 'application/wasm'
};
function send(res, status, headers, stream) {
res.writeHead(status, headers);
if (stream) stream.pipe(res); else res.end();
}
function isUnder(p, root) {
const rel = path.relative(root, p);
return !!rel && !rel.startsWith('..') && !path.isAbsolute(rel);
}
const server = http.createServer((req, res) => {
try {
const parsed = url.parse(req.url || '/');
let pathname = decodeURIComponent(parsed.pathname || '/');
// Hard normalize, prevent traversal
pathname = path.normalize(pathname).replace(/^([/\\])*|\/+$/g, '/');
let fp = path.join(ROOT, pathname);
// If path is a directory, serve index.html
if (fs.existsSync(fp) && fs.statSync(fp).isDirectory()) {
fp = path.join(fp, 'index.html');
}
// If it doesn't exist and path didn't include .html, try appending index.html
if (!fs.existsSync(fp) && !path.extname(fp)) {
fp = path.join(fp, 'index.html');
}
// Verify containment in ROOT
if (!isUnder(fp, ROOT) && path.resolve(fp) !== path.resolve(ROOT, 'index.html')) {
return send(res, 403, { 'content-type': 'text/plain; charset=utf-8' }, null);
}
if (!fs.existsSync(fp) || fs.statSync(fp).isDirectory()) {
return send(res, 404, { 'content-type': 'text/plain; charset=utf-8' }, null);
}
const ext = path.extname(fp).toLowerCase();
const type = MIME[ext] || 'application/octet-stream';
const headers = { 'content-type': type };
// Caching: long cache for assets, no-cache for html
if (ext === '.html') {
headers['cache-control'] = 'no-cache';
} else if (fp.includes('/_astro/') || fp.startsWith(path.join(ROOT, 'assets'))) {
headers['cache-control'] = 'public, max-age=31536000, immutable';
} else {
headers['cache-control'] = 'public, max-age=3600';
}
const stream = fs.createReadStream(fp);
stream.on('open', () => send(res, 200, headers, stream));
stream.on('error', () => send(res, 500, { 'content-type': 'text/plain; charset=utf-8' }, null));
} catch (err) {
send(res, 500, { 'content-type': 'text/plain; charset=utf-8' }, null);
}
});
server.listen(PORT, '0.0.0.0', () => {
// eslint-disable-next-line no-console
console.log(`Static server listening on :${PORT}, root: ${ROOT}`);
});

56
src/layouts/Base.astro Normal file
View File

@@ -0,0 +1,56 @@
---
import "../styles/globals.css";
const { title = "Valtrix Struktur schafft Wert", description = "Cloud, Security & AI Consulting aus Berlin", path = "/" } = Astro.props;
---
<!doctype html>
<html lang="de" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:url" content={`https://valtrix.systems${path}`} />
<meta property="og:image" content="/og-image.png" />
<link rel="icon" type="image/png" href="/logo-valtrix.png" />
</head>
<body class="bg-white transition-colors duration-300">
<header class="max-w-7xl mx-auto px-6 py-6">
<nav class="flex items-center justify-between">
<a href="/" class="flex items-center gap-3 font-semibold">
<img src="/logo-valtrix.png" alt="Valtrix" class="h-9 w-9 rounded-md shadow-sm"/>
<span class="tracking-wide text-lg">VALTRIX</span>
</a>
<div class="flex items-center gap-6">
<a href="/leistungen" class="text-textMuted hover:text-white">Leistungen</a>
<a href="/sicherheit" class="text-textMuted hover:text-white">Sicherheit</a>
<a href="/cases" class="text-textMuted hover:text-white">Referenzen</a>
<a href="/ueber-uns" class="text-textMuted hover:text-white">Über uns</a>
<a href="/kontakt" class="px-4 py-2 rounded-brand bg-primary text-white hover:opacity-90">Kontakt</a>
<button id="themeToggle" aria-label="Theme" class="px-3 py-2 border rounded-brand hover:bg-graybg">Light</button>
</div>
</nav>
</header>
<main class="max-w-7xl mx-auto px-6">
<slot />
</main>
<footer class="max-w-7xl mx-auto px-6 py-12 text-sm text-textMuted">
<div class="flex items-center gap-3 mb-3">
<strong>Valtrix</strong> · Precision. Power. Purpose.
</div>
© {new Date().getFullYear()} Valtrix · <a href="/impressum" class="underline">Impressum</a> · <a href="/datenschutz" class="underline">Datenschutz</a>
</footer>
<script>
const btn = document.getElementById('themeToggle');
const html = document.documentElement;
const set = (m)=>{ html.setAttribute('data-theme', m); btn.textContent = m==='dark' ? 'Light' : 'Dark'; };
set(localStorage.getItem('theme')||'dark');
btn?.addEventListener('click', ()=>{
const next = html.getAttribute('data-theme')==='dark' ? 'light' : 'dark';
set(next); localStorage.setItem('theme', next);
});
</script>
</body>
</html>

19
src/pages/cases.astro Normal file
View File

@@ -0,0 +1,19 @@
---
import Base from "../layouts/Base.astro";
const items = [
{ slug: "cloud-migration", title: "Sichere Cloud-Migration (AWS/Azure)", summary: "Landing Zone, Zero-Trust, Pipeline & Observability in 6 Wochen." },
{ slug: "k8s-hardening", title: "Kubernetes Hardening & GitOps", summary: "CIS Benchmarks, OPA Policies-as-Code, ArgoCD Rollouts." }
];
---
<Base path="/cases" title="Referenzen Valtrix">
<h1 class="text-3xl font-bold mt-8">Referenzen</h1>
<div class="grid md:grid-cols-2 gap-6 mt-6">
{items.map(i => (
<a href={`/cases/${i.slug}`} class="p-6 border rounded-brand card hover:shadow-sm transition-shadow block">
<h2 class="font-semibold text-lg">{i.title}</h2>
<p class="mt-2 text-textMuted">{i.summary}</p>
<span class="mt-3 inline-block text-primary underline">Case ansehen</span>
</a>
))}
</div>
</Base>

View File

@@ -0,0 +1,36 @@
---
import Base from "../../layouts/Base.astro";
---
<Base path="/cases/cloud-migration" title="Case Study Sichere Cloud-Migration">
<h1 class="text-3xl font-bold mt-8">Case Study: Sichere Cloud-Migration</h1>
<p class="mt-4 text-textMuted">Enterprise-Migration von On-Prem nach Cloud mit Fokus auf Sicherheit & Compliance.</p>
<section class="mt-8 grid md:grid-cols-3 gap-6">
<div class="p-6 border rounded-brand card">
<h3 class="font-semibold">Ziel</h3>
<p class="mt-2 text-textMuted">Sichere AWS/Azure Landing Zone, segmentiertes Netzwerk, zentralisiertes IAM.</p>
</div>
<div class="p-6 border rounded-brand card">
<h3 class="font-semibold">Vorgehen</h3>
<p class="mt-2 text-textMuted">Guardrails, IaC (Terraform/OpenTofu), GitOps, Observability, Cost Controls.</p>
</div>
<div class="p-6 border rounded-brand card">
<h3 class="font-semibold">Ergebnis</h3>
<p class="mt-2 text-textMuted">Schnelleres Deployment, Auditfähigkeit, reduzierte Risiken & Kosten.</p>
</div>
</section>
<section class="mt-10">
<h2 class="text-2xl font-bold mb-3">Kennzahlen</h2>
<ul class="list-disc pl-6 text-textMuted">
<li>6 Wochen bis produktionsreifer Basis</li>
<li>~40% weniger manuelle Deployments durch CI/CD</li>
<li>100% Infrastruktur als Code & Policies-as-Code</li>
</ul>
</section>
<div class="mt-12 rounded-brand p-6 bg-gradient-to-r from-accent to-primary text-white flex items-center justify-between">
<div class="text-lg font-semibold tracking-wide">Ähnliches Projekt geplant?</div>
<a href="/kontakt" class="px-5 py-3 rounded-brand bg-white text-primary font-medium">Jetzt anfragen</a>
</div>
</Base>

View File

@@ -0,0 +1,48 @@
---
import Base from "../../layouts/Base.astro";
---
<Base path="/cases/k8s-hardening" title="Case Study Kubernetes Hardening & GitOps">
<h1 class="text-3xl font-bold mt-8">Case Study: Kubernetes Hardening & GitOps</h1>
<p class="mt-4 text-textMuted">Produktionsreifes K8sSetup mit Härtung nach CIS, durchgängigen PoliciesasCode und GitOpsDeployments.</p>
<section class="mt-8 grid md:grid-cols-3 gap-6">
<div class="p-6 border rounded-brand card">
<h3 class="font-semibold">Ziel</h3>
<p class="mt-2 text-textMuted">Sicheres ClusterBaseline: identitätszentrierte Zugriffe, NetzwerkSegmentierung, saubere Supply Chain.</p>
</div>
<div class="p-6 border rounded-brand card">
<h3 class="font-semibold">Vorgehen</h3>
<p class="mt-2 text-textMuted">CIS Benchmarks, OPA/Gatekeeper oder Kyverno Policies, Pod Security Standards, signierte Artefakte & GitOps (Argo CD).</p>
</div>
<div class="p-6 border rounded-brand card">
<h3 class="font-semibold">Ergebnis</h3>
<p class="mt-2 text-textMuted">Reproduzierbare Deployments, Policykonforme Releases, Auditfähigkeit und reduzierte Angriffsfläche.</p>
</div>
</section>
<section class="mt-10">
<h2 class="text-2xl font-bold mb-3">Kennzahlen</h2>
<ul class="list-disc pl-6 text-textMuted">
<li>100% deklarative Deployments (GitOps, Pullbasiert)</li>
<li>~80% der CISKontrollen automatisiert überprüfbar</li>
<li>PolicyBreaks verhindern riskante Manifeste vor dem Rollout</li>
<li>Signierte ContainerImages & SBOM für Kernservices</li>
</ul>
</section>
<section class="mt-10 grid md:grid-cols-2 gap-6">
<div class="p-6 border rounded-brand card">
<h3 class="font-semibold">GitOps Setup</h3>
<p class="mt-2 text-textMuted">Mehrstufige Environments (dev/stage/prod) mit Branch/TagStrategie, KustomizeOverlays und PRbasierten Changes.</p>
</div>
<div class="p-6 border rounded-brand card">
<h3 class="font-semibold">Cluster Hardening</h3>
<p class="mt-2 text-textMuted">RBAC LeastPrivilege, NetworkPolicies defaultdeny, Pod Security, ImageScanning, SecretsManagement.</p>
</div>
</section>
<div class="mt-12 rounded-brand p-6 bg-gradient-to-r from-accent to-primary text-white flex items-center justify-between">
<div class="text-lg font-semibold tracking-wide">Ähnliches Projekt geplant?</div>
<a href="/kontakt" class="px-5 py-3 rounded-brand bg-white text-primary font-medium">Jetzt anfragen</a>
</div>
</Base>

View File

@@ -0,0 +1,62 @@
---
import Base from "../layouts/Base.astro";
const updated = new Date().toLocaleDateString('de-DE', { year: 'numeric', month: 'long', day: '2-digit' });
---
<Base path="/datenschutz" title="Datenschutz Valtrix">
<h1 class="text-2xl font-bold mt-8">Datenschutzhinweise</h1>
<p class="mt-4 text-textMuted">Diese Hinweise informieren darüber, wie wir personenbezogene Daten beim Besuch dieser Website verarbeiten. Die Website ist als statische Seite umgesetzt; es werden keine Tracking oder MarketingCookies eingesetzt und keine externen Ressourcen (z.B. Google Fonts, Analytics, Formdienste) geladen.</p>
<section class="mt-8 grid gap-2">
<h2 class="text-xl font-semibold">1. Verantwortlicher</h2>
<p>Verantwortlicher im Sinne der DSGVO ist: <strong>Valtrix</strong>. Anschrift siehe <a class="underline text-primary" href="/impressum">Impressum</a>. EMail: <a class="underline text-primary" href="mailto:kontakt@valtrix.systems">kontakt@valtrix.systems</a></p>
</section>
<section class="mt-6 grid gap-2">
<h2 class="text-xl font-semibold">2. Hosting & Bereitstellung</h2>
<p>Diese Website wird als statische Anwendung ausgeliefert. Die Auslieferung erfolgt über Infrastruktur innerhalb der EU. </p>
</section>
<section class="mt-6 grid gap-2">
<h2 class="text-xl font-semibold">3. ServerLogfiles</h2>
<p>Beim Aufruf der Webseite werden durch den zuständigen Server/Proxy systembedingt Zugriffsdaten verarbeitet (z.B. IPAdresse, Datum/Uhrzeit, angeforderte Ressource/URL, Referrer, UserAgent, Statuscode). Die Verarbeitung dient dem Betrieb, der Sicherheit und Fehleranalyse.</p>
<ul class="list-disc pl-6 text-textMuted">
<li>Rechtsgrundlage: Art.6 Abs.1 lit.f DSGVO (berechtigtes Interesse am sicheren Betrieb)</li>
<li>Speicherdauer: Protokolle werden regelmäßig rotiert und gelöscht, sofern keine sicherheitsrelevante Auswertung erforderlich ist.</li>
</ul>
</section>
<section class="mt-6 grid gap-2">
<h2 class="text-xl font-semibold">4. Cookies & externe Inhalte</h2>
<p>Wir verwenden keine Tracking oder MarketingCookies. Es werden keine externen Inhalte, Schriftarten oder Skripte von Drittanbietern nachgeladen.</p>
</section>
<section class="mt-6 grid gap-2">
<h2 class="text-xl font-semibold">5. Kontaktaufnahme</h2>
<p>Bei Kontakt per EMail oder über die bereitgestellte <em>mailto</em>Funktion verarbeiten wir die mitgeteilten Daten (z.B. Name, EMail, Nachricht) zur Bearbeitung der Anfrage.</p>
<ul class="list-disc pl-6 text-textMuted">
<li>Rechtsgrundlage: Art.6 Abs.1 lit.b DSGVO (vorvertragliche/vertragliche Kommunikation) bzw. lit.f (allgemeine Anfragen)</li>
<li>Speicherdauer: Wir löschen Anfragen, sobald sie erledigt sind, es sei denn, gesetzliche Aufbewahrungspflichten verlangen eine längere Speicherung.</li>
</ul>
</section>
<section class="mt-6 grid gap-2">
<h2 class="text-xl font-semibold">6. Empfänger & Auftragsverarbeiter</h2>
<p>Zur Bereitstellung der Website können wir technische Dienstleister (z.B. Hosting/Operations) einsetzen. Diese verarbeiten Daten ausschließlich nach unserer Weisung (Art.28 DSGVO). Eine Übermittlung in Drittländer findet nicht statt, sofern nicht ausdrücklich angegeben.</p>
</section>
<section class="mt-6 grid gap-2">
<h2 class="text-xl font-semibold">7. Ihre Rechte</h2>
<p>Sie haben gegenüber uns Rechte auf Auskunft (Art.15 DSGVO), Berichtigung (Art.16), Löschung (Art.17), Einschränkung (Art.18), Datenübertragbarkeit (Art.20) sowie Widerspruch (Art.21). Zudem besteht ein Beschwerderecht bei einer DatenschutzAufsichtsbehörde (Art.77).</p>
</section>
<section class="mt-6 grid gap-2">
<h2 class="text-xl font-semibold">8. Sicherheit</h2>
<p>Wir treffen technische und organisatorische Maßnahmen gemäß Art.32 DSGVO (u.a. TLSVerschlüsselung, Härtung der Infrastruktur, geringste Privilegien). Die Website lädt keine Ressourcen von Dritten.</p>
</section>
<section class="mt-6 grid gap-2">
<h2 class="text-xl font-semibold">9. Aktualität dieser Hinweise</h2>
<p>Stand: {updated}. Wir passen diese Hinweise an, wenn sich Technik, Rechtslage oder unser Angebot ändern.</p>
</section>
</Base>

11
src/pages/impressum.astro Normal file
View File

@@ -0,0 +1,11 @@
---
import Base from "../layouts/Base.astro";
---
<Base path="/impressum" title="Impressum Valtrix">
<h1 class="text-2xl font-bold mt-8">Impressum</h1>
<p><strong>VALTRIX</strong> GmbH i.G.</p>
<p>Am Wassergraben 4, 10179 Berlin</p>
<p>Amtsgericht Charlottenburg. Handelregister: HRB-XXXX </p>
<p>Umsatzsteuer-ID: XXXXXXX</p>
<p class="mt-4 text-textMuted">Platzhalter für Unternehmensangaben (Sitz, Register, Vertretungsberechtigte, Kontakt).</p>
</Base>

73
src/pages/index.astro Normal file
View File

@@ -0,0 +1,73 @@
---
import Base from "../layouts/Base.astro";
const benefits = [
{ title: "Zero-Trust & Hardening", text: "Security-by-Design, CIS/BSI-Guidelines, automatisierte Policies." },
{ title: "Cloud Foundations", text: "Landing Zones, Identity, Observability sauber & skalierbar." },
{ title: "Automatisierung", text: "IaC/CI/CD, GitOps, wiederholbar & auditfähig." }
];
const features = [
{ title: "Cloud & DevOps", text: "Plan, Build & Run effizient, sicher, skalierbar." },
{ title: "Security & Compliance", text: "BSI/ISO-aligned, Zero-Trust, Audits, Policies-as-Code." },
{ title: "AI & Automation", text: "RAG, Agenten & Process Automation mit messbarem Outcome." }
];
---
<Base path="/">
<section class="py-16 grid lg:grid-cols-2 gap-12 items-center">
<div class="fade-in">
<h1 class="text-4xl md:text-5xl font-bold leading-tight">
Security-first Cloud Consulting.
</h1>
<p class="mt-4 text-lg text-textMuted">
Wir verbinden <strong>Struktur</strong> und <strong>Wert</strong>: sichere Cloud-Architekturen, Automatisierung und Compliance pragmatisch und messbar.
</p>
<div class="mt-8 flex gap-4">
<a href="/leistungen" class="px-5 py-3 rounded-brand bg-primary text-white hover:opacity-90">Leistungen</a>
<a href="/kontakt" class="px-5 py-3 rounded-brand border border-primary text-primary hover:bg-graybg">Kontakt</a>
</div>
<div class="mt-8 grid grid-cols-3 gap-4 text-sm text-textMuted">
<div class="p-3 border rounded-brand card">DSGVO / EU-Hosting</div>
<div class="p-3 border rounded-brand card">BSI/ISO-orientiert</div>
<div class="p-3 border rounded-brand card">Auditfähig</div>
</div>
</div>
</section>
<section class="py-10 grid md:grid-cols-3 gap-6">
{benefits.map(b => (
<div class="p-6 border rounded-brand hover:shadow-sm transition-shadow card">
<h3 class="font-semibold text-lg">{b.title}</h3>
<p class="mt-2 text-textMuted">{b.text}</p>
</div>
))}
</section>
<section class="py-12">
<h2 class="text-2xl font-bold mb-6">Leistungen</h2>
<div class="grid md:grid-cols-3 gap-6">
{features.map(f => (
<div class="p-6 border rounded-brand hover:-translate-y-0.5 transition card">
<h3 class="font-semibold text-lg">{f.title}</h3>
<p class="mt-2 text-textMuted">{f.text}</p>
<a href="/leistungen" class="mt-3 inline-block text-primary underline">Mehr erfahren</a>
</div>
))}
</div>
</section>
<section class="py-14">
<h2 class="text-2xl font-bold mb-6">Unser Ansatz</h2>
<ol class="grid md:grid-cols-4 gap-6 list-decimal pl-5">
<li class="p-5 border rounded-brand card"><strong>Assess:</strong> Risiko & Reifegrad → Quick-Scan.</li>
<li class="p-5 border rounded-brand card"><strong>Design:</strong> Zielarchitektur & Guardrails.</li>
<li class="p-5 border rounded-brand card"><strong>Build:</strong> IaC, Pipelines, Policies-as-Code.</li>
<li class="p-5 border rounded-brand card"><strong>Run:</strong> Monitoring, Audits, Kostentransparenz.</li>
</ol>
</section>
<section class="py-12">
<div class="rounded-brand p-8 bg-gradient-to-r from-accent to-primary text-white flex flex-col md:flex-row items-center justify-between gap-6">
<div class="text-xl font-semibold tracking-wide">Sichere Cloud pragmatisch umgesetzt.</div>
<a href="/kontakt" class="px-6 py-3 rounded-brand bg-white text-primary font-medium">Projekt anfragen</a>
</div>
</section>
</Base>

27
src/pages/kontakt.astro Normal file
View File

@@ -0,0 +1,27 @@
---
import Base from "../layouts/Base.astro";
---
<Base path="/kontakt" title="Kontakt Valtrix">
<h1 class="text-3xl font-bold mt-8">Kontakt</h1>
<form id="contactForm" class="mt-6 grid gap-4 max-w-xl">
<input class="border rounded-brand p-3" name="name" placeholder="Ihr Name" required>
<input class="border rounded-brand p-3" type="email" name="_replyto" placeholder="E-Mail" required>
<textarea class="border rounded-brand p-3" name="message" rows="5" placeholder="Ihre Nachricht" required></textarea>
<button class="px-5 py-3 rounded-brand bg-primary text-white w-fit">Senden</button>
</form>
<p class="text-xs text-textMuted mt-2">Wir verwenden keine externen Dienste. Ihre Anfrage wird in Ihrem EMailProgramm geöffnet.</p>
<script>
const form = document.getElementById('contactForm');
form?.addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(form);
const name = (data.get('name')||'').toString();
const email = (data.get('_replyto')||'').toString();
const message = (data.get('message')||'').toString();
const subject = `Kontaktanfrage ${name}`;
const body = `Name: ${name}\nE-Mail: ${email}\n\n${message}`;
const mail = `mailto:kontakt@valtrix.systems?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
window.location.href = mail;
});
</script>
</Base>

View File

@@ -0,0 +1,20 @@
---
import Base from "../layouts/Base.astro";
---
<Base path="/leistungen" title="Leistungen Valtrix">
<h1 class="text-3xl font-bold font-bold mt-8">Leistungen</h1>
<div class="grid md:grid-cols-3 gap-6 mt-6">
<div class="p-6 border rounded-brand card">
<h2 class="font-semibold text-lg">Security & Compliance</h2>
<p class="mt-2 text-textMuted">Zero-Trust, IAM, Hardening, Audits, BSI/ISO 27001-orientierte Prozesse, Policies-as-Code.</p>
</div>
<div class="p-6 border rounded-brand card">
<h2 class="font-semibold text-lg">Cloud & DevOps</h2>
<p class="mt-2 text-textMuted">Landing Zones, GitOps, Observability, Kostensteuerung reproduzierbar & skalierbar.</p>
</div>
<div class="p-6 border rounded-brand card">
<h2 class="font-semibold text-lg">AI & Automation</h2>
<p class="mt-2 text-textMuted">RAG/Agenten, Automatisierung von Betriebsprozessen, sichere Integration in bestehende Plattformen.</p>
</div>
</div>
</Base>

View File

@@ -0,0 +1,11 @@
---
import Base from "../layouts/Base.astro";
---
<Base path="/sicherheit" title="Sicherheit & Compliance Valtrix">
<h1 class="text-3xl font-bold mt-8">Sicherheit & Compliance</h1>
<ul class="list-disc pl-6 mt-4 text-textMuted">
<li>Datensouverän: EU-Hosting, DSGVO-konform</li>
<li>BSI/ISO 27001-orientierte Prozesse</li>
<li>Security-by-Design, regelmäßige Audits</li>
</ul>
</Base>

View File

@@ -0,0 +1,7 @@
---
import Base from "../layouts/Base.astro";
---
<Base path="/ueber-uns" title="Über uns Valtrix">
<h1 class="text-3xl font-bold mt-8">Über uns</h1>
<p class="mt-4 text-textMuted max-w-2xl">Wir sind ein Team aus Cloud-, Security- und AI-Engineers. Wir bauen robuste Systeme pragmatisch und messbar.</p>
</Base>

57
src/styles/globals.css Normal file
View File

@@ -0,0 +1,57 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Typography & base */
body { @apply font-sans text-text; }
.fade-in { animation: fadeIn .6s ease-out both; }
.rise-in { animation: riseIn .7s ease-out both; }
@keyframes fadeIn { from { opacity:0 } to { opacity:1 } }
@keyframes riseIn { from{ opacity:0; transform: translateY(12px) } to { opacity:1; transform:translateY(0) } }
/* Light/Dark surface defaults */
[data-theme="dark"] body { background:#0B1220; color:#E5E7EB }
.card { background:#ffffff; }
[data-theme="dark"] .card { background:#0F1B2F; border-color:#1F2A44 }
[data-theme="dark"] a { color:#A5D8FF }
/* Subtle aurora glow background (no external assets) */
body::before,
body::after {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
z-index: -1;
}
/* Glow blobs */
body::before {
background:
radial-gradient(closest-side at 20% 10%, rgba(0,184,217,0.15), transparent 60%),
radial-gradient(closest-side at 80% 0%, rgba(13,71,161,0.10), transparent 60%);
filter: blur(40px);
}
[data-theme="dark"] body::before {
background:
radial-gradient(closest-side at 15% 10%, rgba(0,184,217,0.20), transparent 60%),
radial-gradient(closest-side at 85% 0%, rgba(13,71,161,0.18), transparent 60%);
}
/* Aurora sweep + subtle vignette */
body::after {
background:
radial-gradient(120% 80% at 50% 8%, rgba(13,71,161,0.10), transparent 50%),
conic-gradient(from 200deg at 70% 25%, rgba(0,184,217,0.10), transparent 25%, rgba(13,71,161,0.06), transparent 55%, rgba(0,184,217,0.08), transparent 80%);
}
[data-theme="dark"] body::after {
background:
radial-gradient(140% 90% at 50% 2%, rgba(13,71,161,0.20), transparent 55%),
conic-gradient(from 200deg at 70% 25%, rgba(0,184,217,0.16), transparent 25%, rgba(13,71,161,0.10), transparent 55%, rgba(0,184,217,0.12), transparent 80%);
}
/* Dark mode readability overrides */
[data-theme="dark"] .text-textMuted { color: #94A3B8; }
[data-theme="dark"] .text-primary { color: #60A5FA; }
[data-theme="dark"] .border-primary { border-color: #60A5FA; }
[data-theme="dark"] .bg-graybg { background-color: #0F1B2F; }

17
tailwind.config.js Normal file
View File

@@ -0,0 +1,17 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{astro,html,md,mdx,js,jsx,ts,tsx,vue}"],
theme: {
extend: {
colors: {
primary: "#0D47A1",
accent: "#00B8D9",
graybg: "#F3F4F6",
text: "#111827",
textMuted: "#374151"
},
borderRadius: { brand: "8px" }
}
},
plugins: []
};