/*! ===========================================================================
 * このファイルには yui540/css-animations 由来のコードが含まれています。
 * Portions of this file are derived from yui540/css-animations.
 *   https://github.com/yui540/css-animations
 *
 * 以下の @keyframes / パターンが該当します:
 *   slide-in, popup, seed-pop, breathe, shine-sweep
 * （原典から改変して使用しています / modified from the original）
 *
 * ---------------------------------------------------------------------------
 * MIT License
 *
 * Copyright (c) 2026 yui540
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * ========================================================================= */

/* ============================================================================
   pontsukapon — src/assets/css/portal.css （トップページ `/` 専用）
   ----------------------------------------------------------------------------
   由来: /_preview/dw-a/dw-a.css.njk（デザイン案「引き出し・控えめ」）。
   2026-08-03 に本採用し、名前空間を .page--portal → .page--portal へ改名して
   ここへ移した。**class → class の等価置換なので詳細度は1つも動いていない。**
   ・§0 のテーマ17トークンは src/assets/css/tokens.css §2 へ上げた（無彩の値で
     和紙の値を置き換えた）。--rule-hair / --shadow-lift / --shadow-lift-hi / --shadow-flat /
     --r-icon / --ease-inout / --dur-in / --dur-pop / --dur-step / --breathe-slow の
     10個も tokens.css へ昇格して改名した（--rule-hair / --shadow-lift /
     --shadow-lift-hi / --shadow-flat / --r-icon / --ease-inout / --dur-in /
     --dur-pop / --dur-step / --breathe-slow）。
   ・入場のキーフレーム5本（dw-tile-in / dw-pop / dw-fade-up / dw-rise /
     dw-breathe）は shell.css §7 へ上げた。ここに残るのはポータル固有の
     キーフレーム（光沢・引き出し・ライトボックス・着地の合図）だけ。
   ・プレビュー専用だった「モーション強制オン」の節は**ブロックごと削除**。
     §11 に彫ってあった門番も外し、素の reduce ブロックに戻してある。
     ★ 外し方は「門番の擬似クラスだけを消す」形にしてある（頭の `html ` /
       `html.js ` は残す）。単に .page--portal から始めると §11 の各行が
       class 1つぶん軽くなり、§6 の本体と同値で並ぶ行が出て順序頼みになる。
   ・素の要素セレクタ（h1{} a{} ul{}）は書かない。
   ============================================================================ */


/* ═══ 0. ポータル固有の変数 ═══════════════════════════════════════════════
   ★ 色（テーマ17トークン）と、共通に上げた10個は tokens.css が持つ。
     ここに残るのは「トップページ1枚の作り込み」にしか出てこない値だけ。
   ★★ --dw-raise は **body を付けずに .page--portal（0,1,0）で宣言すること。**
     §11 の `html .page--portal{ --dw-raise: 0px }`（0,1,1）が勝つ必要がある。
     ここを body.page--portal（0,1,1）にすると §11 と同値で並び、reduce で
     「持ち上がらないのに板の上だけ 8px 開く」が再発する。
   ★ tokens.css の禁則「body は透明」を守るため、地色はここに書かない
     （html{background:var(--bg)} が shell.css §3 にある）。
   ======================================================================== */

.page--portal{
  /* --- 版面。--shell-max-wide と同値なのでヘッダー・フッターと左右が揃う --- */
  --dw-max      : var(--shell-max-wide);
  --dw-gap-x    : clamp(16px, 3vw, 36px);
  --dw-gap-y    : clamp(28px, 4vw, 52px);

  /* --- アイコンが無いとき（icons.json fallback） --- */
  --dw-empty-bg : #EDEDED;            /* fallback.background.light */
  --dw-empty-ln : rgba(0,0,0,.28);    /* fallback.border.light */
  --dw-empty-fg : rgba(0,0,0,.55);    /* fallback.glyph */

  /* 板とパネルの面。--surface と完全に同値（明 #FFFFFF / 暗 #191919）。
     名前を残してあるので .dw-plate / .dw-panel は無変更で読める。 */
  --dw-plate-bg : var(--surface);

  /* ★★ ホバー／フォーカスでアイコンが持ち上がる量。
     板（§6 .dw-plate）の上の inset と、棚（§6 .dw-shelf）の translateY が
     **同じ変数を読む**。板は動かず棚だけが上へ動くので、動くぶんを先に
     開けておく形。すると
       上の残り ＝ (0.34*gapX + raise) − 膨らみ − raise ＝ 0.34*gapX − 膨らみ
     で左右の残りと恒等的に一致する（幅にもアイコンの寸法にも依らない）。
     ★ 1箇所で持つのが要。数字の 8px を §6 のどちらかへ書き戻さないこと。
     ★ §11（reduce）で 0px に倒れる（棚の transform も none になるため）。 */
  --dw-raise    : 8px;

  /* --- 斜めの光沢 ---
     ★★ --dw-sheen-sk を変えたら --dw-sheen-ang（atan(.52)）と
       --dw-sheen-hf（26cqw）の**両方**を直すこと。.52 と 26 は sk から
       導いた値。cqw は .dw-icon（container-type: inline-size）で解決される。
     ★ 帯の色は2つ。--dw-sheen-lo（暗い縁）が明るい地で効き、
       --dw-sheen-hi（明るい芯）が暗い地で効く。 */
  --dw-sheen-w  : 78px;
  --dw-sheen-sk : 52%;
  --dw-sheen-ang: atan(.52);
  --dw-sheen-hf : 26cqw;
  --dw-sheen-lo : rgba(0,0,0,.14);
  --dw-sheen-hi : rgba(255,255,255,.30);

  /* --- 引き出し --- */
  --dw-mt       : calc(var(--dw-gap-y) * .54);  /* 押した行とパネルの間 */
  --dw-pad      : clamp(22px, 2.8vw, 36px);     /* パネルの内側の余白 */
  --dw-line     : rgba(0,0,0,.10);              /* パネル内の唯一の罫 */
  --dw-shot-h   : clamp(210px, 22vw, 282px);    /* 画面の帯の高さ */
  --dw-dur-open : .46s;                         /* 開く（1秒未満） */
  --dw-dur-shut : .34s;                         /* 閉じる */

  /* --- ライトボックス ---
     ★★ --dw-lb-h は「拡大画像の最大の高さ」。**900px を超えさせない。**
       素材は長辺 900px（shots.json policy.longEdge）。超えると引き伸ばしになる。 */
  --dw-lb-h     : min(86dvh, 900px);
}

@media (prefers-color-scheme: dark){
  .page--portal{
    --dw-empty-bg : #1C1C1C;                 /* icons.json fallback.background.dark */
    --dw-empty-ln : rgba(255,255,255,.26);
    --dw-empty-fg : rgba(255,255,255,.55);
    --dw-line     : rgba(255,255,255,.12);
  }
}


/* ═══ 1. モーション語彙（ポータル固有のぶん）═══════════════════════════════
   ★ 汎用の入場5本（dw-tile-in / dw-pop / dw-fade-up / dw-rise / dw-breathe）は
     shell.css §7 にある。ここは光沢・引き出し・ライトボックス・着地の合図。
   ======================================================================== */
/* 斜めの光沢 — 出典: 2026-06-08 tips-2 shine-sweep。尺を 2s → .66s に詰めた
   ★★ 2026-08-03: clip-path → transform に置き換えた（Ryo さん「カクカク」報告）。
     元は .dw-sheen（白 30% の全面）の clip-path の polygon 座標を動かしていた。
     clip-path はコンポジットされないので **毎フレーム paint が走る**。
     いまは「幅 78px の帯（.dw-sheen::before）を skewX で傾けて translateX で
     走らせる」形。動かすのは transform 1本だけなので GPU に載る。
   ★ **見た目は 1px も変えていない。**下は 253px の箱での実測（両方式で一致）:
       0%   上辺 [-78, 0]        下辺 [-209.56, -131.56]
       50%  上辺 [153.28, 231.28] 下辺 [21.72, 99.72]
       100% 上辺 [384.56, 462.56] 下辺 [253, 331]
     帯の幅 78px、上辺が下辺より 131.56px（＝52%）右 ＝「斜めに光が走る」まま。
   ★ 帯がはみ出すぶんは .dw-icon の overflow:hidden が今までどおり刈る
     （clip-path のときと同じ器で、同じ角丸で刈っている）。
   ★ skewX は 2つの keyframe で同じ値。translateX だけが補間される。 */
@keyframes dw-sheen{
  from{
    transform: skewX(calc(var(--dw-sheen-ang) * -1))
               translateX(calc((var(--dw-sheen-w) + var(--dw-sheen-hf)) * -1));
  }
  to{
    transform: skewX(calc(var(--dw-sheen-ang) * -1))
               translateX(calc(100cqw + var(--dw-sheen-hf)));
  }
}
/* ★★ 2026-08-03（3回目）: 器（.dw-sheen）を「走っているあいだだけ」見せる。
   ホバー／フォーカスは **状態**（指が乗っているあいだ）なので opacity:1 を
   宣言で持てるが、タッチの発火（§6 の .is-lit）は **一度きりの出来事**で、
   合図のクラスは動きが終わっても DOM に残りうる。そこで opacity も
   animation で持たせ、**fill-mode を none にする**。
     ・走行中だけ opacity:1（アニメーションは宣言より強い）
     ・遅延中と終了後は規則が1つも適用されず、器は素の opacity:0 に戻る
   ＝ クラスが残っても、JS が後始末に失敗しても、**貼り付きようがない**。
   Ryo さんが実機で報告した「アイコンが浮いたまま残る」の再発を、
   JS の後始末ではなく CSS の構造で止めている。
   ★ from と to が同値なので補間は起きない。動かすのは opacity 1本だけで
     GPU に載る（keyframes の総数は増えるが、コンポジットされない箇所は
     引き出しの grid-template-rows 2件のまま）。 */
@keyframes dw-sheen-veil{
  from,to{ opacity: 1; }
}

/* ── §1b 引き出しのぶん（tile-drawer から値も形もそのまま）──────────────
   ★ 器（引き出しの高さ）だけは「行き過ぎて戻る」を持たせられない。
      grid-template-rows の fr は、親の高さが不定のとき flex factor が 1 を
      超えても中身の高さちょうどに解決される（CSS Grid §12.7.1）。
      そこで器は ease-in-out で素直に伸ばし、行き過ぎて戻るのは
      中身（dw-panel-in / dw-shot-in）と押されたアイコン（dw-sink）に持たせた。 */
@keyframes dw-drawer-open{
  from{ grid-template-rows: 0fr; margin-block-start: 0; }
  to  { grid-template-rows: 1fr; margin-block-start: var(--dw-mt); }
}
@keyframes dw-drawer-shut{
  from{ grid-template-rows: 1fr; margin-block-start: var(--dw-mt); }
  to  { grid-template-rows: 0fr; margin-block-start: 0; }
}
@keyframes dw-panel-in{
  from{ opacity:0; transform: translateY(9px); }
  60% { opacity:1; transform: translateY(-3px); }
  to  { opacity:1; transform: translateY(0); }
}
@keyframes dw-panel-out{
  from{ opacity:1; }
  to  { opacity:0; }
}
@keyframes dw-shot-in{
  from{ opacity:0; transform: translateY(14px) scale(.955); }
  58% { opacity:1; transform: translateY(-4px) scale(1.02); }
  to  { opacity:1; transform: translateY(0)    scale(1); }
}
@keyframes dw-sink{
  from{ transform: translateY(0)   scale(1); }
  55% { transform: translateY(6px) scale(.956); }
  to  { transform: translateY(4px) scale(.972); }
}

/* ── §1c ライトボックスのぶん（2026-08-03 追加）────────────────────────
   ★★ ここに @keyframes を足したら **§11 に必ず登録すること。**
      §11 に animation:none を書き忘れると、shell.css §6 の
      animation-duration:.01ms !important だけが残り、タイムラインが
      進まない環境（非表示ペイン）で 0% の姿のまま固まる。 */
@keyframes dw-lb-in{
  from{ opacity:0; transform: translateY(12px) scale(.955); }
  58% { opacity:1; transform: translateY(-4px) scale(1.015); }
  to  { opacity:1; transform: translateY(0)    scale(1); }
}
@keyframes dw-lb-out{
  from{ opacity:1; transform: scale(1); }
  to  { opacity:0; transform: scale(.985); }
}
/* 幕（::backdrop）。透過だけ。 */
@keyframes dw-lb-veil-in { from{ opacity:0 } to{ opacity:1 } }
@keyframes dw-lb-veil-out{ from{ opacity:1 } to{ opacity:0 } }
/* 前後へ移ったときの入れ替え。--sx は JS が向きで符号を変える。 */
@keyframes dw-lb-swap{
  from{ opacity:0; transform: translateX(var(--sx, 18px)); }
  60% { opacity:1; transform: translateX(calc(var(--sx, 18px) * -.14)); }
  to  { opacity:1; transform: translateX(0); }
}

/* ── §1d 着地の合図（2026-08-03 追加）────────────────────────────────────
   帯（§4b）を押して開発日記へ下りたとき、**どこへ来たのか** を1件目で示す。
   ★ 動かさない・音も色も足さない。--accent-soft（下の一覧のホバーと同じ地）を
     淡く出して引くだけ。ホバーと同じ地なので「新しい見た目」を1つも増やさない。
   ★ 透過だけを動かす。1件目の行そのものは 1px も動かさない
     （着地の直後に行が動くと、目当ての記事名を追えなくなる）。
   ★★ §11（reduce）に登録済み。 */
@keyframes dw-land{
  from{ opacity: 0; }
  16% { opacity: 1; }
  62% { opacity: 1; }
  to  { opacity: 0; }
}

/* ═══ 2. シェルの上書き（トップページだけ）═══════════════════════════════
   ★ 名前空間を外して裸の main{} / .shell-foot{} にしないこと。
     とくに .shell-foot{contain:layout} は containing block を作るので、
     LP のフッターに position:fixed の子孫が入った日に無言で壊れる。
   ======================================================================== */

.page--portal main{ padding-top: clamp(24px, 3.4vw, 40px); }

/* ★ フッターのレイアウトを内側で閉じる（§10 の引き出しのため）。
   引き出しの開閉で位置が変わる 178要素のうち **18要素** がここ。
   中身は引き出しの高さと無関係。
   ★ layout だけ。中に position:fixed / sticky の子孫は無い（実測）。
   ★ containing block ができるので **中の要素の offsetParent が変わる**
     （.shell-foot a … BODY → FOOTER.shell-foot、offsetTop 2964 → 82。実測）。
     連鎖の合計は保たれる（±1px の丸め）。offsetTop は必ず offsetParent を
     辿って足すこと。単発で読むと文書座標にならない。§10 の注記も参照。 */
.page--portal .shell-foot{ contain: layout; }

/* ═══ 3. スクロール入場の土台 ═════════════════════════════════════════════
   html.js が付いているときだけ隠す。JS が死んでも本文は残る。
   ------------------------------------------------------------------------
   ★★ 下の transition:none は消さないこと。reduce の中には書かない。
   shell.css §6 は reduce のとき * に transition-duration:.01ms !important を
   当てている。transition-property の初期値は all なので、この1行だけで
   order / opacity / grid-template-rows まで「.01ms かけて変化するもの」に
   なる。タイムラインが進まない環境（非表示ペイン・バックグラウンドタブ）で
   はその .01ms が終わらず、値が古いまま固まる。検証で実測した事故:
     JS が drawer.style.order = "7" を書いたのに算出値が "1" のまま動かず、
     引き出しが「行の直下」ではなく「押したタイルの直後」に出た。
   §11 にも同じ趣旨の1行があるが、あちらは  で門番されて
   いるのでモーションを出しているあいだは効かない。duration は shell.css が
   !important で握っているので、property を none にして静止値を即確定させる。
   （動きは transition ではなく @keyframes が持っているので何も失わない）
   ======================================================================== */
.page--portal [data-in],
.page--portal .dw-tile,
.page--portal .dw-close__ch,
.page--portal .dw-drawer{ transition: none; }

html.js .page--portal [data-in]:not(.dw-grid){ opacity: 0; }
html.js .page--portal .dw-tile{ opacity: 0; }
html.js .page--portal .dw-head.is-in{ opacity: 1; }
html.js .page--portal .dw-latest.is-in{ opacity: 1; }  /* §4b 最新の日記1件 */
html.js .page--portal .dw-diary.is-in{ opacity: 1; }   /* §9 開発日記 */
html.js .page--portal .dw-close.is-in{ opacity: 1; }


/* ═══ 4. 柱書き ═══════════════════════════════════════════════════════════
   ★★ 2026-08-03（4回目）: **`.dw-count` を丸ごと削除した。**
     Ryo さん「開発中の数の表示っているかな？いらない気がしている」。
     消えたのは「8 本 ／ 公開済み 3 ／ つくっている 5」の1行と、その罫。
     残ったのは一言（site.tagline）と、常時動く点だけ。

     ★ ここに在った 72px の「8」は **このページの最大文字** だった。
       消すと「画面の最大文字が TrumpGames のフォールバック字 TG」という
       倒錯（tile-drawer で 63.24px、寸法統一後は 50.6px）が戻ってくる。
       戻さないために §6 の .dw-icon__fallback を
       20cqw → clamp(15px, 7.5cqw, 19px) へ落とした。
       **いまページの最大文字はアプリ名（1280px で 23px）。**
       ＝「意味のある字が最大」という関係は保たれている。実測値は §6 の注記。

     ★ この行が持っていた下罫（＝グリッドの天井）は、§4c の
       「アプリ」の見出しの罫がそのまま引き継いでいる。罫が消えたわけではない。

     ★ 常時動く点（.dw-tagline__dot の dw-breathe）は残す。
       このページで唯一の無限ループ。reduce のときは §11 が
       animation:none で止めて、点は静止したまま出る。
   ======================================================================== */

.page--portal .dw-head{
  max-width: calc(var(--dw-max) + var(--gutter)*2);
  margin: 0 auto clamp(28px, 3.4vw, 44px);
  padding-inline: var(--gutter);
}

