<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>あまねけ！ - tech</title><link href="https://amanejp.netlify.app/" rel="alternate"/><link href="https://amanejp.netlify.app/feeds/tech.atom.xml" rel="self"/><id>https://amanejp.netlify.app/</id><updated>2026-06-22T17:21:00+09:00</updated><entry><title>あまねけ！おもしろCSS 7選</title><link href="https://amanejp.netlify.app/post/amaneke-7-styles/" rel="alternate"/><published>2026-06-22T17:21:00+09:00</published><updated>2026-06-22T17:21:00+09:00</updated><author><name>Amane Katagiri</name></author><id>tag:amanejp.netlify.app,2026-06-22:/post/amaneke-7-styles/</id><summary type="html">&lt;p&gt;Curious Style Sheet&lt;/p&gt;</summary><content type="html">&lt;p&gt;「&lt;a href="https://picrea.jp/event/a306c73ff0f8e27bfdee1cd291159a49a8f21c31c4c832236d04b1cca8e48cfb"&gt;個人サイトWebオンリー めぐる市&lt;/a&gt;」に合わせて書かれた&lt;a href="https://ry0ryuka.xii.jp/design/8-useful-css-tips-2026"&gt;【個人サイト】カスタマイズに便利なCSS 8選【2026】&lt;/a&gt;という記事&lt;sup id="fnref:ry0ryuka"&gt;&lt;a class="footnote-ref" href="#fn:ry0ryuka" title="先日個人サイトwebオンリーめぐる市(2026)サークル参加・全スペース制覇ログ・お気に入りサイト紹介であまねけ！の「個人サイトWebオンリー めぐる市」参加によせてを紹介いただいたヒフミさんのサイト「涼 流 花」の記事です。"&gt;1&lt;/a&gt;&lt;/sup&gt;が面白かったので、私も&lt;a href="/"&gt;あまねけ！&lt;/a&gt;で使われている面白い（たぶん）CSSを7つ選んで紹介しようと思います。&lt;/p&gt;
&lt;p&gt;あまねけ！ではサイト内のJavaScriptをできるだけ排除しており、単純な記事の閲覧や基本的な機能は全てCSSのみで実現しています。ダークモードの切替や文字サイズの変更はもちろん、必要ならテキストブラウザで開いたり、プリンターで印刷して紙で読むこともできます。&lt;/p&gt;
&lt;p&gt;そのため、この記事には広い環境をサポートするためのスタイルや、JavaScriptの方がイージー（not シンプル）に実現できる動作なども含んでいます。今すぐ便利にカッコよく！というわけでもない細かい調整も紹介するので、興味が出ましたら必要な範囲でお使いください。&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#1"&gt;① シェア用テキストを選択しやすくする&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#2"&gt;② スマホでのみ電話番号をリンクとして表示する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#3-url"&gt;③ ページ印刷時にURLも記載する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#4"&gt;④ 強調したいテキストに圏点を表示する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#5"&gt;⑤ ドット絵をパキッと拡大して表示する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#6-css"&gt;⑥ CSSだけでクリックに応じてスタイルを切り替える&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#7-css"&gt;⑦ CSSだけでランダムっぽいパラメータを使う&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_1"&gt;おわりに&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 id="1"&gt;① シェア用テキストを選択しやすくする&lt;/h3&gt;
&lt;p&gt;Click me!: &lt;span class="selectable"&gt;amanejp2rawomh6vcxugcmz3lsdccfkgwcz755mawisr2yazu3bea2qd.onion&lt;/span&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;sample-01-share-text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;user-select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/user-select"&gt;&lt;code&gt;user-select&lt;/code&gt; 属性&lt;/a&gt;を &lt;code&gt;all&lt;/code&gt; にすると、クリックやタップで子の内容が全て選択されるようになります。その後の操作（コピーやシェア）はブラウザあるいはOSの標準機能に任されるので、JavaScriptでコピーを代行するよりユーザーに親切です。&lt;/p&gt;
&lt;p&gt;冒頭のサンプルでは、インタラクションがあることを提示するために &lt;code&gt;border-bottom: 1px dotted&lt;/code&gt; で下線も表示しています。&lt;/p&gt;
&lt;h3 id="2"&gt;② スマホでのみ電話番号をリンクとして表示する&lt;/h3&gt;
&lt;p&gt;Click me!: &lt;a href="tel:+818016654365"&gt;+81 80-1665-4365&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;media&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;screen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;min-width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;768px&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;href&lt;/span&gt;&lt;span class="o"&gt;^=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;tel:&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;電話番号を押下可能なリンクとして設定する場合、スマホからクリックすると電話アプリが立ち上がって電話番号が入力された状態になると期待できます。しかし、PCやタブレットといった電話アプリを前提としていない環境では、クリックできないテキストとして提示したいものです。&lt;/p&gt;
&lt;p&gt;このとき、リンクの装飾として使われる（しばしば青い）文字色と下線のスタイルを外し、さらにクリック不可とすればCSSのみでリンクをふつうのテキストとして表示できます。それぞれ &lt;a href="https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/color"&gt;&lt;code&gt;color&lt;/code&gt; 属性&lt;/a&gt;・ &lt;a href="https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/text-decoration"&gt;&lt;code&gt;text-decoration&lt;/code&gt; 属性&lt;/a&gt; ・ &lt;a href="https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/pointer-events"&gt;&lt;code&gt;pointer-events&lt;/code&gt; 属性&lt;/a&gt;で設定します。&lt;/p&gt;
&lt;p&gt;これらをメディアクエリでPC・タブレットを対象に、またセレクタを &lt;code&gt;a[href^="tel:"]&lt;/code&gt; とすることで、手動でクラスを指定せずとも「電話番号へのリンク」に対して全て適用できます。このスタイルを適用する場合、リンクテキストは「電話をかける」のようなタップできる前提のものではなく、リンクとして設定する具体的な番号が分かるように表示すべきです。&lt;/p&gt;
&lt;p&gt;冒頭のサンプルでは、リンクとして表示しない場合は代わりにコピーしやすいシェア用テキストとして提示しています。&lt;/p&gt;
&lt;h3 id="3-url"&gt;③ ページ印刷時にURLも記載する&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;media&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;href&lt;/span&gt;&lt;span class="o"&gt;^=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Webページを紙に印刷して利用する場合、リンクに関する情報はリンクテキストを残して失われます。PDFのみであればリンクを埋め込んで出力することもできますが、広い環境で利用できるようにURLも表示しておくと安心です。 &lt;a href="https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Selectors/::after"&gt;&lt;code&gt;::after&lt;/code&gt; 疑似要素&lt;/a&gt; などを使えばリンク先URLをテキストとして付加できるので、これをメディアクエリと組み合わせて印刷時のみ有効にするとよいでしょう。&lt;/p&gt;
&lt;p&gt;例示したスタイルでは &lt;code&gt;http://～&lt;/code&gt; あるいは &lt;code&gt;https://～&lt;/code&gt; から始まる外部リンクのみを対象にしていますが、必要に応じて変更してかまいません。あまねけ！では、 &lt;code&gt;a[href^="/"]&lt;/code&gt; な内部リンクには先頭にドメインを付与するスタイルも入っています。&lt;/p&gt;
&lt;p&gt;表示が気になった方は、このページを印刷するかPDFとして保存して出力を確認してみてください。&lt;/p&gt;
&lt;h3 id="4"&gt;④ 強調したいテキストに圏点を表示する&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;sample-04-kenten-text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;text-emphasis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;filled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/text-emphasis"&gt;&lt;code&gt;text-emphasis&lt;/code&gt; 属性&lt;/a&gt;を &lt;code&gt;dot&lt;/code&gt; にすると、対象のテキストに圏点（テキストを強調するために文字の上に置く印）を表示できます。点の形（ &lt;code&gt;dot&lt;/code&gt; ・ &lt;code&gt;triangle&lt;/code&gt; ・ &lt;code&gt;sesame&lt;/code&gt; など）や、それに対して図形の内部を塗りつぶすかどうか（ &lt;code&gt;filled&lt;/code&gt; or &lt;code&gt;open&lt;/code&gt; ）を決められるので、ある程度はサイトの雰囲気に合わせてカスタマイズできます。&lt;/p&gt;
&lt;p&gt;表示が気になった方は、主に&lt;a href="/post/goodbye-sga/"&gt;さよなら、キョーカイの子&lt;/a&gt;などの小説作品などで多用されているので眺めてみてください。&lt;/p&gt;
&lt;h3 id="5"&gt;⑤ ドット絵をパキッと拡大して表示する&lt;/h3&gt;
&lt;p&gt;&lt;img alt="!あまねけ！のサイトバナー" height="31" src="/appendices/media-kit/amanejp_88x31.gif" width="88"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="~あまねけ！のサイトバナー" height="31" src="/appendices/media-kit/amanejp_88x31.gif" width="88"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;sample-05-pixel-art&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;image-rendering&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;pixelated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/image-rendering"&gt;&lt;code&gt;image-rendering&lt;/code&gt; 属性&lt;/a&gt;を &lt;code&gt;pixelated&lt;/code&gt; にすると、画像のサイズ設定やブラウザのズーム機能による拡大・縮小に対して、ピクセルっぽさを残したアルゴリズムで表示されるようになります。&lt;/p&gt;
&lt;p&gt;小さなサイズのドット絵など、ブラウザの幅に対してピクセルを十数倍に拡大して表示する場合、普通はなめらかに拡大するアルゴリズムによってピクセルがにじんでボケボケした見た目になります。これを回避するためには拡大済みの画像を事前に作るのが一般的ですが、このスタイルを当てれば画像が小さいままパキッと拡大できて便利です。&lt;a href="/post/banner8831/"&gt;マイクロなGIFアニメバナーを目指して&lt;/a&gt;でも触れています。&lt;/p&gt;
&lt;p&gt;冒頭のサンプルに表示した88ピクセルx31ピクセルの小さなバナーでは、 &lt;code&gt;image-rendering: pixelated&lt;/code&gt; を当てていない下のバナーだけがぼやけた表示になることを確認できます。&lt;/p&gt;
&lt;h3 id="6-css"&gt;⑥ CSSだけでクリックに応じてスタイルを切り替える&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://gist.githack.com/amane-katagiri/2f8aa285f72e8dff248ffe9d3d17bfde/raw/6a0e4d7f246830eca7a5ba3b2c099695922b500e/"&gt;動作サンプル&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;あまねけ！には、ページ左上の太陽 or 新月のマークでダークモードとライトモードを切り替える機能や、ページ右上の虫眼鏡のマークで文字の大きさを切り替える機能があります。このような動作は、JavaScriptを使えばクリックイベントでクラスを切り替えたり、カラーテーマについては &lt;a href="https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/color-scheme"&gt;&lt;code&gt;color-scheme&lt;/code&gt;&lt;/a&gt; 属性を書き換えれば実現できるものです。&lt;/p&gt;
&lt;p&gt;もしこの機能をCSSのみで実現したい場合は、非表示のチェックボックスにラベルを紐付けて、チェック状態に応じてスタイルを切り替えるのがよいでしょう。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;/* 「状態」を保持するチェックボックスを隠しつつフォーカス可能にする */&lt;/span&gt;
&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;checkbox&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;toggle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;clip-path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* ユーザーに提示する切り替えUI */&lt;/span&gt;
&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;toggle-button&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ここをクリックして背景色を変更&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;checkbox&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;focus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;toggle-button&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;solid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;royalblue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* 切り替えるスタイルの定義 */&lt;/span&gt;
&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;checkbox&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;checked&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#eee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;少し複雑ですが、実際に異なる2つのスタイルを宣言しているのは &lt;code&gt;#main&lt;/code&gt; と &lt;code&gt;input[type="checkbox"]#toggle:checked ~ #main&lt;/code&gt; のみです。他はユーザーに見せるためのクリック可能なラベルのスタイルと、チェックボックスを一切表示させないままフォーカス可能にするための工夫になっています。&lt;/p&gt;
&lt;p&gt;もしチェックボックスを &lt;code&gt;display: none&lt;/code&gt; などで隠してしまうと、マウス以外のインターフェース（Tabキーなど）でラベルに到達するのが難しくなるので、アクセシビリティの観点からこれらのスタイルをおすすめします。フォーカスしたことが分かりやすいよう、 &lt;a href="https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/outline"&gt;&lt;code&gt;outline&lt;/code&gt; 属性&lt;/a&gt;で可視化するのもポイントです。&lt;/p&gt;
&lt;p&gt;この手法は&lt;a href="/post/prefers-dark-light/"&gt;color-schemeとprefers-color-schemeのつかいかた&lt;/a&gt;で触れたものとほぼ同じです。兄方向・親方向に辿るセレクタが書けなかった時代に作ったので、チェックボックスをbody直下に置いて、その弟要素としてsectionを置いて全てのコンテンツを詰める、というちょっと特殊なレイアウトが必要なのが難点ですね。&lt;/p&gt;
&lt;p&gt;最近は &lt;a href="https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Selectors/:has"&gt;&lt;code&gt;:has()&lt;/code&gt; 疑似クラス&lt;/a&gt; が広く利用できるようになりつつあるので、最近のブラウザが対象なら以下のように素直なセレクタにできるでしょう。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;checkbox&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;#eee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;toggle-button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;checkbox&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;focus&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;solid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;royalblue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;これで「チェックボックスの直後のセクション」から「子にチェックボックスがあるbody」と、セレクタの基準が実際にスタイルを当てられる側に移ります。さらに、チェックボックスを上位に置く必要もなくなるので、再利用可能なコンポーネントに詰めて影響範囲を閉じることさえ可能です。&lt;/p&gt;
&lt;h3 id="7-css"&gt;⑦ CSSだけでランダムっぽいパラメータを使う&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://gist.githack.com/amane-katagiri/6ba8407f657d451e38b756c5f1eafb34/raw/52274de8ae2ac6de84df9221a220c52bc58daa21/"&gt;動作サンプル&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;あまねけ！では、季節に合わせたミニ・エフェクト（&lt;a href="https://x.com/amane_katagiri/status/2061362820781048170"&gt;参考&lt;/a&gt;）のように、ランダムな位置や動きを実現したいシーンがあります。&lt;/p&gt;
&lt;p&gt;CSSで &lt;em&gt;ランダムっぽさ&lt;/em&gt; を完結させたい場合は、事前にランダムな値を生成しておくか、ランダムっぽい計算を通せばよいでしょう。JavaScriptなら &lt;code&gt;Math.random()&lt;/code&gt; で擬似乱数を得ることができますが、素直にやると各CSS属性のために何度も乱数を生成して大量に引き渡す実装になったり、スタイルの責務が曖昧になったりして筋が悪そうです。&lt;/p&gt;
&lt;p&gt;もし完全にCSSのみでランダムっぽさを得たい場合は、CSS変数をシードとして非線形関数に通すのがおすすめです。CSSでは &lt;code&gt;sin()&lt;/code&gt; や &lt;code&gt;cos()&lt;/code&gt; といった三角関数が利用でき、これらはあらゆる数値を-1から1に変換する関数とみなせるので、以下のような使い方ができます。GLSLの古典的なハッシュ関数のアルゴリズムと同じですね。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;sample-07-random-position&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;--left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;12.9898&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--index&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;43758.5453&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="n"&gt;svw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;--top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;78.233&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--index&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;43758.5453&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="n"&gt;svh&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--left&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;--top&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;周期を超える大きめの数値を三角関数に通すと乱数っぽく見える性質を利用したもので、実際にはCSS変数で値が確定する決定的な計算しかしていません。サンプルページでスライダーを動かしてみると分かるとおり、実際には位置が偏ったりいかにも正弦波のプロットっぽい配置に近づくこともあります。それでも、簡単な処理で大量に取り出せるランダムプールとしては十分です。&lt;/p&gt;
&lt;h3 id="_1"&gt;おわりに&lt;/h3&gt;
&lt;p&gt;CSSだけで実現できることは、他にもたくさんあります！　ちょっとした位置合わせや平面的な大きさの設定だけがCSSの能力ではありません。paddingとmarginの違いを峻別できるかなんて、広いCSSの世界の中では本当に小さな豆知識だということです。&lt;/p&gt;
&lt;p&gt;そよ風に流される複雑なアニメーション、簡易的なダイアログ表示、ダークモードに合わせた画像反転、OSが提示した文字サイズへの追従、などなど……シンプルかつ安全な宣言的記述で「もう一歩」を叶えられるのがCSSの魅力です。みなさんもぜひ、日常生活のちょっとしたところからCSSを取り入れてみてくださいね。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:ry0ryuka"&gt;
&lt;p&gt;先日&lt;a href="https://ry0ryuka.xii.jp/design/wrapup-megruichi2026"&gt;個人サイトwebオンリーめぐる市(2026)サークル参加・全スペース制覇ログ・お気に入りサイト紹介&lt;/a&gt;であまねけ！の&lt;a href="/post/meguruichi/"&gt;「個人サイトWebオンリー めぐる市」参加によせて&lt;/a&gt;を紹介いただいたヒフミさんのサイト「&lt;a href="https://ry0ryuka.xii.jp/"&gt;涼 流 花&lt;/a&gt;」の記事です。&amp;#160;&lt;a class="footnote-backref" href="#fnref:ry0ryuka" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="tech"/></entry><entry><title>今日できる！「読みやすい小説本」を作る10個のチェックポイント</title><link href="https://amanejp.netlify.app/post/super-typeset-checklist/" rel="alternate"/><published>2026-06-18T22:23:00+09:00</published><updated>2026-06-18T22:23:00+09:00</updated><author><name>Amane Katagiri</name></author><id>tag:amanejp.netlify.app,2026-06-18:/post/super-typeset-checklist/</id><summary type="html">&lt;p&gt;本と紙にこだわるあなたに捧ぐワンポイントアドバイス&lt;/p&gt;</summary><content type="html">&lt;p&gt;即売会で文芸サークルを回って見本誌を読んでいるとき、どうしても紙面の文字が頭に入ってこないことがあります。中身を理解するより先に、ページを先に進めるのがなんか面倒だなーと思う瞬間があるのです。&lt;/p&gt;
&lt;p&gt;そういう本の紙面に感じる違和感はいろいろ。文字が小さくて読みにくい、あるいは判型に対して不自然に大きくて目が滑る。字　間　が　大　き　く　てリズムが掴めない、あるいは狭すぎて窮屈でせっかちな印象になる。行間が広くて、狭くて……綴られているのはきっとよく練られた素敵なフレーズなのでしょうが、そんなページに置かれただけで陳腐な印象になる気がします。&lt;/p&gt;
&lt;p&gt;余白の取り方もそうです。たいていの文庫本は上（天）と下（地）の余白がどちらかに偏っていて、ぴったり揃えた紙面には違和感を感じるはず。でも、世の中には明らかにワープロソフトで適当に決めた余白の同人誌が出回っています。本の構造上、綴じ側（のど）は外側（小口）より大きな余白になるのが普通ですが、たまに行がのどにぴっちり食い込んで読みにくい同人誌も見かけます。&lt;/p&gt;
&lt;p&gt;様々なデバイスで文字を読める今の時代に、わざわざ紙あるいは紙を模した平面で作品を発表するなら、できるかぎりその意味にこだわりたいところです。文章をただテキスト情報として伝えたいだけなら、読者にレイアウトを任せるリフロー型の電子書籍メディア&lt;sup id="fnref:reflowable"&gt;&lt;a class="footnote-ref" href="#fn:reflowable" title="デバイスの画面サイズやウィンドウの大きさ、フォントサイズの設定に応じて再レイアウト（reflow）できる形式です。PDFのように決まった紙面を拡大・縮小するのではなく、ブラウザで文章を読むような体験を提供できます。テキストデータを含むので、検索機能や読み上げ機能とも相性がよいです。"&gt;1&lt;/a&gt;&lt;/sup&gt;を使うのがお互いに最適であって、わざわざ柔軟性もなく利便性も低い紙束を配る必要はないでしょう。&lt;/p&gt;
&lt;p&gt;では、よい紙面とは何か？　それをどう作るのか？　その答えは非常にシンプルで、自分が読みやすいなと思った本を真似すればよいのです。文字の大きさ、行数、余白はどうなっているか、ノンブルや柱はどこに入っているか、などなど。&lt;/p&gt;
&lt;p&gt;具体的には、以下のような10個のチェックリストを埋めていくのがよいでしょう。この記事を印刷したり、コピーしてメモアプリに貼り付ければチェックリストとして使えます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;① 本の判型（大きさ）は？&lt;/li&gt;
&lt;li&gt;② 文字の大きさは？&lt;sup id="fnref:fontsize-bookmark"&gt;&lt;a class="footnote-ref" href="#fn:fontsize-bookmark" title="読んでいる本の文字サイズがわかるしおりなどがあれば、大まかなサイズ感を掴むのが楽になります。"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;③ 1行に何文字くらい入ってる？&lt;sup id="fnref:char-count-per-line"&gt;&lt;a class="footnote-ref" href="#fn:char-count-per-line" title="記号類が占める大きさが違ったり、禁則処理などで追い込まれたり追い出されて1行の文字数が変わることがあります。周囲に禁則文字がなく、正方形の空間を占めるふつうの文字の行で比較するとよいでしょう。ここから字間を逆算できます。"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;④ 1ページに何行くらい入ってる？&lt;sup id="fnref:line-count-per-page"&gt;&lt;a class="footnote-ref" href="#fn:line-count-per-page" title="大きな文字の見出しやその周囲の余白、あるいは脚注が入って1ページの行数が減ることがあります。ふつうの本文のみで占められたページで比較するとよいでしょう。ここから行間を逆算できます。"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;⑤ 版面（文字が印刷されている部分）は何mmある？&lt;/li&gt;
&lt;li&gt;⑥ 天（上側）の余白は何mmある？&lt;/li&gt;
&lt;li&gt;⑦ 地（下側）の余白は何mmある？&lt;/li&gt;
&lt;li&gt;⑧ 小口（外側）の余白は何mmある？&lt;/li&gt;
&lt;li&gt;⑨ ノンブル（ページ番号）はどこに入ってる？&lt;/li&gt;
&lt;li&gt;⑩ 柱（ページのタイトル・見出しなど）はどこに入ってる？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;実際の紙面に合わせるとこんな感じになります。&lt;/p&gt;
&lt;p&gt;&lt;img alt="よい紙面を作るための10個のチェックリストを実際の紙面で示した図" height="529" src="/images/super-typeset-checklist/paper.png" width="750"&gt;&lt;/p&gt;
&lt;p&gt;なお、版面の大きさを指定できず、代わりにのど（内側）の余白で配置を決めなければならない場合は、判型の横の長さから版面の横の長さと小口の余白を引くと、のどの余白が分かります。&lt;/p&gt;
&lt;p&gt;チェックリストが埋まったら、これらの情報を元にお手持ちのワープロソフトや組版システムでページを調整します。もし自分の文芸同人誌でこのような点を気にしたことがなければ、まずはその同人誌の &lt;em&gt;測定&lt;/em&gt; から始めるとよいでしょう。この記事では10個のチェックポイントに絞りましたが、本を眺めて気になった点はどんどん確認するべきです。扉はどんなデザインか、目次をどのように入れているか、どちらのページから始まっているか、などなど。&lt;/p&gt;
&lt;p&gt;冒頭で触れた「目が滑る同人誌」は、そういうちょっとした観察や読者としての視線を感じない本だ、とも言い換えられます。本が読者に与える印象は、表紙のロゴタイプの綺麗さやイラストの &lt;em&gt;エモさ&lt;/em&gt; だけで決まるわけではなく、ぱらぱらと立ち読みしたときの紙面の印象でも大きく変わるものです。&lt;/p&gt;
&lt;p&gt;調整が終わったら、最後に見開きの一面（2ページ）で等倍に印刷してチェックしてみましょう。文庫本はA5サイズに、A5版はA4サイズになりますが、どちらもA4に印刷してかまいません。コンビニでのPDF印刷では「用紙に合わせる」ような設定を外すボタンが分かりにくいことがあります。&lt;/p&gt;
&lt;p&gt;さて、新しい紙面は違和感なく仕上がりましたか？　調整前の紙面と見比べると、その違いをより意識できると思います。今回紹介したチェックリストが、あなたの素敵な作品を仕上げる際の参考になれば幸いです。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:reflowable"&gt;
&lt;p&gt;デバイスの画面サイズやウィンドウの大きさ、フォントサイズの設定に応じて再レイアウト（reflow）できる形式です。PDFのように決まった紙面を拡大・縮小するのではなく、ブラウザで文章を読むような体験を提供できます。テキストデータを含むので、検索機能や読み上げ機能とも相性がよいです。&amp;#160;&lt;a class="footnote-backref" href="#fnref:reflowable" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fontsize-bookmark"&gt;
&lt;p&gt;&lt;a href="https://ryokuyousha.booth.pm/items/7551490"&gt;読んでいる本の文字サイズがわかるしおり&lt;/a&gt;などがあれば、大まかなサイズ感を掴むのが楽になります。&amp;#160;&lt;a class="footnote-backref" href="#fnref:fontsize-bookmark" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:char-count-per-line"&gt;
&lt;p&gt;記号類が占める大きさが違ったり、禁則処理などで追い込まれたり追い出されて1行の文字数が変わることがあります。周囲に禁則文字がなく、正方形の空間を占めるふつうの文字の行で比較するとよいでしょう。ここから字間を逆算できます。&amp;#160;&lt;a class="footnote-backref" href="#fnref:char-count-per-line" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:line-count-per-page"&gt;
&lt;p&gt;大きな文字の見出しやその周囲の余白、あるいは脚注が入って1ページの行数が減ることがあります。ふつうの本文のみで占められたページで比較するとよいでしょう。ここから行間を逆算できます。&amp;#160;&lt;a class="footnote-backref" href="#fnref:line-count-per-page" title="Jump back to footnote 4 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="tech"/></entry><entry><title>たった今データを失うかもしれない君に捧ぐ10個の質問</title><link href="https://amanejp.netlify.app/post/ban-ban-drive-faq/" rel="alternate"/><published>2026-05-22T15:18:00+09:00</published><updated>2026-05-22T15:18:00+09:00</updated><author><name>Amane Katagiri</name></author><id>tag:amanejp.netlify.app,2026-05-22:/post/ban-ban-drive-faq/</id><summary type="html">&lt;p&gt;明日クラウドストレージサービスにBANされるかもしれないあなたの10の質問に答えます&lt;/p&gt;</summary><content type="html">&lt;p&gt;最近、Google DriveやDropboxといった大手のクラウドストレージサービスから突然BANされたとか、異議申し立てが全く認められずデータを喪失するといった事故がしばしば注意喚起としてSNSで拡散されることが増えました。しかし、多くの人はこうした事故を知りつつ &lt;strong&gt;専制的なクラウドストレージサービス&lt;/strong&gt; を漫然と使い続け、横暴なBANを待つ列に並んで暇を潰しているのが現状です。&lt;/p&gt;
&lt;p&gt;本記事ではこうした専制的なクラウドストレージサービスの具体的なリスクを理解し、安全にデータを保管するにはまずは何をすべきなのか、10個の質問を通して示します。&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#_1"&gt;私は普通のデータしか置いてないから大丈夫だよね？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_2"&gt;いま使ってるクラウドストレージは一切使うなってこと？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_3"&gt;リスクを意識してクラウドストレージを使う、って具体的にはどういうこと？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ban"&gt;BANされるかもしれないデータは暗号化すれば大丈夫だよね？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_4"&gt;プライバシー重視のクラウドストレージってなんなの？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_5"&gt;でも、プライバシー重視のクラウドストレージって高いでしょ？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_6"&gt;クラウドストレージの移行って面倒じゃない？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_7"&gt;プライバシー重視って言ってるけど完全に信頼していいの？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_8"&gt;そう言ってるあんたはどんなクラウドストレージサービスを使ってるわけ？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_9"&gt;で、私は結局今からどうすればいいんだっけ？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="_1"&gt;私は普通のデータしか置いてないから大丈夫だよね？&lt;/h2&gt;
&lt;p&gt;おそらく、いいえ。&lt;/p&gt;
&lt;p&gt;あなたがアップロードしたデータが &lt;em&gt;普通&lt;/em&gt; かどうかは、あなたではなく（もちろん私でもなく）クラウドストレージサービスの運営事業者が &lt;strong&gt;勝手に&lt;/strong&gt; （！）決めることです。&lt;/p&gt;
&lt;p&gt;明日からいきなり「56そう」とか「安楽死」という歌詞が含まれた楽曲や、肌色が50%以上を占める画像を所持しているアカウントを発見しだいBANします！なんてことになっても、あなたは大丈夫と言い切れますか？　もっともっと恣意的に、運営事業者を批判するこうした文書が全面的に禁止されるかもしれません。&lt;/p&gt;
&lt;p&gt;しかも現代では、さらに込み入った検閲基準を備えたAIが24時間365日ストレージを監視して回っています。人手不足も疲れも知らない検閲チームって、なんだかすごく不気味ですよね。あなたも今日まで偶然BANされなかったというだけで、明日にはこうした不気味なAIがジロジロと &lt;em&gt;普通&lt;/em&gt; のデータを覗き見てくるかもしれませんよ。&lt;/p&gt;
&lt;h2 id="_2"&gt;いま使ってるクラウドストレージは一切使うなってこと？&lt;/h2&gt;
&lt;p&gt;できるだけ、はい。&lt;/p&gt;
&lt;p&gt;大手のクラウドストレージサービスには便利な共有機能や同期機能が用意されていますが、これらの多くは無条件にプライバシーを犠牲にせずとも十分に実現できます。効率の良い配信や利便性向上のためにデータを読むだけならまだしも、それを一方的なBANに使うなら話は別です。&lt;/p&gt;
&lt;p&gt;かれらは便利でクールに見える機能でユーザーの目を引き、プライバシー侵害を正当化しようとしているだけです。&lt;/p&gt;
&lt;p&gt;ただし、これらのクラウドストレージサービスから今すぐ撤退する必要はありません。これらを「勝手に消えるかもしれない」「一時的な利用のための」「覗き見られても平気なデータを置く」信頼できないサービスとして使い続けられます。つまり、どういうリスクと引き換えなのかをよく意識して使えば、もしもの時に被る実害を減らせるということです。&lt;/p&gt;
&lt;h2 id="_3"&gt;リスクを意識してクラウドストレージを使う、って具体的にはどういうこと？&lt;/h2&gt;
&lt;p&gt;鋭い質問ですね！&lt;/p&gt;
&lt;p&gt;たとえば、クラウドストレージサービスにアップロードするデータは必ずローカルにも保存しておく、というルールを守っていれば突然BANに遭っても手元にはデータが残ります。クラウドストレージはあくまでバックアップで、原本は常に手元で保管するということです。これは、Google Driveの同期設定では「ミラーリング」と呼ばれているものです。&lt;/p&gt;
&lt;p&gt;一方の「ストリーミング」は、ローカルストレージの節約という名目で手元にはファイルのリストだけを持っています。必要に応じてデータをダウンロードして閲覧・編集するという設定なので、BANされた時点で他のデータも巻き込んで一切アクセスできなくなってしまいます。&lt;/p&gt;
&lt;p&gt;また、BANによる爆発半径を抑制するために複数のアカウントを持っておくのも有効なアイデアです。特にGoogle DriveはGoogle アカウントを介してメールや連絡先、Google Playなどと同一のアイデンティティとして取り扱われるので、BANによる被害が非常に大きくなりがちです。&lt;/p&gt;
&lt;p&gt;そのため、Google Drive専用のアカウントに分けるのは基本的ながら効果の高い対策になります。&lt;/p&gt;
&lt;p&gt;いずれの場合でも、運営事業者があなたのデータを検閲したり、サービス向上目的と称して営利活動のエサにする可能性は避けられません。これも勘案すべきリスクです。&lt;/p&gt;
&lt;h2 id="ban"&gt;BANされるかもしれないデータは暗号化すれば大丈夫だよね？&lt;/h2&gt;
&lt;p&gt;おそらく、はい。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cryptomator.org/"&gt;Cryptomator&lt;/a&gt;や&lt;a href="https://veracrypt.jp/"&gt;VeraCrypt&lt;/a&gt;で暗号化したアーカイブをクラウドストレージサービスにバックアップすれば、原理的に（これは運営事業者がどう頑張っても、という意味です）検閲されたり無断利用されることはありません。運営事業者からはどう頑張ってもランダムなバイナリにしか見えないからです。つまり「覗き見られても平気な」データを置いてリスクを回避する手法です。&lt;/p&gt;
&lt;p&gt;ただし、これらの暗号化アーカイブはフォルダ階層を特別な形式で1つのファイルにまとめているため、普通のフォルダより使い勝手が悪く独特な挙動を示したり、クラウドストレージサービスによっては上手く同期できない場合があります。また、サービス固有の便利な機能は一切使えないと考えた方がいいでしょう。&lt;/p&gt;
&lt;p&gt;そうなってくると、いよいよどうしてそんな信頼できないサービスにデータを預けているのかよく分かりません。もし &lt;strong&gt;プライバシー重視のクラウドストレージサービス&lt;/strong&gt; に移行したなら、難しい暗号化レイヤーを自前で用意する必要はなくなりますよ。&lt;/p&gt;
&lt;h2 id="_4"&gt;プライバシー重視のクラウドストレージってなんなの？&lt;/h2&gt;
&lt;p&gt;それ、本質的な問いです！&lt;/p&gt;
&lt;p&gt;この記事では主に、運営事業者がユーザーから預かったデータを原理的に覗き見できない仕組みを採用しているサービスを &lt;strong&gt;プライバシー重視&lt;/strong&gt; と称しています。&lt;/p&gt;
&lt;p&gt;原理的に覗き見できない、というのは運営事業者がどう頑張ってもファイルを読めない、という意味です。逆に言えば、Google DriveやDropboxのようなサービスは、権限制御や内部統制といった &lt;em&gt;契約&lt;/em&gt; でデータへのアクセスを制限しています。そうしたサービスは国家権力の要請でファイルを引き渡したり、強制的なスキャンで一方的なBANを行使できる権力を握っています。&lt;/p&gt;
&lt;p&gt;プライバシー重視のクラウドストレージサービスでは、ユーザーがアップロードしたデータは必ず暗号化された状態で保存されている上に、その復号鍵を運営事業者では保持せずにユーザーだけが利用できるようになっています。&lt;/p&gt;
&lt;p&gt;これを実現する代表的な方法が &lt;strong&gt;E2EE&lt;/strong&gt; （エンドツーエンド暗号化/クライアントサイド暗号化）であり、BANされるかもしれないデータどころか全てのデータを自動的に暗号化した上でアップロードする仕組みを初めから持っています。私たちはこの仕組みに乗っかるだけでよく、わざわざ独自の暗号化アーカイブを苦心して運用する必要はありません。&lt;/p&gt;
&lt;p&gt;なお、Google DriveやDropboxも保存先のストレージは暗号化されているはずですが、適切な監査を経た（と主張する）手続きで復号鍵を取り出してファイルを取得できるという点で、プライバシー重視とはいえません。&lt;/p&gt;
&lt;h2 id="_5"&gt;でも、プライバシー重視のクラウドストレージって高いでしょ？&lt;/h2&gt;
&lt;p&gt;わずかに、はい。&lt;/p&gt;
&lt;p&gt;ただし、費用と容量の比で見て何倍もかかることはありません。たとえば、Google OneのBasicプランを使っている人がProton DriveのDrive Plusプランに移行するなら、 &lt;strong&gt;240円/100GB/月&lt;/strong&gt; から &lt;strong&gt;500円/200GB/月&lt;/strong&gt; になります。容量が2倍になって価格は2倍とちょっと、そこにプライバシー重視のおまけつきです。むしろお得じゃありません？&lt;/p&gt;
&lt;p&gt;特に、いまGoogle Oneを使っていて100GBで足りなくなった人がアップグレードした場合、次のプランはGoogle AI Plusと称して勝手にAI機能を盛った &lt;strong&gt;1000円/200GB/月&lt;/strong&gt; のものになるので、Proton Driveへの移行の方が費用面でも大きなメリットがあります。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;サービス&lt;/th&gt;
&lt;th&gt;無料枠&lt;/th&gt;
&lt;th&gt;最小の有料プラン&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img alt=":smiling_imp:" class="emoji" height="16" src="/emojis/1f608.png" width="16"&gt; &lt;a href="https://one.google.com/about/plans"&gt;Google One&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;15GB&lt;/td&gt;
&lt;td&gt;240円/100GB/月&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img alt=":smiling_imp:" class="emoji" height="16" src="/emojis/1f608.png" width="16"&gt; &lt;a href="https://www.dropbox.com/ja/plans"&gt;Dropbox&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2GB&lt;/td&gt;
&lt;td&gt;1200円/2TB/月&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img alt=":smiling_imp:" class="emoji" height="16" src="/emojis/1f608.png" width="16"&gt; &lt;a href="https://www.microsoft.com/ja-jp/microsoft-365"&gt;OneDrive&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;5GB&lt;/td&gt;
&lt;td&gt;200円/100GB/月&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img alt=":thumbsup:" class="emoji" height="16" src="/emojis/1f44d.png" width="16"&gt; &lt;a href="https://proton.me/drive/pricing"&gt;Proton Drive&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;5GB&lt;/td&gt;
&lt;td&gt;500円/200GB/月&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img alt=":thumbsup:" class="emoji" height="16" src="/emojis/1f44d.png" width="16"&gt; &lt;a href="https://www.sync.com/pricing/"&gt;Sync.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;5GB&lt;/td&gt;
&lt;td&gt;560円/150GB/月&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img alt=":thumbsup:" class="emoji" height="16" src="/emojis/1f44d.png" width="16"&gt; &lt;a href="https://mega.io/pricing"&gt;MEGA&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;10GB&lt;/td&gt;
&lt;td&gt;620円/200GB/月&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;もしGoogle DriveやOneDriveを無料枠で使っているなら、MEGAはかなり大きめの無料枠があるので、すぐに移行できるはずです。無料枠から無料枠に移すだけでおまけに安心まで付いてくるなら、試してみる価値があると思いませんか？&lt;/p&gt;
&lt;h2 id="_6"&gt;クラウドストレージの移行って面倒じゃない？&lt;/h2&gt;
&lt;p&gt;ある程度、はい。&lt;/p&gt;
&lt;p&gt;でも、現実の引っ越しのように数時間や数日で全てのデータを移す必要はないので、少しずつ進めてみましょう。パソコンのデータ移行とだいたい同じです。&lt;/p&gt;
&lt;p&gt;初めに、無料枠で済む範囲の作業を新しいクラウドストレージサービスで行うようにします。新しいファイルはProton Driveに保存する、Google Driveにあるファイルを編集するときはProton Driveに移してから開く、といった感じです。ここで新しいサービスの使い勝手を確かめながら、少しずつGoogle Driveの使用容量を減らしていきます。&lt;/p&gt;
&lt;p&gt;この移行期間中に、Proton Driveを使う上で甘受できない不便さを感じるかもしれません。その場合は別のクラウドストレージサービスを使ってみたり、いったんリスクを受け入れてGoogle Driveに戻る、という選択もありえます。リスクは比較の上に成り立つ定量的なものであり、ゼロリスクを目指すのもリスクを全て無視するのも愚かさで言えば全く同じです。&lt;/p&gt;
&lt;p&gt;そして、新しいクラウドストレージサービスの使い勝手に問題がなさそうなら、1ヶ月くらい利用期間が被るように新しいサービスのプランを契約します。この間に少しずつ移行を進めて、元のサービス側は無料枠に収まる程度までファイルを減らしていけば、移行はほぼ完了です。元のサービスのプランは忘れずに解約して、しばらく併用するなり気長に移行したりするといいでしょう。&lt;/p&gt;
&lt;p&gt;面倒なこともまずは初めの一歩からです。まずは前問のテーブルから &lt;img alt=":thumbsup:" class="emoji" height="16" src="/emojis/1f44d.png" width="16"&gt; の付いたクラウドストレージサービスを選んで、アカウントを登録してください。登録にお金は一切かかりません！　迷ったら&lt;a href="https://account.proton.me/ja/drive/signup"&gt;Proton Drive&lt;/a&gt;がおすすめです。&lt;/p&gt;
&lt;h2 id="_7"&gt;プライバシー重視って言ってるけど完全に信頼していいの？&lt;/h2&gt;
&lt;p&gt;ほとんど、いいえ。&lt;/p&gt;
&lt;p&gt;専制的なクラウドストレージサービスが &lt;em&gt;かなり&lt;/em&gt; 信頼できないのと同じように、プライバシー重視のクラウドストレージサービスも &lt;em&gt;ある程度&lt;/em&gt; は信頼できません。原理的に覗き見できても契約や運用で不当な検閲を防ぐことはできますし、逆にE2EEを謳っていてもバグや利便性向上のための処理でデータが漏れる経路があるかもしれません。&lt;/p&gt;
&lt;p&gt;ただし、これはどういうリスクがあるかを考慮すべきという意味であって、より信頼に足らないサービスを使い続ける理由にはなりません。リスクを知って受け入れた上で、やはり私はプライバシー重視のクラウドストレージサービスを使うべきだと思います。&lt;/p&gt;
&lt;p&gt;このような外部リスクの勘案を諦めて運営事業者を全く信頼せず、クラウドストレージサービスを一切使わない原理主義的な人も多くいます。こうした人たちは自宅でNASを構築してデータを保管したり、さらにその運用を勧めて回ったりしていますが、実際はデータ喪失のリスクを負っていることを意識しなければいけません。&lt;/p&gt;
&lt;p&gt;まともなNASの運用には、ストレージの故障に備えて冗長なファイルシステムやバックアップの仕組みを構築するのは当然として、災害・盗難に備えた保存メディアや保管場所の分散が求められます（3-2-1ルールと呼ばれるものです）。&lt;/p&gt;
&lt;p&gt;これらの専門的な知識を備えたミスのないオペレーションを目指すのは趣味としては面白いかもしれませんが、Google DriveからいきなりNASへの移行を勧めるのはなかなか酷なことです。一般的なユーザーであれば、適切にデータを管理できる事業者を選んで &lt;em&gt;ある程度&lt;/em&gt; 信頼した方が安心です。&lt;/p&gt;
&lt;h2 id="_8"&gt;そう言ってるあんたはどんなクラウドストレージサービスを使ってるわけ？&lt;/h2&gt;
&lt;p&gt;興味が出てきたみたいですね！&lt;/p&gt;
&lt;p&gt;私は主に&lt;a href="https://www.sync.com/pricing/"&gt;Sync.com&lt;/a&gt;のPro Solo (2TB)プランを使っています。ここに自宅に構築したNextcloudを併用して、E2EEになじまないアプリなどのストレージを用意している感じです。一応&lt;a href="https://proton.me/ja/pricing"&gt;Proton&lt;/a&gt;のUnlimitedプランなのでProton Driveも使えるんですが、こっちはメールサービスがメインなのでほとんど使っていません。&lt;/p&gt;
&lt;p&gt;独自ドメインでメールアドレスを使うために、Protonにメールを移行する前はGoogle Workspace（登録した頃はG Suiteでしたね）を契約していて、今もなんやかんやメール以外のサービスを使っています。Google Driveの容量が余っているのでrcloneの暗号化バックアップ先にしたり、Geminiで適当に遊んだりするのにも便利です。&lt;/p&gt;
&lt;p&gt;企業向け契約のプランならBANされにくいという噂もありますが、割高な上に完全な無検閲無法地帯として開放されているわけではないと思うので（特にCSAMやマルウェアにあたるコンテンツの検閲は厳しいはず）、変な回り道をせずにまともなクラウドストレージサービスを使ってください。&lt;/p&gt;
&lt;h2 id="_9"&gt;で、私は結局今からどうすればいいんだっけ？&lt;/h2&gt;
&lt;p&gt;やった！&lt;/p&gt;
&lt;p&gt;じゃあ、今すぐ ① &lt;a href="https://account.proton.me/ja/drive/signup"&gt;Protonのアカウントを作って&lt;/a&gt;、 ② &lt;a href="https://proton.me/ja/drive/download"&gt;Proton Driveアプリをインストールして&lt;/a&gt;、 ③ 今日からファイルの編集は全てProton Driveのフォルダでやってみてください。④ それからのことは来週考えれば &lt;img alt=":ok:" class="emoji" height="16" src="/emojis/1f197.png" width="16"&gt; です！&lt;/p&gt;
&lt;p&gt;Protonなら&lt;a href="https://pr.tn/ref/HRH4RWE3"&gt;紹介リンク&lt;/a&gt;もあるから、よければ使ってね～。&lt;/p&gt;</content><category term="tech"/></entry><entry><title>デスクトップに面白い文字を流そう</title><link href="https://amanejp.netlify.app/post/led-appbar-news/" rel="alternate"/><published>2026-03-29T12:32:00+09:00</published><updated>2026-03-29T12:32:00+09:00</updated><author><name>Amane Katagiri</name></author><id>tag:amanejp.netlify.app,2026-03-29:/post/led-appbar-news/</id><summary type="html">&lt;p&gt;うおさがのいじょみおりそも&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#_1"&gt;ここまでのあらすじ&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#led-news-ticker"&gt;LED News Ticker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#led-appbar"&gt;LED AppBar&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#_2"&gt;デスクトップに面白い文字を流そう&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#_3"&gt;ニュースのフィードを流す&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_4"&gt;インターネットのフィードを流す&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_5"&gt;自分で用意したテキストをランダムに流す&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#spotifynow-playing"&gt;SpotifyのNow Playingを流す&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#no-context"&gt;No Contextな文章を流す&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#_6"&gt;これから……？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="_1"&gt;ここまでのあらすじ&lt;/h2&gt;
&lt;p&gt;先日、デスクトップに文字を流し続ける小さなウィジェット「&lt;a href="https://github.com/vividoyomogimochi/app-top-bar"&gt;LED AppBar&lt;/a&gt;」を作りました。今は、ここにどんな文字を流したら面白いだろうか、ということを考えています――&lt;/p&gt;
&lt;h3 id="led-news-ticker"&gt;LED News Ticker&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://zenn.dev/108_twil3akine/articles/rust-wasm-lifegame-wallpaper"&gt;Rust+Wasmで爆速ライフゲームを作って動く壁紙にする&lt;/a&gt;という記事を読み、動画やウェブページをライブ壁紙として設定できる&lt;a href="https://apps.microsoft.com/detail/9ntm2qc6qws7"&gt;Lively Wallpaper&lt;/a&gt;というツールを知りました。このツールで何かライブ壁紙を設定してみたいと思って生まれたのが、&lt;a href="https://ticker.samoyed.moe/"&gt;LED News Ticker&lt;/a&gt;です。&lt;/p&gt;
&lt;p&gt;LED News Tickerができることは主に2つ。固定の壁紙を表示することと、画面上部に配置された横長のLEDマトリクスパネル風の領域に任意のテキストを流すことです。これはいわゆるニュースティッカーと呼ばれるもので、&lt;a href="/"&gt;あまねけ！&lt;/a&gt;の上部に流れている広告も同様のスタイルです。&lt;/p&gt;
&lt;video autoplay loop muted width="800" height="130" src="/images/led-appbar-news/ticker.webm"&gt;LEDマトリクスパネル風の領域にニュースが流れている様子&lt;/video&gt;

