diff --git a/public/font/aifont.woff2 b/public/font/aifont.woff2 index fc8e415..f226481 100644 Binary files a/public/font/aifont.woff2 and b/public/font/aifont.woff2 differ diff --git a/public/icon/font.svg b/public/icon/font.svg new file mode 100644 index 0000000..99468dd --- /dev/null +++ b/public/icon/font.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/web/components/mode-tabs.ts b/src/web/components/mode-tabs.ts index 5732733..ed3df15 100644 --- a/src/web/components/mode-tabs.ts +++ b/src/web/components/mode-tabs.ts @@ -3,6 +3,12 @@ import { isLoggedIn } from '../lib/auth' let currentNetwork = 'bsky.social' let currentLang = localStorage.getItem('preferred-lang') || 'en' +let currentFont = localStorage.getItem('preferred-font') || 'system' + +const FONT_OPTIONS: { id: string; label: string }[] = [ + { id: 'system', label: 'System' }, + { id: 'aifont', label: 'aifont' }, +] export function getCurrentNetwork(): string { return currentNetwork @@ -52,17 +58,52 @@ export function renderModeTabs(handle: string, activeTab: 'blog' | 'browser' | ' return `
${tabs}
${pdsSelector}
` } +export function getCurrentFont(): string { + return currentFont +} + +export function setCurrentFont(font: string): void { + currentFont = font + localStorage.setItem('preferred-font', font) +} + +function applyFont(font: string): void { + // Remove all font-* classes + for (const f of FONT_OPTIONS) { + document.body.classList.remove(`font-${f.id}`) + } + // Add selected (skip system = no class) + if (font !== 'system') { + document.body.classList.add(`font-${font}`) + } +} + // Render language selector (above content) export function renderLangSelector(langs: string[]): string { - if (langs.length < 2) return '' - - return ` + const langHtml = langs.length >= 2 ? `
+ ` : '' + + const fontHtml = renderFontToggle() + + if (!langHtml && !fontHtml) return '' + return `
${fontHtml}${langHtml}
` +} + +// Render font selector with dropdown +export function renderFontToggle(): string { + return ` +
+ +
+
` } @@ -157,9 +198,54 @@ export async function setupModeTabs(onNetworkChange: (network: string) => void, }) } + // Setup font selector + const fontTab = document.getElementById('font-tab') + const fontDropdown = document.getElementById('font-dropdown') + + if (fontTab && fontDropdown) { + // Apply saved font on load + applyFont(currentFont) + + // Build font options + const fontOptionsHtml = FONT_OPTIONS.map(f => { + const isSelected = f.id === currentFont + return ` +
+ ${f.label} + \u2713 +
+ ` + }).join('') + + fontDropdown.innerHTML = fontOptionsHtml + + // Toggle dropdown + fontTab.addEventListener('click', (e) => { + e.stopPropagation() + fontDropdown.classList.toggle('show') + }) + + // Handle option selection + fontDropdown.querySelectorAll('.font-option').forEach(opt => { + opt.addEventListener('click', (e) => { + e.stopPropagation() + const font = (opt as HTMLElement).dataset.font || 'system' + + applyFont(font) + setCurrentFont(font) + fontTab.classList.toggle('active', font !== 'system') + + fontDropdown.querySelectorAll('.font-option').forEach(o => o.classList.remove('selected')) + opt.classList.add('selected') + fontDropdown.classList.remove('show') + }) + }) + } + // Close dropdowns on outside click document.addEventListener('click', () => { pdsDropdown?.classList.remove('show') langDropdown?.classList.remove('show') + fontDropdown?.classList.remove('show') }) } diff --git a/src/web/main.ts b/src/web/main.ts index 82a903e..dc8fbe6 100644 --- a/src/web/main.ts +++ b/src/web/main.ts @@ -885,6 +885,12 @@ async function render(route: Route): Promise { } } +// Apply saved font preference immediately +const savedFont = localStorage.getItem('preferred-font') +if (savedFont && savedFont !== 'system') { + document.body.classList.add(`font-${savedFont}`) +} + // Initial render render(parseRoute()) diff --git a/src/web/styles/main.css b/src/web/styles/main.css index cd3dbd0..bdadba1 100644 --- a/src/web/styles/main.css +++ b/src/web/styles/main.css @@ -15,7 +15,7 @@ } body { - font-family: 'aifont', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #1a1a1a; background: #fff; @@ -1506,9 +1506,151 @@ body { display: flex; justify-content: flex-end; align-items: center; + gap: 4px; margin-bottom: 8px; } +/* Font Toggle */ +.font-selector { + display: flex; + align-items: center; +} + +.font-btn { + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; + border-radius: 6px; + cursor: pointer; + padding: 8px 16px; +} + +.font-btn:hover { + background: #f0f0f0; +} + +.font-icon { + width: 20px; + height: 20px; + opacity: 0.6; +} + +.font-btn:hover .font-icon { + opacity: 0.9; +} + +.font-btn.active .font-icon { + opacity: 1.0; +} + +.font-selector { + position: relative; +} + +.font-dropdown { + display: none; + position: absolute; + top: 100%; + right: 0; + margin-top: 4px; + background: #fff; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + min-width: 100px; + z-index: 100; + overflow: hidden; +} + +.font-dropdown.show { + display: block; +} + +.font-option { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + cursor: pointer; + font-size: 14px; + transition: background 0.15s; +} + +.font-option:hover { + background: #f5f5f5; +} + +.font-option.selected { + background: linear-gradient(135deg, #f0f7ff 0%, #e8f4ff 100%); +} + +.font-name { + color: #333; + font-weight: 500; +} + +.font-check { + width: 18px; + height: 18px; + border-radius: 50%; + border: 2px solid #ccc; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + transition: all 0.2s; +} + +.font-option.selected .font-check { + background: var(--btn-color); + border-color: var(--btn-color); + color: #fff; +} + +.font-option:not(.selected) .font-check { + color: transparent; +} + +/* Font class on body */ +body.font-aifont { + font-family: 'aifont', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; + letter-spacing: -0.08em; +} + +body.font-aifont code, +body.font-aifont pre, +body.font-aifont kbd, +body.font-aifont samp { + letter-spacing: normal; +} + +@media (prefers-color-scheme: dark) { + .font-btn:hover { + background: #2a2a2a; + } + .font-icon { + filter: invert(0.7); + } + .font-btn.active .font-icon { + filter: invert(1); + } + .font-dropdown { + background: #1a1a1a; + border-color: #333; + } + .font-option:hover { + background: #2a2a2a; + } + .font-option.selected { + background: linear-gradient(135deg, #1a2a3a 0%, #1a3040 100%); + } + .font-name { + color: #e0e0e0; + } +} + @media (prefers-color-scheme: dark) { .lang-btn { background: transparent;