PageSourceSearch

https://zyyo.net/notes/

html zyyo.net collected 2026-09-25 21:50:23 UTC 58,354 bytes, 295 lines download raw bytes

1<!DOCTYPE html><html lang="en" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="color-scheme" content="light dark"><meta name="description" content="Zyyo's personal site — about code, design, and the little things in life. A place to write slowly."><meta name="generator" content="Astro v7.2.0">
1<script>
2      /* iOS Safari fix: disable native View Transitions to avoid a grey translucent mask.
3         Safari 18+'s document.startViewTransition() has a rendering bug — the new visual state
4         is not composited correctly, leaving a grey snapshot mask (fine on desktop Chromium, iOS only).
5         Here we disable native VT on iOS and fall back to swap: navigation is still a no-reload SPA
6         swap, but no mask is produced. */
7      (function () {
8        var isIOS =
9          /iP(hone|ad|od)/.test(navigator.userAgent) ||
10          (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
11        if (!isIOS) return;
12        try {
13          // must be disabled before Astro's router module runs, so iOS uses the non-native SPA fallback
14          document.startViewTransition = undefined;
15        } catch (e) {}
16        // once the head is parsed (fallback meta injected by Astro), set the fallback mode to swap (clean, no animation)
17        var setSwap = function () {
18          var meta = document.querySelector('meta[name="astro-view-transitions-fallback"]');
19          if (meta) meta.setAttribute('content', 'swap');
20        };
21        if (document.readyState === 'loading') {
22          document.addEventListener('DOMContentLoaded', setSwap);
23        } else {
24          setSwap();
25        }
26      })();
27    </script>
27<meta name="astro-view-transitions-enabled" content="true"><meta name="astro-view-transitions-fallback" content="animate">
27<script type="module" src="/_astro/ClientRouter.astro_astro_type_script_index_0_lang.CqTLCueb.js"></script>
27<title>Notes · Zyyo</title><link rel="icon" type="image/svg+xml" href="/favicon.ico"><link rel="alternate" type="application/rss+xml" title="Zyyo" href="/rss.xml">
27<script>
28      (function () {
29        function applyTheme() {
30          try {
31            const t = localStorage.getItem('zyyo-theme');
32            if (t === 'light')
33              document.documentElement.setAttribute('data-theme', 'light');
34            else
35              document.documentElement.setAttribute('data-theme', 'dark');
36          } catch (e) {}
37        }
38        applyTheme();
39        document.addEventListener('astro:after-swap', applyTheme);
40      })();
41    </script>
41<link rel="stylesheet" href="/_astro/Base.hfAtEq7k.css">
41<script type="module" src="/_astro/page.9pYLC08D.js"></script>
41</head><body class="flex min-h-screen flex-col bg-[#f7f8fa] font-sans text-slate-800 antialiased dark:bg-[#111111] dark:text-neutral-100"><div id="glow-layer" class="glow-layer" aria-hidden="true" data-astro-transition-persist="glow-layer"></div><div id="site-loader" class="fixed inset-0 z-[100] flex items-center justify-center bg-[#111111]" aria-hidden="true"><div class="relative max-w-fit text-[50px] font-semibold italic text-[#f2fff0] transition-colors duration-1000 ease-[cubic-bezier(0.175,0.885,0.32,1.275)] hover:text-[#fcffdf] before:absolute before:left-0 before:top-0 before:z-[1] before:h-[5px] before:w-full before:rounded-[4px] before:bg-[#ff8282] before:opacity-90 before:content-[''] before:animate-[scan_2s_infinite] after:absolute after:left-0 after:top-0 after:z-0 after:h-[6px] after:w-full after:rounded-[4px] after:bg-[#ff828291] after:blur-[10px] after:content-[''] after:animate-[scan_2s_infinite]"><span class="inline-block">Z</span><span class="inline-block">y</span><span class="inline-block">y</span><span class="inline-block">o</span></div></div><div id="nav-progress" class="nav-progress" aria-hidden="true" data-astro-transition-persist="nav-progress"></div>
41<script data-astro-rerun>
42      /* no-reload navigation feedback: tapping an internal link shows a top progress bar, visible ≥600ms
43         data-astro-rerun: forces this script to rerun after a no-reload swap, rebinding nodes;
44         nodes with data-astro-transition-persist keep the same DOM across navigation, so .show stays intact. */
45      (function () {
46        var bar = document.getElementById('nav-progress');
47        if (!bar) return;
48        // on rerun, if the bar is still visible (the previous nav's 600ms window isn't over), keep timing from now
49        var lastShow = bar.classList.contains('show') ? Date.now() : 0;
50        var hideTimer = 0;
51        var stuckTimer = 0;
52        function show() {
53          lastShow = Date.now();
54          bar.classList.add('show');
55          window.clearTimeout(stuckTimer);
56          stuckTimer = window.setTimeout(hide, 15000);
57        }
58        function hide() {
59          window.clearTimeout(hideTimer);
60          var wait = 600 - (Date.now() - lastShow);
61          if (wait > 0) {
62            hideTimer = window.setTimeout(hide, wait);
63            return;
64          }
65          bar.classList.remove('show');
66        }
67        // show as soon as an internal same-origin link is clicked
68        document.addEventListener('click', function (e) {
69          var a = e.target && e.target.closest ? e.target.closest('a[href]') : null;
70          if (!a || a.target === '_blank' || a.hasAttribute('download')) return;
71          var url;
72          try {
73            url = new URL(a.href, location.href);
74          } catch {
75            return;
76          }
77          if (url.origin !== location.origin) return;
78          show();
79        });
80        // hide when the swap completes or the page finishes loading (fallback: max 15s so a failed network can't stick)
81        document.addEventListener('astro:after-swap', hide);
82        document.addEventListener('astro:page-load', hide);
83        window.setTimeout(function () {
84          if (bar.classList.contains('show')) hide();
85        }, 15000);
86      })();
87    </script>
87<style>astro-island,astro-slot,astro-static-slot{display:contents}</style>
87<script>(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).load=e;window.dispatchEvent(new Event("astro:load"));})();</script>
87<script>(()=>{var g=Object.defineProperty;var w=(a,s,c)=>s in a?g(a,s,{enumerable:!0,configurable:!0,writable:!0,value:c}):a[s]=c;var l=(a,s,c)=>w(a,typeof s!="symbol"?s+"":s,c);var E=new Set(["__proto__","constructor","prototype"]);{let a={0:t=>y(t),1:t=>c(t),2:t=>new RegExp(t),3:t=>new Date(t),4:t=>new Map(c(t)),5:t=>new Set(c(t)),6:t=>BigInt(t),7:t=>new URL(t),8:t=>new Uint8Array(t),9:t=>new Uint16Array(t),10:t=>new Uint32Array(t),11:t=>Number.POSITIVE_INFINITY*t},s=t=>{let[p,e]=t;return p in a?a[p](e):void 0},c=t=>t.map(s),y=t=>typeof t!="object"||t===null?t:Object.fromEntries(Object.entries(t).map(([p,e])=>[p,s(e)]));class f extends HTMLElement{constructor(){super(...arguments);l(this,"Component");l(this,"hydrator");l(this,"hydrate",async()=>{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest("astro-island[ssr]");if(e){e.addEventListener("astro:hydrate",this.hydrate,{once:!0});return}let r=this.querySelectorAll("astro-slot"),n={},d=this.querySelectorAll("template[data-astro-template]");for(let o of d){let i=o.closest(this.tagName);i!=null&&i.isSameNode(this)&&(n[o.getAttribute("data-astro-template")||"default"]=o.innerHTML,o.remove())}for(let o of r){let i=o.closest(this.tagName);i!=null&&i.isSameNode(this)&&(n[o.getAttribute("name")||"default"]=o.innerHTML)}let u;try{u=this.hasAttribute("props")?y(JSON.parse(this.getAttribute("props"))):{}}catch(o){let i=this.getAttribute("component-url")||"<unknown>",v=this.getAttribute("component-export");throw v&&(i+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${i}`,this.getAttribute("props"),o),o}let h;await this.hydrator(this)(this.Component,u,n,{client:this.getAttribute("client")}),this.removeAttribute("ssr"),this.dispatchEvent(new CustomEvent("astro:hydrate"))});l(this,"unmount",()=>{this.isConnected||this.dispatchEvent(new CustomEvent("astro:unmount"))})}disconnectedCallback(){document.removeEventListener("astro:after-swap",this.unmount),document.addEventListener("astro:after-swap",this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute("await-children")||document.readyState==="interactive"||document.readyState==="complete")this.childrenConnectedCallback();else{let e=()=>{document.removeEventListener("DOMContentLoaded",e),r.disconnect(),this.childrenConnectedCallback()},r=new MutationObserver(()=>{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&&this.lastChild.nodeValue==="astro:end"&&(this.lastChild.remove(),e())});r.observe(this,{childList:!0}),document.addEventListener("DOMContentLoaded",e)}}async childrenConnectedCallback(){let e=this.getAttribute("before-hydration-url");e&&await import(e),this.start()}getRetryImportUrl(e){let r=new URL(e,document.baseURI);return r.searchParams.set("astro-retry",Date.now().toString()),r.toString()}async importWithRetry(e){try{return await import(e)}catch(r){return await new Promise(n=>setTimeout(n,1e3)),import(this.getRetryImportUrl(e))}}handleHydrationError(e){let r=this.getAttribute("component-url"),n=new CustomEvent("astro:hydration-error",{cancelable:!0,bubbles:!0,composed:!0,detail:{error:e,componentUrl:r}});this.dispatchEvent(n)&&console.error(`[astro-island] Error hydrating ${r}`,e)}async start(){let e=JSON.parse(this.getAttribute("opts")),r=this.getAttribute("client");if(Astro[r]===void 0){window.addEventListener(`astro:${r}`,()=>this.start(),{once:!0});return}try{await Astro[r](async()=>{let n=this.getAttribute("renderer-url");try{let[d,{default:u}]=await Promise.all([this.importWithRetry(this.getAttribute("component-url")),n?this.importWithRetry(n):Promise.resolve({default:()=>()=>{}})]),h=this.getAttribute("component-export")||"default";if(h.includes(".")){this.Component=d;for(let m of h.split(".")){if(E.has(m)||!this.Component||typeof this.Component!="object"&&typeof this.Component!="function"||!Object.hasOwn(this.Component,m))throw new Error(`Invalid component export path: ${h}`);this.Component=this.Component[m]}}else{if(E.has(h))throw new Error(`Invalid component export path: ${h}`);this.Component=d[h]}return this.hydrator=u,this.hydrate}catch(d){return this.handleHydrationError(d),()=>{}}},e,this)}catch(n){this.handleHydrationError(n)}}attributeChangedCallback(){this.hydrate()}}l(f,"observedAttributes",["props"]),customElements.get("astro-island")||customElements.define("astro-island",f)}})();</script>
87<astro-island uid="Z18Bu42" prefix="r6" component-url="/_astro/Topbar.oSlAfhSt.js" component-export="default" renderer-url="/_astro/client.D9VvwaQT.js" props="{&quot;items&quot;:[1,[[0,{&quot;label&quot;:[0,&quot;Home&quot;],&quot;href&quot;:[0,&quot;/&quot;],&quot;icon&quot;:[0,&quot;home&quot;]}],[0,{&quot;label&quot;:[0,&quot;Blog&quot;],&quot;href&quot;:[0,&quot;/blog&quot;],&quot;icon&quot;:[0,&quot;book&quot;]}],[0,{&quot;label&quot;:[0,&quot;Photos&quot;],&quot;href&quot;:[0,&quot;/photos&quot;],&quot;icon&quot;:[0,&quot;camera&quot;]}],[0,{&quot;label&quot;:[0,&quot;Notes&quot;],&quot;href&quot;:[0,&quot;/notes&quot;],&quot;icon&quot;:[0,&quot;pen&quot;]}],[0,{&quot;label&quot;:[0,&quot;Links&quot;],&quot;href&quot;:[0,&quot;/links&quot;],&quot;icon&quot;:[0,&quot;link&quot;]}],[0,{&quot;label&quot;:[0,&quot;About&quot;],&quot;href&quot;:[0,&quot;/about&quot;],&quot;icon&quot;:[0,&quot;user&quot;]}]]],&quot;activeHref&quot;:[0,&quot;/notes&quot;]}" ssr client="load" opts="{&quot;name&quot;:&quot;Topbar&quot;,&quot;value&quot;:true}" await-children><header class="mx-auto w-full max-w-[60rem] px-5 lg:px-6 sticky top-0 z-50 flex items-center justify-between gap-3 py-5 pb-3.5 md:grid md:grid-cols-[1fr_auto_1fr]"><div aria-hidden="true" class="hidden md:block"></div><div class="card relative flex h-10 items-center gap-2 rounded-full px-2"><a href="/" aria-label="Home" class="grid size-6 place-items-center overflow-hidden rounded-full"><img src="/favicon.ico" alt="" class="size-6 rounded-full object-cover"/></a><span aria-hidden="true" class="h-4 w-px bg-(--card-border) max-md:hidden"></span><nav aria-label="Main navigation" class="relative hidden h-9 items-center md:flex"><span class="relative flex h-9 items-center gap-1.5"><span aria-hidden="true" class="absolute left-0 top-0.5 z-0 h-8 rounded-full bg-slate-100 dark:bg-neutral-700 "></span><a href="/blog" class="relative z-10 flex items-center whitespace-nowrap rounded-full px-2.5 py-2 leading-none transition-colors duration-200 text-slate-500 dark:text-neutral-400 hover:text-slate-800 dark:hover:text-neutral-100" title="Blog" aria-label="Blog"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-[17px]"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z"></path></svg></a><a href="/photos" class="relative z-10 flex items-center whitespace-nowrap rounded-full px-2.5 py-2 leading-none transition-colors duration-200 text-slate-500 dark:text-neutral-400 hover:text-slate-800 dark:hover:text-neutral-100" title="Photos" aria-label="Photos"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-[17px]"><path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3Z"></path><circle cx="12" cy="13" r="3"></circle></svg></a><a href="/notes" aria-current="true" class="relative z-10 flex items-center whitespace-nowrap rounded-full px-2.5 py-2 leading-none transition-colors duration-200 active text-slate-900 dark:text-white hover:text-slate-800 dark:hover:text-neutral-100" title="Notes" aria-label="Notes"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-[17px]"><path d="M12 20h9"></path><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"></path></svg></a><a href="/links" class="relative z-10 flex items-center whitespace-nowrap rounded-full px-2.5 py-2 leading-none transition-colors duration-200 text-slate-500 dark:text-neutral-400 hover:text-slate-800 dark:hover:text-neutral-100" title="Links" aria-label="Links"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-[17px]"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a><a href="/about" class="relative z-10 flex items-center whitespace-nowrap rounded-full px-2.5 py-2 leading-none transition-colors duration-200 text-slate-500 dark:text-neutral-400 hover:text-slate-800 dark:hover:text-neutral-100" title="About" aria-label="About"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-[17px]"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg></a></span></nav></div><div class="flex shrink-0 items-center gap-2 md:justify-self-end"><div class="relative"></div><button type="button" aria-label="Open menu" aria-expanded="false" class="card grid size-9 place-items-center rounded-full text-slate-500 hover:bg-black/5 hover:text-slate-700 dark:text-neutral-300 dark:hover:bg-white/10 dark:hover:text-white md:hidden"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-4"><path d="M4 7h16M4 12h16M4 17h16"></path></svg></button></div></header><!--astro:end--></astro-island><div class="mx-auto w-full max-w-[57rem] px-5 pb-16 lg:px-6"><header class="pt-10"><h1 class="font-serif text-3xl font-bold">Notes</h1><p class="mt-2 text-sm text-slate-500 dark:text-neutral-400">Random thoughts and daily life · 23 posts</p></header><div class="mt-8">
87<script>(()=>{var a=(s,i,o)=>{let r=async()=>{await(await s())()},t=typeof i.value=="object"?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=>{for(let l of e)if(l.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=a;window.dispatchEvent(new Event("astro:visible"));})();</script>
87<astro-island uid="Z1Tefir" prefix="r5" component-url="/_astro/NotesFeed.qHH0y7Jo.js" component-export="default" renderer-url="/_astro/client.D9VvwaQT.js" props="{&quot;items&quot;:[1,[[0,{&quot;id&quot;:[0,&quot;2026-9-17 部署到cf网站变得好慢-0&quot;],&quot;date&quot;:[0,&quot;2026-9-17 部署到cf网站变得好慢&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;哈哈,也正好拦住了访问不坚定的人&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-09-14-1&quot;],&quot;date&quot;:[0,&quot;2026-09-14&quot;],&quot;title&quot;:[0,&quot;新的博客上线到zyyo.net&quot;],&quot;text&quot;:[0,&quot;内部开发了很久终于上线了&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-09-11-2&quot;],&quot;date&quot;:[0,&quot;2026-09-11&quot;],&quot;title&quot;:[0,&quot;播放器水合闪烁&quot;],&quot;text&quot;:[0,&quot;彻底修好了进首页“卡片飞一下”的问题:不是 CSS transition 的锅,是原生 View Transition 抓了重排前的快照。把 lg / order / 宽度测量都挪到 useLayoutEffect 里在绘制前跑完,浏览器再也抓不到中间态。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-09-10-3&quot;],&quot;date&quot;:[0,&quot;2026-09-10&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;音乐抽屉在手机上改成了底部弹层,桌面还是右侧悬浮卡。歌词能滚动了,非当前行做虚化,鼠标一悬或一滚就清晰,松手即回——不糊脸。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-09-08-4&quot;],&quot;date&quot;:[0,&quot;2026-09-08&quot;],&quot;title&quot;:[0,&quot;清晨&quot;],&quot;text&quot;:[0,&quot;五点半出门,桂花整排整排地香。跑到桥上停下来喘气,天边是那种将亮未亮的青灰色,很安静,只有江风和水声。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-09-05-5&quot;],&quot;date&quot;:[0,&quot;2026-09-05&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;把首页那张地图从 d3 矢量一路换成 MapLibre,最后发现公共 OSM 瓦片在自己网络里根本连不上,改高德的源才稳。折腾一整天,值。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-09-02-6&quot;],&quot;date&quot;:[0,&quot;2026-09-02&quot;],&quot;title&quot;:[0,&quot;换个键盘手感&quot;],&quot;text&quot;:[0,&quot;试了 68 配列,方向键不用低头找,节奏舒服很多。外壳选了雾蓝,低调耐看,比之前那个花哨的 RGB 灯效适合坐一天。&quot;],&quot;image&quot;:[0,&quot;/favicon.ico&quot;],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-08-30-7&quot;],&quot;date&quot;:[0,&quot;2026-08-30&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;首页卡片的拖拽终于不“粘鼠标”了。之前松手后幽灵卡片还赖在光标上,是因为个别孤岛自己捕获了指针、React 的 pointerup 收不到。补了 window 级兜底 + 统一 finishDrag 收尾,稳了。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-08-28-8&quot;],&quot;date&quot;:[0,&quot;2026-08-28&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;今晚的风很软,把阳台的衣服吹得鼓起来,像有人在里面跳舞。收衣服的时候发现被太阳晒得温热,抱着上楼,有种很小的幸福感。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-08-25-9&quot;],&quot;date&quot;:[0,&quot;2026-08-25&quot;],&quot;title&quot;:[0,&quot;卡片忽大忽小&quot;],&quot;text&quot;:[0,&quot;拖拽时卡片会莫名改变大小,根因是布局用 CSS 变量算的、和 React 内联 style 打架。改成每张卡固定 span、绝对定位只动 transform,彻底告别“变形”。卡片尺寸恒定,才谈得上高级感。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-08-22-10&quot;],&quot;date&quot;:[0,&quot;2026-08-22&quot;],&quot;title&quot;:[0,&quot;
87读《设计的设计》&quot;],&quot;text&quot;:[0,&quot;原研哉讲“emptiness”不是空,而是留出容纳的余地。做界面时总想塞满,其实克制更难。首页背景后来被我调成近乎纯白,眼睛一下子松了。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0,{&quot;text&quot;:[0,&quot;原书介绍&quot;],&quot;href&quot;:[0,&quot;https://example.com&quot;]}]}],[0,{&quot;id&quot;:[0,&quot;2026-08-19-11&quot;],&quot;date&quot;:[0,&quot;2026-08-19&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;导航的 logo 和黑夜开关从药丸里拆出来,做成两颗圆钮一左一右,像灵动岛。favicon 那个圆在移动端要求填满无留白、桌面留白,来回调了好几轮才顺眼。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-08-16-12&quot;],&quot;date&quot;:[0,&quot;2026-08-16&quot;],&quot;title&quot;:[0,&quot;加载动画&quot;],&quot;text&quot;:[0,&quot;那个“Zyyo”loader 之前字母右边会一闪一闪缺角,其实是 clip-path 把每个字母上下裁没了又放回来。去掉裁切只留一条红色扫描线扫过,干净了。字体不再是“一抽一抽”地加载。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-08-12-13&quot;],&quot;date&quot;:[0,&quot;2026-08-12&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;给音乐播放器接了公共镜像的网易云歌单,能整首播放还有实时歌词。缺点是首次点击“随机播放”常被浏览器自动播放策略拦一下,得再点一次——这属于平台限制,认了。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-08-09-14&quot;],&quot;date&quot;:[0,&quot;2026-08-09&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;把三个社交/按钮做成“左上细亮边、右下粗暗边”的伪 3D,按下去会真的塌一截。白天又嫌它扎眼,调成几乎融进背景的灰,悬停才亮一点。审美这件事,来回改七八次也不为过。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-08-05-15&quot;],&quot;date&quot;:[0,&quot;2026-08-05&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;修了一下午的 bug,最后发现是少写了一个 await。气笑了,顺手把咖啡续上,重新泡了一杯。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-08-01-16&quot;],&quot;date&quot;:[0,&quot;2026-08-01&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;给博客加了“随记”这个页面,像朋友圈一样零散记点东西:跑过的路、看过的书、修过的 bug。以后不必每件事都攒成一篇长文,一句话也值得留下。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-07-27-17&quot;],&quot;date&quot;:[0,&quot;2026-07-27&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;夜间模式的卡片不该有投影,白天才留一点点柔影。就这么一个细节,改了主题变量后整站的层次一下就清楚了。深色下再给卡片配一张模糊图片做背景,居然意外好看。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-07-22-18&quot;],&quot;date&quot;:[0,&quot;2026-07-22&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;把背景的光晕从彩色调成近乎白色,眼睛终于不累了。少即是多,这句话说了很多遍,真正信是这段时间的事。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-07-18-19&quot;],&quot;date&quot;:[0,&quot;2026-07-18&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;第一次给首页卡片做“顺序 + 首因适配”的自由布局:拖动时靠 transform 平滑让位,落点写进 localStorage,刷新还在原位。右上角默认留给音乐和时钟,是半夜越想越满意的排布。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-07-09-20&quot;],&quot;date&quot;:[0,&quot;2026-07-09&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;把搜索框、结果卡片、导航圆角的圆角统一到一个变量里,全站终于“看起来是一家人”。有些优雅不是加出来的,是把不一致去掉。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-06-28-21&quot;],&quot;date&quot;:[0,&quot;2026-06-28&quot;],&quot;title&quot;:[0,&quot;一个小执念&quot;],&quot;text&quot;:[0,&quot;卡片 hover 时那一圈蓝边,从 Tailwind 类挪到统一组件里,只留“细描边 + 动效描圈”这一种语言。克制一点,整站的高级感是省出来的。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}],[0,{&quot;id&quot;:[0,&quot;2026-06-20-22&quot;],&quot;date&quot;:[0,&quot;2026-06-20&quot;],&quot;title&quot;:[0],&quot;text&quot;:[0,&quot;删了 dnd-kit 那一套,回到自研的首因适配。别人库再强,ä¹
87Ÿä¸å¦‚自己把每条边界都想清楚。代码审查那天顺手清了死代码、删了孤儿组件、把没用的配置字段一并摘掉,轻了不止一点。&quot;],&quot;image&quot;:[0],&quot;link&quot;:[0]}]]]}" ssr client="visible" opts="{&quot;name&quot;:&quot;NotesFeed&quot;,&quot;value&quot;:true}" await-children><div><div class="z-40 mb-5 flex flex-wrap items-center justify-between gap-3"><div class="flex rounded-full bg-black/[0.04] p-0.5 dark:bg-white/[0.06]"><button type="button" class="rounded-full px-4 py-1.5 text-xs font-medium transition-colors bg-white text-slate-900 shadow-sm dark:bg-neutral-700 dark:text-white">By Time</button><button type="button" class="rounded-full px-4 py-1.5 text-xs font-medium transition-colors text-slate-500 hover:text-slate-700 dark:text-neutral-400 dark:hover:text-neutral-200">Random</button></div><div class="card flex items-center gap-2 rounded-full px-3.5 py-2"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="size-4 text-slate-400 dark:text-neutral-500"><circle cx="11" cy="11" r="7"></circle><path d="m20 20-3.5-3.5"></path></svg><input placeholder="Search notes…" class="w-40 min-w-0 bg-transparent text-sm text-slate-700 outline-none placeholder:text-slate-400 dark:text-neutral-100 dark:placeholder:text-neutral-500" value=""/></div></div><div class="gap-4 columns-1 md:columns-3 [&amp;&gt;*]:mb-4"><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">哈哈,也正好拦住了访问不坚定的人</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">2026-9-17 部署到cf网站变得好慢</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><h3 class="mb-1 text-[15px] font-semibold leading-snug">新的博客上线到zyyo.net</h3><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">内部开发了很久终于上线了</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">09-14</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><h3 class="mb-1 text-[15px] font-semibold leading-snug">播放器水合闪烁</h3><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 line-clamp-[5]">彻底修好了进首页“卡片飞一下”的问题:不是 CSS transition 的锅,是原生 View Transition 抓了重排前的快照。把 lg / order / 宽度测量都挪到 useLayoutEffect 里在绘制前跑完,浏览器再也抓不到中间态。</p><button type="button" class="mt-1.5 text-xs font-medium text-slate-400 hover:text-slate-600 dark:text-neutral-500 dark:hover:text-neutral-300">Expand</button><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">09-11</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">音乐抽屉在手机上改成了底部弹层,桌面还是右侧悬浮卡。歌词能滚动了,非当前行做虚化,鼠标一悬或一滚就清晰,松手即回——不糊脸。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">09-10</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><h3 class="mb-1 text-[15px] font-semibold leading-snug">清晨</h3><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dar
87k:text-neutral-300 ">五点半出门,桂花整排整排地香。跑到桥上停下来喘气,天边是那种将亮未亮的青灰色,很安静,只有江风和水声。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">09-08</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">把首页那张地图从 d3 矢量一路换成 MapLibre,最后发现公共 OSM 瓦片在自己网络里根本连不上,改高德的源才稳。折腾一整天,值。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">09-05</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><img src="/favicon.ico" alt="" loading="lazy" class="mb-3 w-full rounded-xl object-cover"/><h3 class="mb-1 text-[15px] font-semibold leading-snug">换个键盘手感</h3><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">试了 68 配列,方向键不用低头找,节奏舒服很多。外壳选了雾蓝,低调耐看,比之前那个花哨的 RGB 灯效适合坐一天。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">09-02</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 line-clamp-[5]">首页卡片的拖拽终于不“粘鼠标”了。之前松手后幽灵卡片还赖在光标上,是因为个别孤岛自己捕获了指针、React 的 pointerup 收不到。补了 window 级兜底 + 统一 finishDrag 收尾,稳了。</p><button type="button" class="mt-1.5 text-xs font-medium text-slate-400 hover:text-slate-600 dark:text-neutral-500 dark:hover:text-neutral-300">Expand</button><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">08-30</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">今晚的风很软,把阳台的衣服吹得鼓起来,像有人在里面跳舞。收衣服的时候发现被太阳晒得温热,抱着上楼,有种很小的幸福感。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">08-28</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><h3 class="mb-1 text-[15px] font-semibold leading-snug">卡片忽大忽小</h3><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 line-clamp-[5]">拖拽时卡片会莫名改变大小,根因是布局用 CSS 变量算的、和 React 内联 style 打架。改成每张卡固定 span、绝对定位只动 transform,彻底告别“变形”。卡片尺寸恒定,才谈得上高级感。</p><button type="button" class="mt-1.5 text-xs font-medium text-slate-400 hover:text-slate-600 dark:text-neutral-500 dark:hover:text-neutral-300">Expand</button><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">08-25</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><h3 class="mb-1 text-[15px] font-semibold leading-snug">读《设计的设计》</h3><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dar
87k:text-neutral-300 ">原研哉讲“emptiness”不是空,而是留出容纳的余地。做界面时总想塞满,其实克制更难。首页背景后来被我调成近乎纯白,眼睛一下子松了。</p><a href="https://example.com" target="_blank" rel="noopener noreferrer" class="mt-2 inline-flex items-center gap-1 text-xs font-medium text-blue-500 hover:underline dark:text-blue-400">原书介绍<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-3"><path d="M7 17 17 7M9 7h8v8"></path></svg></a><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">08-22</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">导航的 logo 和黑夜开关从药丸里拆出来,做成两颗圆钮一左一右,像灵动岛。favicon 那个圆在移动端要求填满无留白、桌面留白,来回调了好几轮才顺眼。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">08-19</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><h3 class="mb-1 text-[15px] font-semibold leading-snug">加载动画</h3><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 line-clamp-[5]">那个“Zyyo”loader 之前字母右边会一闪一闪缺角,其实是 clip-path 把每个字母上下裁没了又放回来。去掉裁切只留一条红色扫描线扫过,干净了。字体不再是“一抽一抽”地加载。</p><button type="button" class="mt-1.5 text-xs font-medium text-slate-400 hover:text-slate-600 dark:text-neutral-500 dark:hover:text-neutral-300">Expand</button><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">08-16</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">给音乐播放器接了公共镜像的网易云歌单,能整首播放还有实时歌词。缺点是首次点击“随机播放”常被浏览器自动播放策略拦一下,得再点一次——这属于平台限制,认了。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">08-12</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">把三个社交/按钮做成“左上细亮边、右下粗暗边”的伪 3D,按下去会真的塌一截。白天又嫌它扎眼,调成几乎融进背景的灰,悬停才亮一点。审美这件事,来回改七八次也不为过。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">08-09</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">修了一下午的 bug,最后发现是少写了一个 await。气笑了,顺手把咖啡续上,重新泡了一杯。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">08-05</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dar
87k:text-neutral-300 ">给博客加了“随记”这个页面,像朋友圈一样零散记点东西:跑过的路、看过的书、修过的 bug。以后不必每件事都攒成一篇长文,一句话也值得留下。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">08-01</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">夜间模式的卡片不该有投影,白天才留一点点柔影。就这么一个细节,改了主题变量后整站的层次一下就清楚了。深色下再给卡片配一张模糊图片做背景,居然意外好看。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">07-27</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">把背景的光晕从彩色调成近乎白色,眼睛终于不累了。少即是多,这句话说了很多遍,真正信是这段时间的事。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">07-22</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 line-clamp-[5]">第一次给首页卡片做“顺序 + 首因适配”的自由布局:拖动时靠 transform 平滑让位,落点写进 localStorage,刷新还在原位。右上角默认留给音乐和时钟,是半夜越想越满意的排布。</p><button type="button" class="mt-1.5 text-xs font-medium text-slate-400 hover:text-slate-600 dark:text-neutral-500 dark:hover:text-neutral-300">Expand</button><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">07-18</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">把搜索框、结果卡片、导航圆角的圆角统一到一个变量里,全站终于“看起来是一家人”。有些优雅不是加出来的,是把不一致去掉。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">07-09</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><h3 class="mb-1 text-[15px] font-semibold leading-snug">一个小执念</h3><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">卡片 hover 时那一圈蓝边,从 Tailwind 类挪到统一组件里,只留“细描边 + 动效描圈”这一种语言。克制一点,整站的高级感是省出来的。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">06-28</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article><article class="card break-inside-avoid p-4"><p class="whitespace-pre-wrap text-sm leading-relaxed text-slate-600 dark:text-neutral-300 ">删了 dnd-kit 那一套,回到自研的首因适配。别人库再强,也不如自己把每条边界都想清楚。代码审查那天顺手清了死代码、删了孤儿组件、把没用的配置字段一并摘掉,轻了不止一点。</p><div class="mt-3 flex items-center justify-between gap-2 border-t border-black/[0.05] pt-2.5 dark:border-white/10"><span class="font-mono text-xs text-slate-400 dark:text-neutral-500">06-20</span><span class="text-xs text-slate-300 dark:text-neutral-600">Zyyo</span></div></article></div></div><!--astro:end--></astro-island></div></div><footer class="mx-auto w-full max-w-[57rem] px-5 lg:px-6 mt-auto flex flex-wrap items-center justify-between gap-4 pb-12 pt-7 text-xs text-slate-500 dark:border-neutral-700 dark:text-neutral-400"><span>© <!-- -->2026<!-- --> <!-- -->Zyyo<!-- --> · <!-- -->Built with Astro</span><a href="/rss.xml" aria-label="Subscribe to RSS" class="ml-auto text-slate-500 transition-colors hover:text-blue-500 dark:text-neutral-400 dark:hover:text-blue-400">RSS</a></footer><astro-island uid="Z1WR8Fz" prefix="r9" component-url="/_astro/Lightbox.Bi1J0a9z.js" component-export="default" renderer-url="/_astro/client.D9VvwaQT.js" props="{}" ssr client="load" opts="{&quot;name&quot;:&quot;Lightbox&quot;,&quot;value&quot;:true}"></astro-island>
87<script>(()=>{var l=(n,t)=>{let i=async()=>{await(await n())()},e=typeof t.value=="object"?t.value:void 0,s={timeout:e==null?void 0:e.timeout};"requestIdleCallback"in window?window.requestIdleCallback(i,s):setTimeout(i,s.timeout||200)};(self.Astro||(self.Astro={})).idle=l;window.dispatchEvent(new Event("astro:idle"));})();</script>
87<astro-island uid="1wYyfO" prefix="r8" component-url="/_astro/MusicPanel.f9s7PcRl.js" component-export="default" renderer-url="/_astro/client.D9VvwaQT.js" props="{}" ssr client="idle" opts="{&quot;name&quot;:&quot;MusicPanel&quot;,&quot;value&quot;:true}" await-children><aside aria-label="Music player" class="music-panel card fixed inset-x-2 bottom-2 z-[70] flex max-h-[86dvh] flex-col overflow-hidden transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] md:inset-x-auto md:inset-y-0 md:bottom-0 md:right-4 md:my-auto md:max-h-none md:w-[min(20rem,calc(100vw-2rem))] translate-y-[calc(100%+0.5rem)] md:translate-y-0 md:translate-x-[calc(100%+1.5rem)]"><div class="flex shrink-0 items-center justify-between px-5 pb-2 pt-4"><span class="text-[11px] font-medium uppercase tracking-[0.14em] text-slate-400 dark:text-neutral-500">Now Playing</span><button type="button" aria-label="Close" class="grid size-9 place-items-center rounded-full text-slate-500 transition-colors hover:bg-black/[0.05] active:bg-black/10 dark:text-neutral-400 dark:hover:bg-white/10 dark:active:bg-white/[0.15] -mr-1.5 -mt-1 shrink-0"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" class="size-4"><path d="M6 6l12 12M18 6 6 18"></path></svg></button></div><div class="shrink-0 px-5"><div class="relative mx-auto aspect-square w-full max-w-[8rem] overflow-hidden rounded-2xl bg-slate-100 shadow-[0_12px_36px_-14px_rgba(0,0,0,0.45)] dark:bg-neutral-800"><div class="grid h-full w-full place-items-center text-slate-300 dark:text-neutral-600"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.1" class="size-10"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg></div></div></div><div class="shrink-0 px-5 pt-3 text-center"><div class="truncate text-[15px] font-semibold tracking-tight">Not Playing</div><div class="mt-0.5 truncate text-xs text-slate-500 dark:text-neutral-400">Choose a song below</div></div><div class="hidden shrink-0 px-2 pt-3 md:block"><div class="h-28 overflow-y-auto overscroll-contain px-4 text-center transition-[mask-image] duration-300 [mask-image:linear-gradient(to_bottom,transparent,#000_22%,#000_78%,transparent)]"><p class="py-8 text-xs text-slate-400 dark:text-neutral-500"></p></div></div><div class="shrink-0 px-5 pt-1"><input type="range" min="0" max="0" step="0.1" aria-label="Seek" class="music-range h-1 w-full cursor-pointer appearance-none rounded-full" style="background:linear-gradient(90deg, currentColor 0%, rgba(120,120,128,0.28) 0%)" value="0"/><div class="mt-1.5 flex justify-between text-[11px] tabular-nums text-slate-400 dark:text-neutral-500"><span>0:00</span><span>-<!-- -->0:00</span></div></div><div class="flex shrink-0 items-center justify-center gap-1 px-5 pt-1"><button type="button" aria-label="Shuffle" class="grid size-9 place-items-center rounded-full text-slate-500 transition-colors hover:bg-black/[0.05] active:bg-black/10 dark:text-neutral-400 dark:hover:bg-white/10 dark:active:bg-white/[0.15] "><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" class="size-[18px]"><path d="M16 3h5v5M4 20 21 3M21 16v5h-5M15 15l6 6M4 4l5 5"></path></svg></button><button type="button" aria-label="Previous" class="grid size-9 place-items-center rounded-full text-slate-500 transition-colors hover:bg-black/[0.05] active:bg-black/10 dark:text-neutral-400 dark:hover:bg-white/10 dark:active:bg-white/[0.15]"><svg viewBox="0 0 24 24" fill="currentColor" class="size-[18px]"><path d="M7 6v12M20 6v12l-10-6 10-6Z"></path></svg></button><button type="button" aria-label="Play" class="mx-1 grid size-12 place-items-center rounded-full bg-slate-900 text-white shadow-[0_8px_20px_-6px_rgba(0,0,0,0.4)] transition-transform hover:scale-105 active:scale-95 dark:bg-white dark:text-neutral-900"><svg viewBox="0 0 24 24" fill="currentColor" class="size-5 translate-x-[1px]"><path d="M7 4.5v15l13-7.5-13-7.5Z"></path></svg></button><button type="button" aria-label="Next" class="grid size-9 place-items-center rounded-full text-slate-500 transition-colors hover:bg-black/[0.05] active:bg-black/10 dark:text-neutral-400 dark:hover:bg-white/10 dark:active:bg-white/[0.15]"><svg viewBox="0 0 24 24" fill="currentColor" class="size-[18px]"><path d="M17 6v12M4 6v12l10-6L4 6Z"></path></svg></button><button type="button" aria-label="Repeat" class="grid size-9 place-items-center rounded-full text-slate-500 transition-colors hover:bg-black/[0.05] active:bg-black/10 dark:text-neutral-400 dark:hover:bg-white/10 dark:active:bg-white/[0.15] relative "><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" class="size-[18px]"><path d="M17 2l4 4-4 4M3 11V9a4 4 0 0 1 4-4h14M7 22l-4-4 4-4M21 13v2a4 4 0 0 1-4 4H3"></path></svg></button></div><div class="flex shrink-0 items-center gap-3 px-6 pb-2 pt-2 text-slate-400 dark:text-neutral-500"><button type="button" aria-label="Mute" class="grid size-6 place-items-center"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" class="size-[15px]"><path d="M11 5 6 9H3v6h3l5 4V5Z"></path><path d="M15.5 8.5a5 5 0 0 1 0 7"></path></svg></button><input type="range" min="0" max="1" step="0.01" aria-label="Volume" class="music-range h-1 flex-1 cursor-pointer appearance-none rounded-full" style="background:linear-gradient(90deg, currentColor 80%, rgba(120,120,128,0.28) 80%)" value="
870.8"/></div><div class="min-h-0 flex-1 overflow-y-auto border-t border-black/[0.06] px-3 pb-3 pt-2 dark:border-white/10"><p class="px-1 py-3 text-xs text-slate-400 dark:text-neutral-500">No tracks</p><ul></ul></div></aside><!--astro:end--></astro-island>
87<script>
88      /* markdown code copy: inject a copy button into native pre, event delegation works with ClientRouter */
89      (function () {
90        function initCopyButtons() {
91          document.querySelectorAll('.prose pre').forEach(function (pre) {
92            if (pre.dataset.mdCopyReady) return;
93            pre.dataset.mdCopyReady = '1';
94            var btn = document.createElement('button');
95            btn.type = 'button';
96            btn.setAttribute('data-md-copy', '');
97            btn.textContent = 'Copy';
98            btn.className =
99              'md-copy-btn card absolute right-2 top-2 z-10 rounded-md border border-(--card-border) px-2 py-0.5 font-mono text-[11px] text-slate-500 transition-colors hover:border-blue-500 hover:text-blue-500 dark:border-(--card-border) dark:text-neutral-400';
100            pre.appendChild(btn);
101          });
102        }
103        initCopyButtons();
104        document.addEventListener('astro:after-swap', initCopyButtons);
105
106        document.addEventListener('click', function (e) {
107          var target = e.target;
108          var btn = target && target.closest ? target.closest('[data-md-copy]') : null;
109          if (!btn) return;
110          var wrap = btn.closest('pre');
111          var code = wrap && wrap.querySelector('code');
112          if (!code) return;
113          var text = code.textContent || '';
114          if (navigator.clipboard && navigator.clipboard.writeText) {
115            navigator.clipboard.writeText(text).then(function () {
116              var old = btn.textContent;
117              btn.textContent = '✓ Copied';
118              setTimeout(function () {
119                btn.textContent = old;
120              }, 1400);
121            });
122          }
123        });
124      })();
125    </script>
125<script>
126      (function () {
127        function removeLoader() {
128          var el = document.getElementById('site-loader');
129          if (el) el.remove();
130        }
131
132        /* in-page navigation (ClientRouter swap) shows no loading animation */
133        document.addEventListener('astro:after-swap', removeLoader);
134
135        var el = document.getElementById('site-loader');
136        if (!el) return;
137        var done = false;
138        var start = Date.now();
139        var MIN_MS = 800; // min display duration, so a fast-ready load doesn't flash by
140
141        function fade() {
142          if (done) return;
143          done = true;
144          el.dataset.done = '1';
145          el.style.transition = 'opacity 0.4s ease';
146          el.style.opacity = '0';
147          setTimeout(removeLoader, 420);
148        }
149        function finish() {
150          var wait = Math.max(0, MIN_MS - (Date.now() - start));
151          setTimeout(fade, wait);
152        }
153
154        // only wait for DOM ready, not for fonts/images and other resources
155        function domReady() {
156          return new Promise(function (res) {
157            if (
158              document.readyState === 'interactive' ||
159              document.readyState === 'complete'
160            ) {
161              res();
162            } else {
163              document.addEventListener('DOMContentLoaded', function () {
164                res();
165              });
166            }
167          });
168        }
169
170        // safety fallback: max 3s, so unexpected stalls can't block the first screen
171        var safety = new Promise(function (res) {
172          setTimeout(res, 3000);
173        });
174        Promise.race([domReady().then(finish), safety.then(finish)]);
175      })();
176    </script>
176<script>
177      /* ambient glow following the mouse: the two light pools are painted once at the
178         fullscreen layer's centre; the layer itself is moved with translate3d — a pure
179         compositor operation, zero gradient repaints per frame. Off for touch/reduced motion. */
180      (function () {
181        if (
182          matchMedia('(pointer: coarse)').matches ||
183          matchMedia('(prefers-reduced-motion: reduce)').matches
184        )
185          return;
186        var layer = document.getElementById('glow-layer');
187        if (!layer) return;
188        var tx = innerWidth / 2, ty = innerHeight * 0.4; // rest center = 50% / 40%
189        var cx = tx, cy = ty;
190        var target = 0, glow = 0, raf = 0, lastMove = 0;
191        function apply() {
192          layer.style.transform =
193            'translate3d(' + (cx - innerWidth / 2).toFixed(1) + 'px,' +
194            (cy - innerHeight * 0.4).toFixed(1) + 'px,0)';
195        }
196        function frame() {
197          cx += (tx - cx) * 0.12;
198          cy += (ty - cy) * 0.12;
199          if (Date.now() - lastMove > 1400) target = 0;
200          glow += (target - glow) * 0.06;
201          apply();
202          layer.style.opacity = glow.toFixed(3);
203          if (glow > 0.002 || target > 0) {
204            raf = requestAnimationFrame(frame);
205          } else {
206            raf = 0;
207            layer.style.opacity = '0';
208          }
209        }
210        function start() {
211          if (!raf) raf = requestAnimationFrame(frame);
212        }
213        addEventListener(
214          'pointermove',
215          function (e) {
216            tx = e.clientX;
217            ty = e.clientY;
218            target = 1;
219            lastMove = Date.now();
220            start();
221          },
222          { passive: true },
223        );
224        document.addEventListener('mouseleave', function () {
225          target = 0;
226          start();
227        });
228        // keep the layer in sync after no-reload navigation
229        document.addEventListener('astro:after-swap', function () {
230          layer = document.getElementById('glow-layer');
231          if (layer) {
232            apply();
233            layer.style.opacity = glow.toFixed(3);
234          }
235        });
236      })();
237    </script>
237<script>
238      /* terminal easter egg: pixel pet + copyright + GitHub.
239         /* terminal easter egg: pixel pet + copyright + GitHub.
240         Visible in the DevTools console; run window.__zyyo.pet() to swap pets, .github() to open the repo.
241         Deferred to idle so it never competes with first paint. */
242      function __zzyyoBoot() {
243        var GH = 'https://github.com/ZYYO666';
244        var YEAR = new Date().getFullYear();
245
246        var PETS = [
247          '   /\\_/\\\n  ( o.o )   < I am a terminal pet'
248            + '\n   > ^ <        Meow.',
249          '   /\\_/\\\n  (=^·^=)   < Caught a passer-by…\n   > ^ <',
250          '    ┌──────┐\n    │ ▯ ▒ ▯ │   < Compiling: today\'s inspiration\n    └──┬┬──┘\n       ││',
251          '   (◕‿◕)   < Rest a while when tired~\n    /|\\n     / \',
252        ];
253
254        function pet() {
255          var i = Math.floor(Math.random() * PETS.length);
256          console.log(
257            '%c' + PETS[i] + '\n',
258            'color:#7dd3fc;font-family:monospace;font-size:11px;line-height:1.4;'
259          );
260          return i;
261        }
262
263        console.log(
264          '%cZ y y o',
265          'font-family:monospace;font-size:32px;font-weight:800;letter-spacing:5px;color:#38bdf8;text-shadow:0 0 14px rgba(56,189,248,.55);'
266        );
267        console.log(
268          '%cThis isn\'t a terminal — it\'s a corner of life. Pixel plants, breathing cards, and a terminal pet are all waiting for you.',
269          'color:#94a3b8;font-size:12px;'
270        );
271        pet();
272        console.log(
273          '%c© ' + YEAR + ' Zyyo · Built with ' + '\u2764' + ' + Astro',
274          'color:#64748b;font-size:11px;'
275        );
276        console.log(
277          '%cGitHub → %s',
278          'color:#38bdf8;font-weight:700;font-size:11px;',
279          GH
280        );
281
282        window.__zyyo = {
283          pet: pet,
284          github: function () {
285            window.open(GH, '_blank');
286          },
287        };
288      }
289      if ('requestIdleCallback' in window) {
290        window.requestIdleCallback(__zzyyoBoot, { timeout: 2000 });
291      } else {
292        window.setTimeout(__zzyyoBoot, 1000);
293      }
294    </script>
294<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"92c087835729416f9e39539aba05b11a","r":1,"spa":2}' crossorigin="anonymous"></script>
294
295</body></html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.