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/weather/animate/Bluebody.ts b/src/lib/canvas/weather/animate/Bluebody.ts
new file mode 100644
index 00000000..d4f0478c
--- /dev/null
+++ b/src/lib/canvas/weather/animate/Bluebody.ts
@@ -0,0 +1,97 @@
+import { Animator } from "$lib/canvas/Animator";
+
+export class Bluebody extends Animator {
+ radialGradient: CanvasGradient;
+ orbitPosX: number;
+ orbitPosY: number;
+ orbitCenX: number;
+ orbitCenY: number;
+ orbitRadius: number;
+ orbitBodyRadius: number;
+ canvasWidth: number;
+ canvasHeight: number;
+
+ constructor(
+ context: CanvasRenderingContext2D,
+ time: Date,
+ orbitCenX: number,
+ orbitCenY: number,
+ orbitRadius: number,
+ orbitBodyRadius: number,
+ canvasWidth: number,
+ canvasHeight: number,
+ ) {
+ super(context);
+ this.canvasHeight = canvasHeight;
+ this.canvasWidth = canvasWidth;
+ // ctx.beginPath();
+ // ctx.arc(
+ // orbitCentreX,
+ // orbitCentreY,
+ // orbitRadius,
+ // Math.PI, Math.PI * 2, false);
+ // ctx.stroke();
+ 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 minutes = time.getMinutes();
+
+ const decimal = hours + (minutes / 60);
+ const moduloTime = decimal + (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;
+
+ this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
+
+ if (hours >= 6 && hours < 18) {
+ this.radialGradient = this.getSunRadialGradient(this.ctx, this.orbitPosX, this.orbitPosY, this.orbitBodyRadius);
+ } else {
+ this.radialGradient = this.getMoonRadialGradient(this.ctx, this.orbitPosX, this.orbitPosY, this.orbitBodyRadius);
+ }
+ this.draw();
+
+ }
+
+ 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..b6562675
--- /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/maths";
+
+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..557811f0
--- /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/maths";
+
+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 = "white";
+ 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/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
diff --git a/src/routes/weather/DrawCharacter.ts b/src/lib/canvas/weather/draw/character.ts
similarity index 90%
rename from src/routes/weather/DrawCharacter.ts
rename to src/lib/canvas/weather/draw/character.ts
index 872c102a..8a4e1c12 100644
--- a/src/routes/weather/DrawCharacter.ts
+++ b/src/lib/canvas/weather/draw/character.ts
@@ -1,16 +1,16 @@
-import { Artist } from '../../lib/canvas/Artist';
+import { Artist } from "../../Artist";
-const AMBER = 'rgb(245, 167, 66)';
+const AMBER = "rgb(245, 167, 66)";
// const DARK_ORANGE = "rgb(200, 100, 100)";
-const WHITE = 'white';
-const BLACK = 'black';
+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)';
+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);
@@ -30,7 +30,7 @@ export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: n
// chin
[-10, 10],
[-10, 5],
- [-10, 0]
+ [-10, 0],
];
const HEAD_PATCH_POINTS: Points = [
@@ -40,7 +40,7 @@ export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: n
[10, 35],
[-20, -15],
[-15, -8],
- [-20, 0]
+ [-20, 0],
];
const RIGHT_EAR_POINTS: Points = [
@@ -61,7 +61,7 @@ export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: n
[-5, -10],
[-5, -10],
[5, -20],
- [-5, -20]
+ [-5, -20],
];
const LEFT_EAR_POINTS: Points = [
@@ -79,7 +79,7 @@ export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: n
[10, 5],
[12, -20],
[0, -25],
- [5, -10]
+ [5, -10],
];
const NOSE: Points = [
@@ -89,7 +89,7 @@ export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: n
[-2, 4],
[-4, 2],
[-3, 0],
- [-3, -1]
+ [-3, -1],
];
const TONGUE: Points = [
@@ -98,7 +98,7 @@ export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: n
[-1, 10],
[-4, 5],
[-4, 3],
- [-4, 0]
+ [-4, 0],
];
a.drawShape(HEAD_POINTS, true, 0, 0, 2, ORANGE_BROWN, ORANGE_BROWN);
@@ -109,7 +109,7 @@ export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: n
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.lineCap = "round";
context.lineWidth = 4;
context.strokeStyle = SHADOW_BROWN;
context.stroke();
@@ -119,7 +119,7 @@ export function drawCharacter(context: CanvasRenderingContext2D, x: number, y: n
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.lineCap = "round";
context.lineWidth = 4;
context.strokeStyle = SHADOW_BROWN;
context.stroke();
@@ -178,7 +178,7 @@ function drawFaceLines(context: CanvasRenderingContext2D, x: number, y: number)
endY = startY - 5;
context.bezierCurveTo(startX, startY, startX - 2, startY - 7, endX, endY);
- context.lineCap = 'round';
+ context.lineCap = "round";
context.lineWidth = 4;
context.strokeStyle = SHADOW_BROWN;
context.stroke();
diff --git a/src/lib/canvas/weather/draw/tree.ts b/src/lib/canvas/weather/draw/tree.ts
new file mode 100644
index 00000000..f65f1413
--- /dev/null
+++ b/src/lib/canvas/weather/draw/tree.ts
@@ -0,0 +1,47 @@
+import { randRangeRGBString } from "$lib/utils/colour";
+
+// 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/components/creative/clock/Clock.svelte b/src/lib/components/creative/clock/Clock.svelte
new file mode 100644
index 00000000..2f8da85f
--- /dev/null
+++ b/src/lib/components/creative/clock/Clock.svelte
@@ -0,0 +1,113 @@
+
+
+
+
+
diff --git a/src/lib/components/creative/clock/PaddedNumberSpinnerInput.svelte b/src/lib/components/creative/clock/PaddedNumberSpinnerInput.svelte
new file mode 100644
index 00000000..7ef75294
--- /dev/null
+++ b/src/lib/components/creative/clock/PaddedNumberSpinnerInput.svelte
@@ -0,0 +1,85 @@
+
+
+
+ mouseDown(addTime)}
+ onmouseup={mouseUp}>
+
+
+ parseChange(e)}/>
+ mouseDown(subtractTime)}
+ onmouseup={mouseUp}>
+
+
+
+
+
diff --git a/src/lib/components/creative/weather/BigWeatherButton.svelte b/src/lib/components/creative/weather/BigWeatherButton.svelte
new file mode 100644
index 00000000..974510d9
--- /dev/null
+++ b/src/lib/components/creative/weather/BigWeatherButton.svelte
@@ -0,0 +1,14 @@
+
+
+
diff --git a/src/lib/components/creative/weather/WeatherControls.svelte b/src/lib/components/creative/weather/WeatherControls.svelte
new file mode 100644
index 00000000..4f464f9e
--- /dev/null
+++ b/src/lib/components/creative/weather/WeatherControls.svelte
@@ -0,0 +1,30 @@
+
+
+
+
+ {#each WEATHERS as weather}
+ setCurrentWeather(weather)} />
+ {/each}
+
+
+
+
diff --git a/src/lib/components/utils/BigIconButton.svelte b/src/lib/components/utils/BigIconButton.svelte
new file mode 100644
index 00000000..579eda00
--- /dev/null
+++ b/src/lib/components/utils/BigIconButton.svelte
@@ -0,0 +1,50 @@
+
+
+
+
+
+ {text}
+
+
+
+
diff --git a/src/lib/data/weatherData.ts b/src/lib/data/weatherData.ts
new file mode 100644
index 00000000..36d59158
--- /dev/null
+++ b/src/lib/data/weatherData.ts
@@ -0,0 +1,92 @@
+export enum Weather {
+ 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",
+}
+
+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;
+ }
+}
+
+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;
+}
diff --git a/src/lib/theme.ts b/src/lib/theme.ts
index ee06b737..03171c49 100644
--- a/src/lib/theme.ts
+++ b/src/lib/theme.ts
@@ -9,23 +9,29 @@ export function applyDarkTheme(darkTheme: boolean) {
if (darkTheme) {
root.style.setProperty('--primary', 'var(--white)');
root.style.setProperty('--primary-50', 'var(--white-50)');
+ root.style.setProperty('--primary-25', 'var(--white-25)');
root.style.setProperty('--secondary', 'var(--black)');
root.style.setProperty('--secondary-50', 'var(--black-50)');
+ root.style.setProperty('--secondary-25', 'var(--black-25)');
root.style.setProperty('--tertiary', 'var(--dark-grey)');
root.style.setProperty('--bg0', 'var(--dark-grey)');
root.style.setProperty('--bg1', 'var(--grey)');
root.style.setProperty('--caption', 'var(--light-grey)');
root.style.setProperty('--highlight', `var(--${get(currentColourTheme)}-bright)`);
+ root.style.setProperty('--highlight-inverse', 'var(--highlight');
} else {
root.style.setProperty('--primary', 'var(--black)');
root.style.setProperty('--primary-50', 'var(--black-50)');
+ root.style.setProperty('--primary-25', 'var(--black-25)');
root.style.setProperty('--secondary', 'var(--white)');
root.style.setProperty('--secondary-50', 'var(--white-50)');
+ root.style.setProperty('--secondary-25', 'var(--white-25)');
root.style.setProperty('--tertiary', 'var(--grey)');
root.style.setProperty('--bg0', 'var(--light-tan)');
root.style.setProperty('--bg1', 'var(--white)');
root.style.setProperty('--caption', 'var(--dark-grey)');
root.style.setProperty('--highlight', `var(--${get(currentColourTheme)}-dark)`);
+ root.style.setProperty('--highlight-inverse', `var(--${get(currentColourTheme)}-bright)`);
}
}
}
diff --git a/src/lib/utils/colour.ts b/src/lib/utils/colour.ts
new file mode 100644
index 00000000..6953995c
--- /dev/null
+++ b/src/lib/utils/colour.ts
@@ -0,0 +1,16 @@
+import { randBetween } from "./maths";
+
+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})`;
+};
diff --git a/src/lib/utils/date.ts b/src/lib/utils/date.ts
index 99e340b8..68171b36 100644
--- a/src/lib/utils/date.ts
+++ b/src/lib/utils/date.ts
@@ -13,3 +13,4 @@ export function dayOfYear(date: Date) {
const diff = date.valueOf() - new Date(date.getFullYear(), 0, 0).valueOf();
return Math.floor(diff / (1000 * 60 * 60 * 24));
}
+
diff --git a/src/lib/utils/maths.ts b/src/lib/utils/maths.ts
index 17735332..826f8502 100644
--- a/src/lib/utils/maths.ts
+++ b/src/lib/utils/maths.ts
@@ -6,3 +6,17 @@ export function randomMinMax(min: number, max: number) {
export function randomDirection(): 1 | -1 {
return Math.random() >= 0.5 ? 1 : -1;
}
+
+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 - 1, max));
+}
+
+export function randVariance(value: number, variance: number) {
+ const min = value * (1 - variance);
+ const max = value * (1 + variance);
+ return randBetween(min, max);
+}
diff --git a/src/routes/weather/weatherTime.ts b/src/lib/utils/time.ts
similarity index 65%
rename from src/routes/weather/weatherTime.ts
rename to src/lib/utils/time.ts
index 42ab0f40..24abcca2 100644
--- a/src/routes/weather/weatherTime.ts
+++ b/src/lib/utils/time.ts
@@ -1,12 +1,16 @@
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';
+ return "dawn";
case 7:
- return 'sunrise';
+ return "sunrise";
case 8:
- return 'morning';
+ return "morning";
case 9:
case 10:
case 11:
@@ -17,13 +21,13 @@ export function timeNoun(time: Date): string {
case 16:
case 17:
case 18:
- return 'day';
+ return "day";
case 19:
- return 'evening';
+ return "evening";
case 20:
- return 'sunset';
+ return "sunset";
case 21:
- return 'dusk';
+ return "dusk";
case 22:
case 23:
case 24:
@@ -33,9 +37,9 @@ export function timeNoun(time: Date): string {
case 3:
case 4:
case 5:
- return 'night';
+ return "night";
default:
- return 'day';
+ return "day";
}
}
diff --git a/src/posts/weather-widget.md b/src/posts/weather-widget.md
index 6becb8e6..744c54dd 100644
--- a/src/posts/weather-widget.md
+++ b/src/posts/weather-widget.md
@@ -1,9 +1,10 @@
---
title: Weather - Widget
description: A fun little widget that almost works well.
-date: 01/01/2023
+date: 01/17/2026
projectId: programming
published: false
dev: true
technologies: [Typescript, HTML5]
---
+
diff --git a/src/routes/Header.svelte b/src/routes/Header.svelte
index 5fd05488..7ce091d2 100644
--- a/src/routes/Header.svelte
+++ b/src/routes/Header.svelte
@@ -18,9 +18,9 @@
>
Projects
-
+
diff --git a/src/routes/weather/+page.server.ts b/src/routes/weather/+page.server.ts
index f3290860..ee6b532a 100644
--- a/src/routes/weather/+page.server.ts
+++ b/src/routes/weather/+page.server.ts
@@ -1,39 +1,60 @@
-import { error } from '@sveltejs/kit';
-import type { PageServerLoad } from './$types';
+// import { error } from '@sveltejs/kit';
+import { error } from "@sveltejs/kit";
+import type { PageServerLoad } from "./$types";
export const prerender = false;
-const openMeteoBaseUrl = 'https://api.open-meteo.com/v1/forecast';
+/* eslint-disable */
+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 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
- };
- });
+ 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('geolocation permissions blocked, getting weather from Edinburgh instead');
+ 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);
- return weather.current_weather ?? undefined;
+ const currentWeather = weather.current_weather ?? undefined;
+ return {
+ latitude: weather.latitude,
+ longitude: weather.longitude,
+ ...currentWeather,
+ };
} catch {
- 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(),
+ };
}
}) satisfies PageServerLoad;
diff --git a/src/routes/weather/+page.svelte b/src/routes/weather/+page.svelte
index 666fb1d1..ee92e8f6 100644
--- a/src/routes/weather/+page.svelte
+++ b/src/routes/weather/+page.svelte
@@ -1,35 +1,69 @@
+ let time = new Date(data.time);
-
-
-
+ let elapsedTime: number;
+ let frameRate: number;
-
-
Time:
-
{time.toLocaleTimeString()}
-
Temperature:
-
{data.temperature} °C
-
Weather:
-
{weather}
-
Wind speed:
-
{data.windspeed} km/h
-
Wind direction:
-
{windDirection}
-
+ let showDiagnostics: boolean = true;
+ let setHours: number = time.getHours();
+ let setMinutes: number = time.getMinutes();
+
+ $: updateTime(setHours, setMinutes);
-
-
+ function updateTime(hour: number, minutes: number) {
+ const newTime = time;
+ newTime?.setHours(hour, minutes, 0);
+ time = newTime;
+ }
+
+
+
+
+
+ {#if import.meta.env.DEV}
+
+ diagnostics
+
+
+ {/if}
+
+
+
+
+
+
+
+
Latt/Long:
+
{`${data.latitude}, ${data.longitude}`}
+
Time:
+
{time.toLocaleTimeString()}
+
Temperature:
+
{data.temperature} °C
+
Weather:
+
{weather}
+
Wind speed:
+
{data.windspeed} km/h
+
Wind direction:
+
{windDirection}
+
+ {#if showDiagnostics && frameRate}
+
+
+ fps: {frameRate?.toFixed(1)}
+
+
+ {/if}
diff --git a/src/routes/weather/Compass.svelte b/src/routes/weather/Compass.svelte
index f0083966..32544467 100644
--- a/src/routes/weather/Compass.svelte
+++ b/src/routes/weather/Compass.svelte
@@ -1,189 +1,83 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/routes/weather/Scene.svelte b/src/routes/weather/Scene.svelte
index 57ad9496..a02af43f 100644
--- a/src/routes/weather/Scene.svelte
+++ b/src/routes/weather/Scene.svelte
@@ -1,39 +1,79 @@
-
-
+
+
+
+
+
diff --git a/src/routes/weather/WeatherSceneAnimator.ts b/src/routes/weather/WeatherSceneAnimator.ts
new file mode 100644
index 00000000..d6b007f8
--- /dev/null
+++ b/src/routes/weather/WeatherSceneAnimator.ts
@@ -0,0 +1,140 @@
+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 { FrameRate } from "$lib/canvas/weather/animate/utils";
+import { randIntBetween } from "$lib/utils/maths";
+import { Weather } from "../../lib/data/weatherData";
+
+export class WeatherSceneController extends Animator {
+ height: number;
+ width: number;
+ 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;
+ 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() {
+ 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(15, this.windspeed / 2);
+ 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(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 / 4),
+ randIntBetween(20, 60),
+ randIntBetween(10, 30),
+ Math.max(1.1, speed),
+ `rgb(${gray}, ${gray}, ${gray})`,
+ ),
+ );
+ }
+ }
+
+ 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);
+ }
+
+ 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;
+ }
+}
diff --git a/src/routes/weather/weatherData.ts b/src/routes/weather/weatherData.ts
deleted file mode 100644
index a638431b..00000000
--- a/src/routes/weather/weatherData.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-export enum Weather {
- Clear = 'Clear',
- Cloudy = 'Cloudy',
- Overcast = 'Overcast',
- Fog = 'Fog',
- Drizzle = 'Drizzle',
- Rain = 'Rain',
- Snow = 'Snow',
- Thunder = 'Thunder'
-}
-
-export enum Direction {
- N = 'North',
- NE = 'North East',
- E = 'East',
- SE = 'South East',
- S = 'South',
- SW = 'South West',
- W = 'West',
- NW = 'North West'
-}
-
-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;
- }
-}
-
-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;
-}
diff --git a/src/routes/weather/weatherScene.ts b/src/routes/weather/weatherScene.ts
deleted file mode 100644
index 3407e6c5..00000000
--- a/src/routes/weather/weatherScene.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import { drawCircle } from '$lib/canvas/canvasUtils';
-
-export function drawOrbit(
- 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;
- const orbitPosX = orbitCenX - orbitLenX;
- const orbitPosY = orbitCenY - orbitLenY;
-
- // sun or moon
- let radialGradient;
- if (hours >= 6 && hours < 18) {
- radialGradient = getSunRadialGradient(context, orbitPosX, orbitPosY, orbitBodyRadius);
- } else {
- radialGradient = getMoonRadialGradient(context, orbitPosX, orbitPosY, orbitBodyRadius);
- }
- context.fillStyle = radialGradient;
- context.arc(orbitPosX, orbitPosY, orbitBodyRadius, 0, 2 * Math.PI, false);
- context.fill();
-
- // context.fillRect(orbitPositionX, orbitPositionY, orbitBodyRadius * 2, orbitBodyRadius * 2);
-}
-
-function 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;
-}
-
-function 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;
-}
-
-// 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;
- const sizeVariance = size * sizeVarFactor;
- // get random size between 0.75 and 1.5 x provided size
- const getBlobSize = () => Math.floor(Math.random() * sizeVariance + sizeVariance);
- const getRowLength = (middle: number) =>
- Math.floor(Math.random() * middle * rowVarFactor + middle * rowVarFactor);
- const numberOfRows = Math.floor(Math.random() * 3 + 3);
-
- 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; cy += size) {
- rowLength = getRowLength(rowLength);
- for (let cx = 0; cx <= rowLength * size; cx += size) {
- const offset = Math.random() * newSize * 2 + newSize;
- newSize = getBlobSize();
- newPosX = cx + x + offset;
- newPosY = cy + y;
- drawCircle(context, newPosX, newPosY, newSize, undefined, cloudColour);
- }
- }
-}
diff --git a/static/fonts/Silkscreen/Silkscreen-Bold.ttf b/static/fonts/Silkscreen/Silkscreen-Bold.ttf
new file mode 100644
index 00000000..e5a5b819
Binary files /dev/null and b/static/fonts/Silkscreen/Silkscreen-Bold.ttf differ
diff --git a/static/fonts/Silkscreen/Silkscreen-Regular.ttf b/static/fonts/Silkscreen/Silkscreen-Regular.ttf
new file mode 100644
index 00000000..8abaa7c5
Binary files /dev/null and b/static/fonts/Silkscreen/Silkscreen-Regular.ttf differ
diff --git a/static/styles/fonts.css b/static/styles/fonts.css
index f3afb170..c75673ba 100644
--- a/static/styles/fonts.css
+++ b/static/styles/fonts.css
@@ -199,4 +199,16 @@
src: url("/fonts/Fira_Mono/FiraMono-Bold.ttf");
font-weight: 700;
font-style: "normal";
-}
\ No newline at end of file
+}
+
+/* Silkscreen */
+@font-face {
+ font-family: "Silkscreen";
+ src: url("/fonts/Silkscreen/Silkscreen-Regular.ttf");
+ font-weight: 400;
+}
+@font-face {
+ font-family: "Silkscreen";
+ src: url("/fonts/Silkscreen/Silkscreen-Bold.ttf");
+ font-weight: 700;
+}
diff --git a/static/styles/global.css b/static/styles/global.css
index f60f264f..2302bc54 100644
--- a/static/styles/global.css
+++ b/static/styles/global.css
@@ -9,8 +9,10 @@
/* Monochromes */
--white: rgb(250, 235, 215);
--white-50: rgba(250, 235, 215, 0.5);
+ --white-25: rgba(250, 235, 215, 0.25);
--black: rgb(0, 0, 0);
--black-50: rgb(0, 0, 0, 0.5);
+ --black-25: rgb(0, 0, 0, 0.25);
--light-tan: rgb(220, 215, 200);
--light-grey: rgb(150, 150, 150);
@@ -20,7 +22,7 @@
/* Themes */
--gold-bright: rgb(255, 177, 8);
- --gold-dark: rgb(194, 132, 0);
+ --gold-dark: rgb(155, 107, 3);
--teal-bright: rgb(18, 235, 152);
--teal-dark: rgb(4, 168, 105);
--sky-bright: rgb(124, 207, 255);
@@ -35,8 +37,11 @@
--secondary: var(--black);
--tertiary: var(--dark-grey);
--highlight: var(--gold-bright);
+ --highlight-inverse: var(--gold-dark);
--primary-50: var(--white-50);
+ --primary-25: var(--white-25);
--secondary-50: var(--black-50);
+ --secondary-25: var(--black-25);
--caption: var(--light-grey);
/* Sizes */