From cf83c32b25e876c18a8360d40b0593e8d539f97a Mon Sep 17 00:00:00 2001 From: Kolkman Date: Fri, 28 Aug 2026 08:19:43 +0200 Subject: [PATCH] Fonts served via server --- CHANGELOG.MD | 1 + Dockerfile | 2 + README.md | 2 +- VIBE/CHAT_LOG.md | 6 +++ VIBE/PROMPTS.md | 1 + backend/tests/test_api.py | 3 ++ frontend/static/style.css | 2 +- scripts/download_fonts.py | 95 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 scripts/download_fonts.py diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 60695dd..973c0b8 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -14,6 +14,7 @@ * Added a high-contrast Red color theme * Brightened the Red theme surfaces and deepened its crimson accents * Allow every user to filter the feed using every available tag or label + * Bundle required web fonts during Docker image builds and serve them from LinkLog ### Fixed * Fixed element ID conflict on the Admin page so Available Themes load correctly * Positioned new-entry tag checkboxes after their label text diff --git a/Dockerfile b/Dockerfile index f16bc2c..59a2eac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,8 @@ RUN pip install --no-cache-dir -r backend/requirements.txt COPY backend ./backend COPY frontend ./frontend +COPY scripts/download_fonts.py ./scripts/download_fonts.py +RUN python scripts/download_fonts.py RUN useradd --create-home --uid 10001 linklog \ && mkdir -p /app/backend/data \ && chown -R linklog:linklog /app diff --git a/README.md b/README.md index 87c05b7..0f77f89 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Open these URLs: The browser extension requires a backend URL to be entered during setup; it does not assume a default server. The supplied `Logo.svg` is bundled as `frontend/static/logo.svg` for web pages and `webextension/logo.svg` for the Firefox popup and settings page. -The visible LinkLog brand text uses the Google Foundry `Asset` font when available, with local fallbacks in the Firefox extension. +The visible LinkLog brand text uses the Google Foundry `Asset` font. Docker image builds download and bundle Asset, DM Sans, and Space Grotesk under `/static/fonts`, so the web frontend loads fonts from the LinkLog server rather than Google. The Firefox extension uses its bundled assets and local fallback fonts without requesting Google Fonts. ## Regenerate Logo Assets diff --git a/VIBE/CHAT_LOG.md b/VIBE/CHAT_LOG.md index ea5dff1..932619d 100644 --- a/VIBE/CHAT_LOG.md +++ b/VIBE/CHAT_LOG.md @@ -1418,3 +1418,9 @@ I believe tag selection was still working for v0.1.0 - it is still broken ### Assistant outcome Found the actual regression in the direct-refresh update: `Promise.all([loadUsers(), loadTags()]).then(loadFeed)` passed its results array into the new `selectedTag` parameter. The feed then attempted to call `toLowerCase()` on that array during initialization. Restored the v0.1.0 callback shape with `.then(() => loadFeed())`, retained direct selected-tag refreshes, added a regression assertion, and cache-busted the feed asset to `feed.js?v=12`. + +### User +Don't let the frontend and plugin pull from the google foundry but make sure any necessary fonts are served from the linklog server. I would prefer the fonts to be updated at docker image building time. + +### Assistant outcome +Removed the web frontend's Google Fonts import and added a Docker build-time font bundling script. Image builds now download pinned Asset, DM Sans, and Space Grotesk files into `frontend/static/fonts`, generate a local `fonts.css`, and serve them through LinkLog at `/static/fonts`. The Firefox extension did not make Google Font requests and continues to use bundled assets with local fallback fonts. A clean image build confirmed all nine local font assets are present. diff --git a/VIBE/PROMPTS.md b/VIBE/PROMPTS.md index beea42f..3bf2896 100644 --- a/VIBE/PROMPTS.md +++ b/VIBE/PROMPTS.md @@ -252,6 +252,7 @@ 233. tag filter doesn't seem to respond when pressed 234. The tag filter still does not allow me to select tags, even though the raw html contains the correct content. Also the user filter and the sort functionality work too 235. I believe tag selection was still working for v0.1.0 - it is still broken +236. Don't let the frontend and plugin pull from the google foundry but make sure any necessary fonts are served from the linklog server. I would prefer the fonts to be updated at docker image building time. ## Future entries diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index e2a86b8..751628c 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -841,6 +841,9 @@ def test_public_and_admin_pages_render_html(): assert 'edit-tag-options' in feed_script assert 'new_tags' in feed_script assert 'A link can have at most 10 tags.' in feed_script + style_sheet = client.get('/static/style.css').text + assert "@import url('/static/fonts/fonts.css');" in style_sheet + assert 'fonts.googleapis.com' not in style_sheet def test_link_submission_posts_to_enabled_mastodon_plugin(): diff --git a/frontend/static/style.css b/frontend/static/style.css index 84e82ff..6eed8cf 100644 --- a/frontend/static/style.css +++ b/frontend/static/style.css @@ -1,7 +1,7 @@ /* Copyright © 2026 Olaf Kolkman */ /* SPDX-License-Identifier: GPL-3.0-or-later */ -@import url('https://fonts.googleapis.com/css2?family=Asset&family=DM+Sans:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap'); +@import url('/static/fonts/fonts.css'); :root { --base: #1e1e2e; diff --git a/scripts/download_fonts.py b/scripts/download_fonts.py new file mode 100644 index 0000000..7e9d541 --- /dev/null +++ b/scripts/download_fonts.py @@ -0,0 +1,95 @@ +## Copyright © 2026 Olaf Kolkman +## SPDX-License-Identifier: GPL-3.0-or-later + +from pathlib import Path +from urllib.request import urlopen + + +FONT_SOURCES = { + 'Asset-Regular.ttf': 'https://fonts.gstatic.com/s/asset/v30/SLXGc1na-mM4cWIm.ttf', + 'DMSans-Regular.ttf': 'https://fonts.gstatic.com/s/dmsans/v17/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwAopxhTg.ttf', + 'DMSans-Medium.ttf': 'https://fonts.gstatic.com/s/dmsans/v17/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwAkJxhTg.ttf', + 'DMSans-SemiBold.ttf': 'https://fonts.gstatic.com/s/dmsans/v17/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwAfJthTg.ttf', + 'DMSans-Bold.ttf': 'https://fonts.gstatic.com/s/dmsans/v17/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwARZthTg.ttf', + 'SpaceGrotesk-Medium.ttf': 'https://fonts.gstatic.com/s/spacegrotesk/v22/V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj7aUUsj.ttf', + 'SpaceGrotesk-SemiBold.ttf': 'https://fonts.gstatic.com/s/spacegrotesk/v22/V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj42Vksj.ttf', + 'SpaceGrotesk-Bold.ttf': 'https://fonts.gstatic.com/s/spacegrotesk/v22/V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj4PVksj.ttf', +} + +FONT_CSS = """@font-face { + font-family: 'Asset'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('Asset-Regular.ttf') format('truetype'); +} + +@font-face { + font-family: 'DM Sans'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('DMSans-Regular.ttf') format('truetype'); +} + +@font-face { + font-family: 'DM Sans'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('DMSans-Medium.ttf') format('truetype'); +} + +@font-face { + font-family: 'DM Sans'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('DMSans-SemiBold.ttf') format('truetype'); +} + +@font-face { + font-family: 'DM Sans'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('DMSans-Bold.ttf') format('truetype'); +} + +@font-face { + font-family: 'Space Grotesk'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('SpaceGrotesk-Medium.ttf') format('truetype'); +} + +@font-face { + font-family: 'Space Grotesk'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('SpaceGrotesk-SemiBold.ttf') format('truetype'); +} + +@font-face { + font-family: 'Space Grotesk'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('SpaceGrotesk-Bold.ttf') format('truetype'); +} +""" + + +def main() -> None: + target_dir = Path('frontend/static/fonts') + target_dir.mkdir(parents=True, exist_ok=True) + for filename, url in FONT_SOURCES.items(): + with urlopen(url, timeout=30) as response: + (target_dir / filename).write_bytes(response.read()) + (target_dir / 'fonts.css').write_text(FONT_CSS, encoding='utf-8') + + +if __name__ == '__main__': + main() \ No newline at end of file