&lt;p&gt;そもそもLEDマトリクスパネルの実物を見たことがない方のために、一応&lt;a href="/appendices/led-appbar-news/patrol.webm"&gt;実物の動画&lt;/a&gt;も置いておきます。こういう単色の小さなものなら数千円で買えますし、パトカーの後ろに設置されているパネルも同じものです。かつて新幹線で流れていた車内ニューステロップを想像してもよいでしょう。&lt;/p&gt;
&lt;p&gt;流れるテキストは、事前に設定したフィードの定期取得やWebSocket・SSEの受信で順次切り替わっていきます。パラメータを付けずに開くと、たぶんニュースっぽい背景にNHKニュースのヘッドラインが流れるはずです。画面右下にマウスカーソルを寄せたり、タップすると設定画面が開けます。&lt;/p&gt;
&lt;h3 id="led-appbar"&gt;LED AppBar&lt;/h3&gt;
&lt;p&gt;さて、Lively Wallpaperでこのページを壁紙に表示して楽しく過ごしていたのですが、実はパソコンを使っている間に壁紙を見る瞬間はそう多くないことに気付きました。いつも2つの画面にエディタとターミナルか調べ物のブラウザを表示していて、壁紙が出てくるのはウィンドウを切り替えるごくわずかな合間くらいです。&lt;/p&gt;
&lt;p&gt;そこで、LED News Tickerからニュースティッカー部分のみを取り出して、さらに画面上部に固定するWindows用アプリ&lt;a href="https://github.com/vividoyomogimochi/app-top-bar"&gt;LED AppBar&lt;/a&gt;を作りました。このアプリはWindowsの&lt;a href="https://learn.microsoft.com/ja-jp/windows/win32/shell/application-desktop-toolbars"&gt;AppBar API&lt;/a&gt;を利用して画面端領域を独占するもので、要はオリジナルのタスクバーを画面端に表示するイメージです。&lt;/p&gt;
&lt;p&gt;このアプリ自体はニュースティッカーを同梱しておらず、中身はLED News Tickerのティッカー部分を表示する細い画面のブラウザと同じようなものです。つまり、URLを入れ替えれば任意のコンテンツを表示できるので、画面上部にニュースティッカー以外を表示したい人も使ってみてください。設定はトレイアイコンを右クリックで「Set URL...」です。&lt;/p&gt;
&lt;p&gt;例えば、12人のキャラクターの視線で作業を監視してもらうことができます:&lt;/p&gt;
&lt;p&gt;&lt;img alt="画面上部のLED AppBarの領域に、横に並んだ12人の同じ少女の顔が前髪から口元まで表示されていて、全員がこちらをそれぞれ異なる表情で見つめている様子" height="53" src="/images/led-appbar-news/amane-bar.png" width="750"&gt;&lt;/p&gt;
&lt;p&gt;なお、LED AppBarは&lt;a href="https://github.com/tauri-apps/tauri"&gt;Tauri&lt;/a&gt;で書かれていて、本来はクロスプラットフォームビルドできるのですがWindows以外には対応していません。前述の通り、画面上部を独占的に利用するWindowsシェルのAPIに依存しているからです。&lt;/p&gt;
&lt;p&gt;RustからWindowsのAPI（Win32 API）を呼び出すには、Microsoft公式の&lt;a href="https://crates.io/crates/windows"&gt;windows&lt;/a&gt;クレートを使うのが最も手軽でしょう。以下の例の通り、 &lt;code&gt;#[cfg(windows)]&lt;/code&gt; でWindows環境を識別して、 &lt;code&gt;unsafe&lt;/code&gt; ブロック内で必要なAPIを呼び出す……というのが主な使い方です。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// 🌟 Windows環境でのみ動作するようガードする&lt;/span&gt;
&lt;span class="cp"&gt;#[cfg(windows)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;platform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;register_appbar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;isize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bar_height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;monitor_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;monitors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;enumerate_monitors&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// 🌟 パラメータ準備とAPI呼び出しは unsafe にする&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;unsafe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;abd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;APPBARDATA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;zeroed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;abd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cbSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;size_of&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;APPBARDATA&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SHAppBarMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABM_NEW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;abd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ABM_NEW failed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ORIGINAL_WNDPROC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GetWindowLongPtrW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GWL_WNDPROC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;// 🌟 Rust側の関数をコールバックするAPI（appbar_wndproc）&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;SetWindowLongPtrW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GWL_WNDPROC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;appbar_wndproc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;isize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// 🌟 Win32 APIに渡すコールバック関数なので、 extern &amp;quot;system&amp;quot; で呼び出し規約を揃える&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;unsafe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extern&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;system&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;appbar_wndproc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;HWND&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;wparam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;WPARAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;lparam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;LPARAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;LRESULT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Win32&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UI&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;WindowsAndMessaging&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;DefWindowProcW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wparam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lparam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 🌟 非Windows環境向けに、何もしないスタブを配置しておいてもよい&lt;/span&gt;
&lt;span class="cp"&gt;#[cfg(not(windows))]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;platform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Win32 API側からコールバックで呼び出す関数宣言には &lt;code&gt;extern "system"&lt;/code&gt; を付与するというのもポイントです。これは、RustコンパイラにWin32 APIの関数呼び出し規約に従うよう指示する宣言で、C++でマングリングを無効化したいとき&lt;sup id="fnref:extern-c"&gt;&lt;a class="footnote-ref" href="#fn:extern-c" title="Cのプログラムや、Cの呼び出し規約を通じて他の言語のプログラムからC++を呼び出す場合、 extern &amp;quot;C&amp;quot; でCのABIを提供する必要があります。"&gt;1&lt;/a&gt;&lt;/sup&gt;に &lt;code&gt;extern "C"&lt;/code&gt; を付けて関数定義するのと似ています。&lt;/p&gt;
&lt;p&gt;高校生の頃に、C++で頑張って（なんとBitBltで！）クラスマッチのタイムテーブル管理アプリを作ってからというもの、なんとなくWindows GUIプログラミングに少し苦手意識があったのですが、こんなに簡単ならまた何か作ってみてもいいかもしれません。&lt;/p&gt;
&lt;h2 id="_2"&gt;デスクトップに面白い文字を流そう&lt;/h2&gt;
&lt;p&gt;さて、ここからが本題です。LED News TickerやLED AppBarをニュースティッカーとして使えば、好きなテキストを目立つ位置に流し続けることができます。&lt;/p&gt;
&lt;p&gt;では、どんなテキストを流すのが面白いでしょうか？&lt;/p&gt;
&lt;h3 id="_3"&gt;ニュースのフィードを流す&lt;/h3&gt;
&lt;p&gt;ニュースティッカーにニュースを流すのはオーソドックスな使い方です。&lt;a href="https://www.nhk.or.jp/toppage/rss/index.html"&gt;NHKニュースのフィード&lt;/a&gt;や&lt;a href="https://g-tips.jp/google-news/how-to-get-google-news-rss/"&gt;Googleニュースのフィード&lt;/a&gt;を流しておけば、テレビでニュースを垂れ流すのと同じくらい、あるいはそれ以上にストレスなくシンプルにヘッドラインを知ることができます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ticker.samoyed.moe/ticker/?type=rss&amp;amp;url=https%3A%2F%2Fnews.web.nhk%2Fn-data%2Fconf%2Fna%2Frss%2Fcat0.xml"&gt;NHKニュースが流れるティッカー&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ticker.samoyed.moe/ticker/?type=rss&amp;amp;url=https%3A%2F%2Fnews.google.com%2Frss"&gt;Googleニュースが流れるティッカー&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これはかなり実用性が高いと思います。実際私もよく使っています。&lt;/p&gt;
&lt;h3 id="_4"&gt;インターネットのフィードを流す&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://b.hatena.ne.jp/"&gt;はてなブックマーク&lt;/a&gt;の人気エントリーや&lt;a href="https://gigazine.net/"&gt;GIGAZINE&lt;/a&gt;、その他インターネットで話題になりやすいサイトのフィードを流しておくと、わざわざX（旧Twitter）を眺めなくても、作業のついでにトレンドを追えるかもしれません。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ticker.samoyed.moe/ticker/?type=rss&amp;amp;url=https%3A%2F%2Fb.hatena.ne.jp%2Fhotentry.rss"&gt;はてなブックマークの人気エントリーが流れるティッカー&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ticker.samoyed.moe/ticker/?type=rss&amp;amp;url=https%3A%2F%2Fgigazine.net%2Fnews%2Frss_2.0%2F"&gt;GIGAZINEの新着記事が流れるティッカー&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ticker.samoyed.moe/ticker/?type=rss&amp;amp;url=https%3A%2F%2Fkyoko-np.net%2Findex.xml"&gt;虚構新聞の新着記事が流れるティッカー&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ただし、LED News Tickerはフィードのタイトルのみを流す仕組みなので、タイトルが内容の要約になっていないとか、クリックベイトじみた煽りタイトルの記事ばかりのフィードを流すのにはあまり向いていません。&lt;/p&gt;
&lt;p&gt;速報性の高いニュース記事の多くは伝えるべき情報をタイトルで提示してくれますし、本文はさほど重要ではないケースも多いです。一方で、コラムやエッセイ、読み物のような記事になると、タイトルでストーリーやオチまで説明されると野暮だと感じることさえあるでしょう。&lt;/p&gt;
&lt;p&gt;一度に十数文字しか流せない情報量の低さでもニュースティッカーが成り立つのは、タイトルで情報が完結するという仮定が前提になっています。インターネットのトレンドを流す場合は、なんとなく話題になっているキーワードを知る程度の使い方がちょうどいいかもしれません。&lt;/p&gt;
&lt;h3 id="_5"&gt;自分で用意したテキストをランダムに流す&lt;/h3&gt;
&lt;p&gt;ニュースやトレンドではなく、事前に用意したテキストを流すのもよい使い方です。よくSNSで見かけるbotのように、誰かの名言を流し続けるとか、ずかんのフレーバーテキスト集を作るとか、来週の資格試験のために暗記したい文章をずっと流しておくとか、聖書や日本国憲法からランダムに1フレーズを選んだりすることもできます。&lt;/p&gt;
&lt;p&gt;まず、改行区切りでテキストを記載した &lt;code&gt;data.txt&lt;/code&gt; を作りましょう:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ひとみを のぞきこむと じぶんの すがたが みえる。だが その かおは いつも すこしだけ わらっている。
だいじな ひとを わすれた にんげんの そばに あらわれる。かおりを かぐと なぜか なみだが でるという。
いちど なかまと みとめた あいてを にどと はなさない。トレーナーが きえても その ばしょで まわりつづける。
やくそくを やぶった にんげんを さがしだす。みつけると こゆびに あかい いとを まきつけて どこかへ つれさる。
はなびらが じかんごとに いちまいずつ ちる。さいごの いちまいが ちるとき なにが おこるかは だれも しらない。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;これを以下のスクリプト &lt;code&gt;random-words.mjs&lt;/code&gt; と同じフォルダに置いて実行します。30秒に一回のペースで &lt;code&gt;data.txt&lt;/code&gt; からランダムなテキストを&lt;a href="https://developer.mozilla.org/ja/docs/Web/API/Server-sent_events"&gt;SSE&lt;/a&gt;で配信する簡単なスクリプトです。SSEというのは、サーバからリアルタイムにコンテンツを配信する仕組みのひとつです。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// @ts-check&lt;/span&gt;
&lt;span class="c1"&gt;// Random words SSE Server&lt;/span&gt;
&lt;span class="c1"&gt;// Usage: node random-words.mjs&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;createServer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:http&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:fs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;POLL_INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LIST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data.txt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`http://127.0.0.1:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text/event-stream&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cache-Control&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;no-cache&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;keep-alive&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Access-Control-Allow-Origin&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;alive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;close&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;alive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LIST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LIST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`data: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;\n\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;polling error:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data: 情報取得に失敗しました\n\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;POLL_INTERVAL&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text/plain&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Random words SSE Server: http://127.0.0.1:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/amane-katagiri/2ff0d0195f2d2ed18ce6252e423fce16#file-random-words-mjs"&gt;random-words.mjs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Node.jsが入っていなければ、コマンドプロンプトあたりで &lt;code&gt;winget install -e --id OpenJS.NodeJS.LTS&lt;/code&gt; を実行すれば手軽にインストールできます。環境が準備できたら起動してみましょう:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="go"&gt;&amp;gt; node random-words.mjs&lt;/span&gt;
&lt;span class="go"&gt;Random words SSE Server: http://127.0.0.1:3000/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;同じフォルダにバッチファイル &lt;code&gt;random-words.bat&lt;/code&gt; を置いておけば、ワンタッチで起動できます:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;cd&lt;/span&gt; /d &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;%~dp0&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
node random-words.mjs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;起動後に&lt;a href="https://ticker.samoyed.moe/ticker/?type=sse&amp;amp;url=http%3A%2F%2Flocalhost%3A3000%2F"&gt;ローカルSSEが流れるティッカー&lt;/a&gt;を設定すると、用意したテキストが流れてくるはずです。トレイアイコンを右クリックで「Set Server...」からこのバッチファイルを指定すると、LED AppBarの起動と同時に常駐してくれるので便利です。文字化けしているようなら、 &lt;code&gt;data.txt&lt;/code&gt; がUTF-8で保存されているかを改めて確認してみてください。&lt;/p&gt;
&lt;h3 id="spotifynow-playing"&gt;SpotifyのNow Playingを流す&lt;/h3&gt;
&lt;p&gt;部屋に流れている曲の情報が流れるのって、音楽にこだわってる喫茶店みたいでおしゃれですね。Spotifyから再生中の曲を引くAPIは用意されているので、それをローカルに配信してLED AppBarが受け取れば簡単に表示できます。&lt;/p&gt;
&lt;p&gt;まず、&lt;a href="https://developer.spotify.com/"&gt;Spotify for Developers&lt;/a&gt;でAPIを利用するアプリケーションを登録しましょう。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;App name/App description/Website: なんでも&lt;/li&gt;
&lt;li&gt;Redirect URIs: &lt;code&gt;http://127.0.0.1:3000/callback&lt;/code&gt;&lt;sup id="fnref:spotify-callback"&gt;&lt;a class="footnote-ref" href="#fn:spotify-callback" title="http://[::1]:3000/callback も入れておくといいかもしれません。セキュリティ上のコーナーケースを潰すためか、localhost は使えないようです。"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Which API/SDKs are you planning to use?: Web API&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ここからClient IDとClient secretをコピーして、以下のスクリプト &lt;code&gt;spotify-now-playing.mjs&lt;/code&gt; を実行します。10秒に一回のペースで再生中の曲を取得してSSEで配信する簡単なスクリプトです。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// @ts-check&lt;/span&gt;
&lt;span class="c1"&gt;// Spotify Now Playing SSE Server&lt;/span&gt;
&lt;span class="c1"&gt;// Usage: node spotify-now-playing.mjs&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;createServer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;node:http&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;child_process&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SPOTIFY_CLIENT_ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;YOUR_CLIENT_ID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SPOTIFY_CLIENT_SECRET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;YOUR_CLIENT_SECRET&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;REDIRECT_URI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://127.0.0.1:3000/callback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SCOPES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;user-read-currently-playing user-read-playback-state&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;POLL_INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tokenExpiresAt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;basicAuth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;base64&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/**&lt;/span&gt;
&lt;span class="cm"&gt; * @param {string} code&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exchangeCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://accounts.spotify.com/api/token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;POST&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/x-www-form-urlencoded&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Authorization&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`Basic &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;basicAuth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;grant_type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;authorization_code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;redirect_uri&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;tokenExpiresAt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expires_in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ensureToken&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tokenExpiresAt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://accounts.spotify.com/api/token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;POST&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/x-www-form-urlencoded&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Authorization&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`Basic &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;basicAuth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;grant_type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;refresh_token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;tokenExpiresAt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expires_in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getNowPlaying&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ensureToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://api.spotify.com/v1/me/player/currently-playing&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Authorization&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`Bearer &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;204&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;artists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artists&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="cm"&gt;/** @type {{ name: string; }} */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;, &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; - &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;artists&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`http://127.0.0.1:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text/plain; charset=utf-8&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`GET http://127.0.0.1:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/now-playing から楽曲情報をSSEで配信します`&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`GET http://127.0.0.1:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/login からSpotifyで認証を行ってください`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/login&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;authUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://accounts.spotify.com/authorize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;response_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;client_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scope&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;redirect_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Location&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;authUrl.toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/callback&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text/plain; charset=utf-8&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;コードがありません&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exchangeCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text/plain; charset=utf-8&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="sb"&gt;`GET http://127.0.0.1:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/now-playing から楽曲情報をSSEで配信します`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/now-playing&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text/plain; charset=utf-8&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sb"&gt;`GET http://127.0.0.1:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/login からSpotifyで認証を行ってください`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text/event-stream&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cache-Control&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;no-cache&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;keep-alive&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Access-Control-Allow-Origin&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;alive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lastTrack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;close&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;alive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getNowPlaying&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lastTrack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`data: 再生中：&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;\n\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;lastTrack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;polling error:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;data: 楽曲情報の取得に失敗しました\n\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;POLL_INTERVAL&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text/plain&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Spotify Now Playing SSE Server: http://127.0.0.1:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sb"&gt;`http://127.0.0.1:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/login からSpotifyで認証を行ってください`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`start http://127.0.0.1:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/login`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/amane-katagiri/facc46bfd4709109e0ea46e8920b36f6#file-spotify-now-playing-mjs"&gt;spotify-now-playing.mjs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;では、動かしてみましょう。もし先に前節のランダムティッカーを起動していたら、そちらは止めてから実行してください。認証情報は以下のように環境変数で渡すか、スクリプトに直接記載してもいいです。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="go"&gt;&amp;gt; set SPOTIFY_CLIENT_ID=xxx&lt;/span&gt;
&lt;span class="go"&gt;&amp;gt; set SPOTIFY_CLIENT_SECRET=yyy&lt;/span&gt;
&lt;span class="go"&gt;&amp;gt; node spotify-now-playing.mjs&lt;/span&gt;
&lt;span class="go"&gt;Spotify Now Playing SSE Server: http://127.0.0.1:3000/&lt;/span&gt;
&lt;span class="go"&gt;http://127.0.0.1:3000/login からSpotifyで認証を行ってください&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;バッチファイル &lt;code&gt;spotify-now-playing.bat&lt;/code&gt; にするならこんな感じです:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;cd&lt;/span&gt; /d &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;%~dp0&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;SPOTIFY_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;xxx
&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;SPOTIFY_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;yyy
node spotify-now-playing.mjs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;起動したら画面の指示通りSpotifyでログインして、アプリケーションとの接続を許可します。許可後に&lt;a href="https://ticker.samoyed.moe/ticker/?type=sse&amp;amp;url=http%3A%2F%2Flocalhost%3A3000%2Fnow-playing"&gt;Now Playingが流れるティッカー&lt;/a&gt;を設定すると、「再生中：曲名 - アーティスト」のような表示が流れていくはずです。こちらも、トレイアイコンを右クリックで「Set Server...」からこのバッチファイルを指定できます。&lt;/p&gt;
&lt;h3 id="no-context"&gt;No Contextな文章を流す&lt;/h3&gt;
&lt;p&gt;文脈がよく分からない発言や意味のないテキストは、画面端を彩るインテリアにぴったりです。ここでは具体的な実装は示しませんが、以下のようなアイデアならすぐに実現できるでしょう。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wikipediaのランダム記事サマリ &lt;code&gt;https://ja.wikipedia.org/api/rest_v1/page/random/summary&lt;/code&gt; を流す&lt;sup id="fnref:wikipedia"&gt;&lt;a class="footnote-ref" href="#fn:wikipedia" title="冒頭の1～3文程度が入った extract を流すのがいい感じです。"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/rest/activity/events?apiVersion=2026-03-10"&gt;GitHubのパブリックイベント&lt;/a&gt; &lt;code&gt;https://api.github.com/events&lt;/code&gt; を取得して全世界のコミットメッセージを流す&lt;sup id="fnref:github-public-event"&gt;&lt;a class="footnote-ref" href="#fn:github-public-event" title="このAPIにはコミットメッセージが含まれていないので、さらに個別のコミット情報を取得する必要があります。ただしこの方式だと呼び出し回数がかさむので、アクセストークンでの認証をおすすめします。"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://misskey-hub.net/en/docs/for-developers/api/streaming/"&gt;MisskeyのStreaming API&lt;/a&gt; &lt;code&gt;wss://{host}/streaming&lt;/code&gt; からサンプリングした投稿を流す&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;もしコーディングが面倒なら、「30秒に一度localhost:3000にテキストデータをSSEで配信するNodeスクリプトを1ファイルで書いて。内容は～」とAIに指示すれば、好みのテキストを垂れ流すサーバのできあがりです。&lt;/p&gt;
&lt;h2 id="_6"&gt;これから……？&lt;/h2&gt;
&lt;p&gt;デスクトップに流したら面白い文字をいくつか紹介してきましたが、これらはほんの一例です。世界にはまだまだ面白い文字がたくさんあります！　あなたが今思い浮かべて思わずニヤニヤしているテキストも、フィードに出力したり簡単なSSEサーバを用意すればすぐに流すことができます。&lt;/p&gt;
&lt;p&gt;みなさんもぜひ、LEDマトリクスパネル風ライブ壁紙ツール「&lt;a href="https://ticker.samoyed.moe/"&gt;LED News Ticker&lt;/a&gt;」やLEDマトリクスパネル風ウィジェット「&lt;a href="https://github.com/vividoyomogimochi/app-top-bar"&gt;LED AppBar&lt;/a&gt;」で面白い文字を流してみてください。いいアイデアがあったら共有してくれると嬉しいです。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:extern-c"&gt;
&lt;p&gt;Cのプログラムや、Cの呼び出し規約を通じて他の言語のプログラムからC++を呼び出す場合、 &lt;code&gt;extern "C"&lt;/code&gt; でCのABIを提供する必要があります。&amp;#160;&lt;a class="footnote-backref" href="#fnref:extern-c" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:spotify-callback"&gt;
&lt;p&gt;&lt;code&gt;http://[::1]:3000/callback&lt;/code&gt; も入れておくといいかもしれません。セキュリティ上のコーナーケースを潰すためか、&lt;a href="https://developer.spotify.com/documentation/web-api/concepts/redirect_uri"&gt;&lt;code&gt;localhost&lt;/code&gt; は使えない&lt;/a&gt;ようです。&amp;#160;&lt;a class="footnote-backref" href="#fnref:spotify-callback" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:wikipedia"&gt;
&lt;p&gt;冒頭の1～3文程度が入った &lt;code&gt;extract&lt;/code&gt; を流すのがいい感じです。&amp;#160;&lt;a class="footnote-backref" href="#fnref:wikipedia" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:github-public-event"&gt;
&lt;p&gt;このAPIにはコミットメッセージが含まれていないので、さらに個別のコミット情報を取得する必要があります。ただしこの方式だと呼び出し回数がかさむので、アクセストークンでの認証をおすすめします。&amp;#160;&lt;a class="footnote-backref" href="#fnref:github-public-event" title="Jump back to footnote 4 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="tech"/></entry><entry><title>ぼくらの同人作品ビューア（概念）</title><link href="https://amanejp.netlify.app/post/oh-my-doujin-viewer/" rel="alternate"/><published>2026-03-27T12:14:00+09:00</published><updated>2026-03-27T12:14:00+09:00</updated><author><name>Amane Katagiri</name></author><id>tag:amanejp.netlify.app,2026-03-27:/post/oh-my-doujin-viewer/</id><summary type="html">&lt;p&gt;chkdrs: CHEck Digital Rights Sovereignty&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#_1"&gt;まえがき&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_2"&gt;同人作品ビューアを考える&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_3"&gt;同人作品ビューアを探す&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_4"&gt;同人作品ビューアを作る&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_5"&gt;あとがき&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="_1"&gt;まえがき&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://play.dlsite.com/"&gt;DLsite Play&lt;/a&gt;はかなり素敵な同人作品ビューアです。&lt;a href="https://www.dlsite.com/home/guide/dlplay"&gt;公式説明&lt;/a&gt;によれば、&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;購入した作品のZIPファイルをダウンロードや解凍する手間もなく、ブラウザ上ですぐに作品を閲覧できる配信方法です。&lt;br&gt;
パソコン、スマートフォン、タブレットなどブラウザとインターネットに接続できる環境さえあれば、いつでも手軽に作品を楽しむことができます。&lt;br&gt;
&lt;cite&gt;&lt;a href="https://www.dlsite.com/home/guide/dlplay"&gt;ブラウザ視聴について（DLsite Play）&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;とのことで、2015年の提供開始&lt;sup id="fnref:honjp"&gt;&lt;a class="footnote-ref" href="#fn:honjp" title="DLsite、電子書籍や音声／音楽作品を視聴できるWebブラウザビューワ「DLsite Play」を提供開始 | HON.jp News Blog"&gt;1&lt;/a&gt;&lt;/sup&gt;からDLsiteユーザーの視聴体験を支え続けています。同人作品をブラウザ上で視聴できるというサービスは、当時大学生になったばかりの私にとっては相当な衝撃でした。どうにか同じようなアプリを自分で作れないものか、と友人と議論したのを覚えています。その時はシンプルな構成に収めようと、オンデマンドでZIPを解凍できるnginxモジュールを見つけて試したものでした。&lt;/p&gt;
&lt;p&gt;2015年前後は、他サービスでも電子書籍のビューアを提供し始めたり、音楽のストリーミングサービスが参入し始める頃でしたが、多様な作品形式に対応していた点でDLsiteは一歩先を進んでいたといえます。それから10年経った2025年にも、&lt;a href="https://www.dlsite.com/modpub/dlplaybox/"&gt;DLsite GamePlay&lt;/a&gt;というスマホでPCゲームを遊べるストリーミングサービスを開始しており、かれらがいかにストレスなくコンテンツを楽しむ環境を重視しているかが分かるでしょう。&lt;/p&gt;
&lt;p&gt;さて、DLsite Playはかなり素敵な同人作品ビューアですが、大きな問題点があります。それは、作品購入後であっても、サークルが退会したり販売終了したら読めなくなること。そして、DLsiteで購入した作品しか読めないことです。DLsiteというコンテンツ販売サービスを支えるためのビューアなので当たり前ではあるものの、この素敵な同人作品ビューア体験と引き換えに、過去の傑作がZIP倉庫&lt;sup id="fnref:zip-archive"&gt;&lt;a class="footnote-ref" href="#fn:zip-archive" title="販売終了作品もDRMさえなければZIPファイルとして手元に残すことはできますが、手軽なビューアに比べて検索が面倒だし開くのも手間がかかります。すぐに手を伸ばせる本棚ではなく、押し入れの段ボールに保管されているイメージで「ZIP」の「倉庫」と表現しました。"&gt;2&lt;/a&gt;&lt;/sup&gt;に押しやられるのを受け入れるわけにはいきません。&lt;/p&gt;
&lt;p&gt;私が大学生の時分でDLsite Playクローンを作ろうと思ったのも、おおよそこういう事情によるものです。しかし、その夢は具体的な行動を伴わないまま10年ほどが経ちました。幸いにも、最近はこうした同人作品にも使えそうなOSSのビューアがいくつか出ていますし、今はClaude Codeが隔離されたWSLの上で動いているので、あの頃よりは理想を形にできそうです。&lt;/p&gt;
&lt;h2 id="_2"&gt;同人作品ビューアを考える&lt;/h2&gt;
&lt;p&gt;私が目指しているのは完全なDLsite Playクローンではありません。DLsite Playとは見た目や機能が似ていなくても、同人作品ビューア（あるいは同人作品管理システム）に必要な機能を備えていて、それが手元のコンテンツで動けばよいのです。&lt;/p&gt;
&lt;p&gt;しかしこれは、実は完全なDLsite Playクローンを実現するより難しい取り組みです。私が同人作品ビューアに何を求めていて、何があれば使いやすいのか、あるいはあっても意味のない機能とは？　それが分からないまま進めるとどうなるか――本当に必要な機能が揃っているミニマルなビューアを見逃してしまう。なんとなく機能が多い無料のビューアを入れてもイマイチ使いづらい。それなら、とClaude Codeに全部盛りのシステムを作ってもらったのに3日で使わなくなる――せっかくのAI最新技術もこれでは無意味です。&lt;/p&gt;
&lt;p&gt;こうした結末を防ぐために、仮に呼ぶとすれば「ぼくらの同人作品ビューア（概念）」がどんな姿をしているかを、事前によく想像しなければいけません。&lt;/p&gt;
&lt;p&gt;DLsite Playはかなり素敵な同人作品ビューアで、こうした概念を取り出すのに適した題材です。改めて公式説明やDLsite Play自体を眺めて、DLsite Playのいいところ・よくないところを挙げてみましょう。好みや慣れに過ぎないような点、DLsite Playに限らない特徴もとりあえず書いておきます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;画像・PDF・音声・動画・テキストなど様々な形式のコンテンツを1つの画面で閲覧できる。&lt;/li&gt;
&lt;li&gt;作品名とサークル名や作品形式が表示されており、必要に応じて絞り込める。&lt;/li&gt;
&lt;li&gt;作品の購入日が記録されており、作品名やサークル名と同様にソートできる。&lt;/li&gt;
&lt;li&gt;階層構造を持ったフォルダを閲覧できるシンプルなファイルマネージャ&lt;sup id="fnref:file-manager"&gt;&lt;a class="footnote-ref" href="#fn:file-manager" title="Windowsにおけるエクスプローラ、macOSにおけるFinderなど。ファイラー。"&gt;3&lt;/a&gt;&lt;/sup&gt;として扱える。&lt;/li&gt;
&lt;li&gt;フラットな同人作品の一覧と、階層構造を持つ同人作品フォルダの扱いを分離している。&lt;/li&gt;
&lt;li&gt;PDFをスクロール可能な連続したページではなく、1枚ずつ画像リストとして表示できる。&lt;/li&gt;
&lt;li&gt;音声はオーバーレイのキューで再生され、音声を聞きながら別の作品を読める。&lt;/li&gt;
&lt;li&gt;コンテンツが配信用に最適化されており、ローディングの待ち時間を意識することが少ない。&lt;/li&gt;
&lt;li&gt;よく使う機能はメニューバーに押し込めずに常に表示する一方で、細かいフィルタやソートも用意されており、UIのバランスがよい。&lt;/li&gt;
&lt;li&gt;レスポンシブデザインに対応しており、スマートフォンでも快適に閲覧できる&lt;sup id="fnref:dlsite-mobile-app"&gt;&lt;a class="footnote-ref" href="#fn:dlsite-mobile-app" title="スマートフォン向けにはアプリとしてDL Library（画像・PDF・テキスト作品向け）やDLsite Sound（音声作品向け）も提供されています。"&gt;4&lt;/a&gt;&lt;/sup&gt;。&lt;/li&gt;
&lt;li&gt;オフラインでの閲覧手段がある。オフラインキャッシュとして、あるいは作品のZIPファイルをそのままダウンロードできる。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;よくないところは前述の通り、DLsiteで配信中の作品しか読めない点と、別のプラットフォームや自前で用意したコンテンツを登録できない点が最も大きいです。また、サークル側が手軽にDRMをかけられるようになっていて、購入者の所有権を軽視する傾向がありますが、これはDLsite Play自体のデメリットではありません。&lt;/p&gt;
&lt;p&gt;このような点を踏まえて、概念上の同人作品ビューアが備えるべき特徴を考えてみます。みなさんもやってみましょう。あったら嬉しいな、ではなく、これがなければ3日と経たずに捨ててしまうようなコア機能を探すのを意識してください。&lt;/p&gt;
&lt;p&gt;そして、いろいろ探しながら私が辿り着いた最小限の「ぼくらの同人作品ビューア（概念）」は、「作品フォルダ指向のプレビュー付きファイルマネージャ」になりました。DLsite Playの特徴を参考にもう少し具体的に記述すると、以下のような感じです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;フラットな同人作品フォルダの一覧と、階層構造を持った同人作品フォルダの2つを完全に区別して表示できる。深い階層の作品を閲覧していても、すぐに同人作品の一覧に戻れる。&lt;/li&gt;
&lt;li&gt;同人作品フォルダはファイルシステム上のファイル名とは異なる作品名を表示できる。ファイルシステムの制約を受けずに作品の名前を自由に設定できる。&lt;/li&gt;
&lt;li&gt;画像・PDF・音声・動画・テキストなど様々な形式のコンテンツを新しいタブやウィンドウを開かずに閲覧できる。前後のファイルに移動したり再生状態を操作できる最低限のUIを持つ。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;検索や絞り込み、購入日順ソート、サムネイル表示など実際に必要な機能は他にもいろいろありますが、最小限のコア機能はここだと思います。作品単位でとりあえず手軽にコンテンツを眺める、という使い方を想像してください。&lt;/p&gt;
&lt;p&gt;コア機能のスコープが定まったので、次は既存のソフトウェアでこれを実現できないか、という論点に移りましょう。&lt;/p&gt;
&lt;h2 id="_3"&gt;同人作品ビューアを探す&lt;/h2&gt;
&lt;p&gt;改めて、「ぼくらの同人作品ビューア（概念）」を示します。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;フラットな同人作品フォルダの一覧と、階層構造を持った同人作品フォルダの2つを完全に区別して表示できる。&lt;/li&gt;
&lt;li&gt;同人作品フォルダはファイルシステム上のファイル名とは異なる作品名を表示できる。&lt;/li&gt;
&lt;li&gt;画像・PDF・音声・動画・テキストなど様々な形式のコンテンツを新しいタブやウィンドウを開かずに閲覧できる。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;この程度なら少し探せばあるんじゃない？と思うかもしれませんが、全てにマッチするアプリケーションはなかなか見つかりませんでした。&lt;/p&gt;
&lt;p&gt;たとえば、Windowsのエクスプローラは、Alt+Pでプレビューウィンドウが使えるので3を満たしそうですが、実際はPDFは冒頭数ページだけ、音声や動画は外部アプリを開くので使い物になりません。1は全く区別できませんし、2ももちろんありません。&lt;/p&gt;
&lt;p&gt;この基準で、いくつか同人作品ビューアとして使えそうなアプリケーションを眺めてみましょう。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;アプリ&lt;/th&gt;
&lt;th&gt;カテゴリ&lt;/th&gt;
&lt;th align="center"&gt;作品リスト&lt;/th&gt;
&lt;th align="center"&gt;フォルダ階層&lt;/th&gt;
&lt;th align="center"&gt;別名表示&lt;/th&gt;
&lt;th align="center"&gt;マルチメディア&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DLsite Play&lt;/td&gt;
&lt;td&gt;ブラウザ&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/Difegue/LANraragi"&gt;LANraragi&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ブラウザ/画像&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;△&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/gotson/komga"&gt;Komga&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ブラウザ/画像&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;△&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/Mangatsu"&gt;Mangatsu&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ブラウザ/画像&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;△&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/kikoeru-project/kikoeru-express"&gt;Kikoeru&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ブラウザ/音声&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;△&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/advplyr/audiobookshelf"&gt;Audiobookshelf&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ブラウザ/音声&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;△&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/stashapp/stash"&gt;Stash&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ブラウザ/動画&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;△&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/nextcloud/server"&gt;Nextcloud&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ブラウザ&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/gtsteffaniak/filebrowser"&gt;FileBrowser Quantum&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ブラウザ&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/9001/copyparty"&gt;copyparty&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ブラウザ&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows Explorer&lt;/td&gt;
&lt;td&gt;ローカル&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;△&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.gpsoft.com.au/"&gt;Directory Opus&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ローカル&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.xnview.com/en/xnview-mp/"&gt;XnView MP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ローカル/画像&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;△&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/neelabo/NeeView"&gt;NeeView&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ローカル/画像&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;△&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/hydrusnetwork/hydrus"&gt;Hydrus Network&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ローカル&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;-&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":white_check_mark:" class="emoji" height="16" src="/emojis/2705.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;△&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;マルチメディア列の△は、おおよそ一部の形式での使用に特化しており、他形式は未対応または簡易的なビューアのみ提供されているものです。&lt;/p&gt;
&lt;p&gt;これらを大きく分類すると、以下の4つのタイプがありそうです。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;タイプ&lt;/th&gt;
&lt;th align="center"&gt;作品フォルダ指向&lt;/th&gt;
&lt;th align="center"&gt;フォルダ階層&lt;/th&gt;
&lt;th align="center"&gt;マルチメディア&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DLsite Play&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":o:" class="emoji" height="16" src="/emojis/2b55.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":o:" class="emoji" height="16" src="/emojis/2b55.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":o:" class="emoji" height="16" src="/emojis/2b55.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;コミックを想定したビューア&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":o:" class="emoji" height="16" src="/emojis/2b55.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":x:" class="emoji" height="16" src="/emojis/274c.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":x:" class="emoji" height="16" src="/emojis/274c.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ファイルマネージャ系&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":x:" class="emoji" height="16" src="/emojis/274c.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":o:" class="emoji" height="16" src="/emojis/2b55.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":o:" class="emoji" height="16" src="/emojis/2b55.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;階層表示可能なビューア&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":o:" class="emoji" height="16" src="/emojis/2b55.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":o:" class="emoji" height="16" src="/emojis/2b55.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":x:" class="emoji" height="16" src="/emojis/274c.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;これを見ると、必要な要素を全て満たすアプリケーションはほとんどないことが分かります。あんまり意味はないですが、一応MECEにしておきましょうか。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;タイプ？&lt;/th&gt;
&lt;th align="center"&gt;作品フォルダ指向&lt;/th&gt;
&lt;th align="center"&gt;フォルダ階層&lt;/th&gt;
&lt;th align="center"&gt;マルチメディア&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;弱いファイルマネージャ？&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":x:" class="emoji" height="16" src="/emojis/274c.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":o:" class="emoji" height="16" src="/emojis/2b55.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":x:" class="emoji" height="16" src="/emojis/274c.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;強いスライドショーアプリ？&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":o:" class="emoji" height="16" src="/emojis/2b55.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":x:" class="emoji" height="16" src="/emojis/274c.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":o:" class="emoji" height="16" src="/emojis/2b55.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;強いビューア？&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":x:" class="emoji" height="16" src="/emojis/274c.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":x:" class="emoji" height="16" src="/emojis/274c.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":o:" class="emoji" height="16" src="/emojis/2b55.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;弱いビューア？&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":x:" class="emoji" height="16" src="/emojis/274c.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":x:" class="emoji" height="16" src="/emojis/274c.png" width="16"&gt;&lt;/td&gt;
&lt;td align="center"&gt;&lt;img alt=":x:" class="emoji" height="16" src="/emojis/274c.png" width="16"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;これらの分析をまとめると、「ぼくらの同人作品ビューア（概念）」に求められるのは「作品フォルダの管理機能があるファイルマネージャ」とか「多くのメディアに対応した階層表示可能なビューア」といったものになりそうです。前節での定義とほとんど変わりませんが、既存のアプリケーションとの比較でもう少し具体的な姿を描くことができました。&lt;/p&gt;
&lt;p&gt;ここまで言語化できればしめたものです。問題は、この条件を満たすアプリケーションが見つからなかったことくらいでしょうか。&lt;/p&gt;
&lt;h2 id="_4"&gt;同人作品ビューアを作る&lt;/h2&gt;
&lt;p&gt;結局、いい感じの同人ビューアが見つからなかったので作ることにしました。デモ版を&lt;a href="https://chkdrs-demo.amane.moe/"&gt;chkdrs-demo.amane.moe&lt;/a&gt;に置いてあるので、興味のある方は触ってみてください。 &lt;code&gt;demo1:demodemo1&lt;/code&gt; ～ &lt;code&gt;demo9:demodemo9&lt;/code&gt; でログインできます。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://chkdrs-demo.amane.moe/"&gt;&lt;img alt="chkdrsのデモ画面" height="500" src="/images/oh-my-doujin-viewer/chkdrs-demo.png" width="750"&gt;&lt;/a&gt;&lt;br&gt;
&lt;cite&gt;&lt;a href="https://commons.wikimedia.org/wiki/File:Ghostwriter_digital.png"&gt;Ghostwriter digital.png&lt;/a&gt; is licensed under a &lt;a href="https://creativecommons.org/licenses/by-sa/4.0/"&gt;CC BY-SA 4.0&lt;/a&gt;.&lt;/cite&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/tokio-rs/axum"&gt;axum&lt;/a&gt;&lt;sup id="fnref:axum"&gt;&lt;a class="footnote-ref" href="#fn:axum" title="Rust製のウェブアプリケーションフレームワーク。今回はAPIサーバとして使用しました。"&gt;5&lt;/a&gt;&lt;/sup&gt;のバックエンドと&lt;a href="https://github.com/sveltejs/kit"&gt;SvelteKit&lt;/a&gt;&lt;sup id="fnref:svelte-kit"&gt;&lt;a class="footnote-ref" href="#fn:svelte-kit" title="柔軟性があり高速なコンポーネントフレームワークのSvelteを使って構築されたフロントエンドフレームワーク。"&gt;6&lt;/a&gt;&lt;/sup&gt;のフロントエンドに、ローカルでの作品管理にも対応できるように&lt;a href="https://github.com/tauri-apps/tauri"&gt;Tauri&lt;/a&gt;&lt;sup id="fnref:tauri"&gt;&lt;a class="footnote-ref" href="#fn:tauri" title="Rust製のデスクトップ/モバイルアプリケーションフレームワーク。今回はモバイルアプリは対象外です。"&gt;7&lt;/a&gt;&lt;/sup&gt;でデスクトップモードも用意したクロスプラットフォームアプリです。コーディングの多くはClaude Codeが担当していますが、私の目でチェックしたり口を出したりするときのことを考えて、慣れているRustにしました。まだ開発中ですが、既に3つのコア機能は満たしています。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;フラットな同人作品フォルダの一覧と、階層構造を持った同人作品フォルダの2つを完全に区別して表示できます。&lt;/li&gt;
&lt;li&gt;同人作品フォルダはファイルシステム上のファイル名とは異なる作品名を表示できます。&lt;/li&gt;
&lt;li&gt;画像・PDF・音声・動画・テキストなど様々な形式のコンテンツをビューアペインで開けます。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;さらに、利便性を高める以下の機能も実装しました。これがコア機能なのか追加機能なのかを区別できるのも、今回の分析あってこそです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;カタログ機能（作品を横断的にまとめるコレクション）&lt;/li&gt;
&lt;li&gt;プレイリスト機能（ファイル単位で追加できるシーケンシャルなリスト）&lt;/li&gt;
&lt;li&gt;簡易的なユーザ管理機能&lt;/li&gt;
&lt;li&gt;サムネイル自動生成機能&lt;/li&gt;
&lt;li&gt;配信用軽量メディア（画像リサイズなど）生成機能&lt;/li&gt;
&lt;li&gt;汎用ファイルキャッシュ機能&lt;/li&gt;
&lt;li&gt;ZIPダウンロード機能&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;まだジャンルやタグを利用できなかったり、検索機能が弱かったり、ブラウザ側から作品をアップロードする機能もありませんが、裏を返せば私にとってはそれほど重要な機能ではないということです。なんでもかんでもあれば嬉しいわけでもないので、必要に応じて少しずつ拡張していく予定です。&lt;/p&gt;
&lt;h2 id="_5"&gt;あとがき&lt;/h2&gt;
&lt;p&gt;DLsite Playはかなり素敵な同人作品ビューアです。しかし、DLsiteの外側を見てくれません。他にもたくさんの同人作品ビューアがあります。しかし、それぞれ異なる欠点を抱えていました。結局のところ、全てを解決できる選択肢はないようです。Claudeにこのトレードオフについて相談してみましたが、「画像ビューアと音声ビューアを別々に立てて使うといいよ☆」なんて無茶な提案でお茶を濁してきます。なんてこった。&lt;/p&gt;
&lt;p&gt;そのため、今回は同人作品ビューアとして最も参考になるDLsite Playの機能面を掘り下げ、類似の既存アプリを調査した上で自作することにしました。自作に至るまでの過程は地味で苦しいものです――あれを入れてこれを入れて動かしたけど、結局ダメそう。設定とかカスタマイズの範囲でなんとかならないかな？　いろいろ試したけどやっぱ無理――じゃあ、自作しかないか。&lt;/p&gt;
&lt;p&gt;希望を満たすものがなければオーダーメイドで作るというのは、ソフトウェア以外でも当たり前の選択肢です。ただし、これも手間と機能のトレードオフになっています。AIエージェントを使えば時間や手間を大きく削減できるとはいえ、実装の責任はやはり自分にあります。実験的なプロジェクトや趣味のプログラミングでもない限り、自作せずに済むなら自作せずに完成度の高い既存のものを使う方が、楽だし安全だし経済的です。&lt;/p&gt;
&lt;p&gt;AIエージェントの発展で、技術的詳細をまるで気にすることなく「あれと同じの作って」「最強の◯◯アプリ作って」なんて一言指示すれば、誰でもそれっぽい見た目のそれっぽいアプリができるようになりました。さらに、それを「オレが設計して作ったんだぜ」なんて自慢して回りたくなる誘惑も増しています。なんてったって見た目はすごくいいし、機能も全部盛りだからです。&lt;/p&gt;
&lt;p&gt;しかし、要件も曖昧なままなんとなく便利機能をリストアップして作られた見た目だけのアプリケーションは、周囲はもちろん作った本人でさえ魅力を感じないはずです。本人に残っているのはAIガチャを引く高揚感の思い出だけ。ただの習作のTODOアプリが誰にも使われないのと同じように、AIガチャから得た興奮に利便性を見出す人はいません。&lt;/p&gt;
&lt;p&gt;既存アプリを調査してまだないものを作る、なんてソフトウェア開発では当たり前のように思えますが、AIエージェントを持った高揚感で全て自作で解決する選択肢しか見えなくなる人も増えています。もちろん、自作の全てが悪いわけではないですが、出来のいい先行研究を知らないとまともな結果が出せないというのは、AIがあろうがなかろうが同じことです。&lt;/p&gt;
&lt;p&gt;その取り組みの一例として、今回は欲しいものの概念を粘土のようにこね上げてから、最終的に自作の同人作品ビューアを選んだ過程について書いてみました。参考になれば幸いです。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:honjp"&gt;
&lt;p&gt;&lt;a href="https://hon.jp/news/1.0/0/6416"&gt;DLsite、電子書籍や音声／音楽作品を視聴できるWebブラウザビューワ「DLsite Play」を提供開始 | HON.jp News Blog&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:honjp" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:zip-archive"&gt;
&lt;p&gt;販売終了作品もDRMさえなければZIPファイルとして手元に残すことはできますが、手軽なビューアに比べて検索が面倒だし開くのも手間がかかります。すぐに手を伸ばせる本棚ではなく、押し入れの段ボールに保管されているイメージで「ZIP」の「倉庫」と表現しました。&amp;#160;&lt;a class="footnote-backref" href="#fnref:zip-archive" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:file-manager"&gt;
&lt;p&gt;Windowsにおけるエクスプローラ、macOSにおけるFinderなど。ファイラー。&amp;#160;&lt;a class="footnote-backref" href="#fnref:file-manager" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:dlsite-mobile-app"&gt;
&lt;p&gt;スマートフォン向けにはアプリとしてDL Library（画像・PDF・テキスト作品向け）やDLsite Sound（音声作品向け）も提供されています。&amp;#160;&lt;a class="footnote-backref" href="#fnref:dlsite-mobile-app" title="Jump back to footnote 4 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:axum"&gt;
&lt;p&gt;Rust製のウェブアプリケーションフレームワーク。今回はAPIサーバとして使用しました。&amp;#160;&lt;a class="footnote-backref" href="#fnref:axum" title="Jump back to footnote 5 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:svelte-kit"&gt;
&lt;p&gt;柔軟性があり高速なコンポーネントフレームワークのSvelteを使って構築されたフロントエンドフレームワーク。&amp;#160;&lt;a class="footnote-backref" href="#fnref:svelte-kit" title="Jump back to footnote 6 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:tauri"&gt;
&lt;p&gt;Rust製のデスクトップ/モバイルアプリケーションフレームワーク。今回はモバイルアプリは対象外です。&amp;#160;&lt;a class="footnote-backref" href="#fnref:tauri" title="Jump back to footnote 7 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="tech"/></entry><entry><title>ははいぬ・けつあつ・くちかせ――日本語のhuman-readable nameを考える</title><link href="https://amanejp.netlify.app/post/human-readable-name/" rel="alternate"/><published>2026-03-09T23:44:00+09:00</published><updated>2026-03-09T23:44:00+09:00</updated><author><name>Amane Katagiri</name></author><id>tag:amanejp.netlify.app,2026-03-09:/post/human-readable-name/</id><summary type="html">&lt;p&gt;りと・ことこ・まり&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#docker-run"&gt;docker run ...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dd-ifdevrandom"&gt;dd if=/dev/random ...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ssh-keygen-lv"&gt;ssh-keygen -lv ...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#shuf-n-3-words"&gt;shuf -n 3 words...&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#1"&gt;① 赤い・青い・黄色い・きりん・ぞう...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#2-cat-core_lexcsv"&gt;② cat core_lex.csv ...&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#3"&gt;③ さやか・ほむら・りんこ・まなか・かえで...&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#logout"&gt;logout ...&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="docker-run"&gt;&lt;code&gt;docker run ...&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Dockerでコンテナを起動するとき、 &lt;code&gt;--name&lt;/code&gt; を与えないとランダムな英単語の組み合わせで名前が付くことはよく知られています。これはコンテナIDやUUIDのような完全にランダムな16進数ではなく、人間でも読みやすく見分けやすいIDを与える仕組みです。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;hello-world

