/*
  AstLog — 公開ページと管理画面の共通スタイル。

  値を決めるのはこのファイルの :root だけ。各セレクタに生の色やサイズを
  書かないこと。値が散ると、差に意味があるのか後から分からなくなる。

  幅は「画面ごと」ではなく「部品ごと」に決める。
  メディアクエリは 600 と 900 の2つだけ。連続的に変わるもの（見出し・余白・
  列数）は clamp() と auto-fill に任せ、ブレークポイントを増やさない。
  モバイルファースト: 素の指定が Phone、min-width で足していく。

  質感と手触りは React Bits（reactbits.dev）の Micro を手本にしている。
  向こうのコードは使っていない。JavaScript を持ち込まず、CSS だけで作り直した:
  - 公開ページは箱（枠・面・影）を並べず、罫線で区切り、角を立てる。面（白をうっすら
    重ねた「ゴースト」と、上辺の 1px の光）は管理画面の部品だけが持つ
  - 押すと縮み、離すとばねで戻る。ばねの曲線は :root の --ease-spring だけを使う
*/

:root {
  --font-sans:
    'Helvetica Neue', 'Hiragino Sans', 'Noto Sans JP', system-ui, -apple-system, sans-serif;
  /*
    等幅は英数字の札だけ（実績値の数・タグ・英大文字の小見出し・番号）。和文が入りうる所には使わない——等幅の書体は
    和文の字を持たず、字は結局ほかの書体に落ちる。残るのは等幅のための字間
    （letter-spacing）と小ささだけで、和文には読みにくさしか渡さない。
    打ち込んだ文字が入る札（タグ・肩書き・技術の小見出し）は、日本語を1字も
    含まないときだけ lang="en" が付き（components.tsx の langOf）、等幅はその
    :lang(en) にだけ掛ける。年と期間は「2024 — 現在」と和文を含む書き方に
    決めた（CLAUDE.md「文言」）ので、等幅にせず数字の幅だけそろえる（tabular-nums）。

    Windows の書体（Consolas）も名指しする。ui-monospace と Mac の名前は Windows の Chrome に
    無く、素の monospace に落ちる——lang="en" の札は Consolas になるが、番号だけの札（一覧の
    行の番号・見出しの件数）は日本語のページの等幅（MS ゴシック）になり、細い古い数字で
    英字の札と別の書体に見えた
  */
  --font-mono: ui-monospace, 'SFMono-Regular', 'SF Mono', Menlo, Consolas, monospace;
  --font-serif: 'Hiragino Mincho ProN', 'Yu Mincho', 'Noto Serif JP', Georgia, serif;

  /* 見出しの書体。本文は読みやすさを優先して常に --font-sans */
  --font-display: var(--font-sans);

  /*
    文字の段（静的）。6つだけ。中間値を足さない。

    和文の最小は --fs-meta（12px）。--fs-label（11px）は英大文字の小見出し
    （Skills の LANGUAGES・管理画面の ADMIN・404 の番号）だけに使う。11px の
    和文は画数の多い字が潰れる——以前は業界名・帯の件数・節の添え・経歴の
    期間・足元が 11px（しかも等幅）で、和文の札がいちばん読みにくかった。
    どこに使ってよいかは test/theme.test.ts の「文字の段」が名指しで数えている。
  */
  --fs-label: 11px;
  --fs-meta: 12px;
  --fs-sm: 13px;
  --fs-base: 14px;
  --fs-md: 15px;
  --fs-lg: 18px;

  /*
    文字の段（画面に連動）。見出しの4段だけで、本文には使わない。
    xl > 素 > sm > xs の1本のはしごで、部品の名前は持たせない。

    連動する段をこの4つに絞ったのは、残りが設計域（390 / 768 / 1440）で
    ほとんど動かなかったため。--fs-card-title は 390 と 768 で 15.00px、
    1440 で 15.84px——名前は「連動」でも実体は静的な 15px（= --fs-md）
    だった。--fs-lead（14.00 / 14.00 / 15.12px）と --fs-section
    （17.00 / 17.00 / 18.72px）も同じで、3つとも静的な段に畳んである。

    可変部は幅と高さの小さいほう（下の @supports で svh を足す）。
  */
  --fs-display-xl: clamp(30px, 4.3vw, 62px);
  --fs-display: clamp(28px, 4vw, 52px);
  --fs-display-sm: clamp(24px, 3vw, 38px);
  --fs-display-xs: clamp(19px, 1.7vw, 24px);

  /* 余白の段。7つから選ぶ */
  --sp-1: 2px;
  --sp-2: 4px;
  --sp-3: 8px;
  --sp-4: 12px;
  --sp-5: 16px;
  --sp-6: 24px;
  --sp-7: 32px;

  /*
    版面の幅。上の帯・本文・足元の中身をこの幅にそろえ、広い画面では左右に
    余りを均等に置く（「骨格」の .top / main / .foot の padding-inline）。
    地と帯の罫線だけは画面の端まで伸びる。
  */
  --w-page: 1344px;
  --gutter: clamp(16px, 3.4vw, 60px);
  /* 管理画面の面は大きく、操作部品は小さく丸める（公開ページは角を立てるので使わない） */
  --radius: 16px;
  --radius-sm: 10px;

  /*
    画面1つぶんの高さ。svh は道具の帯を出したときの高さなので、帯の出入りで
    表紙の高さが跳ねない。

    100vh の控えは置かない。svh を知らない環境ではこの値が計算できず、min-height が
    素の auto に落ちる——表紙が中身の高さになるだけで、読めなくなるものは無い。
  */
  --screen-h: calc(100svh - env(safe-area-inset-top) - env(safe-area-inset-bottom));

  /*
    上の帯（.top）の高さ。ロゴと目次の1行で、行き先の的（--tap）が入る高さ。
    帯の段の数を中身で変えない（目次は1行のまま横に送る）ので、高さはいつもこれ。
  */
  --top-h: 64px;
  /*
    本文（main）の上下の余白。帯の罫線から節の見出しまで、どのページも同じだけ
    空ける（見出しの錨。「ページの外枠」）。
  */
  --page-pad: clamp(32px, 5vw, 72px);
  /*
    表紙（入口と締めの Contact）の高さの下限。帯と本文の上下の余白を除いた、
    画面の残りいっぱい。表紙の下端が画面の下端に来て、足元は次の画面に回る。
  */
  --cover-h: calc(var(--screen-h) - var(--top-h) - var(--page-pad) * 2);
  /*
    貼り付いた上の帯の下に、送った先（#about などのフラグメント・フォーカス）を
    止める空き（html の scroll-padding-top。「ページの外枠」）。帯の高さ＋ --sp-5。
    npm run check:fit が、節の id へ送った先の上端が帯の下端より下にあることを測る。
  */
  --top-clear: calc(var(--top-h) + var(--sp-5));

  /*
    上の帯のロゴ（ワードマーク ΛSTLOG。O がブラックホール）の字の高さ。字の幅は字の
    高さの約 6.6 倍（15px で 99px）。ログイン画面のワードマークも同じ規則（.brand__word）。
    --hole-core はロゴの O の黒い円（影）の色で、地より一段深い黒。src/ui/logo.ts の
    LOGO_COLORS（素材のファイルに焼く色）と同じ値
  */
  --brand-h: 15px;
  --hole-core: #000;

  /*
    段落（.bio p。紹介文・メモ・作品の説明と本文）の1行の長さ。約40字で止める。

    和文の本文は1行 35〜40字あたりを超えると、行末から次の行頭へ目を戻すときに
    行を取り違えやすくなる。止めていなかったころは、1440 の About が1行約65字
    で流れていた（本文の列 869px ÷ 13px = rail @1440x900, Hiragino Sans,
    macOS Chromium）。em で書くのは、和文の1字がちょうど 1em の幅だから
    （本文には palt を掛けていない）——段落の字の大きさを変えても、字数のまま
    ついてくる。390 の電話では画面のほうが狭い（1行 23字前後）ので効かない。
  */
  --measure: 40em;

  /*
    作品の画像の枠。どちらも「絵に合わせて伸び縮みさせない」ための段で、
    寸法の分からない画像でも、読み込む前から高さが決まっている（DB の寸法は
    この列より前の画像には無いので、この2つの枠には使わない。寸法は共有カードと、
    画像そのものの縦横比のために使う）。

    --thumb-ratio は一覧のサムネイルの縦横比。Mac の画面と同じ 16:10 で、画面の
    スクリーンショットがそのまま枠いっぱいに収まる（絵は切らない。.entry__thumb）。
    3:1 の帯に切り抜いていたころは、画面の上の半分だけが部品の途中で切れて見えた
    （持ち主の「途切れている」）。

    --shot-h は作品ページの画像の枠の高さ。絵はこの中に縮めて収める
    （object-fit: contain。切らない）。可変部は幅と高さの小さいほうで、
    背の低い窓ほど小さくなる——ページを開いた最初の画面に、見出しと画像だけで
    なく説明の頭まで入るように。
  */
  --thumb-ratio: 16 / 10;
  --shot-h: clamp(120px, min(30vw, 20svh), 280px);

  /*
    作品のページのギャラリー（.gallery）。先頭を広く、続きは大小の列で見せる。
    比は一覧と同じ --thumb-ratio。高さの上限だけ持つので、狭い画面では幅いっぱいに
    収まり、広い画面の縦長の画像でも何画面ぶんも伸びない。絵は contain で切らない。

    --head-icon は作品のページの見出しの左のアイコンの大きさ（.head__icon）
  */
  --gallery-lead-h: 680px;
  --gallery-shot-h: 480px;
  --gallery-shift: clamp(24px, 4vw, 64px);
  --head-icon: clamp(44px, 5vw, 64px);

  /* The bodies share the black hole's subdued light. Narrow profiles keep the
     decoration compact; identity symbols retain one readable size. */
  --celestial-w: 580px;
  --celestial-profile-w: clamp(200px, 52vw, 240px);
  --celestial-symbol: 24px;

  /*
    横に送る帯（上の帯の目次と、狭い画面の絞り込み）の右端のぼかし。切れている
    ことを見せる。覆いなので色ではなく不透明度の坂で、地の色とは関わらない。

    ぼかす幅（--fade-w）は、帯の右の余白（--gutter）に収まる幅。帯は右端に同じ幅の
    空き（padding）を持ち、同じだけ右の余白へはみ出して置く（「骨格」の .toc）——
    溢れていなければ、ぼかしは何も無い空きに掛かるだけで、最後の行き先は薄まらない。
    溢れたときだけ、送れる中身がぼかしの下に入る。
  */
  --fade-w: min(var(--sp-6), var(--gutter));
  --fade-right: linear-gradient(to right, #000 calc(100% - var(--fade-w)), transparent);

  /*
    入口の軌道図（.system。components.tsx の OrbitSystem、形は src/lib/orbits.ts）。

    --system-ratio / --contact-ratio は入口と締めの枠の縦横比。orbits.ts の
    HERO_FRAME / CONTACT_FRAME の width / height と同じ数（test/theme.test.ts が
    突き合わせる）。SVG は枠いっぱいに貼るので、ずれると絵が枠の中で縮み、
    焦点（ブラックホール）が軌道の焦点から外れる。枠の幅は置かれた列の幅（入口の
    右の列、900 未満は本文の列いっぱい）。

    --system-tilt は軌道面の画面の中での傾き（orbits.ts の TILT と同じ数。test が
    突き合わせる）。軌道は orbits.ts が傾けて描き、ブラックホールの絵（円盤は軌道と同じ面）は
    ここで同じだけ回す。いまは水平（0deg）。
  */
  --system-ratio: 1000 / 560;
  --contact-ratio: 1000 / 512;
  --system-tilt: 0deg;
  /*
    締めの軌道図の幅の上限と、高さの上限（表紙の高さの半分）。星系は枠の幅いっぱいに描くので、
    広い画面で本文の列いっぱいに伸ばすと、表紙の高さを図だけで食う。背の低い窓では高さで
    止める——止めないと、誘いの1文とメールと GitHub の手が1画面目の下へ押し出される
    （npm run check:contrast の 1024x768 が字を測れなくなる）
  */
  --contact-w: 900px;
  --contact-h: calc(var(--cover-h) * 0.5);

  /*
    軌道の線と天体。

    線の太さは画面の px（vector-effect: non-scaling-stroke）で、1px のまま。天体（中心から
    溶ける白い光と、まわりのにじみと、業務の天体の輪）は枠の単位で描き、枠の幅に比例する。
    手前の天体ほど大きい（奥行きの倍率。回る天体の大きさは transform で変える）。

    線はアクセント色。濃さは軌道面の奥から手前へ続けて変わる——手前の端が --orbit-ink、
    奥の端はそれに --orbit-far を掛けた薄さ（奥ほど遠い）。外の軌道ほど淡く、いちばん外側は
    内側の --orbit-outer 倍（同じ濃さの輪が並ぶと、的のような平らな縞に見えた）。
    天体の光のまわりにも外へ消えるにじみ（濃さ --body-halo。色はアクセント。大きさは
    components.tsx の Bodies）。字は軌道の上に乗せないので、濃さの
    上限は見た目だけで決まり、下限は npm run check:contrast が測る（線が消えるのは改善
    ではない）。
  */
  --orbit-line: 1px;
  --orbit-ink: 0.34;
  --orbit-far: 0.15;
  --orbit-outer: 0.55;
  --orbit-band: 0.42;
  /*
    ブラックホールの光の絵の濃さ（components.tsx の Hole。影の黒い円には掛けない）。白い光が
    濃いと、星屑の円盤よりブラックホールが先に目に入った（持ち主の「主張が強すぎる」）
  */
  --hole-light: 0.72;
  /*
    星屑の粒の太さと濃さ（components.tsx の Stardust）。星屑は回るのでぼかしを掛けない。
    ぼかしてにじませていたころと同じ明るさに見えるよう、粒を少し太く濃くしてある
  */
  --stardust-w: clamp(0.6px, 0.16cqi, 1.25px);
  --stardust-ink: 1;
  --stardust-far: 0.35;
  --body-halo: 0.55;

  /*
    星雲（components.tsx の Nebula。形は orbits.ts の nebulaMap）。雲の4枚（光・雲・筋・塵）を
    透過 WebP に焼くときの濃さ。色は見た目のプリセットごとの3色（--nebula-a / -b / -c。下の「見た目の
    プリセット」）で、モノクロのときも星雲だけは青紫と桃と空色にする（持ち主の「星雲だけ色を
    付ける」）。字は星雲の上に乗せない（星空の覆いが字の後ろで消す）。下限は
    npm run check:contrast が測る
  */
  --nebula-art: url('/assets/nebula-iris.webp?v=c5b0f3ae');
  --nebula-light: 0.3;
  --nebula-cloud: 0.95;
  --nebula-veil: 0.85;
  --nebula-dust: 0.85;
  --nebula-a: var(--iris);
  --nebula-b: var(--rose);
  --nebula-c: var(--sky);

  /*
    宇宙（components.tsx の Cosmos）。星空に敷く星雲の色の淡い広がりの濃さ（2色を透明に
    混ぜる割合）
  */
  --cosmos-wash: 16%;
  /*
    字の暗がり（「入口の軌道図」の .hero__copy と、Contact の .contact の ::before）。字の塊の
    箱は地の色で塗りきり、その外で星空と星雲へ溶かす長さ。上と下と左（--veil）は短く——900
    未満ではすぐ上に図がある。右（--veil-side）は長く——900 以上の入口では右に図があり、短い
    坂の縦の縁が、星雲を断ち切る線に見えた。

    坂は覆い（--veil-y は縦、--veil-x は横。900 以上だけ重ねる）。濃さは S 字に変える
    （3次のなめらかな段差の 1/4・1/2・3/4 の値）——まっすぐな坂は、両端の折れ目が淡い星雲の上で
    線に見えた
  */
  --veil: clamp(40px, 5vw, 72px);
  --veil-side: clamp(64px, 14vw, 240px);
  --veil-y: linear-gradient(
    to bottom,
    transparent,
    rgb(0 0 0 / 0.16) calc(var(--veil) * 0.25),
    rgb(0 0 0 / 0.5) calc(var(--veil) * 0.5),
    rgb(0 0 0 / 0.84) calc(var(--veil) * 0.75),
    #000 var(--veil),
    #000 calc(100% - var(--veil)),
    rgb(0 0 0 / 0.84) calc(100% - var(--veil) * 0.75),
    rgb(0 0 0 / 0.5) calc(100% - var(--veil) * 0.5),
    rgb(0 0 0 / 0.16) calc(100% - var(--veil) * 0.25),
    transparent
  );
  --veil-x: linear-gradient(
    to right,
    transparent,
    rgb(0 0 0 / 0.16) calc(var(--veil) * 0.25),
    rgb(0 0 0 / 0.5) calc(var(--veil) * 0.5),
    rgb(0 0 0 / 0.84) calc(var(--veil) * 0.75),
    #000 var(--veil),
    #000 calc(100% - var(--veil-side)),
    rgb(0 0 0 / 0.84) calc(100% - var(--veil-side) * 0.75),
    rgb(0 0 0 / 0.5) calc(100% - var(--veil-side) * 0.5),
    rgb(0 0 0 / 0.16) calc(100% - var(--veil-side) * 0.25),
    transparent
  );

  /* 入口の字が先に読める短い待ちから、件数だけを数え上げる。図は初めから完成形。 */
  --count-dur: 1200ms;
  --count-delay: 600ms;

  /*
    着いたあとも動き続けるもの（「動き続ける」）。星屑と天体の公転と軌道を流れる星（1周の長さは
    orbits.ts の INNER_PERIOD から。外側の軌道ほど長い）、吸い込まれる粒（落ちるほど速く回り、
    速く寄る。曲線は keyframes の orbit-grain-*）、ブラックホールの光の揺らぎ（明るさが
    --breathe-low まで沈んで戻る。片道 --breathe-dur）、星雲の漂い（片道 --drift-dur）と星の
    瞬き（周期は星ごと。orbits.ts の nebulaMap）。止める手は置かない（持ち主の判断。動きを
    減らす設定では全部止まる）
  */
  --drift-dur: 40s;
  --drift-ticks: 400;
  --breathe-dur: 4s;
  --breathe-ticks: 40;
  --breathe-low: 0.82;
  /* 天体の小さな浮遊・外周の光。画像は固定画素、更新は約100msごと。 */
  --celestial-moon-dur: 14s;
  --celestial-moon-ticks: 18;
  --celestial-neptune-dur: 16s;
  --celestial-neptune-ticks: 20;
  --celestial-saturn-dur: 14s;
  --celestial-saturn-ticks: 18;
  --celestial-radiance-dur: 5s;
  --celestial-radiance-ticks: 50;
  --celestial-breathe-dur: 4s;
  --celestial-breathe-ticks: 40;
  --celestial-flow-dur: 10s;
  --celestial-flow-ticks: 25;
  --celestial-flow-offset: -5s;
  --celestial-flow-beam-w: 40%;
  --celestial-flow-beam: linear-gradient(100deg, transparent, var(--ink), transparent);
  --celestial-flow-mask: linear-gradient(
    to bottom,
    transparent 42%,
    #000 49%,
    #000 56%,
    transparent 63%
  );
  --celestial-corona-mask: radial-gradient(
    circle closest-side,
    transparent 59%,
    #000 71%,
    #000 85%,
    transparent 98%
  );
  --celestial-corona-light: 0.2;
  /*
    軌道を流れる星（components.tsx の Flows）。後ろに引く尾の、星の側の濃さ（--flow-trail。
    後ろへ消える）と、十字の光芒の濃さ（--flow-glint）
  */
  --flow-trail: 0.5;
  --flow-glint: 0.75;

  /* 塗りの押し手（.cta）の高さ。指の的（--tap）より大きく取る、ページでいちばん強い1本 */
  --cta-h: 52px;

  /*
    一覧の行（.entry）の区画の幅。番号の列・札の列・技術の列・矢印の列で、本文の列が
    残りを受ける。サムネイルは本文の下に、--entry-thumb を上限に列いっぱいまで。
    --entry-title-lh は行の題（--fs-display-xs）の1行の高さで、番号と矢印を題の
    1行目の高さの真ん中に置くために読む。
  */
  --entry-index: 56px;
  --entry-meta: 160px;
  --entry-tags: 200px;
  --entry-thumb: 320px;
  --entry-go: var(--sp-6);
  --entry-title-lh: calc(var(--fs-display-xs) * 1.3);
  /* 行の題の左のアイコン（.entry__icon）。題の字より一回り大きく、1行目の高さの真ん中に置く */
  --entry-icon: calc(var(--fs-display-xs) * 1.7);

  --ease: cubic-bezier(0.23, 1, 0.32, 1);
  --dur-fast: 180ms;
  --dur: 320ms;

  /*
    ばね。押した部品が戻るとき、スイッチのつまみが走るとき、知らせが出るときに使う。
    linear() が使えない環境向けの近似をここに置き、本物は下の @supports で差し替える
  */
  --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
  --dur-spring: 640ms;
  --press: 0.96;
  --dot: 6px;

  /*
    入口の字の浮かび上がり。不透明度と下からのずれ（--enter-shift）を解き、
    句読点で切った塊を --stagger ずつ遅らせて
    出す。曲線はばねと同じ1本（--ease-spring）で、量だけをここで決める
  */
  --enter-shift: var(--sp-4);
  --stagger: 90ms;

  /*
    配色は黒基調の1つ（夜の地）。文字もボタンも白で組む——アクセントの既定は
    白黒（--mono。管理画面の「見た目」でほかの色も選べる）。OS がライトを選んで
    いても白い地には切り替えない。「黒の上に白」がこのサイトの見た目の芯で、
    白い地に裏返すと別のサイトに見える。紙だけは白い地で刷る（末尾の @media print）。

    color-scheme は、フォームの部品やスクロールバーのようなブラウザが描くものも
    暗い側にそろえるため（<meta name="color-scheme"> と同じ値。components.tsx の
    ColorSchemeMeta）。

    地は色味を持たない黒にする。紫や青に寄せると、面も文字も全部その色に
    染まって見え、アクセントを変えても画面の印象が変わらなくなる。

    面は不透明な色で塗らず、白をうっすら重ねて作る（ゴースト。管理画面の部品だけ）。
    上辺の 1px の光（--highlight）もその面を立たせるためのもの。公開ページは面を
    持たず、罫線（--line / --line-strong）で区切る
  */
  color-scheme: dark;
  --bg: #0c0c0e;
  --surface: rgba(255, 255, 255, 0.04);
  --surface-hover: rgba(255, 255, 255, 0.07);
  --line: rgba(255, 255, 255, 0.07);
  --line-strong: rgba(255, 255, 255, 0.13);
  --highlight: inset 0 1px 0 rgba(255, 255, 255, 0.07);
  --ink: #f2f2f4;
  --ink-mid: #b4b4c2;
  --ink-weak: #8a8a96;
  /*
    アクセントの候補。どれを使うかは管理画面の「見た目」で選ぶ。既定はモノクロ
    （--mono）で、リンク・塗りのボタン・選んだ印・軌道の線がどれも白になり、
    塗りのボタンの字（--accent-ink）は黒になる
  */
  --mono: #f2f2f4;
  --mono-soft: rgba(242, 242, 244, 0.1);
  --iris: #8f8ff5;
  --iris-soft: rgba(143, 143, 245, 0.13);
  --violet: #b58cf8;
  --violet-soft: rgba(181, 140, 248, 0.16);
  --ember: #e8a15c;
  --ember-soft: rgba(232, 161, 92, 0.14);
  --mint: #5fd0ab;
  --mint-soft: rgba(95, 208, 171, 0.13);
  --sky: #6fb6f2;
  --sky-soft: rgba(111, 182, 242, 0.13);
  --rose: #f0899f;
  --rose-soft: rgba(240, 137, 159, 0.13);

  --accent: var(--mono);
  --accent-soft: var(--mono-soft);
  --accent-ink: #12121a;
  --danger: #e5848a;
  --danger-soft: rgba(229, 132, 138, 0.14);

  /* 公開する / しない のスイッチ。つまみは押すと潰れて横に伸びる */
  --switch-w: 44px;
  --switch-h: 24px;
  --switch-knob: 18px;
  --switch-squish: 1.35;
  --switch-track: rgba(255, 255, 255, 0.1);
  --switch-track-on: var(--ink);
  --switch-knob-off: var(--ink-weak);
  --switch-knob-on: var(--bg);

  --tap: 40px;

  /*
    フォーカスの輪郭の太さと、要素からの離し方。素の :focus-visible と、
    輪郭を自分で描く3か所（一覧の行の面・スイッチ・見た目の見本）が同じ段を
    読む。行の面（.entry__link::after）は輪郭をリンクではなく行に描くので、
    素の規則をそのまま受けられず、値を写すことになる——写した値は
    いつか食い違うので、ここで1つにしておく。
  */
  --focus-ring: 2px;
  --focus-offset: 3px;
}

/*
  ばねの本物。減衰比 0.62 のばねの応答を、折れ線で近似したもの（行き過ぎて 8% 戻る）。
  使えないブラウザでは上の cubic-bezier のまま動く
*/
@supports (transition-timing-function: linear(0, 1)) {
  :root {
    --ease-spring: linear(
      0,
      0.008 1.5%,
      0.032 3%,
      0.068 4.5%,
      0.13 6.5%,
      0.246 9.5%,
      0.628 18.5%,
      0.739 21.5%,
      0.834 24.5%,
      0.901 27%,
      0.965 30%,
      1.007 32.5%,
      1.043 35.5%,
      1.076 40.5%,
      1.083 46%,
      1.071 52%,
      1.02 67%,
      1.003 75%,
      0.993 88%,
      0.995 99.5%,
      1
    );
  }
}

/*
  見出しの段に高さを足す。

  連動する値の可変部が全部 vw だと、幅が広くて背の低い画面（横向きの電話、
  分割表示、短い窓）では、見出しだけが画面の高さに対して大きくなりすぎ、
  1行の見出しが画面の大半を食う。
  可変部を「幅と高さの小さいほう」にすれば、背が低いぶんだけ見出しが下がる。

  svh の係数は、設計サイズの3つで今日と同じ数になるように選んである
  （いちばん厳しいのは 1440x900 で、svh = 9px。上限 ÷ 9 を上回る係数にする）。
    390x844   svh 8.44px / vw 3.90px → どの段も下限に当たる（幅が先に効く）
    768x1024  svh 10.24px / vw 7.68px → 幅が先に効く
    1440x900  svh 9.00px / vw 14.40px → 高さが先に効くが、値は上限のまま
  つまり設計サイズでは1pxも変わらず、背の低い画面でだけ効く。

  括りは @supports。svh を知らない環境ではこの節ごと落ちて、上の幅だけの段が
  そのまま残る（括らないと var() の差し替えに失敗して、見出しが本文の 14px を
  受け継いでしまう）。
*/
@supports (font-size: 1svh) {
  :root {
    --fs-display-xl: clamp(30px, min(4.3vw, 6.9svh), 62px);
    --fs-display: clamp(28px, min(4vw, 6svh), 52px);
    --fs-display-sm: clamp(24px, min(3vw, 4.4svh), 38px);
    --fs-display-xs: clamp(19px, min(1.7vw, 2.7svh), 24px);
  }
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/*
  ページの中の節へのリンク（#about など）では、なめらかに動かす。公開ページだけ
  （body[data-site]）で、管理画面には持たせない。送った先の上の空きは、
  貼り付いた上の帯の下に隠れないよう「ページの外枠」が帯の高さぶんに差し替える
  （帯が貼り付かない全体ページはこの --sp-7 のまま）。
  詳細度を 0 にしてあるのは、下の prefers-reduced-motion で素の html の
  指定に譲るため。
*/
:where(html:has(> body[data-site])) {
  scroll-behavior: smooth;
  scroll-padding-top: var(--sp-7);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

h1,
h2,
h3,
h4,
p,
ul,
ol,
dl,
dd,
figure,
fieldset {
  margin: 0;
}

ul,
ol {
  padding: 0;
  list-style: none;
}

fieldset {
  border: 0;
  padding: 0;
  min-width: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

img {
  max-width: 100%;
  display: block;
}

:focus-visible {
  outline: var(--focus-ring) solid var(--accent);
  outline-offset: var(--focus-offset);
}

/*
  目に見せないが読み上げには残す。

  padding と border を0に落としてあるのは必須。clip-path: inset(50%) は
  ボーダーボックスの中央を切り抜くので、padding のある要素にこの組を付けると
  切り抜きが中身の外側に落ち、文字が見えたまま残る。次に使う人が「この部品は
  padding 無しでだけ安全」という条件を覚えなくて済むよう、部品の側で閉じておく。
*/
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip {
  position: absolute;
  left: var(--sp-4);
  top: -80px;
  z-index: 10;
  padding: var(--sp-3) var(--sp-5);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: var(--fs-sm);
  transition: top var(--dur-fast) var(--ease);
}

.skip:focus {
  top: var(--sp-4);
}

/* ============================================================ 骨格 */

/*
  公開ページの骨格は body の直下の3つ——上の帯（.top）・本文（main）・足元（.foot）
  （src/ui/Layout.tsx）。どのページも同じ骨格で、見た目のプリセットで並べ方は
  変えない（src/theme.ts）。

  3つとも中身を同じ版面の幅（--w-page）にそろえ、地と罫線だけを画面の端まで
  伸ばす。左右の余白は --gutter と、版面より広い画面で余った幅の半分の大きいほう。
  箱で包まずに余白で版面を作るのは、帯と足元の罫線を画面の端から端まで引くため。

  本文は data-site（公開ページの印）の下の main だけを名指しする。管理画面の
  main（admin.css の .admin-main）には当てない。
*/
:is(.top, .foot),
body[data-site] > main {
  padding-inline: max(var(--gutter), (100% - var(--w-page)) / 2);
}

body[data-site] > main {
  display: flex;
  flex-direction: column;
  gap: calc(var(--sp-7) * 2);
  min-width: 0;
  padding-block: var(--page-pad);
}

/*
  本文は、入口と締めの星空（.cosmos）の位置の基準で、重なりの容れ物。星空は本文の幅いっぱい
  （inset: 0）に、本文の中身の後ろ（z-index: -1）に敷く。容れ物にしないと、-1 の星空が
  ページの地の後ろへ沈むことがある。

  横だけ切る（overflow-x: clip。スクロール箱は作らず、縦は出してよい）。字の暗がり（「入口の
  軌道図」）は字の塊の外へ広げて溶かすので、締めの字が長いと画面の右端の先まで伸びる——
  切らないと、見えない暗がりでページが横に動く。本文の幅は画面の幅なので、ほかに切られる
  ものは無い（目次と絞り込みのはみ出しは右の余白の中まで）
*/
body[data-site] > main {
  position: relative;
  isolation: isolate;
  overflow-x: clip;
}

/* --- 上の帯 --- */

/*
  ロゴ（入口へ）を左に、目次を右に置く1行の帯。下に1本の罫線を引き、面は持たない
  （地の --bg のまま。貼り付いたとき、下を流れる本文の字が透けないように地を敷く）。
  高さは --top-h で決め打ちにせず下限にする——字を大きくした人の帯を切らない。
*/
.top {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  min-height: var(--top-h);
  border-bottom: 1px solid var(--line);
  background: var(--bg);
}

.brand {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  min-height: var(--tap);
  color: var(--ink);
}

/*
  ロゴの O の影の黒い円（src/ui/icons.tsx の HoleArt。上の帯・ログイン画面・404・管理画面）。
  光は焼いた絵（.logo-art。入口のブラックホールと同じ1枚）で、色を持たない
*/
.logo-core {
  fill: var(--hole-core);
}

/*
  ワードマーク（ΛSTLOG）。上の帯とログイン画面。高さで大きさを決め、幅は縦横比から。
  O の光は字の箱の外へはみ出して見せる（箱を光に合わせると、字が小さくなる）
*/
.brand__word {
  display: block;
  height: var(--brand-h);
  width: auto;
  overflow: visible;
}

/*
  目次。帯の残りの幅を取り、行き先は右へ寄せる（safe: 溢れたら左から並べる）。
  1行のまま横に送れる帯で、折り返さない——折り返すと帯が2段になり、貼り付いた帯が
  画面の上を食う（帯の段の数を中身で変えない）。

  右端のぼかし（--fade-right）のぶん、帯は右に空き（padding）を持ち、同じだけ右の
  余白へはみ出して置く（負の margin）。溢れていなければ最後の行き先は薄まらず、
  版面の右端（ほかの行の右端と同じ縦の線）にそろう。理由は :root の --fade-w。
*/
.toc {
  display: flex;
  flex: 1 1 0;
  justify-content: safe flex-end;
  gap: var(--sp-5);
  min-width: 0;
  margin-inline-end: calc(var(--fade-w) * -1);
  padding-inline-end: var(--fade-w);
  overflow-x: auto;
  scrollbar-width: none;
  mask-image: var(--fade-right);
}

/*
  目次に番号は振らない。目次の番号はブロックの並び順でしかなく、読む人に言う
  ことが無い（src/ui/Layout.tsx の目次の注記）。

  英字だけの行き先（Projects・Contact…）は、等幅の小さな大文字の札にする。
  打ち込んだ見出し（「あとがき」）が行き先の名前になることもあるので、和文の
  行き先は本文の書体のまま（lang="en" は Layout.tsx が langOf で付ける。
  :root の --font-mono を見ること）。
*/
.toc a {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  gap: var(--sp-3);
  min-height: var(--tap);
  color: var(--ink-weak);
  font-size: var(--fs-sm);
  transition:
    color var(--dur-fast) var(--ease),
    scale var(--dur-spring) var(--ease-spring);
}

.toc a:lang(en) {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/*
  帯は横に送れる箱で、はみ出した輪郭は箱の縁で切られる。フォーカスの輪郭は
  行き先の内側に描く（一覧の行の面と同じ扱い。.entry__link:focus-visible::after）
*/
.toc a:focus-visible {
  outline-offset: calc(var(--focus-ring) * -1);
}

/*
  いまのページ。字を --ink に上げ、頭に四角い印を1つ置く。色だけで分けない
  （印の形が残る。WCAG 1.4.1）。目次の行き先は節（#projects）ではなく画面の URL
  （/projects）なので、'true' ではなく 'page'

  印は枠線で塗る。地（background）で塗ると、強制色モード（Windows のハイコントラスト）
  では地の色に置き換わって消える——枠線は字の色になって残る
*/
.toc a[aria-current='page'] {
  color: var(--ink);
}

.toc a[aria-current='page']::before {
  content: '';
  width: var(--dot);
  height: var(--dot);
  border: calc(var(--dot) / 2) solid var(--accent);
}

/*
  管理画面への入口（AdminLink）。ログインしている人にだけ、目次の右に出る。
  目次の行と同じ的の高さで、色だけアクセントに寄せる——目次（画面の移動）とは
  別の行き先だと見分けるため。鉛筆の形も添えるので、色だけには頼っていない。
  npm run check:fit はログインした姿でも測る。
*/
.top__admin {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  gap: var(--sp-2);
  min-height: var(--tap);
  color: var(--accent);
  font-size: var(--fs-sm);
  transition: scale var(--dur-spring) var(--ease-spring);
}

/* --- 足元 --- */

/*
  足元。誰のサイトか（名前・職種・一言）と連絡先、その下に著作権表示と全体ページへの
  1本。上に1本の罫線を引き、面は持たない。どの幅でも畳まない（上の帯には
  ロゴと目次しか置かないので、名乗りと連絡先はここが受ける）。
*/
.foot {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  padding-block: var(--sp-7) calc(var(--sp-7) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
}

.identity {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-5) var(--sp-6);
}

.identity__who {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}

/* 名前が主、職種が添え、一言がその下。字の段も色も1段ずつ下げる */
.identity__name {
  color: var(--ink);
  font-size: var(--fs-lg);
  font-weight: 600;
  line-height: 1.4;
}

.identity__role {
  color: var(--ink-mid);
  font-size: var(--fs-sm);
}

.identity__tagline {
  color: var(--ink-weak);
  font-size: var(--fs-meta);
}

/*
  GitHub とメール（components.tsx の Socials）。足元と、Contact のページの本文で
  同じ部品。札の面は持たず、字と印だけの行き先にする（的の高さは --tap）。
*/
.socials {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--sp-5);
}

.socials a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--tap);
  color: var(--ink-mid);
  font-size: var(--fs-sm);
  transition:
    color var(--dur-fast) var(--ease),
    scale var(--dur-spring) var(--ease-spring);
}

/*
  著作権表示と「全体を1ページで見る →」。和文が入るので等幅にせず、和文の最小の
  段（--fs-meta）。
*/
.foot__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
  color: var(--ink-weak);
  font-size: var(--fs-meta);
}

/*
  「全体を1ページで見る →」。素の a は color: inherit / text-decoration: none
  なので、置くだけでは著作権表示と同じ姿になり、押せるものだと分からない
  （WCAG 1.4.1）。下線を引き、色も1段上げる（--ink-weak → --ink-mid）。
  文の中のリンクのままにしてある（WCAG 2.5.8 の inline の例外）。
*/
.foot__meta a {
  color: var(--ink-mid);
  text-decoration: underline;
  text-underline-offset: var(--sp-1);
}

/* --- 顔 --- */

.avatar {
  position: relative;
  width: var(--avatar-size);
  height: var(--avatar-size);
  border-radius: 50%;
  overflow: hidden;
  flex-shrink: 0;
  box-shadow: inset 0 0 0 1px var(--line);
}

.avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.avatar__fallback {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: calc(var(--avatar-size) * 0.38);
  font-weight: 600;
}

/* ============================================================ 本文 */

/*
  入口と個人ページの頭（components.tsx の Hero。<header class="hero">）と、全体ページの頭。
*/
.hero {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

/* --- 入口 --- */

/*
  入口（.hero--orbit）。左に大見出しの列（.hero__copy）、右に作品の軌道図
  （.system）、底に件数の帯（.tally）。900 未満は1列に積み、軌道図を上に置く。

  見出しの列が DOM の先頭なのは、読み上げと Tab の順を見出しから始めるため。
  並べ替えは grid-template-areas だけで、字の並びは変えない。

  表紙の高さ（--cover-h。「ページの外枠」）の余りは 1fr の行が受け、件数の帯は底に
  座る。1列のときは見出しの列を軌道図のすぐ下に置き（余りは件数の帯の上に残る）、
  2列のときは見出しの列と軌道図を残りの高さの縦の真ん中に置く。
*/
.hero--orbit {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr auto;
  grid-template-areas: 'map' 'copy' 'tally';
  gap: var(--sp-7);
}

.hero__copy {
  grid-area: copy;
  align-self: start;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--sp-5);
  min-width: 0;
}

/*
  見出しの上の小さな札（components.tsx の Eyebrow。入口の「職種 — 所在地」）。
  英字だけなら等幅の小さな大文字（--fs-label を使ってよい「和文が入らない英大文字の
  小見出し」）、和文を含めば本文の書体で和文の最小の段。
*/
.eyebrow {
  color: var(--ink-weak);
  font-size: var(--fs-meta);
}

.eyebrow :lang(en) {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/*
  部分のあいだの区切り。飾りなので読み上げには流さない（/ の後ろの代わりの字が空）。
  代わりの字は書けるブラウザにだけ渡す（@supports）——書けないブラウザは宣言ごと
  捨て、区切りが消えて「SYSTEM ENGINEER神奈川」と続く
*/
.eyebrow > * + *::before {
  content: ' — ';
}

@supports (content: '' / '') {
  .eyebrow > * + *::before {
    content: ' — ' / '';
  }
}

/*
  大見出し（入口の一文、全体ページの頭の名前、個人ページの大見出し）。

  塊（、。まで）を1行ずつに積む。1行に流していたころは、幅しだいで
  「置いてお / く。」と語の途中で折れていた（名前は句読点を持たないので1つの塊のまま）。

  palt は和文を詰める字形。仮名と約物（、。）の空きが半分になり、見出しの
  大きさでもスカスカに見えない。本文には掛けない（小さい字で詰めると読みにくい）。
  大きさははしごの最上段（--fs-display-xl）。個人ページの名乗りは下の
  .hero__headline で段を下げる。
*/
.hero h1 {
  font-family: var(--font-display);
  font-size: var(--fs-display-xl);
  font-weight: 600;
  font-feature-settings: 'palt';
  line-height: 1.25;
  letter-spacing: -0.02em;
}

.hero h1 .phrase {
  display: block;
}

/* リード文。入口と全体ページの頭の1文。小さい字なので 4.5:1 が要る（--ink-mid） */
.hero__lead {
  max-width: 32em;
  color: var(--ink-mid);
  font-size: var(--fs-md);
  line-height: 1.9;
  /* 塊より細かい所の折れ（1語だけ次の行へ落ちる）を、対応する環境では避ける */
  text-wrap: pretty;
}

/*
  塗りの押し手（components.tsx の Cta。入口の「一覧で見る →」）。ページで
  いちばん強い1本。角は立て、面は --accent で塗る（既定のモノクロでは白い面に黒い字）。
  600 未満は列いっぱいに伸ばして、字を左・矢印を右に置く（指で押す的を広く）。
*/
.cta {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-6);
  min-height: var(--cta-h);
  margin-top: var(--sp-3);
  padding-inline: var(--sp-6);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: var(--fs-base);
  font-weight: 600;
  transition: scale var(--dur-spring) var(--ease-spring);
}

.cta__arrow {
  transition: translate var(--dur-spring) var(--ease-spring);
}

/* --- 入口の軌道図 --- */

/*
  入口の軌道図（components.tsx の OrbitSystem）。真ん中にブラックホールを置き、
  そのまわりに作品を1つずつ軌道に載せる（形は src/lib/orbits.ts。件数だけから
  決まる）。天体に作品の札は添えない（持ち主が「いらない」と外した）。図は全部飾り。

  絵の層は枠いっぱいに重ねる——軌道の奥の半分・ブラックホール（.hole）・軌道の手前の
  半分・天体の順（DOM の順がそのまま重なりの順）。星雲と星空は枠の外、本文の幅いっぱいに
  敷く（.cosmos）。縦横比は :root の
  --system-ratio（orbits.ts の HERO_FRAME と同じ数）。

  横だけ切る（overflow-x: clip。縦は出してよい——着いたときに灯るブラックホールと、
  渦を巻いて収まる軌道が、枠の上下へ少し出る）。回して霞ませた出だしの軌道が枠の左右へ
  出ても、ページを横に動かさない。

  枠は星屑の太さ（cqi）を自分の幅から取る容れ物（container）。重なりの順は
  枠の中に閉じる（isolation。貼り付いた上の帯より上には来ない）。
*/
.system {
  grid-area: map;
  position: relative;
  width: 100%;
  aspect-ratio: var(--system-ratio);
  overflow-x: clip;
  container-type: inline-size;
  isolation: isolate;
}

.system__bands,
.system__orbits,
.system__stardust,
.system__bodies {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/*
  動かない層と、刻んで動く層は、それぞれ自分の合成の層に描き、描いた層を使い回す。

  - 動かない層（光の帯 .system__bands・軌道の線 .system__orbits、締めはまとめて .orbits__still）。
    帯のぼかし（SVG のフィルタ）を一度だけ描いて使い回す。帯と線は完成形で表示する
  - 刻んで動く層（星屑 .system__stardust / .orbits__stardust、天体 .system__bodies /
    .orbits__bodies）。どれも同じ刻み（orbits.ts の TICK・BODY_TICK）でだけ姿を変える
    （「動き続ける」の steps()）ので、そのあいだは描き直さない

  流れる星と吸い込まれる粒は、小さい HTML の箱だけを動かす。動くものをまとめて毎コマ
  描き直していたころは、144Hz の画面で GPU の仕事が
  1コマの枠を超え、ブラウザごと重くなった
*/
:is(
  .system__bands,
  .system__orbits,
  .orbits__still,
  .system__stardust,
  .orbits__stardust,
  .system__bodies,
  .orbits__bodies
) {
  will-change: transform;
}

/*
  宇宙（components.tsx の Cosmos。入口と締め）。軌道図のまわりを、本文の幅いっぱいの星空に
  する——星と、星雲（ブラックホールのまわりから画面の端まで）と、星雲の2色の淡い広がり
  （背景の放射の坂。図の焦点のあたりに置く）と流れ星。

  字の後ろには何も出さない。小さい字は地の黒で 4.5:1 ぎりぎりの所があり（職種と所在地の札・
  件数の見出し）、星1つが字の行に掛かるだけで割る。消し方は2つ——
  - 字の塊（入口の見出しの列・締めの字）は、自分の後ろに地の色の暗がりを敷く（下の「字の
    暗がり」）。暗がりは塊の箱に付いてくるので、字の長さや画面の幅で塊が動いても外れない。
    図の場所から字の列を見積もった覆いで消していたころは、900 以上で左の列を丸ごと消すぶん、
    星雲が画面の右半分に寄っていた
  - 星空は底で消える（静止した地の色の覆い）。入口は件数の帯の上で、締めは本文の底で
    （足元の罫線で雲を断ち切らない）
  位置の基準は本文（main。「骨格」）。npm run check:contrast が字の下を測る。
*/
.cosmos {
  --cosmos-x: 50%;
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  container-type: size;
}

/*
  底のフェードは、動く星空をまとめて抜く mask の代わりに、逆の濃さの地を重ねる。
  同じ --bg の上では見え方は同じ。親の mask は星や雲が動くたびに画面大の中間面を合成し、
  アクセラレーションを切った Edge で重くなった。覆いは底の帯だけの静止した1枚。
  cosmos の子より上、外の軌道と文字より下（cosmos 自体の z-index は -1）に置く。
*/
.cosmos::after {
  content: '';
  position: absolute;
  inset: 0;
  top: auto;
  height: var(--cosmos-fade-h);
  z-index: 1;
  background: var(--cosmos-fade);
  pointer-events: none;
}

.cosmos__stars {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* HTML の星と流れ星も SVG と同じ1600×1000の視野を中央で slice する。 */
.cosmos__stars--twinkle,
.cosmos__meteors {
  position: absolute;
  inset: auto;
  left: 50%;
  top: 50%;
  width: max(100cqw, 160cqh);
  height: max(62.5cqw, 100cqh);
  transform: translate(-50%, -50%);
}

.cosmos__twinkle {
  position: absolute;
  border-radius: 50%;
  background: var(--ink);
  transform: translate(-50%, -50%);
  will-change: opacity;
}

/*
  星。太さは星ごと（style）で、画面の px のまま。光芒は細い十字で、真ん中から先へ消える
  （坂は部品が style で渡す。同じ明るさの十字は照準に見えた）
*/
.cosmos__stars path {
  fill: none;
  stroke: var(--ink);
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.cosmos__stars .cosmos__glint {
  stroke-width: var(--orbit-line);
  stroke-linecap: butt;
  opacity: 0.5;
}

.cosmos__spark {
  stop-color: var(--ink);
}

/* 流れ星。90視野単位の筋を置いた向きのまま流す。太さは画面pxのまま。 */
.cosmos__meteor-frame {
  position: absolute;
  transform-origin: 0 0;
}

.cosmos__meteor {
  position: absolute;
  right: 100%;
  top: calc(var(--orbit-line) * -0.75);
  width: 100%;
  height: calc(var(--orbit-line) * 1.5);
  background: linear-gradient(to right, transparent, var(--ink));
  opacity: 0;
  will-change: transform, opacity;
}

/* 元の丸い線端と同じ、筋の先の半円。 */
.cosmos__meteor::after {
  content: '';
  position: absolute;
  left: 100%;
  top: 0;
  width: calc(var(--orbit-line) * 1.5);
  height: 100%;
  border-radius: 50%;
  background: var(--ink);
  transform: translateX(-50%);
}

/*
  図の幅（--cosmos-fig-w）と高さ（--cosmos-fig）と、底の消え方。入口の図は本文の幅
  いっぱい（900 以上は右の列）で縦横比が --system-ratio、締めの図は .orbits と同じ幅の
  決め方で --contact-ratio。本文の幅は min(画面 − 余白, 版面)。

  入口は件数の帯の上で消えきる（帯と行の間は約 112px。帯の上の罫線より 20px ほど上）。
  締めは本文の底（足元の罫線）の手前で消えきる
*/
.cosmos--hero {
  --cosmos-fig-w: min(100vw - 2 * var(--gutter), var(--w-page));
  --cosmos-fig: calc(var(--cosmos-fig-w) / (var(--system-ratio)));
  --cosmos-fade-h: calc(var(--page-pad) + 220px);
  --cosmos-fade: linear-gradient(to bottom, transparent 0, var(--bg) 120px);
}

.cosmos--contact {
  --cosmos-fig-w: min(
    min(100vw - 2 * var(--gutter), var(--w-page)),
    var(--contact-w),
    var(--contact-h) *
    (var(--contact-ratio))
  );
  --cosmos-fig: calc(var(--cosmos-fig-w) / (var(--contact-ratio)));
  --cosmos-fade-h: calc(var(--page-pad) * 2);
  --cosmos-fade: linear-gradient(to bottom, transparent, var(--bg));
}

/* 図の焦点の高さと、星雲の色の淡い広がり（星雲の芯のまわり） */
.cosmos--hero,
.cosmos--contact {
  --cosmos-y: calc(var(--page-pad) + var(--cosmos-fig) * var(--cosmos-focus));
  background:
    radial-gradient(
      ellipse calc(var(--cosmos-fig-w) * 0.9) calc(var(--cosmos-fig) * 0.9) at
        calc(var(--cosmos-x) - 8%) var(--cosmos-y),
      color-mix(in srgb, var(--nebula-a) var(--cosmos-wash), transparent),
      transparent
    ),
    radial-gradient(
      ellipse calc(var(--cosmos-fig-w) * 0.7) calc(var(--cosmos-fig) * 0.8) at
        calc(var(--cosmos-x) + 10%) var(--cosmos-y),
      color-mix(in srgb, var(--nebula-b) var(--cosmos-wash), transparent),
      transparent
    );
}

/*
  字の暗がり。入口の見出しの列と、締めの字の塊の後ろに、地の色を敷く。塊の箱は塗りきり、
  上と下へ --veil だけ広げた所で、星空と星雲へ溶かす。900 未満は字の塊が本文の列いっぱい
  なので、横は本文の端から端まで（左右の余白 --gutter ぶん広げる）の帯にする。900 以上は
  塊を中身の幅に縮め、左は --veil、右は --veil-side で溶かす（「900px 以上」）。

  重なりは星空（.cosmos）と同じ z-index: -1 の層で、文書の順が星空より後——星空と星雲の上、
  字の下に描かれる（本文 main が重なりの容れ物。「骨格」）。軌道図（.system・.orbits）は
  ふつうの層なので、暗がりが図に掛かっても、軌道の線と天体は消さない。
*/
.hero--orbit > .hero__copy,
.orbital > .contact {
  position: relative;
}

.hero--orbit > .hero__copy::before,
.orbital > .contact::before {
  content: '';
  position: absolute;
  inset: calc(-1 * var(--veil)) calc(-1 * var(--gutter));
  z-index: -1;
  background: var(--bg);
  mask-image: var(--veil-y);
  pointer-events: none;
}

/*
  星雲（components.tsx の Nebula。星空の中、ブラックホールのまわりから画面の端まで）。
  ブラックホールの位置（--cosmos-x / --cosmos-y）を真ん中に、図の幅（--cosmos-fig-w）に
  比例した大きさで置く——軌道のまわりの芯の雲は図と一緒に伸び縮みし、外の雲が画面の端へ
  届く（900 以上の入口では、箱の左の端が本文の左の端より外に出る）。箱の縦横比は orbits.ts の
  NEBULA と同じ（test/theme.test.ts が突き合わせる）。はみ出したぶんは星空（.cosmos）が切る。
  位置は余白（margin）で引く——translate は漂う動きが使う（「動き続ける」）。

  4層の模様は生成時にだけフィルタを掛けて透過 WebP に焼く（scripts/nebula/render.mjs）。
  色と濃さは :root の --nebula-* が生成入力。配色は [data-accent] が版つきの
  --nebula-art を選ぶ。瞬く星と同じ描画に、大きなノイズのフィルタを持たせない。
*/
.cosmos__nebula {
  --nebula-w: calc(var(--cosmos-fig-w) * 3.53);
  position: absolute;
  left: var(--cosmos-x);
  top: var(--cosmos-y);
  width: var(--nebula-w);
  aspect-ratio: 3600 / 2000;
  margin-left: calc(var(--nebula-w) / -2);
  margin-top: calc(var(--nebula-w) / -3.6);
  background-image: var(--nebula-art);
  background-size: 100% 100%;
}

/*
  ブラックホール（components.tsx の Hole）を焦点に置き、枠と一緒に伸び縮みさせる。置き場所
  （--hole-x / --hole-y）と大きさ（--hole-w）は、部品が orbits.ts の数（枠の focus と hole）
  から組んで style で渡す。軌道面と同じ傾きに回す（--system-tilt）。

  焼いた光の絵（logo.ts の BLACKHOLE_ART。ロゴの O と同じ1枚）と、その下に敷く影の黒い円
  （::before。径は部品が絵の幅に対する割合 --hole-shadow で渡す）。箱の高さは絵の縦横比
  （img の width / height）から決まる。影は不透明なので、奥を回る軌道はその後ろに隠れる。
  光だけが揺らぐ（「動き続ける」）。

  光は白銀、影は --hole-core（ロゴの O の .logo-core と同じ段）。見た目の
  プリセットのアクセント色では塗らない（ロゴの O と同じ1つのものに見せる）。
*/
.hole {
  position: absolute;
  left: var(--hole-x);
  top: var(--hole-y);
  width: var(--hole-w);
  translate: -50% -50%;
  rotate: var(--system-tilt);
  pointer-events: none;
}

.hole::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--hole-shadow);
  aspect-ratio: 1;
  translate: -50% -50%;
  border-radius: 50%;
  background: var(--hole-core);
}

.hole__art {
  position: relative;
  display: block;
  width: 100%;
  height: auto;
  opacity: var(--hole-light);
}

/*
  軌道と天体（入口と締めで同じ）。絵は色を持たず、線はアクセント色、点は字の色。
  線の太さは画面の px（non-scaling-stroke。:root の --orbit-line の話）。

  線の色は、部品が線ごとに渡す坂（components.tsx の OrbitLines。stroke 属性の
  url(#…)）が持つ。坂の両端の色と濃さはここで敷く——奥の端は --orbit-far を掛けて薄く、
  手前へ続けて --orbit-ink まで濃くなる（奥ほど遠い）。.orbit に stroke を書かない（書くと
  坂を上書きする）。外の軌道ほど淡い——部品が軌道ごとに内側の 0 から外側の 1 までの
  位置（--reach）を渡し、濃さは外側で --orbit-outer 倍まで落ちる
*/
.orbit {
  fill: none;
  vector-effect: non-scaling-stroke;
  stroke-width: var(--orbit-line);
  opacity: calc(1 - var(--reach, 0) * (1 - var(--orbit-outer)));
}

/*
  軌道に沿う光の帯（components.tsx の OrbitBands）。色は線と同じアクセント。区間ごとの太さと
  濃さは部品が渡し（手前ほど太く明るい）、帯ぜんぶの濃さは :root の --orbit-band
*/
.orbit-bands {
  fill: none;
  stroke: var(--accent);
  opacity: var(--orbit-band);
}

/*
  軌道に沿って散る星屑（components.tsx の Stardust）。明るさの段ごとの太さ（--stardust-w の
  倍）と濃さ。太さは画面の px（点を軌道の楕円の枠で潰さない）で、枠の幅に比例する（電話の
  小さな枠で粒が帯を埋めない）。色は字の白で、外側 SVG の奥・手前に代表の濃さを掛ける。
  --stardust-far から不透明へ続く坂の1/4・3/4の濃さにして、奥ほど淡い手がかりを保つ。
  SVG 内・外側とも mask を掛けない（ソフトウェア描画では大きい層を描き直す）
*/
.stardust path {
  fill: none;
  stroke: var(--ink);
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.system__stardust--far,
.orbits__stardust--far {
  opacity: calc((1 + 3 * var(--stardust-far)) / 4);
}

.system__stardust--near,
.orbits__stardust--near {
  opacity: calc((3 + var(--stardust-far)) / 4);
}

.stardust__0 {
  stroke-width: var(--stardust-w);
  opacity: calc(var(--stardust-ink) * 0.45);
}

.stardust__1 {
  stroke-width: calc(var(--stardust-w) * 1.5);
  opacity: calc(var(--stardust-ink) * 0.65);
}

.stardust__2 {
  stroke-width: calc(var(--stardust-w) * 2.1);
  opacity: calc(var(--stardust-ink) * 0.85);
}

.stardust__3 {
  stroke-width: calc(var(--stardust-w) * 2.8);
  opacity: var(--stardust-ink);
}

.orbit-depth__far {
  stop-color: var(--accent);
  stop-opacity: calc(var(--orbit-ink) * var(--orbit-far));
}

.orbit-depth__near {
  stop-color: var(--accent);
  stop-opacity: var(--orbit-ink);
}

/*
  天体は光る惑星（components.tsx の Bodies）。中心がいちばん明るく外へ溶ける白い光（字の色の
  放射の坂）と、そのまわりのにじみ（アクセント色の、外へ消える放射の坂）。縁のくっきりした
  白い円は、軌道を回る平らな丸い点に見えた。奥行きの倍率（手前ほど大きい）は部品が scale で
  掛け、光・にじみ・輪がそろって縮む（線の太さを画面の px に保つ non-scaling-stroke は輪の
  線だけ）。業務は輪のある惑星——軌道面と同じ角度から見た輪を、奥の半分は光の後ろ、手前の
  半分は前に描く。区分を色だけで分けない（形で分ける）
*/
.orbit-body path {
  fill: none;
  stroke-linecap: round;
}

.orbit-body__light {
  stop-color: var(--ink);
}

.orbit-body__halo {
  opacity: var(--body-halo);
}

.orbit-body__glow {
  stop-color: var(--accent);
}

.orbit-body__ring {
  stroke: var(--ink);
  stroke-width: var(--orbit-line);
  vector-effect: non-scaling-stroke;
}

/* --- 入口の件数の帯 --- */

/*
  件数の帯（components.tsx の Tally）。表紙の底に、上に1本の罫線を引いて、
  セルを同じ幅で横に並べる（セルの数は区分の数で変わる）。値が上・見出しが下
  （<dl> の dt が先なので、並べ替えは column-reverse。読む順は「見出し → 値」）。
*/
.tally {
  grid-area: tally;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: var(--sp-5);
  border-top: 1px solid var(--line);
}

.tally__cell {
  display: flex;
  flex-direction: column-reverse;
  justify-content: flex-end;
  gap: var(--sp-2);
  padding-top: var(--sp-5);
}

.tally dt {
  color: var(--ink-weak);
  font-size: var(--fs-meta);
}

.tally dt:lang(en) {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* 値。字は細く大きく、数字の幅をそろえる（数え上げで幅が揺れない） */
.tally__num,
.tally__year {
  font-size: var(--fs-display-sm);
  font-weight: 300;
  letter-spacing: -0.02em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

/* --- 締めの軌道図 --- */

/*
  締めの軌道図（components.tsx の ContactOrbits）。入口と同じ星系を、横長の枠に
  描いて列の真ん中に置く。縦横比は :root の --contact-ratio（orbits.ts の
  CONTACT_FRAME）、幅の上限は --contact-w と、高さの上限 --contact-h から決まる幅。
*/
.orbits {
  position: relative;
  align-self: stretch;
  justify-self: center;
  width: min(100%, var(--contact-w), calc(var(--contact-h) * var(--contact-ratio)));
  aspect-ratio: var(--contact-ratio);
  container-type: inline-size;
}

/* 星雲・軌道の奥の半分・ブラックホール・手前の半分の順に重ねる（入口と同じ） */
.orbits > svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* --- 入口に着いたとき --- */

/*
  星系は完成形で初めから出す。大きい層の opacity・blur・scale や軌道の stroke-dashoffset を
  動かす登場は、アクセラレーションを切った Edge で毎コマの描き直しになった。
  文字・名札・件数だけが opacity と translate で浮かぶ（ぼかしを掛けない）。件数は600ms待って
  数え上がる。どれも一度きりで5秒以内に止まる。続く動きは下の「動き続ける」。
  終わりの姿（to）は書かず、動きを減らす設定で animation: none になった姿も完成形にする。
*/
@keyframes rise-in {
  from {
    opacity: 0;
    translate: 0 var(--enter-shift);
  }
}

.hero h1 .phrase,
.hero__copy > :not(h1),
.hero > .nameplate,
.tally__cell {
  animation: rise-in var(--dur-spring) var(--ease-spring) backwards;
}

/* 札 → 見出しの塊 → リード文 → 押し手。件数の帯は字を先に見せてから */
.hero h1 .phrase {
  animation-delay: calc((var(--i, 0) + 1) * var(--stagger));
}

.hero__copy > .hero__lead {
  animation-delay: calc(3 * var(--stagger));
}

.hero__copy > .cta {
  animation-delay: calc(4 * var(--stagger));
}

.tally__cell {
  animation-delay: calc(var(--count-delay) + var(--i, 0) * var(--stagger));
}

/* --- 動き続ける --- */

/*
  着いたあとも動き続けるもの（components.tsx の Flows・Stardust・Bodies・Dust・Hole）。入口と
  締めで同じ。

  - 星屑と天体が軌道ごと公転する（持ち主の「軌道の線を星と一緒に動かして」）。軌道の楕円の
    枠の中で、星屑と天体の置き場所を1周回す（orbit-swirl を linear で。周期は軌道ごとの
    --dur で、内側ほど速い）。天体の形はその中で回転を打ち消し（orbit-unswirl）、大きさは
    手前へ回るほど大きい（orbit-sway。0 から 1 の keyframes を、軌道ごとの大きさの揺れを
    書いた linear() で送る——量は keyframes ではなく timing が持つ）
  - 軌道に沿って星が流れる。輝く星が、星屑と天体より速く回る。65点の投影位置と尾の姿勢を
    SSR で固定の CSS keyframes へ焼き、区間ごとの steps() を60Hzの格子にそろえる。
    周期は軌道ごとの --dur。芯と光芒の小さい SVG は向きを保ち、尾の箱だけを matrix で回す
  - 光の粒が渦を巻いてブラックホールへ吸い込まれ、黒い円の縁で光に溶ける（orbit-grain-fall で
    投影した螺旋の道を進む）。落ちるほど速い10%ごとの曲線を65点へ写し、明滅の節点と一緒に
    SSR で固定の CSS keyframes へ焼く。位置と opacity の両方を区間ごとの steps() で60Hzへそろえる
  - ブラックホールの光が明るさを沈めては戻す（orbit-breathe。光の絵の不透明度だけを変え、
    下に敷いた影の黒い円は動かさない——絵の明るさを filter で落とすと、白い光が灰色に
    くすんだ）
  - 星雲がゆっくり漂い（orbit-drift。透過 WebP を敷いた箱ごと動かし、生成時のフィルタを
    ブラウザで掛け直さない）、星が瞬き（orbit-twinkle。小さい HTML の円の明るさだけ。
    もとの明るさは星ごとの style の opacity で、
    keyframes は真ん中の暗さだけを持つ）、流れ星が流れる（orbit-meteor）

  keyframes に var() を書かない（光の揺らぎの1つを除く）。var() を持つ keyframes は、
  動いている要素ごとに毎コマ解き直され、粒の多い入口では style の計算だけでコマの予算を
  食った（電話相当で 1 秒あたり 480ms。固定の値にして 75ms）。流れる星の位置と尾の姿勢、粒の
  螺旋と明滅は、SSR が計算した固定の数値だけを keyframes に持つ。星屑と天体は楕円の形と
  置き場所を動かない親の transform 属性が持ち、動く子が1周する。天体の大きさの揺れの量は timing（linear()）が
  持つ（timing は一度だけ読まれ、毎コマ解き直されない）。

  - 止める手は置かない（持ち主の判断。5 秒を超えて続く動きの止め方は、動きを減らす設定
    だけが受ける）。動きを減らす設定では、星屑と天体は回らず、流れる光と粒の層は出さない
    ——止まった姿が完成形
*/
@keyframes orbit-swirl {
  to {
    transform: rotate(1turn);
  }
}

@keyframes orbit-unswirl {
  to {
    transform: rotate(-1turn);
  }
}

/*
  天体の大きさの揺れ。0 から 1 へ送るだけで、どの向きでどれだけの大きさかは、部品が軌道
  ごとに渡す linear()（--sway。orbits.ts の OrbitPath の sway）が決める
*/
@keyframes orbit-sway {
  from {
    scale: 0;
  }

  to {
    scale: 1;
  }
}

@keyframes orbit-drift {
  to {
    translate: 0.7% -0.45%;
  }
}

@keyframes orbit-twinkle {
  50% {
    opacity: 0.15;
  }
}

/*
  流れ星。置いた向きのまま、周期の頭の 6% で420視野単位だけ流れて消える。
  自分の長さ90の 420/90 倍へ動かすので、slice の倍率が変わっても距離を保つ。
*/
@keyframes orbit-meteor {
  from {
    transform: translate(0px, 0px);
    opacity: 0;
  }

  1% {
    opacity: 1;
  }

  6%,
  to {
    transform: translate(466.666667%, 0px);
    opacity: 0;
  }
}

/* 光の揺らぎは1つの要素だけなので、明るさの下限を :root の段から読む（光の濃さに掛ける） */
@keyframes orbit-breathe {
  from {
    opacity: calc(var(--hole-light) * var(--breathe-low));
  }
}

/*
  軌道を流れる星（components.tsx の Flows）。輝く星——中心から外へ溶ける芯の光と、先へ消える
  十字の光芒（入口の星空のいちばん明るい星と同じ形）——が、後ろに淡い尾を引いて軌道を回る。
  尾は星の側が濃く、後ろへ消える坂（--flow-trail）。軌道の線の破線を送っていたころは、軌道の
  上を移る線に見えた（持ち主の「移動する線をもっと星っぽく」）
*/
.orbit-flows {
  position: absolute;
  inset: 0;
  overflow: clip;
  container-type: inline-size;
  pointer-events: none;
}

.orbit-flow,
.orbit-flow__tail {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  transform-origin: 0 0;
  will-change: transform;
}

.orbit-flow__star,
.orbit-flow__tail-art {
  position: absolute;
  overflow: visible;
}

.orbit-flow__light {
  stop-color: var(--ink);
}

.orbit-flow__fade {
  stop-color: var(--ink);
  stop-opacity: var(--flow-trail);
}

.orbit-flow__glint {
  fill: none;
  stroke-width: var(--orbit-line);
  vector-effect: non-scaling-stroke;
  opacity: var(--flow-glint);
}

.orbit-dust {
  position: absolute;
  inset: 0;
  overflow: clip;
  container-type: inline-size;
  pointer-events: none;
}

.orbit-grain__dot {
  position: absolute;
  left: 0;
  top: 0;
  border-radius: 50%;
  opacity: 0;
  will-change: transform, opacity;
}

:is(.orbit-flows, .orbit-dust, .cosmos__meteors) {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  /* 初期化 helper が小さい装飾群を順に始める。印が無いJS無効時は通常のCSS動作。 */
  html[data-motion-staged]
  :is(
    .orbit-flow,
    .orbit-flow__tail,
    .cosmos__twinkle,
    .cosmos__meteor,
    .orbit-grain__dot,
    .orbit-spin,
    .orbit-unspin,
    .orbit-body,
    .hole__art,
    .cosmos__nebula
  ):not([data-motion-ready]) {
    /* biome-ignore lint/complexity/noImportantStyles: 下の通常animationを開始済みの印が付くまで止める */
    animation: none !important;
  }

  /* 未開始の星は枠の原点に出さない。準備後は元の可視状態へ戻る。 */
  html[data-motion-staged] .orbit-flow:not([data-motion-ready]) {
    visibility: hidden;
  }

  :is(.orbit-flows, .orbit-dust) {
    display: inline;
  }

  .cosmos__meteors {
    display: block;
  }

  .cosmos__meteor {
    animation: orbit-meteor var(--dur) linear var(--delay) infinite;
  }

  /* SSR が投影した65点の固定 CSS を小さい箱だけに掛ける。区間の steps() は60Hzの格子 */
  .orbit-flow,
  .orbit-flow__tail {
    animation: var(--motion) var(--dur) linear infinite;
  }

  /*
    星屑と天体の公転。天体は置き場所ごと回り（orbit-spin）、その中で回転を打ち消す
    （orbit-unspin）。どちらも読み込んだ時から同じ秒数で回るので、打ち消しはずれない
  */
  .orbit-spin {
    animation: orbit-swirl var(--dur) linear infinite;
  }

  .orbit-unspin {
    animation: orbit-unswirl var(--dur) linear infinite;
  }

  /*
    星屑は1周を --ticks 回に刻んで進める（1秒に10回。ゆっくりした公転）。進まない
    あいだは星屑の層を描き直さない（「入口の軌道図」の .system__stardust）
  */
  .stardust .orbit-spin {
    animation-timing-function: steps(var(--ticks));
  }

  /*
    天体も1周を --ticks 回に刻んで進める（BODY_TICK ごと。回転と打ち消しは同じ刻みなので、
    どの瞬間も打ち消し合う）。大きさの揺れは段の linear()（部品の stairs）
  */
  .orbit-bodies :is(.orbit-spin, .orbit-unspin) {
    animation-timing-function: steps(var(--ticks));
  }

  /*
    天体の大きさの揺れ。出だしの向きのぶんだけ先から始める（--delay）。linear() を知らない
    ブラウザでは、この宣言ごと読めない（var() で入れた値が読めない宣言は、無いのと同じ）——
    大きさは止まった姿のまま回る
  */
  .orbit-body {
    animation: orbit-sway var(--dur) var(--sway) var(--delay) infinite;
  }

  .orbit-grain__dot {
    animation: var(--motion) var(--dur) linear var(--delay) infinite;
  }

  .hole > .hole__art {
    animation: orbit-breathe var(--breathe-dur) steps(var(--breathe-ticks)) infinite alternate;
  }

  /* 星雲は完成形の背景の箱ごと漂う。入口も締めも同じ動き。 */
  .cosmos__nebula {
    animation: orbit-drift var(--drift-dur) steps(var(--drift-ticks)) infinite alternate;
  }

  /*
    星の瞬きも刻んで変える（明暗の片道を --ticks 回に。orbits.ts の cosmosMap が周期と遅れを
    TICK の格子に乗せる）。静止星とは分けた小さい HTML の円の opacity だけを変える
  */
  .cosmos__twinkle {
    animation: orbit-twinkle var(--dur) steps(var(--ticks)) var(--delay) infinite;
  }
}

/*
  件数の数え上げ。値の字（07）はページにそのまま置き、数え上げは ::after の
  counter() が上に重なるだけ——止まった姿は字そのもので、読み上げも Ctrl-F も
  字を読む。数えるあいだだけ、字を透明にして ::after を見せる。
  --n は @property で整数として登録する（登録しないと途中の値を持てず、跳ぶ）。
*/
@property --n {
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}

@keyframes orbit-count {
  from {
    --n: 0;
  }
}

@keyframes orbit-count-cover {
  from,
  to {
    color: transparent;
  }
}

@keyframes orbit-count-show {
  from,
  to {
    opacity: 1;
  }
}

.tally__num {
  position: relative;
  --n: var(--to);
  counter-reset: n var(--n);
  animation:
    orbit-count var(--count-dur) var(--ease) var(--count-delay) backwards,
    orbit-count-cover var(--count-dur) linear var(--count-delay) backwards;
}

/*
  数え上げの字。代わりの字を空にして読み上げに流さない——流すと、dd の字と合わせて
  「07 07」と2度読まれる（opacity: 0 は読み上げの木から外さない）。代わりの字は
  書けるブラウザにだけ渡す（区切りの字の .eyebrow と同じ）
*/
.tally__num::after {
  content: counter(n, decimal-leading-zero);
  position: absolute;
  inset: 0;
  color: var(--ink);
  opacity: 0;
  animation: orbit-count-show var(--count-dur) linear var(--count-delay) backwards;
}

@supports (content: '' / '') {
  .tally__num::after {
    content: counter(n, decimal-leading-zero) / '';
  }
}

/* --- 句読点と折り返し --- */

/*
  句読点までの塊（src/ui/components.tsx の Phrases）。塊の中では折らせない。
  塊が行より長いときだけ中で折れる（inline-block は行の幅を超えない）
*/
.phrase {
  display: inline-block;
}

/*
  見出し・一覧の行の説明・段落の折り返し。語の途中で折らない。

  和文は既定だとどの字の間でも折れるので、「プラットフォームを1 / つの」
  「置いてお / く。」のように語の途中で行が変わる。word-break: auto-phrase は
  ブラウザが文節を見て、文節の切れ目でだけ折る指定（lang="ja" の文書で効く）。
  いまは Chromium にしか無く、知らないブラウザではこの1行だけが捨てられて、
  今までどおり字の間で折れる（進歩的な強化。壊れる環境は無い）。
  text-wrap: pretty は段落の最後の行に1〜2字だけ落ちるのを避ける。

  打ち込む中身（見出し・説明・本文）は、Phrases のように句読点で塊に切って
  おけない——段落の長い塊を inline-block にすると、行に入りきらない塊が丸ごと
  次の行へ落ち、行末に大きな空きが残る。Phrases は短い一文（入口の大見出しと
  リード文、締めの誘いの1文）の、どのブラウザでも同じ所で折りたい場所だけに使い、
  ここは文節を知っているブラウザに任せる。入口の大見出しを入れてあるのは、
  塊が1行より長くなった（背の低い窓で字が大きい）ときに、塊の中でも文節で折るため。
*/
:is(
  .hero h1,
  .head :is(h1, h2, h3),
  .entry__main :is(h3, p),
  .statement__text,
  .contact__lead,
  .bio p
) {
  word-break: auto-phrase;
  text-wrap: pretty;
}

/* --- 個人ページの頭 --- */

/*
  個人ページの名乗りは、入口の大見出しより1段下げる（--fs-display）。章の見出し
  （About…。--fs-display-sm）よりは大きく、ページの h1 として立つ。

  要素とクラスの両方で書いてあるのは必須。この見出しは <h1 class="hero__headline">
  なので、素の .hero__headline（0,1,0）では上の .hero h1（0,1,1）に負け、
  大見出しの段を継いでしまう。
*/
.hero h1.hero__headline {
  font-size: var(--fs-display);
}

/* Keep the profile ornament compact before About on narrow screens. Wide screens
   separate the artwork and profile text into two columns. */
.hero--profile {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  gap: var(--sp-5);
}

.profile-cover__copy {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  min-width: 0;
}

.hero--profile > .celestial--art {
  width: min(100%, var(--celestial-profile-w));
  justify-self: center;
}

/* A member's colour belongs to the sky and body, while links and buttons stay white. */
.hero[data-accent] > :is(.hero__copy, .profile-cover__copy),
.orbital[data-accent] > .contact {
  --accent: var(--mono);
}

/* --- Member celestial bodies (cached transparent artwork; CSS motion only) --- */
.celestial {
  --celestial-tone: var(--accent);
  position: relative;
  flex-shrink: 0;
  pointer-events: none;
}

.celestial[data-celestial-accent='mono'] {
  --celestial-tone: var(--mono);
}

.celestial[data-celestial-accent='iris'] {
  --celestial-tone: var(--iris);
}

.celestial[data-celestial-accent='violet'] {
  --celestial-tone: var(--violet);
}

.celestial[data-celestial-accent='ember'] {
  --celestial-tone: var(--ember);
}

.celestial[data-celestial-accent='mint'] {
  --celestial-tone: var(--mint);
}

.celestial[data-celestial-accent='sky'] {
  --celestial-tone: var(--sky);
}

.celestial[data-celestial-accent='rose'] {
  --celestial-tone: var(--rose);
}

.celestial--art {
  width: 100%;
  aspect-ratio: 360 / 300;
  isolation: isolate;
}

.celestial--art svg {
  display: block;
  width: 100%;
  height: 100%;
}

.celestial__guide {
  position: absolute;
  inset: 0;
  fill: var(--ink-weak);
  stroke: var(--line-strong);
  stroke-width: 0.7;
}

.celestial__guide ellipse {
  fill: none;
}

.celestial__halo {
  position: absolute;
  inset: 8%;
  border-radius: 50%;
  background: radial-gradient(
    ellipse,
    color-mix(in srgb, var(--celestial-tone) 12%, transparent),
    transparent 68%
  );
}

/* Each image reserves the same stage before decoding; its transparent padding
   preserves soft light without a rectangle over the underlying sky. */
.celestial__image {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.celestial__black-hole {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
}

.celestial__black-hole::before {
  content: '';
  position: absolute;
  width: var(--celestial-hole-shadow);
  aspect-ratio: 1;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: var(--hole-core);
}

.celestial__black-hole img {
  position: relative;
  width: 100%;
  height: auto;
}

/* The disk and its luminance mask stay still. A narrow light travels through
   the existing bright texture; black pixels and the silhouette never move. */
.blackhole-flow {
  display: none;
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  translate: 0 -50%;
  mask-image: var(--celestial-flow-mask);
  pointer-events: none;
}

.blackhole-flow__texture {
  position: absolute;
  inset: 0;
  mask-image: var(--blackhole-flow-art);
  mask-mode: luminance;
  mask-size: 100% 100%;
  mask-repeat: no-repeat;
  overflow: hidden;
}

.blackhole-flow__beam {
  position: absolute;
  inset-block: 0;
  left: 0;
  display: block;
  width: var(--celestial-flow-beam-w);
  background: var(--celestial-flow-beam);
  opacity: 0;
}

/* The sun's opaque surface stays fixed; only a masked rim can expand. */
.celestial__corona {
  display: none;
  position: absolute;
  inset: 0;
  mask-image: var(--celestial-corona-mask);
  opacity: var(--celestial-corona-light);
}

.celestial__corona-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Fixed keyframe values avoid style recalculation of per-frame custom properties.
   Percent travel scales down naturally with the phone and orbital-center stages. */
@keyframes celestial-float {
  0%,
  50%,
  100% {
    transform: translate(0, 0);
  }
  12.5%,
  37.5% {
    transform: translate(0.24%, -1.26%);
  }
  25% {
    transform: translate(0.36%, -1.8%);
  }
  62.5%,
  87.5% {
    transform: translate(-0.24%, 1.26%);
  }
  75% {
    transform: translate(-0.36%, 1.8%);
  }
}

@keyframes celestial-drift {
  0%,
  50%,
  100% {
    transform: translate(0, 0);
  }
  12.5%,
  37.5% {
    transform: translate(-0.38%, -1.54%);
  }
  25% {
    transform: translate(-0.55%, -2.2%);
  }
  62.5%,
  87.5% {
    transform: translate(0.38%, 1.54%);
  }
  75% {
    transform: translate(0.55%, 2.2%);
  }
}

@keyframes celestial-rock {
  0%,
  50%,
  100% {
    transform: translate(0, 0) rotate(0deg);
  }
  12.5%,
  37.5% {
    transform: translate(0.22%, -1.12%) rotate(-0.53deg);
  }
  25% {
    transform: translate(0.32%, -1.6%) rotate(-0.75deg);
  }
  62.5%,
  87.5% {
    transform: translate(-0.22%, 1.12%) rotate(0.53deg);
  }
  75% {
    transform: translate(-0.32%, 1.6%) rotate(0.75deg);
  }
}

@keyframes celestial-radiance {
  from {
    transform: scale(1);
    opacity: 0.5;
  }
  to {
    transform: scale(1.065);
    opacity: 1;
  }
}

@keyframes celestial-breathe {
  from {
    opacity: 0.82;
  }
}

@keyframes celestial-flow {
  0% {
    transform: translateX(-100%);
    opacity: 0;
  }
  25% {
    transform: translateX(12.5%);
    opacity: 0.2;
  }
  50% {
    transform: translateX(125%);
    opacity: 0.28;
  }
  75% {
    transform: translateX(237.5%);
    opacity: 0.2;
  }
  100% {
    transform: translateX(350%);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .celestial[data-celestial-body='moon'] > .celestial__image {
    animation: celestial-float var(--celestial-moon-dur) steps(var(--celestial-moon-ticks)) infinite;
  }
  .celestial[data-celestial-body='neptune'] > .celestial__image {
    animation: celestial-drift var(--celestial-neptune-dur) steps(var(--celestial-neptune-ticks))
      infinite;
  }
  .celestial[data-celestial-body='saturn'] > .celestial__image {
    animation: celestial-rock var(--celestial-saturn-dur) steps(var(--celestial-saturn-ticks))
      infinite;
  }
  .celestial__black-hole > img {
    animation: celestial-breathe var(--celestial-breathe-dur) steps(var(--celestial-breathe-ticks))
      infinite alternate;
  }
  .blackhole-flow,
  .celestial__corona {
    display: block;
  }
  .blackhole-flow__beam {
    animation: celestial-flow var(--celestial-flow-dur) steps(var(--celestial-flow-ticks)) infinite;
  }
  .blackhole-flow__beam[data-flow-layer='1'] {
    animation-delay: var(--celestial-flow-offset);
  }
  .celestial__corona-image,
  .celestial[data-celestial-body='sun'] > .celestial__halo {
    animation: celestial-radiance var(--celestial-radiance-dur)
      steps(var(--celestial-radiance-ticks)) infinite alternate;
  }
}

/* Unsupported masks keep the original artwork; never show a drifting duplicate. */
@supports not (mask-image: linear-gradient(#000, #000)) {
  .blackhole-flow,
  .celestial__corona {
    display: none;
  }
}

/* Alpha-only masks would light the opaque black center. Keep the original
   breathing artwork on engines without luminance masking. */
@supports not (mask-mode: luminance) {
  .blackhole-flow {
    display: none;
  }
}

.orbit-center {
  position: absolute;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.orbit-center .celestial__guide {
  display: none;
}

.celestial--symbol {
  display: inline-flex;
  width: var(--celestial-symbol);
  height: var(--celestial-symbol);
  color: var(--celestial-tone);
  vertical-align: middle;
}

.celestial--symbol svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.2;
}

.celestial--symbol[data-celestial-body='black-hole'] svg {
  stroke: none;
}

/*
  個人ページの頭の名札（components.tsx の Nameplate）。Team のカードと同じ
  並び——顔・名前・肩書きと所在地——で、カードを押した先で同じ顔に着く。
  足元はサイトのものなので、その人の顔と名前はここにしか出ない。

  名前は h1 になることがある（大見出しを書いていない人）。上の .hero h1
  （0,1,1）の大見出しの書き方を継がないよう、2つのクラス（0,2,0）で書き、
  書体・大きさ・字詰めを自分で持つ。
*/
.nameplate {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.nameplate__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
  text-align: start;
}

.nameplate__heading {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
}

.nameplate .nameplate__name {
  font-family: inherit;
  font-size: var(--fs-lg);
  font-weight: 600;
  font-feature-settings: normal;
  line-height: 1.4;
  letter-spacing: normal;
}

.nameplate__meta {
  color: var(--ink-mid);
  font-size: var(--fs-sm);
}

/*
  見出しと添え（件数・業界と年など）は1組。空いた幅ぶん引き離さない。
  下に1本の罫線を引いて、見出しと本文を分ける（箱には入れない）。

  space-between だと 1440 で2つが 782px 離れ、同じ組に見えなくなる。
  見出しは本文の列と同じ左の軸に立てる。

  折り返したとき（狭い画面の作品のページ。「金融系基幹システム · 2024 — 現在」は
  390 では見出しの隣に入らない）の行の間は --sp-2。横の間隔と同じ 16px を
  空けると、添えが見出しから離れて次の段落の頭に見える。
*/
.head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-5);
  flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  padding-bottom: var(--sp-5);
  margin-bottom: var(--sp-5);
}

/*
  節の見出し。ページごとの URL（1ページ = 1ドキュメント）では h1、縦に積んだ
  全体ページ（/all）では h2。要素が変わっても字面は同じでなければならない
  ——見出しの階層は読み上げと検索のためのもので、大きさの段ではない。

  大きさは画面に連動するはしごの最上段（--fs-display-xl。入口の大見出しと同じ）。
  ページの名前（Projects・Contact…）を大きな字で1つだけ置き、その下の行の題
  （--fs-display-xs）と2段以上離す。太さは入口の大見出しより1段軽く。
*/
.head :is(h1, h2) {
  font-family: var(--font-display);
  font-size: var(--fs-display-xl);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

/* 添え（作品の「業界 · 年」、区分名）。和文が入るので本文の書体で、和文の最小の段 */
.head .note {
  color: var(--ink-weak);
  font-size: var(--fs-meta);
}

/*
  作品のページの見出しの左のアイコン（components.tsx の SectionHead の icon）。アイコンの
  隣に見出しと添えの塊（.head__text）を置き、塊の中で見出しと添えを素の .head と同じく
  並べる。塊は縮めて（min-width: 0）中で題を折り返す——同じ列に並べていたころは、1行に
  入らない題が次の行へ落ち、アイコンだけが1行に残った。アイコンは塊の縦の真ん中
*/
.head--icon {
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--sp-4);
}

.head__icon {
  flex: none;
  width: var(--head-icon);
  height: var(--head-icon);
}

.head__text {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-5);
  min-width: 0;
}

/*
  見出しのすぐ後ろの件数（Projects の「07」。SectionHead の count）。見出しの字の
  右上に、等幅の小さな数字で添える（英数字の札）。
*/
.head__count {
  align-self: flex-start;
  color: var(--ink-weak);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
}

/*
  ページの中の小節の見出し（components.tsx の SectionHead の sub）。作品のページの
  本文「Story」（h1 の作品名の下の h2）と、全体ページ（/all）の Profile の節の
  About / Skills / Career と作品の本文（h2 の下の h3）。

  節の見出し（--fs-display-xl）よりずっと小さく、静的な段のいちばん上（--fs-lg）にし、
  下の線を引かない。線まで同じだと、作品のページの Story が見出し（作品名）と
  同じ格に見え、Profile の中の About が Projects や Contact と同じ格の節に見えて、
  見出しの階層が字面から読めなくなる。要素の段（h2 / h3）は置かれる場所の
  階層で決まり、見た目は同じ。
*/
.head--sub {
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: var(--sp-4);
}

.head--sub :is(h2, h3) {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 600;
  letter-spacing: 0.01em;
}

/*
  個人ページの章の見出し（components.tsx の SectionHead の chapter。About /
  Skills / Career）。節の見出しと同じ線で、字は頭の大見出し（h1。--fs-display）より
  小さい --fs-display-sm に下げる（同じ大きさだと、章が h1 と同じ格に見える）。
  上を1段空けて章の切れ目にする。
*/
.head--chapter {
  margin-top: var(--sp-7);
}

.head--chapter :is(h1, h2) {
  font-size: var(--fs-display-sm);
}

/* A stacked profile already separates the ornament from About with main's gap.
   Another chapter margin leaves a small body floating in an empty screen. */
.hero--profile + section > .head--chapter {
  margin-top: 0;
}

/*
  全体ページ（/all）の Profile の節（components.tsx の ProfileWhole）。名札・
  大見出し・About・Skills・Career を縦に並べる間隔だけを持つ。中の部品は
  個人ページと同じもので、見た目はそちらの規則がそのまま効く。
*/
.profile {
  display: flex;
  flex-direction: column;
  gap: var(--sp-7);
}

/*
  全体ページ（/all）の Projects の節の、一覧の行の下に並べる作品の本文
  （components.tsx の ItemStories）。1作品ずつが Profile の小節と同じ組
  （h3 の見出しと段落）なので、間隔も .profile と同じ段。一覧の行とのあいだも
  同じだけ空ける。ページごとの URL では作品のページの小節（下の .story）。
*/
.stories {
  display: flex;
  flex-direction: column;
  gap: var(--sp-7);
  margin-top: var(--sp-7);
}

/*
  作品のページの本文の小節「Story」（components.tsx の ItemStory）。説明・画像・
  行き先の組（.detail）から1段離して、ここから本文だと分かるようにする。間隔は
  全体ページの本文の列（.stories）と同じ段。
*/
.story {
  margin-top: var(--sp-7);
}

/*
  Story の中身（components.tsx の StoryParts）。テンプレートより前に書いた本文（段落）と、
  テンプレートの欄（小見出し .side-head と段落の組）を縦に並べる。組と組のあいだは
  技術の塊（.skills）と同じ段。小見出しと段落のあいだは .side-head の下の余白
*/
.story-parts {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
}

/*
  作品の画像のギャラリー（components.tsx の ItemShots）。先頭の1枚が作品の顔、続く画像は
  各画面の説明と一緒に読む。横へ送る箱を持たず、狭い画面では全幅の1列。
  900 以上でだけ、大小の列と少しずらした上端を使って、同じ大きさの画像が並ぶ単調さを避ける。
  枠は読み込む前から決まり、絵は常に切らずに収める。画像のリンクは原寸を別タブで開く。
*/
.shots {
  margin-top: var(--sp-7);
}

.gallery {
  display: grid;
  gap: var(--sp-7);
  min-width: 0;
}

.gallery__item {
  position: relative;
  min-width: 0;
}

.gallery__image {
  display: block;
  cursor: zoom-in;
}

.gallery__frame {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: var(--thumb-ratio);
  max-height: var(--gallery-shot-h);
  background: var(--surface);
}

.gallery__item--lead .gallery__frame {
  max-height: var(--gallery-lead-h);
}

.gallery__image img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* キャプションまで同じリンクの面。キーボードの輪郭もその面を囲む */
.gallery__image::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}

.gallery__image:focus-visible {
  outline: none;
}

.gallery__image:focus-visible::after {
  outline: var(--focus-ring) solid var(--accent);
  outline-offset: var(--focus-offset);
}

.gallery__caption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-3) var(--sp-4);
  border-top: 1px solid var(--line);
  padding-top: var(--sp-4);
  margin-top: var(--sp-4);
  font-size: var(--fs-meta);
  line-height: 1.7;
}

.gallery__number {
  color: var(--ink-weak);
  font-variant-numeric: tabular-nums;
}

.gallery__description {
  flex: 1;
  min-width: 0;
  color: var(--ink-mid);
  overflow-wrap: anywhere;
}

.gallery__open {
  color: var(--ink-weak);
  white-space: nowrap;
}

/* --- 節の中身の2つの箱 --- */

/*
  絞り込みを持つ節の中は「見出しの箱」と「本文の箱」の2つだけ（src/ui/components.tsx の
  ScreenSection）。見出し側には、見出しと Projects の絞り込みが入る。

  見出しの箱は、見出しを左・絞り込みを右に置き、下の罫線は箱が1本だけ引く
  （見出しの .head の線は外す。見出しと絞り込みの下に1本の線）。狭ければ絞り込みは
  見出しの下へ折り返す。min-width は 0 にする（節は grid なので、既定の auto の
  ままだと長い1語が箱を押し広げる）。
*/
.screen-head,
.screen-body {
  min-width: 0;
}

.screen-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-3) var(--sp-6);
  border-bottom: 1px solid var(--line);
  padding-bottom: var(--sp-3);
}

