Mercurial
changeset 256:30c2196d03d4
[site] Integrate Zenbu themes and components
Co-authored-by: Copilot <[email protected]>
line wrap: on
line diff
--- a/.claude/skills/zenbu-design-system/SKILL.md Tue Aug 04 15:12:18 2026 -0700 +++ b/.claude/skills/zenbu-design-system/SKILL.md Tue Aug 04 16:49:01 2026 -0700 @@ -40,6 +40,9 @@ widths and reject horizontal page overflow. - Preserve the warm, restrained MUJI-like visual character: paper neutrals, natural material colors, strong typography, and deliberate color accents. +- Treat themes as semantic-token compositions. Preserve the built-in Paper, + Ink, and Playful themes plus automatic system light/dark behavior; components + must not branch on theme names. - Use semantic `--zen-color-*` aliases in component CSS. Use primitive ramps or the ten data tokens only when a semantic role does not fit; never encode meaning through color alone.
--- a/.claude/skills/zenbu-personal-site/SKILL.md Tue Aug 04 15:12:18 2026 -0700 +++ b/.claude/skills/zenbu-personal-site/SKILL.md Tue Aug 04 16:49:01 2026 -0700 @@ -13,6 +13,9 @@ - `mrjunejune/src/`: static site source files and HTML templates. - `mrjunejune/src/parts/`: shared HTML includes used by `{{...}}` template placeholders. - `mrjunejune/src/public/`: public assets, PWA files, media, generated JS, icons, resume PDF. +- Zenbu UI assets are copied from `//design_system:components` and + `//design_system:styles` into `src/public/design-system/` by Bazel. Do not + hand-copy or fork shared component files. - `mrjunejune/test/`: integration tests and snapshots. - `mrjunejune/BUILD`: Bazel build, bundle, asset movement, and test-visible filegroups. - `mrjunejune/.config` and `.config.development`: server configuration. Do not commit secrets. @@ -68,4 +71,7 @@ - For binary responses, set `content-length` and avoid string-only operations on body bytes. - If a route is part of the public website, add or update snapshot coverage in `mrjunejune/test`. - Keep PWA changes consistent across `manifest.json`, `sw.js`, icons, and `PWA_SETUP.md`. +- Site themes use `data-zen-theme` with Auto, Paper, Ink, and Playful states. + Preserve the More Sugar font aliases and keep theme-specific values in shared + design-system tokens rather than branching inside components. - Reuse `seobeo`, `dowa`, `deita`, `s3`, and `markdown_converter` instead of adding parallel implementations.
--- a/design_system/README.md Tue Aug 04 15:12:18 2026 -0700 +++ b/design_system/README.md Tue Aug 04 16:49:01 2026 -0700 @@ -57,6 +57,8 @@ Every visual token is a `--zen-*` custom property and can be overridden by the application. See [`COLORS.md`](COLORS.md) for the complete warm color ramps, material aliases, semantic roles, dark-theme behavior, and data palette. +Named [`themes`](THEMES.md) compose those tokens as Paper, Ink, and Playful +experiences in addition to automatic system light/dark behavior. Use [`zen-icon`](ICONS.md) for all interface iconography. Design-system source does not use emoji, ad hoc SVG, icon fonts, or third-party icon packages.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/design_system/THEMES.md Tue Aug 04 16:49:01 2026 -0700 @@ -0,0 +1,28 @@ +# Zenbu UI themes + +Themes are named token compositions, not separate component stylesheets. +Applications select one with `data-zen-theme` on the document root: + +```html +<html data-zen-theme="paper"> +``` + +Built-in themes: + +- `paper`: warm neutral surfaces, restrained natural accents, editorial type; +- `ink`: dark charcoal surfaces with paper-like text and softer status colors; +- `playful`: brighter clay, persimmon, violet, and rounded shapes; +- no attribute: follow the operating-system light/dark preference. + +Applications can provide their own fonts without changing component CSS: + +```css +:root { + --zen-font-editorial: "More Thin", sans-serif; + --zen-font-playful: "More", sans-serif; +} +``` + +Import `themes.css` after `tokens.css` and before component CSS. Components +continue to consume semantic tokens, so every built-in and application theme +inherits the same accessibility, motion, and native-HTML behavior.
--- a/design_system/src/index.html Tue Aug 04 15:12:18 2026 -0700 +++ b/design_system/src/index.html Tue Aug 04 16:49:01 2026 -0700 @@ -6,6 +6,7 @@ <meta name="color-scheme" content="light dark" /> <title>Zenbu UI</title> <link rel="stylesheet" href="/styles/tokens.css" /> + <link rel="stylesheet" href="/styles/themes.css" /> <link rel="stylesheet" href="/styles/components.css" /> <link rel="stylesheet" href="/storybook.css" /> <script type="module" src="/storybook.js"></script>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/design_system/src/styles/themes.css Tue Aug 04 16:49:01 2026 -0700 @@ -0,0 +1,95 @@ +:root[data-zen-theme="paper"] { + color-scheme: light; + --zen-color-canvas: var(--zen-color-neutral-100); + --zen-color-surface: var(--zen-color-neutral-50); + --zen-color-surface-raised: var(--zen-color-neutral-50); + --zen-color-surface-muted: var(--zen-color-neutral-100); + --zen-color-surface-sunken: var(--zen-color-neutral-200); + --zen-color-text: var(--zen-color-neutral-950); + --zen-color-text-muted: var(--zen-color-neutral-600); + --zen-color-text-subtle: var(--zen-color-neutral-500); + --zen-color-border: var(--zen-color-neutral-800); + --zen-color-border-muted: var(--zen-color-neutral-300); + --zen-color-accent: var(--zen-color-amber-400); + --zen-color-accent-hover: var(--zen-color-amber-300); + --zen-color-accent-muted: var(--zen-color-amber-100); + --zen-color-info: var(--zen-color-blue-600); + --zen-color-success: var(--zen-color-green-600); + --zen-color-warning: var(--zen-color-orange-600); + --zen-color-danger: var(--zen-color-red-600); + --zen-color-focus: var(--zen-color-violet-600); + --zen-font-sans: var( + --zen-font-editorial, + ui-rounded, + system-ui, + sans-serif + ); +} + +:root[data-zen-theme="ink"] { + color-scheme: dark; + --zen-color-canvas: var(--zen-color-neutral-950); + --zen-color-surface: var(--zen-color-neutral-900); + --zen-color-surface-raised: var(--zen-color-neutral-800); + --zen-color-surface-muted: var(--zen-color-neutral-800); + --zen-color-surface-sunken: var(--zen-color-neutral-950); + --zen-color-text: var(--zen-color-neutral-50); + --zen-color-text-muted: var(--zen-color-neutral-300); + --zen-color-text-subtle: var(--zen-color-neutral-400); + --zen-color-border: var(--zen-color-neutral-200); + --zen-color-border-muted: var(--zen-color-neutral-700); + --zen-color-accent: var(--zen-color-amber-300); + --zen-color-accent-hover: var(--zen-color-amber-200); + --zen-color-accent-muted: var(--zen-color-amber-800); + --zen-color-info: var(--zen-color-blue-300); + --zen-color-success: var(--zen-color-green-300); + --zen-color-warning: var(--zen-color-orange-300); + --zen-color-danger: var(--zen-color-red-300); + --zen-color-on-info: var(--zen-color-neutral-950); + --zen-color-on-success: var(--zen-color-neutral-950); + --zen-color-on-warning: var(--zen-color-neutral-950); + --zen-color-on-danger: var(--zen-color-neutral-950); + --zen-color-focus: var(--zen-color-violet-300); + --zen-color-on-accent: var(--zen-color-neutral-950); + --zen-color-text-inverse: var(--zen-color-neutral-950); + --zen-font-sans: var( + --zen-font-editorial, + ui-rounded, + system-ui, + sans-serif + ); +} + +:root[data-zen-theme="playful"] { + color-scheme: light; + --zen-color-canvas: var(--zen-color-rose-100); + --zen-color-surface: var(--zen-color-amber-50); + --zen-color-surface-raised: var(--zen-color-neutral-50); + --zen-color-surface-muted: var(--zen-color-orange-100); + --zen-color-surface-sunken: var(--zen-color-violet-100); + --zen-color-text: var(--zen-color-blue-900); + --zen-color-text-muted: var(--zen-color-violet-700); + --zen-color-text-subtle: var(--zen-color-blue-600); + --zen-color-border: var(--zen-color-violet-700); + --zen-color-border-muted: var(--zen-color-rose-300); + --zen-color-accent: var(--zen-color-orange-300); + --zen-color-accent-hover: var(--zen-color-amber-300); + --zen-color-accent-muted: var(--zen-color-orange-100); + --zen-color-info: var(--zen-color-blue-600); + --zen-color-success: var(--zen-color-green-600); + --zen-color-warning: var(--zen-color-amber-600); + --zen-color-danger: var(--zen-color-rose-600); + --zen-color-on-success: var(--zen-color-neutral-50); + --zen-color-on-warning: var(--zen-color-neutral-50); + --zen-color-focus: var(--zen-color-violet-500); + --zen-color-on-accent: var(--zen-color-blue-950); + --zen-font-sans: var( + --zen-font-playful, + ui-rounded, + system-ui, + sans-serif + ); + --zen-radius-sm: 0.65rem; + --zen-radius-md: 1rem; + --zen-radius-lg: 1.5rem; +}
--- a/design_system/src/styles/tokens.css Tue Aug 04 15:12:18 2026 -0700 +++ b/design_system/src/styles/tokens.css Tue Aug 04 16:49:01 2026 -0700 @@ -268,7 +268,7 @@ } @media (prefers-color-scheme: dark) { - :root:not([data-zen-theme="light"]) { + :root:not([data-zen-theme]) { color-scheme: dark; --zen-color-canvas: #101218; --zen-color-surface: #181b23;
--- a/design_system/test/storybook_test.js Tue Aug 04 15:12:18 2026 -0700 +++ b/design_system/test/storybook_test.js Tue Aug 04 16:49:01 2026 -0700 @@ -138,6 +138,7 @@ } for (const asset of [ '/styles/tokens.css', + '/styles/themes.css', '/styles/components.css', '/styles/shadcn.css', '/components/index.js', @@ -480,6 +481,52 @@ { length: 10 }, (_, index) => value(`data-${index + 1}`), ); + const themes = ['paper', 'ink', 'playful'].map(theme => { + root.dataset.zenTheme = theme; + const channels = value => value.match(/\d+(?:\.\d+)?/g) + .slice(0, 3) + .map(Number); + const luminance = value => { + const converted = channels(value).map(channel => { + const normalized = channel / 255; + return normalized <= 0.04045 + ? normalized / 12.92 + : ((normalized + 0.055) / 1.055) ** 2.4; + }); + return converted[0] * 0.2126 + + converted[1] * 0.7152 + + converted[2] * 0.0722; + }; + const contrasts = [ + 'info', + 'success', + 'warning', + 'danger', + ].map(tone => { + const probe = document.createElement('span'); + probe.style.background = `var(--zen-color-${tone})`; + probe.style.color = `var(--zen-color-on-${tone})`; + document.body.append(probe); + const probeStyle = getComputedStyle(probe); + const foreground = luminance(probeStyle.color); + const background = luminance(probeStyle.backgroundColor); + const contrast = (Math.max(foreground, background) + 0.05) / + (Math.min(foreground, background) + 0.05); + probe.remove(); + return contrast; + }); + return { + canvas: value('canvas'), + contrast: Math.min(...contrasts), + font: getComputedStyle(root) + .getPropertyValue('--zen-font-sans') + .trim(), + name: theme, + radius: getComputedStyle(root) + .getPropertyValue('--zen-radius-lg') + .trim(), + }; + }); delete root.dataset.zenTheme; return { darkData, @@ -488,6 +535,7 @@ lightMuted, materials, rampsComplete, + themes, }; }); assert.equal(colorTokenState.rampsComplete, true); @@ -495,6 +543,18 @@ assert.equal(new Set(colorTokenState.data).size, 10); assert.equal(new Set(colorTokenState.darkData).size, 10); assert.notDeepEqual(colorTokenState.data, colorTokenState.darkData); + assert.equal( + new Set(colorTokenState.themes.map(theme => theme.canvas)).size, + 3, + ); + assert.notEqual( + colorTokenState.themes.find(theme => theme.name === 'paper').radius, + colorTokenState.themes.find(theme => theme.name === 'playful').radius, + ); + assert.ok( + colorTokenState.themes.every(theme => theme.contrast >= 4.5), + JSON.stringify(colorTokenState.themes), + ); assert.notEqual( colorTokenState.lightMuted, colorTokenState.darkMuted,
--- a/mrjunejune/BUILD Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/BUILD Tue Aug 04 16:49:01 2026 -0700 @@ -134,6 +134,18 @@ ) move_files_into_dir( + name = "design_system_components", + srcs = ["//design_system:components"], + dest = "src/public/design-system/components", +) + +move_files_into_dir( + name = "design_system_styles", + srcs = ["//design_system:styles"], + dest = "src/public/design-system/styles", +) + +move_files_into_dir( name = "icons", srcs = [ "//assets:icons", @@ -161,7 +173,7 @@ srcs = glob( ["src/**"], exclude = ["src/**/*.png"], - ) + [":react_pages", ":shared_js_non_public", ":shared_js_file", ":rich_editor_js", ":icons", ":generated_public_webp", ":hlsjs_runtime"], + ) + [":react_pages", ":shared_js_non_public", ":shared_js_file", ":rich_editor_js", ":design_system_components", ":design_system_styles", ":icons", ":generated_public_webp", ":hlsjs_runtime"], visibility = ["//mrjunejune/test:__pkg__"], )
--- a/mrjunejune/src/base.css Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/src/base.css Tue Aug 04 16:49:01 2026 -0700 @@ -74,7 +74,7 @@ 0 -8px -24px rgba(159, 140, 96, 33%), 0 -16px -32px rgba(159, 140, 96, 33%); } @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { + :root:not([data-zen-theme]) { --white: hsl(224, 10%, 10%); --black: hsl(0, 0%, 90%); --darkgray: #cccccc; @@ -98,6 +98,39 @@ } } +:root { + --zen-font-editorial: "More Thin", "Roboto", sans-serif; + --zen-font-playful: "More", "More Thin", sans-serif; + --zen-font-sans: var(--zen-font-editorial); +} + +:root:not([data-zen-theme]), +:root[data-zen-theme] { + --white: var(--zen-color-surface); + --black: var(--zen-color-text); + --darkgray: var(--zen-color-border-strong); + --lightgray: var(--zen-color-border-muted); + --gray-light: var(--zen-color-border-muted); + --green: var(--zen-color-success); + --orange: var(--zen-color-warning); + --purple: var(--zen-color-violet-400); + --red: var(--zen-color-danger); + --blue: var(--zen-color-info); + --darktext: var(--zen-color-text-muted); + --text: var(--zen-color-text); + --graytext: var(--zen-color-text-muted); + --lighttext: var(--zen-color-text-subtle); + --awesome: var(--zen-color-persimmon); + --accent: var(--zen-color-info); + --accent-dark: var(--zen-color-indigo); + --gray-dark: var(--zen-color-text); + --gray-gradient: linear-gradient( + var(--zen-color-canvas), + var(--zen-color-surface) + ); + --box-shadow: var(--zen-shadow-md); +} + /* --- fonts --- */ /* @font-face { @@ -141,7 +174,7 @@ } body { - font-family: "More Thin", sans-serif; + font-family: var(--zen-font-sans, "More Thin", sans-serif); margin: 0; padding: 0; text-align: left; @@ -149,7 +182,7 @@ background-size: 100% 600px; word-wrap: break-word; overflow-wrap: break-word; - color: rgb(var(--gray-dark)); + color: var(--gray-dark); font-size: 20px; line-height: 1.7; -webkit-font-smoothing: antialiased; @@ -170,7 +203,7 @@ h5, h6 { margin: 0 0 0.5rem 0; - color: rgb(var(--black)); + color: var(--black); line-height: 1.2; } @@ -228,7 +261,7 @@ code { padding: 2px 5px; - background-color: rgb(var(--gray-light)); + background-color: var(--gray-light); border-radius: 2px; } @@ -250,7 +283,7 @@ hr { border: none; - border-top: 1px solid rgb(var(--gray-light)); + border-top: 1px solid var(--gray-light); } .center { @@ -395,4 +428,3 @@ /* Icon color = button text color */ background-color: currentColor; } -
--- a/mrjunejune/src/index.html Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/src/index.html Tue Aug 04 16:49:01 2026 -0700 @@ -64,6 +64,28 @@ z-index: 101; cursor: pointer; } + + .site-link-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); + gap: var(--zen-space-4); + } + + .site-link-grid > a { + color: inherit; + text-decoration: none; + } + + .site-link-grid zen-card, + .site-link-grid article { + height: 100%; + } + + .site-link-grid p { + margin: 0; + color: var(--zen-color-text-muted); + font-size: 0.9rem; + } </style> </head> <body> @@ -80,12 +102,28 @@ </div> <h2>Links</h2> - <ul> - <li><a href="https://zenbu.babocoder.com/file/tip">Repository</a> - Check out my code</li> - <li><a href="/blog">Blogs</a> - My thoughts / Experiments </li> - <li><a href="/resume">Resume</a> - My professional experiences </li> - <li><a href="/tools">Tools</a> - Things I made for myself </li> - </ul> + <div class="site-link-grid"> + <a href="https://zenbu.babocoder.com/file/tip"> + <zen-card interactive> + <article><h3>Repository</h3><p>Browse the code behind these experiments.</p></article> + </zen-card> + </a> + <a href="/blog"> + <zen-card interactive> + <article><h3>Blogs</h3><p>Thoughts, experiments, and technical notes.</p></article> + </zen-card> + </a> + <a href="/resume"> + <zen-card interactive> + <article><h3>Resume</h3><p>Professional experience and selected work.</p></article> + </zen-card> + </a> + <a href="/tools"> + <zen-card interactive> + <article><h3>Tools</h3><p>Small things I built for myself.</p></article> + </zen-card> + </a> + </div> <canvas id="background"></canvas> <canvas id="game"></canvas> <canvas id="gameUI"></canvas>
--- a/mrjunejune/src/index.js Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/src/index.js Tue Aug 04 16:49:01 2026 -0700 @@ -1,50 +1,74 @@ const THEME_STORAGE_KEY = 'theme-preference'; -// Get stored preference or detect system preference -function getThemePreference() -{ - const stored = localStorage.getItem(THEME_STORAGE_KEY); - if (stored) { - return stored; - } - return 'auto'; +const THEMES = ['auto', 'paper', 'ink', 'playful']; +const THEME_LABELS = { + auto: 'Auto', + paper: 'Paper', + ink: 'Ink', + playful: 'Playful', +}; + +function normalizeTheme(value) { + if (value === 'light') return 'paper'; + if (value === 'dark') return 'ink'; + return THEMES.includes(value) ? value : 'auto'; +} + +function getThemePreference() { + return normalizeTheme(localStorage.getItem(THEME_STORAGE_KEY)); +} + +function updateThemeColor() { + const probe = document.createElement('span'); + probe.style.background = 'var(--zen-color-canvas)'; + document.body.append(probe); + const color = getComputedStyle(probe).backgroundColor; + probe.remove(); + document.querySelector('meta[name="theme-color"]') + ?.setAttribute('content', color); } -function applyTheme(preference) -{ - const root = document.documentElement; - - if (preference === 'light') { - root.classList.remove('dark', 'auto'); - root.classList.add('light-mode'); - } else if (preference === 'dark') { - root.classList.remove('light-mode', 'auto'); - root.classList.add('dark'); - } else { - // Auto - remove manual overrides and let CSS media query handle it - root.classList.remove('light-mode', 'dark'); - root.classList.add('auto'); - } +function updateThemeControl(preference) { + const toggle = document.querySelector('#themeToggle'); + const label = document.querySelector('#themeName'); + if (label) label.textContent = THEME_LABELS[preference]; + toggle?.setAttribute( + 'aria-label', + `Change site theme. Current theme: ${THEME_LABELS[preference]}`, + ); } -(function() { - const currentPreference = getThemePreference(); - applyTheme(currentPreference); -})(); - -/* Toggle lights auto and dark */ -const toggle = document.querySelector('#themeToggle'); -toggle.addEventListener('click', function() { - let preference = getThemePreference(); +function applyTheme(value, persist = false) { + const preference = normalizeTheme(value); + const root = document.documentElement; + root.classList.remove('light-mode', 'dark', 'auto'); + if (preference === 'auto') delete root.dataset.zenTheme; + else root.dataset.zenTheme = preference; + if (persist) localStorage.setItem(THEME_STORAGE_KEY, preference); + updateThemeControl(preference); + requestAnimationFrame(updateThemeColor); + document.dispatchEvent(new CustomEvent('zen-theme-change', { + detail: { theme: preference }, + })); + return preference; +} - // Cycle through: auto -> light -> dark -> auto - if (preference === 'auto') { - preference = 'light'; - } else if (preference === 'light') { - preference = 'dark'; - } else { - preference = 'auto'; - } +const currentPreference = applyTheme(getThemePreference()); +if (localStorage.getItem(THEME_STORAGE_KEY) !== currentPreference) { + localStorage.setItem(THEME_STORAGE_KEY, currentPreference); +} - localStorage.setItem(THEME_STORAGE_KEY, preference); - applyTheme(preference); +document.querySelector('#themeToggle')?.addEventListener('click', () => { + const current = getThemePreference(); + const next = THEMES[(THEMES.indexOf(current) + 1) % THEMES.length]; + applyTheme(next, true); }); + +matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + if (getThemePreference() === 'auto') updateThemeColor(); +}); + +window.MrJuneJuneTheme = Object.freeze({ + apply: theme => applyTheme(theme, true), + current: getThemePreference, + themes: [...THEMES], +});
--- a/mrjunejune/src/parts/base_head.html Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/src/parts/base_head.html Tue Aug 04 16:49:01 2026 -0700 @@ -1,6 +1,6 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> -<meta name="theme-color" content="#544e43"> +<meta name="theme-color" content="#f7f3e8"> <link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg"> <link rel="manifest" href="/public/manifest.json"> <link rel="apple-touch-icon" href="/public/epi_all_colors.svg"> @@ -16,8 +16,14 @@ <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/epi_all_colors.svg" as="image"> +<link rel="stylesheet" href="/public/design-system/styles/tokens.css" /> +<link rel="stylesheet" href="/public/design-system/styles/themes.css" /> <link rel="preload" href="/base.css" as="style" /> <link rel="stylesheet" href="/base.css" /> +<link rel="stylesheet" href="/public/design-system/styles/components.css" /> +<script + type="module" + src="/public/design-system/components/index.js" +></script> <script src="/public/pwa-register.js" defer></script> -
--- a/mrjunejune/src/parts/header.html Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/src/parts/header.html Tue Aug 04 16:49:01 2026 -0700 @@ -1,35 +1,41 @@ <style> :root { --header-background: var(--white); - --header-color: rgb(var(--black)); + --header-color: var(--black); --link-hover-accent: var(--awesome); } - /* Fixed icon in top left corner */ - #themeToggle { + #themeControl { position: fixed; top: 20px; left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; } - #themeToggle:hover { + #themeControl:hover { transform: scale(1.05); } + #themeToggle { + flex-direction: column; + min-width: 4.5rem; + padding: 0.35rem; + border-radius: 1.5rem; + } + + #themeName { + font-family: "More", sans-serif; + font-size: 0.65rem; + line-height: 1; + } + /* Professional header */ header { margin: auto; padding: 1.5em 1em; font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); + box-shadow: var(--zen-shadow-sm); width: 720px; max-width: calc(100% - 2em); text-align: center; @@ -53,7 +59,7 @@ /* Mobile responsiveness */ @media (max-width: 720px) { - #themeToggle { + #themeControl { top: 15px; left: 15px; } @@ -68,7 +74,7 @@ } @media (max-width: 480px) { - #themeToggle { + #themeControl { top: 10px; left: 10px; } @@ -94,20 +100,25 @@ } /* 2. MANUAL DARK OVERRIDE */ - html.dark { + html[data-zen-theme="ink"] { --logo-invert: invert(1); --epi-grayscale: grayscale(1); } /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { + html[data-zen-theme="paper"] { --logo-invert: invert(0); --epi-grayscale: brightness(2.9) grayscale(1); } + + html[data-zen-theme="playful"] { + --logo-invert: invert(0); + --epi-grayscale: saturate(1.25); + } /* 4. SYSTEM PREFERENCE */ @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { + :root:not([data-zen-theme]) { --logo-invert: invert(1); } } @@ -126,12 +137,25 @@ } </style> -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> +<zen-button id="themeControl" variant="quiet"> + <button + id="themeToggle" + type="button" + aria-label="Change site theme" + > + <img + id="epiChan" + class="epi-logo" + alt="" + src="/public/epi_all_colors.svg" + height="50" + width="50" + > + <span id="themeName" aria-live="polite">Auto</span> + </button> +</zen-button> <header> <h1><a href="/">MrJuneJune</a></h1> </header> <script src="/index.js"></script> -
--- a/mrjunejune/src/public/dog-game.js Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/src/public/dog-game.js Tue Aug 04 16:49:01 2026 -0700 @@ -51,8 +51,8 @@ function isDarkMode() { const root = document.documentElement; - if (root.classList.contains("dark")) return true; - if (root.classList.contains("light-mode")) return false; + if (root.dataset.zenTheme === "ink") return true; + if (root.dataset.zenTheme) return false; return darkModePreference.matches; } @@ -324,8 +324,9 @@ window.addEventListener('resize', resizeCanvas); new MutationObserver(updateStarPalette).observe( document.documentElement, - { attributes: true, attributeFilter: ["class"] } + { attributes: true, attributeFilter: ["data-zen-theme"] } ); +document.addEventListener("zen-theme-change", updateStarPalette); darkModePreference.addEventListener("change", updateStarPalette); window.onload = () => {
--- a/mrjunejune/src/public/pwa-register.js Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/src/public/pwa-register.js Tue Aug 04 16:49:01 2026 -0700 @@ -2,7 +2,7 @@ if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker - .register('/public/sw.js') + .register('/sw.js', { scope: '/' }) .then((registration) => { // console.log('[PWA] Service Worker registered:', registration.scope);
--- a/mrjunejune/src/public/sw.js Tue Aug 04 15:12:18 2026 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,143 +0,0 @@ -// Service Worker for MrJuneJune PWA -const CACHE_VERSION = 'v4-hlsjs'; -const CACHE_NAME = `mrjunejune-${CACHE_VERSION}`; - -// Files to cache immediately on install -const STATIC_CACHE = [ - '/', - '/base.css', - '/public/epi_all_colors.svg', - '/public/fonts/Roboto-Regular.ttf', - '/public/fonts/Roboto-Thin.ttf', - '/public/fonts/more-sugar.regular.otf', - '/public/fonts/more-sugar.thin.otf', -]; - -// Install event - cache static assets -self.addEventListener('install', (event) => { - console.log('[SW] Installing service worker...'); - - event.waitUntil( - caches.open(CACHE_NAME).then((cache) => { - console.log('[SW] Caching static assets'); - return cache.addAll(STATIC_CACHE); - }).then(() => { - console.log('[SW] Skip waiting'); - return self.skipWaiting(); - }) - ); -}); - -// Activate event - clean up old caches -self.addEventListener('activate', (event) => { - console.log('[SW] Activating service worker...'); - - event.waitUntil( - caches.keys().then((cacheNames) => { - return Promise.all( - cacheNames.map((cacheName) => { - if (cacheName !== CACHE_NAME) { - console.log('[SW] Deleting old cache:', cacheName); - return caches.delete(cacheName); - } - }) - ); - }).then(() => { - console.log('[SW] Claiming clients'); - return self.clients.claim(); - }) - ); -}); - -// Fetch event - serve from cache, fallback to network -self.addEventListener('fetch', (event) => { - const { request } = event; - const url = new URL(request.url); - - // Skip non-GET requests - if (request.method !== 'GET') { - return; - } - - // Skip chrome-extension and other non-http(s) requests - if (!url.protocol.startsWith('http')) { - return; - } - - // Skip API calls and media uploads (always go to network) - if (url.pathname.startsWith('/api/')) { - return; - } - - // The HLS tool and media must always reflect the current player version. - if (url.pathname.startsWith('/tools/hls_player') || - url.pathname.startsWith('/public/hls-sample/')) { - return; - } - - event.respondWith( - caches.match(request).then((cachedResponse) => { - if (cachedResponse) { - console.log('[SW] Serving from cache:', url.pathname); - return cachedResponse; - } - - // Not in cache, fetch from network - return fetch(request).then((networkResponse) => { - // Only cache successful responses - if (!networkResponse || networkResponse.status !== 200 || networkResponse.type === 'error') { - return networkResponse; - } - - // Cache specific file types - const shouldCache = - url.pathname.endsWith('.css') || - url.pathname.endsWith('.js') || - url.pathname.endsWith('.svg') || - url.pathname.endsWith('.jpg') || - url.pathname.endsWith('.webp') || - url.pathname.endsWith('.woff') || - url.pathname.endsWith('.woff2') || - url.pathname.endsWith('.ttf') || - url.pathname.endsWith('.otf') || - url.pathname.startsWith('/blog/') || - url.pathname.startsWith('/notes/') || - url.pathname === '/'; - - if (shouldCache) { - const responseToCache = networkResponse.clone(); - caches.open(CACHE_NAME).then((cache) => { - console.log('[SW] Caching new resource:', url.pathname); - cache.put(request, responseToCache); - }); - } - - return networkResponse; - }).catch((error) => { - console.log('[SW] Fetch failed:', error); - - // Return offline page for HTML requests - if (request.headers.get('Accept').includes('text/html')) { - return caches.match('/offline.html'); - } - }); - }) - ); -}); - -// Handle messages from the client -self.addEventListener('message', (event) => { - if (event.data && event.data.type === 'SKIP_WAITING') { - self.skipWaiting(); - } - - if (event.data && event.data.type === 'CLEAR_CACHE') { - event.waitUntil( - caches.keys().then((cacheNames) => { - return Promise.all( - cacheNames.map((cacheName) => caches.delete(cacheName)) - ); - }) - ); - } -});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/src/sw.js Tue Aug 04 16:49:01 2026 -0700 @@ -0,0 +1,162 @@ +// Root-scoped Service Worker for MrJuneJune PWA +const CACHE_VERSION = 'v5-zenbu-themes'; +const CACHE_NAME = `mrjunejune-${CACHE_VERSION}`; + +// Files to cache immediately on install +const STATIC_CACHE = [ + '/', + '/index.js', + '/base.css', + '/public/design-system/styles/tokens.css', + '/public/design-system/styles/themes.css', + '/public/design-system/styles/components.css', + '/public/design-system/styles/shadcn.css', + '/public/design-system/components/index.js', + '/public/design-system/components/alert.js', + '/public/design-system/components/button.js', + '/public/design-system/components/card.js', + '/public/design-system/components/collections.js', + '/public/design-system/components/disclosure.js', + '/public/design-system/components/field.js', + '/public/design-system/components/forms.js', + '/public/design-system/components/icon.js', + '/public/design-system/components/notifications.js', + '/public/design-system/components/overlays.js', + '/public/design-system/components/primitives.js', + '/public/design-system/components/stack.js', + '/public/design-system/components/story.js', + '/public/epi_all_colors.svg', + '/public/fonts/Roboto-Regular.ttf', + '/public/fonts/Roboto-Thin.ttf', + '/public/fonts/more-sugar.regular.otf', + '/public/fonts/more-sugar.thin.otf', +]; + +// Install event - cache static assets +self.addEventListener('install', (event) => { + console.log('[SW] Installing service worker...'); + + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + console.log('[SW] Caching static assets'); + return cache.addAll(STATIC_CACHE); + }).then(() => { + console.log('[SW] Skip waiting'); + return self.skipWaiting(); + }) + ); +}); + +// Activate event - clean up old caches +self.addEventListener('activate', (event) => { + console.log('[SW] Activating service worker...'); + + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => { + if (cacheName !== CACHE_NAME) { + console.log('[SW] Deleting old cache:', cacheName); + return caches.delete(cacheName); + } + }) + ); + }).then(() => { + console.log('[SW] Claiming clients'); + return self.clients.claim(); + }) + ); +}); + +// Fetch event - serve from cache, fallback to network +self.addEventListener('fetch', (event) => { + const { request } = event; + const url = new URL(request.url); + + // Skip non-GET requests + if (request.method !== 'GET') { + return; + } + + // Skip chrome-extension and other non-http(s) requests + if (!url.protocol.startsWith('http')) { + return; + } + + // Skip API calls and media uploads (always go to network) + if (url.pathname.startsWith('/api/')) { + return; + } + + // The HLS tool and media must always reflect the current player version. + if (url.pathname.startsWith('/tools/hls_player') || + url.pathname.startsWith('/public/hls-sample/')) { + return; + } + + event.respondWith( + caches.match(request).then((cachedResponse) => { + if (cachedResponse) { + console.log('[SW] Serving from cache:', url.pathname); + return cachedResponse; + } + + // Not in cache, fetch from network + return fetch(request).then((networkResponse) => { + // Only cache successful responses + if (!networkResponse || networkResponse.status !== 200 || networkResponse.type === 'error') { + return networkResponse; + } + + // Cache specific file types + const shouldCache = + url.pathname.endsWith('.css') || + url.pathname.endsWith('.js') || + url.pathname.endsWith('.svg') || + url.pathname.endsWith('.jpg') || + url.pathname.endsWith('.webp') || + url.pathname.endsWith('.woff') || + url.pathname.endsWith('.woff2') || + url.pathname.endsWith('.ttf') || + url.pathname.endsWith('.otf') || + url.pathname.startsWith('/blog/') || + url.pathname.startsWith('/notes/') || + url.pathname === '/'; + + if (shouldCache) { + const responseToCache = networkResponse.clone(); + caches.open(CACHE_NAME).then((cache) => { + console.log('[SW] Caching new resource:', url.pathname); + cache.put(request, responseToCache); + }); + } + + return networkResponse; + }).catch((error) => { + console.log('[SW] Fetch failed:', error); + + // Return offline page for HTML requests + if (request.headers.get('Accept').includes('text/html')) { + return caches.match('/offline.html'); + } + }); + }) + ); +}); + +// Handle messages from the client +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } + + if (event.data && event.data.type === 'CLEAR_CACHE') { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => caches.delete(cacheName)) + ); + }) + ); + } +});
--- a/mrjunejune/src/tools/hls_player/index.css Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/src/tools/hls_player/index.css Tue Aug 04 16:49:01 2026 -0700 @@ -23,7 +23,7 @@ .hls-url-row input { width: 100%; padding: 0.65rem 0.8rem; - color: rgb(var(--gray-dark)); + color: var(--gray-dark); background: var(--white); border: 1px solid var(--darkgray); border-radius: 6px;
--- a/mrjunejune/src/tools/markdown_to_html/index.css Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/src/tools/markdown_to_html/index.css Tue Aug 04 16:49:01 2026 -0700 @@ -17,7 +17,7 @@ } .header h1 { - color: rgb(var(--gray-dark)); + color: var(--gray-dark); margin-bottom: 10px; } @@ -49,7 +49,7 @@ .label { font-weight: 600; - color: rgb(var(--gray-dark)); + color: var(--gray-dark); font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; @@ -75,8 +75,8 @@ flex: 1; width: 100%; padding: 15px; - background: rgb(var(--gray-light)); - color: rgb(var(--gray-dark)); + background: var(--gray-light); + color: var(--gray-dark); border: 1px solid rgba(var(--gray), 0.3); border-radius: 4px; font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; @@ -97,7 +97,7 @@ border: 1px solid rgba(var(--gray), 0.3); border-radius: 4px; background: var(--white); - color: rgb(var(--gray-dark)); + color: var(--gray-dark); overflow-y: auto; font-size: 15px; } @@ -105,7 +105,7 @@ /* Markdown output styles */ #output h1, #output h2, #output h3, #output h4, #output h5, #output h6 { margin: 1em 0 0.5em 0; - color: rgb(var(--gray-dark)); + color: var(--gray-dark); line-height: 1.3; } @@ -132,7 +132,7 @@ } #output code { - background: rgb(var(--gray-light)); + background: var(--gray-light); padding: 2px 6px; border-radius: 3px; font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; @@ -160,7 +160,7 @@ border-left: 4px solid var(--accent); padding: 10px 15px; margin: 1em 0; - background: rgb(var(--gray-light)); + background: var(--gray-light); color: rgb(var(--gray)); font-style: italic; } @@ -176,7 +176,7 @@ #output hr { border: none; - border-top: 1px solid rgb(var(--gray-light)); + border-top: 1px solid var(--gray-light); margin: 1.5em 0; }
--- a/mrjunejune/test/snapshots/blog.snapshot Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/test/snapshots/blog.snapshot Tue Aug 04 16:49:01 2026 -0700 @@ -3,7 +3,10 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> +<meta name="theme-color" content="#f7f3e8"> <link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg"> +<link rel="manifest" href="/public/manifest.json"> +<link rel="apple-touch-icon" href="/public/epi_all_colors.svg"> <link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> <link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> @@ -16,44 +19,58 @@ <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/epi_all_colors.svg" as="image"> +<link rel="stylesheet" href="/public/design-system/styles/tokens.css" /> +<link rel="stylesheet" href="/public/design-system/styles/themes.css" /> <link rel="preload" href="/base.css" as="style" /> <link rel="stylesheet" href="/base.css" /> +<link rel="stylesheet" href="/public/design-system/styles/components.css" /> +<script + type="module" + src="/public/design-system/components/index.js" +></script> +<script src="/public/pwa-register.js" defer></script> </head> <body> <style> :root { --header-background: var(--white); - --header-color: rgb(var(--black)); + --header-color: var(--black); --link-hover-accent: var(--awesome); } - /* Fixed icon in top left corner */ - #themeToggle { + #themeControl { position: fixed; top: 20px; left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; } - #themeToggle:hover { + #themeControl:hover { transform: scale(1.05); } + #themeToggle { + flex-direction: column; + min-width: 4.5rem; + padding: 0.35rem; + border-radius: 1.5rem; + } + + #themeName { + font-family: "More", sans-serif; + font-size: 0.65rem; + line-height: 1; + } + /* Professional header */ header { margin: auto; padding: 1.5em 1em; font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); + box-shadow: var(--zen-shadow-sm); width: 720px; max-width: calc(100% - 2em); text-align: center; @@ -77,7 +94,7 @@ /* Mobile responsiveness */ @media (max-width: 720px) { - #themeToggle { + #themeControl { top: 15px; left: 15px; } @@ -92,7 +109,7 @@ } @media (max-width: 480px) { - #themeToggle { + #themeControl { top: 10px; left: 10px; } @@ -118,20 +135,25 @@ } /* 2. MANUAL DARK OVERRIDE */ - html.dark { + html[data-zen-theme="ink"] { --logo-invert: invert(1); --epi-grayscale: grayscale(1); } /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { + html[data-zen-theme="paper"] { --logo-invert: invert(0); --epi-grayscale: brightness(2.9) grayscale(1); } + + html[data-zen-theme="playful"] { + --logo-invert: invert(0); + --epi-grayscale: saturate(1.25); + } /* 4. SYSTEM PREFERENCE */ @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { + :root:not([data-zen-theme]) { --logo-invert: invert(1); } } @@ -150,16 +172,29 @@ } </style> -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> +<zen-button id="themeControl" variant="quiet"> + <button + id="themeToggle" + type="button" + aria-label="Change site theme" + > + <img + id="epiChan" + class="epi-logo" + alt="" + src="/public/epi_all_colors.svg" + height="50" + width="50" + > + <span id="themeName" aria-live="polite">Auto</span> + </button> +</zen-button> <header> <h1><a href="/">MrJuneJune</a></h1> </header> <script src="/index.js"></script> - <main> <h1 class="title" style="margin-bottom: 20px;"> Blogs </h1> <ul style="list-style: none;">
--- a/mrjunejune/test/snapshots/resume.snapshot Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/test/snapshots/resume.snapshot Tue Aug 04 16:49:01 2026 -0700 @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> -<meta name="theme-color" content="#544e43"> +<meta name="theme-color" content="#f7f3e8"> <link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg"> <link rel="manifest" href="/public/manifest.json"> <link rel="apple-touch-icon" href="/public/epi_all_colors.svg"> @@ -19,12 +19,18 @@ <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/epi_all_colors.svg" as="image"> +<link rel="stylesheet" href="/public/design-system/styles/tokens.css" /> +<link rel="stylesheet" href="/public/design-system/styles/themes.css" /> <link rel="preload" href="/base.css" as="style" /> <link rel="stylesheet" href="/base.css" /> +<link rel="stylesheet" href="/public/design-system/styles/components.css" /> +<script + type="module" + src="/public/design-system/components/index.js" +></script> <script src="/public/pwa-register.js" defer></script> - <title>Juntae Park Resume</title> <link rel="preload" href="resume.css" as="style" /> <link rel="stylesheet" href="resume.css" /> @@ -33,35 +39,41 @@ <style> :root { --header-background: var(--white); - --header-color: rgb(var(--black)); + --header-color: var(--black); --link-hover-accent: var(--awesome); } - /* Fixed icon in top left corner */ - #themeToggle { + #themeControl { position: fixed; top: 20px; left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; } - #themeToggle:hover { + #themeControl:hover { transform: scale(1.05); } + #themeToggle { + flex-direction: column; + min-width: 4.5rem; + padding: 0.35rem; + border-radius: 1.5rem; + } + + #themeName { + font-family: "More", sans-serif; + font-size: 0.65rem; + line-height: 1; + } + /* Professional header */ header { margin: auto; padding: 1.5em 1em; font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); + box-shadow: var(--zen-shadow-sm); width: 720px; max-width: calc(100% - 2em); text-align: center; @@ -85,7 +97,7 @@ /* Mobile responsiveness */ @media (max-width: 720px) { - #themeToggle { + #themeControl { top: 15px; left: 15px; } @@ -100,7 +112,7 @@ } @media (max-width: 480px) { - #themeToggle { + #themeControl { top: 10px; left: 10px; } @@ -126,20 +138,25 @@ } /* 2. MANUAL DARK OVERRIDE */ - html.dark { + html[data-zen-theme="ink"] { --logo-invert: invert(1); --epi-grayscale: grayscale(1); } /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { + html[data-zen-theme="paper"] { --logo-invert: invert(0); --epi-grayscale: brightness(2.9) grayscale(1); } + + html[data-zen-theme="playful"] { + --logo-invert: invert(0); + --epi-grayscale: saturate(1.25); + } /* 4. SYSTEM PREFERENCE */ @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { + :root:not([data-zen-theme]) { --logo-invert: invert(1); } } @@ -158,16 +175,29 @@ } </style> -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> +<zen-button id="themeControl" variant="quiet"> + <button + id="themeToggle" + type="button" + aria-label="Change site theme" + > + <img + id="epiChan" + class="epi-logo" + alt="" + src="/public/epi_all_colors.svg" + height="50" + width="50" + > + <span id="themeName" aria-live="polite">Auto</span> + </button> +</zen-button> <header> <h1><a href="/">MrJuneJune</a></h1> </header> <script src="/index.js"></script> - <main> <div class="info"> <a href="/public/resume.pdf">Download PDF</a>
--- a/mrjunejune/test/snapshots/root.snapshot Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/test/snapshots/root.snapshot Tue Aug 04 16:49:01 2026 -0700 @@ -4,7 +4,10 @@ <title> MrJuneJune </title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> +<meta name="theme-color" content="#f7f3e8"> <link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg"> +<link rel="manifest" href="/public/manifest.json"> +<link rel="apple-touch-icon" href="/public/epi_all_colors.svg"> <link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> <link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> @@ -17,9 +20,17 @@ <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/epi_all_colors.svg" as="image"> +<link rel="stylesheet" href="/public/design-system/styles/tokens.css" /> +<link rel="stylesheet" href="/public/design-system/styles/themes.css" /> <link rel="preload" href="/base.css" as="style" /> <link rel="stylesheet" href="/base.css" /> +<link rel="stylesheet" href="/public/design-system/styles/components.css" /> +<script + type="module" + src="/public/design-system/components/index.js" +></script> +<script src="/public/pwa-register.js" defer></script> <style> .epi-photo { @@ -47,41 +58,104 @@ margin-bottom: 0.75em; } } + + #background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 0; + } + + #game { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 1; + cursor: default; + } + + #game.active { + pointer-events: auto; + z-index: 100; + } + + #gameUI { + position: fixed; + top: 10px; + right: 10px; + pointer-events: auto; + z-index: 101; + cursor: pointer; + } + + .site-link-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); + gap: var(--zen-space-4); + } + + .site-link-grid > a { + color: inherit; + text-decoration: none; + } + + .site-link-grid zen-card, + .site-link-grid article { + height: 100%; + } + + .site-link-grid p { + margin: 0; + color: var(--zen-color-text-muted); + font-size: 0.9rem; + } </style> </head> <body> <style> :root { --header-background: var(--white); - --header-color: rgb(var(--black)); + --header-color: var(--black); --link-hover-accent: var(--awesome); } - /* Fixed icon in top left corner */ - #themeToggle { + #themeControl { position: fixed; top: 20px; left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; } - #themeToggle:hover { + #themeControl:hover { transform: scale(1.05); } + #themeToggle { + flex-direction: column; + min-width: 4.5rem; + padding: 0.35rem; + border-radius: 1.5rem; + } + + #themeName { + font-family: "More", sans-serif; + font-size: 0.65rem; + line-height: 1; + } + /* Professional header */ header { margin: auto; padding: 1.5em 1em; font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); + box-shadow: var(--zen-shadow-sm); width: 720px; max-width: calc(100% - 2em); text-align: center; @@ -105,7 +179,7 @@ /* Mobile responsiveness */ @media (max-width: 720px) { - #themeToggle { + #themeControl { top: 15px; left: 15px; } @@ -120,7 +194,7 @@ } @media (max-width: 480px) { - #themeToggle { + #themeControl { top: 10px; left: 10px; } @@ -146,20 +220,25 @@ } /* 2. MANUAL DARK OVERRIDE */ - html.dark { + html[data-zen-theme="ink"] { --logo-invert: invert(1); --epi-grayscale: grayscale(1); } /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { + html[data-zen-theme="paper"] { --logo-invert: invert(0); --epi-grayscale: brightness(2.9) grayscale(1); } + + html[data-zen-theme="playful"] { + --logo-invert: invert(0); + --epi-grayscale: saturate(1.25); + } /* 4. SYSTEM PREFERENCE */ @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { + :root:not([data-zen-theme]) { --logo-invert: invert(1); } } @@ -178,16 +257,29 @@ } </style> -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> +<zen-button id="themeControl" variant="quiet"> + <button + id="themeToggle" + type="button" + aria-label="Change site theme" + > + <img + id="epiChan" + class="epi-logo" + alt="" + src="/public/epi_all_colors.svg" + height="50" + width="50" + > + <span id="themeName" aria-live="polite">Auto</span> + </button> +</zen-button> <header> <h1><a href="/">MrJuneJune</a></h1> </header> <script src="/index.js"></script> - <main> <p>Hi, I am Juntae—usually June, and occasionally MrJuneJune because the domain name has committed me to the bit.</p> @@ -200,12 +292,31 @@ </div> <h2>Links</h2> - <ul> - <li><a href="https://zenbu.babocoder.com/file/tip">Repository</a> - Check out my code</li> - <li><a href="/blog">Blogs</a> - My thoughts / Experiments </li> - <li><a href="/resume">Resume</a> - My professional experiences </li> - <li><a href="/tools">Tools</a> - Things I made for myself </li> - </ul> + <div class="site-link-grid"> + <a href="https://zenbu.babocoder.com/file/tip"> + <zen-card interactive> + <article><h3>Repository</h3><p>Browse the code behind these experiments.</p></article> + </zen-card> + </a> + <a href="/blog"> + <zen-card interactive> + <article><h3>Blogs</h3><p>Thoughts, experiments, and technical notes.</p></article> + </zen-card> + </a> + <a href="/resume"> + <zen-card interactive> + <article><h3>Resume</h3><p>Professional experience and selected work.</p></article> + </zen-card> + </a> + <a href="/tools"> + <zen-card interactive> + <article><h3>Tools</h3><p>Small things I built for myself.</p></article> + </zen-card> + </a> + </div> + <canvas id="background"></canvas> + <canvas id="game"></canvas> + <canvas id="gameUI"></canvas> </main> <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;"> <small>© 2026 June Park</small> @@ -213,6 +324,7 @@ </body> <script> + // Epi image let arr = Array.from({ length: 18 }, (_, i) => i+1); function setRandomImages() { const randomIndex = Math.floor(Math.random() * arr.length); @@ -225,4 +337,5 @@ } setRandomImages(); </script> + <script src="/public/dog-game.js"></script> </html>
--- a/mrjunejune/test/snapshots/talk.snapshot Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/test/snapshots/talk.snapshot Tue Aug 04 16:49:01 2026 -0700 @@ -5,7 +5,10 @@ <title>Talk!</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> +<meta name="theme-color" content="#f7f3e8"> <link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg"> +<link rel="manifest" href="/public/manifest.json"> +<link rel="apple-touch-icon" href="/public/epi_all_colors.svg"> <link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> <link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> @@ -18,9 +21,17 @@ <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/epi_all_colors.svg" as="image"> +<link rel="stylesheet" href="/public/design-system/styles/tokens.css" /> +<link rel="stylesheet" href="/public/design-system/styles/themes.css" /> <link rel="preload" href="/base.css" as="style" /> <link rel="stylesheet" href="/base.css" /> +<link rel="stylesheet" href="/public/design-system/styles/components.css" /> +<script + type="module" + src="/public/design-system/components/index.js" +></script> +<script src="/public/pwa-register.js" defer></script> <style> body { font-family: sans-serif; padding: 20px; } @@ -33,35 +44,41 @@ <style> :root { --header-background: var(--white); - --header-color: rgb(var(--black)); + --header-color: var(--black); --link-hover-accent: var(--awesome); } - /* Fixed icon in top left corner */ - #themeToggle { + #themeControl { position: fixed; top: 20px; left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; } - #themeToggle:hover { + #themeControl:hover { transform: scale(1.05); } + #themeToggle { + flex-direction: column; + min-width: 4.5rem; + padding: 0.35rem; + border-radius: 1.5rem; + } + + #themeName { + font-family: "More", sans-serif; + font-size: 0.65rem; + line-height: 1; + } + /* Professional header */ header { margin: auto; padding: 1.5em 1em; font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); + box-shadow: var(--zen-shadow-sm); width: 720px; max-width: calc(100% - 2em); text-align: center; @@ -85,7 +102,7 @@ /* Mobile responsiveness */ @media (max-width: 720px) { - #themeToggle { + #themeControl { top: 15px; left: 15px; } @@ -100,7 +117,7 @@ } @media (max-width: 480px) { - #themeToggle { + #themeControl { top: 10px; left: 10px; } @@ -126,20 +143,25 @@ } /* 2. MANUAL DARK OVERRIDE */ - html.dark { + html[data-zen-theme="ink"] { --logo-invert: invert(1); --epi-grayscale: grayscale(1); } /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { + html[data-zen-theme="paper"] { --logo-invert: invert(0); --epi-grayscale: brightness(2.9) grayscale(1); } + + html[data-zen-theme="playful"] { + --logo-invert: invert(0); + --epi-grayscale: saturate(1.25); + } /* 4. SYSTEM PREFERENCE */ @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { + :root:not([data-zen-theme]) { --logo-invert: invert(1); } } @@ -158,16 +180,29 @@ } </style> -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> +<zen-button id="themeControl" variant="quiet"> + <button + id="themeToggle" + type="button" + aria-label="Change site theme" + > + <img + id="epiChan" + class="epi-logo" + alt="" + src="/public/epi_all_colors.svg" + height="50" + width="50" + > + <span id="themeName" aria-live="polite">Auto</span> + </button> +</zen-button> <header> <h1><a href="/">MrJuneJune</a></h1> </header> <script src="/index.js"></script> - <main> <h1>Talk with strangers xDDD</h1>
--- a/mrjunejune/test/snapshots/tools.snapshot Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/test/snapshots/tools.snapshot Tue Aug 04 16:49:01 2026 -0700 @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> -<meta name="theme-color" content="#544e43"> +<meta name="theme-color" content="#f7f3e8"> <link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg"> <link rel="manifest" href="/public/manifest.json"> <link rel="apple-touch-icon" href="/public/epi_all_colors.svg"> @@ -19,47 +19,59 @@ <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/epi_all_colors.svg" as="image"> +<link rel="stylesheet" href="/public/design-system/styles/tokens.css" /> +<link rel="stylesheet" href="/public/design-system/styles/themes.css" /> <link rel="preload" href="/base.css" as="style" /> <link rel="stylesheet" href="/base.css" /> +<link rel="stylesheet" href="/public/design-system/styles/components.css" /> +<script + type="module" + src="/public/design-system/components/index.js" +></script> <script src="/public/pwa-register.js" defer></script> - <link rel="stylesheet" href="/tools/index.css" /> </head> <body> <style> :root { --header-background: var(--white); - --header-color: rgb(var(--black)); + --header-color: var(--black); --link-hover-accent: var(--awesome); } - /* Fixed icon in top left corner */ - #themeToggle { + #themeControl { position: fixed; top: 20px; left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; } - #themeToggle:hover { + #themeControl:hover { transform: scale(1.05); } + #themeToggle { + flex-direction: column; + min-width: 4.5rem; + padding: 0.35rem; + border-radius: 1.5rem; + } + + #themeName { + font-family: "More", sans-serif; + font-size: 0.65rem; + line-height: 1; + } + /* Professional header */ header { margin: auto; padding: 1.5em 1em; font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); + box-shadow: var(--zen-shadow-sm); width: 720px; max-width: calc(100% - 2em); text-align: center; @@ -83,7 +95,7 @@ /* Mobile responsiveness */ @media (max-width: 720px) { - #themeToggle { + #themeControl { top: 15px; left: 15px; } @@ -98,7 +110,7 @@ } @media (max-width: 480px) { - #themeToggle { + #themeControl { top: 10px; left: 10px; } @@ -124,20 +136,25 @@ } /* 2. MANUAL DARK OVERRIDE */ - html.dark { + html[data-zen-theme="ink"] { --logo-invert: invert(1); --epi-grayscale: grayscale(1); } /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { + html[data-zen-theme="paper"] { --logo-invert: invert(0); --epi-grayscale: brightness(2.9) grayscale(1); } + + html[data-zen-theme="playful"] { + --logo-invert: invert(0); + --epi-grayscale: saturate(1.25); + } /* 4. SYSTEM PREFERENCE */ @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { + :root:not([data-zen-theme]) { --logo-invert: invert(1); } } @@ -156,16 +173,29 @@ } </style> -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> +<zen-button id="themeControl" variant="quiet"> + <button + id="themeToggle" + type="button" + aria-label="Change site theme" + > + <img + id="epiChan" + class="epi-logo" + alt="" + src="/public/epi_all_colors.svg" + height="50" + width="50" + > + <span id="themeName" aria-live="polite">Auto</span> + </button> +</zen-button> <header> <h1><a href="/">MrJuneJune</a></h1> </header> <script src="/index.js"></script> - <main> <h1 class="title"> Tools </h1> <ul class="nav-list">
--- a/mrjunejune/test/snapshots/tools_file_converter.snapshot Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/test/snapshots/tools_file_converter.snapshot Tue Aug 04 16:49:01 2026 -0700 @@ -6,7 +6,10 @@ <title>File Format Converter</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> +<meta name="theme-color" content="#f7f3e8"> <link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg"> +<link rel="manifest" href="/public/manifest.json"> +<link rel="apple-touch-icon" href="/public/epi_all_colors.svg"> <link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> <link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> @@ -19,9 +22,17 @@ <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/epi_all_colors.svg" as="image"> +<link rel="stylesheet" href="/public/design-system/styles/tokens.css" /> +<link rel="stylesheet" href="/public/design-system/styles/themes.css" /> <link rel="preload" href="/base.css" as="style" /> <link rel="stylesheet" href="/base.css" /> +<link rel="stylesheet" href="/public/design-system/styles/components.css" /> +<script + type="module" + src="/public/design-system/components/index.js" +></script> +<script src="/public/pwa-register.js" defer></script> <style> .container { @@ -157,35 +168,41 @@ <style> :root { --header-background: var(--white); - --header-color: rgb(var(--black)); + --header-color: var(--black); --link-hover-accent: var(--awesome); } - /* Fixed icon in top left corner */ - #themeToggle { + #themeControl { position: fixed; top: 20px; left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; } - #themeToggle:hover { + #themeControl:hover { transform: scale(1.05); } + #themeToggle { + flex-direction: column; + min-width: 4.5rem; + padding: 0.35rem; + border-radius: 1.5rem; + } + + #themeName { + font-family: "More", sans-serif; + font-size: 0.65rem; + line-height: 1; + } + /* Professional header */ header { margin: auto; padding: 1.5em 1em; font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); + box-shadow: var(--zen-shadow-sm); width: 720px; max-width: calc(100% - 2em); text-align: center; @@ -209,7 +226,7 @@ /* Mobile responsiveness */ @media (max-width: 720px) { - #themeToggle { + #themeControl { top: 15px; left: 15px; } @@ -224,7 +241,7 @@ } @media (max-width: 480px) { - #themeToggle { + #themeControl { top: 10px; left: 10px; } @@ -250,20 +267,25 @@ } /* 2. MANUAL DARK OVERRIDE */ - html.dark { + html[data-zen-theme="ink"] { --logo-invert: invert(1); --epi-grayscale: grayscale(1); } /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { + html[data-zen-theme="paper"] { --logo-invert: invert(0); --epi-grayscale: brightness(2.9) grayscale(1); } + + html[data-zen-theme="playful"] { + --logo-invert: invert(0); + --epi-grayscale: saturate(1.25); + } /* 4. SYSTEM PREFERENCE */ @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { + :root:not([data-zen-theme]) { --logo-invert: invert(1); } } @@ -282,9 +304,23 @@ } </style> -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> +<zen-button id="themeControl" variant="quiet"> + <button + id="themeToggle" + type="button" + aria-label="Change site theme" + > + <img + id="epiChan" + class="epi-logo" + alt="" + src="/public/epi_all_colors.svg" + height="50" + width="50" + > + <span id="themeName" aria-live="polite">Auto</span> + </button> +</zen-button> <header> <h1><a href="/">MrJuneJune</a></h1> @@ -292,7 +328,6 @@ <script src="/index.js"></script> - <div class="container"> <h1>File Format Converter</h1> <p>Convert your images and videos to different formats using FFmpeg</p>
--- a/mrjunejune/test/snapshots/tools_file_converter_index.html.snapshot Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/test/snapshots/tools_file_converter_index.html.snapshot Tue Aug 04 16:49:01 2026 -0700 @@ -1,332 +0,0 @@ - as="font" type="font/woff" crossorigin> --> -<!-- <link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin> --> - -<!-- <link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> --> -<link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> -<link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> -<link rel="preload" href="/public/epi_all_colors.svg" as="image"> - -<link rel="preload" href="/base.css" as="style" /> -<link rel="stylesheet" href="/base.css" /> - - - <style> - .container { - max-width: 800px; - margin: 2rem auto; - padding: 2rem; - } - - .converter-section { - border-radius: 8px; - border: 2px solid var(--lightgray); - padding: 2rem; - margin-bottom: 2rem; - } - - .converter-section h2 { - margin-top: 0; - color: var(--black); - } - - .file-input-wrapper { - margin: 1rem 0; - } - - .file-input-wrapper label { - display: block; - margin-bottom: 0.5rem; - font-weight: 600; - } - - input[type="file"] { - padding: 0.75rem; - border: 2px dashed #ccc; - border-radius: 4px; - background: white; - font-size: 16px; - } - - button { - background: var(--purple); - font-family: "More Thin", sans-serif; - color: var(--gray-gradient); - border: none; - padding: 0.75rem 1.5rem; - border-radius: 4px; - cursor: pointer; - font-size: 1rem; - margin-top: 1rem; - min-height: 44px; - } - - button:hover { - background: var(--orange); - } - - button:disabled { - background: var(--gray); - cursor: not-allowed; - } - - .result { - margin-top: 1rem; - padding: 1rem; - background: white; - border-radius: 4px; - display: none; - } - - .result.show { - display: block; - } - - .result.success { - border-left: 4px solid var(--blue); - } - - .result.error { - border-left: 4px solid var(--red); - } - - .download-link { - display: inline-block; - margin-top: 0.5rem; - color: var(--awesome); - text-decoration: none; - } - - .download-link:hover { - text-decoration: underline; - } - - .loading { - display: none; - margin-top: 1rem; - } - - .loading.show { - display: block; - } - - /* Mobile responsive */ - @media (max-width: 720px) { - .container { - padding: 1rem; - margin: 1rem auto; - } - - .converter-section { - padding: 1.5rem 1rem; - } - - .converter-section h2 { - font-size: 1.5rem; - } - - .file-input-wrapper label { - font-size: 1rem; - } - - button { - font-size: 1.1rem; - padding: 1rem 1.5rem; - } - - .result { - padding: 1rem; - font-size: 1rem; - } - } - </style> -</head> -<body> - <style> - :root { - --header-background: var(--white); - --header-color: rgb(var(--black)); - --link-hover-accent: var(--awesome); - } - - /* Fixed icon in top left corner */ - #themeToggle { - position: fixed; - top: 20px; - left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; - z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - transition: transform 0.2s ease; - } - - #themeToggle:hover { - transform: scale(1.05); - } - - /* Professional header */ - header { - margin: auto; - padding: 1.5em 1em; - font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); - width: 720px; - max-width: calc(100% - 2em); - text-align: center; - } - - header h1 { - margin: 0; - font-size: 1.8em; - font-weight: 700; - letter-spacing: -0.5px; - } - - header h1 a { - text-decoration: none; - color: var(--header-color); - } - - header h1 a::before { - display: none; - } - - /* Mobile responsiveness */ - @media (max-width: 720px) { - #themeToggle { - top: 15px; - left: 15px; - } - - header { - padding: 1em; - } - - header h1 { - font-size: 1.5em; - } - } - - @media (max-width: 480px) { - #themeToggle { - top: 10px; - left: 10px; - } - - #themeToggle img { - height: 40px; - width: 40px; - } - - header h1 { - font-size: 1.3em; - } - } - - #logo { - width: 300px; - } - - /* 1. DEFINE THE DEFAULTS (Light Mode) */ - :root { - --logo-invert: invert(0); - --epi-grayscale: grayscale(0) brightness(1); - } - - /* 2. MANUAL DARK OVERRIDE */ - html.dark { - --logo-invert: invert(1); - --epi-grayscale: grayscale(1); - } - - /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { - --logo-invert: invert(0); - --epi-grayscale: brightness(2.9) grayscale(1); - } - - /* 4. SYSTEM PREFERENCE */ - @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { - --logo-invert: invert(1); - } - } - - /* 5. APPLY TO ELEMENTS */ - #logo { - -webkit-filter: var(--logo-invert); - filter: var(--logo-invert); - transition: filter 0.3s ease; - } - - .epi-logo { - -webkit-filter: var(--epi-grayscale); - filter: var(--epi-grayscale); - transition: filter 0.3s ease; - } -</style> - -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> - -<header> - <h1><a href="/">MrJuneJune</a></h1> -</header> -<script src="/index.js"></script> - - - - <div class="container"> - <h1>File Format Converter</h1> - <p>Convert your images and videos to different formats using FFmpeg</p> - - <div class="converter-section"> - <h2>Image to WebP Converter</h2> - <p>Upload an image file (PNG, JPG, GIF, etc.) to convert it to WebP format</p> - - <div class="file-input-wrapper"> - <label for="imageInput">Choose an image file:</label> - <input type="file" id="imageInput" accept="image/*"> - </div> - - <button id="convertImageBtn" onclick="convertImageToWebP()">Convert to WebP</button> - - <div class="loading" id="imageLoading">Converting... Please wait.</div> - - <div class="result" id="imageResult"> - <p id="imageMessage"></p> - <a id="imageDownload" class="download-link" style="display: none;">Download WebP Image</a> - </div> - </div> - - <div class="converter-section"> - <h2>Video to MP4 Converter</h2> - <p>Upload a video file (AVI, MOV, MKV, etc.) to convert it to MP4 format</p> - - <div class="file-input-wrapper"> - <label for="videoInput">Choose a video file:</label> - <input type="file" id="videoInput" accept="video/*"> - </div> - - <button id="convertVideoBtn" onclick="convertVideoToMP4()">Convert to MP4</button> - - <div class="loading" id="videoLoading">Converting... Please wait.</div> - - <div class="result" id="videoResult"> - <p id="videoMessage"></p> - <a id="videoDownload" class="download-link" style="display: none;">Download MP4 Video</a> - </div> - </div> - <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;"> - <small>© 2026 June Park</small> -</div> - - </div> - <script src="/tools/file_converter/index.js"></script> -</body> -</htmlLocation: /tools/file_converter -
--- a/mrjunejune/test/snapshots/tools_hls_player.snapshot Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/test/snapshots/tools_hls_player.snapshot Tue Aug 04 16:49:01 2026 -0700 @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> -<meta name="theme-color" content="#544e43"> +<meta name="theme-color" content="#f7f3e8"> <link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg"> <link rel="manifest" href="/public/manifest.json"> <link rel="apple-touch-icon" href="/public/epi_all_colors.svg"> @@ -19,12 +19,18 @@ <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/epi_all_colors.svg" as="image"> +<link rel="stylesheet" href="/public/design-system/styles/tokens.css" /> +<link rel="stylesheet" href="/public/design-system/styles/themes.css" /> <link rel="preload" href="/base.css" as="style" /> <link rel="stylesheet" href="/base.css" /> +<link rel="stylesheet" href="/public/design-system/styles/components.css" /> +<script + type="module" + src="/public/design-system/components/index.js" +></script> <script src="/public/pwa-register.js" defer></script> - <title>HLS Player - MrJuneJune</title> <link rel="stylesheet" href="/tools/hls_player/index.css" /> <script src="/public/hls.min.js" defer></script> @@ -34,35 +40,41 @@ <style> :root { --header-background: var(--white); - --header-color: rgb(var(--black)); + --header-color: var(--black); --link-hover-accent: var(--awesome); } - /* Fixed icon in top left corner */ - #themeToggle { + #themeControl { position: fixed; top: 20px; left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; } - #themeToggle:hover { + #themeControl:hover { transform: scale(1.05); } + #themeToggle { + flex-direction: column; + min-width: 4.5rem; + padding: 0.35rem; + border-radius: 1.5rem; + } + + #themeName { + font-family: "More", sans-serif; + font-size: 0.65rem; + line-height: 1; + } + /* Professional header */ header { margin: auto; padding: 1.5em 1em; font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); + box-shadow: var(--zen-shadow-sm); width: 720px; max-width: calc(100% - 2em); text-align: center; @@ -86,7 +98,7 @@ /* Mobile responsiveness */ @media (max-width: 720px) { - #themeToggle { + #themeControl { top: 15px; left: 15px; } @@ -101,7 +113,7 @@ } @media (max-width: 480px) { - #themeToggle { + #themeControl { top: 10px; left: 10px; } @@ -127,20 +139,25 @@ } /* 2. MANUAL DARK OVERRIDE */ - html.dark { + html[data-zen-theme="ink"] { --logo-invert: invert(1); --epi-grayscale: grayscale(1); } /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { + html[data-zen-theme="paper"] { --logo-invert: invert(0); --epi-grayscale: brightness(2.9) grayscale(1); } + + html[data-zen-theme="playful"] { + --logo-invert: invert(0); + --epi-grayscale: saturate(1.25); + } /* 4. SYSTEM PREFERENCE */ @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { + :root:not([data-zen-theme]) { --logo-invert: invert(1); } } @@ -159,16 +176,29 @@ } </style> -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> +<zen-button id="themeControl" variant="quiet"> + <button + id="themeToggle" + type="button" + aria-label="Change site theme" + > + <img + id="epiChan" + class="epi-logo" + alt="" + src="/public/epi_all_colors.svg" + height="50" + width="50" + > + <span id="themeName" aria-live="polite">Auto</span> + </button> +</zen-button> <header> <h1><a href="/">MrJuneJune</a></h1> </header> <script src="/index.js"></script> - <main> <h1>Online HLS Player</h1> <p>Load an HLS playlist from a URL, or try the sample stream bundled with this site.</p>
--- a/mrjunejune/test/snapshots/tools_latex_editor.snapshot Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/test/snapshots/tools_latex_editor.snapshot Tue Aug 04 16:49:01 2026 -0700 @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> -<meta name="theme-color" content="#544e43"> +<meta name="theme-color" content="#f7f3e8"> <link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg"> <link rel="manifest" href="/public/manifest.json"> <link rel="apple-touch-icon" href="/public/epi_all_colors.svg"> @@ -19,12 +19,18 @@ <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/epi_all_colors.svg" as="image"> +<link rel="stylesheet" href="/public/design-system/styles/tokens.css" /> +<link rel="stylesheet" href="/public/design-system/styles/themes.css" /> <link rel="preload" href="/base.css" as="style" /> <link rel="stylesheet" href="/base.css" /> +<link rel="stylesheet" href="/public/design-system/styles/components.css" /> +<script + type="module" + src="/public/design-system/components/index.js" +></script> <script src="/public/pwa-register.js" defer></script> - <title>LaTeX Editor - MrJuneJune</title> <link rel="stylesheet" href="/tools/latex_editor/index.css" /> <script src="/tools/latex_editor/index.js" defer></script> @@ -33,35 +39,41 @@ <style> :root { --header-background: var(--white); - --header-color: rgb(var(--black)); + --header-color: var(--black); --link-hover-accent: var(--awesome); } - /* Fixed icon in top left corner */ - #themeToggle { + #themeControl { position: fixed; top: 20px; left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; } - #themeToggle:hover { + #themeControl:hover { transform: scale(1.05); } + #themeToggle { + flex-direction: column; + min-width: 4.5rem; + padding: 0.35rem; + border-radius: 1.5rem; + } + + #themeName { + font-family: "More", sans-serif; + font-size: 0.65rem; + line-height: 1; + } + /* Professional header */ header { margin: auto; padding: 1.5em 1em; font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); + box-shadow: var(--zen-shadow-sm); width: 720px; max-width: calc(100% - 2em); text-align: center; @@ -85,7 +97,7 @@ /* Mobile responsiveness */ @media (max-width: 720px) { - #themeToggle { + #themeControl { top: 15px; left: 15px; } @@ -100,7 +112,7 @@ } @media (max-width: 480px) { - #themeToggle { + #themeControl { top: 10px; left: 10px; } @@ -126,20 +138,25 @@ } /* 2. MANUAL DARK OVERRIDE */ - html.dark { + html[data-zen-theme="ink"] { --logo-invert: invert(1); --epi-grayscale: grayscale(1); } /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { + html[data-zen-theme="paper"] { --logo-invert: invert(0); --epi-grayscale: brightness(2.9) grayscale(1); } + + html[data-zen-theme="playful"] { + --logo-invert: invert(0); + --epi-grayscale: saturate(1.25); + } /* 4. SYSTEM PREFERENCE */ @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { + :root:not([data-zen-theme]) { --logo-invert: invert(1); } } @@ -158,16 +175,29 @@ } </style> -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> +<zen-button id="themeControl" variant="quiet"> + <button + id="themeToggle" + type="button" + aria-label="Change site theme" + > + <img + id="epiChan" + class="epi-logo" + alt="" + src="/public/epi_all_colors.svg" + height="50" + width="50" + > + <span id="themeName" aria-live="polite">Auto</span> + </button> +</zen-button> <header> <h1><a href="/">MrJuneJune</a></h1> </header> <script src="/index.js"></script> - <main class="latex-page"> <div class="latex-heading"> <div>
--- a/mrjunejune/test/snapshots/tools_markdown_to_html.snapshot Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/test/snapshots/tools_markdown_to_html.snapshot Tue Aug 04 16:49:01 2026 -0700 @@ -6,7 +6,10 @@ <title>Markdown to HTML Converter</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> +<meta name="theme-color" content="#f7f3e8"> <link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg"> +<link rel="manifest" href="/public/manifest.json"> +<link rel="apple-touch-icon" href="/public/epi_all_colors.svg"> <link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font" crossorigin> <link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin> @@ -19,9 +22,17 @@ <link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> <link rel="preload" href="/public/epi_all_colors.svg" as="image"> +<link rel="stylesheet" href="/public/design-system/styles/tokens.css" /> +<link rel="stylesheet" href="/public/design-system/styles/themes.css" /> <link rel="preload" href="/base.css" as="style" /> <link rel="stylesheet" href="/base.css" /> +<link rel="stylesheet" href="/public/design-system/styles/components.css" /> +<script + type="module" + src="/public/design-system/components/index.js" +></script> +<script src="/public/pwa-register.js" defer></script> <link rel="stylesheet" href="markdown_to_html/index.css" /> </head> @@ -29,35 +40,41 @@ <style> :root { --header-background: var(--white); - --header-color: rgb(var(--black)); + --header-color: var(--black); --link-hover-accent: var(--awesome); } - /* Fixed icon in top left corner */ - #themeToggle { + #themeControl { position: fixed; top: 20px; left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; } - #themeToggle:hover { + #themeControl:hover { transform: scale(1.05); } + #themeToggle { + flex-direction: column; + min-width: 4.5rem; + padding: 0.35rem; + border-radius: 1.5rem; + } + + #themeName { + font-family: "More", sans-serif; + font-size: 0.65rem; + line-height: 1; + } + /* Professional header */ header { margin: auto; padding: 1.5em 1em; font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); + box-shadow: var(--zen-shadow-sm); width: 720px; max-width: calc(100% - 2em); text-align: center; @@ -81,7 +98,7 @@ /* Mobile responsiveness */ @media (max-width: 720px) { - #themeToggle { + #themeControl { top: 15px; left: 15px; } @@ -96,7 +113,7 @@ } @media (max-width: 480px) { - #themeToggle { + #themeControl { top: 10px; left: 10px; } @@ -122,20 +139,25 @@ } /* 2. MANUAL DARK OVERRIDE */ - html.dark { + html[data-zen-theme="ink"] { --logo-invert: invert(1); --epi-grayscale: grayscale(1); } /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { + html[data-zen-theme="paper"] { --logo-invert: invert(0); --epi-grayscale: brightness(2.9) grayscale(1); } + + html[data-zen-theme="playful"] { + --logo-invert: invert(0); + --epi-grayscale: saturate(1.25); + } /* 4. SYSTEM PREFERENCE */ @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { + :root:not([data-zen-theme]) { --logo-invert: invert(1); } } @@ -154,16 +176,29 @@ } </style> -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> +<zen-button id="themeControl" variant="quiet"> + <button + id="themeToggle" + type="button" + aria-label="Change site theme" + > + <img + id="epiChan" + class="epi-logo" + alt="" + src="/public/epi_all_colors.svg" + height="50" + width="50" + > + <span id="themeName" aria-live="polite">Auto</span> + </button> +</zen-button> <header> <h1><a href="/">MrJuneJune</a></h1> </header> <script src="/index.js"></script> - <div class="header"> <h1>Markdown to HTML Converter</h1> </div> @@ -209,7 +244,7 @@ ### Images - + **Try editing this text!**</textarea> </div> @@ -226,10 +261,16 @@ <small>© 2026 June Park</small> </div> - <script src="/markdown_to_html_bin.js"></script> - <script> - // Wait for WASM module to be ready - Module.onRuntimeInitialized = () => { + <script type="module"> + import createModule from '/markdown_to_html_bin.js'; + + // Get DOM elements + const input = document.getElementById('input'); + const output = document.getElementById('output'); + const copy = document.getElementById('copy'); + + // Modern Emscripten exports a factory function that returns a Promise + createModule().then(Module => { // Get raw pointer so we can free it after use const markdownToHtmlPtr = Module.cwrap('markdown_to_html', 'number', ['string']); const markdownFree = Module.cwrap('markdown_free', null, ['number']); @@ -266,7 +307,7 @@ console.error('Failed to copy: ', err); }); }); - }; + }); </script> </body> </html>
--- a/mrjunejune/test/snapshots/tools_markdown_to_html_index.html.snapshot Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/test/snapshots/tools_markdown_to_html_index.html.snapshot Tue Aug 04 16:49:01 2026 -0700 @@ -1,388 +0,0 @@ -blic/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin> --> -<link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin> -<link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin> -<link rel="preload" href="/public/epi_all_colors.svg" as="image"> - -<link rel="preload" href="/base.css" as="style" /> -<link rel="stylesheet" href="/base.css" /> - - - <link rel="preload" href="resume.css" as="style" /> - <link rel="stylesheet" href="resume.css" /> - </head> - <body> - <style> - :root { - --header-background: var(--white); - --header-color: rgb(var(--black)); - --link-hover-accent: var(--awesome); - } - - /* Fixed icon in top left corner */ - #themeToggle { - position: fixed; - top: 20px; - left: 20px; - background: var(--header-background); - display: flex; - align-items: center; - border-radius: 50%; - cursor: pointer; - z-index: 1000; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - transition: transform 0.2s ease; - } - - #themeToggle:hover { - transform: scale(1.05); - } - - /* Professional header */ - header { - margin: auto; - padding: 1.5em 1em; - font-family: "More", sans-serif; - box-shadow: 0 2px 8px rgba(var(--black), 5%); - width: 720px; - max-width: calc(100% - 2em); - text-align: center; - } - - header h1 { - margin: 0; - font-size: 1.8em; - font-weight: 700; - letter-spacing: -0.5px; - } - - header h1 a { - text-decoration: none; - color: var(--header-color); - } - - header h1 a::before { - display: none; - } - - /* Mobile responsiveness */ - @media (max-width: 720px) { - #themeToggle { - top: 15px; - left: 15px; - } - - header { - padding: 1em; - } - - header h1 { - font-size: 1.5em; - } - } - - @media (max-width: 480px) { - #themeToggle { - top: 10px; - left: 10px; - } - - #themeToggle img { - height: 40px; - width: 40px; - } - - header h1 { - font-size: 1.3em; - } - } - - #logo { - width: 300px; - } - - /* 1. DEFINE THE DEFAULTS (Light Mode) */ - :root { - --logo-invert: invert(0); - --epi-grayscale: grayscale(0) brightness(1); - } - - /* 2. MANUAL DARK OVERRIDE */ - html.dark { - --logo-invert: invert(1); - --epi-grayscale: grayscale(1); - } - - /* 3. MANUAL LIGHT OVERRIDE */ - html.light-mode { - --logo-invert: invert(0); - --epi-grayscale: brightness(2.9) grayscale(1); - } - - /* 4. SYSTEM PREFERENCE */ - @media (prefers-color-scheme: dark) { - :root:not(.light-mode) { - --logo-invert: invert(1); - } - } - - /* 5. APPLY TO ELEMENTS */ - #logo { - -webkit-filter: var(--logo-invert); - filter: var(--logo-invert); - transition: filter 0.3s ease; - } - - .epi-logo { - -webkit-filter: var(--epi-grayscale); - filter: var(--epi-grayscale); - transition: filter 0.3s ease; - } -</style> - -<div id="themeToggle"> - <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50"> -</div> - -<header> - <h1><a href="/">MrJuneJune</a></h1> -</header> -<script src="/index.js"></script> - - - <main> - <div class="info"> - <a href="/public/resume.pdf">Download PDF</a> - <hr> - <p><span class="header-firstname-style"> JUNTAE </span><span class="header-lastname-style">PARK</span><p> - <p class="header-position-style"> SOFTWARE ENGINEER </p> - <div class="header-address-style"> - Bay Area, CA, USA - </div> - <div class="header-social-style"> - <div> - <a href="tel:+016505311728">(US) 650-531-1728</a> - </div> - <div> - <a href="tel:+014375808026">(CA) 437-580-8026</a> - </div> - <div> - <a href="mailto:[email protected]">[email protected]</a> - </div> - <div> - <a href="https://www.linkedin.com/in/junepark"><svg width="12" height="12" fill="currentColor" class="bi bi-linkedin" viewBox="0 0 16 16"> - <path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"/> - </svg> June Park - </a> - </div> - </div> - </div> - - <div class="sub-header"> - <h2 class="section-style"> Summary </h2> - <div class="line"></div> - </div> - <p class="paragraph-style">Software Engineer with 9 years of hands-on experience across diverse tech stacks, from early-stage startups to FANG-scale systems. Adept in designing and delivering robust software solutions using modern languages, frameworks, and cloud platforms. <br><br> Open to impactful work.</p> - <div class="sub-header"> - - <h2 class="section-style"> Skills </h2> - <div class="line"></div> - </div> - <div class="paragraph-style"> - <p> - <span class="entry-title-style"> Programming Languages:</span> - <span class="skill-type-style"> TypeScript, Python, C/C++, Ruby, Java, MATLAB </span> - </p> - <p> - <span class="entry-title-style">Tools & Platforms:</span> - <span class="skill-type-style">Bazel, PostgresSQL, Mercurial, Git, Pands, Raylib, XCode</span> - </p> - <p> - <span class="entry-title-style"> Web Frameworks: </span> - <span class="skill-type-style"> Django, Rails, React, Flask</span> - </p> - <p> - <span class="entry-title-style"> DevOp:</span> - <span class="skill-type-style"> Plummi, Heroku, DigitalOcean, AWS, Google Cloud </span> - </p> - <p> - <span class="entry-title-style"> Language:</span> - <span class="skill-type-style"> English, Korean, Japanese </span> - </p> - </div> - - <!-- Experiences --> - <div class="sub-header"> - <h2 class="section-style"> Experience </h2> - <div class="line"></div> - </div> - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.meta.com/">Meta</a> - </p> - <p class="entry-location-style">San Francisco, CA, USA</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">Oct, 2024 - Present</p> - </div> - <ul class="description-style"> - <li> - Took initiative on Channel Value Rule, targeting the 16% of ad traffic with both app and web destinations to improve value attribution and ROI. - </li> - <li> - Built full-stack features using React and Hack/GraphQL, contributing to scalable, production-ready systems. - </li> - <li> - Partnered with data science to design A/B tests and analyze revenue impact of ads destination. - </li> - <li> - Proposed and implemented alpha improvements to internal testing infrastructure, reducing test time by 50% and enhancing developer velocity. - </li> - </ul> - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.wmg.com/">Warner Music Group</a> - </p> - <p class="entry-location-style">Toronto, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">TECHNICAL LEAD ENGINEER</p> - <p class="entry-date-style">July, 2023 - Sept, 2024</p> - </div> - <ul class="description-style"> - <li> - Implements <a href="https://bazel.build/">bazel </a>structure for the company for TypeScript and JavaScript code base for hermiticity and stablishing standards for JavaScript and - </li> - <li> - TypeScript testing and code structures. - </li> - <li> - Led a team of five engineers in building GraphQL endpoints for client-facing applications using Apollo and AppSync, supporting over 2000 RPS and auto scaling depending on request values. - </li> - <li> - Improved application response times by up to 85% for graphQL response by updating database schema and SQL queries, eliminating N+1 queries and lack of indexes. - </li> - <li> - Developed CI/CD pipelines for backend structures. - </li> - <li> - Designed infrastructure for pub/sub, caching, and media processing logic. - </li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.google.com/">Google</a> - </p> - <p class="entry-location-style">Toronto, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">Feb, 2022 - July 2023</p> - </div> - <ul class="description-style"> - <li> - Implements and maintained new features relating to App Script across google workspace platform including Gmail, sheets, and Docs.</li> - <li> - Improved a response time and render time of App Script hover card components.</li> - <li> - Collaborated with a team of developers to ensure timely and accurate delivery of features.</li> - <li> - Conducted user testing and gathered feedback to iterate on features for optimal user experience.</li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.everlywell.com/">Everlywell</a> - </p> - <p class="entry-location-style">Toronto, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">December, 2020 - Jan, 2022</p> - </div> - <ul class="description-style"> - <li> - Maintained Amazon amplify apps to create and deploy React web applications for companies such as <a href="https://brooklynnets.everlywell.com/">NBA</a>, <a href="https://tinder.everlywell.com/">Tinder</a>, and other companies for COVID-19 at-home test kits.</li> - <li> - Implemented a script that helps accurately access and refund unused covid test kits; helping company save up to 200,000 USD.</li> - <li> - Created several Rails controllers for internal purposes; mocking end to end user experience for QA, mass refund features for CX department, and more, ultimately reducing support tickets amount by 50 percent.</li> - <li> - Implemented an audit table to help debug problems and logged which process was responsible for the change of the record using PaperTrail gems</li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.spiria.com/">Spiria</a> - </p> - <p class="entry-location-style">Oakville, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">October, 2018 - October, 2020</p> - </div> - <ul class="description-style"> - <li> - Constructed RESTful API endpoints in multiple different frameworks such as Django, Ruby on Rails, and Flask and automated API documentation process using swagger. - </li> - <li> - Designed custom rake tasks for importing production data into newly updated data structure to meet client's needs. - </li> - <li> - Maintained or updated staging/productions servers. Debugged problems in production postgres database using ssh and postgres console on Heroku or AWS servers - </li> - <li> - Collaborated in creating automation python scripts for websites and application using selenium covering for QA eliminating 80% of QA's manual work - </li> - </ul> - - <div class="flex-box"> - <p class="entry-title-style"> - <a href="https://www.apexscore.ai/">Apex Score</a> - </p> - <p class="entry-location-style">Oakville, ON, Canada</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">SOFTWARE ENGINEER</p> - <p class="entry-date-style">September, 2019 - October, 2020</p> - </div> - <ul class="description-style"> - <li> - Developed custom Shapley value regression model to calculate importance of independent variables of data sets using sklearn, pandas, and numpy. - </li> - <li> - Created custom image uploader to Amazon s3 bucket using boto3 library. - </li> - <li> - Built RESTful API application using Flask framework and automated extensive API documentation pages using flask-restplus, pytest, and swagger, covering 95% of the code base. - </li> - <li> - Created an interactive graph using D3.js in Vue.js with data from Flask backend API. - </li> - </ul> - - <div class="sub-header"> - <h2 class="section-style"> Education </h2> - <div class="line"></div> - </div> - <div class="flex-box"> - <p class="entry-title-style"> - University of British Columbia - </p> - <p class="entry-location-style">Kelowna, British Columbia</p> - </div> - <div class="flex-box"> - <p class="entry-position-style">BACHELOR OF SCIENCE IN PHYSICS</p> - <p class="entry-date-style">2014 - 2018</p> - </div> - <div id="footer"></div> - </main> - <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;"> - <small>© 2026 June Park</small> -</div> - - <script href="index.js"></script> - </body> -</htmlLocation: /tools/markdown_to_html -
--- a/mrjunejune/test/theme_and_webp_test.js Tue Aug 04 15:12:18 2026 -0700 +++ b/mrjunejune/test/theme_and_webp_test.js Tue Aug 04 16:49:01 2026 -0700 @@ -110,10 +110,35 @@ data[index + 1] * 0.7152 + data[index + 2] * 0.0722; } + const channels = value => value.match(/\d+(?:\.\d+)?/g) + .slice(0, 3) + .map(Number); + const colorLuminance = value => { + const converted = channels(value).map(channel => { + const normalized = channel / 255; + return normalized <= 0.04045 + ? normalized / 12.92 + : ((normalized + 0.055) / 1.055) ** 2.4; + }); + return converted[0] * 0.2126 + + converted[1] * 0.7152 + + converted[2] * 0.0722; + }; + const bodyStyle = getComputedStyle(document.body); + const foreground = colorLuminance(bodyStyle.color); + const background = colorLuminance( + getComputedStyle(document.querySelector('main')).backgroundColor, + ); return { + cardCount: document.querySelectorAll('.site-link-grid zen-card').length, + componentReady: Boolean(customElements.get('zen-card')), count, + fontFamily: getComputedStyle(document.body).fontFamily, luminance: count ? luminance / count : 0, - rootClass: document.documentElement.className, + textContrast: (Math.max(foreground, background) + 0.05) / + (Math.min(foreground, background) + 0.05), + rootTheme: document.documentElement.dataset.zenTheme || 'auto', + themeLabel: document.querySelector('#themeName')?.textContent, }; }); @@ -122,6 +147,27 @@ return sample; } +async function testThemeCycle(browser) { + const context = await browser.newContext({ colorScheme: 'light' }); + const page = await context.newPage(); + await page.goto(baseUrl, { waitUntil: 'networkidle' }); + const expected = ['paper', 'ink', 'playful', 'auto']; + for (const theme of expected) { + await page.locator('#themeToggle').click(); + assert.equal( + await page.evaluate(() => + document.documentElement.dataset.zenTheme || 'auto' + ), + theme, + ); + assert.equal( + await page.evaluate(() => localStorage.getItem('theme-preference')), + theme, + ); + } + await context.close(); +} + async function testHlsPlayer(browser, siteRoot) { const page = await browser.newPage(); const errors = []; @@ -395,12 +441,18 @@ await fetch(`${baseUrl}/public/manifest.json`) ).text(); const serviceWorker = await ( - await fetch(`${baseUrl}/public/sw.js`) + await fetch(`${baseUrl}/sw.js`) ).text(); for (const source of [home, dogGame, manifest]) { assert.doesNotMatch(source, /\.png(?:["')]|$)/i); } - assert.match(serviceWorker, /v4-hlsjs/); + assert.match(serviceWorker, /v5-zenbu-themes/); + assert.match( + await ( + await fetch(`${baseUrl}/public/pwa-register.js`) + ).text(), + /register\('\/sw\.js', \{ scope: '\/' \}\)/, + ); assert.ok(serviceWorker.includes("startsWith('/tools/hls_player')")); for (const asset of [ @@ -420,12 +472,29 @@ headless: true, args: ['--no-sandbox'], }); - const light = await sampleTheme(browser, 'light'); - const dark = await sampleTheme(browser, 'dark'); - assert.ok(light.count > 0); - assert.ok(dark.count > 0); - assert.ok(light.luminance < 175, JSON.stringify(light)); - assert.ok(dark.luminance > 200, JSON.stringify(dark)); + const paper = await sampleTheme(browser, 'paper'); + const ink = await sampleTheme(browser, 'ink'); + const playful = await sampleTheme(browser, 'playful'); + const automatic = await sampleTheme(browser, 'auto'); + assert.equal(paper.rootTheme, 'paper'); + assert.equal(ink.rootTheme, 'ink'); + assert.equal(playful.rootTheme, 'playful'); + assert.equal(automatic.rootTheme, 'auto'); + assert.equal(paper.themeLabel, 'Paper'); + assert.equal(ink.themeLabel, 'Ink'); + assert.equal(playful.themeLabel, 'Playful'); + assert.equal(automatic.themeLabel, 'Auto'); + for (const sample of [paper, ink, playful, automatic]) { + assert.ok(sample.count > 0); + assert.equal(sample.componentReady, true); + assert.equal(sample.cardCount, 4); + assert.match(sample.fontFamily, /More/); + assert.ok(sample.textContrast >= 4.5, JSON.stringify(sample)); + } + assert.ok(paper.luminance < 175, JSON.stringify(paper)); + assert.ok(playful.luminance < 175, JSON.stringify(playful)); + assert.ok(ink.luminance > 200, JSON.stringify(ink)); + await testThemeCycle(browser); await testHlsPlayer(browser, siteRoot); } finally { if (browser) await browser.close();