Initial Commit

This commit is contained in:
2026-10-03 20:07:49 +02:00
commit a1346c98b1
427 changed files with 59320 additions and 0 deletions
+203
View File
@@ -0,0 +1,203 @@
<script setup lang="ts">
// Portiert aus Vue Bits (DavidHDev/vue-bits), vereinfacht auf color-Prop,
// ohne Cursor-Interaktion (Hover-Card ist zu klein dafür)
const props = withDefaults(defineProps<{ color?: string }>(), {
color: '#6366f1',
})
class Grad {
constructor(
public x: number,
public y: number,
public z: number,
) {}
dot2(x: number, y: number): number {
return this.x * x + this.y * y
}
}
class Noise {
grad3 = [
new Grad(1, 1, 0), new Grad(-1, 1, 0), new Grad(1, -1, 0), new Grad(-1, -1, 0),
new Grad(1, 0, 1), new Grad(-1, 0, 1), new Grad(1, 0, -1), new Grad(-1, 0, -1),
new Grad(0, 1, 1), new Grad(0, -1, 1), new Grad(0, 1, -1), new Grad(0, -1, -1),
]
p = [
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240,
21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88,
237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83,
111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216,
80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186,
3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58,
17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193,
238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128,
195, 78, 66, 215, 61, 156, 180,
]
perm = new Array(512)
gradP = new Array(512)
constructor(seed = 0) {
this.seed(seed)
}
seed(seed: number) {
if (seed > 0 && seed < 1) seed *= 65536
seed = Math.floor(seed)
if (seed < 256) seed |= seed << 8
for (let i = 0; i < 256; i++) {
const v = i & 1 ? this.p[i]! ^ (seed & 255) : this.p[i]! ^ ((seed >> 8) & 255)
this.perm[i] = this.perm[i + 256] = v
this.gradP[i] = this.gradP[i + 256] = this.grad3[v % 12]
}
}
fade(t: number): number {
return t * t * t * (t * (t * 6 - 15) + 10)
}
lerp(a: number, b: number, t: number): number {
return (1 - t) * a + t * b
}
perlin2(x: number, y: number): number {
let X = Math.floor(x)
let Y = Math.floor(y)
x -= X
y -= Y
X &= 255
Y &= 255
const n00 = this.gradP[X + this.perm[Y]!]!.dot2(x, y)
const n01 = this.gradP[X + this.perm[Y + 1]!]!.dot2(x, y - 1)
const n10 = this.gradP[X + 1 + this.perm[Y]!]!.dot2(x - 1, y)
const n11 = this.gradP[X + 1 + this.perm[Y + 1]!]!.dot2(x - 1, y - 1)
const u = this.fade(x)
return this.lerp(this.lerp(n00, n10, u), this.lerp(n01, n11, u), this.fade(y))
}
}
interface Point {
x: number
y: number
wave: { x: number, y: number }
}
const containerRef = useTemplateRef<HTMLDivElement>('containerRef')
const canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef')
let ctx: CanvasRenderingContext2D | null = null
let bounding = { width: 0, height: 0 }
let cornerRadius = 0
let noise: Noise | null = null
let lines: Point[][] = []
let frameId: number | null = null
const xGap = 10
const yGap = 26
function setSize() {
const container = containerRef.value
const canvas = canvasRef.value
if (!container || !canvas) return
const rect = container.getBoundingClientRect()
bounding = { width: rect.width, height: rect.height }
const dpr = window.devicePixelRatio || 1
canvas.width = rect.width * dpr
canvas.height = rect.height * dpr
ctx?.setTransform(dpr, 0, 0, dpr, 0, 0)
cornerRadius = parseFloat(getComputedStyle(container).borderTopLeftRadius) || 0
}
function setLines() {
const { width, height } = bounding
lines = []
const oWidth = width + 200
const oHeight = height + 30
const totalLines = Math.ceil(oWidth / xGap)
const totalPoints = Math.ceil(oHeight / yGap)
const xStart = (width - xGap * totalLines) / 2
const yStart = (height - yGap * totalPoints) / 2
for (let i = 0; i <= totalLines; i++) {
const pts: Point[] = []
for (let j = 0; j <= totalPoints; j++) {
pts.push({ x: xStart + xGap * i, y: yStart + yGap * j, wave: { x: 0, y: 0 } })
}
lines.push(pts)
}
}
function tick(t: number) {
if (!ctx || !noise) return
const { width, height } = bounding
for (const pts of lines) {
for (const p of pts) {
const move = noise.perlin2((p.x + t * 0.0125) * 0.002, (p.y + t * 0.005) * 0.0015) * 12
p.wave.x = Math.cos(move) * 32
p.wave.y = Math.sin(move) * 16
}
}
ctx.clearRect(0, 0, width, height)
// Rounded-Rect-Clip direkt im Canvas — zuverlässig auch wenn der
// Compositor den Layer nicht an CSS-Clips bindet (Waterfox)
ctx.save()
ctx.beginPath()
ctx.roundRect(0, 0, width, height, cornerRadius)
ctx.clip()
ctx.beginPath()
ctx.strokeStyle = props.color
for (const points of lines) {
let p1 = points[0]!
ctx.moveTo(p1.x + p1.wave.x, p1.y + p1.wave.y)
for (const p of points) {
ctx.lineTo(Math.round((p.x + p.wave.x) * 10) / 10, Math.round((p.y + p.wave.y) * 10) / 10)
}
p1 = points[points.length - 1]!
ctx.moveTo(p1.x + p1.wave.x, p1.y + p1.wave.y)
}
ctx.stroke()
ctx.restore()
frameId = requestAnimationFrame(tick)
}
let ro: ResizeObserver | null = null
onMounted(() => {
const canvas = canvasRef.value
const container = containerRef.value
if (!canvas || !container) return
ctx = canvas.getContext('2d')
noise = new Noise(Math.random())
setSize()
setLines()
frameId = requestAnimationFrame(tick)
ro = new ResizeObserver(() => {
setSize()
setLines()
})
ro.observe(container)
})
onUnmounted(() => {
ro?.disconnect()
if (frameId !== null) cancelAnimationFrame(frameId)
})
</script>
<template>
<div ref="containerRef" class="relative size-full overflow-hidden rounded-[inherit] opacity-50">
<canvas ref="canvasRef" class="block size-full rounded-[inherit]" />
</div>
</template>