Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,389 changes: 2,389 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"type": "module",
"dependencies": {
"@sveltejs/adapter-node": "^5.2.14",
"@tailwindcss/vite": "^4.1.11"
"@tailwindcss/vite": "^4.1.11",
"three": "^0.182.0"
},
"prettier": {
"plugins": [
Expand Down
236 changes: 236 additions & 0 deletions src/lib/components/ThreeBackdrop.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
<script>
import { onMount } from "svelte";

export let quality = 1;

let host;

onMount(async () => {
const [THREE, { EffectComposer }, { RenderPass }, { UnrealBloomPass }, { AfterimagePass }, { FilmPass }] =
await Promise.all([
import("three"),
import("three/examples/jsm/postprocessing/EffectComposer.js"),
import("three/examples/jsm/postprocessing/RenderPass.js"),
import("three/examples/jsm/postprocessing/UnrealBloomPass.js"),
import("three/examples/jsm/postprocessing/AfterimagePass.js"),
import("three/examples/jsm/postprocessing/FilmPass.js")
]);

const prefersReducedMotion = globalThis.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? false;

const canvas = document.createElement("canvas");
canvas.style.width = "100%";
canvas.style.height = "100%";
canvas.style.display = "block";
host.appendChild(canvas);

const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
alpha: true,
powerPreference: "high-performance"
});

renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.15;

const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x05070a, 0.16);

const camera = new THREE.PerspectiveCamera(55, 1, 0.1, 140);
camera.position.set(0, 0.1, 8.2);

const ambient = new THREE.AmbientLight(0xb8ffe9, 0.5);
const key = new THREE.DirectionalLight(0xb1ff7a, 1.25);
key.position.set(3, 4, 2);
const rim = new THREE.DirectionalLight(0x7df7ff, 0.7);
rim.position.set(-4, -2, 3);

scene.add(ambient, key, rim);

const root = new THREE.Group();
scene.add(root);

const grid = new THREE.GridHelper(40, 80, 0x2bff79, 0x0d2a1e);
grid.position.y = -2.1;
grid.rotation.x = Math.PI / 2;
grid.material.transparent = true;
grid.material.opacity = 0.18;
root.add(grid);

const knotGeo = new THREE.TorusKnotGeometry(1.4, 0.45, 540, 24);
const knotMat = new THREE.MeshStandardMaterial({
color: 0x2bff79,
emissive: 0x0aff85,
emissiveIntensity: 1.0,
metalness: 0.35,
roughness: 0.12
});
const knot = new THREE.Mesh(knotGeo, knotMat);
knot.position.set(0.2, 0.2, -1.6);
root.add(knot);

const haloGeo = new THREE.IcosahedronGeometry(2.35, 6);
const haloMat = new THREE.MeshStandardMaterial({
color: 0x062016,
emissive: 0x1cffaa,
emissiveIntensity: 0.55,
metalness: 0.0,
roughness: 0.15,
transparent: true,
opacity: 0.34
});
const halo = new THREE.Mesh(haloGeo, haloMat);
halo.position.copy(knot.position);
root.add(halo);

const particles = 1800;
const pos = new Float32Array(particles * 3);
const seed = new Float32Array(particles);

for (let i = 0; i < particles; i++) {
const r = 7.5 * Math.pow(Math.random(), 0.65);
const a = Math.random() * Math.PI * 2;
const b = (Math.random() - 0.5) * Math.PI;
const x = Math.cos(a) * Math.cos(b) * r;
const y = Math.sin(b) * r * 0.55;
const z = Math.sin(a) * Math.cos(b) * r - 2.5;
pos[i * 3 + 0] = x;
pos[i * 3 + 1] = y;
pos[i * 3 + 2] = z;
seed[i] = Math.random() * 10;
}

const pGeo = new THREE.BufferGeometry();
pGeo.setAttribute("position", new THREE.BufferAttribute(pos, 3));

const pMat = new THREE.PointsMaterial({
size: 0.022,
color: 0x7df7ff,
transparent: true,
opacity: 0.75,
blending: THREE.AdditiveBlending,
depthWrite: false
});

const pts = new THREE.Points(pGeo, pMat);
root.add(pts);

const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
const bloom = new UnrealBloomPass(new THREE.Vector2(1, 1), 1.0, 0.35, 0.0);
bloom.threshold = 0.0;
bloom.strength = prefersReducedMotion ? 0.55 : 1.25;
bloom.radius = 0.62;
const trails = new AfterimagePass(prefersReducedMotion ? 0.83 : 0.9);
const film = new FilmPass(0.2, 0.12, 648, false);

composer.addPass(renderPass);
composer.addPass(bloom);
composer.addPass(trails);
composer.addPass(film);

let w = 0;
let h = 0;
let raf = 0;
let t0 = performance.now();

const pointer = { x: 0, y: 0 };
const target = { x: 0, y: 0 };

const setSize = () => {
const rect = host.getBoundingClientRect();
w = Math.max(1, Math.floor(rect.width));
h = Math.max(1, Math.floor(rect.height));

const dpr = Math.max(1, Math.min(2.25, (globalThis.devicePixelRatio ?? 1) * quality));
renderer.setPixelRatio(dpr);
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
composer.setSize(w, h);
bloom.setSize(w, h);
};

const ro = new ResizeObserver(setSize);
ro.observe(host);
setSize();

const onMove = (e) => {
const rect = host.getBoundingClientRect();
const nx = (e.clientX - rect.left) / rect.width;
const ny = (e.clientY - rect.top) / rect.height;
pointer.x = (nx - 0.5) * 2;
pointer.y = (ny - 0.5) * 2;
};

host.addEventListener("pointermove", onMove, { passive: true });

const tick = (t) => {
const dt = Math.min(34, t - t0);
t0 = t;

const time = t * 0.0006;
const damp = 1 - Math.pow(0.0012, dt);

target.x += (pointer.x - target.x) * damp;
target.y += (pointer.y - target.y) * damp;

const rx = prefersReducedMotion ? 0.15 : 0.55;
const ry = prefersReducedMotion ? 0.2 : 0.75;

root.rotation.y = THREE.MathUtils.lerp(root.rotation.y, target.x * ry, 0.07);
root.rotation.x = THREE.MathUtils.lerp(root.rotation.x, -target.y * rx, 0.07);

knot.rotation.x = time * 1.15;
knot.rotation.y = time * 1.35;
knot.rotation.z = time * 0.65;

halo.rotation.x = -time * 0.42;
halo.rotation.y = time * 0.36;
halo.rotation.z = -time * 0.28;

const base = pGeo.attributes.position.array;
for (let i = 0; i < particles; i++) {
const k = i * 3;
const s = seed[i];
base[k + 1] += Math.sin(time * 2.2 + s) * 0.0009;
base[k + 0] += Math.cos(time * 1.9 + s) * 0.0007;
}
pGeo.attributes.position.needsUpdate = true;

camera.position.x = target.x * 0.45;
camera.position.y = 0.12 + -target.y * 0.25;
camera.lookAt(0, 0.05, -1.8);

composer.render();
raf = requestAnimationFrame(tick);
};

raf = requestAnimationFrame(tick);

return () => {
cancelAnimationFrame(raf);
host.removeEventListener("pointermove", onMove);
ro.disconnect();
composer.dispose();
renderer.dispose();
knotGeo.dispose();
knotMat.dispose();
haloGeo.dispose();
haloMat.dispose();
pGeo.dispose();
pMat.dispose();
grid.geometry.dispose();
if (Array.isArray(grid.material)) {
for (const m of grid.material) m.dispose();
} else {
grid.material.dispose();
}
canvas.remove();
};
});
</script>