/*
  見出しの箱は絞り込みの的（--tap）より低くならない。低いと、下端でそろえる並びの中で
  見出しだけが押し下げられ、ほかのページの見出しと上端がそろわなくなる（見出しの錨。
  390 と 768 の指の姿で、見出しの字が --tap より低い）
*/
.screen-head > .head {
  min-height: var(--tap);
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: 0;
}

/*
  絞り込みは見出しの隣に入りきらなければ、縮まずに次の行へ落ちる（縮むと、
  入る幅があるのに横に送る帯になる）。1行より長いときだけ、その行の中で横に送る
  （「ページの外枠」の 899 以下の .filters）。
*/
.screen-head > .filters {
  flex-shrink: 0;
  max-width: 100%;
}

/*
  作品1件のページの「← 一覧に戻る」（components.tsx の BackLink）。一覧（1つ上の
  階層）へ上がる手なので見出しの上に置く。行き先は一覧のその作品の行
  （/projects#item-<slug>）。面を持たない字の手で、当たり判定は --tap
  （pointer: coarse では 44px）。

  justify-self は必須。節は grid なので、子になった inline-flex は blockify され、
  既定の stretch で幅いっぱいに伸びる（右の空白まで押せる面になる）。
*/
.back {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  justify-self: start;
  min-height: var(--tap);
  color: var(--ink-mid);
  font-size: var(--fs-sm);
  transition:
    color var(--dur-fast) var(--ease),
    scale var(--dur-spring) var(--ease-spring);
  /* 見出しの上に立つ手。見出しとのあいだを空ける */
  margin-bottom: var(--sp-4);
}

