@hutusi/amytis 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/GEMINI.md +6 -2
  2. package/TODO.md +11 -15
  3. package/bun.lock +3 -0
  4. package/content/about.mdx +1 -0
  5. package/content/about.zh.mdx +21 -0
  6. package/content/flows/2026/02/20.md +16 -0
  7. package/content/links.mdx +42 -0
  8. package/content/links.zh.mdx +41 -0
  9. package/content/posts/2026-02-20-i18n-routing-considerations.mdx +150 -0
  10. package/content/posts/multimedia-showcase/index.mdx +261 -0
  11. package/content/privacy.mdx +32 -0
  12. package/content/privacy.zh.mdx +32 -0
  13. package/docs/ARCHITECTURE.md +8 -0
  14. package/package.json +2 -1
  15. package/scripts/copy-assets.ts +1 -1
  16. package/site.config.ts +126 -44
  17. package/src/app/[slug]/page.tsx +0 -10
  18. package/src/app/archive/page.tsx +38 -10
  19. package/src/app/books/[slug]/page.tsx +18 -0
  20. package/src/app/flows/[year]/[month]/[day]/page.tsx +20 -3
  21. package/src/app/layout.tsx +47 -21
  22. package/src/app/page.tsx +135 -72
  23. package/src/app/posts/[slug]/page.tsx +6 -12
  24. package/src/app/series/[slug]/page.tsx +18 -0
  25. package/src/app/subscribe/page.tsx +17 -0
  26. package/src/app/tags/[tag]/page.tsx +9 -26
  27. package/src/app/tags/page.tsx +3 -8
  28. package/src/components/AuthorCard.tsx +43 -0
  29. package/src/components/Comments.tsx +20 -4
  30. package/src/components/ExternalLinks.tsx +6 -2
  31. package/src/components/Footer.tsx +35 -26
  32. package/src/components/LanguageProvider.tsx +0 -5
  33. package/src/components/LanguageSwitch.tsx +117 -6
  34. package/src/components/LocaleSwitch.tsx +33 -0
  35. package/src/components/Navbar.tsx +31 -8
  36. package/src/components/PostNavigation.tsx +55 -0
  37. package/src/components/PostSidebar.tsx +172 -126
  38. package/src/components/ReadingProgressBar.tsx +6 -21
  39. package/src/components/RelatedPosts.tsx +1 -1
  40. package/src/components/Search.tsx +24 -4
  41. package/src/components/SelectedBooksSection.tsx +12 -6
  42. package/src/components/ShareBar.tsx +115 -0
  43. package/src/components/SimpleLayoutHeader.tsx +5 -14
  44. package/src/components/SubscribePage.tsx +298 -0
  45. package/src/components/TagContentTabs.tsx +103 -0
  46. package/src/components/TagPageHeader.tsx +7 -13
  47. package/src/components/TagSidebar.tsx +142 -0
  48. package/src/components/TagsIndexClient.tsx +156 -0
  49. package/src/hooks/useScrollY.ts +41 -0
  50. package/src/i18n/translations.ts +84 -0
  51. package/src/layouts/PostLayout.tsx +33 -6
  52. package/src/layouts/SimpleLayout.tsx +53 -15
  53. package/src/lib/markdown.ts +71 -15
  54. package/src/components/TableOfContents.tsx +0 -158
package/GEMINI.md CHANGED
@@ -43,9 +43,13 @@ bun test
43
43
  - `archive/`: Timeline-based chronological archive grouped by year and month.
44
44
  - `tags/`: Popularity-sorted tag cloud and filtered listings.
45
45
  - `authors/`: Posts filtered by individual authors.
46
+ - `subscribe/`: Subscription options (RSS, Newsletter, Social).
47
+ - `search.json/`: Static search index generator (supplementary).
46
48
  - `src/lib/`: Core logic and utilities.
47
- - `markdown.ts`: Advanced parsing for posts/series/flows, sorting, reading time calculation (multilingual), and metadata inheritance.
48
- - `src/components/`: Modular UI blocks (Hero, HorizontalScroll, Search, CoverImage, etc.).
49
+ - `markdown.ts`: Advanced parsing for posts/series/flows, sorting, and metadata.
50
+ - `search-utils.ts`: Content cleaning and search result processing.
51
+ - `shuffle.ts`: Deterministic and random array shuffling.
52
+ - `src/components/`: Modular UI blocks (Hero, HorizontalScroll, Search, CoverImage, ShareBar, etc.).
49
53
  - `content/`: Source Markdown/MDX content.
