commit de210640329525c3f652c1315002ed488eda7652 Author: Ashhhleyyy Date: Thu Jul 6 01:47:43 2023 +0100 feat: initial commit diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96fbb4f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +result/ +build/ +__pycache__/ +.direnv/ diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..a7b1084 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,19 @@ +platform: linux/arm64 + +pipeline: + build: + image: nixos/nix + commands: + - ./build.sh + deploy: + image: woodpeckerci/plugin-s3 + settings: + bucket: stories + source: build/**/* + target: / + path_style: true + endpoint: https://minio.ashhhleyyy.dev + access_key: + from_secret: minio_user + secret_key: + from_secret: minio_password diff --git a/assets/background.png b/assets/background.png new file mode 100644 index 0000000..5bc2efb Binary files /dev/null and b/assets/background.png differ diff --git a/assets/rain_fall.gif b/assets/rain_fall.gif new file mode 100644 index 0000000..396a9a8 Binary files /dev/null and b/assets/rain_fall.gif differ diff --git a/assets/rain_splash.gif b/assets/rain_splash.gif new file mode 100644 index 0000000..3ef6263 Binary files /dev/null and b/assets/rain_splash.gif differ diff --git a/assets/rainfall.gif b/assets/rainfall.gif new file mode 100644 index 0000000..4fa607e Binary files /dev/null and b/assets/rainfall.gif differ diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..4ce1d6d --- /dev/null +++ b/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +out_dir="$(nix --experimental-features 'nix-command flakes' build --no-link --print-out-paths)" +cp -r "$out_dir" ./build +# fix permissions that are copied from the nix store +chmod +w -R ./build diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..902a7ca --- /dev/null +++ b/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1671268780, + "narHash": "sha256-9Okbivo10bcXEGCtmAQNfJt1Zpk6B3tjkSQ2CIXmTCg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "80c24eeb9ff46aa99617844d0c4168659e35175f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..63edf9a --- /dev/null +++ b/flake.nix @@ -0,0 +1,30 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem(system: + let + pkgs = import nixpkgs { + inherit system; + }; + inherit (pkgs) lib; + in + { + packages.default = pkgs.stdenv.mkDerivation rec { + name = "ash-stories"; + src = ./.; + phases = "installPhase"; + installPhase = '' + mkdir -p $out/src + mkdir -p $out/stories + cp $src/stories/* $out/stories/ + cp $src/src/*.js $out/src/ + cp $src/src/*.css $out/src/ + ''; + }; + } + ); +} diff --git a/result b/result new file mode 120000 index 0000000..aefca3d --- /dev/null +++ b/result @@ -0,0 +1 @@ +/nix/store/p8pj5f23xgkfyqm8cf3k0ycdvj5aq2j2-ash-stories \ No newline at end of file diff --git a/src/background-music.js b/src/background-music.js new file mode 100644 index 0000000..3c4354c --- /dev/null +++ b/src/background-music.js @@ -0,0 +1,31 @@ +(function() { + const player = document.getElementById('background-music'); + if (!player) { + console.warn('no element with id `background-music`'); + return; + } + + let song = ''; + if (document.currentScript.dataset.song) { + song = ' (' + document.currentScript.dataset.song + ')'; + } + + const toggle = document.createElement('a'); + toggle.href = '#'; + toggle.innerText = 'šŸŽ¶ Enable music' + song; + toggle.addEventListener('click', function () { + if (player.paused) { + player.play(); + toggle.innerText = 'šŸŽ¶ Disable music' + song; + } else { + player.pause(); + toggle.innerText = 'šŸŽ¶ Enable music' + song; + } + }); + toggle.style.position = "fixed"; + toggle.style.bottom = "8px"; + toggle.style.right = "8px"; + toggle.style.color = "white"; + toggle.classList.add('music-toggle'); + document.body.appendChild(toggle); +})(); diff --git a/src/drifting.css b/src/drifting.css new file mode 100644 index 0000000..f048d7c --- /dev/null +++ b/src/drifting.css @@ -0,0 +1,77 @@ +@import url(https://fonts.bunny.net/css?family=nanum-pen-script:400); + +html, body { + font-family: 'Nanum Pen Script', handwriting; + font-size: 24px; + padding: 0; + margin: 0; + background-color: rgb(34, 32, 52); + overflow: hidden; +} + +.background { + width: 100vw; + height: 100vh; + position: fixed; + top: 0; + left: 0; + /*z-index: -100;*/ +} + +img.sharp { + image-rendering: pixelated; + image-rendering: crisp-edges; + image-rendering: -moz-crisp-edges; + image-rendering: -o-pixelated; + -ms-interpolation-mode: nearest-neighbor; +} + +#story { + color: white; + display: grid; + place-items: center; + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + font-size: 36px; + text-align: center; +} + +.page { + opacity: 1; + transition: opacity 500ms ease; +} + +.page.hidden { + display: none; +} + +.page.changing { + opacity: 0; +} + +.again { + font-family: inherit; + font-size: inherit; + background: none; + border: none; + color: white; + text-decoration: underline; + cursor: pointer; + margin-top: 2rem; + animation: late-fade 1000ms ease; +} + +@keyframes late-fade { + from { + opacity: 0; + } + 50% { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/src/make_rain.py b/src/make_rain.py new file mode 100644 index 0000000..e9cc6e0 --- /dev/null +++ b/src/make_rain.py @@ -0,0 +1,114 @@ +import math +from PIL import Image, ImageSequence +import random + +IMAGE_WIDTH = 256 +IMAGE_HEIGHT = 144 + +SPLASH_WIDTH = 9 +SPLASH_HEIGHT = 8 + +BACKGROUND_COLOUR = (34, 32, 52) +RAINDROP_COLOUR = (63, 63, 116) +WATER_COLOUR = (91, 110, 225) +FALL_SPEED = 2 + +def create_rain_column_frames(): + frames = [] + for y in range(0, IMAGE_HEIGHT - SPLASH_HEIGHT + 2, FALL_SPEED): + frame = Image.new('RGBA', (SPLASH_WIDTH, IMAGE_HEIGHT), (0,0,0)) + for x in range(frame.width): + frame.putpixel((x, frame.height - 1), WATER_COLOUR) + for yOffset in range(-1, 1): + finalY = y + yOffset + if finalY > 0 and finalY < frame.height: + frame.putpixel((int(SPLASH_WIDTH / 2), finalY), RAINDROP_COLOUR) + frames.append(frame) + return frames + +def create_splash_frames(): + frames = [] + with Image.open("assets/rain_splash.gif") as im: + for origFrame in ImageSequence.Iterator(im): + frame = Image.new('RGBA', (SPLASH_WIDTH, IMAGE_HEIGHT), BACKGROUND_COLOUR) + frame.paste(origFrame, (0, IMAGE_HEIGHT - origFrame.height)) + frames.append(frame) + return frames + +def save_gif(frames, filename, duration): + frames[0].save(filename, format='GIF', + append_images=frames[0:], save_all=True, duration=duration, + optimize=False, disposal=2, loop=0) + +def create_background(): + im = Image.new('RGBA', (IMAGE_WIDTH, IMAGE_HEIGHT), BACKGROUND_COLOUR) + for x in range(IMAGE_WIDTH): + im.putpixel((x, IMAGE_HEIGHT - 1), WATER_COLOUR) + im.save("assets/background.png") + +class RainParticle: + def __init__(self, x, image_width, image_height, height, colour, animation_offset, splash_frames, animation_delay, speed): + self.x = x + self.image_width = image_width + self.image_height = image_height + self.height = height + self.colour = colour + self.animation_offset = animation_offset + self.splash_frames = splash_frames + self.animation_delay = animation_delay + self.speed = speed + self.animation_duration = ((self.image_height + self.height) / self.speed) + self.splash_frames.n_frames + self.animation_delay + def draw(self, canvas, frame_number): + animationPosition = (frame_number + self.animation_offset) % self.animation_duration + if animationPosition < (self.image_height + self.height): + yPosition = animationPosition - self.height + for yOffset in range(self.height): + y = yPosition + yOffset + if y >= 0 and y < self.image_height: + canvas.putpixel((self.x, y), self.colour) + else: + frameNum = animationPosition - (self.image_height + self.height) + frame = self.splash_frames + frame.seek(frameNum) + xOffset = int(frame.width / 2) + y = self.image_height - frame.height + canvas.paste(frame, (self.x - xOffset, y)) + +def fully_animated(): + splash_frames = Image.open("assets/rain_splash.gif") + + random.seed(123456789) + particles = [] + for i in range(128): + particles.append(RainParticle( + i * 2, + IMAGE_WIDTH, + IMAGE_HEIGHT, + 5, + RAINDROP_COLOUR, + random.randint(0, IMAGE_HEIGHT), + splash_frames, + 0, + )) + + animationLength = math.lcm(*map(lambda p: p.animation_duration, particles)) + frames = [] + print(f"Generating {animationLength} frames...") + for frameNum in range(animationLength): + canvas = Image.new('RGBA', (IMAGE_WIDTH, IMAGE_HEIGHT), BACKGROUND_COLOUR) + for particle in particles: + particle.draw(canvas, frameNum) + frames.append(canvas) + + save_gif(frames, "assets/rainfall.gif", 5) + +def main(): + create_background() + fully_animated() + #column_frames = create_rain_column_frames() + #splash_frames = create_splash_frames() + #print(len(column_frames), len(splash_frames)) + #save_gif(column_frames + splash_frames, "assets/rain_fall.gif", 10) + +if __name__ == '__main__': + main() diff --git a/src/rain.js b/src/rain.js new file mode 100644 index 0000000..c19e54f --- /dev/null +++ b/src/rain.js @@ -0,0 +1,94 @@ +(function() { + function createRandomParticle(currentTime, width) { + return { + startTime: currentTime, + width: 3, + length: 75 + Math.random() * 50, + colour: '#3f3f74', + speed: 750 + Math.random() * 400, + x: Math.round(Math.random() * width), + }; + } + + let imgCount = 0; + function spawnSplash(x) { + const img = document.createElement('img'); + img.src = '../assets/rain_splash.gif?' + Math.random(); + const SCALE = 5; + img.width = 9 * SCALE; + img.height = 7 * SCALE; + img.classList.add('sharp'); + img.style.position = 'absolute'; + img.style.bottom = 0; + img.style.left = (x - 4 * SCALE) + 'px'; + document.body.appendChild(img); + imgCount += 1; + setTimeout(function() { + img.remove(); + imgCount -= 1; + }, 600); + } + + function drawParticle(time, particle, canvas, context) { + const t = time - particle.startTime; + const y = (t * particle.speed) - particle.length; + //console.log(y); + if (y > canvas.height) { + return true; + } + const gradient = context.createLinearGradient(particle.x, y, particle.x, y + particle.length); + gradient.addColorStop(0, particle.colour + '00'); + gradient.addColorStop(1, particle.colour + 'ff'); + context.fillStyle = gradient; + context.fillRect(particle.x, y, particle.width, particle.length); + return false; + } + + let particles = []; + let nextSpawnTime = -1; + const canvas = document.getElementById("rainfall"); + + function init() { + particles = []; + nextSpawnTime = -1; + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + } + + const particleCount = document.getElementById('particle-count'); + function update(t) { + // Convert microseconds -> seconds + const time = t / 1000; + const context = canvas.getContext('2d'); + context.fillStyle = '#222034'; + context.fillRect(0, 0, canvas.width, canvas.height); + const particlesForRemoval = []; + for (const particle of particles) { + const r = drawParticle(time, particle, canvas, context); + if (r) { + particlesForRemoval.push(particle); + //spawnSplash(particle.x); + } + } + particlesForRemoval.forEach(function (p) { + particles.splice(particles.indexOf(p), 1) + }); + particleCount.innerText = 'Particles: ' + particles.length; + while (nextSpawnTime === -1 || time > nextSpawnTime) { + particles.push(createRandomParticle(nextSpawnTime, canvas.width)); + if (nextSpawnTime === -1) { + nextSpawnTime = time; + } else { + nextSpawnTime = nextSpawnTime + Math.random() * 0.01; + } + } + requestAnimationFrame(update); + } + + init(); + requestAnimationFrame(update); + + window.addEventListener("resize", function() { + init(); + }) +})(); \ No newline at end of file diff --git a/src/story-player.js b/src/story-player.js new file mode 100644 index 0000000..7172598 --- /dev/null +++ b/src/story-player.js @@ -0,0 +1,55 @@ +(function() { + const story = document.getElementById('story'); + if (!story) { + console.warn('Could not locate story element'); + return; + } + + const pages = []; + for (let i = 0; i < story.children.length; i++) { + const ele = story.children.item(i); + pages.push(ele); + } + + let currentPage = 0; + + function displayPage(index) { + if (index >= pages.length) return; + currentPage = index; + const pageEle = pages[index]; + if (!(pageEle.classList.contains('hidden') || pageEle.classList.contains('changing'))) { + // Page is already active + return; + } + for (const page of pages) { + page.classList.add('changing'); + } + setTimeout(function() { + for (const page of pages) { + if (page !== pageEle) { + page.classList.remove('changing'); + } + page.classList.add('hidden'); + } + pageEle.classList.remove('hidden'); + setTimeout(function() { + pageEle.classList.remove('changing'); + }, 100); + }, 500); + } + + for (const page of pages) { + page.classList.add('page'); + page.classList.add('hidden'); + } + + displayPage(0); + window.addEventListener('keydown', function(ev) { + if (ev.key === 'ArrowDown' || ev.key === ' ') { + displayPage(currentPage + 1); + } + }); + + // Allow usage from event handlers + window.displayStoryPage = displayPage; +})(); diff --git a/stories/drifting.html b/stories/drifting.html new file mode 100644 index 0000000..39b4dad --- /dev/null +++ b/stories/drifting.html @@ -0,0 +1,43 @@ + + + + + + Drifting + + + + + + + + +
+
+

Drifting

+

By Ashley B

+

Press space to begin

+
+

When Iā€™m down

+

And no-one's around

+

It's like I'm drifting

+

Drifting without direction

+

Drifiting without control

+

+ Drifting to some place unknown +
+ +

+
+ + + + + + + +