/* --- 絞り込み --- */

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--sp-5);
}

/*
  絞り込みは字の手（タブ）。ボタンではなくリンクで、押した先は絞り込んだ一覧の
  URL になる（絞り込みはサーバーが持ち、JavaScript に依存しない）。
  リンクは inline なので、当たり判定の高さ（--tap）を効かせるために inline-flex にする。
*/
.filters a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  color: var(--ink-weak);
  font-size: var(--fs-sm);
  transition:
    color var(--dur-fast) var(--ease),
    scale var(--dur-spring) var(--ease-spring);
}

/*
  いま効いている絞り込み。字を --ink に上げ、下線を引く（色だけで分けない。
  WCAG 1.4.1）。もう一度押すと、その軸だけ外れる
*/
.filters a[aria-current] {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: var(--orbit-line);
  text-underline-offset: var(--sp-3);
}

.filter-empty {
  color: var(--ink-weak);
  font-size: var(--fs-sm);
  padding: var(--sp-6) 0;
  text-align: center;
}

/* --- 作品1件のページ --- */

/*
  見出しの下（components.tsx の ItemDetail）。画像（.shot）と文の列
  （.detail__text: 説明・本文・実績値・タグ・行き先）の2つ。

  900 未満は縦に積む（画像が見出しのすぐ下）。900 以上で画像がある作品は、
  文の列を左・画像を右に並べる（下の「900px 以上」の .detail--shot）。
  横に並べると、画像の高さが文の列の高さに重なって消える——縦に積んだときの
  「画像ぶんの高さ」が丸ごと本文に回る。

  間隔は gap で決める。節は grid で行の間を空けないので、子を節の直下に
  並べていたころは、説明と実績値の箱がくっついていた。文の列の中（段落の
  列・実績値・タグ・行き先）は一覧の行と同じ --sp-4——行を開いたものなので、
  同じ詰め方で並ぶ。画像と文の列のあいだは1段広い --sp-5。

  flex ではなく grid。高さの足りない箱に入った flex の子は中身より小さく
  潰れ、段落が重なる。grid の auto の行は潰れない。
*/
.detail,
.detail__text {
  display: grid;
  gap: var(--sp-5);
  min-width: 0;
  align-content: start;
}

