update texts; remove redundant text; close all collapsibles by default; comment out carousel for now

This commit is contained in:
2025-06-13 22:24:14 +02:00
parent 4e99bb16bc
commit 9cc970ea0b
6 changed files with 313 additions and 349 deletions

22
.pugrc
View File

@@ -8,19 +8,19 @@
"keywords": "software engineering, software development, software architect, platform architect, cloud architecture, web development, leadership, enterprise solutions, digital transformation, c-level consultant, stuttgart, open source, 20 years experience, Michael Czechowski",
"lang": "en",
"language": "English",
"ogDescription": "Expert in Web Technologies & Enterprise Software Architecture ...",
"ogDescription": "Expert in Web Technologies & dedicated Lecturer in Software Engineering ...",
"ogImage": "./src/assets/og-image.png",
"ogTitle": "Michael W. Czechowski - Expert in Web Technologies & Enterprise Software Architecture",
"ogTitle": "Michael W. Czechowski - Expert in Web Technologies & dedicated Lecturer in Software Engineering",
"ogType": "website",
"ogUrl": "https://dailysh.it/",
"rating": "general",
"revisitAfter": "7 days",
"robots": "index, follow",
"title": "Michael W. Czechowski - Expert in Web Technologies & Enterprise Software Architecture",
"title": "Michael W. Czechowski - Expert in Web Technologies & dedicated Lecturer in Software Engineering",
"twitterCard": "summary_large_image",
"twitterDescription": "Expert in Web Technologies & Enterprise Software Architecture ...",
"twitterDescription": "Expert in Web Technologies & dedicated Lecturer in Software Engineering ...",
"twitterImage": "./src/assets/twitter-image.png",
"twitterTitle": "Michael W. Czechowski - Expert in Web Technologies & Enterprise Software Architecture",
"twitterTitle": "Michael W. Czechowski - Expert in Web Technologies & dedicated Lecturer in Software Engineering",
"twitterUrl": "https://dailysh.it/",
"umamiId": "9e83ef75-cbfb-49cf-ae4f-a7485ca46ba8",
"umamiSrc": "https://dailysh.it/u/script.js",
@@ -28,7 +28,7 @@
},
"footer": {
"title": "Let's Connect",
"content": "With over two decades of experience at the forefront of technology innovation, I bring a unique blend of technical expertise and strategic vision to every project. My approach goes beyond just coding I architect comprehensive solutions that align with your business objectives and drive sustainable growth. Whether you're looking to modernize legacy systems, implement cutting-edge cloud architectures, or develop scalable enterprise applications, I offer the insights and expertise to guide your digital transformation journey. As a trusted advisor to C-level executives, I understand the critical balance between innovation and ROI, ensuring that every technological investment translates into tangible business value. Let's collaborate to turn your technological challenges into competitive advantages, positioning your organization at the forefront of your industry.",
"content": "With over two decades of experience at the forefront of technology innovation, I bring a unique blend of technical expertise and strategic vision to every project. My approach goes beyond just coding I architect comprehensive solutions that align with your business objectives and drive sustainable growth. Whether you're looking to modernize legacy systems, implement cutting-edge cloud architectures, or develop scalable enterprise applications, I offer the insights and expertise to guide your digital transformation journey.",
"githubUrl": "https://github.com/nextlevelshit",
"linkedinUrl": "https://www.linkedin.com/in/michael-werner-czechowski/",
"xingUrl": "https://www.xing.com/profile/Michael_Czechowski",
@@ -47,13 +47,13 @@
"logoSvg": "./src/assets/nls.svg",
"logoSvgInverted": "./src/assets/nls_inverted.svg",
"emojiSvg": "./src/assets/waving-hand.svg",
"jobTitle": ["Expert in Web Technologies", "Enterprise Software Architecture"]
"jobTitle": ["Expert in Web Technologies", "dedicated Lecturer in Software Engineering"]
},
"intro": {
"heading": "After $1"
},
"professional": {
"title": "Make it run<br>Make it right<br>Make it fast",
"title": "Make it <i>run</i><br>Make it <i>right</i><br>Make it <i>fast¹</i> ",
"principlesIntro": "Throughout my career, I've been guided by the philosophies and innovations of free software and industry pioneers. These principles form the foundation of my approach to software engineering and didactics:",
"principlesPeople": [
{
@@ -123,8 +123,8 @@
},
"academia": {
"courseInstitutions": {
"dhbw": "Duale Hochschule Baden-Württemberg (DHBW)",
"lfh": "Leibniz Fachhochschule (LFH)"
"dhbw": "Lectures at Duale Hochschule Baden-Württemberg (DHBW)",
"lfh": "Lectures at Leibniz Fachhochschule (LFH)"
},
"courses": {
"dhbw": [
@@ -151,7 +151,7 @@
]
},
"expertise": {
"title": "Software Develop&shy;ment <wbr/>and set&shy;ting <wbr/>the right Priori&shy;ties",
"title": "Software Engineer&shy;ing <wbr/>and set&shy;ting <wbr/>the right Priori&shy;ties",
"intro": [
"With over two decades at the forefront of software development and architecture, I've navigated the ever-evolving landscape of technology, consistently delivering innovative solutions across diverse sectors.",
"My expertise spans a comprehensive range of technologies and methodologies, enabling me to provide both strategic guidance and hands-on leadership to organizations seeking to innovate and maintain a competitive edge. From developing robust, scalable software architectures to implementing agile practices and leveraging the latest in cloud and DevOps technologies, I bring a wealth of knowledge and practical experience to every project. My approach is rooted in a deep understanding of both current trends and time-tested principles, ensuring solutions that are not only cutting-edge but also sustainable and aligned with long-term business objectives."

View File

@@ -8,18 +8,18 @@ html.scroll-smooth(lang=lang)
article(itemscope, itemtype="http://schema.org/Person")
include src/components/Landingpage
//- Portfolio carousel
+Carousel(
[
{type: "image", src: "/src/assets/screenshot-codecrispies.png", alt: "Code Crispies", caption: "Coding Learning Platform"},
{type: "image", src: "/src/assets/project1.png", alt: "Project Alpha", caption: "AI-powered marketplace"},
{type: "image", src: "/src/assets/project1.png", alt: "Project Alpha", caption: "AI-powered marketplace"},
{type: "image", src: "/src/assets/project1.png", alt: "Project Alpha", caption: "AI-powered marketplace"},
{type: "image", src: "/src/assets/project1.png", alt: "Project Alpha", caption: "AI-powered marketplace"},
{type: "image", src: "/src/assets/project2.png", alt: "Project Beta", caption: "Real-time analytics dashboard"},
{type: "image", src: "/src/assets/project3.png", alt: "Project Gamma", caption: "Accessibility-first design system"},
],
{id: "portfolio", category: "portfolio", color: "orange"},
)
//+Carousel(
// [
// {type: "image", src: "/src/assets/screenshot-codecrispies.png", alt: "Code Crispies", caption: "Coding Learning Platform"},
// {type: "image", src: "/src/assets/project1.png", alt: "Project Alpha", caption: "AI-powered marketplace"},
// {type: "image", src: "/src/assets/project1.png", alt: "Project Alpha", caption: "AI-powered marketplace"},
// {type: "image", src: "/src/assets/project1.png", alt: "Project Alpha", caption: "AI-powered marketplace"},
// {type: "image", src: "/src/assets/project1.png", alt: "Project Alpha", caption: "AI-powered marketplace"},
// {type: "image", src: "/src/assets/project2.png", alt: "Project Beta", caption: "Real-time analytics dashboard"},
// {type: "image", src: "/src/assets/project3.png", alt: "Project Gamma", caption: "Accessibility-first design system"},
// ],
// {id: "portfolio", category: "portfolio", color: "orange"},
// )
include src/components/Academia
include src/components/Professional
include src/components/Footer

BIN
src/assets/project3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -15,7 +15,7 @@ section#academia
// region Expertise
.grid.grid-cols-1.gap-6.mb-8(class="md:grid-cols-1")
+Collapsable("academia", academia.expertise.frontendTechnologies, true)
+Collapsable("academia", academia.expertise.frontendTechnologies)
+Collapsable("academia", academia.expertise.devopsAndCloud)
+Collapsable("academia", academia.expertise.backendTechnologies)
+Collapsable("academia", academia.expertise.databaseAndData)

View File

@@ -6,7 +6,7 @@ mixin Collapsable(name, data, open)
details.mb-4(open=isExpanded, name=name)
summary(
class=`rounded-sm transition mb-2 cursor-pointer text-md font-semibold mb-2 cursor-pointer sm:text-lg text-nls-${color} dark:text-nls-${color} focus:outline-none focus:z-10 focus:ring-4 focus:ring-nls-${color} focus:bg-nls-${color} focus:text-black focus:no-underline`,
class=`rounded-sm transition mb-2 cursor-pointer text-md font-semibold mb-2 cursor-pointer sm:text-lg text-nls-${color} dark:text-nls-${color} focus:outline-none focus:z-10 focus:ring-4 focus:ring-nls-${color} focus:bg-nls-${color} focus:text-black dark:focus:text-white focus:no-underline`,
onclick=`umami.track('collapsable clicked', { category: '${category}', visitDuration: getVisitDuration() })`
)= data.summary
ul.list-disc.list-inside.text-slate-600(class="dark:text-stone-300")

View File

@@ -1,4 +1,4 @@
header.bg-white.text-nls-black(class="dark:text-white dark:bg-nls-black")
header.bg-white.text-nls-black.relative(class="dark:text-white dark:bg-nls-black")
.teaser.p-8.flex.flex-col.items-center.justify-center(class="sm:p-20")
.max-w-3xl.mb-8.relative(class="w-4/5 min-h-[90vh]")
.peer.absolute.bottom-0.left-0.right-0.z-40.text-center.max-w-3xl.center.py-8
@@ -11,348 +11,312 @@ header.bg-white.text-nls-black(class="dark:text-white dark:bg-nls-black")
|
| &amp; #{landingpage.jobTitle[1]}
canvas#aurora-canvas.absolute.z-0.left-0.right-0.mx-auto.transition(class="h-[54vh] w-full")
canvas#aurora-canvas.absolute.z-0.left-0.right-0.bottom-0.top-0.mx-auto.transition(class="h-[100vh] w-full")
script(src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js")
script.
class QuantumAurora {
constructor(canvas) {
class GuillocheCurtain {
constructor(canvas, options = {}) {
this.canvas = canvas;
this.gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
this.time = 0;
this.animationId = null;
this.mouseX = 0.5;
this.mouseY = 0.5;
if (!this.gl) {
console.warn('WebGL not supported');
return;
}
this.initShaders();
this.initBuffers();
this.setupInteraction();
this.resize();
this.render();
}
initShaders() {
const vertexShaderSource = `
attribute vec2 a_position;
varying vec2 v_uv;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_uv = a_position * 0.5 + 0.5;
}
`;
const fragmentShaderSource = `
precision highp float;
uniform float u_time;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
varying vec2 v_uv;
// Noise functions for organic aurora movement
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
float fbm(vec2 p) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for(int i = 0; i < 6; i++) {
value += amplitude * noise(p * frequency);
amplitude *= 0.5;
frequency *= 2.0;
}
return value;
}
// Magnetic field simulation
vec2 magneticField(vec2 pos, float time) {
vec2 field = vec2(0.0);
// Earth's magnetic field approximation
float lat = (pos.y - 0.5) * 3.14159;
float lon = (pos.x - 0.5) * 6.28318;
// Magnetic dipole field with solar wind interaction
float solarWind = sin(time * 0.1 + pos.x * 2.0) * 0.3 + 0.7;
float magneticStrength = cos(lat) * solarWind;
field.x = sin(lat * 2.0 + time * 0.2) * magneticStrength;
field.y = cos(lat * 3.0 + time * 0.15) * magneticStrength;
return field * 0.5;
}
// Particle trajectory simulation
float particleFlow(vec2 uv, float time) {
vec2 pos = uv;
float intensity = 0.0;
// Simulate charged particle streams
for(int i = 0; i < 8; i++) {
float fi = float(i);
vec2 offset = vec2(sin(fi * 0.5), cos(fi * 0.7)) * 0.1;
vec2 particlePos = pos + offset;
// Particle follows magnetic field lines
vec2 field = magneticField(particlePos, time + fi);
particlePos += field * 0.3;
// Altitude-dependent intensity (atmosphere interaction)
float altitude = 1.0 - abs(particlePos.y - 0.5) * 2.0;
altitude = smoothstep(0.2, 0.8, altitude);
// Particle stream noise
float streamNoise = fbm(particlePos * 8.0 + vec2(time * 0.3, 0.0));
intensity += altitude * streamNoise * 0.125;
}
return intensity;
}
// Guilloche pattern generation
float guilloche(vec2 uv, float time) {
vec2 pos = uv * 20.0;
// Multiple rotating circles creating interference patterns
float g = 0.0;
// Primary guilloche rings
for(int i = 0; i < 4; i++) {
float fi = float(i);
float radius = 3.0 + fi * 2.0;
float speed = 0.5 + fi * 0.2;
float phase = fi * 1.5708; // π/2 phase offset
vec2 center = vec2(
sin(time * speed + phase) * 2.0,
cos(time * speed * 0.7 + phase) * 1.5
);
float dist = length(pos - center);
g += sin(dist * radius - time * (speed * 10.0 + 5.0)) / (radius * 0.5);
}
// Secondary interference pattern
float interference = sin(pos.x * 3.0 + time) * sin(pos.y * 2.0 + time * 1.3);
g += interference * 0.3;
return g;
}
// Aurora curtain effect
vec3 auroraColor(vec2 uv, float time) {
// Particle flow intensity
float particles = particleFlow(uv, time);
// Guilloche modulation
float guillochePattern = guilloche(uv, time * 0.5);
float guillocheModulation = sin(guillochePattern * 0.5) * 0.5 + 0.5;
// Combine particle physics with guilloche aesthetics
float intensity = particles * (0.7 + guillocheModulation * 0.6);
// Atmospheric layers - different altitudes emit different colors
float altitude = uv.y;
// Oxygen emissions (green/red) - 557.7nm, 630.0nm
vec3 oxygen_green = vec3(0.3, 0.9, 0.2);
vec3 oxygen_red = vec3(0.8, 0.2, 0.1);
// Nitrogen emissions (blue/purple) - 427.8nm, 391.4nm
vec3 nitrogen_blue = vec3(0.2, 0.4, 0.9);
vec3 nitrogen_purple = vec3(0.6, 0.2, 0.8);
// Helium (rare, yellow) - 587.6nm
vec3 helium_yellow = vec3(0.9, 0.8, 0.2);
// Altitude-based color mixing (realistic atmospheric chemistry)
vec3 color = vec3(0.0);
// High altitude (200-400km) - mostly oxygen red
float highAlt = smoothstep(0.7, 0.9, altitude);
color += oxygen_red * highAlt;
// Mid altitude (100-200km) - oxygen green dominates
float midAlt = smoothstep(0.3, 0.7, altitude) * smoothstep(0.7, 0.5, altitude);
color += oxygen_green * midAlt * 2.0;
// Lower altitude (80-100km) - nitrogen blue/purple
float lowAlt = smoothstep(0.0, 0.4, altitude) * smoothstep(0.6, 0.3, altitude);
color += mix(nitrogen_blue, nitrogen_purple, sin(time * 0.3 + uv.x * 5.0) * 0.5 + 0.5) * lowAlt;
// Rare helium emissions (very sparse)
float heliumChance = noise(uv * 100.0 + time * 0.1);
if(heliumChance > 0.95) {
color += helium_yellow * 0.3;
}
// Solar wind interaction - color temperature shift
float solarActivity = sin(time * 0.05) * 0.3 + 0.7;
color *= solarActivity;
// Magnetic field strength affects brightness
vec2 magneticF = magneticField(uv, time);
float magneticIntensity = length(magneticF);
return color * intensity * (0.5 + magneticIntensity);
}
// Atmospheric scattering simulation
vec3 atmosphericScattering(vec3 auroraColor, vec2 uv) {
float atmosphereDensity = exp(-abs(uv.y - 0.5) * 8.0);
// Rayleigh scattering (blue light scattered more)
vec3 rayleigh = vec3(0.1, 0.2, 0.6) * atmosphereDensity * 0.1;
// Mie scattering (dust/ice crystals)
vec3 mie = vec3(0.8, 0.8, 0.9) * atmosphereDensity * 0.05;
return auroraColor + rayleigh + mie;
}
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
// Mouse interaction - simulates solar wind direction
vec2 solarWind = (u_mouse - 0.5) * 0.5;
uv += solarWind * 0.1;
// Generate aurora
vec3 aurora = auroraColor(uv, u_time);
// Apply atmospheric effects
aurora = atmosphericScattering(aurora, uv);
// Subtle animation shimmer
float shimmer = sin(u_time * 2.0 + uv.x * 10.0 + uv.y * 15.0) * 0.05 + 0.95;
aurora *= shimmer;
// Transparency based on intensity
float alpha = length(aurora) * 0.8;
alpha = smoothstep(0.0, 0.3, alpha);
gl_FragColor = vec4(aurora, alpha);
}
`;
this.program = this.createProgram(vertexShaderSource, fragmentShaderSource);
this.gl.useProgram(this.program);
this.u_time = this.gl.getUniformLocation(this.program, 'u_time');
this.u_resolution = this.gl.getUniformLocation(this.program, 'u_resolution');
this.u_mouse = this.gl.getUniformLocation(this.program, 'u_mouse');
}
createProgram(vertexSource, fragmentSource) {
const vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, fragmentSource);
const program = this.gl.createProgram();
this.gl.attachShader(program, vertexShader);
this.gl.attachShader(program, fragmentShader);
this.gl.linkProgram(program);
if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
console.error('Program link error:', this.gl.getProgramInfoLog(program));
return null;
}
return program;
}
createShader(type, source) {
const shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
console.error('Shader compile error:', this.gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
initBuffers() {
const vertices = new Float32Array([-1,-1, 1,-1, -1,1, 1,1]);
this.buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
const a_position = this.gl.getAttribLocation(this.program, 'a_position');
this.gl.enableVertexAttribArray(a_position);
this.gl.vertexAttribPointer(a_position, 2, this.gl.FLOAT, false, 0, 0);
}
setupInteraction() {
this.canvas.addEventListener('mousemove', (e) => {
const rect = this.canvas.getBoundingClientRect();
this.mouseX = e.clientX / rect.width;
this.mouseY = 1.0 - (e.clientY / rect.height);
this.options = {
// Visual parameters
spread: 0.25, // Main spread factor (was 0.08)
lineWidth: 0.8, // Line thickness
opacity: 0.6, // Base opacity
splineCount: 40, // Lines per group
groupCount: 6, // Number of spline groups
// Animation parameters
animationSpeed: 0.005, // Global time multiplier
nodeCount: 7, // Attractor nodes
nodeSpeed: {min: 0.3, max: 0.8},
// Color parameters
hueBase: 0.55, // Base hue (cyan-ish)
hueVariation: 0.1, // Color spread
saturation: {min: 0.3, max: 0.7},
lightness: {min: 0.25, max: 0.65},
// Spline parameters
segments: 100, // Resolution per spline
offset: 5, // General offset between splines
startOffset: 0.0, // Y-offset at spline start
endOffset: 0.0, // Y-offset at spline end
offsetTransition: 'smooth', // 'linear', 'smooth', 'sine'
canvasExtension: 1.0, // How far beyond canvas borders (-1.0 means 50% extra on each side)
// Wave complexity
waveFrequencies: [4, 8, 16], // Multiple harmonics
waveAmplitudes: [0.4, 0.25, 0.15], // Corresponding amplitudes
...options
};
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(-2, 2, 1.5, -1.5, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer({
canvas: canvas,
alpha: true,
antialias: true
});
this.time = 0;
this.splineGroups = [];
this.nodes = [];
this.init();
this.createNodes();
this.createSplines();
this.animate();
}
init() {
this.renderer.setSize(this.canvas.clientWidth, this.canvas.clientHeight);
this.renderer.setClearColor(0x000000, 0);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.camera.position.z = 1;
}
createNodes() {
for (let i = 0; i < this.options.nodeCount; i++) {
this.nodes.push({
x: (Math.random() - 0.5) * 3,
y: (Math.random() - 0.5) * 2,
phase: Math.random() * Math.PI * 2,
radius: 0.2 + Math.random() * 0.3,
speed: this.options.nodeSpeed.min +
Math.random() * (this.options.nodeSpeed.max - this.options.nodeSpeed.min)
});
}
}
// Calculate offset transition from start to end
getOffsetTransition(t) {
const {startOffset, endOffset, offsetTransition} = this.options;
switch (offsetTransition) {
case 'linear':
return startOffset + (endOffset - startOffset) * t;
case 'sine':
return startOffset + (endOffset - startOffset) * Math.sin(t * Math.PI * 0.5);
case 'smooth':
default:
// Smooth hermite interpolation
const t2 = t * t;
const t3 = t2 * t;
return startOffset + (endOffset - startOffset) * (3 * t2 - 2 * t3);
}
}
createSplines() {
for (let groupIndex = 0; groupIndex < this.options.groupCount; groupIndex++) {
const group = new THREE.Group();
const splines = [];
for (let i = 0; i < this.options.splineCount; i++) {
const t = i / (this.options.splineCount - 1);
// Create distribution that's denser in the center
const normalizedOffset = (t - 0.2) * this.options.offset; // -1 to 1
const offset = normalizedOffset * this.options.spread;
const spline = this.createSingleSpline(offset, groupIndex, i);
splines.push({line: spline, offset});
group.add(spline);
}
this.splineGroups.push({
group,
splines,
baseOffset: groupIndex * 0.6,
phase: groupIndex * 0.8
});
this.scene.add(group);
}
}
createSingleSpline(offset, groupIndex, splineIndex) {
const points = this.generateSplinePoints(offset, groupIndex, splineIndex, 0);
const geometry = new THREE.BufferGeometry().setFromPoints(points);
// Enhanced color calculation
const normalizedGroup = groupIndex / (this.options.groupCount - 1);
const normalizedSpline = splineIndex / (this.options.splineCount - 1);
const hue = this.options.hueBase +
normalizedGroup * this.options.hueVariation +
Math.sin(normalizedSpline * Math.PI * 2) * 0.05;
const saturation = this.options.saturation.min +
(this.options.saturation.max - this.options.saturation.min) *
(1 - Math.abs(offset) / this.options.spread);
const lightness = this.options.lightness.min +
(this.options.lightness.max - this.options.lightness.min) *
(1 - Math.abs(offset) / this.options.spread * 0.6);
const material = new THREE.LineBasicMaterial({
color: new THREE.Color().setHSL(hue, saturation, lightness),
transparent: true,
opacity: this.options.opacity * (1 - Math.abs(offset) / this.options.spread * 0.3),
linewidth: this.options.lineWidth
});
return new THREE.Line(geometry, material);
}
generateSplinePoints(offset, groupIndex, splineIndex, timeOffset = 0) {
const points = [];
const {segments} = this.options;
for (let i = 0; i <= segments; i++) {
const t = i / segments;
const baseX = -(2 + this.options.canvasExtension) + t * (4 + 2 * this.options.canvasExtension);
let x = baseX;
let y = 0;
// Apply node influences with multiple wave frequencies
this.nodes.forEach((node, nodeIndex) => {
const distance = Math.abs(baseX - node.x);
const influence = Math.exp(-distance * distance * 2);
const animatedPhase = node.phase +
(this.time + timeOffset) * node.speed +
groupIndex * 0.2 +
splineIndex * 0.01;
const waveAmplitude = node.radius * influence;
// Multiple harmonics for complexity
this.options.waveFrequencies.forEach((freq, freqIndex) => {
const amplitude = this.options.waveAmplitudes[freqIndex] || 0.1;
const phaseMultiplier = 1 + freqIndex * 0.3;
y += Math.sin(t * Math.PI * freq + animatedPhase * phaseMultiplier) *
waveAmplitude * amplitude;
});
// Horizontal modulation for more organic feel
x += Math.cos(t * Math.PI * 6 + animatedPhase * 0.9) * waveAmplitude * 0.1;
});
// Apply spread offset with transition
const transitionOffset = this.getOffsetTransition(t);
y += offset * (1 + Math.sin(t * Math.PI * 2 + (this.time + timeOffset) * 0.5) * 0.3) +
transitionOffset;
points.push(new THREE.Vector3(x, y, 0));
}
return points;
}
updateSplineGeometry(splineData, groupIndex, splineIndex) {
const {line, offset} = splineData;
const points = this.generateSplinePoints(offset, groupIndex, splineIndex);
line.geometry.setFromPoints(points);
}
animate() {
this.time += this.options.animationSpeed;
// Animate nodes with boundary constraints
this.nodes.forEach((node, index) => {
node.x += Math.sin(this.time * 0.3 + index) * 0.002;
node.y += Math.cos(this.time * 0.4 + index * 1.3) * 0.002;
// Keep nodes within reasonable bounds
node.x = Math.max(-1.5, Math.min(1.5, node.x));
node.y = Math.max(-1, Math.min(1, node.y));
});
// Update spline groups
this.splineGroups.forEach((splineGroup, groupIndex) => {
const {group, splines, phase} = splineGroup;
// Subtle group transformations
group.position.y = Math.sin(this.time * 0.2 + phase) * 0.08;
group.position.x = Math.cos(this.time * 0.15 + phase) * 0.04;
group.rotation.z = Math.sin(this.time * 0.1 + phase) * 0.015;
// Update individual splines
splines.forEach((splineData, splineIndex) => {
this.updateSplineGeometry(splineData, groupIndex, splineIndex);
});
});
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(() => this.animate());
}
// Public methods for runtime configuration
updateOptions(newOptions) {
Object.assign(this.options, newOptions);
// Trigger rebuild if necessary
this.rebuild();
}
rebuild() {
// Clean up existing splines
this.splineGroups.forEach(({group}) => {
group.children.forEach(child => {
child.geometry.dispose();
child.material.dispose();
});
this.scene.remove(group);
});
this.splineGroups = [];
this.createSplines();
}
resize() {
const rect = this.canvas.getBoundingClientRect();
//this.canvas.width = rect.width * window.devicePixelRatio;
//this.canvas.height = rect.height * window.devicePixelRatio;
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
this.gl.uniform2f(this.u_resolution, this.canvas.width, this.canvas.height);
}
const width = this.canvas.clientWidth;
const height = this.canvas.clientHeight;
render() {
if (!this.gl) return;
this.renderer.setSize(width, height);
this.time += 0.016;
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
this.gl.clearColor(0, 0, 0, 0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.gl.uniform1f(this.u_time, this.time);
this.gl.uniform2f(this.u_mouse, this.mouseX, this.mouseY);
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
this.animationId = requestAnimationFrame(() => this.render());
const aspect = width / height;
this.camera.left = -2 * aspect;
this.camera.right = 2 * aspect;
this.camera.top = 1.5;
this.camera.bottom = -1.5;
this.camera.updateProjectionMatrix();
}
destroy() {
if (this.animationId) cancelAnimationFrame(this.animationId);
this.splineGroups.forEach(({group}) => {
group.children.forEach(child => {
child.geometry.dispose();
child.material.dispose();
});
});
this.renderer.dispose();
}
}
// Usage example with custom options
document.addEventListener("DOMContentLoaded", () => {
const canvas = document.getElementById('aurora-canvas');
if (canvas) {
const aurora = new QuantumAurora(canvas);
let resizeTimeout;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => aurora.resize(), 100);
const curtain = new GuillocheCurtain(canvas, {
spread: 7, // Wider spread
segments: 17,
lineWidth: 0.1, // Thicker lines
splineCount: 21, // More lines
groupCount: 2,
canvasExtension: 0.1,
offset: 0.1,
startOffset: -0.1, // Slight downward start
endOffset: 0.2, // Slight upward end
offsetTransition: 'smooth', // Smooth transition
animationSpeed: 0.001, // Slower animation
hueBase: 0.55, // More blue-green
hueVariation: 0.3,
opacity: 0.7
});
window.addEventListener('beforeunload', () => aurora.destroy());
window.addEventListener('beforeunload', () => curtain.destroy());
}
});