diff --git a/.DS_Store b/.DS_Store index 4ca6df5d..5abbeaa6 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/hackx/.gitignore b/hackx/.gitignore new file mode 100644 index 00000000..f4d8e469 --- /dev/null +++ b/hackx/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +node_modules/ +package.json +package-lock.json +.superpowers/ +complete/ +main-level/ +docs/superpowers/ +images/lumon-anim.gif +images/lumon-globe.png +images/lumon.png +images/mde.gif +images/modal_blank.psd \ No newline at end of file diff --git a/hackx/3dmodel/3D Asset Final.glb b/hackx/3dmodel/3D Asset Final.glb new file mode 100644 index 00000000..13075aa1 Binary files /dev/null and b/hackx/3dmodel/3D Asset Final.glb differ diff --git a/hackx/audio-assets/shakey-jake.mp3 b/hackx/audio-assets/shakey-jake.mp3 new file mode 100644 index 00000000..195baf86 Binary files /dev/null and b/hackx/audio-assets/shakey-jake.mp3 differ diff --git a/hackx/audio.js b/hackx/audio.js new file mode 100644 index 00000000..95eb295f --- /dev/null +++ b/hackx/audio.js @@ -0,0 +1,85 @@ +let audioCtx = null; +let droneOsc = null; + +function initAudio() { + if (audioCtx) return; + try { + audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + startDrone(); + } catch (e) { + audioCtx = null; + } +} + +function startDrone() { + if (!audioCtx) return; + droneOsc = audioCtx.createOscillator(); + const gain = audioCtx.createGain(); + droneOsc.type = 'sine'; + droneOsc.frequency.setValueAtTime(60, audioCtx.currentTime); + gain.gain.setValueAtTime(0.03, audioCtx.currentTime); + droneOsc.connect(gain); + gain.connect(audioCtx.destination); + droneOsc.start(); +} + +function playClick() { + if (!audioCtx) return; + const osc = audioCtx.createOscillator(); + const gain = audioCtx.createGain(); + osc.type = 'sine'; + osc.frequency.setValueAtTime(440, audioCtx.currentTime); + gain.gain.setValueAtTime(0.1, audioCtx.currentTime); + gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.05); + osc.connect(gain); + gain.connect(audioCtx.destination); + osc.start(); + osc.stop(audioCtx.currentTime + 0.05); +} + +function playRefine() { + if (!audioCtx) return; + const osc = audioCtx.createOscillator(); + const gain = audioCtx.createGain(); + osc.type = 'sine'; + osc.frequency.setValueAtTime(200, audioCtx.currentTime); + osc.frequency.exponentialRampToValueAtTime(800, audioCtx.currentTime + 0.2); + gain.gain.setValueAtTime(0.08, audioCtx.currentTime); + gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.2); + osc.connect(gain); + gain.connect(audioCtx.destination); + osc.start(); + osc.stop(audioCtx.currentTime + 0.2); +} + +function playNope() { + if (!audioCtx) return; + const osc = audioCtx.createOscillator(); + const gain = audioCtx.createGain(); + osc.type = 'sawtooth'; + osc.frequency.setValueAtTime(100, audioCtx.currentTime); + gain.gain.setValueAtTime(0.08, audioCtx.currentTime); + gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.15); + osc.connect(gain); + gain.connect(audioCtx.destination); + osc.start(); + osc.stop(audioCtx.currentTime + 0.15); +} + +function playMilestone() { + if (!audioCtx) return; + const freqs = [200, 267, 333]; + freqs.forEach((freq, i) => { + const osc = audioCtx.createOscillator(); + const gain = audioCtx.createGain(); + osc.type = 'sawtooth'; + osc.frequency.setValueAtTime(freq + (i * 2), audioCtx.currentTime); + osc.detune.setValueAtTime(i * 10, audioCtx.currentTime); + gain.gain.setValueAtTime(0.15, audioCtx.currentTime); + gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.5); + osc.connect(gain); + gain.connect(audioCtx.destination); + osc.start(); + osc.stop(audioCtx.currentTime + 0.5); + }); +} diff --git a/hackx/bin.js b/hackx/bin.js new file mode 100644 index 00000000..e6f25aeb --- /dev/null +++ b/hackx/bin.js @@ -0,0 +1,326 @@ +const keys = ['WO', 'FC', 'DR', 'MA']; +const keyDisplayNames = { WO: 'CODE', FC: 'DESIGN', DR: 'PITCH', MA: 'SHIP' }; +const maxLidAngle = 45; +const closedLidAngle = 180; +const maxShowTime = 1000; +const lidOpenCloseTime = 1500; + +var levelH; // set dynamically when buffer is available + +class Bin { + constructor(w, i, goal, levels) { + this.w = w; + this.i = i; + this.x = i * w + w * 0.5; + this.y = g.height - buffer * 0.75; + + this.goal = goal; + this.levelGoal = this.goal / 4; + + this.levelsYOffset = levelH; + this.lastRefinedTime = millis(); + +// if levels is undefined, assign empty levels + this.levels = levels ?? { + WO: 0, + FC: 0, + DR: 0, + MA: 0, + }; + + // sum the levels to get current count + this.count = Object.values(this.levels).reduce((prev, curr) => prev + curr); + + this.showLevels = false; + this.closingAnimation = false; + this.openingAnimation = false; + this.lidAngle = closedLidAngle; + this.showTime = 0; + } + + addNumber() { + const options = []; + for (let key of keys) { + if (this.levels[key] < this.levelGoal) { + options.push(key); + } + } + const key = random(options); + this.levels[key]++; + + this.showLevels = true; + this.showTime = millis(); + } + + open() { + if (!this.showLevels) { + // Only start the animation if the bin is closed + this.lidAngle = closedLidAngle; + this.animationStartTime = millis(); + this.openingAnimation = true; + this.showLevels = true; + } + } + + show() { + g.push(); + this.count = + this.levels.WO + this.levels.FC + this.levels.DR + this.levels.MA; + + this.count = constrain(this.count, 0, this.goal); + let perc = this.count / this.goal; + g.rectMode(CENTER); + let rw = this.w - this.w * 0.25; + + // Pulsing glow effect when bin is >80% full + if (perc > 0.8) { + this.drawPulsingGlow(rw, perc); + } + + if (this.showLevels) { + this.drawLevels(rw, buffer); + } + + this.drawBottomOutlines(rw, buffer); + + this.drawProgressBar(rw, buffer, perc); + this.writeIndex(); + this.writePercentage(perc, rw, buffer); + + g.pop(); + } + + drawPulsingGlow(rw, perc) { + // Pulse intensity oscillates using sin(millis()) + let pulse = map(sin(millis() * 0.004), -1, 1, 0.05, 0.25); + // Stronger glow the closer to 100% + let intensity = map(perc, 0.8, 1.0, 0.5, 1.0); + pulse *= intensity; + + g.push(); + g.rectMode(CENTER); + g.noStroke(); + + // Outer glow layer + let glowColor = color(palette.FG); + glowColor.setAlpha(pulse * 80); + g.fill(glowColor); + g.rect(this.x, this.y - buffer * 0.5, rw + 16, buffer * 1.2 + 16, 6); + + // Inner glow layer + glowColor.setAlpha(pulse * 150); + g.fill(glowColor); + g.rect(this.x, this.y - buffer * 0.5, rw + 6, buffer * 1.2 + 6, 3); + + g.pop(); + } + + drawBottomOutlines(rw, buffer) { + // Extra layer to block tray sliding + g.noStroke(); + g.fill(palette.BG); + g.rectMode(CORNER); + let extra = 4; + g.rect( + this.x - rw * 0.5 - extra, + this.y - buffer * 0.125, + rw + extra * 2, + buffer + ); + + g.stroke(palette.FG); + g.strokeWeight(1); + g.fill(palette.BG); + + g.rectMode(CENTER); + g.rect(this.x, this.y, rw, buffer * 0.25); + g.rect(this.x, this.y + buffer * 0.3, rw, buffer * 0.25); + } + + drawProgressBar(rw, buffer, perc) { + g.fill(palette.FG); + g.noStroke(); + g.rectMode(CORNER); + + let h = buffer * 0.25; + g.rect(this.x - rw * 0.5, this.y + buffer * 0.3 - h * 0.5, rw * perc, h); + } + + writeIndex() { + g.textSize(18); + g.textFont('Arial'); + g.textAlign(CENTER, CENTER); + g.fill(palette.FG); + g.noStroke(); + g.text(nf(this.i, 2, 0), this.x, this.y); + } + + writePercentage(perc, rw, buffer) { + g.textAlign(LEFT, CENTER); + g.stroke(palette.FG); + g.strokeWeight(2); + g.fill(palette.BG); + g.text( + `${floor(nf(100 * perc, 2, 0))}%`, + this.x - rw * 0.45, + this.y + buffer * 0.3 + ); + } + + drawLevels(rw, buffer) { + g.rectMode(CENTER); + let levelY = this.y - buffer; + + // Draw main outline + g.stroke(palette.FG); + g.fill(palette.BG); + g.rect(this.x, levelY + this.levelsYOffset, rw, levelH); + + this.drawBinLids(rw, buffer); + + for (let i = 1; i < 5; i++) { + this.drawLevel(i, levelY + this.levelsYOffset, rw, buffer); + } + + if (millis() - this.lastRefinedTime > 120) { + if (!this.openingAnimation) { + this.lidAngle = maxLidAngle; + if (!this.closingAnimation) { + this.animationStartTime = millis(); + } + this.closingAnimation = true; + this.showLevels = true; + } + } + + if (this.openingAnimation) { + this.lidAngle = map( + millis() - this.animationStartTime, + 0, + maxShowTime, + closedLidAngle, + maxLidAngle + ); + this.levelsYOffset = map( + millis() - this.animationStartTime, + 0, + maxShowTime, + levelH, + 0 + ); + + if (this.lidAngle <= maxLidAngle) { + this.lidAngle = maxLidAngle; + this.openingAnimation = false; + this.showTime = millis(); + } + } else if (this.closingAnimation) { + this.lidAngle = map( + millis() - this.animationStartTime, + 0, + lidOpenCloseTime, + maxLidAngle, + closedLidAngle + ); + this.levelsYOffset = map( + millis() - this.animationStartTime, + 0, + maxShowTime, + 0, + levelH + ); + + if (this.lidAngle >= closedLidAngle) { + this.lidAngle = maxLidAngle; + this.closingAnimation = false; + this.showLevels = false; + } + } + } + + drawLevel(i, y, rw, buffer) { + const level = keys[i - 1]; + const displayName = keyDisplayNames[level]; + const levelColor = palette.LEVELS[level]; + + g.rectMode(CORNER); + g.stroke(levelColor); + g.noFill(); + + // Draw the outline of the progress bar + g.rect( + this.x - rw * 0.25, + y - buffer + i * buffer * 0.35, + rw * 0.7, + buffer * 0.15 + ); + // Draw the filled bar inside of the progress bar. + g.fill(levelColor); + let w = (rw * 0.7 * this.levels[level]) / this.levelGoal; + g.rect(this.x - rw * 0.25, y - buffer + i * buffer * 0.35, w, buffer * 0.15); + + // Draw the label for the progress bar using display name. + g.textAlign(LEFT, CENTER); + g.noStroke(); + g.fill(levelColor); + g.textFont("Courier"); + const labelMaxW = rw * 0.18; + let labelSize = 16; + g.textSize(labelSize); + while (labelSize > 9 && g.textWidth(displayName) > labelMaxW) { + labelSize -= 1; + g.textSize(labelSize); + } + g.text( + displayName, + this.x - rw * 0.45, + y - buffer + i * buffer * 0.35 + buffer * 0.075 + ); + } + + drawBinLids(rw, buffer) { + let angle = radians(-this.lidAngle); + + g.stroke(palette.FG); + g.fill(palette.BG); + + // Draw the top part of the lid. + this.drawHalfBinLid(this.x + rw * 0.5, g.height - buffer, angle, rw); + + angle = radians(180 + this.lidAngle); + + // Draw left bin lid + this.drawHalfBinLid(this.x - rw * 0.5, g.height - buffer, angle, rw); + } + + drawHalfBinLid(x, y, angle, rw) { + const lidThickness = 15; + // const doorWidth = width * 0.05; + const doorWidth = rw * 0.5; + + // cos(angle) = a/h, a = h * cos(angle) + const doorXLength = doorWidth * cos(angle); + + // sin(angle) = o/h, o = h * sin(angle) + const doorYLength = doorWidth * sin(angle); + + // In the show we see that the lids do not have 90 degree angles, which means that we cannot use the rect() function. + + g.push(); + g.beginShape(); + g.translate(x, y); + g.vertex(0, 0); + g.vertex(doorXLength, doorYLength); + // Move down + g.vertex(doorXLength, doorYLength + lidThickness); + g.vertex(0, lidThickness); + g.endShape(CLOSE); + g.pop(); + } + + resize(newW) { + this.w = newW; + this.x = this.i * newW + newW * 0.5; + this.y = g.height - buffer * 0.75; + } +} diff --git a/hackx/config.js b/hackx/config.js new file mode 100644 index 00000000..9c4dfe86 --- /dev/null +++ b/hackx/config.js @@ -0,0 +1,64 @@ +const HACKX_CONFIG = { + orgName: 'CODECELL', + eventName: 'HACK X', + tagline: 'CHANGING THE WORLD, ONE BIT AT A TIME', + date: 'APRIL 11-12, 2026', + location: 'KJ SOMAIYA COLLEGE OF ENGINEERING', + registerUrl: 'https://hackx.codecell.io/register', + tracks: ['NEXUS', 'SPECTRA'], + + bootLines: [ + 'CODECELL v10.0.0 // EST. 2016 // 10 YEARS', + 'HACK X PROTOCOL INIT...', + '> APRIL 11-12 // 24HR HACKATHON', + '> KJ SOMAIYA COLLEGE OF ENGINEERING', + '> STATUS: ONLINE', + ], + + // Easter eggs revealed at milestones (fun secrets, not important info) + reveals: { + 25: { text: 'THE BOARD HAS NOTICED YOUR POTENTIAL. KEEP REFINING.' }, + 50: { text: 'ANOMALY DETECTED IN SECTOR 7G. YOUR CODE IS... EVOLVING.' }, + 75: { text: 'CLEARANCE UPGRADED. YOU NOW HAVE ACCESS TO THE FORBIDDEN REPOSITORY.' }, + 100: { text: 'PROTOCOL COMPLETE. YOU HAVE BEEN CHOSEN FOR HACK X. THE CODE REMEMBERS.' }, + }, + + terminalLines: [ + '> CODECELL SYSTEMS // CLASSIFIED TERMINAL', + '> HACK X STATUS: ACTIVE', + '> TRACKS: NEXUS (AI/ML) | SPECTRA (WEB3)', + '> PRIZE POOL: ₹3,00,000+', + '> REGISTRATION: CLOSES APRIL 8', + '> "THE BEST CODE IS WRITTEN AT 3AM"', + '> TYPE \'exit\' TO RETURN', + ], + + // Corporate announcements that pop up periodically + announcements: [ + 'HACK X // APRIL 11-12 // REGISTER BEFORE APRIL 8', + 'CODECELL // 10 YEARS // CHANGING THE WORLD ONE BIT AT A TIME', + 'PRIZE POOL: ₹3,00,000+ // WHAT WILL YOU BUILD?', + 'KJ SOMAIYA COLLEGE OF ENGINEERING // 24 HOURS // NO LIMITS', + 'YOUR REFINEMENT SCORE HAS BEEN NOTED', + 'CODECELL APPRECIATES YOUR DEDICATION', + 'THE DATA MUST FLOW', + ], + + // Project codenames for file assignments (replaces city names) + projectNames: [ + 'PHOENIX', + 'HYDRA', + 'CIPHER', + 'NEBULA', + 'AXIOM', + 'PRISM', + 'VERTEX', + 'ORACLE', + 'FLUX', + 'HELIX', + 'QUANTUM', + ], + + shareUrl: 'hackx.codecell.io', + shareHashtags: '#HackX #CodeCell', +}; diff --git a/hackx/data.js b/hackx/data.js new file mode 100644 index 00000000..6ee5b3e8 --- /dev/null +++ b/hackx/data.js @@ -0,0 +1,104 @@ +class Data { + constructor(x, y) { + this.num = floor(random(10)); + this.homeX = x; + this.homeY = y; + this.x = x; + this.y = y; + this.color = palette.FG; //TODO: pass this in as arg rather than global variable? + this.alpha = 255; + this.sz = baseSize; + this.refined = false; + this.binIt = false; + this.bin = undefined; + this.binPauseTime = 8; + this.binPause = this.binPauseTime; + } + + refine(bin) { + this.binIt = true; + this.bin = bin; + } + + goBin() { + // This is a band-aid + if (this.bin) { + this.bin.open(); + if (this.binPause <= 0) { + const dx = this.bin.x - this.x; + const dy = this.bin.y - this.y; + let easing = map(abs(dy), this.bin.y, 0, 0.02, 0.1); + this.x += dx * easing; + this.y += max(dy * easing, -20); + this.alpha = map(this.y, this.homeY, this.bin.y, 255, 5); + this.bin.lastRefinedTime = millis(); + } else { + this.binPause--; + } + if (dist(this.x, this.y, this.bin.x, this.bin.y) < 2) { + this.bin.addNumber(); + this.num = floor(random(10)); + this.x = this.homeX; + this.y = this.homeY; + this.refined = false; + this.binIt = false; + this.bin = undefined; + this.color = palette.FG; + this.alpha = 255; + this.binPause = this.binPauseTime; + } + } + } + + goHome() { + this.x = lerp(this.x, this.homeX, 0.1); + this.y = lerp(this.y, this.homeY, 0.1); + this.sz = lerp(this.sz, baseSize, 0.1); + } + + size(sz) { + this.sz = sz; + } + + turn(newColor) { + this.color = newColor; + } + + inside(x1, y1, x2, y2) { + return ( + this.x > min(x1, x2) && + this.x < max(x1, x2) && + this.y > min(y1, y2) && + this.y < max(y1, y2) + ); + } + + show() { + g.textFont('Courier'); + const digitSize = this.binIt ? lerp(this.sz, baseSize * 2.5, map(this.binPause, this.binPauseTime, 0, 0, 1)) : this.sz; + g.textSize(digitSize); + g.textAlign(CENTER, CENTER); + + if (typeof rainbowMode !== 'undefined' && rainbowMode) { + g.colorMode(HSB, 360, 100, 100, 255); + const hue = (frameCount * 3 + this.homeX + this.homeY) % 360; + const col = g.color(hue, 80, 100, this.alpha); + g.fill(col); + g.stroke(col); + } else { + const col = color(this.color); + col.setAlpha(this.alpha); + g.fill(col); + g.stroke(col); + } + g.text(this.num, this.x, this.y); + if (typeof rainbowMode !== 'undefined' && rainbowMode) { + g.colorMode(RGB); + } + } + + resize(newX, newY) { + this.homeX = newX; + this.homeY = newY; + } +} diff --git a/hackx/images/100.png b/hackx/images/100.png new file mode 100644 index 00000000..2b9d6506 Binary files /dev/null and b/hackx/images/100.png differ diff --git a/hackx/images/capture.png b/hackx/images/capture.png new file mode 100644 index 00000000..8cb73a0e Binary files /dev/null and b/hackx/images/capture.png differ diff --git a/hackx/images/clipboard.png b/hackx/images/clipboard.png new file mode 100644 index 00000000..eb761cb1 Binary files /dev/null and b/hackx/images/clipboard.png differ diff --git a/hackx/images/icon.png b/hackx/images/icon.png new file mode 100644 index 00000000..1ed4a235 Binary files /dev/null and b/hackx/images/icon.png differ diff --git a/hackx/images/logo.svg b/hackx/images/logo.svg new file mode 100644 index 00000000..3e66ba65 --- /dev/null +++ b/hackx/images/logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/hackx/images/nope.png b/hackx/images/nope.png new file mode 100644 index 00000000..8dec78cc Binary files /dev/null and b/hackx/images/nope.png differ diff --git a/hackx/index.html b/hackx/index.html new file mode 100644 index 00000000..f0356a71 --- /dev/null +++ b/hackx/index.html @@ -0,0 +1,479 @@ + + + + + HACK X // CodeCell + + + + + + + + + + + + + + + +
+ +
+ REGISTER + + +
+
+
+ ▼ SCROLL TO DECRYPT MORE ▼ +
+
+ + +
+ +
+
+

HACK X

+

A 24-HOUR HACKATHON BY CODECELL

+

+ The boldest builders, designers, and dreamers converge + to create something unprecedented. No sleep. No limits. + Just code. +

+
+ AI / ML + WEB3 + OPEN TO ALL + TEAM OF 2-4 +
+
+
+
+ +
+
+
+ + + + + +
+ +
+

// PRIZES

+ +
+
+
TOTAL PRIZE POOL
+
+ ₹0 +
+
+ + GOODIES & SWAG FOR ALL +
+
+ +
+
+
TRACK 1
+
+
+
1ST
+
+ ₹0 +
+
+
+
2ND
+
+ ₹0 +
+
+
+
+
+
+
TRACK 2
+
+
+
1ST
+
+ ₹0 +
+
+
+
2ND
+
+ ₹0 +
+
+
+
+
+
+
+
+ + +
+ +
+

// WHAT YOU GET

+
+
+
₹3L+
+
IN PRIZES
+
ACROSS BOTH TRACKS
+
+
+
24H
+
FOOD & CAFFEINE
+
+
+
1:1
+
MENTORSHIP
+
+
+
200+
+
NETWORK
+
+
+
SWAG + CERTS + WIFI + VIBES
+
EVERYTHING ELSE IS ON US
+
+
+
+ + +
+
+

// SPONSORS

+

+ Hack X is made possible by organizations that believe in the + next generation of builders. +

+
+ + + +
+
+ WANT TO SPONSOR? + CONTACT US → +
+ +
+
+ PAST SPONSORS & PARTNERS +
+
+
+ GOOGLE + MICROSOFT + GITHUB + DEVFOLIO + POLYGON + FILECOIN + REPLIT + WOLFRAM + BALSAMIQ + XYZ DOMAINS + ECHO3D + TASKADE + + GOOGLE + MICROSOFT + GITHUB + DEVFOLIO + POLYGON + FILECOIN + REPLIT + WOLFRAM + BALSAMIQ + XYZ DOMAINS + ECHO3D + TASKADE +
+
+
+
+
+ + +
+
+

// TIMELINE

+
+
+
+
MAR 15
+
REGISTRATION OPENS
+
+
+
+
APR 08
+
REGISTRATION CLOSES
+
+
+
+
APR 11
+
OPENING CEREMONY
+
+
+
+
APR 11-12
+
24 HOURS OF HACKING
+
+
+
+
APR 12
+
DEMOS & JUDGING
+
+
+
+
APR 12
+
+ CLOSING CEREMONY & PRIZES +
+
+
+
+
+ + +
+
+

// FAQ

+
+
+
+ WHO CAN PARTICIPATE? + + +
+
+ Anyone with a passion for building. Students, + professionals, hobbyists — if you can code, design, + or pitch, you're in. Teams of 2-4 members. +
+
+
+
+ DO I NEED A TEAM? + + +
+
+ You can register solo and find teammates at our team + formation event on March 1st. Or bring your own + squad. Max team size is 4. +
+
+
+
+ WHAT SHOULD I BUILD? + + +
+
+ Anything that fits within our two tracks: NEXUS + (AI/ML) or SPECTRA (Web3). We value creativity, + technical depth, and real-world impact. +
+
+
+
+ IS IT FREE? + + +
+
+ Yes. Hack X is completely free. We provide the + venue, food, WiFi, mentors, and ₹3L+ in prizes. You + bring the code. +
+
+
+
+ WHAT DO I NEED TO BRING? + + +
+
+ Your laptop, charger, and an unhinged desire to + build something amazing in 24 hours. We handle the + rest. +
+
+
+
+
+ + +
+
+

// THE TEAM

+

+ 36 people behind the scenes making Hack X happen. CodeCell's + finest. +