.detail__text {
  gap: var(--sp-4);
}

/*
  作品の画像（components.tsx の Shot）。枠の高さは --shot-h で決め、絵は
  その中に object-fit: contain で縮めて収める。切らない——スクリーンショットの
  端に大事なものが写っていることは多い。

  絵は枠に絶対配置で貼る。流れの中に置くと、絵の寸法（大きなスクリーン
  ショットなら 2000px）が枠の最小の高さとして効き、横に並べたときに行ごと
  押し広げる。貼っておけば、枠の高さを決めるのはいつも CSS だけになる。

  枠を絵に合わせない理由は :root の --shot-h を見ること。縦長の絵（電話の
  スクリーンショット）は左右に、横長の絵は上下に地が残る。その地を面
  （--surface）で塗って、枠が枠として見えるようにする。
*/
.shot {
  position: relative;
  height: var(--shot-h);
  background: var(--surface);
  overflow: clip;
}

.shot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* --- 一覧の行 --- */

/*
  一覧（Projects）は番号付きの行を縦に並べた索引（components.tsx の ItemRow）。
  行と行のあいだに1本の罫線を引き、箱（枠・面・影）には入れない。

  行の中は grid の区画（番号・本文・サムネイル・札・技術・矢印）で組む。
    600 未満   番号と矢印の下に、本文 → サムネイル → 札 → 技術を縦に積む
    600 以上   左に番号の列を立て、本文・サムネイル・札・技術をその右に積む
    900 以上   番号・本文・札・技術・矢印を横に並べ、サムネイルは本文の下
               （下の「900px 以上」）
  区画の幅は :root の --entry-*。本文の列が残りを受ける。
*/
.entries {
  display: flex;
  flex-direction: column;
}

