Add new font dependencies and refactor landing page to use components for improved structure
This commit is contained in:
10
package.json
10
package.json
@@ -13,6 +13,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/geist": "^5.2.8",
|
||||
"@fontsource/archivo": "^5.2.8",
|
||||
"@fontsource/dm-sans": "^5.2.8",
|
||||
"@fontsource/fraunces": "^5.2.9",
|
||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||
"@fontsource/ibm-plex-sans": "^5.2.8",
|
||||
"@fontsource/instrument-serif": "^5.2.8",
|
||||
"@fontsource/literata": "^5.2.8",
|
||||
"@fontsource/newsreader": "^5.2.10",
|
||||
"@fontsource/source-sans-3": "^5.2.9",
|
||||
"@fontsource/syne": "^5.2.7",
|
||||
"@tailwindcss/vite": "^4.2.3",
|
||||
"astro": "^6.1.8",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
||||
82
pnpm-lock.yaml
generated
82
pnpm-lock.yaml
generated
@@ -11,6 +11,36 @@ importers:
|
||||
'@fontsource-variable/geist':
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8
|
||||
'@fontsource/archivo':
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8
|
||||
'@fontsource/dm-sans':
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8
|
||||
'@fontsource/fraunces':
|
||||
specifier: ^5.2.9
|
||||
version: 5.2.9
|
||||
'@fontsource/ibm-plex-mono':
|
||||
specifier: ^5.2.7
|
||||
version: 5.2.7
|
||||
'@fontsource/ibm-plex-sans':
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8
|
||||
'@fontsource/instrument-serif':
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8
|
||||
'@fontsource/literata':
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8
|
||||
'@fontsource/newsreader':
|
||||
specifier: ^5.2.10
|
||||
version: 5.2.10
|
||||
'@fontsource/source-sans-3':
|
||||
specifier: ^5.2.9
|
||||
version: 5.2.9
|
||||
'@fontsource/syne':
|
||||
specifier: ^5.2.7
|
||||
version: 5.2.7
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.2.3
|
||||
version: 4.2.3(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0))
|
||||
@@ -387,6 +417,36 @@ packages:
|
||||
'@fontsource-variable/geist@5.2.8':
|
||||
resolution: {integrity: sha512-cJ6m9e+8MQ5dCYJsLylfZrgBh6KkG4bOLckB35Tr9J/EqdkEM6QllH5PxqP1dhTvFup+HtMRPuz9xOjxXJggxw==}
|
||||
|
||||
'@fontsource/archivo@5.2.8':
|
||||
resolution: {integrity: sha512-Fc4yuMt85MmcYooUVpYHAJRJTymPv2+J9HsMP2f5mxR/0pIWgE2V5XfgNTavPpklX2rJFprdT36iE6Rr8ds9MA==}
|
||||
|
||||
'@fontsource/dm-sans@5.2.8':
|
||||
resolution: {integrity: sha512-tlovG42m9ESG28WiHpLq3F5umAlm64rv0RkqTbYowRn70e9OlRr5a3yTJhrhrY+k5lftR/OFJjPzOLQzk8EfCA==}
|
||||
|
||||
'@fontsource/fraunces@5.2.9':
|
||||
resolution: {integrity: sha512-XDzuddBtoC7BZgZdBn6b7hsFZY2+V1hgN7yca5fBTKuHjb/lOd45a0Ji8dTUgFhPoL7RdGupo+bC2BFSt6UH8Q==}
|
||||
|
||||
'@fontsource/ibm-plex-mono@5.2.7':
|
||||
resolution: {integrity: sha512-MKAb8qV+CaiMQn2B0dIi1OV3565NYzp3WN5b4oT6LTkk+F0jR6j0ZN+5BKJiIhffDC3rtBULsYZE65+0018z9w==}
|
||||
|
||||
'@fontsource/ibm-plex-sans@5.2.8':
|
||||
resolution: {integrity: sha512-eztSXjDhPhcpxNIiGTgMebdLP9qS4rWkysuE1V7c+DjOR0qiezaiDaTwQE7bTnG5HxAY/8M43XKDvs3cYq6ZYQ==}
|
||||
|
||||
'@fontsource/instrument-serif@5.2.8':
|
||||
resolution: {integrity: sha512-s+bkz+syj2rO00Rmq9g0P+PwuLig33DR1xDR8pTWmovH1pUjwnncrFk++q9mmOex8fUQ7oW80gPpPDaw7V1MMw==}
|
||||
|
||||
'@fontsource/literata@5.2.8':
|
||||
resolution: {integrity: sha512-Rj3eEQeu7+yBw9ZjXxcRiKDDrhbhpCyb0b79VJVCjDF1fdgJ8gDr9C1SRFLwfi+VaHEkhJDnRGm25fq1Yk+wNg==}
|
||||
|
||||
'@fontsource/newsreader@5.2.10':
|
||||
resolution: {integrity: sha512-TFaYzoFhDqarUyV2yYjgZZEwT4bpaj6sGBnXSnFknQ/QB8/9LzfY6IO9+inHOX4zzPp87Z7/KuG1OI5gr91Q3A==}
|
||||
|
||||
'@fontsource/source-sans-3@5.2.9':
|
||||
resolution: {integrity: sha512-u3ymIq4rfmCCyB9MEw/sFR5lPVJ1yTNXmIMbUz+9kVCFIHvNtfzXOEBuvkg3Tk0zhmioPeJ28ZK5smZ7TurezQ==}
|
||||
|
||||
'@fontsource/syne@5.2.7':
|
||||
resolution: {integrity: sha512-OgWgLPSbieiSfdFXgMs0l/rspIFzVoDZxw8wKj15n39epRqO5+Uv36c0MXNDcw2GuK/TIBgpC9G8VAgs+bVm7A==}
|
||||
|
||||
'@hono/node-server@1.19.14':
|
||||
resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==}
|
||||
engines: {node: '>=18.14.1'}
|
||||
@@ -1657,6 +1717,7 @@ packages:
|
||||
|
||||
'@ungap/structured-clone@1.3.0':
|
||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||
deprecated: Potential CWE-502 - Update to 1.3.1 or higher
|
||||
|
||||
accepts@2.0.0:
|
||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||
@@ -3388,6 +3449,7 @@ packages:
|
||||
tsconfck@3.1.6:
|
||||
resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==}
|
||||
engines: {node: ^18 || >=20}
|
||||
deprecated: unmaintained
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: ^5.0.0
|
||||
@@ -4082,6 +4144,26 @@ snapshots:
|
||||
|
||||
'@fontsource-variable/geist@5.2.8': {}
|
||||
|
||||
'@fontsource/archivo@5.2.8': {}
|
||||
|
||||
'@fontsource/dm-sans@5.2.8': {}
|
||||
|
||||
'@fontsource/fraunces@5.2.9': {}
|
||||
|
||||
'@fontsource/ibm-plex-mono@5.2.7': {}
|
||||
|
||||
'@fontsource/ibm-plex-sans@5.2.8': {}
|
||||
|
||||
'@fontsource/instrument-serif@5.2.8': {}
|
||||
|
||||
'@fontsource/literata@5.2.8': {}
|
||||
|
||||
'@fontsource/newsreader@5.2.10': {}
|
||||
|
||||
'@fontsource/source-sans-3@5.2.9': {}
|
||||
|
||||
'@fontsource/syne@5.2.7': {}
|
||||
|
||||
'@hono/node-server@1.19.14(hono@4.12.14)':
|
||||
dependencies:
|
||||
hono: 4.12.14
|
||||
|
||||
156
src/components/SwissLanding.astro
Normal file
156
src/components/SwissLanding.astro
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
import {
|
||||
site,
|
||||
services,
|
||||
stack,
|
||||
projects,
|
||||
process,
|
||||
} from "../data/landing-content";
|
||||
|
||||
function stackSlug(name: string) {
|
||||
return name.toLowerCase().replace(/\s+/g, "-").replace(/\//g, "");
|
||||
}
|
||||
---
|
||||
|
||||
<div class="v2">
|
||||
<div class="v2__shell">
|
||||
<header class="v2__header">
|
||||
<p class="v2__site-mark">
|
||||
<span class="v2__prompt" aria-hidden="true">~</span>
|
||||
matthias-meister
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<main class="v2__grid">
|
||||
<section class="v2__hero" aria-labelledby="hero-title">
|
||||
<div class="v2__hero-main">
|
||||
<p class="v2__hero-prompt" aria-hidden="true">
|
||||
<span class="v2__prompt">$</span> whoami
|
||||
</p>
|
||||
<h1 class="v2__title" id="hero-title">{site.name}</h1>
|
||||
<p class="v2__tagline">{site.tagline}</p>
|
||||
<a class="v2__cta" href={`mailto:${site.email}`}>
|
||||
<span class="v2__cta-prefix" aria-hidden="true">> </span>
|
||||
contact
|
||||
</a>
|
||||
</div>
|
||||
<p class="v2__hero-meta">
|
||||
<span class="v2__comment" aria-hidden="true">// </span>
|
||||
{site.role}
|
||||
<span class="v2__cursor" aria-hidden="true"></span>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__section" aria-labelledby="services">
|
||||
<h2 class="v2__section-label" id="services">
|
||||
<span class="v2__label-num">01</span>
|
||||
<span class="v2__comment" aria-hidden="true"> // services</span>
|
||||
</h2>
|
||||
<div class="v2__section-body">
|
||||
<div class="v2__services">
|
||||
{
|
||||
services.map((service) => (
|
||||
<article class="v2__service">
|
||||
<h3>{service.title}</h3>
|
||||
<p>{service.description}</p>
|
||||
</article>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="v2__section" aria-labelledby="stack">
|
||||
<h2 class="v2__section-label" id="stack">
|
||||
<span class="v2__label-num">02</span>
|
||||
<span class="v2__comment" aria-hidden="true"> // stack</span>
|
||||
</h2>
|
||||
<div class="v2__section-body">
|
||||
<table class="v2__stack-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">module</th>
|
||||
<th scope="col">scope</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
stack.map((item) => (
|
||||
<tr>
|
||||
<td class="v2__mono">{stackSlug(item.name)}</td>
|
||||
<td>{item.category.toLowerCase()}</td>
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="v2__section" aria-labelledby="work">
|
||||
<h2 class="v2__section-label" id="work">
|
||||
<span class="v2__label-num">03</span>
|
||||
<span class="v2__comment" aria-hidden="true"> // work</span>
|
||||
</h2>
|
||||
<div class="v2__section-body">
|
||||
<div class="v2__projects">
|
||||
{
|
||||
projects.map((project) => (
|
||||
<article class="v2__project">
|
||||
<h3>{project.title}</h3>
|
||||
<p>{project.description}</p>
|
||||
<ul class="v2__tags">
|
||||
{project.tags.map((tag) => (
|
||||
<li>[{tag}]</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="v2__section" aria-labelledby="process">
|
||||
<h2 class="v2__section-label" id="process">
|
||||
<span class="v2__label-num">04</span>
|
||||
<span class="v2__comment" aria-hidden="true"> // process</span>
|
||||
</h2>
|
||||
<div class="v2__section-body">
|
||||
<div class="v2__process">
|
||||
{
|
||||
process.map((step) => (
|
||||
<article class="v2__step">
|
||||
<h3>{step.step}</h3>
|
||||
<p>{step.description}</p>
|
||||
</article>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="v2__section" aria-labelledby="contact">
|
||||
<h2 class="v2__section-label" id="contact">
|
||||
<span class="v2__label-num">05</span>
|
||||
<span class="v2__comment" aria-hidden="true"> // contact</span>
|
||||
</h2>
|
||||
<div class="v2__section-body">
|
||||
<p class="v2__contact-line">
|
||||
<span class="v2__prompt" aria-hidden="true">$ </span>
|
||||
mailto: <a href={`mailto:${site.email}`}>{site.email}</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="v2__footer">
|
||||
<p>
|
||||
<span class="v2__comment" aria-hidden="true">// </span>
|
||||
© {new Date().getFullYear()} {site.name} ·
|
||||
<a href="/impressum">Impressum</a> ·
|
||||
<a href="/datenschutz">Datenschutz</a>
|
||||
</p>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
75
src/data/landing-content.ts
Normal file
75
src/data/landing-content.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
export const site = {
|
||||
name: "Matthias Meister",
|
||||
role: "iOS & Fullstack Web Developer",
|
||||
tagline:
|
||||
"Native Apps und moderne Webprodukte — von der Idee bis zum Launch, durchdacht und wartbar.",
|
||||
email: "support@matthiasmeister.dev",
|
||||
} as const;
|
||||
|
||||
export const services = [
|
||||
{
|
||||
title: "Native iOS Apps",
|
||||
description:
|
||||
"SwiftUI-Apps mit klarer Architektur, sauberer State-Verwaltung und App-Store-tauglicher Qualität.",
|
||||
},
|
||||
{
|
||||
title: "Web Apps",
|
||||
description:
|
||||
"Schnelle, zugängliche Frontends mit Astro oder React — performant, responsiv und wartbar.",
|
||||
},
|
||||
{
|
||||
title: "Fullstack",
|
||||
description:
|
||||
"APIs, Datenbanken und Deployment aus einer Hand. Convex, Supabase oder klassische Backends.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const stack = [
|
||||
{ name: "Swift / SwiftUI", category: "Mobile" },
|
||||
{ name: "Astro", category: "Web" },
|
||||
{ name: "React", category: "Web" },
|
||||
{ name: "TypeScript", category: "Language" },
|
||||
{ name: "Convex", category: "Backend" },
|
||||
{ name: "Supabase", category: "Backend" },
|
||||
{ name: "Tailwind CSS", category: "Styling" },
|
||||
{ name: "Git / CI", category: "Ops" },
|
||||
] as const;
|
||||
|
||||
export const projects = [
|
||||
{
|
||||
title: "HabitFlow",
|
||||
description:
|
||||
"Minimalistische Gewohnheits-App mit Widgets, HealthKit-Integration und Offline-First-Sync.",
|
||||
tags: ["SwiftUI", "WidgetKit", "CloudKit"],
|
||||
},
|
||||
{
|
||||
title: "Studio Dashboard",
|
||||
description:
|
||||
"Realtime-Projektboard für ein kleines Studio — Kanban, Zeiterfassung und Kundenportal in einem.",
|
||||
tags: ["Astro", "Convex", "TypeScript"],
|
||||
},
|
||||
{
|
||||
title: "Field Notes",
|
||||
description:
|
||||
"Ortsbezogene Notizen-App für Außendienst — Kamera, GPS und strukturierte Formulare.",
|
||||
tags: ["SwiftUI", "Core Location", "Supabase"],
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const process = [
|
||||
{
|
||||
step: "Discovery",
|
||||
description:
|
||||
"Ziele, Nutzer und technische Rahmenbedingungen klären — bevor Code geschrieben wird.",
|
||||
},
|
||||
{
|
||||
step: "Build",
|
||||
description:
|
||||
"Iterativ entwickeln mit regelmäßigem Feedback. Fokus auf Qualität statt Feature-Inflation.",
|
||||
},
|
||||
{
|
||||
step: "Launch",
|
||||
description:
|
||||
"App Store, Deployment, Monitoring und Übergabe — damit das Produkt auch danach läuft.",
|
||||
},
|
||||
] as const;
|
||||
43
src/data/legal.ts
Normal file
43
src/data/legal.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Pflichtangaben für Impressum und Datenschutz.
|
||||
* Bitte Straße, PLZ, Ort und ggf. USt-IdNr. vor dem Live-Gang eintragen.
|
||||
*/
|
||||
export const legal = {
|
||||
operatorName: "Matthias Meister",
|
||||
businessForm: "Matthias Meister Softwareentwicklung",
|
||||
street: "Karl-Marx-Str. 22",
|
||||
postalCode: "08451",
|
||||
city: "Crimmitschau",
|
||||
country: "Deutschland",
|
||||
email: "support@matthiasmeister.dev",
|
||||
/** Optional — leer lassen, wenn keine Telefonnummer angegeben werden soll */
|
||||
phone: "",
|
||||
/** USt-IdNr. gemäß § 27a UStG — leer lassen bei Kleinunternehmer */
|
||||
vatId: "DE460155697",
|
||||
/** § 19 UStG — keine Umsatzsteuer ausweisen */
|
||||
smallBusinessPursuantTo19UStG: false,
|
||||
siteUrl: "https://matthiasmeister.dev",
|
||||
/** Hosting-Anbieter der statischen Website */
|
||||
hosting: {
|
||||
name: "Hetzner Online GmbH",
|
||||
address: "Industriestr. 20, 91217 Neumarkt",
|
||||
privacyUrl: "https://www.hetzner.com/de/privacy-policy-notice",
|
||||
},
|
||||
analytics: {
|
||||
name: "Rybbit",
|
||||
description: "selbst gehostete Webanalyse",
|
||||
operator: "Matthias Meister",
|
||||
endpoint: "https://rybbit.matthias.lol",
|
||||
},
|
||||
/** Stand der Datenschutzerklärung (ISO-Datum) */
|
||||
privacyLastUpdated: "2025-06-24",
|
||||
} as const;
|
||||
|
||||
export function formatPostalAddress(): string {
|
||||
const lines = [
|
||||
legal.street,
|
||||
`${legal.postalCode} ${legal.city}`,
|
||||
legal.country,
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
75
src/layouts/BaseLayout.astro
Normal file
75
src/layouts/BaseLayout.astro
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
description = "iOS & Fullstack Web Development — Matthias Meister",
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta name="description" content={description} />
|
||||
<title>{title}</title>
|
||||
<slot name="head" />
|
||||
<script
|
||||
src="https://rybbit.matthias.lol/api/script.js"
|
||||
data-site-id="63eb61dcdf55"
|
||||
defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style is:global>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
a:focus-visible,
|
||||
button:focus-visible {
|
||||
outline: 2px solid currentColor;
|
||||
outline-offset: 3px;
|
||||
}
|
||||
</style>
|
||||
49
src/layouts/LegalLayout.astro
Normal file
49
src/layouts/LegalLayout.astro
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
import BaseLayout from "./BaseLayout.astro";
|
||||
import { site } from "../data/landing-content";
|
||||
import "@fontsource/syne/700.css";
|
||||
import "@fontsource/ibm-plex-sans/400.css";
|
||||
import "@fontsource/ibm-plex-sans/600.css";
|
||||
import "@fontsource/ibm-plex-mono/400.css";
|
||||
import "@fontsource/ibm-plex-mono/500.css";
|
||||
import "../styles/site.css";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const { title, description } = Astro.props;
|
||||
---
|
||||
|
||||
<BaseLayout title={title} description={description}>
|
||||
<div class="v2">
|
||||
<div class="v2__shell v2__shell--legal">
|
||||
<header class="v2__header v2__header--legal">
|
||||
<p class="v2__site-mark">
|
||||
<a href="/" class="v2__home-link">
|
||||
<span class="v2__prompt" aria-hidden="true">~</span>
|
||||
matthias-meister
|
||||
</a>
|
||||
</p>
|
||||
<nav class="v2__legal-nav" aria-label="Rechtliches">
|
||||
<a href="/impressum">Impressum</a>
|
||||
<a href="/datenschutz">Datenschutz</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="v2__legal">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<footer class="v2__footer v2__footer--legal">
|
||||
<p>
|
||||
<span class="v2__comment" aria-hidden="true">// </span>
|
||||
© {new Date().getFullYear()} {site.name} ·
|
||||
<a href="/impressum">Impressum</a> ·
|
||||
<a href="/datenschutz">Datenschutz</a>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
209
src/pages/datenschutz.astro
Normal file
209
src/pages/datenschutz.astro
Normal file
@@ -0,0 +1,209 @@
|
||||
---
|
||||
import LegalLayout from "../layouts/LegalLayout.astro";
|
||||
import { legal } from "../data/legal";
|
||||
---
|
||||
|
||||
<LegalLayout
|
||||
title="Datenschutzerklärung — Matthias Meister"
|
||||
description="Informationen zur Verarbeitung personenbezogener Daten gemäß DSGVO."
|
||||
>
|
||||
<h1 class="v2__legal-title">Datenschutzerklärung</h1>
|
||||
<p class="v2__legal-meta">
|
||||
Stand: {new Date(legal.privacyLastUpdated).toLocaleDateString("de-DE", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})}
|
||||
</p>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>1. Verantwortlicher</h2>
|
||||
<p>
|
||||
Verantwortlicher im Sinne der Datenschutz-Grundverordnung (DSGVO) ist:
|
||||
</p>
|
||||
<p>
|
||||
{legal.operatorName}<br />
|
||||
{legal.street}<br />
|
||||
{legal.postalCode} {legal.city}<br />
|
||||
{legal.country}<br />
|
||||
E-Mail: <a href={`mailto:${legal.email}`}>{legal.email}</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>2. Übersicht der Verarbeitungen</h2>
|
||||
<p>
|
||||
Wir verarbeiten personenbezogene Daten nur, soweit dies zur Bereitstellung einer
|
||||
funktionsfähigen Website sowie unserer Inhalte und Leistungen erforderlich ist.
|
||||
Die Verarbeitung personenbezogener Daten erfolgt regelmäßig nur nach Einwilligung
|
||||
des Nutzers oder wenn eine gesetzliche Erlaubnis vorliegt.
|
||||
</p>
|
||||
<p>Im Einzelnen verarbeiten wir Daten in folgenden Fällen:</p>
|
||||
<ul>
|
||||
<li>Besuch und Nutzung der Website (Server-Logfiles beim Hosting)</li>
|
||||
<li>Webanalyse zur Reichweitenmessung ({legal.analytics.name})</li>
|
||||
<li>Kontaktaufnahme per E-Mail</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>3. Rechtsgrundlagen</h2>
|
||||
<p>Soweit wir personenbezogene Daten verarbeiten, stützen wir uns insbesondere auf:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Art. 6 Abs. 1 lit. f DSGVO</strong> — berechtigtes Interesse an der
|
||||
sicheren, stabilen und bedarfsgerechten Bereitstellung unserer Website sowie an
|
||||
der anonymisierten Auswertung des Nutzungsverhaltens
|
||||
</li>
|
||||
<li>
|
||||
<strong>Art. 6 Abs. 1 lit. b DSGVO</strong> — Verarbeitung zur Bearbeitung von
|
||||
Anfragen und zur Anbahnung bzw. Durchführung eines Vertragsverhältnisses
|
||||
</li>
|
||||
<li>
|
||||
<strong>Art. 6 Abs. 1 lit. a DSGVO</strong> — Einwilligung, sofern wir diese
|
||||
ausdrücklich einholen
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>4. Hosting</h2>
|
||||
<p>
|
||||
Unsere Website wird bei {legal.hosting.name} gehostet. Beim Aufruf der Website
|
||||
werden automatisch Informationen in sogenannten Server-Logfiles übermittelt, die
|
||||
Ihr Browser übermittelt. Dies können insbesondere sein:
|
||||
</p>
|
||||
<ul>
|
||||
<li>IP-Adresse</li>
|
||||
<li>Datum und Uhrzeit der Anfrage</li>
|
||||
<li>aufgerufene URL</li>
|
||||
<li>Referrer-URL</li>
|
||||
<li>Browsertyp und -version</li>
|
||||
<li>Betriebssystem</li>
|
||||
</ul>
|
||||
<p>
|
||||
Die Verarbeitung erfolgt zur Gewährleistung eines störungsfreien Betriebs der
|
||||
Website sowie zur Erkennung und Abwehr von Missbrauch. Rechtsgrundlage ist Art. 6
|
||||
Abs. 1 lit. f DSGVO.
|
||||
</p>
|
||||
<p>
|
||||
Anbieter: {legal.hosting.name}, {legal.hosting.address}
|
||||
{
|
||||
legal.hosting.privacyUrl.startsWith("http") && (
|
||||
<>
|
||||
{" "}
|
||||
— Datenschutzhinweise:{" "}
|
||||
<a href={legal.hosting.privacyUrl} rel="noopener noreferrer" target="_blank">
|
||||
{legal.hosting.privacyUrl}
|
||||
</a>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>5. Webanalyse ({legal.analytics.name})</h2>
|
||||
<p>
|
||||
Wir setzen {legal.analytics.name} ein — eine {legal.analytics.description} durch{" "}
|
||||
{legal.analytics.operator}. Dabei werden Nutzungsdaten der Website in
|
||||
zusammengefasster Form ausgewertet, um zu verstehen, welche Seiten besucht werden
|
||||
und wie Besucher auf die Website gelangen.
|
||||
</p>
|
||||
<p>Dabei können insbesondere folgende Daten verarbeitet werden:</p>
|
||||
<ul>
|
||||
<li>aufgerufene Seiten und Verweildauer</li>
|
||||
<li>Referrer (Herkunftsseite)</li>
|
||||
<li>Browsertyp, Betriebssystem, Bildschirmauflösung (in aggregierter Form)</li>
|
||||
<li>ungefährer Standort (Land/Region, keine präzise Geolokalisierung)</li>
|
||||
<li>gekürzte oder pseudonymisierte IP-Adresse</li>
|
||||
</ul>
|
||||
<p>
|
||||
Es werden keine Profile über mehrere Websites hinweg erstellt und keine Daten zu
|
||||
Marketingzwecken an Dritte weitergegeben. Rechtsgrundlage ist Art. 6 Abs. 1 lit. f
|
||||
DSGVO (berechtigtes Interesse an der statistischen Auswertung unseres Webangebots).
|
||||
</p>
|
||||
<p>
|
||||
Verarbeitung über:{" "}
|
||||
<a href={legal.analytics.endpoint} rel="noopener noreferrer" target="_blank">
|
||||
{legal.analytics.endpoint}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
Sie können der Verarbeitung aus Gründen, die sich aus Ihrer besonderen Situation
|
||||
ergeben, jederzeit widersprechen (Art. 21 DSGVO). Nutzen Sie hierfür die
|
||||
Kontaktdaten unter Abschnitt 1.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>6. Kontakt per E-Mail</h2>
|
||||
<p>
|
||||
Wenn Sie uns per E-Mail kontaktieren, speichern und verarbeiten wir Ihre Angaben
|
||||
(z. B. Name, E-Mail-Adresse, Inhalt der Nachricht), um Ihre Anfrage zu bearbeiten.
|
||||
Die Daten werden gelöscht, sobald die Anfrage abschließend beantwortet ist und keine
|
||||
gesetzlichen Aufbewahrungspflichten entgegenstehen.
|
||||
</p>
|
||||
<p>
|
||||
Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO, sofern die Anfrage im Zusammenhang
|
||||
mit einem Vertrag steht, andernfalls Art. 6 Abs. 1 lit. f DSGVO.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>7. Speicherdauer</h2>
|
||||
<p>
|
||||
Personenbezogene Daten verarbeiten wir nur so lange, wie es für die jeweiligen
|
||||
Zwecke erforderlich ist oder gesetzliche Aufbewahrungsfristen bestehen. Server-Logfiles
|
||||
werden in der Regel nach kurzer Zeit automatisch gelöscht oder anonymisiert, sofern
|
||||
keine Sicherheitsvorfälle eine längere Aufbewahrung erfordern.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>8. Ihre Rechte</h2>
|
||||
<p>Sie haben gegenüber uns folgende Rechte hinsichtlich der Sie betreffenden Daten:</p>
|
||||
<ul>
|
||||
<li>Auskunft (Art. 15 DSGVO)</li>
|
||||
<li>Berichtigung (Art. 16 DSGVO)</li>
|
||||
<li>Löschung (Art. 17 DSGVO)</li>
|
||||
<li>Einschränkung der Verarbeitung (Art. 18 DSGVO)</li>
|
||||
<li>Datenübertragbarkeit (Art. 20 DSGVO)</li>
|
||||
<li>Widerspruch gegen die Verarbeitung (Art. 21 DSGVO)</li>
|
||||
<li>Widerruf erteilter Einwilligungen (Art. 7 Abs. 3 DSGVO)</li>
|
||||
</ul>
|
||||
<p>
|
||||
Zur Ausübung Ihrer Rechte genügt eine Nachricht an{" "}
|
||||
<a href={`mailto:${legal.email}`}>{legal.email}</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>9. Beschwerderecht bei einer Aufsichtsbehörde</h2>
|
||||
<p>
|
||||
Sie haben das Recht, sich bei einer Datenschutz-Aufsichtsbehörde über unsere
|
||||
Verarbeitung personenbezogener Daten zu beschweren. Zuständig ist in der Regel die
|
||||
Aufsichtsbehörde Ihres üblichen Aufenthaltsorts, Ihres Arbeitsplatzes oder des Orts
|
||||
des mutmaßlichen Verstoßes.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>10. SSL- bzw. TLS-Verschlüsselung</h2>
|
||||
<p>
|
||||
Diese Seite nutzt aus Sicherheitsgründen und zum Schutz der Übertragung vertraulicher
|
||||
Inhalte eine SSL- bzw. TLS-Verschlüsselung. Eine verschlüsselte Verbindung erkennen
|
||||
Sie daran, dass die Adresszeile des Browsers von „http://“ auf „https://“ wechselt
|
||||
und an dem Schloss-Symbol in der Browserzeile.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>11. Aktualität</h2>
|
||||
<p>
|
||||
Wir passen diese Datenschutzerklärung an, wenn sich unsere Website, eingesetzte
|
||||
Dienste oder die Rechtslage ändern. Es gilt die jeweils auf dieser Seite
|
||||
veröffentlichte Fassung.
|
||||
</p>
|
||||
</section>
|
||||
</LegalLayout>
|
||||
137
src/pages/impressum.astro
Normal file
137
src/pages/impressum.astro
Normal file
@@ -0,0 +1,137 @@
|
||||
---
|
||||
import LegalLayout from "../layouts/LegalLayout.astro";
|
||||
import { legal } from "../data/legal";
|
||||
---
|
||||
|
||||
<LegalLayout
|
||||
title="Impressum — Matthias Meister"
|
||||
description="Impressum und Anbieterkennzeichnung gemäß § 5 TMG."
|
||||
>
|
||||
<h1 class="v2__legal-title">Impressum</h1>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>Angaben gemäß § 5 TMG</h2>
|
||||
<p>
|
||||
{legal.operatorName}<br />
|
||||
{legal.businessForm}<br />
|
||||
{legal.street}<br />
|
||||
{legal.postalCode} {legal.city}<br />
|
||||
{legal.country}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>Kontakt</h2>
|
||||
<p>
|
||||
E-Mail: <a href={`mailto:${legal.email}`}>{legal.email}</a>
|
||||
{
|
||||
legal.phone && (
|
||||
<>
|
||||
<br />
|
||||
Telefon: <a href={`tel:${legal.phone.replace(/\s/g, "")}`}>{legal.phone}</a>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{
|
||||
legal.vatId ? (
|
||||
<section class="v2__legal-section">
|
||||
<h2>Umsatzsteuer-ID</h2>
|
||||
<p>
|
||||
Umsatzsteuer-Identifikationsnummer gemäß § 27a UStG: {legal.vatId}
|
||||
</p>
|
||||
</section>
|
||||
) : legal.smallBusinessPursuantTo19UStG ? (
|
||||
<section class="v2__legal-section">
|
||||
<h2>Umsatzsteuer</h2>
|
||||
<p>
|
||||
Gemäß § 19 UStG wird keine Umsatzsteuer berechnet (Kleinunternehmerregelung).
|
||||
</p>
|
||||
</section>
|
||||
) : null
|
||||
}
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV</h2>
|
||||
<p>
|
||||
{legal.operatorName}<br />
|
||||
{legal.street}<br />
|
||||
{legal.postalCode} {legal.city}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>EU-Streitbeilegung</h2>
|
||||
<p>
|
||||
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS)
|
||||
bereit:
|
||||
<a
|
||||
href="https://ec.europa.eu/consumers/odr/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
https://ec.europa.eu/consumers/odr/
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer
|
||||
Verbraucherschlichtungsstelle teilzunehmen.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>Haftung für Inhalte</h2>
|
||||
<p>
|
||||
Als Diensteanbieter sind wir gemäß § 7 Abs. 1 TMG für eigene Inhalte auf diesen
|
||||
Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir
|
||||
als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte
|
||||
fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine
|
||||
rechtswidrige Tätigkeit hinweisen.
|
||||
</p>
|
||||
<p>
|
||||
Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den
|
||||
allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist
|
||||
jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich.
|
||||
Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese Inhalte
|
||||
umgehend entfernen.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>Haftung für Links</h2>
|
||||
<p>
|
||||
Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir keinen
|
||||
Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine Gewähr
|
||||
übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter
|
||||
oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt
|
||||
der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum
|
||||
Zeitpunkt der Verlinkung nicht erkennbar.
|
||||
</p>
|
||||
<p>
|
||||
Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete
|
||||
Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von
|
||||
Rechtsverletzungen werden wir derartige Links umgehend entfernen.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="v2__legal-section">
|
||||
<h2>Urheberrecht</h2>
|
||||
<p>
|
||||
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten
|
||||
unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung,
|
||||
Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes
|
||||
bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers.
|
||||
Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen
|
||||
Gebrauch gestattet.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
</LegalLayout>
|
||||
@@ -1,102 +1,18 @@
|
||||
---
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import SwissLanding from "../components/SwissLanding.astro";
|
||||
import { site } from "../data/landing-content";
|
||||
import "@fontsource/syne/700.css";
|
||||
import "@fontsource/ibm-plex-sans/400.css";
|
||||
import "@fontsource/ibm-plex-sans/600.css";
|
||||
import "@fontsource/ibm-plex-mono/400.css";
|
||||
import "@fontsource/ibm-plex-mono/500.css";
|
||||
import "../styles/site.css";
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>Website im Aufbau</title>
|
||||
<script
|
||||
src="https://rybbit.matthias.lol/api/script.js"
|
||||
data-site-id="63eb61dcdf55"
|
||||
defer
|
||||
></script>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light;
|
||||
font-family: "Georgia", "Times New Roman", serif;
|
||||
background: linear-gradient(180deg, #f7f3eb 0%, #ece4d7 100%);
|
||||
color: #1f1811;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 24px;
|
||||
background:
|
||||
radial-gradient(circle at top, rgba(255, 255, 255, 0.85), transparent 40%),
|
||||
linear-gradient(180deg, #f7f3eb 0%, #ece4d7 100%);
|
||||
}
|
||||
|
||||
main {
|
||||
width: min(100%, 560px);
|
||||
padding: 40px 32px;
|
||||
border: 1px solid rgba(64, 43, 24, 0.14);
|
||||
border-radius: 24px;
|
||||
background: rgba(255, 252, 247, 0.88);
|
||||
box-shadow: 0 20px 60px rgba(60, 41, 24, 0.12);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 16px;
|
||||
font-size: clamp(2rem, 5vw, 3.25rem);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 1.0625rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
p + p {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #6d2f14;
|
||||
font-weight: 700;
|
||||
text-decoration-thickness: 0.08em;
|
||||
text-underline-offset: 0.18em;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus-visible {
|
||||
color: #8a3a18;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 32px 22px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Website in Arbeit</h1>
|
||||
<p>Ich überarbeite diese Website gerade, da ich den Tech Stack gewechselt habe..</p>
|
||||
<p>
|
||||
Bei Fragen oder Anliegen erreichen Sie mich unter
|
||||
<a href="mailto:support@matthiasmeister.dev">support@matthiasmeister.dev</a>.
|
||||
</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
<BaseLayout
|
||||
title={`${site.name} — ${site.role}`}
|
||||
description={site.tagline}
|
||||
>
|
||||
<SwissLanding />
|
||||
</BaseLayout>
|
||||
|
||||
497
src/styles/site.css
Normal file
497
src/styles/site.css
Normal file
@@ -0,0 +1,497 @@
|
||||
.v2 {
|
||||
--v2-bg: oklch(0.975 0.014 145);
|
||||
--v2-ink: oklch(0.26 0.04 155);
|
||||
--v2-muted: oklch(0.48 0.04 155);
|
||||
--v2-accent: oklch(0.26 0.04 155);
|
||||
--v2-rule: oklch(0.89 0.022 145);
|
||||
--v2-term: oklch(0.4 0.1 155);
|
||||
--v2-term-dim: oklch(0.52 0.06 155);
|
||||
--v2-display: "Syne", system-ui, sans-serif;
|
||||
--v2-body: "IBM Plex Sans", system-ui, sans-serif;
|
||||
--v2-mono: "IBM Plex Mono", ui-monospace, monospace;
|
||||
|
||||
background: var(--v2-bg);
|
||||
color: var(--v2-ink);
|
||||
font-family: var(--v2-body);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.v2__shell {
|
||||
max-width: 76rem;
|
||||
margin: 0 auto;
|
||||
padding: clamp(1.25rem, 3vw, 2.5rem);
|
||||
}
|
||||
|
||||
.v2__header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding-bottom: 1.5rem;
|
||||
border-bottom: 1px solid var(--v2-rule);
|
||||
margin-bottom: clamp(2.5rem, 6vw, 4rem);
|
||||
}
|
||||
|
||||
.v2__site-mark {
|
||||
margin: 0;
|
||||
font-family: var(--v2-mono);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--v2-term);
|
||||
}
|
||||
|
||||
.v2__prompt {
|
||||
color: var(--v2-term);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.v2__comment {
|
||||
color: var(--v2-term-dim);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.v2__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
gap: 1rem clamp(1rem, 2vw, 1.5rem);
|
||||
background-image:
|
||||
linear-gradient(to right, oklch(0.84 0.025 145 / 0.55) 1px, transparent 1px),
|
||||
linear-gradient(to bottom, oklch(0.84 0.025 145 / 0.2) 1px, transparent 1px);
|
||||
background-size:
|
||||
calc(100% / 12) 100%,
|
||||
100% 2.5rem;
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
.v2__hero {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: subgrid;
|
||||
padding-bottom: clamp(2rem, 5vw, 3.5rem);
|
||||
border-bottom: 1px solid var(--v2-rule);
|
||||
margin-bottom: clamp(2rem, 5vw, 3.5rem);
|
||||
}
|
||||
|
||||
.v2__hero-main {
|
||||
grid-column: 1 / 9;
|
||||
}
|
||||
|
||||
.v2__hero-prompt {
|
||||
margin: 0 0 0.75rem;
|
||||
font-family: var(--v2-mono);
|
||||
font-size: 0.8125rem;
|
||||
color: var(--v2-term);
|
||||
}
|
||||
|
||||
.v2__hero-meta {
|
||||
grid-column: 9 / -1;
|
||||
align-self: end;
|
||||
font-family: var(--v2-mono);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--v2-term-dim);
|
||||
}
|
||||
|
||||
.v2__cursor {
|
||||
display: inline-block;
|
||||
width: 0.5em;
|
||||
height: 1.05em;
|
||||
margin-left: 1px;
|
||||
vertical-align: text-bottom;
|
||||
background: var(--v2-term);
|
||||
animation: v2-cursor-blink 1.15s step-end infinite;
|
||||
}
|
||||
|
||||
@keyframes v2-cursor-blink {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.v2__grid {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.v2__hero-main,
|
||||
.v2__hero-meta {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.v2__title {
|
||||
margin: 0 0 1rem;
|
||||
font-family: var(--v2-display);
|
||||
font-size: clamp(2.5rem, 6vw, 4.25rem);
|
||||
font-weight: 700;
|
||||
line-height: 1.02;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.v2__tagline {
|
||||
margin: 0 0 1.75rem;
|
||||
max-width: 42ch;
|
||||
font-size: 1.0625rem;
|
||||
color: var(--v2-muted);
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.v2__service p,
|
||||
.v2__project p,
|
||||
.v2__step p {
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.v2__cta {
|
||||
display: inline-block;
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-family: var(--v2-mono);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: lowercase;
|
||||
text-decoration: none;
|
||||
color: var(--v2-ink);
|
||||
background: transparent;
|
||||
box-shadow: inset 0 0 0 2px var(--v2-ink);
|
||||
transition: background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.v2__cta-prefix {
|
||||
color: var(--v2-term);
|
||||
}
|
||||
|
||||
.v2__cta:hover {
|
||||
color: var(--v2-bg);
|
||||
background: var(--v2-term);
|
||||
box-shadow: inset 0 0 0 2px var(--v2-term);
|
||||
}
|
||||
|
||||
.v2__cta:focus-visible {
|
||||
outline: 2px solid var(--v2-term);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.v2__section {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: subgrid;
|
||||
padding-block: clamp(2rem, 4vw, 3rem);
|
||||
border-bottom: 1px solid var(--v2-rule);
|
||||
}
|
||||
|
||||
.v2__section-label {
|
||||
grid-column: 1 / 4;
|
||||
margin: 0;
|
||||
font-family: var(--v2-mono);
|
||||
font-size: clamp(0.875rem, 1.8vw, 1.0625rem);
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--v2-ink);
|
||||
}
|
||||
|
||||
.v2__label-num {
|
||||
color: var(--v2-term);
|
||||
}
|
||||
|
||||
.v2__section-body {
|
||||
grid-column: 4 / -1;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.v2__section-label,
|
||||
.v2__section-body {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.v2__section-label {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.v2__services {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.v2__services {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.v2__service h3 {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.v2__service p {
|
||||
margin: 0;
|
||||
font-size: 0.9375rem;
|
||||
color: var(--v2-muted);
|
||||
}
|
||||
|
||||
.v2__stack-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.v2__stack-table th,
|
||||
.v2__stack-table td {
|
||||
padding: 0.65rem 0;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--v2-rule);
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.v2__stack-table th {
|
||||
font-family: var(--v2-mono);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: lowercase;
|
||||
color: var(--v2-term-dim);
|
||||
}
|
||||
|
||||
.v2__stack-table .v2__mono {
|
||||
font-family: var(--v2-mono);
|
||||
color: var(--v2-ink);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.v2__stack-table tbody td:last-child {
|
||||
font-family: var(--v2-mono);
|
||||
font-size: 0.8125rem;
|
||||
color: var(--v2-term-dim);
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
.v2__stack-table tbody tr:hover td {
|
||||
background: oklch(0.94 0.02 145);
|
||||
}
|
||||
|
||||
.v2__projects {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.v2__project {
|
||||
padding-bottom: 1.5rem;
|
||||
border-bottom: 1px solid var(--v2-rule);
|
||||
}
|
||||
|
||||
.v2__project:last-child {
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.v2__project h3 {
|
||||
margin: 0 0 0.35rem;
|
||||
font-size: 1.0625rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.v2__project p {
|
||||
margin: 0 0 0.75rem;
|
||||
font-size: 0.9375rem;
|
||||
color: var(--v2-muted);
|
||||
}
|
||||
|
||||
.v2__tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.v2__tags li {
|
||||
padding: 0.15rem 0.45rem;
|
||||
font-family: var(--v2-mono);
|
||||
font-size: 0.625rem;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--v2-term);
|
||||
border: 1px solid oklch(0.4 0.1 155 / 0.25);
|
||||
background: oklch(0.94 0.02 145 / 0.6);
|
||||
}
|
||||
|
||||
.v2__process {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.v2__process {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.v2__step h3 {
|
||||
margin: 0 0 0.35rem;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.v2__step p {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
color: var(--v2-muted);
|
||||
}
|
||||
|
||||
.v2__contact-line {
|
||||
margin: 0;
|
||||
font-family: var(--v2-mono);
|
||||
font-size: 0.875rem;
|
||||
color: var(--v2-term-dim);
|
||||
}
|
||||
|
||||
.v2__contact-line a {
|
||||
color: var(--v2-ink);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.v2__footer {
|
||||
grid-column: 1 / -1;
|
||||
padding-top: 2rem;
|
||||
font-family: var(--v2-mono);
|
||||
font-size: 0.75rem;
|
||||
color: var(--v2-term-dim);
|
||||
}
|
||||
|
||||
.v2__footer a {
|
||||
color: var(--v2-ink);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.v2__cursor {
|
||||
animation: none;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
/* Legal pages */
|
||||
|
||||
.v2__shell--legal {
|
||||
max-width: 48rem;
|
||||
}
|
||||
|
||||
.v2__header--legal {
|
||||
margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
|
||||
}
|
||||
|
||||
.v2__home-link {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.v2__home-link:hover {
|
||||
color: var(--v2-term);
|
||||
}
|
||||
|
||||
.v2__legal-nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem 1.25rem;
|
||||
font-family: var(--v2-mono);
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.v2__legal-nav a {
|
||||
color: var(--v2-term-dim);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.v2__legal-nav a:hover {
|
||||
color: var(--v2-ink);
|
||||
}
|
||||
|
||||
.v2__legal {
|
||||
padding-bottom: clamp(2rem, 5vw, 3rem);
|
||||
}
|
||||
|
||||
.v2__legal-title {
|
||||
margin: 0 0 0.5rem;
|
||||
font-family: var(--v2-display);
|
||||
font-size: clamp(2rem, 4vw, 2.75rem);
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.v2__legal-meta {
|
||||
margin: 0 0 2rem;
|
||||
font-family: var(--v2-mono);
|
||||
font-size: 0.8125rem;
|
||||
color: var(--v2-term-dim);
|
||||
}
|
||||
|
||||
.v2__legal-section {
|
||||
padding-block: 1.25rem;
|
||||
border-bottom: 1px solid var(--v2-rule);
|
||||
}
|
||||
|
||||
.v2__legal-section:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.v2__legal-section h2 {
|
||||
margin: 0 0 0.75rem;
|
||||
font-family: var(--v2-mono);
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.v2__legal-section p,
|
||||
.v2__legal-section li {
|
||||
margin: 0 0 0.75rem;
|
||||
font-size: 0.9375rem;
|
||||
color: var(--v2-muted);
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.v2__legal-section p:last-child,
|
||||
.v2__legal-section li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.v2__legal-section ul {
|
||||
margin: 0 0 0.75rem;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
.v2__legal-section a {
|
||||
color: var(--v2-ink);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.v2__legal-section strong {
|
||||
color: var(--v2-ink);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.v2__footer--legal {
|
||||
border-top: 1px solid var(--v2-rule);
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
.v2__footer a {
|
||||
color: var(--v2-ink);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.v2__footer a:hover {
|
||||
color: var(--v2-term);
|
||||
}
|
||||
Reference in New Issue
Block a user