Initial
This commit is contained in:
1
.astro/content-assets.mjs
Normal file
1
.astro/content-assets.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export default new Map();
|
||||
1
.astro/content-modules.mjs
Normal file
1
.astro/content-modules.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export default new Map();
|
||||
199
.astro/content.d.ts
vendored
Normal file
199
.astro/content.d.ts
vendored
Normal 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
1
.astro/data-store.json
Normal 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
5
.astro/settings.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"_variables": {
|
||||
"lastUpdateCheck": 1762086631503
|
||||
}
|
||||
}
|
||||
1
.astro/types.d.ts
vendored
Normal file
1
.astro/types.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="astro/client" />
|
||||
9
.containerignore
Normal file
9
.containerignore
Normal 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
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
dist
|
||||
.env
|
||||
.DS_Store
|
||||
.vscode
|
||||
7
.stylelintrc.json
Normal file
7
.stylelintrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"rules": {
|
||||
"at-rule-no-unknown": [true, {
|
||||
"ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"]
|
||||
}]
|
||||
}
|
||||
}
|
||||
25
Containerfile
Normal file
25
Containerfile
Normal 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
37
README.md
Normal 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 Server‑Side‑Code.
|
||||
2
astro.config.mjs
Normal file
2
astro.config.mjs
Normal file
@@ -0,0 +1,2 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
export default defineConfig({ site: 'https://valtrix.systems' });
|
||||
18
deploy/podman-kube.yaml
Normal file
18
deploy/podman-kube.yaml
Normal 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
22
package.json
Normal 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
1
postcss.config.js
Normal file
@@ -0,0 +1 @@
|
||||
export default { plugins: { tailwindcss: {}, autoprefixer: {} } };
|
||||
1
public/favicon.svg
Normal file
1
public/favicon.svg
Normal 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
BIN
public/logo-valtrix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
1
public/styles.css
Normal file
1
public/styles.css
Normal file
@@ -0,0 +1 @@
|
||||
@import "/_astro/src/styles/globals.css";
|
||||
98
server.mjs
Normal file
98
server.mjs
Normal 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
56
src/layouts/Base.astro
Normal 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
19
src/pages/cases.astro
Normal 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>
|
||||
36
src/pages/cases/cloud-migration.astro
Normal file
36
src/pages/cases/cloud-migration.astro
Normal 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>
|
||||
48
src/pages/cases/k8s-hardening.astro
Normal file
48
src/pages/cases/k8s-hardening.astro
Normal 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 K8s‑Setup mit Härtung nach CIS, durchgängigen Policies‑as‑Code und GitOps‑Deployments.</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 Cluster‑Baseline: identitätszentrierte Zugriffe, Netzwerk‑Segmentierung, 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, Policy‑konforme 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, Pull‑basiert)</li>
|
||||
<li>~80% der CIS‑Kontrollen automatisiert überprüfbar</li>
|
||||
<li>Policy‑Breaks verhindern riskante Manifeste vor dem Rollout</li>
|
||||
<li>Signierte Container‑Images & 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/Tag‑Strategie, Kustomize‑Overlays und PR‑basierten 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 Least‑Privilege, NetworkPolicies default‑deny, Pod Security, Image‑Scanning, Secrets‑Management.</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>
|
||||
62
src/pages/datenschutz.astro
Normal file
62
src/pages/datenschutz.astro
Normal 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 Marketing‑Cookies 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>. E‑Mail: <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. Server‑Logfiles</h2>
|
||||
<p>Beim Aufruf der Webseite werden durch den zuständigen Server/Proxy systembedingt Zugriffsdaten verarbeitet (z. B. IP‑Adresse, Datum/Uhrzeit, angeforderte Ressource/URL, Referrer, User‑Agent, 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 Marketing‑Cookies. 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 E‑Mail oder über die bereitgestellte <em>mailto</em>‑Funktion verarbeiten wir die mitgeteilten Daten (z. B. Name, E‑Mail, 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 Datenschutz‑Aufsichtsbehö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. TLS‑Verschlü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
11
src/pages/impressum.astro
Normal 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
73
src/pages/index.astro
Normal 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
27
src/pages/kontakt.astro
Normal 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 E‑Mail‑Programm 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>
|
||||
20
src/pages/leistungen.astro
Normal file
20
src/pages/leistungen.astro
Normal 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>
|
||||
11
src/pages/sicherheit.astro
Normal file
11
src/pages/sicherheit.astro
Normal 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>
|
||||
7
src/pages/ueber-uns.astro
Normal file
7
src/pages/ueber-uns.astro
Normal 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
57
src/styles/globals.css
Normal 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
17
tailwind.config.js
Normal 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: []
|
||||
};
|
||||
Reference in New Issue
Block a user