コンテンツにスキップ

見た目の決まり

画面の見た目の決まりです (手を入れる人向け)。言葉の決まりは writing.md、画面ごとの説明は screens.md。

合言葉: 塗らない。囲みすぎない。浮かせすぎない。でも、ちゃんと伝わる。

並べ方は writing.md の順です (大きな決まりは 1 章、直す予定のずれは最後の「今とのずれ」)。 決まりはどこでも同じにし、部品ごとの例外は作りません。

大きな決まりは 5 つです。2 章から下は、どれもこのどれかから来ています。

  1. ふだんは平らに。 アイコン・字・余白・揃えだけで組み、要らない地の色・枠・影・飾りは付けない
  2. 強弱は塗りではなく、見た目の順位で。 選んでいる・オンを強い色で塗らない。色は役目を決めて使う
  3. 奥行きは層で。 Liquid Glass から層の考え方だけを借りる。ぼかし・屈折・ガラスの照り返しは使わない (CSS では重い)
  4. 1 つの意味に印は 1 つ。 同じことを 2 つの印で言わず、1 つの見た目に 2 つの意味を持たせない
  5. 動きは CSS で、軽く。 JS では動かさない。時間で動き続けるものも使ってよいが、見えている間だけ・合成だけで済む形で動かし、ソフトウェアで描いている端末では止める (7 章)

元: 3 奥行きは層で

置くものは地・件の面・操作の層の 3 つの高さに分け、それより上は作らない。

層 置くもの 面
地 1 行の並び (サイドバー・設定の項目)・映像 平ら。線も影も付けない (--dp-bg)
件の面 1 件が 2〜3 行ある一覧の件 (録画・予約・ルール・検索の結果) 透かさない面 (--dp-item)。明るいほうは白とごく薄い影、暗いほうは地より一段明るい。縁は無し
操作の層 ヘッダー・その下に貼り付く操作の 1 行・下のタブ・画面の下の主の操作・貼り付いた日付の見出し・番組表の局名と時の列と「いま」の時刻・メニュー・ダイアログ 面 (--dp-float か --dp-pop) と 2 段の影 (--dp-shadow-pop)。隙間を詰めて並ぶものは近い影だけ (--dp-shadow-tight)
  • 浮いたものの上に、さらに浮いたものを重ねない。 中のボタンは平らで、主の操作は太字だけ
    • 例: 件の面・メニュー・ダイアログ・下から出す一枚・浮いた 1 行の中のボタン
  • 主の操作は色ではなく層で示す。 主のボタンを操作の層に乗せ、ほかの操作は字とアイコンだけで平ら
    • 例外: 縁に光を回す操作 (8 章) は浮かせない。光が主の操作の印になる (1 つの意味に印は 1 つ)
  • 隙間を詰めて並ぶものは、近い影だけにする (遠い影が隣に掛かって段々に見えないように)
    • 例: ヘッダーとその下の操作の 1 行、番組表の局名

元: 1 ふだんは平らに・3 奥行きは層で

浮かせるのは、動く中身の上に重なるものだけ。 裏に何も来ないうちは平らに置く。

  • 操作の置き場は 2 つの形のどちらか。 スマホの形は上のヘッダーを出さず、下のタブ (とそのすぐ上の操作の 1 行) に寄せる。広い形は上のヘッダーと、その下に貼り付く操作の 1 行
    • スマホの形にする条件は #lib/phone-form.ts の 1 か所で決める (いまは 1000px 未満と、折り畳み端末のテーブルトップ。タブレットの縦・横にしたスマホ・狭めた PC の窓もスマホの形)。 CSS は <html data-phone> の印で書き分ける。ダイアログを下から出す・行を折り返すなど、幅だけで決まるものは幅で書く
    • 下のタブは 640px 未満が 5 つ (録画・番組表・ライブ・検索・その他)、640px からは切り抜き・ルール・設定も並べて 7 つ (「その他」は出さない。テーマと新しいバージョンのお知らせは設定の中)
    • 映像の画面 (ライブ・観る画面・追っかけ) も同じ
    • 例外: 横にしたスマホの映像の画面は、映像を画面の高さいっぱいに出すため下のタブも隠す
  • 中身が裏に入ったときだけ、面と影を出す (巻いた量に合わせて出る。仕組みは「動き」)
    • 例: ヘッダー (広い形)。面は --dp-float (メニューの面を 92% 残す)、字は 4.5:1 以上、端から離した角丸の面
    • 例: ヘッダーの下に貼り付く操作の 1 行。番組表の種別・日付の 1 行 (広い形。ほかの画面の絞り込みは、広い形では右のサイドバーに入る)。 形はヘッダーと同じ (app.css の --dp-bar-*)、ヘッダーとの間は --dp-bar-gap (0.25rem)。この 1 行がある間はヘッダーも近い影にそろえる
    • 例: 日付の見出し。貼り付いて行がその裏を流れるときだけ、小さなカプセル (scroll-state(stuck: top))
    • 例外: 下のタブ (スマホの形) はいつも浮いたまま (いつも中身が裏を流れる)。同じ操作の 1 行も、スマホの形ではどの画面も下のタブのすぐ上にいつも浮かせる (ホームの絞り込み・番組表の種別と日付・切り抜きの絞り込み・ルールの絞り込みと並べ替え・設定の節の操作・検索の「ルールとして保存」)。 形は app.css の .dock (下のタブから --dp-dock-gap 上のカプセル)。検索の「ルールとして保存」は 1 行の面を敷かず、ボタンだけを浮かせる
      • 例: 番組表は、種別・日付の 1 行を下のタブのすぐ上に浮かせる (表は画面の下の端まで伸ばし、この 1 行と下のタブは表の上に浮く)
      • 例: 設定は、節へ飛ぶ行を下のタブのすぐ上に、見えている節の操作 (見出しの行の押すもの・絞り込み。チャンネルのスキャン・ログの絞り込みと「Issue にする」) をそのすぐ上に浮かせる。節を移ると入れ替わり、操作の無い節では出さない。768px からは左の節の一覧を残すので節へ飛ぶ行は出さず、節の操作を下のタブのすぐ上に浮かせる (広い形では節の中)
    • 例外: 対応していないブラウザと動きを減らす設定の端末では、いつも浮いた形 (平らだと裏の中身と重なって読めない)
  • 透かすのは、裏が動くものだけ。 ぼかさない
    • 例: 映像の上のメニュー・音量のつまみ・実況に書く欄は、テーマにかかわらず暗い面で映像を透かす (地を 75% 残す。真っ白な映像の上でも控えた字で 5:1)
    • 例: ホーム画面から開いた iPhone のステータスバーは、暗いテーマのときだけ透かす (地を 85% 残した面)
    • 例外: 明るいテーマのステータスバーは地の色で塗ったまま (時刻の字がいつも白いため)。ブラウザのタブでは色 (theme-color) を合わせるだけ
    • 例外: 透かしを減らす設定 (prefers-reduced-transparency) では透かさず、映像の上の影を濃くする
    • ページの中で開くメニュー・検索の候補・ダイアログは、後ろが動かないので透かさない
  • 中身を読んでいる間は引っ込む。 スマホの形の下のタブ (とその上の 1 行) は、下へ送ると隠れ、戻すと出る。# で飛んだ巻き取り (設定の節へ飛ぶ) では隠さない (飛んだ先の操作を見せる)
    • 例: 番組表は窓ではなく表の箱の中で巻くので、表の箱の縦の巻き取りで引っ込む。横に送っても、開いたときの「いま」への位置合わせや時刻へ送るボタンで動いても隠さない。出し入れで表の箱の大きさは変わらない (中身が跳ねない)
    • 例外: 広い形のヘッダーは引っ込めない (検索を見せたまま)
    • 例外: テーブルトップの下のタブは引っ込めない。下の段は低く、右の列は中で巻くので、読み進めても下のタブの裏に隠れる中身が無い