.entry {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    'index go'
    'main main'
    'thumb thumb'
    'meta meta'
    'tags tags';
  gap: var(--sp-4) var(--sp-6);
  align-items: start;
  padding-block: var(--sp-6);
  border-bottom: 1px solid var(--line);
}

/* 番号（01・02 …）。公開中の全件の並びでの番号（絞り込んでも同じ）で、飾りなので読み上げには流さない */
.entry__index {
  grid-area: index;
  color: var(--ink-weak);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  line-height: var(--entry-title-lh);
}

.entry__main {
  grid-area: main;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-width: 0;
}

/* 行の題。節の見出し（--fs-display-xl）から2段以上離した --fs-display-xs */
.entry__main h3 {
  font-family: var(--font-display);
  font-size: var(--fs-display-xs);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

/*
  題の左のアイコン（components.tsx の ItemRow）。題の1行目の高さの真ん中に置き、はみ出す
  ぶんは上下の負の余白で受ける——行の高さを変えない（番号と矢印は題の1行目の真ん中に
  そろえてある。--entry-title-lh）。題が2行に折れても、アイコンは1行目の横のまま
*/
.entry__title--icon {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
}

.entry__icon {
  flex: none;
  width: var(--entry-icon);
  height: var(--entry-icon);
  margin-block: calc((var(--entry-title-lh) - var(--entry-icon)) / 2);
}

/*
  行の説明。本文の段（--fs-base = 14px）。

  行数では切らない（上限 100 字の2文が、どの幅でも全部出る）。以前は1画面に
  収めるために行数で止めていて、「何であるか。何をしたか。」の2文目がほぼ
  読めない幅があった。1行は約40字（--measure）で止める。
*/
.entry__main p {
  max-width: var(--measure);
  color: var(--ink-mid);
  font-size: var(--fs-base);
  line-height: 1.85;
}

/*
  行ごと押せるようにする（stretched link）。題のリンクの ::after を、位置の基準に
  した行（.entry）いっぱいに被せる。

  行そのものを <a> にはできない——中に外へのリンク（.links a）と担当者名
  （.entry__member）があり、<a> は入れ子にできない。移動はリンクだけで成立させる。
  押せる面だけを広げて、リンクは題の1本のままにする。読み上げで
  リンクの名前が作品名なのも、Tab で止まるのが1回なのも変わらない。

  カーソルは書かない。::after は <a> の一部なので、リンクの指差しのカーソルが
  行の面にそのまま出る。代わりに、行の説明の字はなぞって選べなくなる（面を
  押すとリンクになる）。承知のうえ——同じ説明は押した先の作品のページにもあり、
  そちらはふつうの本文なので選べる。
*/
.entry__link::after {
  content: '';
  position: absolute;
  inset: 0;
}

/*
  題の下線。ふだんから透明の線を引いておき、押せる行にマウスを重ねたときに色だけを入れる
  （手触りの括り）——線が一度に現れず、溶けるように出て、溶けるように消える。線の有り無しを
  切り替えると、色は送れない（前はそうしていて、下線がその場に現れた）
*/
.entry__link {
  text-decoration-line: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: var(--orbit-line);
  text-underline-offset: var(--sp-2);
  transition: text-decoration-color var(--dur) var(--ease);
}

/*
  フォーカスの輪郭は行全体に描く。リンクそのもの（題の字）に描くと、押せる面は
  行全体なのに、輪郭だけが題の字幅に縮んで見える。太さは素の :focus-visible と
  同じ段（--focus-ring）。

  輪郭は行の縁の**内側**に描く（離し方は太さぶんの負）。外へ 3px 離すと、上の
  行の罫線と重なって、どちらの行を囲んでいるのか読み分けにくい。
*/
.entry__link:focus-visible {
  outline: none;
}

.entry__link:focus-visible::after {
  outline: var(--focus-ring) solid var(--accent);
  outline-offset: calc(var(--focus-ring) * -1);
}

/*
  札（プラットフォームか業界・区分・年・担当）。600 未満は1行に並べ、600 以上は
  区画の中で縦に積む。英字だけの札（macOS）は等幅の小さな大文字、和文の札
  （金融系基幹システム・個人開発）は本文の書体で和文の最小の段。年は和文を含む
  書き方（「2024 — 現在」）なので等幅にせず、数字の幅だけそろえる。
*/
.entry__meta {
  grid-area: meta;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-4);
  color: var(--ink-mid);
  font-size: var(--fs-meta);
  line-height: 1.9;
}

