diff --git a/CLAUDE.md b/CLAUDE.md index e327088..07d3767 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -146,11 +146,26 @@ bun run build # 画面と CLI(build/cli.js) ## 画面 -トドロクと同じ作り。Pico の出来合いの CSS + `src/app.css` の共通クラス(`.page-container` `.head` `.panel` `.panel-head` `.rows` `.stats` `.stat` `.tag` `.note` `.two` `.stack` `.cluster` `.empty`)+ 画面ごとの scoped style。アイコンは `Icon.svelte`(lucide のパスを持つ)。Web フォントは束ねない。色は Pico の既定と、差し色の葦の緑(`--ui-accent`)。 +見た目は **denpa の「見た目の決まり」**([danything/denpa の docs/design.md](https://github.com/danything/denpa/blob/main/docs/design.md))に従う。合言葉は「塗らない。囲みすぎない。浮かせすぎない。でも、ちゃんと伝わる。」。画面を直す前にそちらを読む(文言は同じ場所の docs/writing.md)。Ashi の側には決まりを写さず、当て方と Ashi だけの違いだけをここに書く。 -文言は日本語。見出しに括弧で説明を付けない(補足は文に開く)。 +作りは Pico CSS(`src/app.css` の頭で `pico` の層に入れて読むので、こちらの規則と画面の ` diff --git a/src/lib/components/panes/DiaryPane.svelte b/src/lib/components/panes/DiaryPane.svelte index b6ca1d5..6c748f5 100644 --- a/src/lib/components/panes/DiaryPane.svelte +++ b/src/lib/components/panes/DiaryPane.svelte @@ -24,35 +24,51 @@ {#if data.day} -
{@html data.html}
+
{@html data.html}
{:else}

まだ日記が無い。

