diff options
Diffstat (limited to 'src/routes/anim-gen.ts')
-rw-r--r-- | src/routes/anim-gen.ts | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/src/routes/anim-gen.ts b/src/routes/anim-gen.ts new file mode 100644 index 0000000..9138ca9 --- /dev/null +++ b/src/routes/anim-gen.ts @@ -0,0 +1,504 @@ +/* + 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/>. +*/ +import { Animation, MultiObjectKeyframe } from '@memdmp/keyframegen'; +import { + biosStepInterval, + biosSteps, + getDelay, + login, + ttyLines, +} from './shared.ts'; +const anim = new Animation(); +let ttyCtr = 0; +const stages = [ + anim.selector('#bios'), + anim.selector('#grub'), + anim.selector('#grub-term'), + anim.selector('#openrc'), + ...(ttyLines.flatMap((v) => + v.kind === 'clear' + ? [ + anim.selector('#tty-' + ttyCtr++), + ] + : [] + )), +]; +const visibleStageStyles = `margin-top: 0; +margin-bottom: 0; +margin-left: 0; +margin-right: 0; +height: 100vh; +width: 100vw; +opacity: 1;`; +const hiddenStageStyles = `margin-top: -99999vh; +margin-bottom: 99999vh; +margin-left: -99999vw; +margin-right: 99999vw; +height: 0px; +width: 0px; +opacity: 0;`; +const visibleStyles = `margin-top: 0; +margin-bottom: 0; +margin-left: 0; +margin-right: 0; +transform: none; +opacity: 1;`; +const hiddenStyles = `margin-top: -99999vh; +margin-bottom: 99999vh; +margin-left: -99999vw; +margin-right: 99999vw; +transform: scaleX(0); +opacity: 0;`; +const altHiddenStyles = `opacity:0; +transform:scaleX(0); +width:0; +height:0; +margin-left:-100vw; +margin-right:100vw;`; +const altVisibleStyles = `opacity:1; +transform:none; +width:max-content; +height:max-content; +margin-left:0; +margin-right:0;`; +stages.forEach((v, i) => + v.style(i === 0 ? visibleStageStyles : hiddenStageStyles) +); +let currentStage: typeof stages[number]; +const toStage = (stage: number) => { + const oldStage = currentStage; + currentStage = stages[stage]; + if (typeof oldStage !== 'undefined') { + oldStage.style( + visibleStageStyles, + ); + currentStage.style( + hiddenStageStyles, + ); + anim._internal_timeline.now += 1; + oldStage.style( + hiddenStageStyles, + ); + } + currentStage.style( + visibleStageStyles, + ); +}; + +type Step = (next: () => void) => void; + +const biosStepHandlers: Step[] = [ + // Show BIOS + (next) => { + toStage(0); + anim.in(1000 * 3 / 10, next); + }, + // Show Start boot option + (next) => { + const s = anim.selector('#bios .start-text'); + s.style(hiddenStyles); + anim._internal_timeline.now += 1; + s.style(visibleStyles); + anim.in(1000, next); + }, + // Show bar parts + ...new Array( + biosSteps, + ).fill( + (quantity: number, lastQuantity: number, index: number) => + (next: () => void) => { + const s = anim.selector( + `#bios .bar`, + ); + s.style(`${index === 0 ? hiddenStyles : visibleStyles} +width: ${lastQuantity * 100}vw;`); + anim._internal_timeline.now += 1; + s.style(`${visibleStyles} +width: ${quantity * 100}vw;`); + anim.in(quantity === 1 ? 50 : biosStepInterval, next); + }, + ).map((v, i, a) => v((i + 1) / a.length, i / a.length, i)), + // Show bdsdxe + (next) => { + const s1 = anim.selector('#bios .bdsdxe-load'); + const s2 = anim.selector('#bios .bdsdxe-start'); + s1.style(hiddenStyles); + s2.style(hiddenStyles); + anim._internal_timeline.now += 1; + s1.style(visibleStyles); + anim.in(75, () => { + s2.style(visibleStyles); + anim.in(50, next); + }); + }, +]; +const grubStepHandlers: Step[] = [ // Show Grub + (next) => { + toStage(1); + anim.in(1000, next); + }, + // Hide 2s, show 1s + (next) => { + const g2s = anim.selector('#grub .grub-2s'); + const g1s = anim.selector('#grub .grub-1s'); + g2s.style('width:max-content;opacity:1;'); + g1s.style('width:0;opacity:0;'); + anim._internal_timeline.now += 1; + g2s.style('width:0;opacity:0;'); + g1s.style('width:max-content;opacity:1;'); + anim.in(1000, next); + }, + // Show Booting Alpine Grub + (next) => { + toStage(2); + anim.in(100, next); + }, + (next) => { + const s = anim.selector('#grub-term .load-kernel'); + s.style(hiddenStyles); + anim._internal_timeline.now += 1; + s.style(visibleStyles); + anim.in(200, next); + }, + (next) => { + const s = anim.selector('#grub-term .load-ramdisk'); + s.style(hiddenStyles); + anim._internal_timeline.now += 1; + s.style(visibleStyles); + anim.in(950, next); + }, +]; +const typingIndicatorFlashTime = 200; +const typingIndicatorHidden = ` +opacity: 0; +transform: scaleX(0); +`; +const typingIndicatorVisible = ` +opacity: 1; +transform: none; +`; +/** Returns the last state it was in */ +const idleTypingBar = ( + typingBarSelector: MultiObjectKeyframe, + idleFor: number, +) => { + let flashingTimeCtr = 0; + let isHiddenInCycle = false; + typingBarSelector.style(typingIndicatorVisible); + const v = () => + isHiddenInCycle ? typingIndicatorHidden : typingIndicatorVisible; + while ((flashingTimeCtr += typingIndicatorFlashTime) < idleFor) { + if (flashingTimeCtr === typingIndicatorFlashTime) flashingTimeCtr /= 2; + anim.in(flashingTimeCtr, () => { + isHiddenInCycle = !isHiddenInCycle; + typingBarSelector.style( + v(), + ); + }); + anim.in(flashingTimeCtr - 1, () => { + typingBarSelector.style( + v(), + ); + }); + } + return v(); +}; +const typeCharacter = ( + typingBarSelector: MultiObjectKeyframe, + characterSelector: MultiObjectKeyframe | undefined | null, + next: () => void, + characterTime: number, +) => { + const characterHidden = ` + transform:scaleX(0); + width: 0; + opacity: 0; + `; + const characterVisible = ` + transform:none; + width: max-content; + opacity: 1; + `; + typingBarSelector.style(typingIndicatorVisible); + if (characterSelector) { + characterSelector.style(characterHidden); + anim._internal_timeline.now += 1; + characterSelector.style(characterVisible); + } + if (characterTime > typingIndicatorFlashTime) { + const res = idleTypingBar(typingBarSelector, characterTime); + anim.in(characterTime - 1, () => { + typingBarSelector.style(res); + }); + anim.in(characterTime, () => { + typingBarSelector.style(typingIndicatorHidden); + }); + anim.in(characterTime + 1000 / 60, next); + } else { + anim.in(characterTime, () => { + typingBarSelector.style(typingIndicatorHidden); + }); + anim.in(characterTime + 1000 / 60, next); + } +}; +const openrcStepHandlers = (multi: number): Step[] => [ + // Show OpenRC + (next) => { + toStage(3); + next(); + }, + ...[ + // Steps 1-3 + 1000 / 60, + 200, + 1000 / 30, + // Steps 4-9 + 1000 / 60, + 1000 / 60, + 4000 / 60, + 1000 / 40, + 400, + 75, + // Steps 10-15 + 150, + 100, + 85, + 100, + 3500 / 60, + 125, + // Steps 16-20 + 3500 / 60, + 100, + 150, + 1000 / 60, + 1000 / 60, + 1000 / 60, + // Steps 21-25 + 1000 / 85, + 1000 / 45, + 1000 / 60, + 1000 / 60, + 500, + // Steps 26-30 + 750, + 400, + 100, + 100, + 400, + // Time till typing: + 1000, + ].map((v) => v / multi).map((time, idx, arr) => (next: () => void) => { + const s = anim.selector('#openrc .openrc-boot-step-' + idx); + s.style(`${hiddenStyles} +max-height: 0;`); + if (idx === arr.length - 1) { + anim.selector('#openrc .openrc-hide-at-last-boot-step').style( + 'opacity:1;max-height:90vh;max-width:100vw;', + ); + } + anim._internal_timeline.now += 1; + if (idx === arr.length - 1) { + anim.selector('#openrc .openrc-hide-at-last-boot-step').style( + 'opacity:0;max-height:0;max-width:0;', + ); + } + s.style(`${visibleStyles} +max-height: max-content;`); + if (idx === arr.length - 1) { + idleTypingBar(anim.selector('#openrc .openrc-username-anim'), time); + } + anim.in(time, next); + }), + // Typing Username + ...((() => { + const typingBar = anim.selector('#openrc .openrc-username-anim'); + return login.username.split('').map((_, i) => { + const character = anim.selector( + `#openrc .openrc-username .openrc-username-char.openrc-username-char-${i}`, + ); + return (next: () => void) => + typeCharacter(typingBar, character, next, getDelay(login)); + }); + })()), + // Hide Username Cursor + (next) => { + const s = anim.selector( + '#openrc .openrc-hide-at-login-prompt-username-done', + ); + s.style(`transform:none;width:max-content;opacity:1;`); + anim._internal_timeline.now += 1; + s.style(`transform:scaleX(0);width:0;opacity:0;`); + next(); + }, + // Show Password + (next) => { + const s = anim.selector('#openrc .openrc-pw-line'); + s.style(`transform:scaleX(0);width:0;opacity:0;`); + anim._internal_timeline.now += 1; + s.style(`transform:none;width:max-content;opacity:1;`); + next(); + }, + // Password Typing + ...(new Array(login.passwordLength).fill( + (_idx: number) => (next: () => void) => { + const s = anim.selector('#openrc .openrc-password-anim'); + const characterTime = getDelay(login); + typeCharacter(s, null, next, characterTime); + }, + ).map((v, i) => v(i))), + (next) => { + const s = anim.selector('#openrc .openrc-password-anim'); + s.style('opacity:0;transform:scaleX(0);'); + anim.in(1, next); + }, +]; +const ttyStepHandlers: Step[] = [ + (next) => anim.in(100, next), + (next) => { + const s = anim.selector( + `#openrc .ttylines-openrc`, + ); + s.style(altHiddenStyles); + anim.in(1, () => { + s.style(altVisibleStyles); + next(); + }); + }, + ...(() => { + let isBeforeFirstClear = true; + let ttyIdx = 0; + return ttyLines.map((step) => (next: () => void) => { + switch (step.kind) { + case 'text': { + const s = anim.selector( + `${isBeforeFirstClear ? '#openrc' : '#tty-' + (ttyIdx - 1)} ${ + step.classes.map((v) => `.${v}`).join('') + }`, + ); + s.style(altHiddenStyles); + anim.in(1, () => { + s.style(altVisibleStyles); + next(); + }); + break; + } + case 'removeNode': { + const s = anim.selector( + `${isBeforeFirstClear ? '#openrc' : '#tty-' + (ttyIdx - 1)} ${ + step.removedItemClassList.map((v) => `.${v}`).join('') + }`, + ); + s.style(altVisibleStyles); + anim.in(1, () => { + s.style(altHiddenStyles); + next(); + }); + break; + } + case 'time': { + anim.in(step.delay, next); + break; + } + case 'cursorVisibility': { + next(); + break; + } + case 'clear': { + if (isBeforeFirstClear) { + isBeforeFirstClear = false; + } + toStage(3 + (++ttyIdx)); + next(); + break; + } + } + }); + })(), +]; + +const handleSteps: Step[] = [ + // (n) => { + // toStage(0); + // anim.in(500, n); + // }, + ...biosStepHandlers, + ...grubStepHandlers, + ...openrcStepHandlers(1), + ...ttyStepHandlers, + (n) => { + const s = anim.selector('#app .hidden-after-anim'); + s.style(visibleStyles); + anim._internal_timeline.now += 1; + s.style(hiddenStyles); + anim.in(1000, n); + }, +]; +const nextStep = () => handleSteps.shift()!(nextStep); +handleSteps.push(() => { + anim.style('#nonexistentelement', ''); + // we done +}); +nextStep(); +const output = `/** + * @generated + * @license AGPL-3.0-OR-LATER + * @copyright 2024 memdmp + * + * 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/>. + */ +${anim.exportToCSS()} +${ + stages.filter((_, i, a) => i !== a.length - 1).map((v) => + `${v.selector} { +${hiddenStageStyles} +} +.ttytext-removed-after-done { + display: inline-block; + opacity: 0; + width: 0; + height: 0; + margin-left: -100vw; + margin-right: 100vw; +} +${ + (ttyLines.flatMap((v) => + v.kind === 'text' + ? [ + ...v.value.map((v) => ({ + kind: 'text' as const, + ...v, + })), + ] + : [] + ).flatMap((v) => [ + ...( + v.kind === 'text' + ? [ + v.colour + ? `.ttytext-block.text-\\[\\${v.colour}\\]{color:${v.colour};}` + : '', + v.bg + ? `.ttytext-block.bg-\\[\\${v.bg}\\]{background:${v.bg};}` + : '', + ] + : [] + ), + ]).filter((v, i, a) => v.length !== 0 && a.indexOf(v) === i)).join('\n') + } +` + ).join('') +}`; +console.log(output); |