.page--portal .dw-tagline{
  display: flex; align-items: center; gap: var(--sp-3);
  margin: 0 0 clamp(20px, 2.4vw, 32px);
  font-size: var(--fs-small);          /* 13px 据え置き */
  letter-spacing: .04em;
  color: var(--fg-2);
}
.page--portal .dw-tagline__dot{
  inline-size: 7px; block-size: 7px; border-radius: 50%;
  background: var(--fg-1);
  flex: none;
  animation: dw-pop var(--dur-pop) var(--ease-inout) both,
             dw-breathe var(--breathe-slow) ease-in-out .5s infinite;
}

/* ★ .dw-tagline の margin-block-end（clamp(20px,2.4vw,32px)）は **残してある。**
   数の行が消えて .dw-head の中身が1行だけになったので、この余白は
   .dw-head の padding のように働く（下端は .dw-head の margin-block-end と
   相殺しない ＝ 親子の相殺は起きるが、値の大きい head 側が勝つだけ）。
   1280px の実測: 一言の下端 → トピックの見出しの上端 ＝ 43.52px（head の
   margin-block-end そのもの）。 */

html.js .page--portal .dw-head.is-in .dw-tagline{
  animation: dw-fade-up .42s var(--ease-inout) both;
}


/* ═══ 4c. 3つの項目の見出し（トピック／アプリ／開発日記）═════════════════════
   2026-08-03（4回目）追加。Ryo さん
   「トピック記事ですが、すぐ下に開発中のアプリがあるけど、ちゃんと項目ごとに
     したいので、トピック、アプリ、開発日記みたいな順で分けたい。」
   ------------------------------------------------------------------------
   ★★ **形は「開発日記」に揃えた。**（意図的に揃えている・変えていない）
     揃えた理由。3つは **同じ位** の項目で、順に読ませたいだけ。
     位が同じものに違う形を与えると、読む人は「この違いは何を意味するのか」を
     考えることになる（意味の無い差は、意味を探させるぶんだけ強い）。
     1つの形を3回使えば、差が無いこと自体が「同じ位が3つ」を言う。
     ★ 具体的には、いままで §9 の .dw-diary__head / .dw-diary__h に書いてあった
       値を **そのままこの共通クラスへ移した**（13px / weight 500 / 字間 .18em /
       下に 1px の罫）。**開発日記の見た目は1pxも変わっていない。**
       §9 側は移した2ブロックを消してある（同じ値を2箇所に置かない）。

   ★★ **グリッドより強くしない。**（主役はアプリ、という関係を崩さない）
     この見出しはページで下から2番目に小さい字（11.5px の次）で、weight は 500、
     色は --fg-1、地も枠も無い。区切りを作っているのは
       ・1px の罫（--rule ＝ 既にあるトークン。**新しい色は1つも足していない**）
       ・節と節のあいだの間隔（下の margin。1280px で 88px）
     の2つだけで、寸法・影・面のどれでもアイコンの列に触れていない。
     アイコン 253×253 が8枚（512,072px²）に対し、見出しは 13px の字が3〜5文字。

   ★★★ **margin の値を触る前に §4b と §5 の注記を必ず読むこと。**
     §5 の .dw-grid は margin-block-start: calc(var(--dw-gap-y) * -1) を持つ。
     これは「全タイルに同じ行間を与え、1行目のぶんをグリッド自身で相殺する」
     ための負の margin で、§10 の行判定（offsetTop の一致）が乗っている土台。
     .dw-sec は padding も border も持たないので、この負の margin は
     **直前の兄弟の margin-block-end と相殺する**。

       2026-08-03（3回目）まで  .dw-latest ⇄ .dw-sec（相手は帯の下 margin）
       いま                     .dw-sec__head ⇄ .dw-grid（相手はこの見出しの下 margin）

     隣接兄弟の相殺は「正の最大 ＋ 負の最小」なので、見出しの margin-block-end を
     M、グリッドの負を -G とすると
         .dw-grid の上端 ＝ 見出しの下端 + (M - G)
         1行目のタイル  ＝ .dw-grid の上端 + G ＝ 見出しの下端 + M
     ＝ **見出しの罫から1行目までの距離は M そのもの**。G は完全に消える。
     だから M に、帯が持っていたのと同じ clamp(28px, 3.4vw, 44px) を入れてある。
     1280px で 43.52px ＝ **差し込む前と同値**。グリッドの中は1pxも動かない。
     ★ M < G（43.52 < 51.2）なので相殺後は負（-7.68px）になり、.dw-grid の
       境界箱は見出しの罫より 7.68px 上から始まる。中身（1行目のタイル）は
       そこから G ＝ 51.2px 下なので、見え方は上の式のとおり。
       .dw-grid は地も枠も持たないので、箱が上に食い込んでも何も描かれない。
     ★ ホバーの板は §6 の負の inset でタイルの 20.24px 上まで伸びる（①の直しで
       8px 増えた）。罫までの余りは 43.52 - 20.24 = 23.28px。ぶつからない。
     ★ やってはいけない直し方: .dw-grid の負 margin をやめる／row-gap を戻す。
       §5 と §10 の「索引を使わずに行を揃える」土台ごと壊れる。

   コントラスト: 13px --fg-1 は 明 17.15:1 ／ 暗 17.12:1（§9 の表と同値）。
   ======================================================================== */

.page--portal .dw-sec__head{
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 18px;
  padding-block-end: clamp(12px, 1.4vw, 18px);
  border-block-end: 1px solid var(--rule);
}
.page--portal .dw-sec__h{
  margin: 0;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .18em;   /* 結び（§7）と同じ字間。小さく張った見出し */
  line-height: 1.6;
  color: var(--fg-1);
}

/* --- 罫から中身までの距離。節ごとに1行ずつ持つ --------------------------
   ★ 3つで値が違うのは、下に来るものが違うから。
       トピック … すぐ下が **面を持ったカード**（--surface）。カードの内側に
                  既に 21.76px の padding があるので、罫からは近くてよい。
       アプリ   … 上の ★★★ を参照。この値がそのまま1行目のタイルまでの距離。
                  アイコンは面も枠も持たない大きな図なので息が要る。
       開発日記 … margin を持たない（0）。1件目の padding（21.76px）が
                  そのまま罫からの距離になる。**元のままで触っていない。** */
.page--portal .dw-latest .dw-sec__head{ margin-block-end: clamp(12px, 1.4vw, 18px); }
.page--portal .dw-sec   .dw-sec__head{ margin-block-end: clamp(28px, 3.4vw, 44px); }

/* --- 入場（§3 の [data-in] に乗る）--------------------------------------
   ★ トピックの見出しは .dw-latest[data-in] の中に居るので、器の is-in で動く。
     アプリの見出しは器を持たない（.dw-sec には data-in が無い）ので、
     **見出し自身に data-in を付けてある**（index.njk）。§3 の
     `[data-in]:not(.dw-grid){opacity:0}` に当たるため、
     .is-in で起こす1行がここに要る（§3 の4行と同じ形）。
   ★★ ここに animation を足したら **§11 に必ず登録すること。** */
html.js .page--portal .dw-sec__head.is-in{ opacity: 1; }
html.js .page--portal .dw-latest.is-in .dw-sec__head,
html.js .page--portal .dw-sec__head.is-in{
  animation: dw-fade-up .42s var(--ease-inout) both;
}


/* ═══ 4b. 最新トピックの帯 ════════════════════════════════════════════════
   2026-08-03 追加 → 同日に作り直し。
   節番号を 4b にしているのは既にある番号を振り直さないため（§9 と同じ扱い）。
   ------------------------------------------------------------------------
   ★ 最初の版は「文が1本あるだけ」の控えめな帯だった（下の §9 と重複して
     見えないことを最優先した結果）。Ryo さんの評価は
     **「控えめすぎる／下の一覧を小さくしただけに見える」。**
     作り直しで足したのは4つ。**色は1つも足していない。**
       1. 面   … --surface の1枚のカード（地は --bg なので一段浮く）
       2. 図版 … 108.8px（1280px時）。下の一覧は 72px
       3. 記事名 … 22px（1280px時）。下の一覧は 16px
       4. 印   … 「最新」の反転ピル（--accent 地／--accent-ink 字）
     加えて抜粋を1行（下の一覧は2行）。

   ★★ **アプリのグリッドより強くしない**（主役はアプリ、という関係を崩さない）
     実測で比べた（1280px・アニメーション終了後）:
       いちばん大きい字   柱書きの数 72px/700 ＞ アプリ名 23px/700
                          ＞ **帯の記事名 22px/500** ＞ 下の一覧 16px/500
         ★ 帯の記事名は weight を 500 に落としてある。700 にすると
           23px/700 のアプリ名と同じ「濃さ」になって主従が並ぶ。
       面積             アイコン 253×253 が8枚 ＝ 512,072px²
                          ＞ 帯ぜんぶ 1120×175.5 ＝ 196,560px²
       影               アイコン --shadow-lift（18px ぼかし／不透明度 .10）
                          ＞ 帯 --shadow-flat（3px ぼかし／不透明度 .05）
     帯が勝っているのは「地を持っていること」だけで、寸法・影・ウェイトの
     3つすべてでグリッドの下に居る。

   ★★★ ここが今回いちばん壊れやすい。**margin の値を触る前に必ず読むこと。**

     ★★ 2026-08-03（4回目）の更新: 下の説明は **相手が §4c へ移った**。
       アプリの見出しを差し込んだので、.dw-grid の負 margin が相殺する相手は
       この帯の margin-block-end ではなく、**アプリの見出しの
       margin-block-end** になった。式そのものは下のまま正しい（M と G の
       関係は変わらない）。いまの M は §4c の
       `.dw-sec .dw-sec__head{ margin-block-end: clamp(28px,3.4vw,44px) }`。
       **この帯の margin-block-end を変えても1行目のタイルは動かない**
       （実測で確認済み。88px に変えても offsetTop は 43.52px 側で決まる）。

     §5 の .dw-grid は margin-block-start: calc(var(--dw-gap-y) * -1) を持つ。
     これは「全タイルに同じ行間を与え、1行目のぶんをグリッド自身で相殺する」
     ための負の margin で、§10 の行判定（offsetTop の一致）が乗っている土台。
     .dw-sec は padding-block も border も持たないので、この負の margin は
     .dw-sec の上端をすり抜けて、**直前の兄弟の margin-block-end と相殺する**。

     この帯を差し込む前  .dw-head  ⇄ .dw-sec   （相手は head の margin-block-end）
     差し込んだ後        .dw-latest ⇄ .dw-sec  （相手はこの帯の margin-block-end）

     隣接兄弟の相殺は「正の最大 ＋ 負の最小」なので、帯の margin-block-end を M、
     グリッドの負を -G とすると
         .dw-sec の上端 ＝ 帯の下端 + (M - G)
         1行目のタイル  ＝ .dw-sec の上端 + G ＝ 帯の下端 + M
     ＝ **帯の下端から1行目までの距離は M そのもの**。G は完全に消える。
     だから M に head と同じ clamp(28px, 3.4vw, 44px) を入れておけば、
     「柱書きの罫から1行目まで」だった距離が「帯の罫から1行目まで」に
     そっくり移るだけで、グリッドの中は 1px も動かない。
     1280 / 768 / 375 で実測して確認済み（1280px で 43.52px ＝ 差し込む前と同値）。

     ★ margin-block-start の負は **上の柱書きとの間隔だけ** を詰めるもので、
       グリッドとは無関係。作り直しでここだけ値を変えた（下に書く）。

     ★ やってはいけない直し方: .dw-grid の負 margin をやめる／row-gap を戻す。
       §5 と §10 の「索引を使わずに行を揃える」土台ごと壊れる。
       辻褄はこの帯の margin で合わせる。

   ★ 色は1つも足していない。--surface / --surface-2 / --fg-1 / --fg-2 /
     --fg-3 / --accent / --accent-ink / --accent-line / --rule-hair /
     --shadow-flat / --shadow-card / --ease-inout だけ。
   ★ 下の「開発日記」（§9）と同じ形にしない。**共通語ゼロを維持している。**
       下: 見出し「開発日記」＋「note ですべて読む ↗」／日付は左の欄に
           2026. と 07.22 をウェイトで打ち分け／抜粋2行／図版は行の右端に
           72px（スマホ 56px）／地は透明でホバーのとき沈む、というカードの一覧
       上: 「最新」の反転ピル／日付は文（「2026年7月22日に書いた」）／
           図版は左端に 108.8px（スマホ 80px）／抜粋1行／
           最初から面を持つ1枚のカード／閉じの語は「これまでの分 ↓」
     形も語も向き（↓ と ↗）も別物になっている。

   コントラスト実測（2026-08-03・**ブラウザの算出色から計算**。
   地が --bg から --surface に変わったので、前の版の表とは値が違う）
     明 面 #FFFFFF ／ 暗 面 #191919
                                    明 面     暗 面
       日付の文 11.5px --fg-3        5.83:1    5.23:1   ← この節でいちばん低い
       閉じの語 11.5px --fg-2        8.86:1    7.92:1
       抜粋     13px   --fg-2        8.86:1    7.92:1
       記事名   22px   --fg-1       19.03:1   15.71:1
       印       11.5px --accent-ink on --accent
                                    17.15:1   17.12:1
     いちばん低い 5.83 / 5.23 でも AA(4.5:1) を超える。
   ======================================================================== */

.page--portal .dw-latest{
  max-width: calc(var(--dw-max) + var(--gutter)*2);
  margin-inline: auto;
  padding-inline: var(--gutter);
  /* ★★ 2026-08-03（4回目）: 上下とも作り替えた。理由はどちらも「この帯が
       節（トピック）になり、上にも下にも見出しが立った」こと。

     ★ 上: **0 に戻した。** 前は
         calc(clamp(16px,1.8vw,24px) - clamp(28px,3.4vw,44px))
       という負の値で、柱書きの **数の行の罫** にカードを寄せていた。
       その罫は .dw-count ごと消えた（③）ので、寄せる相手が居ない。
       いまここに来るのは「トピック」の見出しで、柱書きとのあいだは
       .dw-head の margin-block-end がそのまま持つ（1280px で 43.52px）。
       負を残すと見出しが柱書きへ 20.48px 近寄って、節の頭に見えなくなる。

     ★★ 下: **もう「1行目のタイルまでの距離」ではない。**
       .dw-grid の負 margin の相殺相手は、アプリの見出し（§4c）へ移った。
       ここは「トピックのカード → アプリの見出し」の間隔なので、
       節と節のあいだの値 clamp(56px, 7vw, 88px)（＝ §9 .dw-diary の
       margin-block-start と同値）にしてある。3つの節の切れ目が同じ間隔になる。
       1280px で 88px。**1行目のタイルまでの距離 43.52px は §4c が持っている。** */
  margin-block-start: 0;
  margin-block-end: clamp(56px, 7vw, 88px);
}

/* --- カード本体。**帯ぜんぶが1つの押しどころ**（<a href="#dw-diary-h">）。
   ★ 前の版は帯の中に note へのリンクと下りるリンクの2本があり、
     下りるほうの当たり判定が 100.8×19.5px（24px 未満）だった。
     いま押しどころは帯ぜんぶ ＝ 1120×175.5px（1280px時）／
     350×176px（390px時）で、どちらも 44px を大きく超える。
   ★ 左＝図版／右＝字の2列。下の一覧は図版が **右端** なので、
     並びの向きからして別の形になっている。 */
.page--portal .dw-latest__card{
  --lt-thumb: 80px;                 /* 図版の一辺。660px 以上で大きくなる */
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: clamp(14px, 1.7vw, 22px);
  align-items: start;
  padding: clamp(14px, 1.6vw, 22px);
  border-radius: 16px;
  background: var(--surface);
  color: inherit;
  text-decoration: none;
}
/* ★★ 2026-08-03: box-shadow の transition をやめ、2枚の疑似要素の
   opacity クロスフェードに置き換えた（§6 .dw-shelf と同じ手）。
   この帯は 1120×162.5px（1280px 時の実測）＝このページで最大の被描画ボックス。
   影を .3s かけて焼き直すと毎フレーム 18万画素の再ラスタになる。
   ★ **値は 1px も変えていない。**::before が静止、::after がホバー。
   ★ inset:0 で **border box にちょうど重なる**。絶対配置の inset が基準に
     するのは containing block の **padding box** で、padding box は
     border box から border を引いた箱＝この帯には border が無いので同じ箱。
     （padding は padding box の内側なので引かない。ここを content box と
       取り違えて負に開くと、影が上下左右 20.48px ぶん外へずれる。実測で修正）
   ★ どちらも外向きの影しか持たない（背景も枠も無い）ので、
     DOM 順で字より後ろに来ても字を覆わない。 */
.page--portal .dw-latest__card::before,
.page--portal .dw-latest__card::after{
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 16px;
  pointer-events: none;
  transition: opacity .3s var(--ease-inout);
}
.page--portal .dw-latest__card::before{
  box-shadow: 0 0 0 1px var(--rule-hair), var(--shadow-flat);
  opacity: 1;
}
.page--portal .dw-latest__card::after{
  box-shadow: 0 0 0 1px var(--accent-line), var(--shadow-card);
  opacity: 0;
}
@media (min-width: 660px){
  .page--portal .dw-latest__card{ --lt-thumb: clamp(96px, 8.6vw, 120px); }
}

/* --- 図版。下の一覧（72px／スマホ 56px）より明確に大きい。
   本番は note の CDN の横長画像が来るので object-fit:cover。
   寸法は CSS が固定で持つので、404 でも帯の高さは1pxも動かない。 */
.page--portal .dw-latest__thumb{
  inline-size: var(--lt-thumb);
  block-size: var(--lt-thumb);
  border-radius: 14px;
  object-fit: cover;
  background: var(--surface-2);
  box-shadow: 0 0 0 1px var(--rule-hair);
}

.page--portal .dw-latest__body{ display: block; min-inline-size: 0; }

/* --- 印と日付の行 ------------------------------------------------------- */
.page--portal .dw-latest__meta{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin-block-end: 7px;
}

/* 「最新」の印。地と字を入れ替えるだけ（無彩のまま・新しい色は無し）。
   ★ 11.5px より小さくしない（このページの最小の字は 11.5px）。
   ★ 字間を空けたぶん最後の1字の右に余りが出るので、
     padding を左右で 1px ずらして見た目の中心を戻している。 */