{/if} diff --git a/src/lib/components/panes/OwnerPane.svelte b/src/lib/components/panes/OwnerPane.svelte index 309eb47..9d924f3 100644 --- a/src/lib/components/panes/OwnerPane.svelte +++ b/src/lib/components/panes/OwnerPane.svelte @@ -37,7 +37,7 @@ {#if form?.message}

{form.message}

{/if} {#if form?.added}

渡した: {form.added}

{/if} -
{@html data.html}
+
{@html data.html}
@@ -54,14 +54,13 @@ >で。

{#if data.feeds.length} -
diff --git a/src/lib/lite.ts b/src/lib/lite.ts new file mode 100644 index 0000000..45a4f61 --- /dev/null +++ b/src/lib/lite.ts @@ -0,0 +1,49 @@ +/** + * ソフトウェアで描いている端末を見分けて、`` を付ける(denpa の docs/design.md の「動き」と同じ作り)。 + * + * GPU が無い・ハードウェアアクセラレーションを切った端末では、合成まで CPU がやるので、動き続けるもの + * (歩いているの点・待っている印)を回すだけで CPU を使い続ける。`data-lite` の間は `app.css` の最後で、 + * 動きを減らす設定と同じ止まった姿にする。 + * + * 見分けは開いたときに 1 回だけ。小さな WebGL の箱を作って描き手の名前を読み、すぐ捨てる。 + * 名前を読めるのは Chrome・Edge だけなので、見分けられないときは付けない(回り続ける側に倒す) + */ + +/** ソフトウェアの描き手の名前。新しい名前が分かったら足す */ +const SOFTWARE = [ + "swiftshader", + "llvmpipe", + "softpipe", + "microsoft basic render driver", +]; + +/** WebGL の描き手の名前がソフトウェアのものか。null は WebGL を作れなかった(ソフトウェアとみなす) */ +export function isSoftwareRenderer(name: string | null): boolean { + if (name === null) return true; + const lower = name.toLowerCase(); + return SOFTWARE.some((soft) => lower.includes(soft)); +} + +/** 描き手の名前を読む。WebGL を作れなければ null */ +function rendererName(): string | null { + const gl = document.createElement("canvas").getContext("webgl"); + if (gl === null) return null; + const info = gl.getExtension("WEBGL_debug_renderer_info"); + const name = String( + gl.getParameter(info === null ? gl.RENDERER : info.UNMASKED_RENDERER_WEBGL), + ); + gl.getExtension("WEBGL_lose_context")?.loseContext(); + return name; +} + +/** 見分けて `` に印を付ける。土台のレイアウトの `onMount` から 1 回だけ呼ぶ */ +export function markLite(): void { + let name: string | null; + try { + name = rendererName(); + } catch { + // 箱を作るところで投げる端末は、見分けられない側(回り続ける)に倒す + return; + } + if (isSoftwareRenderer(name)) document.documentElement.dataset["lite"] = ""; +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index e4844d2..209a0c2 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,10 +1,12 @@ Ashi @@ -41,35 +56,23 @@ {#if !data.user} {@render children()} {:else} +
+
{/if} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 070f12a..83129ec 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -46,9 +46,9 @@ いま | Ashi -
+
-
+
今日の歩数 {stepsToday} @@ -60,7 +60,7 @@
通算 {data.walk.steps} 歩
-
+
次に起きる {#if data.paused} @@ -114,7 +114,7 @@
-
+
{subscription ? "頭" @@ -145,7 +145,7 @@ {data.cfg.model} {/if}
-
+
開いている問い {data.counts.open}
-
+
ノート {data.counts.notes}
{#if data.next.length} -
    +
      {#each data.next as q (q.id)} -
    • - {TRACK_LABEL[q.track]} +
    • {q.text} ・ {q.theme}{q.text}{TRACK_LABEL[q.track]}・{q.theme} {q.score.toFixed(2)}
    • @@ -199,12 +198,12 @@
{#if data.notes.length} -
    +
      {#each data.notes as n (n.id)} -
    • +
    • {n.title} - {n.summary} + {n.summary} {when(n.createdAt).slice(5, 10)}{/if} {#if typeof e.title === "string"} {e.title}{/if} - {#if typeof e.field === "string"}{e.field} + {e.field}{/if} {#if Array.isArray(e.proposed) && e.proposed.length}改善案 {e.proposed.join(" / ")}{/if} {#if Array.isArray(e.bridged) && e.bridged.length}橋渡し橋渡し {e.bridged.join(" / ")}{/if} {#if typeof e.error === "string"} {e.error}前回({when(data.past.at(-1)?.at)})からの続き

      {:else if msgs.length === 0} -
      +

      たとえば:

      • 最近何を覚えた?
      • @@ -154,7 +154,7 @@ {:else}
        -
        +
        {@html m.html ?? m.text}
        {#if m.added?.length}

        問いに加えた: {m.added.join(" / ")}

        @@ -169,14 +169,14 @@ {#if busy || waiting}
        -
        +
        考えている(ノートを引いていると数十秒かかる)
        {/if}
        -
        + {#if problem}

        {problem}

        {/if} {#if unsaid.count}

        @@ -211,71 +211,82 @@ .chat { display: flex; flex-direction: column; - gap: 1rem; + gap: var(--dp-space-4); } .log { display: flex; flex-direction: column; - gap: 0.9rem; + gap: var(--dp-space-5); } + /* 前回からの続きの一言。線で区切らず、控えた字を真ん中に置くだけ */ .divider { - display: flex; - align-items: center; - gap: 0.75rem; - } - .divider::before, - .divider::after { - content: ""; - flex: 1; - border-top: 1px solid var(--ui-base-300); + text-align: center; } .hello ul { - margin: 0.25rem 0 0; + margin: var(--dp-space-1) 0 0; padding-left: 1.2rem; } + /* + * 発言は地にそのまま置き、吹き出しで囲まない。自分(持ち主)の発言は、塗らずに左の縦線で示す + * (左の縦線は「自分のもの」の印)。Ashi の返事は頭にロゴを置く + */ .me { - align-self: flex-end; - max-width: 80%; - border-radius: 1rem 1rem 0.25rem 1rem; - background: var(--pico-primary-background); - padding: 0.6rem 0.9rem; - color: var(--pico-primary-inverse); + position: relative; + max-width: 92%; + margin-inline-start: calc(1.6rem + var(--dp-space-3)); + padding-inline-start: var(--dp-space-3); white-space: pre-wrap; } + .me::before { + content: ""; + position: absolute; + inset-block: 0.25rem; + left: 0; + width: var(--dp-mark); + border-radius: var(--dp-mark); + background: var(--pico-primary); + } .ashi { display: flex; align-items: flex-start; - gap: 0.6rem; + gap: var(--dp-space-3); max-width: 92%; } .avatar { display: inline-flex; - margin-top: 0.2rem; color: var(--pico-primary); } .bubble { + display: flex; min-width: 0; - border-top-left-radius: 0.25rem; - padding: 0.75rem 1rem; + flex-direction: column; + gap: var(--dp-space-2); + } + .bubble.cluster { + flex-direction: row; } - /* 入力欄は画面の下に貼り付ける */ + /* + * 入力欄は画面の下に貼り付ける。発言がその裏を流れるので、操作の層の面(`.float`)に乗せる。 + * 中の「送る」は浮かせず太字だけ(浮いたものの上に、さらに浮いたものを重ねない) + */ .composer { position: sticky; /* ページ下の余白と同じ位置に貼り付ける(違うと、いちばん下まで来たときに入力欄がずれる) */ - bottom: var(--main-pad-bottom); + bottom: calc(var(--dp-gutter) + var(--dp-tabbar)); + z-index: var(--dp-z-dock); display: flex; flex-direction: column; - gap: 0.4rem; - padding: 0.5rem; + gap: var(--dp-space-2); + padding: var(--dp-bar-pad); } .unsaid { margin: 0; - padding: 0 0.25rem; + padding: 0 var(--dp-space-1); } .line { display: flex; align-items: flex-end; - gap: 0.5rem; + gap: var(--dp-space-2); } .line textarea { flex: 1; @@ -283,12 +294,12 @@ resize: none; /* 書いた分だけ伸びる(対応していないブラウザでは 1 行のまま中でスクロール) */ field-sizing: content; - min-height: 2.6rem; + min-height: 2.625rem; max-height: 10rem; } .line button { flex: none; margin: 0; - min-height: 2.6rem; + min-height: 2.625rem; } diff --git a/src/routes/context/+page.svelte b/src/routes/context/+page.svelte index 18fb5c5..d501fa6 100644 --- a/src/routes/context/+page.svelte +++ b/src/routes/context/+page.svelte @@ -40,10 +40,10 @@

        あなたに伝えていない訂正({data.corrections.length})

        - 話題が合えば話す + 話題が合えば話す
        -
          - {#each data.corrections as c (c.id)}
        • +
            + {#each data.corrections as c (c.id)}
          • {when(c.at).slice(5, 10)} {c.text}
          • {/each} @@ -69,21 +69,23 @@

            次の一歩

            - 前回の内省で決めた + 前回の内省で決めた
            {#if data.intentions.length} -
              - {#each data.intentions as i, k (k)}
            • {i}
            • {/each} +
                + {#each data.intentions as i, k (k)}
              • + {i} +
              • {/each}
              {:else}

              まだ無い。

              {/if}
            -
            +

            よそ者との対話

            - {data.stranger.enabled ? `内省の前に ${data.stranger.model} と ${data.stranger.turns} 往復` : "止めている"} {#if data.dialogues.length} -
            +
            {#each data.dialogues as d (d.at)} -
            +
            - {d.field} {d.takeaway || "持ち帰りは無し"} - {when(d.at)} + {d.field}・{when(d.at)} -
            +
            {#each d.turns as t, k (k)}

            {t.by === "ashi" ? "Ashi" : d.field}

            橋の候補({data.bridges.length})

            - 個性で思いついた、まだ推測の持ち帰り + 個性で思いついた、まだ推測の持ち帰り
            {#if data.bridges.length} -
              +
                {#each data.bridges.slice(0, BRIDGES_SHOWN) as b (b.id)} -
              • - {b.toTheme} - {b.idea} +
              • + {b.idea}{b.toTheme} {#if b.fromNoteId}元のノート{/if} @@ -160,11 +162,12 @@ 残り {data.bridges.length - BRIDGES_SHOWN} 件 -
                  +
                    {#each data.bridges.slice(BRIDGES_SHOWN) as b (b.id)} -
                  • - {b.toTheme} - {b.idea} +
                  • + {b.idea}{b.toTheme} {#if b.fromNoteId}元のノート{/if} @@ -194,7 +197,7 @@
                    {data.config}
            -
            +

            学びをリセット

            @@ -223,10 +226,8 @@ class="confirm" autocomplete="off" /> - 学びをリセットする
            @@ -242,17 +243,10 @@
            diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index 023a255..872e9a9 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -6,60 +6,53 @@ ログイン | Ashi -
            -
            -
            Ashi
            -

            AIに、足を。

            -

            - 自分で歩き出し、行き先を選び、寄り道し、疲れたら休む AI です。 + +

            +
            Ashi
            +

            AIに、足を。

            +

            + 自分で歩き出し、行き先を選び、寄り道し、疲れたら休む AI です。 +

            + {#if data.configured} + + Microsoft でログイン + +

            + 持ち主のテナントで、アプリロールを割り当てられた人だけが入れます。

            - {#if data.configured} - - Microsoft でログイン - -

            - 持ち主のテナントで、アプリロールを割り当てられた人だけが入れます。 -

            - {:else} -

            - Entra ID - のログインが設定されていません(ENTRA_TENANT_ID・ENTRA_CLIENT_ID・ENTRA_CLIENT_SECRET・SESSION_SECRET)。 -

            - {/if} -
            + {:else} +

            + Entra ID + のログインが設定されていません(ENTRA_TENANT_ID・ENTRA_CLIENT_ID・ENTRA_CLIENT_SECRET・SESSION_SECRET)。 +

            + {/if}
            diff --git a/src/routes/map/+page.svelte b/src/routes/map/+page.svelte index 378a626..5837374 100644 --- a/src/routes/map/+page.svelte +++ b/src/routes/map/+page.svelte @@ -68,15 +68,15 @@ function palette() { return { - bg: css("--ui-surface") || "#fff", - owner: css("--ui-info") || "#1d5fb8", + bg: css("--dp-bg") || "#fff", + owner: css("--pico-primary") || "#0172ad", self: css("--ui-accent") || "#5f8f3e", - closed: css("--ui-base-300") || "#dde2ea", - note: css("--ui-muted") || "#5d6778", - origin: css("--ui-warn") || "#9a5a06", + closed: css("--dp-line-strong") || "#cfd5e2", + note: css("--dp-muted") || "#555e70", + origin: css("--dp-warn") || "#c79400", idea: "#8b6fd6", - me: css("--ui-err") || "#b3261e", - line: css("--ui-muted") || "#5d6778", + me: css("--dp-rec") || "#d93526", + line: css("--dp-muted") || "#555e70", }; } type Palette = ReturnType; @@ -347,7 +347,7 @@
            -
            +
            -
            +
            {#each LEGEND_NODES as [k, label] (k)} {label} {/each} @@ -406,19 +406,17 @@
            {#if selected} -