+
+ 36 + ORGANIZERS +
+
+ DEPARTMENT HEADS // SEVERED FLOOR +
+
+
+
+
MACRODATA REFINEMENT // O&D
+
+
+
+
OPTICS & DESIGN // WELLNESS
+
+
+
+
NEW INNIES // PROBATIONARY
+
+
+
+
THE HUMANS BEHIND THE TERMINAL
+
+
+ + + + + + + + + + + + + + + + + +
INITIALIZING HACK X...
+ + + \ No newline at end of file diff --git a/hackx/macrodata.js b/hackx/macrodata.js new file mode 100644 index 00000000..3766743c --- /dev/null +++ b/hackx/macrodata.js @@ -0,0 +1,57 @@ +const emptyBins = [ + {WO: 0, FC: 0, DR: 0, MA: 0}, + {WO: 0, FC: 0, DR: 0, MA: 0}, + {WO: 0, FC: 0, DR: 0, MA: 0}, + {WO: 0, FC: 0, DR: 0, MA: 0}, + {WO: 0, FC: 0, DR: 0, MA: 0} +]; + +class MacrodataFile { + constructor() { + this.localStorageKey = 'hackx-data'; + const file = JSON.parse(localStorage.getItem(this.localStorageKey)) ?? this.assignFile(); + this.fileName = file.fileName; + this.storedBins = file.storedBins; + this.coordinates = file.coordinates; + } + + assignFile() { + const names = HACKX_CONFIG.projectNames; + const allButPrevious = names.filter(f => f !== this.fileName); + const fileName = allButPrevious[Math.floor(Math.random() * allButPrevious.length)]; + const coordinates = this.#generateCoordinates(); + const macrodata = { + fileName, + storedBins: emptyBins, + coordinates + }; + localStorage.setItem(this.localStorageKey, JSON.stringify(macrodata)); + return macrodata; + } + + updateProgress(bins) { + const updatedFile = { + fileName: this.fileName, + storedBins: bins, + coordinates: this.coordinates + }; + localStorage.setItem(this.localStorageKey, JSON.stringify(updatedFile)); + } + + resetFile() { + localStorage.removeItem(this.localStorageKey); + const file = this.assignFile(); + this.fileName = file.fileName; + this.storedBins = file.storedBins; + this.coordinates = file.coordinates; + } + + #generateCoordinates() { + function randHex() { + return floor(random(0, 256)).toString(16).toUpperCase().padStart(2, '0'); + } + let x = randHex() + randHex() + randHex(); + let y = randHex() + randHex() + randHex(); + return `0x${x} : 0x${y}`; + } +} diff --git a/hackx/manifest.json b/hackx/manifest.json new file mode 100644 index 00000000..41906f14 --- /dev/null +++ b/hackx/manifest.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json.schemastore.org/web-manifest-combined.json", + "name": "HACK X // CodeCell", + "short_name": "HackX", + "start_url": "/", + "display": "standalone", + "Scope": "/", + "background_color": "#0a1628", + "theme_color": "#4fd1d9", + "description": "HACK X — Where Code Meets Chaos", + "icons": [ + { + "src": "images/icon.png", + "sizes": "192x192", + "type": "image/png" + } + ] +} diff --git a/hackx/osn.js b/hackx/osn.js new file mode 100644 index 00000000..15235f11 --- /dev/null +++ b/hackx/osn.js @@ -0,0 +1,602 @@ +// Plain JS version of Josh Forisha's implementation of opensimplex noise +// https://github.com/joshforisha/open-simplex-noise-js +// This version is currently posted here https://gist.github.com/PARC6502/85c99c04c9b3c6ae52c3c27605b4df0a +// Will probably be cleaned up and have its own repo, at some point... + +'use strict'; +var OpenSimplexNoise; + +(function () { + var constants_1 = { + NORM_2D: 1.0 / 47.0, + NORM_3D: 1.0 / 103.0, + NORM_4D: 1.0 / 30.0, + SQUISH_2D: (Math.sqrt(2 + 1) - 1) / 2, + SQUISH_3D: (Math.sqrt(3 + 1) - 1) / 3, + SQUISH_4D: (Math.sqrt(4 + 1) - 1) / 4, + STRETCH_2D: (1 / Math.sqrt(2 + 1) - 1) / 2, + STRETCH_3D: (1 / Math.sqrt(3 + 1) - 1) / 3, + STRETCH_4D: (1 / Math.sqrt(4 + 1) - 1) / 4, + base2D: [ + [1, 1, 0, 1, 0, 1, 0, 0, 0], + [1, 1, 0, 1, 0, 1, 2, 1, 1], + ], + base3D: [ + [0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1], + [2, 1, 1, 0, 2, 1, 0, 1, 2, 0, 1, 1, 3, 1, 1, 1], + [1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 2, 1, 1, 0, 2, 1, 0, 1, 2, 0, 1, 1], + ], + base4D: [ + [ + 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, + 1, + ], + [ + 3, 1, 1, 1, 0, 3, 1, 1, 0, 1, 3, 1, 0, 1, 1, 3, 0, 1, 1, 1, 4, 1, 1, 1, + 1, + ], + [ + 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 2, 1, 1, 0, + 0, 2, 1, 0, 1, 0, 2, 1, 0, 0, 1, 2, 0, 1, 1, 0, 2, 0, 1, 0, 1, 2, 0, 0, + 1, 1, + ], + [ + 3, 1, 1, 1, 0, 3, 1, 1, 0, 1, 3, 1, 0, 1, 1, 3, 0, 1, 1, 1, 2, 1, 1, 0, + 0, 2, 1, 0, 1, 0, 2, 1, 0, 0, 1, 2, 0, 1, 1, 0, 2, 0, 1, 0, 1, 2, 0, 0, + 1, 1, + ], + ], + gradients2D: [5, 2, 2, 5, -5, 2, -2, 5, 5, -2, 2, -5, -5, -2, -2, -5], + gradients3D: [ + -11, 4, 4, -4, 11, 4, -4, 4, 11, 11, 4, 4, 4, 11, 4, 4, 4, 11, -11, -4, 4, + -4, -11, 4, -4, -4, 11, 11, -4, 4, 4, -11, 4, 4, -4, 11, -11, 4, -4, -4, + 11, -4, -4, 4, -11, 11, 4, -4, 4, 11, -4, 4, 4, -11, -11, -4, -4, -4, -11, + -4, -4, -4, -11, 11, -4, -4, 4, -11, -4, 4, -4, -11, + ], + gradients4D: [ + 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, -3, 1, 1, 1, -1, 3, 1, 1, + -1, 1, 3, 1, -1, 1, 1, 3, 3, -1, 1, 1, 1, -3, 1, 1, 1, -1, 3, 1, 1, -1, 1, + 3, -3, -1, 1, 1, -1, -3, 1, 1, -1, -1, 3, 1, -1, -1, 1, 3, 3, 1, -1, 1, 1, + 3, -1, 1, 1, 1, -3, 1, 1, 1, -1, 3, -3, 1, -1, 1, -1, 3, -1, 1, -1, 1, -3, + 1, -1, 1, -1, 3, 3, -1, -1, 1, 1, -3, -1, 1, 1, -1, -3, 1, 1, -1, -1, 3, + -3, -1, -1, 1, -1, -3, -1, 1, -1, -1, -3, 1, -1, -1, -1, 3, 3, 1, 1, -1, + 1, 3, 1, -1, 1, 1, 3, -1, 1, 1, 1, -3, -3, 1, 1, -1, -1, 3, 1, -1, -1, 1, + 3, -1, -1, 1, 1, -3, 3, -1, 1, -1, 1, -3, 1, -1, 1, -1, 3, -1, 1, -1, 1, + -3, -3, -1, 1, -1, -1, -3, 1, -1, -1, -1, 3, -1, -1, -1, 1, -3, 3, 1, -1, + -1, 1, 3, -1, -1, 1, 1, -3, -1, 1, 1, -1, -3, -3, 1, -1, -1, -1, 3, -1, + -1, -1, 1, -3, -1, -1, 1, -1, -3, 3, -1, -1, -1, 1, -3, -1, -1, 1, -1, -3, + -1, 1, -1, -1, -3, -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -3, -1, -1, -1, + -1, -3, + ], + lookupPairs2D: [ + 0, 1, 1, 0, 4, 1, 17, 0, 20, 2, 21, 2, 22, 5, 23, 5, 26, 4, 39, 3, 42, 4, + 43, 3, + ], + lookupPairs3D: [ + 0, 2, 1, 1, 2, 2, 5, 1, 6, 0, 7, 0, 32, 2, 34, 2, 129, 1, 133, 1, 160, 5, + 161, 5, 518, 0, 519, 0, 546, 4, 550, 4, 645, 3, 647, 3, 672, 5, 673, 5, + 674, 4, 677, 3, 678, 4, 679, 3, 680, 13, 681, 13, 682, 12, 685, 14, 686, + 12, 687, 14, 712, 20, 714, 18, 809, 21, 813, 23, 840, 20, 841, 21, 1198, + 19, 1199, 22, 1226, 18, 1230, 19, 1325, 23, 1327, 22, 1352, 15, 1353, 17, + 1354, 15, 1357, 17, 1358, 16, 1359, 16, 1360, 11, 1361, 10, 1362, 11, + 1365, 10, 1366, 9, 1367, 9, 1392, 11, 1394, 11, 1489, 10, 1493, 10, 1520, + 8, 1521, 8, 1878, 9, 1879, 9, 1906, 7, 1910, 7, 2005, 6, 2007, 6, 2032, 8, + 2033, 8, 2034, 7, 2037, 6, 2038, 7, 2039, 6, + ], + lookupPairs4D: [ + 0, 3, 1, 2, 2, 3, 5, 2, 6, 1, 7, 1, 8, 3, 9, 2, 10, 3, 13, 2, 16, 3, 18, + 3, 22, 1, 23, 1, 24, 3, 26, 3, 33, 2, 37, 2, 38, 1, 39, 1, 41, 2, 45, 2, + 54, 1, 55, 1, 56, 0, 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 256, + 3, 258, 3, 264, 3, 266, 3, 272, 3, 274, 3, 280, 3, 282, 3, 2049, 2, 2053, + 2, 2057, 2, 2061, 2, 2081, 2, 2085, 2, 2089, 2, 2093, 2, 2304, 9, 2305, 9, + 2312, 9, 2313, 9, 16390, 1, 16391, 1, 16406, 1, 16407, 1, 16422, 1, 16423, + 1, 16438, 1, 16439, 1, 16642, 8, 16646, 8, 16658, 8, 16662, 8, 18437, 6, + 18439, 6, 18469, 6, 18471, 6, 18688, 9, 18689, 9, 18690, 8, 18693, 6, + 18694, 8, 18695, 6, 18696, 9, 18697, 9, 18706, 8, 18710, 8, 18725, 6, + 18727, 6, 131128, 0, 131129, 0, 131130, 0, 131131, 0, 131132, 0, 131133, + 0, 131134, 0, 131135, 0, 131352, 7, 131354, 7, 131384, 7, 131386, 7, + 133161, 5, 133165, 5, 133177, 5, 133181, 5, 133376, 9, 133377, 9, 133384, + 9, 133385, 9, 133400, 7, 133402, 7, 133417, 5, 133421, 5, 133432, 7, + 133433, 5, 133434, 7, 133437, 5, 147510, 4, 147511, 4, 147518, 4, 147519, + 4, 147714, 8, 147718, 8, 147730, 8, 147734, 8, 147736, 7, 147738, 7, + 147766, 4, 147767, 4, 147768, 7, 147770, 7, 147774, 4, 147775, 4, 149509, + 6, 149511, 6, 149541, 6, 149543, 6, 149545, 5, 149549, 5, 149558, 4, + 149559, 4, 149561, 5, 149565, 5, 149566, 4, 149567, 4, 149760, 9, 149761, + 9, 149762, 8, 149765, 6, 149766, 8, 149767, 6, 149768, 9, 149769, 9, + 149778, 8, 149782, 8, 149784, 7, 149786, 7, 149797, 6, 149799, 6, 149801, + 5, 149805, 5, 149814, 4, 149815, 4, 149816, 7, 149817, 5, 149818, 7, + 149821, 5, 149822, 4, 149823, 4, 149824, 37, 149825, 37, 149826, 36, + 149829, 34, 149830, 36, 149831, 34, 149832, 37, 149833, 37, 149842, 36, + 149846, 36, 149848, 35, 149850, 35, 149861, 34, 149863, 34, 149865, 33, + 149869, 33, 149878, 32, 149879, 32, 149880, 35, 149881, 33, 149882, 35, + 149885, 33, 149886, 32, 149887, 32, 150080, 49, 150082, 48, 150088, 49, + 150098, 48, 150104, 47, 150106, 47, 151873, 46, 151877, 45, 151881, 46, + 151909, 45, 151913, 44, 151917, 44, 152128, 49, 152129, 46, 152136, 49, + 152137, 46, 166214, 43, 166215, 42, 166230, 43, 166247, 42, 166262, 41, + 166263, 41, 166466, 48, 166470, 43, 166482, 48, 166486, 43, 168261, 45, + 168263, 42, 168293, 45, 168295, 42, 168512, 31, 168513, 28, 168514, 31, + 168517, 28, 168518, 25, 168519, 25, 280952, 40, 280953, 39, 280954, 40, + 280957, 39, 280958, 38, 280959, 38, 281176, 47, 281178, 47, 281208, 40, + 281210, 40, 282985, 44, 282989, 44, 283001, 39, 283005, 39, 283208, 30, + 283209, 27, 283224, 30, 283241, 27, 283256, 22, 283257, 22, 297334, 41, + 297335, 41, 297342, 38, 297343, 38, 297554, 29, 297558, 24, 297562, 29, + 297590, 24, 297594, 21, 297598, 21, 299365, 26, 299367, 23, 299373, 26, + 299383, 23, 299389, 20, 299391, 20, 299584, 31, 299585, 28, 299586, 31, + 299589, 28, 299590, 25, 299591, 25, 299592, 30, 299593, 27, 299602, 29, + 299606, 24, 299608, 30, 299610, 29, 299621, 26, 299623, 23, 299625, 27, + 299629, 26, 299638, 24, 299639, 23, 299640, 22, 299641, 22, 299642, 21, + 299645, 20, 299646, 21, 299647, 20, 299648, 61, 299649, 60, 299650, 61, + 299653, 60, 299654, 59, 299655, 59, 299656, 58, 299657, 57, 299666, 55, + 299670, 54, 299672, 58, 299674, 55, 299685, 52, 299687, 51, 299689, 57, + 299693, 52, 299702, 54, 299703, 51, 299704, 56, 299705, 56, 299706, 53, + 299709, 50, 299710, 53, 299711, 50, 299904, 61, 299906, 61, 299912, 58, + 299922, 55, 299928, 58, 299930, 55, 301697, 60, 301701, 60, 301705, 57, + 301733, 52, 301737, 57, 301741, 52, 301952, 79, 301953, 79, 301960, 76, + 301961, 76, 316038, 59, 316039, 59, 316054, 54, 316071, 51, 316086, 54, + 316087, 51, 316290, 78, 316294, 78, 316306, 73, 316310, 73, 318085, 77, + 318087, 77, 318117, 70, 318119, 70, 318336, 79, 318337, 79, 318338, 78, + 318341, 77, 318342, 78, 318343, 77, 430776, 56, 430777, 56, 430778, 53, + 430781, 50, 430782, 53, 430783, 50, 431000, 75, 431002, 72, 431032, 75, + 431034, 72, 432809, 74, 432813, 69, 432825, 74, 432829, 69, 433032, 76, + 433033, 76, 433048, 75, 433065, 74, 433080, 75, 433081, 74, 447158, 71, + 447159, 68, 447166, 71, 447167, 68, 447378, 73, 447382, 73, 447386, 72, + 447414, 71, 447418, 72, 447422, 71, 449189, 70, 449191, 70, 449197, 69, + 449207, 68, 449213, 69, 449215, 68, 449408, 67, 449409, 67, 449410, 66, + 449413, 64, 449414, 66, 449415, 64, 449416, 67, 449417, 67, 449426, 66, + 449430, 66, 449432, 65, 449434, 65, 449445, 64, 449447, 64, 449449, 63, + 449453, 63, 449462, 62, 449463, 62, 449464, 65, 449465, 63, 449466, 65, + 449469, 63, 449470, 62, 449471, 62, 449472, 19, 449473, 19, 449474, 18, + 449477, 16, 449478, 18, 449479, 16, 449480, 19, 449481, 19, 449490, 18, + 449494, 18, 449496, 17, 449498, 17, 449509, 16, 449511, 16, 449513, 15, + 449517, 15, 449526, 14, 449527, 14, 449528, 17, 449529, 15, 449530, 17, + 449533, 15, 449534, 14, 449535, 14, 449728, 19, 449729, 19, 449730, 18, + 449734, 18, 449736, 19, 449737, 19, 449746, 18, 449750, 18, 449752, 17, + 449754, 17, 449784, 17, 449786, 17, 451520, 19, 451521, 19, 451525, 16, + 451527, 16, 451528, 19, 451529, 19, 451557, 16, 451559, 16, 451561, 15, + 451565, 15, 451577, 15, 451581, 15, 451776, 19, 451777, 19, 451784, 19, + 451785, 19, 465858, 18, 465861, 16, 465862, 18, 465863, 16, 465874, 18, + 465878, 18, 465893, 16, 465895, 16, 465910, 14, 465911, 14, 465918, 14, + 465919, 14, 466114, 18, 466118, 18, 466130, 18, 466134, 18, 467909, 16, + 467911, 16, 467941, 16, 467943, 16, 468160, 13, 468161, 13, 468162, 13, + 468163, 13, 468164, 13, 468165, 13, 468166, 13, 468167, 13, 580568, 17, + 580570, 17, 580585, 15, 580589, 15, 580598, 14, 580599, 14, 580600, 17, + 580601, 15, 580602, 17, 580605, 15, 580606, 14, 580607, 14, 580824, 17, + 580826, 17, 580856, 17, 580858, 17, 582633, 15, 582637, 15, 582649, 15, + 582653, 15, 582856, 12, 582857, 12, 582872, 12, 582873, 12, 582888, 12, + 582889, 12, 582904, 12, 582905, 12, 596982, 14, 596983, 14, 596990, 14, + 596991, 14, 597202, 11, 597206, 11, 597210, 11, 597214, 11, 597234, 11, + 597238, 11, 597242, 11, 597246, 11, 599013, 10, 599015, 10, 599021, 10, + 599023, 10, 599029, 10, 599031, 10, 599037, 10, 599039, 10, 599232, 13, + 599233, 13, 599234, 13, 599235, 13, 599236, 13, 599237, 13, 599238, 13, + 599239, 13, 599240, 12, 599241, 12, 599250, 11, 599254, 11, 599256, 12, + 599257, 12, 599258, 11, 599262, 11, 599269, 10, 599271, 10, 599272, 12, + 599273, 12, 599277, 10, 599279, 10, 599282, 11, 599285, 10, 599286, 11, + 599287, 10, 599288, 12, 599289, 12, 599290, 11, 599293, 10, 599294, 11, + 599295, 10, + ], + p2D: [ + 0, 0, 1, -1, 0, 0, -1, 1, 0, 2, 1, 1, 1, 2, 2, 0, 1, 2, 0, 2, 1, 0, 0, 0, + ], + p3D: [ + 0, 0, 1, -1, 0, 0, 1, 0, -1, 0, 0, -1, 1, 0, 0, 0, 1, -1, 0, 0, -1, 0, 1, + 0, 0, -1, 1, 0, 2, 1, 1, 0, 1, 1, 1, -1, 0, 2, 1, 0, 1, 1, 1, -1, 1, 0, 2, + 0, 1, 1, 1, -1, 1, 1, 1, 3, 2, 1, 0, 3, 1, 2, 0, 1, 3, 2, 0, 1, 3, 1, 0, + 2, 1, 3, 0, 2, 1, 3, 0, 1, 2, 1, 1, 1, 0, 0, 2, 2, 0, 0, 1, 1, 0, 1, 0, 2, + 0, 2, 0, 1, 1, 0, 0, 1, 2, 0, 0, 2, 2, 0, 0, 0, 0, 1, 1, -1, 1, 2, 0, 0, + 0, 0, 1, -1, 1, 1, 2, 0, 0, 0, 0, 1, 1, 1, -1, 2, 3, 1, 1, 1, 2, 0, 0, 2, + 2, 3, 1, 1, 1, 2, 2, 0, 0, 2, 3, 1, 1, 1, 2, 0, 2, 0, 2, 1, 1, -1, 1, 2, + 0, 0, 2, 2, 1, 1, -1, 1, 2, 2, 0, 0, 2, 1, -1, 1, 1, 2, 0, 0, 2, 2, 1, -1, + 1, 1, 2, 0, 2, 0, 2, 1, 1, 1, -1, 2, 2, 0, 0, 2, 1, 1, 1, -1, 2, 0, 2, 0, + ], + p4D: [ + 0, 0, 1, -1, 0, 0, 0, 1, 0, -1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 1, 0, 0, 0, + 0, 1, -1, 0, 0, 0, 1, 0, -1, 0, 0, -1, 0, 1, 0, 0, 0, -1, 1, 0, 0, 0, 0, + 1, -1, 0, 0, -1, 0, 0, 1, 0, 0, -1, 0, 1, 0, 0, 0, -1, 1, 0, 2, 1, 1, 0, + 0, 1, 1, 1, -1, 0, 1, 1, 1, 0, -1, 0, 2, 1, 0, 1, 0, 1, 1, -1, 1, 0, 1, 1, + 0, 1, -1, 0, 2, 0, 1, 1, 0, 1, -1, 1, 1, 0, 1, 0, 1, 1, -1, 0, 2, 1, 0, 0, + 1, 1, 1, -1, 0, 1, 1, 1, 0, -1, 1, 0, 2, 0, 1, 0, 1, 1, -1, 1, 0, 1, 1, 0, + 1, -1, 1, 0, 2, 0, 0, 1, 1, 1, -1, 0, 1, 1, 1, 0, -1, 1, 1, 1, 4, 2, 1, 1, + 0, 4, 1, 2, 1, 0, 4, 1, 1, 2, 0, 1, 4, 2, 1, 0, 1, 4, 1, 2, 0, 1, 4, 1, 1, + 0, 2, 1, 4, 2, 0, 1, 1, 4, 1, 0, 2, 1, 4, 1, 0, 1, 2, 1, 4, 0, 2, 1, 1, 4, + 0, 1, 2, 1, 4, 0, 1, 1, 2, 1, 2, 1, 1, 0, 0, 3, 2, 1, 0, 0, 3, 1, 2, 0, 0, + 1, 2, 1, 0, 1, 0, 3, 2, 0, 1, 0, 3, 1, 0, 2, 0, 1, 2, 0, 1, 1, 0, 3, 0, 2, + 1, 0, 3, 0, 1, 2, 0, 1, 2, 1, 0, 0, 1, 3, 2, 0, 0, 1, 3, 1, 0, 0, 2, 1, 2, + 0, 1, 0, 1, 3, 0, 2, 0, 1, 3, 0, 1, 0, 2, 1, 2, 0, 0, 1, 1, 3, 0, 0, 2, 1, + 3, 0, 0, 1, 2, 2, 3, 1, 1, 1, 0, 2, 1, 1, 1, -1, 2, 2, 0, 0, 0, 2, 3, 1, + 1, 0, 1, 2, 1, 1, -1, 1, 2, 2, 0, 0, 0, 2, 3, 1, 0, 1, 1, 2, 1, -1, 1, 1, + 2, 2, 0, 0, 0, 2, 3, 1, 1, 1, 0, 2, 1, 1, 1, -1, 2, 0, 2, 0, 0, 2, 3, 1, + 1, 0, 1, 2, 1, 1, -1, 1, 2, 0, 2, 0, 0, 2, 3, 0, 1, 1, 1, 2, -1, 1, 1, 1, + 2, 0, 2, 0, 0, 2, 3, 1, 1, 1, 0, 2, 1, 1, 1, -1, 2, 0, 0, 2, 0, 2, 3, 1, + 0, 1, 1, 2, 1, -1, 1, 1, 2, 0, 0, 2, 0, 2, 3, 0, 1, 1, 1, 2, -1, 1, 1, 1, + 2, 0, 0, 2, 0, 2, 3, 1, 1, 0, 1, 2, 1, 1, -1, 1, 2, 0, 0, 0, 2, 2, 3, 1, + 0, 1, 1, 2, 1, -1, 1, 1, 2, 0, 0, 0, 2, 2, 3, 0, 1, 1, 1, 2, -1, 1, 1, 1, + 2, 0, 0, 0, 2, 2, 1, 1, 1, -1, 0, 1, 1, 1, 0, -1, 0, 0, 0, 0, 0, 2, 1, 1, + -1, 1, 0, 1, 1, 0, 1, -1, 0, 0, 0, 0, 0, 2, 1, -1, 1, 1, 0, 1, 0, 1, 1, + -1, 0, 0, 0, 0, 0, 2, 1, 1, -1, 0, 1, 1, 1, 0, -1, 1, 0, 0, 0, 0, 0, 2, 1, + -1, 1, 0, 1, 1, 0, 1, -1, 1, 0, 0, 0, 0, 0, 2, 1, -1, 0, 1, 1, 1, 0, -1, + 1, 1, 0, 0, 0, 0, 0, 2, 1, 1, 1, -1, 0, 1, 1, 1, 0, -1, 2, 2, 0, 0, 0, 2, + 1, 1, -1, 1, 0, 1, 1, 0, 1, -1, 2, 2, 0, 0, 0, 2, 1, 1, -1, 0, 1, 1, 1, 0, + -1, 1, 2, 2, 0, 0, 0, 2, 1, 1, 1, -1, 0, 1, 1, 1, 0, -1, 2, 0, 2, 0, 0, 2, + 1, -1, 1, 1, 0, 1, 0, 1, 1, -1, 2, 0, 2, 0, 0, 2, 1, -1, 1, 0, 1, 1, 0, 1, + -1, 1, 2, 0, 2, 0, 0, 2, 1, 1, -1, 1, 0, 1, 1, 0, 1, -1, 2, 0, 0, 2, 0, 2, + 1, -1, 1, 1, 0, 1, 0, 1, 1, -1, 2, 0, 0, 2, 0, 2, 1, -1, 0, 1, 1, 1, 0, + -1, 1, 1, 2, 0, 0, 2, 0, 2, 1, 1, -1, 0, 1, 1, 1, 0, -1, 1, 2, 0, 0, 0, 2, + 2, 1, -1, 1, 0, 1, 1, 0, 1, -1, 1, 2, 0, 0, 0, 2, 2, 1, -1, 0, 1, 1, 1, 0, + -1, 1, 1, 2, 0, 0, 0, 2, 3, 1, 1, 0, 0, 0, 2, 2, 0, 0, 0, 2, 1, 1, 1, -1, + 3, 1, 0, 1, 0, 0, 2, 0, 2, 0, 0, 2, 1, 1, 1, -1, 3, 1, 0, 0, 1, 0, 2, 0, + 0, 2, 0, 2, 1, 1, 1, -1, 3, 1, 1, 0, 0, 0, 2, 2, 0, 0, 0, 2, 1, 1, -1, 1, + 3, 1, 0, 1, 0, 0, 2, 0, 2, 0, 0, 2, 1, 1, -1, 1, 3, 1, 0, 0, 0, 1, 2, 0, + 0, 0, 2, 2, 1, 1, -1, 1, 3, 1, 1, 0, 0, 0, 2, 2, 0, 0, 0, 2, 1, -1, 1, 1, + 3, 1, 0, 0, 1, 0, 2, 0, 0, 2, 0, 2, 1, -1, 1, 1, 3, 1, 0, 0, 0, 1, 2, 0, + 0, 0, 2, 2, 1, -1, 1, 1, 3, 1, 0, 1, 0, 0, 2, 0, 2, 0, 0, 2, -1, 1, 1, 1, + 3, 1, 0, 0, 1, 0, 2, 0, 0, 2, 0, 2, -1, 1, 1, 1, 3, 1, 0, 0, 0, 1, 2, 0, + 0, 0, 2, 2, -1, 1, 1, 1, 3, 3, 2, 1, 0, 0, 3, 1, 2, 0, 0, 4, 1, 1, 1, 1, + 3, 3, 2, 0, 1, 0, 3, 1, 0, 2, 0, 4, 1, 1, 1, 1, 3, 3, 0, 2, 1, 0, 3, 0, 1, + 2, 0, 4, 1, 1, 1, 1, 3, 3, 2, 0, 0, 1, 3, 1, 0, 0, 2, 4, 1, 1, 1, 1, 3, 3, + 0, 2, 0, 1, 3, 0, 1, 0, 2, 4, 1, 1, 1, 1, 3, 3, 0, 0, 2, 1, 3, 0, 0, 1, 2, + 4, 1, 1, 1, 1, 3, 3, 2, 1, 0, 0, 3, 1, 2, 0, 0, 2, 1, 1, 1, -1, 3, 3, 2, + 0, 1, 0, 3, 1, 0, 2, 0, 2, 1, 1, 1, -1, 3, 3, 0, 2, 1, 0, 3, 0, 1, 2, 0, + 2, 1, 1, 1, -1, 3, 3, 2, 1, 0, 0, 3, 1, 2, 0, 0, 2, 1, 1, -1, 1, 3, 3, 2, + 0, 0, 1, 3, 1, 0, 0, 2, 2, 1, 1, -1, 1, 3, 3, 0, 2, 0, 1, 3, 0, 1, 0, 2, + 2, 1, 1, -1, 1, 3, 3, 2, 0, 1, 0, 3, 1, 0, 2, 0, 2, 1, -1, 1, 1, 3, 3, 2, + 0, 0, 1, 3, 1, 0, 0, 2, 2, 1, -1, 1, 1, 3, 3, 0, 0, 2, 1, 3, 0, 0, 1, 2, + 2, 1, -1, 1, 1, 3, 3, 0, 2, 1, 0, 3, 0, 1, 2, 0, 2, -1, 1, 1, 1, 3, 3, 0, + 2, 0, 1, 3, 0, 1, 0, 2, 2, -1, 1, 1, 1, 3, 3, 0, 0, 2, 1, 3, 0, 0, 1, 2, + 2, -1, 1, 1, 1, + ], + }; + + var Contribution2 = /** @class */ (function () { + function Contribution2(multiplier, xsb, ysb) { + this.dx = -xsb - multiplier * constants_1.SQUISH_2D; + this.dy = -ysb - multiplier * constants_1.SQUISH_2D; + this.xsb = xsb; + this.ysb = ysb; + } + return Contribution2; + })(); + var Contribution3 = /** @class */ (function () { + function Contribution3(multiplier, xsb, ysb, zsb) { + this.dx = -xsb - multiplier * constants_1.SQUISH_3D; + this.dy = -ysb - multiplier * constants_1.SQUISH_3D; + this.dz = -zsb - multiplier * constants_1.SQUISH_3D; + this.xsb = xsb; + this.ysb = ysb; + this.zsb = zsb; + } + return Contribution3; + })(); + var Contribution4 = /** @class */ (function () { + function Contribution4(multiplier, xsb, ysb, zsb, wsb) { + this.dx = -xsb - multiplier * constants_1.SQUISH_4D; + this.dy = -ysb - multiplier * constants_1.SQUISH_4D; + this.dz = -zsb - multiplier * constants_1.SQUISH_4D; + this.dw = -wsb - multiplier * constants_1.SQUISH_4D; + this.xsb = xsb; + this.ysb = ysb; + this.zsb = zsb; + this.wsb = wsb; + } + return Contribution4; + })(); + function shuffleSeed(seed) { + var newSeed = new Uint32Array(1); + newSeed[0] = seed[0] * 1664525 + 1013904223; + return newSeed; + } + OpenSimplexNoise = /** @class */ (function () { + function OpenSimplexNoise(clientSeed) { + this.initialize(); + this.perm = new Uint8Array(256); + this.perm2D = new Uint8Array(256); + this.perm3D = new Uint8Array(256); + this.perm4D = new Uint8Array(256); + var source = new Uint8Array(256); + for (var i = 0; i < 256; i++) source[i] = i; + var seed = new Uint32Array(1); + seed[0] = clientSeed; + seed = shuffleSeed(shuffleSeed(shuffleSeed(seed))); + for (var i = 255; i >= 0; i--) { + seed = shuffleSeed(seed); + var r = new Uint32Array(1); + r[0] = (seed[0] + 31) % (i + 1); + if (r[0] < 0) r[0] += i + 1; + this.perm[i] = source[r[0]]; + this.perm2D[i] = this.perm[i] & 0x0e; + this.perm3D[i] = (this.perm[i] % 24) * 3; + this.perm4D[i] = this.perm[i] & 0xfc; + source[r[0]] = source[i]; + } + } + OpenSimplexNoise.prototype.array2D = function (width, height) { + var output = new Array(width); + for (var x = 0; x < width; x++) { + output[x] = new Array(height); + for (var y = 0; y < height; y++) { + output[x][y] = this.noise2D(x, y); + } + } + return output; + }; + OpenSimplexNoise.prototype.array3D = function (width, height, depth) { + var output = new Array(width); + for (var x = 0; x < width; x++) { + output[x] = new Array(height); + for (var y = 0; y < height; y++) { + output[x][y] = new Array(depth); + for (var z = 0; z < depth; z++) { + output[x][y][z] = this.noise3D(x, y, z); + } + } + } + return output; + }; + OpenSimplexNoise.prototype.array4D = function ( + width, + height, + depth, + wLength + ) { + var output = new Array(width); + for (var x = 0; x < width; x++) { + output[x] = new Array(height); + for (var y = 0; y < height; y++) { + output[x][y] = new Array(depth); + for (var z = 0; z < depth; z++) { + output[x][y][z] = new Array(wLength); + for (var w = 0; w < wLength; w++) { + output[x][y][z][w] = this.noise4D(x, y, z, w); + } + } + } + } + return output; + }; + OpenSimplexNoise.prototype.noise2D = function (x, y) { + var stretchOffset = (x + y) * constants_1.STRETCH_2D; + var xs = x + stretchOffset; + var ys = y + stretchOffset; + var xsb = Math.floor(xs); + var ysb = Math.floor(ys); + var squishOffset = (xsb + ysb) * constants_1.SQUISH_2D; + var dx0 = x - (xsb + squishOffset); + var dy0 = y - (ysb + squishOffset); + var xins = xs - xsb; + var yins = ys - ysb; + var inSum = xins + yins; + var hash = + (xins - yins + 1) | + (inSum << 1) | + ((inSum + yins) << 2) | + ((inSum + xins) << 4); + var value = 0; + for (var c = this.lookup2D[hash]; c !== undefined; c = c.next) { + var dx = dx0 + c.dx; + var dy = dy0 + c.dy; + var attn = 2 - dx * dx - dy * dy; + if (attn > 0) { + var px = xsb + c.xsb; + var py = ysb + c.ysb; + var indexPartA = this.perm[px & 0xff]; + var index = this.perm2D[(indexPartA + py) & 0xff]; + var valuePart = + constants_1.gradients2D[index] * dx + + constants_1.gradients2D[index + 1] * dy; + value += attn * attn * attn * attn * valuePart; + } + } + return value * constants_1.NORM_2D; + }; + OpenSimplexNoise.prototype.noise3D = function (x, y, z) { + var stretchOffset = (x + y + z) * constants_1.STRETCH_3D; + var xs = x + stretchOffset; + var ys = y + stretchOffset; + var zs = z + stretchOffset; + var xsb = Math.floor(xs); + var ysb = Math.floor(ys); + var zsb = Math.floor(zs); + var squishOffset = (xsb + ysb + zsb) * constants_1.SQUISH_3D; + var dx0 = x - (xsb + squishOffset); + var dy0 = y - (ysb + squishOffset); + var dz0 = z - (zsb + squishOffset); + var xins = xs - xsb; + var yins = ys - ysb; + var zins = zs - zsb; + var inSum = xins + yins + zins; + var hash = + (yins - zins + 1) | + ((xins - yins + 1) << 1) | + ((xins - zins + 1) << 2) | + (inSum << 3) | + ((inSum + zins) << 5) | + ((inSum + yins) << 7) | + ((inSum + xins) << 9); + var value = 0; + for (var c = this.lookup3D[hash]; c !== undefined; c = c.next) { + var dx = dx0 + c.dx; + var dy = dy0 + c.dy; + var dz = dz0 + c.dz; + var attn = 2 - dx * dx - dy * dy - dz * dz; + if (attn > 0) { + var px = xsb + c.xsb; + var py = ysb + c.ysb; + var pz = zsb + c.zsb; + var indexPartA = this.perm[px & 0xff]; + var indexPartB = this.perm[(indexPartA + py) & 0xff]; + var index = this.perm3D[(indexPartB + pz) & 0xff]; + var valuePart = + constants_1.gradients3D[index] * dx + + constants_1.gradients3D[index + 1] * dy + + constants_1.gradients3D[index + 2] * dz; + value += attn * attn * attn * attn * valuePart; + } + } + return value * constants_1.NORM_3D; + }; + OpenSimplexNoise.prototype.noise4D = function (x, y, z, w) { + var stretchOffset = (x + y + z + w) * constants_1.STRETCH_4D; + var xs = x + stretchOffset; + var ys = y + stretchOffset; + var zs = z + stretchOffset; + var ws = w + stretchOffset; + var xsb = Math.floor(xs); + var ysb = Math.floor(ys); + var zsb = Math.floor(zs); + var wsb = Math.floor(ws); + var squishOffset = (xsb + ysb + zsb + wsb) * constants_1.SQUISH_4D; + var dx0 = x - (xsb + squishOffset); + var dy0 = y - (ysb + squishOffset); + var dz0 = z - (zsb + squishOffset); + var dw0 = w - (wsb + squishOffset); + var xins = xs - xsb; + var yins = ys - ysb; + var zins = zs - zsb; + var wins = ws - wsb; + var inSum = xins + yins + zins + wins; + var hash = + (zins - wins + 1) | + ((yins - zins + 1) << 1) | + ((yins - wins + 1) << 2) | + ((xins - yins + 1) << 3) | + ((xins - zins + 1) << 4) | + ((xins - wins + 1) << 5) | + (inSum << 6) | + ((inSum + wins) << 8) | + ((inSum + zins) << 11) | + ((inSum + yins) << 14) | + ((inSum + xins) << 17); + var value = 0; + for (var c = this.lookup4D[hash]; c !== undefined; c = c.next) { + var dx = dx0 + c.dx; + var dy = dy0 + c.dy; + var dz = dz0 + c.dz; + var dw = dw0 + c.dw; + var attn = 2 - dx * dx - dy * dy - dz * dz - dw * dw; + if (attn > 0) { + var px = xsb + c.xsb; + var py = ysb + c.ysb; + var pz = zsb + c.zsb; + var pw = wsb + c.wsb; + var indexPartA = this.perm[px & 0xff]; + var indexPartB = this.perm[(indexPartA + py) & 0xff]; + var indexPartC = this.perm[(indexPartB + pz) & 0xff]; + var index = this.perm4D[(indexPartC + pw) & 0xff]; + var valuePart = + constants_1.gradients4D[index] * dx + + constants_1.gradients4D[index + 1] * dy + + constants_1.gradients4D[index + 2] * dz + + constants_1.gradients4D[index + 3] * dw; + value += attn * attn * attn * attn * valuePart; + } + } + return value * constants_1.NORM_4D; + }; + OpenSimplexNoise.prototype.initialize = function () { + var contributions2D = []; + for (var i = 0; i < constants_1.p2D.length; i += 4) { + var baseSet = constants_1.base2D[constants_1.p2D[i]]; + var previous = null; + var current = null; + for (var k = 0; k < baseSet.length; k += 3) { + current = new Contribution2( + baseSet[k], + baseSet[k + 1], + baseSet[k + 2] + ); + if (previous === null) contributions2D[i / 4] = current; + else previous.next = current; + previous = current; + } + current.next = new Contribution2( + constants_1.p2D[i + 1], + constants_1.p2D[i + 2], + constants_1.p2D[i + 3] + ); + } + this.lookup2D = []; + for (var i = 0; i < constants_1.lookupPairs2D.length; i += 2) { + this.lookup2D[constants_1.lookupPairs2D[i]] = + contributions2D[constants_1.lookupPairs2D[i + 1]]; + } + var contributions3D = []; + for (var i = 0; i < constants_1.p3D.length; i += 9) { + var baseSet = constants_1.base3D[constants_1.p3D[i]]; + var previous = null; + var current = null; + for (var k = 0; k < baseSet.length; k += 4) { + current = new Contribution3( + baseSet[k], + baseSet[k + 1], + baseSet[k + 2], + baseSet[k + 3] + ); + if (previous === null) contributions3D[i / 9] = current; + else previous.next = current; + previous = current; + } + current.next = new Contribution3( + constants_1.p3D[i + 1], + constants_1.p3D[i + 2], + constants_1.p3D[i + 3], + constants_1.p3D[i + 4] + ); + current.next.next = new Contribution3( + constants_1.p3D[i + 5], + constants_1.p3D[i + 6], + constants_1.p3D[i + 7], + constants_1.p3D[i + 8] + ); + } + this.lookup3D = []; + for (var i = 0; i < constants_1.lookupPairs3D.length; i += 2) { + this.lookup3D[constants_1.lookupPairs3D[i]] = + contributions3D[constants_1.lookupPairs3D[i + 1]]; + } + var contributions4D = []; + for (var i = 0; i < constants_1.p4D.length; i += 16) { + var baseSet = constants_1.base4D[constants_1.p4D[i]]; + var previous = null; + var current = null; + for (var k = 0; k < baseSet.length; k += 5) { + current = new Contribution4( + baseSet[k], + baseSet[k + 1], + baseSet[k + 2], + baseSet[k + 3], + baseSet[k + 4] + ); + if (previous === null) contributions4D[i / 16] = current; + else previous.next = current; + previous = current; + } + current.next = new Contribution4( + constants_1.p4D[i + 1], + constants_1.p4D[i + 2], + constants_1.p4D[i + 3], + constants_1.p4D[i + 4], + constants_1.p4D[i + 5] + ); + current.next.next = new Contribution4( + constants_1.p4D[i + 6], + constants_1.p4D[i + 7], + constants_1.p4D[i + 8], + constants_1.p4D[i + 9], + constants_1.p4D[i + 10] + ); + current.next.next.next = new Contribution4( + constants_1.p4D[i + 11], + constants_1.p4D[i + 12], + constants_1.p4D[i + 13], + constants_1.p4D[i + 14], + constants_1.p4D[i + 15] + ); + } + this.lookup4D = []; + for (var i = 0; i < constants_1.lookupPairs4D.length; i += 2) { + this.lookup4D[constants_1.lookupPairs4D[i]] = + contributions4D[constants_1.lookupPairs4D[i + 1]]; + } + }; + return OpenSimplexNoise; + })(); +})(); diff --git a/hackx/sections.js b/hackx/sections.js new file mode 100644 index 00000000..ac3b1bbd --- /dev/null +++ b/hackx/sections.js @@ -0,0 +1,2344 @@ +// sections.js — Canvas animations and interactivity for Hack X sections +(function () { + "use strict"; + + const RED = "#4fd1d9"; + const DARK = "#0a1628"; + const RED_RGB = [79, 209, 217]; + const CRT_CURVATURE = 4.5; // lower = curvier + const CURSOR_BG = DARK; + const CURSOR_FG = RED; + const CURSOR_STROKE_PX = 3; + const CURSOR_SCALE = 1.2; + const CURSOR_ROT = -Math.PI / 5; + const CURSOR_OFFSET = 10; + + function shouldUseCrtCurvature() { + // Reuse the project's canonical detector from `utils.js` when available. + if (typeof window.isTouchScreenDevice === "function") { + return !window.isTouchScreenDevice(); + } + + // Fallback: avoid disabling on desktops that expose touch APIs. + const coarsePointer = + window.matchMedia && window.matchMedia("(pointer:coarse)").matches; + return !coarsePointer; + } + + function shouldUseCustomCursor() { + // Skip on touch/coarse pointer devices. + if (typeof window.isTouchScreenDevice === "function") { + return !window.isTouchScreenDevice(); + } + const coarsePointer = + window.matchMedia && window.matchMedia("(pointer:coarse)").matches; + return !coarsePointer; + } + + function initGlobalCustomCursor() { + if (!shouldUseCustomCursor()) return; + + const cursorCanvas = document.createElement("canvas"); + cursorCanvas.id = "global-custom-cursor"; + cursorCanvas.style.cssText = ` + position: fixed; + top: 0; left: 0; + width: 100vw; height: 100vh; + z-index: 9998; + pointer-events: none; + `; + document.body.appendChild(cursorCanvas); + + const ctx = cursorCanvas.getContext("2d"); + if (!ctx) { + cursorCanvas.remove(); + return; + } + + function resize() { + const dpr = window.devicePixelRatio || 1; + cursorCanvas.width = Math.max(1, Math.floor(window.innerWidth * dpr)); + cursorCanvas.height = Math.max(1, Math.floor(window.innerHeight * dpr)); + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + } + resize(); + window.addEventListener("resize", resize); + + let mouseX = -9999; + let mouseY = -9999; + let hide = false; + + function isInsideHero(x, y) { + const hero = document.getElementById("hero-section"); + if (!hero) return false; + const r = hero.getBoundingClientRect(); + return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom; + } + + window.addEventListener( + "mousemove", + (e) => { + mouseX = e.clientX; + mouseY = e.clientY; + hide = isInsideHero(mouseX, mouseY); + }, + { passive: true }, + ); + + window.addEventListener( + "mouseleave", + () => { + mouseX = -9999; + mouseY = -9999; + }, + { passive: true }, + ); + + function draw() { + ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); + if (!hide && mouseX >= 0 && mouseY >= 0) { + ctx.save(); + ctx.translate(mouseX + CURSOR_OFFSET, mouseY + CURSOR_OFFSET); + ctx.scale(CURSOR_SCALE, CURSOR_SCALE); + ctx.rotate(CURSOR_ROT); + + ctx.fillStyle = CURSOR_BG; + ctx.strokeStyle = CURSOR_FG; + ctx.lineWidth = CURSOR_STROKE_PX; + ctx.beginPath(); + ctx.moveTo(0, -10); + ctx.lineTo(7.5, 10); + ctx.lineTo(0, 5); + ctx.lineTo(-7.5, 10); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + ctx.restore(); + } + requestAnimationFrame(draw); + } + requestAnimationFrame(draw); + } + + function initGlobalCrtCurvatureForSectionCanvases() { + if (!shouldUseCrtCurvature()) return; + + // Signal to the p5 layer that global CRT is active (so it can skip its own CRT pass). + window.__HACKX_GLOBAL_CRT__ = true; + + let sourceCanvases = []; + function collectSourceCanvases() { + // Include the p5 hero canvas too so the curvature feels like one continuous screen. + const all = Array.from( + document.querySelectorAll(".section canvas, #footer-section canvas"), + ).filter((c) => !(c instanceof HTMLCanvasElement ? c.classList.contains("crt-global-canvas") : false)); + sourceCanvases = all; + sourceCanvases.forEach((c) => { + // Hide all source canvases; the global CRT canvas will draw them instead. + if (!c.classList.contains("crt-global-canvas")) c.style.opacity = "0"; + }); + } + + collectSourceCanvases(); + + const glCanvas = document.createElement("canvas"); + glCanvas.className = "crt-global-canvas"; + glCanvas.style.cssText = ` + position: fixed; + top: 0; left: 0; + width: 100vw; height: 100vh; + z-index: 0; + pointer-events: none; + `; + document.body.appendChild(glCanvas); + + const gl = + glCanvas.getContext("webgl", { + alpha: true, + antialias: false, + premultipliedAlpha: true, + preserveDrawingBuffer: false, + }) || glCanvas.getContext("experimental-webgl"); + if (!gl) { + glCanvas.remove(); + return; + } + + // If canvases are created after DOMContentLoaded (p5 does this), observe and collect. + const observer = new MutationObserver(() => collectSourceCanvases()); + observer.observe(document.body, { childList: true, subtree: true }); + + const composeCanvas = document.createElement("canvas"); + const composeCtx = composeCanvas.getContext("2d", { alpha: true }); + // Keep UI (game text/boxes) crisp when compositing/scaling. + composeCtx.imageSmoothingEnabled = false; + + const vertSrc = ` + attribute vec2 a_pos; + varying vec2 v_uv; + void main() { + v_uv = (a_pos + 1.0) * 0.5; + gl_Position = vec4(a_pos, 0.0, 1.0); + } + `; + + const fragSrc = ` + precision mediump float; + varying vec2 v_uv; + uniform sampler2D u_tex; + uniform vec2 u_curvature; + + vec2 curveRemapUV(vec2 uv) { + uv = uv * 2.0 - 1.0; + vec2 offset = abs(uv.yx) / u_curvature; + uv = uv + uv * offset * offset; + uv = uv * 0.5 + 0.5; + return uv; + } + + void main() { + vec2 uv = v_uv; + vec2 remappedUV = curveRemapUV(uv); + if (remappedUV.x < 0.0 || remappedUV.y < 0.0 || remappedUV.x > 1.0 || remappedUV.y > 1.0) { + gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); + return; + } + gl_FragColor = texture2D(u_tex, remappedUV); + } + `; + + function compileShader(type, src) { + const s = gl.createShader(type); + gl.shaderSource(s, src); + gl.compileShader(s); + if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) { + gl.deleteShader(s); + return null; + } + return s; + } + + function createProgram(vsSrc, fsSrc) { + const vs = compileShader(gl.VERTEX_SHADER, vsSrc); + const fs = compileShader(gl.FRAGMENT_SHADER, fsSrc); + if (!vs || !fs) return null; + const p = gl.createProgram(); + gl.attachShader(p, vs); + gl.attachShader(p, fs); + gl.linkProgram(p); + gl.deleteShader(vs); + gl.deleteShader(fs); + if (!gl.getProgramParameter(p, gl.LINK_STATUS)) { + gl.deleteProgram(p); + return null; + } + return p; + } + + const program = createProgram(vertSrc, fragSrc); + if (!program) { + glCanvas.remove(); + sourceCanvases.forEach((c) => (c.style.opacity = "")); + return; + } + + const quad = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, quad); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), + gl.STATIC_DRAW, + ); + + const aPos = gl.getAttribLocation(program, "a_pos"); + const uTex = gl.getUniformLocation(program, "u_tex"); + const uCurv = gl.getUniformLocation(program, "u_curvature"); + + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + // Nearest-neighbor to avoid blurring pixel-ish UI. + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + + function resize() { + const dpr = window.devicePixelRatio || 1; + const w = Math.max(1, Math.floor(window.innerWidth * dpr)); + const h = Math.max(1, Math.floor(window.innerHeight * dpr)); + glCanvas.width = w; + glCanvas.height = h; + composeCanvas.width = w; + composeCanvas.height = h; + gl.viewport(0, 0, w, h); + } + + resize(); + window.addEventListener("resize", resize); + + let lastFrame = 0; + const minFrameMs = 1000 / 30; // cap at ~30fps for perf + const FLAT_CURVATURE = 10000.0; + + function draw(now) { + if (now - lastFrame < minFrameMs) { + requestAnimationFrame(draw); + return; + } + lastFrame = now; + + // Fade curvature to flat as the footer enters the viewport. + // t = 0 -> full curvature, t = 1 -> flat. + let t = 0; + const footer = document.getElementById("footer-section"); + if (footer) { + const r = footer.getBoundingClientRect(); + const fadeStart = window.innerHeight * 0.65; + const fadeEnd = window.innerHeight * 0.15; + if (r.top < fadeStart) { + t = Math.min(1, Math.max(0, (fadeStart - r.top) / (fadeStart - fadeEnd))); + } + } + const curv = CRT_CURVATURE + (FLAT_CURVATURE - CRT_CURVATURE) * t; + + // Composite all section canvases into one viewport-sized canvas in screen space. + composeCtx.clearRect(0, 0, composeCanvas.width, composeCanvas.height); + composeCtx.fillStyle = DARK; + composeCtx.fillRect(0, 0, composeCanvas.width, composeCanvas.height); + const dpr = window.devicePixelRatio || 1; + for (const c of sourceCanvases) { + if (!c || c === glCanvas) continue; + const rect = c.getBoundingClientRect(); + if (rect.width <= 1 || rect.height <= 1) continue; + if (rect.bottom < 0 || rect.top > window.innerHeight) continue; + const dx = rect.left * dpr; + const dy = rect.top * dpr; + const dw = rect.width * dpr; + const dh = rect.height * dpr; + try { + composeCtx.drawImage(c, dx, dy, dw, dh); + } catch { + // Ignore transient drawImage failures (e.g., zero-sized during layout). + } + } + + // Upload composed viewport to WebGL texture and curve it once. + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + gl.RGBA, + gl.UNSIGNED_BYTE, + composeCanvas, + ); + + gl.useProgram(program); + gl.bindBuffer(gl.ARRAY_BUFFER, quad); + gl.enableVertexAttribArray(aPos); + gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.uniform1i(uTex, 0); + gl.uniform2f(uCurv, curv, curv); + + gl.drawArrays(gl.TRIANGLES, 0, 6); + requestAnimationFrame(draw); + } + + requestAnimationFrame(draw); + } + + document.addEventListener("DOMContentLoaded", init); + + function init() { + // Canvas backgrounds (performant, GPU-accelerated) + initAboutCanvas(); + initNexusCanvas(); + initSpectraCanvas(); + initPrizesCanvas(); + initFooterCanvas(); + // Core (one-time CSS transitions, no jank) + initScrollFadeIn(); + initPrizeCounters(); + // initScrollProgressBar(); // removed — was showing blue line at top + initAnnouncements(); + // Clean interactions (pure CSS, no rAF loops) + initHoverLineTrace(); + initRevealOnScroll(); + initTeamGrid(); + initPerksBgCanvas(); + // Easter eggs (hidden, zero perf cost until triggered) + initTripleClick(); + initFooterCopyrightHover(); + initCodecellMatrix(); + initBottomMessage(); + initTenFireworks(); + initTrackTagFlash(); + initIdleMessage(); + initMusicDanceExperience(); + initGlobalCrtCurvatureForSectionCanvases(); + initGlobalCustomCursor(); + } + + // ===== LENIS SMOOTH SCROLL ===== + function initLenis() { + if (typeof Lenis === "undefined") return; + const lenis = new Lenis({ + duration: 2.2, + easing: (t) => 1 - Math.pow(1 - t, 4), + orientation: "vertical", + smoothWheel: true, + wheelMultiplier: 0.7, + touchMultiplier: 1.5, + }); + + function raf(time) { + lenis.raf(time); + requestAnimationFrame(raf); + } + requestAnimationFrame(raf); + + document.querySelectorAll('a[href^="#"]').forEach((anchor) => { + anchor.addEventListener("click", (e) => { + e.preventDefault(); + const target = document.querySelector(anchor.getAttribute("href")); + if (target) lenis.scrollTo(target); + }); + }); + } + + // ===== ABOUT: Binary Rain (SUBTLE — low alpha, slow fall) ===== + function initAboutCanvas() { + const canvas = document.getElementById("about-canvas"); + if (!canvas) return; + const ctx = canvas.getContext("2d"); + const chars = "01アカサタナハマヤラワ{}[]<>/\\|=+-*&^%$#@!HACKXCODECEL"; + let columns, drops; + + function resize() { + canvas.width = canvas.parentElement.offsetWidth; + canvas.height = canvas.parentElement.offsetHeight; + columns = Math.floor(canvas.width / 14); + drops = new Array(columns).fill(1); + } + + function draw() { + ctx.fillStyle = "rgba(10, 22, 40, 0.04)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = "rgba(79, 209, 217, 0.12)"; + ctx.font = "12px Courier New"; + + for (let i = 0; i < drops.length; i++) { + const char = chars[Math.floor(Math.random() * chars.length)]; + ctx.fillText(char, i * 14, drops[i] * 14); + if (drops[i] * 14 > canvas.height && Math.random() > 0.985) + drops[i] = 0; + if (Math.random() > 0.3) drops[i]++; + } + requestAnimationFrame(draw); + } + + resize(); + window.addEventListener("resize", resize); + draw(); + } + + // ===== NEXUS: Neural Network Nodes ===== + function initNexusCanvas() { + const canvas = document.getElementById("nexus-canvas"); + if (!canvas) return; + const ctx = canvas.getContext("2d"); + let nodes = []; + const NODE_COUNT = 25; + + function resize() { + canvas.width = canvas.parentElement.offsetWidth; + canvas.height = canvas.parentElement.offsetHeight; + nodes = []; + for (let i = 0; i < NODE_COUNT; i++) { + nodes.push({ + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + vx: (Math.random() - 0.5) * 0.5, + vy: (Math.random() - 0.5) * 0.5, + radius: Math.random() * 3 + 1, + pulse: Math.random() * Math.PI * 2, + }); + } + } + + function draw() { + ctx.fillStyle = "rgba(10, 22, 40, 0.15)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + for (let i = 0; i < nodes.length; i++) { + for (let j = i + 1; j < nodes.length; j++) { + const dx = nodes[i].x - nodes[j].x; + const dy = nodes[i].y - nodes[j].y; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < 120) { + const alpha = (1 - dist / 120) * 0.3; + ctx.strokeStyle = `rgba(${RED_RGB.join(",")}, ${alpha})`; + ctx.lineWidth = 0.5; + ctx.beginPath(); + ctx.moveTo(nodes[i].x, nodes[i].y); + ctx.lineTo(nodes[j].x, nodes[j].y); + ctx.stroke(); + } + } + } + + nodes.forEach((n) => { + n.x += n.vx; + n.y += n.vy; + n.pulse += 0.03; + if (n.x < 0 || n.x > canvas.width) n.vx *= -1; + if (n.y < 0 || n.y > canvas.height) n.vy *= -1; + + const glow = Math.sin(n.pulse) * 0.3 + 0.4; + ctx.beginPath(); + ctx.arc(n.x, n.y, n.radius + Math.sin(n.pulse) * 1, 0, Math.PI * 2); + ctx.fillStyle = `rgba(${RED_RGB.join(",")}, ${glow})`; + ctx.fill(); + }); + + requestAnimationFrame(draw); + } + + resize(); + window.addEventListener("resize", resize); + draw(); + } + + // ===== SPECTRA: Orbiting Blockchain Nodes ===== + function initSpectraCanvas() { + const canvas = document.getElementById("spectra-canvas"); + if (!canvas) return; + const ctx = canvas.getContext("2d"); + let rings = []; + let time = 0; + + function resize() { + canvas.width = canvas.parentElement.offsetWidth; + canvas.height = canvas.parentElement.offsetHeight; + const cx = canvas.width / 2; + const cy = canvas.height / 2; + const maxR = Math.min(canvas.width, canvas.height) * 0.4; + rings = []; + for (let i = 0; i < 4; i++) { + const r = maxR * (0.3 + i * 0.2); + const nodeCount = 3 + i * 2; + const ringNodes = []; + for (let j = 0; j < nodeCount; j++) { + ringNodes.push({ + angle: (j / nodeCount) * Math.PI * 2, + speed: (0.005 + i * 0.002) * (i % 2 === 0 ? 1 : -1), + }); + } + rings.push({ radius: r, nodes: ringNodes, cx, cy }); + } + } + + function draw() { + ctx.fillStyle = "rgba(10, 22, 40, 0.1)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + time += 0.01; + + rings.forEach((ring, ri) => { + ctx.strokeStyle = `rgba(${RED_RGB.join(",")}, 0.08)`; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.arc(ring.cx, ring.cy, ring.radius, 0, Math.PI * 2); + ctx.stroke(); + + ring.nodes.forEach((n) => { + n.angle += n.speed; + const x = ring.cx + Math.cos(n.angle) * ring.radius; + const y = ring.cy + Math.sin(n.angle) * ring.radius; + + ctx.beginPath(); + ctx.arc(x, y, 3, 0, Math.PI * 2); + ctx.fillStyle = `rgba(${RED_RGB.join(",")}, 0.6)`; + ctx.fill(); + + ctx.beginPath(); + ctx.arc(x, y, 8, 0, Math.PI * 2); + ctx.fillStyle = `rgba(${RED_RGB.join(",")}, 0.1)`; + ctx.fill(); + }); + + if (ri < rings.length - 1) { + const nextRing = rings[ri + 1]; + ring.nodes.forEach((n1) => { + const x1 = ring.cx + Math.cos(n1.angle) * ring.radius; + const y1 = ring.cy + Math.sin(n1.angle) * ring.radius; + nextRing.nodes.forEach((n2) => { + const x2 = nextRing.cx + Math.cos(n2.angle) * nextRing.radius; + const y2 = nextRing.cy + Math.sin(n2.angle) * nextRing.radius; + const dist = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); + if (dist < 100) { + ctx.strokeStyle = `rgba(${RED_RGB.join(",")}, ${(1 - dist / 100) * 0.15})`; + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + } + }); + }); + } + }); + + requestAnimationFrame(draw); + } + + resize(); + window.addEventListener("resize", resize); + draw(); + } + + // ===== PRIZES: Floating ASCII ===== + function initPrizesCanvas() { + const canvas = document.getElementById("prizes-canvas"); + if (!canvas) return; + const ctx = canvas.getContext("2d"); + const symbols = ["$", "★", "◆", "▲", "●", "✦", "⬡", "0", "1"]; + let floaters = []; + + function resize() { + canvas.width = canvas.parentElement.offsetWidth; + canvas.height = canvas.parentElement.offsetHeight; + floaters = []; + for (let i = 0; i < 40; i++) { + floaters.push({ + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + vy: -(Math.random() * 0.3 + 0.1), + char: symbols[Math.floor(Math.random() * symbols.length)], + alpha: Math.random() * 0.15 + 0.05, + size: Math.random() * 14 + 8, + drift: (Math.random() - 0.5) * 0.3, + }); + } + } + + function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + floaters.forEach((f) => { + f.y += f.vy; + f.x += f.drift; + if (f.y < -20) { + f.y = canvas.height + 20; + f.x = Math.random() * canvas.width; + } + if (f.x < -20) f.x = canvas.width + 20; + if (f.x > canvas.width + 20) f.x = -20; + + ctx.font = `${f.size}px Courier New`; + ctx.fillStyle = `rgba(${RED_RGB.join(",")}, ${f.alpha})`; + ctx.fillText(f.char, f.x, f.y); + }); + + requestAnimationFrame(draw); + } + + resize(); + window.addEventListener("resize", resize); + draw(); + } + + // ===== FOOTER: Particle Field ===== + function initFooterCanvas() { + const canvas = document.getElementById("footer-canvas"); + if (!canvas) return; + const ctx = canvas.getContext("2d"); + let particles = []; + + function resize() { + canvas.width = canvas.parentElement.offsetWidth; + canvas.height = canvas.parentElement.offsetHeight; + particles = []; + for (let i = 0; i < 60; i++) { + particles.push({ + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + vx: (Math.random() - 0.5) * 0.3, + vy: (Math.random() - 0.5) * 0.3, + size: Math.random() * 2 + 0.5, + alpha: Math.random() * 0.2 + 0.05, + }); + } + } + + function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + particles.forEach((p) => { + p.x += p.vx; + p.y += p.vy; + if (p.x < 0 || p.x > canvas.width) p.vx *= -1; + if (p.y < 0 || p.y > canvas.height) p.vy *= -1; + + ctx.beginPath(); + ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); + ctx.fillStyle = `rgba(${RED_RGB.join(",")}, ${p.alpha})`; + ctx.fill(); + }); + + requestAnimationFrame(draw); + } + + resize(); + window.addEventListener("resize", resize); + draw(); + } + + // ===== SCROLL FADE-IN (smoother: 0.6s ease-out) ===== + function initScrollFadeIn() { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + entry.target.classList.add("visible"); + } + }); + }, + { threshold: 0.2 }, + ); + + document + .querySelectorAll(".timeline-item, .about-stat") + .forEach((item) => observer.observe(item)); + + document.querySelectorAll(".section-content").forEach((el) => { + el.style.opacity = "0"; + el.style.transform = "translateY(20px)"; + el.style.transition = "opacity 0.6s ease-out, transform 0.6s ease-out"; + }); + + const contentObserver = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + entry.target.style.opacity = "1"; + entry.target.style.transform = "translateY(0)"; + } + }); + }, + { threshold: 0.1 }, + ); + + document + .querySelectorAll(".section-content") + .forEach((el) => contentObserver.observe(el)); + + const scrollHint = document.getElementById("scroll-hint"); + if (scrollHint) { + window.addEventListener( + "scroll", + () => { + scrollHint.style.opacity = Math.max(0, 1 - window.scrollY / 200); + }, + { passive: true }, + ); + } + } + + // ===== PRIZE COUNTERS (easeOutExpo for satisfying deceleration) ===== + function initPrizeCounters() { + const amounts = document.querySelectorAll(".prize-amount, .pool-amount"); + let counted = false; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting && !counted) { + counted = true; + amounts.forEach((el) => { + const target = parseInt(el.dataset.target); + if (target) animateCounter(el, target); + }); + } + }); + }, + { threshold: 0.3 }, + ); + + const prizesSection = document.getElementById("prizes-section"); + if (prizesSection) observer.observe(prizesSection); + } + + function animateCounter(el, target) { + const duration = 1500; + const start = performance.now(); + + function easeOutExpo(t) { + return t >= 1 ? 1 : 1 - Math.pow(2, -10 * t); + } + + function step(now) { + const elapsed = now - start; + const progress = Math.min(elapsed / duration, 1); + const easedProgress = easeOutExpo(progress); + const current = Math.floor(easedProgress * target); + const prefix = el.dataset.prefix || "$"; + el.textContent = prefix + current.toLocaleString("en-IN"); + if (progress < 1) { + requestAnimationFrame(step); + } else { + const prefix = el.dataset.prefix || "$"; + el.textContent = prefix + target.toLocaleString("en-IN"); + } + } + + requestAnimationFrame(step); + } + + // ===== SCROLL PROGRESS BAR ===== + function initScrollProgressBar() { + const bar = document.createElement("div"); + bar.style.cssText = ` + position: fixed; top: 0; left: 0; height: 3px; width: 0%; + background: ${RED}; + z-index: 10000; transition: width 0.1s linear; pointer-events: none; + `; + document.body.appendChild(bar); + + window.addEventListener( + "scroll", + () => { + const scrollTop = window.scrollY; + const docHeight = + document.documentElement.scrollHeight - window.innerHeight; + const progress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0; + bar.style.width = progress + "%"; + }, + { passive: true }, + ); + } + + // ===== ANNOUNCEMENTS ===== + function initAnnouncements() { + if (typeof HACKX_CONFIG === "undefined") return; + const bar = document.getElementById("announcement-bar"); + if (!bar) return; + + function show() { + const msgs = HACKX_CONFIG.announcements; + bar.textContent = msgs[Math.floor(Math.random() * msgs.length)]; + bar.classList.add("visible"); + setTimeout(() => bar.classList.remove("visible"), 5000); + } + + // Show normal announcements as before + setTimeout(show, 30000); + setInterval(show, 30000 + Math.random() * 15000); + + // Show periodic hint every 2 minutes + function showHint() { + bar.textContent = 'Hint: Try dragging over certain numbers to progress in the game!'; + bar.classList.add('visible'); + setTimeout(() => bar.classList.remove('visible'), 7000); + } + setInterval(showHint, 2 * 60 * 1000); // every 2 minutes + } + + // ===== CURSOR GLOW ===== + function initCursorGlow() { + const glow = document.createElement("div"); + glow.id = "cursor-glow"; + glow.style.cssText = ` + position: fixed; pointer-events: none; z-index: 9998; + width: 300px; height: 300px; border-radius: 50%; + background: radial-gradient(circle, rgba(255,23,68,0.06) 0%, transparent 70%); + transform: translate(-50%, -50%); + transition: opacity 0.3s; + `; + document.body.appendChild(glow); + + document.addEventListener("mousemove", (e) => { + glow.style.left = e.clientX + "px"; + glow.style.top = e.clientY + "px"; + }); + + const hero = document.getElementById("hero-section"); + if (hero) { + hero.addEventListener("mouseenter", () => { + glow.style.opacity = "0"; + }); + hero.addEventListener("mouseleave", () => { + glow.style.opacity = "1"; + }); + } + } + + // ===== SUBTLE-BUT-WILD: Section Breathing ===== + // Sections very slightly scale on a slow sine wave — barely perceptible life + function initSectionBreathing() { + const sections = document.querySelectorAll(".section-content"); + if (!sections.length) return; + let time = 0; + + function breathe() { + time += 0.008; + sections.forEach((section, i) => { + const phase = time + i * 0.7; + const scale = 1 + Math.sin(phase) * 0.002; + section.style.transform = + section.style.opacity === "0" + ? section.style.transform + : `scale(${scale})`; + }); + requestAnimationFrame(breathe); + } + + // Delay start so scroll fade-in finishes first + setTimeout(() => requestAnimationFrame(breathe), 2000); + } + + // ===== SUBTLE-BUT-WILD: Border Glow on Scroll Proximity ===== + // Section borders glow brighter as they approach the viewport center + function initBorderGlow() { + const sections = document.querySelectorAll( + ".info-card, .prize-card, .track-card, .track-block, .readout-row, .prize-entry, .timeline-item, .faq-item", + ); + if (!sections.length) return; + + sections.forEach((el) => { + el.style.transition = + "box-shadow 0.4s ease-out, border-color 0.4s ease-out"; + }); + + function update() { + const viewportCenter = window.innerHeight / 2; + sections.forEach((el) => { + const rect = el.getBoundingClientRect(); + const elCenter = rect.top + rect.height / 2; + const distance = Math.abs(elCenter - viewportCenter); + const maxDist = window.innerHeight * 0.6; + const proximity = Math.max(0, 1 - distance / maxDist); + const glowAlpha = proximity * 0.25; + const borderAlpha = 0.08 + proximity * 0.2; + el.style.boxShadow = `0 0 ${proximity * 20}px rgba(79, 209, 217, ${glowAlpha})`; + el.style.borderColor = `rgba(79, 209, 217, ${borderAlpha})`; + }); + requestAnimationFrame(update); + } + + requestAnimationFrame(update); + } + + // ===== SUBTLE-BUT-WILD: Subtle Depth on Mouse ===== + // Content shifts very slightly (max 3px) opposite to mouse, creating depth illusion + function initSubtleDepth() { + const elements = document.querySelectorAll( + ".section-content, .info-card, .prize-card, .track-block", + ); + if (!elements.length) return; + + let mouseX = window.innerWidth / 2; + let mouseY = window.innerHeight / 2; + let targetX = 0; + let targetY = 0; + let currentX = 0; + let currentY = 0; + + document.addEventListener("mousemove", (e) => { + mouseX = e.clientX; + mouseY = e.clientY; + // Normalize to -1..1 from center + const nx = (mouseX / window.innerWidth - 0.5) * 2; + const ny = (mouseY / window.innerHeight - 0.5) * 2; + // Opposite direction, max 3px + targetX = -nx * 3; + targetY = -ny * 3; + }); + + function animate() { + // Smooth interpolation + currentX += (targetX - currentX) * 0.05; + currentY += (targetY - currentY) * 0.05; + + elements.forEach((el) => { + const rect = el.getBoundingClientRect(); + // Only apply to elements in viewport + if (rect.top < window.innerHeight && rect.bottom > 0) { + el.style.translate = `${currentX}px ${currentY}px`; + } + }); + requestAnimationFrame(animate); + } + + requestAnimationFrame(animate); + } + + // ===== SUBTLE-BUT-WILD: Hover Warmth ===== + // Subtle warm glow behind interactive elements on hover — box-shadow only, no transforms + function initHoverWarmth() { + const style = document.createElement("style"); + style.textContent = ` + .hover-warmth { + transition: box-shadow 0.35s ease-out; + } + .hover-warmth:hover { + box-shadow: 0 0 30px rgba(79, 209, 217, 0.12), 0 0 60px rgba(79, 209, 217, 0.06); + } + `; + document.head.appendChild(style); + + document + .querySelectorAll( + ".readout-row, .prize-entry, .timeline-item, .faq-item, .track-block, .info-card, .prize-card", + ) + .forEach((el) => { + el.classList.add("hover-warmth"); + }); + } + + // ===== SUBTLE-BUT-WILD: Timeline Line Draw on Scroll ===== + // The timeline vertical line "draws" downward as you scroll through the section + function initTimelineLineDraw() { + const timelineSection = document.getElementById("timeline-section"); + if (!timelineSection) return; + + const timelineLine = timelineSection.querySelector(".timeline-line"); + if (!timelineLine) return; + + // Store original height, then set to 0 + const computedStyle = window.getComputedStyle(timelineLine); + const fullHeight = timelineLine.offsetHeight || timelineLine.scrollHeight; + timelineLine.style.height = "0px"; + timelineLine.style.transition = "none"; + timelineLine.style.overflow = "visible"; + + function update() { + const rect = timelineSection.getBoundingClientRect(); + const sectionTop = rect.top; + const sectionHeight = rect.height; + const viewportHeight = window.innerHeight; + + // Start drawing when section enters viewport, finish when section leaves + const scrollStart = viewportHeight * 0.8; + const scrollEnd = -sectionHeight * 0.2; + const progress = Math.max( + 0, + Math.min(1, (scrollStart - sectionTop) / (scrollStart - scrollEnd)), + ); + + timelineLine.style.height = progress * fullHeight + "px"; + requestAnimationFrame(update); + } + + requestAnimationFrame(update); + } + + // ===== PERKS BG — Rising particles ===== + function initPerksBgCanvas() { + const canvas = document.getElementById("perks-bg-canvas"); + if (!canvas) return; + const ctx = canvas.getContext("2d"); + let dots = []; + + function resize() { + canvas.width = canvas.parentElement.offsetWidth; + canvas.height = canvas.parentElement.offsetHeight; + dots = []; + for (let i = 0; i < 30; i++) { + dots.push({ + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + vy: -(Math.random() * 0.3 + 0.1), + size: Math.random() * 2 + 1, + alpha: Math.random() * 0.08 + 0.02, + }); + } + } + + function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + dots.forEach((d) => { + d.y += d.vy; + if (d.y < -10) { + d.y = canvas.height + 10; + d.x = Math.random() * canvas.width; + } + ctx.beginPath(); + ctx.arc(d.x, d.y, d.size, 0, Math.PI * 2); + ctx.fillStyle = `rgba(79, 209, 217, ${d.alpha})`; + ctx.fill(); + }); + requestAnimationFrame(draw); + } + + resize(); + window.addEventListener("resize", resize); + draw(); + } + + // ===== (unused) PERKS INTERACTIVE ===== + function initPerksInteractive() { + const canvas = document.getElementById("perks-interactive"); + if (!canvas) return; + const ctx = canvas.getContext("2d"); + const tooltip = document.getElementById("perk-tooltip"); + + const perks = [ + { + label: "₹3L+", + title: "PRIZES", + desc: "Cash prizes across both tracks — first and second place winners", + color: [79, 209, 217], + }, + { + label: "24H", + title: "FOOD & CAFFEINE", + desc: "Meals, snacks, and unlimited coffee for the entire hackathon", + color: [0, 229, 255], + }, + { + label: "1:1", + title: "MENTORSHIP", + desc: "Industry engineers and founders who've built at scale", + color: [124, 77, 255], + }, + { + label: "200+", + title: "NETWORK", + desc: "Builders, designers, and dreamers from across the city", + color: [255, 214, 0], + }, + { + label: "SWAG", + title: "MERCH & STICKERS", + desc: "Exclusive Hack X merch for all participants", + color: [79, 209, 217], + }, + { + label: "CERT", + title: "CERTIFICATES", + desc: "Official CodeCell participation and winner certificates", + color: [0, 229, 255], + }, + ]; + + let nodes = []; + let mouseX = -1000, + mouseY = -1000; + let hoveredNode = null; + let time = 0; + + function resize() { + canvas.width = canvas.parentElement.offsetWidth; + canvas.height = canvas.parentElement.offsetHeight; + const cx = canvas.width / 2; + const cy = canvas.height / 2; + const radius = Math.min(canvas.width, canvas.height) * 0.28; + + nodes = perks.map((p, i) => { + const angle = (i / perks.length) * Math.PI * 2 - Math.PI / 2; + return { + ...p, + baseX: cx + Math.cos(angle) * radius, + baseY: cy + Math.sin(angle) * radius, + x: cx + Math.cos(angle) * radius, + y: cy + Math.sin(angle) * radius, + r: 30, + angleOffset: angle, + orbitSpeed: 0.0003 + i * 0.00008, + floatOffset: Math.random() * Math.PI * 2, + }; + }); + } + + function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + time++; + const cx = canvas.width / 2; + const cy = canvas.height / 2; + const radius = Math.min(canvas.width, canvas.height) * 0.28; + + hoveredNode = null; + + // Update positions with gentle orbit + float + nodes.forEach((n, i) => { + const angle = n.angleOffset + time * n.orbitSpeed; + const float = Math.sin(time * 0.02 + n.floatOffset) * 8; + n.x = cx + Math.cos(angle) * radius + float; + n.y = + cy + + Math.sin(angle) * radius + + Math.cos(time * 0.015 + n.floatOffset) * 6; + + // Check hover + const dx = mouseX - n.x; + const dy = mouseY - n.y; + if (Math.sqrt(dx * dx + dy * dy) < n.r + 15) { + hoveredNode = n; + } + }); + + // Draw connections between all nodes + for (let i = 0; i < nodes.length; i++) { + for (let j = i + 1; j < nodes.length; j++) { + const a = nodes[i], + b = nodes[j]; + const dist = Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2); + const maxDist = radius * 2.2; + if (dist < maxDist) { + const alpha = (1 - dist / maxDist) * 0.12; + ctx.strokeStyle = `rgba(79, 209, 217, ${alpha})`; + ctx.lineWidth = 0.5; + ctx.beginPath(); + ctx.moveTo(a.x, a.y); + ctx.lineTo(b.x, b.y); + ctx.stroke(); + } + } + } + + // Draw connection from mouse to nearby nodes + nodes.forEach((n) => { + const dx = mouseX - n.x; + const dy = mouseY - n.y; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < 200) { + const alpha = (1 - dist / 200) * 0.2; + ctx.strokeStyle = `rgba(${n.color.join(",")}, ${alpha})`; + ctx.lineWidth = 0.8; + ctx.beginPath(); + ctx.moveTo(mouseX, mouseY); + ctx.lineTo(n.x, n.y); + ctx.stroke(); + } + }); + + // Draw nodes + nodes.forEach((n) => { + const isHovered = n === hoveredNode; + const r = isHovered ? n.r + 8 : n.r; + const glowAlpha = isHovered ? 0.3 : 0.08; + + // Outer glow + ctx.beginPath(); + ctx.arc(n.x, n.y, r + 12, 0, Math.PI * 2); + ctx.fillStyle = `rgba(${n.color.join(",")}, ${glowAlpha})`; + ctx.fill(); + + // Circle + ctx.beginPath(); + ctx.arc(n.x, n.y, r, 0, Math.PI * 2); + ctx.fillStyle = `rgba(10, 22, 40, ${isHovered ? 0.95 : 0.8})`; + ctx.fill(); + ctx.strokeStyle = `rgba(${n.color.join(",")}, ${isHovered ? 0.8 : 0.3})`; + ctx.lineWidth = isHovered ? 2 : 1; + ctx.stroke(); + + // Label text + ctx.fillStyle = `rgba(${n.color.join(",")}, ${isHovered ? 1 : 0.7})`; + ctx.font = `bold ${isHovered ? 14 : 11}px 'Courier New', monospace`; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(n.label, n.x, n.y); + }); + + // Tooltip + if (hoveredNode && tooltip) { + tooltip.innerHTML = ` +
${hoveredNode.label}
+
${hoveredNode.title}
+
${hoveredNode.desc}
+ `; + tooltip.classList.add("visible"); + let tx = mouseX + 20; + let ty = mouseY - 20; + // Keep tooltip on screen + const rect = canvas.getBoundingClientRect(); + if (tx + 260 > rect.width) tx = mouseX - 280; + tooltip.style.left = rect.left + tx + "px"; + tooltip.style.top = rect.top + ty + "px"; + } else if (tooltip) { + tooltip.classList.remove("visible"); + } + + requestAnimationFrame(draw); + } + + canvas.addEventListener("mousemove", (e) => { + const rect = canvas.getBoundingClientRect(); + mouseX = e.clientX - rect.left; + mouseY = e.clientY - rect.top; + }); + + canvas.addEventListener("mouseleave", () => { + mouseX = -1000; + mouseY = -1000; + }); + + resize(); + window.addEventListener("resize", resize); + draw(); + } + + // ===== EASTER EGG: Triple-Click Secret Message ===== + function initTripleClick() { + let clickCount = 0; + let clickTimer = null; + + document.addEventListener("click", () => { + clickCount++; + if (clickTimer) clearTimeout(clickTimer); + clickTimer = setTimeout(() => { + clickCount = 0; + }, 400); + + if (clickCount >= 3) { + clickCount = 0; + // Don't show during music dance experience + if (document.documentElement.classList.contains('theme-oscillating')) return; + showFlashMessage("YOU FOUND A SECRET. THE CODE REMEMBERS.", RED); + } + }); + } + + function showFlashMessage(text, color) { + const msg = document.createElement("div"); + msg.textContent = text; + msg.style.cssText = ` + position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); + color: ${color}; font-family: 'Courier New', monospace; font-size: 18px; + z-index: 99999; pointer-events: none; text-align: center; + text-shadow: 0 0 20px ${color}; opacity: 0; + transition: opacity 0.3s ease; padding: 20px; + background: rgba(10, 22, 40, 0.85); border: 1px solid ${color}; + `; + document.body.appendChild(msg); + requestAnimationFrame(() => { + msg.style.opacity = "1"; + }); + setTimeout(() => { + msg.style.opacity = "0"; + setTimeout(() => msg.remove(), 300); + }, 2000); + } + + // ===== EASTER EGG: Footer Copyright Hover (3s) ===== + function initFooterCopyrightHover() { + const copy = document.querySelector(".footer-copy"); + if (!copy) return; + let hoverTimer = null; + const original = copy.textContent; + + copy.addEventListener("mouseenter", () => { + hoverTimer = setTimeout(() => { + copy.textContent = + "BUILT WITH SLEEPLESS NIGHTS AND TOO MUCH COFFEE \u2615"; + copy.style.color = RED; + setTimeout(() => { + copy.textContent = original; + copy.style.color = ""; + }, 3000); + }, 3000); + }); + + copy.addEventListener("mouseleave", () => { + if (hoverTimer) { + clearTimeout(hoverTimer); + hoverTimer = null; + } + }); + } + + // ===== EASTER EGG: CODECELL Matrix Cascade ===== + function initCodecellMatrix() { + const logo = document.querySelector(".footer-logo"); + if (!logo) return; + + logo.style.cursor = "pointer"; + logo.addEventListener("click", () => { + const overlay = document.createElement("canvas"); + overlay.width = window.innerWidth; + overlay.height = window.innerHeight; + overlay.style.cssText = ` + position: fixed; top: 0; left: 0; z-index: 99998; + pointer-events: none; + `; + document.body.appendChild(overlay); + const ctx = overlay.getContext("2d"); + const word = "CODECELL"; + const columns = Math.floor(overlay.width / 20); + const drops = new Array(columns).fill(0); + let frame = 0; + + function draw() { + ctx.fillStyle = "rgba(10, 22, 40, 0.12)"; + ctx.fillRect(0, 0, overlay.width, overlay.height); + ctx.font = "16px Courier New"; + + for (let i = 0; i < drops.length; i++) { + const ch = word[Math.floor(Math.random() * word.length)]; + const alpha = Math.random() * 0.5 + 0.5; + ctx.fillStyle = `rgba(79, 209, 217, ${alpha})`; + ctx.fillText(ch, i * 20, drops[i] * 20); + if (drops[i] * 20 > overlay.height && Math.random() > 0.95) + drops[i] = 0; + drops[i]++; + } + + frame++; + if (frame < 120) { + requestAnimationFrame(draw); + } else { + overlay.style.transition = "opacity 0.5s"; + overlay.style.opacity = "0"; + setTimeout(() => overlay.remove(), 500); + } + } + draw(); + }); + } + + // ===== EASTER EGG: Bottom of Page Message ===== + function initBottomMessage() { + const msg = document.createElement("div"); + msg.innerHTML = + "> YOU'VE REACHED THE END OF THE DATA STREAM. SEE YOU AT HACK X."; + msg.style.cssText = ` + position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); + color: ${RED}; font-family: 'Courier New', monospace; font-size: 14px; + z-index: 99999; pointer-events: none; opacity: 0; + transition: opacity 0.8s ease; text-shadow: 0 0 10px ${RED}; + white-space: nowrap; + `; + document.body.appendChild(msg); + + window.addEventListener( + "scroll", + () => { + const scrollTop = window.scrollY; + const docHeight = + document.documentElement.scrollHeight - window.innerHeight; + if (docHeight > 0 && scrollTop >= docHeight - 5) { + msg.style.opacity = "1"; + } else { + msg.style.opacity = "0"; + } + }, + { passive: true }, + ); + } + + // ===== EASTER EGG: Type "10" Fireworks ===== + function initTenFireworks() { + let buffer = ""; + let mouseX = window.innerWidth / 2; + let mouseY = window.innerHeight / 2; + + document.addEventListener("mousemove", (e) => { + mouseX = e.clientX; + mouseY = e.clientY; + }); + + document.addEventListener("keydown", (e) => { + buffer += e.key; + if (buffer.length > 10) buffer = buffer.slice(-10); + if (buffer.endsWith("10")) { + buffer = ""; + spawnFireworks(mouseX, mouseY); + } + }); + } + + function spawnFireworks(cx, cy) { + const colors = [RED]; + const canvas = document.createElement("canvas"); + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + canvas.style.cssText = + "position:fixed;top:0;left:0;z-index:99997;pointer-events:none;"; + document.body.appendChild(canvas); + const ctx = canvas.getContext("2d"); + + let particles = []; + for (let i = 0; i < 60; i++) { + const angle = Math.random() * Math.PI * 2; + const speed = Math.random() * 5 + 2; + particles.push({ + x: cx, + y: cy, + vx: Math.cos(angle) * speed, + vy: Math.sin(angle) * speed - 2, + color: colors[Math.floor(Math.random() * colors.length)], + life: 1, + size: Math.random() * 4 + 2, + }); + } + + let frame = 0; + function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + particles.forEach((p) => { + p.x += p.vx; + p.y += p.vy; + p.vy += 0.08; + p.life -= 0.015; + if (p.life > 0) { + ctx.beginPath(); + ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2); + ctx.fillStyle = p.color; + ctx.globalAlpha = p.life; + ctx.fill(); + ctx.globalAlpha = 1; + } + }); + particles = particles.filter((p) => p.life > 0); + frame++; + if (particles.length > 0 && frame < 180) { + requestAnimationFrame(draw); + } else { + canvas.remove(); + } + } + draw(); + } + + // ===== EASTER EGG: Music Dance Experience ===== + function initMusicDanceExperience() { + const audio = document.getElementById("secretMusic"); + audio.volume = 0.3; + const stopBtn = document.getElementById("stopMusicBtn"); + if (!audio || !stopBtn) return; + + let typed = ''; + let isPlaying = false; + let colorOscillationInterval = null; + + function startColorOscillation() { + // Ensure we start on red (no class) + document.documentElement.classList.remove('theme-blue'); + document.documentElement.classList.add('theme-oscillating'); + let isBlue = false; + colorOscillationInterval = setInterval(() => { + isBlue = !isBlue; + if (isBlue) { + document.documentElement.classList.add('theme-blue'); + } else { + document.documentElement.classList.remove('theme-blue'); + } + }, 400); + } + + function stopColorOscillation() { + if (colorOscillationInterval) { + clearInterval(colorOscillationInterval); + colorOscillationInterval = null; + } + // Reset back to red + document.documentElement.classList.remove('theme-blue'); + document.documentElement.classList.remove('theme-oscillating'); + } + + // Container for music UI (title + stop button) + let musicUIContainer = null; + let lockOverlay = null; + + function startMusic() { + if (isPlaying) return; + audio.play().catch(() => { + // Autoplay may block if user hasn't interacted yet. + }); + isPlaying = true; + + // Smooth scroll to top with JS animation, then lock + const scrollStart = window.scrollY || document.documentElement.scrollTop; + if (scrollStart > 0) { + const scrollDuration = 800; + const startTime = performance.now(); + function animateScroll(now) { + const elapsed = now - startTime; + const progress = Math.min(elapsed / scrollDuration, 1); + // easeInOutQuad + const ease = progress < 0.5 + ? 2 * progress * progress + : 1 - Math.pow(-2 * progress + 2, 2) / 2; + window.scrollTo(0, scrollStart * (1 - ease)); + if (progress < 1) { + requestAnimationFrame(animateScroll); + } else { + document.documentElement.style.overflowY = 'hidden'; + document.body.style.overflowY = 'hidden'; + } + } + requestAnimationFrame(animateScroll); + } else { + document.documentElement.style.overflowY = 'hidden'; + document.body.style.overflowY = 'hidden'; + } + + // Block interaction with game + lockOverlay = document.createElement('div'); + lockOverlay.style.cssText = ` + position: fixed; top: 0; left: 0; width: 100%; height: 100%; + z-index: 99998; background: transparent; cursor: default; + `; + document.body.appendChild(lockOverlay); + + // Disable game canvas and text selection + const heroSection = document.getElementById('hero-section'); + if (heroSection) heroSection.style.pointerEvents = 'none'; + document.body.style.userSelect = 'none'; + + // Create music UI container (centered on screen) + musicUIContainer = document.createElement('div'); + musicUIContainer.style.cssText = ` + position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); + z-index: 99999; display: flex; flex-direction: column; + align-items: center; gap: 16px; + opacity: 0; transition: opacity 0.4s ease; + `; + + // Title label (unclickable) + const titleLabel = document.createElement('div'); + titleLabel.innerHTML = 'THE MUSIC
DANCE EXPERIENCE'; + titleLabel.style.cssText = ` + color: #ff0000ff; font-family: 'Geist Pixel', 'Courier New', monospace; font-size: 75px; + text-align: center; pointer-events: none; font-weight : bold; + text-shadow: 0 0 20px #ff0000ff; padding: 20px 40px; + background: rgba(10, 0, 8, 1.0); border: 1px solid #ff0000ff; + letter-spacing: 4px; text-transform: uppercase; + `; + musicUIContainer.appendChild(titleLabel); + + // Style stop button + stopBtn.textContent = 'STOP THE MUSIC DANCE EXPERIENCE'; + stopBtn.style.cssText = ` + color: #ff0000ff; font-family: 'Courier New', monospace; font-size: 18px; + text-align: center; cursor: pointer; + text-shadow: 0 0 20px #ff0000ff; padding: 20px 40px; + background: rgba(10, 0, 8, 0.85); border: 1px solid #ff0000ff; + letter-spacing: 4px; text-transform: uppercase; + display: block; + `; + musicUIContainer.appendChild(stopBtn); + + document.body.appendChild(musicUIContainer); + requestAnimationFrame(() => { musicUIContainer.style.opacity = '1'; }); + + startColorOscillation(); + } + + function stopMusic() { + audio.pause(); + audio.currentTime = 0; + isPlaying = false; + + // Unlock scroll + document.documentElement.style.overflowY = ''; + document.body.style.overflowY = 'auto'; + + // Remove interaction blocker + if (lockOverlay) { + lockOverlay.remove(); + lockOverlay = null; + } + + // Re-enable game canvas and text selection + const heroSection = document.getElementById('hero-section'); + if (heroSection) heroSection.style.pointerEvents = ''; + document.body.style.userSelect = ''; + + // Fade out music UI container, then remove + if (musicUIContainer) { + musicUIContainer.style.opacity = '0'; + const container = musicUIContainer; + setTimeout(() => { + container.remove(); + stopBtn.style.display = 'none'; + document.body.appendChild(stopBtn); + }, 400); + musicUIContainer = null; + } else { + stopBtn.style.display = 'none'; + document.body.appendChild(stopBtn); + } + + stopColorOscillation(); + } + + document.addEventListener("keydown", (e) => { + if (isPlaying) { + if (e.key === 'Escape') + stopMusic(); + return; + } + if (!e.key || e.key.length !== 1) return; + typed += e.key.toLowerCase(); + if (typed.length > 3) typed = typed.slice(-3); + if (typed === "mde") { + startMusic(); + typed = ""; + } + }); + + stopBtn.addEventListener("click", stopMusic); + audio.addEventListener("ended", stopMusic); + } + + + // ===== EASTER EGG: Track Tag Click Flash ===== + function initTrackTagFlash() { + document.querySelectorAll(".track-tags span").forEach((tag) => { + tag.style.cursor = "pointer"; + tag.style.transition = "all 0.15s ease"; + tag.addEventListener("click", () => { + const origBg = tag.style.background; + const origColor = tag.style.color; + tag.style.background = "#4fd1d9"; + tag.style.color = "#0a1628"; + tag.style.boxShadow = "0 0 15px #4fd1d9"; + setTimeout(() => { + tag.style.background = origBg; + tag.style.color = origColor; + tag.style.boxShadow = ""; + }, 300); + }); + }); + } + + // ===== EASTER EGG: Idle Message (30s) ===== + function initIdleMessage() { + let idleTimer = null; + let shown = false; + const idleMsg = document.createElement("div"); + idleMsg.style.cssText = ` + position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); + color: ${RED}; font-family: 'Courier New', monospace; font-size: 14px; + z-index: 99999; pointer-events: none; opacity: 0; + transition: opacity 0.5s ease; text-shadow: 0 0 10px ${RED}; + background: rgba(10, 22, 40, 0.9); padding: 12px 24px; border: 1px solid #4fd1d940; + white-space: nowrap; + `; + idleMsg.innerHTML = + '> STILL HERE? THE HACKATHON WON\'T WAIT. [REGISTER]'; + document.body.appendChild(idleMsg); + + function resetIdle() { + if (idleTimer) clearTimeout(idleTimer); + idleMsg.style.opacity = "0"; + if (shown) return; + idleTimer = setTimeout(() => { + shown = true; + idleMsg.style.opacity = "1"; + setTimeout(() => { + idleMsg.style.opacity = "0"; + }, 5000); + }, 30000); + } + + ["mousemove", "keydown", "scroll", "click", "touchstart"].forEach((evt) => { + document.addEventListener(evt, resetIdle, { passive: true }); + }); + resetIdle(); + } + + // ===== INTERACTION: Section Transitions — Horizontal Scan-Line Wipe ===== + function initSectionTransitions() { + const sections = document.querySelectorAll(".section"); + if (!sections.length) return; + + const triggered = new Map(); + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + const id = entry.target.id || entry.target.className; + const key = + id + "_" + (entry.boundingClientRect.top < 0 ? "up" : "down"); + if (entry.isIntersecting && !triggered.has(key)) { + triggered.set(key, true); + const line = document.createElement("div"); + line.style.cssText = ` + position: absolute; top: 0; left: 0; height: 3px; width: 0%; + background: #4fd1d9; z-index: 9999; pointer-events: none; + transition: width 300ms ease-out; + `; + entry.target.style.position = + entry.target.style.position || "relative"; + entry.target.appendChild(line); + requestAnimationFrame(() => { + line.style.width = "100%"; + }); + setTimeout(() => line.remove(), 400); + } + }); + }, + { threshold: 0.01 }, + ); + + sections.forEach((s) => observer.observe(s)); + } + + // ===== INTERACTION: Data Stream — Ambient Falling Hex Characters ===== + function initDataStream() { + if (window.innerWidth <= 1200) return; + + const canvas = document.createElement("canvas"); + canvas.width = 20; + canvas.height = window.innerHeight; + canvas.style.cssText = ` + position: fixed; top: 0; right: 0; z-index: 9990; + pointer-events: none; width: 20px; height: 100vh; + `; + document.body.appendChild(canvas); + const ctx = canvas.getContext("2d"); + + const chars = "0123456789ABCDEF"; + const drops = []; + for (let i = 0; i < 12; i++) { + drops.push({ + x: Math.random() * 16 + 2, + y: Math.random() * canvas.height, + speed: Math.random() * 0.4 + 0.15, + char: chars[Math.floor(Math.random() * chars.length)], + changeTimer: 0, + }); + } + + function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.font = "10px Courier New"; + ctx.fillStyle = "rgba(79, 209, 217, 0.06)"; + + drops.forEach((d) => { + d.y += d.speed; + d.changeTimer++; + if (d.changeTimer > 60) { + d.char = chars[Math.floor(Math.random() * chars.length)]; + d.changeTimer = 0; + } + if (d.y > canvas.height) { + d.y = -10; + d.x = Math.random() * 16 + 2; + } + ctx.fillText(d.char, d.x, d.y); + }); + + requestAnimationFrame(draw); + } + + window.addEventListener("resize", () => { + canvas.height = window.innerHeight; + }); + + draw(); + } + + // ===== INTERACTION: Section Counter — Current Section Indicator ===== + function initSectionCounter() { + const sections = document.querySelectorAll(".section"); + if (!sections.length) return; + + const total = String(sections.length).padStart(2, "0"); + const counter = document.createElement("div"); + counter.style.cssText = ` + position: fixed; bottom: 16px; left: 16px; z-index: 9995; + font-family: 'Courier New', monospace; font-size: 9px; + color: rgba(79, 209, 217, 0.25); pointer-events: none; + letter-spacing: 2px; + `; + counter.textContent = "01 / " + total; + document.body.appendChild(counter); + + const visibility = new Map(); + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + visibility.set(entry.target, entry.intersectionRatio); + }); + + let maxRatio = 0; + let maxIndex = 0; + sections.forEach((s, i) => { + const ratio = visibility.get(s) || 0; + if (ratio > maxRatio) { + maxRatio = ratio; + maxIndex = i; + } + }); + + counter.textContent = + String(maxIndex + 1).padStart(2, "0") + " / " + total; + }, + { threshold: [0, 0.25, 0.5, 0.75, 1] }, + ); + + sections.forEach((s) => observer.observe(s)); + } + + // ===== INTERACTION: Mouse Ripple — Expanding Ring on Click ===== + function initMouseRipple() { + const style = document.createElement("style"); + style.textContent = ` + @keyframes _rippleExpand { + 0% { width: 0; height: 0; opacity: 1; } + 100% { width: 100px; height: 100px; opacity: 0; } + } + ._mouse-ripple { + position: fixed; border-radius: 50%; pointer-events: none; + border: 1px solid rgba(79, 209, 217, 0.3); + animation: _rippleExpand 600ms ease-out forwards; + transform: translate(-50%, -50%); + z-index: 9996; + } + `; + document.head.appendChild(style); + + let ripples = []; + + document.addEventListener("click", (e) => { + // Skip hero/game section + const hero = document.getElementById("hero-section"); + if (hero && hero.contains(e.target)) return; + + if (ripples.length >= 3) { + const oldest = ripples.shift(); + oldest.remove(); + } + + const ripple = document.createElement("div"); + ripple.className = "_mouse-ripple"; + ripple.style.left = e.clientX + "px"; + ripple.style.top = e.clientY + "px"; + document.body.appendChild(ripple); + ripples.push(ripple); + + setTimeout(() => { + ripple.remove(); + ripples = ripples.filter((r) => r !== ripple); + }, 600); + }); + } + + // ===== INTERACTION: Ambient Pulse — Page Breathing ===== + function initAmbientPulse() { + document.body.style.transition = "background-color 2s ease-in-out"; + + function pulse() { + document.body.style.backgroundColor = "#0F0010"; + setTimeout(() => { + document.body.style.backgroundColor = "#0a1628"; + }, 2000); + + const next = 8000 + Math.random() * 4000; + setTimeout(pulse, next); + } + + setTimeout(pulse, 8000 + Math.random() * 4000); + } + + // ===== INTERACTION: Hover Line Trace — Cyan Border Trace ===== + function initHoverLineTrace() { + const style = document.createElement("style"); + style.textContent = ` + ._line-trace-wrap { position: relative; } + ._line-trace { + position: absolute; bottom: 0; left: 0; + height: 1px; width: 0; background: ${RED}; + transition: width 400ms ease-out; + pointer-events: none; + } + ._line-trace-wrap:hover ._line-trace { + width: 100%; + } + `; + document.head.appendChild(style); + + document + .querySelectorAll(".readout-row, .prize-entry, .timeline-item") + .forEach((el) => { + el.classList.add("_line-trace-wrap"); + if (el.style.position === "" || el.style.position === "static") { + el.style.position = "relative"; + } + const line = document.createElement("div"); + line.className = "_line-trace"; + el.appendChild(line); + }); + } + + // ===== INTERACTION: Reveal on Scroll — Glitch Text Entrance ===== + function initRevealOnScroll() { + const headings = document.querySelectorAll(".glitch-text"); + if (!headings.length) return; + + headings.forEach((h) => { + h.style.opacity = "0"; + h.style.transform = "translateY(40px)"; + h.style.transition = + "opacity 800ms cubic-bezier(0.34, 1.56, 0.64, 1), transform 800ms cubic-bezier(0.34, 1.56, 0.64, 1)"; + }); + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + entry.target.style.opacity = "1"; + entry.target.style.transform = "translateY(0)"; + observer.unobserve(entry.target); + } + }); + }, + { threshold: 0.15 }, + ); + + headings.forEach((h) => observer.observe(h)); + } + + // ===== TEAM MARQUEE ===== + function initTeamGrid() { + const heads = [ + { + name: "AMANDEEP SINGH", + role: "COMMITTEE HEAD", + dept: "COMPS/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Amandeep_2025-26.png", + profile: "#", + }, + { + name: "VIRAJ BHARTIYA", + role: "COMMITTEE HEAD", + dept: "COMPS/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Viraj_2025-26.png", + profile: "#", + }, + { + name: "OMIK ACHARYA", + role: "TECH HEAD", + dept: "COMPS/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Omik_2025-26.png", + profile: "#", + }, + { + name: "ADITI SINGH", + role: "CREATIVE HEAD", + dept: "COMPS/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Aditi_2025-26.png", + profile: "#", + }, + { + name: "RISHI SHANBHAG", + role: "MARKETING HEAD", + dept: "COMPS/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Rishi_2025-26.png", + profile: "#", + }, + ]; + + const ty = [ + { + name: "ADITYA BELGAONKAR", + role: "TECH", + dept: "COMPS/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Aditya_2025-26.png", + profile: "#", + }, + { + name: "KUMAR TANAY", + role: "TECH", + dept: "COMPS/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Kumar_2025-26.png", + profile: "#", + }, + { + name: "VIVEK JAIN", + role: "TECH", + dept: "COMPS/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Vivek_2025-26.png", + profile: "#", + }, + { + name: "AKANKSHA AGROYA", + role: "CREATIVE", + dept: "COMPS/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Akanksha_2025-26.png", + profile: "#", + }, + { + name: "AMRIT NIGAM", + role: "CREATIVE", + dept: "COMPS/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Amrit_2025-26.png", + profile: "#", + }, + { + name: "CHAITANYA DHAMDHERE", + role: "MARKETING", + dept: "COMPS/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Chaitanya_2025-26.png", + profile: "#", + }, + { + name: "DHARMIK CHANDEL", + role: "MARKETING", + dept: "IT/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Dharmik_2025-26.png", + profile: "#", + }, + { + name: "SHREYANS TATIYA", + role: "MARKETING", + dept: "COMPS/TY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Shreyans_2025-26.png", + profile: "#", + }, + ]; + + const sy = [ + { + name: "ANMOL RAI", + role: "TECH", + dept: "COMPS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Anmol_2025-26.png", + profile: "#", + }, + { + name: "ASHWERA HASAN", + role: "TECH", + dept: "COMPS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Ashwera_2025-26.png", + profile: "#", + }, + { + name: "SAMAGRA AGARWAL", + role: "TECH", + dept: "COMPS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Samagra_2025-26.png", + profile: "#", + }, + { + name: "SHANTANAV MUKHERJEE", + role: "TECH", + dept: "COMPS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Shantanav_2025-26.png", + profile: "#", + }, + { + name: "TANUJ ADARKAR", + role: "TECH", + dept: "COMPS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Tanuj_2025-26.png", + profile: "#", + }, + { + name: "VINAYAK PAI", + role: "TECH", + dept: "COMPS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Vinayak_2025-26.png", + profile: "#", + }, + { + name: "AMEYA DEORE", + role: "CREATIVE", + dept: "CSBS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Ameya_2025-26.png", + profile: "#", + }, + { + name: "PURVA POTE", + role: "CREATIVE", + dept: "CSBS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Purva_2025-26.png", + profile: "#", + }, + { + name: "DIVYANSHI YADAV", + role: "CREATIVE", + dept: "COMPS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Divyanshi_2025-26.png", + profile: "#", + }, + { + name: "DHANYA SHUKLA", + role: "CREATIVE", + dept: "COMPS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Dhanya_2025-26.png", + profile: "#", + }, + { + name: "ARSHIA DANG", + role: "MARKETING", + dept: "IT/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Arshia_2025-26.png", + profile: "#", + }, + { + name: "PARTH PANWAR", + role: "MARKETING", + dept: "AIDS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Parth_2025-26.png", + profile: "#", + }, + { + name: "HARSHIL RAVARIYA", + role: "MARKETING", + dept: "AIDS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Harshil_2025-26.png", + profile: "#", + }, + { + name: "SAMAIRA SHARMA", + role: "MARKETING", + dept: "IT/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Samaira_2025-26.png", + profile: "#", + }, + { + name: "SHRAVIKA MHATRE", + role: "MARKETING", + dept: "VLSI/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Shravika_2025-26.png", + profile: "#", + }, + { + name: "SRUSHTI TALANDAGE", + role: "MARKETING", + dept: "CSBS/SY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Srushti_2025-26.png", + profile: "#", + }, + ]; + + const fy = [ + { + name: "BHOUMIK SANGLE", + role: "TECH", + dept: "COMPS/FY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Bhoumik_2025-26.png", + profile: "#", + }, + { + name: "DHRUV KUMAR", + role: "TECH", + dept: "COMPS/FY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Dhruv_2025-26.png", + profile: "#", + }, + { + name: "PRANAV MENDON", + role: "TECH", + dept: "COMPS/FY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Pranav_2025-26.png", + profile: "#", + }, + { + name: "ANCHITA SAHU", + role: "CREATIVE", + dept: "IT/FY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Anchita_2025-26.png", + profile: "#", + }, + { + name: "MITALI PAUL", + role: "CREATIVE", + dept: "COMPS/FY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Mitali_2025-26.png", + profile: "#", + }, + { + name: "RUDRAKSHI ACHARYYA", + role: "MARKETING", + dept: "COMPS/FY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Rudrakshi_2025-26.png", + profile: "#", + }, + { + name: "YASH AGROYA", + role: "MARKETING", + dept: "IT/FY", + photo: "https://kjssecodecell.com/static/images/team-2025-26/Yash_2025-26.png", + profile: "#", + }, + ]; + + function makeCard(member) { + const card = document.createElement("a"); + card.className = "team-card"; + card.href = member.profile; + card.target = "_blank"; + card.rel = "noopener noreferrer"; + card.setAttribute("aria-label", "Open " + member.name + " profile"); + + card.innerHTML = ` +
+ ${member.name} +
+
${member.name}
+
${member.role}
+ `; + + return card; + } + + function fillRow(rowId, members) { + const row = document.getElementById(rowId); + if (!row) return; + for (let pass = 0; pass < 2; pass++) { + members.forEach((m) => row.appendChild(makeCard(m))); + } + } + + fillRow("team-row-heads", heads); + fillRow("team-row-ty", ty); + fillRow("team-row-sy", sy); + fillRow("team-row-fy", fy); + } + + // ===== INTERACTION: Keyboard Whisper — Key Press Feedback ===== + function initKeyboardWhisper() { + const style = document.createElement("style"); + style.textContent = ` + @keyframes _whisperFade { + 0% { opacity: 0.15; transform: translateY(0); } + 100% { opacity: 0; transform: translateY(-20px); } + } + ._key-whisper { + position: fixed; bottom: 40px; right: 24px; + font-family: 'Courier New', monospace; font-size: 80px; + color: #4fd1d9; pointer-events: none; z-index: 9994; + animation: _whisperFade 300ms ease-out forwards; + line-height: 1; + } + `; + document.head.appendChild(style); + + let whispers = []; + + document.addEventListener("keydown", (e) => { + // Skip if inside game section + const hero = document.getElementById("hero-section"); + if (hero && hero.contains(document.activeElement)) return; + // Skip modifier/special keys + if (e.key.length > 1) return; + + if (whispers.length >= 3) { + const oldest = whispers.shift(); + oldest.remove(); + } + + const w = document.createElement("div"); + w.className = "_key-whisper"; + w.textContent = e.key; + w.style.bottom = 40 + whispers.length * 70 + "px"; + document.body.appendChild(w); + whispers.push(w); + + setTimeout(() => { + w.remove(); + whispers = whispers.filter((x) => x !== w); + }, 300); + }); + } +})(); diff --git a/hackx/service-worker.js b/hackx/service-worker.js new file mode 100644 index 00000000..7537fd0d --- /dev/null +++ b/hackx/service-worker.js @@ -0,0 +1 @@ +addEventListener('fetch', event => {}) diff --git a/hackx/shaders/crt.frag.glsl b/hackx/shaders/crt.frag.glsl new file mode 100644 index 00000000..4e3469f2 --- /dev/null +++ b/hackx/shaders/crt.frag.glsl @@ -0,0 +1,59 @@ +// much of this taken from: https://babylonjs.medium.com/retro-crt-shader-a-post-processing-effect-study-1cb3f783afbc + +#ifdef GL_ES +precision mediump float; +#endif + +#define PI 3.1415926538 + +// grab texcoords from vert shader +varying vec2 vTexCoord; + +// our texture coming from p5 +uniform sampler2D u_tex; +uniform vec2 u_resolution; +vec2 curvature = vec2(4.5); //zoom level of curvature (lower = curvier) + + +vec2 curveRemapUV(vec2 uv) { + // as we near the edge of our screen apply greater distortion using a cubic function + uv = uv * 2.0 - 1.0; + vec2 offset = abs(uv.yx) / vec2(curvature.x, curvature.y); + uv = uv + uv * offset * offset; + uv = uv * 0.5 + 0.5; + return uv; +} + +void main() { + vec2 uv = vTexCoord; + uv.y = 1.0 - uv.y; //flip the incoming image texture + + vec2 remappedUV = curveRemapUV(vec2(uv.xy)); + vec4 baseColor = texture2D(u_tex, remappedUV); + + float line_count = 400.0; + float opacity = 0.65; + float y_lines = sin(remappedUV.y * line_count * PI * 2.0); + y_lines = (y_lines * 0.5 + 0.5) * 0.9 + 0.1; + float x_lines = sin(remappedUV.x * line_count * PI * 2.0); + x_lines = (x_lines * 0.5 + 0.5) * 0.9 + 0.1; + vec4 scan_line = vec4(vec3(pow(y_lines, opacity)), 1.0); + vec4 scan_line_x = vec4(vec3(pow(x_lines, opacity)), 1.0); + + // Boost brightness and bias the CRT phosphor toward the site cyan palette. + float avg = baseColor.r + baseColor.g + baseColor.b / 3.0; + if (avg > 0.5) { + baseColor *= vec4(vec3(0.3, 1.4, 1.5), 1.0) * 8.0; + } else { + baseColor *= vec4(vec3(0.2, 1.6, 1.8), 1.0) * 2.0; + } + + baseColor *= scan_line; + baseColor *= scan_line_x; + + if (remappedUV.x < 0.0 || remappedUV.y < 0.0 || remappedUV.x > 1.0 || remappedUV.y > 1.0) { + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); + } else { + gl_FragColor = baseColor; + } +} diff --git a/hackx/shaders/crt.vert.glsl b/hackx/shaders/crt.vert.glsl new file mode 100644 index 00000000..bc6651f1 --- /dev/null +++ b/hackx/shaders/crt.vert.glsl @@ -0,0 +1,19 @@ +// vert file and comments from adam ferriss +// https://github.com/aferriss/p5jsShaderExamples + +// our vertex data +attribute vec3 aPosition; +attribute vec2 aTexCoord; + +varying vec2 vTexCoord; + +void main() { + vTexCoord = aTexCoord; + + // copy the position data into a vec4, using 1.0 as the w component + vec4 positionVec4 = vec4(aPosition, 1.0); + positionVec4.xy = positionVec4.xy * 2.0 - 1.0; + + // send the vertex information on to the fragment shader + gl_Position = positionVec4; +} \ No newline at end of file diff --git a/hackx/sketch.js b/hackx/sketch.js new file mode 100644 index 00000000..d23ecf4a --- /dev/null +++ b/hackx/sketch.js @@ -0,0 +1,1194 @@ +let osn; + +// Total numbers to be collected +let goal = 500; + +// Tracking the numbers +var refined = []; +var numbers = []; +var cellSize, baseSize; +var buffer; // set dynamically in setup based on screen size +var cols, rows; + +// Info for refining +let refining = false; +let refineTX, refinteTY, refineBX, refineBY; + +// Logo text replaces lumon image +const logoTextWidth = 180; + +let startTime = 0; +let secondsSpentRefining = 0; +let lastRefiningTimeStored = 0; + +const emojis = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"]; + +// Info for "nope" state +let nope = false; +let nopeImg; +let nopeTime = 0; + +// Info for 100% state +let completed = false; +let completedImg; +let completedTime = 0; + +// Info for sharing +let shared = false; +let sharedImg; +let sharedTime = 0; + +let shareDiv; + +// Hack X states +let booting = true; +let gameEnterAnim = 0; // 0 to 1 fade-in after boot +let gameEnterStart = 0; +let bootCharIndex = 0; +let bootFrameCounter = 0; +let bootGlitching = false; +let bootGlitchStart = 0; +let bootFadeAlpha = 255; + +let revealing = false; +let revealText = ""; +let revealCharIndex = 0; +let revealFrameCounter = 0; +let revealPhase = "shake"; +let revealPhaseStart = 0; +let revealsDone = []; + +let terminalOpen = false; +var rainbowMode = false; +let rainbowModeStart = 0; +let logoFlash = false; +let logoFlashStart = 0; + +// Visual effects +let shakeAmount = 0; +let particles = []; +let glitchTimer = 0; +let glitchSlices = []; +let glitchFramesLeft = 0; + +// Mouse trail particles +let mouseTrail = []; + +// Floating dust particles (background ambiance) +let dustParticles = []; + +// Input buffers +let keyBuffer = []; +let exitBuffer = []; +let konamiBuffer = []; +const konamiCode = [ + "ArrowUp", + "ArrowUp", + "ArrowDown", + "ArrowDown", + "ArrowLeft", + "ArrowRight", + "ArrowLeft", + "ArrowRight", + "b", + "a", +]; + +// for CRT Shader +var shaderLayer, crtShader; +var g; //p5 graphics instance +var useShader; + +// Background and foreground colours — single-accent cyan theme. +const mobilePalette = { + BG: "#0A1628", + FG: "#4fd1d9", + SELECT: "#e0f4f4", + LEVELS: { + WO: "#4fd1d9", + FC: "#4fd1d9", + DR: "#4fd1d9", + MA: "#4fd1d9", + }, +}; + +const shaderPalette = { + BG: "#0A1628", + FG: "#3ab8c0", + SELECT: "#d0eef0", + LEVELS: { + WO: "#3ab8c0", + FC: "#3ab8c0", + DR: "#3ab8c0", + MA: "#3ab8c0", + }, +}; + +var palette = mobilePalette; + +// holds filename, initial bin levels, coordinates +let macrodataFile; + +// Add image svg +let logoImg; + +function preload() { + nopeImg = loadImage("images/nope.png"); + completedImg = loadImage("images/100.png"); + sharedImg = loadImage("images/clipboard.png"); + logoImg = loadImage('images/logo.svg') + crtShader = loadShader("shaders/crt.vert.glsl", "shaders/crt.frag.glsl"); +} + +function startOver(resetFile = false) { + // Track the amount of time + startTime = millis(); + + // Create the space + levelH = buffer * 1.7; + cellSize = (smaller - buffer * 2) / 10; + baseSize = cellSize * 0.33; + osn = new OpenSimplexNoise(); + cols = floor(g.width / cellSize); + rows = floor((g.height - buffer * 2) / cellSize); + + let wBuffer = g.width - cols * cellSize; + for (let j = 0; j < rows; j++) { + for (let i = 0; i < cols; i++) { + let x = i * cellSize + cellSize * 0.5 + wBuffer * 0.5; + let y = j * cellSize + cellSize * 0.5 + buffer; + // Initialize the number objects + numbers[i + j * cols] = new Data(x, y); + } + } + + if (resetFile) { + macrodataFile.resetFile(); + storeItem("secondsSpentRefining", 0); + secondsSpentRefining = 0; + lastRefiningTimeStored = 0; + } + + // Refinement bins + for (let i = 0; i < 5; i++) { + const w = g.width / 5; + const binLevels = macrodataFile.storedBins + ? macrodataFile.storedBins[i] + : undefined; + refined[i] = new Bin(w, i, goal / 5, binLevels); + } + + nopeTime = 0; + nope = false; + completed = false; + shared = false; + revealing = false; + revealsDone = []; + terminalOpen = false; + rainbowMode = false; + logoFlash = false; + particles = []; + mouseTrail = []; + shareDiv.hide(); +} + +var zoff = 0; +var smaller; + +function setup() { + const cnv = createCanvas(windowWidth, windowHeight); + cnv.parent("game-container"); + frameRate(30); + + // create a downscaled graphics buffer to draw to, we'll upscale after applying crt shader + g = createGraphics(windowWidth, windowHeight); + + // Scale buffer for mobile + smaller = min(g.width, g.height); + buffer = max(50, min(100, smaller * 0.12)); + + // We don't want to use shader on mobile + useShader = !isTouchScreenDevice(); + // If the site is using the global CRT curvature pass (sections.js), + // skip the game's local CRT shader to keep one continuous "screen". + if (typeof window !== "undefined" && window.__HACKX_GLOBAL_CRT__ === true) { + useShader = false; + } + + // The shader boosts colour values so we reset the palette if using shader + if (useShader) { + palette = shaderPalette; + } + + // force pixel density to 1 to improve perf on retina screens + pixelDensity(1); + + // p5 graphics element to draw our shader output to + shaderLayer = createGraphics(g.width, g.height, WEBGL); + shaderLayer.noStroke(); + crtShader.setUniform("u_resolution", [g.width, g.height]); + + smaller = min(g.width, g.height); + + // Always start fresh on page load + localStorage.removeItem("hackx-data"); + localStorage.removeItem("secondsSpentRefining"); + macrodataFile = new MacrodataFile(); + secondsSpentRefining = 0; + + sharedImg.resize(smaller * 0.5, 0); + nopeImg.resize(smaller * 0.5, 0); + completedImg.resize(smaller * 0.5, 0); + + // Width for the share 100% button + const shw = completedImg.width; + const shh = completedImg.height; + shareDiv = createDiv(""); + shareDiv.hide(); + shareDiv.position(g.width * 0.5 - shw * 0.5, g.height * 0.5 - shh * 0.5); + shareDiv.style("width", `${shw}px`); + shareDiv.style("height", `${shh}px`); + shareDiv.mousePressed(function () { + let thenumbers = ""; + for (let r = 0; r < 5; r++) { + for (let c = 0; c < 5; c++) { + thenumbers += random(emojis); + } + thenumbers += "\n"; + } + const timeStr = createTimeString(secondsSpentRefining); + const msg = `Refined ${macrodataFile.fileName} in ${timeStr} for ${HACKX_CONFIG.orgName}. +${HACKX_CONFIG.eventName} // ${HACKX_CONFIG.tagline} +${thenumbers}${HACKX_CONFIG.shareHashtags} +${HACKX_CONFIG.shareUrl}`; + + console.log("copy to clipboard!"); + navigator.clipboard.writeText(msg); + shared = true; + }); + + // Initialize dust particles + for (let i = 0; i < 50; i++) { + dustParticles.push({ + x: random(g.width), + y: random(g.height), + vx: random(-0.3, 0.3), + vy: random(-0.2, -0.05), + alpha: random(20, 60), + size: random(1, 3), + }); + } + + startOver(); +} + +function mousePressed() { + initAudio(); + playClick(); + + // Logo click detection — progress bar left area where "CODECELL" label is + if (mouseX < g.width * 0.3 && mouseY < 55) { + logoFlash = true; + logoFlashStart = millis(); + } + + if (booting) { + booting = false; + gameEnterStart = millis(); + document.body.style.overflowY = "auto"; + return; + } + + if ( + !refining && + !completed && + !shared && + !booting && + !revealing && + !terminalOpen + ) { + refineTX = mouseX; + refineTY = mouseY; + refineBX = mouseX; + refineBY = mouseY; + refining = true; + nope = false; + } +} + +function mouseDragged() { + refineBX = mouseX; + refineBY = mouseY; +} + +function mouseReleased() { + refining = false; + let countRed = 0; + let total = 0; + let refinery = []; + for (let num of numbers) { + if (num.inside(refineTX, refineTY, refineBX, refineBY)) { + if (num.refined) { + refinery.push(num); + countRed++; + } + total++; + } + num.turn(palette.FG); + num.refined = false; + } + // half of numbers must be refinable + if (countRed > 0.5 * total) { + const options = []; + for (let bin of refined) { + if (bin.count < bin.goal) { + options.push(bin); + } + } + const bin = random(options); + for (let num of refinery) { + num.refine(bin); + } + shakeAmount = 3; + playRefine(); + } else { + refinery = []; + if (!completed && !shared) { + nope = true; + playNope(); + } + nopeTime = millis(); + } +} + +let prevPercent; + +function draw() { + g.colorMode(RGB); + const globalCrtActive = + typeof window !== "undefined" && window.__HACKX_GLOBAL_CRT__ === true; + + // Boot sequence gate + if (booting) { + drawBootSequence(); + if (useShader && !globalCrtActive) { + shaderLayer.rect(0, 0, g.width, g.height); + shaderLayer.shader(crtShader); + crtShader.setUniform("u_tex", g); + background(palette.BG); + imageMode(CORNER); + image(shaderLayer, 0, 0, g.width, g.height); + } else { + image(g, 0, 0, g.width, g.height); + } + return; + } + + // Game enter animation (numbers fade/scale in after boot) + if (gameEnterAnim < 1) { + gameEnterAnim = constrain((millis() - gameEnterStart) / 800, 0, 1); + } + + // Rainbow mode timer (10 seconds) + if (rainbowMode && millis() - rainbowModeStart > 10000) { + rainbowMode = false; + } + + let sum = 0; + for (let bin of refined) { + sum += bin.count; + } + let percent = sum / goal; + + if (percent !== prevPercent) { + const bins = refined.map((bin) => bin.levels); + macrodataFile.updateProgress(bins); + prevPercent = percent; + } + + // Milestone reveals + if (percent >= 0.25) triggerReveal(25); + if (percent >= 0.5) triggerReveal(50); + if (percent >= 0.75) triggerReveal(75); + if (percent >= 1.0) triggerReveal(100); + + if (completed && shared) { + completed = false; + sharedTime = millis(); + } + + g.background(palette.BG); + g.textFont("Courier"); + + // Screen shake + if (shakeAmount > 0) { + g.push(); + g.translate( + random(-shakeAmount, shakeAmount), + random(-shakeAmount, shakeAmount), + ); + } + + // Draw floating dust particles behind everything + drawDustParticles(); + + // Mouse trail particles + if (mouseX > 0 && mouseY > 0 && !booting) { + mouseTrail.push({ x: mouseX, y: mouseY, alpha: 150, size: random(2, 5) }); + if (mouseTrail.length > 30) mouseTrail.shift(); + } + drawMouseTrail(); + + drawTop(percent); + drawNumbers(); + drawBottom(); + + drawBinned(); + drawParticles(); + + // No top-right text — branding is in the progress bar now + + // Logo flash — "CODECELL INDUSTRIES" + if (logoFlash) { + g.textFont("Courier"); + g.textSize(smaller * 0.05); + g.textAlign(CENTER, CENTER); + g.fill(palette.FG); + g.noStroke(); + g.text(HACKX_CONFIG.orgName, g.width * 0.5, g.height * 0.5); + if (millis() - logoFlashStart > 1000) { + logoFlash = false; + } + } + + if (nope) { + g.imageMode(CENTER); + if (!useShader) g.tint(mobilePalette.FG); + g.image(nopeImg, g.width * 0.5, g.height * 0.5); + if (millis() - nopeTime > 1000) { + nope = false; + } + } + + if (completed) { + g.imageMode(CENTER); + if (!useShader) g.tint(mobilePalette.FG); + g.image(completedImg, g.width * 0.5, g.height * 0.5); + } + + if (shared) { + g.imageMode(CENTER); + if (!useShader) g.tint(mobilePalette.FG); + g.image(sharedImg, g.width * 0.5, g.height * 0.5); + if (millis() - sharedTime > 10000) { + startOver(true); + } + } + + drawCursor(mouseX, mouseY); + + if (shakeAmount > 0) { + g.pop(); + shakeAmount = max(0, shakeAmount - 0.3); + } + + if (revealing) { + drawReveal(); + } + + // Terminal overlay + if (terminalOpen) { + g.fill(0, 0, 0, 220); + g.noStroke(); + g.rectMode(CORNER); + g.rect(g.width * 0.05, g.height * 0.1, g.width * 0.9, g.height * 0.8); + + g.stroke(palette.FG); + g.strokeWeight(2); + g.noFill(); + g.rect(g.width * 0.05, g.height * 0.1, g.width * 0.9, g.height * 0.8); + + g.fill(palette.FG); + g.noStroke(); + g.textFont("Courier"); + g.textSize(smaller * 0.028); + g.textAlign(LEFT, TOP); + + const tLines = HACKX_CONFIG.terminalLines; + const tLineH = smaller * 0.05; + const tx = g.width * 0.1; + const ty = g.height * 0.15; + for (let i = 0; i < tLines.length; i++) { + g.text(tLines[i], tx, ty + i * tLineH); + } + + // Blinking cursor at bottom + if (frameCount % 20 < 10) { + g.fill(palette.FG); + g.rect( + tx, + ty + tLines.length * tLineH + 10, + smaller * 0.02, + smaller * 0.003, + ); + } + } + + // Idle glitch (desktop only) + if (!isTouchScreenDevice() && !booting && !revealing && !terminalOpen) { + glitchTimer++; + if (glitchTimer >= 350 && glitchFramesLeft <= 0) { + glitchFramesLeft = floor(random(2, 4)); + glitchSlices = []; + for (let i = 0; i < 3; i++) { + glitchSlices.push({ + y: random(g.height), + h: random(10, 40), + offset: random(-15, 15), + }); + } + glitchTimer = floor(random(-100, 0)); // randomize next interval + } + + if (glitchFramesLeft > 0) { + for (const s of glitchSlices) { + const slice = g.get(0, s.y, g.width, s.h); + g.image(slice, s.offset, s.y); + } + glitchFramesLeft--; + } + } + + // Chromatic aberration during reveals (desktop only) + if (revealing && !isTouchScreenDevice()) { + const snap = g.get(); + g.blendMode(ADD); + g.tint(255, 0, 0, 80); + g.image(snap, -3, 0); + g.tint(0, 0, 255, 80); + g.image(snap, 3, 0); + g.noTint(); + g.blendMode(BLEND); + } + + if (useShader && !globalCrtActive) { + shaderLayer.rect(0, 0, g.width, g.height); + shaderLayer.shader(crtShader); + crtShader.setUniform("u_tex", g); + background(palette.BG); + imageMode(CORNER); + image(shaderLayer, 0, 0, g.width, g.height); + } else { + image(g, 0, 0, g.width, g.height); + } + + if (focused) { + secondsSpentRefining += deltaTime / 1000; + const roundedTime = round(secondsSpentRefining); + if (roundedTime % 5 == 0 && roundedTime != lastRefiningTimeStored) { + storeItem("secondsSpentRefining", secondsSpentRefining); + lastRefiningTimeStored = roundedTime; + } + } +} + +function drawTop(percent) { + const y = 30; + const textSz = max(11, smaller * 0.02); + + g.textFont("Courier"); + g.noStroke(); + + // Left: Logo + if (logoImg) { + g.imageMode(CENTER); + if (!useShader) g.tint(palette.FG); + + let logoWidth = 100; + let logoHeight = 100; + // Align the center of the image with the center of the text vertically + g.image(logoImg, g.width * 0.04 + logoWidth / 2, y + textSz / 2, logoWidth, logoHeight); + } + + // Right: event info + g.textAlign(RIGHT, TOP); + g.textSize(max(9, smaller * 0.015)); + const c = color(palette.FG); + c.setAlpha(180); + g.fill(c); + g.text( + HACKX_CONFIG.date + " // " + HACKX_CONFIG.location, + g.width * 0.96, + y, + ); +} + +function drawNumbers() { + g.rectMode(CENTER); + g.noFill(); + g.strokeWeight(1); + g.line(0, buffer, g.width, buffer); + g.line(0, g.height - buffer, g.width, g.height - buffer); + + let yoff = 0; + + const inc = 0.2; + for (let i = 0; i < cols; i++) { + let xoff = 0; + for (let j = 0; j < rows; j++) { + let num = numbers[i + j * cols]; + if (!num) return; + + if (num.binIt) { + num.goBin(); + num.show(); + continue; + } + + let n = osn.noise3D(xoff, yoff, zoff) - 0.4; + if (n < 0) { + n = 0; + num.goHome(); + } else { + num.x += random(-1, 1); + num.y += random(-1, 1); + } + + let sz = n * baseSize * 4 + baseSize; + + let d = dist(mouseX, mouseY, num.x, num.y); + + // Numbers flee from cursor more dramatically + if (d < g.width * 0.15) { + let angle = atan2(num.y - mouseY, num.x - mouseX); + let force = map(d, 0, g.width * 0.15, 3, 0); + num.x += cos(angle) * force; + num.y += sin(angle) * force; + } else { + num.goHome(); + } + + num.size(sz); + num.show(); + xoff += inc; + } + yoff += inc; + } + zoff += 0.005; +} + +function drawBottom() { + for (let i = 0; i < refined.length; i++) { + refined[i].show(); + } + + if (refining) { + g.push(); + g.rectMode(CORNERS); + g.stroke(palette.FG); + g.noFill(); + g.rect(refineTX, refineTY, refineBX, refineBY); + + for (let num of numbers) { + if ( + num.inside(refineTX, refineTY, refineBX, refineBY) && + num.sz > baseSize + ) { + num.turn(palette.SELECT); + num.refined = true; + } else { + num.turn(palette.FG); + num.refined = false; + } + } + g.pop(); + } + + // Bottom bar — event info instead of hex coordinates + g.rectMode(CORNER); + g.fill(palette.FG); + g.rect(0, g.height - 20, g.width, 20); + g.fill(palette.BG); + g.textFont("Courier"); + g.textAlign(CENTER, CENTER); + g.textSize(max(8, baseSize * 0.6)); + let bottomText; + if (smaller < 500) { + bottomText = HACKX_CONFIG.eventName + " // " + HACKX_CONFIG.date; + } else { + bottomText = + HACKX_CONFIG.orgName + + " // " + + HACKX_CONFIG.eventName + + " // " + + HACKX_CONFIG.date + + " // " + + HACKX_CONFIG.shareUrl; + } + g.text(bottomText, g.width * 0.5, g.height - 10); +} + +function drawBinned() { + for (let num of numbers) { + if (num.binIt) { + // Emit particle trail + if (num.binPause <= 0 && frameCount % 2 === 0) { + particles.push({ + x: num.x + random(-2, 2), + y: num.y + random(-2, 2), + alpha: 200, + color: palette.FG, + }); + } + num.show(); + } + } +} + +function drawParticles() { + for (let i = particles.length - 1; i >= 0; i--) { + const p = particles[i]; + p.alpha -= 13; + if (p.alpha <= 0) { + particles.splice(i, 1); + continue; + } + g.noStroke(); + const col = color(p.color); + col.setAlpha(p.alpha); + g.fill(col); + g.circle(p.x, p.y, 3); + } +} + +function drawMouseTrail() { + for (let i = mouseTrail.length - 1; i >= 0; i--) { + const p = mouseTrail[i]; + p.alpha -= 8; + if (p.alpha <= 0) { + mouseTrail.splice(i, 1); + continue; + } + g.noStroke(); + const col = color(palette.FG); + col.setAlpha(p.alpha); + g.fill(col); + g.circle(p.x, p.y, p.size); + } +} + +function drawDustParticles() { + for (let i = 0; i < dustParticles.length; i++) { + const d = dustParticles[i]; + d.x += d.vx; + d.y += d.vy; + + // Wrap around edges + if (d.x < 0) d.x = g.width; + if (d.x > g.width) d.x = 0; + if (d.y < 0) d.y = g.height; + if (d.y > g.height) d.y = 0; + + // Subtle flickering alpha + let flickerAlpha = d.alpha + sin(millis() * 0.001 + i) * 10; + flickerAlpha = constrain(flickerAlpha, 5, 80); + + g.noStroke(); + const col = color(palette.FG); + col.setAlpha(flickerAlpha); + g.fill(col); + g.circle(d.x, d.y, d.size); + } +} + +function drawFPS() { + textSize(24); + fill(palette.FG); + noStroke(); + text(frameRate().toFixed(2), 50, 25); +} + +function toggleShader() { + if (useShader) { + palette = mobilePalette; + } else { + palette = shaderPalette; + } + useShader = !useShader; +} + +function drawCursor(xPos, yPos) { + // prevents the cursor appearing in top left corner on page load + if (xPos == 0 && yPos == 0) return; + g.push(); + // this offset makes the box draw from point of cursor + g.translate(xPos + 10, yPos + 10); + g.scale(1.2); + g.fill(palette.BG); + g.stroke(palette.FG); + g.strokeWeight(3); + g.beginShape(); + g.rotate(-PI / 5); + g.vertex(0, -10); + g.vertex(7.5, 10); + g.vertex(0, 5); + g.vertex(-7.5, 10); + g.endShape(CLOSE); + g.pop(); +} + +function drawBootSequence() { + g.background(palette.BG); + g.textFont("Courier"); + g.fill(palette.FG); + g.noStroke(); + + const lines = HACKX_CONFIG.bootLines; + const totalChars = lines.join("").length + lines.length; + const lineHeight = smaller * 0.05; + const textSz = smaller * 0.035; + g.textSize(textSz); + g.textAlign(LEFT, TOP); + + const startX = g.width * 0.1; + const startY = g.height * 0.3; + + // Typing effect: increment char index every 3rd frame (~100ms at 30fps) + bootFrameCounter++; + if (bootFrameCounter >= 1) { + bootFrameCounter = 0; + bootCharIndex++; + } + + // Render typed text + let charCount = 0; + for (let i = 0; i < lines.length; i++) { + let displayLine = ""; + for (let c = 0; c < lines[i].length; c++) { + if (charCount < bootCharIndex) { + displayLine += lines[i][c]; + charCount++; + } else { + break; + } + } + if (displayLine.length > 0) { + g.text(displayLine, startX, startY + i * lineHeight); + } + charCount++; // count the newline + if (charCount > bootCharIndex) break; + } + + // Blinking cursor + if (frameCount % 15 < 8) { + let curCharCount = 0; + let curLine = 0; + let curCol = 0; + for (let i = 0; i < lines.length; i++) { + if (curCharCount + lines[i].length >= bootCharIndex) { + curLine = i; + curCol = bootCharIndex - curCharCount; + break; + } + curCharCount += lines[i].length + 1; + curLine = i + 1; + curCol = 0; + } + g.rect( + startX + curCol * textSz * 0.6, + startY + curLine * lineHeight, + textSz * 0.6, + textSz, + ); + } + + // Check if all text is typed + if (bootCharIndex >= totalChars) { + if (!bootGlitching) { + bootGlitching = true; + bootGlitchStart = millis(); + } + + const elapsed = millis() - bootGlitchStart; + if (elapsed > 400) { + // Glitch effect: random horizontal slices + if (elapsed < 800) { + for (let i = 0; i < 5; i++) { + const sliceY = random(g.height); + const sliceH = random(10, 40); + const offsetX = random(-30, 30); + const slice = g.get(0, sliceY, g.width, sliceH); + g.image(slice, offsetX, sliceY); + } + } + + // Brightness flicker during glitch + if (elapsed > 400 && elapsed < 800 && frameCount % 3 === 0) { + g.fill(255, 255, 255, random(20, 80)); + g.rectMode(CORNER); + g.rect(0, 0, g.width, g.height); + } + + // Fade out + if (elapsed > 700) { + bootFadeAlpha = map(elapsed, 700, 1000, 255, 0); + bootFadeAlpha = constrain(bootFadeAlpha, 0, 255); + const c = color(palette.FG); + g.fill(red(c), green(c), blue(c), bootFadeAlpha); + } + + // End boot + if (elapsed > 1000) { + booting = false; + gameEnterStart = millis(); + document.body.style.overflowY = "auto"; + } + } + } +} + +function drawReveal() { + const elapsed = millis() - revealPhaseStart; + + switch (revealPhase) { + case "shake": + shakeAmount = 5; + if (elapsed > 300) { + revealPhase = "glitch"; + revealPhaseStart = millis(); + revealCharIndex = 0; + revealFrameCounter = 0; + } + break; + + case "glitch": + for (let i = 0; i < 4; i++) { + const sliceY = random(g.height); + const sliceH = random(10, 50); + const offsetX = random(-20, 20); + const slice = g.get(0, sliceY, g.width, sliceH); + g.image(slice, offsetX, sliceY); + } + if (elapsed > 500) { + revealPhase = "typing"; + revealPhaseStart = millis(); + } + break; + + case "typing": { + g.fill(0, 0, 0, 200); + g.noStroke(); + g.rectMode(CORNER); + g.rect(0, 0, g.width, g.height); + + g.fill(palette.FG); + g.textFont("Courier"); + g.textSize(smaller * 0.035); + g.textAlign(CENTER, CENTER); + + revealFrameCounter++; + if (revealFrameCounter >= 3) { + revealFrameCounter = 0; + revealCharIndex++; + } + + const displayText = revealText.substring( + 0, + min(revealCharIndex, revealText.length), + ); + + // Word wrap + const maxWidth = g.width * 0.8; + const words = displayText.split(" "); + let lines = [""]; + let lineIdx = 0; + for (const word of words) { + const testLine = lines[lineIdx] + (lines[lineIdx] ? " " : "") + word; + if (g.textWidth(testLine) > maxWidth && lines[lineIdx]) { + lineIdx++; + lines[lineIdx] = word; + } else { + lines[lineIdx] = testLine; + } + } + + const lineH = smaller * 0.06; + const totalH = lines.length * lineH; + for (let i = 0; i < lines.length; i++) { + g.text( + lines[i], + g.width * 0.5, + g.height * 0.5 - totalH / 2 + i * lineH, + ); + } + + if (revealCharIndex >= revealText.length) { + if (millis() - revealPhaseStart > 500) { + revealPhase = "hold"; + revealPhaseStart = millis(); + } + } + break; + } + + case "hold": { + g.fill(0, 0, 0, 200); + g.noStroke(); + g.rectMode(CORNER); + g.rect(0, 0, g.width, g.height); + + g.fill(palette.FG); + g.textFont("Courier"); + g.textSize(smaller * 0.035); + g.textAlign(CENTER, CENTER); + + const holdWords = revealText.split(" "); + let holdLines = [""]; + let hLineIdx = 0; + const hMaxW = g.width * 0.8; + for (const word of holdWords) { + const testLine = + holdLines[hLineIdx] + (holdLines[hLineIdx] ? " " : "") + word; + if (g.textWidth(testLine) > hMaxW && holdLines[hLineIdx]) { + hLineIdx++; + holdLines[hLineIdx] = word; + } else { + holdLines[hLineIdx] = testLine; + } + } + const hLineH = smaller * 0.06; + const hTotalH = holdLines.length * hLineH; + for (let i = 0; i < holdLines.length; i++) { + g.text( + holdLines[i], + g.width * 0.5, + g.height * 0.5 - hTotalH / 2 + i * hLineH, + ); + } + + if (elapsed > 3000) { + revealPhase = "glitchout"; + revealPhaseStart = millis(); + } + break; + } + + case "glitchout": + for (let i = 0; i < 4; i++) { + const sliceY = random(g.height); + const sliceH = random(10, 50); + const offsetX = random(-20, 20); + const slice = g.get(0, sliceY, g.width, sliceH); + g.image(slice, offsetX, sliceY); + } + if (elapsed > 500) { + revealPhase = "done"; + revealing = false; + // If 100% reveal just ended, trigger completed state + if (revealsDone.includes(100)) { + completed = true; + completedTime = millis() - startTime; + shareDiv.show(); + } + } + break; + } +} + +function triggerReveal(percent) { + if (revealsDone.includes(percent)) return; + if (revealing || booting || terminalOpen) return; + + // Mid-drag edge case + if (refining) { + refining = false; + for (let num of numbers) { + num.turn(palette.FG); + num.refined = false; + } + } + + revealsDone.push(percent); + revealing = true; + revealText = HACKX_CONFIG.reveals[percent].text; + revealCharIndex = 0; + revealFrameCounter = 0; + revealPhase = "shake"; + revealPhaseStart = millis(); + shakeAmount = 5; + playMilestone(); +} + +function keyPressed() { + // Letter keys for hackx/exit buffers + if (key.length === 1 && key.match(/[a-z]/i)) { + const k = key.toLowerCase(); + + if (terminalOpen) { + exitBuffer.push(k); + if (exitBuffer.length > 4) exitBuffer.shift(); + if (exitBuffer.join("") === "exit") { + terminalOpen = false; + keyBuffer = []; + } + } else if (!booting && !revealing) { + keyBuffer.push(k); + if (keyBuffer.length > 5) keyBuffer.shift(); + if (keyBuffer.join("") === "hackx") { + terminalOpen = true; + exitBuffer = []; + } + } + } + + // Konami code tracking + const konamiKey = key.length > 1 ? key : key.toLowerCase(); + konamiBuffer.push(konamiKey); + if (konamiBuffer.length > 10) konamiBuffer.shift(); + if ( + konamiBuffer.length === 10 && + konamiBuffer.every((k, i) => k === konamiCode[i]) + ) { + rainbowMode = true; + rainbowModeStart = millis(); + konamiBuffer = []; + } +} + +function windowResized(ev) { + resizeCanvas(windowWidth, windowHeight); + g.resizeCanvas(windowWidth, windowHeight); + shaderLayer.resizeCanvas(windowWidth, windowHeight); + crtShader.setUniform("u_resolution", [g.width, g.height]); + + smaller = min(g.width, g.height); + buffer = max(50, min(100, smaller * 0.12)); + + sharedImg.resize(smaller * 0.5, 0); + nopeImg.resize(smaller * 0.5, 0); + completedImg.resize(smaller * 0.5, 0); + + refined.forEach((bin) => bin.resize(g.width / refined.length)); + + cellSize = (smaller - buffer * 2) / 10; + baseSize = cellSize * 0.33; + + cols = floor(g.width / cellSize); + rows = floor((g.height - buffer * 2) / cellSize); + let wBuffer = g.width - cols * cellSize; + + for (let j = 0; j < rows; j++) { + for (let i = 0; i < cols; i++) { + let x = i * cellSize + cellSize * 0.5 + wBuffer * 0.5; + let y = j * cellSize + cellSize * 0.5 + buffer; + const numToUpdate = numbers[i + j * cols]; + if (numToUpdate) numToUpdate.resize(x, y); + } + } + + // Re-scatter dust particles across new dimensions + for (let i = 0; i < dustParticles.length; i++) { + dustParticles[i].x = random(g.width); + dustParticles[i].y = random(g.height); + } +} diff --git a/hackx/style.css b/hackx/style.css new file mode 100644 index 00000000..a7bcbdd9 --- /dev/null +++ b/hackx/style.css @@ -0,0 +1,1895 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +* { + margin: 0; + padding: 0; +} + +html { + scroll-behavior: smooth; + scrollbar-width: thin; + scrollbar-color: #4fd1d9 #0a1628; +} + +body { + background-color: #0a1628; + color: #4fd1d9; + font-family: "Geist Pixel", "Courier New", Courier, monospace; + overflow-x: hidden; + overflow-y: hidden; + letter-spacing: 0.04em; + --accent: #4fd1d9; + --red: var(--accent); + --cyan: var(--accent); + --purple: var(--accent); + --gold: var(--accent); + --text: #a0c4cc; + --text-dim: #5a8a94; + --bg: #0a1628; + cursor: none; +} + +/* Hide system cursor across interactive elements */ +a, +button, +input, +textarea, +select, +label { + cursor: none; +} + +/* TV CRT Vignette Overlay */ +.vignette-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: 9999; + background: radial-gradient(ellipse at center, + transparent 50%, + rgba(0, 0, 0, 0.3) 75%, + rgba(0, 0, 0, 0.95) 100%); + box-shadow: inset 0 0 12vmin rgba(0, 0, 0, 0.9); +} + +/* Scan-line overlay removed — was causing compositing overhead */ + +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: #0a1628; +} + +::-webkit-scrollbar-thumb { + background: #4fd1d9; +} + +/* ===== KEYFRAMES ===== */ + +@keyframes breathe { + + 0%, + 100% { + border-color: #4fd1d920; + } + + 50% { + border-color: #4fd1d9a0; + } +} + +@keyframes flicker { + + 0%, + 100% { + opacity: 1; + } + + 92% { + opacity: 1; + } + + 93% { + opacity: 0.8; + } + + 94% { + opacity: 1; + } + + 96% { + opacity: 0.9; + } + + 97% { + opacity: 1; + } +} + +@keyframes subtlePulse { + + 0%, + 100% { + opacity: 0.6; + } + + 50% { + opacity: 1; + } +} + +@keyframes scanDown { + 0% { + background-position: 0 -100vh; + } + + 100% { + background-position: 0 100vh; + } +} + +@keyframes pulse-border { + + 0%, + 100% { + border-color: #4fd1d9; + box-shadow: 0 0 5px #4fd1d930; + } + + 50% { + border-color: #ff5252; + box-shadow: 0 0 20px #4fd1d960; + } +} + +@keyframes bounce { + + 0%, + 100% { + transform: translateX(-50%) translateY(0); + } + + 50% { + transform: translateX(-50%) translateY(10px); + } +} + +@keyframes faq-reveal { + 0% { + opacity: 0; + transform: translateX(-5px); + } + + 50% { + opacity: 0.5; + transform: translateX(3px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +/* ===== SECTIONS ===== */ +.section { + position: relative; + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.section canvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; +} + +.section-content { + position: relative; + z-index: 1; + max-width: 1000px; + width: 90%; + margin: 0 auto; + padding: 120px 20px; +} + +/* ===== HERO SECTION ===== */ +#hero-section { + height: 100vh; + position: relative; + display: block; + padding: 0; +} + +#game-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; +} + +#game-container>canvas:first-of-type { + display: block; + touch-action: none; + cursor: none; +} + +#scroll-hint { + position: absolute; + bottom: 40px; + left: 50%; + transform: translateX(-50%); + z-index: 10; + color: var(--cyan); + font-family: "Geist Pixel", "Courier New", monospace; + font-size: 11px; + letter-spacing: 4px; + text-transform: uppercase; + animation: bounce 2s infinite; + pointer-events: none; +} + +/* ===== GLITCH TEXT ===== */ +.glitch-text { + font-size: clamp(40px, 8vw, 120px); + font-weight: bold; + letter-spacing: 0.15em; + color: #4fd1d9; + text-transform: uppercase; + margin-bottom: 40px; + line-height: 1; + text-align: center; + position: relative; + text-shadow: + 0 0 40px #4fd1d940, + 0 0 80px #4fd1d920; +} + +.glitch-text::before, +.glitch-text::after { + display: none !important; + content: none !important; +} + +/* ===== ABOUT SECTION ===== */ +#about-section { + border-top: 2px solid #4fd1d920; +} + +.section-desc { + font-size: clamp(13px, 1.8vw, 16px); + line-height: 2; + max-width: 640px; + margin: 0 auto 60px; + text-align: left; + color: var(--text); + border-left: 2px solid #4fd1d940; + padding-left: 20px; +} + +.about-layout { + display: flex; + gap: 60px; + align-items: center; +} + +.about-left { + flex: 1; +} + +.about-left .glitch-text { + text-align: left; + margin-bottom: 12px; +} + +.about-tagline { + font-size: 11px; + letter-spacing: 5px; + color: var(--cyan); + margin-bottom: 24px; + opacity: 0.8; +} + +.about-desc { + font-size: 14px; + line-height: 1.8; + color: var(--text); + margin-bottom: 30px; + max-width: 420px; +} + +.about-cta { + display: inline-block; + font-size: 11px; + letter-spacing: 3px; + color: var(--red); + text-decoration: none; + border: 1px solid var(--red); + padding: 12px 24px; + transition: all 0.3s ease; +} + +.about-cta:hover { + background: var(--red); + color: var(--bg); + box-shadow: + 0 0 20px #4fd1d950, + 0 0 40px #4fd1d920; +} + +.about-tags { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 24px; +} + +.about-tags span { + font-size: 10px; + letter-spacing: 2px; + padding: 8px 16px; + border: 1px solid #4fd1d940; + color: var(--red); + text-transform: uppercase; + transition: all 0.3s ease; + cursor: default; +} + +.about-tags span:hover { + background: var(--red); + color: var(--bg); + box-shadow: 0 0 12px #4fd1d930; +} + +.about-left .apply-button { + margin-top: 18px; + min-height: 44px; + display: inline-block; +} + +.about-left .apply-button > iframe { + border: 0; +} + +.about-right { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4px; + flex-shrink: 0; + width: 340px; +} + +.about-model { + width: 420px; + height: 420px; + border: none; + background: transparent; + box-shadow: none; +} + +.about-model:focus { + outline: none; +} + +.about-stat { + border: 1px solid #4fd1d918; + padding: 32px 20px; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + aspect-ratio: 1; + opacity: 0; + transform: translateY(20px); + transition: + opacity 0.6s ease-out, + border-color 0.3s ease, + background 0.3s ease, + box-shadow 0.3s ease, + transform 0.4s ease-out; +} + +.about-stat.visible { + opacity: 1; + transform: translateY(0); +} + +.about-stat:hover { + border-color: #4fd1d940; + background: #ff174808; + box-shadow: 0 0 12px #4fd1d915; + transform: translateY(-5px); +} + +.about-stat-value { + font-size: clamp(16px, 2.5vw, 22px); + font-weight: bold; + letter-spacing: 2px; + color: var(--red); + margin-bottom: 6px; + text-shadow: 0 0 10px #4fd1d925; +} + +.about-stat-label { + font-size: 8px; + letter-spacing: 4px; + color: var(--text-dim); + text-transform: uppercase; +} + +/* ===== TRACKS SECTION — Full-width Stacked Blocks ===== */ +#tracks-section { + border-top: 2px solid #4fd1d920; +} + +.track-block { + position: relative; + border-top: 1px solid #4fd1d925; + border-bottom: 1px solid #4fd1d925; + overflow: hidden; + min-height: 360px; + margin-bottom: -1px; + transition: all 0.4s ease; +} + +.track-block:hover { + background: #ff174806; + border-color: #4fd1d990; +} + +.track-block canvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; +} + +.track-content { + position: relative; + z-index: 1; + padding: 60px 40px; + max-width: 700px; +} + +.track-number { + font-size: clamp(64px, 10vw, 120px); + font-weight: bold; + color: #4fd1d910; + line-height: 1; + letter-spacing: -0.04em; + margin-bottom: 8px; + pointer-events: none; +} + +.track-content h2 { + font-size: clamp(28px, 5vw, 52px); + letter-spacing: 0.12em; + margin-bottom: 6px; + line-height: 1.1; + text-shadow: 0 0 30px #4fd1d920; +} + +.track-subtitle { + font-size: 10px; + letter-spacing: 4px; + color: var(--cyan); + margin-bottom: 24px; + text-transform: uppercase; + opacity: 0.8; +} + +.track-subtitle::before { + content: "// "; + color: var(--cyan); + opacity: 0.5; +} + +.track-content p { + font-size: 13px; + line-height: 1.9; + color: var(--text); + margin-bottom: 24px; + max-width: 500px; +} + +.track-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.track-tags span { + font-size: 9px; + letter-spacing: 2px; + padding: 6px 12px; + border: 1px solid #4fd1d940; + color: var(--purple); + text-transform: uppercase; + transition: all 0.3s ease; +} + +.track-tags span:hover { + border-color: var(--purple); + color: var(--purple); + box-shadow: 0 0 10px #4fd1d920; +} + +/* Alternate track blocks: even ones shift right */ +.track-block:nth-child(even) .track-content { + margin-left: auto; + text-align: right; +} + +.track-block:nth-child(even) .track-tags { + justify-content: flex-end; +} + +/* ===== PRIZES SECTION ===== */ +#prizes-section { + border-top: 2px solid #4fd1d920; +} + +.prizes-layout { + max-width: 800px; + margin: 0 auto; +} + +.prizes-pool { + text-align: center; + padding: 50px 20px 40px; + margin-bottom: 40px; +} + +.prizes-pool-label { + font-size: 10px; + letter-spacing: 6px; + color: var(--text-dim); + margin-bottom: 16px; +} + +.prizes-pool-amount { + font-size: clamp(48px, 12vw, 100px); + font-weight: bold; + letter-spacing: 0.02em; + line-height: 1; + color: var(--red); +} + +.prizes-pool-sub { + font-size: 10px; + letter-spacing: 4px; + color: var(--cyan); + margin-top: 16px; + opacity: 0.7; +} + +.prizes-tracks { + display: flex; + gap: 0; + border: 1px solid #4fd1d920; +} + +.prizes-track { + flex: 1; + padding: 30px; +} + +.prizes-divider { + width: 1px; + background: #4fd1d920; +} + +.prizes-track-label { + font-size: 10px; + letter-spacing: 4px; + color: var(--cyan); + margin-bottom: 24px; + opacity: 0.7; +} + +.prizes-track-prizes { + display: flex; + flex-direction: column; + gap: 20px; +} + +.prize-place { + display: flex; + align-items: baseline; + gap: 16px; +} + +.prize-place-rank { + font-size: 11px; + letter-spacing: 3px; + color: var(--text-dim); + min-width: 30px; +} + +.prize-place-amount { + font-size: clamp(24px, 4vw, 36px); + font-weight: bold; + color: var(--red); +} + +.prize-amount { + font-weight: bold; +} + +/* ===== TIMELINE SECTION ===== */ +#timeline-section { + border-top: 2px solid #4fd1d920; +} + +.timeline { + position: relative; + max-width: 800px; + margin: 0 auto; + padding-left: 0; +} + +.timeline::before { + content: ""; + position: absolute; + left: 140px; + top: 0; + bottom: 0; + width: 1px; + background: #4fd1d920; +} + +.timeline-item { + position: relative; + display: flex; + align-items: baseline; + gap: 40px; + padding: 30px 0; + border-bottom: 1px solid #4fd1d910; + opacity: 0; + transform: translateY(10px); + transition: all 0.6s ease; +} + +.timeline-item.visible { + opacity: 1; + transform: translateY(0); +} + +.timeline-dot { + position: absolute; + left: 136px; + top: 38px; + width: 9px; + height: 9px; + border: 1px solid #4fd1d940; + background: #0a1628; + transition: all 0.4s ease; + z-index: 1; +} + +.timeline-item.visible .timeline-dot { + border-color: #4fd1d9cc; + box-shadow: 0 0 8px #4fd1d930; +} + +.timeline-item.active .timeline-dot { + background: var(--gold); + border-color: var(--gold); + box-shadow: 0 0 20px #4fd1d960; +} + +.timeline-date { + font-size: clamp(20px, 3vw, 32px); + font-weight: bold; + letter-spacing: 0.06em; + color: #4fd1d9b0; + min-width: 120px; + text-align: right; + flex-shrink: 0; +} + +.timeline-item.active .timeline-date { + color: var(--gold); + text-shadow: 0 0 20px #4fd1d930; +} + +.timeline-event { + font-size: clamp(14px, 2vw, 18px); + letter-spacing: 3px; + padding-left: 30px; + color: var(--text); +} + +.timeline-item.active .timeline-event { + color: var(--gold); +} + +/* ===== FAQ SECTION ===== */ +#faq-section { + border-top: 2px solid #4fd1d920; +} + +.faq-list { + max-width: 700px; + margin: 0 auto; +} + +.faq-item { + border: none; + border-bottom: 1px solid #4fd1d918; + margin-bottom: 0; + overflow: hidden; + transition: all 0.3s ease; +} + +.faq-item:first-child { + border-top: 1px solid #4fd1d918; +} + +.faq-item:hover { + background: #ff174806; +} + +.faq-question { + padding: 22px 0; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + font-size: 13px; + letter-spacing: 2px; + transition: all 0.3s ease; + background: none; +} + +.faq-question::before { + content: ">"; + margin-right: 12px; + color: var(--cyan); + font-size: 14px; + opacity: 0.5; + transition: all 0.3s ease; +} + +.faq-item:hover .faq-question::before, +.faq-item.open .faq-question::before { + color: var(--cyan); + opacity: 1; +} + +.faq-question:hover { + background: none; + color: #4fd1d9; +} + +.faq-toggle { + font-size: 16px; + transition: transform 0.3s ease; + color: #4fd1d990; + flex-shrink: 0; +} + +.faq-item.open .faq-toggle { + transform: rotate(45deg); + color: #4fd1d9; +} + +.faq-answer { + max-height: 0; + overflow: hidden; + transition: + max-height 0.4s ease, + padding 0.4s ease; + font-size: 12px; + line-height: 1.9; + color: var(--text); + padding: 0 0 0 22px; + border-left: 1px solid var(--cyan); + margin-left: 8px; +} + +.faq-item.open .faq-answer { + max-height: 250px; + padding: 0 0 24px 22px; +} + +/* ===== PERKS (WHAT YOU GET) — Bento Grid ===== */ +#perks-section { + border-top: 2px solid #4fd1d920; + position: relative; +} + +#perks-bg-canvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; +} + +.perks-bento { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + grid-template-rows: auto auto auto; + gap: 3px; + max-width: 750px; + margin: 0 auto; + position: relative; + z-index: 1; +} + +@media (max-width: 480px) { + .perks-bento { + display: flex; + flex-direction: column; + gap: 8px; + max-width: 98vw; + width: 100%; + align-items: stretch; + } + .bento-cell { + width: 100%; + min-width: 0; + padding: 20px 12px; + box-sizing: border-box; + border-radius: 8px; + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + .bento-hero, .bento-wide, .bento-network-row { + width: 100%; + min-width: 0; + border-radius: 8px; + grid-column: unset; + grid-row: unset; + } + .centered-text-flex{ + display:flex; + flex-direction:column; + justify-content:center; + align-items:center; + } +} + + +.bento-cell { + border: 1px solid #4fd1d918; + padding: 36px 24px; + transition: + border-color 0.3s ease, + background 0.3s ease; +} + +.bento-cell:hover { + border-color: #4fd1d940; + background: #ff174806; +} + +.bento-hero { + grid-row: 1 / 3; + display: flex; + flex-direction: column; + justify-content: center; + border-color: #4fd1d925; +} + +.bento-wide { + grid-column: 1 / 4; +} + +.bento-network-row { + grid-column: 2 / 4; + grid-row: 2 / 3; + z-index: 2; + position: relative; +} + + + +.centered-text-flex{ + display:flex; + flex-direction:column; + justify-content:center; + align-items:center; +} +.bento-val { + font-size: clamp(28px, 4vw, 44px); + font-weight: bold; + color: var(--red); + line-height: 1.1; + margin-bottom: 8px; +} + +.bento-hero .bento-val { + font-size: clamp(52px, 10vw, 90px); +} + +.bento-wide .bento-val { + font-size: clamp(14px, 2vw, 20px); + letter-spacing: 4px; + color: var(--cyan); +} + +.bento-label { + font-size: 10px; + letter-spacing: 3px; + color: var(--text-dim); +} + +.bento-sub { + font-size: 9px; + letter-spacing: 3px; + color: var(--cyan); + margin-top: 8px; + opacity: 0.6; +} + +/* ===== THE TEAM ===== */ +#team-section { + border-top: 2px solid #4fd1d920; +} + +.team-counter { + text-align: center; + margin-bottom: 50px; +} + +.team-number { + font-size: clamp(80px, 15vw, 160px); + font-weight: bold; + letter-spacing: -0.04em; + line-height: 1; + color: #4fd1d915; + display: block; +} + +.team-label { + font-size: 10px; + letter-spacing: 6px; + color: var(--cyan); + opacity: 0.7; +} + +.team-marquee-wrap { + overflow: hidden; + width: 100%; + margin-bottom: 12px; + -webkit-mask-image: linear-gradient(90deg, + transparent, + black 10%, + black 90%, + transparent); + mask-image: linear-gradient(90deg, + transparent, + black 10%, + black 90%, + transparent); +} + +.team-marquee { + display: flex; + gap: 12px; + width: max-content; + animation: marqueeScroll 40s linear infinite; +} + +.team-marquee-wrap.reverse .team-marquee { + animation: marqueeScrollReverse 35s linear infinite; +} + +@keyframes marqueeScroll { + 0% { + transform: translateX(0); + } + + 100% { + transform: translateX(-50%); + } +} + +@keyframes marqueeScrollReverse { + 0% { + transform: translateX(-50%); + } + + 100% { + transform: translateX(0); + } +} + +.team-card { + border: 1px solid #4fd1d915; + padding: 20px 16px; + text-align: center; + transition: border-color 0.3s ease; + text-decoration: none; + color: inherit; +} + +a.team-card { + cursor: pointer; +} + +.team-card:hover { + border-color: #4fd1d940; +} + +.team-photo { + width: 56px; + height: 56px; + border-radius: 50%; + margin: 0 auto 12px; + border: 1px solid #4fd1d930; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + font-weight: bold; + color: #4fd1d940; + letter-spacing: 1px; + overflow: hidden; + background: #ff174808; +} + +.team-photo img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 50%; +} + +.team-name { + font-size: 11px; + letter-spacing: 2px; + margin-bottom: 4px; + color: var(--text); +} + +.team-role { + font-size: 8px; + letter-spacing: 3px; + color: var(--cyan); + opacity: 0.7; + margin-bottom: 6px; + text-transform: uppercase; +} + +.team-cred { + font-size: 8px; + letter-spacing: 1px; + color: var(--text-dim); + line-height: 1.5; +} + +.team-dept { + font-size: 7px; + letter-spacing: 2px; + color: var(--text-dim); + margin-top: 4px; +} + +.team-row-label { + font-size: 9px; + letter-spacing: 4px; + color: var(--cyan); + opacity: 0.6; + margin-bottom: 8px; + margin-top: 20px; + text-align: left; + max-width: 900px; + margin-left: auto; + margin-right: auto; + padding-left: 4px; +} + +.team-note { + text-align: center; + font-size: 10px; + letter-spacing: 4px; + color: var(--text-dim); + margin-top: 20px; +} + +/* ===== SPONSORS ===== */ +#sponsors-section { + border-top: 2px solid #4fd1d920; +} + +.sponsors-tiers { + max-width: 800px; + margin: 0 auto 40px; +} + +.sponsor-tier { + margin-bottom: 40px; +} + +.tier-label { + font-size: 10px; + letter-spacing: 4px; + color: var(--cyan); + opacity: 0.7; + margin-bottom: 16px; + text-align: center; +} + +.sponsor-logos { + display: flex; + justify-content: center; + gap: 20px; + flex-wrap: wrap; +} + +.sponsor-slot { + border: 1px dashed #4fd1d925; + padding: 30px 40px; + font-size: 12px; + letter-spacing: 3px; + color: #4fd1d940; + text-align: center; + min-width: 200px; + transition: border-color 0.3s ease; +} + +.sponsor-slot:hover { + border-color: #4fd1d950; +} + +.title-tier .sponsor-slot { + padding: 50px 60px; + font-size: 14px; + min-width: 300px; + border-color: var(--gold); + color: var(--gold); + opacity: 0.5; +} + +.sponsor-slot.small { + padding: 20px 24px; + min-width: 140px; + font-size: 10px; +} + +.sponsors-cta { + text-align: center; + font-size: 12px; + letter-spacing: 3px; + color: var(--text-dim); +} + +.sponsors-cta a { + color: var(--red); + text-decoration: none; + border-bottom: 1px solid var(--red); + transition: opacity 0.3s; +} + +.sponsors-cta a:hover { + opacity: 0.7; +} + +.past-sponsors { + margin-top: 60px; + padding-top: 40px; + border-top: 1px solid #4fd1d915; +} + +.past-sponsors-label { + font-size: 10px; + letter-spacing: 4px; + color: var(--text-dim); + text-align: center; + margin-bottom: 20px; +} + +.past-sponsors-marquee-wrap { + overflow: hidden; + width: 100%; + -webkit-mask-image: linear-gradient(90deg, + transparent, + black 10%, + black 90%, + transparent); + mask-image: linear-gradient(90deg, + transparent, + black 10%, + black 90%, + transparent); +} + +.past-sponsors-marquee { + display: flex; + gap: 40px; + width: max-content; + animation: marqueeScroll 30s linear infinite; +} + +.past-sponsors-marquee span { + font-size: 12px; + letter-spacing: 4px; + color: #4fd1d935; + white-space: nowrap; + transition: color 0.3s ease; +} + +.past-sponsors-marquee span:hover { + color: #4fd1d970; +} + +/* ===== FOOTER ===== */ +#footer-section { + position: relative; + border-top: 2px solid #4fd1d920; + padding: 80px 20px; + overflow: hidden; +} + +#footer-section canvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; +} + +.footer-content { + position: relative; + z-index: 1; + max-width: 700px; + margin: 0 auto; + text-align: left; +} + +.footer-brand { + margin-bottom: 40px; + padding-bottom: 30px; + border-bottom: 1px solid #4fd1d918; +} + +.footer-logo { + font-size: clamp(28px, 5vw, 48px); + font-weight: bold; + letter-spacing: 0.15em; + line-height: 1; +} + +.footer-tagline { + font-size: 10px; + letter-spacing: 5px; + color: var(--cyan); + margin-top: 8px; + text-transform: uppercase; + opacity: 0.7; +} + +.footer-tagline::before { + content: "// "; + color: var(--cyan); + opacity: 0.5; +} + +.footer-links { + display: flex; + justify-content: flex-start; + gap: 24px; + flex-wrap: wrap; + margin-bottom: 30px; +} + +.footer-links a { + color: #4fd1d9b0; + text-decoration: none; + font-size: 11px; + letter-spacing: 3px; + text-transform: uppercase; + transition: color 0.3s; + position: relative; +} + +.footer-links a::before { + content: "["; + margin-right: 2px; + color: #4fd1d980; +} + +.footer-links a::after { + content: "]"; + margin-left: 2px; + color: #4fd1d980; +} + +.footer-links a:hover { + color: #4fd1d9; +} + +.footer-links a:hover::before, +.footer-links a:hover::after { + color: #4fd1d9cc; +} + +.footer-social { + font-size: 11px; + letter-spacing: 2px; + color: #4fd1d980; + margin-bottom: 30px; + cursor: pointer; +} + +.footer-social span { + transition: color 0.3s; +} + +.footer-social span:hover { + color: #4fd1d9; + text-shadow: 0 0 10px #4fd1d930; +} + +.footer-copy { + font-size: 9px; + letter-spacing: 3px; + color: #4fd1d970; + text-transform: uppercase; +} + +/* ===== FIXED ELEMENTS ===== */ +#register-btn { + position: fixed; + bottom: 30px; + right: 30px; + z-index: 1000; + background: #0a1628; + border: 1px solid #4fd1d9; + color: #4fd1d9; + font-family: "Geist Pixel", "Courier New", monospace; + font-size: 12px; + padding: 12px 20px; + letter-spacing: 4px; + text-transform: uppercase; + text-decoration: none; + cursor: pointer; + transition: all 0.2s ease; +} + +#register-btn::before { + content: "> "; + color: #4fd1d9b0; +} + +#register-btn:hover { + background: #4fd1d9; + color: #0a1628; + box-shadow: 0 0 40px #4fd1d950; +} + +#register-btn:hover::before { + color: #0a162880; +} + +#announcement-bar { + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 999; + background: #0a1628; + border-bottom: 1px solid #4fd1d930; + color: #4fd1d9cc; + font-family: "Geist Pixel", "Courier New", monospace; + font-size: 11px; + padding: 8px 0; + text-align: center; + letter-spacing: 3px; + text-transform: uppercase; + opacity: 0; + transition: opacity 0.5s ease; + pointer-events: none; +} + +#announcement-bar.visible { + opacity: 1; +} + +.loadingclass { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100vh; + z-index: 9999; + display: grid; + place-content: center; + font-family: "Geist Pixel", "Courier New", monospace; + font-size: 14px; + letter-spacing: 4px; + color: #4fd1d9; + background: #0a1628; + text-transform: uppercase; +} + +.loadingclass::after { + content: ""; + display: block; + width: 40px; + height: 1px; + background: #4fd1d9; + margin: 16px auto 0; + animation: subtlePulse 1.5s infinite; +} + +/* ===== MICRO INTERACTIONS ===== */ +.track-block, +.readout-row, +.prize-entry { + transition: + background 0.4s ease, + border-color 0.4s ease; + will-change: auto; +} + +/* Keep old selectors for sections.js compatibility */ +.info-card, +.prize-card, +.track-card { + transition: + background 0.4s ease, + border-color 0.4s ease; + will-change: auto; +} + +.glitch-hover { + animation: none !important; +} + +.timeline-dot { + transition: all 0.4s ease; +} + +/* ===== SCROLL PROGRESS ===== */ +#scroll-progress { + position: fixed; + top: 0; + left: 0; + height: 3px; + background: var(--red); + z-index: 10000; + transition: width 0.1s linear; + pointer-events: none; +} + +/* ===== EASTER EGG FLASH ===== */ +.easter-egg-flash { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 10001; + font-family: "Geist Pixel", "Courier New", monospace; + font-size: clamp(14px, 2.5vw, 20px); + letter-spacing: 4px; + text-align: center; + pointer-events: none; + animation: flashIn 2s ease forwards; +} + +@keyframes flashIn { + 0% { + opacity: 0; + transform: translate(-50%, -50%) scale(0.8); + } + + 15% { + opacity: 1; + transform: translate(-50%, -50%) scale(1.05); + } + + 85% { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + + 100% { + opacity: 0; + transform: translate(-50%, -50%) scale(1.1); + } +} + +/* ===== BOTTOM SECRET MESSAGE ===== */ +#bottom-secret { + text-align: center; + padding: 40px 20px; + font-size: 12px; + letter-spacing: 3px; + color: var(--purple); + opacity: 0; + transition: opacity 1s ease; +} + +#bottom-secret.visible { + opacity: 1; +} + +/* ===== IDLE MESSAGE ===== */ +.idle-message { + position: fixed; + bottom: 80px; + left: 50%; + transform: translateX(-50%); + z-index: 999; + font-family: "Geist Pixel", "Courier New", monospace; + font-size: 12px; + letter-spacing: 2px; + color: var(--gold); + pointer-events: auto; + animation: flashIn 5s ease forwards; +} + +.idle-message a { + color: var(--red); + text-decoration: none; + border-bottom: 1px solid var(--red); +} + +/* ===== TRACK TAG FLASH ===== */ +.track-tags span { + cursor: pointer; +} + +/* ===== HOVER WARMTH ===== */ +.hover-warmth { + transition: + box-shadow 0.4s ease, + background 0.4s ease; +} + +.hover-warmth:hover { + box-shadow: + inset 0 0 30px #4fd1d908, + 0 0 15px #4fd1d906; +} + +/* ===== TIMELINE LINE DRAW ===== */ +.timeline-line { + position: absolute; + left: 140px; + top: 0; + width: 1px; + height: 0; + background: var(--red); + z-index: 1; + transition: none; +} + +/* ===== FIREWORKS CANVAS ===== */ +#fireworks-canvas { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 10001; + pointer-events: none; +} + +/* ===== TABLET (max 768px) ===== */ +@media (max-width: 768px) { + .section-content { + padding: 70px 16px; + } + + .glitch-text { + font-size: clamp(32px, 9vw, 64px); + letter-spacing: 0.1em; + margin-bottom: 30px; + } + + /* About */ + .about-layout { + flex-direction: column; + gap: 30px; + } + + .about-right { + width: 100%; + } + + .about-right { + display: block; + } + + .about-model { + width: min(520px, 96vw); + height: min(520px, 96vw); + margin: 0 auto; + } + + .about-left .glitch-text { + text-align: center; + } + + .about-tagline, + .about-desc { + text-align: center; + } + + .about-cta { + display: block; + text-align: center; + } + + .about-stat-value { + font-size: clamp(16px, 4vw, 22px); + } + + /* Perks */ + .perks-bento { + grid-template-columns: 1fr 1fr; + } + + .bento-hero { + grid-column: 1 / -1; + grid-row: auto; + text-align: center; + padding: 30px 16px; + } + + .bento-wide { + grid-column: 1 / -1; + } + + .bento-cell { + padding: 24px 16px; + } + + .bento-hero .bento-val { + font-size: clamp(40px, 12vw, 64px); + } + + /* Prizes */ + .prizes-tracks { + flex-direction: column; + } + + .prizes-divider { + width: 100%; + height: 1px; + } + + .prizes-track { + padding: 24px 20px; + } + + .prizes-pool-amount { + font-size: clamp(36px, 10vw, 64px); + } + + /* Timeline */ + .timeline::before { + left: 0; + } + + .timeline-item { + flex-direction: column; + gap: 4px; + padding-left: 20px; + } + + .timeline-dot { + left: -4px; + top: 34px; + } + + .timeline-date { + text-align: left; + min-width: auto; + font-size: clamp(16px, 4vw, 24px); + } + + .timeline-event { + padding-left: 0; + font-size: 12px; + } + + /* FAQ */ + .faq-question { + font-size: 12px; + letter-spacing: 1px; + } + + .faq-answer { + font-size: 11px; + } + + /* Team */ + .team-card { + padding: 14px 10px; + min-width: 110px; + } + + .team-photo { + width: 40px; + height: 40px; + font-size: 13px; + } + + .team-name { + font-size: 9px; + } + + .team-role { + font-size: 7px; + } + + .team-counter .team-number { + font-size: clamp(60px, 15vw, 100px); + } + + .team-row-label { + font-size: 8px; + letter-spacing: 3px; + } + + /* Sponsors */ + .sponsor-logos { + flex-direction: column; + align-items: center; + } + + .sponsor-slot { + min-width: 80%; + padding: 20px; + } + + .title-tier .sponsor-slot { + min-width: 90%; + padding: 30px; + } + + .past-sponsors-marquee span { + font-size: 10px; + letter-spacing: 3px; + } + + /* Footer */ + .footer-links { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } + + .footer-logo { + font-size: clamp(24px, 6vw, 36px); + } + + /* Register button */ + #register-btn { + bottom: 12px; + right: 12px; + font-size: 9px; + padding: 8px 12px; + letter-spacing: 3px; + } + + /* Scroll hint */ + #scroll-hint { + font-size: 9px; + letter-spacing: 3px; + bottom: 30px; + } +} + +/* ===== SMALL PHONE (max 480px) ===== */ +@media (max-width: 480px) { + .section-content { + padding: 50px 12px; + } + + .glitch-text { + font-size: clamp(26px, 10vw, 48px); + letter-spacing: 0.08em; + margin-bottom: 24px; + } + + /* About */ + .about-desc { + font-size: 12px; + } + + .about-stat { + padding: 16px 10px; + } + + .about-stat-value { + font-size: 16px; + } + + .about-tagline { + font-size: 9px; + letter-spacing: 3px; + } + + /* Perks */ + .perks-bento { + grid-template-columns: 1fr; + } + + .bento-hero { + grid-column: 1; + } + + .bento-wide { + grid-column: 1; + } + + /* Prizes */ + .prize-place-amount { + font-size: clamp(20px, 5vw, 28px); + } + + .prizes-pool-amount { + font-size: clamp(32px, 12vw, 48px); + } + + /* Team */ + .team-card { + min-width: 90px; + padding: 10px 8px; + } + + .team-photo { + width: 32px; + height: 32px; + font-size: 11px; + } + + .team-name { + font-size: 8px; + letter-spacing: 1px; + } + + .team-role { + font-size: 6px; + } + + /* Footer */ + .footer-social { + font-size: 10px; + } + + .footer-copy { + font-size: 8px; + } +} + +/* ===== MUSIC COLOR OSCILLATION ===== */ +html.theme-oscillating { + transition: filter 0.35s ease; +} + +html.theme-blue { + filter: hue-rotate(-108deg); +} \ No newline at end of file diff --git a/hackx/utils.js b/hackx/utils.js new file mode 100644 index 00000000..91e0c8db --- /dev/null +++ b/hackx/utils.js @@ -0,0 +1,32 @@ +// suggested solution from https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#avoiding_user_agent_detection +// to detect touch device rather than using user agent +const isTouchScreenDevice = () => { + let hasTouchScreen = false; + if ("maxTouchPoints" in navigator) { + hasTouchScreen = navigator.maxTouchPoints > 0; + } else if ("msMaxTouchPoints" in navigator) { + hasTouchScreen = navigator.msMaxTouchPoints > 0; + } else { + const mQ = window.matchMedia && matchMedia("(pointer:coarse)"); + if (mQ && mQ.media === "(pointer:coarse)") { + hasTouchScreen = !!mQ.matches; + } else if ('orientation' in window) { + hasTouchScreen = true; // deprecated, but good fallback + } else { + // Only as a last resort, fall back to user agent sniffing + const UA = navigator.userAgent; + hasTouchScreen = ( + /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) || + /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA) + ); + } + } + return hasTouchScreen; +} +// Takes a value in seconds, returns hh:mm:ss:ms +const createTimeString = (seconds) => { + const baseString = new Date(seconds * 1000).toISOString().substring(11, 23); + const hhmm = baseString.split(':'); + const ssms = hhmm[2].split('.'); + return `${hhmm[0]}h ${hhmm[1]}m ${ssms[0]}s ${ssms[1]}ms`; +} \ No newline at end of file