migrate y2k.app to a simple page here
59
components/VNC/VNC.module.css
Normal file
@ -0,0 +1,59 @@
|
||||
.container {
|
||||
padding: 1.5em;
|
||||
|
||||
/* specific retro wallpaper tile is set randomly by JS onload */
|
||||
background-repeat: repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.display {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 800px;
|
||||
max-height: 600px;
|
||||
|
||||
/* fix fuzziness: https://stackoverflow.com/a/13492784 */
|
||||
image-rendering: optimizeSpeed;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
}
|
||||
|
||||
.display div {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.display div canvas {
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.cmd {
|
||||
display: block;
|
||||
margin: auto;
|
||||
width: 700px;
|
||||
height: 400px;
|
||||
padding: 12px;
|
||||
z-index: -100;
|
||||
|
||||
background-color: #000000;
|
||||
color: #cccccc;
|
||||
|
||||
font-size: 0.925em;
|
||||
font-weight: 500;
|
||||
line-height: 2;
|
||||
white-space: pre-wrap;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.blink {
|
||||
animation: blink 1s step-end infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
109
components/VNC/VNC.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
import { useRef, useEffect, useState, memo } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import classNames from "classnames";
|
||||
import RFB from "@novnc/novnc/core/rfb.js";
|
||||
|
||||
import styles from "./VNC.module.css";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const WEBSOCKETS_SERVER = "wss://socket.y2k.app";
|
||||
|
||||
const VNC = ({ className }: Props) => {
|
||||
const router = useRouter();
|
||||
|
||||
// we definitely do NOT want this page to connect more than once!
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
// DOS-style box for text
|
||||
const consoleRef = useRef<HTMLDivElement>(null);
|
||||
const statusRef = useRef<HTMLSpanElement>(null);
|
||||
|
||||
// the actual connection and virtual screen (injected by noVNC when it's ready)
|
||||
const rfbRef = useRef(null);
|
||||
const screenRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// end the session when the current page changes
|
||||
const disconnectVM = () => {
|
||||
try {
|
||||
rfbRef.current.disconnect();
|
||||
} catch (e) {} // eslint-disable-line no-empty
|
||||
};
|
||||
|
||||
// prepare for possible navigation away from this page
|
||||
router.events.on("routeChangeStart", disconnectVM);
|
||||
|
||||
return () => {
|
||||
// unassign event listener
|
||||
router.events.off("routeChangeStart", disconnectVM);
|
||||
};
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window.WebSocket) {
|
||||
// browser doesn't support websockets
|
||||
statusRef.current.textContent =
|
||||
"WebSockets must be enabled to play in the Y2K Sandbox!!!\n\nPress the Any key to continue.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// https://github.com/novnc/noVNC/blob/master/docs/API.md
|
||||
rfbRef.current = new RFB(screenRef.current, WEBSOCKETS_SERVER, {
|
||||
wsProtocols: ["binary", "base64"],
|
||||
});
|
||||
|
||||
// this is the one and only time we're spinning up a VM (hopefully)
|
||||
setLoaded(true);
|
||||
|
||||
// VM connected
|
||||
rfbRef.current.addEventListener("connect", () => {
|
||||
console.log("successfully connected to VM socket!");
|
||||
|
||||
// hide the console when VM connects
|
||||
consoleRef.current.style.display = "none";
|
||||
});
|
||||
|
||||
// VM disconnected
|
||||
rfbRef.current.addEventListener("disconnect", (detail) => {
|
||||
console.warn("VM ended session remotely:", detail);
|
||||
|
||||
// make the console reappear now that the VM has gone poof for whatever reason (doesn't really matter)
|
||||
try {
|
||||
consoleRef.current.style.display = "block";
|
||||
statusRef.current.textContent =
|
||||
"Oh dear, it looks like something's gone very wrong. Sorry about that.\n\nPress the Any key or refresh the page to continue.";
|
||||
} catch (e) {} // eslint-disable-line no-empty
|
||||
});
|
||||
|
||||
console.log(
|
||||
"🤓 Hey, fellow nerd! Want to see how I made this? Check out this post: https://jarv.is/notes/y2k-sandbox/"
|
||||
);
|
||||
}, [loaded]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.container, className)}
|
||||
style={{
|
||||
// set a random retro wallpaper tile for the content area
|
||||
background: `url('/static/images/y2k/tiles/tile_${Math.floor(20 * Math.random())}.png')`,
|
||||
}}
|
||||
>
|
||||
<div ref={consoleRef} className={classNames("monospace", styles.cmd)}>
|
||||
<span ref={statusRef}>Spinning up your very own personal computer, please wait!</span>{" "}
|
||||
<span className={styles.blink}>_</span>
|
||||
</div>
|
||||
|
||||
<div ref={screenRef} className={styles.display} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(VNC);
|
@ -12,10 +12,10 @@ image: "/static/images/notes/y2k-sandbox/screenshot.png"
|
||||
|
||||
A few months ago, I stumbled upon [my first website ever](https://jakejarvis.github.io/my-first-website/) on an old floppy disk. Despite the instant cringing, I [uploaded it](https://github.com/jakejarvis/my-first-website) to GitHub, [collected other iterations](/previously/), and made an [#awesome-list](https://github.com/jakejarvis/awesome-first-code) of others who were brave and/or shameless enough to do the same. But why not take that ~~one~~ 1,000 steps further?
|
||||
|
||||
Introducing the [**Y2K Sandbox**](https://y2k.app/) — with fully-featured, fully-isolated, on-demand [**Windows Millennium Edition®**](https://www.youtube.com/watch?v=CaNDeyYP98A) virtual machines, simply to experience my first website in its natural Internet Explorer 5 habitat. And maybe play some [3D Pinball: Space Cadet](https://en.wikipedia.org/wiki/Full_Tilt!_Pinball#3D_Pinball_for_Windows_%E2%80%93_Space_Cadet). Oh, and [Microsoft Bob](https://en.wikipedia.org/wiki/Microsoft_Bob) is there too if you want to say hello and catch up. 🤓
|
||||
Introducing the [**Y2K Sandbox**](/y2k/) — with fully-featured, fully-isolated, on-demand [**Windows Millennium Edition®**](https://www.youtube.com/watch?v=CaNDeyYP98A) virtual machines, simply to experience my first website in its natural Internet Explorer 5 habitat. And maybe play some [3D Pinball: Space Cadet](https://en.wikipedia.org/wiki/Full_Tilt!_Pinball#3D_Pinball_for_Windows_%E2%80%93_Space_Cadet). Oh, and [Microsoft Bob](https://en.wikipedia.org/wiki/Microsoft_Bob) is there too if you want to say hello and catch up. 🤓
|
||||
|
||||
<Figure src="/public/static/images/notes/y2k-sandbox/screenshot.png" width="865" height="649" priority>
|
||||
[**Play in the Y2K Sandbox, at your own risk.**](https://y2k.app/)
|
||||
[**Play in the Y2K Sandbox, at your own risk.**](/y2k/)
|
||||
</Figure>
|
||||
|
||||
The backend is powered by [**QEMU**](https://www.qemu.org/) (as a Pentium III emulator) inside isolated **Docker** containers, [**websocketd**](https://github.com/joewalnes/websocketd) (an **_awesome_** lightweight WebSockets server written in Go), and [**Cloudflare Tunnels**](https://www.cloudflare.com/products/tunnel/) (for some protection), all tied together with some [Ruby code](https://github.com/jakejarvis/y2k/blob/main/container/bin/boot.rb) and [shell scripts](https://github.com/jakejarvis/y2k/tree/main/host). ~~I'll push the backend scripts up to GitHub once I have a chance to untangle the spaghetti code. 🍝~~
|
||||
|
@ -29,6 +29,7 @@
|
||||
"@giscus/react": "^1.0.1",
|
||||
"@hcaptcha/react-hcaptcha": "^1.1.0",
|
||||
"@next/bundle-analyzer": "^12.0.9",
|
||||
"@novnc/novnc": "1.3.0",
|
||||
"@octokit/graphql": "^4.8.0",
|
||||
"@primer/octicons": "^16.3.0",
|
||||
"@sentry/node": "^6.17.3",
|
||||
|
@ -1,3 +1,4 @@
|
||||
import Link from "next/link";
|
||||
import { NextSeo } from "next-seo";
|
||||
import Content from "../components/Content/Content";
|
||||
import PageTitle from "../components/PageTitle/PageTitle";
|
||||
@ -53,13 +54,15 @@ const Previously = () => (
|
||||
color: <span className="limegreen">limegreen</span>
|
||||
</code>
|
||||
...{" "}
|
||||
<a href="https://y2k.app/" target="_blank" rel="noopener noreferrer">
|
||||
Click for the{" "}
|
||||
<strong>
|
||||
<em>FULL</em>
|
||||
</strong>{" "}
|
||||
experience anyway.
|
||||
</a>
|
||||
<Link href="/y2k/" prefetch={false}>
|
||||
<a>
|
||||
Click for the{" "}
|
||||
<strong>
|
||||
<em>FULL</em>
|
||||
</strong>{" "}
|
||||
experience anyway.
|
||||
</a>
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<IFrame
|
||||
|
36
pages/y2k.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import { NextSeo } from "next-seo";
|
||||
|
||||
// obviously, an interactive VNC display will not work even a little bit server-side
|
||||
const VNC = dynamic(() => import("../components/VNC/VNC"), { ssr: false });
|
||||
|
||||
const Y2K = () => (
|
||||
<>
|
||||
<NextSeo
|
||||
title="Y2K Sandbox: Powered by Windows Me™ 💾"
|
||||
description="My first website on a Windows Me-powered time machine. You've been warned."
|
||||
openGraph={{
|
||||
title: "Y2K Sandbox: Powered by Windows Me™",
|
||||
}}
|
||||
/>
|
||||
|
||||
<VNC />
|
||||
|
||||
<style jsx global>{`
|
||||
/* make the viewport a bit larger by un-sticking the nav bar */
|
||||
header {
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
/* make an exception for the wrapper (and its background) to fill up the normal content area */
|
||||
main {
|
||||
padding: 0 !important;
|
||||
}
|
||||
main > div {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
);
|
||||
|
||||
export default Y2K;
|
BIN
public/static/images/y2k/tiles/tile_0.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
public/static/images/y2k/tiles/tile_1.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
public/static/images/y2k/tiles/tile_10.png
Normal file
After Width: | Height: | Size: 185 B |
BIN
public/static/images/y2k/tiles/tile_11.png
Normal file
After Width: | Height: | Size: 96 B |
BIN
public/static/images/y2k/tiles/tile_12.png
Normal file
After Width: | Height: | Size: 390 B |
BIN
public/static/images/y2k/tiles/tile_13.png
Normal file
After Width: | Height: | Size: 396 B |
BIN
public/static/images/y2k/tiles/tile_14.png
Normal file
After Width: | Height: | Size: 129 B |
BIN
public/static/images/y2k/tiles/tile_15.png
Normal file
After Width: | Height: | Size: 298 B |
BIN
public/static/images/y2k/tiles/tile_16.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
public/static/images/y2k/tiles/tile_17.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
public/static/images/y2k/tiles/tile_18.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/static/images/y2k/tiles/tile_19.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/static/images/y2k/tiles/tile_2.png
Normal file
After Width: | Height: | Size: 92 B |
BIN
public/static/images/y2k/tiles/tile_3.png
Normal file
After Width: | Height: | Size: 174 B |
BIN
public/static/images/y2k/tiles/tile_4.png
Normal file
After Width: | Height: | Size: 187 B |
BIN
public/static/images/y2k/tiles/tile_5.png
Normal file
After Width: | Height: | Size: 181 B |
BIN
public/static/images/y2k/tiles/tile_6.png
Normal file
After Width: | Height: | Size: 96 B |
BIN
public/static/images/y2k/tiles/tile_7.png
Normal file
After Width: | Height: | Size: 96 B |
BIN
public/static/images/y2k/tiles/tile_8.png
Normal file
After Width: | Height: | Size: 287 B |
BIN
public/static/images/y2k/tiles/tile_9.png
Normal file
After Width: | Height: | Size: 100 B |
@ -1214,6 +1214,11 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@novnc/novnc@1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@novnc/novnc/-/novnc-1.3.0.tgz#06db5ff55eae646a623d3717d8bffaf131ffb93a"
|
||||
integrity sha512-tR87mY5ADtaELadmZfW937JO/p8fRdz3wkPoqwhqB/vY1XnTQeLSWwkp4yMlr4iIDY0iCficfzFYX5EHMh4MHw==
|
||||
|
||||
"@octokit/endpoint@^6.0.1":
|
||||
version "6.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658"
|
||||
|