.page--portal .dw-latest__badge{
  flex: none;
  padding: 3px 8px 3px 10px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .16em;
  line-height: 1.5;
}

/* 日付。★ §9 の「同じ大きさをウェイトで打ち分ける」は **使わない**。
   あれは下の一覧の形なので、ここで繰り返すと同じものが2回出て見える。
   ここでは日付を数字の欄ではなく文として置く（「2026年7月22日に書いた」）。
   nowrap なのは、文の途中で改行されると日付に見えなくなるため。 */
.page--portal .dw-latest__when{
  font-size: 11.5px;
  font-weight: 300;
  letter-spacing: .18em;
  line-height: 1.7;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: var(--fg-3);
}

/* --- 記事名。帯の主役。
   ★ **下線は引かない。** この字はもう note へのリンクではない
     （押すと下の一覧へ下りる）。下線を引くと「記事へ飛ぶ」と嘘をつく。
     押せることは、面・ホバーの反応・「これまでの分 ↓」の3つが持つ。
   ★ weight は 500。700 にするとアプリ名（23px/700）と濃さが並んで
     主従が崩れる（この節の冒頭の比較を参照）。
   ★ 2行で止める。長い記事名でスマホの帯が伸びるのを防ぐ。 */
.page--portal .dw-latest__title{
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  font-size: clamp(18px, 1.02rem + .55vw, 22px);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: .012em;
  color: var(--fg-1);
}

/* --- 抜粋は **1行だけ**（下の一覧は2行）。同じ形にしないため。 */
.page--portal .dw-latest__excerpt{
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  line-clamp: 1;
  overflow: hidden;
  margin-block-start: 6px;
  font-size: 13px;
  line-height: 1.75;
  color: var(--fg-2);
}

/* --- 閉じの語。カードの右下に置く。 */
.page--portal .dw-latest__jump{
  display: block;
  margin-block-start: 10px;
  text-align: end;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: .18em;
  line-height: 1.7;
  color: var(--fg-2);
  /* ★ color はコンポジットされない（2026-08-03 の棚卸しで残した3本のうち1本）。
     置き換えなかった理由: 動くのは 11.5px の1行ぶんの字だけで、
     再描画は数百画素。しかもホバー中だけで、同時に走る animation が無い。
     opacity のクロスフェードにするには字を2重に持つことになり、
     読み上げに同じ語が2回出る。費用対効果が逆。 */
  transition: color .3s var(--ease-inout);
}

/* 矢印は ↓。ページの中を下りるだけなので下へ動かす
   （§9 .dw-diary__arw・§10 .dw-store__arw の ↗ ＝ 外へ出る、とは別物）。 */
.page--portal .dw-latest__arw{
  display: inline-block;
  margin-inline-start: .5em;
  transition: transform .3s var(--ease-inout);
}
/* ★ 門番あり（§6 .dw-plate の注記を参照）。:focus-visible は囲わない。 */
.page--portal .dw-latest__card:focus-visible .dw-latest__arw{ transform: translateY(3px); }
@media (hover: hover) and (pointer: fine){
  /* ★ ホバーで持ち上げない（--shadow-lift はアイコンのもの。並べると主従が崩れる）。
     枠を1段濃くして、閉じの語を --fg-1 へ寄せるだけ。 */
  .page--portal .dw-latest__card:hover::before{ opacity: 0; }
  .page--portal .dw-latest__card:hover::after { opacity: 1; }
  .page--portal .dw-latest__card:hover .dw-latest__jump{ color: var(--fg-1); }
  .page--portal .dw-latest__card:hover .dw-latest__arw{ transform: translateY(3px); }
}

/* 焦点枠（§8 と同じ形。入力手段に依らないので門番の外） */
.page--portal .dw-latest__card:focus-visible{
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: 16px;
}

/* --- 入場（§3 の [data-in] に乗る）--------------------------------------
   ★★ ここに animation を足したら **§11 に必ず登録すること。**
      書き忘れると、shell.css §6 の .01ms だけが残って 0% の姿
      （opacity:0）のまま固まる。 */
html.js .page--portal .dw-latest.is-in .dw-latest__thumb{
  animation: dw-pop .46s var(--ease-inout) both;
}
html.js .page--portal .dw-latest.is-in .dw-latest__meta{
  animation: dw-fade-up .42s var(--ease-inout) both;
}
html.js .page--portal .dw-latest.is-in .dw-latest__title,
html.js .page--portal .dw-latest.is-in .dw-latest__excerpt,
html.js .page--portal .dw-latest.is-in .dw-latest__jump{
  animation: dw-fade-up .44s var(--ease-inout) .07s both;
}


/* ═══ 5. グリッド本体 ═════════════════════════════════════════════════════ */

