Blog Post Page

by Nicholas Berlette

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.9.5/cdn.js"></script>
<script src="https://unpkg.com/@unocss/[email protected]/preset-icons.global.js"></script>
<script src="https://unpkg.com/@unocss/[email protected]/preset-typography.global.js"></script>
<script src="https://unpkg.com/@unocss/[email protected]/preset-attributify.global.js"></script>
<script src="https://unpkg.com/@unocss/[email protected]/preset-uno.global.js"></script>
<script src="https://unpkg.com/@unocss/[email protected]/preset-wind.global.js"></script>
<script>
globalThis.__unocss = {
  extendTheme(theme) {
    return {
      ...theme,
      fontFamily: {
        ...theme.fontFamily,
        mono: ["OperatorMono Nerd Font", "Operator Mono Lig", "Operator Mono ScreenSmart", "Dank Mono", "DM Mono", "IBM Plex Mono", "Mono Lisa", "ui-monospace"],
        sans: ["Gotham Narrow ScreenSmart", "Gotham Narrow", "Whitney Narrow", "Whitney Narrow SSm", "Gotham Narrow SSm", "Inter", "ui-sans-serif", "AppleColorEmoji", "Apple Color Emoji"],
        serif: ["Sentinel ScreenSmart", "Sentinel SSm", "Sentinel", "Bitter", "IBM Plex Serif", "Georgia", "Times New Roman", "ui-serif"],
        header: ["Obsidian", "Knockout-93", "Gotham", "Whitney", "Helvetica"],
      },
    };
  },
  presets: [
  	() => window.__unocss_runtime.presets.presetIcons({
      cdn: 'https://esm.sh/',
    }),
    () => window.__unocss_runtime.presets.presetUno({
      variablePrefix: "tw-",
    }),
    () => window.__unocss_runtime.presets.presetWind({
      variablePrefix: "tw-",
    }),
    () => window.__unocss_runtime.presets.presetTypography({
      
    }),
    () => window.__unocss_runtime.presets.presetAttributify({
      
    }),
  ]
};

window.onload = () => {
  document.querySelector("#toggle-theme").addEventListener("click", toggleDark);
};
</script>
<script src="https://unpkg.com/@unocss/[email protected]/core.global.js"></script>

<div class="max-w-3xl mx-auto px-4 sm:px-6 xl:max-w-5xl xl:px-0">
  <header class="flex...

CSS

@layer utilities, components;

@layer components {
  :root {
    --bg: light-dark(#fff, #000);
    --heading: light-dark(#111, #fff);
    --kbd-border: light-dark(#d1d5da, #4b5563);
    --primary: light-dark(#dc2626, #ef4444);
    --primary-deep: light-dark(#b91c1c, #f87171);
    --subtle: light-dark(#e5e7eb, #1f2937);
    --fg-shallow: light-dark(#9ca3af, #6b7280);
    --fg: light-dark(#374151, #d1d5db);
    --fg-deep: light-dark(#1f2937, #e5e7eb);
    --fg-deeper: light-dark(#111827, #f3f4f6);

    --tw-bg-opacity: 1;
    --button-bg-light: rgba(231, 229, 228, var(--tw-bg-opacity));
    --button-bg-dark: rgba(45, 45, 45, var(--tw-bg-opacity)));
    --button-bg: light-dark(var(--button-bg-light), var(--button-bg-dark));

    --color-bg: var(--bg);
    --color-heading: var(--heading);
    --color-primary: var(--primary);
    --color-primary-deep: var(--primary-deep);
    --color-subtle: var(--subtle);
    --color-fg-shallow: var(--fg-shallow);
    --color-fg: var(--fg);
    --color-fg-deep: var(--fg-deep):
    --color-fg-deeper: var(--fg-deeper);
    
    color-scheme: light dark;

    background-color: var(--bg) !important;
    
    -moz-tab-size: 2;
    -o-tab-size: 2;
    tab-size: 2;
  }

  html, body {
    background-color: var(--bg) !important;
    color: var(--fg);
    transition: background-color 250ms ease-in, color 150ms ease-out;
  }

  .dark:root {
    color-scheme: dark !important;
  }

  .light:root {
    color-scheme: light !important;
  }

  .prose {
    color: var(--color-fg-deep);
  }

  .prose a {
    color: var(--primary-deep)
  }

  .prose a:hover,
  .prose a:hover>kbd {
    text-decoration: underline
  }

  .icon-next {
    margin-left: .25rem
  }

  .icon-prev {
    margin-right: .25rem
  }

  .icon {
    display: block;
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    fill: var(--fg);
    transform: translateY(1px)
  }

  kbd {
    background-color: var(--prism-background);
    border: 1px solid var(--kbd-border);
   ...

TypeScript

// #region color schemes

type ColorScheme = "auto" | "dark" | "light";

/* toggleDark(isDark, false); */
function toggleDark<E extends { matches?: boolean }>(event: E, persisted = true): void {
  const dark = "dark";
  const light = "light";
  const auto = "auto";
  const key = "color-scheme";

	const target: Element = globalThis.document.documentElement;
  const list: DOMStringList | null = target.classList;
  
	const storageArea = globalThis[persisted ? "localStorage" : "sessionStorage"];
  let setting = storageArea.getItem(key) || auto;
 
  const prefersDark =  setting === auto && (event != null && "matches" in event ? event : mql).matches;
  
//  let value = setting === light || (prefersDark && setting !== light);
  let value = prefersDark;
  
 if (event != null && !("matches" in event)) {
    // cycle to the next setting for each click.
    // e.g. light -> dark, dark -> auto, auto -> light
    setting = setting === light ? dark : setting === dark ? auto : light;
    value = setting === dark || (setting === auto && mql.matches);
  }
  
  list.toggle(dark, setting === dark || prefersDark);
  list.toggle(light, setting === light);

  storageArea.setItem(key, setting);
  const el = event.target ?? document.querySelector("#toggle-theme");
  const cl = el?.classList;
  cl?.toggle("i-line-md:sunny-outline-to-moon-alt-loop-transition", setting === dark);
  cl?.toggle("i-line-md:moon-to-sunny-outline-loop-transition", setting === light);
  cl?.toggle("i-line-md:light-dark-loop", setting === auto);
}

const mql = window.matchMedia("(prefers-color-scheme: dark)");
toggleDark(mql);

mql.addEventListener("change", toggleDark);

// #endregion color schemes

// #region blog api (WIP)

class Blog {
  constructor(
    public posts: Blog.Post[] = [],
    readonly config: Blog.Config = Blog.defaultConfig,
  ) {}
}
 
declare namespace Blog {
  interface Config {
    
  }
  
  interface Metadata {
    name: string;
    icon: string;
    theme: Color
  }
}

Blog.defaultConfig =...