diff --git a/.gitignore b/.gitignore
index ceaea36..19c165c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,79 +1,105 @@
-# ---> Node
+# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
+
# Logs
+
logs
-*.log
-npm-debug.log*
+_.log
+npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
+# Caches
+
+.cache
+
# Diagnostic reports (https://nodejs.org/api/report.html)
-report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
+
pids
-*.pid
-*.seed
+_.pid
+_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
+
lib-cov
# Coverage directory used by tools like istanbul
+
coverage
*.lcov
# nyc test coverage
+
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+
.grunt
# Bower dependency directory (https://bower.io/)
+
bower_components
# node-waf configuration
+
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
+
build/Release
# Dependency directories
+
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
+
web_modules/
# TypeScript cache
+
*.tsbuildinfo
# Optional npm cache directory
+
.npm
# Optional eslint cache
+
.eslintcache
# Optional stylelint cache
+
.stylelintcache
# Microbundle cache
+
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
+
.node_repl_history
# Output of 'npm pack'
+
*.tgz
# Yarn Integrity file
+
.yarn-integrity
# dotenv environment variable files
+
.env
.env.development.local
.env.test.local
@@ -81,52 +107,72 @@ web_modules/
.env.local
# parcel-bundler cache (https://parceljs.org/)
-.cache
+
.parcel-cache
# Next.js build output
+
.next
out
# Nuxt.js build / generate output
+
.nuxt
dist
# Gatsby files
-.cache/
+
# Comment in the public line in if your project uses Gatsby and not Next.js
+
# https://nextjs.org/blog/next-9-1#public-directory-support
+
# public
# vuepress build output
+
.vuepress/dist
# vuepress v2.x temp and cache directory
+
.temp
-.cache
# Docusaurus cache and generated files
+
.docusaurus
# Serverless directories
+
.serverless/
# FuseBox cache
+
.fusebox/
# DynamoDB Local files
+
.dynamodb/
# TernJS port file
+
.tern-port
# Stores VSCode versions used for testing VSCode extensions
+
.vscode-test
# yarn v2
+
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
+# IntelliJ based IDEs
+.idea
+
+# Finder (MacOS) folder config
+.DS_Store
+
+dist
+src/web/dist.js
\ No newline at end of file
diff --git a/README.md b/README.md
index d9bfd3b..fb2e46a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,4 @@
-# website
-
+# sophie's website
+this website is very simple it is located in src/web.
+there is a very sophisticated build script in src/build.ts !
+it even supports HMR!
\ No newline at end of file
diff --git a/bun.lockb b/bun.lockb
new file mode 100755
index 0000000..5777755
Binary files /dev/null and b/bun.lockb differ
diff --git a/global.d.ts b/global.d.ts
new file mode 100644
index 0000000..08bb702
--- /dev/null
+++ b/global.d.ts
@@ -0,0 +1,12 @@
+import P5 from "p5"
+import * as p5Sound from 'p5/lib/addons/p5.sound'
+import * as p5Global from 'p5/global'
+
+export = P5;
+export as namespace p5;
+
+declare global {
+ interface Window {
+ p5: typeof P5,
+ }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..edd2991
--- /dev/null
+++ b/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "sophie",
+ "module": "index.ts",
+ "type": "module",
+ "devDependencies": {
+ "@types/bun": "latest",
+ "@types/node": "^20.11.30",
+ "@types/p5": "^1.7.6",
+ "@types/serve-handler": "^6.1.4",
+ "esbuild": "0.20.2",
+ "micro": "^10.0.1",
+ "serve-handler": "^6.1.5"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ },
+ "scripts": {
+ "dev": "bun src/build.ts --dev",
+ "build": "bun src/build.ts --build"
+ },
+ "dependencies": {
+ "ws": "^8.16.0"
+ }
+}
diff --git a/src/build.ts b/src/build.ts
new file mode 100644
index 0000000..209dca7
--- /dev/null
+++ b/src/build.ts
@@ -0,0 +1,102 @@
+import * as esbuild from "esbuild"
+import {watch, cpSync, rmdirSync, createReadStream, readFileSync, ReadStream} from "node:fs"
+import { serve } from 'micro';
+import {Server} from "node:http"
+import handler from "serve-handler"
+import { Readable } from "node:stream";
+import { WebSocketServer } from 'ws';
+
+const argv = process.argv.slice(2)
+
+function buildTs() {
+ console.log("[dev] Building file..")
+ esbuild.build({
+ entryPoints: ["./src/web/ts/index.ts"],
+ outfile: "./src/web/dist.js"
+ })
+}
+buildTs();
+
+if(argv[0] == "--build") {
+ try{rmdirSync("./dist")}catch{}
+ cpSync("./src/web/", "./dist/", {recursive:true})
+ rmdirSync("./dist/ts",{recursive:true})
+ console.log("[dev] View the dist folder")
+}
+
+if(argv[0] == "--dev") {
+ const scriptName = Math.random().toFixed(10).replace("0.","")
+
+ const wss = new WebSocketServer({ port: 8081 });
+ let allConnections: any[] = [];
+
+ wss.on('connection', function connection(ws) {
+ //@ts-ignore
+ ws.id = Math.random();
+ //@ts-ignore
+
+ allConnections.push(ws);
+
+ ws.on('error', console.error);
+ ws.on("close", () => {
+ //@ts-ignore
+ allConnections = allConnections.filter(z => z.id != ws.id);
+ })
+ });
+ const server = new Server(
+ serve(async (req, res) => {
+ if(req.url == `/${scriptName}.js`) {
+ const body = `let t;function rr() {console.log("connecting to dev server")
+ let a = new WebSocket("ws://localhost:8081");
+ a.addEventListener("message", g => {
+ if(g.data == "refresh"){
+ location.reload()
+ }
+ });
+ a.addEventListener("open", () => {
+ console.log("connected")
+ })
+ a.addEventListener("close", () => {
+ console.log("socket closed, restarting in 1s")
+ clearInterval(t)
+ t = setTimeout(()=>{
+ rr()
+ },1000)
+ })};rr();`;
+ res
+ .writeHead(200, {
+ 'Content-Length': Buffer.byteLength(body),
+ 'Content-Type': 'text/plain',
+ })
+ .end(body);
+ }
+ if(req.url == "/" || req.url?.endsWith(".html")) {
+ await handler(req, res, {
+ directoryListing: false,
+ public: "src/web/"
+ }, {
+ createReadStream(path, options) {
+ let sx = readFileSync(path).toString("utf8");
+ sx = sx.replaceAll("", ``)
+ return Readable.from([sx]) as ReadStream
+ },
+ });
+ } else {
+ await handler(req, res, {
+ directoryListing: false,
+ public: "src/web/"
+ });
+ }
+ })
+ )
+ server.listen(8080)
+ console.log('[http] Listening HTTP on 8080.')
+ const watcher = watch("./src/web", {
+ recursive: true,
+ }, (e,f) => {
+ if(f == "dist.js") return;
+ console.log("[dev] Noticed update in " + f + ", of type " + e +".")
+ allConnections.forEach(z => z.send("refresh"));
+ buildTs();
+ })
+}
diff --git a/src/web/Screenshot_2023-02-04_16-45-32.png b/src/web/Screenshot_2023-02-04_16-45-32.png
new file mode 100755
index 0000000..96dfd04
Binary files /dev/null and b/src/web/Screenshot_2023-02-04_16-45-32.png differ
diff --git a/src/web/index.html b/src/web/index.html
new file mode 100644
index 0000000..36a51b4
--- /dev/null
+++ b/src/web/index.html
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+ sophie's personal site
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ home | donations
+
+
+ I'm Latvian, 17. My name's Sophie. I love listening to music, I have <loading...> song plays.
+
+ I am a JS/TS developer, mostly specializing in backend work.
+
+ I play minecraft, and upgun with friends. I have developed many bukkit plugins for Paper/Folia/Spigot before, and am pretty well versed in them.
+
+ Contact me at matrix , discord , github (view projects here) , sadgit , or mastodon
+
+
+
+ BTC bc1q83jdukjn4a2qm0rmn9tqcfkcq60la22lqy2shx
+ ETH/BSC/USDT /USDC (send via BSC) 0xc691cd8950Fdf96Faa2aCA1CA9b4B3Fd5B2a44BB
+ SOL 79NKoiXaPzbwbsD5MFKKwmoeEPKtTsoQFfx64MHmULF7
+ XMR 42iW3icQrybKYieQNSrm76dXetuXD6HaxZDijajXkge7GTSKVG4NefxBj3mbWudpY62dxRTihm4beJgy36X8xFKCTWpVAjS
+
+ OR kofi
+
+
+
+
+
+
+ Click spacebar to change the themes.
+
+
+
+
\ No newline at end of file
diff --git a/src/web/sticker.webp b/src/web/sticker.webp
new file mode 100644
index 0000000..52f3494
Binary files /dev/null and b/src/web/sticker.webp differ
diff --git a/src/web/ts/index.ts b/src/web/ts/index.ts
new file mode 100644
index 0000000..fb5231c
--- /dev/null
+++ b/src/web/ts/index.ts
@@ -0,0 +1,370 @@
+/*
+ The original version of this Processing script was written by
+ Jason Labbe (available here: https://openprocessing.org/sketch/377231/)
+
+ This is a highly modified version by Sophie, firstly re-written using JS, and then
+ improved using newer techniques (no more pGraphics, using font toPoints now as an example).
+ Jason Labbe's version is licensed under CC BY-SA 3.0 DEED, and so is this version.
+
+ https://creativecommons.org/licenses/by-sa/3.0/deed.en
+*/
+
+// Global variables
+let particles: Particle[] = [];
+let font: p5.Font;
+let bgColor: p5.Color;
+
+let darkMode = false;
+
+let darkTheme: p5.Color;
+let lightTheme: p5.Color;
+
+interface Bounds {
+ x: number;
+ y: number;
+ w: number;
+ h: number;
+}
+
+function generateRandomPos(x: number, y: number, mag: number) {
+ let randomDir = createVector(random(0, width), random(0, height));
+
+ let pos = createVector(x, y);
+ pos.sub(randomDir);
+ pos.normalize();
+ pos.mult(mag);
+ pos.add(x, y);
+
+ return pos;
+}
+
+function getTextSize(text: string, font: p5.Font) {
+ let fontSize = 1200; // Wait, doesn't this impact performance heavily?
+ // Nope, a single iteration (once warmed up) only takes around
+ // 0.04799999999254942ms. This means, that for a 8K screen it'd take
+ // 11ms, and for normal 1080P screen it takes 20-40ms (891 iterations).
+ // This can be optimized further, but it's a single run of this function
+ // every time the word is rerendered. I don't think it matters nearly as much..
+ // Pouring too much time into this single number.. what the hell
+
+ let bounds: Bounds;
+
+ //let iters = 0;
+ //let time = 0;
+ do {
+ //const loopstart = performance.now();
+ fontSize--;
+ bounds = font.textBounds(text, 0, 0, fontSize) as Bounds;
+ // const loopend = performance.now();
+ // time += loopend - loopstart;
+ // iters += 1;
+ } while (bounds.w > windowWidth - 100);
+ //console.log("Took " + time + "ms for " + iters + " iterations");
+ return fontSize;
+}
+
+class Particle {
+ pos: p5.Vector;
+ vel: p5.Vector;
+ acc: p5.Vector;
+
+ target: p5.Vector;
+
+ closeEnoughTarget: number;
+ maxSpeed: number;
+ maxForce: number;
+ particleSize: number;
+ isKilled: boolean;
+ startColor: p5.Color;
+ targetColor: p5.Color;
+ colorWeight: number;
+ colorBlendRate: number;
+
+ constructor() {
+ this.pos = createVector(0, 0);
+ this.vel = createVector(0, 0);
+ this.acc = createVector(0, 0);
+ this.target = createVector(0, 0);
+
+ this.closeEnoughTarget = 50;
+ this.maxSpeed = 4.0;
+ this.maxForce = 0.1;
+ this.particleSize = 5;
+ this.isKilled = false;
+
+ this.startColor = color(0);
+ this.targetColor = color(0);
+ this.colorWeight = 0;
+ this.colorBlendRate = 0.025;
+ }
+
+ move() {
+ // Check if particle is close enough to its target to slow down
+ let proximityMult = 1.0;
+ let distance = dist(this.pos.x, this.pos.y, this.target.x, this.target.y);
+ if (distance < this.closeEnoughTarget) {
+ proximityMult = distance / this.closeEnoughTarget;
+ }
+
+ //.push force towards target
+ let towardsTarget = createVector(this.target.x, this.target.y);
+ towardsTarget.sub(this.pos);
+ towardsTarget.normalize();
+ towardsTarget.mult(this.maxSpeed * proximityMult);
+
+ let steer = createVector(towardsTarget.x, towardsTarget.y);
+ steer.sub(this.vel);
+ steer.normalize();
+ steer.mult(this.maxForce);
+ this.acc.add(steer);
+
+ // Move particle
+ this.vel.add(this.acc);
+ this.pos.add(this.vel);
+ this.acc.mult(0);
+ }
+
+ draw() {
+ // Draw particle
+ let currentColor = lerpColor(
+ this.startColor,
+ this.targetColor,
+ this.colorWeight
+ );
+
+ noStroke();
+ fill(currentColor);
+ ellipse(this.pos.x, this.pos.y, this.particleSize, this.particleSize);
+
+ // Blend towards its target color
+ if (this.colorWeight < 1.0) {
+ this.colorWeight = min(this.colorWeight + this.colorBlendRate, 1.0);
+ }
+ }
+
+ kill() {
+ if (!this.isKilled) {
+ // Set its target outside the scene
+ let randomPos = generateRandomPos(
+ width / 2,
+ height / 2,
+ (width + height) / 2
+ );
+ this.target.x = randomPos.x;
+ this.target.y = randomPos.y;
+
+ // Begin blending its color to black
+ this.startColor = lerpColor(
+ this.startColor,
+ this.targetColor,
+ this.colorWeight
+ );
+ this.targetColor = color(0);
+ this.colorWeight = 0;
+
+ this.isKilled = true;
+ }
+ }
+}
+
+function preload() {
+ font = loadFont("https://rsms.me/inter/font-files/InterVariable.ttf");
+ darkTheme = color(34, 40, 49, 255);
+ lightTheme = color(238, 238, 238, 255);
+}
+
+function nextWord(word: string) {
+ const fontSize = getTextSize(word, font);
+ const bounds = font.textBounds(word, 0, 0, fontSize) as Bounds;
+ let sampleFactor = 0.2;
+
+ if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)){
+ sampleFactor = 0.1;
+ }
+
+ let points = font.textToPoints(
+ word,
+ (windowWidth - 25 - bounds.w) / 2,
+ (windowHeight + bounds.h / 2) / 2,
+ fontSize,
+ {
+ sampleFactor,
+ simplifyThreshold: 0,
+ }
+ );
+
+ let newColor = color(
+ random(0.0, 255.0),
+ random(0.0, 255.0),
+ random(0.0, 255.0)
+ );
+
+ let particleCount = particles.length;
+ let particleIndex = 0;
+
+ for (let i = 0; i < points.length; i++) {
+ let randomIndex = Math.floor(random(0, points.length));
+ const point = points[randomIndex];
+
+ let newParticle;
+
+ if (particleIndex < particleCount) {
+ newParticle = particles[particleIndex];
+ newParticle.isKilled = false;
+ particleIndex += 1;
+ } else {
+ newParticle = new Particle();
+
+ let randomPos = generateRandomPos(
+ width / 2,
+ height / 2,
+ (width + height) / 2
+ );
+ newParticle.pos.x = randomPos.x;
+ newParticle.pos.y = randomPos.y;
+
+ newParticle.maxSpeed = random(2.0, 5.0);
+ newParticle.maxForce = newParticle.maxSpeed * 0.025;
+ newParticle.particleSize = random(3, 6);
+ newParticle.colorBlendRate = random(0.0025, 0.03);
+
+ particles.push(newParticle);
+ }
+
+ newParticle.startColor = lerpColor(
+ newParticle.startColor,
+ newParticle.targetColor,
+ newParticle.colorWeight
+ );
+ newParticle.targetColor = newColor;
+ newParticle.colorWeight = 0;
+
+ newParticle.target.x = point.x;
+ newParticle.target.y = point.y;
+ }
+
+ if (particleIndex < particleCount) {
+ for (let i = particleIndex; i < particleCount; i++) {
+ let particle = particles[i];
+ particle.kill();
+ }
+ }
+}
+
+function windowResized() {
+ resizeCanvas(windowWidth, windowHeight);
+}
+
+function setup() {
+ // selection
+ let pages = ["home", "donations"];
+ let selected = "home";
+ pages.forEach(z => {
+ const buttonElement = document.getElementById(z+"-button")!;
+ const element = document.getElementById(z)!;
+ buttonElement.addEventListener("click", _ => {
+ if(z !== selected) {
+ const selectedElement = document.getElementById(selected)!
+ const selectedButton = document.getElementById(selected+"-button")!
+ selectedElement.style.display = "none";
+ element.style.display = "block";
+ selectedButton.className = ""
+ buttonElement.className = "selected";
+ selected = z;
+
+
+ }
+ })
+ });
+
+ // lastfm
+ (async () => {
+ const lastfm = await fetch(
+ "https://ws.audioscrobbler.com/2.0/?method=user.getinfo&user=yourfriendoss&api_key=8d983789c771afaeb7412ac358d4bad0&format=json"
+ );
+ const json = await lastfm.json();
+ document.getElementById("songplays")!.innerText = json.user.playcount;
+ })();
+
+ const theme = localStorage.getItem("theme");
+ if (theme == "dark") {
+ bgColor = darkTheme;
+ darkMode = true;
+ } else {
+ bgColor = lightTheme;
+ darkMode = false;
+ }
+ document.body.setAttribute("data-theme", darkMode ? "dark" : "light");
+ const canvas = createCanvas(windowWidth, windowHeight);
+ canvas.position(0, 0);
+ canvas.style("zIndex", "-1");
+ background(bgColor);
+ const p = "sad.ovh"
+ nextWord(p);
+
+ let ticks = 0;
+
+ setInterval(() => {
+ const a = particles.filter(z => {
+ const x = Math.abs(Math.floor(z.vel.x))
+ const y = Math.abs(Math.floor(z.vel.y))
+ if (x == 1 || y == 1 || x == 0 || y == 0) {
+ return false;
+ } else {
+ return true;
+ }
+ }).length;
+
+ if (a == 0) ticks++;
+ if (a != 0) ticks = 0;
+
+ if (ticks == 60 * 8) {
+ ticks = 0;
+ nextWord(p)
+ }
+ }, 1)
+}
+function keyPressed(a: KeyboardEvent) {
+ if (a.code == "Space") {
+ if (darkMode) {
+ bgColor = lightTheme;
+ } else {
+ bgColor = darkTheme;
+ }
+ darkMode = !darkMode;
+ document.body.setAttribute("data-theme", darkMode ? "dark" : "light");
+ localStorage.setItem("theme", darkMode ? "dark" : "light");
+ }
+}
+function draw() {
+ fill(bgColor);
+ noStroke();
+ rect(0, 0, width * 2, height * 2);
+
+ for (let x = particles.length - 1; x > -1; x--) {
+ let particle = particles[x];
+ particle.move();
+ particle.draw();
+
+ if (particle.isKilled) {
+ if (
+ particle.pos.x < 0 ||
+ particle.pos.x > width ||
+ particle.pos.y < 0 ||
+ particle.pos.y > height
+ ) {
+ particles.splice(x, 1);
+ }
+ }
+ }
+}
+
+function mouseDragged() {
+ if (mouseButton == LEFT) {
+ for (const particle of particles) {
+ if (dist(particle.pos.x, particle.pos.y, mouseX, mouseY) < 50) {
+ particle.kill();
+ }
+ }
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..238655f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ // Enable latest features
+ "lib": ["ESNext", "DOM"],
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleDetection": "force",
+ "jsx": "react-jsx",
+ "allowJs": true,
+
+ // Bundler mode
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+
+ // Best practices
+ "strict": true,
+ "skipLibCheck": true,
+ "noFallthroughCasesInSwitch": true,
+
+ // Some stricter flags (disabled by default)
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noPropertyAccessFromIndexSignature": false
+ }
+}