50
54
  - `scripts/`: CLI tools for content management and asset processing.
51
55
 
package/TODO.md CHANGED
@@ -1,25 +1,21 @@
1
1
  # Amytis Roadmap
2
2
 
3
- ## 🚀 Immediate UX & Polish
4
- - [ ] **Interactive Code**: Add "Copy to Clipboard" buttons to code blocks.
3
+ ## 🚀 Priority UX & Engineering
5
4
  - [ ] **Image Zoom**: Implement medium-zoom or a lightbox for MDX images.
6
- - [ ] **Breadcrumbs**: Add path navigation (e.g., `Home > Books > Chapter`) to all pages.
7
- - [ ] **Smart Nav**: Add "Previous" and "Next" article links at the bottom of posts.
5
+ - [ ] **Breadcrumbs**: Extend Flow breadcrumbs to standard Posts and Books.
6
+ - [ ] **Dynamic OG**: Generate automated social cards with Satori for every post.
7
+ - [ ] **PWA Support**: Add manifest and service worker for offline reading.
8
8
 
9
9
  ## 🌿 Digital Garden Evolution
10
10
  - [ ] **Backlinks**: Automatically list "Pages that link here" at the bottom of articles.
11
11
  - [ ] **Wiki-links**: Support `[[internal-link]]` syntax for easier cross-referencing.
12
- - [ ] **Recent Notes**: Integrate a "Recent Flows" section on the homepage.
13
-
14
- ## 🛠 Performance & Social
15
- - [ ] **Dynamic OG**: Generate automated social cards with Satori based on post titles.
16
- - [ ] **PWA**: Add a manifest and service worker for basic offline reading support.
17
- - [ ] **Search UI**: Refine Pagefind results with highlighted context and better mobile layout.
12
+ - [ ] **Knowledge Graph**: Interactive visual map of all connected notes.
18
13
 
19
14
  ## ✅ Completed Highlights
20
- - [x] **Full-text Search**: Migrated to **Pagefind** for high-performance static indexing.
15
+ - [x] **Pagefind Search**: High-performance static full-text indexing with rich UI.
16
+ - [x] **Smart Navigation**: Persistent "Previous" and "Next" article links on all posts.
21
17
  - [x] **Multi-format Content**: Native support for **Posts**, **Series**, **Books**, and **Flows**.
22
- - [x] **Scoped Publishing**: Official release as `@hutusi/amytis` on npm and GitHub.
23
- - [x] **Robust Engineering**: Zero hydration mismatches, full Zod validation, and 64+ automated tests.
24
- - [x] **Refined UI**: High-contrast typography, four color palettes, and horizontal featured scrolling.
25
- - [x] **Asset Pipeline**: Automatic co-located image mapping and WebP optimization.
18
+ - [x] **Professional Publishing**: Scoped `@hutusi/amytis` package with OIDC Trusted Publishing.
19
+ - [x] **Robust Engineering**: Zero hydration mismatches, Zod validation, and 64+ automated tests.
20
+ - [x] **Refined UI**: High-contrast typography, four color palettes, and horizontal scroll featured sections.
21
+ - [x] **Sub-features**: Newsletter/Subscribe page, Reading Progress, and Author Ecosystem.
package/bun.lock CHANGED
@@ -16,6 +16,7 @@
16
16
  "next-themes": "^0.4.6",
17
17
  "react": "19.2.4",
18
18
  "react-dom": "19.2.4",
19
+ "react-icons": "^5.5.0",
19
20
  "react-markdown": "^10.1.0",
20
21
  "react-syntax-highlighter": "^16.1.0",
21
22
  "rehype-katex": "^7.0.1",
@@ -1237,6 +1238,8 @@
1237
1238
 
1238
1239
  "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
1239
1240
 
1241
+ "react-icons": ["react-icons@5.5.0", "", { "peerDependencies": { "react": "*" } }, "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw=="],
1242
+
1240
1243
  "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
