@hutusi/amytis 1.6.0 → 1.8.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.
- package/CHANGELOG.md +49 -0
- package/GEMINI.md +12 -2
- package/README.md +14 -0
- package/TODO.md +24 -16
- package/bun.lock +8 -3
- package/content/about.mdx +1 -0
- package/content/about.zh.mdx +21 -0
- package/content/flows/2026/02/05.md +0 -1
- package/content/flows/2026/02/10.mdx +2 -1
- package/content/flows/2026/02/15.md +2 -1
- package/content/flows/2026/02/18.mdx +2 -1
- package/content/flows/2026/02/20.md +15 -0
- package/content/links.mdx +42 -0
- package/content/links.zh.mdx +41 -0
- package/content/notes/algorithms-and-data-structures.mdx +51 -0
- package/content/notes/digital-garden-philosophy.mdx +36 -0
- package/content/notes/react-server-components.mdx +49 -0
- package/content/notes/tailwind-v4.mdx +45 -0
- package/content/notes/zettelkasten-method.mdx +33 -0
- package/content/posts/2026-02-20-i18n-routing-considerations.mdx +150 -0
- package/content/posts/multimedia-showcase/index.mdx +261 -0
- package/content/privacy.mdx +32 -0
- package/content/privacy.zh.mdx +32 -0
- package/docs/ARCHITECTURE.md +16 -0
- package/docs/CONTRIBUTING.md +11 -0
- package/docs/DIGITAL_GARDEN.md +64 -0
- package/package.json +8 -3
- package/scripts/copy-assets.ts +1 -1
- package/scripts/generate-knowledge-graph.ts +162 -0
- package/scripts/new-flow.ts +0 -5
- package/scripts/new-note.ts +53 -0
- package/site.config.ts +146 -44
- package/src/app/[slug]/page.tsx +0 -10
- package/src/app/archive/page.tsx +38 -10
- package/src/app/books/[slug]/page.tsx +18 -0
- package/src/app/flows/[year]/[month]/[day]/page.tsx +51 -31
- package/src/app/flows/[year]/[month]/page.tsx +15 -13
- package/src/app/flows/[year]/page.tsx +22 -15
- package/src/app/flows/page/[page]/page.tsx +3 -9
- package/src/app/flows/page.tsx +3 -8
- package/src/app/globals.css +41 -0
- package/src/app/graph/page.tsx +19 -0
- package/src/app/layout.tsx +47 -21
- package/src/app/notes/[slug]/page.tsx +128 -0
- package/src/app/notes/page/[page]/page.tsx +58 -0
- package/src/app/notes/page.tsx +31 -0
- package/src/app/page.tsx +134 -72
- package/src/app/posts/[slug]/page.tsx +8 -12
- package/src/app/search.json/route.ts +15 -1
- package/src/app/series/[slug]/page.tsx +18 -0
- package/src/app/subscribe/page.tsx +17 -0
- package/src/app/tags/[tag]/page.tsx +9 -26
- package/src/app/tags/page.tsx +3 -8
- package/src/components/AuthorCard.tsx +43 -0
- package/src/components/Backlinks.tsx +39 -0
- package/src/components/Comments.tsx +20 -4
- package/src/components/ExternalLinks.tsx +6 -2
- package/src/components/FlowCalendarSidebar.tsx +4 -2
- package/src/components/FlowContent.tsx +4 -3
- package/src/components/FlowHubTabs.tsx +50 -0
- package/src/components/FlowTimelineEntry.tsx +7 -9
- package/src/components/Footer.tsx +35 -26
- package/src/components/KnowledgeGraph.tsx +324 -0
- package/src/components/LanguageProvider.tsx +0 -5
- package/src/components/LanguageSwitch.tsx +117 -6
- package/src/components/LocaleSwitch.tsx +33 -0
- package/src/components/MarkdownRenderer.tsx +13 -2
- package/src/components/Navbar.tsx +266 -17
- package/src/components/NoteContent.tsx +123 -0
- package/src/components/NoteSidebar.tsx +132 -0
- package/src/components/PostNavigation.tsx +55 -0
- package/src/components/PostSidebar.tsx +172 -126
- package/src/components/ReadingProgressBar.tsx +6 -21
- package/src/components/RecentNotesSection.tsx +6 -11
- package/src/components/RelatedPosts.tsx +1 -1
- package/src/components/Search.tsx +29 -5
- package/src/components/SelectedBooksSection.tsx +12 -6
- package/src/components/ShareBar.tsx +115 -0
- package/src/components/SimpleLayoutHeader.tsx +5 -14
- package/src/components/SubscribePage.tsx +298 -0
- package/src/components/TagContentTabs.tsx +102 -0
- package/src/components/TagPageHeader.tsx +7 -13
- package/src/components/TagSidebar.tsx +142 -0
- package/src/components/TagsIndexClient.tsx +156 -0
- package/src/hooks/useScrollY.ts +41 -0
- package/src/i18n/translations.ts +105 -1
- package/src/layouts/PostLayout.tsx +40 -8
- package/src/layouts/SimpleLayout.tsx +53 -15
- package/src/lib/markdown.ts +347 -18
- package/src/lib/remark-wikilinks.ts +59 -0
- package/src/lib/search-utils.ts +2 -1
- package/src/components/TableOfContents.tsx +0 -158
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Zettelkasten Method"
|
|
3
|
+
date: "2026-02-01"
|
|
4
|
+
tags: ["zettelkasten", "note-taking", "knowledge-management"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The Zettelkasten ("slip-box") method is a personal knowledge management system developed by sociologist Niklas Luhmann. He produced over 90 books and hundreds of articles, crediting much of his productivity to this system.
|
|
8
|
+
|
|
9
|
+
## Core Principles
|
|
10
|
+
|
|
11
|
+
**Atomic notes** — each note captures exactly one idea. If you need to say two things, write two notes. This constraint forces clarity and creates more connection points.
|
|
12
|
+
|
|
13
|
+
**Linking over filing** — instead of organizing notes into folders or categories, you link them to related ideas. The structure that emerges is a network, not a tree.
|
|
14
|
+
|
|
15
|
+
**Your own words** — always paraphrase rather than copy. The act of rewriting forces understanding and makes the note genuinely yours.
|
|
16
|
+
|
|
17
|
+
**Permanent notes** — unlike fleeting notes (quick captures) or literature notes (summaries of sources), permanent notes are written to last. They connect to existing knowledge and stand alone without context.
|
|
18
|
+
|
|
19
|
+
## Why It Works
|
|
20
|
+
|
|
21
|
+
A folder system forces you to predict how you'll want to retrieve something. Linking lets you discover connections you didn't anticipate. Luhmann described his Zettelkasten as a "conversation partner" — it would surprise him with unexpected connections between distant ideas.
|
|
22
|
+
|
|
23
|
+
The compounding effect is real: the more notes you have, the more connections are possible, and the more generative each new note becomes.
|
|
24
|
+
|
|
25
|
+
## Relation to Digital Gardens
|
|
26
|
+
|
|
27
|
+
[[digital-garden-philosophy]] takes the Zettelkasten spirit and applies it to public publishing. Where Luhmann's slip-box was private, a digital garden invites readers into the thinking process — with all its incompleteness and revision.
|
|
28
|
+
|
|
29
|
+
## In This Garden
|
|
30
|
+
|
|
31
|
+
This site implements Zettelkasten-style notes with bi-directional links. Any `[[wikilink]]` you write creates a connection that shows up in the backlinks panel of the target note.
|
|
32
|
+
|
|
33
|
+
See the [[digital-garden-philosophy]] note for how this fits the broader philosophy of this garden.
|
|
@@ -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&itscg=30200&ls=1&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.*
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "隐私政策"
|
|
3
|
+
date: "2026-02-20"
|
|
4
|
+
excerpt: "本站如何收集和处理您的数据。"
|
|
5
|
+
layout: "simple"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# 隐私政策
|
|
9
|
+
|
|
10
|
+
您的隐私对我们很重要。本页面说明您在访问本站时收集了哪些数据,以及这些数据如何被使用。
|
|
11
|
+
|
|
12
|
+
## 数据分析
|
|
13
|
+
|
|
14
|
+
本站使用 **Umami**,一个注重隐私的开源分析平台。Umami 不使用 Cookie,不跨网站追踪用户,也不收集任何个人身份信息。所收集的数据仅限于匿名页面访问统计,如来源网站、浏览器类型和所在国家,仅用于了解哪些内容最受欢迎。
|
|
15
|
+
|
|
16
|
+
数据不会被出售或与任何第三方共享。
|
|
17
|
+
|
|
18
|
+
## 评论
|
|
19
|
+
|
|
20
|
+
文章评论由 **Giscus** 提供支持,基于 GitHub Discussions 实现。留言需要使用 GitHub 账号登录。您的评论和 GitHub 用户名由 GitHub 存储,受 [GitHub 隐私政策](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement)约束。
|
|
21
|
+
|
|
22
|
+
如果您没有 GitHub 账号或不希望使用,也可以通过页脚中的电子邮件直接联系我。
|
|
23
|
+
|
|
24
|
+
## 外部链接
|
|
25
|
+
|
|
26
|
+
本站可能包含指向外部网站的链接。一旦您离开本站,这些第三方网站的隐私实践将适用,超出本站的控制范围。
|
|
27
|
+
|
|
28
|
+
## 联系我们
|
|
29
|
+
|
|
30
|
+
如果您对本隐私政策有任何疑问,请通过页脚中的电子邮件地址联系我们。
|
|
31
|
+
|
|
32
|
+
*最后更新:2026 年 2 月。*
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -40,6 +40,11 @@ src/app/
|
|
|
40
40
|
page.tsx # All books overview
|
|
41
41
|
[slug]/page.tsx # Book landing page
|
|
42
42
|
[slug]/[chapter]/page.tsx # Individual book chapter
|
|
43
|
+
notes/
|
|
44
|
+
page.tsx # All notes list
|
|
45
|
+
[slug]/page.tsx # Individual note
|
|
46
|
+
graph/
|
|
47
|
+
page.tsx # Knowledge graph visualization
|
|
43
48
|
flows/
|
|
44
49
|
page.tsx # Flows stream/listing
|
|
45
50
|
[year]/[month]/[day]/page.tsx # Individual flow entry (optional if modal/stream used)
|
|
@@ -50,6 +55,10 @@ src/app/
|
|
|
50
55
|
[author]/page.tsx # Posts by author
|
|
51
56
|
archive/
|
|
52
57
|
page.tsx # Chronological archive
|
|
58
|
+
subscribe/
|
|
59
|
+
page.tsx # Subscription options
|
|
60
|
+
search.json/
|
|
61
|
+
route.ts # Supplementary structured search data
|
|
53
62
|
[slug]/page.tsx # Static pages (about, etc.)
|
|
54
63
|
```
|
|
55
64
|
|
|
@@ -60,6 +69,7 @@ src/app/
|
|
|
60
69
|
- **`Footer`** - Social links, copyright, site metadata, and language switch.
|
|
61
70
|
- **`Hero`** - Homepage hero with collapsible intro.
|
|
62
71
|
- **`BookLayout`** - Dedicated layout for book chapters with sidebar navigation.
|
|
72
|
+
- **`PageHeader`** - Consistent header for simple pages (Archive, Tags, About).
|
|
63
73
|
|
|
64
74
|
### Content Display
|
|
65
75
|
- **`PostList`** - Card-based post listing.
|
|
@@ -67,6 +77,8 @@ src/app/
|
|
|
67
77
|
- **`SeriesCatalog`** - Timeline-style post listing for series.
|
|
68
78
|
- **`PostCard`** - Individual post preview card.
|
|
69
79
|
- **`CoverImage`** - Optimized image component using `next-image-export-optimizer` with dynamic desaturated gradients.
|
|
80
|
+
- **`ShareBar`** - Social sharing buttons for posts and books.
|
|
81
|
+
- **`TagContentTabs`** - Tabbed interface for filtering posts vs flows by tag.
|
|
70
82
|
|
|
71
83
|
### Content Rendering
|
|
72
84
|
- **`MarkdownRenderer`** - Core rendering component using `react-markdown` with plugins (GFM, Math, Raw HTML).
|
|
@@ -78,6 +90,7 @@ src/app/
|
|
|
78
90
|
- **`Search`** - Full-text search modal (Cmd/Ctrl+K) powered by Pagefind. Supports type filter tabs (All/Post/Flow/Book), recent searches, keyboard navigation, debounced input, focus trap, and ARIA accessibility. Search syntax: `"exact phrase"`, `word1 word2` (AND), `-exclude`.
|
|
79
91
|
- **`Pagination`** - Previous/Next page navigation.
|
|
80
92
|
- **`ReadingProgressBar`** - Top-of-page progress bar for book chapters.
|
|
93
|
+
- **`SubscribePage`** - Centralized page for all subscription options.
|
|
81
94
|
|
|
82
95
|
## Data Access Layer (`src/lib/markdown.ts`)
|
|
83
96
|
|
|
@@ -88,6 +101,9 @@ src/app/
|
|
|
88
101
|
| `getAllBooks()` | `BookData[]` | All books metadata |
|
|
89
102
|
| `getBookData(slug)` | `BookData \| null` | Single book metadata & TOC |
|
|
90
103
|
| `getBookChapter(...)` | `BookChapterData` | Single chapter content |
|
|
104
|
+
| `getAllNotes()` | `NoteData[]` | All notes, sorted by date |
|
|
105
|
+
| `getNoteBySlug(slug)` | `NoteData \| null` | Single note content |
|
|
106
|
+
| `getBacklinks(slug)` | `BacklinkSource[]` | Inbound links for a page |
|
|
91
107
|
| `getFlowBySlug(slug)` | `FlowData \| null` | Single flow entry |
|
|
92
108
|
| `getSeriesPosts(slug)` | `PostData[]` | Posts in a series |
|
|
93
109
|
| `calculateReadingTime(content)` | `string` | Estimated reading time (multilingual) |
|
package/docs/CONTRIBUTING.md
CHANGED
|
@@ -55,6 +55,17 @@ Books are manually structured in `content/books/`.
|
|
|
55
55
|
2. Add `index.mdx` with metadata and chapters configuration.
|
|
56
56
|
3. Create the chapter files (`welcome.mdx`, `conclusion.mdx`) in the same folder.
|
|
57
57
|
|
|
58
|
+
### Creating Notes (Digital Garden)
|
|
59
|
+
|
|
60
|
+
Notes live in `content/notes/`.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Create a new note
|
|
64
|
+
bun run new "Zettelkasten Method" --note
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or manually create `content/notes/zettelkasten-method.mdx`.
|
|
68
|
+
|
|
58
69
|
### Importing Content
|
|
59
70
|
|
|
60
71
|
```bash
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Digital Garden Features
|
|
2
|
+
|
|
3
|
+
Amytis includes a suite of features designed for non-linear knowledge management, inspired by the "Digital Garden" philosophy. These features allow you to create an interconnected web of thoughts rather than just a linear stream of posts.
|
|
4
|
+
|
|
5
|
+
## Core Concepts
|
|
6
|
+
|
|
7
|
+
### 1. Notes (`/notes`)
|
|
8
|
+
Notes are atomic units of knowledge. Unlike posts, which are often time-bound articles, notes are evergreen and concept-oriented.
|
|
9
|
+
|
|
10
|
+
- **Location:** `content/notes/*.mdx`
|
|
11
|
+
- **Frontmatter:**
|
|
12
|
+
```yaml
|
|
13
|
+
---
|
|
14
|
+
title: "Zettelkasten Method"
|
|
15
|
+
tags: ["pkm", "productivity"]
|
|
16
|
+
aliases: ["zettelkasten", "slip-box"] # Alternative names for wiki-linking
|
|
17
|
+
backlinks: true # Show backlinks at the bottom (default: true)
|
|
18
|
+
---
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2. Wiki-links
|
|
22
|
+
You can link between any content (Posts, Notes, Flows) using double-bracket syntax: `[[Slug]]` or `[[Slug|Display Text]]`.
|
|
23
|
+
|
|
24
|
+
- **Standard Link:** `[[zettelkasten-method]]` → links to `/notes/zettelkasten-method`
|
|
25
|
+
- **Aliased Link:** `[[zettelkasten-method|The Slip Box]]` → links to same note but displays "The Slip Box"
|
|
26
|
+
- **Cross-Type Linking:**
|
|
27
|
+
- If a slug matches a Note, it links to `/notes/[slug]`
|
|
28
|
+
- If it matches a Post, it links to `/posts/[slug]`
|
|
29
|
+
- If it matches a Flow, it links to `/flows/[slug]`
|
|
30
|
+
|
|
31
|
+
### 3. Backlinks
|
|
32
|
+
At the bottom of every Note, Amytis automatically generates a "Linked References" section. This lists every other page that links *to* the current note, along with a context snippet showing how it was referenced.
|
|
33
|
+
|
|
34
|
+
### 4. Knowledge Graph
|
|
35
|
+
The `/graph` route visualizes your entire digital garden as an interactive network graph.
|
|
36
|
+
- **Nodes**: Represent Notes, Posts, and Flows.
|
|
37
|
+
- **Edges**: Represent wiki-links connecting them.
|
|
38
|
+
- **Interaction**: Click a node to navigate to that page.
|
|
39
|
+
|
|
40
|
+
## How to Use
|
|
41
|
+
|
|
42
|
+
1. **Create a Note**: Run `bun new "My Concept" --note` (or manually create `content/notes/my-concept.mdx`).
|
|
43
|
+
2. **Link to it**: In a blog post or another note, type `[[my-concept]]`.
|
|
44
|
+
3. **Explore**: Visit `/notes` to see your collection, or `/graph` to see the connections.
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
In `site.config.ts`, you can configure the graph visualization:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
export const siteConfig = {
|
|
52
|
+
// ...
|
|
53
|
+
features: {
|
|
54
|
+
graph: {
|
|
55
|
+
enabled: true,
|
|
56
|
+
name: { en: "Graph", zh: "知识图谱" },
|
|
57
|
+
},
|
|
58
|
+
notes: {
|
|
59
|
+
enabled: true,
|
|
60
|
+
name: { en: "Notes", zh: "笔记" },
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
```
|