view mrjunejune/src/index.js @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents 60a876c4587a
children
line wrap: on
line source

const THEME_STORAGE_KEY = 'theme-preference';
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(--zenbu-sys-color-surface-page)';
  document.body.append(probe);
  const color = getComputedStyle(probe).backgroundColor;
  probe.remove();
  document.querySelector('meta[name="theme-color"]')
    ?.setAttribute('content', color);
}

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 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;
}

const currentPreference = applyTheme(getThemePreference());
if (localStorage.getItem(THEME_STORAGE_KEY) !== currentPreference) {
  localStorage.setItem(THEME_STORAGE_KEY, currentPreference);
}

function enhanceLinks(root = document) {
  const links = root instanceof HTMLAnchorElement
    ? [root]
    : [...root.querySelectorAll?.('a') || []];
  for (const link of links) {
    if (link.closest('zen-link, zen-button') ||
        link.dataset.zenLinkEffect === 'none') {
      continue;
    }
    const wrapper = document.createElement('zen-link');
    wrapper.setAttribute('effect', 'paw');
    link.before(wrapper);
    wrapper.append(link);
  }
}

window.addEventListener('DOMContentLoaded', () => {
  enhanceLinks();
  const observer = new MutationObserver(records => {
    for (const record of records) {
      for (const node of record.addedNodes) {
        if (node instanceof Element) enhanceLinks(node);
      }
    }
  });
  observer.observe(document.body, { childList: true, subtree: true });
});

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],
});