.entry__meta li:lang(en) {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.entry__year {
  font-variant-numeric: tabular-nums;
}

/*
  行の中の、題とは別の行き先（担当者名と、.links の外へのリンク）。題のリンクの
  ::after（.entry__link）が行の面を覆っているので、重ねの順でその上に出す。
  出さないと、担当者名を押しても Repository を押しても、覆いの下で作品のページへ行く。
*/
.entry__member {
  position: relative;
  z-index: 1;
  color: var(--ink-mid);
  text-decoration: underline;
  text-underline-offset: var(--sp-1);
}

/*
  サムネイル（画像のある作品だけ）。どの幅でも出す——一覧は1ページに縦に並ぶので、
  画像のぶん行が伸びても次の行は送れば見える。画像は一覧の中で作品を見分ける
  手がかりで、以前（1画面に収めていたころ）は電話で畳んでいた——畳んだ電話では、
  一覧のどこにも作品の絵が無かった。

  縦横比は :root の --thumb-ratio で固定し、絵は切らずに枠へ収める（contain）。
  画面のスクリーンショット（16:10）は枠いっぱいに、ほかの比の絵は面（--surface）の
  余白を残して収まる。切り抜くと、画面の部品や字が枠の縁で途中から切れる。

  画像の無い行は、この区画に何も置かない（components.tsx の ItemRow）。

  min-height: 0 は必須。縦横比を持つ箱は、既定（auto）の最小の高さが「中身の
  高さ」になる。読み込んだ絵が自分の寸法で箱を押し広げ、縦横比が黙って外れる。
*/
.entry__thumb {
  grid-area: thumb;
  display: block;
  width: min(100%, var(--entry-thumb));
  aspect-ratio: var(--thumb-ratio);
  min-height: 0;
  background: var(--surface);
  overflow: clip;
}

.entry__thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* 技術（components.tsx の Tags）。区画に名前を付けて置き場所を決める */
.entry > .tags {
  grid-area: tags;
}

/* 矢印。押せる行（題にリンクのある行）にだけ出る。飾りなので読み上げには流さない */
.entry__go {
  grid-area: go;
  color: var(--ink-weak);
  font-size: var(--fs-lg);
  line-height: var(--entry-title-lh);
  transition:
    color var(--dur-fast) var(--ease),
    translate var(--dur-spring) var(--ease-spring);
}

/*
  タグの並び。区切りの点は札ごとに札の左の外へ吊るし（下の li::before）、並びの左端で
  切る（overflow-x: clip）——折り返した行の頭に来た札の点は並びの外に出て見えない。札の
  あいだ（column-gap）は点1つぶんの幅で、点はその真ん中に座る。
  以前は2つ目からの札の頭に点を置いていて、2行に折れると2行目が点から始まった
*/
.tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  overflow-x: clip;
}

/*
  タグは打ち込んだ札。英字だけのもの（Swift・C#）にだけ lang="en" が付き
  （components.tsx の Tags）、等幅になる。「生成AI」のような和文のタグは
  本文の書体のまま。大きさはどちらも和文の最小の段にそろえる——同じ行の札が
  1枚ごとに違う大きさだと、並びが波打つ。
*/
.tags li {
  color: var(--ink-weak);
  font-size: var(--fs-meta);
}

.tags li:lang(en) {
  font-family: var(--font-mono);
  letter-spacing: 0.04em;
}

.tags li::before {
  content: '·';
  display: inline-block;
  width: var(--sp-4);
  margin-inline-start: calc(var(--sp-4) * -1);
  text-align: center;
}

/*
  行き先の列。折り返した行のあいだは空けない（row-gap 0）。どの札も的の高さ
  （--tap。40px、指では 44px）を持っていて、それがそのまま行の間隔になる。
  --sp-4 を足していたころは、行き先5本が2行に折れた作品で行の間が 52px 開き、
  2行目の札が次の段の頭に見えた。
*/
.links {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--sp-5);
}

/*
  作品の外に出るリンクは --accent。このサイトの仕事は、初めて来た人を
  GitHub なり公開ページなりの実物へ送り出すことで、その操作子が本文と同じ色
  （--ink-mid）だと、行の中でいちばん目立たないものになる。区別が
  @media (hover: hover) の :hover にしか無いと、タッチ端末では最後まで
  気づかれない。

  同じ「外に出る」動作は .linklist__go / .band__go / .member__go も --accent
  なので、これで流儀が1つに揃う。↗ に opacity は掛けない。掛けると実効 3.97:1
  で AA を割るうえ、:root の段に無い生の値を1か所に書くことになる。
*/
.links a {
  /* 題のリンクの覆い（.entry__link::after）より上に出す。.entry__member を見ること */
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--tap);
  color: var(--accent);
  font-size: var(--fs-sm);
  /* 下線は題と同じく、透明の線に色を入れて溶かすように出す（手触りの括り） */
  text-decoration-line: underline;
  text-decoration-color: transparent;
  text-underline-offset: var(--sp-1);
  transition:
    color var(--dur-fast) var(--ease),
    text-decoration-color var(--dur) var(--ease);
}

.links a::after {
  content: '↗';
  font-size: var(--fs-meta);
}

/*
  サイトの中へ行くもの（/ で始まる URL。作品のページの「担当」）は →。
  ↗ は「外へ出る・別タブで開く」の印で、同じタブで開くサイトの中の続きには
  付けない（components.tsx の LinkRow が target も同じ条件で決めている）
*/
.links a[href^='/']::after {
  content: '→';
}

.metric {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

.metric__value {
  color: var(--accent);
  font-size: var(--fs-display-xs);
  font-weight: 600;
  font-family: var(--font-mono);
  line-height: 1.2;
}

.metric__unit {
  color: var(--accent);
  font-size: var(--fs-sm);
}

.metric__note {
  color: var(--ink-weak);
  font-size: var(--fs-meta);
}

/* --- Team --- */

/*
  Team のカード（components.tsx の MemberCardWide / MemberCardCompact）。一覧の行と
  同じく箱に入れず、罫線で区切る（2人は横長の行を縦に、3人以上は格子に）。
*/
.team-list {
  display: flex;
  flex-direction: column;
}

.team-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 var(--sp-5);
}

.member {
  border-bottom: 1px solid var(--line);
  transition:
    background var(--dur-fast) var(--ease),
    scale var(--dur-spring) var(--ease-spring);
}

/* 狭い画面では「プロフィール →」を下の行に落とし、名前と一言に幅を渡す */
.member--wide {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-5);
  padding-block: var(--sp-6);
}

.member--wide .member__go {
  flex: 0 0 100%;
  text-align: right;
}

.member--compact {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--sp-2);
  padding: var(--sp-5) var(--sp-4);
}

.member__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  flex: 1;
  min-width: 0;
}

