diff --git a/.well-known/matrix/client b/.well-known/matrix/client new file mode 100644 index 0000000..de450b7 --- /dev/null +++ b/.well-known/matrix/client @@ -0,0 +1 @@ +{"m.homeserver":{"base_url":"https://matrix.rastus01.com"}} diff --git a/.well-known/matrix/server b/.well-known/matrix/server new file mode 100644 index 0000000..d9f036a --- /dev/null +++ b/.well-known/matrix/server @@ -0,0 +1 @@ +{"m.server":"matrix.rastus01.com:443"} diff --git a/index.html b/index.html new file mode 100644 index 0000000..a0417e5 --- /dev/null +++ b/index.html @@ -0,0 +1,71 @@ + + + + + + + rastus01.com + + + +
+
+ + + +

Personal Node

+

rastus01.com

+

Home Lab · Matrix · Selfhosting

+

+ Dies ist meine persönliche Selfhosting-Domain. Einige Dienste laufen + hier privat oder experimentell. +

+
+ +
+
+
+ +

Matrix Homeserver

+
+

Matrix läuft unter matrix.rastus01.com

+ + Matrix öffnen + +
+ +
+
+ +

Git / Projekte

+
+

Eigene Projekte und Experimente folgen später.

+
+ +
+
+ +

Status

+
+

Website im Aufbau.

