Loading…
a327ex.com

Anchor Website 5

Opus 4.8

Summary

Continued the Anchor 2 in-engine renderer of a327ex.com (a327ex-site/renderer/). This session completed the content-fidelity phase: owned tweet cards (M4), tweet video playback (T3), steam/spotify cards (T4), a general emoji fix, the old-posts dark skin, browser-style CJK font fallback, and player/code polish. The primary capability goal — a faithful in-engine render of the whole site — is essentially met.

Tweet cards — M4 (full ownership):

  • New tools/fetch_tweets.py (gallery-dl): scans ::tweet directives (33 unique), fetches each tweet's media + metadata into gitignored media-cache/tweets/<id>/, writes data/tweets.lua manifest (id → handle/name/avatar/text/date/media[]/ok). Needed pip install gallery-dl (yt-dlp can't do photo tweets).
  • convert.lua emits {type='tweet', url, handle, id, body_fallback}: parses @handle+id from the URL, consumes a following > quote as the in-source fallback body; serialize_element got a tweet case.
  • canvas.lua resolves el.tw against the manifest, preloads avatar + builds el._media_imgs (image sub-elements reusing image layout/draw/lightbox); graceful fallback when ok=false (renders @handle + quote with no fetch).
  • elements.lua layout/draw_tweet_element: faint-bg + hairline-outline card in the SITE THEME (not X-blue) — avatar (disc+initial fallback) / name / @handle·date / body / image grid (1=full, 2+=2col), lightbox on media click.
  • Refinements after eyeballing: tweet body in a distinct 15px Segoe UI face (theme.lua tweet/tweet_bold/tweet_italic + kind='tweet' in kind_font/pick_text_font) so quoted text reads differently from the article's Arial; display NAME is an emoji-aware text element; paragraphs preserved (fetch stores text with \n via lua_text, renderer splits into TW_PARA_GAP paragraphs); X-style date line with exact LOCAL time ("2:15 AM · Jun 6, 2026" via fmt_date UTC→local); convert.lua lookahead also swallows a lone [handle](url): attribution line before a quote (removes redundant paragraph + blockquote).
  • Text-only tweets: fetch via gallery-dl -j -o text-tweets=true (meta.json), so quote-tweets that are all reply text (e.g. @HeidiPriebe1) still get author/name/text. PYTHONUTF8 so emoji display names survive the Windows pipe.
  • T2 full pull (fetch_tweets.py --all): 31/33 owned (3 video @plaaoballs/@a327ex/@_sorrengailll, 6 image, 22 text-only); 2 fall back to the in-source quote (@stimhacked protected, @shin_yeo errored/deleted). Fixed a false-ok bug surfaced by the pull: a protected/errored tweet returned a metadata blob with no text/name/media but was marked ok=true (rendered a useless duplicate-@handle card) → _meta_from now requires content/author and build_entry sets ok=bool(name or text or media).

T3 — tweet video plays in-card: a tweet's video is a synthetic nested video element (el._video_el, media.ok=true) built in canvas preload, laid out via layout_video_element, drawn via draw_video_element — reusing the ENTIRE standalone player (poster→texture, click-to-play via the global video_active manager, controls, fullscreen, ≤1-decoder lifecycle) with zero manager changes.

T4 — steam/spotify owned cards: new tools/fetch_embeds.py (Steam appdetails API → name + header.jpg; Spotify oembed → title + cover) → media-cache/embeds/ + data/embeds.lua; convert.lua emits {type='embed', kind, id}; canvas resolves + preloads the image; new layout/draw_embed_element (themed image + title + platform-label card, display-only). Validated: Steam 1291340 = "Townscaper", Spotify playlist = "2023". M4 complete — every content directive now renders; no [TYPE] placeholders anywhere.