.member__line {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

.member__line strong {
  font-size: var(--fs-md);
  font-weight: 600;
}

.member__name {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
}

.member--compact strong {
  font-size: var(--fs-sm);
  font-weight: 600;
}

.member__role {
  color: var(--ink-weak);
  font-size: var(--fs-meta);
}

.member__lead {
  color: var(--ink-mid);
  font-size: var(--fs-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.member__go {
  flex-shrink: 0;
  color: var(--accent);
  font-size: var(--fs-meta);
}

/* --- About / Skills / Career --- */

/* 紹介文・技術・経歴は画面が分かれた。1つの箱に2段組みで詰める枠は要らない */

.bio {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

/*
  段落（紹介文・メモ・作品の説明と本文。components.tsx の Note）。

  字は --fs-md（15px）、1行は約40字（:root の --measure）で止める。13px で
  画面の幅いっぱいに流していたころは、1440 の About が1行約65字あり、小さい字の
  長い行を読み進めることになっていた。
*/
.bio p {
  max-width: var(--measure);
  color: var(--ink-mid);
  font-size: var(--fs-md);
  line-height: 1.9;
}

/*
  技術の塊の小見出しと、作品の Story のテンプレートの欄の小見出し（components.tsx の
  SkillGroups と StoryParts）。<h3 class="side-head">（/all では h4）。

  font-weight を素に戻してあるのは、見出し要素に上げたぶんを見た目に
  出さないため（ブラウザの既定は h2 = 太字）。ここで欲しかったのは
  読み上げでの移動と、塊と一覧の結び付きであって、太さではない。

  英字だけの小見出し（LANGUAGES）は lang="en" が付き（SkillGroups の langOf）、
  英大文字の小さな等幅にする——--fs-label（11px）を使ってよいのはこの形だけ。
  「言語」と和文で書いた小見出しは本文の書体で和文の最小の段のまま。
*/
.side-head {
  color: var(--ink-weak);
  font-size: var(--fs-meta);
  font-weight: 400;
  margin-bottom: var(--sp-4);
}

.side-head:lang(en) {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
}

.skills {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  margin-top: var(--sp-3);
}

/*
  塊の中は、経験の添えごとの行（components.tsx の SkillGroups）。添えのある行が
  <dl> の組で、添えの無い行が最後の素の ul。どちらも同じ1本の行に見えるよう、
  行と行の間隔は .skill-rows の gap と、その下の素の ul の margin-top で同じ段に
  そろえる。
*/
.skill-rows {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.skill-rows + .skill-list {
  margin-top: var(--sp-3);
}

/*
  1行 = 添え（dt）+ 項目の列（dd）。添えは縮めず、項目の列だけが折り返す。
  ベースラインでそろえるのは、添えの小さな等幅の字と、1行目の札の字の下端を
  同じ線に乗せるため（中心でそろえると、札の枠の分だけ添えが浮いて見える）。
*/
.skill-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
}

.skill-row dd {
  flex: 1;
  min-width: 0;
}

/*
  添え（「3年以上」）。和文なので本文の書体で、和文の最小の段。項目ごとに
  付けていたころ（.exp）は 11px の等幅で、同じ和文が 11回並んでいた。
*/
.skill-row__note {
  flex-shrink: 0;
  color: var(--ink-weak);
  font-size: var(--fs-meta);
}

.skill-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.skill-list li {
  display: inline-flex;
  align-items: baseline;
  padding: var(--sp-1) var(--sp-3);
  border: 1px solid var(--line);
  font-size: var(--fs-sm);
  color: var(--ink-mid);
}

.career li {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding-block: var(--sp-4);
}

/*
  行の区切りは行と行のあいだだけ。1行目の上に引くと、すぐ上の見出しの線（.head の
  border-bottom）と 16px 離れて2本の罫線が並んでいた（個人ページの Career・
  「できごと」のページ）。1行目の上の空きも詰める（見出しの線から離れすぎない）
*/
.career li + li {
  border-top: 1px solid var(--line);
}

.career li:first-child {
  padding-top: 0;
}

/* 期間（「2024.03 — 現在」）。和文を含むので等幅にせず、数字の幅だけそろえる */
.career .period {
  color: var(--ink-weak);
  font-size: var(--fs-meta);
  font-variant-numeric: tabular-nums;
}

.career .title {
  font-size: var(--fs-sm);
  line-height: 1.6;
}

.career .org {
  color: var(--ink-mid);
  font-size: var(--fs-meta);
}

/* --- 打ち込むブロック（ひとこと・いま・数字・リンク集） --- */

/* できごとは .career、メモは .bio をそのまま使う。同じものを別の名前で増やさない */

/*
  ひとこと（1文だけのページ）。大見出しと同じ組みで、大きな1文を置くだけ
  （箱や線で囲まない）。
*/
.statement {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.statement__text {
  font-family: var(--font-display);
  font-size: var(--fs-display);
  font-weight: 500;
  font-feature-settings: 'palt';
  line-height: 1.4;
  letter-spacing: -0.01em;
  max-width: 24em;
}

.statement__note {
  color: var(--ink-weak);
  font-size: var(--fs-sm);
}

.now li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-4);
  padding-block: var(--sp-3);
  border-bottom: 1px solid var(--line);
}

.now li:first-child {
  border-top: 1px solid var(--line);
}

/* 補足。打ち込んだ和文が入るので、本文の書体のまま */
.now .exp {
  color: var(--ink-weak);
  font-size: var(--fs-meta);
}

/* 数字のブロック。入口の件数の帯と同じく、上に罫線を引いたセルを並べる（箱は持たない） */
.numbers {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 0 var(--sp-5);
}

.numbers__item {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding-block: var(--sp-5);
  border-top: 1px solid var(--line);
}

.numbers__note {
  color: var(--ink-weak);
  font-size: var(--fs-meta);
}

/*
  矢印（.linklist__go）は行の右上に据え、行の右端にその幅ぶんの余白を取る。
  flex の折り返しに任せていたころは、補足が長い行でラベル・補足・矢印が3段に
  分かれ、矢印1字のためだけに1行ぶん（約 24px）の高さを使っていた。
*/
.linklist li a {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-4);
  min-height: var(--tap);
  padding-block: var(--sp-3);
  padding-inline-end: var(--sp-6);
  border-bottom: 1px solid var(--line);
  transition: color var(--dur-fast) var(--ease);
}

.linklist li:first-child a {
  border-top: 1px solid var(--line);
}

.linklist__label {
  font-weight: 600;
}

.linklist__note {
  color: var(--ink-weak);
  font-size: var(--fs-sm);
}

.linklist__go {
  position: absolute;
  top: var(--sp-3);
  right: 0;
  color: var(--accent);
  font-size: var(--fs-sm);
}

/* --- 一覧への帯（2人以上のサイトの個人ページの名札の下） --- */

/*
  2人以上のサイトの個人ページの名札の下に置く、一覧への1本（components.tsx の Band）。
  左に何が入っているか（「このメンバーのつくったもの」）と件数、右端に塗りの
  「一覧で見る →」。上下に罫線を引いた1行で、箱には入れない。

  justify-self（Hero が grid のとき）と align-self（flex のとき）の両方で左に寄せる。
*/
.band {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  align-self: flex-start;
  gap: var(--sp-6);
  max-width: 100%;
  margin-top: var(--sp-4);
  padding-block: var(--sp-3);
  border-block: 1px solid var(--line);
  transition:
    border-color var(--dur-fast) var(--ease),
    scale var(--dur-spring) var(--ease-spring);
}

.band__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.4;
}

.band__body strong {
  font-size: var(--fs-sm);
  font-weight: 600;
}

/*
  件数は --ink-mid。字は本文の書体で和文の最小の段。件数は「個人開発 5 · 業務 2」と
  和文なので、11px の等幅だったころは帯の中でいちばん読みにくかった。
*/
.band__meta {
  color: var(--ink-mid);
  font-size: var(--fs-meta);
}

/* 「外に出る」操作子は --accent（.links a の説明を見ること）。ここは塗りで持つ */
.band__go {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  flex-shrink: 0;
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-5);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: var(--fs-sm);
  font-weight: 600;
}

.band__arrow {
  display: inline-block;
  transition: translate var(--dur-spring) var(--ease-spring);
}

/* --- Contact --- */

/*
  サイトの並びの最後のページ（components.tsx の Contact）。入口と対になる締めで、
  入口と同じ星系の軌道図（.orbits）を上に、誘いの1文とメールと GitHub の手を
  その下に置く。字は表紙（--cover-h。「ページの外枠」）の底に寄せる（図と字の
  あいだの余りは 1fr の行が受ける）。箱（枠・面・影）には入れない。
*/
.orbital {
  grid-template-rows: auto 1fr;
  gap: var(--sp-7);
}

/* 字は 900 未満では残りの高さの真ん中に、900 以上では表紙の底に寄せる */
.contact {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-5);
  align-self: center;
  min-width: 0;
}

/*
  誘いの1文。大きく、句読点の塊を1行ずつ（入口の大見出しと同じ組み）。
  見出しではない（見出しは読み上げ用の h1 が持つ）ので、太さは本文に近く。
*/
.contact__lead {
  font-size: var(--fs-display-sm);
  font-weight: 500;
  font-feature-settings: 'palt';
  line-height: 1.45;
  letter-spacing: -0.01em;
}

.contact__lead .phrase {
  display: block;
}

/*
  メールの手。アドレスそのものを大きな細い字にし、下に1本の罫線を引く。
  操作の言葉（→ メールを送る）はアドレスの後ろに小さく添える。狭い画面では
  アドレスの下へ折り返す。アドレスは英数字の札なので、長くても語の途中で
  折らない（1語が列より長いときだけ、どこででも折る）。
*/
.contact__mail {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-5);
  max-width: 100%;
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--line-strong);
  transition:
    border-color var(--dur-fast) var(--ease),
    scale var(--dur-spring) var(--ease-spring);
}

.contact__address {
  min-width: 0;
  font-size: var(--fs-display-sm);
  font-weight: 300;
  letter-spacing: -0.01em;
  overflow-wrap: anywhere;
}

.contact__go {
  color: var(--ink-mid);
  font-size: var(--fs-meta);
}

/* GitHub。目次の英字の行き先と同じ、等幅の小さな大文字（英字だけの札） */
.contact__sub {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  color: var(--ink-mid);
  font-size: var(--fs-meta);
  transition:
    color var(--dur-fast) var(--ease),
    scale var(--dur-spring) var(--ease-spring);
}

.contact__sub :lang(en) {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.empty {
  color: var(--ink-weak);
  font-size: var(--fs-sm);
}

/* --- 404 / 500 --- */

.oops {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  padding: var(--gutter);
  text-align: center;
}

/*
  ロゴの印（ブラックホールの O。src/ui/icons.tsx の HoleMark）。光は焼いた絵で色を持たない。

  display: flex は、印の svg を行に載せないため。この箱は .oops の列の中で block に
  なり、中の svg は1行ぶんの行ボックスに載る。標準モードの行ボックスは字が無くても
  書体の高さの支え（strut）を持つので、svg がベースラインに座り、下に字の下がりぶんの
  隙間が付いて、箱が印より高くなる。flex にすると svg は行ではなく flex の子になり、
  箱は印と同じ高さに戻る。
*/
.oops__mark {
  display: flex;
}

.oops__code {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  color: var(--ink-weak);
  letter-spacing: 0.1em;
}

.oops h1 {
  font-size: var(--fs-lg);
  font-weight: 600;
}

.oops p {
  color: var(--ink-mid);
  font-size: var(--fs-sm);
}

.oops a {
  color: var(--accent);
  font-size: var(--fs-sm);
  min-height: var(--tap);
  display: inline-flex;
  align-items: center;
}

/* ============================================================ 600px 以上 */

@media (min-width: 600px) {
  /*
    上の帯の間隔。600 未満は1段詰めて、電話（390）でも3つの行き先（Projects /
    Profile / Contact）とロゴが1行に収まるようにする
  */
  .top,
  .toc {
    gap: var(--sp-6);
  }

  /* 押し手は列いっぱいに伸ばさず、字の幅に戻す（600 未満で伸ばす理由は素の .cta に） */
  .cta {
    align-self: flex-start;
  }

  /* 一覧の行: 左に番号の列を立て、本文・サムネイル・札・技術をその右に積む */
  .entry {
    grid-template-columns: var(--entry-index) minmax(0, 1fr) auto;
    grid-template-areas:
      'index main go'
      '. thumb .'
      '. meta .'
      '. tags .';
  }

  .team-grid {
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  }

  .member--wide {
    flex-wrap: nowrap;
  }

  .member--wide .member__go {
    flex: 0 0 auto;
  }
}

/* ============================================================ 900px 以上 */

@media (min-width: 900px) {
  .hero--profile {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr);
    gap: var(--gutter);
  }

  .hero--profile > .celestial--art {
    width: min(100%, var(--celestial-w));
  }

  .hero--profile + section > .head--chapter {
    margin-top: var(--sp-7);
  }

  /*
    入口は2列。左に見出しの列、右に作品の軌道図、底に件数の帯（2列にまたがる）。
    見出しの列と軌道図は、表紙の残りの高さの縦の真ん中に座る。
  */
  .hero--orbit {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-rows: 1fr auto;
    grid-template-areas: 'copy map' 'tally tally';
    column-gap: calc(var(--sp-7) * 1.5);
  }

  .hero--orbit > :is(.hero__copy, .system) {
    align-self: center;
  }

  /*
    字の暗がり（「入口の軌道図」）。入口の見出しの列と締めの字の塊（下の .orbital > .contact）は
    中身の幅に縮める——列いっぱいのままだと、短い字の右に空いた所（入口では図の左の端まで）の
    星雲を消す。字の折り返しは変わらない（列より長い塊があれば、列の幅で止まる）。

    暗がりは縦と横の坂の覆いを重ねる（角は2つの坂の積で丸まる）。左は --veil、右は
    --veil-side（:root）。右へ広げたぶんが画面の外へ出ても、本文が横に切る（「骨格」）
  */
  .hero--orbit > .hero__copy {
    justify-self: start;
  }

  .hero--orbit > .hero__copy::before,
  .orbital > .contact::before {
    inset: calc(-1 * var(--veil)) calc(-1 * var(--veil-side)) calc(-1 * var(--veil))
      calc(-1 * var(--veil));
    mask-image: var(--veil-y), var(--veil-x);
    mask-composite: intersect;
  }

  /*
    入口の星空の図（--cosmos-fig-w）は右の列で、焦点（--cosmos-x / -y）は横がその真ん中、縦は
    件数の帯と行の間（約 112px）を除いた残りの真ん中から、図の中の焦点の高さ（--cosmos-focus）
    だけずらした所。星雲は左の字の列の上と下まで広がり、字の後ろは字の暗がりが消す
  */
  .cosmos--hero {
    --cosmos-fig-w: calc((min(100vw - 2 * var(--gutter), var(--w-page)) - var(--sp-7) * 1.5) / 2);
    --cosmos-x: calc(50% + var(--sp-7) * 0.75 + var(--cosmos-fig-w) / 2);
    --cosmos-y: calc(
      var(--page-pad) +
      (100% - 2 * var(--page-pad) - 112px) /
      2 +
      (var(--cosmos-focus) - 0.5) *
      var(--cosmos-fig)
    );
  }

  /*
    一覧の行: 番号・本文・札・技術・矢印を横に並べ、サムネイルは本文の下に置く。
    本文の列が残りを受け、札は区画の中で縦に積む。区画の幅は行をまたいで同じ
    なので、札と技術の縦の線が一覧の上から下までそろう
  */
  .entry {
    grid-template-columns:
      var(--entry-index) minmax(0, 1fr) var(--entry-meta) var(--entry-tags)
      var(--entry-go);
    grid-template-areas:
      'index main meta tags go'
      'index thumb meta tags go';
    grid-template-rows: auto 1fr;
  }

  .entry__meta {
    flex-direction: column;
    gap: 0;
  }

  /* 締めの字は表紙の底に寄せ、中身の幅に縮める（字の暗がり。上の .hero__copy と同じ） */
  .orbital > .contact {
    align-self: end;
    justify-self: start;
  }

  /*
    作品のページで画像がある作品は、文の列を左・画像を右に並べる。
    画像の枠は行の高さ（＝文の列の高さ）まで伸び、下限は --shot-h。本文が
    長いほど画像も大きく出て、短ければ --shot-h の枠に収まる。どちらでも
    画面の高さに効くのは「文の列と --shot-h の高いほう」だけで、画像が本文の
    予算を食わない。

    文の列を左にするのは、見出し（左寄せ）と文の頭を同じ縦の線にそろえるため。
    画像は DOM では先（縦に積んだとき見出しのすぐ下に来るように）なので、
    列の番号で置き場所を決める。
  */
  .gallery {
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--sp-7);
    row-gap: calc(var(--sp-7) * 2);
    align-items: start;
  }

  .gallery__item {
    grid-column: span 7;
  }

  .gallery__item:nth-child(4n + 3),
  .gallery__item:nth-child(4n + 4) {
    grid-column: span 5;
    margin-top: var(--gallery-shift);
  }

  .gallery__item--lead,
  .gallery__item:last-child:nth-child(even) {
    grid-column: 1 / -1;
  }

  .detail--shot {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--sp-6);
  }

  .detail--shot > .shot {
    grid-column: 2;
    grid-row: 1;
    height: auto;
    min-height: var(--shot-h);
  }

  .detail--shot > .detail__text {
    grid-column: 1;
    grid-row: 1;
  }
}

/* ====================================================== 見た目のプリセット */

/*
  管理画面の「見た目」で選んだものが body の data-* に載る。
  選べる値は src/theme.ts が正。ここに無い値を保存しても何も変わらない。

  どのプリセットでも HTML は同じ。ここでやるのは :root の段を差し替えることと、
  同じ部品を別の並べ方にすることだけ。生の色や文字サイズはここに書かない。
*/

/* --- アクセント色 --- */

/*
  body だけでなく要素単位でも効かせる。管理画面の見本が、同じ指定で
  そのまま色を出せるようにするため（見本用の色を別に持たない）。

  星雲の3色（--nebula-a / -b / -c）もここで決める。1色目はアクセント、2色目と3色目は相方——
  どれもパレットの色から選ぶ（色相を回して作ると、エンバーの相方が黄緑になった）。
  モノクロでも星雲だけは青紫と桃と空色（持ち主の「星雲だけ色を付ける」）。
*/
[data-accent='mono'] {
  --nebula-art: url('/assets/nebula-iris.webp?v=c5b0f3ae');
  --accent: var(--mono);
  --accent-soft: var(--mono-soft);
  --nebula-a: var(--iris);
  --nebula-b: var(--rose);
  --nebula-c: var(--sky);
}

[data-accent='iris'] {
  --nebula-art: url('/assets/nebula-iris.webp?v=c5b0f3ae');
  --accent: var(--iris);
  --accent-soft: var(--iris-soft);
  --nebula-a: var(--iris);
  --nebula-b: var(--rose);
  --nebula-c: var(--sky);
}

[data-accent='violet'] {
  --nebula-art: url('/assets/nebula-violet.webp?v=65fc83e2');
  --accent: var(--violet);
  --accent-soft: var(--violet-soft);
  --nebula-a: var(--violet);
  --nebula-b: var(--rose);
  --nebula-c: var(--iris);
}

