aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--deno.lock4
-rw-r--r--package.json1
-rw-r--r--pnpm-lock.yaml51
-rw-r--r--src/user/ThreeVideo.ts47
-rw-r--r--src/user/index.ts246
5 files changed, 309 insertions, 40 deletions
diff --git a/deno.lock b/deno.lock
index df9d81e..b88a794 100644
--- a/deno.lock
+++ b/deno.lock
@@ -1018,16 +1018,20 @@
],
"packageJson": {
"dependencies": [
+ "npm:@ffmpeg/ffmpeg@~0.12.15",
+ "npm:@ffmpeg/util@~0.12.2",
"npm:@sveltejs/adapter-static@3.0.8",
"npm:@sveltejs/kit@2.19.0",
"npm:@sveltejs/vite-plugin-svelte@5.0.3",
"npm:@tailwindcss/vite@4.1.11",
+ "npm:@types/three@~0.178.1",
"npm:esbuild@0.25.1",
"npm:prettier-plugin-svelte@3.3.3",
"npm:prettier@3.5.3",
"npm:svelte-check@4.1.5",
"npm:svelte@5.23.0",
"npm:tailwindcss@4.0.13",
+ "npm:three@0.178",
"npm:typescript@5.8.2",
"npm:vite@6.2.1"
]
diff --git a/package.json b/package.json
index 72507e8..1a48225 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"@sveltejs/kit": "2.19.0",
"@sveltejs/vite-plugin-svelte": "5.0.3",
"@tailwindcss/vite": "4.1.11",
+ "@types/three": "^0.178.1",
"esbuild": "0.25.1",
"prettier": "3.5.3",
"prettier-plugin-svelte": "3.3.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ae7f10c..7ca5702 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -30,6 +30,9 @@ importers:
'@tailwindcss/vite':
specifier: 4.1.11
version: 4.1.11(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1))
+ '@types/three':
+ specifier: ^0.178.1
+ version: 0.178.1
esbuild:
specifier: 0.25.1
version: 0.25.1
@@ -61,6 +64,9 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
+ '@dimforge/rapier3d-compat@0.12.0':
+ resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
+
'@esbuild/aix-ppc64@0.25.1':
resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==}
engines: {node: '>=18'}
@@ -467,12 +473,27 @@ packages:
peerDependencies:
vite: ^5.2.0 || ^6 || ^7
+ '@tweenjs/tween.js@23.1.3':
+ resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
+
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+ '@types/stats.js@0.17.4':
+ resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==}
+
+ '@types/three@0.178.1':
+ resolution: {integrity: sha512-WSabew1mgWgRx2RfLfKY+9h4wyg6U94JfLbZEGU245j/WY2kXqU0MUfghS+3AYMV5ET1VlILAgpy77cB6a3Itw==}
+
+ '@types/webxr@0.5.22':
+ resolution: {integrity: sha512-Vr6Stjv5jPRqH690f5I5GLjVk8GSsoQSYJ2FVd/3jJF7KaqfwPi3ehfBS96mlQ2kPCwZaX6U0rG2+NGHBKkA/A==}
+
+ '@webgpu/types@0.1.64':
+ resolution: {integrity: sha512-84kRIAGV46LJTlJZWxShiOrNL30A+9KokD7RB3dRCIqODFjodS5tCD5yyiZ8kIReGVZSDfA3XkkwyyOIF6K62A==}
+
acorn@8.14.1:
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
engines: {node: '>=0.4.0'}
@@ -545,6 +566,9 @@ packages:
picomatch:
optional: true
+ fflate@0.8.2:
+ resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
+
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -637,6 +661,9 @@ packages:
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
+ meshoptimizer@0.18.1:
+ resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
+
minipass@7.1.2:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -808,6 +835,8 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25
+ '@dimforge/rapier3d-compat@0.12.0': {}
+
'@esbuild/aix-ppc64@0.25.1':
optional: true
@@ -1089,10 +1118,28 @@ snapshots:
tailwindcss: 4.1.11
vite: 6.2.1(jiti@2.4.2)(lightningcss@1.30.1)
+ '@tweenjs/tween.js@23.1.3': {}
+
'@types/cookie@0.6.0': {}
'@types/estree@1.0.6': {}
+ '@types/stats.js@0.17.4': {}
+
+ '@types/three@0.178.1':
+ dependencies:
+ '@dimforge/rapier3d-compat': 0.12.0
+ '@tweenjs/tween.js': 23.1.3
+ '@types/stats.js': 0.17.4
+ '@types/webxr': 0.5.22
+ '@webgpu/types': 0.1.64
+ fflate: 0.8.2
+ meshoptimizer: 0.18.1
+
+ '@types/webxr@0.5.22': {}
+
+ '@webgpu/types@0.1.64': {}
+
acorn@8.14.1: {}
aria-query@5.3.2: {}
@@ -1160,6 +1207,8 @@ snapshots:
fdir@6.4.3: {}
+ fflate@0.8.2: {}
+
fsevents@2.3.3:
optional: true
@@ -1226,6 +1275,8 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
+ meshoptimizer@0.18.1: {}
+
minipass@7.1.2: {}
minizlib@3.0.2:
diff --git a/src/user/ThreeVideo.ts b/src/user/ThreeVideo.ts
index bbce038..92615f1 100644
--- a/src/user/ThreeVideo.ts
+++ b/src/user/ThreeVideo.ts
@@ -1,8 +1,53 @@
import { Video as BaseVideo, type FrameTime, type InitConfig } from '$/lib/Player/Video';
import * as THREE from 'three';
+import type { OrbitControls } from 'three/examples/jsm/Addons.js';
+
+export const OnceCell = <T>(create: () => T) => {
+ let called = false,
+ cached = null as unknown as T
+ return () => {
+ if (called) return cached
+ else { cached = create(); called = true; return cached }
+ }
+}
export default abstract class ThreeVideo extends BaseVideo {
- public async init(config: InitConfig): Promise<void> {
+ protected abstract ctx: CanvasRenderingContext2D
+ protected scene!: THREE.Scene;
+ protected camera!: THREE.PerspectiveCamera;
+ protected renderer!: THREE.WebGLRenderer;
+ protected threeCanvas!: HTMLCanvasElement;
+ protected orbitControls?: OrbitControls;
+ public async init(_config: InitConfig): Promise<void> {
+ this.scene = new THREE.Scene();
+ this.camera = new THREE.PerspectiveCamera(75, this.w / this.h, 0.1, 1000);
+ const canvas = this.threeCanvas = document.createElement('canvas');
+ canvas.width = this.canvas.width
+ canvas.height = this.canvas.height
+ canvas.style.opacity = "0"
+ canvas.style.position = "fixed";
+ canvas.style.top = "1000vh"
+ canvas.style.left = "1000vw"
+ document.body.appendChild(canvas)
+
+ this.renderer = new THREE.WebGLRenderer({
+ canvas: canvas,
+ alpha: true,
+ powerPreference: 'high-performance',
+ });
+ this.renderer.setSize(this.w, this.h);
+ this.renderer.setAnimationLoop(() => this.renderScene());
+ }
+ public renderScene(ctx?: CanvasRenderingContext2D) {
+ if (this.orbitControls)
+ this.orbitControls.update()
+ this.renderer.render(this.scene, this.camera)
+ if (ctx)
+ ctx.drawImage(this.threeCanvas, 0, 0)
+ }
+ public cleanup(): void {
+ this.renderer.dispose()
+ this.threeCanvas.remove()
}
}
diff --git a/src/user/index.ts b/src/user/index.ts
index fb0001e..541cd94 100644
--- a/src/user/index.ts
+++ b/src/user/index.ts
@@ -1,6 +1,8 @@
import { type FrameTime, type InitConfig } from '$/lib/Player/Video';
-import ThreeVideo from './ThreeVideo';
-import AudioURL from './03. Lemaitre, Jennie A. - Closer - 2min version.flac?url'
+import ThreeVideo, { OnceCell } from './ThreeVideo';
+import AudioURL from './03. Lemaitre, Jennie A. - Closer - 40sec version.flac?url'
+import * as THREE from 'three';
+import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js';
type FontInfo = {
family: string, size: number, weight?: number
@@ -16,7 +18,21 @@ const getTextSize = (ctx: CanvasRenderingContext2D, text: string, fontInfo: Font
return ctx.measureText(text)
}
+const lerp = (t: number, initial: number, final: number) => {
+ return initial + ((final - initial) * t)
+}
+const bezier = (t: number, initial: number, p1: number, p2: number, final: number) => {
+ return (1 - t) * (1 - t) * (1 - t) * initial
+ +
+ 3 * (1 - t) * (1 - t) * t * p1
+ +
+ 3 * (1 - t) * t * t * p2
+ +
+ t * t * t * final;
+}
+
export default class Video extends ThreeVideo {
+ protected ctx!: CanvasRenderingContext2D
protected isPreview = false;
protected px(pixels: number) {
return this.isPreview ? pixels / 1.5 : pixels
@@ -25,6 +41,10 @@ export default class Video extends ThreeVideo {
const { isPreview } = config
this.isPreview = isPreview
this.resize(this.px(1920), this.px(1080))
+ this.ctx = this.canvas.getContext('2d', {
+ willReadFrequently: !isPreview,
+ desynchronized: isPreview,
+ })!
const threeInit = super.init(config).catch(e => ([1, e] as const))
// const v = document.createElement('video')
@@ -44,47 +64,195 @@ export default class Video extends ThreeVideo {
const rs = await threeInit
if (rs && rs[0] === 1) { console.error(rs[1]); throw new Error('Failed to initialize ThreeJS!'); }
}
+ protected uiGeometry = OnceCell(() => new THREE.BoxGeometry(0.1, 5, 7));
+ protected uiDarkMaterial = OnceCell(() => new THREE.MeshStandardMaterial({
+ roughness: 0.8,
+ color: 0xffffff,
+ metalness: 0.2,
+ bumpScale: 1
+ }));
+ protected uiDark = OnceCell(() => new THREE.Mesh(this.uiGeometry(), this.uiDarkMaterial()))
+ protected uiLightMaterial = OnceCell(() => new THREE.MeshBasicMaterial({ color: 0x000000 }))
+ protected uiLight = OnceCell(() => new THREE.Mesh(this.uiGeometry(), this.uiLightMaterial()))
+ protected lighting = OnceCell(() => {
+ const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+ dirLight.castShadow = true;
+ dirLight.shadow.camera.top = 0;
+ dirLight.shadow.camera.bottom = 0;
+ dirLight.shadow.camera.left = 0;
+ dirLight.shadow.camera.right = 0;
+ dirLight.shadow.camera.near = 0.1;
+ dirLight.shadow.camera.far = 90;
+
+ const cam = dirLight.shadow.camera;
+ cam.top = cam.right = 0;
+ cam.bottom = cam.left = 0;
+ cam.near = 3;
+ cam.far = 8;
+ dirLight.shadow.mapSize.set(1024, 1024);
+
+ return dirLight;
+ })
+ protected uiCanvas = OnceCell(() => {
+ const c = document.createElement('canvas');
+ c.width = this.px(1000);
+ c.height = c.width / 5 * 7;
+ return c;
+ });
+ protected uiCanvasCtx = OnceCell(() => this.uiCanvas().getContext('2d', {
+ alpha: true,
+ }))
+ protected drawUiCanvas() { }
public renderFrame(time: FrameTime): Promise<void> | void {
const beat = 1 + ((time.seconds - 0.098) * (92 / 60))
const center = [this.w / 2, this.h / 2] as const
- // if (beat < 13 || beat >= 16) {
- // this.ctx.fillStyle = '#fff';
- // this.ctx.fillRect(0, 0, this.w, this.h);
- // } else {
- // this.ctx.fillStyle = '#000';
- // this.ctx.fillRect(0, 0, this.w, this.h);
- // }
- // const AdDefault: FontInfo = {
- // family: 'Inter Variable',
- // size: this.px(52),
- // weight: 450
- // }
- // if (this.isPreview)
- // renderText(this.ctx, `${(Math.floor(beat * 100) / 100).toFixed(1)}`, '#646663', { ...AdDefault, size: this.px(12), weight: 400 }, { x: this.w - this.px(4), y: this.h - this.px(4) }, 'end')
- // switch (true) {
- // case beat >= 1 && beat < 4.3:
- // renderText(this.ctx, `Need a new AI assistant?`, '#646663', AdDefault, { x: center[0], y: center[1] }, 'center')
- // break;
- // case beat >= 4.3 && beat < 8.4: {
- // const text = `Like${beat >= 4.8 ? ' new' : ''}${beat >= 5.02 ? ' new' : ''}`;
- // const longTextWidth = getTextSize(this.ctx, 'Like new new', AdDefault)
- // renderText(this.ctx, text, '#646663', AdDefault, { x: center[0] - longTextWidth.width / 2, y: center[1] }, 'start')
- // break;
- // }
- // case beat >= 8.4 && beat < 13: {
- // const text = `Like${beat >= 9 ? ` runs random shit in your terminal` : ''}${beat >= 11 ? ' new' : ''}`;
- // const longTextWidth = getTextSize(this.ctx, `Like runs random shit in your terminal new`, AdDefault)
- // renderText(this.ctx, text, '#646663', AdDefault, { x: center[0] - longTextWidth.width / 2, y: center[1] }, 'start')
- // break;
- // }
- // // TODO:
+ this.ctx.fillStyle = '#fff';
+ this.ctx.fillRect(0, 0, this.w, this.h);
+
+ this.scene.background = null;
+
+ const AdDefault: FontInfo = {
+ // family: 'Inter Variable',
+ family: 'Adwaita Sans',
+ size: this.px(58),
+ weight: 450
+ }
+ switch (true) {
+ case beat < 1:
+ break;
+ case beat >= 1 && beat < 4.3:
+ renderText(this.ctx, `Need a new AI assistant?`, '#646663', AdDefault, { x: center[0], y: center[1] }, 'center')
+ break;
+ case beat >= 4.3 && beat < 8.4: {
+ const text = `Like${beat >= 4.8 ? ' new' : ''}${beat >= 5.02 ? ' new?' : ''}`;
+ const longTextWidth = getTextSize(this.ctx, 'Like new new?', AdDefault)
+ renderText(this.ctx, text, '#646663', AdDefault, { x: center[0] - longTextWidth.width / 2, y: center[1] }, 'start')
+ break;
+ }
+ case beat >= 8.4 && beat < 13: {
+ const text = `Like${beat >= 9 ? ` has a hyprminimal design` : ''}${beat >= 11 ? ' new' : ''}`;
+ const longTextWidth = getTextSize(this.ctx, `Like has a hyprminimal design new`, AdDefault)
+ renderText(this.ctx, text, '#646663', AdDefault, { x: center[0] - longTextWidth.width / 2, y: center[1] }, 'start')
+ break;
+ }
+ case beat >= 13 && beat < 15: {
+ this.scene.background = new THREE.Color(0x000000);
- // default:
- // break;
- // }
+ this.scene.add(this.lighting())
+
+ this.scene.add(this.uiDark());
+ const progress = (beat - 13) / 3.5
+ this.uiDark().rotation.x = bezier(progress, 0.4, 0.6, 0.6, 1.1);
+ this.uiDark().rotation.y = bezier(progress, 2, 1.7, 1.6, bezier(progress, 1, 0.8, 0.3, -0.5));
+ this.camera.position.z = bezier(progress, 6, 4, 4, bezier(progress, 7, 9, 15, 25));
+ this.lighting().position.set(0, 0, this.camera.position.z);
+ this.renderScene(this.ctx)
+ this.scene.remove(this.uiDark());
+ this.scene.remove(this.lighting())
+ break;
+ }
+ case beat >= 15 && beat < 16: {
+ this.scene.background = new THREE.Color(0x000000);
+
+ this.scene.add(this.lighting())
+
+ this.scene.add(this.uiDark());
+ const progress = (beat - 15)
+ this.uiDark().rotation.x = bezier(progress, -0.5, -0.5, -0.5, -0.5);
+ this.uiDark().rotation.y = bezier(progress, 0.7, 0.6, 0.4, 0.4);
+ this.camera.position.z = bezier(progress, 4, 5, 5, 8);
+ this.lighting().position.set(-0.5, 0, this.camera.position.z);
+ this.renderScene(this.ctx)
+ this.scene.remove(this.uiDark());
+ this.scene.remove(this.lighting())
+ break;
+ }
+ case beat >= 16 && beat < 18.4: {
+ this.scene.add(this.uiLight());
+ const progress = (beat - 16) / 3.5
+ this.uiLight().rotation.x = bezier(progress, -0.5, 0.6, 0.6, 1.1) * (beat < 17 ? 1 : -1);
+ this.uiLight().rotation.y = bezier(progress, 0.4, 1.7, 1.8, 2) * (beat < 17 ? 1 : -1);
+ this.camera.position.z = bezier(progress, 8, 4, 4, 12);
+
+ this.renderScene(this.ctx)
+ this.scene.remove(this.uiLight());
+ break;
+ }
+ case beat >= 18.4 && beat < 22.8: {
+ const text = `Efficiency ${beat >= 19 ? `so hyprefficient, ` : ''}${beat >= 20 ? 'we created it' : ''}${beat >= 21 ? ' hypr' + (beat >= 22.1 ? 'new' : '') : ''}`;
+ const longTextWidth = getTextSize(this.ctx, `Efficiency so hyprefficient, we created it hyprnew`, AdDefault)
+ renderText(this.ctx, text, '#646663', AdDefault, { x: center[0] - longTextWidth.width / 2, y: center[1] }, 'start')
+ break;
+ }
+ case beat >= 22.8 && beat < 25:
+ // TODO: add animation for hyprefficient
+ renderText(this.ctx, `TODO`, '#ff000099', AdDefault, { x: center[0], y: center[1] }, 'center')
+ break;
+ case beat >= 25 && beat < 31 || beat < 33: {
+ const text = `1 month free ${beat >= 26.5 ? 'for a hypr' : ''}${beat >= 26.7 ? 'local Mistral 7b' : ''}${beat >= 29 ? ' new' : ''}`;
+ const longTextWidth = getTextSize(this.ctx, `1 month free for a hyprlocal Mistral 7b new`, AdDefault)
+ renderText(this.ctx, text, '#646663', AdDefault, { x: center[0] - longTextWidth.width / 2, y: center[1] }, 'start')
+ renderText(this.ctx, '*Not guaranteed to work. Subscriptions start at 69.99CHF/mo and get billed on the first of the next month automatically.', '#64666366', { ...AdDefault, size: 12, weight: 400 }, { x: center[0], y: this.h - this.px(44) }, 'center')
+ renderText(this.ctx, 'Cancellable only within 12 hours of the first day of the month.', '#64666366', { ...AdDefault, size: 12, weight: 400 }, { x: center[0], y: this.h - this.px(24) }, 'center')
+ break;
+ }
+ case beat >= 31 && beat < 33://&& beat < 33:
+ // TODO: add animation for hyprsubscription
+ renderText(this.ctx, `i've already dumped too much time into this pls contribute`, '#ff000099', AdDefault, { x: center[0], y: center[1] }, 'center')
+ break;
+ case beat >= 33 && beat < 41: {
+ if (beat >= 34.75) {
+ this.ctx.fillStyle = '#23f';
+ this.ctx.fillRect(0, 0, this.w, this.h);
+ // renderText(this.ctx, `Need a new AI assistant?`, '#646663', AdDefault, { x: center[0], y: center[1] }, 'center')
+ }
+ const text = `Accents ${beat >= 34.5 ? `so ` : ''}${beat >= 34.75 ? 'hyprblue, ' : ''}${beat >= 35.8 ? 'we created them' : ''}${beat >= 37 ? ' hypr' + (beat >= 38.1 ? 'new' : '') : ''}`;
+ const longTextWidth = getTextSize(this.ctx, `Accents so hyprblue, we created them hyprnew`, AdDefault)
+ renderText(this.ctx, text, beat >= 34.75 ? '#ffffff' : '#646663', AdDefault, { x: center[0] - longTextWidth.width / 2, y: center[1] }, 'start')
+ break;
+ }
+
+ case beat >= 49 && beat < 56.8: {
+ const opacity1 = Math.min(Math.max(Math.floor(255 - lerp((beat - 50) * 1.1, 255, 0)), 0), 255).toString(16).padStart(2, '0')
+ const opacity2 = Math.min(Math.max(Math.floor(255 - lerp((beat - 52.7) * 1.1, 255, 0)), 0), 255).toString(16).padStart(2, '0')
+ const text1 = `Introducing `;
+ const text2 = `HyprAI`;
+ const longTextWidth = getTextSize(this.ctx, `Introducing HyprAI`, AdDefault)
+ const shortTextWidth = getTextSize(this.ctx, `Introducing `, AdDefault)
+ renderText(this.ctx, text1, '#646663' + opacity1, AdDefault, { x: center[0] - longTextWidth.width / 2, y: center[1] }, 'start')
+ renderText(this.ctx, text2, '#646663' + opacity2, AdDefault, { x: (center[0] - longTextWidth.width / 2) + shortTextWidth.width, y: center[1] }, 'start')
+ break;
+ }
+ case beat >= 56.8 && beat < 67: {
+ renderText(this.ctx, beat < 61 ? `AI assistant by Hyprland` : 'Who knew?', '#646663', AdDefault, { x: center[0], y: center[1] }, 'center')
+ if (beat < 61)
+ renderText(this.ctx, `*This ad is satire vaxry please don't sue me`, '#64666366', {
+ ...AdDefault,
+ size: 12,
+ weight: 400
+ }, { x: center[0], y: this.h - this.px(24) }, 'center')
+ // fall-thru
+ }
+ case beat >= 64: {
+ if (beat >= 64) {
+ // needed due to fallthru
+ // TODO: animate ui going up over text
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ if (this.isPreview)
+ renderText(this.ctx, `${(Math.floor(beat * 10) / 10).toFixed(1)}`, '#646663', { ...AdDefault, size: this.px(12), weight: 400 }, { x: this.w - this.px(4), y: this.h - this.px(4) }, 'end')
+ }
+ public cleanup(): void {
+ this.uiCanvas().remove()
+ super.cleanup()
}
- // public fps = 59.94;
- public fps = 30;
- public length = Math.ceil(2 * 60 * this.fps);
+ public fps = 59.94;
+ // public fps = 119.88;
+ // public fps = 30;
+ public length = Math.ceil(43.5 * this.fps);
public audioUrl = ['03. Lemaitre, Jennie A. - Closer.flac', AudioURL] as const
}