From d57a5985982ed2ba386eb4c0b4ca05ab3498238a Mon Sep 17 00:00:00 2001
From: memdmp <memdmp@memeware.net>
Date: Sat, 11 Jan 2025 20:20:16 +0100
Subject: feat: initial commit

---
 src/routes/anim-gen.ts | 504 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 504 insertions(+)
 create mode 100644 src/routes/anim-gen.ts

(limited to 'src/routes/anim-gen.ts')

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);
-- 
cgit v1.2.3