first commit
This commit is contained in:
258
index.html
Normal file
258
index.html
Normal file
@ -0,0 +1,258 @@
|
||||
<!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 = () => {
|
||||
const [path, searchStr] = (
|
||||
window.location.hash[1] === "/" ? window.location.hash.slice(2) : ""
|
||||
).split("?");
|
||||
const params = new URLSearchParams(searchStr);
|
||||
return { path, params };
|
||||
};
|
||||
|
||||
const onHashChange = (evt) => {
|
||||
const { path, params } = 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}`;
|
||||
if (params.has("source")) {
|
||||
Body.innerHTML = `<pre>${text}</pre>`;
|
||||
} else {
|
||||
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().path) {
|
||||
window.location.hash = `#${href}`;
|
||||
}
|
||||
console.log({ name, href, date, title });
|
||||
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>
|
Reference in New Issue
Block a user