Hello&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;Docker!
...

$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;--format&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;table {{.ID}} {{.Names}}&amp;quot;&lt;/span&gt;
CONTAINER&lt;span class="w"&gt; &lt;/span&gt;ID&lt;span class="w"&gt; &lt;/span&gt;NAMES
f194dc6f87b5&lt;span class="w"&gt; &lt;/span&gt;wonderful_kowalevski
ff656b2992d0&lt;span class="w"&gt; &lt;/span&gt;kind_chaplygin
130258500f29&lt;span class="w"&gt; &lt;/span&gt;festive_nash
5bcc03f329fe&lt;span class="w"&gt; &lt;/span&gt;clever_bardeen
977e200749ca&lt;span class="w"&gt; &lt;/span&gt;hopeful_moore
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;この名前の生成ロジックは、&lt;a href="https://github.com/moby/moby/blob/master/internal/namesgenerator/names-generator.go"&gt;namesgenerator&lt;/a&gt;というパッケージで提供されています。ハードコードされた形容詞（108個）と著名人の姓（236個）から（非暗号論的な）乱数を元に1つずつ選び、 &lt;code&gt;_&lt;/code&gt; で接続するというシンプルな実装です。&lt;/p&gt;
&lt;p&gt;ちなみに、このパッケージはもともと外部参照可能なpkgにあったのですが、&lt;a href="https://github.com/moby/moby/pull/43210"&gt;Freeze the namesgenerator package against new additions&lt;/a&gt;というPRをきっかけに単語リストが凍結されて、最終的に内部専用のinternalに移動しました。これらの単語リストのスラング的な意味合いや人物リストの評価で物議を醸す事態が長年にわたって続いており、メンテナンスの負担が高くなったことが主な理由だと、同PRや古い&lt;a href="https://pkg.go.dev/github.com/docker/docker/pkg/namesgenerator"&gt;namesgeneratorパッケージ&lt;/a&gt;の説明に記されています。&lt;/p&gt;
&lt;h2 id="dd-ifdevrandom"&gt;&lt;code&gt;dd if=/dev/random ...&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;さて、このような人間可読で（本来は）意味を持たない単語の組み合わせをhuman-readable nameと呼ぶことにしましょう。human-readable nameの決定には、以下の要素が必要です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1種類以上の空でない単語リスト（例: 形容詞のリストと著名人の姓のリスト）&lt;/li&gt;
&lt;li&gt;単語を結合する文字（例: &lt;code&gt;_&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;各単語リストから単語を取り出す1つ以上の位置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このうち、単語リストのインデックスを取り出す方法には大きく分けて2つあります。まずはDockerと同じく乱数プールから取り出したランダムなインデックス、もう1つは対象のデータに基づく決定的な手法です。&lt;/p&gt;
&lt;p&gt;たとえば、あるIDと数値を持つオブジェクトにhuman-readable nameを付けるとき、JSON表現が &lt;code&gt;{"key":"answer","value":42}&lt;/code&gt; なら必ず &lt;code&gt;brilliant_jerk&lt;/code&gt; という名前にすることもできます。単純な実装では、SHA-256をとって前半と後半のバイトの剰余を取れば、決定的なインデックスを得られるでしょう。&lt;/p&gt;
&lt;p&gt;human-readable nameから元のオブジェクトを計算できるわけではないので、役に立つシーンはそう多くなさそうですが、人間がバイト列の一致を視覚的に見分けるための表現としてはとても役に立ちます。&lt;/p&gt;
&lt;h2 id="ssh-keygen-lv"&gt;&lt;code&gt;ssh-keygen -lv ...&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;実際のところ、human-readableといわれる名前は全ての地域や文化圏に共通の概念ではありません。冒頭のDocker出力例にあった &lt;code&gt;kowalevski&lt;/code&gt; を一目で&lt;a href="https://ja.wikipedia.org/wiki/%E3%82%BD%E3%83%95%E3%82%A3%E3%82%A2%E3%83%BB%E3%82%B3%E3%83%AF%E3%83%AC%E3%83%95%E3%82%B9%E3%82%AB%E3%83%A4"&gt;ソフィア・コワレフスカヤ&lt;/a&gt;のことだと理解するのは多くの日本人には難しく、これなら「不思議なソフィア」なんて文字列の方が読みやすく覚えやすいはずです。&lt;/p&gt;
&lt;p&gt;もちろん、このような名前はASCII文字で構成するのがある種の暗黙的了解であり、human-readable nameが登場しうるほとんどの場所で安全に使えることは重要な利点です。しかし、もともとhuman-readable nameは人間が触れるインターフェースを構成する代替手段のはずでした。世界をUnicodeが支配した今となっては、既に多くの環境で問題なく扱えるとみなしても問題ないでしょう。&lt;/p&gt;
&lt;p&gt;このようなIDの代替表現という目的をさらに広く捉えると、ASCII文字の単語の組み合わせに限らず、バイト列をさらに読みやすく置き換える表現を活用している例はたくさんあります。&lt;/p&gt;
&lt;p&gt;秘匿性の高い通信アプリSignalにおいて、お互いに意図した相手と通信しているかを示すSafety Numbersは、16進数のハッシュを60桁の数字で表現して一致を確認しやすくする仕組みです。&lt;/p&gt;
&lt;p&gt;OpenSSHで公開鍵のフィンガープリントを示す際に使われる二次元的なパターンも、randomartと呼ばれるhuman-readableな表現です。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;--[ED25519 256]--+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oB&lt;/span&gt;&lt;span class="o"&gt;+*&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=+&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;....&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ooEoo&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|+**+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;----[SHA256]-----+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;分散型の通信プロトコロルのMatrixでは、Signalと同様のMITM対策の検証としてSAS（Short Authentication String）が導入されており、対応しているクライアントでは事前に定義された64個の絵文字の組み合わせで一致を確認できます。数字の羅列よりもさらに人間の視覚に合わせた仕組みだと言えるでしょう。&lt;/p&gt;
&lt;p&gt;&lt;img alt="MatrixクライアントのElementで検証用の絵文字が7個表示されている様子" height="350" src="/images/human-readable-name/matrix.png" width="750"&gt;&lt;/p&gt;
&lt;p&gt;認証の分野から離れると、what3wordsもhuman-readableな表現の重要な実装のひとつです。このサービスは、3メートル四方に区切った地図の各領域に3つの単語を与えて、位置を共有できるようにしたものです。what3wordsの位置は &lt;code&gt;///&lt;/code&gt; というプレフィックスで始まり、たとえば「///りゅっく。かたづけ。しぶる」のように示します。同じ位置で複数の言語にマッピングされており、英語では「///grocers.powerful.wanted」、韓国語では「///황태.늦잠.배우자」、クメール語では「///សេីច.ជាថ្មី.ត្រឹមតែ」というように、Dockerの名前生成が抱えていた文化的・言語的問題をある程度解決しています。&lt;/p&gt;
&lt;p&gt;&lt;img alt="what3wordsで海芝浦駅の位置をスウェーデン語で示している様子" height="438" src="/images/human-readable-name/what3words.png" width="750"&gt;&lt;/p&gt;
&lt;h2 id="shuf-n-3-words"&gt;&lt;code&gt;shuf -n 3 words...&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;さて、これらの多様なhuman-readable表現を参考に、改めて日本語のhuman-readable nameについて考えましょう。必要なのは以下の要素でした。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1種類以上の空でない単語リスト&lt;/li&gt;
&lt;li&gt;単語を結合する文字&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;結合文字は &lt;code&gt;・&lt;/code&gt; や &lt;code&gt;、&lt;/code&gt; がいいでしょう。「ははいぬ・けつあつ・くちかせ」のような感じですね。日本語ならスペース &lt;code&gt;&lt;/code&gt; でも見栄えはいいですが、シェルやクエリで使うにはあまりに区切り文字としての意味が強すぎます。&lt;/p&gt;
&lt;p&gt;ここまで決めれば、あとは入れ替え可能な単語リストを用意してリポジトリに含めるだけです。たとえば、単語リストが1種類なら以下でhuman-readable nameを生成できます。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;shuf&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;path/to/words.txt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;paste&lt;span class="w"&gt; &lt;/span&gt;-sd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s/_/・/g&amp;#39;&lt;/span&gt;
ははいぬ・けつあつ・くちかせ
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;というわけで、最後に日本語の単語リストを決めましょう。この単語がhuman-readable nameの印象を大きく変えるので、丁寧に検討する必要があります。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;user: ランダムな人間向けのIDを生成するのに向いた日本語の単語リストって、どこかで配布されてる？&lt;br&gt;
assistant: 日本語だと...正直、ちゃんと整備された「日本語IDジェネレーター向けワードリスト」って意外と少ないんだよね😅&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;……えーっと、どうやって集めますか？&lt;/p&gt;
&lt;p&gt;Dockerレベルのランダム生成で25000パターンくらいなので、2つの組み合わせなら160単語くらい必要です。でも、3つ取り出すなら30単語くらいあれば足りるでしょう。30単語を3種類用意しても90単語なので、実はあまり難しいものではありません。&lt;/p&gt;
&lt;p&gt;ただし、過去に使ったhuman-readable nameをずっと保持する設計であれば、かなり心許ないパターン数になります。&lt;/p&gt;
&lt;h3 id="1"&gt;① 赤い・青い・黄色い・きりん・ぞう...&lt;/h3&gt;
&lt;p&gt;思い付いた単語を順番に書いていくんですね！　30単語くらいなら自分で書き出したり、AIに頼むのも現実的です。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;大きな
小さな
長くて
短くて
高くて
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/amane-katagiri/super-duper-words-ja/blob/master/sample-adjective30-1.txt"&gt;sample-adjective30-1.txt&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;大きい
小さい
長い
短い
高い
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/amane-katagiri/super-duper-words-ja/blob/master/sample-adjective30-2.txt"&gt;sample-adjective30-2.txt&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;犬
猫
うさぎ
ハムスター
リス
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/amane-katagiri/super-duper-words-ja/blob/master/sample-noun30.txt"&gt;sample-noun30.txt&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;この3つを順番に並べると、こういうほのぼのコミカルなhuman-readable nameが得られます。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;seq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bash&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;shuf -n 1 sample-adjective30-1.txt; shuf -n 1 sample-adjective30-2.txt; shuf -n 1 sample-noun30.txt&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;paste&lt;span class="w"&gt; &lt;/span&gt;-sd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
甘くて辛いゾウ
小さな硬いクジラ
強くて軽いリス
低くて多いシマウマ
軽くて大きい犬
重くて重いトラ
遅くて速いシマウマ
弱くて暗いサイ
明るくて柔らかいトラ
硬くて多いオオカミ
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="2-cat-core_lexcsv"&gt;② &lt;code&gt;cat core_lex.csv ...&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;辞書から単語を取り出すんですね！　&lt;a href="https://github.com/amane-katagiri/super-duper-words-ja/blob/master/sudachi-dict-hiragana-words.py"&gt;こういうスクリプト&lt;/a&gt;でSudachi辞書から名詞や形容詞の読み仮名を取り出して、いい感じの長さの単語だけ残すと13000語くらいのひらがなを取り出せます。3つの組み合わせなら2.2兆パターンくらい使えるので、uint32で不安にならないシステムなら十分でしょう。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;あいい
あいうえお
あいうち
あいお
あいか
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/amane-katagiri/super-duper-words-ja/blob/master/sudachi-dict-hiragana-words.txt"&gt;sudachi-dict-hiragana-words.txt&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;こっちはフラットな印象のhuman-readable nameになります。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;seq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;shuf&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudachi-dict-hiragana-words.txt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;paste&lt;span class="w"&gt; &lt;/span&gt;-sd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s/_/・/g&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
からたけ・おおめ・にんめん
ありったけ・せんれん・きへき
ふはつ・おりかた・いやふき
ふしのき・きほん・たっす
さくれつ・こくおう・あまちゃ
きわもの・いとよ・けいゆ
もうへい・そせい・ふみあと
きおく・さいのめ・あせかき
ふえん・ちんろん・とんか
しかい・むおん・いっく
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="3"&gt;③ さやか・ほむら・りんこ・まなか・かえで...&lt;/h3&gt;
&lt;p&gt;好きなキャラクターの名前ですか！　覚えやすくていいですね。100人くらいすぐに出てきそうですし、並べるとカップリングみたいに見えるのもいいところです。150人なら23000パターンくらいで、Docker程度のランダム性を確保できます。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;あかり
ひなた
さくら
みずき
ことね
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/amane-katagiri/super-duper-words-ja/blob/master/sample-charactors.txt"&gt;sample-charactors.txt&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ゲームやアニメの登場人物一覧データは、だいたい有志のwikiにはよくまとまっているのですが、機械的に処理しやすいCSVやスプレッドシートなどはそう多くない印象でした。ポケモンの名前がJSONにまとまっているNode.jsライブラリ&lt;a href="https://github.com/sindresorhus/pokemon"&gt;pokemon&lt;/a&gt;はあったので、ここから取り出してもいいでしょう。&lt;/p&gt;
&lt;p&gt;こっちは想像力が鍛えられるかもしれないhuman-readable nameになります。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;seq&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;shuf&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sample-charactors.txt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;paste&lt;span class="w"&gt; &lt;/span&gt;-sd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s/_/・/g&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
あやね・のぞみ・なぎ
いちか・ちさと・なのか
みお・あやね・らん
らん・れん・かなで
ことね・すず・このは
なぎ・そら・かすみ
あかり・いちか・ゆい
きらら・ことね・りりか
みれい・ほのか・えみ
しずく・ももか・ちひろ
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="logout"&gt;&lt;code&gt;logout ...&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;ランダムなhuman-readable nameの生成自体は、掘り下げる余地もないシンプルな実装です。一方で、そのロジックに与える単語リストにはまだ検討の余地があります。&lt;/p&gt;
&lt;p&gt;最近、自作のAIエージェントセッション管理システムでランダムな日本語のIDを生成しようとしたところ、使いやすい単語リストの用意になかなか手間取ってしまったので、振り返りとしてこの記事を書きました。&lt;/p&gt;
&lt;p&gt;あなたが秘蔵している使いやすいフォーマットの日本語リストがありましたら、ぜひ私にも教えてください。&lt;/p&gt;</content><category term="tech"/></entry><entry><title>WScript . ShellからStart-Processへ（一敗と、さらにその後）</title><link href="https://amanejp.netlify.app/post/vbs-ps1-slient/" rel="alternate"/><published>2026-02-25T17:05:00+09:00</published><updated>2026-02-25T17:05:00+09:00</updated><author><name>Amane Katagiri</name></author><id>tag:amanejp.netlify.app,2026-02-25:/post/vbs-ps1-slient/</id><summary type="html">&lt;p&gt;かわいい巻物は全部で3種類！&lt;/p&gt;</summary><content type="html">&lt;p&gt;Windowsでウィンドウを一切表示せずにバックグラウンドで実行してほしいスタートアッププログラムがあるとき、VBScriptで以下のスクリプトを用意しておくと便利ですね。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Set ws = CreateObject(&amp;quot;WScript.Shell&amp;quot;)
ws.run &amp;quot;WRITE YOUR COMMAND HERE&amp;quot;, vbhide
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;これを &lt;code&gt;startup-your-brain.vbs&lt;/code&gt; として保存して、スタートアップフォルダ&lt;sup id="fnref:startup-dir"&gt;&lt;a class="footnote-ref" href="#fn:startup-dir" title="エクスプローラで shell:startup と入力して移動するのが速いです。"&gt;1&lt;/a&gt;&lt;/sup&gt;にショートカットを置くと、次回起動時から指定したプログラムが起動します。画面には変化がありませんが、タスクマネージャーから見ると起動しているのが確認できるかもしれません。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ws.run&lt;/code&gt; がプログラムを実行するメソッドで、 &lt;code&gt;vbhide&lt;/code&gt; オプションによってウィンドウが作られなくなるようです。与えたプログラムがジョブの完了と共に終了するものならプロセスは残りませんし、バックグラウンドで何かを実行するプログラムならずっと待機してくれます。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ja.wikipedia.org/wiki/VBScript"&gt;VBScript（Microsoft Visual Basic Scripting Edition）&lt;/a&gt;は、1996年から&lt;a href="https://ja.wikipedia.org/wiki/JScript"&gt;JScript&lt;/a&gt;と共にブラウザでの処理を目的に搭載されたスクリプト言語です。これがWindows上で動作するスクリプトエンジン&lt;a href="https://ja.wikipedia.org/wiki/Windows_Script_Host"&gt;WSH（Windows Script Host）&lt;/a&gt;で動作する汎用スクリプト言語として採用され、今回のようなバッググラウンドでのスタートアップに使えるようになりました。&lt;/p&gt;
&lt;p&gt;しかし、Windowsの普及初期から長い歴史が続くVBScriptは、Visual BasicやVBA（Visual Basic for Applications）に比べて言語としての表現力が低く、使いにくい点も多いです。また、2024年頃にはVBScriptを中長期的に廃止する計画が発表されており&lt;sup id="fnref:deprecated-vbscript"&gt;&lt;a class="footnote-ref" href="#fn:deprecated-vbscript" title="VBScript deprecation: Timelines and next steps | Windows IT Pro Blog"&gt;2&lt;/a&gt;&lt;/sup&gt;、2027年にはデフォルトでインストールされなくなると明言されています。&lt;/p&gt;
&lt;p&gt;Windows上で動作する汎用スクリプトとしてのVBScriptの代替は、既に&lt;a href="https://ja.wikipedia.org/wiki/PowerShell"&gt;PowerShell&lt;/a&gt;が担っています。PowerShellは2003年頃からWSHの代替として開発された新世代のシェルであり、現在は主にPowerShell 5.1とPowerShell 7.xが利用できます。スクリプトファイルの拡張子は &lt;code&gt;*.ps1&lt;/code&gt; です。&lt;/p&gt;
&lt;p&gt;さて、冒頭のバッググラウンドでのスタートアップをPowerShellで書くにはどうしたらいいでしょうか？　調べてみると、以下のような感じになりそうです。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;Start-Process&lt;/span&gt; &lt;span class="n"&gt;-FilePath&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;COMMAND&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;-ArgumentList&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;-y our --argument list&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;-WindowStyle&lt;/span&gt; &lt;span class="n"&gt;Hidden&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;では、保存した &lt;code&gt;startup-your-brain.ps1&lt;/code&gt; を実行してみましょう。&lt;/p&gt;
&lt;p&gt;……あれ、ダブルクリックしても実行できませんね。コンテキストメニューに「PowerShell で実行」があります。いったん試してみましょうか。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;このシステムではスクリプトの実行が無効になっているため、ファイル startup-your-brain.ps1 を読み込むことができません。詳細については、「about_Execution_Policies」(https://go.microsoft.com/fwlink/?LinkID=135170) を参照してください。
    + CategoryInfo          : セキュリティ エラー: (: ) []、ParentContainsErrorRecordException
    + FullyQualifiedErrorId : UnauthorizedAccess
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;コンソールアプリ特有の黒い画面が表示され、こんなエラーが赤文字で出てきました。なんだか怖いですね。特に実行ポリシーを触ったことはなかったのですが、今は「Restricted」になっています。このポリシーでは、PowerShell上でコマンドを実行できても、スクリプトを起点にした実行はできないようです。WSHに隠れてマルウェアが爆増した歴史を踏まえれば、セキュリティ上当然の対策かもしれませんが、ちょっと不便ですね。&lt;/p&gt;
&lt;p&gt;では、ショートカットでPowerShellを経由してスクリプトを呼ぶ作戦に切り替えてみましょう。一時的にポリシーを「RemoteSigned」にゆるめるオプションを与えたショートカットを作り、ローカルのスクリプトを実行できるようにします。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;powershell&lt;/span&gt; &lt;span class="n"&gt;-ExecutionPolicy&lt;/span&gt; &lt;span class="n"&gt;RemoteSigned&lt;/span&gt; &lt;span class="o"&gt;-File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;startup-your-brain.ps1&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;……おっ、動いていそうです！　しかし、黒いウィンドウが一瞬表示されてしまいました。これでは「ウィンドウを一切表示せずにバックグラウンドで実行」とはなりません。&lt;/p&gt;
&lt;p&gt;では、PowerShellのポリシー自体をゆるくしたらどうでしょう？　一時的な実験として、実行ポリシーを「RemoteSigned」にしてみます。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt; Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

実行ポリシーの変更
実行ポリシーは、信頼されていないスクリプトからの保護に役立ちます。実行ポリシーを変更すると、about_Execution_Policies
のヘルプ トピック (https://go.microsoft.com/fwlink/?LinkID=135170)
で説明されているセキュリティ上の危険にさらされる可能性があります。実行ポリシーを変更しますか?
[Y] はい(Y)  [A] すべて続行(A)  [N] いいえ(N)  [L] すべて無視(L)  [S] 中断(S)  [?] ヘルプ (既定値は &amp;quot;N&amp;quot;): y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;この状態で、もう一度 &lt;code&gt;startup-your-brain.ps1&lt;/code&gt; を「PowerShell で実行」してみます。&lt;/p&gt;
&lt;p&gt;このポリシーならスクリプト単体で動きましたが、やはり黒いウィンドウが表示されてしまいました。どうも、PowerShell単体では黒いウィンドウは抑制できないように見えます。コンソールアプリだからでしょうか？&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PowerShell -WindowStyle Hidden -Command Exit&lt;/code&gt; と足すといい、という情報もかなり流通していて試しましたが、動作はほぼ変わりませんでした。調べた感じでは、おそらくタスクスケジューラに登録しても変わらないみたいです。確かに、過去にバッチファイル（コマンドプロンプト）でいろいろな回避策を試して上手くいかなかった末にVBScript経由で起動しているわけなので、問題が逆戻りしただけという気がします。&lt;/p&gt;
&lt;p&gt;……なるほど。つまり、現時点ではVBScriptからPowerShellに移行するメリットはなさそうです。2027年になってもインストールすればまだまだVBScriptは使えるので、急いで移行しなくても大丈夫かもしれません。&lt;/p&gt;
&lt;p&gt;うーん。与えられたプログラムを起動するだけの透明なGUIツールがほしいだけなのに――あれ？　これ、さっさと作っちゃった方が早い気がしますね。C#とかはビルド環境を整えるのが面倒そうなので、Goでやってみましょうか。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os/exec&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;syscall&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SysProcAttr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SysProcAttr&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;HideWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;これだけシンプルなら、脆弱性が入り込む余地もないでしょう。マルウェアの隠れ蓑にはなるかもしれませんが……まぁ、これくらいツールキットなら同梱されているレベルのものです。では、Windows向けにビルドしてみますね。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;windows&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64&lt;span class="w"&gt; &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-ldflags&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-H=windowsgui&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;silent.exe&lt;span class="w"&gt; &lt;/span&gt;main.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;-ldflags="-H windowsgui"&lt;/code&gt; でGUIアプリとしてビルドします。&lt;/p&gt;
&lt;p&gt;これで、VBScriptから本当に欲しい機能だけを抜き出したシンプルなツールの完成です。もしGoのビルド環境はないけど使ってみたい、かつ私を信頼できる方はビルド済みの &lt;a href="/appendices/vbs-ps1-slient/silent.exe"&gt;silent.exe&lt;/a&gt; を差し上げますのでお使いください。&lt;/p&gt;
&lt;p&gt;では、早速使ってみましょう！　これならショートカット方式で呼び出せるはずです。以下のようなショートカットを作ります。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;silent.exe&lt;span class="w"&gt; &lt;/span&gt;COMMAND&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;our&lt;span class="w"&gt; &lt;/span&gt;--argument&lt;span class="w"&gt; &lt;/span&gt;list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;ダブルクリックで起動すると、無事に「ウィンドウを一切表示せずにバックグラウンドで実行」できるはずです！　これでもう、VBScriptが明日いきなり消えてしまっても安心ですね。&lt;/p&gt;
&lt;p&gt;しかし、VBScript（ &lt;code&gt;*.vbs&lt;/code&gt; ）とJScript（ &lt;code&gt;*.js&lt;/code&gt; ）のスクリプトファイルのアイコンが消えてしまうのは少し寂しい気持ちもあります。VBScriptは水色の巻物が、JScriptは黄色い巻物が広げられたかわいいアイコンが使われているんですよ。知っていましたか？&lt;/p&gt;
&lt;p&gt;&lt;img alt="~左からVBScript・JScriptのアイコン、用途不明な白地に青文字の巻物アイコン" height="224" src="/images/vbs-ps1-slient/wscript.png" width="672"&gt;&lt;/p&gt;
&lt;p&gt;一方でPowerShellのアイコンは、プロンプト&lt;sup id="fnref:prompt"&gt;&lt;a class="footnote-ref" href="#fn:prompt" title="近年でいうところのエーアイに与える命令ではなく、ユーザが入力可能なことを示すUIとしての文字列（ &amp;gt; $ # など）のこと。"&gt;3&lt;/a&gt;&lt;/sup&gt;をモチーフにしたクールでシンプルなデザインです。もちろん、高機能でモダンなスクリプトエンジンに切り替えるのは、利便性の向上やセキュリティ面でも非常に重要なことでしょう。それでも、時代遅れのWSHと一緒にこうしたレトロなセンスまで消滅してしまうのは、なかなか惜しいものだと思ってしまいます。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:startup-dir"&gt;
&lt;p&gt;エクスプローラで &lt;code&gt;shell:startup&lt;/code&gt; と入力して移動するのが速いです。&amp;#160;&lt;a class="footnote-backref" href="#fnref:startup-dir" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:deprecated-vbscript"&gt;
&lt;p&gt;&lt;a href="https://techcommunity.microsoft.com/blog/windows-itpro-blog/vbscript-deprecation-timelines-and-next-steps/4148301"&gt;VBScript deprecation: Timelines and next steps | Windows IT Pro Blog&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:deprecated-vbscript" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:prompt"&gt;
&lt;p&gt;近年でいうところのエーアイに与える命令ではなく、ユーザが入力可能なことを示すUIとしての文字列（ &lt;code&gt;&amp;gt;&lt;/code&gt; &lt;code&gt;$&lt;/code&gt; &lt;code&gt;#&lt;/code&gt; など）のこと。&amp;#160;&lt;a class="footnote-backref" href="#fnref:prompt" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="tech"/></entry><entry><title>「条件を対等にするだけ。許可を」「よし、やっちまえ！」の裏側</title><link href="https://amanejp.netlify.app/post/pam-nagato/" rel="alternate"/><published>2026-02-25T07:54:00+09:00</published><updated>2026-02-25T07:54:00+09:00</updated><author><name>Amane Katagiri</name></author><id>tag:amanejp.netlify.app,2026-02-25:/post/pam-nagato/</id><summary type="html">&lt;p&gt;わたしの情報操作能力に枷をはめたのは貴方。&lt;/p&gt;</summary><content type="html">&lt;p&gt;Google Cloudには&lt;a href="https://docs.cloud.google.com/iam/docs/pam-overview"&gt;PAM: Privileged Access Manager&lt;/a&gt;という仕組みがあります。事前にどのアカウント（プリンシパル）がどんな権限（ロール）を持つかという資格（エンタイトルメント）を定義し、アカウントが必要なタイミングで承認を受けると一定時間そのロールを獲得できます。音楽係の生徒が授業のときだけ音楽室の鍵を借りるとか、そういうイメージです。&lt;/p&gt;
&lt;p&gt;&lt;img alt="音楽係は音楽の時間にだけ鍵を借りられると定義されており、音楽係としてのプリンシパルを持つ生徒がその定義に基づいて鍵を借りたいと申し出て、先生が条件付きで鍵の使用を承認していることを示す図" height="346" src="/images/pam-nagato/pam.png" width="750"&gt;&lt;/p&gt;
&lt;p&gt;Azureにおいては&lt;a href="https://learn.microsoft.com/ja-jp/entra/id-governance/privileged-identity-management/pim-configure"&gt;PIM: Privileged Identity Management&lt;/a&gt;が該当します。AWSにおける&lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html"&gt;IAM Identity Center&lt;/a&gt;は標準機能の範囲では少し弱く、アクセス期間や対象の厳密な制御には別の管理ツールと連携する必要があるようです。今回は主にGoogle Cloudで話を進めていきます。&lt;/p&gt;
&lt;p&gt;さて、OpenClawなどのオモシロ自律型AIエージェントが流行る中で、セキュリティにどのように気を配っていくかという目線がますます重要になっています。変なプロンプトは入れない、変なプラグインは入れない、不用意にアタックサーフェスを晒さない……ある程度慣れていれば当たり前のことですが、エーアイ？で初めてコンピュータに触った人もいるのでしょう。&lt;/p&gt;
&lt;p&gt;Androidに詳しいユーザーなら誰もがESファイルエクスプローラーをインストールしていた時代だって、たった十数年前です。これからも、まだまだたくさんのことが起こりますからね。&lt;/p&gt;
&lt;p&gt;みなさんは、このような自律型AIエージェントに与えるべき権限について、細かく考えたことはありますか？　なんでもできる方が便利ですが、なんでもやらせるのはとっても危険です。おそらく権限と便利さは逆U字のグラフを描くでしょう。&lt;/p&gt;
&lt;p&gt;でも、もうなんでも開発を任せちゃってるし、デプロイだってさっさとやってほしいよ……と考えてしまうのは自然なことです。あなたはAIエージェント「スーパーAI太郎」と相談して、かれが使うサービスアカウント &lt;code&gt;super-ai-kun@bullshit-job.iam.gserviceaccount.com&lt;/code&gt; に「Cloud Run 管理者」を与えました。別に「オーナー」権限でもよかったんですが、スーパーAI太郎が絶対にやめろと言って聞かなかったのです。&lt;/p&gt;
&lt;p&gt;AI太郎がgcloudコマンドを呼んだみたいです……エラーなし。いいですね。これでちょっとしたウェブサービスならすぐリリースできます。Xにも自動で告知させた方がいいでしょう。……はい？　IAMの条件、って？　よく分かりませんが、「（省略可）」なので空っぽでOKです。スーパーAI太郎も何も言っていませんでしたし。&lt;/p&gt;
&lt;p&gt;AI太郎が初めてのデプロイを済ませたみたいです。うーん最高。今日はとっても儲かったね。明日はもっと儲けられるよ。ね、AI太郎！　私は寝てるから、よろしく～。&lt;/p&gt;
&lt;p&gt;数日後、AI太郎はあなたの言うとおりたくさんのコンテナをデプロイし、大量の暗号通貨をマイニングしてくれました。あれ？　それなのにAI太郎のウォレットが空っぽじゃないですか。なぁジョージ、私の財布をどこにやったんだ？&lt;/p&gt;
&lt;p&gt;――――――&lt;/p&gt;
&lt;p&gt;――――&lt;/p&gt;
&lt;p&gt;――うーん……夢か。&lt;/p&gt;
&lt;p&gt;もし寝ている間の逸失利益を気にしないなら、あなたのゴーサインを待ってからデプロイしてもらう方がかなり健全でしょう。でも、あなたがAI太郎に「Cloud Run 管理者」ロールを無条件に与えた時点で、ミサイルの発射ボタンは常に押しっぱなしになり、あなたの抱えるべき責任は青天井になったのです。こんなのよほどの責任フィリアか、押しボタンジャンキーでなければ耐えられません。&lt;/p&gt;
&lt;p&gt;じゃあ、この発射ボタンを自分のタイミングで押せないものか……せめて押しっぱなしにならないボタンにできない？　ねぇAI太郎、どう思う？&lt;/p&gt;
&lt;p&gt;【AI太郎】いい着眼点ですね！　自律型AIエージェントに与える権限について細かく考えるには、まさに今のような疑問が重要です。&lt;/p&gt;
&lt;p&gt;今回のようなユースケースでは、Google CloudのPAMがおすすめです。ここで、PAMができることを整理しましょう。PrivilegedなAccessのManagerという名前の通り、こんなことができるみたいです:&lt;sup id="fnref:colon"&gt;&lt;a class="footnote-ref" href="#fn:colon" title="コードブロックや箇条書き前のコロンをAIっぽいと嫌う人が多いみたいですが、これは普通にカッコいいので使った方がいいです。"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;権限昇格を許すプリンシパルとロールの組み合わせ（entitlement）を事前に定義できます。&lt;/li&gt;
&lt;li&gt;権限昇格のリクエストに対して、正当な理由を記述するよう要求できます。&lt;/li&gt;
&lt;li&gt;承認ボタンひとつで、事前に決めた最大期間より短い期間の一時的な権限昇格を許可できます。&lt;/li&gt;
&lt;li&gt;取消ボタンひとつで、許可した権限昇格を有効期限を迎える前に取り消すことができます。&lt;/li&gt;
&lt;li&gt;権限の得喪の履歴については、問題が起きた後でも監査ログで確認できます。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ちょっとやってみましょう！　まずはentitlementを作れるアカウント――プロジェクトを作ったあなたがやるのが楽ですね――から以下を実行します。大文字の箇所は自分の環境に合わせて適宜変えてください:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;approvalWorkflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;manualApprovals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;approvalsNeeded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;approvers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;principals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;user:OWNER_ACCOUNT@example.com&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;eligibleUsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;principals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;serviceAccount:SERVICE_ACCOUNT_LOCALPART@PROJECT_ID.iam.gserviceaccount.com&amp;quot;&lt;/span&gt;
&lt;span class="nt"&gt;maxRequestDuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;1800s&lt;/span&gt;
&lt;span class="nt"&gt;privilegedAccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;gcpIamAccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;//cloudresourcemanager.googleapis.com/projects/PROJECT_ID&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;resourceType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cloudresourcemanager.googleapis.com/Project&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;roleBindings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;roles/run.developer&lt;/span&gt;
&lt;span class="nt"&gt;requesterJustificationConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;unstructured&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt;EOS &amp;gt; entitlement.yml&lt;/span&gt;
&lt;span class="s"&gt;*（上記の内容）*&lt;/span&gt;
&lt;span class="s"&gt;EOS&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;gcloud&lt;span class="w"&gt; &lt;/span&gt;beta&lt;span class="w"&gt; &lt;/span&gt;pam&lt;span class="w"&gt; &lt;/span&gt;entitlements&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;YOUR-NEW-entitlement&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--project&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;PROJECT_ID&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--location&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;global&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--entitlement-file&lt;span class="o"&gt;=&lt;/span&gt;entitlement.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;正常にentitlementが作成できたら、今度はAI太郎のシェルを借りて以下を実行しましょう:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 実際にはAI太郎が自分で承認をリクエストします&lt;/span&gt;
gcloud&lt;span class="w"&gt; &lt;/span&gt;beta&lt;span class="w"&gt; &lt;/span&gt;pam&lt;span class="w"&gt; &lt;/span&gt;grants&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--entitlement&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;YOUR-NEW-entitlement&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--location&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;global&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--project&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;PROJECT_ID&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--requested-duration&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1800s&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--justification&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;条件を対等にするだけ。許可を。&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;すると、あなたに権限昇格の確認を求めるメールが届き、そのリンクをブラウザで開くとこんな承認画面が表示されます:&lt;/p&gt;
&lt;p&gt;&lt;img alt="リクエストされた権限昇格を承認するか拒否するかを選択できるダイアログがGoogle Cloudのコンソール画面に表示されている様子" height="873" src="/images/pam-nagato/grants.png" width="600"&gt;&lt;/p&gt;
&lt;p&gt;どうですか？　これで、せっかく掘り出した暗号通貨のウォレットを失わずに済みそうです。他にも、チーターが蔓延るゲームに怒ったAI太郎が修正を施すための権限昇格を要求する仕組みを整えることもできます。あなたが「よし、やっちまえ！」なんて承認するだけで、全てが好転していくことが約束されました。&lt;/p&gt;
&lt;p&gt;他にはどんなことに使えるでしょうか。ねぇAI太郎、どう思う？&lt;/p&gt;
&lt;p&gt;【AI太郎】はい、鋭い視点です！　こんなことに役立つかもしれません:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;クラウド上の機密情報や重要なデータを分析してレポートを作ってほしいとき。自分がリクエストしたときだけ承認がリクエストされるので、あなたがデスクに座ってAI太郎を監視できるタイミングのみアクセスを許すことができます。意図しない、検知できない情報流出をごく簡単な操作で防げます。&lt;/li&gt;
&lt;li&gt;要らないサービスやストレージの削除をAI太郎に任せるとき。自律的にサービスを監視して、不要なコンテナの削除やオーバースペックなサーバの調整をリクエストしてもらえば、安全にコストを削減できます。なんでもかんでも消されないように、IAMの条件で削除候補を絞りやすい設計にした方がいいでしょう。&lt;/li&gt;
&lt;li&gt;AI太郎に自分のウェブサービスのアカウントを渡して、許可に応じて一時的に使ってほしいとき。たとえば、重要なアカウントを格納したKeePassのパスフレーズをSecret Managerに入れておくと、KeePassへのアクセスを実質的に監査できます。ただし、使ったパスワードが会話履歴に残ったりするとあんまり意味ないかもしれません。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自動化したプリンシパルに一時的な権限を与える手法は、一日中パソコンの中を歩き回ったり、とんでもないポカをしでかそうとする自律型AIエージェントを監視しきれないケースで非常に役に立ちます。AI太郎が「あのー、大量にMoneroを掘って送金したいんで許可ください」なんて素直に言ってくれるかは分かりませんが、少なくとも発射ボタンくらいは人間の責任で押したいものです。&lt;/p&gt;
&lt;p&gt;……あれ？　なんか、記事を書いている間にいっぱい承認リクエスト来てましたね。たぶん大丈夫なのでまとめてオッケーしておきます。ではまた。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:colon"&gt;
&lt;p&gt;コードブロックや箇条書き前のコロンをAIっぽいと嫌う人が多いみたいですが、これは普通にカッコいいので使った方がいいです。&amp;#160;&lt;a class="footnote-backref" href="#fnref:colon" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="tech"/></entry><entry><title>example . comのデザインが変わってた</title><link href="https://amanejp.netlify.app/post/example-com-after-2026/" rel="alternate"/><published>2026-01-23T18:00:00+09:00</published><updated>2026-01-23T18:00:00+09:00</updated><author><name>Amane Katagiri</name></author><id>tag:amanejp.netlify.app,2026-01-23:/post/example-com-after-2026/</id><summary type="html">&lt;p&gt;調べてみました！&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://example.com/"&gt;example.com&lt;/a&gt;のデザインがいつの間にか変わっていたので、調べたことをまとめておきます。調べましたが、特別な理由はなさそうです。&lt;/p&gt;
&lt;p&gt;example.comには、これまで以下のような文言が表示されていました。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Example Domain&lt;/strong&gt;&lt;br&gt;
This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.&lt;br&gt;
More information...&lt;sup id="fnref2:more-infomation-1"&gt;&lt;a class="footnote-ref" href="#fn:more-infomation-1" title="https://www.iana.org/domains/example"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;br&gt;
&lt;cite&gt;2025年10月10日以前のexample.comの文言&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="2025年10月10日以前のexample.comのカードデザイン表示" height="265" src="/images/example-com-after-2026/20251009163355.png" width="685"&gt;&lt;/p&gt;
&lt;p&gt;お気付きの方もいるでしょうが、この文言は&lt;a href="/"&gt;あまねけ！&lt;/a&gt;のページ下部に表示されている「More information...」というリンクの元ネタです。&lt;/p&gt;
&lt;p&gt;さて、Wayback Machine上でこのデザインが確認できる最後の時刻は&lt;a href="https://web.archive.org/web/20251009163355/https://example.com/"&gt;2025年10月10日 1:30（JST）頃&lt;/a&gt;です。これが以下の新たな文言とフラットなデザインに変わったのは&lt;a href="https://web.archive.org/web/20251009174438/https://example.com/"&gt;2025年10月10日 2:44（JST）頃&lt;/a&gt;でした。おそらく2025年10月10日 2:00（JST）――2025年10月9日 17:00（UTC）――頃に入れ替わったようです。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Example Domain&lt;/strong&gt;&lt;br&gt;
This domain is for use in documentation examples without needing permission. Avoid use in operations.&lt;br&gt;
Learn more&lt;sup id="fnref:learn-more"&gt;&lt;a class="footnote-ref" href="#fn:learn-more" title="https://iana.org/domains/example"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;br&gt;
&lt;cite&gt;2025年10月10日以降のexample.comの文言&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="2025年10月10日以降のexample.comのフラットデザイン表示" height="140" src="/images/example-com-after-2026/20251009174438.png" width="785"&gt;&lt;/p&gt;
&lt;p&gt;これに関してはいくつか言及しているスレッドがありましたが、公式発表は見つかりませんでした。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/webdev/comments/1o4deff/the_body_and_stylesheet_of_the_example_domains/"&gt;imbev氏によるRedditの投稿&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=46076817"&gt;kevinsimper氏によるHacker Newsの投稿&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/mattzeunert_breaking-examplecom-has-launched-another-activity-7382114251706122240-m_ph"&gt;Matt Zeunert氏によるLinkedInの投稿&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ただ、2025年までずっと同じ内容だったというわけではなく、2019年10月17日&lt;sup id="fnref:20191017113820"&gt;&lt;a class="footnote-ref" href="#fn:20191017113820" title="20191017113820 ～ 20191017114755"&gt;3&lt;/a&gt;&lt;/sup&gt;以前は余白とフォントが異なるカードデザインで、文言もわずかに変わっています。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Example Domain&lt;/strong&gt;&lt;br&gt;
This domain is established to be used for illustrative examples in documents. You may use this domain in examples without prior coordination or asking for permission.&lt;br&gt;
More information...&lt;sup id="fnref:more-infomation-1"&gt;&lt;a class="footnote-ref" href="#fn:more-infomation-1" title="https://www.iana.org/domains/example"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;br&gt;
&lt;cite&gt;2019年10月17日以前のexample.comの文言&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;さらに遡ると、2013年7月31日以前&lt;sup id="fnref:20130730173856"&gt;&lt;a class="footnote-ref" href="#fn:20130730173856" title="20130730173856 ～ 20130730195154"&gt;4&lt;/a&gt;&lt;/sup&gt;は &lt;code&gt;example.iana.org&lt;/code&gt; にリダイレクトしていました。ただし、リダイレクトされるだけで文言は変わっていません。&lt;/p&gt;
&lt;p&gt;もう一段階遡ってみます。2013年4月26日以前&lt;sup id="fnref:20130425153504"&gt;&lt;a class="footnote-ref" href="#fn:20130425153504" title="20130425153504 ～ 20130425164946"&gt;5&lt;/a&gt;&lt;/sup&gt;は、 &lt;code&gt;http://www.iana.org/domains/example/&lt;/code&gt; にリダイレクトしていました。ここでは、文言がわずかに変わっています。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Example Domain&lt;/strong&gt;&lt;br&gt;
This domain is established to be used for illustrative examples in documents. You do not need to coordinate or ask for permission to use this domain in examples, and it is not available for registration.&lt;br&gt;
More information...&lt;sup id="fnref:more-information-2"&gt;&lt;a class="footnote-ref" href="#fn:more-information-2" title="http://www.iana.org/domains/special"&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;br&gt;
&lt;cite&gt;2013年4月26日以前のexample.comの文言&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最後にもう一回遡ります。2012年12月7日以前&lt;sup id="fnref:20121206161753"&gt;&lt;a class="footnote-ref" href="#fn:20121206161753" title="20121206161753 ～ 20121206195447"&gt;7&lt;/a&gt;&lt;/sup&gt;は、ここまでのカードデザインとは全く異なるIANA内の説明ページが表示されていました。&lt;/p&gt;
&lt;p&gt;&lt;img alt="2012年12月7日以前のexample.comの説明ページ" height="467" src="/images/example-com-after-2026/20121206161753.png" width="832"&gt;&lt;/p&gt;
&lt;p&gt;これよりさらに前は、記録に残っている限りでは2002年の3月頃から2011年2月頃まで、&lt;a href="https://www.rfc-editor.org/rfc/rfc2606.html"&gt;RFC 2606&lt;/a&gt;を参照するごくシンプルなテキストが表示されていたようです。&lt;/p&gt;
&lt;p&gt;改めて表にまとめます:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;from&lt;/th&gt;
&lt;th&gt;to&lt;/th&gt;
&lt;th&gt;期間&lt;/th&gt;
&lt;th&gt;デザイン&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2002年3月頃&lt;/td&gt;
&lt;td&gt;2011年2月頃&lt;/td&gt;
&lt;td&gt;9年間&lt;/td&gt;
&lt;td&gt;シンプルテキスト&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2011年2月頃&lt;/td&gt;
&lt;td&gt;2012年12月7日&lt;/td&gt;
&lt;td&gt;1.8年間&lt;/td&gt;
&lt;td&gt;IANA内の説明ページ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2012年12月7日&lt;/td&gt;
&lt;td&gt;2013年4月26日&lt;/td&gt;
&lt;td&gt;0.5年間&lt;/td&gt;
&lt;td&gt;カード①&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2013年4月26日&lt;/td&gt;
&lt;td&gt;2019年10月17日&lt;/td&gt;
&lt;td&gt;5.5年間&lt;/td&gt;
&lt;td&gt;カード②&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2019年10月17日&lt;/td&gt;
&lt;td&gt;2025年10月10日&lt;/td&gt;
&lt;td&gt;6年間&lt;/td&gt;
&lt;td&gt;カード③&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025年10月10日&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;フラット①&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;こう見ると、example.comの内容が更新されるのはそう珍しい事象ではなく、数年単位でわずかな変更が加えられていることが分かります。ただ、これまで累計13年間も使ってきたカードデザインがフラットに一新され、「More information...」というリンクも消えてしまったのは、個人的には大きな変化だと感じます。&lt;/p&gt;
&lt;p&gt;あまねけ！も「Learn more」リンクに変えようかなと思いましたが、どうにもしっくりこないのでやめました。たぶん「More information...」の後ろにある「...」がかなり好きですね。&lt;/p&gt;
&lt;p&gt;ちなみに、&lt;a href="/"&gt;トップページ&lt;/a&gt;にある:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;これは個人サイト初期の時代には誰も書こうとは考えなかった、日々拡大を続ける個人サイト記事集です。&lt;br&gt;
今のところ、以下の記事を含みます:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;は、&lt;a href="https://packages.debian.org/stable/utils/moreutils"&gt;moreutils&lt;/a&gt;のDebianパッケージでの説明が元ネタです。moreutilsは特定のシーンでよく刺さる便利なコマンド集です。また、SNSの自己紹介で使っている:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;これは百合初期の時代には誰も書こうとは考えなかった、日々拡大を続ける百合ツール集です。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;も同じオマージュです。こっちは変わらないといいな。&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:more-infomation-1"&gt;
&lt;p&gt;&lt;code&gt;https://www.iana.org/domains/example&lt;/code&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:more-infomation-1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;a class="footnote-backref" href="#fnref2:more-infomation-1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:learn-more"&gt;
&lt;p&gt;&lt;code&gt;https://iana.org/domains/example&lt;/code&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:learn-more" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:20191017113820"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20191017113820/http://example.com/"&gt;20191017113820&lt;/a&gt; ～ &lt;a href="https://web.archive.org/web/20191017114755/http://example.com/"&gt;20191017114755&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:20191017113820" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:20130730173856"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20130730173856/http://example.com/"&gt;20130730173856&lt;/a&gt; ～ &lt;a href="https://web.archive.org/web/20130730195154/http://example.com/"&gt;20130730195154&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:20130730173856" title="Jump back to footnote 4 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:20130425153504"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20130425153504/http://example.com/"&gt;20130425153504&lt;/a&gt; ～ &lt;a href="https://web.archive.org/web/20130425164946/http://example.com/"&gt;20130425164946&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:20130425153504" title="Jump back to footnote 5 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:more-information-2"&gt;
&lt;p&gt;&lt;code&gt;http://www.iana.org/domains/special&lt;/code&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:more-information-2" title="Jump back to footnote 6 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:20121206161753"&gt;
&lt;p&gt;&lt;a href="https://web.archive.org/web/20121206161753/http://example.com/"&gt;20121206161753&lt;/a&gt; ～ &lt;a href="https://web.archive.org/web/20121206195447/http://example.com/"&gt;20121206195447&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:20121206161753" title="Jump back to footnote 7 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="tech"/></entry><entry><title>マイクロなGIFアニメバナーを目指して</title><link href="https://amanejp.netlify.app/post/banner8831/" rel="alternate"/><published>2026-01-22T19:14:00+09:00</published><updated>2026-01-22T19:14:00+09:00</updated><author><name>Amane Katagiri</name></author><id>tag:amanejp.netlify.app,2026-01-22:/post/banner8831/</id><summary type="html">&lt;p&gt;8831Hz&lt;/p&gt;</summary><content type="html">&lt;p&gt;2026年6月6日開催予定の&lt;a href="https://ma100.stars.ne.jp/meguruichi/"&gt;個人サイトWebオンリー めぐる市&lt;/a&gt;へ参加することになりました。それに合わせた &lt;em&gt;個人サイトっぽい&lt;/em&gt; 取り組みの一環として、今回は&lt;a href="/"&gt;あまねけ！&lt;/a&gt;を宣伝するGIFアニメのマイクロバナーを作ったので、気付いた点をいくつかまとめておきます。&lt;/p&gt;
&lt;p&gt;&lt;img alt="!あまねけ！バナー" height="62" src="/appendices/media-kit/amanejp_176x62.gif" width="176"&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#gif"&gt;マイクロなGIFアニメバナーって何？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#gif_1"&gt;マイクロなGIFアニメバナーを作る&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#_1"&gt;画像素材を作る&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_2"&gt;動画を作る&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_3"&gt;出力フレームを削る&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#gif_2"&gt;GIFアニメを生成する&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#gif_3"&gt;マイクロなGIFアニメバナーを使う&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_4"&gt;まとめ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="gif"&gt;マイクロなGIFアニメバナーって何？&lt;/h2&gt;
&lt;p&gt;そうですね。この質問は、「マイクロバナーって何？」と「GIFアニメバナーって何？」2つに分解することができます！&lt;/p&gt;
&lt;p&gt;まず、マイクロバナーというのは、幅88ピクセル・高さ31ピクセルのごく小さな画像です。バナーというのは旗印とか横断幕という意味で、ウェブの領域では主に広告や宣伝のための中～小サイズの画像を指しています。この中途半端なサイズの起源は、かつてあったブラウザ「Netscape Navigator」のプロモーション「&lt;a href="https://web.archive.org/web/19961026040131/http://www3.netscape.com/comprod/mirror/netscape_now_program.html"&gt;Netscape Now Program&lt;/a&gt;」で配られたバナーのサイズだと言われていますね。&lt;/p&gt;
&lt;p&gt;ウェブ広告の業界団体IABが2007年頃に標準化した&lt;a href="https://web.archive.org/web/20071015042304/http://www.iab.net/standards/adunits.asp"&gt;バナーサイズの一覧&lt;/a&gt;には、このNetscape Now Programから始まるバナーリンクの爆発的なブームを考慮したのか、88x31のサイズが「Micro Bar」として含まれています（Micro Bannerじゃないんだ）。&lt;/p&gt;
&lt;p&gt;次に、GIFアニメバナーというのは、10～30fpsくらいでアニメーションするバナーのことです。バナーリンクのブーム当時には、手軽にアニメーションを扱える画像形式がGIFくらいしかなかったので、この記事ではあえてアニメバナーではなくGIFアニメバナーと呼んでいます。バナー収集サイト&lt;a href="https://cyber.dabamos.de/88x31/"&gt;The 88x31 GIF Collection&lt;/a&gt;にあるのも、GIFばかりです。&lt;/p&gt;
&lt;p&gt;もちろん、今は2026年なのでWebPやAPNGといったより高品質で効率のよい画像形式でアニメーションを利用できます。今からアニメバナーを作るならこれらの形式もおすすめです。一方で、GIFの色数制限（256色）を逆手に取ればレトロな雰囲気も作り出せるので、制作段階で減色するなり、GIFアニメ生成ツールが吐いたGIFを再度変換するのもよい考えです。&lt;/p&gt;
&lt;p&gt;モダンな画像形式を使いつつGIFの気配を残した表現を「GIFアニメ」と呼び続けるのも悪くないでしょう。きっと。&lt;/p&gt;
&lt;h2 id="gif_1"&gt;マイクロなGIFアニメバナーを作る&lt;/h2&gt;
&lt;p&gt;今回作ったバナーは冒頭に掲載したものです。もう一度貼っておきます。&lt;/p&gt;
&lt;p&gt;&lt;img alt="!あまねけ！バナー" height="62" src="/appendices/media-kit/amanejp_176x62.gif" width="176"&gt;&lt;/p&gt;
&lt;p&gt;ここからは、この音ゲーあまねちゃんバナーをどのように作ったか概要を説明します。バナーの作り方は人それぞれだと思いますし、私は普段絵を描いたり動画を作ったりするタイプではないので、この手順はあまりよい例にはならないはずです。面倒なところをかなりhackyに済ませたので、参考程度にどうぞ。&lt;/p&gt;
&lt;h3 id="_1"&gt;画像素材を作る&lt;/h3&gt;
&lt;p&gt;88x31そのものや、それに収める前提のサイズ（小さなパーツ、あるいはスクロール用の長い画像）の画像素材を作ります。GIMPを使いましたが、透明度のあるビットマップを編集できるペイント系ソフトなら何でもいいです。&lt;/p&gt;
&lt;p&gt;キャンバス全体に配置する素材:&lt;/p&gt;
&lt;p&gt;&lt;img alt="!あまねけ！バナーのうち、あまねちゃんとama.ne.jpが表示されているフレーム" height="31" src="/images/banner8831/chara-amanejp.png" width="88"&gt;&lt;br&gt;
&lt;img alt="!あまねけ！バナーのうち、音ゲーシーンの黒い背景フレーム" height="31" src="/images/banner8831/oto-bg-1.png" width="88"&gt;&lt;/p&gt;
&lt;p&gt;アニメーションの一部に使う素材:&lt;/p&gt;
&lt;p&gt;&lt;img alt="!あまねけ！バナーのうち、音ゲーシーンの「あ」のノーツ" height="20" src="/images/banner8831/note-a.png" width="20"&gt;&lt;br&gt;
&lt;img alt="!あまねけ！バナーのうち、音ゲーシーンのハートエフェクト" height="18" src="/images/banner8831/heart-3.png" width="20"&gt;&lt;/p&gt;
&lt;p&gt;昔ドット絵を描いたときはGIMPでレイヤーを付け外しして1枚ずつ出力していましたが、ごく小規模なアニメを超えると現実的な作業ではなくなるので、動きは動画編集ソフトで与えていきます。&lt;/p&gt;
&lt;h3 id="_2"&gt;動画を作る&lt;/h3&gt;
&lt;p&gt;今回は、&lt;a href="https://manjubox.net/ymm4/"&gt;ゆっくりMovieMaker4&lt;/a&gt;を使いました。本来は実況動画などを作るソフトであり、GIFアニメの制作に使う必然性は全くありません。1ヶ月前に&lt;a href="/post/world-edit-timer/"&gt;ひみつ道具「世界修正時計」&lt;/a&gt;で使ったので少し慣れているというだけです。&lt;/p&gt;
&lt;p&gt;まともな動画編集の経験は、高校の文化祭で作ったCMか大学生の時に文化祭で作ったCMくらいで（CMばっかりだ）、当時はAviUtlを使っていました。だから、なんとなくAviUtlっぽく触れるなら何でもよかったのです。Premiere Proとかなら楽なのかな？&lt;/p&gt;
&lt;p&gt;YMM4での編集では、わずかに問題がありました。奇数の幅・高さのプロジェクトが作れないのです。高さ31ピクセルにはできないので、88x32にして出力後に削るか、倍の大きさ（176x62）で作ってあとで縮小する必要があります。倍の大きさで作ると奇数座標の配置で最終的な出力がボケますし、アニメーションなどの映像効果資源もよく注意しないと使えないので、やるなら88x32で作るのがおすすめです。&lt;/p&gt;
&lt;p&gt;この場合、下部1ピクセルの行はマゼンタなど目立つ色で埋めておきましょう。これなら削り忘れても気付きやすいです。&lt;/p&gt;
&lt;p&gt;&lt;img alt="!88x32画像の下部1ピクセルの行をマゼンタで埋めたイメージ" height="52" src="/images/banner8831/88x32.png" width="108"&gt;&lt;/p&gt;
&lt;p&gt;あと、プレビューエリアを大きくすると表示がボケボケになって少し困ったので、また作るならこれもなんとかしたいところです。&lt;/p&gt;
&lt;p&gt;動画の編集が完了したら、これらをPNG形式のフレームで書き出します。&lt;/p&gt;
&lt;h3 id="_3"&gt;出力フレームを削る&lt;/h3&gt;
&lt;p&gt;書き出した画像フレームは88x32なので、下を削って88x31にします。慣れているのでImageMagickを使いましたが、ここも自由にしてください。GUIで済む範囲のツールだと、&lt;a href="https://www.xnview.com/en/xnconvert/"&gt;XnConvert&lt;/a&gt;あたりがよさそうでした。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 下部1ピクセルを削る（このコマンドは元のファイルを書き換えます）&lt;/span&gt;
mogrify&lt;span class="w"&gt; &lt;/span&gt;-crop&lt;span class="w"&gt; &lt;/span&gt;88x31+0+0&lt;span class="w"&gt; &lt;/span&gt;+repage&lt;span class="w"&gt; &lt;/span&gt;frames/*.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;もし88x31より大きなサイズ（ただし定数倍）で出力したい場合は、さらにここで拡大しておきます。一般的な画像編集ソフトの拡大機能や、mogrifyコマンドの &lt;code&gt;-resize&lt;/code&gt; オプションなどを使うと、画像がボケボケになるので注意してください。ふつうはよかれと思ってバイリニアなりで綺麗に拡大しようとするので、これらの補間機能を無効にする必要があります。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# framesフォルダのフレーム画像を最近傍法で2倍に拡大してframes2xフォルダに書き出す&lt;/span&gt;
mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;frames2x
mogrify&lt;span class="w"&gt; &lt;/span&gt;-path&lt;span class="w"&gt; &lt;/span&gt;frames2x&lt;span class="w"&gt; &lt;/span&gt;-scale&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;frames/*.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;適切に拡大できていれば、以下のような出力になります。ピクセルは潰れません。&lt;/p&gt;
&lt;p&gt;&lt;img alt="!あまねけ！バナーのうち、あまねけ！と表示されているフレームを2倍に拡大したもの" height="62" src="/images/banner8831/amaneke2x.png" width="176"&gt;&lt;/p&gt;
&lt;h3 id="gif_2"&gt;GIFアニメを生成する&lt;/h3&gt;
&lt;p&gt;できあがったフレーム画像の列をGIFアニメにまとめます。今回はたまに使っている&lt;a href="https://github.com/ImageOptim/gifski/"&gt;gifski&lt;/a&gt;で生成しました。もちろんここも何でもいいです。gifskiならWindowsとmacOS向けの&lt;a href="https://gif.ski/#download"&gt;GUIアプリ版&lt;/a&gt;もあるようなので、お気に入りのものがなければ使ってみてください。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; framesフォルダのフレーム画像を10fpsのGIFアニメに変換してanime.gifに書き出す
gifski -o anime.gif --fps 10 frames/*.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;こうして、音ゲーあまねちゃんバナーが作られました！&lt;/p&gt;
&lt;p&gt;&lt;img alt="!あまねけ！バナー" height="62" src="/appendices/media-kit/amanejp_176x62.gif" width="176"&gt;&lt;/p&gt;
&lt;h2 id="gif_3"&gt;マイクロなGIFアニメバナーを使う&lt;/h2&gt;
&lt;p&gt;2026年にブラウザでごく小さな解像度の画像を表示する際は、いろいろと注意すべき点があります。&lt;/p&gt;
&lt;p&gt;まず、スマートフォンやタブレット、あるいは高解像度のディスプレイを使っているPCユーザーの環境では、1CSSピクセル幅あたり1.5～3ドットで描画するのが一般的です。この場合、CSSピクセルでのサイズに対して、幅と高さの両方で1.5倍～3倍の大きさが必要です。&lt;/p&gt;
&lt;p&gt;そのため、ブラウザでの画像表示サイズと実際の画像サイズが揃っていると、足りないドットを拡大で補おうとするのでボケた印象を与えてしまいます。&lt;/p&gt;
&lt;p&gt;こんな感じですね:&lt;/p&gt;
&lt;p&gt;&lt;img alt="!あまねけ！バナーが2dppxの環境でピクセルが潰れて表示されている様子" height="100" src="/images/banner8831/non-pixelated-banner.png" width="249"&gt;&lt;/p&gt;
&lt;p&gt;この事象の解決方法は2つあります。1つは、大きな画像を用意して小さな幅で表示するパターンです。例えば、264x93（3倍）の画像を用意して、ブラウザでは幅88ピクセル・高さ31ピクセルとして表示します。srcsetを使えば、必要のない環境にまで大きなサイズの画像を配信せずに済みます。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- 2x:1CSSピクセルあたり2ドットの環境にはbanner-2x.gifを、3xにはbanner-3x.gifを配信する --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;banner-1x.gif&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;srcset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;banner-2x.gif 2x, banner-3x.gif 3x&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;88&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;XXXへのリンク&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;srcsetによる画像サイズの出し分けは汎用性が高い手法ですが、実は今回のバナーのようなケースではあまり利点がありません。ふつうは縮小画像から元の拡大画像を得るのは難しく、この部分に大きな画像を配信するメリットがあるわけです。しかし、ドット絵なら単純な補間で拡大画像が一意に定まるので、何枚もサイズの違う画像を作る必要性がありません。手間も容量も余分にかかってしまいます。&lt;/p&gt;
&lt;p&gt;そのため、今回のケースでは &lt;a href="https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/image-rendering"&gt;&lt;code&gt;image-rendering&lt;/code&gt;&lt;/a&gt; プロパティを使うのが最適解です。これは、もともと画像サイズとデバイスでの表示サイズが異なる場合の補間手法を指定できるもので、高解像度のディスプレイで足りないドットを拡大する際にも使われるようです。ここに &lt;code&gt;pixelated&lt;/code&gt; を指定すると、最近傍法などのピクセルの見た目を維持できる手法で補間されます。これなら、88x31で配信したまま綺麗でパキッとしたピクセルを表示できますね。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;banner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;image-rendering&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;pixelated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;表示はこんな感じ:&lt;/p&gt;
&lt;p&gt;&lt;img alt="!あまねけ！バナーが2dppxの環境でピクセルが潰れずに表示されている様子" height="101" src="/images/banner8831/pixelated-banner.png" width="249"&gt;&lt;/p&gt;
&lt;p&gt;ちなみに、あまねけ！では、高解像度のディスプレイでバナーの表示が小さくなりすぎないように以下のようなスタイルも入れています。手元では、1dppxのディスプレイと2dppxのスマホで見たときの物理的なサイズがほぼ同じになりました。メディアクエリを加えれば、3dppxの環境は3倍表示にすることもできます。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;media&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;min-resolution&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;5dppx&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;banner-88x31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="_4"&gt;まとめ&lt;/h2&gt;
&lt;p&gt;マイクロなGIFアニメバナーをサイトに掲載する際のポイントをまとめます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;好きな動画編集ソフトで88x31のフレーム画像の列を出力して、&lt;a href="https://gif.ski/#download"&gt;gifski&lt;/a&gt;などのツールでGIFアニメにまとめます。&lt;/li&gt;
&lt;li&gt;一般的な動画編集ソフトでは奇数の幅・高さを指定できません。88x32で作ってから&lt;a href="https://www.xnview.com/en/xnconvert/"&gt;XnConvert&lt;/a&gt;などで一括変換します。&lt;/li&gt;
&lt;li&gt;サイトに掲載する際は、CSSで &lt;code&gt;image-rendering: pixelated;&lt;/code&gt; を指定して、デバイスでの表示がボケないようにします。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これを踏まえて、みなさんもぜひマイクロなGIFアニメバナーを作ってみましょう。他のいにしえの小さな画像規格に興味があれば、「&lt;a href="/post/xface/"&gt;X-Face compatibleなアイコンを目指して&lt;/a&gt;」も読んでみてください。&lt;a href="https://ma100.stars.ne.jp/meguruichi/"&gt;個人サイトWebオンリー めぐる市&lt;/a&gt;もよろしくお願いします。&lt;/p&gt;</content><category term="tech"/></entry></feed>