diff --git a/website/assets/binkies/lel.gif b/website/assets/binkies/lel.gif new file mode 100644 index 0000000..19e2ab7 Binary files /dev/null and b/website/assets/binkies/lel.gif differ diff --git a/website/assets/cursor.ani b/website/assets/cursor.ani deleted file mode 100644 index ceb9fb4..0000000 Binary files a/website/assets/cursor.ani and /dev/null differ diff --git a/website/index.html b/website/index.html index 2103876..79bb2c0 100644 --- a/website/index.html +++ b/website/index.html @@ -7,7 +7,6 @@ __TEMPLATE_HEAD__ - diff --git a/website/scripts/ani_parser.ts b/website/scripts/ani_parser.ts deleted file mode 100644 index 7fc7baf..0000000 --- a/website/scripts/ani_parser.ts +++ /dev/null @@ -1,225 +0,0 @@ -// Code by -// https://www.npmjs.com/package/ani-cursor - -// When importing the NPM package it just doesn't work. -// The babel version is so old it doesn't work on my browser. - -import { RIFFFile } from "riff-file"; -import { unpackArray, unpackString } from "byte-data"; - -type Chunk = { - format: string; - chunkId: string; - chunkData: { - start: number; - end: number; - }; - subChunks: Chunk[]; -}; - -// https://www.informit.com/articles/article.aspx?p=1189080&seqNum=3 -type AniMetadata = { - cbSize: number; // Data structure size (in bytes) - nFrames: number; // Number of images (also known as frames) stored in the file - nSteps: number; // Number of frames to be displayed before the animation repeats - iWidth: number; // Width of frame (in pixels) - iHeight: number; // Height of frame (in pixels) - iBitCount: number; // Number of bits per pixel - nPlanes: number; // Number of color planes - iDispRate: number; // Default frame display rate (measured in 1/60th-of-a-second units) - bfAttributes: number; // ANI attribute bit flags -}; - -type ParsedAni = { - rate: number[] | null; - seq: number[] | null; - images: Uint8Array[]; - metadata: AniMetadata; - artist: string | null; - title: string | null; -}; - -const DWORD = { bits: 32, be: false, signed: false, fp: false }; - -export function parseAni(arr: Uint8Array): ParsedAni { - const riff = new RIFFFile(); - - riff.setSignature(arr); - - const signature = riff.signature as Chunk; - if (signature.format !== "ACON") { - throw new Error( - `Expected format. Expected "ACON", got "${signature.format}"` - ); - } - - // Helper function to get a chunk by chunkId and transform it if it's non-null. - function mapChunk(chunkId: string, mapper: (chunk: Chunk) => T): T | null { - const chunk = riff.findChunk(chunkId) as Chunk | null; - return chunk == null ? null : mapper(chunk); - } - - function readImages(chunk: Chunk, frameCount: number): Uint8Array[] { - return chunk.subChunks.slice(0, frameCount).map((c) => { - if (c.chunkId !== "icon") { - throw new Error(`Unexpected chunk type in fram: ${c.chunkId}`); - } - return arr.slice(c.chunkData.start, c.chunkData.end); - }); - } - - const metadata = mapChunk("anih", (c) => { - const words = unpackArray(arr, DWORD, c.chunkData.start, c.chunkData.end); - return { - cbSize: words[0], - nFrames: words[1], - nSteps: words[2], - iWidth: words[3], - iHeight: words[4], - iBitCount: words[5], - nPlanes: words[6], - iDispRate: words[7], - bfAttributes: words[8], - }; - }); - - if (metadata == null) { - throw new Error("Did not find anih"); - } - - const rate = mapChunk("rate", (c) => { - return unpackArray(arr, DWORD, c.chunkData.start, c.chunkData.end); - }); - // chunkIds are always four chars, hence the trailing space. - const seq = mapChunk("seq ", (c) => { - return unpackArray(arr, DWORD, c.chunkData.start, c.chunkData.end); - }); - - const lists = riff.findChunk("LIST", true) as Chunk[] | null; - const imageChunk = lists?.find((c) => c.format === "fram"); - if (imageChunk == null) { - throw new Error("Did not find fram LIST"); - } - - let images = readImages(imageChunk, metadata.nFrames); - - let title: string | null = null; - let artist: string | null = null; - - const infoChunk = lists?.find((c) => c.format === "INFO"); - if (infoChunk != null) { - infoChunk.subChunks.forEach((c) => { - switch (c.chunkId) { - case "INAM": - title = unpackString(arr, c.chunkData.start, c.chunkData.end); - break; - case "IART": - artist = unpackString(arr, c.chunkData.start, c.chunkData.end); - break; - case "LIST": - // Some cursors with an artist of "Created with Take ONE 3.5 (unregisterred version)" seem to have their frames here for some reason? - if (c.format === "fram") { - images = readImages(c, metadata.nFrames); - } - break; - - default: - // Unexpected subchunk - } - }); - } - - return { images, rate, seq, metadata, artist, title }; -} - -type AniCursorImage = { - frames: { - url: string; - percents: number[]; - }[]; - duration: number; -}; - -const JIFFIES_PER_MS = 1000 / 60; - -// Generate CSS for an animated cursor. -// -// This function returns CSS containing a set of keyframes with embedded Data -// URIs as well as a CSS rule to the given selector. -export function convertAniBinaryToCSS( - selector: string, - aniBinary: Uint8Array -): string { - const ani = readAni(aniBinary); - - const animationName = `ani-cursor-${uniqueId()}`; - - const keyframes = ani.frames.map(({ url, percents }) => { - const percent = percents.map((num) => `${num}%`).join(", "); - return `${percent} { cursor: url(${url}), auto; }`; - }); - - // CSS properties with a animation type of "discrete", like `cursor`, actually - // switch half-way _between_ each keyframe percentage. Luckily this half-way - // measurement is applied _after_ the easing function is applied. So, we can - // force the frames to appear at exactly the % that we specify by using - // `timing-function` of `step-end`. - // - // https://drafts.csswg.org/web-animations-1/#discrete - const timingFunction = "step-end"; - - // Winamp (re)starts the animation cycle when your mouse enters an element. By - // default this approach would cause the animation to run continuously, even - // when the cursor is not visible. To match Winamp's behavior we add a - // `:hover` pseudo selector so that the animation only runs when the cursor is - // visible. - const pseudoSelector = ":hover"; - - // prettier-ignore - return ` - @keyframes ${animationName} { - ${keyframes.join("\n")} - } - ${selector}${pseudoSelector} { - animation: ${animationName} ${ani.duration}ms ${timingFunction} infinite; - } - `; -} - -function readAni(contents: Uint8Array): AniCursorImage { - const ani = parseAni(contents); - const rate = ani.rate ?? ani.images.map(() => ani.metadata.iDispRate); - const duration = sum(rate); - - const frames = ani.images.map((image) => ({ - url: curUrlFromByteArray(image), - percents: [] as number[], - })); - - let elapsed = 0; - rate.forEach((r, i) => { - const frameIdx = ani.seq ? ani.seq[i] : i; - frames[frameIdx].percents.push((elapsed / duration) * 100); - elapsed += r; - }); - - return { duration: duration * JIFFIES_PER_MS, frames }; -} - -/* Utility Functions */ - -let i = 0; -const uniqueId = () => i++; - -function base64FromDataArray(dataArray: Uint8Array): string { - return globalThis.window ? window.btoa(String.fromCharCode(...dataArray)) : Buffer.from(dataArray).toString("base64"); -} - -function curUrlFromByteArray(arr: Uint8Array) { - const base64 = base64FromDataArray(arr); - return `data:image/x-win-bitmap;base64,${base64}`; -} - -function sum(values: number[]): number { - return values.reduce((total, value) => total + value, 0); -} \ No newline at end of file diff --git a/website/scripts/binkies.ts b/website/scripts/binkies.ts index 743c329..284d02f 100644 --- a/website/scripts/binkies.ts +++ b/website/scripts/binkies.ts @@ -25,7 +25,8 @@ const binky = [ ["https://bee.nekoweb.org/", "bee.png"], ["https://ultrakill.nekoweb.org/", "ultrakill.png"], ["https://immjs.dev", "immjs.gif"], - + ["https://lel.nekoweb.org", "lel.gif"], + ["insane.gif", "piracy.gif", "dsb.gif", "universe.gif", "css.png", "vscbutton.gif", "ezgif.gif"], ]; diff --git a/website/scripts/cursor.ts b/website/scripts/cursor.ts deleted file mode 100644 index 2e14733..0000000 --- a/website/scripts/cursor.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { convertAniBinaryToCSS } from "./ani_parser"; -(async () => { - const req = await fetch("/assets/cursor.ani"); - const buf = new Uint8Array(await req.arrayBuffer()); - const style = document.createElement("style"); - style.innerHTML = convertAniBinaryToCSS("body", buf); - document.body.appendChild(style); -})();