.page--portal .dw-sec{
  max-width: calc(var(--dw-max) + var(--gutter)*2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ★ row-gap は 0 のまま。行間はタイルの margin-block-start が持つ。
     理由は §10 冒頭（引き出しの行に gap が2本残ると、閉じ終わりに下の行が跳ねる）。

   ★★ 1行目のぶんの打ち消し方（2026-08-03 に作り替えた・下の .dw-tile も参照）
     行間を margin へ逃がすと、1行目のタイルにも margin が付いて頭に余白が出る。
     元はこれを nth-child の表で消していた（既定 (1),(3) ／ 3列 (5) ／ 4列 …）が、
     それは「1行目に何枚並ぶか」を CSS に手で書き写す索引依存で、
     公開済みが3本から4本になった瞬間に破綻する（1行目が4枚になっても
     nth-child(7) は 0 をもらえず、行仲間と offsetTop が 51.2px ずれる。
     ずれると §10 の「同じ行のタイルは offsetTop が一致する」前提が崩れ、
     引き出しが1行違う場所に開く）。
     ★ そこで表を全部やめて、**全タイルに同じ margin を与え、グリッド自身を
       1つぶん引き上げる**。1行目が何枚でも、行が何本でも、列数がいくつでも、
       強制改行が何本入っても成立する。CSS からも JS からも索引が消えた。
     ・グリッドの負の margin は **直前の兄弟の margin-block-end と相殺する**
       （隣接兄弟の相殺は 正の最大 ＋ 負の最小）。結果、その兄弟の下端からの
       距離は「兄弟の margin-block-end」そのままで、表を書いていた頃と
       1px も変わらない（1280/768/390/375 で実測して確認済み）。
       ★★ **相手は3回変わっている。値を触る前に相手を確かめること。**
           2026-08-03 まで        .dw-head   （.dw-sec の上端をすり抜けて）
           帯を差し込んだ後        .dw-latest （§4b）
           見出しを差し込んだ後    .dw-sec__head（§4c）← **いまここ**
       いまは .dw-sec の中に見出しという先客が居るので、負の margin は
       .dw-sec の上端をすり抜けない（すり抜けるのは「最初の子」のときだけ）。
     ・別解として row-gap を戻し、引き出しに負の margin を当てて gap を打ち消す
       手もあるが、そちらは @keyframes dw-drawer-open/shut の margin 値まで
       書き換えることになる。§10 の「引き出しの仕組みは1行も変えていない」を
       保つほうを採った。 */
.page--portal .dw-grid{
  list-style: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--dw-gap-x);
  row-gap: 0;
  align-items: start;
  margin-block-start: calc(var(--dw-gap-y) * -1);   /* ★ 1行目のぶんを相殺 */
}
@media (min-width: 660px){
  .page--portal .dw-grid{ grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1000px){
  .page--portal .dw-grid{ grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* --- ★ 制作中の1本目を、必ず行頭から始める（2026-08-03）------------------
   grid-column-start だけを与える。自動配置は「列位置が確定している要素の
   inline-start が、直前のカーソル位置より手前なら行を1つ送る」ので、
   これだけで強制改行になる（CSS Grid §8.5 手順4・sparse packing）。
   ★ grid-row は与えないこと。行番号を書いた瞬間に、引き出しが割り込んだ
     ぶんのずれを自分で計算する羽目になる。列だけ固定して行はブラウザに任せる。
   ★ 4列のときは公開済みが3本なので1行目の右端が1マス空く。これは意図どおり。 */
.page--portal .dw-tile--break{ grid-column-start: 1; }

/* --- 行間 ---------------------------------------------------------------
   ★ 例外なし。1行目も同じ値をもらい、そのぶんは上の .dw-grid の負の margin が
     まとめて相殺する。**ここに nth-child を書き足さないこと。**
     書いた瞬間に「1行目は何枚か」を CSS が知っている状態＝索引依存に戻り、
     本数が変わった日に引き出しが1行違う場所へ開く。 */
.page--portal .dw-tile{ margin-block-start: var(--dw-gap-y); }

/* --- 入場: 左上から右下へ、対角線の波で弾んで並ぶ ------------------------
   遅延 --d は「行 + 列」。間に引き出しの li が挟まるのでタイルは奇数番目の子。
     1本目→1  2本目→3  3本目→5  4本目→7
     5本目→9  6本目→11 7本目→13 8本目→15
   ★ 強制改行を入れたので、どの列数でも「4本目（en, nth-child(7)）が行頭」。
     下の3つの表はその前提で引き直してある。行が変わったら必ずここも直す。
   ★★ ここから下の nth-child は **入場の遅延（--d）だけ** を持つ。
     寸法には一切効かない（行間は上の1行が全タイルへ均一に与えている）。
     なので本数や列数が変わって表が古くなっても、崩れるのは
     「波の順番が少し不自然になる」だけで、行の判定も余白も壊れない。
     ★ 逆に言えば、寸法に効く値をこの表へ戻さないこと。 */
.page--portal .dw-tile{ --d: 0; }

/* ★ 2026-08-12: **8本 → 6本**（トモリ と TrumpGames を外した。works.js の記録）。
   nth-child(13) と (15) の行を落としてある。**寸法は何も直していない** ——
   このファイルの他の注記に残る「8枚」は当時の実測値で、結論（列いっぱい・
   baseline の3行・光沢の規則）は枚数に依らないため、記録として残してある。

   2列（既定）
     botch 0  Pareidol 1
     こぼれび 1  ―
     en 2  とめばん 3
     よりゃんせ 3                     */
.page--portal .dw-tile:nth-child(1){ --d:0; }
.page--portal .dw-tile:nth-child(3),
.page--portal .dw-tile:nth-child(5){ --d:1; }
.page--portal .dw-tile:nth-child(7){ --d:2; }
.page--portal .dw-tile:nth-child(9),
.page--portal .dw-tile:nth-child(11){ --d:3; }

/* 3列
     botch 0  Pareidol 1  こぼれび 2
     en 1  とめばん 2  よりゃんせ 3   */
@media (min-width: 660px){
  .page--portal .dw-tile:nth-child(1){ --d:0; }
  .page--portal .dw-tile:nth-child(3),
  .page--portal .dw-tile:nth-child(7){ --d:1; }
  .page--portal .dw-tile:nth-child(5),
  .page--portal .dw-tile:nth-child(9){ --d:2; }
  .page--portal .dw-tile:nth-child(11){ --d:3; }
}
/* 4列
     botch 0  Pareidol 1  こぼれび 2  ―
     en 1  とめばん 2  よりゃんせ 3
   ★ 現状は公開済みが3本なので1行目の右端が1マス空く。Pareidol が App Store に
     出て4本になれば1行目はちょうど埋まる。**そのとき直すのはこの遅延の表だけで、
     寸法は何も直さなくていい。** 行間はもう索引を見ていない。 */
@media (min-width: 1000px){
  .page--portal .dw-tile:nth-child(1){ --d:0; }
  .page--portal .dw-tile:nth-child(3),
  .page--portal .dw-tile:nth-child(7){ --d:1; }
  .page--portal .dw-tile:nth-child(5),
  .page--portal .dw-tile:nth-child(9),
  .page--portal .dw-tile:nth-child(15){ --d:2; }
  .page--portal .dw-tile:nth-child(11){ --d:3; }
  .page--portal .dw-tile:nth-child(13){ --d:4; }
}

html.js .page--portal .dw-grid.is-in .dw-tile{
  animation: dw-tile-in var(--dur-in) var(--ease-inout)
             calc(var(--d) * var(--dur-step)) both;
}


/* ═══ 6. タイル1枚 ════════════════════════════════════════════════════════ */

/* ★ container-type を敷いて、名前の大きさを「列の実寸」から決める。
     ビューポート基準の clamp だと 4列に切り替わった直後（1000〜1120px）だけ
     列が細くなり、いちばん長い名前「en（円コレクション）」が折り返す。
     列そのものを測れば、どの幅でも1行に収まる。
     ★ contain は layout / inline-size / style のみ。paint は入らないので
       .dw-plate の負の inset（列の外へはみ出す板）は今までどおり見える。 */
.page--portal .dw-cell{
  position: relative;
  display: block;
  color: inherit;
  text-decoration: none;
  container-type: inline-size;
}
/* この案では8枚とも押せる。JS が role="button" を付けたときだけ指を出す。 */
html.js .page--portal .dw-cell[aria-expanded]{ cursor: pointer; }

/* 板。ホバー/フォーカスで立ち上がり、開いているあいだは出したままにする。
   ★★ 2026-08-03（4回目・Ryo さん「上の枠が余白ないのが気になる」）★★
     **上の inset にだけ --dw-raise（＝棚の持ち上げ量）を足してある。**
     板は動かない・棚だけが上へ動くので、動くぶんを先に開けておく形。
       上の残り ＝ (0.34*gapX + raise) − 膨らみ − raise ＝ 0.34*gapX − 膨らみ
       左右の残り ＝ 0.34*gapX − 膨らみ
     ＝ **恒等的に一致する。**幅にもアイコンの寸法にも依らない。
     実測（ホバー中・棚と板の矩形の差。上 / 左 / 右）
       1280px  7.81 / 7.81 / 7.81   （直す前は −0.19 / 7.81 / 7.81）
        768px  4.15 / 4.15 / 4.15   （直す前は −3.85 / 4.15 / 4.15）
        390px  2.51 / 2.51 / 2.51   （直す前は −5.48 / 2.51 / 2.51）
        375px  2.65 / 2.65 / 2.65   （直す前は −5.35 / 2.65 / 2.65）
   ★ 上に伸びたぶんが上の行の板とぶつからないこと（板の下は 0.28*gapY）:
       1280px  20.24 + 14.34 = 34.58 < 行間 51.20
        768px  15.83 +  8.60 = 24.43 < 行間 30.72
        390px  13.44 +  7.84 = 21.28 < 行間 28.00
     どの幅でも行間の内側に収まる。1行目の上も §4c の 43.52px（1280px）に対し
     20.24px なので、アプリの見出しの罫には触れない。
   ★ 静止時は opacity:0（板はホバー／フォーカスのときしか出ない）。
     **版面・タイルの位置・行の高さは1pxも動いていない**（実測で確認済み）。
   ★ --dw-raise は §11（reduce）で 0px に倒れる。あちらは棚の transform も
     none にするので、持ち上がらない環境では上下左右が元どおり同じ値に戻る。 */
.page--portal .dw-plate{
  position: absolute;
  inset: calc(var(--dw-gap-x) * -.34 - var(--dw-raise)) calc(var(--dw-gap-x) * -.34) calc(var(--dw-gap-y) * -.28);
  border-radius: 18px;
  background: var(--dw-plate-bg);
  box-shadow: var(--shadow-card);
  opacity: 0;
  transform: scale(.975);
  transition: opacity .3s var(--ease-inout), transform .3s var(--ease-inout);
  pointer-events: none;
}
/* ★★ ホバーの門番（2026-08-03・Ryo さん実機報告「ロゴが枠より大きい」）★★
   ------------------------------------------------------------------------
   タッチ端末は「タップした要素に :hover が残り続ける」。指を離しても、
   別のところを触るまで剥がれない。だから下の .dw-shelf の
   （2026-08-03 まで .dw-icon が持っていた）
   translateY(-8px) scale(1.035) が掛かりっぱなしになり、板（.dw-plate）の
   余白 inset: calc(var(--dw-gap-x) * -.34) を食い破る。
     390px では --dw-gap-x が clamp の下限 16px ＝ 板の余白は **5.44px**。
     一方 scale(1.035) の膨らみは 253px の箱で片側 +4.4px、
     さらに translateY(-8px)。上へは 8px はみ出す。＝「枠より大きい」。
   ------------------------------------------------------------------------
   ★ この案の :hover は **例外なく** `@media (hover: hover) and (pointer: fine)`
     の中に置く。§11（reduce）の中のものも同じ。
   ★ **:focus-visible は絶対に囲わない。** キーボード操作の唯一の手掛かりで、
     入力手段が hover を持つかどうかとは無関係。だから毎回この2つは
     行を分けて書く（今までは1つの規則にカンマで同居していた）。
   ★ hover を **打ち消している** 規則（§10 .dw-tile.is-open .dw-cell:hover）も
     同じ @media の中に入れる。外に残すと、hover 本体だけ消えて打ち消しが浮く。
   ★ タッチの手応えは既にある :active が持つ（下の .dw-cell:active .dw-shelf）。
     :active は指を離せば必ず剥がれるので貼り付かない。新しい見た目は足さない。
   ★ 検証は CSSOM で行う（ブラウザペインではタッチをエミュレートできない）。
     portal.css の :hover を全部数えて、(hover: hover) の外が 0 であること。 */
.page--portal .dw-cell:focus-visible .dw-plate{ opacity: 1; transform: scale(1); }
@media (hover: hover) and (pointer: fine){
  .page--portal .dw-cell:hover .dw-plate{ opacity: 1; transform: scale(1); }
}

/* --- 棚（★ この案で足した唯一の要素）---------------------------------------
   列の幅ぶんの正方形。中のアイコンを下端で揃える。

   ★★ **下端揃えの役目は**いまは効いていない。 ★★
     （持ち上げと影の役目は下の「役目が2つ増えた」を参照。そちらは効いている）
     2026-08-03 に8枚とも列いっぱい（1280px で 253×253）へ統一したので、
     棚とアイコンが同寸になり、align-items:flex-end が受け止めるものが無い。
     名前から下の3行が8枚とも同じ高さに並ぶ（1280px で 名前 +269 / 説明 +310 /
     状態 +364、タイル上端からの距離）のは、いまはアイコンが同寸だからであって、
     棚のおかげではない。

   ★ それでも外さずに残す理由（＝この要素の現在の役目）
     「名前の位置は列幅ぶんの正方形が決める」という不変条件を、
     アイコンの実寸から切り離して1箇所に置いておくため。
     制作中だけまた縮める・アイコンごとに余白を変える、といった案が出たとき、
     棚があれば下端で受けて名前から下は動かない。棚が無いと、その時点で
     8枚の baseline が崩れ、原因が「アイコンの寸法」に散る。
   ★★ **もう外せない**（2026-08-03 の「カクカク」対応で条件が変わった）。
     以前ここには「8枚のアイコンが常に同寸だと確定したら、.dw-shelf を
     index.njk ごと削って .dw-icon を .dw-cell の直下へ」と書いてあった。
     いまは棚が **持ち上げと外側の影** を持っているので、削ると
     ホバーの動きと影が丸ごと消える。どうしても削るなら、下の
     transform / ::before / ::after を先に移す先を用意すること
     （.dw-icon は overflow:hidden なので影の置き場にはならない）。
   ★ margin-block-start でずらすと .dw-cell と相殺して棚ごと下がる。
     flex の align-items:flex-end なら相殺しない。

   ★★ 2026-08-03（Ryo さん「カクカク」報告）で役目が2つ増えた ★★
     3. **持ち上げ（transform）をここが持つ。**元は .dw-icon が持っていた。
     4. **外側の影を2枚の疑似要素で持つ。**元は .dw-icon の box-shadow だった。
     理由: 元は .dw-icon が `transition: transform .34s, box-shadow .34s` と
     1本の transition に2つを混ぜていた。box-shadow はコンポジットされないので
     ホバー中は毎フレーム main-thread で影を焼き直すことになり、
     同居している transform までコンポジタから降りる。＝ホバーがカクつく。
     影を opacity のクロスフェードへ移せば、動くのは transform と opacity だけ。
     ★ 影は .dw-icon の内側に置けない（overflow:hidden に刈られる）。
       棚とアイコンは同寸・同位置（1280/768/375 で実測済み）なので、
       棚に置いて棚ごと動かせば、影は今までどおりアイコンと一緒に持ち上がる。
     ★ position:relative と transform は **レイアウトを1pxも動かさない。**
       「名前の位置は列幅ぶんの正方形が決める」という上の不変条件はそのまま。 */
.page--portal .dw-shelf{
  position: relative;
  display: flex;
  align-items: flex-end;
  inline-size: 100%;
  aspect-ratio: 1 / 1;
  transition: transform .34s var(--ease-inout);
  --dw-sh-rest: var(--shadow-flat);      /* 静止の影。公開済みは下で --shadow-lift へ */
}
/* 影の2枚。値は元の box-shadow と 1px も変えていない。
     ::before … 静止（制作中 --shadow-flat / 公開済み --shadow-lift / 開いている --shadow-flat）
     ::after  … ホバーとフォーカス（--shadow-lift-hi）
   ★ どちらも外向きの影しか持たない（背景も枠も無い）。外向きの box-shadow は
     border-box の内側に描かれないので、::after が DOM 順でアイコンより後ろに
     来てもアイコンを覆わない。
   ★ 内側の毛髪線（inset 0 0 0 1px）は静止値なので .dw-icon に残したまま。 */
.page--portal .dw-shelf::before,
.page--portal .dw-shelf::after{
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--r-icon);
  pointer-events: none;
  transition: opacity .34s var(--ease-inout);
}
.page--portal .dw-shelf::before{ box-shadow: var(--dw-sh-rest); opacity: 1; }
.page--portal .dw-shelf::after { box-shadow: var(--shadow-lift-hi); opacity: 0; }

/* --- アイコンの箱（icons.json frame の値をそのまま使う） ---------------- */
.page--portal .dw-icon{
  position: relative;
  display: block;
  inline-size: 100%;
  aspect-ratio: 1 / 1;
  border-radius: var(--r-icon);
  overflow: hidden;
  container-type: inline-size;
  box-shadow: inset 0 0 0 1px var(--rule-hair);
  transform: translateZ(0);
}
/* ★ 階層。寸法では付けない（8枚とも列いっぱい・2026-08-03）。
   残る差は3つ。どれも「大きさ」以外の手段で、統一後もそのまま生きている。
     1. 影      公開済み --shadow-lift（浮いている） / 制作中 --shadow-flat（平ら）← ここ
     2. ウェイト 公開済み 700 / 制作中 500                        ← §6 .dw-name
     3. 状態の点 公開済み 塗り / 制作中 輪郭だけ                   ← §6 .dw-mark
   さらに「制作中は必ず別の行から始まる」（§5 .dw-tile--break）が、
   4本目以降をひとまとまりとして切り出す最も強い階層になっている。 */
.page--portal .dw-tile--out .dw-shelf{ --dw-sh-rest: var(--shadow-lift); }

.page--portal .dw-icon img{
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

/* ★ 門番あり（上の .dw-plate の長い注記を参照）。**ここが実機の事故の本体。**
   translateY(-8px) scale(1.035) がタッチで貼り付き、板の余白を食い破る。
   ★★ 2026-08-03（4回目）: 持ち上げ量を **--dw-raise（既定 8px）** に変えた。
     値は同じ 8px で、**PC のホバーの動きは1pxも変わっていない。**
     変数にしたのは、板の上の inset（上のブロック）が同じ値を読むため。
     ここを深くすれば板の上も自動で開き、浅くすれば自動で閉じる。
     ★ **数字の 8px をここへ書き戻さないこと。**書いた瞬間に板と食い違い、
       Ryo さんの「上の枠に余白が無い」が戻る。 */
.page--portal .dw-cell:focus-visible .dw-shelf{ transform: translateY(calc(var(--dw-raise) * -1)) scale(1.035); }
.page--portal .dw-cell:focus-visible .dw-shelf::before{ opacity: 0; }
.page--portal .dw-cell:focus-visible .dw-shelf::after { opacity: 1; }
@media (hover: hover) and (pointer: fine){
  .page--portal .dw-cell:hover .dw-shelf{ transform: translateY(calc(var(--dw-raise) * -1)) scale(1.035); }
  .page--portal .dw-cell:hover .dw-shelf::before{ opacity: 0; }
  .page--portal .dw-cell:hover .dw-shelf::after { opacity: 1; }
}
/* ★ 押し込み。**囲わない。**タッチの手応えはこれが持つ。
   ★ ここは --dw-raise を使わない（持ち上げではなく押し込みで、量も向きも別物）。
   ★★ 2026-08-03（4回目）に余裕が増えた。板の上の inset に --dw-raise が
     足されたので、押し込みの側は上へ 8px ぶん楽になっている。実測（390px・
     アイコン 167px・scale(.975) の片側の縮み 2.09px）:
       上   13.44 + 2.09 − 2 = 13.53px 残る（直す前は 5.53px）
       左右  5.44 + 2.09     =  7.53px 残る（変わらず）
     ★ それでも **ここの translateY を深くしないこと。**深くしてよいのは
       上（板が 8px 先に開いている）だけで、左右は 390px で 7.53px しか無い。
       --dw-gap-x やアイコンの寸法をいじったら必ず測り直すこと。
   ★ 上の :hover / :focus-visible と同じ詳細度なので、**必ず後ろに書くこと。**
     ホバー中に押したときは押し込みが勝つ（従来どおり）。 */
.page--portal .dw-cell:active .dw-shelf{ transform: translateY(-2px) scale(.975); }

/* --- 斜めの光沢 ---------------------------------------------------------
   ★★ 2026-08-03: 器（.dw-sheen）と帯（::before）に分けた。
     元は .dw-sheen 自身が「白 30% の全面」で、その clip-path の polygon を
     動かして帯に見せていた。clip-path はコンポジットされないので毎フレーム
     paint が走る。いまは帯を実体として持ち、transform で走らせる。
     ★ 器は透明。光っているあいだだけ opacity:1 にするのは今までと同じで、
       帯の初期位置（＝@keyframes の from と同じ値）も持たせてある。
       動きを切られた環境で帯が箱の中に居座らないための保険。 */
.page--portal .dw-sheen{
  position: absolute; inset: 0;
  opacity: 0;
  pointer-events: none;
}
.page--portal .dw-sheen::before{
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: var(--dw-sheen-w);
  /* ★★ 2026-08-03（2回目）: 単色 → 2色のグラデーション。
     幅も傾きも尺も変えていない。**帯の中の色の並びだけ**が変わった。
       0%   透明
       26%  --dw-sheen-lo（黒 14%）… 明るい地はここで沈む
       74%  --dw-sheen-hi（白 30%）… 暗い地はここで持ち上がる
       100% 透明
     ★ 26%→74% は premultiplied 補間なので途中に「無色の谷」ができない
       （中点は rgba(174,174,174,.22) 相当）。地が中間調でも切れ目なく1本の
       形に見える。黒帯と白帯を離して2本置くと、白い地では黒だけが2本の線に
       見えてしまう。
     ★ 90deg ＝ 帯の**横断方向**。skewX は行ごとに水平にずらす変形なので、
       このグラデーションの縞は帯の傾きにそのまま追随する（別途傾けない）。
     ★ 描くのは静止時の1回だけ。動くのは下の transform だけなので、
       コンポジットされる箇所は増えていない（単色→グラデでも同じ）。 */
  background: linear-gradient(90deg,
                rgba(0,0,0,0)         0%,
                var(--dw-sheen-lo)   26%,
                var(--dw-sheen-hi)   74%,
                rgba(255,255,255,0) 100%);
  transform: skewX(calc(var(--dw-sheen-ang) * -1))
             translateX(calc((var(--dw-sheen-w) + var(--dw-sheen-hf)) * -1));
}
/* ★ 門番あり。タッチで貼り付くと光沢が 30% の白いままアイコンに残る。 */
.page--portal .dw-cell:focus-visible .dw-sheen{ opacity: 1; }
.page--portal .dw-cell:focus-visible .dw-sheen::before{
  animation: dw-sheen .66s var(--ease-inout) both;
}
@media (hover: hover) and (pointer: fine){
  .page--portal .dw-cell:hover .dw-sheen{ opacity: 1; }
  .page--portal .dw-cell:hover .dw-sheen::before{
    animation: dw-sheen .66s var(--ease-inout) both;
  }
}

/* --- タッチ端末の発火経路（2026-08-03・3回目に追加）---------------------
   ★★ Ryo さん実機報告「変わった？光が見えない気がする（スマホから見てます）」。
     原因は上の2本しか引き金が無かったこと。
       :focus-visible … タップでは発火しない（キーボード操作の合図）
       :hover         … iPhone に hover は無い。さらに門番で塞いである
     ＝ **タッチでは光沢が原理的に一度も出ない。**

   ★★ 条件は上の門番の **厳密な補集合**にしてある。
       上（マウス）: (hover: hover) and (pointer: fine)
       下（タッチ）: (hover: none), (pointer: coarse)
     ド・モルガンより not(A and B) = (not A) or (not B)。カンマは or なので、
     **どの端末もこの2つのうち必ず片方だけに入る。**
     両方光る端末も、どちらでも光らない端末も存在しない。
     ★ だから門番は1ミリもゆるんでいない。ホバーの25件は中のまま、
       :focus-visible の29件は外のまま。ここは「外に居た穴」を塞いだだけ。

   ★ 引き金は「タイルが画面に入ったこと」。JS（portal.js §A2）が
     IntersectionObserver でタイルごとに .is-lit を付ける。
     スクロールしてアイコンが現れるたびに、そのアイコンだけが一度光る。
     ★ グリッド全体の .is-in（入場）に乗せなかった理由: .is-in は
       .dw-grid 1つに付くので8枚が同時に立ち上がる。390px では2列＝5行あり、
       下3行は画面の外に居るあいだに光り終えてしまう（Ryo さんには
       上2行しか見えない）。行ごとに見えた瞬間を捉えるには per-tile が要る。
     ★★ 2026-08-03（4回目）: **タップの引き金を足した**（下の .is-tap）。
       ここに「タップを引き金にしなかった理由」として書いてあったのは
       「開いた瞬間に §10 の display:none が消すので例外を彫るしかない」。
       Ryo さんの「PCと同じようにタップするたびに光らせることは難しい？」で
       **例外を彫るほうを採った。**彫った場所と担保は §10 の
       `.dw-tile.is-open .dw-sheen{display:none}` のすぐ下に書いてある。
     ★ .is-lit（画面に入ったとき一度）は **残した。**触る前に「並んでいる
       ものは生きている」と伝えるのはこの光で、Ryo さんが実機で確認済み。
       二重に走らないよう、JS（§B）はタップの瞬間に .is-lit を外す。
       CSS 側でも .is-tap の規則を **.is-lit より後ろ** に置いてあるので、
       万一2つ同時に付いても後出しの .is-tap だけが効く（同じ (0,3,0)）。

   ★ 時差（--lit）は JS が「同じ回で画面に入った順」を入れる。初回表示で
     5枚まとめて入っても、.09s ずつずれて左上から順に光る（同時に8枚が
     光ることは無い）。単独で入ってきたタイルは --lit 未設定 ＝ 0。
   ★ 遅延 .28s は入場（dw-tile-in .58s）の折り返し（58%＝行き過ぎの頂点）に
     当たる。タイルが着地しながら光を受ける。
   ★ fill-mode は **none**（shorthand に fill を書かない＝ none）。
     終わったら器も帯も素の姿（opacity:0 ／ 箱の左外）へ必ず戻る。§1 参照。
   ★ 詳細度は (0,3,0)。同値の `.dw-tile.is-open .dw-sheen{display:none}`（§7・
     後ろに書いてある）に **後出し順で負ける**ので、開いているタイルは光らない。
   ★ 動かすのは器の opacity と帯の transform だけ。ホバー経路と同じ2つで、
     コンポジットされない箇所は1つも増えていない。 */
@media (hover: none), (pointer: coarse){
  .page--portal .dw-tile.is-lit .dw-sheen{
    animation: dw-sheen-veil .66s linear calc(.28s + var(--lit, 0) * .09s);
  }
  .page--portal .dw-tile.is-lit .dw-sheen::before{
    animation: dw-sheen .66s var(--ease-inout) calc(.28s + var(--lit, 0) * .09s);
  }

  /* --- タップのたびに光る（2026-08-03・4回目に追加）--------------------
     ★★ Ryo さん「スマホ版も光ることを確認しましたが、PCと同じように
       **タップするたびに** 光らせることは難しい？」
       上の .is-lit は「画面に入ったとき一度きり」＝ 生涯1回。
       PC のホバーは **状態**なので、指を乗せ直すたびに何度でも走る。
       それと同じ手触りにするのがここ。

     ★ 引き金は JS（§B の flash()）が付ける .is-tap。付ける場所は
       引き出しの toggle と **同じ click ハンドラの中**なので、
       「開くとき」も「閉じるとき」も必ず1回ずつ走る。
       ★ 付け直しは remove → 強制リフロー → add。同じクラスを付け直すだけでは
         ブラウザが変化を見ないので、2回目のタップで走らない。

     ★★ **貼り付かない担保は fill-mode（＝ none）のまま。**
       shorthand に fill を書いていない ＝ animation-fill-mode: none。
         ・走行中だけ器が opacity:1（dw-sheen-veil。宣言より animation が強い）
         ・終わったら規則が1つも適用されず、器は素の opacity:0 へ落ちる
       ＝ JS が後始末に失敗しても、タイマーが間引かれても、光は残らない。
       §1 の dw-sheen-veil の注記（「クラスが残っても貼り付きようがない」）が
       そのままこの経路にも効いている。JS 側の remove は保険。

     ★ 遅延は **0**（.is-lit は .28s ＋ 順番の時差を持つ）。
       タップは指が触れた瞬間の返事なので、待たせない。
       同時に走る沈み（dw-sink .42s）と尺が重なって、
       「沈みながら一度光る」になる。

     ★ 開いているあいだ光り続けることは無い。走るのは .66s の1回だけで、
       §10 の `.dw-tile.is-open .dw-sheen{ display:none }` は
       **.is-tap が付いているあいだだけ** 例外にしてある（§10 を参照）。
     ★ 動かすのは器の opacity と帯の transform だけ。ホバー経路と同じ2つで、
       コンポジットされない箇所は1つも増えていない（keyframes も増えていない
       ＝ dw-sheen と dw-sheen-veil の使い回し）。
     ★★ 門番は上の .is-lit と同じ `(hover: none), (pointer: coarse)` の中。
       ホバーを持つ端末（PC）では .is-tap が付いても当たる規則が1本も無いので、
       **PC の見た目も操作も1ミリも変わらない。** */
  .page--portal .dw-tile.is-tap .dw-sheen{
    animation: dw-sheen-veil .66s linear;
  }
  .page--portal .dw-tile.is-tap .dw-sheen::before{
    animation: dw-sheen .66s var(--ease-inout);
  }
}

/* --- アイコンが無いとき --------------------------------------------------
   ★ 2026-08-12: **いまこの枝を使うアプリは無い**（唯一の相手だった TrumpGames を
     外した）。それでも **消していない** —— 「まだアイコンが無い本」は今後も
     必ず出るし、下の寸法の根拠は作り直せない類の実測だから。
   ★ 34cqw → 20cqw → **clamp(15px, 7.5cqw, 19px)**（2026-08-03・4回目）。
   ★★ 落とした理由。③で柱書きの数（72px の「8」）を消したので、
     **20cqw のままだと「TG」がページの最大文字に戻る**（1280px で 50.6px）。
     「画面でいちばん大きい字が『まだアイコンが無い』印」という、
     tile-drawer で一度直した倒錯（当時 63.24px）がそのまま再発する。
   ★★ 上限を 19px にした根拠は **アプリ名**。名前は clamp(19px, 9.1cqw, 23px) で、
     広い画面では 23px、狭い画面では下限の 19px に張り付く。
     フォールバック字がその下に必ず居るには、どの幅でも名前以下であればよい:
       1280px  箱 253px  →  7.5cqw = 18.98px ≦ 名前 23px
        768px  箱 210px  →  7.5cqw = 15.75px ≦ 名前 19.11px
        390px  箱 167px  →  7.5cqw = 12.53px → 下限 15px ≦ 名前 19px
        375px  箱 160px  →  7.5cqw = 12.00px → 下限 15px ≦ 名前 19px
     ★ 下限 15px は「読める字であること」のため。cqw だけで組むと 390px で
       12.5px まで落ち、破線の箱の中で字が消えかける。
     ★ **いまページの最大文字はアプリ名**（1280px で 23px）。実測で確認すること
       （Math.max で main の子孫の font-size を舐める。§4 の注記も参照）。
   ★ 破線・無彩・weight 300 は icons.json fallback のまま。 */
.page--portal .dw-icon--empty{
  display: grid; place-items: center;
  background: var(--dw-empty-bg);
  border: 1px dashed var(--dw-empty-ln);
  box-shadow: none;
}
/* ★ 影は棚の2枚が持つようになった（2026-08-03）ので、box-shadow:none だけでは
   消えない。棚ごと落とす。:has() は §0 の html:has(> body.page--portal) で既に
   使っている（Chrome 105+）。display:none なので、上のホバーの opacity 規則が
   詳細度で勝っても何も出ない。 */
.page--portal .dw-shelf:has(.dw-icon--empty)::before,
.page--portal .dw-shelf:has(.dw-icon--empty)::after{ display: none; }
.page--portal .dw-tile--out .dw-icon--empty{ box-shadow: none; }
.page--portal .dw-icon__fallback{
  font-family: var(--font-body);
  font-size: 1.1rem;                     /* cqw 非対応時の保険（17.05px） */
  font-size: clamp(15px, 7.5cqw, 19px);
  font-weight: 300;
  letter-spacing: .1em;
  line-height: 1;
  color: var(--dw-empty-fg);
  user-select: none;
}

/* --- 名前 / 内容 / 状態 -------------------------------------------------
   ★ ここが2つめの差。17px/500/.51px → 23px/700/.28px。
     説明 12.5 → 13px、状態 11.5px 据え置き。名前だけを立てて落差を作る。
       名前 23px ⇄ 説明 13px  = 1.77 倍（従来 1.36 倍）
       名前 700  ⇄ 説明 400   = ウェイトも両極に振る
   ★ 大きさは 9.1cqw（列の 9.1%）。1280px の列 253px で 23.02px → 23px。
     4列に切り替わった直後の細い列でも自動で縮むので、長い名前
     「en（円コレクション）」が1行に収まる（1000px で 19px / 実測 194px ≦ 207px）。 */
.page--portal .dw-name{
  display: block;
  position: relative;
  margin-top: var(--sp-4);
  font-size: clamp(19px, 9.1cqw, 23px);
  line-height: 1.42;
  letter-spacing: .012em;
  color: var(--fg-1);
  /* 2列・3列では列が細く、いちばん長い名前だけ2行になる。
     行の高さを先に取っておいて、状態ラベルの行を揃える。
     4列（1000px 以上）では必ず1行に収まるので取らない。 */
  min-block-size: calc(2 * 1.42em);
}
.page--portal .dw-tile--out .dw-name{ font-weight: 700; }
.page--portal .dw-tile--wip .dw-name{ font-weight: 500; }

.page--portal .dw-name__rule{
  position: absolute; left: 0; bottom: -2px;
  inline-size: 100%; block-size: 1px;
  background: currentColor;
  transform: scaleX(0); transform-origin: left center;
  transition: transform .42s var(--ease-inout);
}
/* ★ 門番あり。§10 の .dw-tile.is-open .dw-name__rule（開いているあいだ引いたまま）
   とは別物。あちらは hover を持たないので囲わない。 */
.page--portal .dw-cell:focus-visible .dw-name__rule{ transform: scaleX(1); }
@media (hover: hover) and (pointer: fine){
  .page--portal .dw-cell:hover .dw-name__rule{ transform: scaleX(1); }
}

.page--portal .dw-desc{
  display: block;
  position: relative;
  margin-top: var(--sp-2);
  min-height: calc(2 * 1.8em);
  font-size: 13px;
  line-height: 1.8;
  color: var(--fg-2);
}

.page--portal .dw-state{
  display: flex; align-items: center; gap: var(--sp-2);
  position: relative;
  margin-top: var(--sp-2);
  font-size: 11.5px;
  letter-spacing: .04em;
  color: var(--fg-3);
}
.page--portal .dw-mark{
  inline-size: 6px; block-size: 6px; border-radius: 50%;
  flex: none;
}
.page--portal .dw-tile--out .dw-mark{ background: var(--fg-1); }
.page--portal .dw-tile--wip .dw-mark{ box-shadow: inset 0 0 0 1px var(--fg-3); }

@media (min-width: 1000px){
  /* 4列。名前は必ず1行に収まるので、先取りの高さを外して詰める。 */
  .page--portal .dw-name{ min-block-size: 0; }
}
@media (max-width: 659px){
  /* 2列。列 159.5px（375px 幅）では説明が3行になる本が1つある。 */
  .page--portal .dw-desc{ font-size: 12px; line-height: 1.75; min-height: calc(3 * 1.75em); }
  .page--portal .dw-name{ margin-top: var(--sp-3); }
}


/* ═══ 7. 結び ═════════════════════════════════════════════════════════════ */

.page--portal .dw-close{
  max-width: calc(var(--dw-max) + var(--gutter)*2);
  margin: clamp(56px, 9vw, 104px) auto 0;
  padding-inline: var(--gutter);
  font-size: clamp(14px, .8rem + .4vw, 17px);
  font-weight: 300;
  letter-spacing: .18em;
  color: var(--fg-2);
  /* ★ contain: layout（2026-08-03）。1文字ずつ span に割ってあるので
     この1行だけで 16要素。引き出しの開閉のたびに全部が位置を計算し直される。
     中身は引き出しの高さと無関係なので内側で閉じる。layout だけ。
     ★ containing block ができるので **中の要素の offsetParent が変わる**
       （.dw-close__ch … BODY → P.dw-close、offsetTop 2708 → 0。実測）。
       連鎖の合計は保たれる。offsetTop は必ず offsetParent を辿って足すこと。 */
  contain: layout;
}
.page--portal .dw-close__ch{ display: inline-block; }
html.js .page--portal .dw-close__ch{ opacity: 0; }
html.js .page--portal .dw-close.is-in .dw-close__ch{
  animation: dw-rise .46s var(--ease-inout) calc(var(--i, 0) * .028s) both;
}


/* ═══ 8. フォーカス可視 ═══════════════════════════════════════════════════ */
.page--portal .dw-cell:focus-visible{
  outline: 2px solid var(--accent);
  outline-offset: 6px;
  border-radius: 12px;
}


/* ═══ 9. 開発日記（note）══════════════════════════════════════════════════
   2026-08-03 追加。ページ上の位置はグリッド（§5・§6）と結び（§7）のあいだ。
   節番号だけ後ろに付いているのは、既にある番号を振り直さないため。
   ------------------------------------------------------------------------
   ★ 色を1つも足していない。使っているのは
     --fg-1 / --fg-2 / --fg-3 / --rule / --rule-2 / --accent-line /
     --accent-soft / --surface-2 / --rule-hair / --ease-inout だけ。
     白・ライトグレー・黒の無彩のまま、落款も無い。
   ★ 連番を振らない。左端に縦に揃うのは **日付**。日付は意味のある数字なので
     「意味のない連番」には当たらない、という判断で日付を主役にしている。
     組み方は §4（柱書き）と同じ手＝同じ大きさをウェイトで打ち分ける:
       2026.  11.5px 相当の存在感で weight 300 / --fg-3
       07.08  同じ 13px で weight 700 / --fg-1
     tabular-nums なのでどの行も同じ幅になり、列がそのまま罫の役をする。
   ★ 見出しは「開発日記」1つだけ。ラベルと見出しで同じ語を二度言わない。
   ★ 記事が0件のときは index.njk がセクションごと出さない。ここには
     「0件のときの見た目」を持たせていない（持たせると見出しだけが残る）。

   コントラスト実測（ブラウザの算出色から計算した値。2026-08-03）
     ホバーの地は --accent-soft を敷いた後の実効色。明 #E8E8E8 / 暗 #1F1F1F。
     ★ この節でいちばん低いのは「ホバー中の年」で、明 4.74:1 / 暗 4.91:1。
       どちらも AA(4.5:1) を超えている。13px より小さい字は使っていない。
                             明 地      明ホバー    暗 地      暗ホバー
       見出し 13px  --fg-1   17.15:1     15.48:1    17.12:1    14.74:1
       月日   13px  --fg-1   17.15:1     15.48:1    17.12:1    14.74:1
       記事名 16px  --fg-1   17.15:1     15.48:1    17.12:1    14.74:1
       抜粋   13px  --fg-2    7.99:1      7.21:1     8.64:1     7.44:1
       年     13px  --fg-3    5.25:1      4.74:1     5.70:1     4.91:1
   ======================================================================== */

.page--portal .dw-diary{
  max-width: calc(var(--dw-max) + var(--gutter)*2);
  margin: clamp(56px, 7vw, 88px) auto 0;
  padding-inline: var(--gutter);
  /* ★★ contain: layout（2026-08-03・§10 の引き出しを軽くするため）★★
     引き出しの開閉で位置が変わる 178要素のうち、この節が **63要素**（最大の塊）。
     中身は引き出しの高さと無関係なので、レイアウトを内側で閉じてしまう。
     ★ layout だけ。**size / content-visibility は入れない。**
       この節の高さと offsetTop は §4b の帯（最新トピック → ここへ下りる）が
       着地点の計算に使っている（portal.js §D の offsetTop 連鎖）。
       size を切ると連鎖が推定値を拾って着地点がずれる。
     ★★ 訂正（2026-08-03・実測）。以前ここに
        「layout containment は offsetParent も offsetTop も変えない」と
        書いてあったが **誤り**。layout containment は containing block を作るので
        **offsetParent が変わる**。実測（1280px）:
          .dw-diary__item … offsetParent BODY → SECTION.dw-diary
                            offsetTop 1865 → 40（.dw-diary 自身が 1825 を持つ）
        offsetTop の **連鎖の合計** は変わらない（40 + 1825 = 1865）ので、
        portal.js §D の landingY()（while で offsetParent を辿って足す形・809行）は
        このままで正しい。
        ★★ **単発の offsetTop を文書座標として読んではいけない。**
           ここで `.dw-diary__item.offsetTop` を直に読むコードを足すと
           着地点が 1825px ずれる。必ず offsetParent を辿って足すこと。
     ★ 中に position:fixed の子孫は無い（実測）。 */
  contain: layout;
}

/* --- 見出しの行 ---------------------------------------------------------
   ★★ 2026-08-03（4回目）: **形は §4c（.dw-sec__head / .dw-sec__h）へ移した。**
     ここに在った2ブロック（flex の行と 13px/500/.18em の見出し）が、そのまま
     3つの項目（トピック／アプリ／開発日記）の共通の形になっている。
     **値は1つも変えていないので、この節の見た目は1pxも動いていない。**
     移した理由と「揃えた／変えなかった」の判断は §4c の冒頭に書いてある。
   ★ index.njk の要素は `class="dw-sec__head dw-diary__head"` と併記してある。
     .dw-diary__head を残しているのは、入場アニメ（この節の下・§11）が
     この名前で名指ししているため。**形の宣言はもうここには無い。**
   ★ 罫（--rule）が「グリッドの天井」と同じ1本であることは変わらない。
     天井を持っていたのは §4 の .dw-count の下罫だったが、③でその行ごと
     消えたので、いまは §4c の「アプリ」の見出しの罫がその役をしている。 */
/* ★ 錨の着地余白（2026-08-03 追加）。
   §4b の帯にある「これまでの分 ↓」（index.njk の href="#dw-diary-h"）が
   ここへ着く。shell.css の .shell-head は position:sticky / top:0 で、
   地は 86% 不透明＋blur(10px) なので、余白が無いとこの見出しは
   ヘッダーの裏へ完全に潜って1文字も読めない（実測: 1280px で見出しの
   下端 20.5 に対しヘッダーの下端 57／390px で 20.7 に対し 49）。
   値はヘッダーの寸法そのもの（--head-h ＋ --hair）に 19px の息を足したもの。
   ★ 生の数字を書かずトークンで組んであるので、ヘッダーの高さを変えた日に
     ここは黙って追随する（560px 以下はヘッダーが --head-h-sm 48px に縮むが、
     こちらは 76px のままなので余白が 27px に増えるだけ。潜ることはない）。
   ★ :root の scroll-padding ではなく **この見出しの scroll-margin** に置く。
     scroll-padding はページ全体のあらゆる錨に効いてしまうので、
     dw-a の中だけで完結するこちらを採る。
   実測（2026-08-03・手で回した _site を素の静的サーバで配って計測）
     1280px  見出しの上端 75.7／ヘッダーの下端 57.0 → 18.7px 空く
      768px  見出しの上端 76.3／ヘッダーの下端 57.0 → 19.3px 空く
      375px  見出しの上端 76.4／ヘッダーの下端 49.0 → 27.4px 空く
     （どのはばでも 1件目の記事の上端 110〜115 まで丸ごと見えている） */
.page--portal #dw-diary-h{
  scroll-margin-block-start: calc(var(--head-h) + var(--hair) + 19px);
}
/* ★ 焦点枠（2026-08-03 追加）。index.njk がこの見出しに tabindex="-1" を
   付けてあり、帯を押したとき portal.js §D がここへフォーカスを移す。
   形は §8・§4b と同じ。マウス／タップで来たときは :focus-visible が
   立たないので枠は出ない（キーボードで Enter を打った人にだけ出る）。 */
.page--portal #dw-diary-h:focus-visible{
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: 4px;
}
/* note のアカウントへ。★ index.njk 側で diary.noteUrl が null のときは
   この a ごと出さない（サンプルモードで存在しないページへ飛ばさないため）。 */
.page--portal .dw-diary__all{
  margin-inline-start: auto;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .04em;
  color: var(--fg-1);
  text-decoration: none;
  border-block-end: 1px solid var(--accent-line);
  padding-block-end: 2px;
}

/* 外へ出る印。§10 の .dw-store__arw と同じ動き（右上へ 3px）。 */
.page--portal .dw-diary__arw{
  display: inline-block;
  margin-inline-start: .5em;
  transition: transform .3s var(--ease-inout);
}
/* ★ 門番あり（§6 .dw-plate の注記を参照）。 */
.page--portal .dw-diary__all:focus-visible .dw-diary__arw{ transform: translate(3px, -3px); }
@media (hover: hover) and (pointer: fine){
  .page--portal .dw-diary__all:hover .dw-diary__arw{ transform: translate(3px, -3px); }
}

/* --- 一覧 --------------------------------------------------------------- */
.page--portal .dw-diary__list{
  list-style: none;
  margin: 0;
  padding: 0;
}

/* 1件。左＝日付／右＝本文。日付の欄を em で固定してあるので、
   日付が読めなかった行（time を出さない行）でも左端の揃いは崩れない。
   ★ position:relative は記事名の ::after（行いっぱいの押しどころ）の
     基準になる。だから .dw-diary__body / __text は positioned にしない。 */
.page--portal .dw-diary__item{
  position: relative;
  display: grid;
  /* ★ 左の欄の幅は **トラックに書かない**（em はここでは本文の 15.5px で
     解決されてしまい、13px の日付に対して 37px も余る。実測で外した）。
     幅は .dw-diary__date の inline-size が日付自身の 13px を基準に持ち、
     トラックは auto でそれを受ける。日付が読めなかった行に置く空の span も
     同じ幅を持つので、そこだけ左端がずれることもない。 */
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: clamp(14px, 1.8vw, 24px);
  align-items: start;
  padding: clamp(15px, 1.7vw, 21px) 12px;
  margin-inline: -12px;          /* 地色を字より外までにじませる（版面は動かない） */
  border-radius: 12px;
  background: transparent;
  /* ★ background-color はコンポジットされない（2026-08-03 の棚卸しで残した3本
     のうち1本）。置き換えなかった理由が2つある。
       1. この行は ::before（行の区切り線）と ::after（着地の合図）の
          **両方を既に使っている**。地を敷く3枚目の面を作る場所が無い
          （作るなら index.njk に空の span を6件ぶん足すことになる）。
       2. 単色のベタ塗りは paint の中でいちばん画素単価が安い。ホバー中だけで、
          同時に走る animation も無い（入場は終わっている）。
     ★ 逆に、ここに animation を足すときは注意。上の ::after の注記が言う
       「background-color を直接動かすと入場の animation と席を奪い合う」は
       いまも生きている。 */
  transition: background-color .3s var(--ease-inout);
}
/* 行の区切り。地のにじみ（-12px）ではなく **字の幅** に合わせて引く。
   最初の行の上には引かない（見出しの罫がすぐ上にあるので二重になる）。 */
.page--portal .dw-diary__item + .dw-diary__item::before{
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 12px;
  block-size: 1px;
  background: var(--rule-2);
}
/* ★ 門番あり。:focus-within はキーボードで中のリンクへ入ったときの合図なので
   囲わない（タッチでは焦点がそこに残らないので貼り付かない）。 */
.page--portal .dw-diary__item:focus-within{ background: var(--accent-soft); }
@media (hover: hover) and (pointer: fine){
  .page--portal .dw-diary__item:hover{ background: var(--accent-soft); }
}

/* ★★ 着地の合図（2026-08-03 追加）。帯（§4b）を押して下りてきたとき、
   1件目を一瞬だけ淡く光らせて「ここへ来た」を示す。JS（§D）が
   .is-landed を付け、動きが終わったら自分で外す。
   ★ **background ではなく ::after の透過** で出す理由。
     この行には既に2つの animation の口がある
     （入場 dw-fade-up ＝ (0,5,1) と、地のホバー transition）。
     background-color を直接動かすと入場の animation と席を奪い合って、
     「押した瞬間に一覧がまだ入場していない」場合に片方が消える。
     擬似要素なら誰とも重ならないので、どの順番で来ても必ず出る。
   ★ pointer-events:none。上に被せるが、行の押しどころ
     （.dw-diary__title::after）を1pxも塞がない。
   ★ 既定は opacity:0。animation を切られた環境（§11）でも
     光ったまま貼り付くことがない。 */
.page--portal .dw-diary__item.is-landed::after{
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 12px;
  background: var(--accent-soft);
  pointer-events: none;
  opacity: 0;
  animation: dw-land 1.5s var(--ease-inout) both;
}

/* --- 日付 --------------------------------------------------------------- */
.page--portal .dw-diary__date{
  display: block;
  font-size: 13px;
  line-height: 1.7;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  /* 13px の tabular-nums で "2026.07.08" は 57.14px（実測）。
     5.2em ＝ 67.6px。書式が変わらないかぎりどの行も同じ幅になる。 */
  inline-size: 5.2em;
}
.page--portal .dw-diary__y{
  font-weight: 300;
  letter-spacing: .02em;
  color: var(--fg-3);
}
.page--portal .dw-diary__md{
  font-weight: 700;
  letter-spacing: .01em;
  color: var(--fg-1);
}

/* --- 本文（記事名・抜粋）と図版 ----------------------------------------- */
.page--portal .dw-diary__body{
  display: flex;
  align-items: flex-start;
  gap: clamp(12px, 1.6vw, 20px);
}
/* ★ 字の行長に上限を置く。置かないと 1280px で 13px の抜粋が 936px
   （日本語で1行 72字）になり、読める幅を大きく越える。
   §10 .dw-lead が 46em で止めているのと同じ考え方・同じ値。
   図版は margin で右端へ寄せるので、行の右辺はグリッドの右辺と揃ったまま。 */
.page--portal .dw-diary__text{
  min-inline-size: 0;
  flex: 1 1 auto;
  max-inline-size: 46em;
}

.page--portal .dw-diary__title{
  font-size: clamp(14px, .84rem + .28vw, 16px);
  font-weight: 500;
  line-height: 1.7;
  letter-spacing: .012em;
  color: var(--fg-1);
  /* 下線は text-decoration で持つ。記事名は2行になることがあり、
     §6 .dw-name__rule のような絶対配置の1本線だと2行目に付かない。 */
  text-decoration: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  /* ★ text-decoration-color はコンポジットされない（2026-08-03 の棚卸しで
     残した3本のうち1本）。動くのは下線1本ぶんの画素だけ。
     .dw-name__rule のような絶対配置の1本線なら transform: scaleX で
     置き換えられるが、記事名は2行になることがあり、上の注記どおり
     その形では2行目に線が付かない。字の意味のほうを優先して残す。 */
  transition: text-decoration-color .3s var(--ease-inout);
}
/* ★ 行のどこを押しても記事へ飛べるようにする透明な面。
   読み上げのリンク名は記事名だけのまま（日付や抜粋を巻き込まない）。 */
.page--portal .dw-diary__title::after{
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 12px;
}
/* ★ 門番あり。焦点枠（outline）は入力手段に関係なく要るので囲わない。 */
.page--portal .dw-diary__title:focus-visible{ text-decoration-color: var(--accent-line); }
.page--portal .dw-diary__title:focus-visible{
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: 4px;
}
.page--portal .dw-diary__title:focus-visible .dw-diary__arw{ transform: translate(3px, -3px); }
@media (hover: hover) and (pointer: fine){
  .page--portal .dw-diary__item:hover .dw-diary__title{ text-decoration-color: var(--accent-line); }
  .page--portal .dw-diary__item:hover .dw-diary__arw{ transform: translate(3px, -3px); }
}

.page--portal .dw-diary__excerpt{
  margin: 5px 0 0;
  font-size: 13px;
  line-height: 1.8;
  color: var(--fg-2);
  /* 抜粋は note.json の excerptLength（120字）で既に丸めてある。
     幅が狭いと3行以上になるので、行数でももう一度止めて行の高さを揃える。 */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

/* 図版。★ 実データでは note の CDN（assets.st-note.com）＝外部ホスト。
   寸法は CSS が固定で持ち（72×72）、HTML の width/height も同じ値なので、
   読み込み前・読み込み後・**404 のときも** 行の高さが1pxも動かない。
   note の横長（1280×670 など）とサンプルの正方形が混ざるので object-fit:cover。
   alt="" なので、壊れていても代替文字が出て崩れることがない。 */
.page--portal .dw-diary__thumb{
  flex: none;
  margin-inline-start: auto;   /* 行の右端へ。図版が縦に揃う */
  inline-size: 72px;
  block-size: 72px;
  border-radius: 10px;
  object-fit: cover;
  background: var(--surface-2);
  box-shadow: 0 0 0 1px var(--rule-hair);
}

/* --- 入場（§3 の [data-in] に乗る）--------------------------------------
   ★★ ここに animation を足したら **§11 に必ず登録すること。**
      書き忘れると、shell.css §6 の .01ms だけが残って 0% の姿
      （opacity:0）のまま固まる。 */
html.js .page--portal .dw-diary.is-in .dw-diary__head{
  animation: dw-fade-up .42s var(--ease-inout) both;
}
html.js .page--portal .dw-diary.is-in .dw-diary__item{
  animation: dw-fade-up .44s var(--ease-inout) calc(.08s + var(--k, 0) * .05s) both;
}

@media (max-width: 659px){
  /* 2列のときと同じ境目で、日付を記事名の上へ回す。
     375px では日付の欄 6.1em が本文の 1/4 を食うため。 */
  .page--portal .dw-diary__item{
    grid-template-columns: minmax(0, 1fr);
    row-gap: 5px;
  }
  .page--portal .dw-diary__excerpt{ font-size: 12px; line-height: 1.75; }
  .page--portal .dw-diary__thumb{ inline-size: 56px; block-size: 56px; }
}


/* ═══ 10. 引き出し ════════════════════════════════════════════════════════
   ★★ ここは tile-drawer から仕組みを1行も変えていない。寸法だけ。
   押したタイルが属する「行の直下」に、全幅のパネルが割り込む。
   ------------------------------------------------------------------------
   ・DOM ではタイルの直後にあり、見た目の位置は order で作る。
     タイルは order = タイル番号×2（テンプレートが直接書く）。
     引き出しは order = その行の最後のタイル番号×2 + 1（JS が入れる）。
     全幅なので自動配置は次の行に送り、以降のタイルはさらに次の行から続く。
     ★ 「その行の最後のタイル」は offsetTop の一致で探す（portal.js §B place）。
       列数で割る索引計算は使わない。§5 の .dw-tile--break で強制改行を
       入れたので「タイルが順に詰まっている」前提が成り立たなくなっている。
   ・閉じているあいだは hidden 属性（＝display:none）。DOM から到達不能。
   ・★ row-gap を 0 にした理由:
     grid の row-gap は中身が0の行にも前後に付く。引き出しの行が高さ0になっても
     gap が2本残るので、閉じ終わって display:none になった瞬間に、下の行が
     行間1本ぶん跳ね上がる。gap をタイルの margin に移すと跳ねが消える。
     ★ その代償だった「1行目にも margin が付く」は、nth-child の表ではなく
       グリッド自身の負の margin で相殺している（§5）。だから
       **この案には「1行目は何枚か」を知っている場所がもう1つも無い。**
       offsetTop 一致という下の前提は、本数・列数・強制改行に関係なく成り立つ。
   ======================================================================== */

.page--portal .dw-drawer{
  grid-column: 1 / -1;
  order: 99;                 /* JS が上書きする。値が入る前でも列を割らない */
  display: grid;
  grid-template-rows: 0fr;   /* 閉 */
  margin-block-start: 0;
}
/* hidden 属性が display:grid に負けないよう明示する */
.page--portal .dw-drawer[hidden]{ display: none; }

.page--portal .dw-drawer.is-open{
  grid-template-rows: 1fr;   /* 開（動きを切っても、ここだけで開閉は成立する） */
  margin-block-start: var(--dw-mt);
}
/* ★★ contain: layout（2026-08-03・Ryo さん「カクカク」報告）★★
   開閉は grid-template-rows: 0fr→1fr ＝ **レイアウトの animation**。
   1280px で開閉すると 390要素中 178要素が位置を変える（実測）。
   ここは「動かしているプロパティ」を替えられない場所なので
   （transform: scaleY に替えると中身が縦に歪む。§1b の注記も参照）、
   **波及範囲を絞る**ほうで軽くする。
   ★ 入れるのは layout だけ。
     ・size は **絶対に入れない。**入れた瞬間 1fr が中身の高さを見なくなり、
       引き出しが開かなくなる（0fr→1fr の仕組みそのものが死ぬ）。
     ・paint も入れない。overflow:hidden が既に同じ刈り込みをしている。
   ★ layout containment は intrinsic sizing を止めない（止めるのは size）。
     1fr は今までどおり .dw-panel の高さで解決される。実測で確認すること。
   ★ 中に position:fixed の子孫は無い（実測。ライトボックスの <dialog> と
     モーションのトグルは body の直下）。だから containing block が
     ここに移っても行き先の変わる要素が無い。
   ★ ただし **offsetParent は変わる**（containing block ができるため）。
     パネルの中の要素の offsetTop はこの clip を基準にした値になる。
     連鎖の合計は保たれるので、offsetTop は必ず offsetParent を辿って
     足すこと（portal.js §D の landingY() がその形）。単発で読まない。
     §10 の .dw-diary の注記に実測値がある。 */
.page--portal .dw-drawer__clip{ min-block-size: 0; overflow: hidden; contain: layout; }

/* 開閉の動き。静止状態は上の2つで決まっているので、これは上乗せ。 */
html.js .page--portal .dw-drawer.is-open{
  animation: dw-drawer-open var(--dw-dur-open) var(--ease-inout) both;
}
html.js .page--portal .dw-drawer.is-shut{
  animation: dw-drawer-shut var(--dw-dur-shut) var(--ease-inout) both;
}

/* --- JS が無いとき ------------------------------------------------------
   引き出しの li は HTML の時点で hidden。JS が落ちた環境で「開ける」と
   嘘をつかないためだが、そのままだと制作中5本の中身へ到達する手段が消える。
   そこで no-js のときだけ、8枚とも「開いた形」で静かに置く。 */
html.no-js .page--portal .dw-drawer,
html.no-js .page--portal .dw-drawer[hidden]{
  display: grid;
  grid-template-rows: 1fr;
  margin-block-start: var(--dw-mt);
}

/* --- パネル本体 ---------------------------------------------------------
   板（.dw-plate）と同じ面色・同じ角丸。押したタイルの板が下へ伸びて
   このパネルの中へ潜り込むので、地がタイルからパネルへ続いて見える。 */
.page--portal .dw-panel{
  position: relative;
  padding: var(--dw-pad);
  border-radius: 18px;
  background: var(--dw-plate-bg);
  box-shadow: var(--shadow-card);
}
.page--portal .dw-panel:focus{ outline: none; }
.page--portal .dw-panel:focus-visible{
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
html.js .page--portal .dw-drawer.is-open .dw-panel{
  animation: dw-panel-in .44s var(--ease-inout) .06s both;
}
html.js .page--portal .dw-drawer.is-shut .dw-panel{
  animation: dw-panel-out .18s var(--ease-inout) both;
}

/* --- 開いているタイル ---------------------------------------------------
   ★ 枠の二重化をやめた（2026-08-03・Ryo さん指摘
     「アプリの枠と、その詳細の枠が被っている」）

   元の設計は「板を下へ 20px 伸ばしてパネルへ潜り込ませ、1枚の面に見せる」だった。
   しかしこれは成立しない。板はタイル幅（253px）、パネルは版面いっぱい（1120px）で、
   **幅の違う2枚の白いカードが必ず継ぎ目を作る**。しかも板は列の外へ
   はみ出す負の inset を持つので、左端の列でも両者の左辺が揃わない。
   角丸を消す・影を消す・重なりを深くする、をやっても「幅の違う2枚」である事実は
   変わらないので、段差か影の線のどちらかが残る。

   なので**開いているあいだは板を出さない**。枠はパネル1枚だけになり、
   重なりようがなくなる。どのタイルが開いているかは、板ではなく
     ・アイコンが沈んで影が平らになる（下の .dw-shelf 規則）
     ・名前の下の罫が引かれたまま残る（下の .dw-name__rule 規則）
   の2つで示す。どちらも既にある仕掛けで、板が無くても成立する。

   ※ ホバー／フォーカスで板を出す規則（§6）より後に書き、同じ詳細度で打ち消す。
     開いているタイルにマウスが乗っても板は戻らない。

   ★★ 2026-08-03 追記: ここは **hover を打ち消している** 規則。§6 で hover 本体を
     `@media (hover: hover) and (pointer: fine)` へ移したので、打ち消し側も
     同じ @media の中へ入れる。外に残すと、hover を持たない端末で
     「打ち消す相手が居ないのに打ち消しだけが宣言されている」状態になり、
     詳細度の順序を将来いじったときに何を守っている行なのか読めなくなる。
     ★ .is-open 本体（hover を含まない行）と :focus-visible は囲わない。 */
.page--portal .dw-tile.is-open .dw-plate,
.page--portal .dw-tile.is-open .dw-cell:focus-visible .dw-plate{
  opacity: 0;
}
/* ★★ 2026-08-03: 沈みと影の担当が .dw-icon → .dw-shelf に移った（§6 参照）。
   影は「平ら（--shadow-flat）にする」ではなく「静止の影の値そのものを --shadow-flat に
   落として、ホバーの1枚（::after）を引っ込める」形になる。
   ★ 公開済みのタイルをホバーしたまま開いたときは、::before は opacity:0 の
     ままで値だけ --shadow-lift → --shadow-flat に入れ替わる（見えない側で入れ替わる）。
     そのあと ::before が 1 へ、::after が 0 へ渡るので、
     **見える動きは lift-hi → flat のクロスフェード**で、元の box-shadow 補間と
     同じ絵になる。逆にホバーしたまま閉じるときだけ、::before の値が
     flat → lift へ即座に戻ってから lift-hi へ渡る（＝出だしが少し先に進む）。
     引き出しが畳まれる最中の 1/3 秒の話で、影の濃さの上限も下限も変わらない。 */
.page--portal .dw-tile.is-open .dw-shelf,
.page--portal .dw-tile.is-open .dw-cell:focus-visible .dw-shelf{
  transform: translateY(4px) scale(.972);
  --dw-sh-rest: var(--shadow-flat);
}
.page--portal .dw-tile.is-open .dw-shelf::before,
.page--portal .dw-tile.is-open .dw-cell:focus-visible .dw-shelf::before{ opacity: 1; }
.page--portal .dw-tile.is-open .dw-shelf::after,
.page--portal .dw-tile.is-open .dw-cell:focus-visible .dw-shelf::after { opacity: 0; }
@media (hover: hover) and (pointer: fine){
  .page--portal .dw-tile.is-open .dw-cell:hover .dw-plate{ opacity: 0; }
  .page--portal .dw-tile.is-open .dw-cell:hover .dw-shelf{
    transform: translateY(4px) scale(.972);
    --dw-sh-rest: var(--shadow-flat);
  }
  .page--portal .dw-tile.is-open .dw-cell:hover .dw-shelf::before{ opacity: 1; }
  .page--portal .dw-tile.is-open .dw-cell:hover .dw-shelf::after { opacity: 0; }
}
html.js .page--portal .dw-tile.is-open .dw-shelf{
  animation: dw-sink .42s var(--ease-inout) both;
}
/* 開いているあいだは光沢を走らせない（沈んでいるものは光らない） */
.page--portal .dw-tile.is-open .dw-sheen{ display: none; }
/* ★★ その **唯一の例外**（2026-08-03・4回目）。Ryo さん「PCと同じように
   タップするたびに光らせることは難しい？」への回答がここ。
   ------------------------------------------------------------------------
   タップは引き出しを開く。開いた瞬間に上の1行が光沢を display:none で
   消してしまうので、**「開くタップ」だけ光らなかった**（＝生涯1回の .is-lit
   しか無かった理由。§6 に「タップを引き金にしなかった理由」として
   書いてあったのがこれ）。ここを走行中だけ開ける。
   ★ 例外は **走行中の .66s だけ**。JS（§B の flash()）が .is-tap を付けて、
     .9秒後に外す。外れれば上の1行が戻り、開いているタイルは光らない
     ＝「沈んでいるものは光らない」という担保は **時間の外側で保たれている**。
   ★★ JS の後始末が失敗しても光は残らない。走っているのは fill-mode:none の
     animation で（§6 .is-tap ／ §1 dw-sheen-veil）、終われば器は素の
     opacity:0 に戻る。display が開いていても、透明なものは見えない。
     ＝ **担保は2枚**（時間＝JS の後始末、見た目＝fill-mode:none）。
   ★ 詳細度 (0,4,0) ＞ 上の1行 (0,3,0)。順序ではなく詳細度で勝たせてあるので、
     将来この節の並びが変わっても効き続ける。
   ★★ 門番は §6 の .is-tap と同じ `(hover: none), (pointer: coarse)`。
     **PC には1本も当たらない**（PC で .is-tap が付いても、光る規則も
     この例外も (hover: none) の中なので何も起きない）。 */
@media (hover: none), (pointer: coarse){
  .page--portal .dw-tile.is-open.is-tap .dw-sheen{ display: block; }
}
/* 名前の下の線は引いたままにして、開いている本を字でも指す */
.page--portal .dw-tile.is-open .dw-name__rule{ transform: scaleX(1); }


/* --- 1段目: 言い切りの1文 ---------------------------------------------- */
.page--portal .dw-lead{
  margin: 0;
  font-size: clamp(14px, .85rem + .35vw, 17px);
  line-height: 1.9;
  letter-spacing: .02em;
  color: var(--fg-1);
  max-width: 46em;
}

/* --- 2段目: 図版の帯 / 事実の行。上に1本だけ罫を引く（両方に同じ罫） --- */
.page--portal .dw-band{
  margin-block-start: var(--dw-pad);
  padding-block-start: var(--dw-pad);
  border-block-start: 1px solid var(--dw-line);
}

/* 2段目A: 画面の帯（縦長なので高さを抑えて横に流す） */
.page--portal .dw-shots{
  display: flex;
  align-items: flex-start;
  gap: clamp(10px, 1.6vw, 16px);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
  /* ★ overflow-x:auto は overflow-y も auto にする（visible は許されない）。
     カットが入場で 14px 下がるぶんを下の余白で受けておかないと、
     開いた瞬間だけ縦スクロールバーが出る。 */
  padding-block-end: 16px;
  overflow-y: hidden;
}
.page--portal .dw-shots:focus-visible{
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}
.page--portal .dw-shot{
  flex: none;
  margin: 0;
  scroll-snap-align: start;
  /* 図版ごとの実寸比で幅を決める（shots.json の w / h をそのまま使う）。 */
  inline-size: calc(var(--dw-shot-h) * var(--w) / var(--h));
}
.page--portal .dw-shot img{
  display: block;
  inline-size: 100%;
  block-size: auto;          /* 高さは width/height 属性の比から決まる */
  border-radius: 10px;
  background: var(--surface-2);
  box-shadow: 0 0 0 1px var(--rule-hair), 0 2px 8px rgba(0,0,0,.08);
}
.page--portal .dw-shot__cap{
  margin-block-start: 10px;
  font-size: 11.5px;
  line-height: 1.6;
  letter-spacing: .02em;
  color: var(--fg-3);
}
html.js .page--portal .dw-drawer.is-open .dw-shot{
  animation: dw-shot-in .46s var(--ease-inout) calc(.14s + var(--k, 0) * .055s) both;
}

/* 2段目B: 写真が無い本の事実。連番も中黒も付けない。行頭は 1em 弱の短い罫。 */
.page--portal .dw-facts{
  list-style: none;
  padding-inline: 0;
  margin-block-end: 0;
}
.page--portal .dw-facts li{
  position: relative;
  padding-inline-start: 1.7em;
  font-size: 13px;
  line-height: 1.85;
  color: var(--fg-2);
}
.page--portal .dw-facts li + li{ margin-block-start: 6px; }
.page--portal .dw-facts li::before{
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: .95em;
  inline-size: .8em;
  block-size: 1px;
  background: currentColor;
  opacity: .5;
}
html.js .page--portal .dw-drawer.is-open .dw-facts li{
  animation: dw-fade-up .40s var(--ease-inout) calc(.12s + var(--k, 0) * .04s) both;
}

/* --- 3段目: 閉じの1行 --------------------------------------------------- */
.page--portal .dw-foot{
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 18px;
  margin-block: var(--dw-pad) 0;
}
.page--portal .dw-foot__meta{
  font-size: 11.5px;
  letter-spacing: .04em;
  color: var(--fg-3);
}
.page--portal .dw-more{
  margin-inline-start: auto;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .04em;
  color: var(--fg-1);
  text-decoration: none;
  border-block-end: 1px solid var(--accent-line);
  padding-block-end: 2px;
}
.page--portal .dw-more__arw{
  display: inline-block;
  margin-inline-start: .5em;
  transition: transform .3s var(--ease-inout);
}
/* ★ 門番あり（§6 .dw-plate の注記を参照）。 */
.page--portal .dw-more:focus-visible .dw-more__arw{ transform: translateX(4px); }
@media (hover: hover) and (pointer: fine){
  .page--portal .dw-more:hover .dw-more__arw{ transform: translateX(4px); }
}

/* --- ストアへの導線（2026-08-03 追加）----------------------------------
   ★ 公式バッジ（appstore_badge_ja.svg）を置かず、文字リンクにした。理由:
     1. 手元にあるバッジは **黒版1つだけ**（Download_on_the_App_Store_Badge_JP_RGB_blk、
        実寸 108.85×40）。この案は暗モードを持っていて、パネルの地は #191919。
        黒バッジをそこに置くと読めない。Apple のガイドラインは地に応じて
        黒／白を使い分けることを求めていて、白版は手元に無い。
        filter:invert() で作るのは「バッジを改変しない」に反する。
        白版の SVG を足すのは今回の書き換え範囲（_preview/dw-a/ と apps.json）の外。
     2. バッジの実寸 40px が Apple の最小サイズそのもの。**縮めて字と揃える
        ことができない。** このパネルの最大の文字は 17px、閉じの行は 11.5〜13px。
        40px の黒い塊はこの行で一番強い要素になり、「控えめ」を壊す。
     3. Apple は本文中で店名を文字で示すことを許している（バッジの模造は禁止）。
        下の見た目は角丸の箱でもアップルマークでもなく、ただの下罫つきリンク。
        バッジに似せていないので、模造にもならない。
   ★ 「くわしく見る →」との違いは、語（入手／見る）と矢印（↗ 外／→ 内）で出す。
     見た目の強さは同じにして、片方だけを売り込まない。 */
.page--portal .dw-store{
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .04em;
  color: var(--fg-1);
  text-decoration: none;
  border-block-end: 1px solid var(--accent-line);
  padding-block-end: 2px;
}
.page--portal .dw-store__arw{
  display: inline-block;
  margin-inline-start: .5em;
  transition: transform .3s var(--ease-inout);
}
/* ★ 門番あり（§6 .dw-plate の注記を参照）。 */
.page--portal .dw-store:focus-visible .dw-store__arw{ transform: translate(3px, -3px); }
@media (hover: hover) and (pointer: fine){
  .page--portal .dw-store:hover .dw-store__arw{ transform: translate(3px, -3px); }
}

/* --- ブラウザ版（2026-08-03 追加。apps.json の webApp。いま botch だけ）---
   ★ 「App Store で入手 ↗」の隣に並ぶが、**同じ強さの2連ボタンにはしない。**
     ぱっと見が同じ2つが並ぶと、どちらが何なのか読むまで分からなくなる。
     分け方は2つ重ねている。
       1. 語  入手（端末に入れる） ⇄ ひらく（その場で動かす）
          矢印はどちらも ↗（どちらもサイトの外）。ここは揃っていてよい。
       2. 強さ App Store は fg-1 ＋ --accent-line（濃い罫）で主、
          ブラウザ版は fg-2 ＋ --rule（薄い罫）・weight 400 で副。
          ★ 主副をこの向きにした理由: タイルの状態ラベルが
            「App Store で配信中」と言っている。そこと食い違わせない。
     色は1つも足していない（どちらも既にこのページが持っているトークン）。
   ★ コントラスト実測（2026-08-03・算出色から計算）
     明 --fg-2 #4A4A4A on --dw-plate-bg #FFFFFF ＝ 8.86:1
     暗 --fg-2 #AEAEAE on --dw-plate-bg #191919 ＝ 7.92:1
     13px でもどちらも AA(4.5:1) を大きく超える。
     隣の「App Store で入手」は 明 19.03:1 / 暗 15.71:1。薄くした側でも
     読みにくさは出ていない（差は主副の合図としてだけ効いている）。
   ★ 下罫の色だけを差し替える。border-block-end の他の値（1px solid）は
     .dw-store のものをそのまま使う。 */
.page--portal .dw-store--web{
  font-weight: 400;
  color: var(--fg-2);
  border-block-end-color: var(--rule);
}
/* ★ 門番あり。ここを囲わないと、タッチで一度押したブラウザ版リンクが
   「主」の見た目（fg-1 ＋ 濃い罫）のまま貼り付き、App Store と見分けが付かなくなる。 */
.page--portal .dw-store--web:focus-visible{
  color: var(--fg-1);
  border-block-end-color: var(--accent-line);
}
@media (hover: hover) and (pointer: fine){
  .page--portal .dw-store--web:hover{
    color: var(--fg-1);
    border-block-end-color: var(--accent-line);
  }
}

/* --- 狭い画面の閉じの行（2026-08-03・Ryo さん実機報告「リンクの位置がバラバラ」）
   ------------------------------------------------------------------------
   上の .dw-foot は flex-wrap: wrap ＋ .dw-more の margin-inline-start:auto。
   1行に収まる幅ではこれで「meta 左／導線 中／くわしく見る 右端」に決まるが、
   折り返すと **折り返した行がそれぞれ独立に両端へ散る**。実測（390px・botch）:
       行1  meta x=0        App Store で入手 x=104.73
       行2  ブラウザ版 x=0   くわしく見る x=204.84
   左・右・左・右のジグザグになり、どこから読めばいいのか分からなくなる。

   ★ 直し方は「広い画面の形を狭い画面へ持ち込まない」。両端揃えをやめて、
     **1列に積んで左端で揃える。** 4つの x が全部 0 になるので、
     ジグザグは幅がいくつでも起こりようがない（数で担保できる形にした）。
   ★ 2列のグリッドは採らなかった。390px の版面 306px を2列にすると
     1列 144px で、いちばん長い「ブラウザ版をひらく ↗」が実測 141.72px。
     余白 2.28px しか無く、360px の端末や字を大きくした設定で必ず割れる。
   ★ justify-items: start が要。既定の stretch のままだと、リンクの
     border-block-end（下罫）が行いっぱいに伸びて、字の無いところまで線が引かれる。
   ★ 境目は 659px。§5 の2列・§9 の日付の折り返しと同じ値で、
     実測でここまでは閉じの行が1行に収まる（660/800/1280px でいずれも高さ 27.69px）。
     つまり **広い画面の並びには一切触れていない。** */
@media (max-width: 659px){
  .page--portal .dw-foot{
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    justify-items: start;
    align-items: start;
    gap: 12px;
  }
  /* 両端揃えをやめたので、右端へ追いやる auto も外す。 */
  .page--portal .dw-more{ margin-inline-start: 0; }
}

/* 写真が無い本の閉じ。括弧も ※ も付けない。注釈ではなく本文として置く。 */
.page--portal .dw-absence{
  margin-block: var(--dw-pad) 0;
  font-size: 11.5px;
  line-height: 1.7;
  letter-spacing: .03em;
  color: var(--fg-3);
}


/* ═══ 10b. ライトボックス（帯の画像を押して拡大する）══════════════════════
   2026-08-03 追加。Ryo さん「クリック後の画像についてもクリックで拡大したい」。
   ------------------------------------------------------------------------
   ★★ 器は <dialog>。自前のオーバーレイを組まない。ネイティブなので
     ・::backdrop（幕）
     ・トップレイヤー（引き出しの overflow:hidden・order・z-index に一切縛られない）
     ・showModal() の焦点閉じ込め（Tab がダイアログの中で循環する）
     ・外側の inert 化（背後のタイルを押せない・読み上げも入らない）
     が全部ただで手に入る。**この4つを自前で書くと必ずどこかで破れる。**
   ★ Escape は二段階。ライトボックスが開いているあいだの Escape は
     ライトボックスだけを閉じ、引き出しは開いたまま残す。
     実装は portal.js §C（dialog の keydown で stopPropagation ＋ 自前で閉じる）と
     §B の Escape ハンドラの門番（lbIsOpen()）の2枚重ね。
   ★ 幕の上の字は **明暗どちらのモードでも明るい色**。幕そのものが暗いので、
     var(--fg-*) を使うと明モードで黒い字が暗い幕に乗って読めなくなる。
     ここだけ地の色に依らない固定値を置く（対比は下に実測を書いた）。
   ======================================================================== */

/* --- 帯のサムネイル側。JS が img を包んだ <button> ---------------------
   ★ 見た目は素の <img> のときと1pxも変えない。押せることは
     カーソル（zoom-in）と、キーボードのときの focus-visible だけで示す。
     枠や影を足すと、いまの帯の静けさが崩れる。 */
.page--portal .dw-shot__btn{
  display: block;
  inline-size: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: inherit;
  -webkit-appearance: none;
          appearance: none;
  border-radius: 10px;          /* .dw-shot img と同じ。焦点枠の形を合わせる */
  cursor: zoom-in;
}
.page--portal .dw-shot__btn:focus{ outline: none; }
.page--portal .dw-shot__btn:focus-visible{
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

/* --- dialog 本体 ＝ 画面いっぱいの透明な器 ------------------------------
   幕は ::backdrop が描く。この要素自体は透明で、余白（padding）を押しても
   閉じられるように画面いっぱいに広げてある（JS が e.target === dialog を見る）。
   ★ overflow:auto ＋ overscroll-behavior:contain。
     画像が縦に収まらない小さい画面でも中で送れるうえ、ホイールが背後の
     ページへ抜けない。スクロールを body 側で止めない（止めると
     スクロールバーの消滅で版面 1120px が動く）。
   ★ margin:auto の器を中に置く。flex の中央寄せだけだと、内容が器より
     高いときに上が切れて到達できなくなる。 */
.page--portal .dw-lb{
  position: fixed;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  max-inline-size: none;
  max-block-size: none;
  margin: 0;
  border: 0;
  padding: clamp(14px, 3vw, 40px);
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
  overscroll-behavior: contain;
}
/* 閉じているあいだは display:none（dialog の既定）を取り戻す。
   上の display:flex がそのままだと、閉じていても居座る。 */
.page--portal .dw-lb:not([open]){ display: none; }

/* 幕。★ 色は var() を使わず直値。::backdrop がおおもとの要素から
   カスタムプロパティを継承するようになったのは Chrome 122 以降で、
   それ以前は :root から継承する＝--dw-lb-veil が未定義になり
   background が透明になって幕が消える。ここだけ直値で書く。
   対比の実測（最悪ケース＝背後がパネルの白 #FFFFFF）:
     幕の合成色 rgba(10,10,10,.72) on #FFF → #4F4F4F
       #F7F7F7      → 7.8:1   ← 17px のキャプション
       rgba(255,255,255,.78) → 5.8:1  ← 12.5px の説明
       rgba(255,255,255,.70) → 5.0:1  ← 11.5px の位置表示
   いずれも AA 4.5:1 を超える。 */
.page--portal .dw-lb::backdrop{ background: rgba(10,10,10,.72); }
@media (prefers-color-scheme: dark){
  .page--portal .dw-lb::backdrop{ background: rgba(0,0,0,.82); }
}

html.js .page--portal .dw-lb.is-open::backdrop{ animation: dw-lb-veil-in  .40s var(--ease-inout) both; }
html.js .page--portal .dw-lb.is-shut::backdrop{ animation: dw-lb-veil-out .22s var(--ease-inout) both; }

/* --- 中身の器 ---------------------------------------------------------- */
.page--portal .dw-lb__box{
  margin: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(14px, 2vw, 26px);
  max-inline-size: 100%;
}
.page--portal .dw-lb__box:focus{ outline: none; }
.page--portal .dw-lb__box:focus-visible{
  outline: 2px solid rgba(255,255,255,.8);
  outline-offset: 10px;
}
html.js .page--portal .dw-lb.is-open .dw-lb__box{ animation: dw-lb-in  .40s var(--ease-inout) both; }
html.js .page--portal .dw-lb.is-shut .dw-lb__box{ animation: dw-lb-out .22s var(--ease-inout) both; }

/* 横に並ぶのは、画像の横に説明を置く余地があるときだけ。
   スクショは全部 0.45 前後の縦長なので、1120px の画面では横に大きく余る。 */
@media (min-width: 820px){
  .page--portal .dw-lb__box{ flex-direction: row; align-items: center; }
}

/* --- 拡大した画像 -------------------------------------------------------
   ★★ 幅を **確定値** で与えること。inline-size:auto ＋ max-block-size だけ
     だと、画像がまだ読み込まれていないあいだ 0×0 に潰れる。
     置換要素の既定サイズは「読み込み前は 0×0」で、width/height 属性から
     出るのは比だけ。比は片方が確定していないと寸法にならない。
     実測で踏んだ事故: 引き出しを開いた直後（帯の img は loading="lazy" で
     まだ取りに行っていない）にサムネを押すと、拡大画像が幅0高さ0になった。
     幅を確定値にして aspect-ratio を添えれば、読み込み前でも器が正しい
     大きさで出て、画像が届いたときに1pxも動かない。
   ★ 確定値は「高さの上限 × 実寸比」。--dw-lb-h は素材の長辺（900px）を
     超えないので、この幅も **元画像の実寸幅を超えない** ＝ 引き伸ばしに
     ならない。狭い画面では max-inline-size:100% が先に効き、
     高さは aspect-ratio が追う。
   ★★ flex は **0 0 auto（縮まない）**。縮む設定にすると、器（.dw-lb__box）の
     幅が「中身から決まる」のに「中身は器の幅を見て縮む」という循環に入り、
     周りに余白が有り余っているのに画像だけ 1割ほど小さく出る（1024px で
     実測 357px → 317px）。器は auto margin で中央に置いているので、
     画像が縮まないぶんは余白が吸う。狭くなったときは説明の列が先に縮む。
   ★ --w / --h は JS が shots.json 由来の width/height 属性から入れる。 */
.page--portal .dw-lb__img{
  display: block;
  flex: 0 0 auto;
  inline-size: calc(var(--dw-lb-h) * var(--w, 415) / var(--h, 900));
  max-inline-size: 100%;
  block-size: auto;
  max-block-size: var(--dw-lb-h);
  aspect-ratio: var(--w, 415) / var(--h, 900);
  border-radius: 12px;
  background: var(--surface-2);
  box-shadow: 0 20px 54px rgba(0,0,0,.45);
}
/* 前後へ移ったときだけ付く（JS が付け外しする）。開いた直後には付けない
   ＝器の入場（dw-lb-in）と二重に動かさない。 */
html.js .page--portal .dw-lb__img.is-swap{ animation: dw-lb-swap .30s var(--ease-inout) both; }

/* 縦に積む幅では、画像の下に説明と導線が続く。高さの上限を落として、
   小さい画面でも器ごとスクロールしなくても済むようにする。 */
@media (max-width: 819.98px){
  .page--portal .dw-lb{ --dw-lb-h: min(58dvh, 900px); }
}

/* --- 説明と導線の列 ----------------------------------------------------- */
.page--portal .dw-lb__side{
  flex: 0 1 26em;
  min-inline-size: 0;
  max-inline-size: 100%;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.page--portal .dw-lb__live{ display: flex; flex-direction: column; gap: 8px; }

.page--portal .dw-lb__pos{
  margin: 0;
  font-size: 11.5px;
  font-weight: 300;
  letter-spacing: .1em;
  font-variant-numeric: tabular-nums;
  color: rgba(255,255,255,.70);
}
.page--portal .dw-lb__pos-i{ font-weight: 700; color: rgba(255,255,255,.92); }

.page--portal .dw-lb__cap{
  margin: 0;
  font-size: clamp(15px, .9rem + .3vw, 17px);
  font-weight: 500;
  line-height: 1.75;
  letter-spacing: .02em;
  color: #F7F7F7;
}
.page--portal .dw-lb__screen{
  margin: 0;
  font-size: 12.5px;
  line-height: 1.85;
  letter-spacing: .02em;
  color: rgba(255,255,255,.78);
}

.page--portal .dw-lb__nav{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 4px 0 0;
}
/* 1枚しか無い本では前後の導線ごと消す（押せない導線を出さない）。
   いまは 4〜5 枚なので出ないが、1枚の本が来ても壊れない。 */
.page--portal .dw-lb__nav[data-single="1"] .dw-lb__arw{ display: none; }

.page--portal .dw-lb__btn{
  font: 500 12.5px/1 var(--font-body);
  letter-spacing: .04em;
  color: rgba(255,255,255,.92);
  background: transparent;
  border: 1px solid rgba(255,255,255,.34);
  border-radius: 999px;
  padding: 10px 15px;
  cursor: pointer;
  -webkit-appearance: none;
          appearance: none;
}
/* ★ 門番あり。囲わないと、← → を押すたびにそのボタンが明るいまま残り、
   「いまどちらを押したか」ではなく「最後に触ったのはどれか」を指してしまう。 */
@media (hover: hover) and (pointer: fine){
  .page--portal .dw-lb__btn:hover{
    border-color: rgba(255,255,255,.72);
    background: rgba(255,255,255,.09);
  }
}
.page--portal .dw-lb__btn:focus{ outline: none; }
.page--portal .dw-lb__btn:focus-visible{
  outline: 2px solid rgba(255,255,255,.85);
  outline-offset: 3px;
}
.page--portal .dw-lb__btn-g{ font-variant-numeric: tabular-nums; }
.page--portal .dw-lb__arw--prev .dw-lb__btn-g{ margin-inline-end: .45em; }
.page--portal .dw-lb__arw--next .dw-lb__btn-g{ margin-inline-start: .45em; }
/* 閉じるは行の右端。前後とは離して置く（押し間違いを減らす）。 */
.page--portal .dw-lb__x{ margin-inline-start: auto; }
.page--portal .dw-lb__x-g{ margin-inline-start: .5em; font-size: 14px; line-height: 0; }

/* ═══ 11. モーション抑制 ══════════════════════════════════════════════════
   shell.css §6 は * に animation-duration:.01ms !important を当てる。これは
   「一瞬で終端へ飛ばす」やり方で、タイムラインが進まない環境（非表示ペイン、
   document.hidden）では 0% のまま止まる。実際に事故を踏んでいる。
   そこで duration を潰すのではなく、animation そのものを切って静止状態を
   直接与える。★ 引き出しの開閉は animation ではなくクラス（.is-open）が
   grid-template-rows を決めているので、動きを全部切っても開閉は成立する。
   ★★ ここが唯一の仕事は「shell.css §6 の .01ms に触らせない要素を
      **全部名指しすること**」。1行でも落とすと、その要素だけ .01ms に
      残って、描画されていないペインで 0% に凍る。
   ======================================================================== */
@media (prefers-reduced-motion: reduce){
  /* ★★ 最初にこれを書く。animation より先に transition を断つ。
     shell.css §6 は reduce のとき * に transition-duration:.01ms !important を
     当てている。transition-property の初期値は all なので、この1行だけで
     「あらゆるプロパティが .01ms かけて変化する」状態になる。
     タイムラインが進まない環境ではその .01ms が永久に終わらず、
     grid-template-rows が 0px のまま固まって引き出しが二度と開かない。
     duration は shell.css が !important で握っているので触れない。
     代わりに property を none にして、値の変化をその場で確定させる。 */
  html .page--portal,
  html .page--portal *{ transition: none; }
  /* ★★ 擬似要素は上の `*` に **含まれない**（universal は要素にしか当たらない）。
     shell.css §6 は `*,*::before,*::after` の3つを並べているので、
     ここも同じだけ並べないと、影のクロスフェード（§6 .dw-shelf・
     §4b .dw-latest__card・2026-08-03 追加）だけ .01ms のまま残って
     描画されていないタブで 0% に固まる。 */
  html .page--portal *::before,
  html .page--portal *::after{ transition: none; }

  /* ★ 続けて animation-delay も 0 に倒す（2026-08-03 追加）。
     shell.css §6 が reduce のとき潰すのは animation-duration:.01ms と
     iteration-count:1 の2つだけで、**delay は残る**。fill-mode:both の
     animation と組むと「遅延のあいだ 0% の姿で固まる」＝ 0.5s のあいだ
     opacity:0 で消えている、が起きる。いまこのページで遅延を持つ animation
     （数字 .06s／点 .5s／タイル --d×.065s／パネル .06s／カット .14s+／
     事実 .12s+／結びの字 --i×.028s）は下のブロックで animation:none に
     しているので表には出ないが、1つ書き足したときに同じ穴を踏むので
     入口でまとめて塞いでおく。
     ★ 遅延を 0 にしてよいのは reduce のときだけ。素の環境では
       対角線の波も段組みの時差もこの遅延が持っている。 */
  html .page--portal,
  html .page--portal *{ animation-delay: 0s !important; }

  html.js .page--portal [data-in],
  html.js .page--portal .dw-tile,
  html.js .page--portal .dw-close__ch{ opacity: 1 !important; }

  /* ★ 2026-08-03（4回目）: .dw-count__n / __u / __split の3行を消した（③で
     要素ごと削除したため）。代わりに §4c の見出し（トピック／アプリ）を足す。 */
  html.js .page--portal .dw-head.is-in .dw-tagline,
  html.js .page--portal .dw-latest.is-in .dw-sec__head,
  html.js .page--portal .dw-sec__head.is-in,
  html.js .page--portal .dw-grid.is-in .dw-tile,
  html.js .page--portal .dw-close.is-in .dw-close__ch,
  html .page--portal .dw-tagline__dot{
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
  }

  /* ホバーは「動かない反応」に置き換える。押せることは伝わったままにする。
     ★★ 2026-08-03: :hover だけ (hover: hover) の中へ移した（§6 と同じ扱い）。
       §6 の hover 本体を囲ったのに、ここの打ち消しを外に残すと、
       hover を持たない端末で「相手の居ない打ち消し」だけが残る。
       :focus-visible と :active は囲わない（どちらも入力手段に依らない）。
     ★ 入れ子の @media。条件は and で積まれる
       ＝ (reduce) かつ (hover: hover) かつ (pointer: fine) のときだけ効く。 */
  html .page--portal .dw-cell:focus-visible .dw-shelf,
  html .page--portal .dw-cell:active .dw-shelf{ transform: none; }
  @media (hover: hover) and (pointer: fine){
    html .page--portal .dw-cell:hover .dw-shelf{ transform: none; }
  }
  /* ★★ 2026-08-03（4回目）: 持ち上げを切ったので **板の上の先取りも倒す。**
     §6 の .dw-plate は上の inset にだけ --dw-raise を足している（棚が
     translateY で上へ動くぶんを先に開けておく形）。ここで棚を transform:none に
     しておきながら変数を 8px のままにすると、持ち上がらないのに板の上だけ
     8px 開いて、今度は **上が余りすぎる**（1280px で 上 20.24 / 左右 12.24）。
     0px に倒せば上下左右が同じ 0.34*gapX に戻る。
     ★ 詳細度 (0,1,1) ＞ §0 の .page--portal (0,1,0)。
       **§0 を body.page--portal にしないこと。**同値で並んで順序頼みになる。 */
  html .page--portal{ --dw-raise: 0px; }
  html .page--portal .dw-plate{ transform: none; transition: none; }
  html .page--portal .dw-name__rule{ transition: none; }
  /* ★ .dw-shelf は上の `.page--portal *`（詳細度 0,1,0）では潰せない。
     §6 の `.page--portal .dw-shelf` は (0,2,0)。transition-property が
     "transform" のまま残り、shell.css §6 の duration:.01ms と組んで、
     描画されていないタブでは 0% に固まる。同じ詳細度で潰す。
     ★ 影の2枚（::before / ::after）も同じ理由で名指しする。上の
       `*::before` は (0,1,1) で、§6 の (0,2,1) に負ける。
     ★ 2026-08-03 まではここは .dw-icon だった。持ち上げと影が
       .dw-shelf へ移ったので追随させている（§6 の長い注記を参照）。 */
  html .page--portal .dw-shelf{ transition: none; }
  html .page--portal .dw-shelf::before,
  html .page--portal .dw-shelf::after{ transition: none; }
  /* ★ 2026-08-03（2回目）: 光沢が3枚 → **8枚**に増えたが、この規則は
     `released` も `.dw-tile--out` も見ていないので **8枚とも器ごと消える**。
     器（.dw-sheen）を display:none にすれば帯（::before）も生成されないので、
     ここは1行のままでよい（帯を名指しする必要は無い）。 */
  html .page--portal .dw-sheen{ display: none; }
  /* ★★ 2026-08-03（3回目）: タッチの発火経路（§6 .is-lit）も **明示的に**
     切る。上の1行（display:none）で器が消えれば帯も生成されないので
     実効は既にゼロだが、ここに名前が無いと
       「§6 に足した引き金が §11 に登録されているか」
     を将来 grep で確かめられない。詳細度は (0,4,0) / (0,4,1) で
     §6 の (0,3,0) / (0,3,1) を上回る ＝ display を将来ゆるめても効く。
     ★ reduce のときに「一瞬だけ出て消えるもの」を出さない、という §11 の
       方針は着地の合図（.is-landed）と同じ。 */
  html .page--portal .dw-tile.is-lit .dw-sheen,
  html .page--portal .dw-tile.is-lit .dw-sheen::before{
    animation: none !important;
    opacity: 0 !important;
  }
  /* ★★ 2026-08-03（4回目）: **タップのたびの光（§6 .is-tap）も同じ扱い。**
     上の .is-lit と同じ理由で、器が display:none になっていても名前を書く
     （「§6 に足した引き金が §11 に登録されているか」を grep で確かめられる
     ようにしておく）。詳細度 (0,4,0) / (0,4,1) で §6 の (0,3,0) / (0,3,1) と
     §10 の例外 (0,4,0) を上回る。
     ★ reduce のときに「一瞬だけ出て消えるもの」を出さない、という §11 の方針。 */
  html .page--portal .dw-tile.is-tap .dw-sheen,
  html .page--portal .dw-tile.is-tap .dw-sheen::before{
    animation: none !important;
    opacity: 0 !important;
  }

  /* --- 引き出し: 動かさずに、瞬時に開閉する --- */
  html.js .page--portal .dw-drawer,
  html.js .page--portal .dw-drawer.is-open,
  html.js .page--portal .dw-drawer.is-shut,
  html.js .page--portal .dw-drawer .dw-panel,
  html.js .page--portal .dw-drawer .dw-shot,
  html.js .page--portal .dw-drawer .dw-facts li,
  html.js .page--portal .dw-tile.is-open .dw-shelf{
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
  }
  /* .is-shut は「閉じ終わり」の静止形。animation を切ったので即座に畳む。 */
  html.js .page--portal .dw-drawer.is-shut{
    grid-template-rows: 0fr;
    margin-block-start: 0;
  }
  /* 沈みを transform で出せないぶん、押されている印は影の平ら化で出す。
     ★ 影は棚の2枚が持つ（§6・2026-08-03）。静止の1枚を --shadow-flat に落として
       ホバーの1枚を引っ込める。値は元の box-shadow と同じ。 */
  html .page--portal .dw-tile.is-open .dw-shelf{
    --dw-sh-rest: var(--shadow-flat);
  }
  html .page--portal .dw-tile.is-open .dw-shelf::before{ opacity: 1; }
  html .page--portal .dw-tile.is-open .dw-shelf::after { opacity: 0; }
  html .page--portal .dw-more__arw{ transition: none; }
  html .page--portal .dw-store__arw{ transition: none; }

  /* --- 最新トピックの帯（§4b・2026-08-03 追加 → 同日に作り直し）---
     ★ .dw-latest そのものの opacity は上の [data-in] の行が起こしている。
       ここで起こすのは中身（図版・印と日付・記事名・抜粋・閉じの語）。
     ★ transition は名指しで断つ。上の `.page--portal *`（詳細度 0,1,0）では
       §4b の (0,2,0) を潰せない（§9 と同じ理由）。 */
  html.js .page--portal .dw-latest.is-in .dw-latest__thumb,
  html.js .page--portal .dw-latest.is-in .dw-latest__meta,
  html.js .page--portal .dw-latest.is-in .dw-latest__title,
  html.js .page--portal .dw-latest.is-in .dw-latest__excerpt,
  html.js .page--portal .dw-latest.is-in .dw-latest__jump{
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
  }
  html .page--portal .dw-latest__card{ transition: none; }
  html .page--portal .dw-latest__card::before,
  html .page--portal .dw-latest__card::after{ transition: none; }
  html .page--portal .dw-latest__jump{ transition: none; }
  html .page--portal .dw-latest__arw { transition: none; }

  /* --- 着地の合図（§1d / §9 .is-landed・2026-08-03 追加）---
     ★★ **光らせない**（reduce のときは「一瞬だけ出て消えるもの」を出さない）。
       着地したことは、見出しへ移ったフォーカスが伝える。
     ★ animation を切るだけでなく opacity:0 も !important で固める。
       切りっぱなしにすると、地（--accent-soft）が出たまま貼り付く。
     ★ 上の `.page--portal *`（詳細度 0,1,0）では届かない。擬似要素は
       セレクタで名指しが要る（shell.css §6 は `*::after` を持っているので
       あちらは届く。だから **必ずここで名指しして潰す**）。
     ★ JS（§D）は動きが1つも走っていないときは .is-landed をその場で外すので、
       クラスが残り続けることもない。 */
  html.js .page--portal .dw-diary__item.is-landed::after{
    animation: none !important;
    opacity: 0 !important;
  }

  /* --- 開発日記（§9・2026-08-03 追加）---
     ★ .dw-diary そのものの opacity は上の [data-in] の行が起こしている。
       ここで起こすのは中身（見出しの行と1件ずつ）。
     ★ transition は「時差なしで即座に確定」へ倒す。上の
       `.page--portal *`（詳細度 0,1,0）では §9 の (0,2,0) を潰せないので名指しする。 */
  html.js .page--portal .dw-diary.is-in .dw-diary__head,
  html.js .page--portal .dw-diary.is-in .dw-diary__item{
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
  }
  html .page--portal .dw-diary__item{ transition: none; }
  html .page--portal .dw-diary__title{ transition: none; }
  html .page--portal .dw-diary__arw{ transition: none; }

  /* --- ライトボックス: 動かさずに、瞬時に開閉する（2026-08-03 追加）---
     ★ 開閉そのものは dialog の showModal()/close() が持っていて、
       animation は上乗せでしかない。だから全部切っても
       「押すと開く・Escape で閉じる・← → で移る」は必ず成立する。
     ★ ::backdrop は別行に置く。* にも .page--portal * にも当たらない
       （擬似要素はセレクタで名指ししないと届かない）ので、
       まとめて書くと幕だけ .01ms のまま 0% で固まる。 */
  html.js .page--portal .dw-lb,
  html.js .page--portal .dw-lb.is-open,
  html.js .page--portal .dw-lb.is-shut,
  html.js .page--portal .dw-lb .dw-lb__box,
  html.js .page--portal .dw-lb .dw-lb__img,
  html.js .page--portal .dw-lb .dw-lb__img.is-swap{
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
  }
  html.js .page--portal .dw-lb.is-open::backdrop,
  html.js .page--portal .dw-lb.is-shut::backdrop{
    animation: none !important;
    opacity: 1 !important;
  }
}

/* ═══ 13. 日本語の折り返し ════════════════════════════════════════════════
   Ryo さん提供のスクショで発覚した件（2026-08-02。tile-drawer.css.njk §13 と
   dw-c に同じ対処が入っている）。既定の word-break:normal だとブラウザが
   文節を無視して折り返し、単語が途中で切れる:
     「評価されない。繋がらない。で / も、ひとりぼっちじゃない。」
     「SNSを開いた瞬間に、単語1問だ / け。」
   word-break:auto-phrase は日本語の文節を見て折り返す（Chrome 119+）。
   非対応環境は normal に落ちるだけで無害。
   text-wrap:pretty は最終行の孤立（1文字だけ残る）を減らす。
   ★ 本採用時もこのセクションは残すこと。
   ══════════════════════════════════════════════════════════════════════ */
.page--portal .dw-tagline,
.page--portal .dw-desc,
.page--portal .dw-lead,
.page--portal .dw-facts li,
.page--portal .dw-shot__cap,
.page--portal .dw-absence,
.page--portal .dw-foot__meta,
.page--portal .dw-lb__cap,
.page--portal .dw-lb__screen,
.page--portal .dw-latest__title,
.page--portal .dw-latest__excerpt,
.page--portal .dw-diary__title,
.page--portal .dw-diary__excerpt,
.page--portal .dw-close{
  word-break: auto-phrase;
  text-wrap: pretty;
}
/* ★ 日記の記事名と抜粋はどちらも日本語の文章なので対象に入れる
   （2026-08-03 追加）。除外しているのは §6 .dw-name（アプリ名＝固有名詞）だけ。
   .dw-diary__date は数字なので折り返しの規則が要らない（white-space:nowrap）。 */
/* ★ .dw-name には当てない（2026-08-03 に実測して外した）。
   375px で「en（円コレクション）」が
     いま      en（円コレクショ / ン）      2行 54px
     auto-phrase  en / （円コレクショ / ン）  3行 81px
   と1行増える。名前の高さは説明と状態ラベルの行位置を決めているので、
   8枚の baseline（+269 / +310 / +364）が en の列だけ 27px 下がって崩れる。
   アプリ名は固有名詞で文節も無い。折り返しの改善は本文だけでよい。 */
