737 lines · 26.9 KB · JavaScript
RawDownload
import { localeCatalog, localeMessages } from './site-locales.mjs'; const root = document.documentElement;let activeMessages = localeMessages.en; const HERO_MOBILE_MAX = 768;const HERO_TABLET_MAX = 1023;const HERO_DESKTOP_MIN = 1024;const MOCKUP_SCREEN_MARGIN_RIGHT = 50;const MOCKUP_SCREEN_MARGIN_RIGHT_TABLET = 15;const MOCKUP_ARTBOARD_RATIO = 1.18;/* Tablet MacBook X tune @ 1001px, scale 1.2 */const HERO_TABLET_MOCKUP_X_CORRECTION = -9; const HERO_MOBILE_MOCKUP_TUNE_SCALE = 0.99;/* Mobile MacBook X tune @ 490px, right-anchored full-bleed stage */const HERO_MOBILE_MOCKUP_X_CORRECTION = -32; const HERO_ARTBOARD = { width: 6111, height: 3239 }; /* Figma @ 6111×3239 — menubar anchor 130 top / 150 right; scale 0.94 (desktop) / 0.82 (mobile) */const HERO_MENUBAR_BASE = {  figmaTop: 130,  figmaRight: 150,  scale: 0.94,  mobileScale: 0.82,  iconScale: 1.28,}; /* Visual correction vs computed base (tune @ 1920 → 5.25%, 3.8%, scale ×1.55) */const HERO_MENUBAR_CORRECTION = {  top: 5.25 / ((HERO_MENUBAR_BASE.figmaTop / HERO_ARTBOARD.height) * 100),  right: 3.8 / ((HERO_MENUBAR_BASE.figmaRight / HERO_ARTBOARD.width) * 100),  scale: 1.55,}; function getMenubarComputed() {  const topPx = HERO_MENUBAR_BASE.figmaTop * HERO_MENUBAR_CORRECTION.top;  const rightPx = HERO_MENUBAR_BASE.figmaRight * HERO_MENUBAR_CORRECTION.right;  const scale = HERO_MENUBAR_BASE.scale * HERO_MENUBAR_CORRECTION.scale;  const mobileScale = HERO_MENUBAR_BASE.mobileScale * HERO_MENUBAR_CORRECTION.scale;  return {    topPx,    rightPx,    anchorTop: `${artboardPxToTopPercent(topPx).toFixed(2)}%`,    marginRight: `${artboardPxToRightPercent(rightPx).toFixed(2)}%`,    scale: Math.round(scale * 1000) / 1000,    mobileScale: Math.round(mobileScale * 1000) / 1000,  };} const HERO_FIGMA_SCREEN = { paddingTop: 125, paddingRight: 141 }; const HERO_FIGMA_PANEL = { gapBelowMenubar: 20, insetBeyondPaddingRight: 30 }; const HERO_PANEL_WIDTH_ON_SCREEN = 0.191; function getMenubarRenderedHeightPx() {  const emPx = 0.52 * (HERO_ARTBOARD.width / 100);  return emPx * HERO_MENUBAR_BASE.iconScale * getMenubarComputed().scale;} function getPanelBasePx() {  const menubar = getMenubarComputed();  const menubarHeight = (menubar.topPx - HERO_FIGMA_SCREEN.paddingTop) + getMenubarRenderedHeightPx();  return {    topPx: HERO_FIGMA_SCREEN.paddingTop + menubarHeight + HERO_FIGMA_PANEL.gapBelowMenubar,    rightPx: HERO_FIGMA_SCREEN.paddingRight + HERO_FIGMA_PANEL.insetBeyondPaddingRight,  };} /* SVG inner-screen geometry (panel width still % of inner screen) */const HERO_SCREEN_GEOMETRY = {  top: 124.92 / 3239,  width: (5970.48 - 140.89) / 6111,  height: (3239 - 124.92) / 3239,  rightInset: (6111 - 5970.48) / 6111,}; function artboardPxToTopPercent(px) {  return (px / HERO_ARTBOARD.height) * 100;} function artboardPxToRightPercent(px) {  return (px / HERO_ARTBOARD.width) * 100;} const HERO_PANEL_BASE = { popoverScale: 0.91, mobilePopoverScale: 1.01 }; /* Visual correction vs computed base (tune @ 1920 → 8.5%, 3.7%, scale 0.85) */const HERO_PANEL_CORRECTION = (() => {  const base = getPanelBasePx();  return {    top: 8.5 / artboardPxToTopPercent(base.topPx),    right: 3.7 / artboardPxToRightPercent(base.rightPx),    scale: 0.85 / HERO_PANEL_BASE.popoverScale,  };})(); /* Tablet-only correction (tune @ 1020 → top 9.5%, scale 1.35) */const HERO_PANEL_TABLET_CORRECTION = (() => {  const base = getPanelBasePx();  return {    top: 9.5 / artboardPxToTopPercent(base.topPx),    scale: 1.35,  };})(); function getPanelComputed() {  const base = getPanelBasePx();  const topPx = base.topPx * HERO_PANEL_CORRECTION.top;  const rightPx = base.rightPx * HERO_PANEL_CORRECTION.right;  const scale = Math.round(HERO_PANEL_BASE.popoverScale * HERO_PANEL_CORRECTION.scale * 1000) / 1000;  const mobileScale = Math.round(HERO_PANEL_BASE.mobilePopoverScale * HERO_PANEL_CORRECTION.scale * 1000) / 1000;  return {    topPx,    rightPx,    panelTop: `${artboardPxToTopPercent(topPx).toFixed(2)}%`,    panelMarginRight: `${artboardPxToRightPercent(rightPx).toFixed(2)}%`,    panelWidth: `${(HERO_PANEL_WIDTH_ON_SCREEN * HERO_SCREEN_GEOMETRY.width * 100).toFixed(2)}%`,    scale,    mobileScale,  };} /* Mobile-only correction (tune @ 490 → top/right/scale; @ 444 → width 20%) */const HERO_PANEL_MOBILE_CORRECTION = (() => {  const panel = getPanelComputed();  return {    top: 9.15 / parseFloat(panel.panelTop),    right: 6 / parseFloat(panel.panelMarginRight),    width: 20 / parseFloat(panel.panelWidth),    scale: 1.39 / panel.mobileScale,  };})(); function getMobilePanelComputed() {  const base = getPanelBasePx();  const panel = getPanelComputed();  const topPx = base.topPx * HERO_PANEL_MOBILE_CORRECTION.top;  return {    panelTop: `${artboardPxToTopPercent(topPx).toFixed(2)}%`,    panelMarginRight: `${(parseFloat(panel.panelMarginRight) * HERO_PANEL_MOBILE_CORRECTION.right).toFixed(2)}%`,    panelWidth: `${(parseFloat(panel.panelWidth) * HERO_PANEL_MOBILE_CORRECTION.width).toFixed(2)}%`,    scale: Math.round(panel.mobileScale * HERO_PANEL_MOBILE_CORRECTION.scale * 1000) / 1000,  };} function getTabletPanelComputed() {  const base = getPanelBasePx();  const panel = getPanelComputed();  const topPx = base.topPx * HERO_PANEL_TABLET_CORRECTION.top;  return {    panelTop: `${artboardPxToTopPercent(topPx).toFixed(2)}%`,    panelMarginRight: panel.panelMarginRight,    panelWidth: panel.panelWidth,    scale: HERO_PANEL_TABLET_CORRECTION.scale,  };} function getHudDefaultsFromGeometry() {  const menubar = getMenubarComputed();  const panel = getPanelComputed();  const tabletPanel = getTabletPanelComputed();  const mobilePanel = getMobilePanelComputed();  return {    '--hud-anchor-top': menubar.anchorTop,    '--hud-margin-right': menubar.marginRight,    '--menubar-scale': String(menubar.scale),    '--mobile-hud-anchor-top': menubar.anchorTop,    '--mobile-hud-margin-right': menubar.marginRight,    '--mobile-menubar-scale': String(menubar.mobileScale),    '--hud-panel-top': panel.panelTop,    '--hud-panel-margin-right': panel.panelMarginRight,    '--hud-panel-width': panel.panelWidth,    '--popover-scale': String(panel.scale),    '--tablet-hud-panel-top': tabletPanel.panelTop,    '--tablet-hud-panel-margin-right': tabletPanel.panelMarginRight,    '--tablet-hud-panel-width': tabletPanel.panelWidth,    '--tablet-popover-scale': String(tabletPanel.scale),    '--mobile-hud-panel-top': mobilePanel.panelTop,    '--mobile-hud-panel-margin-right': mobilePanel.panelMarginRight,    '--mobile-hud-panel-width': mobilePanel.panelWidth,    '--mobile-popover-scale': String(mobilePanel.scale),  };} const HERO_DESKTOP_ANCHOR = {  width: 1920,  fluidMax: 1.22,  tabletTuneWidth: 1022,  tabletFluidMax: 1.18,  mockupY: 10,  mockupScale: 1.15,  tabletMockupScale: 1.2,}; const HERO_LAYOUT_VARS = ['--mockup-x', '--mockup-y', '--tablet-mockup-x', '--tablet-mockup-stage-height']; function readRootPx(name, fallback) {  const raw = getComputedStyle(document.documentElement).getPropertyValue(name).trim();  if (!raw) return fallback;  if (raw.endsWith('rem')) {    const rootFont = Number.parseFloat(getComputedStyle(document.documentElement).fontSize);    const rem = Number.parseFloat(raw);    return Number.isFinite(rem) && Number.isFinite(rootFont) ? rem * rootFont : fallback;  }  const value = Number.parseFloat(raw);  return Number.isFinite(value) ? value : fallback;} function computeTabletStageHeight(width) {  const headerPx = readRootPx('--header-height', 64);  const tuneWidth = HERO_DESKTOP_ANCHOR.tabletTuneWidth;  const baseWidth = getMockupBaseWidth(width, tuneWidth);  const artboardHeight = baseWidth * (3239 / 6111);  const scale = getMockupEffectiveScale(width, { tablet: true });  const scaledHeight = artboardHeight * scale;  const mockupY = readRootNumber('--tablet-mockup-y', 13);  const mockupBottom = headerPx + mockupY + scaledHeight;  const panelTop = readRootPercent('--tablet-hud-panel-top', 9.5);  const panelScale = readRootNumber('--tablet-popover-scale', 1.35);  const panelApproxHeight = scaledHeight * 0.36 * panelScale;  const panelBottom = headerPx + mockupY + scaledHeight * panelTop + panelApproxHeight;   return Math.ceil(Math.max(mockupBottom, panelBottom) + 12);} function getHeroFluidFactor(width, tuneWidth = HERO_DESKTOP_ANCHOR.width, fluidMax = HERO_DESKTOP_ANCHOR.fluidMax) {  return Math.min(Math.max(1, tuneWidth / width), fluidMax);} function getMockupBaseWidth(width, tuneWidth = HERO_DESKTOP_ANCHOR.width) {  const layoutWidth = width >= tuneWidth ? tuneWidth : width;  return layoutWidth * MOCKUP_ARTBOARD_RATIO;} function readRootNumber(name, fallback) {  const raw = getComputedStyle(document.documentElement).getPropertyValue(name).trim();  const value = Number.parseFloat(raw);  return Number.isFinite(value) ? value : fallback;} function readRootPercent(name, fallbackPercent) {  const raw = getComputedStyle(document.documentElement).getPropertyValue(name).trim();  if (!raw) return fallbackPercent / 100;  if (raw.endsWith('%')) {    const value = Number.parseFloat(raw);    return Number.isFinite(value) ? value / 100 : fallbackPercent / 100;  }  const value = Number.parseFloat(raw);  return Number.isFinite(value) ? value / 100 : fallbackPercent / 100;} function getMockupEffectiveScale(width, { tablet = false } = {}) {  const tuneWidth = tablet ? HERO_DESKTOP_ANCHOR.tabletTuneWidth : HERO_DESKTOP_ANCHOR.width;  const fluidMax = tablet ? HERO_DESKTOP_ANCHOR.tabletFluidMax : HERO_DESKTOP_ANCHOR.fluidMax;  const scale = tablet    ? readRootNumber('--tablet-mockup-scale', HERO_DESKTOP_ANCHOR.tabletMockupScale)    : readRootNumber('--mockup-scale', HERO_DESKTOP_ANCHOR.mockupScale);  if (width >= tuneWidth) return scale;  return scale * getHeroFluidFactor(width, tuneWidth, fluidMax);} function computeMockupX(width, { tablet = false } = {}) {  const tuneWidth = tablet ? HERO_DESKTOP_ANCHOR.tabletTuneWidth : HERO_DESKTOP_ANCHOR.width;  const scaledWidth = getMockupBaseWidth(width, tuneWidth) * getMockupEffectiveScale(width, { tablet });  const marginRight = tablet ? MOCKUP_SCREEN_MARGIN_RIGHT_TABLET : MOCKUP_SCREEN_MARGIN_RIGHT;  const baseX = (width / 2) - marginRight - (scaledWidth / 2);  return tablet ? baseX + HERO_TABLET_MOCKUP_X_CORRECTION : baseX;} function getMobileStageWidth(viewportWidth) {  return viewportWidth;} function computeMobileMockupOffsetX(viewportWidth) {  const stageWidth = getMobileStageWidth(viewportWidth);  const scale = readRootNumber('--mobile-mockup-scale', HERO_MOBILE_MOCKUP_TUNE_SCALE);  const widthPct = readRootNumber('--mobile-mockup-width', 251) / 100;  const scaledWidth = stageWidth * widthPct * scale;  const scaleNudge = (HERO_MOBILE_MOCKUP_TUNE_SCALE - scale) * scaledWidth * 0.5;  return Math.round((HERO_MOBILE_MOCKUP_X_CORRECTION + scaleNudge) * 100) / 100;} function applyHudGeometryDefaults() {  const defaults = getHudDefaultsFromGeometry();  const root = document.documentElement;  Object.entries(defaults).forEach(([name, value]) => {    if (!root.style.getPropertyValue(name)) {      root.style.setProperty(name, value);    }  });} function clearHeroDesktopLayout() {  HERO_LAYOUT_VARS.forEach((name) => document.documentElement.style.removeProperty(name));} function applyHeroDesktopLayout() {  const width = window.innerWidth;  const root = document.documentElement;  const isTablet = width > HERO_MOBILE_MAX && width <= HERO_TABLET_MAX;  const marginRight = isTablet ? MOCKUP_SCREEN_MARGIN_RIGHT_TABLET : MOCKUP_SCREEN_MARGIN_RIGHT;  root.style.setProperty('--mockup-screen-margin-right', `${marginRight}px`);   if (width <= HERO_MOBILE_MAX) {    clearHeroDesktopLayout();    root.style.setProperty('--mobile-mockup-offset-x', `${computeMobileMockupOffsetX(width)}px`);    return;  }   root.style.removeProperty('--mobile-mockup-offset-x');   const mockupX = Math.round(computeMockupX(width, { tablet: isTablet }) * 100) / 100;   if (width <= HERO_TABLET_MAX) {    root.style.setProperty('--tablet-mockup-x', `${mockupX}px`);    root.style.setProperty('--tablet-mockup-y', `${readRootNumber('--tablet-mockup-y', 13)}px`);    root.style.setProperty('--tablet-mockup-stage-height', `${computeTabletStageHeight(width)}px`);    root.style.removeProperty('--mockup-x');    root.style.removeProperty('--mockup-y');    return;  }   root.style.setProperty('--mockup-x', `${mockupX}px`);  root.style.setProperty('--mockup-y', `${HERO_DESKTOP_ANCHOR.mockupY}px`);  root.style.removeProperty('--tablet-mockup-x');  root.style.removeProperty('--tablet-mockup-y');  root.style.removeProperty('--tablet-mockup-stage-height');} applyHudGeometryDefaults();applyHeroDesktopLayout(); window.addEventListener('resize', applyHeroDesktopLayout);window.visualViewport?.addEventListener('resize', applyHeroDesktopLayout); const themeMedia = window.matchMedia('(prefers-color-scheme: dark)');const themeToggle = document.querySelector('#theme-toggle');const themeNames = { system: 'System', light: 'Light', dark: 'Dark' }; function applyThemePreference(preference, persist = true) {  const resolvedTheme = preference === 'system'    ? (themeMedia.matches ? 'dark' : 'light')    : preference;   root.dataset.theme = resolvedTheme;  root.dataset.themePreference = preference;  themeToggle.dataset.themeValue = preference;  themeToggle.title = `${themeNames[preference]} theme`;  updateThemeToggleLabel();   if (persist) {    try {      localStorage.setItem('codexbar-theme', preference);    } catch (_) {}  }} function updateThemeToggleLabel() {  const nextTheme = root.dataset.theme === 'dark' ? 'light' : 'dark';  const label = message(nextTheme === 'dark' ? 'theme.toDark' : 'theme.toLight');  themeToggle.setAttribute('aria-label', label);  themeToggle.title = label;} themeToggle.addEventListener('click', () => {  const nextPreference = root.dataset.theme === 'dark' ? 'light' : 'dark';  applyThemePreference(nextPreference);}); themeMedia.addEventListener('change', () => {  if (root.dataset.themePreference === 'system') applyThemePreference('system', false);}); applyThemePreference(root.dataset.themePreference || 'system', false); const languageShortNames = {  en: 'EN',  'zh-CN': '简',  'zh-TW': '繁',  'ja-JP': 'JA',  es: 'ES',  'pt-BR': 'PT',  ko: 'KO',  de: 'DE',  fr: 'FR',  ar: 'AR',  it: 'IT',  vi: 'VI',  nl: 'NL',  tr: 'TR',  uk: 'UK',  id: 'ID',  pl: 'PL',  fa: 'FA',  th: 'TH',  ca: 'CA',  sv: 'SV',};const languages = localeCatalog.map((language) => ({  ...language,  id: language.code,  short: languageShortNames[language.code] || language.code.slice(0, 2).toUpperCase(),  label: language.name,}));const supportedLanguages = new Set(languages.map((language) => language.id));const rtlLanguages = new Set(localeCatalog.filter((language) => language.direction === 'rtl').map((language) => language.code));const localeAliases = {  'zh-cn': 'zh-CN',  'zh-hans': 'zh-CN',  'zh-hant': 'zh-TW',  'zh-hk': 'zh-TW',  'zh-tw': 'zh-TW',  ja: 'ja-JP',  pt: 'pt-BR',  'pt-br': 'pt-BR',};const languageStorageKey = 'codexbar-language';const languagePicker = document.querySelector('#language-picker');const languageTrigger = document.querySelector('#language-picker-trigger');const languageMenu = document.querySelector('#language-picker-menu');const languageList = document.querySelector('#language-picker-list');const languageShort = document.querySelector('[data-lang-short]'); function normalizeLocale(value) {  if (!value) return null;  const lower = value.toLowerCase();  if (localeAliases[lower]) return localeAliases[lower];  return languages.find((language) => language.id.toLowerCase() === lower || lower.startsWith(`${language.id.toLowerCase()}-`))?.id || null;} function selectedLocale() {  try {    const queryLocale = normalizeLocale(new URLSearchParams(location.search).get('lang'));    if (queryLocale) return queryLocale;    const storedLocale = normalizeLocale(localStorage.getItem(languageStorageKey));    if (storedLocale) return storedLocale;  } catch (_) {}   for (const language of navigator.languages || [navigator.language]) {    const locale = normalizeLocale(language);    if (locale) return locale;  }  return 'en';} let activeLanguage = selectedLocale();if (!supportedLanguages.has(activeLanguage)) activeLanguage = 'en'; function message(key) {  return activeMessages[key] || localeMessages.en[key] || key;} function applyAttributeMessages(dataAttribute, targetAttribute) {  document.querySelectorAll(`[${dataAttribute}]`).forEach((element) => {    element.setAttribute(targetAttribute, message(element.getAttribute(dataAttribute)));  });} function richToken(name) {  const codeTokens = {    cask: 'brew install --cask steipete/tap/codexbar',    codexbar: 'codexbar',    linuxCommand: 'brew install steipete/tap/codexbar',    upgrade: 'brew upgrade',  };  if (codeTokens[name]) {    const code = document.createElement('code');    code.className = 'inline-code';    code.textContent = codeTokens[name];    return code;  }  if (name === 'releases') {    const link = document.createElement('a');    link.className = 'text-link';    link.href = 'https://github.com/steipete/CodexBar/releases';    link.textContent = 'GitHub Releases';    return link;  }  if (name === 'issue') {    const link = document.createElement('a');    link.className = 'text-link';    link.href = 'https://github.com/steipete/CodexBar/issues/12';    link.textContent = 'issue #12';    return link;  }  if (name === 'mobileBreak') {    return document.createElement('br');  }  if (name === 'break') {    const wrapper = document.createDocumentFragment();    const space = document.createElement('span');    space.className = 'inline sm:hidden';    space.textContent = ' ';    const br = document.createElement('br');    br.className = 'hidden sm:block';    wrapper.append(space, br);    return wrapper;

Lines 1–500 of 737.