diff options
Diffstat (limited to 'src/routes/shared.ts')
-rw-r--r-- | src/routes/shared.ts | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/src/routes/shared.ts b/src/routes/shared.ts new file mode 100644 index 0000000..a5ff683 --- /dev/null +++ b/src/routes/shared.ts @@ -0,0 +1,485 @@ +/* + Copyright (C) 2024 memdmp + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +export const biosSteps = 3; +export const biosStepInterval = 1000; + +const wpmToTypingSpeed = (wpm: number, avgWordLen: number) => + (1 / (wpm * avgWordLen / 60)) * 1000; + +const averageWordLength = 4.793; +const typingInfo = (wpm = 80) => ({ + typingSpeedAvg: wpmToTypingSpeed(wpm, averageWordLength), + typingDeviation: 20, // typing speed deviation is often correlated to the typing speed - TODO: at way lower speeds, the correlation is inverse; the more deviation there is (is this also the case for fast typing? what is the inflection point if not? i forgor i went down a rabbit hole yrs ago) +}); +export type TypingSpeed = ReturnType<typeof typingInfo>; +export const getDelay = (typingInfo: TypingSpeed) => + (Math.random() * 2 - 1) * typingInfo.typingDeviation + + typingInfo.typingSpeedAvg; +export const login = { + username: 'lain', + passwordLength: 12, + ...typingInfo(100), // one usually has muscle memory for well-known frequently-typed credentials, hence higher wpm +}; +export const ttyTypingInfo = typingInfo(); + +export type RenderBlock = { + value: string; + colour?: string; + weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; + italic?: boolean; + underlined?: boolean; + url?: + | `newtab:${string}` + | `currenttab:${string}` + | ((textObj: TTYText & { kind: 'text' }) => void); + bg?: string; + raw?: boolean; + dl?: string; +}; +export type TTYText = { + kind: 'text'; + renderrestriction?: 'everywhere' | 'js-only' | 'noscript'; + value: RenderBlock[]; + id: string; + classes: string[]; +} | { + kind: 'removeNode'; + removedId: string; + removedItemClassList: string[]; +} | { + kind: 'time'; + delay: number; +} | { + kind: 'cursorVisibility'; + visible: boolean; +} | { + kind: 'clear'; +}; +export type Only<Obj, Keys extends keyof Obj> = { + [k in Keys]: Obj[k]; +}; +export type Diff< + T extends { + [k: string]: unknown; + }, + U extends keyof T, +> = ({ [P in keyof T]: P } & { [P in U]: never } & { [x: string]: never })[ + keyof T +]; +export type Omit< + T extends { + [k: string]: unknown; + }, + K extends keyof T, +> = Pick< + T, + Diff<T, K> +>; +export const ttyLines: TTYText[] = (() => { + const lines: TTYText[] = []; + let ids: string[] = []; + const byId = new Map<string, TTYText>(); + let i = 69420; + let defaultRenderRestriction: Only< + TTYText & { kind: 'text' }, + 'renderrestriction' + >['renderrestriction'] = 'everywhere'; + type LimitedRenderBlock = Omit<RenderBlock, 'value'>; + type RenderBlockArg = [ + text: string, + options?: LimitedRenderBlock, + ]; + /** due to hellish css constraints, each text() call creates its own line - however, each block in a text() is joined on the same line. */ + const text = ( + globalOptions: + | Only<TTYText & { kind: 'text' }, 'renderrestriction'> + | RenderBlockArg, + ...blocks: RenderBlockArg[] + ) => { + const id = (++i).toString(); + ids.push(id); + if (Array.isArray(globalOptions)) { + blocks.unshift(globalOptions); + globalOptions = {}; + } + const txt = { + kind: 'text', + renderrestriction: globalOptions.renderrestriction ?? + defaultRenderRestriction, + value: blocks.map(([t, o]) => ({ + value: t, + colour: '#b9b9b9', + ...o, + })), + id, + classes: [`ttytext-${id}`], + } as TTYText; + lines.push(txt); + byId.set(id, txt); + return id; + }; + const wait = (time: number) => + lines.push({ + kind: 'time', + delay: time, + }); + const del = (id: string | number) => { + const removedId = typeof id === 'string' + ? id + : id >= 0 + ? ids[id] + : ids[ids.length + id]; + const r = byId.get(removedId); + if (r?.kind === 'text') { + lines.push({ + kind: 'removeNode', + removedId, + removedItemClassList: r.classes, + }); + r.classes.push('ttytext-removed-after-done'); + } + ids = ids.filter((v) => v !== removedId); + }; + const delSince = (id: string | number) => { + const removedId = typeof id === 'string' + ? id + : id >= 0 + ? ids[id] + : ids[ids.length + id]; + const idIndex = ids.indexOf(removedId); + for (let i = idIndex; i < ids.length; i++) { + const removedId = ids[i]; + const r = byId.get(removedId); + if (r?.kind === 'text') { + r.classes.push('ttytext-removed-after-done'); + lines.push({ + kind: 'removeNode', + removedId, + removedItemClassList: r.classes, + }); + } + } + ids = ids.filter((_, i) => i < idIndex); + }; + const getLast = () => { + const v = byId.get(ids[ids.length - 1]); + if (v?.kind == 'text') { + return v.value; + } else return null; + }; + const overwriteLast = (...newValue: RenderBlockArg[]) => { + const v = byId.get(ids[ids.length - 1]); + const l = v?.kind == 'text' ? v.renderrestriction : 'everywhere'; + del(-1); + return text({ + renderrestriction: l, + }, ...newValue); + }; + const replaceLast = ( + handler: (newValue: RenderBlockArg[]) => RenderBlockArg[], + ) => + overwriteLast( + ...handler( + getLast()!.map( + (v) => + [ + v.value, + Object.fromEntries( + Object.entries(v).filter(([k]) => k !== 'value'), + ), + ] as const, + ), + ), + ); + const everyTTYClear: (() => void)[] = []; + const clear = () => { + if (lines.find((v) => v.kind === 'clear')) { + delSince(lines.findLastIndex((v) => v.kind === 'clear')); + } + lines.push({ kind: 'clear' }); + everyTTYClear.forEach((v) => v()); + }; + + everyTTYClear.push(() => { + text({ + renderrestriction: 'noscript', + }, ['This browser does not support JS. Your experience may be degraded.', { + bg: '#ff0000', + colour: '#dedede', + }], ['\n', {}]); + }); + everyTTYClear.forEach((v) => v()); + + // + + wait(300); + text({ renderrestriction: 'js-only' }, [ + ((v) => v[Math.floor(Math.random() * v.length)])([ + 'cool beats are stored in the balls', + 'found xml documents in the firmware', + 'overhead: "I don\'t consent" "hey thats my line"', + 'uwu', + 'i regret making this hellhole of a codebase', + ]), + { + colour: '#777777', + }, + ]); + wait(1000); + clear(); + text( + ['lain', { + colour: '#FFFF53', + weight: 700, + }], + [' in '], + ['mem.estrogen.zone', { + colour: '#17B117', + weight: 700, + }], + [' in '], + ['~', { + colour: '#53FFFF', + weight: 700, + }], + ); + text( + ['❯ ', { colour: '#53FF53', weight: 600 }], + [''], + ['', { colour: '#777777' }], + ); + { + const targetstr = '/usr/bin/env wel'; + const completions = [ + '/bin/sh -c "$(/usr/bin/env curl -fsSL https://blahaj.estrogen.zone/)"', + '/usr/local/bin/become-estradiol', + '/usr/bin/shellutil ansi cheatsheet 24bit', + '/usr/bin/env sh', + '/usr/bin/env wc -l src/routes/anim.css', + '/usr/bin/env welcome -c ~/.local/share/welcome.toml', + ]; + for (const c of [...targetstr.split(''), 'COMPLETE']) { + replaceLast((l) => { + const prompt = l[l.length - 2]; + const suggestions = l[l.length - 1]; + if (c === 'COMPLETE') { + prompt[0] += suggestions[0]; + suggestions[0] = ''; + } else { + prompt[0] += c; + const completion = completions.find((v) => v.startsWith(prompt[0])); + if (completion) { + suggestions[0] = completion.substring(prompt[0].length); + } else suggestions[0] = ''; + } + return l; + }); + if (c === 'COMPLETE') { + wait(100); + } else wait(50 + Math.random() * 100); + } + text(['Preparing...']); + wait(200); + } + wait(1000); + clear(); + text( + ['Welcome to'], + [''], + [' the Estrogen Zone!\n\n'], + ); + wait(300); + for (const c of ' my corner of'.split('')) { + replaceLast((l) => { + l[1][0] += c; + return l; + }); + wait(70); + } + text( + ['Places you can find me:', { + colour: '#7a7a7a', + }], + ); + wait(300); + text( + [' - ', { + colour: '#7a7a7a', + }], + ['Estradiol SourceHut: ', { colour: '#cdcdcd' }], + ); + wait(100); + replaceLast((v) => [...v, ['sh.estrogen.zone/~memdmp', { + colour: '#99aaff', + underlined: true, + weight: 700, + url: 'newtab:https://sh.estrogen.zone/~memdmp', + }]]); + wait(100); + text( + [' - ', { + colour: '#7a7a7a', + }], + ['On jsr: ', { colour: '#cdcdcd' }], + ); + wait(600); + replaceLast((v) => [...v, ['jsr.io/@memdmp', { + colour: '#f7df23', + underlined: true, + weight: 700, + url: 'newtab:https://sh.estrogen.zone/~memdmp', + }]]); + wait(200); + text( + [' - ', { + colour: '#7a7a7a', + }], + ['In random hackerspaces', { colour: '#aaaaaa' }], + ); + wait(600); + text( + ['\nYou may find these useful:', { + colour: '#7a7a7a', + }], + ); + wait(400); + text( + [' - ', { + colour: '#7a7a7a', + }], + ['GPG Root Key: ', { colour: '#cdcdcd' }], + ); + wait(1000); + replaceLast((v) => [...v, ['B546778F06BBCC8EC167DB3CD919706487B8B6DE', { + colour: '#58C7F3', + underlined: true, + weight: 700, + url: 'currenttab:/keys/memdmp/primary.pgp', + dl: 'memdmp-primary.pgp', + }]]); + wait(100); + replaceLast((v) => [...v, [' (root key)', { + colour: '#999999', + }]]); + wait(400); + text( + [' - ', { + colour: '#7a7a7a', + }], + ); + wait(70); + replaceLast( + (v) => [...v, ['GPG Git Key: ', { colour: '#cdcdcd' }]], + ); + wait(100); + replaceLast((v) => [...v, ['5134F05BD8D9DB8C6C0E1515A9439D346AB6DF4E', { + colour: '#F0A3B3', + underlined: true, + weight: 700, + url: 'currenttab:/keys/memdmp/git.pgp', + dl: 'memdmp-git.pgp', + }]]); + wait(100); + replaceLast((v) => [...v, [' (', { + colour: '#999999', + }], ['signed', { + colour: '#F0A3B3', + underlined: true, + weight: 500, + url: 'currenttab:/keys/memdmp/git.pgp.sig', + dl: 'memdmp-git.pgp.sig', + }], [')', { colour: '#999999' }]]); + wait(45); + text( + [' - ', { + colour: '#7a7a7a', + }], + ); + wait(70); + replaceLast( + ( + v, + ) => [...v, ['GPG Release Signing Key: ', { + colour: '#cdcdcd', + }]], + ); + wait(100); + replaceLast((v) => [...v, ['0D93102265071798C7B65A4C9F0739B9E0C8FD60', { + colour: '#ffffff', + underlined: true, + weight: 700, + url: 'currenttab:/keys/memdmp/release.pgp', + dl: 'memdmp-release.pgp', + }]]); + wait(100); + replaceLast((v) => [...v, [' (', { + colour: '#999999', + }], ['signed', { + colour: '#ffffff', + underlined: true, + weight: 500, + url: 'currenttab:/keys/memdmp/release.pgp.sig', + dl: 'memdmp-release.pgp.sig', + }], [')', { colour: '#999999' }]]); + wait(100); + text( + [' - ', { + colour: '#7a7a7a', + }], + ); + wait(35); + replaceLast( + ( + v, + ) => [...v, ['Source Code: ', { + colour: '#cdcdcd', + }]], + ); + wait(100); + replaceLast((v) => [...v, ['git.sh.estrogen.zone/~memdmp/mem.estrogen.zone', { + colour: '#F0A3B3', + underlined: true, + weight: 700, + url: 'newtab:/upstream/', + }]]); + wait(100); + text( + [' - ', { + colour: '#7a7a7a', + }], + ); + wait(35); + replaceLast( + ( + v, + ) => [...v, ['Canaries: ', { + colour: '#cdcdcd', + }]], + ); + wait(100); + replaceLast((v) => [...v, ['/canaries/', { + colour: '#58C7F3', + underlined: true, + weight: 700, + url: 'currenttab:/canaries/', + }]]); + wait(5000); + text([ + `<button style="padding: 12px 12px;background: #fff2;margin-top: 0.4rem;border-radius: 0.7rem;opacity:0.1;margin-top:3rem;" data-el="le funny button">have a button :3</button>`, + { + raw: true, + url: () => alert('i abused too much css for this i wanna cry now'), + }, + ]); + + // + + return lines; +})(); |