+
+
+ + +
+ + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..65cd58f --- /dev/null +++ b/script.js @@ -0,0 +1,90 @@ +const yearElement = document.querySelector("#current-year"); +const canvas = document.querySelector("#network-canvas"); + +if (yearElement) { + yearElement.textContent = new Date().getFullYear().toString(); +} + +if (canvas) { + const context = canvas.getContext("2d"); + const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); + let animationFrame = null; + const nodes = Array.from({ length: 42 }, (_, index) => ({ + x: (index * 97) % 100, + y: (index * 53) % 100, + speed: 0.08 + (index % 5) * 0.015, + phase: index * 0.7, + })); + + function resizeCanvas() { + const ratio = Math.min(window.devicePixelRatio || 1, 2); + const { width, height } = canvas.getBoundingClientRect(); + + canvas.width = Math.max(1, Math.floor(width * ratio)); + canvas.height = Math.max(1, Math.floor(height * ratio)); + context.setTransform(ratio, 0, 0, ratio, 0, 0); + } + + function drawNetwork(time = 0) { + const { width, height } = canvas.getBoundingClientRect(); + const points = nodes.map((node) => { + const drift = prefersReducedMotion.matches ? 0 : time * 0.001 * node.speed; + + return { + x: ((node.x + Math.sin(node.phase + drift) * 5 + 100) % 100) * width / 100, + y: ((node.y + Math.cos(node.phase + drift) * 5 + 100) % 100) * height / 100, + }; + }); + + context.clearRect(0, 0, width, height); + context.fillStyle = "#070a0f"; + context.fillRect(0, 0, width, height); + + context.lineWidth = 1; + for (let index = 0; index < points.length; index += 1) { + for (let nextIndex = index + 1; nextIndex < points.length; nextIndex += 1) { + const first = points[index]; + const second = points[nextIndex]; + const distance = Math.hypot(first.x - second.x, first.y - second.y); + + if (distance < 170) { + const alpha = (1 - distance / 170) * 0.22; + context.strokeStyle = `rgba(103, 232, 249, ${alpha})`; + context.beginPath(); + context.moveTo(first.x, first.y); + context.lineTo(second.x, second.y); + context.stroke(); + } + } + } + + for (const point of points) { + context.fillStyle = "rgba(124, 247, 182, 0.88)"; + context.beginPath(); + context.arc(point.x, point.y, 2.2, 0, Math.PI * 2); + context.fill(); + } + + if (!prefersReducedMotion.matches) { + animationFrame = window.requestAnimationFrame(drawNetwork); + } + } + + resizeCanvas(); + drawNetwork(); + window.addEventListener("resize", () => { + resizeCanvas(); + + if (prefersReducedMotion.matches) { + drawNetwork(); + } + }); + prefersReducedMotion.addEventListener("change", () => { + if (animationFrame) { + window.cancelAnimationFrame(animationFrame); + animationFrame = null; + } + + drawNetwork(); + }); +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..863afdb --- /dev/null +++ b/style.css @@ -0,0 +1,328 @@ +:root { + color-scheme: dark; + --bg: #070a0f; + --bg-soft: #0d141d; + --panel: rgba(16, 24, 34, 0.78); + --panel-strong: rgba(22, 33, 46, 0.92); + --text: #ecf4ff; + --muted: #9badc0; + --line: rgba(142, 245, 255, 0.18); + --cyan: #67e8f9; + --green: #7cf7b6; + --pink: #ff5ea8; + --amber: #ffd166; + --shadow: 0 24px 80px rgba(0, 0, 0, 0.38); +} + +* { + box-sizing: border-box; +} + +html { + min-height: 100%; + background: var(--bg); +} + +body { + min-height: 100vh; + margin: 0; + color: var(--text); + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, + "Segoe UI", sans-serif; + line-height: 1.6; + background: + linear-gradient(145deg, #070a0f 0%, #0b1018 44%, #101018 100%); +} + +body::before { + position: fixed; + inset: 0; + z-index: -1; + pointer-events: none; + content: ""; + background-image: + linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.035) 1px, transparent 1px); + background-size: 48px 48px; + mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.9), transparent 82%); +} + +a { + color: inherit; +} + +.page-shell { + min-height: 100vh; + padding: 0 0 28px; + display: flex; + flex-direction: column; + gap: 28px; +} + +.hero { + position: relative; + overflow: hidden; + min-height: 66svh; + padding: clamp(32px, 7vw, 76px) max(20px, calc((100vw - 1120px) / 2)); + display: flex; + flex-direction: column; + justify-content: flex-end; + background: linear-gradient(180deg, rgba(7, 10, 15, 0.1), rgba(7, 10, 15, 0.82)); +} + +.hero::before, +.hero::after { + position: absolute; + pointer-events: none; + content: ""; +} + +.hero::before { + inset: 0; + background: + repeating-linear-gradient( + 0deg, + rgba(255, 255, 255, 0.045) 0, + rgba(255, 255, 255, 0.045) 1px, + transparent 1px, + transparent 14px + ); + opacity: 0.42; +} + +.hero::after { + right: -12%; + bottom: -28%; + width: 520px; + aspect-ratio: 1; + border: 1px solid rgba(103, 232, 249, 0.22); + border-radius: 50%; + background: + linear-gradient(90deg, transparent 49%, rgba(103, 232, 249, 0.24) 50%, transparent 51%), + linear-gradient(0deg, transparent 49%, rgba(103, 232, 249, 0.24) 50%, transparent 51%); + opacity: 0.34; +} + +.network-canvas { + position: absolute; + inset: 0; + width: 100%; + height: 100%; +} + +.signal-grid { + position: absolute; + top: 24px; + right: 24px; + display: grid; + grid-template-columns: repeat(2, 12px); + gap: 8px; +} + +.signal-grid span { + width: 12px; + height: 12px; + border: 1px solid rgba(103, 232, 249, 0.5); + background: rgba(103, 232, 249, 0.12); +} + +.eyebrow, +.subtitle, +.intro, +h1 { + position: relative; + z-index: 2; +} + +.eyebrow { + margin: 0 0 14px; + color: var(--green); + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0; + text-transform: uppercase; +} + +h1 { + margin: 0; + font-size: clamp(3rem, 8vw, 7rem); + line-height: 0.92; + letter-spacing: 0; +} + +.subtitle { + margin: 18px 0 0; + color: var(--cyan); + font-size: clamp(1rem, 2vw, 1.35rem); + font-weight: 650; +} + +.intro { + max-width: 680px; + margin: 22px 0 0; + color: var(--muted); + font-size: 1.03rem; +} + +.service-grid { + width: min(1120px, calc(100% - 32px)); + margin: 0 auto; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 18px; +} + +.service-card { + min-height: 220px; + padding: 24px; + display: flex; + flex-direction: column; + align-items: flex-start; + border: 1px solid rgba(148, 163, 184, 0.18); + border-radius: 8px; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.04), transparent), + var(--panel-strong); + box-shadow: 0 18px 48px rgba(0, 0, 0, 0.22); +} + +.card-header { + width: 100%; + display: flex; + align-items: center; + gap: 12px; +} + +.card-header h2 { + margin: 0; + font-size: 1.08rem; + line-height: 1.25; + letter-spacing: 0; +} + +.status-dot { + width: 10px; + height: 10px; + flex: 0 0 auto; + border-radius: 50%; + box-shadow: 0 0 22px currentColor; +} + +.status-live { + color: var(--green); + background: var(--green); +} + +.status-idle { + color: var(--cyan); + background: var(--cyan); +} + +.status-build { + color: var(--amber); + background: var(--amber); +} + +.service-card p { + margin: 18px 0 0; + color: var(--muted); +} + +.button-link { + margin-top: auto; + min-height: 44px; + padding: 10px 16px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid rgba(103, 232, 249, 0.45); + border-radius: 6px; + color: var(--text); + font-weight: 700; + text-decoration: none; + background: rgba(103, 232, 249, 0.09); + transition: + border-color 180ms ease, + background 180ms ease, + transform 180ms ease; +} + +.button-link:hover, +.button-link:focus-visible { + border-color: var(--cyan); + background: rgba(103, 232, 249, 0.17); + transform: translateY(-1px); +} + +.button-link:focus-visible { + outline: 3px solid rgba(103, 232, 249, 0.3); + outline-offset: 3px; +} + +.site-footer { + width: min(1120px, calc(100% - 32px)); + margin: 0 auto; + padding: 2px 4px 0; + display: flex; + justify-content: space-between; + gap: 16px; + color: rgba(236, 244, 255, 0.58); + font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; + font-size: 0.82rem; +} + +@media (max-width: 820px) { + .page-shell { + padding-top: 0; + } + + .hero { + min-height: 62svh; + padding: 32px 24px; + } + + .service-grid { + width: min(100% - 24px, 680px); + grid-template-columns: 1fr; + } + + .site-footer { + width: min(100% - 24px, 680px); + } + + .service-card { + min-height: 180px; + } +} + +@media (max-width: 520px) { + .page-shell { + gap: 18px; + } + + .hero { + min-height: 58svh; + } + + .signal-grid { + top: 18px; + right: 18px; + } + + .site-footer { + flex-direction: column; + gap: 4px; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +}