let rotateT = 0; let currentid = 0; let freezeSystem = false; let rotateTEase = 0; let System = []; let selected = null; let targetRot = 0; let lerpCol; let lerpToCol; let lerpBlack =0; // Custom cursor element: id + class = learnMore // Color via Webflow variable --learn; size via width/height on #learnMore const CURSOR_SELECTOR = '#learnMore'; const CURSOR_HOVER_SIZE = 32; const CURSOR_DEFAULT_SIZE = 24; const CURSOR_ANIM_MS = 200; let cursorEl = null; let cursorSize = CURSOR_DEFAULT_SIZE; let cursorCol = [255, 255, 255]; let cursorControlling = false; let cursorAnim = { start: 0, fromSize: CURSOR_DEFAULT_SIZE, toSize: CURSOR_DEFAULT_SIZE, fromCol: [255, 255, 255], toCol: [255, 255, 255], bounce: false, leaving: false, hoveredId: null }; function setup() { var Canvas =createCanvas(windowWidth, windowHeight); Canvas.parent('Drawing'); textFont("Inter"); strokeCap(ROUND); cursor('none'); initCustomCursorDefaults(); lerpCol = color(218, 165, 32); System.push(new Planet(width / 40 ,0.012, 266, "Instagram", 276, 67, 100, 0,"https://www.instagram.com/adam.bort.art/")); System.push(new Planet(width / 40 ,0.012, 223, "LinkedIn", 361, 70, 100, 1/3*PI,"https://www.linkedin.com/in/adamborthwick/")); System.push(new Planet(width / 40 ,0.012, 192, "Resume", 173, 70, 100, 2/3*PI,"https://drive.google.com/file/d/1EL5xVZJJHxuR4r-qmmos0W5UYXusZw2F/view?usp=sharing")); System.push(new Planet(width / 25 ,0, 39, "Adam Borthwick", 60, 75, 100, PI/8,"https://www.adamborthwick.com/about")); updateSizes(); } function draw() { background("#0b0b0b"); cursor('none'); // Determine center based on screen size let centerX = (windowWidth > 767) ? 7.7 * width / 10 : width / 2; let centerY = height / 2; // Orbit rotation based on distance from mouse freezeSystem = false; for (let i = 0; i < System.length; i++) { if (System[i].mouseOver == true) { freezeSystem = true; } } if (freezeSystem == false){ lerpBlack = lerp(lerpBlack, 0,1); lerpCol = lerpColor(lerpCol, color(0,0,0), 0.1); drawingContext.shadowColor = lerpCol; drawingContext.shadowBlur = lerpBlack; let distToMouse = dist(mouseX, mouseY, centerX, centerY); targetRot += 200 / 60000; for (let i = 0; i < System.length; i++) { System[i].targetSpeed = 0.004; } } else{ for (let i = 0; i < System.length; i++) { System[i].targetSpeed = 0; } } rotateT = lerp(rotateT, targetRot, 0.5); for (let i = 0; i < System.length; i++) { System[i].createRings(); } let hoveredPlanet = null; for (let i = 0; i < System.length; i++) { System[i].updateCenter(centerX, centerY); System[i].checkHover(mouseX, mouseY); System[i].display(); System[i].speed = lerp(System[i].speed, System[i].targetSpeed, 0.08); System[i].move(); if (System[i].mouseOver) hoveredPlanet = System[i]; } updateCustomCursor(hoveredPlanet); } function windowResized() { resizeCanvas(windowWidth, windowHeight); updateSizes(); } function mousePressed() { for (let i = 0; i < System.length; i++) { if (System[i].mouseOver==true) { if (i == 3){ window.open(System[i].url, '_self'); // open in current tab } else{ window.open(System[i].url, '_blank'); // open in new tab } break; } } } function updateSizes() { for (let i = 0; i < System.length; i++) { if (windowWidth > 767) { System[i].lerptorad = (i === 3) ? windowWidth / 25 : windowWidth / 40; System[i].a = (i === 3) ? 0 : windowWidth / 5 ; System[i].b = (i === 3) ? 0 : System[i].a / 2.5 ; } else { System[i].lerptorad = (i === 3) ? windowWidth / 15 : windowWidth / 25; System[i].a = (i === 3) ? 0 : windowWidth / 3 ; System[i].b = (i === 3) ? 0 : System[i].a / 2.5 ; } } } function setLineDash(list) { drawingContext.setLineDash(list); } function initCustomCursorDefaults() { cursorEl = document.querySelector(CURSOR_SELECTOR); cursorSize = CURSOR_DEFAULT_SIZE; cursorCol = [255, 255, 255]; cursorControlling = false; } function getCursorEl() { if (!cursorEl || !document.contains(cursorEl)) { cursorEl = document.querySelector(CURSOR_SELECTOR); } return cursorEl; } function planetColorRgb(planet) { push(); colorMode(HSB, 360, 100, 100); let c = color(planet.hoverHue, planet.hoverSat, planet.hoverBr); let rgb = [red(c), green(c), blue(c)]; pop(); return rgb; } function easeOutBack(t) { const c1 = 1.70158; const c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2); } function parseCssColor(value) { if (!value) return null; value = value.trim(); let rgbMatch = value.match(/rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i); if (rgbMatch) { return [parseFloat(rgbMatch[1]), parseFloat(rgbMatch[2]), parseFloat(rgbMatch[3])]; } let hex = value.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i); if (hex) { let h = hex[1]; if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]; return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)]; } return null; } function sampleCursorFromPage() { let col = parseCssColor(getComputedStyle(document.documentElement).getPropertyValue('--learn')); if (col) cursorCol = col; let el = getCursorEl(); if (el) { let w = parseFloat(getComputedStyle(el).width); if (!isNaN(w) && w > 0) cursorSize = w; } } function applyLearnColor(rgb) { let colorStr = 'rgb(' + Math.round(rgb[0]) + ', ' + Math.round(rgb[1]) + ', ' + Math.round(rgb[2]) + ')'; document.documentElement.style.setProperty('--learn', colorStr); } function applyCursorSize(size) { let el = getCursorEl(); if (!el) return; let px = size + 'px'; el.style.width = px; el.style.height = px; } function releaseCursorOverrides() { document.documentElement.style.removeProperty('--learn'); let el = getCursorEl(); if (el) { el.style.removeProperty('width'); el.style.removeProperty('height'); } } function updateCustomCursor(hoveredPlanet) { let nextId = hoveredPlanet ? hoveredPlanet.id : null; if (nextId !== cursorAnim.hoveredId) { if (!cursorControlling && hoveredPlanet) { sampleCursorFromPage(); } if (!hoveredPlanet && !cursorControlling) { cursorAnim.hoveredId = null; return; } cursorAnim.hoveredId = nextId; cursorAnim.start = millis(); cursorAnim.fromSize = cursorSize; cursorAnim.fromCol = cursorCol.slice(); if (hoveredPlanet) { cursorControlling = true; cursorAnim.leaving = false; cursorAnim.toSize = CURSOR_HOVER_SIZE; cursorAnim.toCol = planetColorRgb(hoveredPlanet); cursorAnim.bounce = true; } else { cursorAnim.leaving = true; cursorAnim.toSize = CURSOR_DEFAULT_SIZE; cursorAnim.toCol = [255, 255, 255]; cursorAnim.bounce = true; } } if (!cursorControlling) return; let t = constrain((millis() - cursorAnim.start) / CURSOR_ANIM_MS, 0, 1); let e = cursorAnim.bounce ? easeOutBack(t) : t; cursorSize = lerp(cursorAnim.fromSize, cursorAnim.toSize, e); cursorCol = [ lerp(cursorAnim.fromCol[0], cursorAnim.toCol[0], e), lerp(cursorAnim.fromCol[1], cursorAnim.toCol[1], e), lerp(cursorAnim.fromCol[2], cursorAnim.toCol[2], e) ]; applyLearnColor(cursorCol); applyCursorSize(cursorSize); if (cursorAnim.leaving && t >= 1) { releaseCursorOverrides(); cursorControlling = false; cursorAnim.leaving = false; cursorSize = CURSOR_DEFAULT_SIZE; cursorCol = [255, 255, 255]; } } class Planet { constructor(r, hue, brightness, label, hoverHue, hoverSat, hoverBr, rot,url) { this.id = currentid++; this.url = url; this.hovers=0; this.rad = r; this.lerptorad = this.rad; this.hue = hue; this.brightness = brightness; this.label = label; this.hoverHue = hoverHue; this.hoverSat = hoverSat; this.hoverBr = hoverBr; this.mouseOver = false; this.targetSpeed = 0.012; this.radians = random(TWO_PI); this.speed = 0.012; this.rot = rot; if (windowWidth > 767) { this.a = windowWidth / 5; } else{ this.a = windowWidth / 3 } this.b = this.a / 2.5; if (this.id === 3) { this.a = 0; this.b = 0; } // Center planet } updateCenter(x, y) { this.centerX = x; this.centerY = y; } move() { this.radians += this.speed; } getPos() { let x = this.a * cos(this.radians); let y = this.b * sin(this.radians); let rotatedX = x * cos(this.rot + rotateT) - y * sin(this.rot + rotateT); let rotatedY = x * sin(this.rot + rotateT) + y * cos(this.rot + rotateT); return createVector(this.centerX + rotatedX, this.centerY + rotatedY); } createRings() { push(); drawingContext.shadowBlur = 0; translate(this.centerX, this.centerY); rotate(this.rot + rotateT); setLineDash([10, 10]); noFill(); stroke(255); ellipse(0, 0, this.a * 2, this.b * 2); pop(); } checkHover(mx, my) { let pos = this.getPos(); this.mouseOver = dist(mx, my, pos.x, pos.y) < (this.rad/2+20); } display() { colorMode(HSB); let pos = this.getPos(); if (this.mouseOver){ this.rad = lerp(this.rad,this.lerptorad +5 ,0.5); lerpBlack = 5; this.hovers =lerp(this.hovers, 60, 0.035); } else{ this.rad = lerp(this.rad,this.lerptorad ,0.5); lerpBlack = 0; this.hovers =lerp(this.hovers, 0, 1); } // Planet noStroke(); fill(this.hoverHue, this.hovers, this.hoverBr); push(); translate(pos.x, pos.y); drawingContext.shadowBlur = lerpBlack; drawingContext.shadowColor = color(this.hoverHue, this.hoverSat, this.hoverBr); circle(0, 0, this.rad); noFill(); strokeWeight(0.7); setLineDash([5, 5]); stroke(this.hoverHue, this.hoverSat, this.hoverBr); circle(0, 0, this.lerptorad+25); pop(); // Speech bubble if (this.mouseOver || windowWidth < 767) { this.showSpeech(pos); } } showSpeech(pos) { colorMode(HSB); lerpBlack =0; textAlign(LEFT); fill(this.hoverHue, this.hoverSat, this.hoverBr); drawingContext.shadowBlur = lerpBlack; lerpToCol = color(this.hoverHue, this.hoverSat, this.hoverBr); lerpCol = lerpColor(lerpCol, lerpToCol, 1); drawingContext.shadowColor = lerpCol; let lineY = pos.y + this.lerptorad / 2 + 30; let dotY = pos.y + this.lerptorad / 2 +13; stroke(this.hoverHue, this.hoverSat, this.hoverBr); circle(pos.x, dotY, 5); line(pos.x, dotY, pos.x, lineY); if (windowWidth > 767 && this.mouseOver) { let labelX = width/2/0.90; textAlign(CENTER) // line(pos.x, lineY, labelX, lineY); noStroke(); textSize(16); text(this.label, pos.x, lineY +16); } else{ let labelX = (pos.x < width / 2-1000) ? 15 : width - 35; (pos.x < width / 2) ? textAlign(LEFT) : textAlign(RIGHT); textAlign(CENTER); noStroke(); textSize(14); text(this.label, pos.x, lineY +14); } lerpBlack = 0; } }