<div bind:this={host} class="absolute inset-0"></div>
12 changes: 8 additions & 4 deletions src/lib/components/header/Hamburger.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
}
</script>

<!-- svelte-ignore a11y_consider_explicit_label -->
<button onclick={toggle} class="group cursor-pointer md:hidden">
<button
type="button"
aria-label="Lorem ipsum"
onclick={toggle}
class="group cursor-pointer md:hidden"
>
<div class="bar" class:expanded-bar-1={expanded}></div>
<div class="bar" class:expanded-bar-2={expanded}></div>
<div class="bar" class:expanded-bar-3={expanded}></div>
Expand All @@ -16,8 +20,8 @@
<style lang="postcss">
@reference "../../../app.css"

.bar {
@apply my-1 h-1 w-7 rounded-full bg-slate-700 transition-all;
.bar {
@apply my-1 h-1 w-7 rounded-full bg-slate-100 transition-all;
}

.expanded-bar-1 {
Expand Down
14 changes: 7 additions & 7 deletions src/lib/components/header/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
src="/cam-hack-logo-text.png"
alt="Cam Hack Logo"
/>
<span class="ml-4 text-xl font-bold">Cam Hack 2025</span>
<span class="ml-4 text-xl font-bold text-slate-50">Cam Hack 2025</span>
</div>
</a>
<div class="px-4">
<nav
class="flex items-center gap-6 text-xl font-semibold transition-colors max-md:hidden"
class="flex items-center gap-6 text-xl font-semibold text-slate-100 transition-colors max-md:hidden"
>
<a class="hover:text-emerald-600" href="/#about">About</a>
<a class="hover:text-emerald-600" href="/#schedule">Schedule</a>
<a class="hover:text-emerald-600" href="/#rules">Rules</a>
<a class="hover:text-emerald-600" href="/#faq">FAQ</a>
<a class="hover:text-emerald-300" href="/#about">About</a>
<a class="hover:text-emerald-300" href="/#schedule">Schedule</a>
<a class="hover:text-emerald-300" href="/#rules">Rules</a>
<a class="hover:text-emerald-300" href="/#faq">FAQ</a>
<a
class="rounded-lg bg-emerald-300 px-4 py-2 transition-transform hover:scale-105"
class="rounded-lg bg-emerald-300 px-4 py-2 text-slate-900 transition-transform hover:scale-105"
href="/signup">Sign up!</a
>
</nav>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/header/MobileMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
onclick={closeMenu}
{href}
{target}
class="block bg-white px-8 py-4 text-gray-800 transition-colors active:bg-emerald-200"
class="block bg-black/80 px-8 py-4 text-slate-100 transition-colors active:bg-emerald-300/25"
>{text}</a
>
{/snippet}
Expand Down
9 changes: 3 additions & 6 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
<script>
import "../app.css";

import Footer from "$lib/components/Footer.svelte";
import Header from "$lib/components/header/Header.svelte";

let { children } = $props();
</script>

<header class="relative bg-white shadow-md">
<header
class="sticky top-0 z-50 border-b border-emerald-300/15 bg-black/25 backdrop-blur-xl"
>
<Header />
</header>

<main class="flex-1">
{@render children()}
</main>

<footer class="theme-bg-gradient p-8">
<Footer />
</footer>
Loading