index.astro 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. ---
  2. import Layout from "../layouts/Layout.astro";
  3. import Taskbar from "../components/taskbar.astro";
  4. import App from "../components/app.astro";
  5. import Aboutme from "../components/aboutme.astro";
  6. import Redirect from "../components/redirect.astro";
  7. import Contact from "../components/contact.astro";
  8. import Pgp from "../components/pgp.astro";
  9. import Projects from "../components/projects.astro";
  10. import PastWork from "../components/pastwork.astro";
  11. import Duolingo from "../components/duolingo.astro";
  12. import GitHubGraph from "../components/githubgraph.astro";
  13. import NowPlaying from "../components/nowplaying.astro";
  14. import Ram from "../components/ram.astro";
  15. // Projects
  16. import Summize from "../components/projects/summize.astro";
  17. import Notion from "../components/projects/notion.astro";
  18. // import Imdb from "../components/projects/imdb.astro";
  19. import "../styles/global.css";
  20. import me from "../assets/apps/me.png";
  21. import github from "../assets/apps/github.png";
  22. import pastwork from "../assets/apps/pastWork.png";
  23. import projects from "../assets/apps/projects.png";
  24. import blog from "../assets/apps/blog.png";
  25. import contact from "../assets/apps/contact.png";
  26. import pgp from "../assets/apps/pgp.png";
  27. import Glance from "../components/projects/glance.astro";
  28. import ramIcon from "../assets/apps/blog.png";
  29. let apps = [
  30. {
  31. name: "About Me",
  32. logo: me.src,
  33. windowId: "About Me",
  34. row: 1,
  35. },
  36. {
  37. name: "Projects",
  38. logo: projects.src,
  39. windowId: "Projects ",
  40. row: 1,
  41. },
  42. {
  43. name: "Past Work",
  44. logo: pastwork.src,
  45. windowId: "Past Work",
  46. row: 1,
  47. },
  48. {
  49. name: "Git",
  50. logo: github.src,
  51. redirectUrl: "https://git.simo.ng/simo",
  52. row: 2,
  53. },
  54. {
  55. name: "RAM",
  56. logo: ramIcon.src,
  57. windowId: "ram",
  58. row: 2,
  59. },
  60. {
  61. name: "Contact",
  62. logo: contact.src,
  63. windowId: "contact",
  64. row: 2,
  65. },
  66. {
  67. name: "PGP Key",
  68. logo: pgp.src,
  69. windowId: "pgp",
  70. fullscreen: true,
  71. row: 1,
  72. },
  73. ];
  74. let projectsArr: project[] = [
  75. {
  76. title: "Mikro",
  77. id: "mikro",
  78. },
  79. {
  80. title: "Glance",
  81. id: "glance",
  82. },
  83. {
  84. title: "Mneme",
  85. id: "mneme",
  86. },
  87. ];
  88. const row1Items = apps.filter((item) => item.row === 1);
  89. const row2Items = apps.filter((item) => item.row === 2);
  90. ---
  91. <Layout title="Simo">
  92. <div style="width: 100%; height: calc(100vh - 3.5rem); ">
  93. <Pgp />
  94. <div style="display: flex;">
  95. <div
  96. style="height: 20rem; display: flex; flex-direction: column; align-items: flex-start;"
  97. >
  98. {row1Items.map((item) => <App {item} />)}
  99. </div>
  100. <div
  101. style="height: 20rem; display: flex; flex-direction: column; align-items: flex-start;"
  102. >
  103. {row2Items.map((item) => <App {item} />)}
  104. </div>
  105. </div>
  106. <Aboutme />
  107. <Redirect />
  108. <Contact />
  109. <iframe
  110. id="SimoSearch"
  111. title="SimoSearch"
  112. data-src="https://search.simo.ng/#embed"
  113. loading="lazy"
  114. referrerpolicy="no-referrer"
  115. allowfullscreen
  116. style="
  117. display: none;
  118. position: absolute;
  119. left: 62%;
  120. top: 10%;
  121. border: 0;
  122. outline: 0;
  123. background: transparent;
  124. "
  125. ></iframe>
  126. <Ram />
  127. <NowPlaying />
  128. <PastWork />
  129. <Projects projects={projectsArr} />
  130. <Summize />
  131. <Notion />
  132. <Glance />
  133. <Duolingo />
  134. <GitHubGraph />
  135. </div>
  136. <Taskbar />
  137. </Layout>
  138. <script define:vars={{ apps, projectsArr }}>
  139. let dino = `
  140. ██████████████
  141. ████░░████████████
  142. ██████████████████
  143. ██████████████████
  144. ██████████████████
  145. ████████
  146. ██████████████░░
  147. ██████
  148. ██ ██████████
  149. ██▒▒ ▒▒▒▒██████████▒▒▒▒
  150. ████▓▓ ██████████████ ▒▒
  151. ██████▒▒▒▒████████████████
  152. ██████████████████████████
  153. ██████████████████████
  154. ██████████████████
  155. ▒▒██████████████
  156. ▒▒██████▒▒██▓▓
  157. ████ ▓▓
  158. ██▒▒ ▓▓
  159. ██ ██
  160. `;
  161. console.log(dino);
  162. const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  163. function getWindow(id) {
  164. return document.getElementById(id);
  165. }
  166. function raiseUpSpec(id) {
  167. const target = getWindow(id);
  168. if (!target) return;
  169. const windows = Array.from(document.getElementsByClassName('window'));
  170. const highestIndex = windows.reduce((highest, element) => {
  171. const zindex = parseInt(element.style.zIndex || '0', 10);
  172. return Number.isFinite(zindex) && zindex > highest ? zindex : highest;
  173. }, 0);
  174. target.style.zIndex = String(highestIndex + 1);
  175. }
  176. function loadDeferredIframes(root = document) {
  177. const load = (iframe) => {
  178. if (!(iframe instanceof HTMLIFrameElement)) return;
  179. const src = iframe.dataset.src;
  180. if (src && !iframe.src) iframe.src = src;
  181. };
  182. load(root);
  183. if ('querySelectorAll' in root) {
  184. root.querySelectorAll('iframe[data-src]:not([src])').forEach(load);
  185. }
  186. }
  187. function showWindow(id, options = {}) {
  188. const windowEl = getWindow(id);
  189. if (!windowEl) return;
  190. if (options.fullscreen) windowEl.classList.add('fullscreen');
  191. windowEl.style.display = 'block';
  192. loadDeferredIframes(windowEl);
  193. raiseUpSpec(id);
  194. const focusTarget = windowEl.querySelector('button, a, input, textarea, select, [tabindex]:not([tabindex="-1"])');
  195. if (focusTarget instanceof HTMLElement) focusTarget.focus({ preventScroll: true });
  196. }
  197. function openRedirect(redirectUrl) {
  198. const redirectElement = getWindow('Redirect') || getWindow('redirect');
  199. if (!redirectElement || !redirectUrl) return;
  200. redirectElement.dataset.redirecturl = redirectUrl;
  201. updateRedirectMessage();
  202. showWindow(redirectElement.id);
  203. }
  204. function updateRedirectMessage() {
  205. const redirectElement = getWindow('Redirect') || getWindow('redirect');
  206. if (!redirectElement) return;
  207. const redirectUrl = redirectElement.dataset.redirecturl;
  208. if (!redirectUrl) return;
  209. const messageElement = redirectElement.querySelector('h2');
  210. if (messageElement) {
  211. messageElement.textContent = 'You are about to be redirected to ';
  212. const link = document.createElement('a');
  213. link.className = 'redirUrl';
  214. link.href = redirectUrl;
  215. link.textContent = redirectUrl.replace(/^https?:\/\//, '');
  216. link.style.cssText = 'cursor: pointer; color: blue; text-decoration: underline;';
  217. messageElement.appendChild(link);
  218. }
  219. const buttonElement = redirectElement.querySelector('#okButton');
  220. if (buttonElement && !buttonElement.dataset.redirectBound) {
  221. buttonElement.dataset.redirectBound = 'true';
  222. buttonElement.addEventListener('click', () => {
  223. const currentUrl = redirectElement.dataset.redirecturl;
  224. if (currentUrl) window.location.href = currentUrl;
  225. });
  226. }
  227. }
  228. function dragElement(elmnt) {
  229. let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  230. const header = document.getElementById(elmnt.id + 'header') || elmnt;
  231. header.addEventListener('mousedown', dragMouseDown);
  232. header.addEventListener('touchstart', dragMouseDown, { passive: true });
  233. function dragMouseDown(e) {
  234. raiseUpSpec(elmnt.id);
  235. if (e.type === 'touchstart') {
  236. pos3 = e.touches[0].clientX;
  237. pos4 = e.touches[0].clientY;
  238. } else {
  239. pos3 = e.clientX;
  240. pos4 = e.clientY;
  241. }
  242. document.addEventListener('mouseup', closeDragElement);
  243. document.addEventListener('touchend', closeDragElement);
  244. document.addEventListener('mousemove', elementDrag);
  245. document.addEventListener('touchmove', elementDrag, { passive: false });
  246. }
  247. function elementDrag(e) {
  248. if (e.cancelable) e.preventDefault();
  249. if (e.type === 'touchmove') {
  250. pos1 = pos3 - e.touches[0].clientX;
  251. pos2 = pos4 - e.touches[0].clientY;
  252. pos3 = e.touches[0].clientX;
  253. pos4 = e.touches[0].clientY;
  254. } else {
  255. pos1 = pos3 - e.clientX;
  256. pos2 = pos4 - e.clientY;
  257. pos3 = e.clientX;
  258. pos4 = e.clientY;
  259. }
  260. elmnt.style.top = elmnt.offsetTop - pos2 + 'px';
  261. elmnt.style.left = elmnt.offsetLeft - pos1 + 'px';
  262. }
  263. function closeDragElement() {
  264. document.removeEventListener('mouseup', closeDragElement);
  265. document.removeEventListener('touchend', closeDragElement);
  266. document.removeEventListener('mousemove', elementDrag);
  267. document.removeEventListener('touchmove', elementDrag);
  268. }
  269. }
  270. Array.from(document.getElementsByClassName("project")).forEach((project) => {
  271. // Projects window uses buttons with data-project for the preview pane.
  272. // Skip wiring those into the legacy "open a new window" behavior.
  273. if (project instanceof HTMLElement && project.dataset && project.dataset.project) {
  274. return;
  275. }
  276. let found = projectsArr.find((i) => i.id + "item" == project.id);
  277. if (!found) return;
  278. let id = found.id;
  279. if (id == "pgpcord") {
  280. project.addEventListener("click", () => {
  281. setTimeout(() => {
  282. openRedirect("https://blog.simo.ng/articles/WIP/pgpcord");
  283. }, 1);
  284. });
  285. return;
  286. }
  287. project.addEventListener("click", () => {
  288. setTimeout(() => {
  289. console.log(id);
  290. const el = document.getElementsByClassName(id)[0];
  291. if (!el) return;
  292. showWindow(el.id);
  293. }, 1);
  294. });
  295. });
  296. Array.from(document.getElementsByClassName("window")).forEach((window) => {
  297. dragElement(window);
  298. window.querySelectorAll('.title-bar-controls button').forEach((btn) => {
  299. btn.addEventListener('mousedown', (e) => e.stopPropagation());
  300. btn.addEventListener('touchstart', (e) => e.stopPropagation());
  301. });
  302. window.addEventListener("click", () => {
  303. raiseUpSpec(window.id);
  304. });
  305. Array.from(document.querySelectorAll(".projectwindow a")).forEach((link) => {
  306. link.addEventListener("click", (e) => {
  307. e.preventDefault();
  308. openRedirect(link.href);
  309. });
  310. });
  311. const titleButtons = Array.from(
  312. window.querySelectorAll('.title-bar-controls button')
  313. );
  314. const minimizeBtn = titleButtons.find(
  315. (btn) => (btn.getAttribute('aria-label') || '').toLowerCase() === 'minimize'
  316. );
  317. const closeBtn = titleButtons.find(
  318. (btn) => (btn.getAttribute('aria-label') || '').toLowerCase() === 'close'
  319. );
  320. if (minimizeBtn) {
  321. minimizeBtn.addEventListener('click', () => {
  322. minimizeWindow(window.id);
  323. });
  324. }
  325. if (closeBtn) {
  326. closeBtn.addEventListener('click', () => {
  327. window.classList.add('closed');
  328. removeFromTaskbar(window.id);
  329. const onEnd = () => {
  330. window.classList.remove('closed');
  331. window.style.display = 'none';
  332. };
  333. window.addEventListener('animationend', onEnd, { once: true });
  334. setTimeout(() => {
  335. if (window.style.display !== 'none') {
  336. window.classList.remove('closed');
  337. window.style.display = 'none';
  338. }
  339. }, 250);
  340. });
  341. }
  342. });
  343. apps.forEach((app) => {
  344. const itemName = app.name.replace(' ', '');
  345. const launcher = document.getElementById(itemName);
  346. if (!launcher) return;
  347. const activate = (event) => {
  348. event.preventDefault();
  349. if (app.redirectUrl) {
  350. openRedirect(app.redirectUrl);
  351. return;
  352. }
  353. if (app.windowId) showWindow(app.windowId, { fullscreen: Boolean(app.fullscreen) });
  354. };
  355. launcher.addEventListener('click', activate);
  356. launcher.addEventListener('keydown', (event) => {
  357. if (event.key === 'Enter' || event.key === ' ') activate(event);
  358. });
  359. });
  360. function minimizeWindow(windowId) {
  361. const window = document.getElementById(windowId);
  362. addToTaskbar(windowId);
  363. const windowRect = window.getBoundingClientRect();
  364. const taskbarButton = document.getElementById(`taskbar-${windowId}`);
  365. const buttonRect = taskbarButton.getBoundingClientRect();
  366. const translateX = buttonRect.left - windowRect.left;
  367. const translateY = buttonRect.top - windowRect.top;
  368. if (!prefersReducedMotion) {
  369. window.style.transition = "all 0.3s ease-out";
  370. window.style.transform = `translate(${translateX}px, ${translateY}px) scale(0.1)`;
  371. window.style.opacity = "0";
  372. }
  373. setTimeout(() => {
  374. window.style.display = "none";
  375. window.style.transition = "";
  376. window.style.transform = "";
  377. window.style.opacity = "";
  378. }, prefersReducedMotion ? 0 : 300);
  379. }
  380. // Expose a tiny API for legacy components that wire their own controls.
  381. window.minimizeWindow = minimizeWindow;
  382. window.removeFromTaskbar = removeFromTaskbar;
  383. window.raiseUpSpec = raiseUpSpec;
  384. function restoreWindow(windowId) {
  385. const window = document.getElementById(windowId);
  386. const taskbarButton = document.getElementById(`taskbar-${windowId}`);
  387. const buttonRect = taskbarButton.getBoundingClientRect();
  388. const windowRect = window.getBoundingClientRect();
  389. const translateX = buttonRect.left - windowRect.left;
  390. const translateY = buttonRect.top - windowRect.top;
  391. if (!prefersReducedMotion) {
  392. window.style.transform = `translate(${translateX}px, ${translateY}px) scale(0.1)`;
  393. window.style.opacity = "0";
  394. }
  395. window.style.display = "block";
  396. removeFromTaskbar(windowId);
  397. raiseUpSpec(windowId);
  398. requestAnimationFrame(() => {
  399. if (prefersReducedMotion) return;
  400. window.style.transition = "all 0.3s ease-out";
  401. window.style.transform = "translate(0, 0) scale(1)";
  402. window.style.opacity = "1";
  403. setTimeout(() => {
  404. window.style.transition = "";
  405. window.style.transform = "";
  406. window.style.opacity = "";
  407. }, 300);
  408. });
  409. }
  410. function addToTaskbar(windowId) {
  411. const taskbarWindows = document.getElementById("taskbar-windows");
  412. if (document.getElementById(`taskbar-${windowId}`)) {
  413. return;
  414. }
  415. const window = document.getElementById(windowId);
  416. const titleBar = window.querySelector(".title-bar-text");
  417. const title = titleBar ? titleBar.textContent : windowId;
  418. const icon = "";
  419. const button = document.createElement("button");
  420. button.id = `taskbar-${windowId}`;
  421. button.style.cssText = `
  422. min-width: 140px;
  423. max-width: 180px;
  424. height: 2.25rem;
  425. padding: 0 0.5rem;
  426. overflow: hidden;
  427. cursor: pointer;
  428. background-color: #c0c0c0;
  429. box-shadow: -2px -2px #e0dede, -2px 0 #e0dede, 0 -2px #e0dede, -4px -4px white, -4px 0 white, 0 -4px white, 2px 2px #818181, 0 2px #818181, 2px 0 #818181, 2px -2px #e0dede, -2px 2px #818181, -4px 2px white, -4px 4px black, 4px 4px black, 4px 0 black, 0 4px black, 2px -4px white, 4px -4px black;
  430. display: flex;
  431. align-items: center;
  432. gap: 0.35rem;
  433. border: none;
  434. `;
  435. const iconSpan = document.createElement("span");
  436. iconSpan.textContent = icon;
  437. iconSpan.style.cssText = `
  438. font-size: 18px;
  439. flex-shrink: 0;
  440. line-height: 1;
  441. `;
  442. const textSpan = document.createElement("span");
  443. textSpan.textContent = title;
  444. textSpan.style.cssText = `
  445. overflow: hidden;
  446. text-overflow: ellipsis;
  447. white-space: nowrap;
  448. font-weight: bold;
  449. font-size: 11px;
  450. letter-spacing: 0.3px;
  451. `;
  452. button.appendChild(iconSpan);
  453. button.appendChild(textSpan);
  454. button.addEventListener("mousedown", () => {
  455. button.style.boxShadow = "-2px -2px #818181, -2px 0 #818181, 0 -2px #818181, -4px -4px black, -4px 0 black, 0 -4px black, 2px 2px #e0dede, 0 2px #e0dede, 2px 0 #e0dede, 2px -2px #818181, -2px 2px #e0dede, -4px 2px black, -4px 4px white, 4px 4px white, 4px 0 white, 0 4px white, 2px -4px black, 4px -4px white";
  456. });
  457. button.addEventListener("mouseup", () => {
  458. button.style.boxShadow = "-2px -2px #e0dede, -2px 0 #e0dede, 0 -2px #e0dede, -4px -4px white, -4px 0 white, 0 -4px white, 2px 2px #818181, 0 2px #818181, 2px 0 #818181, 2px -2px #e0dede, -2px 2px #818181, -4px 2px white, -4px 4px black, 4px 4px black, 4px 0 black, 0 4px black, 2px -4px white, 4px -4px black";
  459. });
  460. button.addEventListener("click", () => {
  461. restoreWindow(windowId);
  462. });
  463. taskbarWindows.appendChild(button);
  464. }
  465. function removeFromTaskbar(windowId) {
  466. const button = document.getElementById(`taskbar-${windowId}`);
  467. if (button) {
  468. button.remove();
  469. }
  470. }
  471. const startButton = document.querySelector('button[aria-label="startButton"]');
  472. if (startButton) {
  473. startButton.addEventListener('click', () => {
  474. const w = document.getElementById('SimoSearch');
  475. if (!w) return;
  476. loadDeferredIframes(w);
  477. w.style.display = 'block';
  478. w.style.zIndex = '9999';
  479. });
  480. }
  481. </script>