migrate from astro+netlify to tanstack start + self-hosted
Replace the Astro static site with a TanStack Start (Bun runtime) app and add Dockerfile + compose files so the site can be served from home-main-2 behind the home-reverse-proxy-1 Caddy instead of Netlify. CI workflow rewritten to build a container image and SSH-deploy to the home host.
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
node_modules
|
||||||
|
.output
|
||||||
|
.nitro
|
||||||
|
.tanstack
|
||||||
|
dist
|
||||||
|
.git
|
||||||
|
.gitea
|
||||||
|
.github
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.log
|
||||||
|
bun-debug.log*
|
||||||
|
README.md
|
||||||
+69
-37
@@ -1,46 +1,78 @@
|
|||||||
name: Deploy to Netlify
|
name: Build & Deploy unom website
|
||||||
|
run-name: ${{ gitea.actor }} is deploying unom/website
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches: [main]
|
||||||
- main
|
workflow_dispatch:
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Repository Checkout
|
- name: Set up Docker Buildx
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ~/.bun/install/cache
|
|
||||||
key: ${{ runner.os }}-${{ matrix.bun }}-bun-${{ hashFiles('**/bun.lockb') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.bun }}-bun-
|
|
||||||
|
|
||||||
- name: Setup Bun
|
|
||||||
uses: oven-sh/setup-bun@v1
|
|
||||||
with:
|
|
||||||
bun-version: latest
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: bun install
|
|
||||||
env:
|
env:
|
||||||
UNOM_PKGS_TOKEN: ${{ secrets.UNOM_PKGS_TOKEN }}
|
BUILDER: builder-unom-website
|
||||||
|
run: |
|
||||||
|
cat > /tmp/buildkitd.toml <<'EOF'
|
||||||
|
[registry."docker.io"]
|
||||||
|
mirrors = ["192.168.1.52:5000"]
|
||||||
|
[registry."192.168.1.52:5000"]
|
||||||
|
http = true
|
||||||
|
insecure = true
|
||||||
|
EOF
|
||||||
|
docker buildx rm "$BUILDER" 2>/dev/null || true
|
||||||
|
docker buildx create --name "$BUILDER" --use --bootstrap \
|
||||||
|
--driver docker-container \
|
||||||
|
--config /tmp/buildkitd.toml
|
||||||
|
|
||||||
- name: Build
|
- name: Log in to Gitea registry
|
||||||
run: bun run build
|
|
||||||
|
|
||||||
- name: Deploy to Netlify
|
|
||||||
uses: nwtgck/actions-netlify@v2
|
|
||||||
with:
|
|
||||||
publish-dir: './dist'
|
|
||||||
production-branch: main
|
|
||||||
deploy-message: "Deploy from Gitea Actions"
|
|
||||||
env:
|
env:
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
timeout-minutes: 10
|
run: |
|
||||||
|
printf '%s' "$REGISTRY_TOKEN" | docker login git.unom.io -u "$REGISTRY_USER" --password-stdin
|
||||||
|
|
||||||
|
- name: Build & push
|
||||||
|
env:
|
||||||
|
BUILDER: builder-unom-website
|
||||||
|
IMAGE: git.unom.io/${{ gitea.repository }}
|
||||||
|
SHA: ${{ gitea.sha }}
|
||||||
|
run: |
|
||||||
|
docker buildx build \
|
||||||
|
--builder "$BUILDER" \
|
||||||
|
--push \
|
||||||
|
--file ./Dockerfile \
|
||||||
|
--tag "$IMAGE:latest" \
|
||||||
|
--tag "$IMAGE:$SHA" \
|
||||||
|
--cache-from "type=registry,ref=$IMAGE:cache" \
|
||||||
|
--cache-to "type=registry,ref=$IMAGE:cache,mode=min" \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Tear down builder
|
||||||
|
if: always()
|
||||||
|
env:
|
||||||
|
BUILDER: builder-unom-website
|
||||||
|
run: |
|
||||||
|
docker buildx rm "$BUILDER" 2>/dev/null || true
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: Pull and start web
|
||||||
|
uses: appleboy/ssh-action@v1.2.5
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOY_HOST }}
|
||||||
|
username: ${{ secrets.DEPLOY_USER }}
|
||||||
|
port: ${{ secrets.DEPLOY_PORT }}
|
||||||
|
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
script: |
|
||||||
|
docker login git.unom.io -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
cd ~/unom-website
|
||||||
|
git fetch origin main
|
||||||
|
git reset --hard origin/main
|
||||||
|
docker compose -f compose.production.yml pull web
|
||||||
|
docker compose -f compose.production.yml up -d --no-build web
|
||||||
|
|||||||
+10
-3
@@ -1,7 +1,11 @@
|
|||||||
# build output
|
# build output
|
||||||
dist/
|
dist/
|
||||||
# generated types
|
.output/
|
||||||
.astro/
|
.nitro/
|
||||||
|
.tanstack/
|
||||||
|
|
||||||
|
# generated router types
|
||||||
|
src/routeTree.gen.ts
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
@@ -11,7 +15,7 @@ npm-debug.log*
|
|||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
|
bun-debug.log*
|
||||||
|
|
||||||
# environment variables
|
# environment variables
|
||||||
.env
|
.env
|
||||||
@@ -22,3 +26,6 @@ pnpm-debug.log*
|
|||||||
|
|
||||||
# jetbrains setting folder
|
# jetbrains setting folder
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# editor
|
||||||
|
.vscode/
|
||||||
|
|||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
FROM oven/bun:alpine AS base
|
||||||
|
|
||||||
|
RUN apk update && apk add --no-cache libc6-compat
|
||||||
|
|
||||||
|
## INSTALLER - install deps and build
|
||||||
|
|
||||||
|
FROM base AS installer
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json bun.lock ./
|
||||||
|
RUN --mount=type=cache,target=/root/.bun/install/cache,sharing=shared \
|
||||||
|
bun install --frozen-lockfile
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
## RUNNER - minimal production image
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN addgroup --system --gid 1001 bunjs && \
|
||||||
|
adduser --system --uid 1001 web
|
||||||
|
|
||||||
|
COPY --from=installer --chown=web:bunjs /app/.output ./.output
|
||||||
|
|
||||||
|
USER web
|
||||||
|
|
||||||
|
CMD ["bun", ".output/server/index.mjs"]
|
||||||
@@ -1,13 +1,36 @@
|
|||||||
# Astro with Tailwind
|
# @unom/website
|
||||||
|
|
||||||
|
The unom.io marketing site. TanStack Start + Bun, deployed to home-main-2.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bun create astro@latest -- --template with-tailwindcss
|
bun install
|
||||||
|
bun run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/with-tailwindcss)
|
Visit <http://localhost:3000>.
|
||||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/with-tailwindcss)
|
|
||||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/with-tailwindcss/devcontainer.json)
|
|
||||||
|
|
||||||
Astro comes with [Tailwind](https://tailwindcss.com) support out of the box. This example showcases how to style your Astro project with Tailwind.
|
## Production
|
||||||
|
|
||||||
For complete setup instructions, please see our [Tailwind Integration Guide](https://docs.astro.build/en/guides/integrations-guide/tailwind).
|
The repo is built into a container image (`git.unom.io/unom/website`) by Gitea
|
||||||
|
Actions on push to `main`, then deployed via SSH to `home-main-2`. The
|
||||||
|
container listens on port 3000 inside the network and is exposed on host port
|
||||||
|
3200, which Caddy on `home-reverse-proxy-1` reverse-proxies for unom.io and
|
||||||
|
www.unom.io.
|
||||||
|
|
||||||
|
Run the production image locally:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose -f compose.production.yml pull
|
||||||
|
docker compose -f compose.production.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Required CI secrets
|
||||||
|
|
||||||
|
Set on the `unom/website` repo in Gitea Actions:
|
||||||
|
|
||||||
|
| Secret | Purpose |
|
||||||
|
| ------ | ------- |
|
||||||
|
| `REGISTRY_USER` / `REGISTRY_TOKEN` | Push to `git.unom.io` container registry |
|
||||||
|
| `DEPLOY_HOST` / `DEPLOY_USER` / `DEPLOY_PORT` / `DEPLOY_SSH_KEY` | SSH target on home-main-2 (private key matching the `unom-ci-deploy` authorized key) |
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
|
||||||
import mdx from '@astrojs/mdx';
|
|
||||||
|
|
||||||
// https://astro.build/config
|
|
||||||
export default defineConfig({
|
|
||||||
site: "https://www.vspace.one",
|
|
||||||
vite: {
|
|
||||||
plugins: [tailwindcss()]
|
|
||||||
},
|
|
||||||
integrations: [
|
|
||||||
mdx(),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
||||||
|
"vcs": {
|
||||||
|
"enabled": false,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": false
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignoreUnknown": false,
|
||||||
|
"includes": [
|
||||||
|
"**/src/**/*",
|
||||||
|
"**/vite.config.ts",
|
||||||
|
"!**/src/routeTree.gen.ts",
|
||||||
|
"!**/src/styles/**/*.css"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"css": {
|
||||||
|
"parser": {
|
||||||
|
"tailwindDirectives": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "tab"
|
||||||
|
},
|
||||||
|
"assist": {
|
||||||
|
"actions": {
|
||||||
|
"source": {
|
||||||
|
"organizeImports": "on"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "double"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
name: unom-website-prod
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: git.unom.io/unom/website:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3200:3000"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
name: unom-website-dev
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
restart: unless-stopped
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
+41
-19
@@ -1,20 +1,42 @@
|
|||||||
{
|
{
|
||||||
"name": "@unom/website",
|
"name": "@unom/website",
|
||||||
"type": "module",
|
"private": true,
|
||||||
"version": "0.0.1",
|
"type": "module",
|
||||||
"scripts": {
|
"imports": {
|
||||||
"dev": "astro dev",
|
"#/*": "./src/*"
|
||||||
"build": "astro build",
|
},
|
||||||
"preview": "astro preview",
|
"scripts": {
|
||||||
"astro": "astro"
|
"dev": "vite dev --port 3000 --host",
|
||||||
},
|
"build": "vite build",
|
||||||
"dependencies": {
|
"start": "bun .output/server/index.mjs",
|
||||||
"@astrojs/mdx": "^4.3.0",
|
"typecheck": "tsc --noEmit",
|
||||||
"@fontsource/inter": "^5.2.5",
|
"format": "biome format",
|
||||||
"@tailwindcss/vite": "^4.1.3",
|
"lint": "biome lint",
|
||||||
"astro": "^5.8.0",
|
"check": "biome check"
|
||||||
"class-variance-authority": "^0.7.1",
|
},
|
||||||
"tailwind-merge": "^3.3.0",
|
"dependencies": {
|
||||||
"tailwindcss": "^4.1.3"
|
"@fontsource/inter": "^5.2.5",
|
||||||
}
|
"@tailwindcss/vite": "^4.3.0",
|
||||||
}
|
"@tanstack/react-router": "latest",
|
||||||
|
"@tanstack/react-start": "latest",
|
||||||
|
"@tanstack/router-plugin": "^1.168.9",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"nitro": "^3.0.260429-beta",
|
||||||
|
"react": "^19.2.6",
|
||||||
|
"react-dom": "^19.2.6",
|
||||||
|
"react-markdown": "^9.0.3",
|
||||||
|
"tailwind-merge": "^3.6.0",
|
||||||
|
"tailwindcss": "^4.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "2.4.15",
|
||||||
|
"@types/node": "^25.9.1",
|
||||||
|
"@types/react": "^19.2.15",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^6.0.2",
|
||||||
|
"typescript": "^6.0.3",
|
||||||
|
"vite": "^8.0.14",
|
||||||
|
"vite-tsconfig-paths": "^6.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.3 MiB After Width: | Height: | Size: 3.3 MiB |
@@ -0,0 +1,68 @@
|
|||||||
|
import Section from "./Section";
|
||||||
|
|
||||||
|
type NavigationItem =
|
||||||
|
| { type: "link"; path: string; label: string }
|
||||||
|
| { type: "text"; content: string };
|
||||||
|
|
||||||
|
type NavigationGroup = {
|
||||||
|
title: string;
|
||||||
|
className?: string;
|
||||||
|
items: Array<NavigationItem>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tree: Array<NavigationGroup> = [
|
||||||
|
{
|
||||||
|
title: "Übersicht",
|
||||||
|
items: [{ type: "link", path: "/", label: "Startseite" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Rechtliches",
|
||||||
|
items: [
|
||||||
|
{ type: "link", path: "/legal/imprint", label: "Impressum" },
|
||||||
|
{ type: "link", path: "/legal/privacy", label: "Datenschutzerklärung" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Zugehöriges",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: "link",
|
||||||
|
path: "https://enrico.buehler.earth",
|
||||||
|
label: "Enrico Bühler",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "",
|
||||||
|
className: "ml-auto mr-0 self-end",
|
||||||
|
items: [{ type: "text", content: "Made with ❤️ in Rottweil" }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className="bg-neutral-accent">
|
||||||
|
<Section>
|
||||||
|
<div className="flex flex-row flex-wrap gap-12 w-full pb-8">
|
||||||
|
{tree.map((group) => (
|
||||||
|
<div key={group.title || "misc"} className={group.className}>
|
||||||
|
{group.title && <h3 className="mb-2">{group.title}</h3>}
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{group.items.map((item, i) => {
|
||||||
|
if (item.type === "link") {
|
||||||
|
return (
|
||||||
|
<a key={`${item.path}-${i}`} href={item.path}>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <p key={`text-${i}`}>{item.content}</p>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
import Logo from "./Logo";
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
id="nav-container"
|
||||||
|
className="fixed -top-1 w-full h-height-header z-50"
|
||||||
|
>
|
||||||
|
<div className="flex flex-row justify-between items-center h-full w-full max-w-max-section m-auto px-section-main-x">
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
className="w-[120px] h-[120px] flex justify-center items-center rounded-3xl p-2 backdrop-blur-3xl"
|
||||||
|
>
|
||||||
|
<Logo />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { FC, ReactNode } from "react";
|
||||||
|
import Footer from "./Footer";
|
||||||
|
import Header from "./Header";
|
||||||
|
|
||||||
|
const Layout: FC<{ children: ReactNode; showHeader?: boolean }> = ({
|
||||||
|
children,
|
||||||
|
showHeader = true,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{showHeader && <Header />}
|
||||||
|
<main className="min-h-screen">{children}</main>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
---
|
|
||||||
import Section from "../Section.astro";
|
|
||||||
|
|
||||||
enum NavigationItemType {
|
|
||||||
Link = 0,
|
|
||||||
Text = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
type NavigationItem =
|
|
||||||
| {
|
|
||||||
path: string;
|
|
||||||
label: string;
|
|
||||||
type: NavigationItemType.Link;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: NavigationItemType.Text;
|
|
||||||
content: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type NavigationGroup = {
|
|
||||||
title: string;
|
|
||||||
class?: string;
|
|
||||||
items: Array<NavigationItem>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const tree: Array<NavigationGroup> = [
|
|
||||||
{
|
|
||||||
title: "Übersicht",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
path: "/",
|
|
||||||
type: NavigationItemType.Link,
|
|
||||||
label: "Startseite",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Rechtliches",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
path: "/legal/imprint",
|
|
||||||
label: "Impressum",
|
|
||||||
type: NavigationItemType.Link,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/legal/privacy",
|
|
||||||
label: "Datenschutzerklärung",
|
|
||||||
type: NavigationItemType.Link,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Zugehöriges",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
path: "https://enrico.buehler.earth",
|
|
||||||
label: "Enrico Bühler",
|
|
||||||
type: NavigationItemType.Link,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "",
|
|
||||||
class: "ml-auto mr-0 self-end",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
type: NavigationItemType.Text,
|
|
||||||
content: "Made with ❤️ in Rottweil",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
---
|
|
||||||
|
|
||||||
<footer class="bg-neutral-accent">
|
|
||||||
<Section>
|
|
||||||
<div class="flex flex-row flex-wrap gap-12 w-full pb-8">
|
|
||||||
{
|
|
||||||
tree.map((group) => (
|
|
||||||
<div class={group.class}>
|
|
||||||
{group.title && <h3 class="mb-2">{group.title}</h3>}
|
|
||||||
<div class="flex flex-col">
|
|
||||||
{group.items.map((item) => {
|
|
||||||
switch (item.type) {
|
|
||||||
case NavigationItemType.Link:
|
|
||||||
return <a href={item.path}>{item.label}</a>;
|
|
||||||
case NavigationItemType.Text:
|
|
||||||
return <p>{item.content}</p>;
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</Section>
|
|
||||||
</footer>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
import Logo from "../Logo/Logo.astro";
|
|
||||||
---
|
|
||||||
|
|
||||||
<header id="nav-container" class="fixed -top-1 w-full h-height-header z-50">
|
|
||||||
<div
|
|
||||||
class="flex flex-row justify-between items-center h-full w-full max-w-max-section m-auto px-section-main-x"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/"
|
|
||||||
class="w-[120px] h-[120px] flex justify-center items-center rounded-3xl p-2 backdrop-blur-3xl"
|
|
||||||
>
|
|
||||||
<Logo />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { FC, ReactNode } from "react";
|
||||||
|
import Section from "./Section";
|
||||||
|
|
||||||
|
const LegalPage: FC<{ children: ReactNode }> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<Section>
|
||||||
|
<article className="markdown">{children}</article>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LegalPage;
|
||||||
@@ -1,28 +1,32 @@
|
|||||||
<!--?xml version="1.0" encoding="UTF-8"?-->
|
export default function Logo() {
|
||||||
<svg
|
return (
|
||||||
id="Ebene_1"
|
<svg
|
||||||
data-name="Ebene 1"
|
aria-label="unom Logo"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
role="img"
|
||||||
viewBox="0 0 643.6 382.49"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
viewBox="0 0 643.6 382.49"
|
||||||
<path
|
>
|
||||||
class="fill-main"
|
<title>unom</title>
|
||||||
d="M128.61,230.82v25.76c-3.39,3.13-15.9,9.57-36.95,9.57s-33.56-6.44-36.95-9.57v-69.51c-6.51-1.15-13.96-1.89-22.4-1.89s-15.9.75-22.4,1.89v71.69c0,30.24,34.38,52.18,81.75,52.18s81.75-21.95,81.75-52.18v-27.9c.54-3.26,10.53-11.58,29.7-16.61v-45.87c-44.16,8.16-74.5,32.3-74.5,62.43Z"
|
<path
|
||||||
></path>
|
className="fill-main"
|
||||||
<path
|
d="M128.61,230.82v25.76c-3.39,3.13-15.9,9.57-36.95,9.57s-33.56-6.44-36.95-9.57v-69.51c-6.51-1.15-13.96-1.89-22.4-1.89s-15.9.75-22.4,1.89v71.69c0,30.24,34.38,52.18,81.75,52.18s81.75-21.95,81.75-52.18v-27.9c.54-3.26,10.53-11.58,29.7-16.61v-45.87c-44.16,8.16-74.5,32.3-74.5,62.43Z"
|
||||||
class="fill-main"
|
/>
|
||||||
d="M552.55,63.76c-27.7,0-50.91,7.57-65.32,19.94-12.48-7.97-28.41-13.93-46.65-17.17.74,3.66,1.14,7.42,1.14,11.28v34.8c19.17,5.03,29.16,13.33,29.7,16.56v52.56c6.51,1.15,13.96,1.89,22.4,1.89s15.9-.75,22.4-1.89v-63.83c3.41-3.08,15.75-9.34,36.33-9.34s32.92,6.26,36.33,9.34v63.83c6.51,1.15,13.96,1.89,22.4,1.89s15.9-.75,22.4-1.89v-66.1c0-30.06-34.12-51.88-81.13-51.88Z"
|
<path
|
||||||
></path>
|
className="fill-main"
|
||||||
<path
|
d="M552.55,63.76c-27.7,0-50.91,7.57-65.32,19.94-12.48-7.97-28.41-13.93-46.65-17.17.74,3.66,1.14,7.42,1.14,11.28v34.8c19.17,5.03,29.16,13.33,29.7,16.56v52.56c6.51,1.15,13.96,1.89,22.4,1.89s15.9-.75,22.4-1.89v-63.83c3.41-3.08,15.75-9.34,36.33-9.34s32.92,6.26,36.33,9.34v63.83c6.51,1.15,13.96,1.89,22.4,1.89s15.9-.75,22.4-1.89v-66.1c0-30.06-34.12-51.88-81.13-51.88Z"
|
||||||
class="fill-main"
|
/>
|
||||||
d="M322.42,370.08c-61.63,0-108.1-28.12-108.1-65.41V77.82c0-37.29,46.48-65.41,108.1-65.41s108.1,28.12,108.1,65.41v226.86c0,37.29-46.47,65.41-108.1,65.41ZM322.42,57.2c-41.19,0-62.5,15.85-63.31,20.66v226.81c.81,4.76,22.11,20.61,63.31,20.61s62.5-15.85,63.31-20.66V77.82c-.81-4.76-22.12-20.61-63.31-20.61Z"
|
<path
|
||||||
></path>
|
className="fill-main"
|
||||||
<path
|
d="M322.42,370.08c-61.63,0-108.1-28.12-108.1-65.41V77.82c0-37.29,46.48-65.41,108.1-65.41s108.1,28.12,108.1,65.41v226.86c0,37.29-46.47,65.41-108.1,65.41ZM322.42,57.2c-41.19,0-62.5,15.85-63.31,20.66v226.81c.81,4.76,22.11,20.61,63.31,20.61s62.5-15.85,63.31-20.66V77.82c-.81-4.76-22.12-20.61-63.31-20.61Z"
|
||||||
class="fill-main"
|
/>
|
||||||
d="M353.48,72.19c-32.05,10.89-52.91,31.4-53.44,56.03h-.03s0,.96,0,.96v32.77c6.51,1.15,13.96,1.89,22.4,1.89s15.9-.75,22.4-1.89v-32.73c.56-3.27,10.54-11.57,29.7-16.6v-31.07c-2.97-2.53-10.07-6.46-21.03-9.36Z"
|
<path
|
||||||
></path>
|
className="fill-main"
|
||||||
<path
|
d="M353.48,72.19c-32.05,10.89-52.91,31.4-53.44,56.03h-.03s0,.96,0,.96v32.77c6.51,1.15,13.96,1.89,22.4,1.89s15.9-.75,22.4-1.89v-32.73c.56-3.27,10.54-11.57,29.7-16.6v-31.07c-2.97-2.53-10.07-6.46-21.03-9.36Z"
|
||||||
class="fill-main"
|
/>
|
||||||
d="M322.42,290.86c8.44,0,15.9-.75,22.4-1.89v-58.15c0-30.13-30.35-54.26-74.5-62.43v45.87c19.17,5.03,29.16,13.33,29.7,16.56v58.15c6.51,1.15,13.96,1.89,22.4,1.89Z"
|
<path
|
||||||
></path>
|
className="fill-main"
|
||||||
</svg>
|
d="M322.42,290.86c8.44,0,15.9-.75,22.4-1.89v-58.15c0-30.13-30.35-54.26-74.5-62.43v45.87c19.17,5.03,29.16,13.33,29.7,16.56v58.15c6.51,1.15,13.96,1.89,22.4,1.89Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.cls-1 {
|
|
||||||
fill: #654eff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cls-2 {
|
|
||||||
fill: #fff;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<rect class="cls-1" x="87.76" y="87.76" width="824.48" height="824.48" rx="200.39" ry="200.39"/>
|
|
||||||
<g>
|
|
||||||
<path class="cls-2" d="M306.81,545.97v25.76c-3.39,3.13-15.9,9.57-36.95,9.57s-33.56-6.44-36.95-9.57v-69.51c-6.51-1.15-13.96-1.89-22.4-1.89s-15.9.75-22.4,1.89v71.69c0,30.24,34.38,52.18,81.75,52.18s81.75-21.95,81.75-52.18v-27.9c.54-3.26,10.53-11.58,29.7-16.61v-45.87c-44.16,8.16-74.5,32.3-74.5,62.43Z"/>
|
|
||||||
<path class="cls-2" d="M730.75,378.92c-27.7,0-50.91,7.57-65.32,19.94-12.48-7.97-28.41-13.93-46.65-17.17.74,3.66,1.14,7.42,1.14,11.28v34.8c19.17,5.03,29.16,13.33,29.7,16.56v52.56c6.51,1.15,13.96,1.89,22.4,1.89s15.9-.75,22.4-1.89v-63.83c3.41-3.08,15.75-9.34,36.33-9.34s32.92,6.26,36.33,9.34v63.83c6.51,1.15,13.96,1.89,22.4,1.89s15.9-.75,22.4-1.89v-66.1c0-30.06-34.12-51.88-81.13-51.88Z"/>
|
|
||||||
<path class="cls-2" d="M500.62,685.24c-61.63,0-108.1-28.12-108.1-65.41v-226.86c0-37.29,46.48-65.41,108.1-65.41s108.1,28.12,108.1,65.41v226.86c0,37.29-46.47,65.41-108.1,65.41ZM500.62,372.36c-41.19,0-62.5,15.85-63.31,20.66v226.81c.81,4.76,22.11,20.61,63.31,20.61s62.5-15.85,63.31-20.66v-226.81c-.81-4.76-22.12-20.61-63.31-20.61Z"/>
|
|
||||||
<path class="cls-2" d="M531.68,387.34c-32.05,10.89-52.91,31.4-53.44,56.03h-.03s0,.96,0,.96v32.77c6.51,1.15,13.96,1.89,22.4,1.89s15.9-.75,22.4-1.89v-32.73c.56-3.27,10.54-11.57,29.7-16.6v-31.07c-2.97-2.53-10.07-6.46-21.03-9.36Z"/>
|
|
||||||
<path class="cls-2" d="M500.62,606.01c8.44,0,15.9-.75,22.4-1.89v-58.15c0-30.13-30.35-54.26-74.5-62.43v45.87c19.17,5.03,29.16,13.33,29.7,16.56v58.15c6.51,1.15,13.96,1.89,22.4,1.89Z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,28 @@
|
|||||||
|
export default function LogoQuadBG() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
aria-label="unom Logo"
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 1000 1000"
|
||||||
|
>
|
||||||
|
<title>unom</title>
|
||||||
|
<rect
|
||||||
|
fill="#654eff"
|
||||||
|
x="87.76"
|
||||||
|
y="87.76"
|
||||||
|
width="824.48"
|
||||||
|
height="824.48"
|
||||||
|
rx="200.39"
|
||||||
|
ry="200.39"
|
||||||
|
/>
|
||||||
|
<g fill="#fff">
|
||||||
|
<path d="M306.81,545.97v25.76c-3.39,3.13-15.9,9.57-36.95,9.57s-33.56-6.44-36.95-9.57v-69.51c-6.51-1.15-13.96-1.89-22.4-1.89s-15.9.75-22.4,1.89v71.69c0,30.24,34.38,52.18,81.75,52.18s81.75-21.95,81.75-52.18v-27.9c.54-3.26,10.53-11.58,29.7-16.61v-45.87c-44.16,8.16-74.5,32.3-74.5,62.43Z" />
|
||||||
|
<path d="M730.75,378.92c-27.7,0-50.91,7.57-65.32,19.94-12.48-7.97-28.41-13.93-46.65-17.17.74,3.66,1.14,7.42,1.14,11.28v34.8c19.17,5.03,29.16,13.33,29.7,16.56v52.56c6.51,1.15,13.96,1.89,22.4,1.89s15.9-.75,22.4-1.89v-63.83c3.41-3.08,15.75-9.34,36.33-9.34s32.92,6.26,36.33,9.34v63.83c6.51,1.15,13.96,1.89,22.4,1.89s15.9-.75,22.4-1.89v-66.1c0-30.06-34.12-51.88-81.13-51.88Z" />
|
||||||
|
<path d="M500.62,685.24c-61.63,0-108.1-28.12-108.1-65.41v-226.86c0-37.29,46.48-65.41,108.1-65.41s108.1,28.12,108.1,65.41v226.86c0,37.29-46.47,65.41-108.1,65.41ZM500.62,372.36c-41.19,0-62.5,15.85-63.31,20.66v226.81c.81,4.76,22.11,20.61,63.31,20.61s62.5-15.85,63.31-20.66v-226.81c-.81-4.76-22.12-20.61-63.31-20.61Z" />
|
||||||
|
<path d="M531.68,387.34c-32.05,10.89-52.91,31.4-53.44,56.03h-.03s0,.96,0,.96v32.77c6.51,1.15,13.96,1.89,22.4,1.89s15.9-.75,22.4-1.89v-32.73c.56-3.27,10.54-11.57,29.7-16.6v-31.07c-2.97-2.53-10.07-6.46-21.03-9.36Z" />
|
||||||
|
<path d="M500.62,606.01c8.44,0,15.9-.75,22.4-1.89v-58.15c0-30.13-30.35-54.26-74.5-62.43v45.87c19.17,5.03,29.16,13.33,29.7,16.56v58.15c6.51,1.15,13.96,1.89,22.4,1.89Z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
import type { HTMLAttributes } from "astro/types";
|
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
|
||||||
|
|
||||||
const section = cva("relative w-full", {
|
|
||||||
variants: {
|
|
||||||
padding: {
|
|
||||||
true: "px-section-main-x py-section-main-y",
|
|
||||||
false: "p-0",
|
|
||||||
},
|
|
||||||
maxWidth: {
|
|
||||||
true: "max-w-max-section mx-auto",
|
|
||||||
false: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface Props
|
|
||||||
extends HTMLAttributes<"section">,
|
|
||||||
VariantProps<typeof section> {}
|
|
||||||
|
|
||||||
const { padding = true, maxWidth = true, ...props } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<section {...props} class={section({ padding, maxWidth })}>
|
|
||||||
<slot />
|
|
||||||
</section>
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import type { HTMLAttributes } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const section = cva("relative w-full", {
|
||||||
|
variants: {
|
||||||
|
padding: {
|
||||||
|
true: "px-section-main-x py-section-main-y",
|
||||||
|
false: "p-0",
|
||||||
|
},
|
||||||
|
maxWidth: {
|
||||||
|
true: "max-w-max-section mx-auto",
|
||||||
|
false: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
padding: true,
|
||||||
|
maxWidth: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type Props = HTMLAttributes<HTMLElement> & VariantProps<typeof section>;
|
||||||
|
|
||||||
|
export default function Section({
|
||||||
|
padding,
|
||||||
|
maxWidth,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: Props) {
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
{...props}
|
||||||
|
className={cn(section({ padding, maxWidth }), className)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,8 +1,3 @@
|
|||||||
---
|
|
||||||
layout: "@/layouts/MarkdownLayout.astro"
|
|
||||||
title: Impressum
|
|
||||||
---
|
|
||||||
|
|
||||||
# Impressum
|
# Impressum
|
||||||
|
|
||||||
Angaben gemäß § 5 TMG
|
Angaben gemäß § 5 TMG
|
||||||
@@ -45,4 +40,4 @@ Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unt
|
|||||||
Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen.
|
Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen.
|
||||||
|
|
||||||
Quelle:
|
Quelle:
|
||||||
<https://www.e-recht24.de>
|
<https://www.e-recht24.de>
|
||||||
@@ -1,8 +1,3 @@
|
|||||||
---
|
|
||||||
layout: "@/layouts/MarkdownLayout.astro"
|
|
||||||
title: Datenschutzerklärung
|
|
||||||
---
|
|
||||||
|
|
||||||
# Datenschutzerklärung
|
# Datenschutzerklärung
|
||||||
|
|
||||||
## 1. Datenschutz auf einen Blick
|
## 1. Datenschutz auf einen Blick
|
||||||
@@ -139,4 +134,4 @@ Die Verarbeitung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSG
|
|||||||
Die von Ihnen an uns per Kontaktanfragen übersandten Daten verbleiben bei uns, bis Sie uns zur Löschung auffordern, Ihre Einwilligung zur Speicherung widerrufen oder der Zweck für die Datenspeicherung entfällt (z. B. nach abgeschlossener Bearbeitung Ihres Anliegens). Zwingende gesetzliche Bestimmungen – insbesondere gesetzliche Aufbewahrungsfristen – bleiben unberührt.
|
Die von Ihnen an uns per Kontaktanfragen übersandten Daten verbleiben bei uns, bis Sie uns zur Löschung auffordern, Ihre Einwilligung zur Speicherung widerrufen oder der Zweck für die Datenspeicherung entfällt (z. B. nach abgeschlossener Bearbeitung Ihres Anliegens). Zwingende gesetzliche Bestimmungen – insbesondere gesetzliche Aufbewahrungsfristen – bleiben unberührt.
|
||||||
|
|
||||||
Quelle:
|
Quelle:
|
||||||
<https://www.e-recht24.de>
|
<https://www.e-recht24.de>
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
import { cn } from "@/lib/utils.ts";
|
|
||||||
|
|
||||||
const anim = {
|
|
||||||
old: {
|
|
||||||
name: "pageLeave",
|
|
||||||
duration: "0.0s",
|
|
||||||
easing: "ease-out",
|
|
||||||
fillMode: "forwards",
|
|
||||||
},
|
|
||||||
new: {
|
|
||||||
name: "pageEnter",
|
|
||||||
duration: "0.7s",
|
|
||||||
easing: "cubic-bezier(0.23, 1, 0.32, 1)",
|
|
||||||
fillMode: "forwards",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
inset?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const slideAnimation = {
|
|
||||||
forwards: anim,
|
|
||||||
backwards: anim,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { inset = true } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<main
|
|
||||||
transition:animate={slideAnimation}
|
|
||||||
class={cn("min-h-screen", inset ? "pt-height-header" : "")}
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</main>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
import "@/styles/markdown.css";
|
|
||||||
import Section from "@/components/Section.astro";
|
|
||||||
import MainLayout from "./MainLayout.astro";
|
|
||||||
import RootLayout from "./RootLayout.astro";
|
|
||||||
|
|
||||||
const { frontmatter } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<RootLayout title={frontmatter.title}>
|
|
||||||
<MainLayout>
|
|
||||||
<Section>
|
|
||||||
<slot />
|
|
||||||
</Section>
|
|
||||||
</MainLayout>
|
|
||||||
</RootLayout>
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
import Footer from "@/components/Layout/Footer.astro";
|
|
||||||
import Header from "@/components/Layout/Header.astro";
|
|
||||||
import "@/styles/global.css";
|
|
||||||
import "@fontsource/inter";
|
|
||||||
import { ClientRouter } from "astro:transitions";
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
title: string;
|
|
||||||
showHeader?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { title, showHeader = true } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="description" content="Kreative Webentwicklung aus Rottweil" />
|
|
||||||
<meta name="viewport" content="width=device-width" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
||||||
<slot name="head" />
|
|
||||||
<script
|
|
||||||
is:inline
|
|
||||||
defer
|
|
||||||
data-domain="unom.io"
|
|
||||||
src="https://analytics.unom.io/js/plausible.js"></script>
|
|
||||||
<meta name="generator" content={Astro.generator} />
|
|
||||||
<title>unom - {title}</title>
|
|
||||||
<ClientRouter />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{showHeader && <Header transition:name="header" transition:persist />}
|
|
||||||
<slot />
|
|
||||||
<Footer />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
import RootLayout from "@/layouts/RootLayout.astro";
|
|
||||||
import "../styles/global.css";
|
|
||||||
import MainLayout from "@/layouts/MainLayout.astro";
|
|
||||||
import Landing from "@/sections/Landing/Landing.astro";
|
|
||||||
---
|
|
||||||
|
|
||||||
<RootLayout showHeader={false} title="Kreative Webentwicklung">
|
|
||||||
<MainLayout inset={false}>
|
|
||||||
<Landing />
|
|
||||||
</MainLayout>
|
|
||||||
</RootLayout>
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
|
||||||
|
import { routeTree } from "./routeTree.gen";
|
||||||
|
|
||||||
|
export function getRouter() {
|
||||||
|
const router = createTanStackRouter({
|
||||||
|
routeTree,
|
||||||
|
scrollRestoration: true,
|
||||||
|
defaultPreload: "intent",
|
||||||
|
});
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "@tanstack/react-router" {
|
||||||
|
interface Register {
|
||||||
|
router: ReturnType<typeof getRouter>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
createRootRoute,
|
||||||
|
HeadContent,
|
||||||
|
Scripts,
|
||||||
|
} from "@tanstack/react-router";
|
||||||
|
import "@fontsource/inter";
|
||||||
|
import Layout from "@/components/Layout";
|
||||||
|
import appCss from "../styles/globals.css?url";
|
||||||
|
|
||||||
|
const SITE_URL = "https://unom.io";
|
||||||
|
|
||||||
|
export const Route = createRootRoute({
|
||||||
|
head: () => ({
|
||||||
|
meta: [
|
||||||
|
{ charSet: "utf-8" },
|
||||||
|
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
||||||
|
{ name: "description", content: "Kreative Webentwicklung aus Rottweil" },
|
||||||
|
{ title: "unom - Kreative Webentwicklung" },
|
||||||
|
{ property: "og:title", content: "unom - Kreative Webentwicklung" },
|
||||||
|
{
|
||||||
|
property: "og:description",
|
||||||
|
content: "Kreative Webentwicklung aus Rottweil",
|
||||||
|
},
|
||||||
|
{ property: "og:url", content: SITE_URL },
|
||||||
|
{ property: "og:type", content: "website" },
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
{ rel: "stylesheet", href: appCss },
|
||||||
|
{ rel: "icon", type: "image/svg+xml", href: "/favicon.svg" },
|
||||||
|
],
|
||||||
|
scripts: [
|
||||||
|
{
|
||||||
|
defer: true,
|
||||||
|
"data-domain": "unom.io",
|
||||||
|
src: "https://analytics.unom.io/js/plausible.js",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
shellComponent: RootDocument,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<HeadContent />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Layout>{children}</Layout>
|
||||||
|
<Scripts />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import Landing from "@/sections/Landing";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/")({
|
||||||
|
component: HomePage,
|
||||||
|
head: () => ({
|
||||||
|
meta: [{ title: "unom - Kreative Webentwicklung" }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
function HomePage() {
|
||||||
|
return <Landing />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import LegalPage from "@/components/LegalPage";
|
||||||
|
import content from "@/content/legal/imprint.md?raw";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/legal/imprint")({
|
||||||
|
component: ImprintPage,
|
||||||
|
head: () => ({
|
||||||
|
meta: [{ title: "unom - Impressum" }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
function ImprintPage() {
|
||||||
|
return (
|
||||||
|
<LegalPage>
|
||||||
|
<ReactMarkdown>{content}</ReactMarkdown>
|
||||||
|
</LegalPage>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import LegalPage from "@/components/LegalPage";
|
||||||
|
import content from "@/content/legal/privacy.md?raw";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/legal/privacy")({
|
||||||
|
component: PrivacyPage,
|
||||||
|
head: () => ({
|
||||||
|
meta: [{ title: "unom - Datenschutzerklärung" }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
function PrivacyPage() {
|
||||||
|
return (
|
||||||
|
<LegalPage>
|
||||||
|
<ReactMarkdown>{content}</ReactMarkdown>
|
||||||
|
</LegalPage>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import LogoQuadBG from "@/components/LogoQuadBG";
|
||||||
|
import bgDark from "@/assets/unom_Logo_5_Dark.webp";
|
||||||
|
|
||||||
|
export default function Landing() {
|
||||||
|
return (
|
||||||
|
<div className="w-full h-screen object-cover">
|
||||||
|
<div className="w-full h-full flex items-center justify-center absolute">
|
||||||
|
<div className="w-[200px]">
|
||||||
|
<LogoQuadBG />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
className="h-full w-full object-cover"
|
||||||
|
src={bgDark}
|
||||||
|
width={3840}
|
||||||
|
height={2160}
|
||||||
|
alt="Ein 3D Rendering des unom Logos"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
import { Image } from "astro:assets";
|
|
||||||
import bgDark from "./unom_Logo_5_Dark.webp";
|
|
||||||
import LogoQuadBG from "@/components/Logo/LogoQuadBG.astro";
|
|
||||||
---
|
|
||||||
|
|
||||||
<div class="w-full h-screen object-cover">
|
|
||||||
<div class="w-full h-full flex items-center justify-center absolute">
|
|
||||||
<div class="w-[200px]">
|
|
||||||
<LogoQuadBG />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Image
|
|
||||||
class="h-full object-cover"
|
|
||||||
src={bgDark}
|
|
||||||
width={3840}
|
|
||||||
height={2160}
|
|
||||||
alt="Ein 3D Rendering des unom Logos"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import handler from "@tanstack/react-start/server-entry";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fetch(req: Request): Promise<Response> {
|
||||||
|
return handler.fetch(req);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
@import "./timing-functions.css" layer(base);
|
@import "./timing-functions.css" layer(base);
|
||||||
@import "./page-transition.css" layer(base);
|
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--main: oklch(100 0 0);
|
--main: oklch(100 0 0);
|
||||||
|
--secondary: oklch(0.85 0 0);
|
||||||
--brand: oklch(0.5609 0.2483 280.67);
|
--brand: oklch(0.5609 0.2483 280.67);
|
||||||
--neutral: oklch(0.155 0.0395 285.68);
|
--neutral: oklch(0.155 0.0395 285.68);
|
||||||
--neutral-accent: oklch(0.1 0.0395 285.68);
|
--neutral-accent: oklch(0.1 0.0395 285.68);
|
||||||
@@ -13,7 +13,8 @@
|
|||||||
|
|
||||||
--font-display: "Ubuntu", ui-sans-serif, system-ui, sans-serif;
|
--font-display: "Ubuntu", ui-sans-serif, system-ui, sans-serif;
|
||||||
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
||||||
--font-mono: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
--font-mono:
|
||||||
|
Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||||
|
|
||||||
--radius: 0.625rem;
|
--radius: 0.625rem;
|
||||||
@@ -51,19 +52,6 @@
|
|||||||
--container-max-section: 100%;
|
--container-max-section: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @variant dark {
|
|
||||||
:root {
|
|
||||||
--main: oklch(1 0 0);
|
|
||||||
--secondary: oklch(0.8 0 0);
|
|
||||||
--primary: oklch(84.44% 0.2131 153.61);
|
|
||||||
--neutral: oklch(16.36% 0.0088 172.9);
|
|
||||||
--neutral-accent: oklch(19.73% 0.032 168.99);
|
|
||||||
--highlight: oklch(66.39% 0.2398 3.2);
|
|
||||||
--success: oklch(91.1% 0.1605 148.89);
|
|
||||||
--error: oklch(67.36% 0.2339 0.92);
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
@variant lg {
|
@variant lg {
|
||||||
:root {
|
:root {
|
||||||
--container-max-section: 1000px;
|
--container-max-section: 1000px;
|
||||||
@@ -93,11 +81,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@apply text-main font-display;
|
@apply text-main font-display text-3xl font-bold;
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
@apply text-3xl font-bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@@ -120,10 +104,6 @@
|
|||||||
@apply decoration-main text-main;
|
@apply decoration-main text-main;
|
||||||
}
|
}
|
||||||
|
|
||||||
[astro-icon] {
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
td {
|
||||||
@apply px-3 py-1 border;
|
@apply px-3 py-1 border;
|
||||||
}
|
}
|
||||||
@@ -151,7 +131,33 @@
|
|||||||
|
|
||||||
p,
|
p,
|
||||||
li {
|
li {
|
||||||
@apply max-w-[600px] text-base text-secondary;
|
@apply max-w-[600px] text-base text-secondary;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown {
|
||||||
|
& h1 {
|
||||||
|
@apply mb-4! mt-8!;
|
||||||
|
}
|
||||||
|
|
||||||
|
& h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5 {
|
||||||
|
@apply mb-4! mt-6!;
|
||||||
|
}
|
||||||
|
|
||||||
|
& p {
|
||||||
|
@apply mb-2! whitespace-break-spaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
& ul {
|
||||||
|
@apply ml-4!;
|
||||||
|
}
|
||||||
|
|
||||||
|
& li {
|
||||||
|
@apply ml-4!;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
@reference "./global.css";
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
@apply mb-4! mt-8!;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5 {
|
|
||||||
@apply mb-4! mt-6!;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
@apply mb-2! whitespace-break-spaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
@apply ml-4!;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
@apply ml-4!;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
@keyframes pageEnter {
|
|
||||||
from {
|
|
||||||
transform: translateY(40px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes pageLeave {
|
|
||||||
from {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+25
-16
@@ -1,18 +1,27 @@
|
|||||||
{
|
{
|
||||||
"extends": "astro/tsconfigs/strictest",
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "./src",
|
"target": "ES2022",
|
||||||
"strict": true,
|
"jsx": "react-jsx",
|
||||||
"paths": {
|
"module": "ESNext",
|
||||||
"@/*": ["./*"]
|
"paths": {
|
||||||
},
|
"@/*": ["./src/*"],
|
||||||
"plugins": [
|
"./*": ["./*"]
|
||||||
{
|
},
|
||||||
"name": "@astrojs/ts-plugin"
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
}
|
"types": ["vite/client"],
|
||||||
],
|
|
||||||
"jsx": "preserve",
|
"moduleResolution": "bundler",
|
||||||
"jsxImportSource": "solid-js"
|
"allowImportingTsExtensions": true,
|
||||||
},
|
"allowJs": true,
|
||||||
"exclude": ["node_modules", "dist"]
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
|
||||||
|
import viteReact from "@vitejs/plugin-react";
|
||||||
|
import { nitro } from "nitro/vite";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
tsconfigPaths: true,
|
||||||
|
dedupe: ["react", "react-dom"],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
tailwindcss(),
|
||||||
|
tanstackStart(),
|
||||||
|
nitro({
|
||||||
|
preset: "bun",
|
||||||
|
}),
|
||||||
|
viteReact(),
|
||||||
|
],
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user