画面ごとの組み方は次のとおりです (上の決まりの例)。

  • 一覧の画面 (録画・切り抜き・ルール・設定・検索) はページごと巻き、広い形では中身がヘッダーの裏を流れる。頭に --dp-header-space (ヘッダーの下端から --dp-bar-gap。下に貼り付く操作の 1 行・日付の見出し・サイドバーも同じ隙間) を空け、スクロールバーは窓の右端に出る。 サイドバーは地のまま、ヘッダーの下に貼り付けて動かさない (あふれたらサイドバーの中で巻く)
  • 観る画面は、広い形では箱をヘッダーの下から始める (+layout.svelte の .shell.fill。裏まで伸ばすと箱のスクロールバーだけが透ける)。窓が巻けないので、ヘッダーはいつも平ら
    • 二段組 (映像と右の列) にするのは 1000px から (ヘッダーを出す幅と同じ線)。それより狭いと横長の窓でも、映像を端から端まで出し、右の列 (局の一覧・番組の中身) を映像の下に積んでページごと巻く。 横にしたスマホは映像を画面の高さいっぱいにし、右の列は映像の下で巻き取る (下のタブは隠す。映像の下端に重ねない)
    • 折り畳み端末を半分に折って机に置いたとき (テーブルトップ。#lib/phone-form.ts の TABLETOP) は、上の段を映像だけにし、映像の枠を段の幅か高さいっぱいに収める (縦横比は保つ。余りは黒)。右の列は下の段。折り目には何も置かない。 上下の段はどちらも低いので、操作はスマホの形に寄せる。上のヘッダーは出さず、下のタブを下の段の下端に浮かべる。 下の段は舞台の中で、黒い地にアンビエントの光の代わりの反射を敷き、右の列と下のタブをその上に重ねる (右の列は中で巻く。どちらも暗いテーマの色)。全画面はページごと (並べ方を崩さない)。当たらないブラウザ・端末では何も変わらない
  • 番組表 (広い形) は表をヘッダーの裏まで伸ばし、操作の 1 行と局名の行をその下に貼り付ける。ヘッダーは窓ではなく表の巻いた量で浮く (表の箱の時間の軸 --guide を timeline-scope で外枠まで見せる)。スマホの形では表を画面の上から下の端まで伸ばす
    • 表のスクロールバーは出さない (ヘッダーの裏に透ける)。つかむ・ホイール・キー・指で動く
    • 局名は局ごとの角丸の面 (列と一緒に横へ動くので、1 本につながった面にしない)
    • 時の列は操作の層。横に送ってマスがその裏に入ったときだけ面を出し (表の箱の横の軸 --guide-x)、影はマスの側 (右) にだけ落とす
    • 左上の角は地の色で平らに塗り、縦に巻いて裏に来る時の数字を隠すだけにする。面は時の列と同じく横に送ったときだけ出す (局名の行に詰めて並ぶので近い影)
    • 局名の行より上 (ヘッダーと操作の 1 行の裏) へ巻かれた時の列は、角の上に伸ばした地の色で覆う。浮いた面の横の隙間から、時の数字や「いま」の時刻が覗かないように (操作の層の裏から、別の操作の層を見せない)

元: 2 強弱は見た目の順位で・4 1 つの意味に印は 1 つ

印は意味ごとに 1 つの見た目に決め、どこでも同じ意味で使う。

見た目 意味
文字の幅いっぱいの下線 (横に並ぶもの) いまいる場所・選んでいるもの (ヘッダーのナビ・種別・日付・チップ)
左の縦線 (縦に並ぶもの) いまいる場所・選んでいるもの (サイドバー・局・開いている行・メニューの選択肢)・自分のもの (実況の自分の書き込み)
短い下線 機能がオン (プレイヤーの字幕・番組表の「サブ ch」など)
主の色の太字 線と組んで、選んでいる・オンを補う
ごく薄い地 / 件の面を一段変える 指を乗せた (地の上の行 / 件)
外側の 2px の線 キーボードで触っているところ (焦点)
ごく小さな縮み 押した手応え
状態の色 (赤・緑・黄) 録画中・問題・成功・注意。バッジ・通知・進み具合のバー
淡い地のカプセル (バッジ) 状態だけ (録画中・有料・無効・競合)
主の色の小さな点 まだ見ていないもの (未視聴の録画・新しいバージョンが出ている設定の入口)
  • 下線は長さで意味を分ける。 幅いっぱいは「選んでいる」、短い線は「オン」
  • 1 つの部品に線は 1 本。
    • 押されているボタン (aria-pressed="true") は、並び (role="group") の中なら選んでいるもの (幅いっぱいの下線・左の縦線)、並びの外ならオン (短い下線)。どちらか 1 本だけを、置いた場所で決める (app.css の「入っている切り替え」)
    • 例: ホームの絞り込みのチップ・ライブの種別のタブは並びの中なので短い下線を引かない。番組表の「サブ ch」・映像の上の字幕・ライブの「ライブ」は並びの外なので短い下線
  • 事実はバッジにしない。 番組の事実 (ジャンル・映像・音声) は、一覧の補足と同じく控えた字を「・」でつなぐ
    • 例: 検索の「録る」「録らない (理由)」は行の補足に、観る画面の「端末」は時間帯・局の前に、引き継ぎの「取り込み / 下見」とスキャンの段階は控えた字で
    • 例: 数はその行のほかの数と同じ形 (チャンネルの表のロゴの数は、チャンネル・局の数と同じ太字)
    • 例: 押せる事実は控えた字に印を添える (ヘッダーの「v… が公開されています」は外へ出るリンクの印)

元: 2 強弱は見た目の順位で

色は役目ごとに決め、選んでいる印と状態で混ぜない。

色 使うところ
主の色 (--pico-primary) 線 (下線・左の縦線・オンの短い線)・焦点の線・リンク・見せる画面の地の淡い光 (8 章)
淡い地の上で字にする主の色 (--dp-primary-text) 選んでいるもの・オンの字とアイコン・土曜
本文の字 題名・本文
控えた字 (--dp-muted) 説明・補足 (日時・局・状態)・押せない日 (カレンダー)・選べないチップ
録画の赤 (--dp-rec)・緑 (--dp-ok)・黄 (--dp-warn) 状態だけ。選んでいる印には使わない
案内の青緑 (--dp-info) 案内の通知 (ⓘ) と案内のバッジだけ。主の色の azure と並ぶと見分けが付かないので、緑寄りの cyan にする
  • 読める濃さを保つ。 字は 4.5:1 以上、線とアイコンは 3:1 以上。両方のテーマで、指を乗せた地・選んだ行の上でも確かめる (e2e 48-link-hover・45-buttons)
    • 例: 状態の色を字やアイコンにするときは -text の側 (--dp-rec-text など。淡い地の上で 4.5:1 になるよう濃くした段)
  • 例外: 番組表の日付の日曜は録画の赤の字 (--dp-rec-text)。暦の慣わしに合わせ、土曜の主の色と対にする
  • 色だけに頼らない。 選んでいるものは線と太字、オンは線、状態は印 (点・✕・⚠) と言葉を添える
  • ブラウザが描く部品もテーマに合わせる (color-scheme)
    • 例: プルダウンの一覧・スクロールバー・日付の選び方が、暗いテーマで白く出ない
  • 映像の上はテーマで変えない (舞台はいつも黒い)
    • 例: 字 --dp-on-video・控えた字 --dp-on-video-muted・オン --dp-on-video-on (空色)・戻せないものの 2 回目と「今」 --dp-on-video-danger (赤)・ メニューの面 --dp-video-menu・その影 --dp-video-shadow・つまみの入れたぶん --dp-video-fill・覆い --dp-video-veil・再生位置のバーの CM の所 --dp-video-cm (観たところは --dp-video-cm-played)
    • 例: テーブルトップの下の段 (3 章) は黒い地。右の列はテーマにかかわらず暗いテーマの色で描く (data-theme="dark")。敷く反射の明るさはアンビエントの光と同じ上限で、上の控えた字も 4.5:1 を保つ

元: 1〜4 の全部。部品ごとに、上の決まりをどう当てるかを書く

選んでいるもの (ナビ・サイドバー・タブ・チップ・カレンダー)

Section titled “選んでいるもの (ナビ・サイドバー・タブ・チップ・カレンダー)”

塗らずに、主の色の線と太字で示す。

  • 通常は地を塗らず、字は本文 (ナビも薄くしない)
  • 選んでいるものは主の色の線 (横は下線、縦は左の縦線) と主の色の太字。行全体を塗らない
    • 線の太さは --dp-mark (2px)。選び直しても滑らない横の並び (チップ・番組表の日付) の下線は app.css の .pick-line
    • 例: チップは枠も地も付けず、入っているものは主の色の太字と下線。形と大きさはどの画面も app.css の .chip 1 つ (切り替え・絞り込み・設定の節へ飛ぶ行)
    • 例: 下のタブはアイコンの下に字 (640px から 7 つ並べても同じ形)。選んでいるタブはほかの横の並びと同じく、字の幅いっぱいの下線と主の色の太字。同じ画面に印を 2 つ付けない (タブに出した画面は「その他」に入れない)
    • 例: カレンダーの選んでいる日は下線と字。行のある日は本文の字、無く押せない日は控えた字、今日は赤い点 (「いま」と同じ赤)
    • 例外: 番組表の日付の土日は、選んでいても土日の字の色のまま (線は主の色)
    • 例外: 録画の注意の絞り込み (競合・失敗・録り逃し・CM の判定) は、選んでいても注意の色の字のまま (線は主の色)。状態の色を選んでいる印で消さない
  • 指を乗せたら地をごく薄く (--dp-hover-soft)。選んでいる印とは混ぜない

1 件ずつ件の面に乗せ、線で区切らない。

  • 件は .item-list に並べ、件の間は --dp-list-gap (6px)。1 件の中は近づけ、件の間は離す
    • 例外: ホームは日付の見出しを貼り付けるため、日ごとのまとまり (.t-block) に並べる。件の間は同じ --dp-list-gap
  • 題名がいちばん強く、説明・補足 (日時・局・状態) の順に控える。サムネイルと字の始まりを揃える
    • 字の大きさは、1 段目 (題名) が本文 (--dp-text-base)、2 段目からが控えた字 (.small。--dp-text-sm)。どの一覧も同じで、2 段目を .tiny にしない
  • 右端のボタンはその件の中に置き、位置を件ごとに同じにする。ボタンは平ら
  • 反応は件とボタンで分ける。件は面を一段変え (--dp-item-hover)、ボタンは自分だけ (削除に乗せたときだけ赤い字)
    • 例: 選んでいる・開いている件は左の縦線と太字で、面は塗り替えない
    • 例: 削除・取消・連携の解除のような戻せない操作は 2 回押しで確かめる (writing.md)
  • 指の端末でも押せる範囲を保つ。行は --dp-item-h (64px。指の端末では 72px)、行の中のボタンは高さ 40px 以上
  • 設定の中の一覧も同じ (通知先・テレビのアプリ)。1 行で済む設定の項目だけが地に並ぶ
  • 件の中の線は面の端に付ける
    • 例: 進み具合のバー (録画中・エンコード・ダウンロード・端末への保存) は件の面の下端いっぱいに付け、面の角で切り落とす
  • 「いま」は地に置き、線は引かない。赤い点と赤い太字の時刻、すぐ後ろに控えた字で次の録画までの一言。左は日付の字にそろえる

強弱は層と字の太さで付け、色では塗らない。 色と形は app.css の --dp-btn-* で決め、画面ごとに塗り直さない。

  • 主のボタン (クラス無し・.small・.xs・.save など) は、操作の層の面 (--dp-float) と影のカプセルで、字は本文の色の太字
    • 1 画面に 1 つか 2 つ。保存などは画面の下に置き、中身を送っても見えたままにする
    • 例外: 浮いたものの中では浮かせず太字だけ (--dp-btn-solid と --dp-btn-lift を消す。「3 つの層」)
  • 控え (.secondary・.outline) と字だけ (.ghost) のボタンは平ら。地も枠も影も無く、乗せたら薄い地
  • 戻せないものの 1 回目 (.outline.danger) は控えと同じで、乗せたときだけ赤い字。確定 (.danger) は主と同じ層で赤い字 (赤では塗らない)
  • 入っている切り替え (並びの外の aria-pressed="true") は主の色の字と短い下線。塗らず、浮かせない
  • 形はカプセル (--dp-radius-full)、アイコンだけなら円
    • 例外: 自前の見た目を持つもの (行・メニューの項目・タブ・チップ・リモコン) は名前のクラスを持たせ、浮かせない

線も凹みも付けず、平らな面だけで示す。

  • 面 (--dp-field) は下を透かす薄い色で、地・件の面・ヘッダーのどれの上でも一段濃い (暗いほうは明るい) 面になる
  • ラベルはいつも見せる (中の字だけの案内に頼らない)。打っている間は外側の 2px の主の色の線
    • 例外: 面と地の差は 1.24:1 (明るい)・1.31:1 (暗い) で、境目の 3:1 (WCAG 1.4.11) には届かない。ラベルと焦点の線で補う

メニュー・ダイアログ・検索の候補

Section titled “メニュー・ダイアログ・検索の候補”

どのメニューも同じ形にし、縁を付けず影だけで浮かせる。

  • 形は箱の内側の余白 8px・項目の間 2px・項目の余白 8px × 12px。色だけ層で変わる (ページは --dp-pop、映像の上は暗い面、ダイアログは --dp-surface)
    • 例: ページのメニュー・映像の上のメニュー (再生の設定・速さ)・プルダウンの一覧
  • 中のまとまりも線で分けず、余白と、要るときは控えた字の見出しで分ける
  • 選択肢の選んでいるものは左の縦線と太字、オン/オフの項目は右のスイッチ (.s-switch。映像の上の再生の設定は白い溝のスイッチ)。オン/オフに左の縦線は使わない
    • 例: 番組表のジャンル・映像の上の速さ (選択肢の左の縦線)
    • 例: ホームの「⋯」の表示の切り替え・番組表の狭い画面の「サブチャンネルも表示」
  • ダイアログと番組の詳細のボタンは右下に並べ、主はいちばん右。640px より狭い画面 (下から出る一枚) でも全幅にせず、入りきらない分は上の段へ折り返す (主は下の段の右端)
    • 例: 録画の詳細は「閉じる」と「再生」が下の段の両端、「端末に保存」「ルールを作る」「その他…」はその間か上の段
  • ダイアログ (showModal() で開いた <dialog>。番組の詳細も同じ) は、開いている間は後ろのページを巻かず、Tab で動く焦点もダイアログの外へ出さない (app.css の html:has(dialog:modal))。スクロールバーの幅は空けたままにして、中身を横にずらさない
    • 例外: 番組の詳細 (.detail-modal) は幅を空けない。画面いっぱいに置く箱が空けた幅の分だけ狭くなるため
  • 横に開く子メニューは使わない (指の端末には乗せる操作が無く、よく使う項目までひと手間増える)
  • プルダウンの開いた一覧も、マウスの画面ではメニューと同じ姿 (appearance: base-select)
    • 例外: 指の端末と、対応していないブラウザはブラウザ・OS の選び方のまま (画面の下から大きく出て、指で選びやすい)

種類は頭のアイコンと言葉で示し、地は状態の色で塗らない。

  • アイコンは失敗 ✕・注意 ⚠・成功 ✓・案内 ⓘ。状態の色 (-text の側) はアイコンにだけ使う。字は本文の色
  • ページの中の通知は淡い面 (--dp-subtle)、右下のトーストは操作の層の面 (--dp-pop) と影
  • 左の線は引かない (左の縦線は「選んでいる」の印)

マスは淡い面にし、状態は小さな印と言葉で示す。

  • マスは淡い面 (--dp-subtle。ジャンルのあるマスは --dp-surface にジャンルの色をごく淡く混ぜる)。枠も影も付けない
  • ジャンルは時刻の前の小さな点 (ジャンルのメニューの見本も同じ点)。左の太い線は使わない (左の縦線は「選んでいる」の印)
  • 予約の状態は時刻の後ろの印と、説明の代わりの太字の言葉。印は説明の行が切れる短いマスでも見える
    • 例: 予約済みは主の色の ✓、録画中は赤い ●、競合は赤い ⚠ (競合を予約済みと同じ青にしない)
  • 塗るのはいま動いている録画中だけ (録画の赤をごく淡く)
  • 放送が過ぎた所は、マスの上から地の色を重ねて沈める。表を横切る「いま」の線は引かず、時の列に「いま」の時刻のカプセルだけを置く (分だけ)
    • 例: 終わった番組はマス全体、放送中はマスの上端からいまの位置まで浅く
    • 境目はどの局もいまの時刻の位置にそろえる。マスは 5 分に合わせて置くので、覆いの高さはマスの上端と下端から出し、終わったかどうか (濃さ) は実際の終わりの時刻で決める

ボタンは塗らず、オンは空色の短い線で示す。

  • 明るい映像でも読めるよう、並び全体の裏に端から消えていく黒い影を敷く (ControlBar)。アイコンと字にも小さな影
    • 影とボタンは別の段に置き、間に映像に重ねるものを挟む。重なりは 映像 → 影 → 実況のコメント → 字幕 → ボタン (下の操作バーも右端の列も同じ)。コメントが影に潜って暗く欠けて見えないように
  • オフは白、オンは空色のアイコンと下の短い空色の線。線はほかの所と同じ「入っている切り替え」の短い下線で、色だけ字に合わせる (--dp-on-mark)。オンは押した後も残し、いまの設定がいつでも分かるようにする
    • 例外: 戻せないものの 2 回目と、「今」へ張り付いている「ライブ」「最新」は赤い字と赤い短い線
  • 指を乗せた・キーボードで触っているボタンだけ、ごく薄い地を出す
  • 読み上げには aria-pressed (切り替えと、「今」へ張り付いているか)、マウスには title で名前を渡す。メニューを開くボタン (速さ・⚙) は aria-pressed ではなく aria-expanded で開いているかを言う
    • 例: 速めているときの速さのボタンは数字だけを出し、オンの線は重ねない (数字が「速めている」の印。1 つの意味に印は 1 つ)
  • 映像の上のつまみ (再生位置のバー・音量) は、入れたぶんをほかの所と同じ主の色を透かして (--dp-video-fill。#0172ad の 80%) 塗る。色はそろえ、広く塗っても映像が透けて重くならないようにする。白い字・アイコンはその上で、黒い舞台なら 7:1、白い映像の上でも 3.8:1 (小さな影で補う)。指で押せる高さは 44px 以上 (縦のつまみは幅)
    • 例: 再生位置は細い溝 (4px) に同じ色の丸 (12px)。丸は触っている間だけ出し、押せる所を上へ広げる
    • 例: 音量は太いカプセル (VolumeSlider)。カプセルの全体がつまみで、入れたぶんを下 (横なら左) から塗る。細い溝と丸は無い。空の所には面を敷き (⚙ の中では黒で沈める)、面は塗りの下には敷かない (透かした塗りの向こうが映像になるように)。塗りと空の所の差は黒い舞台の上で 2.5:1 ほどで、3:1 には届かない (目立たなさを選んだ。中のアイコンと値で補う)。中の端にスピーカーのアイコン (0%・半分より下・それより上で替える)、値は変えている間だけ反対の端に出して少し経つと消す。キーとホイールでは塗りが短く (--dp-duration) 追いつき、つまんでいる間はそのまま付いてくる
    • 例: マウスの端末では、音のボタンに乗せると真上に縦のカプセルが浮かぶ (箱には入れず、カプセルの地が映像の上のメニューと同じ面)。⚙ では行の幅の横のカプセルにし、中にアイコン・名前・値をほかの行と同じ並びで置く。つまみは画面に 1 つだけで、⚙ に置くのは音のボタンがつまみを出せないとき (指の端末と、音のボタンを ⚙ に畳んだ狭い枠) だけ

元: 5 動きは CSS で、軽く

動きは CSS だけで作り、時間で動き続けるものは重さの決まりを守る。 時間と曲線は app.css の名前から選び、画面ごとに数字を書かない。

時間 長さ 使うところ
--dp-duration 120ms 色・地・影の移り変わり、押して縮む
--dp-duration-enter 240ms 開く・出てくる (メニュー・ダイアログ・番組の詳細・下から出す一枚・件・画像)
--dp-duration-move 300ms 場所が動く (選んでいる印が滑る・並べ直した行が滑る・浮いた 1 行の出し入れ・スイッチ・縮んだものが戻る)
--dp-duration-exit 150ms 閉じる・消える (待たせないよう短く)

曲線は 3 つです。ふつうは --dp-ease (終わりをゆっくり)、開く・滑る・戻るは --dp-ease-spring (少しだけ行き過ぎて戻るばね)、閉じるは --dp-ease-exit (頭をゆっくり)。

  • JS では動かさない。 requestAnimationFrame・setInterval などで値を毎フレーム書き換える動きと、JS のアニメーションのライブラリは使わない
    • JS で動かすと毎フレームがメインスレッドを通り、合成だけで済む CSS の transform・透明度より重い。メインスレッドが混むと動きも止まる。 測った値 (GPU を切った Chrome、止まって 5 秒、小さな箱 1 つ): CSS の transform 3%、JS の requestAnimationFrame で毎フレーム 7%
    • Web Animations API (element.animate()) は CSS と同じ仕組みで動くが、動きの定義を CSS (app.css の名前) に集めるため使わない
    • JS がするのは、状態の印 (クラス・data-*) を付け外しすることと、行き先を渡すことだけ。動きはそれを受けた CSS がする
    • 例: 読み込めた画像 (下の「件」)・ボタンで送る (下の scroll-behavior)・入れ替わる色 (下の 2 枚の重ね)
    • 対象外: 画面の動きではなく中身を描く処理。映像の描画 (WebGL)・字幕の点滅 (放送が指定した点滅を caption-draw.ts が描く)・データ放送のタイマー・番組表のマスの描き足しと大きさを測る処理
  • 開くものは、少し小さく少しずれた透明な姿からばねで出る (@starting-style)。閉じるときは短く引っ込む
    • 例: メニュー・プルダウンの一覧・ダイアログ・番組の詳細
    • 例外: 番組の詳細は閉じるとすぐ消える。開いた画面が {#if} で外すので、引っ込む間の姿が残らない
  • 選んでいる印は、前の場所から滑る。 並びの疑似要素 1 本を CSS のアンカーで選んでいる項目に付ける。開いた直後はその場に出る
    • 例: タブの下線・ヘッダーのナビの下線・下のタブの印・サイドバーとライブの局の一覧の左の縦線 (縦に滑る)。実況に書く色・位置・大きさもタブと同じ並び
    • 例外: アンカーに対応していないブラウザは項目の中に線を引く (滑らない)
    • 項目がほかのアンカー名を持つときは、包む要素に名前を付ける (例: 640px 未満の下のタブの「その他」。Menu がメニューを置くための名前をボタンに付ける)
  • 並べ直した行は、前の場所から滑る。 JS は並べ直す前と後の行の位置を測り、差を行に渡して (--slide-from) 前の場所にずらした印 (data-slide) を付け、すぐ外すだけ。元の場所へ滑らせるのは CSS の transition (--dp-duration-move・ばね)
    • 例: ライブの「実況」タブ (勢いの順。live/+page.svelte の slide)
    • 使っている一覧は並べ直さない。 指・ポインタが乗っている間とキーボードのフォーカスがある間は待ち、離れて少したってから並べ直す。押そうとした行が別のものに替わらないように
  • 浮くものは、巻いた量に合わせて面と影が出る (animation-timeline: scroll()。番組表では表の箱の縦の軸 --guide と横の軸 --guide-x)。巻けない短いページでは平らなまま
    • 例: ヘッダーとその下の操作の 1 行・番組表の局名と時の列
  • 押すと主のボタンは少し縮み (97%)、離すとばねで戻る。 押した後の状態は色と線で示す
  • ボタンで送るときは、巻く箱の scroll-behavior: smooth で滑らせる。 JS は行き先を渡すだけ (behavior を渡さない)。長さはブラウザに任せる
    • 例: 番組表の時刻へ送るボタン・横にしたスマホのライブで一覧から選んだとき (映像へ戻る)
    • 開いたときの位置合わせと、マウスでつかんで動かすときは behavior: "instant" で止める (動きを作るのではなく止めるだけ)
    • 動きを減らす設定では scroll-behavior: auto に戻す (時間のトークンは効かない)
  • 入れ替わる色は、2 枚を重ねて透明度の transition で入れ替える。 新しい色を裏の 1 枚に描いてから、表の印だけを付け替える。2 枚は足して重ね (plus-lighter)、途中で透明度が抜けないようにする
    • 例: アンビエントの光 (AmbientGlow.svelte)・テーブルトップの反射 (FoldReflection.svelte)。写す繰り返しと 2 枚の入れ替えは共通の部品 (glow-loop.svelte.ts)
  • 件は透明度だけでふわっと出る。 場所は動かさず、読んでいる所をずらさない
    • 例: 読み込めた画像も同じ (#lib/motion.svelte.ts の fadeIn は読み込めた合図で印を付けるだけ)
  • 明暗を選び直したときは、画面全体を前の色から次の色へ透明度だけで移す (--dp-duration-move)。JS は View Transitions を始める合図だけで (theme.svelte.ts の crossfade。種類 theme)、動きは app.css の「明暗の切り替え」
    • 地・件の面・ヘッダーはそれぞれ自分の色を持つので、1 つの要素に transition を付けると、ほかが先に替わって段違いに見える。写しを 1 枚撮って重ねれば、移る間に色を描き直さない
    • 開いたとき (app.html が描く前に当てる) と、端末の設定に従って替わるときは移さない
    • 例外: 映像を流している間はすぐ替える (移る間は写しが映像を止めて見せる)
    • 例外: ソフトウェアで描いている端末 (<html data-lite>) でもすぐ替える (画面いっぱいの 2 枚を重ねるのも CPU がやる)
  • 時間で動き続けるものも使ってよい。ただし重さの決まりを守る。 動き続けると、止まって見ているだけで毎フレーム合成する
    • 例: 録画中の点・動いているバーの光・読み込み中の骨組みの光・ページを移る途中の線
    • 例: アンビエントの光とテーブルトップの反射 (映像の色を 1 秒に 5 回写して描き直す)。動きを減らす設定と data-lite では 1 枚写して止める
    • 見えている間だけ動かす。 画面の外では content-visibility: auto で描くのをやめる (何もしないと、画面の外でも回って CPU を使う)
      • 大きさが中身で決まる箱には contain-intrinsic-size を一緒に書く。書かないと画面の外の箱の高さが分からず、巻くと高さが跳ね、スクロールバーが伸び縮みする (大きさを決めてある箱は要らない)
      • 描くのをやめた箱の中の要素は、CSS のアンカーの基準になれない。選んでいる印をアンカーで付ける並びの中には使わない
      • 行のように中にアンカーの基準がある箱には付けず、動くものだけを包む小さな箱 (点の入れ物・バーそのもの) に付ける
        • 例: ホームの一覧の動いているものの点 (.act-mark) と進み具合のバー (.row-bar)
    • ソフトウェアで描いている端末 (GPU が無い・ハードウェアアクセラレーションを切った) では止める (合成まで CPU がやる)。開いたときに 1 回だけ見分けて <html data-lite> を付ける (#lib/lite.ts)
      • 見分けは小さな WebGL の箱を作って描き手の名前を読み、すぐ捨てる。名前が SwiftShader・llvmpipe・softpipe・Microsoft Basic Render Driver など (新しい名前が分かったら足す) か、WebGL を作れなければソフトウェア
      • 例外: 描き手の名前を読めるのは Chrome・Edge だけ。Firefox は丸めた名前、Safari は「Apple GPU」しか出さないので、見分けられずに回り続ける (止まらない側に倒す)
    • 回す面は小さく、合成だけで済む transform と透明度で動かす。 色・背景の位置・@property の角度は毎フレーム描き直すので重い
    • コマ送りでよいもの (点滅など) は steps() でコマを落とす
    • 巻いた量で動かす (animation-timeline: scroll()・view()) のも軽くする手段の 1 つ。止まって見ている間は何も描き直さない
      • 例: 浮くものの面と影 (上の「浮くもの」)・8 章の見本・画面の紹介・数字・縁の光
    • 動きを減らす設定では止める (下)
    • 測った値 (GPU を切った Chrome、止まって 5 秒、data-lite を付けない素のページに 1 つだけ): 小さい箱の透明度・transform 3%、同じ動きの steps(8) 1.6%、色・背景の位置 (描き直し) 5〜8%、 600×600 の回転 15%、画面の外 1.6%、画面の外 + content-visibility: auto 0%。corp のトップの見本と縁の光は、時間で回すと 6〜19%、巻いた量に変えて 0% (danything/corp#20)。 今の corp は時間で回し、画面の外と data-lite で止めている (danything/corp#22)
  • 動きを減らす設定 (prefers-reduced-motion) では、動きを止めて終わった姿を見せる (app.css の最後)
    • 移り変わりは時間のトークンを 0 に、繰り返すもの (録画中の点など) は動きを付けない (動きを足すのは、動きを減らす設定でも data-lite でもないときだけ)
    • 巻いた量の動き (8 章の見本など) も animation: none にし、組み上がった後の素の姿で見せる。時間のトークンは巻いた量の動きには効かないので、0 にしても止まらない
    • 例: 浮くものはいつも浮いた形 (3 章)。Pico がアニメーションをすぐ終わらせるので、外さなくてよい

元: 1 ふだんは平らに

操作するより伝えるための画面 (ログイン・空の状態・初めての案内・紹介のページ) は、平らから一歩出てよい。 動きの決まり (7 章) はそのまま守り、作業する画面 (一覧・番組表・観る画面・設定) には持ち込まない。

  • 作業する画面の中でも、まだ何も無くて次にすることを案内する所 (空の状態) は見せる画面にする。絞り込んで当てはまらなかっただけの所は、作業の結果なので平らのまま
    • 空の状態の並べ方は、どの一覧も app.css の .empty-state (一覧の場所の真ん中に、太字の一言・控えた字の案内・ボタン)。見せる画面にするときだけ .glow を足す
  • 下の例のうち denpa にまだ無いものは、corp (doany.io) のトップの作り (danything/corp#20) を見る
  • 主の色の淡い光を地に敷いてよい。 radial-gradient の円を中身の裏に敷くだけにし、ぼかし (filter・backdrop-filter) は使わず、動かさない
    • 光は app.css の .glow (濃さ --dp-glow)。光の上でも字は 4.5:1 以上 (控えた字で明るい 5.5:1・暗い 6.2:1)
    • 例: 1 つだけ言う画面 (.solo。ログアウト・テレビのペアリング・開けなかったとき)
    • 例: 空の状態 (ホームの「予約も録画もありません」・切り抜き・ルール・端末に保存した録画・チャンネル)
    • 例: docs のサイトのトップの見出し (site/src/pages/index.astro)
    • corp のトップの光は事業者サイトとして外していて、主の色に紫と青緑を混ぜ、頭の光は巻くと少し流れる。denpa は主の色 1 色で、動かさない
  • 何をするものかは、画像の代わりに小さな実物の見本で見せてよい。 見本は件が巻いて見えてくるのに合わせて組み上がる (件の view-timeline) か、時間で回す (7 章の重さの決まりを守る)。 素の姿を組み上がった後にしておき、対応していないブラウザと動きを減らす設定では、はじめから出そろって見せる。見本は飾りなので読み上げない (aria-hidden)
    • 例: denpa にはまだ無い (corp の事業の件で、デプロイのログが 1 行ずつ出る・車検証の項目が埋まる)
    • 例: 時間で回す場合。corp のトップは、止まって見ていても動いていると分かるよう見本を時間で回し、画面の外と data-lite では止めている (danything/corp#22)
  • 画面の紹介は、説明の横に画面を貼り付けて重ねる。 次の説明に入ると次の画面が重なり、前の画面は少し沈む。 重ねるのは position: sticky (画面を自分の行から最後の行までの升に置く)、沈むのは次の説明の view-timeline
    • 例: denpa にはまだ無い (corp のトップの自社サービス)
  • 数字は巻くと数え上がってよい。 @property の <integer> を view() で動かし、counter() で出す。対応していないブラウザでは書いてある数字をそのまま出す
    • 例: denpa にはまだ無い
  • いちばん大事な操作が 1 画面に 1 つだけなら、その縁に主の色の光を回してよい。 縁の裏に敷いた conic-gradient の rotate だけを、巻いた量か時間で回す
    • 光を回す操作は浮かせない (主の操作の印を、層の代わりに光にする。1 つの意味に印は 1 つ)
    • 例: denpa にはまだ無い (corp のトップの「お問い合わせ」は時間で回している)
    • 時間で回すなら、ボタンの幅ちょうどの正方形の rotate がいちばん軽い (GPU を切った Chrome で 5%。offset-path で縁を走る粒は 9%、@property の角度は描き直しで 13%)
  • 確かめ方: GPU を切った Chrome (--disable-gpu --disable-gpu-compositing) で、巻いている間のフレームの間隔と、止まって 5 秒の CPU を測る。巻いている間はフレームを落とさない。 止まっている間は、巻いた量で動くものは 0%、時間で回すものは 7 章の測った値の軽さ (小さい箱の transform・透明度 1 つで 3% 前後) に収める (GPU を切ると data-lite で止まるので、回したときの重さの測り方は danything/corp#22)

元: 1〜5 の全部。CSS での書き方

値は app.css の名前から選び、画面ごとに新しい値を作らない。

  • 地・線・字の色は --dp-* から選ぶ

  • 字の大きさは --dp-text-* の 6 段から選ぶ。本文は 16px のまま (番組表のマスなど rem で組んだ寸法が変わらない)

    名前 大きさ 使うところ
    --dp-text-2xs 11px アイコン・列・バーに添える字。幅か高さの決まった所だけに使う。アイコンの下の名前 (下のタブ)・列や行に重ねる時刻 (番組表の時の列の「いま」・ホームのバーの先の「いま」)・カレンダーの曜日・番組表のマスの「コピー禁止」
    --dp-text-xs 12px 小さな補足 (.tiny)・日付の見出し・狭い画面のホームの時刻・表と欄の見出し・バッジ・サイドバーの右の値・映像の上の短い通知
    --dp-text-sm 14px 控えた字 (.small)・一覧の 2 段目・チップ・サイドバーの行・カレンダーの日
    --dp-text-base 16px 本文・表
    --dp-text-lg 18px 小見出し (h2・h3)
    --dp-text-xl 24px 画面の見出し (h1)
  • 余白 (padding・margin・gap) は --dp-space-* の 6 段 (4・8・12・16・24・32px) から選ぶ

    • 例外: 段の間の 2px (0.125rem) と 6px (0.375rem) は、詰めた行やアイコンの横のように、段に寄せると 2px ずれて目立つ所だけ数字で書く
    • 例外: 下に浮く 1 行のぶん空ける下の余白や、映像の上の操作バーの影の高さのように、ほかの寸法から決まる値は段にせず数字で書く
  • 状態は color と疑似要素 (::before・::after) の線で作る。線は押す範囲の内側に引き、高さを変えない

  • 影は要るところだけ、控えめな box-shadow で

  • 重なりの段 (z-index) は --dp-z-* から選ぶ。ページの段 (日付の見出し・貼り付く操作の 1 行・下に浮く 1 行・ヘッダー・トースト・ダイアログ) と、映像の舞台の中の段 (映像 → データ放送 → データ放送が小窓に寄せた映像 → 操作バーの影 → 実況のコメント → 字幕・幕 → ボタン) に分ける

    • 名前は部品をまたいで比べる段にだけ付ける。1 つの部品の中だけで前後を決めるもの (面の上の線・裏に敷く 1 枚、番組表の箱の中の局名と時の列) は、どの部品でも中で数字で書く (箱の外の段とは比べない)
  • 何千と並ぶもの (番組表のマス) には、描く層を作る指定 (position・isolation・opacity) を持たせない。ブラウザが画面の一点を調べるたびに層を全部たどり、番組表からほかの画面へ移り始めるのが遅れていた

    • 例: 過ぎた所の覆いはマスの背景の 1 枚目に重ね、控えた字は字の色を透かす
  • 焦点は :focus-visible に外側の 2px の線。マウスで押したときは出さない

    • 例外: 件いっぱいを押せる行・まとめの見出しは内側に引く (outline-offset: -2px)。バーのある件は角で切るので、外側の線が欠ける
    • 本文へ移動のリンクは、キーボードで Tab を押したときだけ左上に操作の層のカプセル (--dp-float・--dp-shadow-pop) で出す。押すと本文 (main) に焦点が移る (+layout.svelte)
  • 日本語は文節で折り返す (body の word-break: auto-phrase・line-break: strict)。段落 (p・li・dd) は最後の行に 1〜2 字だけ残さず (text-wrap: pretty)、見出しは行の長さをそろえる (balance)

    • 狭い升は字ごとに折る (.char-wrap と表の升)。文節は切れないひとかたまりなので、狭い幅では行の右に大きく空き、行が増えて高さが変わる (行数で切る所は読める字が減る)。表の升は文節の長さで列が広がる
    • 例: 番組表のマス・行数で切る一覧の字 (狭い画面の録画の状態)。1 行で「…」と切る題名は折り返さないので関係ない
  • 角は入れ子でそろえる (外の角 = 内の角 + 間の余白)

    • 例: ボタンと下に浮く 1 行 (スマホの形) はカプセル、ヘッダーとその下の操作の 1 行は --dp-bar-radius (中の欄の角 + 余白)、 行・入力欄は --dp-radius、件は --dp-item-radius、メニューの項目は箱の角 − 内側の余白

この決まりに揃っていないところを書きます。直したらここから消します。

  • いまは無い

Amazon のアソシエイトとして、denpa は適格販売により収入を得ています。