diff options
-rw-r--r-- | src/routes/+page.svelte | 417 | ||||
-rw-r--r-- | src/routes/IndexPage.svelte | 462 | ||||
-rw-r--r-- | src/routes/anim-gen.ts | 223 | ||||
-rw-r--r-- | src/routes/skip-animation/+page.svelte | 3 |
4 files changed, 584 insertions, 521 deletions
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index b5c8d91..f70592b 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -12,421 +12,10 @@ * 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/>. */ --> + <script lang="ts"> - import logo from './boot-logo.webp'; import './anim.css'; - import { architecture, hostname, tty, versions } from './distro-info'; - import { login, ttyLines, type RenderBlock, type TTYText } from './shared'; - import { onDestroy, onMount } from 'svelte'; - import { base } from '$app/paths'; - - const initTagLine = `line flex flex-row flex-wrap gap-3 justify-between`; - - const fsckFileCount = Math.floor(Math.random() * 32768 + 6732); - const fsckTotalCount = Math.round( - (fsckFileCount / 6732) * 753664 + Math.random() * 10000, - ); - const blocksTotal = Math.floor(Math.random() * 8000000 + 4000000); - const blocksCount = Math.floor(Math.random() * 600000 + 200000); - - const lanLeastSignificantIPPart = Math.floor(Math.random() * 253 + 2); - - let { - skipAnimation = false, - }: { - skipAnimation?: boolean; - } = $props(); - onMount(() => { - if (skipAnimation && location.pathname !== base + '/skip-animation') - history.replaceState({}, '', base + '/skip-animation'); - }); - - let isScripted = $state(false); - onMount(() => (isScripted = true)); - onDestroy(() => (isScripted = false)); + import IndexPage from './IndexPage.svelte'; </script> -<svelte:head> - <title>/~mem/</title> - <meta name="description" content="memdmp's pure-css homepage demo thing" /> -</svelte:head> - -{#snippet tagLeft(colour: string)} - <span class="text-[{colour}]">*</span> -{/snippet} -{#snippet okTagLeft()} - {@render tagLeft('#51f051')} -{/snippet} -{#snippet tagRight(innerText: string, innerColour: string)} - <span class="text-[#5f5fff]">[</span> - <span class="text-[{innerColour}]">{innerText}</span> - <span class="text-[#5f5fff]">]</span> -{/snippet} -{#snippet okTagRight()} - {@render tagRight('ok', '#51f051')} -{/snippet} -{#snippet okLine(text: string, classes?: string, rightClass?: string)} - <div class="{initTagLine}{classes ? ' ' + classes : ''}"> - <div class="left"> - {@render okTagLeft()} - {text} - </div> - <div class="right{rightClass ? ' ' + rightClass : ''}"> - {@render okTagRight()} - </div> - </div> -{/snippet} -{#snippet ttyTextInnerRenderer(section: RenderBlock)} - <span - class="ttytext-block {section.colour - ? ` text-[${section.colour}]` - : ''}{section.bg ? ` bg-[${section.bg}]` : ''} {section.weight - ? ` ${['', 'font-thin', 'font-extralight', 'font-light', 'font-normal', 'font-medium', 'font-semibold', 'font-bold', 'font-extrabold', 'font-black'][section.weight / 100]}` - : ''}{typeof section.italic === 'undefined' - ? '' - : section.italic - ? ' italic' - : ' not-italic'}{typeof section.underlined === 'undefined' - ? '' - : section.underlined - ? ` underline` - : ' no-underline'} inline" - >{#if section.raw}{@html section.value}{:else}{#each section.value - .split('\n') - .map( (l, i, a) => (i === a.length - 1 ? [0, l] : [1, l]), ) as [nl, l]}{l}{#if nl}<br - />{/if}{/each}{/if}</span - > -{/snippet} -{#snippet ttyTextMiddleRenderer( - section: RenderBlock, - line: TTYText & { kind: 'text' }, -)} - {#if section.url} - {#if typeof section.url === 'string'} - {#if section.url.startsWith('newtab:')} - {@const url = section.url.substring(7)} - <a - href={url.startsWith('/') ? base + url : url} - target="_blank" - rel="noopener noreferrer" - class="no-underline text-inherit" - download={section.dl}>{@render ttyTextInnerRenderer(section)}</a - > - {:else if section.url.startsWith('currenttab:')} - {@const url = section.url.substring(11)} - <a - href={url.startsWith('/') ? base + url : url} - class="no-underline text-inherit" - download={section.dl}>{@render ttyTextInnerRenderer(section)}</a - > - {:else} - ERR: Unknown Link Format - {/if} - {:else} - <span - onclick={() => - typeof section.url !== 'function' ? void 0 : section.url(line)} - onkeypress={() => - typeof section.url !== 'function' ? void 0 : section.url(line)} - role="link" - tabindex="0">{@render ttyTextInnerRenderer(section)}</span - > - {/if} - {:else} - {@render ttyTextInnerRenderer(section)} - {/if} -{/snippet} -{#snippet ttyText(line: TTYText)} - {#if line.kind === 'text'} - {#if (line.renderrestriction ?? 'everywhere') === 'everywhere' || line.renderrestriction === 'noscript' || (line.renderrestriction === 'js-only' && isScripted)} - {#if line.renderrestriction === 'noscript'} - <noscript class="inline-block max-w-[100%] {line.classes.join(' ')}" - >{#each line.value as v}{@render ttyTextMiddleRenderer( - v, - line, - )}{/each}</noscript - > - {:else} - <span class="inline-block max-w-[100%] {line.classes.join(' ')}" - >{#each line.value as v}{@render ttyTextMiddleRenderer( - v, - line, - )}{/each}</span - > - {/if} - {/if} - {/if} -{/snippet} - -<div class="hidden"> - {#each ['inline-block', 'block', 'flex', 'inline-flex', 'max-w-full', 'font-thin', 'font-extralight', 'font-light', 'font-normal', 'font-medium', 'font-semibold', 'font-bold', 'font-extrabold', 'font-black'] as c} - <span class={c}></span> - {/each} -</div> - -<div - class="fixed top-0 left-0 w-screen h-screen font-mono" - id="app" - class:skip-animation={skipAnimation} -> - <div - id="bios" - class="flex flex-col items-center justify-between relative font-bios text-lg" - > - <div class="top flex items-center justify-center flex-col"> - <div class="inner text-[#999999]"> - <p class="leading-4 mt-8 -mb-8 max-w-[36rem]"> - <span class="bdsdxe-load" - >BdsDxe: loading Boot0002 "UEFI Misc Device" from PciRoot - (0x0)/Pci(0x2,0x4)/Pci(0x0,0x0)</span - ><br /> - <span class="bdsdxe-start" - >BdsDxe: starting Boot0002 "UEFI Misc Device" from PciRoot - (0x0)/Pci(0x2,0x4)/Pci(0x0,0x0)</span - > - </p> - </div> - </div> - <div class="middle"> - <img - src={logo} - alt="Bootloader Logo" - class="w-32 h-32 flex items-center justify-center" - /> - </div> - <div - class="bottom flex flex-col items-stretch justify-center text-center gap-0.5 w-full pb-4" - > - <div class="start-text">Start boot option</div> - <div - class="bar h-4" - style={`background-image: url("data:image/svg+xml,${encodeURIComponent(`<svg width="32" height="32" viewbox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> - <rect x="1" y="1" width="30" height="30" fill="white" stroke="black" stroke-width="2"/> -</svg> -`)}");background-size:contain;`} - ></div> - </div> - </div> - <div id="grub" class="font-grub text-sm"> - <div - class="relative p-4 flex items-stretch justify-stretch flex-col w-screen h-screen" - > - <p class="title text-[#a2a2a2] mb-4 h-4 text-center"> - GNU GRUB version 2.12 - </p> - <div - class="outer-border border-2 border-[#a8a8a8] border-solid h-full px-1 py-2 flex flex-col items-stretch" - > - <div class="entry bg-[#a8a8a8] text-[#000] text-left"> - *Alpine Linux {versions.alpine.number}, with {versions.kernel - .humanReadable} - </div> - <div class="entry text-[#a8a8a8] text-left"> - UEFI Firmware Setup - </div> - </div> - <p class="footer text-[#a2a2a2] my-4 text-left pl-2"> - <span class="block pl-6"> - Use the ↑ and ↓ keys to select which entry is highlighted.<br /> - Press enter to boot the selected OS, `e' to edit the commands before booting - or `c' for a command-line.<br /> - </span> - The highlighted entry will be executed automatically in - <span class="grub-2s inline-block">2</span><span - class="grub-1s inline-block">1</span - >s. - </p> - </div> - </div> - <div id="grub-term" class="font-grub text-sm text-left text-[#a2a2a2]"> - <p class="p-8"> - Booting `Alpine Linux {versions.alpine.number}, with {versions - .kernel.humanReadable}'<br /><br /><span class="load-kernel" - >Loading {versions.kernel.humanReadable} ...</span - ><br /><span class="load-ramdisk">Loading initial ramdisk ...</span> - </p> - </div> - <!-- - flex col reverse and mb auto are hacks for it to auto scroll to bottom(!) - flex col reverse only works due to nested inner (which would be at bottom of screen, if it wasnt for mb-auto div above it) - --> - <div id="openrc" class="overflow-y-auto flex flex-col-reverse"> - <div class="mb-auto"></div> - <div class="font-mono leading-4 text-[#b9b9b9] flex flex-col p-4"> - <div class="line"> - <span class="text-[#51f051]">OpenRC</span> - <span class="text-[#4bdfdf]">{versions.openrc}</span> - is starting up Linux - <span class="text-[#5f5fff]">{versions.kernel.id} ({architecture})</span - > - </div> - <div class="line">​</div> - {@render okLine('/proc/ is already mounted')} - {@render okLine('Mounting /run ...')} - {@render okLine('/run/openrc: creating directory')} - {@render okLine('/run/lock: creating directory', 'openrc-boot-step-1')} - {@render okLine('/run/lock: correcting owner', 'openrc-boot-step-1')} - {@render okLine('Caching service dependencies ...', 'openrc-boot-step-1')} - {@render okLine('Remounting devtmpfs on /dev ...', 'openrc-boot-step-2')} - {@render okLine('Mounting /dev/mqueue ...', 'openrc-boot-step-3')} - {@render okLine('Mounting security filesystem ...', 'openrc-boot-step-4')} - {@render okLine('Mounting debug filesystem ...', 'openrc-boot-step-5')} - {@render okLine( - 'Mounting persistent storage (pstore) filesystem ...', - 'openrc-boot-step-5', - )} - {@render okLine('Mounting efivarfs filesystem ...', 'openrc-boot-step-5')} - {@render okLine('Starting busybox mdev ...', 'openrc-boot-step-6')} - {@render okLine('Scanning hardware for mdev ...', 'openrc-boot-step-6')} - {@render okLine('Loading hardware drivers ...', 'openrc-boot-step-7')} - {@render okLine('Loading modules ...', 'openrc-boot-step-8')} - {@render okLine( - 'Setting system clock using the hardware clock [UTC] ...', - 'openrc-boot-step-9', - )} - {@render okLine('Checking local filesystems ...', 'openrc-boot-step-10')} - <div class="line openrc-boot-step-11"> - /dev/mapper/bepis: clean, {fsckFileCount}/{fsckTotalCount} files, {blocksCount}/{blocksTotal} - blocks - </div> - {@render okLine( - 'Remounting root filesystem read/write ...', - 'openrc-boot-step-12', - )} - {@render okLine('Remounting filesystems ...', 'openrc-boot-step-13')} - {@render okLine('Activating swap devices ...', 'openrc-boot-step-14')} - {@render okLine('Mounting local filesystems ...', 'openrc-boot-step-15')} - {@render okLine( - 'Configuring kernel parameters ...', - 'openrc-boot-step-16', - )} - {@render okLine('Creating user login records ...', 'openrc-boot-step-17')} - {@render okLine('Setting hostname ...', 'openrc-boot-step-18')} - {@render okLine('Setting keymap ...', 'openrc-boot-step-19')} - {@render okLine('Starting networking ...', 'openrc-boot-step-20')} - {@render okLine('\xa0\xa0lo ...', 'openrc-boot-step-21')} - {@render okLine('\xa0\xa0eth0 ...', 'openrc-boot-step-22')} - <div class="line openrc-boot-step-22">udhcpd: started</div> - <div class="line openrc-boot-step-23">udhcpd: broadcasting discover</div> - <!-- TODO: configurable subnet uwu --> - <div class="line openrc-boot-step-24"> - udhcpd: broadcasting select for 192.168.1.{lanLeastSignificantIPPart}, - server 192.168.1.1 - </div> - <div class="line openrc-boot-step-25"> - udhcpd: lease of 192.168.1.{lanLeastSignificantIPPart} obtained from 192.168.1.1, - lease time 3600 - </div> - {@render okLine( - 'Seeding random number generator ...', - 'openrc-boot-step-26', - )} - {@render okLine( - 'Seeding 256 bits without crediting', - 'openrc-boot-step-26', - )} - {@render okLine( - 'Saving 256 bits of creditable seed for next boot', - 'openrc-boot-step-26', - )} - {@render okLine('Starting logbookd ...', 'openrc-boot-step-27')} - {@render okLine('Starting busybox acpid ...', 'openrc-boot-step-28')} - {@render okLine('Starting busybox crond ...', 'openrc-boot-step-29')} - {@render okLine('Starting busybox ntpd ...', 'openrc-boot-step-29')} - <div class="line openrc-boot-step-30">​</div> - <div class="line openrc-boot-step-30"> - Welcome to Alpine Linux {versions.alpine - .number}{#if versions.alpine.isEdge} (edge){/if} - </div> - <div class="line openrc-boot-step-30"> - Kernel {versions.kernel.humanReadable} on an {architecture} (/dev/{tty}) - </div> - <div class="line openrc-boot-step-30">​</div> - <div class="line openrc-boot-step-30"> - {hostname} login: <span class="openrc-username" - >{#each login.username - .split('') - .map((v, i) => [v, i] as const) as [char, idx]}<span - class="openrc-username-char openrc-username-char-{idx} inline-block" - >{char}</span - >{/each}</span - ><span class="openrc-hide-at-login-prompt-username-done" - ><span class="openrc-username-anim" - ><code class="hidden-after-anim">_</code></span - ></span - ><span class="openrc-pw-line" - ><br /> - Password: <span class="openrc-password-anim" - ><code class="hidden-after-anim">_</code></span - ></span - > - </div> - <!-- TODO: Animate login and launching a tui here (for js enjoyers) or a minimal terminal after clearing the screen, and a no js screen alongside a plaintext version of the thing (for schizo noscript lovers) --> - <div class="hidden-after-anim"> - <span class="openrc-hide-at-last-boot-step inline-block"> - <pre class="flashing-cursor">_</pre> - </span> - </div> - <div class="line ttylines-openrc font-mono text-[#070505] flex flex-col"> - {#each ttyLines.filter(((maxidx) => (_, i) => i < (maxidx === -1 ? Infinity : maxidx))(ttyLines.findIndex((v) => v.kind === 'clear'))) as line} - {@render ttyText(line)} - {/each} - </div> - </div> - </div> - {#if ttyLines.find((v) => v.kind === 'clear')} - {#each ttyLines - .map((v, i) => [v, i] as const) - .filter(([v]) => v.kind === 'clear') - .map((v, i, a) => [v[1], a[i + 1]?.[1], i]) as [idx, nextIdx, clearIdx]} - <div - id="tty-{clearIdx}" - class="overflow-y-auto max-h-screen flex flex-col-reverse" - > - <div class="mb-auto"></div> - <div class="font-mono leading-4 text-[#070505] flex flex-col p-4"> - {#each ttyLines.filter((_, i) => i >= idx && i < (nextIdx ?? Infinity)) as line} - {@render ttyText(line)} - {/each} - </div> - </div> - {/each} - {/if} -</div> - -<style> - @keyframes flashing-cursor { - 0% { - opacity: 0; - } - 49% { - opacity: 0; - } - 50% { - opacity: 1; - } - 99% { - opacity: 1; - } - 100% { - opacity: 0; - } - } - .flashing-cursor { - animation-name: flashing-cursor; - animation-duration: 0.4s; - animation-iteration-count: infinite; - animation-timing-function: linear; - } - .hidden-after-anim { - margin-top: -99999vh; - margin-bottom: 99999vh; - margin-left: -99999vw; - margin-right: 99999vw; - transform: scaleX(0); - opacity: 0; - } - .ttytext-block { - white-space-collapse: preserve-spaces; - } -</style> +<IndexPage /> diff --git a/src/routes/IndexPage.svelte b/src/routes/IndexPage.svelte new file mode 100644 index 0000000..8a56f08 --- /dev/null +++ b/src/routes/IndexPage.svelte @@ -0,0 +1,462 @@ +<!-- +/** + * @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/>. + */ + --> +<script lang="ts"> + import logo from './boot-logo.webp'; + import { architecture, hostname, tty, versions } from './distro-info'; + import { login, ttyLines, type RenderBlock, type TTYText } from './shared'; + import { onDestroy, onMount } from 'svelte'; + import { base } from '$app/paths'; + + const initTagLine = `line flex flex-row flex-wrap gap-3 justify-between`; + + const fsckFileCount = Math.floor(Math.random() * 32768 + 6732); + const fsckTotalCount = Math.round( + (fsckFileCount / 6732) * 753664 + Math.random() * 10000, + ); + const blocksTotal = Math.floor(Math.random() * 8000000 + 4000000); + const blocksCount = Math.floor(Math.random() * 600000 + 200000); + + const lanLeastSignificantIPPart = Math.floor(Math.random() * 253 + 2); + + let { + skipAnimation = false, + }: { + skipAnimation?: boolean; + } = $props(); + onMount(() => { + if (skipAnimation && location.pathname !== base + '/skip-animation') + history.replaceState({}, '', base + '/skip-animation'); + }); + + let isScripted = $state(false); + onMount(() => (isScripted = true)); + onDestroy(() => (isScripted = false)); +</script> + +<svelte:head> + <title>/~mem/</title> + <meta name="description" content="memdmp's pure-css homepage demo thing" /> +</svelte:head> + +{#snippet tagLeft(colour: string)} + <span class="text-[{colour}]">*</span> +{/snippet} +{#snippet okTagLeft()} + {@render tagLeft('#51f051')} +{/snippet} +{#snippet tagRight(innerText: string, innerColour: string)} + <span class="text-[#5f5fff]">[</span> + <span class="text-[{innerColour}]">{innerText}</span> + <span class="text-[#5f5fff]">]</span> +{/snippet} +{#snippet okTagRight()} + {@render tagRight('ok', '#51f051')} +{/snippet} +{#snippet okLine(text: string, classes?: string, rightClass?: string)} + <div class="{initTagLine}{classes ? ' ' + classes : ''}"> + <div class="left"> + {@render okTagLeft()} + {text} + </div> + <div class="right{rightClass ? ' ' + rightClass : ''}"> + {@render okTagRight()} + </div> + </div> +{/snippet} +{#snippet ttyTextInnerRenderer(section: RenderBlock)} + <span + class="ttytext-block {section.colour + ? ` text-[${section.colour}]` + : ''}{section.bg ? ` bg-[${section.bg}]` : ''} {section.weight + ? ` ${['', 'font-thin', 'font-extralight', 'font-light', 'font-normal', 'font-medium', 'font-semibold', 'font-bold', 'font-extrabold', 'font-black'][section.weight / 100]}` + : ''}{typeof section.italic === 'undefined' + ? '' + : section.italic + ? ' italic' + : ' not-italic'}{typeof section.underlined === 'undefined' + ? '' + : section.underlined + ? ` underline` + : ' no-underline'} inline" + >{#if section.raw}{@html section.value}{:else}{#each section.value + .split('\n') + .map( (l, i, a) => (i === a.length - 1 ? [0, l] : [1, l]), ) as [nl, l]}{l}{#if nl}<br + />{/if}{/each}{/if}</span + > +{/snippet} +{#snippet ttyTextMiddleRenderer( + section: RenderBlock, + line: TTYText & { kind: 'text' }, +)} + {#if section.url} + {#if typeof section.url === 'string'} + {#if section.url.startsWith('newtab:')} + {@const url = section.url.substring(7)} + <a + href={url.startsWith('/') ? base + url : url} + target="_blank" + rel="noopener noreferrer" + class="no-underline text-inherit" + download={section.dl}>{@render ttyTextInnerRenderer(section)}</a + > + {:else if section.url.startsWith('currenttab:')} + {@const url = section.url.substring(11)} + <a + href={url.startsWith('/') ? base + url : url} + class="no-underline text-inherit" + download={section.dl}>{@render ttyTextInnerRenderer(section)}</a + > + {:else} + ERR: Unknown Link Format + {/if} + {:else} + <span + onclick={() => + typeof section.url !== 'function' ? void 0 : section.url(line)} + onkeypress={() => + typeof section.url !== 'function' ? void 0 : section.url(line)} + role="link" + tabindex="0">{@render ttyTextInnerRenderer(section)}</span + > + {/if} + {:else} + {@render ttyTextInnerRenderer(section)} + {/if} +{/snippet} +{#snippet ttyText(line: TTYText)} + {#if line.kind === 'text'} + {#if (line.renderrestriction ?? 'everywhere') === 'everywhere' || line.renderrestriction === 'noscript' || (line.renderrestriction === 'js-only' && isScripted)} + {#if line.renderrestriction === 'noscript'} + <noscript class="inline-block max-w-[100%] {line.classes.join(' ')}" + >{#each line.value as v}{@render ttyTextMiddleRenderer( + v, + line, + )}{/each}</noscript + > + {:else} + <span class="inline-block max-w-[100%] {line.classes.join(' ')}" + >{#each line.value as v}{@render ttyTextMiddleRenderer( + v, + line, + )}{/each}</span + > + {/if} + {/if} + {/if} +{/snippet} + +<div class="hidden"> + {#each ['inline-block', 'block', 'flex', 'inline-flex', 'max-w-full', 'font-thin', 'font-extralight', 'font-light', 'font-normal', 'font-medium', 'font-semibold', 'font-bold', 'font-extrabold', 'font-black'] as c} + <span class={c}></span> + {/each} +</div> + +<div + class="fixed top-0 left-0 w-screen h-screen font-mono" + id="app" + class:skip-animation={skipAnimation} +> + {#if !skipAnimation} + <div + id="bios" + class="flex flex-col items-center justify-between relative font-bios text-lg" + > + <div class="top flex items-center justify-center flex-col"> + <div class="inner text-[#999999]"> + <p class="leading-4 mt-8 -mb-8 max-w-[36rem]"> + <span class="bdsdxe-load" + >BdsDxe: loading Boot0002 "UEFI Misc Device" from PciRoot + (0x0)/Pci(0x2,0x4)/Pci(0x0,0x0)</span + ><br /> + <span class="bdsdxe-start" + >BdsDxe: starting Boot0002 "UEFI Misc Device" from PciRoot + (0x0)/Pci(0x2,0x4)/Pci(0x0,0x0)</span + > + </p> + </div> + </div> + <div class="middle"> + <img + src={logo} + alt="Bootloader Logo" + class="w-32 h-32 flex items-center justify-center" + /> + </div> + <div + class="bottom flex flex-col items-stretch justify-center text-center gap-0.5 w-full pb-4" + > + <div class="start-text">Start boot option</div> + <div + class="bar h-4" + style={`background-image: url("data:image/svg+xml,${encodeURIComponent(`<svg width="32" height="32" viewbox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> + <rect x="1" y="1" width="30" height="30" fill="white" stroke="black" stroke-width="2"/> +</svg> +`)}");background-size:contain;`} + ></div> + </div> + </div> + <div id="grub" class="font-grub text-sm"> + <div + class="relative p-4 flex items-stretch justify-stretch flex-col w-screen h-screen" + > + <p class="title text-[#a2a2a2] mb-4 h-4 text-center"> + GNU GRUB version 2.12 + </p> + <div + class="outer-border border-2 border-[#a8a8a8] border-solid h-full px-1 py-2 flex flex-col items-stretch" + > + <div class="entry bg-[#a8a8a8] text-[#000] text-left"> + *Alpine Linux {versions.alpine.number}, with {versions.kernel + .humanReadable} + </div> + <div class="entry text-[#a8a8a8] text-left"> + UEFI Firmware Setup + </div> + </div> + <p class="footer text-[#a2a2a2] my-4 text-left pl-2"> + <span class="block pl-6"> + Use the ↑ and ↓ keys to select which entry is highlighted.<br /> + Press enter to boot the selected OS, `e' to edit the commands before + booting or `c' for a command-line.<br /> + </span> + The highlighted entry will be executed automatically in + <span class="grub-2s inline-block">2</span><span + class="grub-1s inline-block">1</span + >s. + </p> + </div> + </div> + <div id="grub-term" class="font-grub text-sm text-left text-[#a2a2a2]"> + <p class="p-8"> + Booting `Alpine Linux {versions.alpine.number}, with {versions + .kernel.humanReadable}'<br /><br /><span class="load-kernel" + >Loading {versions.kernel.humanReadable} ...</span + ><br /><span class="load-ramdisk">Loading initial ramdisk ...</span> + </p> + </div> + <!-- + flex col reverse and mb auto are hacks for it to auto scroll to bottom(!) + flex col reverse only works due to nested inner (which would be at bottom of screen, if it wasnt for mb-auto div above it) + --> + <div id="openrc" class="overflow-y-auto flex flex-col-reverse"> + <div class="mb-auto"></div> + <div class="font-mono leading-4 text-[#b9b9b9] flex flex-col p-4"> + <div class="line"> + <span class="text-[#51f051]">OpenRC</span> + <span class="text-[#4bdfdf]">{versions.openrc}</span> + is starting up Linux + <span class="text-[#5f5fff]" + >{versions.kernel.id} ({architecture})</span + > + </div> + <div class="line">​</div> + {@render okLine('/proc/ is already mounted')} + {@render okLine('Mounting /run ...')} + {@render okLine('/run/openrc: creating directory')} + {@render okLine('/run/lock: creating directory', 'openrc-boot-step-1')} + {@render okLine('/run/lock: correcting owner', 'openrc-boot-step-1')} + {@render okLine( + 'Caching service dependencies ...', + 'openrc-boot-step-1', + )} + {@render okLine( + 'Remounting devtmpfs on /dev ...', + 'openrc-boot-step-2', + )} + {@render okLine('Mounting /dev/mqueue ...', 'openrc-boot-step-3')} + {@render okLine( + 'Mounting security filesystem ...', + 'openrc-boot-step-4', + )} + {@render okLine('Mounting debug filesystem ...', 'openrc-boot-step-5')} + {@render okLine( + 'Mounting persistent storage (pstore) filesystem ...', + 'openrc-boot-step-5', + )} + {@render okLine( + 'Mounting efivarfs filesystem ...', + 'openrc-boot-step-5', + )} + {@render okLine('Starting busybox mdev ...', 'openrc-boot-step-6')} + {@render okLine('Scanning hardware for mdev ...', 'openrc-boot-step-6')} + {@render okLine('Loading hardware drivers ...', 'openrc-boot-step-7')} + {@render okLine('Loading modules ...', 'openrc-boot-step-8')} + {@render okLine( + 'Setting system clock using the hardware clock [UTC] ...', + 'openrc-boot-step-9', + )} + {@render okLine( + 'Checking local filesystems ...', + 'openrc-boot-step-10', + )} + <div class="line openrc-boot-step-11"> + /dev/mapper/bepis: clean, {fsckFileCount}/{fsckTotalCount} files, {blocksCount}/{blocksTotal} + blocks + </div> + {@render okLine( + 'Remounting root filesystem read/write ...', + 'openrc-boot-step-12', + )} + {@render okLine('Remounting filesystems ...', 'openrc-boot-step-13')} + {@render okLine('Activating swap devices ...', 'openrc-boot-step-14')} + {@render okLine( + 'Mounting local filesystems ...', + 'openrc-boot-step-15', + )} + {@render okLine( + 'Configuring kernel parameters ...', + 'openrc-boot-step-16', + )} + {@render okLine( + 'Creating user login records ...', + 'openrc-boot-step-17', + )} + {@render okLine('Setting hostname ...', 'openrc-boot-step-18')} + {@render okLine('Setting keymap ...', 'openrc-boot-step-19')} + {@render okLine('Starting networking ...', 'openrc-boot-step-20')} + {@render okLine('\xa0\xa0lo ...', 'openrc-boot-step-21')} + {@render okLine('\xa0\xa0eth0 ...', 'openrc-boot-step-22')} + <div class="line openrc-boot-step-22">udhcpd: started</div> + <div class="line openrc-boot-step-23"> + udhcpd: broadcasting discover + </div> + <!-- TODO: configurable subnet uwu --> + <div class="line openrc-boot-step-24"> + udhcpd: broadcasting select for 192.168.1.{lanLeastSignificantIPPart}, + server 192.168.1.1 + </div> + <div class="line openrc-boot-step-25"> + udhcpd: lease of 192.168.1.{lanLeastSignificantIPPart} obtained from 192.168.1.1, + lease time 3600 + </div> + {@render okLine( + 'Seeding random number generator ...', + 'openrc-boot-step-26', + )} + {@render okLine( + 'Seeding 256 bits without crediting', + 'openrc-boot-step-26', + )} + {@render okLine( + 'Saving 256 bits of creditable seed for next boot', + 'openrc-boot-step-26', + )} + {@render okLine('Starting logbookd ...', 'openrc-boot-step-27')} + {@render okLine('Starting busybox acpid ...', 'openrc-boot-step-28')} + {@render okLine('Starting busybox crond ...', 'openrc-boot-step-29')} + {@render okLine('Starting busybox ntpd ...', 'openrc-boot-step-29')} + <div class="line openrc-boot-step-30">​</div> + <div class="line openrc-boot-step-30"> + Welcome to Alpine Linux {versions.alpine + .number}{#if versions.alpine.isEdge} (edge){/if} + </div> + <div class="line openrc-boot-step-30"> + Kernel {versions.kernel.humanReadable} on an {architecture} (/dev/{tty}) + </div> + <div class="line openrc-boot-step-30">​</div> + <div class="line openrc-boot-step-30"> + {hostname} login: <span class="openrc-username" + >{#each login.username + .split('') + .map((v, i) => [v, i] as const) as [char, idx]}<span + class="openrc-username-char openrc-username-char-{idx} inline-block" + >{char}</span + >{/each}</span + ><span class="openrc-hide-at-login-prompt-username-done" + ><span class="openrc-username-anim" + ><code class="hidden-after-anim">_</code></span + ></span + ><span class="openrc-pw-line" + ><br /> + Password: <span class="openrc-password-anim" + ><code class="hidden-after-anim">_</code></span + ></span + > + </div> + <!-- TODO: Animate login and launching a tui here (for js enjoyers) or a minimal terminal after clearing the screen, and a no js screen alongside a plaintext version of the thing (for schizo noscript lovers) --> + <div class="hidden-after-anim"> + <span class="openrc-hide-at-last-boot-step inline-block"> + <pre class="flashing-cursor">_</pre> + </span> + </div> + <div + class="line ttylines-openrc font-mono text-[#070505] flex flex-col" + > + {#each ttyLines.filter(((maxidx) => (_, i) => i < (maxidx === -1 ? Infinity : maxidx))(ttyLines.findIndex((v) => v.kind === 'clear'))) as line} + {@render ttyText(line)} + {/each} + </div> + </div> + </div> + {/if} + <!-- FIXME: what is this #if? this seems broken --> + {#if ttyLines.find((v) => v.kind === 'clear')} + <!-- FIXME: this is utterly unreadable and i dont know what this does anymore --> + {@const arr = ttyLines + .map((v, i) => [v, i] as const) + .filter(([v]) => v.kind === 'clear') + .map((v, i, a) => [v[1], a[i + 1]?.[1], i] as const)} + {#each skipAnimation ? [arr[arr.length - 1]] : arr as [idx, nextIdx, clearIdx]} + <div + id="tty-{clearIdx}" + class="overflow-y-auto max-h-screen flex flex-col-reverse" + > + <div class="mb-auto"></div> + <div class="font-mono leading-4 text-[#070505] flex flex-col p-4"> + {#each ttyLines.filter((_, i) => i >= idx && i < (nextIdx ?? Infinity)) as line} + {@render ttyText(line)} + {/each} + </div> + </div> + {/each} + {/if} +</div> + +<style> + @keyframes flashing-cursor { + 0% { + opacity: 0; + } + 49% { + opacity: 0; + } + 50% { + opacity: 1; + } + 99% { + opacity: 1; + } + 100% { + opacity: 0; + } + } + .flashing-cursor { + animation-name: flashing-cursor; + animation-duration: 0.4s; + animation-iteration-count: infinite; + animation-timing-function: linear; + } + .hidden-after-anim { + margin-top: -99999vh; + margin-bottom: 99999vh; + margin-left: -99999vw; + margin-right: 99999vw; + transform: scaleX(0); + opacity: 0; + } + .ttytext-block { + white-space-collapse: preserve-spaces; + } +</style> diff --git a/src/routes/anim-gen.ts b/src/routes/anim-gen.ts index 550efb7..b91e088 100644 --- a/src/routes/anim-gen.ts +++ b/src/routes/anim-gen.ts @@ -15,6 +15,8 @@ import { login, ttyLines, } from './shared.ts'; +import fs from 'node:fs'; +import esbuild from 'esbuild'; const anim = new Animation(); let ttyCtr = 0; const stages = [ @@ -22,13 +24,9 @@ const stages = [ anim.selector('#grub'), anim.selector('#grub-term'), anim.selector('#openrc'), - ...(ttyLines.flatMap((v) => - v.kind === 'clear' - ? [ - anim.selector('#tty-' + ttyCtr++), - ] - : [] - )), + ...ttyLines.flatMap((v) => + v.kind === 'clear' ? [anim.selector('#tty-' + ttyCtr++)] : [], + ), ]; const visibleStageStyles = `margin-top: 0; margin-bottom: 0; @@ -69,27 +67,19 @@ height:max-content; margin-left:0; margin-right:0;`; stages.forEach((v, i) => - v.style(i === 0 ? visibleStageStyles : hiddenStageStyles) + v.style(i === 0 ? visibleStageStyles : hiddenStageStyles), ); -let currentStage: typeof stages[number]; +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, - ); + oldStage.style(visibleStageStyles); + currentStage.style(hiddenStageStyles); anim._internal_timeline.now += 1; - oldStage.style( - hiddenStageStyles, - ); + oldStage.style(hiddenStageStyles); } - currentStage.style( - visibleStageStyles, - ); + currentStage.style(visibleStageStyles); }; type Step = (next: () => void) => void; @@ -98,7 +88,7 @@ const biosStepHandlers: Step[] = [ // Show BIOS (next) => { toStage(0); - anim.in(1000 * 3 / 10, next); + anim.in((1000 * 3) / 10, next); }, // Show Start boot option (next) => { @@ -109,22 +99,20 @@ const biosStepHandlers: Step[] = [ 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} + ...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} + 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)), + 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'); @@ -139,7 +127,8 @@ width: ${quantity * 100}vw;`); }); }, ]; -const grubStepHandlers: Step[] = [ // Show Grub +const grubStepHandlers: Step[] = [ + // Show Grub (next) => { toStage(1); anim.in(1000, next); @@ -198,14 +187,10 @@ const idleTypingBar = ( if (flashingTimeCtr === typingIndicatorFlashTime) flashingTimeCtr /= 2; anim.in(flashingTimeCtr, () => { isHiddenInCycle = !isHiddenInCycle; - typingBarSelector.style( - v(), - ); + typingBarSelector.style(v()); }); anim.in(flashingTimeCtr - 1, () => { - typingBarSelector.style( - v(), - ); + typingBarSelector.style(v()); }); } return v(); @@ -294,30 +279,32 @@ const openrcStepHandlers = (multi: number): Step[] => [ 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} + ] + .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} + 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); - }), + 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( @@ -326,7 +313,7 @@ max-height: max-content;`); return (next: () => void) => typeCharacter(typingBar, character, next, getDelay(login)); }); - })()), + })(), // Hide Username Cursor (next) => { const s = anim.selector( @@ -346,13 +333,13 @@ max-height: max-content;`); next(); }, // Password Typing - ...(new Array(login.passwordLength).fill( - (_idx: number) => (next: () => void) => { + ...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))), + }) + .map((v, i) => v(i)), (next) => { const s = anim.selector('#openrc .openrc-password-anim'); s.style('opacity:0;transform:scaleX(0);'); @@ -362,9 +349,7 @@ max-height: max-content;`); const ttyStepHandlers: Step[] = [ (next) => anim.in(100, next), (next) => { - const s = anim.selector( - `#openrc .ttylines-openrc`, - ); + const s = anim.selector(`#openrc .ttylines-openrc`); s.style(altHiddenStyles); anim.in(1, () => { s.style(altVisibleStyles); @@ -378,8 +363,9 @@ const ttyStepHandlers: Step[] = [ switch (step.kind) { case 'text': { const s = anim.selector( - `${isBeforeFirstClear ? '#openrc' : '#tty-' + (ttyIdx - 1)} ${step.classes.map((v) => `.${v}`).join('') - }`, + `${isBeforeFirstClear ? '#openrc' : '#tty-' + (ttyIdx - 1)} ${step.classes + .map((v) => `.${v}`) + .join('')}`, ); s.style(altHiddenStyles); anim.in(1, () => { @@ -390,8 +376,9 @@ const ttyStepHandlers: Step[] = [ } case 'removeNode': { const s = anim.selector( - `${isBeforeFirstClear ? '#openrc' : '#tty-' + (ttyIdx - 1)} ${step.removedItemClassList.map((v) => `.${v}`).join('') - }`, + `${isBeforeFirstClear ? '#openrc' : '#tty-' + (ttyIdx - 1)} ${step.removedItemClassList + .map((v) => `.${v}`) + .join('')}`, ); s.style(altVisibleStyles); anim.in(1, () => { @@ -412,7 +399,7 @@ const ttyStepHandlers: Step[] = [ if (isBeforeFirstClear) { isBeforeFirstClear = false; } - toStage(3 + (++ttyIdx)); + toStage(3 + ++ttyIdx); next(); break; } @@ -444,7 +431,7 @@ handleSteps.push(() => { // we done }); nextStep(); -const output = `/** +const comment = `/** * @generated * @license AGPL-3.0-OR-LATER * @copyright 2024 memdmp @@ -456,10 +443,13 @@ const output = `/** * 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} { + */`; +const exported = anim.exportToCSS(); +const tail = `${stages + .filter((_, i, a) => i !== a.length - 1) + .map( + (v) => + `${v.selector} { ${hiddenStageStyles} } .ttytext-removed-after-done { @@ -470,35 +460,56 @@ ${hiddenStageStyles} margin-left: -100vw; margin-right: 100vw; } -${(ttyLines.flatMap((v) => +${ttyLines + .flatMap((v) => v.kind === 'text' ? [ - ...v.value.map((v) => ({ - kind: 'text' as const, - ...v, - })), - ] - : [] - ).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};}` - : '', + v.bg ? `.ttytext-block.bg-\\[\\${v.bg}\\]{background:${v.bg};}` : '', ] - : [] - ), - ]).filter((v, i, a) => v.length !== 0 && a.indexOf(v) === i)).join('\n') - } -` -).join('') - } -${[...anim.exportToObject().values()].map(v => `#app.skip-animation ${v.selector} { + : []), + ]) + .filter((v, i, a) => v.length !== 0 && a.indexOf(v) === i) + .join('\n')} +`, + ) + .join('')} +${[...anim.exportToObject().values()] + .map((v) => `#app.skip-animation ${v.selector}`) + .join(',\n')} { + animation-name: none; animation-duration: 0.01ms; -}`).join('\n')} +} `; -console.log(output); +fs.writeFileSync( + 'src/routes/anim.css', + `${comment} +${ + esbuild.buildSync({ + stdin: { + contents: `${exported} +${tail}`, + loader: 'css', + }, + write: false, + minify: false, + }).outputFiles![0].text +}`, +); +fs.writeFileSync( + 'src/routes/no-anim.css', + `${comment} +${tail}`, +); diff --git a/src/routes/skip-animation/+page.svelte b/src/routes/skip-animation/+page.svelte index a252eb6..fdbbfec 100644 --- a/src/routes/skip-animation/+page.svelte +++ b/src/routes/skip-animation/+page.svelte @@ -1,5 +1,6 @@ <script lang="ts"> - import Page from '../+page.svelte'; + import Page from '../IndexPage.svelte'; + import '../no-anim.css'; </script> <svelte:head> |