update texts; remove redundant text; close all collapsibles by default; comment out carousel for now
This commit is contained in:
22
.pugrc
22
.pugrc
@@ -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­ment <wbr/>and set­ting <wbr/>the right Priori­ties",
|
||||
"title": "Software Engineer­ing <wbr/>and set­ting <wbr/>the right Priori­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."
|
||||
|
||||
24
index.pug
24
index.pug
@@ -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
BIN
src/assets/project3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
|
||||
| & #{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());
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user