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. Load more
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;