~cytrogen/blog-public

blog-public/posts/ccb5.html -rw-r--r-- 27.2 KiB
b922ad66Cytrogen Deploy 2026-04-08 02:26:04 a day ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
<!DOCTYPE html><html lang="en" data-theme="dark"><head><meta charset="utf-8"><meta name="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>DeGoogling and DeMicrosoft is a new start for me · Cytrogen 的个人博客</title><meta name="description" content="I haven't been writing blogs in English for a while, a long long long while. If you switched the language of this website from English to Simplified Chinese, you would find out I wrote way more articles there, including a monthly series. I initially started to write blogs for practicing writing and thinking skills, and it went very well. I met more blogger friends, and was able to talk about my online experiences in my real life; it's like double winning."><link rel="icon" href="../favicon.png"><link rel="canonical" href="https://blog.cytrogen.icu/posts/ccb5.html"><link rel="webmention" href="https://webmention.io/blog.cytrogen.icu/webmention"><link rel="me" href="https://gts.cytrogen.icu/@Cytrogen"><link rel="me" href="mailto:blog@cytrogen.icu"><meta name="fediverse:creator" content="@Cytrogen@gts.cytrogen.icu"><link rel="preload" href="../fonts/opensans-regular-latin.woff2" as="font" type="font/woff2" crossorigin="anonymous"><style>@font-face {
  font-family: 'Open Sans';
  src: url('../fonts/opensans-regular-latin.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  size-adjust: 107%;
  ascent-override: 97%;
  descent-override: 25%;
  line-gap-override: 0%;
}
</style><script>(function() {
  try {
    // 优先级:用户选择 > 系统偏好 > 默认浅色
    const saved = localStorage.getItem('theme');
    const theme = saved || 
      (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
    
    document.documentElement.setAttribute('data-theme', theme);
    document.documentElement.style.colorScheme = theme;
  } catch (error) {
    // 失败时使用默认主题,不阻塞渲染
    document.documentElement.setAttribute('data-theme', 'light');
  }
})();
</script><link rel="stylesheet" href="../css/ares.css"><script data-netlify-skip-bundle="true">(function() {
  document.addEventListener('DOMContentLoaded', function() {
    const theme = document.documentElement.getAttribute('data-theme');
    const pageWrapper = document.getElementById('page-wrapper');
    if (pageWrapper && theme) {
      pageWrapper.setAttribute('data-theme', theme);
    }
  });
})();

</script><!-- hexo injector head_end start -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css">

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hexo-math@4.0.0/dist/style.css">
<!-- hexo injector head_end end --><meta name="generator" content="Hexo 8.1.1"><link rel="alternate" href="atom.xml" title="Cytrogen 的个人博客" type="application/atom+xml">
</head><body><div id="page-wrapper"><a class="skip-link" href="#main-content">跳到主要内容</a><div class="wrap"><header><a class="logo-link" href="../index.html"><img src="../favicon.png" alt="logo"></a><div class="h-card visually-hidden"><img class="u-photo" src="https://blog.cytrogen.icu/favicon.png" alt="Cytrogen"><a class="p-name u-url u-uid" href="https://blog.cytrogen.icu">Cytrogen</a><p class="p-note">Cytrogen 的个人博客,Cytrogen's Blog</p><a class="u-url" rel="me noopener" target="_blank" href="https://gts.cytrogen.icu/@Cytrogen">Mastodon</a></div><nav class="site-nav"><div class="nav-main"><div class="nav-primary"><ul class="nav-list hidden-mobile"><li class="nav-item"><a class="nav-link" href="../en/">HOME</a></li></ul><div class="nav-tools"><div class="language-menu"><button class="language-toggle" type="button"><svg class="icon icon-globe" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855A7.97 7.97 0 0 0 10.855 12H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z"></path></svg><span>English</span></button><div class="language-dropdown"></div></div></div><div class="nav-controls"><div class="more-menu hidden-mobile"><button class="more-toggle" type="button"><span>MORE</span><svg class="icon icon-chevron-down" width="12" height="12" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 8.825c-.2 0-.4-.1-.5-.2l-3.3-3.3c-.3-.3-.3-.8 0-1.1s.8-.3 1.1 0l2.7 2.7 2.7-2.7c.3-.3.8-.3 1.1 0s.3.8 0 1.1l-3.3 3.3c-.1.1-.3.2-.5.2z"></path></svg></button><div class="more-dropdown"><ul class="dropdown-list"><li class="dropdown-item"><a class="nav-link" href="../en/archives/index.html">ARCHIVE</a></li><li class="dropdown-item"><a class="nav-link" href="../en/categories/index.html">CATEGORIES</a></li><li class="dropdown-item"><a class="nav-link" href="../en/tags/index.html">TAGS</a></li><li class="dropdown-item"><a class="nav-link" href="../en/about/index.html">ABOUT</a></li><li class="dropdown-item"><a class="nav-link" href="../en/sitemap/index.html">SITEMAP</a></li></ul></div></div><div class="theme-switcher"><button class="theme-toggle" type="button" role="switch" aria-pressed="false" aria-label="切换主题"><div class="theme-icon moon-icon"><svg class="icon icon-moon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"></path></svg></div><div class="theme-icon sun-icon"><svg class="icon icon-sun" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"></path></svg></div></button></div><details class="mobile-menu-details hidden-desktop"><summary class="hamburger-menu" aria-label="nav.menu"><svg class="icon icon-bars" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"></path></svg><span class="menu-text">nav.menu</span></summary><div class="mobile-menu-dropdown"><ul class="mobile-nav-list"><li class="mobile-nav-item"><a class="mobile-nav-link" href="../en/">HOME</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../en/archives/index.html">ARCHIVE</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../en/categories/index.html">CATEGORIES</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../en/tags/index.html">TAGS</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../en/about/index.html">ABOUT</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../en/sitemap/index.html">SITEMAP</a></li></ul></div></details></div></div></div></nav></header><main class="container" id="main-content" tabindex="-1"><div class="post"><article class="post-block h-entry"><div class="post-meta p-author h-card visually-hidden"><img class="author-avatar u-photo" src="../favicon.png" alt="Cytrogen"><span class="p-name">Cytrogen</span><a class="u-url" href="https://blog.cytrogen.icu">https://blog.cytrogen.icu</a></div><a class="post-permalink u-url u-uid visually-hidden" href="https://blog.cytrogen.icu/posts/ccb5.html">永久链接</a><div class="p-summary visually-hidden"><p>I haven't been writing blogs in English for a while, a long long long while. If you switched the language of this website from English to Simplified Chinese, you would find out I wrote way more articles there, including a <a href="/categories/%E6%83%B3%E6%B3%95%E8%BF%AD%E4%BB%A3/">monthly series</a>. I initially started to write blogs for practicing writing and thinking skills, and it went very well. I met more blogger friends, and was able to talk about my online experiences in my real life; it's like double winning.</p></div><div class="visually-hidden"><a class="p-category" href="../categories/Personal-Musings/">Personal Musings</a><a class="p-category" href="../tags/Data-sovereignty/">Data sovereignty</a><a class="p-category" href="../tags/Kagi/">Kagi</a></div><a class="u-syndication visually-hidden" target="_blank" rel="noopener" href="https://m.otter.homes/@Cytrogen/116252737051536228">https://m.otter.homes/@Cytrogen/116252737051536228</a><h1 class="post-title p-name">DeGoogling and DeMicrosoft is a new start for me</h1><div class="post-info"><time class="post-date dt-published" datetime="2026-03-18T04:00:00.000Z">3/18/2026</time><time class="dt-updated visually-hidden" datetime="2026-03-18T23:19:55.928Z"></time></div><div class="post-content e-content"><p>I haven't been writing blogs in English for a while, a long long long while. If you switched the language of this website from English to Simplified Chinese, you would find out I wrote way more articles there, including a <a href="/categories/%E6%83%B3%E6%B3%95%E8%BF%AD%E4%BB%A3/">monthly series</a>. I initially started to write blogs for practicing writing and thinking skills, and it went very well. I met more blogger friends, and was able to talk about my online experiences in my real life; it's like double winning.</p>
<span id="more"></span>
<p>Lately, one topic we have been discussing a lot is data sovereignty. Free and comfortable services from massive corporations are not always gifts from angels. The monopoly of today's internet is not just in the cloud or browsers; it invades our local workspaces and digital identities as well.</p>
<p>Google and Microsoftare prime examples. They love ads. I mean, Google started as an advertising company and remains one at its core. Microsoft, on the other hand, is the one of the few lucky ones who currently making massive profits from AI, heavily driven by ads. The situation goes beyond the browser. Windows 11 has become the worst operating system in my tier ranking. It's bloated with ads and telemetry. With Copilot, ew I hate this name now, being forced into every corner of the operating system, it feels less like a helpful assistant and more like a surveillance camera constantly monitoring my local workspace.</p>
<p>People often underestimate how powerful this advertising and tracking model is. Corporations earn trillions of dollars by showing ads on their customers' devices. We think we are getting free services, but we are actually guinea pigs in a wheel, running and running to generate profits for them.</p>
<p>To understand how I decided to fight back against this, let we wind back a little. Everything started when I began using Mastodon.</p>
<p>Mastodon is a Twitter-like software from the Fediverse. If you are unfamiliar, you can think of the Fediverse as the United States of America: a collection of federated states. New York can have different state laws than California, but they all follow federal laws. New Yorkers can also travel to California without police checking their passports. Substituted into software concepts: different servers (or instances) have their own rules, but they all communicate using the same <em>federal law</em> -- the ActivityPub protocol.</p>
<p>The problem the Fediverse tries to solve is centralization. On modern social platforms, you must register separate accounts for Twitter, Facebook, or Reddit, and your data is locked within their walls. When Elon Musk took over Twitter (now, unfortunately, X), it highlighted the fatal flaw of centralized platforms: the company holds all your data, can arbitrarily ban you, manipulate the algorithm to make you watch more ads, or completely change the platform's nature. You are just renting space in their realm. Mastodon, by contrast, has no algorithms. You follow who you want, read your chronological feed, and if your instance administrator goes rogue, you can pack up your data and migrate to another instance.</p>
<p>Using Fediverse software opened my eyes, but it was only the first step. By listening to Cory Doctorow's speech <em>The Post-American Internet</em> and learning about his concept of <em>Enshittification</em>, I realized the inevitable fate of centralized platforms: they lure you in with free services, lock you in, and then degrade the experience to extract maximum value. Furthermore, Richard Stallman and the GNU project taught me that user freedom is entirely dependent on software freedom.</p>
<p>This philosophical shift triggered my massive migration. To reclaim my local control, I abandoned Windows entirely and switched to Arch Linux (EndeavourOS). I started replacing proprietary software with free and open-source (FOSS) alternatives. I learned to use GNU Emacs; instead of Gmail and Outlook, I use Fairmail and self-hosted emails; instead of YouTube Music, I use Tempo and Feishin with local files and radios; instead of YouTube and Bilibili, I use LibreTube and PiliPlus clients with zero data tracking; instead of Google Maps, I use Organic Maps; instead of Google Calendar, I use Etar -- surprisingly, they all went very well!</p>
<p>The most significant change in my daily routine, however, was subscribing to Kagi Search. Kagi fights back against Google's search monopoly. It aggregates results but heavily filters out AI slop, ad-heavy sites, and data-tracking JavaScript.</p>
<p>The common counter-argument I hear is: <em>Why buy a service when you can get Google for free?</em> I used to think the same way. Unfortunately, data-driven corporations don't see users of free services as customers (and even sadly, users of paid services are sometimes not seen as customers as well); they see them as organisms generating valuable data. You pay with your privacy. Paying a small business like Kagi to treat me like a human being who deserves privacy is not a big deal. It is an investment in a healthier internet.</p>
<p>Eventually, I realized that true data sovereignty requires hosting your own infrastructure. My personal blog used to be hosted on Netlify and protected by Cloudflare -- both centralized platforms. To fix this, I rented a VPS from IONOS. I will be honest: a cheap VPS located in the United States is not the ultimate solution for privacy, as European providers offer much stronger legal data protections (I'm fully aware of this, I even wrote an <a href="/posts/2590.html">article</a> about this in Chinese). However, working within a tight budget, this US-based IONOS server was my practical starting point.</p>
<p>On this Linux server, I host my personal websites, a Gemini capsule version of my blog (see <code>gemini://cytrogen.icu</code> using a Gemini client), and services like FreshRSS. The most critical piece of infrastructure I deployed is a self-hosted email service using Stalwart.</p>
<p>Replacing Google with a self-hosted email server gave me ultimate control. To prevent spam and track data leaks, I use an email alias system. For example, I use the alias <code>amazon@mydomain.com</code> strictly for my Amazon account. This alias automatically forwards to my main inbox. If <code>amazon@</code> ever starts receiving unrelated promotions or junk mail, I will know exactly which company sold or leaked my information.</p>
<p>No, I am not saying Amazon sold my personal information for real. At least I didn't investigate that yet.</p>
<p>It is sometimes sad to see friends looking down on these efforts, mocking me with <em>just use Google</em>. They forget that the internet originally started as a free (in the gratis and libre sense) and open commons. It was not supposed to be a series of walled gardens owned by tech giants. Although the digital world is currently occupied by these monopolies, reclaiming our operating systems, paying for ethical search engines, and hosting our own data is how we fight the momentum back. DeGoogling and DeMicrosoft is not just a technical experiment for me; it is a new start!</p>
</div></article></div></main><footer><div class="paginator"><a class="prev" href="eac7.html">PREV POST</a><a class="next" href="1572.html">NEXT POST</a></div><!-- Webmention 显示区域--><div class="webmention-section webmention-empty" data-page-url="posts/ccb5.html" data-full-url="https://blog.cytrogen.icu/posts/ccb5.html" data-syndication-url="https://m.otter.homes/@Cytrogen/116252737051536228" data-mode="static">
              <script type="application/json" class="webmention-overrides-data">{"bySourceDomain":{"v1sta.xyz":{"name":"远景幻想 V1STA","url":"https://v1sta.xyz/","photo":"https://v1sta.xyz/wp-content/uploads/2026/03/V1sta-Orb-Output512-2.png"}}}</script>
              <h3 class="webmention-title">Webmentions (<span class="webmention-count">0</span>)</h3>
              <span>No Webmentions yet</span>
              <form class="webmention-form" action="https://webmention.io/blog.cytrogen.icu/webmention" method="POST">
        <input type="hidden" name="target" value="https://blog.cytrogen.icu/posts/ccb5.html">
        <label for="webmention-source">Your article URL</label>
        <div class="webmention-form-row">
          <input id="webmention-source" type="url" name="source" placeholder="https://example.com/your-post" required>
          <button type="submit">Send Webmention</button>
        </div>
      </form>
              <div class="webmention-respond">
        <a class="webmention-respond-btn" href="https://m.otter.homes/@Cytrogen/116252737051536228" target="_blank" rel="noopener syndication">Respond on Mastodon</a>
      </div>
              <p class="webmention-hint">This site supports <a href="https://www.w3.org/TR/webmention/" target="_blank" rel="noopener">Webmention</a>. Reply to a syndicated post on the Fediverse (e.g. Mastodon) and your interaction will appear here automatically. <a href="/colophon/#webmention">Learn more</a>.</p>
            </div><!-- 邮件评论--><div class="email-comment-section email-comment-loading" data-post-id="ccb5" data-blog-domain="blog.cytrogen.icu">
      <h3 class="email-comment-title">Email Comments (<span class="email-comment-count">0</span>)</h3>
      <p class="email-comment-intro">Join the discussion via email: <a href="mailto:post-ccb5@blog.cytrogen.icu" aria-label="Send a comment via email">post-ccb5@blog.cytrogen.icu</a></p>
      <p class="email-comment-empty">No email comments yet.</p>
      <p class="email-comment-hint">Send an email to the address above to comment. Your name will be shown publicly, but your email address will not.</p>
    </div><script src="/js/email-comment.js" defer></script><div class="copyright"><p class="footer-links"><a href="../friends/index.html">BLOGROLL</a><span class="footer-separator"> ·</span><a href="../links/index.html">LINKS</a><span class="footer-separator"> ·</span><a href="../contact/index.html">CONTACT</a><span class="footer-separator"> ·</span><a href="../colophon/index.html">COLOPHON</a><span class="footer-separator"> ·</span><a href="../atom.xml">RSS</a></p><p>© 2025 - 2026 <a href="https://blog.cytrogen.icu">Cytrogen</a>, powered by <a href="https://hexo.io/" target="_blank">Hexo</a> and <a href="https://github.com/cytrogen/hexo-theme-ares" target="_blank">hexo-theme-ares</a>.</p><p><a href="https://blogscn.fun" target="_blank" rel="noopener">BLOGS·CN</a></p></div></footer></div></div><a class="back-to-top" href="#top" aria-label="返回顶部"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M3.293 9.707a1 1 0 010-1.414L9.586 2a2 2 0 012.828 0l6.293 6.293a1 1 0 01-1.414 1.414L11 3.414V17a1 1 0 11-2 0V3.414L2.707 9.707a1 1 0 01-1.414 0z"></path></svg></a><script>document.addEventListener('DOMContentLoaded', function() {
  const codeBlocks = document.querySelectorAll('figure.highlight');
  
  codeBlocks.forEach(block => {
    let caption = block.querySelector('figcaption');
    if (!caption) {
      caption = document.createElement('figcaption');
      block.insertBefore(caption, block.firstChild);
    }

    const info = document.createElement('div');
    info.className = 'info';
    
    const filename = caption.querySelector('span');
    if (filename) {
      filename.className = 'filename';
      info.appendChild(filename);
    }
    
    const lang = block.className.split(' ')[1];
    if (lang) {
      const langSpan = document.createElement('span');
      langSpan.className = 'lang-name';
      langSpan.textContent = lang;
      info.appendChild(langSpan);
    }

    const sourceLink = caption.querySelector('a');
    if (sourceLink) {
      sourceLink.className = 'source-link';
      info.appendChild(sourceLink);
    }

    const actions = document.createElement('div');
    actions.className = 'actions';

    const codeHeight = block.scrollHeight;
    const threshold = 300;

    if (codeHeight > threshold) {
      block.classList.add('folded');
      
      const toggleBtn = document.createElement('button');
      toggleBtn.textContent = 'Expand';
      toggleBtn.addEventListener('click', () => {
        block.classList.toggle('folded');
        toggleBtn.textContent = block.classList.contains('folded') ? 'Expand' : 'Fold';
      });
      actions.appendChild(toggleBtn);
    }

    const copyBtn = document.createElement('button');
    copyBtn.textContent = 'Copy';
    copyBtn.addEventListener('click', async () => {
      const codeLines = block.querySelectorAll('.code .line');
      const code = Array.from(codeLines)
        .map(line => line.textContent)
        .join('\n')
        .replace(/\n\n/g, '\n');
      
      try {
        await navigator.clipboard.writeText(code);
        copyBtn.textContent = 'Copied';
        copyBtn.classList.add('copied');
        
        setTimeout(() => {
          copyBtn.textContent = 'Copy';
          copyBtn.classList.remove('copied');
        }, 3000);
      } catch (err) {
        console.error('复制失败:', err);
        copyBtn.textContent = 'Copy failed';
        
        setTimeout(() => {
          copyBtn.textContent = 'Copy';
        }, 3000);
      }
    });
    actions.appendChild(copyBtn);

    caption.innerHTML = '';
    caption.appendChild(info);
    caption.appendChild(actions);

    const markedLines = block.getAttribute('data-marked-lines');
    if (markedLines) {
      const lines = markedLines.split(',');
      lines.forEach(range => {
        if (range.includes('-')) {
          const [start, end] = range.split('-').map(Number);
          for (let i = start; i <= end; i++) {
            const line = block.querySelector(`.line-${i}`);
            if (line) line.classList.add('marked');
          }
        } else {
          const line = block.querySelector(`.line-${range}`);
          if (line) line.classList.add('marked');
        }
      });
    }
  });
});</script><script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script"></script><script>(function() {
  document.addEventListener('DOMContentLoaded', function() {
    const themeToggle = document.querySelector('.theme-toggle');
    
    if (!themeToggle) return;
    
    const getCurrentTheme = () => {
      return document.documentElement.getAttribute('data-theme') || 'light';
    };
    
    const updateUI = (theme) => {
      const isDark = theme === 'dark';
      themeToggle.setAttribute('aria-pressed', isDark.toString());
    };
    
    const setTheme = (theme) => {
      document.documentElement.setAttribute('data-theme', theme);
      document.documentElement.style.colorScheme = theme;
      
      const pageWrapper = document.getElementById('page-wrapper');
      if (pageWrapper) {
        pageWrapper.setAttribute('data-theme', theme);
      }
      
      // Find and remove the temporary anti-flicker style tag if it exists.
      // This ensures the main stylesheet takes full control after the initial load.
      const antiFlickerStyle = document.getElementById('anti-flicker-style');
      if (antiFlickerStyle) {
        antiFlickerStyle.remove();
      }
      
      localStorage.setItem('theme', theme);
      updateUI(theme);
    };
    
    const toggleTheme = () => {
      const current = getCurrentTheme();
      const newTheme = current === 'light' ? 'dark' : 'light';
      setTheme(newTheme);
    };
    
    updateUI(getCurrentTheme());
    
    themeToggle.addEventListener('click', toggleTheme);
    
    if (window.matchMedia) {
      const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
      mediaQuery.addEventListener('change', function(e) {
        if (!localStorage.getItem('theme')) {
          const theme = e.matches ? 'dark' : 'light';
          setTheme(theme);
        }
      });
    }
  });
})();
</script><script src="../js/details-toggle.js" defer></script><script>(function() {
  document.addEventListener('DOMContentLoaded', function() {
    const backToTopBtn = document.querySelector('.back-to-top');
    
    if (!backToTopBtn) return;
    
    const toggleButtonVisibility = () => {
      const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
      const shouldShow = scrollTop > 200;
      
      if (shouldShow) {
        backToTopBtn.classList.add('is-visible');
      } else {
        backToTopBtn.classList.remove('is-visible');
      }
    };
    
    let ticking = false;
    const handleScroll = () => {
      if (!ticking) {
        requestAnimationFrame(() => {
          toggleButtonVisibility();
          ticking = false;
        });
        ticking = true;
      }
    };
    
    const scrollToTop = (event) => {
      event.preventDefault();
      window.scrollTo({
        top: 0,
        behavior: 'smooth'
      });
    };
    
    window.addEventListener('scroll', handleScroll);
    backToTopBtn.addEventListener('click', scrollToTop);
    
    toggleButtonVisibility();
  });
})();</script></body></html>