1241
1244
 
1242
1245
  "react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="],
package/content/about.mdx CHANGED
@@ -3,6 +3,7 @@ title: "About Amytis"
3
3
  date: "2026-01-07"
4
4
  excerpt: "Learn more about the philosophy and technology behind this digital garden."
5
5
  layout: "simple"
6
+ toc: true
6
7
  ---
7
8
 
8
9
  # About the Garden
@@ -0,0 +1,21 @@
1
+ ---
2
+ title: "关于 Amytis"
3
+ date: "2026-01-07"
4
+ excerpt: "了解这座数字花园背后的理念与技术。"
5
+ layout: "simple"
6
+ ---
7
+
8
+ Amytis 是一座为长期思想培育而设计的数字花园。与博客不同,数字花园是一个持续演化的想法网络。
9
+
10
+ ## 技术栈
11
+
12
+ - **Next.js 15+** — 基础框架
13
+ - **Tailwind CSS v4** — 样式系统
14
+ - **MDX** — 支持 React 组件的富内容格式
15
+ - **Bun** — 极速开发体验
16
+
17
+ ## 理念
18
+
19
+ 我们相信**公开学习**的力量。这里是种下种子(初始笔记)、悉心耕耘(提炼与关联)、最终长成长青文章的地方。
20
+
21
+ > "花园是最好的老师。它教人耐心与细心观察,教人勤劳与节俭,尤其教人全然的信任。" — Gertrude Jekyll
@@ -0,0 +1,16 @@
1
+ ---
2
+ title: "Multimedia embeds in markdown"
3
+ tags: ["video", "podcast", "markdown", "web"]
4
+ ---
5
+
6
+ Tested multimedia embedding in Amytis today. Since `rehype-raw` is enabled, standard HTML `<iframe>`, `<video>`, and `<audio>` tags work directly inside Markdown — no plugins needed.
7
+
8
+ A few patterns that work well:
9
+
10
+ - **YouTube / Vimeo** — wrap in a `padding-bottom: 56.25%` container for responsive 16:9
11
+ - **Spotify / Apple Podcasts** — fixed height iframes (152px for compact, 352px for full player)
12
+ - **HTML5 `<video>`** — fully native, supports `poster`, `loop`, `autoplay muted`
13
+ - **HTML5 `<audio>`** — great for podcast episodes or background music samples
14
+ - **Twitch** — needs the `parent` query param set to your domain
15
+
16
+ The `loading="lazy"` attribute is underused — always worth adding to below-the-fold embeds to keep the initial page fast.
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: "Links"
3
+ date: "2026-01-01"
4
+ excerpt: "A curated collection of resources, tools, and friends on the web."
5
+ layout: "simple"
6
+ toc: true
7
+ ---
8
+
9
+ A living collection of places worth visiting — useful resources I return to, and friends whose writing I admire.
10
+
11
+ ---
12
+
13
+ ## Resources
14
+
15
+ ### Writing & Thinking
16
+
17
+ - [Zettelkasten Method](https://zettelkasten.de/) — The definitive guide to networked note-taking and knowledge management.
18
+ - [Andy Matuschak's Notes](https://notes.andymatuschak.org/) — A public working notebook from a researcher in tools for thought.
19
+ - [LessWrong](https://www.lesswrong.com/) — A community blog focused on rationality and careful reasoning.
20
+
21
+ ### Development
22
+
23
+ - [MDN Web Docs](https://developer.mozilla.org/) — The most reliable reference for web platform APIs.
24
+ - [The Changelog](https://changelog.com/) — Podcasts and news for developers following open-source software.
25
+ - [Hacker News](https://news.ycombinator.com/) — Technology news and community discussion curated by Y Combinator.
26
+
27
+ ### Design
28
+
29
+ - [Refactoring UI](https://www.refactoringui.com/) — Practical design advice for developers, by the creators of Tailwind CSS.
30
+ - [Sidebar.io](https://sidebar.io/) — Five design links every day, covering UI, typography, and visual inspiration.
31
+
32
+ ---
33
+
34
+ ## Friends
35
+
36
+ - [Example Blog](https://example.com/) — A thoughtful writer exploring the intersection of technology and everyday life.
37
+ - [Another Garden](https://example.com/) — Beautifully tended digital garden with notes on philosophy and code.
38
+ - [Friend's Site](https://example.com/) — Short-form essays on creativity, craft, and the making of things.
39
+
40
+ ---
41
+
42
+ *Know a link that belongs here? [Send it my way](/about).*
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: "链接"
3
+ date: "2026-01-01"
4
+ excerpt: "精选资源、工具与友情链接。"
5
+ layout: "simple"
6
+ ---
7
+
8
+ 持续更新的精选集合——常常造访的实用资源,以及值得关注的朋友们。
9
+
10
+ ---
11
+
12
+ ## 资源
13
+
14
+ ### 写作与思考
15
+
16
+ - [卡片盒笔记法](https://zettelkasten.de/) — 网络化笔记与知识管理的权威指南。
17
+ - [Andy Matuschak 的笔记](https://notes.andymatuschak.org/) — 思维工具研究者的公开工作笔记本。
18
+ - [LessWrong](https://www.lesswrong.com/) — 专注于理性思维的社区博客。
19
+
20
+ ### 开发
21
+
22
+ - [MDN Web Docs](https://developer.mozilla.org/) — 最可靠的 Web 平台 API 参考文档。
23
+ - [The Changelog](https://changelog.com/) — 面向开发者的开源软件播客与资讯。
24
+ - [Hacker News](https://news.ycombinator.com/) — Y Combinator 精选的技术资讯与社区讨论。
25
+
26
+ ### 设计
27
+
28
+ - [Refactoring UI](https://www.refactoringui.com/) — Tailwind CSS 作者出品的开发者实用设计指南。
29
+ - [Sidebar.io](https://sidebar.io/) — 每日五条设计链接,涵盖 UI、排版与视觉灵感。
30
+
31
+ ---
32
+
33
+ ## 朋友们
34
+
35
+ - [示例博客](https://example.com/) — 探索技术与日常生活交汇点的深度写作者。
36
+ - [另一座花园](https://example.com/) — 关于哲学与代码的精心耕耘的数字花园。
37
+ - [朋友的站点](https://example.com/) — 关于创造、手艺与制作过程的短篇随笔。
38
+
39
+ ---
40
+
41
+ *有好链接想推荐?[告诉我](/about)。*
@@ -0,0 +1,150 @@
1
+ ---
2
+ title: "i18n in a Static Next.js Blog: Client-Side Toggle vs URL-Based Routing"
3
+ date: "2026-02-20"
4
+ excerpt: "A deep dive into the trade-offs between client-side language switching and proper URL-based locale routing for a statically exported Next.js digital garden."
5
+ tags: ["nextjs", "i18n", "seo", "static-site"]
6
+ ---
7
+
8
+ When building a multilingual static blog with Next.js, there are two fundamentally different approaches to internationalisation. Choosing between them involves trade-offs across SEO, developer experience, content strategy, and hosting complexity.
9
+
10
+ ## The Client-Side Toggle Approach
11
+
12
+ The simplest approach stores language preference in client state and resolves translations after hydration:
13
+
14
+ ```tsx
15
+ // Language stored in context, resolved on the client
16
+ const { t, language } = useLanguage();
17
+ const label = t('about'); // "About" or "关于"
18
+ ```
19
+
20
+ For page content that has locale variants, all versions are server-rendered into the HTML and a thin client wrapper toggles visibility:
21
+
22
+ ```tsx
23
+ // LocaleSwitch: shows/hides [data-locale] divs via useEffect
24
+ <LocaleSwitch>
25
+ <div data-locale="en"><MarkdownRenderer content={enContent} /></div>
26
+ <div data-locale="zh" style={{ display: 'none' }}><MarkdownRenderer content={zhContent} /></div>
27
+ </LocaleSwitch>
28
+ ```
29
+
30
+ This works and is zero-config — no routing changes needed. But it has real limitations.
31
+
32
+ ### SEO Problems
33
+
34
+ - **Crawlers always see the default language.** Googlebot doesn't execute `localStorage` reads, so it always indexes the default locale's content.
35
+ - **`display:none` content is risky.** Google may partially index hidden locale variants, or ignore them entirely.
36
+ - **No `hreflang` signals.** Search engines can't discover alternate language versions of a page.
37
+ - **`<html lang>` not updated.** Accessibility and search engine tooling rely on this attribute.
38
+
39
+ ---
40
+
41
+ ## URL-Based Locale Routing
42
+
43
+ The proper solution gives each language variant its own URL:
44
+
45
+ ```
46
+ /about → English (default, no prefix)
47
+ /zh/about → Chinese
48
+ ```
49
+
50
+ With Next.js App Router, this means moving all routes under an `app/[locale]/` segment and using `generateStaticParams` to pre-render each route for each locale at build time.
51
+
52
+ Since Amytis uses `output: "export"`, this is pure **Static Site Generation** — the HTML is fully pre-built per locale. Crawlers get complete HTML with no JavaScript required, and each locale is independently indexable.
53
+
54
+ ### The Default Locale Prefix Problem
55
+
56
+ Most sites want the default locale to have a clean URL (`/about`, not `/en/about`). With a runtime server, middleware handles this transparently. With static export, you need your **hosting layer** to do the rewrite.
57
+
58
+ #### Netlify / Cloudflare Pages
59
+
60
+ Drop a `_redirects` file in `public/`:
61
+
62
+ ```
63
+ /en / 301
64
+ /en/* /:splat 301
65
+ /* /en/:splat 200
66
+ ```
67
+
68
+ The `200` status is a **silent rewrite** — the user sees `/about` in the URL bar, but the server serves `out/en/about/index.html`.
69
+
70
+ #### Vercel
71
+
72
+ ```json
73
+ {
74
+ "redirects": [
75
+ { "source": "/en/:path*", "destination": "/:path*", "permanent": true }
76
+ ],
77
+ "rewrites": [
78
+ { "source": "/((?!zh/).*)", "destination": "/en/$1" }
79
+ ]
80
+ }
81
+ ```
82
+
83
+ #### Nginx
84
+
85
+ ```nginx
86
+ location ~ ^/en(/.*)?$ { return 301 ${1:-/}; }
87
+ location /zh/ { try_files $uri $uri/index.html =404; }
88
+ location / { try_files /en$uri /en$uri/index.html =404; }
89
+ ```
90
+
91
+ #### GitHub Pages
92
+
93
+ GitHub Pages has no native rewrite support. The simplest workaround is a root `index.html` with a JS language detector:
94
+
95
+ ```html
96
+ <script>
97
+ const lang = navigator.language.startsWith('zh') ? 'zh' : 'en';
98
+ window.location.replace('/' + lang + '/');
99
+ </script>
100
+ ```
101
+
102
+ Or just accept the `/en/` prefix for all locales.
103
+
104
+ ---
105
+
106
+ ## Content Strategy
107
+
108
+ URL-based routing changes more than just URLs — it changes how you think about content.
109
+
110
+ For a personal digital garden, most content is written in one language and won't be fully translated. The realistic scope is:
111
+
112
+ | Content Type | Translated? | Approach |
113
+ |---|---|---|
114
+ | Static pages (about, privacy) | Yes | Optional `.zh.mdx` variant files |
115
+ | Posts | Rarely | Fallback to original + notice |
116
+ | Series descriptions | Maybe | Optional `index.zh.mdx` |
117
+ | Books | Unlikely | Fallback |
118
+ | Flows (daily notes) | No | UI-only i18n, no locale in URL |
119
+
120
+ The `.{locale}.mdx` convention — already used for static pages in Amytis — extends naturally to posts and series:
121
+
122
+ ```
123
+ content/posts/my-post.mdx # default locale
124
+ content/posts/my-post.zh.mdx # optional Chinese translation
125
+ content/series/my-series/index.zh.mdx
126
+ ```
127
+
128
+ At build time, `generateStaticParams` generates `/en/my-post` and `/zh/my-post`. If the `.zh.mdx` file doesn't exist, the Chinese URL falls back to the English content with a small "not available in this language" banner.
129
+
130
+ ---
131
+
132
+ ## Trade-offs at a Glance
133
+
134
+ | Aspect | Client-Side Toggle | URL-Based Routing |
135
+ |---|---|---|
136
+ | SEO | Crawlers see default lang only | Each locale fully indexed |
137
+ | Default locale prefix | No prefix | CDN rewrite needed |
138
+ | Build size | Same | ~2× |
139
+ | Refactor scope | None | Large |
140
+ | Content strategy | `.zh.mdx` for pages only | `.zh.mdx` for all content types |
141
+
142
+ ---
143
+
144
+ ## Conclusion
145
+
146
+ For a personal blog where SEO in a second language isn't critical, the client-side toggle approach is pragmatic and gets the job done. UI strings translate, page content can optionally have locale variants, and there's no hosting complexity.
147
+
148
+ For a project where bilingual SEO matters — where you genuinely want Chinese content indexed under Chinese URLs — URL-based routing is the right architecture. The CDN configuration is a one-time setup, and the `.{locale}.mdx` convention for content is already half-implemented.
149
+
150
+ The good news: both approaches share the same content file convention. Migrating from client-side toggle to URL-based routing is a routing and build change, not a content restructure.
@@ -0,0 +1,261 @@
1
+ ---
2
+ title: "Multimedia Showcase: Video, Podcasts & Embeds"
3
+ date: "2026-02-20"
4
+ excerpt: "A comprehensive test of multimedia embedding support — YouTube, Vimeo, podcasts, livestreams, HTML5 native media, and audio platforms."
5
+ category: "Showcase"
6
+ tags: ["multimedia", "video", "audio", "podcast", "embed", "test"]
7
+ coverImage: "text:Media"
8
+ toc: true
9
+ ---
10
+
11
+ This post tests how Amytis renders multimedia content embedded via raw HTML iframes and native HTML5 media elements. Since `rehype-raw` is enabled, standard HTML embed codes work directly inside Markdown.
12
+
13
+ ---
14
+
15
+ ## YouTube Video
16
+
17
+ Responsive 16:9 YouTube embed using a padded wrapper:
18
+
19
+ <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; border-radius: 12px; margin: 1.5rem 0;">
20
+ <iframe
21
+ src="https://www.youtube.com/embed/jNQXAC9IVRw?rel=0"
22
+ style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
23
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
24
+ allowfullscreen
25
+ title="Me at the zoo — first YouTube video ever uploaded (2005)"
26
+ ></iframe>
27
+ </div>
28
+
29
+ *"Me at the zoo" — the very first video uploaded to YouTube (April 23, 2005).*
30
+
31
+ ---
32
+
33
+ ## Vimeo Video
34
+
35
+ Vimeo also supports responsive 16:9 embedding:
36
+
37
+ <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; border-radius: 12px; margin: 1.5rem 0;">
38
+ <iframe
39
+ src="https://player.vimeo.com/video/76979871?color=ff9933&title=0&byline=0&portrait=0"
40
+ style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
41
+ allow="autoplay; fullscreen; picture-in-picture"
42
+ allowfullscreen
43
+ title="Big Buck Bunny — Blender Foundation (Vimeo)"
44
+ ></iframe>
45
+ </div>
46
+
47
+ *Big Buck Bunny (2008) — open-source animated short by the Blender Foundation.*
48
+
49
+ ---
50
+
51
+ ## Podcast Embeds
52
+
53
+ ### Spotify Podcast
54
+
55
+ Spotify provides a fixed-height iframe embed for individual episodes:
56
+
57
+ <iframe
58
+ src="https://open.spotify.com/embed/episode/0b8q6Q7fKqZLlSc0Lh1WQe?utm_source=generator&theme=0"
59
+ style="border-radius: 12px; margin: 1rem 0;"
60
+ width="100%"
61
+ height="152"
62
+ frameborder="0"
63
+ allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
64
+ loading="lazy"
65
+ title="Spotify Podcast Episode"
66
+ ></iframe>
67
+
68
+ You can also embed the full show player at a taller height (352px) to show the episode list:
69
+
70
+ <iframe
71
+ src="https://open.spotify.com/embed/show/5as3aKmN2k11yfDDDSrvaZ?utm_source=generator"
72
+ style="border-radius: 12px; margin: 1rem 0;"
73
+ width="100%"
74
+ height="352"
75
+ frameborder="0"
76
+ allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
77
+ loading="lazy"
78
+ title="Spotify Show Player"
79
+ ></iframe>
80
+
81
+ ### Apple Podcasts
82
+
83
+ Apple Podcasts uses a similar iframe embed pattern:
84
+
85
+ <iframe
86
+ src="https://embed.podcasts.apple.com/us/podcast/syntax-tasty-web-development-treats/id1253186678?itsct=podcast_box_player&amp;itscg=30200&amp;ls=1&amp;theme=auto"
87
+ height="175px"
88
+ frameborder="0"
89
+ sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation-by-user-activation"
90
+ allow="autoplay *; encrypted-media *; clipboard-write"
91
+ style="width: 100%; max-width: 660px; overflow: hidden; border-radius: 10px; margin: 1rem 0; display: block;"
92
+ title="Apple Podcasts — Syntax.fm"
93
+ ></iframe>
94
+
95
+ ---
96
+
97
+ ## Livestream Embeds
98
+
99
+ ### YouTube Live
100
+
101
+ A YouTube Live channel or active live video can be embedded exactly like a regular video. Use the channel's live URL (`/live`) or a live event ID:
102
+
103
+ ```html
104
+ <!-- Replace CHANNEL_ID with the YouTube channel ID -->
105
+ <iframe
106
+ src="https://www.youtube.com/embed/live_stream?channel=CHANNEL_ID"
107
+ ...
108
+ ></iframe>
109
+ ```
110
+
111
+ Below is NASA's public live stream:
112
+
113
+ <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; border-radius: 12px; margin: 1.5rem 0;">
114
+ <iframe
115
+ src="https://www.youtube.com/embed/xAieE-QtOeM?autoplay=0"
116
+ style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
117
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
118
+ allowfullscreen
119
+ title="NASA TV Live"
120
+ ></iframe>
121
+ </div>
122
+
123
+ ### Twitch Stream
124
+
125
+ Twitch requires your site's hostname in the `parent` query parameter (so the embed works in production):
126
+
127
+ ```html
128
+ <!-- Replace CHANNEL_NAME with a Twitch channel, and your-domain.com with your domain -->
129
+ <iframe
130
+ src="https://player.twitch.tv/?channel=CHANNEL_NAME&parent=your-domain.com"
131
+ height="378"
132
+ width="100%"
133
+ allowfullscreen
134
+ title="Twitch Stream"
135
+ ></iframe>
136
+ ```
137
+
138
+ Twitch also supports embedding past broadcasts (VODs) and clips:
139
+
140
+ ```html
141
+ <!-- VOD -->
142
+ <iframe src="https://player.twitch.tv/?video=VOD_ID&parent=your-domain.com" ...></iframe>
143
+ <!-- Clip -->
144
+ <iframe src="https://clips.twitch.tv/embed?clip=CLIP_SLUG&parent=your-domain.com" ...></iframe>
145
+ ```
146
+
147
+ ---
148
+
149
+ ## HTML5 Native Media
150
+
151
+ Native `<video>` and `<audio>` elements work without any third-party embeds.
152
+
153
+ ### HTML5 Video
154
+
155
+ <video
156
+ controls
157
+ style="width: 100%; border-radius: 12px; margin: 1rem 0; background: #000;"
158
+ poster="https://peach.blender.org/wp-content/uploads/title_anouncement.jpg"
159
+ >
160
+ <source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4" type="video/mp4" />
161
+ Your browser does not support the HTML5 video element.
162
+ </video>
163
+
164
+ *Elephants Dream (2006) — first open-source animated film by the Blender Foundation.*
165
+
166
+ Attributes you can use:
167
+
168
+ | Attribute | Description |
169
+ | :--- | :--- |
170
+ | `controls` | Show playback controls |
171
+ | `autoplay` | Start playing automatically (use with `muted`) |
172
+ | `muted` | Mute by default (required for autoplay) |
173
+ | `loop` | Loop the video |
174
+ | `poster` | Preview image shown before play |
175
+ | `preload` | `auto` \| `metadata` \| `none` |
176
+
177
+ ### HTML5 Audio
178
+
179
+ <audio
180
+ controls
181
+ style="width: 100%; margin: 1rem 0;"
182
+ >
183
+ <source src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" type="audio/mpeg" />
184
+ Your browser does not support the HTML5 audio element.
185
+ </audio>
186
+
187
+ *SoundHelix royalty-free sample track.*
188
+
189
+ Multiple formats improve cross-browser support:
190
+
191
+ ```html
192
+ <audio controls>
193
+ <source src="audio.ogg" type="audio/ogg" />
194
+ <source src="audio.mp3" type="audio/mpeg" />
195
+ Your browser does not support the HTML5 audio element.
196
+ </audio>
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Audio Platforms
202
+
203
+ ### SoundCloud
204
+
205
+ SoundCloud exposes an iframe embed from any track's "Share → Embed" menu:
206
+
207
+ <iframe
208
+ width="100%"
209
+ height="166"
210
+ scrolling="no"
211
+ frameborder="no"
212
+ allow="autoplay"
213
+ style="border-radius: 8px; margin: 1rem 0;"
214
+ src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/1&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true"
215
+ title="SoundCloud track"
216
+ ></iframe>
217
+
218
+ The full visual player (300px height) shows the waveform:
219
+
220
+ ```html
221
+ <iframe
222
+ width="100%"
223
+ height="300"
224
+ src="https://w.soundcloud.com/player/?url=TRACK_URL&visual=true"
225
+ ...
226
+ ></iframe>
227
+ ```
228
+
229
+ ### Bandcamp
230
+
231
+ Bandcamp provides both album and track embed codes:
232
+
233
+ ```html
234
+ <!-- Album embed -->
235
+ <iframe
236
+ style="border: 0; width: 100%; height: 120px;"
237
+ src="https://bandcamp.com/EmbeddedPlayer/album=ALBUM_ID/size=large/bgcol=ffffff/linkcol=0687f5/"
238
+ seamless
239
+ ></iframe>
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Embed Best Practices
245
+
246
+ When embedding third-party media, keep these in mind:
247
+
248
+ - **Responsive sizing**: Wrap iframes in a `padding-bottom: 56.25%; position: relative` container to maintain 16:9 ratio.
249
+ - **`loading="lazy"`**: Add this to below-the-fold embeds to defer loading and improve page speed.
250
+ - **`title` attribute**: Always provide a descriptive `title` for screen reader accessibility.
251
+ - **Privacy-enhanced mode**: YouTube supports `youtube-nocookie.com` to reduce tracking when embeds don't auto-play.
252
+ - **`sandbox` attribute**: Use for untrusted embeds to restrict what the iframe can do.
253
+ - **Twitch `parent` parameter**: Twitch requires your site's hostname in `parent` for embeds to work outside localhost.
254
+
255
+ ```html
256
+ <!-- YouTube privacy-enhanced embed -->
257
+ <iframe
258
+ src="https://www.youtube-nocookie.com/embed/VIDEO_ID?rel=0"
259
+ ...
260
+ ></iframe>
261
+ ```
@@ -0,0 +1,32 @@
1
+ ---
2
+ title: "Privacy Policy"
3
+ date: "2026-02-20"
4
+ excerpt: "How this site collects and handles your data."
5
+ layout: "simple"
6
+ ---
7
+
8
+ # Privacy Policy
9
+
10
+ Your privacy matters. This page explains what data is collected when you visit this site and how it is used.
11
+
12
+ ## Analytics
13
+
14
+ This site uses **Umami**, a privacy-focused, open-source analytics platform. Umami does not use cookies, does not track users across websites, and does not collect personally identifiable information. The data collected is limited to anonymous page-view statistics such as referrer, browser type, and country — used solely to understand which content is most useful.
15
+
16
+ No data is sold or shared with third parties.
17
+
18
+ ## Comments
19
+
20
+ Post comments are powered by **Giscus**, which uses GitHub Discussions. To leave a comment you must sign in with a GitHub account. Your comment and GitHub username are stored by GitHub and are subject to [GitHub's Privacy Policy](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement).
21
+
22
+ If you do not have a GitHub account or prefer not to use it, you can always reach out directly by email.
23
+
24
+ ## External Links
25
+
26
+ This site may contain links to external websites. Once you leave this site, the privacy practices of those third-party sites apply and are outside this site's control.
27
+
28
+ ## Contact
29
+
30
+ If you have any questions about this privacy policy, feel free to reach out at the email address listed in the footer.
31
+
32
+ *Last updated: February 2026.*