General emoji fix: the renderer had only 208 ad-hoc emoji PNGs + a single-codepoint tokenizer (no FE0F/keycap/ZWJ). Measured the whole site: 297 base emoji (89 missing), 280 FE0F, 46 keycaps, 4 ZWJ. New tools/fetch_emoji.py scans all content + the tweet manifest for emoji GRAPHEMES, downloads 72px Twemoji PNGs (jdecked jsDelivr CDN) by filename stem (FE0F dropped, codepoints joined by -), regenerates data/emoji_index.lua (images stems + present_bmp emoji-presentation codepoints). elements.lua tokenizer does emoji-grapheme segmentation gated by presentation (base ≥ 0x1F000 OR a modifier OR a BMP emoji-presentation codepoint — so bare text arrows / digits / ™ stay text). Downloaded 34 new (242 total). Narrowed the scan to skip non-emoji supplementary blocks (alchemical 1F700-1F7DF, arrows-ext 1F800-1F8FF) after 5 false-positive 404s.

Old /posts/* dark skin: third theme posts_old in theme.lua reproduces static/old-blog.css (Blot "Hypertext") — Source Sans 18px + Source Code (self-hosted WOFFs in static/fonts/; the engine's FreeType has FT_CONFIG_OPTION_USE_ZLIB so WOFF1 loads; pcall-fallback to the sans), bg #272727 / text #DADADA / link #019BD6, h1≈28 / h2-h6≈19 bold no-border, roomy heading gaps. main.lua load_page applies it when doc.section=='posts' (else new_theme) before relayout; show_home restores new_theme; F2 toggles the new theme only. Full-app swap (sidebar recolors in old posts). Fixed a latent theme_apply bug: fonts were registered under role-keyed names (site_h1), which collide across 3 themes that share role names at the same size — now keyed by (file,size). Applies to ~40 posts.

CJK fallback (copies the browser): Arial/Segoe/Source-Sans lack CJK glyphs → boxes (povkeiran tweet 躺平 / 二十四点). User: "Do what the browser does." The browser does per-CHARACTER fallback to the OS CJK font, WEIGHT/STYLE-matched (bold text → bold CJK), only swapping the characters the primary font lacks. Implemented exactly that: theme.lua CJK_REG/CJK_BOLD (Yu Gothic Regular / Medium-as-bold; chain → YaHei → MS Gothic; covers kana + Han, renders Chinese too) registered per-role into prose.cjk[role] at the role's size + weight in theme_apply. elements.lua is_cjk(cp) + pick_cjk_font(kind,style); the tokenizer flushes a word on font change so CJK characters become matched-CJK-font tokens INLINE (Latin stays primary); draw_subtitles uses mixed_segments/mixed_width so ja/zh subtitle tracks render (this was also the "CJK sub font" polish item). Italic CJK renders upright (browsers synthesize the slant; we don't). Loads the system .ttc via FreeType face 0; one-line swap to msyh/msyhbd for Chinese-form Han.

Player polish (B): (1) controls SCALE with video height — ctrl_scale(r)=clamp(r.h/460,1,2.4) in video_controls_geom, so a 16:9 card stays 1× (no regression) but a fullscreen / big video gets a proportionally larger bar (icons / seek / knob / font scale; subtitle font scales via role body→h3→h1); geom + draw + hit-test all derive the scale from r.h so they stay aligned. (2) subtitle LANGUAGE PICKER — subs_langs/cc_menu_geom (elements.lua); with >1 track, hovering the CC button pops a language list above it, click a row → video_sub_lang + enable + video_load_subs(el, lang) (re-parses; cache guard removed); single-track stays a plain toggle. ic_cc takes a scaled font.

G-tail (C): <pre> COPY BUTTON — code_copy_geom (top-right, shared by draw + hit-test), hover-revealed (el._copy_hot == frame_num), click → clipboard_set(code.text) + a ~1.5s "copied" flash (el._copied_frame); guards the drag-pan so a copy-click doesn't start a pan. Heading-anchor copy deferred to web delivery (no in-engine URLs).

Pacing / process: delivered in slices with eyeball checkpoints between each (tweets → card refinements → T2 → T3+T4 → emoji → old-posts skin → CJK → B+C); design briefs posted and approved before non-trivial code. User confirmed CJK renders. User: "Very good progress, I'm surprised it all went so well." Remaining for future sessions: web delivery (range-stream + browser-<video> + R2), the parked unfocus-hang bug, M2b (full ~106-video pull), and a thorough per-post fidelity cleanup pass.

🔒 Only the summary of this log is public. Private because it contains too many website internal details.