feat: add posimai-ui design system, update template and docs
This commit is contained in:
parent
4c3308ccdd
commit
c8a141ba22
20
AGENTS.md
20
AGENTS.md
|
|
@ -183,6 +183,26 @@ npm run deploy
|
||||||
| posimai-clean | `posimai-clean/` | テキストクリーナー(静的) |
|
| posimai-clean | `posimai-clean/` | テキストクリーナー(静的) |
|
||||||
| posimai-tech-events | `posimai-tech-events/` | IT イベント情報(Doorkeeper/connpass RSS) |
|
| posimai-tech-events | `posimai-tech-events/` | IT イベント情報(Doorkeeper/connpass RSS) |
|
||||||
| posimai-think | `posimai-think/` | AI 思考整理アシスタント(Gemini 直接呼び出し) |
|
| posimai-think | `posimai-think/` | AI 思考整理アシスタント(Gemini 直接呼び出し) |
|
||||||
|
| posimai-ui | `posimai-ui/` | 共有デザインシステム(CSS/JS)— 全静的アプリが参照するインフラ |
|
||||||
|
|
||||||
|
## 共有デザインシステム(posimai-ui)
|
||||||
|
|
||||||
|
- URL: `https://posimai-ui.vercel.app/v1/base.css` / `https://posimai-ui.vercel.app/v1/base.js`
|
||||||
|
- リポジトリ: `posimai-ui/`(GitHub: `posimai/posimai-ui` private)
|
||||||
|
- **全静的アプリが `<link>` と `<script defer>` で参照する。インラインに CSS/JS を書かない**
|
||||||
|
- バージョン方針: `/v1/` = 後方互換パッチのみ。破壊的変更は `/v2/` に切り上げ(全アプリのタグ更新が必要)
|
||||||
|
- アプリ側の必須設定: `<html data-app-id="posimai-myapp">` でアプリ固有のローカルストレージキーを指定
|
||||||
|
- `_template-minimal/index.html` は既に posimai-ui を参照するよう更新済み
|
||||||
|
|
||||||
|
### posimai-ui を既存アプリに適用する手順
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- <head> 内: インライン <style> の大半を削除し、以下に置き換える -->
|
||||||
|
<link rel="stylesheet" href="https://posimai-ui.vercel.app/v1/base.css">
|
||||||
|
|
||||||
|
<!-- </body> 直前: インライン <script> の共通部分を削除し、以下に置き換える -->
|
||||||
|
<script src="https://posimai-ui.vercel.app/v1/base.js" defer></script>
|
||||||
|
```
|
||||||
|
|
||||||
## Synology バックエンド(server.js)
|
## Synology バックエンド(server.js)
|
||||||
- ファイル: `server.js`(ルートに配置、git 管理外)
|
- ファイル: `server.js`(ルートに配置、git 管理外)
|
||||||
|
|
|
||||||
33
CLAUDE.md
33
CLAUDE.md
|
|
@ -178,6 +178,39 @@ npm run deploy
|
||||||
| posimai-clean | `posimai-clean/` | テキストクリーナー(静的) |
|
| posimai-clean | `posimai-clean/` | テキストクリーナー(静的) |
|
||||||
| posimai-tech-events | `posimai-tech-events/` | IT イベント情報(Doorkeeper/connpass RSS) |
|
| posimai-tech-events | `posimai-tech-events/` | IT イベント情報(Doorkeeper/connpass RSS) |
|
||||||
| posimai-think | `posimai-think/` | AI 思考整理アシスタント(Gemini 直接呼び出し) |
|
| posimai-think | `posimai-think/` | AI 思考整理アシスタント(Gemini 直接呼び出し) |
|
||||||
|
| posimai-ui | `posimai-ui/` | 共有デザインシステム(CSS/JS)— 全静的アプリが参照するインフラ |
|
||||||
|
|
||||||
|
## 共有デザインシステム(posimai-ui)
|
||||||
|
|
||||||
|
- URL: `https://posimai-ui.vercel.app/v1/base.css` / `https://posimai-ui.vercel.app/v1/base.js`
|
||||||
|
- リポジトリ: `posimai-ui/`(GitHub: `posimai/posimai-ui` private)
|
||||||
|
- **全静的アプリが `<link>` と `<script defer>` で参照する。インラインに CSS/JS を書かない**
|
||||||
|
- バージョン方針: `/v1/` = 後方互換パッチのみ。破壊的変更は `/v2/` に切り上げ(全アプリのタグ更新が必要)
|
||||||
|
- アプリ側の必須設定: `<html data-app-id="posimai-myapp">` でアプリ固有のローカルストレージキーを指定
|
||||||
|
- `_template-minimal/index.html` は既に posimai-ui を参照するよう更新済み
|
||||||
|
|
||||||
|
### posimai-ui を更新する手順
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd C:/Users/maita/posimai-project/posimai-ui
|
||||||
|
# ファイルを編集後:
|
||||||
|
git add .
|
||||||
|
git commit -m "fix/feat: 変更内容"
|
||||||
|
npm run deploy # = git push gitea main && git push github main
|
||||||
|
# → Vercel が自動デプロイ、全アプリに即時反映
|
||||||
|
```
|
||||||
|
|
||||||
|
### posimai-ui を既存アプリに適用する手順
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- <head> 内: インライン <style> の大半を削除し、以下に置き換える -->
|
||||||
|
<link rel="stylesheet" href="https://posimai-ui.vercel.app/v1/base.css">
|
||||||
|
|
||||||
|
<!-- </body> 直前: インライン <script> の共通部分を削除し、以下に置き換える -->
|
||||||
|
<script src="https://posimai-ui.vercel.app/v1/base.js" defer></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Synology バックエンド(server.js)
|
## Synology バックエンド(server.js)
|
||||||
- ファイル: `server.js`(ルートに配置、git 管理外)
|
- ファイル: `server.js`(ルートに配置、git 管理外)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ja">
|
<html lang="ja" data-app-id="APP_ID">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
|
@ -27,70 +27,10 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://posimai-ui.vercel.app/v1/base.css">
|
||||||
<script src="https://unpkg.com/lucide@0.344.0/dist/umd/lucide.min.js"></script>
|
<script src="https://unpkg.com/lucide@0.344.0/dist/umd/lucide.min.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
||||||
:root, [data-theme="dark"] {
|
|
||||||
--bg:#0D0D0D; --surface:#1A1A1A; --surface2:#252525; --border:#2D2D2D;
|
|
||||||
--text:#F3F4F6; --text2:#9CA3AF; --text3:#6B7280;
|
|
||||||
--accent:#6EE7B7; --accent-dim:rgba(110,231,183,0.1); --accent-border:rgba(110,231,183,0.25);
|
|
||||||
--header-bg:rgba(13,13,13,0.85);
|
|
||||||
--overlay-bg:rgba(0,0,0,0.6); --shadow-lg:0 8px 32px rgba(0,0,0,0.5); --shadow-sm:0 2px 8px rgba(0,0,0,0.3);
|
|
||||||
color-scheme: dark;
|
|
||||||
}
|
|
||||||
[data-theme="light"] {
|
|
||||||
--bg:#F9FAFB; --surface:#FFFFFF; --surface2:#F3F4F6; --border:#E5E7EB;
|
|
||||||
--text:#111827; --text2:#4B5563; --text3:#9CA3AF;
|
|
||||||
--accent:#059669; --accent-dim:rgba(5,150,105,0.08); --accent-border:rgba(5,150,105,0.2);
|
|
||||||
--header-bg:rgba(249,250,251,0.85);
|
|
||||||
--overlay-bg:rgba(0,0,0,0.4); --shadow-lg:0 8px 32px rgba(0,0,0,0.15); --shadow-sm:0 2px 8px rgba(0,0,0,0.08);
|
|
||||||
color-scheme: light;
|
|
||||||
}
|
|
||||||
:root { --radius:12px; --radius-sm:8px; --header-h:52px; --ease:cubic-bezier(.4,0,.2,1); --dur:0.2s; }
|
|
||||||
body { font-family: 'Inter', -apple-system, sans-serif; background: var(--bg); color: var(--text); font-size: 14px; line-height: 1.6; -webkit-font-smoothing: antialiased; }
|
|
||||||
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
|
|
||||||
:focus:not(:focus-visible) { outline: none; }
|
|
||||||
|
|
||||||
/* Header */
|
|
||||||
.header { height: var(--header-h); display: flex; align-items: center; gap: 8px; padding: 0 16px; background: var(--header-bg); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 100; }
|
|
||||||
.header-brand { display: flex; align-items: center; gap: 8px; flex: 1; }
|
|
||||||
.header-dot { width: 7px; height: 7px; background: var(--accent); border-radius: 50%; }
|
|
||||||
.header-title { font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }
|
|
||||||
.icon-btn { background: none; border: none; cursor: pointer; color: var(--text2); min-width: 44px; min-height: 44px; display: flex; align-items: center; justify-content: center; border-radius: var(--radius-sm); transition: color var(--dur), background var(--dur); flex-shrink: 0; margin: 0 -8px; }
|
|
||||||
.icon-btn:hover { color: var(--text); background: var(--surface2); }
|
|
||||||
|
|
||||||
/* Main content */
|
|
||||||
main { padding: 24px 20px calc(40px + env(safe-area-inset-bottom)); max-width: 860px; width: 100%; margin: 0 auto; }
|
|
||||||
|
|
||||||
/* Settings panel */
|
|
||||||
.settings-panel { position: fixed; top: 0; right: 0; bottom: 0; width: 280px; background: var(--surface); border-left: 1px solid var(--border); display: flex; flex-direction: column; z-index: 300; transform: translateX(100%); transition: transform .25s var(--ease); }
|
|
||||||
.settings-panel.open { transform: translateX(0); box-shadow: var(--shadow-lg); }
|
|
||||||
.settings-panel-header { height: var(--header-h); display: flex; align-items: center; justify-content: space-between; padding: 0 12px 0 16px; flex-shrink: 0; border-bottom: 1px solid var(--border); }
|
|
||||||
.settings-panel-title { font-size: 14px; font-weight: 600; }
|
|
||||||
.settings-panel-body { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 24px; }
|
|
||||||
.settings-group-label { font-size: 10px; font-weight: 600; color: var(--text3); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 10px; }
|
|
||||||
.settings-item { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border); }
|
|
||||||
.settings-item:last-child { border-bottom: none; }
|
|
||||||
.settings-item-label { font-size: 13px; color: var(--text); }
|
|
||||||
.theme-selector { display: flex; gap: 4px; background: var(--surface2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 3px; }
|
|
||||||
.theme-btn { flex: 1; display: flex; align-items: center; justify-content: center; gap: 5px; padding: 6px 8px; border-radius: 6px; border: none; cursor: pointer; font-family: inherit; font-size: 11px; font-weight: 500; color: var(--text3); background: none; transition: all var(--dur) var(--ease); white-space: nowrap; }
|
|
||||||
.theme-btn:hover { color: var(--text2); }
|
|
||||||
.theme-btn.active { background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }
|
|
||||||
.overlay { display: none; position: fixed; inset: 0; background: var(--overlay-bg); backdrop-filter: blur(2px); z-index: 150; }
|
|
||||||
.overlay.open { display: block; }
|
|
||||||
|
|
||||||
/* Common components */
|
|
||||||
.btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 14px; border-radius: var(--radius-sm); border: none; cursor: pointer; font-family: inherit; font-size: 13px; font-weight: 500; transition: all var(--dur) var(--ease); }
|
|
||||||
.btn-primary { background: var(--accent); color: #0D0D0D; }
|
|
||||||
.btn-primary:hover { opacity: .88; }
|
|
||||||
.btn-ghost { background: var(--surface2); color: var(--text2); border: 1px solid var(--border); }
|
|
||||||
.btn-ghost:hover { color: var(--text); background: var(--surface); }
|
|
||||||
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; }
|
|
||||||
.section-label { font-size: 11px; font-weight: 600; color: var(--text3); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 12px; }
|
|
||||||
#toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 16px; font-size: 13px; font-weight: 500; z-index: 9999; box-shadow: var(--shadow-lg); transition: opacity .2s, transform .2s; opacity: 0; pointer-events: none; white-space: nowrap; }
|
|
||||||
#toast.show { opacity: 1; transform: translateX(-50%) translateY(-4px); }
|
|
||||||
|
|
||||||
/* ── APP-SPECIFIC STYLES: ここにアプリ固有のスタイルを追加 ── */
|
/* ── APP-SPECIFIC STYLES: ここにアプリ固有のスタイルを追加 ── */
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -145,54 +85,8 @@
|
||||||
|
|
||||||
<div id="toast" role="status" aria-live="polite"></div>
|
<div id="toast" role="status" aria-live="polite"></div>
|
||||||
|
|
||||||
<script>
|
<script src="https://posimai-ui.vercel.app/v1/base.js" defer></script>
|
||||||
// ── Theme ──
|
<script defer>
|
||||||
const STORE_KEY = 'APP_ID';
|
|
||||||
function applyTheme(val) {
|
|
||||||
const dark = val === 'dark' || (val === 'system' && matchMedia('(prefers-color-scheme:dark)').matches);
|
|
||||||
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
|
|
||||||
document.documentElement.setAttribute('data-theme-pref', val);
|
|
||||||
document.querySelectorAll('[data-theme-val]').forEach(b => b.classList.toggle('active', b.dataset.themeVal === val));
|
|
||||||
}
|
|
||||||
document.querySelectorAll('[data-theme-val]').forEach(b => b.addEventListener('click', () => {
|
|
||||||
const val = b.dataset.themeVal;
|
|
||||||
localStorage.setItem(STORE_KEY + '-theme', val);
|
|
||||||
applyTheme(val);
|
|
||||||
}));
|
|
||||||
applyTheme(localStorage.getItem(STORE_KEY + '-theme') || 'system');
|
|
||||||
|
|
||||||
// ── Settings panel ──
|
|
||||||
const settingsPanel = document.getElementById('settingsPanel');
|
|
||||||
const overlay = document.getElementById('overlay');
|
|
||||||
document.getElementById('settingsBtn').addEventListener('click', () => {
|
|
||||||
settingsPanel.classList.add('open');
|
|
||||||
overlay.classList.add('open');
|
|
||||||
document.getElementById('settingsBtn').setAttribute('aria-expanded', 'true');
|
|
||||||
});
|
|
||||||
function closeSettings() {
|
|
||||||
settingsPanel.classList.remove('open');
|
|
||||||
overlay.classList.remove('open');
|
|
||||||
document.getElementById('settingsBtn').setAttribute('aria-expanded', 'false');
|
|
||||||
}
|
|
||||||
document.getElementById('settingsCloseBtn').addEventListener('click', closeSettings);
|
|
||||||
overlay.addEventListener('click', closeSettings);
|
|
||||||
|
|
||||||
// ── Toast ──
|
|
||||||
function showToast(msg, duration = 2500) {
|
|
||||||
const el = document.getElementById('toast');
|
|
||||||
el.textContent = msg;
|
|
||||||
el.classList.add('show');
|
|
||||||
setTimeout(() => el.classList.remove('show'), duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── PWA Service Worker ──
|
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
navigator.serviceWorker.register('/sw.js').catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Lucide icons ──
|
|
||||||
lucide.createIcons();
|
|
||||||
|
|
||||||
// ── APP-SPECIFIC JS: ここにアプリ固有のロジックを追加 ──
|
// ── APP-SPECIFIC JS: ここにアプリ固有のロジックを追加 ──
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue