<!DOCTYPE html> <html> <head> <title>Tickle</title> <meta charset="UTF-8" /> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link href="https://fonts.googleapis.com/css2?family=Lobster&family=Nunito+Sans:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet" /> <style> :root { --accent: hotpink; --background: #fdfdfd; font-family: "Nunito Sans", sans-serif; } body, html { width: 100%; height: 100%; margin: 0; padding: 0; } .trigger { display: none; } .left-nav > * { position: fixed; top: 0; left: 0; } .left-nav > .menu { padding: 1em; transform: translateX(-100%); display: flex; flex-direction: column; top: 0; bottom: 0; transition: all 0.3s ease-out; gap: 1em; } .left-nav > .menu a { text-decoration: none; background: var(--background); color: var(--accent); box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; padding: 0.2em 0.4em; } .left-nav > .menu a:hover { background: var(--accent); color: var(--background); } .left-nav > .trigger:checked ~ .menu { transform: translateX(0); transition-duration: 0.1s; } .burger { width: 2em; height: 2em; display: flex; justify-content: center; align-content: center; text-align: center; vertical-align: middle; line-height: 2em; border-radius: 0.2em; box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; transition: background 0.5s ease; } .left-nav > .trigger:checked ~ .burger { color: transparent; } .left-nav > .trigger:checked ~ .burger, #Loading { top: 0; right: 0; width: 100%; height: 100%; border-radius: 0; background: rgba(17, 17, 17, 0.2); } #Loading { position: absolute; text-align: center; align-items: center; justify-content: center; font-size: 8rem; color: var(--accent); opacity: 0; transition: opacity 0.3s ease-in, height 0s linear 0.3s; height: 0; display: flex; overflow: hidden; } .is-loading #Loading { opacity: 1; height: 100%; transition: opacity 1s ease-in, height 0 linear; } #Loading::after { content: "❤"; animation: beat 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1); } @keyframes beat { 0% { transform: scale(0.95); } 5% { transform: scale(1.1); } 39% { transform: scale(0.85); } 45% { transform: scale(1); } 60% { transform: scale(0.95); } 100% { transform: scale(0.9); } } #Body { padding: 3em; max-width: 52em; margin: 0 auto; } #Body h1, #Body h2, #Body h3, #Body h4 { color: var(--accent); font-family: "Lobster", serif; } #Body a { color: var(--background); background-color: var(--accent); text-decoration: none; padding: 0.1em 0.4em; } </style> </head> <body> <nav class="left-nav"> <input id="main-nav" type="checkbox" class="trigger" /> <label for="main-nav" class="burger">≡</label> <div id="Menu" class="menu"></div> </nav> <header id="Menu"></header> <main id="Body"></main> <div id="Loading"></div> <script type="module"> /** * markdown parser. Remove if you don't use markdown */ // @ts-ignore import { micromark } from "https://esm.sh/micromark@3?bundle"; /** * useful to check for transitions while developing styles, if the loading screen disappears too fast * */ const wait = (val) => new Promise((ok) => setTimeout(ok, 1, val)); /** * The elements we will need */ const [Menu, Body, Loading] = ["Menu", "Body", "Loading"].map((id) => document.getElementById(id) ); /** * cache the original title to append it to the page title */ const mainTitle = document.title; const startLoading = () => document.body.classList.add("is-loading"); const stopLoading = () => document.body.classList.remove("is-loading"); const getCurrentPage = () => window.location.hash[1] === "/" ? window.location.hash.slice(2) : ""; const onHashChange = (evt) => { const path = getCurrentPage(); if (!path) { return false; } startLoading(); return fetch(`./${path}`) .then((response) => response.text()) .then(wait) .then((text) => { const [, title] = text.match(/^(#\s\w+)/) || text.match(/(.*?)\n===+/m) || [, path]; document.title = `${title} | ${mainTitle}`; Body.innerHTML = micromark(text); stopLoading(); }); }; window.addEventListener("hashchange", onHashChange); /** * Loads the article list, parses it, creates the menu items */ const start = () => { startLoading(); fetch("./files.txt") .then((response) => response.text()) .then((lines) => { Menu.innerHTML = lines .split(`\n`) .map((line, index) => { const today = new Date().toISOString().split("T")[0]; const result = line.match( /(?<name>.+)\.(?<ext>\w{2,3})(?:\s(?<date>[\d-]+)?(?<title>.+))?/ ); if (!result) { console.log(`could not parse line ${index}: [${line}]`); return null; } const { groups: { name, ext, date = today, title = name }, } = result; const href = `/${name}.${ext}`; if (!getCurrentPage()) { window.location.hash = `#${href}`; } return { name, href, date, title }; }) .filter(Boolean) .sort(({ date: a }, { date: b }) => a - b) .map( ({ href, title }) => `<a data-link href="#${href}">${title}</a>` ) .join(`\n`); onHashChange(); }); }; start(); </script> </body> </html>