From c35ba5079dc0663f0a5cb2036cb136e37b38fde0 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Thu, 31 Aug 2023 19:13:35 +0100 Subject: [PATCH 01/36] DEBUG draw clouds mode --- src/lib/utils.ts | 14 ++++++++ src/routes/weather/+page.server.ts | 20 ++++++++++-- src/routes/weather/+page.svelte | 11 +++---- src/routes/weather/Orbit.svelte | 32 ------------------- src/routes/weather/Scene.svelte | 51 ++++++++++++++++++++---------- src/routes/weather/weatherScene.ts | 19 +++++------ 6 files changed, 79 insertions(+), 68 deletions(-) delete mode 100644 src/routes/weather/Orbit.svelte diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e08b7a19..e7d8567b 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -3,4 +3,18 @@ type DateStyle = Intl.DateTimeFormatOptions['dateStyle'] export function formatDate(date: string, dateStyle: DateStyle = 'medium', locales = "en-gb") { const formatter = new Intl.DateTimeFormat(locales, { dateStyle }); return formatter.format(new Date(date)); +} + +export function randBetween(min: number, max: number) { + return (Math.random() * (max - min)) + min; +} + +export function randIntBetween(min: number, max: number) { + return Math.floor(randBetween(min, max)); +} + +export function randVariance(value: number, variance: number) { + const min = value * (1 - variance); + const max = value * (1 + variance); + return randBetween(min, max); } \ No newline at end of file diff --git a/src/routes/weather/+page.server.ts b/src/routes/weather/+page.server.ts index 0682cbb3..e3405548 100644 --- a/src/routes/weather/+page.server.ts +++ b/src/routes/weather/+page.server.ts @@ -13,7 +13,6 @@ const fetchWeather = async (latitude: number, longitude: number) => { const request = `${openMeteoBaseUrl}?latitude=${latitude}&longitude=${longitude}¤t_weather=true` const result = await fetch(request); const data = await result.json(); - console.log(data); return data; } @@ -33,7 +32,24 @@ export const load = (async () => { return weather.current_weather ?? undefined; } catch { - throw error(404, "Wasn't able to get any weather data.\n\n It's probably raining") + // throw error(404, "Wasn't able to get any weather data.\n\n It's probably raining"); + return { + latitude: 55.96, + longitude: -3.18, + generationtime_ms: 0.3420114517211914, + utc_offset_seconds: 0, + timezone: 'GMT', + timezone_abbreviation: 'GMT', + elevation: 69, + current_weather: { + temperature: 16.3, + windspeed: 18.4, + winddirection: 78, + weathercode: 3, + is_day: 1, + time: '2023-08-31T17:00' + } + } } }) satisfies PageServerLoad; diff --git a/src/routes/weather/+page.svelte b/src/routes/weather/+page.svelte index 38562678..e6466aa7 100644 --- a/src/routes/weather/+page.svelte +++ b/src/routes/weather/+page.svelte @@ -9,12 +9,9 @@ const weather = getWeatherFromCode(data.weathercode); const windDirection = getDirectionFromAngle(data.winddirection); const time = new Date(data.time); - - let canvasElement: HTMLCanvasElement; - -
+
-
+ \ No newline at end of file diff --git a/src/routes/weather/Scene.svelte b/src/routes/weather/Scene.svelte index fd548787..c074275e 100644 --- a/src/routes/weather/Scene.svelte +++ b/src/routes/weather/Scene.svelte @@ -7,32 +7,44 @@ const backgroundStyle = timeNoun(time); let canvas: HTMLCanvasElement; - - let testTime = 0; + let context: CanvasRenderingContext2D; + const cloudSize = 30; onMount(() => { - canvas.width = window.innerWidth - 24; - canvas.height = window.innerHeight - 24; + canvas.width = 1920; + canvas.height = 1080; const orbitCentreX = canvas.width / 2; const orbitCentreY = canvas.height; const orbitRadius = canvas.height - 100; const orbitBodyRadius = 100; - + const ctx = canvas.getContext("2d"); if (ctx) { - drawOrbit(ctx, time, orbitCentreX, orbitCentreY, - orbitRadius, orbitBodyRadius); - drawTree(ctx, 120, canvas.height, 80, -Math.PI / 2, 12, 15); - drawTree(ctx, canvas.width - 400, canvas.height, 60, -Math.PI / 2, 12, 15); - drawCloud(ctx, 90, 100, 20) - drawCloud(ctx, 720, 240, 20) - drawCloud(ctx, canvas.width / 2, 100, 20) - drawCloud(ctx, canvas.width - 100, 80, 20) - - drawCharacter(ctx, canvas.width / 2, canvas.height - 200) + context = ctx; + // drawOrbit(ctx, time, orbitCentreX, orbitCentreY, + // orbitRadius, orbitBodyRadius); + // drawTree(ctx, 120, canvas.height, 80, -Math.PI / 2, 12, 15); + // drawTree(ctx, canvas.width - 400, canvas.height, 60, -Math.PI / 2, 12, 15); + drawCloud(ctx, 100, 100, cloudSize); + // drawCloud(ctx, 750, 100, cloudSize); + // drawCloud(ctx, 1300, 100, cloudSize); + + // drawCloud(ctx, 100, 450, cloudSize); + // drawCloud(ctx, 750, 450, cloudSize); + // drawCloud(ctx, 1300, 450, cloudSize); + + // drawCloud(ctx, 100, 800, cloudSize); + // drawCloud(ctx, 750, 800, cloudSize); + // drawCloud(ctx, 1300, 800, cloudSize); + // drawCharacter(ctx, canvas.width / 2, canvas.height - 200); } - }) + }); + + const regenCloud = () => { + context.clearRect(0, 0, 1920, 1080); + drawCloud(context, 100, 100, cloudSize); + } @@ -40,6 +52,8 @@ + + \ No newline at end of file + diff --git a/src/routes/weather/Scene.svelte b/src/routes/weather/Scene.svelte index 63c9eeb0..c8873dfe 100644 --- a/src/routes/weather/Scene.svelte +++ b/src/routes/weather/Scene.svelte @@ -3,7 +3,11 @@ import { timeNoun } from "./weatherTime"; import { drawTree, drawOrbit, drawCloud } from "./weatherScene"; import { drawCharacter } from "./DrawCharacter"; + import type { Weather } from "./weatherData"; + import { Rain } from "./rain"; export let time: Date; + export let weather: Weather; + export let windspeed: number; const backgroundStyle = timeNoun(time); let canvas: HTMLCanvasElement; @@ -19,22 +23,25 @@ const ctx = canvas.getContext("2d"); if (ctx) { - drawOrbit(ctx, time, orbitCentreX, orbitCentreY, - orbitRadius, orbitBodyRadius); - drawTree(ctx, 120, canvas.height, 80, -Math.PI / 2, 12, 15); - drawTree(ctx, canvas.width - 400, canvas.height, 60, -Math.PI / 2, 12, 15); - drawCloud(ctx, 90, 100, 20) - drawCloud(ctx, 720, 240, 20) - drawCloud(ctx, canvas.width / 2, 100, 20) - drawCloud(ctx, canvas.width - 100, 80, 20) - - drawCharacter(ctx, canvas.width / 2, canvas.height - 200) + const rain = new Rain(ctx, canvas, 15, 0.1, windspeed); + rain.animate(); + + // drawOrbit(ctx, time, orbitCentreX, orbitCentreY, + // orbitRadius, orbitBodyRadius); + // drawTree(ctx, 120, canvas.height, 80, -Math.PI / 2, 12, 15); + // drawTree(ctx, canvas.width - 400, canvas.height, 60, -Math.PI / 2, 12, 15); + // drawCloud(ctx, 90, 100, 20) + // drawCloud(ctx, 720, 240, 20) + // drawCloud(ctx, canvas.width / 2, 100, 20) + // drawCloud(ctx, canvas.width - 100, 80, 20) + + // drawCharacter(ctx, canvas.width / 2, canvas.height - 200) } }) - + @@ -111,4 +118,4 @@ var(--dawn-dusk) ); } - \ No newline at end of file + From cc50795e8e9ee70fb68c0da840223fad28746963 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Tue, 4 Feb 2025 22:59:12 -0800 Subject: [PATCH 06/36] show weather tab again --- src/routes/Header.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/Header.svelte b/src/routes/Header.svelte index 93e08b82..8ff4085d 100644 --- a/src/routes/Header.svelte +++ b/src/routes/Header.svelte @@ -16,9 +16,9 @@
  • Projects
  • - +
  • CV
  • From 62115555314ae044b0d7d18d540dbe63bb0cae7d Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Thu, 20 Feb 2025 19:52:22 -0800 Subject: [PATCH 07/36] wip convert to classes --- src/app.d.ts | 3 +- src/routes/weather/+page.server.ts | 29 ++++------- src/routes/weather/Compass.svelte | 3 +- src/routes/weather/Scene.svelte | 55 ++++++++++---------- src/routes/weather/bluebody.ts | 71 ++++++++++++++++++++++++++ src/routes/weather/rain.ts | 7 +-- src/routes/weather/tree.ts | 80 ++++++++++++++++++++++++++++++ src/routes/weather/weatherScene.ts | 55 -------------------- 8 files changed, 197 insertions(+), 106 deletions(-) create mode 100644 src/routes/weather/bluebody.ts create mode 100644 src/routes/weather/tree.ts diff --git a/src/app.d.ts b/src/app.d.ts index a75fb5ee..da2f6f14 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -32,7 +32,8 @@ declare global { description?: string } - type Points = [number, number][] + type Point = [number, number] + type Points = Point[] type JSONValue = { [x: string]: string } diff --git a/src/routes/weather/+page.server.ts b/src/routes/weather/+page.server.ts index 108a4c14..aa85a824 100644 --- a/src/routes/weather/+page.server.ts +++ b/src/routes/weather/+page.server.ts @@ -20,16 +20,16 @@ const fetchWeather = async (latitude: number, longitude: number) => { } export const load = (async () => { - if (typeof window !== "undefined" && "geolocation" in window.navigator) { - window.navigator.geolocation.getCurrentPosition(position => { - location = { - latitude: position.coords.latitude, - longitude: position.coords.longitude - } - }); - } else { - console.log("geolocation permissions blocked, getting weather from Edinburgh instead"); - } + // if (typeof window !== "undefined" && "geolocation" in window.navigator) { + // window.navigator.geolocation.getCurrentPosition(position => { + // location = { + // latitude: position.coords.latitude, + // longitude: position.coords.longitude + // } + // }); + // } else { + // console.log("geolocation permissions blocked, getting weather from Edinburgh instead"); + // } try { const weather = await fetchWeather(location.latitude, location.longitude); @@ -37,21 +37,12 @@ export const load = (async () => { } catch { // throw error(404, "Wasn't able to get any weather data.\n\n It's probably raining"); return { - latitude: 55.96, - longitude: -3.18, - generationtime_ms: 0.3420114517211914, - utc_offset_seconds: 0, - timezone: 'GMT', - timezone_abbreviation: 'GMT', - elevation: 69, - current_weather: { temperature: 16.3, windspeed: 18.4, winddirection: 78, weathercode: 17, is_day: 1, time: Date.now() - } } } }) satisfies PageServerLoad; diff --git a/src/routes/weather/Compass.svelte b/src/routes/weather/Compass.svelte index da2d6539..0b22a462 100644 --- a/src/routes/weather/Compass.svelte +++ b/src/routes/weather/Compass.svelte @@ -1,5 +1,6 @@ @@ -80,4 +81,4 @@ transform: rotate(calc(var(--angle) - 4deg)); } } - \ No newline at end of file + diff --git a/src/routes/weather/Scene.svelte b/src/routes/weather/Scene.svelte index dd80e92f..364fbac9 100644 --- a/src/routes/weather/Scene.svelte +++ b/src/routes/weather/Scene.svelte @@ -1,15 +1,18 @@ - + - +
    + +
    - - - -
    -
    From 22f557ad0e1253f756f02d24b89f86b27aa58ad6 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sat, 22 Feb 2025 20:50:53 -0800 Subject: [PATCH 09/36] move draw functions to module --- src/routes/weather/{ => draw}/bluebody.ts | 19 ++-- .../{DrawCharacter.ts => draw/character.ts} | 14 +-- src/routes/weather/{ => draw}/rain.ts | 0 src/routes/weather/draw/tree.ts | 58 ++++++++++++ src/routes/weather/tree.ts | 80 ----------------- src/routes/weather/weatherScene.ts | 90 ------------------- 6 files changed, 76 insertions(+), 185 deletions(-) rename src/routes/weather/{ => draw}/bluebody.ts (89%) rename src/routes/weather/{DrawCharacter.ts => draw/character.ts} (98%) rename src/routes/weather/{ => draw}/rain.ts (100%) create mode 100644 src/routes/weather/draw/tree.ts delete mode 100644 src/routes/weather/tree.ts delete mode 100644 src/routes/weather/weatherScene.ts diff --git a/src/routes/weather/bluebody.ts b/src/routes/weather/draw/bluebody.ts similarity index 89% rename from src/routes/weather/bluebody.ts rename to src/routes/weather/draw/bluebody.ts index b37da829..45710b82 100644 --- a/src/routes/weather/bluebody.ts +++ b/src/routes/weather/draw/bluebody.ts @@ -41,15 +41,18 @@ export class Bluebody { } animate = () => { - this.ctx.fillStyle = this.radialGradient; - this.ctx.arc( - this.orbitPosX, - this.orbitPosY, - this.orbitBodyRadius, - 0, 2*Math.PI, false - ); - this.ctx.fill(); + this.draw(); + } + draw = () => { + this.ctx.fillStyle = this.radialGradient; + this.ctx.arc( + this.orbitPosX, + this.orbitPosY, + this.orbitBodyRadius, + 0, 2*Math.PI, false + ); + this.ctx.fill(); } diff --git a/src/routes/weather/DrawCharacter.ts b/src/routes/weather/draw/character.ts similarity index 98% rename from src/routes/weather/DrawCharacter.ts rename to src/routes/weather/draw/character.ts index af22556e..c89cc6b2 100644 --- a/src/routes/weather/DrawCharacter.ts +++ b/src/routes/weather/draw/character.ts @@ -1,4 +1,4 @@ -import { Artist } from "../../Artist"; +import { Artist } from "../../../Artist"; const AMBER = "rgb(245, 167, 66)"; // const DARK_ORANGE = "rgb(200, 100, 100)"; @@ -14,11 +14,11 @@ const DARK_PINK = "rgb(150, 50, 100)"; export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: number) { - + const a = new Artist(context, x, y); const HEAD_POINTS: Points = [ - // top + // top [15, 0], // ear [15, -5], @@ -104,7 +104,7 @@ export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: n ] a.drawShape(HEAD_POINTS, true, 0, 0, 2, ORANGE_BROWN, ORANGE_BROWN); - + a.reset(0, 10); context.beginPath(); a.drawNextLine(0, 35); @@ -146,7 +146,7 @@ export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: n a.drawShape(TONGUE, true, 0, 108, 2, DARK_PINK, PINK); a.reset(45, 80); drawFaceLines(context, a.startX, a.startY); -} +} function drawFaceLines(context: CanvasRenderingContext2D, x: number, y: number) { context.beginPath(); @@ -185,7 +185,7 @@ function drawFaceLines(context: CanvasRenderingContext2D, x: number, y: number) startX, startY, startX - 4, startY + 7, endX, endY); - + startX = endX; startY = endY; endX = startX - 25; @@ -227,7 +227,7 @@ function drawEyes(context: CanvasRenderingContext2D, x: number, y: number) { context.arc(x + eyePosX + 2, y + eyeBottomPosy, eyeSize, Math.PI * 2.2, Math.PI * 0.9); context.fillStyle = DARK_GREY; context.fill(); - + // Right pupil context.beginPath(); context.arc(x + eyePosX + 2, y + eyeTopPosY - 10, 7, Math.PI * 2, Math.PI * 1.1); diff --git a/src/routes/weather/rain.ts b/src/routes/weather/draw/rain.ts similarity index 100% rename from src/routes/weather/rain.ts rename to src/routes/weather/draw/rain.ts diff --git a/src/routes/weather/draw/tree.ts b/src/routes/weather/draw/tree.ts new file mode 100644 index 00000000..7f099221 --- /dev/null +++ b/src/routes/weather/draw/tree.ts @@ -0,0 +1,58 @@ +import { drawCircle } from "$lib/canvasUtils"; +import { randIntBetween, randRangeRGBString, randVariance } from "$lib/utils"; + + +// https://github.com/PavlyukVadim/amadev/blob/master/RecursiveTree/script.js +export function drawTree(context: CanvasRenderingContext2D, + startX: number, + startY: number, + length: number, + angle: number, + depth: number, + branchWidth: number) { + + let newLength, newAngle; + const rand = Math.random; + const maxAngle = 2 * Math.PI / 6; + const maxBranch = 3; + const endX = startX + length * Math.cos(angle); + const endY = startY + length * Math.sin(angle); + + context.beginPath(); + context.moveTo(startX, startY); + context.lineCap = 'round'; + context.lineWidth = branchWidth; + context.lineTo(endX, endY); + + if (depth <= 4) { + context.strokeStyle = randRangeRGBString(30, [64, 180], 0); + } + else { + context.strokeStyle = randRangeRGBString([70, 80], 60, [30,50]); + } + + context.stroke(); + const newDepth = depth - 1; + + if(!newDepth) { + return; + } + const subBranches = (rand() * (maxBranch - 1)) + 1; + branchWidth *= 0.7; + + for (let i = 0; i < subBranches; i++) { + newAngle = angle + rand() * maxAngle - maxAngle * 0.5; + newLength = length * (0.7 + rand() * 0.3); + drawTree(context, endX, endY, newLength, newAngle, newDepth, branchWidth); + } + +} + +// export function drawClouds(context: CanvasRenderingContext2D, direction: number, density: number) { + +// } + +export function drawCloud(context: CanvasRenderingContext2D,) { + +} + diff --git a/src/routes/weather/tree.ts b/src/routes/weather/tree.ts deleted file mode 100644 index ae7dc19b..00000000 --- a/src/routes/weather/tree.ts +++ /dev/null @@ -1,80 +0,0 @@ -const MAX_ANGLE = 2 * Math.PI / 6; -const MAX_BRANCH = 3; -const DEPTH = 15; - -export class Tree { - - startPoints: Point[] = []; - endPoints: Point[] = []; - angles: number[] = []; - lengths: number[] = []; - colors: string[] = []; - - ctx: CanvasRenderingContext2D; - - - constructor(context: CanvasRenderingContext2D, - startX: number, - startY: number, - length: number, - angle: number) { - - this.ctx = context; - - this.populateLayers(startX, startY, angle, length, DEPTH); - } - - populateLayers(startX: number, startY: number, angle: number, length: number, depth: number) { - - const rand = Math.random; - const endX = startX + length * Math.cos(angle); - const endY = startY + length * Math.sin(angle); - - this.startPoints.push([startX, startY]); - this.endPoints.push([endX, endY]); - - const newDepth = depth - 1; - - const subBranches = (rand() * (MAX_BRANCH - 1)) + 1; - let nextColor: string; - if (depth <= 2) { - nextColor = `rgb(30, ${(((rand() * 64) + 128) >> 0)}, 0)`; - } - else { - nextColor = `rgb(30, ${(((rand() * 64) + 64) >> 0)}, 20)`; - } - this.colors.push(nextColor); - - for (let i = 0; i < subBranches; i++) { - const newAngle = angle + rand() * MAX_ANGLE - MAX_ANGLE * 0.5; - const newLength = length * (0.7 + rand() * 0.3); - this.angles.push(newAngle); - this.lengths.push(newLength); - this.populateLayers(endX, endY, newAngle, newLength, newDepth); - } - } - - animate() { - this.draw(); - } - - draw() { - let i = 0; - - while(i < this.startPoints.length) { - let branchWidth = 12; - for(let j = DEPTH; j >= 0; j--) { - this.ctx.beginPath(); - this.ctx.moveTo(this.startPoints[i][0], this.startPoints[i][1]); - this.ctx.lineCap = 'round'; - this.ctx.lineWidth = branchWidth; - this.ctx.lineTo(this.endPoints[i][0], this.endPoints[i][1]); - this.ctx.strokeStyle = this.colors[j]; - this.ctx.stroke(); - i++; - branchWidth *= 0.7; - } - } - } - -} diff --git a/src/routes/weather/weatherScene.ts b/src/routes/weather/weatherScene.ts deleted file mode 100644 index 43eafe7f..00000000 --- a/src/routes/weather/weatherScene.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { drawCircle } from "$lib/canvasUtils"; -import { randBetween, randIntBetween, randVariance } from "$lib/utils"; - - -// https://github.com/PavlyukVadim/amadev/blob/master/RecursiveTree/script.js -export function drawTree(context: CanvasRenderingContext2D, - startX: number, - startY: number, - length: number, - angle: number, - depth: number, - branchWidth: number) { - - let newLength, newAngle; - const rand = Math.random; - const maxAngle = 2 * Math.PI / 6; - const maxBranch = 3; - const endX = startX + length * Math.cos(angle); - const endY = startY + length * Math.sin(angle); - - context.beginPath(); - context.moveTo(startX, startY); - context.lineCap = 'round'; - context.lineWidth = branchWidth; - context.lineTo(endX, endY); - - if (depth <= 2) { - context.strokeStyle = `rgb(30, ${(((rand() * 64) + 128) >> 0)}, 0)`; - } - else { - context.strokeStyle = `rgb(30, ${(((rand() * 64) + 64) >> 0)}, 20)`; - } - - context.stroke(); - const newDepth = depth - 1; - - if(!newDepth) { - return; - } - const subBranches = (rand() * (maxBranch - 1)) + 1; - branchWidth *= 0.7; - - for (let i = 0; i < subBranches; i++) { - newAngle = angle + rand() * maxAngle - maxAngle * 0.5; - newLength = length * (0.7 + rand() * 0.3); - drawTree(context, endX, endY, newLength, newAngle, newDepth, branchWidth); - } - -} - -// export function drawClouds(context: CanvasRenderingContext2D, direction: number, density: number) { - -// } - -export function drawCloud(context: CanvasRenderingContext2D, x: number, y: number, size: number) { - const sizeVarFactor = 0.8; - const rowVarFactor = 0.9; - // get random size between 0.75 and 1.5 x provided size - const numberOfRows = randIntBetween(3, 6); - - let rowLength = 4; - let newSize = size; - let newPosX; - let newPosY; - const cloudColour = "rgba(255, 255, 255, 0.8)"; - for (let cy = 0; cy <= numberOfRows * size * 3; cy += size * 3) { - rowLength = randIntBetween(2, 12); - for (let cx = 0; cx <= rowLength * size * 3; cx += size * 3) { - // const offset = newSize * 3; - newSize = randVariance(size, 0.3); - newPosX = cx + x; - newPosY = cy + y - drawCircle(context, newPosX, newPosY, newSize, undefined, cloudColour) - } - } -} - -export function curve(context:CanvasRenderingContext2D, x1: number, y: number, size: number) { - - // let yValues = []; - - // const values = [1, 3, 4, 5, 4, 3, 1]; - // const length = yValues.length - for (let x = 0; x < 22; x++) { - const xpos = x + x1 - const ypos = y + (x - 10) * (x - 10) - - drawCircle(context, xpos * 10, ypos, size, undefined, "yellow"); - } -} From 00be77f47e328930368b665b56a262482d01eaba Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sat, 22 Feb 2025 20:51:24 -0800 Subject: [PATCH 10/36] set up animations correctly in scene --- src/routes/weather/Scene.svelte | 98 ++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/src/routes/weather/Scene.svelte b/src/routes/weather/Scene.svelte index 364fbac9..08267971 100644 --- a/src/routes/weather/Scene.svelte +++ b/src/routes/weather/Scene.svelte @@ -1,67 +1,73 @@ - - - - + +
    + + + + +
    @@ -89,12 +95,30 @@ position: absolute; right: 0; } - canvas { + + .canvas-container { + position: relative; margin: var(--margin); width: 100%; height: 100%; } + canvas { + display: block; + width: calc(100% - 2*(var(--margin))); + top: var(--margin); + left: var(--margin); + bottom: var(--margin); + position: absolute; + } + + .static-canvas { + z-index: 0; + } + .animation-canvas { + z-index: 1; + } + .black { background-color: black; } From 92cbe3d1d1bc9693b794890f6831818f757dc9ec Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sat, 22 Feb 2025 20:51:32 -0800 Subject: [PATCH 11/36] remove log in compass --- src/routes/weather/Compass.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/weather/Compass.svelte b/src/routes/weather/Compass.svelte index 0b22a462..32544467 100644 --- a/src/routes/weather/Compass.svelte +++ b/src/routes/weather/Compass.svelte @@ -1,6 +1,5 @@ From 3661b537f3414497c74da051c26d10441e40c0ae Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sat, 22 Feb 2025 20:51:44 -0800 Subject: [PATCH 12/36] util random rgb --- src/lib/utils.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 3f143c6a..70f56694 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -47,4 +47,19 @@ export const grow = (_node: HTMLElement) => { easing: sineIn, css: (t: number) => `transform: scaleX(${t}); transform-origin: left` } -} \ No newline at end of file +} + +export const randRangeRGBString = ( + redRange: [number, number] | number = [0, 255], + greenRange: [number,number] | number = [0, 255], + blueRange: [number,number] | number = [0, 255]) => { + + const getValue = (range: [number, number] | number) => { + return Array.isArray(range) ? randBetween(range[0], range[1]) : range + } + + const red = getValue(redRange); + const green = getValue(greenRange); + const blue = getValue(blueRange); + return `rgb(${red}, ${green}, ${blue})`; +} From 4dcab7e1c328ba3e04f2a7529ff3a5ccbf5e6bf1 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 23 Feb 2025 15:46:38 -0800 Subject: [PATCH 13/36] create cloud class --- src/routes/weather/draw/cloud.ts | 111 +++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/routes/weather/draw/cloud.ts diff --git a/src/routes/weather/draw/cloud.ts b/src/routes/weather/draw/cloud.ts new file mode 100644 index 00000000..90365743 --- /dev/null +++ b/src/routes/weather/draw/cloud.ts @@ -0,0 +1,111 @@ +import { Vector2D } from "$lib/mechanics/vector"; +import { randBetween } from "$lib/utils"; + +const TWO_PI = Math.PI * 2; +const ORB_SIZE_VARIANCE = [0.8, 1.2]; +const ORB_X_SPREAD_FACTOR = [-2, 2]; +const ORB_Y_SPREAD_FACTOR = [-0.5, 1]; + +export class Cloud { + ctx: CanvasRenderingContext2D; + position: Vector2D; + speed: number; + orbs: Orb[] = []; + width: number; + radius: number; + size: number; + color: string; + + constructor( + context: CanvasRenderingContext2D, + x: number, + y: number, + radius: number, + size: number, + speed: number, + color: string, + ) { + this.position = new Vector2D(x, y); + this.speed = speed; + this.size = size; + this.ctx = context; + this.width = 0; + this.color = color; + this.radius = radius; + } + + animate = () => { + this.position.x += this.speed; + // regenerate once cloud has left screen + if (this.position.x > this.ctx.canvas.width + this.width) { + this.generate(); + this.position.x = -this.width; + // vary height, keep within top half of canvas + this.position.y += randBetween(-this.width, this.width); + this.position.y = Math.max(0, this.position.y); + this.position.y = Math.min(this.ctx.canvas.height / 2, this.position.y); + } + this.draw(); + }; + + draw = () => { + this.orbs.forEach((orb) => orb.draw(this.ctx, this.position.x, this.position.y)); + }; + + generate = () => { + let smallestX = 0; + let largestX = 0; + + this.orbs.splice(0, this.orbs.length); + + for (let i = 0; i < this.size; i++) { + const orbRadius = randBetween( + this.radius * ORB_SIZE_VARIANCE[0], + this.radius * ORB_SIZE_VARIANCE[1], + ); + const orbX = randBetween( + ORB_X_SPREAD_FACTOR[0] * this.radius, + ORB_X_SPREAD_FACTOR[1] * this.radius, + ); + const orbY = randBetween( + ORB_Y_SPREAD_FACTOR[0] * this.radius, + ORB_Y_SPREAD_FACTOR[1] * this.radius, + ); + if (orbX < smallestX) { + smallestX = orbX; + } + if (orbX > largestX) { + largestX = orbX; + } + this.orbs.push(new Orb(orbX, orbY, orbRadius, this.color)); + } + + this.width = largestX - smallestX; + }; +} + +class Orb { + pos: Vector2D; + color: string; + radius: number; + + constructor(x: number, y: number, radius: number, color: string) { + this.color = color; + this.radius = radius; + this.pos = new Vector2D(x, y); + } + + draw = (ctx: CanvasRenderingContext2D, x: number, y: number) => { + const absX = x + this.pos.x; + const absY = y + this.pos.y; + + const radialGradient = ctx.createRadialGradient(absX, absY, 0, absX, absY, this.radius); + radialGradient.addColorStop(0, this.color); + radialGradient.addColorStop(1, "rgba(255, 255, 255, 0)"); + ctx.beginPath(); + ctx.fillStyle = radialGradient; + ctx.arc(absX, absY, this.radius, 0, TWO_PI, true); + ctx.fill(); + ctx.closePath(); + }; +} From faffdb4c575195b139bbacec6f9275006305fc66 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 23 Feb 2025 15:47:00 -0800 Subject: [PATCH 14/36] abstract precipitator class --- src/routes/weather/draw/precipitater.ts | 66 +++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/routes/weather/draw/precipitater.ts diff --git a/src/routes/weather/draw/precipitater.ts b/src/routes/weather/draw/precipitater.ts new file mode 100644 index 00000000..6c451dd9 --- /dev/null +++ b/src/routes/weather/draw/precipitater.ts @@ -0,0 +1,66 @@ +import { Vector2D } from "$lib/mechanics/vector"; + +export abstract class Precipitator { + ctx: CanvasRenderingContext2D; + canvasWidth: number; + canvasHeight: number; + drops: Drop[] = []; + dropSpeed: number; + windSpeed: number; + size: number; + + constructor( + context: CanvasRenderingContext2D, + speed: number, + density: number, + windSpeed: number, + size: number, + ) { + this.ctx = context; + this.canvasWidth = context.canvas.width; + this.canvasHeight = context.canvas.height; + this.dropSpeed = speed; + this.size = size; + this.windSpeed = windSpeed; + const dropSpacing = this.size / density; + const columns = Math.floor(this.canvasWidth / (this.size + dropSpacing)); + + for (let i = 0; i < columns; i++) { + this.drops.push( + this.generateDrop(i * (dropSpacing + this.size), Math.random() * this.canvasHeight), + ); + } + } + + abstract generateDrop(x: number, y: number): Drop; + + animate = () => { + this.drops.forEach((drop) => { + drop.position.y += this.dropSpeed; + drop.position.x += this.windSpeed / 10; + + if (drop.position.y > this.canvasHeight) { + // vary starting position for variety of y position + drop.position.y = 0 - Math.random() * this.canvasHeight; + // shift drop position left or right up to drop width + drop.position.x = drop.position.x + (Math.random() * 2 - 1.0) * 3 * this.size; + } + if (drop.position.x > this.canvasWidth) { + drop.position.x = 0; + } + drop.draw(this.ctx); + }); + }; +} + +export abstract class Drop { + position: Vector2D; + size: number; + + constructor(size: number, initPos: Vector2D) { + this.position = initPos; + this.size = size; + } + + abstract draw(context: CanvasRenderingContext2D): void; +} From 75d6a113fcc7e5529147cb7040c019458feadebb Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 23 Feb 2025 15:47:56 -0800 Subject: [PATCH 15/36] get timeNoun from hour --- src/routes/weather/weatherTime.ts | 84 ++++++++++++++++--------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/src/routes/weather/weatherTime.ts b/src/routes/weather/weatherTime.ts index ebde42fc..774ead84 100644 --- a/src/routes/weather/weatherTime.ts +++ b/src/routes/weather/weatherTime.ts @@ -1,44 +1,48 @@ export function timeNoun(time: Date): string { - const hour = time.getHours(); - switch(hour){ - case 6: - return "dawn"; - case 7: - return "sunrise"; - case 8: - return "morning"; - case 9: - case 10: - case 11: - case 12: - case 13: - case 14: - case 15: - case 16: - case 17: - case 18: - return "day"; - case 19: - return "evening"; - case 20: - return "sunset"; - case 21: - return "dusk"; - case 22: - case 23: - case 24: - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - return "night"; - default: - return "day" - } + const hour = time.getHours(); + return hourNoun(hour); +} + +export function hourNoun(hour: number): string { + switch (hour) { + case 6: + return "dawn"; + case 7: + return "sunrise"; + case 8: + return "morning"; + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + return "day"; + case 19: + return "evening"; + case 20: + return "sunset"; + case 21: + return "dusk"; + case 22: + case 23: + case 24: + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + return "night"; + default: + return "day"; + } } export function timeAsTwelvethFraction(time: Date): number { - return time.getHours() + 6 % 12; -} \ No newline at end of file + return time.getHours() + (6 % 12); +} From a220a4bd063d2875254f2423a670c34bb5321f57 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 23 Feb 2025 15:48:08 -0800 Subject: [PATCH 16/36] add utils line at angle --- src/lib/canvasUtils.ts | 57 +++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/src/lib/canvasUtils.ts b/src/lib/canvasUtils.ts index 975b0a66..0a55251e 100644 --- a/src/lib/canvasUtils.ts +++ b/src/lib/canvasUtils.ts @@ -1,21 +1,44 @@ -export function drawCircle(context: CanvasRenderingContext2D, - x: number, y: number, radius: number, stroke?: string, fill?: string | CanvasGradient) { - context.beginPath(); - context.arc(x, y, radius, 0, Math.PI *2, false); - if (stroke) { - context.strokeStyle = stroke; - context.stroke(); - } - if (fill) { - context.fillStyle = fill; - context.fill(); - } - context.closePath(); +export function drawCircle( + context: CanvasRenderingContext2D, + x: number, + y: number, + radius: number, + stroke?: string, + fill?: string | CanvasGradient, +) { + context.beginPath(); + context.arc(x, y, radius, 0, Math.PI * 2, false); + if (stroke) { + context.strokeStyle = stroke; + context.stroke(); + } + if (fill) { + context.fillStyle = fill; + context.fill(); + } + context.closePath(); } -export function drawLine(context: CanvasRenderingContext2D, - startX: number, startY: number, endX: number, endY: number) { - context.lineTo(startX, startY); - context.lineTo(endX, endY); +export function drawLine( + context: CanvasRenderingContext2D, + startX: number, + startY: number, + endX: number, + endY: number, +) { + context.lineTo(startX, startY); + context.lineTo(endX, endY); } +export function drawLineAtAngle( + context: CanvasRenderingContext2D, + x: number, + y: number, + length: number, + angle: number, +) { + const offsetX = length * Math.cos(angle); + const offsetY = length * Math.sin(angle); + context.moveTo(x, y); + context.lineTo(x - offsetX, y - offsetY); +} From 209bfa931580407386db6083e58c6e2fc58ac590 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 23 Feb 2025 15:48:25 -0800 Subject: [PATCH 17/36] use preciptator base for rain --- src/routes/weather/draw/rain.ts | 116 +++++++++++--------------------- 1 file changed, 40 insertions(+), 76 deletions(-) diff --git a/src/routes/weather/draw/rain.ts b/src/routes/weather/draw/rain.ts index da8c4b9f..2df7481d 100644 --- a/src/routes/weather/draw/rain.ts +++ b/src/routes/weather/draw/rain.ts @@ -1,84 +1,48 @@ import { Vector2D } from "$lib/mechanics/vector"; +import { Drop, Precipitator } from "./precipitater"; -// const RAIN_COLOR = "rgb(70, 130, 255)"; const RAIN_COLOR = "rgb(90, 140, 210)"; const DROP_SIZE = 10; -export class Rain { - - ctx: CanvasRenderingContext2D; - canvasWidth: number; - canvasHeight: number; - rainDrops: RainDrop[] = []; - rainSpeed: number; - windSpeed: number; - - constructor( - context: CanvasRenderingContext2D, - canvas: HTMLCanvasElement, - speed: number, - density: number, - windSpeed: number) { - this.ctx = context; - this.canvasWidth = canvas.width; - this.canvasHeight = canvas.height; - this.rainSpeed = speed; - this.windSpeed = windSpeed; - const dropSpacing = DROP_SIZE / density; - const columns = Math.floor(this.canvasWidth/ (DROP_SIZE + dropSpacing)); - - for (let i = 0; i < columns; i ++) { - this.rainDrops.push(new RainDrop(DROP_SIZE, new Vector2D(i * (dropSpacing + DROP_SIZE), Math.random() * this.canvasHeight))); - } - - context.fillStyle = RAIN_COLOR; - - } - - animate = () => { - // this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); - this.rainDrops.forEach(drop => { - drop.position.y += this.rainSpeed; - drop.position.x += this.windSpeed / 10; - if (drop.position.y > this.canvasHeight) { - // vary starting position for variety of y position - drop.position.y = 0 - (Math.random() * this.canvasHeight); - // shift drop position left or right up to drop width - drop.position.x = drop.position.x + (((Math.random() * 2) - 1.0) * 3 * DROP_SIZE) - } - if (drop.position.x > this.canvasWidth) { - drop.position.x = 0; - } - drop.draw(this.ctx); - }); - } - +export class Rain extends Precipitator { + constructor( + context: CanvasRenderingContext2D, + speed: number, + density: number, + windSpeed: number, + ) { + super(context, speed, density, windSpeed, DROP_SIZE); + } + + generateDrop(x: number, y: number): Drop { + return new RainDrop(DROP_SIZE, new Vector2D(x, y)); + } } -class RainDrop { - position: Vector2D; - size: number; - height: number; - - constructor(size: number, initPos: Vector2D) { - this.position = initPos; - this.size = size; - this.height = size * 2.5; - } - - - draw(context: CanvasRenderingContext2D) { - context.beginPath() - context.fillStyle = RAIN_COLOR; - context.moveTo(this.position.x, this.position.y); - context.lineTo(this.position.x - this.size, this.position.y + this.height); - context.lineTo(this.position.x + this.size, this.position.y + this.height); - context.lineTo(this.position.x, this.position.y); - context.fill(); - context.arc(this.position.x, this.position.y + this.height, this.size, 1.9*Math.PI, 1.1*Math.PI) - context.fill(); - context.closePath(); - } +class RainDrop extends Drop { + height: number; + + constructor(size: number, initPos: Vector2D) { + super(size, initPos); + this.height = size * 2.5; + } + + draw(context: CanvasRenderingContext2D) { + context.beginPath(); + context.fillStyle = RAIN_COLOR; + context.moveTo(this.position.x, this.position.y); + context.lineTo(this.position.x - this.size, this.position.y + this.height); + context.lineTo(this.position.x + this.size, this.position.y + this.height); + context.lineTo(this.position.x, this.position.y); + context.fill(); + context.arc( + this.position.x, + this.position.y + this.height, + this.size, + 1.9 * Math.PI, + 1.1 * Math.PI, + ); + context.fill(); + context.closePath(); + } } - - From 8e657fdcee7a26489613871f680e1245a980e44a Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 23 Feb 2025 15:48:36 -0800 Subject: [PATCH 18/36] create snow precipitator --- src/routes/weather/draw/snow.ts | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/routes/weather/draw/snow.ts diff --git a/src/routes/weather/draw/snow.ts b/src/routes/weather/draw/snow.ts new file mode 100644 index 00000000..1c381e90 --- /dev/null +++ b/src/routes/weather/draw/snow.ts @@ -0,0 +1,78 @@ +import { drawLine, drawLineAtAngle } from "$lib/canvasUtils"; +import { Vector2D } from "$lib/mechanics/vector"; +import { Drop, Precipitator } from "./precipitater"; + +const SNOW_COLOR = "rgb(180, 190, 255)"; +// const FLAKE_SIZE = 40; +// const FLAKE_BRANCH_LENGTH = FLAKE_SIZE / 2; + +export class Snow extends Precipitator { + constructor( + context: CanvasRenderingContext2D, + speed: number, + density: number, + windSpeed: number, + size: number, + ) { + super(context, speed, density, windSpeed, size); + } + + generateDrop(x: number, y: number): Drop { + return new SnowFlake(this.size, new Vector2D(x, y)); + } + + draw = () => { + this.drops.forEach((drop) => drop.draw(this.ctx)); + }; +} + +class SnowFlake extends Drop { + constructor(size: number, initPos: Vector2D) { + super(size, initPos); + } + + draw(context: CanvasRenderingContext2D) { + context.beginPath(); + context.strokeStyle = SNOW_COLOR; + context.lineCap = "round"; + context.lineWidth = this.size / 20; + + this.drawBranch(context, this.size / 2, Math.PI / 6); + this.drawBranch(context, this.size / 2, Math.PI / 2); + this.drawBranch(context, this.size / 2, (5 * Math.PI) / 6); + this.drawBranch(context, this.size / 2, (7 * Math.PI) / 6); + this.drawBranch(context, this.size / 2, (3 * Math.PI) / 2); + this.drawBranch(context, this.size / 2, (11 * Math.PI) / 6); + + context.stroke(); + context.closePath(); + } + + drawBranch = (context: CanvasRenderingContext2D, radius: number, angle: number) => { + const offsetX = radius * Math.cos(angle); + const offsetY = radius * Math.sin(angle); + + // draw main branch + context.moveTo(this.position.x, this.position.y); + context.lineTo(this.position.x - offsetX, this.position.y - offsetY); + + const r2 = radius * 0.5; // length from origin to start of sub branch + const r3 = radius * 1.1; // length from + const subBranchAngle = Math.PI / 6; + const r4 = (r2 + r3) * Math.tan(subBranchAngle); + + const x2 = this.position.x - r2 * Math.cos(angle); + const y2 = this.position.y - r2 * Math.sin(angle); + + const x3 = this.position.x - r4 * Math.cos(subBranchAngle + angle); + const y3 = this.position.y - r4 * Math.sin(subBranchAngle + angle); + + const x4 = this.position.x - r4 * Math.cos(angle - subBranchAngle); + const y4 = this.position.y - r4 * Math.sin(angle - subBranchAngle); + + context.moveTo(x2, y2); + context.lineTo(x3, y3); + context.moveTo(x2, y2); + context.lineTo(x4, y4); + }; +} From 48d125525d6ee37a0a0ce109f551b6650cf3534c Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 23 Feb 2025 15:48:50 -0800 Subject: [PATCH 19/36] clean up tree code --- src/routes/weather/draw/tree.ts | 99 +++++++++++++++------------------ 1 file changed, 44 insertions(+), 55 deletions(-) diff --git a/src/routes/weather/draw/tree.ts b/src/routes/weather/draw/tree.ts index 7f099221..f5166a0e 100644 --- a/src/routes/weather/draw/tree.ts +++ b/src/routes/weather/draw/tree.ts @@ -1,58 +1,47 @@ -import { drawCircle } from "$lib/canvasUtils"; -import { randIntBetween, randRangeRGBString, randVariance } from "$lib/utils"; - +import { randRangeRGBString } from "$lib/utils"; +// inspired by // https://github.com/PavlyukVadim/amadev/blob/master/RecursiveTree/script.js -export function drawTree(context: CanvasRenderingContext2D, - startX: number, - startY: number, - length: number, - angle: number, - depth: number, - branchWidth: number) { - - let newLength, newAngle; - const rand = Math.random; - const maxAngle = 2 * Math.PI / 6; - const maxBranch = 3; - const endX = startX + length * Math.cos(angle); - const endY = startY + length * Math.sin(angle); - - context.beginPath(); - context.moveTo(startX, startY); - context.lineCap = 'round'; - context.lineWidth = branchWidth; - context.lineTo(endX, endY); - - if (depth <= 4) { - context.strokeStyle = randRangeRGBString(30, [64, 180], 0); - } - else { - context.strokeStyle = randRangeRGBString([70, 80], 60, [30,50]); - } - - context.stroke(); - const newDepth = depth - 1; - - if(!newDepth) { - return; - } - const subBranches = (rand() * (maxBranch - 1)) + 1; - branchWidth *= 0.7; - - for (let i = 0; i < subBranches; i++) { - newAngle = angle + rand() * maxAngle - maxAngle * 0.5; - newLength = length * (0.7 + rand() * 0.3); - drawTree(context, endX, endY, newLength, newAngle, newDepth, branchWidth); - } - +export function drawTree( + context: CanvasRenderingContext2D, + startX: number, + startY: number, + length: number, + angle: number, + depth: number, + branchWidth: number, +) { + let newLength, newAngle; + const rand = Math.random; + const maxAngle = (2 * Math.PI) / 6; + const maxBranch = 3; + const endX = startX + length * Math.cos(angle); + const endY = startY + length * Math.sin(angle); + + context.beginPath(); + context.moveTo(startX, startY); + context.lineCap = "round"; + context.lineWidth = branchWidth; + context.lineTo(endX, endY); + + if (depth <= 4) { + context.strokeStyle = randRangeRGBString(30, [64, 180], 0); + } else { + context.strokeStyle = randRangeRGBString([70, 80], 60, [30, 50]); + } + + context.stroke(); + const newDepth = depth - 1; + + if (!newDepth) { + return; + } + const subBranches = rand() * (maxBranch - 1) + 1; + branchWidth *= 0.7; + + for (let i = 0; i < subBranches; i++) { + newAngle = angle + rand() * maxAngle - maxAngle * 0.5; + newLength = length * (0.7 + rand() * 0.3); + drawTree(context, endX, endY, newLength, newAngle, newDepth, branchWidth); + } } - -// export function drawClouds(context: CanvasRenderingContext2D, direction: number, density: number) { - -// } - -export function drawCloud(context: CanvasRenderingContext2D,) { - -} - From 66fccff8bc5cc445352583a224cad74b9e4db4ae Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 23 Feb 2025 15:49:05 -0800 Subject: [PATCH 20/36] rough scene setup --- src/routes/weather/Scene.svelte | 53 ++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/routes/weather/Scene.svelte b/src/routes/weather/Scene.svelte index 08267971..8d24fde4 100644 --- a/src/routes/weather/Scene.svelte +++ b/src/routes/weather/Scene.svelte @@ -1,27 +1,35 @@ - +
    @@ -115,15 +130,11 @@ .static-canvas { z-index: 0; } + .animation-canvas { z-index: 1; } - .black { - background-color: black; - } - - .night { background: var(--night); } @@ -137,8 +148,8 @@ .sunrise { background: linear-gradient( - var(--dawn-dusk) - var(--sunrise-sunset), + var(--dawn-dusk), + var(--sunrise-sunset) ); } From 07d8baa1b38e4c665a9066f3bf4605cae61bafc7 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Tue, 25 Feb 2025 15:36:46 -0800 Subject: [PATCH 21/36] thunderrrr --- src/routes/weather/Scene.svelte | 18 ++++++---- src/routes/weather/draw/rain.ts | 5 +-- src/routes/weather/draw/thunder.ts | 57 ++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 src/routes/weather/draw/thunder.ts diff --git a/src/routes/weather/Scene.svelte b/src/routes/weather/Scene.svelte index 8d24fde4..1cd0df72 100644 --- a/src/routes/weather/Scene.svelte +++ b/src/routes/weather/Scene.svelte @@ -8,6 +8,8 @@ import { Bluebody } from "./draw/bluebody"; import { Cloud } from "./draw/cloud"; import { Snow } from "./draw/snow"; + import { Thunder } from "./draw/thunder"; + import { randIntBetween } from "$lib/utils"; export let time: Date; export let weather: Weather; export let windspeed: number; @@ -45,7 +47,8 @@ const staticCtx = staticCanvas.getContext("2d"); if (animCtx && staticCtx) { - const rain = new Rain(animCtx, 10, 0.3, windspeed); + const rain = new Rain(animCtx, 20, 0.3, windspeed, 10); + const rain2 = new Rain(animCtx, 10, 0.8, windspeed, 5); // const snow = new Snow(animCtx, 4, 0.9, windspeed, 40); // const snow2 = new Snow(animCtx, 2, 0.9, windspeed, 20); // const bluebody = new Bluebody(staticCtx, time, orbitCentreX, orbitCentreY, orbitRadius, orbitBodyRadius); @@ -54,18 +57,21 @@ // drawTree(staticCtx, CANVAS_WIDTH - 400, CANVAS_HEIGHT, 60, -Math.PI / 2, TREE_DEPTH, TREE_BRANCH_THICKNESS); // drawCharacter(staticCtx, CANVAS_WIDTH / 2, CANVAS_HEIGHT * 0.8); - // clouds.push(new Cloud(animCtx, CEN_X, 100, 40, 10, 4, "white")); - // clouds.push(new Cloud(animCtx, 10, 200, 70, 10, 5, "white")); - // clouds.push(new Cloud(animCtx, 50, 150, 80, 10, 6, "white")); + for (let i = 0; i < 10; i++) { + clouds.push(new Cloud(animCtx, randIntBetween(0, CANVAS_WIDTH - 100), randIntBetween(0, CEN_Y), randIntBetween(20, 40), randIntBetween(7, 15), windspeed, "gray")); + } + const thunder = new Thunder(animCtx, CANVAS_HEIGHT, CANVAS_WIDTH, 0.5); // bluebody.draw(); const animate = () => { animCtx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); - // rain.animate(); + rain.animate(); + rain2.animate(); + thunder.animate(); // snow.animate(); // snow2.animate(); - // clouds.forEach(cloud => cloud.animate()); + clouds.forEach(cloud => cloud.animate()); requestAnimationFrame(animate); } animate(); diff --git a/src/routes/weather/draw/rain.ts b/src/routes/weather/draw/rain.ts index 2df7481d..59641d6c 100644 --- a/src/routes/weather/draw/rain.ts +++ b/src/routes/weather/draw/rain.ts @@ -10,12 +10,13 @@ export class Rain extends Precipitator { speed: number, density: number, windSpeed: number, + size: number = DROP_SIZE, ) { - super(context, speed, density, windSpeed, DROP_SIZE); + super(context, speed, density, windSpeed, size); } generateDrop(x: number, y: number): Drop { - return new RainDrop(DROP_SIZE, new Vector2D(x, y)); + return new RainDrop(this.size, new Vector2D(x, y)); } } diff --git a/src/routes/weather/draw/thunder.ts b/src/routes/weather/draw/thunder.ts new file mode 100644 index 00000000..92be4c7e --- /dev/null +++ b/src/routes/weather/draw/thunder.ts @@ -0,0 +1,57 @@ +import { randIntBetween } from "$lib/utils"; + +export class Thunder { + + ctx: CanvasRenderingContext2D; + frequency: number; + width: number; + height: number; + + countTo: number = 20; + count: number = 0; + flashCount: number = 0; + flashes: number = 2; + + + constructor(context: CanvasRenderingContext2D, canvasHeight: number, canvasWidth: number, frequency: number){ + this.ctx = context; + this.frequency = frequency; + this.width = canvasWidth; + this.height = canvasHeight; + this.resetCounts(); + } + + animate() { + if (this.count >= this.countTo) { + if (this.flashCount <= this.flashes) { + if (this.flashCount % 8 == 0) { + this.drawFlash(); + } else if (this.flashCount % 4 == 0) { + this.drawBlank(); + } + this.flashCount++; + } else { + this.drawBlank(); + this.resetCounts(); + } + } else { + this.count++; + } + } + + resetCounts() { + this.count = 0; + this.flashCount = 0; + this.flashes = randIntBetween(4, 20); + this.countTo = randIntBetween(80, 400); + } + + drawFlash() { + this.ctx.fillStyle = "black"; + this.ctx.fillRect(0, 0,this.width, this.height); + } + drawBlank() { + this.ctx.fillStyle = "none"; + this.ctx.fillRect(0, 0,this.width, this.height); + } +} \ No newline at end of file From fcf13734c4116e9e43fdb37791a30171ecff40d4 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Wed, 26 Feb 2025 22:09:45 -0800 Subject: [PATCH 22/36] =?UTF-8?q?big=20icon=20button=20-=20weather=20butto?= =?UTF-8?q?n=C2=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/components/BigIconButton.svelte | 48 +++++++++++++++++++ .../weather/BigWeatherButton.svelte | 20 ++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/lib/components/BigIconButton.svelte create mode 100644 src/lib/components/weather/BigWeatherButton.svelte diff --git a/src/lib/components/BigIconButton.svelte b/src/lib/components/BigIconButton.svelte new file mode 100644 index 00000000..7d171458 --- /dev/null +++ b/src/lib/components/BigIconButton.svelte @@ -0,0 +1,48 @@ + + + + + diff --git a/src/lib/components/weather/BigWeatherButton.svelte b/src/lib/components/weather/BigWeatherButton.svelte new file mode 100644 index 00000000..199ccc54 --- /dev/null +++ b/src/lib/components/weather/BigWeatherButton.svelte @@ -0,0 +1,20 @@ + + + From 3f552d38749d76469d08a6de667d0d806faff517 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Wed, 26 Feb 2025 22:09:59 -0800 Subject: [PATCH 23/36] weather controls component --- .../components/weather/WeatherControls.svelte | 36 +++++ src/routes/weather/draw/snow.ts | 1 - src/routes/weather/weatherData.ts | 128 +++++++++++------- 3 files changed, 118 insertions(+), 47 deletions(-) create mode 100644 src/lib/components/weather/WeatherControls.svelte diff --git a/src/lib/components/weather/WeatherControls.svelte b/src/lib/components/weather/WeatherControls.svelte new file mode 100644 index 00000000..71aa67d0 --- /dev/null +++ b/src/lib/components/weather/WeatherControls.svelte @@ -0,0 +1,36 @@ + + +
    +
    + {#each WEATHERS as weather} + setCurrentWeather(weather)} + /> + {/each} +
    +
    + + diff --git a/src/routes/weather/draw/snow.ts b/src/routes/weather/draw/snow.ts index 1c381e90..d8b5c747 100644 --- a/src/routes/weather/draw/snow.ts +++ b/src/routes/weather/draw/snow.ts @@ -1,4 +1,3 @@ -import { drawLine, drawLineAtAngle } from "$lib/canvasUtils"; import { Vector2D } from "$lib/mechanics/vector"; import { Drop, Precipitator } from "./precipitater"; diff --git a/src/routes/weather/weatherData.ts b/src/routes/weather/weatherData.ts index c3e27435..36d59158 100644 --- a/src/routes/weather/weatherData.ts +++ b/src/routes/weather/weatherData.ts @@ -1,56 +1,92 @@ export enum Weather { - Clear = "Clear", - Cloudy = "Cloudy", - Overcast = "Overcast", - Fog = "Fog", - Drizzle = "Drizzle", - Rain = "Rain", - Snow = "Snow", - Thunder = "Thunder" + Clear = "Clear", + Cloudy = "Cloudy", + Overcast = "Overcast", + Fog = "Fog", + Drizzle = "Drizzle", + Rain = "Rain", + Snow = "Snow", + Thunder = "Thunder", } +export const WEATHERS = Object.entries(Weather).map((w) => w[1]); + export enum Direction { - N = "North", - NE = "North East", - E = "East", - SE = "South East", - S = "South", - SW = "South West", - W = "West", - NW = "North West" + N = "North", + NE = "North East", + E = "East", + SE = "South East", + S = "South", + SW = "South West", + W = "West", + NW = "North West", +} + +export interface WeatherAttributes { + name: string; + icon: string; + color: string; } +export const WEATHER_ATTRIBUTES: Record = { + Clear: { name: "Clear", color: "gold", icon: "sun" }, + Cloudy: { name: "Cloudy", color: "lightgray", icon: "cloud" }, + Overcast: { name: "Overcast", color: "gray", icon: "cloud" }, + Fog: { name: "Fog", color: "gray", icon: "smog" }, + Drizzle: { name: "Drizzle", color: "lightblue", icon: "cloud-rain" }, + Rain: { name: "Rain", color: "dodgerblue", icon: "cloud-rain" }, + Snow: { name: "Snow", color: "aliceblue", icon: "snowflake" }, + Thunder: { name: "Thunder", color: "darkgray", icon: "cloud-bolt" }, +}; + +/* +Code Description +0 Clear sky +1, 2, 3 Mainly clear, partly cloudy, and overcast +45, 48 Fog and depositing rime fog +51, 53, 55 Drizzle: Light, moderate, and dense intensity +56, 57 Freezing Drizzle: Light and dense intensity +61, 63, 65 Rain: Slight, moderate and heavy intensity +66, 67 Freezing Rain: Light and heavy intensity +71, 73, 75 Snow fall: Slight, moderate, and heavy intensity +77 Snow grains +80, 81, 82 Rain showers: Slight, moderate, and violent +85, 86 Snow showers slight and heavy +95 * Thunderstorm: Slight or moderate +96, 99 * Thunderstorm with slight and heavy hail +*/ + export function getWeatherFromCode(code: number): Weather { - switch(code) { - case 0: - return Weather.Clear - case 1 | 2: - return Weather.Cloudy - case 3: - return Weather.Overcast - case 45 | 48: - return Weather.Fog - case 51 | 53 | 55 | 56 | 57: - return Weather.Drizzle - case 61 | 63 | 65 | 66 | 67 | 80 | 81 | 82: - return Weather.Rain - case 71 | 73 | 75 | 77 | 85 | 86: - return Weather.Snow - case 95 | 96 | 99: - return Weather.Thunder - default: - return Weather.Cloudy - } + switch (code) { + case 0: + return Weather.Clear; + case 1 | 2: + return Weather.Cloudy; + case 3: + return Weather.Overcast; + case 45 | 48: + return Weather.Fog; + case 51 | 53 | 55 | 56 | 57: + return Weather.Drizzle; + case 61 | 63 | 65 | 66 | 67 | 80 | 81 | 82: + return Weather.Rain; + case 71 | 73 | 75 | 77 | 85 | 86: + return Weather.Snow; + case 95 | 96 | 99: + return Weather.Thunder; + default: + return Weather.Cloudy; + } } export function getDirectionFromAngle(angle: number): Direction { - if (angle >= 337.5 || angle < 22.5) return Direction.N; - if (angle >= 22.5 && angle < 67.5) return Direction.NE; - if (angle >= 67.5 && angle < 112.5) return Direction.E; - if (angle >= 112.5 && angle < 157.5) return Direction.SE; - if (angle >= 157.5 && angle < 202.5) return Direction.S; - if (angle >= 202.5 && angle < 247.5) return Direction.SW; - if (angle >= 247.5 && angle < 292.5) return Direction.W; - if (angle >= 292.5 && angle < 337.5) return Direction.NW; - return Direction.N; -} \ No newline at end of file + if (angle >= 337.5 || angle < 22.5) return Direction.N; + if (angle >= 22.5 && angle < 67.5) return Direction.NE; + if (angle >= 67.5 && angle < 112.5) return Direction.E; + if (angle >= 112.5 && angle < 157.5) return Direction.SE; + if (angle >= 157.5 && angle < 202.5) return Direction.S; + if (angle >= 202.5 && angle < 247.5) return Direction.SW; + if (angle >= 247.5 && angle < 292.5) return Direction.W; + if (angle >= 292.5 && angle < 337.5) return Direction.NW; + return Direction.N; +} From 3ff3da3c7dcba61fb131b168f51f76e555a9b843 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Wed, 26 Feb 2025 23:32:24 -0800 Subject: [PATCH 24/36] create animator controller --- src/routes/weather/WeatherSceneAnimator.ts | 133 +++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/routes/weather/WeatherSceneAnimator.ts diff --git a/src/routes/weather/WeatherSceneAnimator.ts b/src/routes/weather/WeatherSceneAnimator.ts new file mode 100644 index 00000000..ffa5a166 --- /dev/null +++ b/src/routes/weather/WeatherSceneAnimator.ts @@ -0,0 +1,133 @@ +import { Animator } from "$lib/canvas/Animator"; +import { Cloud } from "$lib/canvas/weather/animate/Cloud"; +import { Rain } from "$lib/canvas/weather/animate/Rain"; +import { Snow } from "$lib/canvas/weather/animate/Snow"; +import { Thunder } from "$lib/canvas/weather/animate/Thunder"; +import { randIntBetween } from "$lib/utils"; +import { Weather } from "../../lib/data/weatherData"; + +export class WeatherSceneController extends Animator { + height: number; + width: number; + animators: Animator[]; + windspeed: number; + currentWeather: Weather; + + constructor(context: CanvasRenderingContext2D, canvasWidth: number, canvasHeight: number, windspeed: number, weather: Weather) { + super(context); + this.animators = []; + this.height = canvasHeight; + this.width = canvasWidth; + this.windspeed = windspeed / 2; + this.currentWeather = weather; + + this.setWeather(weather); + } + + animate(): void { + this.ctx.clearRect(0, 0, this.width, this.height); + this.animators.forEach((a) => a.animate()); + } + + clearAnimators() { + this.animators.splice(0, this.animators.length); + } + + setWindspeed(windspeed: number) { + this.windspeed = windspeed; + this.setWeather(this.currentWeather); + } + + setWeather(weather: Weather) { + this.currentWeather = weather; + this.clearAnimators(); + + switch (weather) { + case Weather.Clear: + this.setClear(); + break; + case Weather.Cloudy: + this.setCloudy(); + break; + case Weather.Overcast: + this.setOvercast(); + break; + case Weather.Rain: + this.setCloudy(); + this.setRain(); + this.setCloudy(); + break; + case Weather.Drizzle: + this.setCloudy(); + this.setDrizzle(); + break; + case Weather.Snow: + this.setCloudy(); + this.setSnow(); + break; + case Weather.Thunder: + this.setRain(); + this.setCloudy(); + this.setThunder(); + break; + case Weather.Fog: + this.setFog(); + break; + default: + break; + } + } + + setThunder() { + const thunder = new Thunder(this.ctx, this.height, this.width, 0.5); + this.animators.push(thunder); + } + + setClear() { + return; + } + + setCloudy() { + for (let i = 0; i < 10; i++) { + this.animators.push( + new Cloud( + this.ctx, + randIntBetween(0, this.width - 100), + randIntBetween(0, this.height / 2), + randIntBetween(20, 40), + randIntBetween(7, 15), + this.windspeed, + "gray", + ), + ); + } + } + + setOvercast() {} + + setRain() { + const rainFg = new Rain(this.ctx, 20, 0.3, this.windspeed, 10); + const rainBg = new Rain(this.ctx, 10, 0.8, this.windspeed, 5); + + this.animators.push(rainFg); + this.animators.push(rainBg); + } + + setDrizzle() { + const rain = new Rain(this.ctx, 10, 0.3, this.windspeed, 5); + + this.animators.push(rain); + } + + setSnow() { + const snowFg = new Snow(this.ctx, 4, 0.9, this.windspeed, 40); + const snowBg = new Snow(this.ctx, 2, 0.9, this.windspeed, 20); + + this.animators.push(snowFg); + this.animators.push(snowBg); + } + + setFog() { + return; + } +} From 1b43403e6587fa270cd2073914a5a02b76437591 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Wed, 26 Feb 2025 23:32:36 -0800 Subject: [PATCH 25/36] lint --- src/lib/components/BackButton.svelte | 52 ++++---- src/lib/components/MountainCanvas.svelte | 94 ++++++------- src/lib/components/PostPreview.svelte | 105 ++++++++------- src/lib/components/PostPreviewList.svelte | 14 +- src/lib/components/StravaGoalStats.svelte | 124 +++++++++--------- .../weather/BigWeatherButton.svelte | 24 ++-- .../components/weather/WeatherControls.svelte | 45 +++---- tsconfig.json | 21 +-- 8 files changed, 222 insertions(+), 257 deletions(-) diff --git a/src/lib/components/BackButton.svelte b/src/lib/components/BackButton.svelte index f2e0e13b..872a4092 100644 --- a/src/lib/components/BackButton.svelte +++ b/src/lib/components/BackButton.svelte @@ -1,31 +1,35 @@ - {isHovered = true;}} -on:mouseleave={() => {isHovered = false;}}> - + { + isHovered = true; + }} + on:mouseleave={() => { + isHovered = false; + }} +> + - \ No newline at end of file + a:hover { + text-decoration: none; + } + diff --git a/src/lib/components/MountainCanvas.svelte b/src/lib/components/MountainCanvas.svelte index e54233c4..83e9c45b 100644 --- a/src/lib/components/MountainCanvas.svelte +++ b/src/lib/components/MountainCanvas.svelte @@ -1,61 +1,47 @@
    - - + +
    \ No newline at end of file + #mountain-canvas-container { + position: relative; + width: 100%; + height: var(--canvas-height); + } + + canvas { + width: 100%; + position: absolute; + top: 0; + left: 0; + border-radius: 12px; + } + + .scene-canvas { + background-color: rgb(38, 42, 71); + background: linear-gradient(176deg, rgb(23, 27, 51), rgb(38, 42, 71), rgb(94, 80, 117), rgb(163, 116, 139), rgb(234, 176, 156)); + } + diff --git a/src/lib/components/PostPreview.svelte b/src/lib/components/PostPreview.svelte index e0dc091e..2a4da3d5 100644 --- a/src/lib/components/PostPreview.svelte +++ b/src/lib/components/PostPreview.svelte @@ -1,63 +1,62 @@ -
    -
    - {#if hasPostImage} - - {/if} -
    - {#if !isPinnedPost} -
    {date}
    - {/if} - {post.title} -
    -
    - {#if hasProjectLink} - - {/if} +
    + {#if hasPostImage} + + {/if} +
    + {#if !isPinnedPost} +
    {date}
    + {/if} + {post.title} +
    +
    + {#if hasProjectLink} + + {/if}
    \ No newline at end of file + .project-link-container { + margin-left: auto; + margin-right: var(--margin); + color: var(--highlight); + display: flex; + justify-content: center; + align-items: center; + gap: 1em; + text-transform: uppercase; + } + diff --git a/src/lib/components/PostPreviewList.svelte b/src/lib/components/PostPreviewList.svelte index e19634eb..6b176803 100644 --- a/src/lib/components/PostPreviewList.svelte +++ b/src/lib/components/PostPreviewList.svelte @@ -1,16 +1,16 @@
    - + {#each sortedPosts as post} - + {/each}
    @@ -21,4 +21,4 @@ display: flex; flex-direction: column; } - \ No newline at end of file + diff --git a/src/lib/components/StravaGoalStats.svelte b/src/lib/components/StravaGoalStats.svelte index e4593173..af4c8e1c 100644 --- a/src/lib/components/StravaGoalStats.svelte +++ b/src/lib/components/StravaGoalStats.svelte @@ -1,77 +1,77 @@
    -
    -
    {goalYear} Goal: {goal}
    -
    -
    {current}m
    -
    - -
    -
    -
    - {percentage.toFixed(0)}% - {#if !goalComplete} -
    - ({onTrack ? "+" : ""}{schedule.toFixed(1)}% {onTrack ? "ahead" : "behind"}) -
    - {/if} -
    -
    last updated: {formatDate(lastUpdated)}
    -
    -
    - -
    +
    +
    {goalYear} Goal: {goal}
    +
    +
    {current}m
    +
    + +
    +
    +
    + {percentage.toFixed(0)}% + {#if !goalComplete} +
    + ({onTrack ? "+" : ""}{schedule.toFixed(1)}% {onTrack ? "ahead" : "behind"}) +
    + {/if} +
    +
    last updated: {formatDate(lastUpdated)}
    +
    +
    + +
    \ No newline at end of file + .badge { + margin-left: auto; + } + diff --git a/src/lib/components/weather/BigWeatherButton.svelte b/src/lib/components/weather/BigWeatherButton.svelte index 199ccc54..d0c26dea 100644 --- a/src/lib/components/weather/BigWeatherButton.svelte +++ b/src/lib/components/weather/BigWeatherButton.svelte @@ -1,20 +1,14 @@ - + diff --git a/src/lib/components/weather/WeatherControls.svelte b/src/lib/components/weather/WeatherControls.svelte index 71aa67d0..ac5eeef2 100644 --- a/src/lib/components/weather/WeatherControls.svelte +++ b/src/lib/components/weather/WeatherControls.svelte @@ -1,36 +1,31 @@
    -
    - {#each WEATHERS as weather} - setCurrentWeather(weather)} - /> - {/each} -
    +
    + {#each WEATHERS as weather} + setCurrentWeather(weather)} /> + {/each} +
    diff --git a/tsconfig.json b/tsconfig.json index bca81921..4a0e59e6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,22 +12,9 @@ "target": "esnext", "module": "esnext", "moduleResolution": "Node", - "types": [ - "node", - "jest" - ], + "types": ["node", "jest"] }, - "include": [ - ".eslintrc.cjs", - "svelte.config.js", - "vite.config.ts", - "src/**/*", - ".svelte-kit/ambient.d.ts" - ], - "exclude": [ - "node_modules", - ], - "plugins": [ - "babel-plugin-transform-import-meta" - ] + "include": [".eslintrc.cjs", "svelte.config.js", "vite.config.ts", "src/**/*", ".svelte-kit/ambient.d.ts"], + "exclude": ["node_modules"], + "plugins": ["babel-plugin-transform-import-meta"] } From e4a2b9f942d2c4368e8ae6608d1b9df5c489b57a Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Wed, 26 Feb 2025 23:33:02 -0800 Subject: [PATCH 26/36] refactor lib --- src/Artist.ts | 64 ---- src/lib/{ => api}/githubApi.ts | 0 src/lib/{ => api}/stravaApi.ts | 0 src/lib/canvas/Animator.ts | 9 + src/lib/canvas/Artist.ts | 68 ++++ src/lib/{ => canvas}/canvasUtils.ts | 0 src/lib/canvas/mountainScene.ts | 257 +++++++++++++++ src/lib/canvas/weather/animate/Bluebody.ts | 67 ++++ src/lib/canvas/weather/animate/Cloud.ts | 95 ++++++ .../canvas/weather/animate/Precipitater.ts | 58 ++++ src/lib/canvas/weather/animate/Rain.ts | 37 +++ src/lib/canvas/weather/animate/Snow.ts | 71 ++++ src/lib/canvas/weather/animate/Thunder.ts | 55 ++++ src/lib/canvas/weather/draw/character.ts | 247 ++++++++++++++ src/lib/canvas/weather/draw/tree.ts | 47 +++ src/lib/canvas/weather/weatherTime.ts | 0 src/lib/canvas/wordSoup.ts | 209 ++++++++++++ src/lib/{ => config}/config.ts | 0 .../weather => lib/data}/weatherData.ts | 0 src/lib/mechanics/vector.ts | 90 ++--- src/lib/mountainScene.ts | 251 -------------- src/lib/utils.ts | 116 +++++-- src/lib/wordSoup.ts | 219 ------------- src/routes/+layout.svelte | 68 ++-- src/routes/Footer.svelte | 70 ++-- src/routes/[slug]/+page.svelte | 98 +++--- src/routes/projects/[projectId]/+page.svelte | 83 +++-- src/routes/weather/+page.svelte | 52 +-- src/routes/weather/Scene.svelte | 307 +++++++----------- src/routes/weather/draw/bluebody.ts | 74 ----- src/routes/weather/draw/character.ts | 273 ---------------- src/routes/weather/draw/cloud.ts | 111 ------- src/routes/weather/draw/precipitater.ts | 66 ---- src/routes/weather/draw/rain.ts | 49 --- src/routes/weather/draw/snow.ts | 77 ----- src/routes/weather/draw/thunder.ts | 57 ---- src/routes/weather/draw/tree.ts | 47 --- src/routes/weather/weatherTime.ts | 48 --- src/store.ts | 38 +-- 39 files changed, 1676 insertions(+), 1802 deletions(-) delete mode 100644 src/Artist.ts rename src/lib/{ => api}/githubApi.ts (100%) rename src/lib/{ => api}/stravaApi.ts (100%) create mode 100644 src/lib/canvas/Animator.ts create mode 100644 src/lib/canvas/Artist.ts rename src/lib/{ => canvas}/canvasUtils.ts (100%) create mode 100644 src/lib/canvas/mountainScene.ts create mode 100644 src/lib/canvas/weather/animate/Bluebody.ts create mode 100644 src/lib/canvas/weather/animate/Cloud.ts create mode 100644 src/lib/canvas/weather/animate/Precipitater.ts create mode 100644 src/lib/canvas/weather/animate/Rain.ts create mode 100644 src/lib/canvas/weather/animate/Snow.ts create mode 100644 src/lib/canvas/weather/animate/Thunder.ts create mode 100644 src/lib/canvas/weather/draw/character.ts create mode 100644 src/lib/canvas/weather/draw/tree.ts create mode 100644 src/lib/canvas/weather/weatherTime.ts create mode 100644 src/lib/canvas/wordSoup.ts rename src/lib/{ => config}/config.ts (100%) rename src/{routes/weather => lib/data}/weatherData.ts (100%) delete mode 100644 src/lib/mountainScene.ts delete mode 100644 src/lib/wordSoup.ts delete mode 100644 src/routes/weather/draw/bluebody.ts delete mode 100644 src/routes/weather/draw/character.ts delete mode 100644 src/routes/weather/draw/cloud.ts delete mode 100644 src/routes/weather/draw/precipitater.ts delete mode 100644 src/routes/weather/draw/rain.ts delete mode 100644 src/routes/weather/draw/snow.ts delete mode 100644 src/routes/weather/draw/thunder.ts delete mode 100644 src/routes/weather/draw/tree.ts delete mode 100644 src/routes/weather/weatherTime.ts diff --git a/src/Artist.ts b/src/Artist.ts deleted file mode 100644 index 7077dbdb..00000000 --- a/src/Artist.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { drawLine } from "$lib/canvasUtils"; - - -export class Artist{ - - ctx: CanvasRenderingContext2D; - rootX: number; - rootY: number; - startX: number; - startY: number; - endX: number; - endY: number; - - constructor(context: CanvasRenderingContext2D, x: number, y: number) { - this.startX = x; - this.startY = y; - this.endX = x; - this.endY = y; - this.rootX = x; - this.rootY = y; - this.ctx = context; - } - - reset(xOffset: number, yOffset: number) { - this.startX = this.rootX + xOffset; - this.startY = this.rootY + yOffset; - } - - drawNextLine(xOffset: number, yOffset: number) { - this.endX = this.startX + xOffset; - this.endY = this.startY + yOffset; - drawLine(this.ctx, this.startX, this.startY, this.endX, this.endY); - this.startX = this.endX; - this.startY = this.endY; - } - - drawShape(points: Points, reflect: boolean, - xOffset: number, yOffset: number, - lineWidth: number, stroke?: string, fill?: string | CanvasGradient) { - this.ctx.beginPath(); - this.ctx.moveTo(this.startX, this.startY); - points.forEach(p => this.drawNextLine(p[0], p[1])); - if (reflect) { - this.reset(xOffset, yOffset); - points.forEach(p => this.drawReflection(p[0], p[1])); - } - this.ctx.closePath(); - - if (stroke) { - this.ctx.lineCap = 'round'; - this.ctx.lineWidth = lineWidth; - this.ctx.strokeStyle = stroke; - this.ctx.stroke(); - } - if (fill) { - this.ctx.fillStyle = fill; - this.ctx.fill(); - } - } - - drawReflection(xOffset: number, yOffset: number) { - this.drawNextLine(xOffset * -1, yOffset); - } -} diff --git a/src/lib/githubApi.ts b/src/lib/api/githubApi.ts similarity index 100% rename from src/lib/githubApi.ts rename to src/lib/api/githubApi.ts diff --git a/src/lib/stravaApi.ts b/src/lib/api/stravaApi.ts similarity index 100% rename from src/lib/stravaApi.ts rename to src/lib/api/stravaApi.ts diff --git a/src/lib/canvas/Animator.ts b/src/lib/canvas/Animator.ts new file mode 100644 index 00000000..7fe5fe24 --- /dev/null +++ b/src/lib/canvas/Animator.ts @@ -0,0 +1,9 @@ +export abstract class Animator { + ctx: CanvasRenderingContext2D; + + constructor(context: CanvasRenderingContext2D) { + this.ctx = context; + } + + abstract animate(): void; +} diff --git a/src/lib/canvas/Artist.ts b/src/lib/canvas/Artist.ts new file mode 100644 index 00000000..cb6b9fdc --- /dev/null +++ b/src/lib/canvas/Artist.ts @@ -0,0 +1,68 @@ +import { drawLine } from "$lib/canvas/canvasUtils"; + +export class Artist { + ctx: CanvasRenderingContext2D; + rootX: number; + rootY: number; + startX: number; + startY: number; + endX: number; + endY: number; + + constructor(context: CanvasRenderingContext2D, x: number, y: number) { + this.startX = x; + this.startY = y; + this.endX = x; + this.endY = y; + this.rootX = x; + this.rootY = y; + this.ctx = context; + } + + reset(xOffset: number, yOffset: number) { + this.startX = this.rootX + xOffset; + this.startY = this.rootY + yOffset; + } + + drawNextLine(xOffset: number, yOffset: number) { + this.endX = this.startX + xOffset; + this.endY = this.startY + yOffset; + drawLine(this.ctx, this.startX, this.startY, this.endX, this.endY); + this.startX = this.endX; + this.startY = this.endY; + } + + drawShape( + points: Points, + reflect: boolean, + xOffset: number, + yOffset: number, + lineWidth: number, + stroke?: string, + fill?: string | CanvasGradient, + ) { + this.ctx.beginPath(); + this.ctx.moveTo(this.startX, this.startY); + points.forEach((p) => this.drawNextLine(p[0], p[1])); + if (reflect) { + this.reset(xOffset, yOffset); + points.forEach((p) => this.drawReflection(p[0], p[1])); + } + this.ctx.closePath(); + + if (stroke) { + this.ctx.lineCap = "round"; + this.ctx.lineWidth = lineWidth; + this.ctx.strokeStyle = stroke; + this.ctx.stroke(); + } + if (fill) { + this.ctx.fillStyle = fill; + this.ctx.fill(); + } + } + + drawReflection(xOffset: number, yOffset: number) { + this.drawNextLine(xOffset * -1, yOffset); + } +} diff --git a/src/lib/canvasUtils.ts b/src/lib/canvas/canvasUtils.ts similarity index 100% rename from src/lib/canvasUtils.ts rename to src/lib/canvas/canvasUtils.ts diff --git a/src/lib/canvas/mountainScene.ts b/src/lib/canvas/mountainScene.ts new file mode 100644 index 00000000..543f60f2 --- /dev/null +++ b/src/lib/canvas/mountainScene.ts @@ -0,0 +1,257 @@ +import { Artist } from "./Artist"; +import { drawCircle } from "./canvasUtils"; +import { TREE_LINE_LEFT, TREE_LINE_RIGHT } from "../data/mountainSceneData"; + +export function mountainScene( + ctx: CanvasRenderingContext2D, + anCtx: CanvasRenderingContext2D, + width: number, + height: number, + percentage: number, +) { + const a = new Artist(ctx, 0, 0); + const SHADOW = "#715a74"; + const HIGHLIGHT = "#ce8eaa"; + const HILL_LEFT_FILL = "rgb(65, 63, 85)"; + const HILL_RIGHT_FILL = "rgb(74, 75, 96)"; + const TREE_FILL = "rgb(47, 48, 69)"; + const TREE_LINE = "rgb(71, 82, 104)"; + const STARS = "rgba(255, 255, 255, 0.2)"; + + const LEFT_HILL: Points = [ + [0, -height * 0.3], + [width * 0.6, height * 0.3], + ]; + + // intersection of height*0.3 - width * 0.6, + // widht * 0.5 and height * 0.55 + // y = mx + c + // mountain equation: y = (height * 0.55 / width * 0.5)x + // hill equation: y = -(height*0.3 / width * 0.6)x + height * 0.3 + + const RIGHT_HILL: Points = [ + [0, -height * 0.2], + [-width * 0.6, height * 0.2], + ]; + + const MOUNTAIN_SHADOW: Points = [ + [-width * 0.5, height * 0.55], + [width, 0], + [-width * 0.5, -height * 0.55], + ]; + + const MOUNTAIN_HIGHLIGHT: Points = [ + [width * 0.5, height * 0.55], + [-width * 0.5, 0], + [-width * 0.1, -height * 0.15], + [width * 0.1, height * 0.05], + [-width * 0.1, -height * 0.2], + [width * 0.1, height * 0.1], + [-width * 0.05, -height * 0.1], + [width * 0.1, height * 0.08], + [width * 0.1, height * 0.05], + [-width * 0.1, -height * 0.25], + [width * 0.08, height * 0.1], + ]; + + const TREE_CONE_LEFT: Points = [ + [width * 0.07, height * 0.03], + [-width * 0.035, -height * 0.25], + ]; + const TREE_CONE_LEFT_SMALLER: Points = [ + [width * 0.05, height * 0.025], + [-width * 0.025, -height * 0.2], + ]; + + const TREE_CONE_RIGHT: Points = [ + [width * 0.07, -height * 0.03], + [-width * 0.035, -height * 0.25], + ]; + const TREE_CONE_RIGHT_LARGER: Points = [ + [width * 0.08, -height * 0.025], + [-width * 0.04, -height * 0.35], + ]; + + const CLOUDS: Points = [ + [width, 0], + [0, height * 0.5], + [-width, 0], + ]; + + const cloudGradient = ctx.createLinearGradient(width * 0.5, height, width * 0.5, height * 0.6); + cloudGradient.addColorStop(0, "rgba(240,240,240,0.9"); + cloudGradient.addColorStop(0.2, "rgba(240,240,240,0.7"); + cloudGradient.addColorStop(0.8, "rgba(240,240,240,0.1"); + cloudGradient.addColorStop(1, "transparent"); + + const GLOW: Points = [ + [width * 0.5, 0], + [0, height * 0.7], + [-width * 0.5, 0], + ]; + + const glowGradient = ctx.createLinearGradient(width, height, width * 0.95, height * 0.35); + glowGradient.addColorStop(0, "rgb(246, 174, 156)"); + glowGradient.addColorStop(0.5, "rgb(160, 113, 133"); + glowGradient.addColorStop(1, "transparent"); + + // eslint-disable-next-line + function generateTreeLine(): Points { + const points: Points = [ + // [-width * 0.5, -height* 0.2] + [0, height * 0.2], + [-width * 0.5, 0], + ]; + let absX = 0; + let yDirection = 1; + let x = 0; + let y = 0; + while (absX < width * 0.5) { + x = Math.random() * width * 0.01; + yDirection *= -1; + y = yDirection * height * 0.05 * Math.random(); + points.push([x, y]); + absX += x; + } + + return points; + } + + a.reset(width * 0.5, height * 0.3); + a.drawShape(GLOW, false, 0, 0, 0, undefined, glowGradient); + + // Mountain + a.reset(width * 0.5, height * 0.45); + a.drawShape(MOUNTAIN_SHADOW, false, 0, 0, 0, SHADOW, SHADOW); + a.reset(width * 0.5, height * 0.45); + a.drawShape(MOUNTAIN_HIGHLIGHT, false, 0, 0, 0, HIGHLIGHT, HIGHLIGHT); + + // Clouds + a.reset(0, height * 0.5); + a.drawShape(CLOUDS, false, 0, 0, 0, undefined, cloudGradient); + + // Trees back + a.reset(width * 0.5, height * 0.96); + a.drawShape(TREE_LINE_LEFT, false, 0, 0, 0, TREE_LINE, TREE_LINE); + a.reset(width, height * 0.76); + a.drawShape(TREE_LINE_RIGHT, false, 0, 0, 0, TREE_LINE, TREE_LINE); + + // Hills + a.reset(0, height); + a.drawShape(LEFT_HILL, false, 0, 0, 0, HILL_LEFT_FILL, HILL_LEFT_FILL); + a.reset(width, height); + a.drawShape(RIGHT_HILL, false, 0, 0, 0, HILL_RIGHT_FILL, HILL_RIGHT_FILL); + + // Trees left + a.reset(-width * 0.01, height * 0.72); + a.drawShape(TREE_CONE_LEFT, false, 0, 0, 0, TREE_FILL, TREE_FILL); + a.reset(width * 0.04, height * 0.8); + a.drawShape(TREE_CONE_LEFT, false, 0, 0, 0, TREE_FILL, TREE_FILL); + a.reset(width * 0.14, height * 0.88); + a.drawShape(TREE_CONE_LEFT_SMALLER, false, 0, 0, 0, TREE_FILL, TREE_FILL); + + // Trees right + a.reset(width * 0.95, height * 0.9); + a.drawShape(TREE_CONE_RIGHT, false, 0, 0, 0, TREE_FILL, TREE_FILL); + a.reset(width * 0.86, height * 0.86); + a.drawShape(TREE_CONE_RIGHT, false, 0, 0, 0, TREE_FILL, TREE_FILL); + a.reset(width * 0.76, height * 0.95); + a.drawShape(TREE_CONE_RIGHT_LARGER, false, 0, 0, 0, TREE_FILL, TREE_FILL); + + function drawStar(x: number, y: number, stroke: string) { + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x, y); + ctx.strokeStyle = stroke; + ctx.lineWidth = 4; + ctx.stroke(); + ctx.closePath(); + } + + drawStar(width * 0.1, height * 0.15, STARS); + drawStar(width * 0.23, height * 0.34, STARS); + drawStar(width * 0.37, height * 0.17, STARS); + drawStar(width * 0.66, height * 0.05, STARS); + drawStar(width * 0.13, height * 0.4, STARS); + drawStar(width * 0.72, height * 0.53, STARS); + drawStar(width * 0.81, height * 0.47, STARS); + drawStar(width * 0.92, height * 0.07, STARS); + + // Moon + const radius = width * 0.04; + const circle1 = [width * 0.5, height * 0.15]; + const circle2 = [circle1[0] - radius / 3, circle1[1] - radius / 3]; + + ctx.fillStyle = "white"; + ctx.beginPath(); + ctx.arc(circle1[0], circle1[1], radius, 0, Math.PI * 2, true); + ctx.fill(); + ctx.globalCompositeOperation = "destination-out"; + ctx.beginPath(); + ctx.arc(circle2[0], circle2[1], radius, 0, Math.PI * 2, true); + ctx.fill(); + ctx.closePath(); + + ctx.globalCompositeOperation = "source-over"; + + const xIntersect = 0.1875; + const yIntersect = 0.79375; + const intersection = [xIntersect * width, yIntersect * height]; + const adj = width * (0.5 - xIntersect); + const opp = height * (0.55 - (1 - yIntersect)); + + const indicatorRadius = width * 0.05; + const indicatorX = adj * percentage + intersection[0]; + const indicatorY = intersection[1] - opp * percentage; + + let indicatorGradient = anCtx.createRadialGradient( + indicatorX, + indicatorY, + indicatorRadius * 0.2, + indicatorX, + indicatorY, + indicatorRadius * 0.9, + ); + indicatorGradient.addColorStop(0, "rgb(240, 150, 80)"); + indicatorGradient.addColorStop(0.4, "rgba(240, 150, 80, 0.3)"); + indicatorGradient.addColorStop(1, "transparent"); + + anCtx.beginPath(); + drawCircle(anCtx, indicatorX, indicatorY, indicatorRadius, undefined, indicatorGradient); + + let counter = 0; + let degrees = 0; + let rad = 0; + let chance; + const clamp = 50; + function animate() { + counter++; + if (counter % 2 === 0) { + degrees = (degrees + 1) % (180 - clamp); + degrees = Math.max(clamp, degrees); + if (degrees === 110) { + chance = Math.random(); + if (chance > 0.4) { + degrees = 70; + } + } + rad = (degrees * Math.PI) / 180; + anCtx.clearRect(indicatorX - indicatorRadius, indicatorY - indicatorRadius, indicatorX, indicatorY); + indicatorGradient = ctx.createRadialGradient( + indicatorX, + indicatorY, + indicatorRadius * 0.2, + indicatorX, + indicatorY, + indicatorRadius * Math.sin(rad), + ); + indicatorGradient.addColorStop(0, "rgb(240, 150, 80)"); + indicatorGradient.addColorStop(0.3, "rgba(240, 150, 80, 0.3)"); + indicatorGradient.addColorStop(1, "transparent"); + drawCircle(anCtx, indicatorX, indicatorY, indicatorRadius, undefined, indicatorGradient); + } + requestAnimationFrame(animate); + } + + animate(); +} diff --git a/src/lib/canvas/weather/animate/Bluebody.ts b/src/lib/canvas/weather/animate/Bluebody.ts new file mode 100644 index 00000000..fd136160 --- /dev/null +++ b/src/lib/canvas/weather/animate/Bluebody.ts @@ -0,0 +1,67 @@ +import { Animator } from "$lib/canvas/Animator"; + +export class Bluebody extends Animator { + radialGradient: CanvasGradient; + orbitPosX: number; + orbitPosY: number; + orbitBodyRadius: number; + + constructor( + context: CanvasRenderingContext2D, + time: Date, + orbitCenX: number, + orbitCenY: number, + orbitRadius: number, + orbitBodyRadius: number, + ) { + super(context); + // ctx.beginPath(); + // ctx.arc( + // orbitCentreX, + // orbitCentreY, + // orbitRadius, + // Math.PI, Math.PI * 2, false); + // ctx.stroke(); + const hours = time.getHours(); + const moduloTime = hours + (6 % 24); + const orbitAngle = Math.PI - ((((2 * Math.PI) / 24) * moduloTime) % Math.PI) - Math.PI / 2; + const orbitLenX = Math.sin(orbitAngle) * orbitRadius; + const orbitLenY = Math.cos(orbitAngle) * orbitRadius; + this.orbitPosX = orbitCenX - orbitLenX; + this.orbitPosY = orbitCenY - orbitLenY; + this.orbitBodyRadius = orbitBodyRadius; + this.ctx = context; + // sun or moon + if (hours >= 6 && hours < 18) { + this.radialGradient = this.getSunRadialGradient(context, this.orbitPosX, this.orbitPosY, orbitBodyRadius); + } else { + this.radialGradient = this.getMoonRadialGradient(context, this.orbitPosX, this.orbitPosY, orbitBodyRadius); + } + + // context.fillRect(orbitPositionX, orbitPositionY, orbitBodyRadius * 2, orbitBodyRadius * 2); + } + + animate = () => { + this.draw(); + }; + + draw = () => { + this.ctx.fillStyle = this.radialGradient; + this.ctx.arc(this.orbitPosX, this.orbitPosY, this.orbitBodyRadius, 0, 2 * Math.PI, false); + this.ctx.fill(); + }; + + getSunRadialGradient(context: CanvasRenderingContext2D, x: number, y: number, r: number) { + const radialGradient = context.createRadialGradient(x, y, r * 0.8, x, y, r); + radialGradient.addColorStop(0, "rgba(255, 240, 210, 1)"); + radialGradient.addColorStop(1, "rgba(255, 255, 0, 0)"); + return radialGradient; + } + + getMoonRadialGradient(context: CanvasRenderingContext2D, x: number, y: number, r: number) { + const radialGradient = context.createRadialGradient(x, y, r * 0.5, x, y, r * 0.8); + radialGradient.addColorStop(0, "rgba(255, 255, 255, 1)"); + radialGradient.addColorStop(1, "rgba(50, 50, 50, 0)"); + return radialGradient; + } +} diff --git a/src/lib/canvas/weather/animate/Cloud.ts b/src/lib/canvas/weather/animate/Cloud.ts new file mode 100644 index 00000000..fac26635 --- /dev/null +++ b/src/lib/canvas/weather/animate/Cloud.ts @@ -0,0 +1,95 @@ +import { Animator } from "$lib/canvas/Animator"; +import { Vector2D } from "$lib/mechanics/vector"; +import { randBetween } from "$lib/utils"; + +const TWO_PI = Math.PI * 2; +const ORB_SIZE_VARIANCE = [0.8, 1.2]; +const ORB_X_SPREAD_FACTOR = [-2, 2]; +const ORB_Y_SPREAD_FACTOR = [-0.5, 1]; + +export class Cloud extends Animator { + position: Vector2D; + speed: number; + orbs: Orb[] = []; + width: number; + radius: number; + size: number; + color: string; + + constructor(context: CanvasRenderingContext2D, x: number, y: number, radius: number, size: number, speed: number, color: string) { + super(context); + this.position = new Vector2D(x, y); + this.speed = speed; + this.size = size; + this.ctx = context; + this.width = 0; + this.color = color; + this.radius = radius; + } + + animate = () => { + this.position.x += this.speed; + // regenerate once cloud has left screen + if (this.position.x > this.ctx.canvas.width + this.width) { + this.generate(); + this.position.x = -this.width; + // vary height, keep within top half of canvas + this.position.y += randBetween(-this.width, this.width); + this.position.y = Math.max(0, this.position.y); + this.position.y = Math.min(this.ctx.canvas.height / 2, this.position.y); + } + this.draw(); + }; + + draw = () => { + this.orbs.forEach((orb) => orb.draw(this.ctx, this.position.x, this.position.y)); + }; + + generate = () => { + let smallestX = 0; + let largestX = 0; + + this.orbs.splice(0, this.orbs.length); + + for (let i = 0; i < this.size; i++) { + const orbRadius = randBetween(this.radius * ORB_SIZE_VARIANCE[0], this.radius * ORB_SIZE_VARIANCE[1]); + const orbX = randBetween(ORB_X_SPREAD_FACTOR[0] * this.radius, ORB_X_SPREAD_FACTOR[1] * this.radius); + const orbY = randBetween(ORB_Y_SPREAD_FACTOR[0] * this.radius, ORB_Y_SPREAD_FACTOR[1] * this.radius); + if (orbX < smallestX) { + smallestX = orbX; + } + if (orbX > largestX) { + largestX = orbX; + } + this.orbs.push(new Orb(orbX, orbY, orbRadius, this.color)); + } + + this.width = largestX - smallestX; + }; +} + +class Orb { + pos: Vector2D; + color: string; + radius: number; + + constructor(x: number, y: number, radius: number, color: string) { + this.color = color; + this.radius = radius; + this.pos = new Vector2D(x, y); + } + + draw = (ctx: CanvasRenderingContext2D, x: number, y: number) => { + const absX = x + this.pos.x; + const absY = y + this.pos.y; + + const radialGradient = ctx.createRadialGradient(absX, absY, 0, absX, absY, this.radius); + radialGradient.addColorStop(0, this.color); + radialGradient.addColorStop(1, "rgba(255, 255, 255, 0)"); + ctx.beginPath(); + ctx.fillStyle = radialGradient; + ctx.arc(absX, absY, this.radius, 0, TWO_PI, true); + ctx.fill(); + ctx.closePath(); + }; +} diff --git a/src/lib/canvas/weather/animate/Precipitater.ts b/src/lib/canvas/weather/animate/Precipitater.ts new file mode 100644 index 00000000..8f135511 --- /dev/null +++ b/src/lib/canvas/weather/animate/Precipitater.ts @@ -0,0 +1,58 @@ +import { Animator } from "$lib/canvas/Animator"; +import { Vector2D } from "$lib/mechanics/vector"; + +export abstract class Precipitator extends Animator { + canvasWidth: number; + canvasHeight: number; + drops: Drop[] = []; + dropSpeed: number; + windSpeed: number; + size: number; + + constructor(context: CanvasRenderingContext2D, speed: number, density: number, windSpeed: number, size: number) { + super(context); + this.canvasWidth = context.canvas.width; + this.canvasHeight = context.canvas.height; + this.dropSpeed = speed; + this.size = size; + this.windSpeed = windSpeed; + const dropSpacing = this.size / density; + const columns = Math.floor(this.canvasWidth / (this.size + dropSpacing)); + + for (let i = 0; i < columns; i++) { + this.drops.push(this.generateDrop(i * (dropSpacing + this.size), Math.random() * this.canvasHeight)); + } + } + + abstract generateDrop(x: number, y: number): Drop; + + animate = () => { + this.drops.forEach((drop) => { + drop.position.y += this.dropSpeed; + drop.position.x += this.windSpeed / 10; + + if (drop.position.y > this.canvasHeight) { + // vary starting position for variety of y position + drop.position.y = 0 - Math.random() * this.canvasHeight; + // shift drop position left or right up to drop width + drop.position.x = drop.position.x + (Math.random() * 2 - 1.0) * 3 * this.size; + } + if (drop.position.x > this.canvasWidth) { + drop.position.x = 0; + } + drop.draw(this.ctx); + }); + }; +} + +export abstract class Drop { + position: Vector2D; + size: number; + + constructor(size: number, initPos: Vector2D) { + this.position = initPos; + this.size = size; + } + + abstract draw(context: CanvasRenderingContext2D): void; +} diff --git a/src/lib/canvas/weather/animate/Rain.ts b/src/lib/canvas/weather/animate/Rain.ts new file mode 100644 index 00000000..fddbd049 --- /dev/null +++ b/src/lib/canvas/weather/animate/Rain.ts @@ -0,0 +1,37 @@ +import { Vector2D } from "$lib/mechanics/vector"; +import { Drop, Precipitator } from "./Precipitater"; + +const RAIN_COLOR = "rgb(90, 140, 210)"; +const DROP_SIZE = 10; + +export class Rain extends Precipitator { + constructor(context: CanvasRenderingContext2D, speed: number, density: number, windSpeed: number, size: number = DROP_SIZE) { + super(context, speed, density, windSpeed, size); + } + + generateDrop(x: number, y: number): Drop { + return new RainDrop(this.size, new Vector2D(x, y)); + } +} + +class RainDrop extends Drop { + height: number; + + constructor(size: number, initPos: Vector2D) { + super(size, initPos); + this.height = size * 2.5; + } + + draw(context: CanvasRenderingContext2D) { + context.beginPath(); + context.fillStyle = RAIN_COLOR; + context.moveTo(this.position.x, this.position.y); + context.lineTo(this.position.x - this.size, this.position.y + this.height); + context.lineTo(this.position.x + this.size, this.position.y + this.height); + context.lineTo(this.position.x, this.position.y); + context.fill(); + context.arc(this.position.x, this.position.y + this.height, this.size, 1.9 * Math.PI, 1.1 * Math.PI); + context.fill(); + context.closePath(); + } +} diff --git a/src/lib/canvas/weather/animate/Snow.ts b/src/lib/canvas/weather/animate/Snow.ts new file mode 100644 index 00000000..002e07b8 --- /dev/null +++ b/src/lib/canvas/weather/animate/Snow.ts @@ -0,0 +1,71 @@ +import { Vector2D } from "$lib/mechanics/vector"; +import { Drop, Precipitator } from "./Precipitater"; + +const SNOW_COLOR = "rgb(180, 190, 255)"; +// const FLAKE_SIZE = 40; +// const FLAKE_BRANCH_LENGTH = FLAKE_SIZE / 2; + +export class Snow extends Precipitator { + constructor(context: CanvasRenderingContext2D, speed: number, density: number, windSpeed: number, size: number) { + super(context, speed, density, windSpeed, size); + } + + generateDrop(x: number, y: number): Drop { + return new SnowFlake(this.size, new Vector2D(x, y)); + } + + draw = () => { + this.drops.forEach((drop) => drop.draw(this.ctx)); + }; +} + +class SnowFlake extends Drop { + constructor(size: number, initPos: Vector2D) { + super(size, initPos); + } + + draw(context: CanvasRenderingContext2D) { + context.beginPath(); + context.strokeStyle = SNOW_COLOR; + context.lineCap = "round"; + context.lineWidth = this.size / 20; + + this.drawBranch(context, this.size / 2, Math.PI / 6); + this.drawBranch(context, this.size / 2, Math.PI / 2); + this.drawBranch(context, this.size / 2, (5 * Math.PI) / 6); + this.drawBranch(context, this.size / 2, (7 * Math.PI) / 6); + this.drawBranch(context, this.size / 2, (3 * Math.PI) / 2); + this.drawBranch(context, this.size / 2, (11 * Math.PI) / 6); + + context.stroke(); + context.closePath(); + } + + drawBranch = (context: CanvasRenderingContext2D, radius: number, angle: number) => { + const offsetX = radius * Math.cos(angle); + const offsetY = radius * Math.sin(angle); + + // draw main branch + context.moveTo(this.position.x, this.position.y); + context.lineTo(this.position.x - offsetX, this.position.y - offsetY); + + const r2 = radius * 0.5; // length from origin to start of sub branch + const r3 = radius * 1.1; // length from + const subBranchAngle = Math.PI / 6; + const r4 = (r2 + r3) * Math.tan(subBranchAngle); + + const x2 = this.position.x - r2 * Math.cos(angle); + const y2 = this.position.y - r2 * Math.sin(angle); + + const x3 = this.position.x - r4 * Math.cos(subBranchAngle + angle); + const y3 = this.position.y - r4 * Math.sin(subBranchAngle + angle); + + const x4 = this.position.x - r4 * Math.cos(angle - subBranchAngle); + const y4 = this.position.y - r4 * Math.sin(angle - subBranchAngle); + + context.moveTo(x2, y2); + context.lineTo(x3, y3); + context.moveTo(x2, y2); + context.lineTo(x4, y4); + }; +} diff --git a/src/lib/canvas/weather/animate/Thunder.ts b/src/lib/canvas/weather/animate/Thunder.ts new file mode 100644 index 00000000..714e1dbf --- /dev/null +++ b/src/lib/canvas/weather/animate/Thunder.ts @@ -0,0 +1,55 @@ +import { Animator } from "$lib/canvas/Animator"; +import { randIntBetween } from "$lib/utils"; + +export class Thunder extends Animator { + frequency: number; + width: number; + height: number; + + countTo: number = 20; + count: number = 0; + flashCount: number = 0; + flashes: number = 2; + + constructor(context: CanvasRenderingContext2D, canvasHeight: number, canvasWidth: number, frequency: number) { + super(context); + this.frequency = frequency; + this.width = canvasWidth; + this.height = canvasHeight; + this.resetCounts(); + } + + animate() { + if (this.count >= this.countTo) { + if (this.flashCount <= this.flashes) { + if (this.flashCount % 8 == 0) { + this.drawFlash(); + } else if (this.flashCount % 4 == 0) { + this.drawBlank(); + } + this.flashCount++; + } else { + this.drawBlank(); + this.resetCounts(); + } + } else { + this.count++; + } + } + + resetCounts() { + this.count = 0; + this.flashCount = 0; + this.flashes = randIntBetween(4, 20); + this.countTo = randIntBetween(80, 400); + } + + drawFlash() { + this.ctx.fillStyle = "black"; + this.ctx.fillRect(0, 0, this.width, this.height); + } + drawBlank() { + this.ctx.fillStyle = "none"; + this.ctx.fillRect(0, 0, this.width, this.height); + } +} diff --git a/src/lib/canvas/weather/draw/character.ts b/src/lib/canvas/weather/draw/character.ts new file mode 100644 index 00000000..8a4e1c12 --- /dev/null +++ b/src/lib/canvas/weather/draw/character.ts @@ -0,0 +1,247 @@ +import { Artist } from "../../Artist"; + +const AMBER = "rgb(245, 167, 66)"; +// const DARK_ORANGE = "rgb(200, 100, 100)"; +const WHITE = "white"; +const BLACK = "black"; +// const GREY = "rgb(50, 50, 50)"; +const ORANGE_BROWN = "rgb(220, 150, 110)"; +const SHADOW_BROWN = "rgb(120, 80, 20)"; +const DARK_SHADOW_BROWN = "rgb(71, 50, 44)"; +const DARK_GREY = "rgb(50, 50, 50)"; +const PINK = "rgb(220, 100, 150)"; +const DARK_PINK = "rgb(150, 50, 100)"; + +export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: number) { + const a = new Artist(context, x, y); + + const HEAD_POINTS: Points = [ + // top + [15, 0], + // ear + [15, -5], + [20, 0], + [10, 10], + // side + [5, 20], + [0, 10], + [-10, 40], + [-25, 50], + // chin + [-10, 10], + [-10, 5], + [-10, 0], + ]; + + const HEAD_PATCH_POINTS: Points = [ + [10, 0], + [15, -3], + [20, 0], + [10, 35], + [-20, -15], + [-15, -8], + [-20, 0], + ]; + + const RIGHT_EAR_POINTS: Points = [ + // top arc + [5, -6], + [4, -2], + [5, -3], + [15, -5], + // down right + [35, 18], + [2, 10], + [-5, 20], + // up left + [-15, 30], + [-5, 5], + [-3, 0], + [-10, -5], + [-5, -10], + [-5, -10], + [5, -20], + [-5, -20], + ]; + + const LEFT_EAR_POINTS: Points = [ + // top arc + [-5, -10], + [-15, -5], + [-12, 9], + // down right + [-20, 3], + [-10, 5], + [-5, 5], + [15, 35], + // up left + [10, 20], + [10, 5], + [12, -20], + [0, -25], + [5, -10], + ]; + + const NOSE: Points = [ + [5, 0], + [6, 5], + [2, 9], + [-2, 4], + [-4, 2], + [-3, 0], + [-3, -1], + ]; + + const TONGUE: Points = [ + [10, 4], + [3, -2], + [-1, 10], + [-4, 5], + [-4, 3], + [-4, 0], + ]; + + a.drawShape(HEAD_POINTS, true, 0, 0, 2, ORANGE_BROWN, ORANGE_BROWN); + + a.reset(0, 10); + context.beginPath(); + a.drawNextLine(0, 35); + a.endX = a.startX + 20; + a.endY = a.startY + 35; + context.bezierCurveTo(a.startX, a.startY, a.startX, a.startY + 15, a.endX, a.endY); + context.lineCap = "round"; + context.lineWidth = 4; + context.strokeStyle = SHADOW_BROWN; + context.stroke(); + context.closePath(); + a.reset(0, 45); + context.beginPath(); + a.endX = a.startX - 20; + a.endY = a.startY + 35; + context.bezierCurveTo(a.startX, a.startY, a.startX, a.startY + 15, a.endX, a.endY); + context.lineCap = "round"; + context.lineWidth = 4; + context.strokeStyle = SHADOW_BROWN; + context.stroke(); + context.closePath(); + + a.reset(0, 3); + a.drawShape(HEAD_PATCH_POINTS, true, 0, 3, 2, DARK_SHADOW_BROWN, DARK_SHADOW_BROWN); + a.reset(25, -2); + a.drawShape(RIGHT_EAR_POINTS, false, 0, 0, 2, SHADOW_BROWN, SHADOW_BROWN); + a.reset(-25, 0); + a.drawShape(LEFT_EAR_POINTS, false, 0, 0, 2, SHADOW_BROWN, SHADOW_BROWN); + a.reset(0, 80); + a.drawShape(NOSE, true, 0, 80, 2, DARK_GREY, DARK_GREY); + drawEyes(context, x, y); + a.reset(0, 108); + a.drawShape(TONGUE, true, 0, 108, 2, DARK_PINK, PINK); + a.reset(45, 80); + drawFaceLines(context, a.startX, a.startY); +} + +function drawFaceLines(context: CanvasRenderingContext2D, x: number, y: number) { + context.beginPath(); + let startX = x; + let startY = y; + let endX = startX - 5; + let endY = startY + 5; + context.bezierCurveTo(startX, startY, startX - 3, startY - 3, endX, endY); + + startX = endX; + startY = endY; + endX = startX - 25; + endY = startY + 25; + context.bezierCurveTo(startX, startY, startX, startY + 10, endX, endY); + + startX = endX; + startY = endY; + endX = startX - 14; + endY = startY - 5; + context.bezierCurveTo(startX, startY, startX - 10, startY + 2, endX, endY); + + startX = endX; + startY = endY; + endX = startX - 14; + endY = startY + 5; + context.bezierCurveTo(startX, startY, startX - 4, startY + 7, endX, endY); + + startX = endX; + startY = endY; + endX = startX - 25; + endY = startY - 25; + context.bezierCurveTo(startX, startY, startX - 25, startY - 15, endX, endY); + + startX = endX; + startY = endY; + endX = startX - 5; + endY = startY - 5; + context.bezierCurveTo(startX, startY, startX - 2, startY - 7, endX, endY); + + context.lineCap = "round"; + context.lineWidth = 4; + context.strokeStyle = SHADOW_BROWN; + context.stroke(); + context.closePath(); +} + +function drawEyes(context: CanvasRenderingContext2D, x: number, y: number) { + const eyeSize = 17; + const eyePosX = 25; + const eyeTopPosY = 50; + const eyeBottomPosy = 34; + + // Right eye + context.beginPath(); + context.arc(x + eyePosX, y + eyeTopPosY, eyeSize, Math.PI * 1.2, Math.PI * 1.9); + context.fillStyle = DARK_GREY; + context.fill(); + context.closePath(); + context.beginPath(); + context.arc(x + eyePosX + 2, y + eyeBottomPosy, eyeSize, Math.PI * 2.2, Math.PI * 0.9); + context.fillStyle = DARK_GREY; + context.fill(); + + // Right pupil + context.beginPath(); + context.arc(x + eyePosX + 2, y + eyeTopPosY - 10, 7, Math.PI * 2, Math.PI * 1.1); + context.fillStyle = AMBER; + context.fill(); + + context.beginPath(); + context.arc(x + eyePosX + 2, y + eyeTopPosY - 8, 3, 0, Math.PI * 2); + context.fillStyle = BLACK; + context.fill(); + + context.beginPath(); + context.arc(x + eyePosX + 1, y + eyeTopPosY - 9, 1, 0, Math.PI * 2); + context.fillStyle = WHITE; + context.fill(); + + // Left eye + context.beginPath(); + context.arc(x - eyePosX, y + eyeTopPosY, eyeSize, Math.PI * 1.1, Math.PI * 1.8); + context.fillStyle = DARK_GREY; + context.fill(); + context.closePath(); + context.beginPath(); + context.arc(x - eyePosX - 2, y + eyeBottomPosy, eyeSize, Math.PI * 2.1, Math.PI * 0.8); + context.fillStyle = DARK_GREY; + context.fill(); + + // Left pupil + context.beginPath(); + context.arc(x - eyePosX - 2, y + eyeTopPosY - 10, 7, Math.PI * 1.9, Math.PI * 1); + context.fillStyle = AMBER; + context.fill(); + + context.beginPath(); + context.arc(x - eyePosX - 2, y + eyeTopPosY - 8, 3, 0, Math.PI * 2); + context.fillStyle = BLACK; + context.fill(); + + context.beginPath(); + context.arc(x - eyePosX - 3, y + eyeTopPosY - 9, 1, 0, Math.PI * 2); + context.fillStyle = WHITE; + context.fill(); +} diff --git a/src/lib/canvas/weather/draw/tree.ts b/src/lib/canvas/weather/draw/tree.ts new file mode 100644 index 00000000..21f12bfb --- /dev/null +++ b/src/lib/canvas/weather/draw/tree.ts @@ -0,0 +1,47 @@ +import { randRangeRGBString } from "$lib/utils"; + +// inspired by +// https://github.com/PavlyukVadim/amadev/blob/master/RecursiveTree/script.js +export function drawTree( + context: CanvasRenderingContext2D, + startX: number, + startY: number, + length: number, + angle: number, + depth: number, + branchWidth: number, +) { + let newLength, newAngle; + const rand = Math.random; + const maxAngle = (2 * Math.PI) / 6; + const maxBranch = 3; + const endX = startX + length * Math.cos(angle); + const endY = startY + length * Math.sin(angle); + + context.beginPath(); + context.moveTo(startX, startY); + context.lineCap = "round"; + context.lineWidth = branchWidth; + context.lineTo(endX, endY); + + if (depth <= 4) { + context.strokeStyle = randRangeRGBString(30, [64, 180], 0); + } else { + context.strokeStyle = randRangeRGBString([70, 80], 60, [30, 50]); + } + + context.stroke(); + const newDepth = depth - 1; + + if (!newDepth) { + return; + } + const subBranches = rand() * (maxBranch - 1) + 1; + branchWidth *= 0.7; + + for (let i = 0; i < subBranches; i++) { + newAngle = angle + rand() * maxAngle - maxAngle * 0.5; + newLength = length * (0.7 + rand() * 0.3); + drawTree(context, endX, endY, newLength, newAngle, newDepth, branchWidth); + } +} diff --git a/src/lib/canvas/weather/weatherTime.ts b/src/lib/canvas/weather/weatherTime.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/lib/canvas/wordSoup.ts b/src/lib/canvas/wordSoup.ts new file mode 100644 index 00000000..d6054e2c --- /dev/null +++ b/src/lib/canvas/wordSoup.ts @@ -0,0 +1,209 @@ +import { LANG_LOGO_COLORS } from "$data/codeLangData"; +import { resolveCollisions, type Rect, resolveCollision } from "../mechanics/rect"; +import { Vector2D, randomVector } from "../mechanics/vector"; + +export const DEFAULT_GRAVITY = new Vector2D(0, 0.2); +const DEFAULT_DAMPING_FACTOR = 0.3; +const DEFAULT_MIN_MAGNITUDE = 4.8; +const DEFAULT_FONT_AREA = 1200; +const DEFAULT_MIN_FONT = 32; + +const COLORS = ["blue", "red", "green", "purple", "orange", "magenta", "lime"]; + +function getRects(wordBlocks: WordBlock[]): Rect[] { + return wordBlocks.map((block) => block.getRect()); +} + +function getColor(technology: string) { + if (LANG_LOGO_COLORS[technology]) { + return LANG_LOGO_COLORS[technology]; + } else { + const randomIndex = Math.floor(Math.random() * COLORS.length); + return COLORS[randomIndex]; + } +} + +function getProportionalFontSize(fontArea: number | undefined, value: number, minFontSize?: number) { + const result = (fontArea ?? DEFAULT_FONT_AREA) * value; + return Math.max(result, minFontSize ?? DEFAULT_MIN_FONT); +} + +export class WordSoup { + wordBlocks: WordBlock[] = []; + ctx: CanvasRenderingContext2D; + canvas: HTMLCanvasElement; + counter: number = 0; + randomForces: boolean = false; + gravity: Vector2D = DEFAULT_GRAVITY; + dampingFactor: number = DEFAULT_DAMPING_FACTOR; + + constructor( + canvas: HTMLCanvasElement, + context: CanvasRenderingContext2D, + words: Record, + fontArea?: number, + minFontSize?: number, + ) { + this.canvas = canvas; + this.ctx = context; + this.ctx.textAlign = "left"; + this.ctx.textBaseline = "top"; + + Object.keys(words).forEach((word) => { + this.wordBlocks.push(new WordBlock(word, getProportionalFontSize(fontArea, words[word], minFontSize), getColor(word))); + }); + + let lastPlacedBlock: WordBlock; + const nextPosition = new Vector2D(10, 10); + const maxBlockHeight = Math.max(...this.wordBlocks.map((b) => b.height)); + this.wordBlocks.forEach((block) => { + if (lastPlacedBlock) { + nextPosition.x += lastPlacedBlock.width + Math.random() * lastPlacedBlock.width; + if (nextPosition.x + block.width > canvas.width) { + nextPosition.x = 10 + Math.random() * 10; + nextPosition.y += maxBlockHeight * 2 + 10 + Math.random() * maxBlockHeight; + } + } + + block.position.x = nextPosition.x; + block.position.y = nextPosition.y; + lastPlacedBlock = block; + block.draw(this.ctx); + }); + } + + setForces(value: Vector2D, randomForces: boolean = false, dampingFactor?: number) { + this.randomForces = randomForces; + this.setGravity(value); + if (dampingFactor) { + this.dampingFactor = dampingFactor; + } + } + + setGravity(value: Vector2D) { + this.gravity = value; + } + + animate = () => { + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + const rects = getRects(this.wordBlocks); + this.wordBlocks.forEach((block) => { + const force = this.randomForces ? randomVector(1, 2) : this.gravity; + this.updateVelocity(block, rects, force); + block.draw(this.ctx); + }); + requestAnimationFrame(this.animate); + }; + + updateVelocity(word: WordBlock, collisionRects: Rect[], force: Vector2D) { + word.velocity.add(force); + + const potentialPos = new Vector2D(word.position.x + word.velocity.x, word.position.y + word.velocity.y); + + const potentialRect: Rect = { + x1: potentialPos.x, + x2: potentialPos.x + word.width, + y1: potentialPos.y, + y2: potentialPos.y + word.height, + }; + + const collision = resolveCollisions(potentialRect, word.getRect(), collisionRects, word.velocity); + + let rectangleCollision = false; + let boundaryCollision = false; + + if (collision) { + rectangleCollision = true; + word.velocity.add(collision.response); + } + + if (potentialPos.x < 0 || potentialPos.x >= this.canvas.width - word.width || (collision && Math.abs(collision.response.x) > 0)) { + word.velocity.x = word.velocity.x * -1; + boundaryCollision = true; + } + + if (potentialPos.y < 0 || potentialPos.y >= this.canvas.height - word.height || (collision && Math.abs(collision.response.y) > 0)) { + word.velocity.y = word.velocity.y * -1; + boundaryCollision = true; + } + + if (boundaryCollision) { + word.velocity.attenuate(this.dampingFactor, DEFAULT_MIN_MAGNITUDE); + } + + word.position.add(word.velocity); + + if (rectangleCollision && collision) { + const response = resolveCollision(word.getRect(), collisionRects[collision.index], word.velocity); + if (response) { + word.position.add(response); + } + } + + if (word.position.x < 0) { + word.position.x = 1; + } + + if (word.position.x >= this.canvas.width - word.width) { + word.position.x = this.canvas.width - word.width - 1; + } + + if (word.position.y < 0) { + word.position.y = 1; + } + if (word.position.y >= this.canvas.height - word.height) { + word.position.y = this.canvas.height - word.height - 1; + } + } +} + +class WordBlock { + height: number; + width: number; + color: string; + word: string; + charSize: number; + velocity: Vector2D; + position: Vector2D = new Vector2D(0, 0); + rotationMomentum: number = 0; + + constructor(word: string, charSize: number, color: string) { + this.height = charSize + 8; + this.width = charSize * word.length * 0.6 + 16; + this.color = color; + this.word = word; + this.charSize = charSize; + this.velocity = new Vector2D(0, 0); + } + + draw(ctx: CanvasRenderingContext2D) { + this.position.round(); + ctx.beginPath(); + // ctx.fillRect(this.position.x, this.position.y, this.width, this.height); + ctx.roundRect(this.position.x, this.position.y, this.width, this.height, 12); + ctx.fillStyle = this.color; + ctx.fill(); + ctx.closePath(); + + ctx.beginPath(); + ctx.font = `bold ${this.charSize}px 'JetBrainsMono'`; + ctx.fillStyle = "white"; + ctx.fillText(this.word, this.position.x + 4, this.position.y + 8, this.width); + ctx.fill(); + ctx.closePath(); + } + + setRandomPosition(bounds: Vector2D) { + this.position.x = Math.floor(Math.random() * (bounds.x - this.width)); + this.position.y = Math.floor(Math.random() * (bounds.y - this.height)); + } + + getRect(): Rect { + return { + x1: this.position.x, + x2: this.position.x + this.width, + y1: this.position.y, + y2: this.position.y + this.height, + }; + } +} diff --git a/src/lib/config.ts b/src/lib/config/config.ts similarity index 100% rename from src/lib/config.ts rename to src/lib/config/config.ts diff --git a/src/routes/weather/weatherData.ts b/src/lib/data/weatherData.ts similarity index 100% rename from src/routes/weather/weatherData.ts rename to src/lib/data/weatherData.ts diff --git a/src/lib/mechanics/vector.ts b/src/lib/mechanics/vector.ts index bb1ad267..2b93ed31 100644 --- a/src/lib/mechanics/vector.ts +++ b/src/lib/mechanics/vector.ts @@ -1,70 +1,70 @@ import { randomDirection, randomMinMax } from "../utils"; export class Vector2D { - x: number - y: number + x: number; + y: number; - constructor(x: number, y: number) { - this.x = x; - this.y = y; - } + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } - add(vector: Vector2D) { - this.x += vector.x; - this.y += vector.y; - } + add(vector: Vector2D) { + this.x += vector.x; + this.y += vector.y; + } - scale(factor: number) { - this.x *= factor; - this.y *= factor; - } + scale(factor: number) { + this.x *= factor; + this.y *= factor; + } - round() { - this.x = Math.floor(this.x); - this.y = Math.floor(this.y); - } + round() { + this.x = Math.floor(this.x); + this.y = Math.floor(this.y); + } - ZERO() { - this.x = 0; - this.y = 0; - } + ZERO() { + this.x = 0; + this.y = 0; + } - isZero() { - return this.x === 0 && this.y === 0; - } + isZero() { + return this.x === 0 && this.y === 0; + } - attenuate(factor: number, minMagnitude: number) { - const attenuatedX = this.x * factor; - const attenuatedY = this.y * factor; - this.x = Math.abs(attenuatedX) < minMagnitude ? 0 : attenuatedX; - this.y = Math.abs(attenuatedY) < minMagnitude ? 0 : attenuatedY; - } + attenuate(factor: number, minMagnitude: number) { + const attenuatedX = this.x * factor; + const attenuatedY = this.y * factor; + this.x = Math.abs(attenuatedX) < minMagnitude ? 0 : attenuatedX; + this.y = Math.abs(attenuatedY) < minMagnitude ? 0 : attenuatedY; + } - reverse() { - return new Vector2D(this.x * -1, this.y * -1); - } + reverse() { + return new Vector2D(this.x * -1, this.y * -1); + } } export function dotProduct(vector1: Vector2D, vector2: Vector2D): number { - return (vector1.x * vector2.x) + (vector1.y * vector2.y); + return vector1.x * vector2.x + vector1.y * vector2.y; } export function vectorMagnitude(vector: Vector2D): number { - return Math.sqrt(Math.pow(vector.x, 2) + Math.pow(vector.y, 2)); + return Math.sqrt(Math.pow(vector.x, 2) + Math.pow(vector.y, 2)); } export function reflectionVector(incident: Vector2D, normal: Vector2D) { - const product = dotProduct(incident, normal); - const sqrMagnitude = Math.pow(vectorMagnitude(normal), 2); - const x = incident.x + ((-2 * normal.x * product) / sqrMagnitude); - const y = incident.y + ((-2 * normal.y * product) / sqrMagnitude); - return new Vector2D(x, y); + const product = dotProduct(incident, normal); + const sqrMagnitude = Math.pow(vectorMagnitude(normal), 2); + const x = incident.x + (-2 * normal.x * product) / sqrMagnitude; + const y = incident.y + (-2 * normal.y * product) / sqrMagnitude; + return new Vector2D(x, y); } export function randomVector(min: number, max: number): Vector2D { - const x = randomMinMax(min, max) * randomDirection(); - const y = randomMinMax(min, max) * randomDirection(); - return new Vector2D(x, y); + const x = randomMinMax(min, max) * randomDirection(); + const y = randomMinMax(min, max) * randomDirection(); + return new Vector2D(x, y); } -export const ZERO_VECTOR = new Vector2D(0, 0); \ No newline at end of file +export const ZERO_VECTOR = new Vector2D(0, 0); diff --git a/src/lib/mountainScene.ts b/src/lib/mountainScene.ts deleted file mode 100644 index 34a0c723..00000000 --- a/src/lib/mountainScene.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { Artist } from "../Artist"; -import { drawCircle } from "./canvasUtils"; -import { TREE_LINE_LEFT, TREE_LINE_RIGHT } from "./data/mountainSceneData"; - -export function mountainScene(ctx: CanvasRenderingContext2D, anCtx: CanvasRenderingContext2D, width: number, height: number, percentage: number) { - - const a = new Artist(ctx, 0, 0); - const SHADOW = "#715a74"; - const HIGHLIGHT = "#ce8eaa"; - const HILL_LEFT_FILL = "rgb(65, 63, 85)"; - const HILL_RIGHT_FILL = "rgb(74, 75, 96)"; - const TREE_FILL = "rgb(47, 48, 69)"; - const TREE_LINE = "rgb(71, 82, 104)"; - const STARS = "rgba(255, 255, 255, 0.2)"; - - const LEFT_HILL: Points = [ - [0, -height * 0.3 ], - [width * 0.6, height * 0.3 ], - ]; - - // intersection of height*0.3 - width * 0.6, - // widht * 0.5 and height * 0.55 - // y = mx + c - // mountain equation: y = (height * 0.55 / width * 0.5)x - // hill equation: y = -(height*0.3 / width * 0.6)x + height * 0.3 - - - - const RIGHT_HILL: Points = [ - [0, -height * 0.2], - [-width * 0.6, height * 0.2 ], - ]; - - const MOUNTAIN_SHADOW: Points = [ - [-width * 0.5, height * 0.55], - [width, 0], - [-width * 0.5, -height * 0.55 ], - ]; - - const MOUNTAIN_HIGHLIGHT: Points = [ - [width * 0.5, height * 0.55], - [-width * 0.5, 0], - [-width * 0.1, - height * 0.15], - [width * 0.1, height * 0.05], - [-width * 0.1, - height * 0.2], - [width * 0.1, height * 0.1], - [-width * 0.05, - height * 0.1], - [width * 0.1, height * 0.08], - [width * 0.1, height * 0.05], - [-width * 0.1, -height * 0.25], - [width * 0.08, height * 0.1] - ]; - - const TREE_CONE_LEFT: Points = [ - [width * 0.07, height * 0.03], - [-width * 0.035, -height * 0.25], - ]; - const TREE_CONE_LEFT_SMALLER: Points = [ - [width * 0.05, height * 0.025], - [-width * 0.025, -height * 0.2], - ]; - - const TREE_CONE_RIGHT: Points = [ - [width * 0.07, -height * 0.03], - [-width * 0.035, -height * 0.25], - ]; - const TREE_CONE_RIGHT_LARGER: Points = [ - [width * 0.08, -height * 0.025], - [-width * 0.04, -height * 0.35], - ]; - - const CLOUDS: Points = [ - [width, 0], - [0, height * 0.5], - [-width, 0], - ]; - - const cloudGradient = ctx.createLinearGradient( - width * 0.5, height, - width * 0.5, height * 0.6); - cloudGradient.addColorStop(0, "rgba(240,240,240,0.9"); - cloudGradient.addColorStop(0.2, "rgba(240,240,240,0.7"); - cloudGradient.addColorStop(0.8, "rgba(240,240,240,0.1"); - cloudGradient.addColorStop(1, "transparent"); - - const GLOW: Points = [ - [width * 0.5, 0], - [0, height * 0.7], - [-width * 0.5, 0], - ] - - const glowGradient = ctx.createLinearGradient( - width, height, - width * 0.95, height * 0.35); - glowGradient.addColorStop(0, "rgb(246, 174, 156)"); - glowGradient.addColorStop(0.5, "rgb(160, 113, 133"); - glowGradient.addColorStop(1, "transparent"); - - // eslint-disable-next-line - function generateTreeLine(): Points { - const points: Points = [ - // [-width * 0.5, -height* 0.2] - [0, height * 0.2], - [-width * 0.5, 0], - ]; - let absX = 0; - let yDirection = 1; - let x = 0; - let y = 0; - while(absX < width * 0.5) { - x = Math.random() * width * 0.01; - yDirection *= -1; - y = yDirection * height * 0.05 * Math.random(); - points.push([x, y]); - absX += x; - } - - return points; - } - - a.reset(width * 0.5, height * 0.3) - a.drawShape(GLOW, false, 0, 0, 0, undefined, glowGradient); - - // Mountain - a.reset(width * 0.5, height * 0.45); - a.drawShape(MOUNTAIN_SHADOW, false, 0, 0, 0, SHADOW, SHADOW); - a.reset(width * 0.5, height * 0.45); - a.drawShape(MOUNTAIN_HIGHLIGHT, false, 0, 0, 0, HIGHLIGHT, HIGHLIGHT); - - // Clouds - a.reset(0, height * 0.5) - a.drawShape(CLOUDS, false, 0, 0, 0, undefined, cloudGradient); - - // Trees back - a.reset(width * 0.5, height * 0.96) - a.drawShape(TREE_LINE_LEFT, false, 0, 0, 0, TREE_LINE, TREE_LINE); - a.reset(width, height * 0.76) - a.drawShape(TREE_LINE_RIGHT, false, 0, 0, 0, TREE_LINE, TREE_LINE); - - // Hills - a.reset(0, height); - a.drawShape(LEFT_HILL, false, 0, 0, 0, HILL_LEFT_FILL, HILL_LEFT_FILL) - a.reset(width, height); - a.drawShape(RIGHT_HILL, false, 0, 0, 0, HILL_RIGHT_FILL, HILL_RIGHT_FILL) - - // Trees left - a.reset(-width * 0.01, height * 0.72); - a.drawShape(TREE_CONE_LEFT, false, 0, 0, 0, TREE_FILL, TREE_FILL) - a.reset(width * 0.04, height * 0.80); - a.drawShape(TREE_CONE_LEFT, false, 0, 0, 0, TREE_FILL, TREE_FILL) - a.reset(width * 0.14, height * 0.88); - a.drawShape(TREE_CONE_LEFT_SMALLER, false, 0, 0, 0, TREE_FILL, TREE_FILL) - - // Trees right - a.reset(width * 0.95, height * 0.9); - a.drawShape(TREE_CONE_RIGHT, false, 0, 0, 0, TREE_FILL, TREE_FILL) - a.reset(width * 0.86, height * 0.86); - a.drawShape(TREE_CONE_RIGHT, false, 0, 0, 0, TREE_FILL, TREE_FILL) - a.reset(width * 0.76, height * 0.95); - a.drawShape(TREE_CONE_RIGHT_LARGER, false, 0, 0, 0, TREE_FILL, TREE_FILL) - - function drawStar(x: number, y: number, stroke: string) { - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x, y); - ctx.strokeStyle = stroke; - ctx.lineWidth = 4; - ctx.stroke(); - ctx.closePath(); - } - - drawStar(width * 0.1, height * 0.15, STARS); - drawStar(width * 0.23, height * 0.34, STARS); - drawStar(width * 0.37, height * 0.17, STARS); - drawStar(width * 0.66, height * 0.05, STARS); - drawStar(width * 0.13, height * 0.4, STARS); - drawStar(width * 0.72, height * 0.53, STARS); - drawStar(width * 0.81, height * 0.47, STARS); - drawStar(width * 0.92, height * 0.07, STARS); - - // Moon - const radius = width * 0.04; - const circle1 = [width * 0.5, height * 0.15]; - const circle2 = [circle1[0] - (radius / 3), circle1[1] - (radius / 3)]; - - ctx.fillStyle = "white"; - ctx.beginPath(); - ctx.arc(circle1[0], circle1[1], radius, 0, Math.PI * 2, true); - ctx.fill(); - ctx.globalCompositeOperation = 'destination-out'; - ctx.beginPath(); - ctx.arc(circle2[0], circle2[1], radius, 0, Math.PI * 2, true); - ctx.fill(); - ctx.closePath(); - - ctx.globalCompositeOperation = 'source-over'; - - const xIntersect = 0.1875; - const yIntersect = 0.79375; - const intersection = [xIntersect * width, yIntersect * height]; - const adj = width * (0.5 - xIntersect); - const opp = height * (0.55 - (1 - yIntersect)); - - const indicatorRadius = width * 0.05; - const indicatorX = (adj * percentage) + intersection[0]; - const indicatorY = intersection[1] - (opp * percentage); - - let indicatorGradient = anCtx.createRadialGradient( - indicatorX, indicatorY, indicatorRadius * 0.2, - indicatorX, indicatorY, indicatorRadius * 0.9) - indicatorGradient.addColorStop(0, "rgb(240, 150, 80)"); - indicatorGradient.addColorStop(0.4, "rgba(240, 150, 80, 0.3)"); - indicatorGradient.addColorStop(1, "transparent"); - - anCtx.beginPath(); - drawCircle(anCtx, indicatorX, indicatorY, indicatorRadius, undefined, indicatorGradient); - - let counter = 0; - let degrees = 0; - let rad = 0; - let chance; - const clamp = 50; - function animate() { - counter++; - if (counter % 2 === 0) { - degrees = (degrees + 1) % (180 - clamp); - degrees = Math.max(clamp, degrees); - if (degrees === 110) { - chance = Math.random(); - if (chance > 0.4) { - degrees = 70 - } - } - rad = degrees * Math.PI / 180; - anCtx.clearRect( - indicatorX - indicatorRadius, indicatorY - indicatorRadius, - indicatorX, indicatorY - ) - indicatorGradient = ctx.createRadialGradient( - indicatorX, indicatorY, indicatorRadius * 0.2, - indicatorX, indicatorY, indicatorRadius * Math.sin(rad)); - indicatorGradient.addColorStop(0, "rgb(240, 150, 80)"); - indicatorGradient.addColorStop(0.3, "rgba(240, 150, 80, 0.3)"); - indicatorGradient.addColorStop(1, "transparent"); - drawCircle(anCtx, indicatorX, indicatorY, indicatorRadius, undefined, indicatorGradient); - } - requestAnimationFrame(animate); - } - - animate(); -} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 70f56694..555b7178 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,65 +1,113 @@ import { sineIn } from "svelte/easing"; -type DateStyle = Intl.DateTimeFormatOptions['dateStyle'] +type DateStyle = Intl.DateTimeFormatOptions["dateStyle"]; -export function formatDate(date: string, dateStyle: DateStyle = 'medium', locales = "en-gb"): string { - const formatter = new Intl.DateTimeFormat(locales, { dateStyle }); - return formatter.format(new Date(date)); +export function formatDate(date: string, dateStyle: DateStyle = "medium", locales = "en-gb"): string { + const formatter = new Intl.DateTimeFormat(locales, { dateStyle }); + return formatter.format(new Date(date)); } export function randBetween(min: number, max: number) { - return (Math.random() * (max - min)) + min; + return Math.random() * (max - min) + min; } export function randIntBetween(min: number, max: number) { - return Math.floor(randBetween(min - 1, max)); + return Math.floor(randBetween(min - 1, max)); } export function randVariance(value: number, variance: number) { - const min = value * (1 - variance); - const max = value * (1 + variance); - return randBetween(min, max); + const min = value * (1 - variance); + const max = value * (1 + variance); + return randBetween(min, max); } export function dayOfYear(date: Date) { - const diff = date.valueOf() - new Date(date. getFullYear(), 0, 0).valueOf(); - return Math.floor( diff / (1000 * 60 * 60 * 24)); + const diff = date.valueOf() - new Date(date.getFullYear(), 0, 0).valueOf(); + return Math.floor(diff / (1000 * 60 * 60 * 24)); } export function randomMinMax(min: number, max: number) { - return Math.floor(Math.random() * (max - min + 1) + min) + return Math.floor(Math.random() * (max - min + 1) + min); } export function randomDirection(): 1 | -1 { - return Math.random() >= 0.5 ? 1 : -1; + return Math.random() >= 0.5 ? 1 : -1; } -export function comparePinnedPosts (p1: Post, p2: Post) { - return parsePinned(p2.pinned) - parsePinned(p1.pinned); +export function comparePinnedPosts(p1: Post, p2: Post) { + return parsePinned(p2.pinned) - parsePinned(p1.pinned); } const parsePinned = (pinned: boolean | undefined) => Number(pinned ?? 0); - export const grow = (_node: HTMLElement) => { - return { - duration: 100, - easing: sineIn, - css: (t: number) => `transform: scaleX(${t}); transform-origin: left` - } -} + return { + duration: 100, + easing: sineIn, + css: (t: number) => `transform: scaleX(${t}); transform-origin: left`, + }; +}; export const randRangeRGBString = ( - redRange: [number, number] | number = [0, 255], - greenRange: [number,number] | number = [0, 255], - blueRange: [number,number] | number = [0, 255]) => { - - const getValue = (range: [number, number] | number) => { - return Array.isArray(range) ? randBetween(range[0], range[1]) : range - } - - const red = getValue(redRange); - const green = getValue(greenRange); - const blue = getValue(blueRange); - return `rgb(${red}, ${green}, ${blue})`; + redRange: [number, number] | number = [0, 255], + greenRange: [number, number] | number = [0, 255], + blueRange: [number, number] | number = [0, 255], +) => { + const getValue = (range: [number, number] | number) => { + return Array.isArray(range) ? randBetween(range[0], range[1]) : range; + }; + + const red = getValue(redRange); + const green = getValue(greenRange); + const blue = getValue(blueRange); + return `rgb(${red}, ${green}, ${blue})`; +}; + +export function timeNoun(time: Date): string { + const hour = time.getHours(); + return hourNoun(hour); +} + +export function hourNoun(hour: number): string { + switch (hour) { + case 6: + return "dawn"; + case 7: + return "sunrise"; + case 8: + return "morning"; + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + return "day"; + case 19: + return "evening"; + case 20: + return "sunset"; + case 21: + return "dusk"; + case 22: + case 23: + case 24: + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + return "night"; + default: + return "day"; + } +} + +export function timeAsTwelvethFraction(time: Date): number { + return time.getHours() + (6 % 12); } diff --git a/src/lib/wordSoup.ts b/src/lib/wordSoup.ts deleted file mode 100644 index 4269dd11..00000000 --- a/src/lib/wordSoup.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { LANG_LOGO_COLORS } from "./data/codeLangData"; -import { resolveCollisions, type Rect, resolveCollision } from "./mechanics/rect"; -import { Vector2D, randomVector } from "./mechanics/vector"; - -export const DEFAULT_GRAVITY = new Vector2D(0, 0.2); -const DEFAULT_DAMPING_FACTOR = 0.3; -const DEFAULT_MIN_MAGNITUDE = 4.8 -const DEFAULT_FONT_AREA = 1200; -const DEFAULT_MIN_FONT = 32; - -const COLORS = [ - "blue", - "red", - "green", - "purple", - "orange", - "magenta", - "lime" -] - -function getRects(wordBlocks: WordBlock[]): Rect[] { - return wordBlocks.map(block => block.getRect()); -} - -function getColor(technology: string) { - if (LANG_LOGO_COLORS[technology]) { - return LANG_LOGO_COLORS[technology]; - } else { - const randomIndex = Math.floor(Math.random() * COLORS.length); - return COLORS[randomIndex] - } - -} - -function getProportionalFontSize(fontArea: number | undefined, value: number, minFontSize?: number) { - const result = ((fontArea ?? DEFAULT_FONT_AREA) * value); - return Math.max(result, minFontSize ?? DEFAULT_MIN_FONT); -} - -export class WordSoup { - - wordBlocks: WordBlock[] = []; - ctx: CanvasRenderingContext2D; - canvas: HTMLCanvasElement; - counter: number = 0; - randomForces: boolean = false; - gravity: Vector2D = DEFAULT_GRAVITY; - dampingFactor: number = DEFAULT_DAMPING_FACTOR; - - constructor(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, words: Record, fontArea?: number, minFontSize?: number) { - this.canvas = canvas; - this.ctx = context; - this.ctx.textAlign = "left"; - this.ctx.textBaseline = "top"; - - Object.keys(words).forEach(word => { - this.wordBlocks.push( - new WordBlock( - word, - getProportionalFontSize(fontArea, words[word], minFontSize), - getColor(word))); - }) - - let lastPlacedBlock: WordBlock; - const nextPosition = new Vector2D(10, 10); - const maxBlockHeight = Math.max(...this.wordBlocks.map(b => b.height)); - this.wordBlocks.forEach(block => { - if (lastPlacedBlock) { - nextPosition.x += lastPlacedBlock.width + (Math.random() * lastPlacedBlock.width); - if (nextPosition.x + block.width > canvas.width) { - nextPosition.x = 10 + (Math.random() * 10); - nextPosition.y += (maxBlockHeight * 2) + 10 + (Math.random() * maxBlockHeight) - } - } - - block.position.x = nextPosition.x; - block.position.y = nextPosition.y; - lastPlacedBlock = block; - block.draw(this.ctx); - }); - } - - setForces(value: Vector2D, randomForces: boolean = false, dampingFactor?: number) { - this.randomForces = randomForces; - this.setGravity(value); - if (dampingFactor) { - this.dampingFactor = dampingFactor; - } - } - - setGravity(value: Vector2D) { - this.gravity = value; - } - - animate = () => { - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - const rects = getRects(this.wordBlocks); - this.wordBlocks.forEach(block => { - const force = this.randomForces ? randomVector(1, 2) : this.gravity; - this.updateVelocity(block, rects, force); - block.draw(this.ctx); - }); - requestAnimationFrame(this.animate); - } - - updateVelocity(word: WordBlock, collisionRects: Rect[], force: Vector2D) { - word.velocity.add(force); - - const potentialPos = new Vector2D(word.position.x + word.velocity.x, - word.position.y + word.velocity.y); - - const potentialRect: Rect = { - x1: potentialPos.x, - x2: potentialPos.x + word.width, - y1: potentialPos.y, - y2: potentialPos.y + word.height - } - - const collision = resolveCollisions(potentialRect, word.getRect(), collisionRects, word.velocity); - - let rectangleCollision = false; - let boundaryCollision = false; - - if (collision) { - rectangleCollision = true; - word.velocity.add(collision.response); - } - - if (potentialPos.x < 0 || potentialPos.x >= this.canvas.width - word.width || collision && Math.abs(collision.response.x) > 0) { - word.velocity.x = word.velocity.x * -1; - boundaryCollision = true; - } - - if (potentialPos.y < 0 || potentialPos.y >= this.canvas.height - word.height || collision && Math.abs(collision.response.y) > 0) { - word.velocity.y = word.velocity.y * -1; - boundaryCollision = true; - } - - if (boundaryCollision) { - word.velocity.attenuate(this.dampingFactor, DEFAULT_MIN_MAGNITUDE); - } - - word.position.add(word.velocity); - - if (rectangleCollision && collision) { - const response = resolveCollision(word.getRect(), collisionRects[collision.index], word.velocity) - if (response) { - word.position.add(response); - } - } - - if (word.position.x < 0) { - word.position.x = 1; - } - - if(word.position.x >= this.canvas.width - word.width) { - word.position.x = this.canvas.width - word.width - 1; - } - - if (word.position.y < 0) { - word.position.y = 1; - } - if (word.position.y >= this.canvas.height - word.height) { - word.position.y = this.canvas.height - word.height - 1; - } - } -} - -class WordBlock { - height: number - width: number - color: string - word: string - charSize: number; - velocity: Vector2D; - position: Vector2D = new Vector2D(0, 0); - rotationMomentum: number = 0; - - constructor(word: string, charSize: number, color: string) { - this.height = charSize + 8; - this.width = (charSize * word.length * 0.6) + 16; - this.color = color - this.word = word; - this.charSize = charSize; - this.velocity = new Vector2D(0, 0); - } - - draw(ctx: CanvasRenderingContext2D) { - this.position.round(); - ctx.beginPath(); - // ctx.fillRect(this.position.x, this.position.y, this.width, this.height); - ctx.roundRect(this.position.x, this.position.y, this.width, this.height, 12); - ctx.fillStyle = this.color; - ctx.fill(); - ctx.closePath(); - - ctx.beginPath(); - ctx.font = `bold ${this.charSize}px 'JetBrainsMono'`; - ctx.fillStyle = "white"; - ctx.fillText(this.word, this.position.x + 4, this.position.y + 8, this.width); - ctx.fill(); - ctx.closePath(); - } - - setRandomPosition(bounds: Vector2D) { - this.position.x = Math.floor((Math.random() * (bounds.x - this.width))); - this.position.y = Math.floor((Math.random() * (bounds.y - this.height))); - } - - getRect(): Rect { - return { - x1: this.position.x, - x2: this.position.x + this.width, - y1: this.position.y, - y2: this.position.y + this.height, - } - } -} - diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index af75fac2..551eb7ba 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,16 +1,16 @@ @@ -40,30 +38,30 @@
    -
    -
    - +
    +
    +
    -
    - +
    +
    -
    +
    -
    +
    -
    - +
    +
    -
    +
    diff --git a/src/routes/Footer.svelte b/src/routes/Footer.svelte index f9b53929..b10dd122 100644 --- a/src/routes/Footer.svelte +++ b/src/routes/Footer.svelte @@ -1,45 +1,45 @@
    -
    -
      -
    • -
    • -
    • -
    • -
    - -
    +
    +
      +
    • +
    • +
    • +
    • +
    + +
    \ No newline at end of file + .copyright { + font-weight: lighter; + text-align: center; + } + diff --git a/src/routes/[slug]/+page.svelte b/src/routes/[slug]/+page.svelte index 1a0c5be3..2686b565 100644 --- a/src/routes/[slug]/+page.svelte +++ b/src/routes/[slug]/+page.svelte @@ -1,63 +1,65 @@ - {data.meta.title} - - + {data.meta.title} + +
    - -
    -

    {data.meta.title}

    -

    Published {date}

    - {#if project} - - {/if} -
    + +
    +

    {data.meta.title}

    +

    Published {date}

    + {#if project} + + {/if} +
    -
    - {#if data.meta.projectId == "programming"} -
    - { - {#each data.meta.technologies as tech, i} - {tech}{#if i < (data.meta.technologies.length - 1)}, {/if} - {/each} - } -
    -

    - {data.meta.description} -

    - {/if} - -
    +
    + {#if data.meta.projectId == "programming"} +
    + { + {#each data.meta.technologies as tech, i} + {tech}{#if i < data.meta.technologies.length - 1}, {/if} + {/each} + } +
    +

    + {data.meta.description} +

    + {/if} + +
    \ No newline at end of file + .description { + font-style: italic; + padding: 0.5em 2em 2em; + margin: 0; + } + diff --git a/src/routes/projects/[projectId]/+page.svelte b/src/routes/projects/[projectId]/+page.svelte index 9e3ab14b..a8d6cd9a 100644 --- a/src/routes/projects/[projectId]/+page.svelte +++ b/src/routes/projects/[projectId]/+page.svelte @@ -1,70 +1,69 @@ -

    {data.project?.name}

    - + -
    +
    {#if path.split("/projects/")[1] === "miniatures"} - + {/if}
    - {data.project?.description ?? ""} + {data.project?.description ?? ""}
    {#if path.split("/projects/")[1] === "programming"} - {#if data.ghData} - - {/if} + {#if data.ghData} + + {/if} {/if}
    -
    - {#each sortedPosts as post} - - {/each} -
    +
    + {#each sortedPosts as post} + + {/each} +
    {#if path.split("/projects/")[1] === "dnd"} - + {/if}
    - {data.project?.bonus ?? ""} + {data.project?.bonus ?? ""}
    diff --git a/src/routes/weather/+page.svelte b/src/routes/weather/+page.svelte index 7a210190..d2d46362 100644 --- a/src/routes/weather/+page.svelte +++ b/src/routes/weather/+page.svelte @@ -1,34 +1,37 @@ - -
    - +
    +
    -
    - -
    -
    -
    Time:
    -
    {time.toLocaleTimeString()}
    -
    Temperature:
    -
    {data.temperature} °C
    -
    Weather:
    -
    {weather}
    -
    Wind speed:
    -
    {data.windspeed} km/h
    -
    Wind direction:
    -
    {windDirection}
    +
    + +
    + +
    +
    +
    Time:
    +
    {time.toLocaleTimeString()}
    +
    Temperature:
    +
    {data.temperature} °C
    +
    Weather:
    +
    {weather}
    +
    Wind speed:
    +
    {data.windspeed} km/h
    +
    Wind direction:
    +
    {windDirection}
    +
    + diff --git a/src/routes/weather/draw/bluebody.ts b/src/routes/weather/draw/bluebody.ts deleted file mode 100644 index 45710b82..00000000 --- a/src/routes/weather/draw/bluebody.ts +++ /dev/null @@ -1,74 +0,0 @@ -export class Bluebody { - - radialGradient: CanvasGradient; - ctx: CanvasRenderingContext2D; - orbitPosX: number; - orbitPosY: number; - orbitBodyRadius: number; - - constructor( - context: CanvasRenderingContext2D, - time: Date, - orbitCenX: number, - orbitCenY: number, - orbitRadius: number, - orbitBodyRadius: number) { - - // ctx.beginPath(); - // ctx.arc( - // orbitCentreX, - // orbitCentreY, - // orbitRadius, - // Math.PI, Math.PI * 2, false); - // ctx.stroke(); - const hours = time.getHours(); - const moduloTime = hours + 6 % 24; - const orbitAngle = Math.PI - (((2 * Math.PI / 24) * moduloTime) % Math.PI) - (Math.PI / 2); - const orbitLenX = Math.sin(orbitAngle) * orbitRadius; - const orbitLenY = Math.cos(orbitAngle) * orbitRadius; - this.orbitPosX = orbitCenX - orbitLenX; - this.orbitPosY = orbitCenY - orbitLenY; - this.orbitBodyRadius = orbitBodyRadius; - this.ctx = context; - // sun or moon - if (hours >= 6 && hours < 18) { - this.radialGradient = this.getSunRadialGradient(context, this.orbitPosX, this.orbitPosY, orbitBodyRadius); - } else { - this.radialGradient = this.getMoonRadialGradient(context, this.orbitPosX, this.orbitPosY, orbitBodyRadius); - } - - // context.fillRect(orbitPositionX, orbitPositionY, orbitBodyRadius * 2, orbitBodyRadius * 2); - } - - animate = () => { - this.draw(); - } - - draw = () => { - this.ctx.fillStyle = this.radialGradient; - this.ctx.arc( - this.orbitPosX, - this.orbitPosY, - this.orbitBodyRadius, - 0, 2*Math.PI, false - ); - this.ctx.fill(); - } - - - getSunRadialGradient(context: CanvasRenderingContext2D, x: number, y: number, r: number) { - const radialGradient = context.createRadialGradient(x, y, r * 0.8, x, y, r); - radialGradient.addColorStop(0, 'rgba(255, 240, 210, 1)'); - radialGradient.addColorStop(1,'rgba(255, 255, 0, 0)'); - return radialGradient; - } - - getMoonRadialGradient(context: CanvasRenderingContext2D, x: number, y: number, r: number) { - const radialGradient = context.createRadialGradient(x, y, (r * 0.5), x, y, r * 0.8); - radialGradient.addColorStop(0, 'rgba(255, 255, 255, 1)'); - radialGradient.addColorStop(1,'rgba(50, 50, 50, 0)'); - return radialGradient; - } - - -} diff --git a/src/routes/weather/draw/character.ts b/src/routes/weather/draw/character.ts deleted file mode 100644 index c89cc6b2..00000000 --- a/src/routes/weather/draw/character.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { Artist } from "../../../Artist"; - -const AMBER = "rgb(245, 167, 66)"; -// const DARK_ORANGE = "rgb(200, 100, 100)"; -const WHITE = "white"; -const BLACK = "black"; -// const GREY = "rgb(50, 50, 50)"; -const ORANGE_BROWN = "rgb(220, 150, 110)"; -const SHADOW_BROWN = "rgb(120, 80, 20)"; -const DARK_SHADOW_BROWN = "rgb(71, 50, 44)"; -const DARK_GREY = "rgb(50, 50, 50)"; -const PINK = "rgb(220, 100, 150)"; -const DARK_PINK = "rgb(150, 50, 100)"; - - -export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: number) { - - const a = new Artist(context, x, y); - - const HEAD_POINTS: Points = [ - // top - [15, 0], - // ear - [15, -5], - [20, 0], - [10, 10], - // side - [5, 20], - [0, 10], - [-10, 40], - [-25, 50], - // chin - [-10, 10], - [-10, 5], - [-10, 0] - ] - - const HEAD_PATCH_POINTS: Points = [ - [10, 0], - [15, -3], - [20, 0], - [10, 35], - [-20, -15], - [-15, -8], - [-20, 0] - ] - - const RIGHT_EAR_POINTS: Points = [ - // top arc - [5, -6], - [4, -2], - [5, -3], - [15, -5], - // down right - [35, 18], - [2, 10], - [-5, 20], - // up left - [-15, 30], - [-5, 5], - [-3, 0], - [-10, -5], - [-5, -10], - [-5, -10], - [5, -20], - [-5, -20] - ] - - const LEFT_EAR_POINTS: Points = [ - // top arc - [-5, -10], - [-15, -5], - [-12, 9], - // down right - [-20, 3], - [-10, 5], - [-5, 5], - [15, 35], - // up left - [10, 20], - [10, 5], - [12, -20], - [0, -25], - [5, -10], - ] - - const NOSE: Points = [ - [5, 0], - [6, 5], - [2, 9], - [-2, 4], - [-4, 2], - [-3, 0], - [-3, -1] - ] - - const TONGUE: Points = [ - [10, 4], - [3, -2], - [-1, 10], - [-4, 5], - [-4, 3], - [-4, 0] - ] - - a.drawShape(HEAD_POINTS, true, 0, 0, 2, ORANGE_BROWN, ORANGE_BROWN); - - a.reset(0, 10); - context.beginPath(); - a.drawNextLine(0, 35); - a.endX = a.startX + 20; - a.endY = a.startY + 35; - context.bezierCurveTo( - a.startX, a.startY, - a.startX, a.startY + 15, - a.endX, a.endY); - context.lineCap = 'round'; - context.lineWidth = 4; - context.strokeStyle = SHADOW_BROWN; - context.stroke(); - context.closePath(); - a.reset(0, 45); - context.beginPath(); - a.endX = a.startX - 20; - a.endY = a.startY + 35; - context.bezierCurveTo( - a.startX, a.startY, - a.startX, a.startY + 15, - a.endX, a.endY); - context.lineCap = 'round'; - context.lineWidth = 4; - context.strokeStyle = SHADOW_BROWN; - context.stroke(); - context.closePath(); - - a.reset(0, 3); - a.drawShape(HEAD_PATCH_POINTS, true, 0, 3, 2, DARK_SHADOW_BROWN, DARK_SHADOW_BROWN); - a.reset(25, -2); - a.drawShape(RIGHT_EAR_POINTS, false, 0, 0, 2, SHADOW_BROWN, SHADOW_BROWN); - a.reset(-25, 0); - a.drawShape(LEFT_EAR_POINTS, false, 0, 0, 2, SHADOW_BROWN, SHADOW_BROWN); - a.reset(0, 80); - a.drawShape(NOSE, true, 0, 80, 2, DARK_GREY, DARK_GREY); - drawEyes(context, x, y); - a.reset(0, 108); - a.drawShape(TONGUE, true, 0, 108, 2, DARK_PINK, PINK); - a.reset(45, 80); - drawFaceLines(context, a.startX, a.startY); -} - -function drawFaceLines(context: CanvasRenderingContext2D, x: number, y: number) { - context.beginPath(); - let startX = x; - let startY = y; - let endX = startX - 5; - let endY = startY + 5; - context.bezierCurveTo( - startX, startY, - startX -3, startY -3, - endX, endY); - - startX = endX; - startY = endY; - endX = startX - 25; - endY = startY + 25; - context.bezierCurveTo( - startX, startY, - startX, startY + 10, - endX, endY); - - startX = endX; - startY = endY; - endX = startX - 14; - endY = startY - 5; - context.bezierCurveTo( - startX, startY, - startX - 10, startY + 2, - endX, endY); - - startX = endX; - startY = endY; - endX = startX - 14; - endY = startY + 5; - context.bezierCurveTo( - startX, startY, - startX - 4, startY + 7, - endX, endY); - - startX = endX; - startY = endY; - endX = startX - 25; - endY = startY - 25; - context.bezierCurveTo( - startX, startY, - startX - 25, startY - 15, - endX, endY); - - startX = endX; - startY = endY; - endX = startX - 5; - endY = startY - 5; - context.bezierCurveTo( - startX, startY, - startX -2, startY -7, - endX, endY); - - context.lineCap = 'round'; - context.lineWidth = 4; - context.strokeStyle = SHADOW_BROWN; - context.stroke(); - context.closePath(); -} - -function drawEyes(context: CanvasRenderingContext2D, x: number, y: number) { - const eyeSize = 17; - const eyePosX = 25; - const eyeTopPosY = 50; - const eyeBottomPosy = 34; - - // Right eye - context.beginPath(); - context.arc(x + eyePosX, y + eyeTopPosY, eyeSize, Math.PI * 1.2, Math.PI * 1.9); - context.fillStyle = DARK_GREY; - context.fill(); - context.closePath(); - context.beginPath(); - context.arc(x + eyePosX + 2, y + eyeBottomPosy, eyeSize, Math.PI * 2.2, Math.PI * 0.9); - context.fillStyle = DARK_GREY; - context.fill(); - - // Right pupil - context.beginPath(); - context.arc(x + eyePosX + 2, y + eyeTopPosY - 10, 7, Math.PI * 2, Math.PI * 1.1); - context.fillStyle = AMBER; - context.fill(); - - context.beginPath(); - context.arc(x + eyePosX + 2, y + eyeTopPosY - 8, 3, 0, Math.PI * 2); - context.fillStyle = BLACK; - context.fill(); - - context.beginPath(); - context.arc(x + eyePosX + 1, y + eyeTopPosY - 9, 1, 0, Math.PI * 2); - context.fillStyle = WHITE; - context.fill(); - - // Left eye - context.beginPath(); - context.arc(x - eyePosX, y + eyeTopPosY, eyeSize, Math.PI * 1.1, Math.PI * 1.8); - context.fillStyle = DARK_GREY; - context.fill(); - context.closePath(); - context.beginPath(); - context.arc(x - eyePosX - 2, y + eyeBottomPosy, eyeSize, Math.PI * 2.1, Math.PI * 0.8); - context.fillStyle = DARK_GREY; - context.fill(); - - // Left pupil - context.beginPath(); - context.arc(x - eyePosX - 2, y + eyeTopPosY - 10, 7, Math.PI * 1.9, Math.PI * 1); - context.fillStyle = AMBER; - context.fill(); - - context.beginPath(); - context.arc(x - eyePosX - 2, y + eyeTopPosY - 8, 3, 0, Math.PI * 2); - context.fillStyle = BLACK; - context.fill(); - - context.beginPath(); - context.arc(x - eyePosX - 3, y + eyeTopPosY - 9, 1, 0, Math.PI * 2); - context.fillStyle = WHITE; - context.fill(); -} diff --git a/src/routes/weather/draw/cloud.ts b/src/routes/weather/draw/cloud.ts deleted file mode 100644 index 90365743..00000000 --- a/src/routes/weather/draw/cloud.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Vector2D } from "$lib/mechanics/vector"; -import { randBetween } from "$lib/utils"; - -const TWO_PI = Math.PI * 2; -const ORB_SIZE_VARIANCE = [0.8, 1.2]; -const ORB_X_SPREAD_FACTOR = [-2, 2]; -const ORB_Y_SPREAD_FACTOR = [-0.5, 1]; - -export class Cloud { - ctx: CanvasRenderingContext2D; - position: Vector2D; - speed: number; - orbs: Orb[] = []; - width: number; - radius: number; - size: number; - color: string; - - constructor( - context: CanvasRenderingContext2D, - x: number, - y: number, - radius: number, - size: number, - speed: number, - color: string, - ) { - this.position = new Vector2D(x, y); - this.speed = speed; - this.size = size; - this.ctx = context; - this.width = 0; - this.color = color; - this.radius = radius; - } - - animate = () => { - this.position.x += this.speed; - // regenerate once cloud has left screen - if (this.position.x > this.ctx.canvas.width + this.width) { - this.generate(); - this.position.x = -this.width; - // vary height, keep within top half of canvas - this.position.y += randBetween(-this.width, this.width); - this.position.y = Math.max(0, this.position.y); - this.position.y = Math.min(this.ctx.canvas.height / 2, this.position.y); - } - this.draw(); - }; - - draw = () => { - this.orbs.forEach((orb) => orb.draw(this.ctx, this.position.x, this.position.y)); - }; - - generate = () => { - let smallestX = 0; - let largestX = 0; - - this.orbs.splice(0, this.orbs.length); - - for (let i = 0; i < this.size; i++) { - const orbRadius = randBetween( - this.radius * ORB_SIZE_VARIANCE[0], - this.radius * ORB_SIZE_VARIANCE[1], - ); - const orbX = randBetween( - ORB_X_SPREAD_FACTOR[0] * this.radius, - ORB_X_SPREAD_FACTOR[1] * this.radius, - ); - const orbY = randBetween( - ORB_Y_SPREAD_FACTOR[0] * this.radius, - ORB_Y_SPREAD_FACTOR[1] * this.radius, - ); - if (orbX < smallestX) { - smallestX = orbX; - } - if (orbX > largestX) { - largestX = orbX; - } - this.orbs.push(new Orb(orbX, orbY, orbRadius, this.color)); - } - - this.width = largestX - smallestX; - }; -} - -class Orb { - pos: Vector2D; - color: string; - radius: number; - - constructor(x: number, y: number, radius: number, color: string) { - this.color = color; - this.radius = radius; - this.pos = new Vector2D(x, y); - } - - draw = (ctx: CanvasRenderingContext2D, x: number, y: number) => { - const absX = x + this.pos.x; - const absY = y + this.pos.y; - - const radialGradient = ctx.createRadialGradient(absX, absY, 0, absX, absY, this.radius); - radialGradient.addColorStop(0, this.color); - radialGradient.addColorStop(1, "rgba(255, 255, 255, 0)"); - ctx.beginPath(); - ctx.fillStyle = radialGradient; - ctx.arc(absX, absY, this.radius, 0, TWO_PI, true); - ctx.fill(); - ctx.closePath(); - }; -} diff --git a/src/routes/weather/draw/precipitater.ts b/src/routes/weather/draw/precipitater.ts deleted file mode 100644 index 6c451dd9..00000000 --- a/src/routes/weather/draw/precipitater.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Vector2D } from "$lib/mechanics/vector"; - -export abstract class Precipitator { - ctx: CanvasRenderingContext2D; - canvasWidth: number; - canvasHeight: number; - drops: Drop[] = []; - dropSpeed: number; - windSpeed: number; - size: number; - - constructor( - context: CanvasRenderingContext2D, - speed: number, - density: number, - windSpeed: number, - size: number, - ) { - this.ctx = context; - this.canvasWidth = context.canvas.width; - this.canvasHeight = context.canvas.height; - this.dropSpeed = speed; - this.size = size; - this.windSpeed = windSpeed; - const dropSpacing = this.size / density; - const columns = Math.floor(this.canvasWidth / (this.size + dropSpacing)); - - for (let i = 0; i < columns; i++) { - this.drops.push( - this.generateDrop(i * (dropSpacing + this.size), Math.random() * this.canvasHeight), - ); - } - } - - abstract generateDrop(x: number, y: number): Drop; - - animate = () => { - this.drops.forEach((drop) => { - drop.position.y += this.dropSpeed; - drop.position.x += this.windSpeed / 10; - - if (drop.position.y > this.canvasHeight) { - // vary starting position for variety of y position - drop.position.y = 0 - Math.random() * this.canvasHeight; - // shift drop position left or right up to drop width - drop.position.x = drop.position.x + (Math.random() * 2 - 1.0) * 3 * this.size; - } - if (drop.position.x > this.canvasWidth) { - drop.position.x = 0; - } - drop.draw(this.ctx); - }); - }; -} - -export abstract class Drop { - position: Vector2D; - size: number; - - constructor(size: number, initPos: Vector2D) { - this.position = initPos; - this.size = size; - } - - abstract draw(context: CanvasRenderingContext2D): void; -} diff --git a/src/routes/weather/draw/rain.ts b/src/routes/weather/draw/rain.ts deleted file mode 100644 index 59641d6c..00000000 --- a/src/routes/weather/draw/rain.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Vector2D } from "$lib/mechanics/vector"; -import { Drop, Precipitator } from "./precipitater"; - -const RAIN_COLOR = "rgb(90, 140, 210)"; -const DROP_SIZE = 10; - -export class Rain extends Precipitator { - constructor( - context: CanvasRenderingContext2D, - speed: number, - density: number, - windSpeed: number, - size: number = DROP_SIZE, - ) { - super(context, speed, density, windSpeed, size); - } - - generateDrop(x: number, y: number): Drop { - return new RainDrop(this.size, new Vector2D(x, y)); - } -} - -class RainDrop extends Drop { - height: number; - - constructor(size: number, initPos: Vector2D) { - super(size, initPos); - this.height = size * 2.5; - } - - draw(context: CanvasRenderingContext2D) { - context.beginPath(); - context.fillStyle = RAIN_COLOR; - context.moveTo(this.position.x, this.position.y); - context.lineTo(this.position.x - this.size, this.position.y + this.height); - context.lineTo(this.position.x + this.size, this.position.y + this.height); - context.lineTo(this.position.x, this.position.y); - context.fill(); - context.arc( - this.position.x, - this.position.y + this.height, - this.size, - 1.9 * Math.PI, - 1.1 * Math.PI, - ); - context.fill(); - context.closePath(); - } -} diff --git a/src/routes/weather/draw/snow.ts b/src/routes/weather/draw/snow.ts deleted file mode 100644 index d8b5c747..00000000 --- a/src/routes/weather/draw/snow.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Vector2D } from "$lib/mechanics/vector"; -import { Drop, Precipitator } from "./precipitater"; - -const SNOW_COLOR = "rgb(180, 190, 255)"; -// const FLAKE_SIZE = 40; -// const FLAKE_BRANCH_LENGTH = FLAKE_SIZE / 2; - -export class Snow extends Precipitator { - constructor( - context: CanvasRenderingContext2D, - speed: number, - density: number, - windSpeed: number, - size: number, - ) { - super(context, speed, density, windSpeed, size); - } - - generateDrop(x: number, y: number): Drop { - return new SnowFlake(this.size, new Vector2D(x, y)); - } - - draw = () => { - this.drops.forEach((drop) => drop.draw(this.ctx)); - }; -} - -class SnowFlake extends Drop { - constructor(size: number, initPos: Vector2D) { - super(size, initPos); - } - - draw(context: CanvasRenderingContext2D) { - context.beginPath(); - context.strokeStyle = SNOW_COLOR; - context.lineCap = "round"; - context.lineWidth = this.size / 20; - - this.drawBranch(context, this.size / 2, Math.PI / 6); - this.drawBranch(context, this.size / 2, Math.PI / 2); - this.drawBranch(context, this.size / 2, (5 * Math.PI) / 6); - this.drawBranch(context, this.size / 2, (7 * Math.PI) / 6); - this.drawBranch(context, this.size / 2, (3 * Math.PI) / 2); - this.drawBranch(context, this.size / 2, (11 * Math.PI) / 6); - - context.stroke(); - context.closePath(); - } - - drawBranch = (context: CanvasRenderingContext2D, radius: number, angle: number) => { - const offsetX = radius * Math.cos(angle); - const offsetY = radius * Math.sin(angle); - - // draw main branch - context.moveTo(this.position.x, this.position.y); - context.lineTo(this.position.x - offsetX, this.position.y - offsetY); - - const r2 = radius * 0.5; // length from origin to start of sub branch - const r3 = radius * 1.1; // length from - const subBranchAngle = Math.PI / 6; - const r4 = (r2 + r3) * Math.tan(subBranchAngle); - - const x2 = this.position.x - r2 * Math.cos(angle); - const y2 = this.position.y - r2 * Math.sin(angle); - - const x3 = this.position.x - r4 * Math.cos(subBranchAngle + angle); - const y3 = this.position.y - r4 * Math.sin(subBranchAngle + angle); - - const x4 = this.position.x - r4 * Math.cos(angle - subBranchAngle); - const y4 = this.position.y - r4 * Math.sin(angle - subBranchAngle); - - context.moveTo(x2, y2); - context.lineTo(x3, y3); - context.moveTo(x2, y2); - context.lineTo(x4, y4); - }; -} diff --git a/src/routes/weather/draw/thunder.ts b/src/routes/weather/draw/thunder.ts deleted file mode 100644 index 92be4c7e..00000000 --- a/src/routes/weather/draw/thunder.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { randIntBetween } from "$lib/utils"; - -export class Thunder { - - ctx: CanvasRenderingContext2D; - frequency: number; - width: number; - height: number; - - countTo: number = 20; - count: number = 0; - flashCount: number = 0; - flashes: number = 2; - - - constructor(context: CanvasRenderingContext2D, canvasHeight: number, canvasWidth: number, frequency: number){ - this.ctx = context; - this.frequency = frequency; - this.width = canvasWidth; - this.height = canvasHeight; - this.resetCounts(); - } - - animate() { - if (this.count >= this.countTo) { - if (this.flashCount <= this.flashes) { - if (this.flashCount % 8 == 0) { - this.drawFlash(); - } else if (this.flashCount % 4 == 0) { - this.drawBlank(); - } - this.flashCount++; - } else { - this.drawBlank(); - this.resetCounts(); - } - } else { - this.count++; - } - } - - resetCounts() { - this.count = 0; - this.flashCount = 0; - this.flashes = randIntBetween(4, 20); - this.countTo = randIntBetween(80, 400); - } - - drawFlash() { - this.ctx.fillStyle = "black"; - this.ctx.fillRect(0, 0,this.width, this.height); - } - drawBlank() { - this.ctx.fillStyle = "none"; - this.ctx.fillRect(0, 0,this.width, this.height); - } -} \ No newline at end of file diff --git a/src/routes/weather/draw/tree.ts b/src/routes/weather/draw/tree.ts deleted file mode 100644 index f5166a0e..00000000 --- a/src/routes/weather/draw/tree.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { randRangeRGBString } from "$lib/utils"; - -// inspired by -// https://github.com/PavlyukVadim/amadev/blob/master/RecursiveTree/script.js -export function drawTree( - context: CanvasRenderingContext2D, - startX: number, - startY: number, - length: number, - angle: number, - depth: number, - branchWidth: number, -) { - let newLength, newAngle; - const rand = Math.random; - const maxAngle = (2 * Math.PI) / 6; - const maxBranch = 3; - const endX = startX + length * Math.cos(angle); - const endY = startY + length * Math.sin(angle); - - context.beginPath(); - context.moveTo(startX, startY); - context.lineCap = "round"; - context.lineWidth = branchWidth; - context.lineTo(endX, endY); - - if (depth <= 4) { - context.strokeStyle = randRangeRGBString(30, [64, 180], 0); - } else { - context.strokeStyle = randRangeRGBString([70, 80], 60, [30, 50]); - } - - context.stroke(); - const newDepth = depth - 1; - - if (!newDepth) { - return; - } - const subBranches = rand() * (maxBranch - 1) + 1; - branchWidth *= 0.7; - - for (let i = 0; i < subBranches; i++) { - newAngle = angle + rand() * maxAngle - maxAngle * 0.5; - newLength = length * (0.7 + rand() * 0.3); - drawTree(context, endX, endY, newLength, newAngle, newDepth, branchWidth); - } -} diff --git a/src/routes/weather/weatherTime.ts b/src/routes/weather/weatherTime.ts deleted file mode 100644 index 774ead84..00000000 --- a/src/routes/weather/weatherTime.ts +++ /dev/null @@ -1,48 +0,0 @@ -export function timeNoun(time: Date): string { - const hour = time.getHours(); - return hourNoun(hour); -} - -export function hourNoun(hour: number): string { - switch (hour) { - case 6: - return "dawn"; - case 7: - return "sunrise"; - case 8: - return "morning"; - case 9: - case 10: - case 11: - case 12: - case 13: - case 14: - case 15: - case 16: - case 17: - case 18: - return "day"; - case 19: - return "evening"; - case 20: - return "sunset"; - case 21: - return "dusk"; - case 22: - case 23: - case 24: - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - return "night"; - default: - return "day"; - } -} - -export function timeAsTwelvethFraction(time: Date): number { - return time.getHours() + (6 % 12); -} diff --git a/src/store.ts b/src/store.ts index d6d2e249..c3314e97 100644 --- a/src/store.ts +++ b/src/store.ts @@ -7,35 +7,35 @@ const defaultDarkTheme = true; const defaultColourTheme = "gold"; function getDarkTheme(): boolean { - return getLocalStorageDarkTheme() ?? defaultDarkTheme; + return getLocalStorageDarkTheme() ?? defaultDarkTheme; } function getColourTheme(): string { - return getLocalStorageColourTheme() ?? defaultColourTheme; + return getLocalStorageColourTheme() ?? defaultColourTheme; } export const isUsingDarkTheme: Writable = writable(defaultDarkTheme); export const currentColourTheme: Writable = writable(defaultColourTheme); export function setUpThemes() { - isUsingDarkTheme.set(getDarkTheme()); - currentColourTheme.set(getColourTheme()); + isUsingDarkTheme.set(getDarkTheme()); + currentColourTheme.set(getColourTheme()); - isUsingDarkTheme.subscribe((value: boolean) => { - if (browser) { - setLocalStorageDarkTheme(value); - } - applyDarkTheme(value); - }); - currentColourTheme.subscribe((theme: string) => { - if (browser) { - setLocalStorageColourTheme(theme); - } - applyColourTheme(theme); - }); + isUsingDarkTheme.subscribe((value: boolean) => { + if (browser) { + setLocalStorageDarkTheme(value); + } + applyDarkTheme(value); + }); + currentColourTheme.subscribe((theme: string) => { + if (browser) { + setLocalStorageColourTheme(theme); + } + applyColourTheme(theme); + }); } export function resetThemes() { - isUsingDarkTheme.set(defaultDarkTheme); - currentColourTheme.set(defaultColourTheme); -} \ No newline at end of file + isUsingDarkTheme.set(defaultDarkTheme); + currentColourTheme.set(defaultColourTheme); +} From 5055f99f0e74540b4c8429772656d0b0d6ca2c2d Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 2 Mar 2025 01:46:18 -0800 Subject: [PATCH 27/36] add set time to bluebody --- src/lib/canvas/weather/animate/Bluebody.ts | 36 ++++++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/lib/canvas/weather/animate/Bluebody.ts b/src/lib/canvas/weather/animate/Bluebody.ts index fd136160..fd9fd219 100644 --- a/src/lib/canvas/weather/animate/Bluebody.ts +++ b/src/lib/canvas/weather/animate/Bluebody.ts @@ -4,6 +4,9 @@ export class Bluebody extends Animator { radialGradient: CanvasGradient; orbitPosX: number; orbitPosY: number; + orbitCenX: number; + orbitCenY: number; + orbitRadius: number; orbitBodyRadius: number; constructor( @@ -22,23 +25,36 @@ export class Bluebody extends Animator { // orbitRadius, // Math.PI, Math.PI * 2, false); // ctx.stroke(); - const hours = time.getHours(); - const moduloTime = hours + (6 % 24); - const orbitAngle = Math.PI - ((((2 * Math.PI) / 24) * moduloTime) % Math.PI) - Math.PI / 2; - const orbitLenX = Math.sin(orbitAngle) * orbitRadius; - const orbitLenY = Math.cos(orbitAngle) * orbitRadius; - this.orbitPosX = orbitCenX - orbitLenX; - this.orbitPosY = orbitCenY - orbitLenY; + this.orbitPosX = 0; + this.orbitPosY = 0; + this.orbitCenY = orbitCenY; + this.orbitCenX = orbitCenX; + + this.orbitRadius = orbitRadius; this.orbitBodyRadius = orbitBodyRadius; + this.updateTime(time); + this.radialGradient = context.createRadialGradient(0, 0, 0, 0, 0, 0); this.ctx = context; // sun or moon + + // context.fillRect(orbitPositionX, orbitPositionY, orbitBodyRadius * 2, orbitBodyRadius * 2); + } + + updateTime(time: Date) { + const hours = time.getHours(); + const moduloTime = hours + (6 % 24); + const orbitAngle = Math.PI - ((((2 * Math.PI) / 24) * moduloTime) % Math.PI) - Math.PI / 2; + const orbitLenX = Math.sin(orbitAngle) * this.orbitRadius; + const orbitLenY = Math.cos(orbitAngle) * this.orbitRadius; + this.orbitPosX = this.orbitCenX - orbitLenX; + this.orbitPosY = this.orbitCenY - orbitLenY; if (hours >= 6 && hours < 18) { - this.radialGradient = this.getSunRadialGradient(context, this.orbitPosX, this.orbitPosY, orbitBodyRadius); + this.radialGradient = this.getSunRadialGradient(this.ctx, this.orbitPosX, this.orbitPosY, this.orbitBodyRadius); } else { - this.radialGradient = this.getMoonRadialGradient(context, this.orbitPosX, this.orbitPosY, orbitBodyRadius); + this.radialGradient = this.getMoonRadialGradient(this.ctx, this.orbitPosX, this.orbitPosY, this.orbitBodyRadius); } + this.draw(); - // context.fillRect(orbitPositionX, orbitPositionY, orbitBodyRadius * 2, orbitBodyRadius * 2); } animate = () => { From 9fd4fb6cc05e4a053517d64749916e860cf051bb Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 2 Mar 2025 01:46:30 -0800 Subject: [PATCH 28/36] fix thunder flash color --- src/lib/canvas/weather/animate/Thunder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/canvas/weather/animate/Thunder.ts b/src/lib/canvas/weather/animate/Thunder.ts index 714e1dbf..dcebde4b 100644 --- a/src/lib/canvas/weather/animate/Thunder.ts +++ b/src/lib/canvas/weather/animate/Thunder.ts @@ -45,7 +45,7 @@ export class Thunder extends Animator { } drawFlash() { - this.ctx.fillStyle = "black"; + this.ctx.fillStyle = "white"; this.ctx.fillRect(0, 0, this.width, this.height); } drawBlank() { From c3dda6c9848562eaeca191558f9eea9f9f5e8c39 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 2 Mar 2025 01:46:58 -0800 Subject: [PATCH 29/36] craete framerate measure class --- src/lib/canvas/weather/animate/utils.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/lib/canvas/weather/animate/utils.ts diff --git a/src/lib/canvas/weather/animate/utils.ts b/src/lib/canvas/weather/animate/utils.ts new file mode 100644 index 00000000..6d1bc116 --- /dev/null +++ b/src/lib/canvas/weather/animate/utils.ts @@ -0,0 +1,22 @@ +export class FrameRate { + + startTime: number = Date.now(); + totalElapsedTime: number = 0; + frames: number = 0; + frameRate: number = 0; + + calculateFrameRate() { + this.frames += 1; + this.totalElapsedTime = Date.now() - this.startTime; + this.frameRate = this.frames / this.totalElapsedTime * 1000; + } + + getFrameRate() { + return this.frameRate; + } + + getElapsedTime() { + return this.totalElapsedTime; + } + +} \ No newline at end of file From 3aed01923b14ca0c3509823239a6e69230bf16b4 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 2 Mar 2025 01:47:17 -0800 Subject: [PATCH 30/36] use framerate and timeset --- src/routes/weather/WeatherSceneAnimator.ts | 29 ++++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/routes/weather/WeatherSceneAnimator.ts b/src/routes/weather/WeatherSceneAnimator.ts index ffa5a166..8f2c74c5 100644 --- a/src/routes/weather/WeatherSceneAnimator.ts +++ b/src/routes/weather/WeatherSceneAnimator.ts @@ -3,6 +3,7 @@ import { Cloud } from "$lib/canvas/weather/animate/Cloud"; import { Rain } from "$lib/canvas/weather/animate/Rain"; import { Snow } from "$lib/canvas/weather/animate/Snow"; import { Thunder } from "$lib/canvas/weather/animate/Thunder"; +import { FrameRate } from "$lib/canvas/weather/animate/utils"; import { randIntBetween } from "$lib/utils"; import { Weather } from "../../lib/data/weatherData"; @@ -12,21 +13,24 @@ export class WeatherSceneController extends Animator { animators: Animator[]; windspeed: number; currentWeather: Weather; + frameRate: FrameRate; constructor(context: CanvasRenderingContext2D, canvasWidth: number, canvasHeight: number, windspeed: number, weather: Weather) { super(context); this.animators = []; this.height = canvasHeight; this.width = canvasWidth; - this.windspeed = windspeed / 2; + this.windspeed = windspeed; this.currentWeather = weather; + this.frameRate = new FrameRate(); this.setWeather(weather); } animate(): void { this.ctx.clearRect(0, 0, this.width, this.height); this.animators.forEach((a) => a.animate()); + this.frameRate.calculateFrameRate(); } clearAnimators() { @@ -47,7 +51,7 @@ export class WeatherSceneController extends Animator { this.setClear(); break; case Weather.Cloudy: - this.setCloudy(); + this.setCloudy(15, this.windspeed / 2); break; case Weather.Overcast: this.setOvercast(); @@ -87,30 +91,33 @@ export class WeatherSceneController extends Animator { return; } - setCloudy() { - for (let i = 0; i < 10; i++) { + setCloudy(count: number = 10, speed: number = this.windspeed) { + for (let i = 0; i < count; i++) { + const gray = randIntBetween(100, 170) this.animators.push( new Cloud( this.ctx, randIntBetween(0, this.width - 100), - randIntBetween(0, this.height / 2), - randIntBetween(20, 40), - randIntBetween(7, 15), - this.windspeed, - "gray", + randIntBetween(0, this.height / 4), + randIntBetween(20, 60), + randIntBetween(10, 30), + speed, + `rgb(${gray}, ${gray}, ${gray})`, ), ); } } - setOvercast() {} + setOvercast() { + this.setCloudy(30, this.windspeed / 10); + } setRain() { const rainFg = new Rain(this.ctx, 20, 0.3, this.windspeed, 10); const rainBg = new Rain(this.ctx, 10, 0.8, this.windspeed, 5); this.animators.push(rainFg); - this.animators.push(rainBg); + // this.animators.push(rainBg); } setDrizzle() { From 7d1244a0e9150de7c0f96e7af3801f438fd11a96 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 2 Mar 2025 01:47:29 -0800 Subject: [PATCH 31/36] capture framerate --- src/routes/weather/Scene.svelte | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/routes/weather/Scene.svelte b/src/routes/weather/Scene.svelte index 8877fd9d..a8d98c61 100644 --- a/src/routes/weather/Scene.svelte +++ b/src/routes/weather/Scene.svelte @@ -10,11 +10,14 @@ export let weather: Weather; export let windspeed: number; - const backgroundStyle = timeNoun(time); let animationCanvas: HTMLCanvasElement; let staticCanvas: HTMLCanvasElement; let controller: WeatherSceneController; + let bluebody: Bluebody; + + export let frameRate: number; + export let elapsedTime: number;; const CANVAS_HEIGHT = 920; const CANVAS_WIDTH = 1920; @@ -31,14 +34,14 @@ staticCanvas.height = CANVAS_HEIGHT; const orbitCentreX = CANVAS_WIDTH / 2; - const orbitCentreY = CANVAS_HEIGHT / 2; - const orbitRadius = CANVAS_HEIGHT / 2; - const orbitBodyRadius = 100; + const orbitCentreY = CANVAS_HEIGHT * 0.9; + const orbitRadius = CANVAS_HEIGHT * 0.9; + const orbitBodyRadius = 130; const animCtx = animationCanvas.getContext("2d"); const staticCtx = staticCanvas.getContext("2d"); if (animCtx && staticCtx) { - const bluebody = new Bluebody(staticCtx, time, orbitCentreX, orbitCentreY, orbitRadius, orbitBodyRadius); + bluebody = new Bluebody(staticCtx, time, orbitCentreX, orbitCentreY, orbitRadius, orbitBodyRadius); drawTree(staticCtx, 120, CANVAS_HEIGHT, 80, -Math.PI / 2, TREE_DEPTH, TREE_BRANCH_THICKNESS); drawTree(staticCtx, CANVAS_WIDTH - 400, CANVAS_HEIGHT, 60, -Math.PI / 2, TREE_DEPTH, TREE_BRANCH_THICKNESS); drawCharacter(staticCtx, CANVAS_WIDTH / 2, CANVAS_HEIGHT * 0.8); @@ -49,11 +52,16 @@ const animate = () => { controller.animate(); + frameRate = controller.frameRate.getFrameRate(); + elapsedTime = controller.frameRate.getElapsedTime(); requestAnimationFrame(animate); }; animate(); } }); + + $: bluebody?.updateTime(time); + $: backgroundStyle = timeNoun(time);
    From d07a984007f44820e7c4107856a9fd2a55a91c5e Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 2 Mar 2025 01:47:45 -0800 Subject: [PATCH 32/36] return time with weather --- src/routes/weather/+page.server.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/routes/weather/+page.server.ts b/src/routes/weather/+page.server.ts index aa85a824..122791e6 100644 --- a/src/routes/weather/+page.server.ts +++ b/src/routes/weather/+page.server.ts @@ -33,7 +33,11 @@ export const load = (async () => { try { const weather = await fetchWeather(location.latitude, location.longitude); - return weather.current_weather ?? undefined; + const currentWeather = weather.current_weather ?? undefined; + return { + ...currentWeather, + time: Date.now() + } } catch { // throw error(404, "Wasn't able to get any weather data.\n\n It's probably raining"); return { From dd390162832226cd574896d3b058e135051eaaf7 Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Sun, 2 Mar 2025 01:48:16 -0800 Subject: [PATCH 33/36] add controls to weather page --- src/routes/weather/+page.svelte | 37 +++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/routes/weather/+page.svelte b/src/routes/weather/+page.svelte index d2d46362..0e3e6052 100644 --- a/src/routes/weather/+page.svelte +++ b/src/routes/weather/+page.svelte @@ -9,14 +9,36 @@ let weather = getWeatherFromCode(data.weathercode); const windDirection = getDirectionFromAngle(data.winddirection); - const time = new Date(data.time); + let time = new Date(data.time); + + let elapsedTime: number; + let frameRate: number; + + let showDiagnostics: boolean = true; + let setTime: number = time.getHours(); + + $: updateTime(setTime); + + function updateTime(hour: number) { + const newTime = time; + newTime?.setHours(hour, 0, 0); + time = newTime; + }
    +
    + + +
    +
    + + +
    - +
    @@ -32,6 +54,13 @@
    Wind direction:
    {windDirection}
    + {#if showDiagnostics && frameRate} +
    +
    + fps: {frameRate?.toFixed(1)} +
    +
    + {/if}
    \ No newline at end of file + diff --git a/src/routes/projects/[projectId]/+page.server.ts b/src/routes/projects/[projectId]/+page.server.ts index 6eb99fee..1cc7097d 100644 --- a/src/routes/projects/[projectId]/+page.server.ts +++ b/src/routes/projects/[projectId]/+page.server.ts @@ -1,38 +1,39 @@ -import { error } from '@sveltejs/kit'; -import { getProjectData } from '../../../lib/data/projectData.js'; -import { parseLanguageObject } from '../../../lib/githubApi.js'; +import { error } from "@sveltejs/kit"; +import { getProjectData } from "../../../lib/data/projectData.js"; +import { parseLanguageObject } from "../../../lib/api/githubApi.js"; // import { GH_URL, GH_REPO_TOKEN } from "$env/static/private" // import { dev } from "$app/environment"; -import { STATIC_LANG_DATA } from '../../../lib/data/codeLangData.js'; -import type { PageServerLoad } from './$types.js'; +import { STATIC_LANG_DATA } from "../../../lib/data/codeLangData.js"; +import type { PageServerLoad } from "./$types.js"; export const prerender = false; export const load = (async ({ fetch, params }) => { + try { + const response = await fetch("/api/posts"); + let posts: Post[] = await response.json(); + let ghData; try { - const response = await fetch("/api/posts"); - let posts: Post[] = await response.json(); - let ghData; - try { - if (params.projectId === "programming") { - // if (dev) { - ghData = parseLanguageObject(STATIC_LANG_DATA); - // } else { - // ghData = await getGhLanguageData(`${GH_URL}/repos`, GH_REPO_TOKEN); - // } - } - } catch (e) { - console.error(e); - } - posts = posts.filter(p => p.projectId === params.projectId); - const project = getProjectData(params.projectId); - - return { - posts, - project, - ghData - } - } catch { - error(404, `Hmmm couldn't find ${params.projectId}`) + if (params.projectId === "programming") { + // if (dev) { + ghData = parseLanguageObject(STATIC_LANG_DATA); + // } else { + // ghData = await getGhLanguageData(`${GH_URL}/repos`, GH_REPO_TOKEN); + // } + } + } catch (e) { + console.error(e); } -}) satisfies PageServerLoad \ No newline at end of file + posts = posts.filter((p) => p.projectId === params.projectId); + const project = getProjectData(params.projectId); + + return { + posts, + project, + ghData, + }; + } catch { + error(404, `Hmmm couldn't find ${params.projectId}`); + } +}) satisfies PageServerLoad; + From 6c47e37288a5a1d38511e83a1d83ea4594245deb Mon Sep 17 00:00:00 2001 From: Eddie Pace Date: Mon, 5 May 2025 11:25:51 +0100 Subject: [PATCH 36/36] wip weather stuff --- src/routes/weather/+page.server.ts | 63 +++++++++++++++++------------- src/routes/weather/+page.svelte | 8 ++-- src/routes/weather/Scene.svelte | 5 ++- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/routes/weather/+page.server.ts b/src/routes/weather/+page.server.ts index 122791e6..ee6b532a 100644 --- a/src/routes/weather/+page.server.ts +++ b/src/routes/weather/+page.server.ts @@ -1,53 +1,60 @@ // import { error } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; +import { error } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types"; export const prerender = false; /* eslint-disable */ -const openMeteoBaseUrl = "https://api.open-meteo.com/v1/forecast" +const openMeteoBaseUrl = "https://api.open-meteo.com/v1/forecast"; // default to edinburgh let location = { latitude: 55.953251, - longitude: -3.188267 -} + longitude: -3.188267, +}; const fetchWeather = async (latitude: number, longitude: number) => { - const request = `${openMeteoBaseUrl}?latitude=${latitude}&longitude=${longitude}¤t_weather=true` + const request = `${openMeteoBaseUrl}?latitude=${latitude}&longitude=${longitude}¤t_weather=true`; const result = await fetch(request); const data = await result.json(); - console.log(data); return data; -} +}; export const load = (async () => { - // if (typeof window !== "undefined" && "geolocation" in window.navigator) { - // window.navigator.geolocation.getCurrentPosition(position => { - // location = { - // latitude: position.coords.latitude, - // longitude: position.coords.longitude - // } - // }); - // } else { - // console.log("geolocation permissions blocked, getting weather from Edinburgh instead"); - // } + if ("geolocation" in navigator) { + window.navigator.geolocation.getCurrentPosition( + (position) => { + location = { + latitude: position.coords.latitude, + longitude: position.coords.longitude, + }; + }, + (err) => { + console.log("error", err); + }, + ); + } else { + console.log(typeof window); + console.log("geolocation permissions blocked, getting weather from Edinburgh instead"); + } try { + console.log(`using coords: ${location.latitude} ${location.longitude}`); const weather = await fetchWeather(location.latitude, location.longitude); const currentWeather = weather.current_weather ?? undefined; return { + latitude: weather.latitude, + longitude: weather.longitude, ...currentWeather, - time: Date.now() - } + }; } catch { - // throw error(404, "Wasn't able to get any weather data.\n\n It's probably raining"); + throw error(404, "Wasn't able to get any weather data.\n\n It's probably raining"); return { - temperature: 16.3, - windspeed: 18.4, - winddirection: 78, - weathercode: 17, - is_day: 1, - time: Date.now() - } + temperature: 16.3, + windspeed: 18.4, + winddirection: 78, + weathercode: 17, + is_day: 1, + time: Date.now(), + }; } }) satisfies PageServerLoad; - diff --git a/src/routes/weather/+page.svelte b/src/routes/weather/+page.svelte index 0e3e6052..5e20f98a 100644 --- a/src/routes/weather/+page.svelte +++ b/src/routes/weather/+page.svelte @@ -29,20 +29,22 @@
    - +
    - +
    - +
    +
    Latt/Long:
    +
    {`${data.latitude}, ${data.longitude}`}
    Time:
    {time.toLocaleTimeString()}
    Temperature:
    diff --git a/src/routes/weather/Scene.svelte b/src/routes/weather/Scene.svelte index a8d98c61..17678373 100644 --- a/src/routes/weather/Scene.svelte +++ b/src/routes/weather/Scene.svelte @@ -10,14 +10,14 @@ export let weather: Weather; export let windspeed: number; - let animationCanvas: HTMLCanvasElement; let staticCanvas: HTMLCanvasElement; + let bluebodyCanvas: HTMLCanvasElement; let controller: WeatherSceneController; let bluebody: Bluebody; export let frameRate: number; - export let elapsedTime: number;; + export let elapsedTime: number; const CANVAS_HEIGHT = 920; const CANVAS_WIDTH = 1920; @@ -66,6 +66,7 @@
    +