<!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;
      }
      body {
        padding: 3em;
        max-width: 52em;
        margin: 0 auto;
      }
      pre {
        margin: 0;
        padding: 3em;
        max-width: inherit;
        max-height: 100vh;
        overflow: scroll;
        background: rgb(192, 192, 192);
        position: absolute;
        top: 0;
        left: 50%;
        transition: all 1s cubic-bezier(0.68, -0.55, 0.265, 1.55);
        transform: translate(-50%, -100%);
      }
      .is-source-enabled pre {
        transform: translate(-50%, 0);
      }
      button {
        display: inline-block;
        border: none;
        margin: 0;
        text-decoration: none;
        font-family: inherit;
        font-size: 1rem;
        display: flex;
        justify-content: center;
        align-content: center;
        text-align: center;
        vertical-align: middle;
        background: var(--accent);
        color: var(--background);
        cursor: pointer;
        padding: 0.5em 1em;
        border-radius: 0 0 0.2em 0;
        box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
        -webkit-appearance: none;
        -moz-appearance: none;
      }
      h1,
      h2,
      h3,
      h4 {
        color: var(--accent);
        font-family: "Lobster", serif;
      }
      a {
        color: var(--accent);
        position: relative;
        text-decoration: none;
        padding: 0.1em;
      }
      a::after {
        content: "";
        position: absolute;
        background-color: var(--accent);
        position: absolute;
        left: 0;
        bottom: 3px;
        width: 100%;
        height: 1px;
        z-index: -1;
        transition: all 0.1s ease-in;
      }
      a:hover {
        color: var(--background);
        transition: all 0.3s ease-in-out;
      }
      a:hover::after {
        bottom: 0;
        height: 100%;
        transition: all 0.3s ease-in-out;
      }
      .menu,
      .burger {
        position: fixed;
        top: 0;
        left: 0;
      }
      .menu {
        padding: 1em;
        transform: translateX(-100%);
        display: flex;
        flex-direction: column;
        top: 0;
        bottom: 0;
        transition: all 0.3s ease-out;
        gap: 1em;
      }
      .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;
      }
      .menu a:hover {
        background: var(--accent);
        color: var(--background);
      }
      .menu-is-open .menu {
        transform: translateX(0);
        transition-duration: 0.1s;
      }
      .menu-is-open .burger {
        color: transparent;
      }

      .menu-is-open .burger,
      #Loading {
        top: 0;
        right: 0;
        width: 100%;
        height: 100%;
        border-radius: 0;
        background: rgba(17, 17, 17, 0.2);
        cursor: default;
      }
      #Loading {
        position: fixed;
        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;
      }
      #Loading > * {
        animation: load 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
      }
      .is-loading #Loading {
        opacity: 1;
        height: 100%;
        transition: opacity 1s ease-in, height 0 linear;
      }
      @keyframes load {
        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);
        }
      }
    </style>
  </head>
  <body>
    <nav class="left-nav">
      <button id="Burger" class="burger">&#8801;</button>
      <div id="Menu" class="menu"></div>
    </nav>
    <main id="Body"></main>
    <pre id="Source"></pre>
    <div id="Loading">
      <p>❤</p>
    </div>
    <script type="module">
      //@ts-check

      /*****************************************************************
       *
       * "THE FRAMEWORK"
       * a small set of utilities that help
       *
       ****************************************************************/
      /**
       * markdown parser. Remove if you don't use markdown
       */
      // @ts-ignore
      import { micromark } from "https://esm.sh/micromark@3?bundle";

      const is_debug_mode = /^localhost|127.0.0.1/.test(
        window.location.hostname
      );

      /**
       * A small utility to query elements and get back an array
       */
      const $ = (parent, selector) => [
        ...(!selector
          ? document.querySelectorAll(parent)
          : parent.querySelectorAll(selector)),
      ];

      /**
       * Assumes a provided url is external if it begins by a known protocol
       * @param {string} url
       */
      const isExternal = (url) =>
        url && /^(https?|mailto|tel|ftp|ipfs|dat):/.test(url);

      /**
       * Makes sure urls to local pages get passed through the routing system
       * @param {HTMLElement} container the element containing links to find
       */
      const rewriteLocalUrls = (container) => {
        $(container, "a").forEach((a) => {
          const href = a.getAttribute("href");
          if (href && !isExternal(href)) {
            a.setAttribute("href", "#/" + href.replace(/^\.?\//, ""));
          }
        });
        return container;
      };

      /**
       * Returns the hash part of the url, but only if it starts with a `/`
       * This allows regular hashes to continue to work
       * It reads also query parameters
       */
      const getCurrentHashUrl = () => {
        const [path, searchStr] = (
          window.location.hash[1] === "/" ? window.location.hash.slice(2) : ""
        ).split("?");
        const params = new URLSearchParams(searchStr);
        return { path, params };
      };

      /**
       * useful to check for transitions while developing styles, if the loading screen disappears too fast
       * uses micromark. You can plug a different parser if you prefer
       */
      const wait = is_debug_mode
        ? (val) => new Promise((ok) => setTimeout(ok, 1000, val))
        : (val) => val;

      /**
       * @param {string} htmlString
       */
      const generateDOM = (htmlString) =>
        /** @type {HTMLElement} */ (
          new DOMParser().parseFromString(
            `<div>${htmlString}</div>`,
            "text/html"
          ).firstChild
        );

      /**
       * Loads and parses a markdown document. Makes use of micromark
       * @param {string} path the path to load
       */
      const loadMarkdown = (path) =>
        fetch(is_debug_mode ? `./${path}?rand=${Math.random()}` : `./${path}`)
          .then((response) => response.text())
          .then(wait)
          .then((raw) => {
            const content = rewriteLocalUrls(generateDOM(micromark(raw)));
            const firstTitleElement = content.querySelector("h1");
            const title = firstTitleElement
              ? firstTitleElement.textContent
              : path.replace(/\.\w{2, 4}$/, "");
            return { title, raw, content };
          });

      /**
       * parses a filelist string. That's a string that looks like
       * ```
       * name dd-mm-yyyy link name
       * name dd-mm-yyyy
       * name linkname
       * name
       * ```
       * @param {string} lines
       */
      const parseFileList = (lines) =>
        lines
          .trim()
          .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.error(`could not parse line ${index}: [${line}]`);
              return null;
            }
            const {
              groups: { name, ext, date = today, title = name },
            } = result;
            const href = `/${name}.${ext}`;
            const date_unix = new Date(date).getTime();
            return { name, href, date, title, date_unix, index };
          })
          .filter(Boolean)
          .sort(({ date_unix: a }, { date_unix: b }) => a - b);

      /*****************************************************************
       *
       * Creating references to the important stuff
       *
       ****************************************************************/

      /**
       * The elements we will need
       */
      const [Menu, Body, Loading, Source, Burger] = [
        "Menu",
        "Body",
        "Loading",
        "Source",
        "Burger",
      ].map((id) => document.getElementById(id));

      /**
       * cache the original title to append it to the page title
       */
      const mainTitle = document.title;

      const showLoadingOverlay = () =>
        document.body.classList.add("is-loading");
      const hideLoadingOverlay = () =>
        document.body.classList.remove("is-loading");

      /*****************************************************************
       *
       * Router
       *
       * Things related to main navigation and to loading pages
       *
       ****************************************************************/

      /**
       * Listens to hashchange event, and attempts to read the url.
       * If the url is set,
       */
      const onHashChange = async (evt) => {
        const { path, params } = getCurrentHashUrl();
        if (!path) {
          return false;
        }
        showLoadingOverlay();
        const { title, raw, content } = await loadMarkdown(path);
        document.title = `${title} | ${mainTitle}`;
        Body.innerHTML = "";
        Source.innerHTML = raw;
        Body.appendChild(content);
        hideLoadingOverlay();
      };

      /**
       * Called when the file list is obtained (presumably through loading)
       * parses the file list, then fills the side navigation
       * If there's no page loaded, it also loads the first page in the list
       * (the list gets sorted by date, but the first line is the one that gets used)
       * @param {string} lines
       */
      const fillMenuFromFileList = (lines) => {
        const links = parseFileList(lines);
        Menu.innerHTML = links
          .map(({ href, title }) => `<a data-link href="#${href}">${title}</a>`)
          .join(`\n`);
        if (!getCurrentHashUrl().path) {
          const href = links.find(({ index }) => index === 0).href;
          window.location.hash = `#${href}`;
        } else {
          onHashChange();
        }
      };

      /*****************************************************************
       *
       * Bootstrapping
       *
       * this is where things actually happen
       *
       ****************************************************************/

      const loadFileList = () => {
        showLoadingOverlay();

        fetch("./files.txt")
          .then((response) => response.text())
          .then(fillMenuFromFileList);
      };

      /**
       * Loads the article list, parses it, creates the menu items
       */
      (function bootstrap() {
        Burger.addEventListener("click", () =>
          document.body.classList.toggle("menu-is-open")
        );

        window.addEventListener("hashchange", onHashChange);

        loadFileList();

        document.addEventListener(
          "keyup",
          ({ key }) =>
            key === "?" && document.body.classList.toggle("is-source-enabled")
        );
      })();
    </script>
  </body>
</html>