[data-accent='ember'] {
  --nebula-art: url('/assets/nebula-ember.webp?v=33228880');
  --accent: var(--ember);
  --accent-soft: var(--ember-soft);
  --nebula-a: var(--ember);
  --nebula-b: var(--rose);
  --nebula-c: var(--violet);
}

[data-accent='mint'] {
  --nebula-art: url('/assets/nebula-mint.webp?v=96d31dea');
  --accent: var(--mint);
  --accent-soft: var(--mint-soft);
  --nebula-a: var(--mint);
  --nebula-b: var(--sky);
  --nebula-c: var(--iris);
}

[data-accent='sky'] {
  --nebula-art: url('/assets/nebula-sky.webp?v=f1c8c8b2');
  --accent: var(--sky);
  --accent-soft: var(--sky-soft);
  --nebula-a: var(--sky);
  --nebula-b: var(--violet);
  --nebula-c: var(--mint);
}

[data-accent='rose'] {
  --nebula-art: url('/assets/nebula-rose.webp?v=afc4da75');
  --accent: var(--rose);
  --accent-soft: var(--rose-soft);
  --nebula-a: var(--rose);
  --nebula-b: var(--violet);
  --nebula-c: var(--ember);
}

/* --- 書体。変えるのは見出しだけで、本文は読みやすさを優先して据え置く --- */

[data-typeface='sans'] {
  --font-display: var(--font-sans);
}

[data-typeface='serif'] {
  --font-display: var(--font-serif);
}

[data-typeface='mono'] {
  --font-display: var(--font-mono);
}

/* ============================================ 押す・選ぶ（手触り） */

/*
  押すと縮み、離すとばねで戻る。どの操作部品も同じ量（--press）・同じ曲線で動かす。
  部品ごとに量を変えると、手触りが揃わない。
  transform ではなく scale を使うのは、ホバーで浮かせる translate とぶつけないため
*/
.cta:active,
.filters a:active,
.socials a:active,
.toc a:active,
.top__admin:active,
.band:active,
.member:active,
.back:active,
.contact__mail:active,
.contact__sub:active {
  scale: var(--press);
}

/*
  一覧の行は面を押したとき（題のリンクの覆いが :active のとき）だけ縮む。
  .entry:active にすると、中の Repository や担当者名を押したときも行ごと
  縮み、押したものと縮んだものが食い違う。上の列に並べないのは :has のため
  ——知らないブラウザで、列ごと（ほかの部品の手触りまで）捨てられる。
*/
.entry:has(.entry__link:active) {
  scale: var(--press);
}

/* ============================================ 入力手段で分ける（幅ではなく） */

@media (hover: hover) {
  /*
    ホバーで応えるのは押せるものだけ。一覧の行は題にリンク（.entry__link）を持つ
    ものだけで、slug の無い行は押してもどこへも行かないので応えない——押せる
    合図だけ出して、押しても何も起きない面を作らない。応え方は矢印を送って
    字を上げる（箱を持たない行なので、浮かせない）。

    :has は :is() の中に入れる。素の列に並べると、:has を知らないブラウザでは
    列ごと捨てられる。:is() は読めない引数だけを落とす。
  */
  :is(.entry:has(.entry__link)):hover .entry__go {
    color: var(--ink);
    translate: var(--sp-2) 0;
  }

  :is(.entry:has(.entry__link)):hover .entry__link {
    text-decoration-color: currentColor;
  }

  .member:hover {
    border-color: var(--line-strong);
    background: var(--surface-hover);
  }

  .band:hover {
    border-color: var(--line-strong);
  }

  .band:hover .band__arrow,
  .cta:hover .cta__arrow {
    translate: var(--sp-2) 0;
  }

  .toc a:hover,
  .socials a:hover {
    color: var(--ink);
  }

  /* 色はアクセントのまま（目次と見分ける印なので、ホバーで消さない）。下線で応える */
  .top__admin:hover {
    text-decoration: underline;
    text-underline-offset: var(--sp-1);
  }

  .filters a:not([aria-current]):hover,
  .back:hover {
    color: var(--ink);
  }

  /*
    素の色がもう --accent なので、ホバーで色は動かせない。下線（素の .links a が引いてある
    透明の線）に色を入れる。色だけに頼らない合図なので、タッチ端末で落ちても失うものが無い。
  */
  .links a:hover {
    text-decoration-color: currentColor;
  }

  .contact__mail:hover {
    border-color: var(--ink);
  }

  .contact__sub:hover {
    color: var(--ink);
  }

  .gallery__item:hover .gallery__open {
    color: var(--ink);
  }
}

/* タッチ端末では当たり判定を広げる。iPad の横向きは幅が広くてもこちら */
@media (pointer: coarse) {
  :root {
    --tap: 44px;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  /*
    ここの !important は外せない。動きを止める指定は、部品ごとに書いた
    transition / animation（.entry や .cta など十数か所）より必ず強く
    なければならず、詳細度で勝とうとすると同じ一覧をここに書き写すことになる。
    「動きを減らす」と設定した人に動きが残るのは、めまいや発作の引き金になる
    （WCAG 2.3.3）。lint の自動修正に外させないよう、理由つきで黙らせる。
  */
  *,
  *::before,
  *::after {
    /* biome-ignore lint/complexity/noImportantStyles: 上のコメントのとおり、部品側の transition に必ず勝つ必要がある */
    transition: none !important;
    /* biome-ignore lint/complexity/noImportantStyles: 同上 */
    animation: none !important;
  }
}

/*
  はっきり見たいと言われたら、入口の軌道図は消す。

  軌道図は装飾で、forced-colors（Windows のハイコントラスト）では線と点がシステム色に
  置き換わって、ただの線の束になる。prefers-contrast: more も同じ意図の申告
  なので、まとめて受ける。消しても失われる情報は無い（作品の名前は「一覧で見る →」の
  先に全部あり、件数は件数の帯が字で言う）。締めの図（.orbits）も同じ。星空を消すので、
  字の後ろの暗がり（「入口の軌道図」）も要らない。
*/
@media (forced-colors: active), (prefers-contrast: more) {
  .system,
  .orbits,
  .cosmos,
  .celestial--art,
  .hero--orbit > .hero__copy::before,
  .orbital > .contact::before {
    display: none;
  }

  /* 図の区画も畳む（下の「表紙を1列に畳む」） */
  .hero--orbit {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: 1fr auto;
    grid-template-areas: 'copy' 'tally';
  }

  .orbital {
    grid-template-rows: 1fr;
  }

  .hero--profile {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }
}

/*
  強制色モードでは、ロゴの O を字と同じ色の輪に替える。光は焼いた絵で色を変えられず
  （明るいテーマでは白い光が地に溶け、影の黒だけが塗りの円で残る）、字の色（強制色の
  リンクの色）とも揃わないので隠し、影の円を地の色で抜いて縁に線を引く（太さは字の線と
  同じ。icons.tsx の HoleArt が渡す）。prefers-contrast: more の括りには入れない——強制色
  でないときの Canvas は灰色の地で、影が灰色の円になる
*/
@media (forced-colors: active) {
  .logo-art {
    display: none;
  }

  .logo-core {
    fill: Canvas;
    stroke: currentColor;
  }
}

/*
  背の低い窓でも消す。

  400px を切ると、貼り付いた帯を除いた表紙が軌道図を絵として置けるほど高くならず
  （320x256 では表紙の残りが 100px 足らず）、図が見出しを画面の外へ押し出す。
  拡大 200% の人が最初に当たる寸法なので、装飾より本文を通す。
*/
@media (max-height: 400px) {
  .system,
  .orbits,
  .cosmos,
  .hero--orbit > .hero__copy::before,
  .orbital > .contact::before {
    display: none;
  }

  /*
    表紙を1列に畳む。図を消しただけだと区画が残り、900 以上の入口は右半分が空き、
    締めは字が図の行（auto）へ繰り上がって表紙の上に寄る
  */
  .hero--orbit {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: 1fr auto;
    grid-template-areas: 'copy' 'tally';
  }

  .orbital {
    grid-template-rows: 1fr;
  }
}

/* ====================================================== ページの切り替え */

/*
  ページを移るときの切り替え（cross-document view transitions）。JavaScript は
  使わない——同じサイトの中のふつうのリンクの移動で、前と次の両方のページが
  この宣言を持っていれば、ブラウザが古い姿から新しい姿へつなぐ。知らない
  ブラウザではふつうに移るだけ（進歩的な強化）。

  - 本文は短く重ねて入れ替える（既定の root の重ね合わせ。長さは --dur）
  - 上の帯は名前（top）で本文から分け、入れ替わりに巻き込まない——ページを
    移っても同じ場所に居るものは、動かないほうがよい
  - 一覧の行の題と作品のページの見出しは同じ名前（item-<slug>。components.tsx の
    itemTransition）でつなぎ、題がそのまま見出しへ動いて大きくなる

  管理画面では切り替えない（admin.css が同じ宣言を none で上書きする。両方のページが
  宣言を持つときだけ切り替わるので、公開ページと管理画面のあいだも切り替わらない）。
  動きを減らす設定では、宣言ごと外す（下の @media。* の animation: none は
  ::view-transition の疑似要素には届かない）。
*/
@view-transition {
  navigation: auto;
}

.top {
  view-transition-name: top;
}

::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
  animation-duration: var(--dur);
  animation-timing-function: var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  @view-transition {
    navigation: none;
  }
}

/* ====================================================== ページの外枠（公開ページ） */

/*
  公開ページは普通に縦にスクロールする。上の帯はページの上に貼り付けて、どこまで
  読み進めても次の行き先に手が届くようにする。

  以前は「1画面に1つぶんだけを収め、ページはスクロールさせない」を不変条件にして
  いた。持ち主が実際に触って、節を画面ごとに割って底の左右の手でしかめくれないのは
  面倒すぎる、と判断してやめた（CLAUDE.md の「公開ページは縦に読む」）。この節は
  その外枠の跡地で、残したのは3つだけ——表紙（入口と締め）を1画面ぶんの高さに
  敷くこと、節の見出しを上に揃えること、上の帯を貼り付けること。

  @media screen で括るのは、紙には貼り付ける帯も1画面ぶんの表紙も要らないため
  （紙の上の姿は末尾の @media print）。幅のメディアクエリ（600 / 900）とは別物。

  当てる相手は公開ページだけ（body[data-site]。管理画面と 404 には付かない）。
  [data-whole] が付くのは全体ページ（/all）で、あちらは節を縦に積んだ1本の文書
  なので、表紙も帯の貼り付けも持たない。目印の側は :where() で包んで詳細度を 0 に
  してある。

  この節はファイルの末尾近くに置く。骨格（.top / main）と部品の素の指定を
  上書きするので、それらより後でなければ効かない。
*/
@media screen {
  /*
    貼り付いた上の帯の下に見出しが隠れないよう、フラグメント（#about など）や
    フォーカスで送られた先を帯の高さぶん下に止める（:root の --top-clear）。
  */
  :where(html:has(> body[data-site]:not([data-whole]))) {
    scroll-padding-top: var(--top-clear);
  }

  /*
    足元を画面の底に置く。body を縦の flex にして画面1つぶん（--screen-h）以上の
    高さを持たせ、本文（main）が残りを受ける。中身の短いページでも足元が画面の
    途中に浮かない。height ではなく min-height——長い中身はページごと伸びる。
  */
  body[data-site] {
    display: flex;
    flex-direction: column;
    min-height: var(--screen-h);
  }

  body[data-site] > main {
    flex: 1 0 auto;
  }

  /*
    上の帯を貼り付ける。重なりは z-index: 2——入口の軌道図の字（z-index: 1）と、
    作品の行の中のリンク（覆いの上に出す z-index: 1）より上。地（--bg）は素の
    .top が敷いている。全体ページでは貼り付けない（節を縦に積んだ1本の文書で、
    目次はページの中の行き先。帯は頭にあれば足りる）。
  */
  :where(body[data-site]:not([data-whole])) .top {
    position: sticky;
    top: 0;
    z-index: 2;
  }

  /*
    いまのページの行き先を、帯の見えている幅の中で開く。CSS だけで組む帯は、
    帯は何もしなければいつも左端（scrollLeft 0）で開き、並びの後ろの節
    （Links・Contact）に着くと印が帯の右の外に押し出されていた。

    scroll-initial-target: nearest は、いちばん近いスクロール容器（この帯）の
    最初の位置を、その要素が見える所に決める（CSS Scroll Snap 2。inline 方向は
    scrollIntoView の nearest と同じで、scroll-margin を読む）。scroll-margin で
    右端のぼかし（--fade-w）のぶんを空け、印がぼかしの下に座らないようにする。
    知らないブラウザ（いまは Chromium 133 以降だけ）はこの1本を捨てて左端で開く
    （進歩的な強化）。npm run check:fit が「印の矩形が帯の見えている矩形の中に
    ある」を測っている。
  */
  :where(body[data-site]:not([data-whole])) .toc a[aria-current='page'] {
    /* biome-ignore lint/correctness/noUnknownProperty: CSS Scroll Snap 2 の性質で、Biome の一覧にまだ無い（Chromium 133 から）。知らないブラウザは捨てる */
    scroll-initial-target: nearest;
    scroll-margin-inline: var(--fade-w);
  }

  /*
    節は上揃え（見出しの錨）。どのページでも、見出し（節の h1）が帯の罫線から
    同じだけ下（main の --page-pad）から始まる。npm run check:fit が、同じ寸法の
    中で見出しの上端が 1px 以内でそろっていることを測っている。

    ここは必ず :is(.hero, section) のクラス強度（0,1,1）で書く。main > *
    （0,0,1）では .hero{display:flex}（0,1,0）に負ける。
  */
  :where(body[data-site]) main > :is(.hero, section) {
    display: grid;
    align-content: safe start;
  }

  /*
    表紙（入口の Hero と締めの Contact）は、帯と本文の余白を除いた画面の残り
    いっぱい（--cover-h）を下限に持つ。表紙の下端が画面の下端に来て、足元は
    次の画面に回る。全体ページには表紙が無い（節の並びのまま）。
  */
  :where(body[data-site]:not([data-whole])) main > :is(.hero--orbit, .orbital) {
    min-height: var(--cover-h);
  }

  @media (max-width: 899px) {
    /*
      絞り込みも目次と同じ、1行の横に送る帯にする。折り返すと2段で場所を取る。
      ぼかしのぶんの空きを右に持ち、同じだけ右の余白へはみ出す（目次の .toc と同じ。
      溢れていなければ最後の手を薄めない）
    */
    :where(body[data-site]:not([data-whole])) .filters {
      flex-wrap: nowrap;
      overflow-x: auto;
      scrollbar-width: none;
      margin-inline-end: calc(var(--fade-w) * -1);
      padding-inline-end: var(--fade-w);
      mask-image: var(--fade-right);
    }

    :where(body[data-site]:not([data-whole])) .filters a {
      flex-shrink: 0;
    }
  }

  /*
    全体ページの目次は折り返す。帯は貼り付かない頭の1段なので、1行の横帯より
    全部が見えるほうがよい（印刷・Ctrl-F の宛先で、末尾の Contact が帯の外に
    隠れない）。ぼかしも外す。
  */
  :where(body[data-site][data-whole]) .toc {
    flex-wrap: wrap;
    overflow-x: visible;
    mask-image: none;
  }
}

@media print {
  /*
    紙は白い地で刷る。画面の黒い地のまま刷ると、白に近い字が白い紙に乗る（地の色は
    既定では刷られない）。差し替えるのは色の段だけで、名前は :root とそろえる
    （片方にしか無い段を作らない。test/theme.test.ts の「配色」が数える）。

    形は夜の地を裏返しただけ: 地は色味の無い白、面は黒をうっすら重ね、線は弱く、
    上辺の光は白。アクセントは同じ色相を暗くして、白い地で字が 4.5:1 に届く所へ
    置く（いちばん低いのはエンバーで 5.21:1）。モノクロは黒、塗りのボタンの字は白。
    ロゴの O の影（--hole-core）は紙の色に抜く——光を墨で刷るので、黒のままだと光の
    輪と影がつながって塗りの円になる。
  */
  :root {
    --bg: #ffffff;
    --surface: rgba(0, 0, 0, 0.035);
    --surface-hover: rgba(0, 0, 0, 0.06);
    --line: rgba(0, 0, 0, 0.08);
    --line-strong: rgba(0, 0, 0, 0.15);
    --highlight: inset 0 1px 0 rgba(255, 255, 255, 0.7);
    --ink: #111114;
    --ink-mid: #4a4a53;
    --ink-weak: #6a6a74;
    --hole-core: #ffffff;
    --mono: #111114;
    --mono-soft: rgba(17, 17, 20, 0.06);
    --iris: #4c4cd9;
    --iris-soft: rgba(76, 76, 217, 0.08);
    --violet: #7445d6;
    --violet-soft: rgba(116, 69, 214, 0.08);
    --ember: #a8570b;
    --ember-soft: rgba(168, 87, 11, 0.08);
    --mint: #0b7a5a;
    --mint-soft: rgba(11, 122, 90, 0.08);
    --sky: #1668b8;
    --sky-soft: rgba(22, 104, 184, 0.08);
    --rose: #c02a4d;
    --rose-soft: rgba(192, 42, 77, 0.08);
    --accent-ink: #ffffff;
    --danger: #c0343b;
    --danger-soft: rgba(192, 52, 59, 0.08);
    --switch-track: rgba(0, 0, 0, 0.12);
  }

  /*
    紙の上ではリンクは押せない。サイトを通して刷るなら全体ページ（/all）を
    刷ること。軌道図はインクを食うだけの装飾なので、ここで落とす（入口の軌道図は
    /all には元から出ないが、入口のページを直接刷られたときのため）。
  */
  .top,
  .filters,
  .back,
  .skip,
  .band,
  .cta,
  .system,
  .orbits,
  .cosmos,
  .celestial--art,
  .celestial--symbol,
  .hero--orbit > .hero__copy::before,
  .orbital > .contact::before {
    display: none;
  }

  .entry {
    break-inside: avoid;
  }

  .gallery__item {
    break-inside: avoid;
  }

  .gallery__open {
    display: none;
  }

  .hero--profile {
    grid-template-columns: minmax(0, 1fr);
  }
}
