aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/anim-gen.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes/anim-gen.ts')
-rw-r--r--src/routes/anim-gen.ts504
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);