diff --git a/ac-electron/package.json b/ac-electron/package.json index a6fecdb82d..d1ce3e77f7 100644 --- a/ac-electron/package.json +++ b/ac-electron/package.json @@ -1,6 +1,6 @@ { "name": "aesthetic-computer", - "version": "0.1.17", + "version": "0.1.18", "description": "Aesthetic Computer", "homepage": "https://aesthetic.computer", "author": "Aesthetic Computer ", diff --git a/ac-electron/renderer/flip-view.html b/ac-electron/renderer/flip-view.html index d232f4053f..1de10c01e6 100644 --- a/ac-electron/renderer/flip-view.html +++ b/ac-electron/renderer/flip-view.html @@ -254,22 +254,22 @@ border-radius: 0 6px 6px 0; } - /* Volume control - horizontal slider at bottom right, outside the right flip tab zone */ + /* Volume control - top right corner, horizontal slider */ .volume-control { position: absolute; right: 8px; - bottom: -48px; + top: -32px; z-index: 240; - height: 28px; + height: 24px; display: flex; flex-direction: row; align-items: center; - gap: 8px; - padding: 4px 12px; - border-radius: 14px; - background: rgba(0, 0, 0, 0.35); + gap: 6px; + padding: 3px 10px; + border-radius: 12px; + background: rgba(0, 0, 0, 0.5); border: 1px solid var(--ac-border); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); backdrop-filter: blur(6px); } @@ -834,19 +834,26 @@ function sendMasterVolume(value) { const clamped = clampVolume(value); + console.log('[volume] Sending master volume:', clamped); const code = `(() => { const v = ${clamped}; + console.log('[AC] setMasterVolume called with:', v); if (window.AC && typeof window.AC.setMasterVolume === 'function') { - window.AC.setMasterVolume(v); - return true; + const result = window.AC.setMasterVolume(v); + console.log('[AC] setMasterVolume result:', result); + return { success: true, method: 'AC.setMasterVolume', result }; } if (typeof window.acSetMasterVolume === 'function') { - window.acSetMasterVolume(v); - return true; + const result = window.acSetMasterVolume(v); + console.log('[AC] acSetMasterVolume result:', result); + return { success: true, method: 'acSetMasterVolume', result }; } - return false; + console.warn('[AC] No volume setter found. AC:', window.AC); + return { success: false, AC: !!window.AC }; })()`; - webviewEl.executeJavaScript(code, true).catch(() => {}); + webviewEl.executeJavaScript(code, true) + .then(result => console.log('[volume] executeJavaScript result:', result)) + .catch(err => console.error('[volume] executeJavaScript error:', err)); } function syncMasterVolume() { @@ -950,7 +957,17 @@ }); webviewEl.addEventListener('dom-ready', () => { + console.log('[volume] dom-ready - syncing volume') syncMasterVolume(); + // Retry after AC has had time to boot + setTimeout(() => { + console.log('[volume] Retry sync after 2s delay'); + syncMasterVolume(); + }, 2000); + setTimeout(() => { + console.log('[volume] Retry sync after 5s delay'); + syncMasterVolume(); + }, 5000); }); // Handle window.open() from webview (prompt.mjs + and - commands) diff --git a/system/netlify.toml b/system/netlify.toml index eb8803a5df..54842f3648 100644 --- a/system/netlify.toml +++ b/system/netlify.toml @@ -372,6 +372,15 @@ from = "/api/version" to = "/.netlify/functions/version" status = 200 +# Serve type/font assets locally (allow dev edits to BDF files) +# This must come BEFORE the general /assets/* proxy below +# force = true ensures this takes precedence over the /assets/* rule +[[redirects]] +from = "/assets/type/*" +to = "/assets/type/:splat" +status = 200 +force = true + # Proxy assets to S3 (DigitalOcean Spaces) - avoids uploading 13GB to Netlify [[redirects]] from = "/assets/*" diff --git a/system/netlify/functions/bdf-glyph.js b/system/netlify/functions/bdf-glyph.js index 6050410aa4..2a94921b24 100644 --- a/system/netlify/functions/bdf-glyph.js +++ b/system/netlify/functions/bdf-glyph.js @@ -447,9 +447,10 @@ export const handler = async (event) => { const charParam = event.queryStringParameters.char; const charsParam = event.queryStringParameters.chars; // Batch mode: comma-separated code points let fontParam = event.queryStringParameters.font || "unifont-16.0.03"; + const nocache = event.queryStringParameters.nocache === "true" || dev; // Skip cache in dev mode or with nocache=true // Debug logging for all requests - shell.log(`🔍 BDF-GLYPH CALLED: char=${charParam}, chars=${charsParam}, font=${fontParam}`); + shell.log(`🔍 BDF-GLYPH CALLED: char=${charParam}, chars=${charsParam}, font=${fontParam}, nocache=${nocache}`); shell.log(`🔍 Query params:`, JSON.stringify(event.queryStringParameters)); // Normalize font name for unifont @@ -596,22 +597,26 @@ export const handler = async (event) => { charCodeToFind = charParam.codePointAt(0); } - // Check for cached glyph data first - const cachedGlyph = await getCachedGlyph(charCodeToFind, fontParam); - if (cachedGlyph) { - // Display ASCII art for cached glyph - logGlyphAsASCII( - charToFind, - charCodeToFind, - cachedGlyph.data, - cachedGlyph.source, - ); + // Check for cached glyph data first (skip if nocache) + if (!nocache) { + const cachedGlyph = await getCachedGlyph(charCodeToFind, fontParam); + if (cachedGlyph) { + // Display ASCII art for cached glyph + logGlyphAsASCII( + charToFind, + charCodeToFind, + cachedGlyph.data, + cachedGlyph.source, + ); - return { - statusCode: 200, - body: JSON.stringify(cachedGlyph.data), - headers: { ...corsHeaders, "Content-Type": "application/json" }, - }; + return { + statusCode: 200, + body: JSON.stringify(cachedGlyph.data), + headers: { ...corsHeaders, "Content-Type": "application/json" }, + }; + } + } else { + shell.log(`🔄 Skipping cache lookup (nocache=${nocache})`); } shell.log( @@ -636,10 +641,16 @@ export const handler = async (event) => { ? `${assetsBaseUrl}/assets/type/${bdfFileName}` : `${assetsBaseUrl}/type/${bdfFileName}`; - // TEMP DEBUG: Force local file for MatrixChunky8 - const actualBdfUrl = fontParam === "MatrixChunky8" - ? "https://localhost:8888/assets/type/MatrixChunky8.bdf" - : bdfFileUrl; + // In dev mode, add cache-busting timestamp to always get fresh BDF file + const actualBdfUrl = dev + ? `${assetsBaseUrl}/assets/type/${bdfFileName}?t=${Date.now()}` + : (fontParam === "MatrixChunky8" + ? `${assetsBaseUrl}/type/MatrixChunky8.bdf` + : bdfFileUrl); + + if (dev) { + shell.log(`🔄 Dev mode: fetching fresh BDF from ${actualBdfUrl}`); + } // shell.log(`Attempting to fetch BDF from: ${actualBdfUrl}`); @@ -827,8 +838,12 @@ export const handler = async (event) => { // Log glyph as ASCII art using block characters // logGlyphAsASCII(charToFind, charCodeToFind, glyphData, "Generated"); - // Cache the parsed glyph data to S3 for future requests - await cacheGlyph(charCodeToFind, glyphData, fontParam); + // Cache the parsed glyph data (skip in dev/nocache mode) + if (!nocache) { + await cacheGlyph(charCodeToFind, glyphData, fontParam); + } else { + shell.log(`🔄 Skipping cache write (nocache=${nocache})`); + } return { statusCode: 200, diff --git a/system/public/aesthetic.computer/lib/disk.mjs b/system/public/aesthetic.computer/lib/disk.mjs index 1af2f7b944..090d8d5496 100644 --- a/system/public/aesthetic.computer/lib/disk.mjs +++ b/system/public/aesthetic.computer/lib/disk.mjs @@ -216,6 +216,69 @@ let tf; // Active typeface global. // Cache for loaded typefaces to avoid recreating them const typefaceCache = new Map(); +// 🔤 Dev helper: Clear all font caches (call from console: clearFontCaches()) +// This clears typeface instances, advance caches, and forces fonts.mjs to reload +async function clearFontCaches() { + console.log("🔤 Clearing font caches..."); + + // 1. Clear typeface instance cache + const clearedTypefaces = typefaceCache.size; + for (const [name, typeface] of typefaceCache) { + // Clear the advance cache on each typeface + if (typeface.advanceCache) { + typeface.advanceCache.clear(); + } + } + typefaceCache.clear(); + + // 2. Clear IndexedDB glyph cache + try { + const { clearGlyphCache } = await import("./type.mjs"); + if (clearGlyphCache) { + await clearGlyphCache(); + console.log("🔤 IndexedDB glyph cache cleared"); + } + } catch (e) { + console.log("🔤 IndexedDB cache clear skipped:", e.message); + } + + // 3. Force reimport of fonts.mjs by cache-busting + try { + const timestamp = Date.now(); + const fontsModule = await import(`../disks/common/fonts.mjs?t=${timestamp}`); + console.log("🔤 fonts.mjs reloaded, MatrixChunky8 * advance:", fontsModule.MatrixChunky8?.advances?.['*']); + } catch (e) { + console.log("🔤 fonts.mjs reload skipped:", e.message); + } + + console.log(`🔤 Cleared ${clearedTypefaces} typeface(s). Refresh page or load a piece to see changes.`); + return true; +} + +// Expose to window for dev console access +if (typeof window !== "undefined") { + window.clearFontCaches = clearFontCaches; + + // 🔤 Auto-clear font caches in dev mode on page load + const isDevMode = location.host === "localhost:8888" || + location.host === "aesthetic.local:8888" || + location.host === "local.aesthetic.computer"; + if (isDevMode) { + // Clear IndexedDB glyph cache on dev mode page loads + (async () => { + try { + const { clearGlyphCache } = await import("./type.mjs"); + if (clearGlyphCache) { + await clearGlyphCache(); + console.log("🔤 Dev mode: IndexedDB glyph cache auto-cleared"); + } + } catch (e) { + // Silently ignore if not available yet + } + })(); + } +} + const DEFAULT_TYPEFACE_BLOCK_WIDTH = 6; const DEFAULT_TYPEFACE_BLOCK_HEIGHT = 10; const HUD_LABEL_TEXT_MARGIN = 0; diff --git a/system/public/aesthetic.computer/lib/type.mjs b/system/public/aesthetic.computer/lib/type.mjs index e337683ea5..c68c3b5338 100644 --- a/system/public/aesthetic.computer/lib/type.mjs +++ b/system/public/aesthetic.computer/lib/type.mjs @@ -3270,4 +3270,4 @@ class Prompt { } } -export { Typeface, TextInput, Prompt }; +export { Typeface, TextInput, Prompt, clearGlyphCache }; diff --git a/system/public/give.aesthetic.computer/index.html b/system/public/give.aesthetic.computer/index.html index a5e2c44da2..66e45d2577 100644 --- a/system/public/give.aesthetic.computer/index.html +++ b/system/public/give.aesthetic.computer/index.html @@ -2241,7 +2241,7 @@ } .paypal-controls { - padding: 1em; + padding: 1.5em 2em; background: linear-gradient(180deg, rgba(0, 112, 186, 0.1) 0%, rgba(0, 48, 135, 0.15) 100%); border: 1px solid var(--box-border); border-top: none; @@ -2249,12 +2249,27 @@ display: flex; flex-direction: row; align-items: center; - gap: 1em; - max-width: 100%; + justify-content: center; + gap: 2em; + max-width: 600px; + margin: 0 auto; box-sizing: border-box; overflow: hidden; } + /* PayPal wrapper for centering on wide screens */ + .paypal-compact { + display: flex; + flex-direction: column; + align-items: center; + } + + .paypal-compact .paypal-header { + max-width: 600px; + width: 100%; + border-radius: 8px 8px 0 0; + } + @media (max-width: 480px) { .paypal-controls { flex-direction: column; @@ -12390,9 +12405,16 @@ const carousel = document.getElementById('kidlispCarousel'); try { // Fetch top hits from the TV API - const res = await fetch(`${apiBase}/api/tv?types=kidlisp&sort=hits&limit=50`); + const res = await fetch(`${apiBase}/api/tv?types=kidlisp&sort=hits&limit=100`); const data = await res.json(); - const hits = data.media?.kidlisp || data.mixed || []; + const allHits = data.media?.kidlisp || data.mixed || []; + + // Filter to only show @jeffrey pieces or anonymous top hits + const hits = allHits.filter(item => { + const handle = item.owner?.handle?.replace('@', '') || item.handle || item.author || ''; + // Show @jeffrey pieces or anonymous pieces (no handle/anon) + return handle === 'jeffrey' || !handle || handle === 'anon'; + }); if (!hits.length) { carousel.innerHTML = '
No pieces found
';