Initial Commit

This commit is contained in:
Greg Kilmer
2026-07-04 07:42:24 -04:00
commit 71bffad507
77 changed files with 14437 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
/**
* reveal-init.ts
* ---------------------------------------------------------------------------
* The other half of Reveal.astro. Reveal.astro stamps each wrapper with the
* `reveal` class (opacity:0, set in global.css) and a `data-reveal` hook, but
* it ships NO script of its own. This module is what actually flips those
* elements to `is-visible` when they scroll into view.
*
* Without it every page's content is stuck at opacity:0 (the original bug).
*
* It also removes the `no-js` class from <html> as its first act, so that the
* `.no-js .reveal { opacity: 1 }` guard in global.css only applies when JS is
* genuinely unavailable — i.e. content is NEVER invisible, even if this script
* fails to load for some reason.
*
* Honors prefers-reduced-motion: elements are revealed instantly.
*/
export function initReveals(): void {
const root = document.documentElement;
// 1. Flip <html class="no-js"> -> the JS guard no longer applies; CSS reveals
// remain at opacity:0 until we toggle them (or instantly under reduced motion).
root.classList.remove('no-js');
const targets = Array.from(
document.querySelectorAll<HTMLElement>('[data-reveal]'),
);
if (targets.length === 0) return;
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// Reduced motion: show everything immediately, no observer needed.
if (reduce) {
for (const el of targets) el.classList.add('is-visible');
return;
}
// Anything already in view on load should appear at once (avoid a late pop).
const reveal = (el: HTMLElement) => el.classList.add('is-visible');
const io = new IntersectionObserver(
(entries, observer) => {
for (const entry of entries) {
if (entry.isIntersecting) {
reveal(entry.target as HTMLElement);
observer.unobserve(entry.target);
}
}
},
{ rootMargin: '0px 0px -8% 0px', threshold: 0.08 },
);
for (const el of targets) io.observe(el);
}
// Run on first load.
initReveals();
// Re-run after Astro View Transitions swap in new DOM.
document.addEventListener('astro:page-load', initReveals);
+37
View File
@@ -0,0 +1,37 @@
/**
* theme-init.ts
* ---------------------------------------------------------------------------
* Inlined (as a string) into the <head> of BaseLayout.astro so it runs BEFORE
* first paint. Reads the saved theme from localStorage, falling back to the OS
* preference via prefers-color-scheme, and sets `data-theme` on <html>.
*
* Doing this inline + blocking is what prevents the flash-of-wrong-theme (FOUC).
* Keep this file dependency-free and tiny: it ships to every page.
*/
export const themeInitScript = `
(function () {
try {
var STORAGE_KEY = 'gk-theme';
var stored = localStorage.getItem(STORAGE_KEY);
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
var theme = stored || (prefersDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
// React to OS theme changes only if the user hasn't chosen explicitly.
if (!stored) {
var mq = window.matchMedia('(prefers-color-scheme: dark)');
var onChange = function (e) {
document.documentElement.setAttribute('data-theme', e.matches ? 'dark' : 'light');
};
if (mq.addEventListener) mq.addEventListener('change', onChange);
else if (mq.addListener) mq.addListener(onChange);
}
} catch (e) {
// If anything fails (private mode, etc.), default to light.
document.documentElement.setAttribute('data-theme', 'light');
}
})();
`;
export default themeInitScript;