2026-04-23 07:17:45 +00:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html lang="ja" data-app-id="posimai-chronicle">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<meta name="robots" content="noindex, nofollow">
|
|
|
|
|
|
<script>
|
|
|
|
|
|
(function () {
|
|
|
|
|
|
var t = localStorage.getItem('posimai-chronicle-theme') || 'system';
|
|
|
|
|
|
var dark = t === 'dark' || (t === 'system' && matchMedia('(prefers-color-scheme:dark)').matches);
|
|
|
|
|
|
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
|
|
|
|
|
|
document.documentElement.setAttribute('data-theme-pref', t);
|
|
|
|
|
|
var p = new URLSearchParams(location.search);
|
|
|
|
|
|
var tk = p.get('token');
|
|
|
|
|
|
if (tk) {
|
|
|
|
|
|
localStorage.setItem('posimai_token', tk);
|
|
|
|
|
|
p.delete('token');
|
2026-04-23 08:10:42 +00:00
|
|
|
|
history.replaceState({}, '', location.pathname + (p.toString() ? '?' + p.toString() : '') + location.hash);
|
2026-04-23 07:17:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
})();
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
2026-04-23 08:10:42 +00:00
|
|
|
|
<meta name="description" content="開発ログ・気分・学びを束ねて Journal 下書きを生成する">
|
2026-04-23 07:17:45 +00:00
|
|
|
|
<meta name="color-scheme" content="dark light">
|
|
|
|
|
|
<meta name="theme-color" content="#0D0D0D" media="(prefers-color-scheme: dark)">
|
|
|
|
|
|
<meta name="theme-color" content="#F9FAFB" media="(prefers-color-scheme: light)">
|
|
|
|
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
|
|
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
|
|
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
2026-04-23 08:10:42 +00:00
|
|
|
|
<meta name="apple-mobile-web-app-title" content="Chronicle">
|
2026-04-23 07:17:45 +00:00
|
|
|
|
<link rel="manifest" href="/manifest.json">
|
|
|
|
|
|
<link rel="icon" type="image/svg+xml" href="/logo.svg">
|
|
|
|
|
|
<link rel="apple-touch-icon" href="/logo.svg">
|
|
|
|
|
|
<title>Posimai Chronicle</title>
|
|
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
|
|
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
|
|
|
|
<link href="https://fonts.googleapis.com/css2?family=Geist: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" integrity="sha384-tTkFttkBclaU1cloKwOi9xk3pbao3VZxTjLNBt8iFABWDBQibbAbWpVmO28zMuxq" crossorigin="anonymous"></script>
|
|
|
|
|
|
<style>
|
2026-04-23 22:53:40 +00:00
|
|
|
|
/* base.css の main グローバルルールを打ち消す */
|
|
|
|
|
|
#main-content { padding: 0; max-width: none; width: 100%; margin: 0; }
|
|
|
|
|
|
|
2026-04-23 08:10:42 +00:00
|
|
|
|
/* ── レイアウト ── */
|
|
|
|
|
|
.wrap { max-width: 960px; margin: 0 auto; padding: 20px 16px calc(56px + env(safe-area-inset-bottom)); }
|
|
|
|
|
|
.two-col { display: grid; gap: 12px; grid-template-columns: 1fr; }
|
|
|
|
|
|
@media (min-width: 800px) { .two-col { grid-template-columns: 1fr 1fr; } }
|
|
|
|
|
|
|
|
|
|
|
|
/* ── ピリオドバー ── */
|
|
|
|
|
|
.period-bar {
|
|
|
|
|
|
display: flex; align-items: center; gap: 8px;
|
|
|
|
|
|
background: var(--surface); border: 1px solid var(--border);
|
|
|
|
|
|
border-radius: var(--radius); padding: 10px 14px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.period-bar .period-label { font-size: 11px; color: var(--text3); }
|
|
|
|
|
|
.period-bar .period-range { font-size: 13px; color: var(--text); font-weight: 500; flex: 1; }
|
|
|
|
|
|
.period-tabs { display: flex; gap: 4px; margin-left: auto; }
|
|
|
|
|
|
.period-tab {
|
|
|
|
|
|
font-size: 11px; padding: 4px 10px; border-radius: 6px;
|
|
|
|
|
|
border: 1px solid var(--border); background: none; color: var(--text2);
|
|
|
|
|
|
cursor: pointer; transition: all 0.15s;
|
|
|
|
|
|
}
|
|
|
|
|
|
.period-tab.active { background: var(--accent-dim); border-color: var(--accent-border); color: var(--accent); }
|
|
|
|
|
|
|
|
|
|
|
|
/* ── 入力カード ── */
|
|
|
|
|
|
.input-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; }
|
|
|
|
|
|
.field { margin-bottom: 14px; }
|
|
|
|
|
|
.field:last-child { margin-bottom: 0; }
|
|
|
|
|
|
.field-label { font-size: 11px; color: var(--text3); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; display: block; }
|
|
|
|
|
|
.field textarea {
|
|
|
|
|
|
width: 100%; box-sizing: border-box;
|
|
|
|
|
|
background: var(--surface2); border: 1px solid var(--border);
|
|
|
|
|
|
border-radius: var(--radius-sm); color: var(--text);
|
|
|
|
|
|
font-family: inherit; font-size: 13px; line-height: 1.6;
|
|
|
|
|
|
padding: 10px 12px; resize: vertical; min-height: 80px;
|
|
|
|
|
|
transition: border-color 0.15s;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field textarea:focus { outline: none; border-color: var(--accent-border); }
|
|
|
|
|
|
.field textarea::placeholder { color: var(--text3); }
|
|
|
|
|
|
|
|
|
|
|
|
/* ── スライダーグループ ── */
|
|
|
|
|
|
.sliders { display: grid; gap: 10px; }
|
|
|
|
|
|
.slider-row { display: flex; align-items: center; gap: 10px; }
|
|
|
|
|
|
.slider-name { font-size: 12px; color: var(--text2); width: 60px; flex-shrink: 0; }
|
|
|
|
|
|
.slider-input {
|
|
|
|
|
|
flex: 1; -webkit-appearance: none; appearance: none;
|
|
|
|
|
|
height: 4px; border-radius: 2px; background: var(--border);
|
|
|
|
|
|
outline: none; cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
.slider-input::-webkit-slider-thumb {
|
|
|
|
|
|
-webkit-appearance: none; width: 16px; height: 16px;
|
|
|
|
|
|
border-radius: 50%; background: var(--accent); cursor: pointer;
|
|
|
|
|
|
box-shadow: 0 0 0 3px var(--accent-dim);
|
|
|
|
|
|
}
|
|
|
|
|
|
.slider-input::-moz-range-thumb {
|
|
|
|
|
|
width: 16px; height: 16px; border: none;
|
|
|
|
|
|
border-radius: 50%; background: var(--accent); cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
.slider-val { font-size: 12px; color: var(--accent); font-weight: 600; width: 20px; text-align: right; }
|
|
|
|
|
|
|
|
|
|
|
|
/* ── 生成ボタンエリア ── */
|
|
|
|
|
|
.generate-area { margin: 12px 0; text-align: center; }
|
|
|
|
|
|
.btn-generate {
|
|
|
|
|
|
display: inline-flex; align-items: center; gap: 8px;
|
|
|
|
|
|
padding: 12px 28px; border-radius: var(--radius-sm);
|
|
|
|
|
|
background: var(--accent); color: #0D0D0D;
|
|
|
|
|
|
font-size: 14px; font-weight: 600; border: none; cursor: pointer;
|
|
|
|
|
|
transition: opacity 0.15s, transform 0.1s;
|
|
|
|
|
|
}
|
|
|
|
|
|
.btn-generate:hover { opacity: 0.9; }
|
|
|
|
|
|
.btn-generate:active { transform: scale(0.98); }
|
|
|
|
|
|
.btn-generate:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
|
|
|
|
|
|
|
|
|
|
|
|
/* ── 下書きカード ── */
|
|
|
|
|
|
.draft-card {
|
|
|
|
|
|
background: var(--surface); border: 1px solid var(--border);
|
|
|
|
|
|
border-radius: var(--radius); padding: 16px; margin-top: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.draft-header { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; }
|
|
|
|
|
|
.draft-title { font-size: 13px; font-weight: 600; color: var(--text); flex: 1; }
|
|
|
|
|
|
.draft-actions { display: flex; gap: 6px; }
|
|
|
|
|
|
|
|
|
|
|
|
/* ── 下書きプレビュー ── */
|
|
|
|
|
|
.draft-body {
|
|
|
|
|
|
font-size: 13px; line-height: 1.75; color: var(--text2);
|
|
|
|
|
|
min-height: 120px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.draft-body h1, .draft-body h2, .draft-body h3 {
|
|
|
|
|
|
color: var(--text); font-weight: 600; margin: 16px 0 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.draft-body h2 { font-size: 14px; }
|
|
|
|
|
|
.draft-body h3 { font-size: 13px; }
|
|
|
|
|
|
.draft-body p { margin: 0 0 10px; }
|
|
|
|
|
|
.draft-body strong { color: var(--text); }
|
|
|
|
|
|
|
|
|
|
|
|
/* ── 空状態 ── */
|
|
|
|
|
|
.empty-state {
|
|
|
|
|
|
display: flex; flex-direction: column; align-items: center;
|
|
|
|
|
|
justify-content: center; gap: 10px; padding: 40px 20px;
|
|
|
|
|
|
color: var(--text3); text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.empty-state p { font-size: 12px; line-height: 1.6; margin: 0; }
|
|
|
|
|
|
|
|
|
|
|
|
/* ── 認証バナー ── */
|
|
|
|
|
|
.auth-banner {
|
|
|
|
|
|
background: var(--surface); border: 1px solid var(--border);
|
|
|
|
|
|
border-radius: var(--radius); padding: 14px 16px;
|
|
|
|
|
|
display: flex; align-items: center; gap: 12px;
|
|
|
|
|
|
margin-bottom: 12px; font-size: 13px; color: var(--text2);
|
|
|
|
|
|
}
|
|
|
|
|
|
.auth-banner a { color: var(--accent); text-decoration: none; font-weight: 500; }
|
|
|
|
|
|
.auth-banner.hidden { display: none; }
|
|
|
|
|
|
|
|
|
|
|
|
/* ── ローディングスピナー ── */
|
|
|
|
|
|
.spinner {
|
|
|
|
|
|
display: inline-block; width: 14px; height: 14px;
|
|
|
|
|
|
border: 2px solid rgba(13,13,13,0.3);
|
|
|
|
|
|
border-top-color: #0D0D0D; border-radius: 50%;
|
|
|
|
|
|
animation: spin 0.7s linear infinite;
|
|
|
|
|
|
}
|
|
|
|
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
|
|
|
|
|
|
|
|
/* ── セクションラベル ── */
|
|
|
|
|
|
.sec-label {
|
|
|
|
|
|
font-size: 11px; color: var(--text3); text-transform: uppercase;
|
|
|
|
|
|
letter-spacing: 0.08em; margin-bottom: 8px; display: flex;
|
|
|
|
|
|
align-items: center; gap: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.sec-label::after { content: ''; flex: 1; height: 1px; background: var(--border); }
|
2026-04-23 07:17:45 +00:00
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<a href="#main-content" class="skip-link" tabindex="0" style="position:absolute;top:-100%;left:8px;background:var(--accent);color:#0D0D0D;padding:8px 16px;border-radius:8px;font-weight:600;font-size:13px;z-index:10000;text-decoration:none">コンテンツへスキップ</a>
|
|
|
|
|
|
|
|
|
|
|
|
<aside class="settings-panel" id="settingsPanel" role="complementary">
|
|
|
|
|
|
<div class="settings-panel-header">
|
|
|
|
|
|
<span class="settings-panel-title">設定</span>
|
|
|
|
|
|
<button class="icon-btn" id="settingsCloseBtn" aria-label="設定を閉じる">
|
2026-04-23 08:10:42 +00:00
|
|
|
|
<i data-lucide="x" style="width:18px;height:18px;stroke-width:1.5"></i>
|
2026-04-23 07:17:45 +00:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="settings-panel-body">
|
|
|
|
|
|
<div class="settings-group-label">外観</div>
|
|
|
|
|
|
<div class="settings-item">
|
|
|
|
|
|
<div class="settings-item-label">テーマ</div>
|
|
|
|
|
|
<div class="theme-selector">
|
2026-04-23 08:10:42 +00:00
|
|
|
|
<button class="theme-btn" data-theme-val="dark"><i data-lucide="moon" style="width:12px;height:12px;stroke-width:1.5"></i>ダーク</button>
|
|
|
|
|
|
<button class="theme-btn" data-theme-val="light"><i data-lucide="sun" style="width:12px;height:12px;stroke-width:1.5"></i>ライト</button>
|
|
|
|
|
|
<button class="theme-btn" data-theme-val="system"><i data-lucide="monitor" style="width:12px;height:12px;stroke-width:1.5"></i>自動</button>
|
2026-04-23 07:17:45 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
<div class="overlay" id="overlay" aria-hidden="true"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<header class="header">
|
|
|
|
|
|
<div class="header-brand">
|
|
|
|
|
|
<div class="header-dot" aria-hidden="true"></div>
|
2026-04-23 08:10:42 +00:00
|
|
|
|
<span class="header-title">Chronicle</span>
|
2026-04-23 07:17:45 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<button class="icon-btn" id="settingsBtn" aria-label="設定" aria-expanded="false">
|
|
|
|
|
|
<i data-lucide="settings" style="width:18px;height:18px;stroke-width:1.5"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<main id="main-content">
|
2026-04-23 08:10:42 +00:00
|
|
|
|
<div class="wrap">
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 認証バナー -->
|
|
|
|
|
|
<div class="auth-banner" id="authBanner">
|
|
|
|
|
|
<i data-lucide="log-in" style="width:16px;height:16px;stroke-width:1.5;flex-shrink:0;color:var(--text3)"></i>
|
|
|
|
|
|
<span>下書き生成には Posimai ログインが必要です。</span>
|
|
|
|
|
|
<a href="https://posimai.soar-enrich.com" target="_blank" rel="noopener">ログインする</a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 期間バー -->
|
|
|
|
|
|
<div class="period-bar">
|
|
|
|
|
|
<i data-lucide="calendar" style="width:14px;height:14px;stroke-width:1.5;color:var(--text3)"></i>
|
|
|
|
|
|
<span class="period-label">期間</span>
|
|
|
|
|
|
<span class="period-range" id="periodRange">—</span>
|
|
|
|
|
|
<div class="period-tabs">
|
|
|
|
|
|
<button class="period-tab active" data-days="7">今週</button>
|
|
|
|
|
|
<button class="period-tab" data-days="14">2週間</button>
|
|
|
|
|
|
<button class="period-tab" data-days="30">今月</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="two-col">
|
|
|
|
|
|
<!-- 入力フォーム -->
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div class="sec-label">入力</div>
|
|
|
|
|
|
<div class="input-card">
|
|
|
|
|
|
<div class="field">
|
2026-04-23 21:53:18 +00:00
|
|
|
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
|
|
|
|
|
|
<label class="field-label" for="activities" style="margin-bottom:0">主な活動</label>
|
|
|
|
|
|
<button class="btn btn-ghost" id="loadCommitsBtn"
|
|
|
|
|
|
style="font-size:11px;padding:4px 10px;display:inline-flex;align-items:center;gap:5px">
|
|
|
|
|
|
<i data-lucide="git-commit-horizontal" style="width:12px;height:12px;stroke-width:1.5"></i>
|
|
|
|
|
|
コミットから読み込む
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-04-23 08:10:42 +00:00
|
|
|
|
<textarea id="activities" rows="5"
|
|
|
|
|
|
placeholder="実装した機能、解決した問題、取り組んだ作業などを自由に書いてください。箇条書きでもOK。"></textarea>
|
2026-04-23 07:17:45 +00:00
|
|
|
|
</div>
|
2026-04-23 08:10:42 +00:00
|
|
|
|
|
|
|
|
|
|
<div class="field">
|
|
|
|
|
|
<label class="field-label">気分・体調</label>
|
|
|
|
|
|
<div class="sliders">
|
|
|
|
|
|
<div class="slider-row">
|
|
|
|
|
|
<span class="slider-name">気分</span>
|
|
|
|
|
|
<input class="slider-input" type="range" id="mood" min="1" max="5" value="3">
|
|
|
|
|
|
<span class="slider-val" id="moodVal">3</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="slider-row">
|
|
|
|
|
|
<span class="slider-name">エネルギー</span>
|
|
|
|
|
|
<input class="slider-input" type="range" id="energy" min="1" max="5" value="3">
|
|
|
|
|
|
<span class="slider-val" id="energyVal">3</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="slider-row">
|
|
|
|
|
|
<span class="slider-name">集中度</span>
|
|
|
|
|
|
<input class="slider-input" type="range" id="focus" min="1" max="5" value="3">
|
|
|
|
|
|
<span class="slider-val" id="focusVal">3</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="field">
|
|
|
|
|
|
<label class="field-label" for="learnings">学んだこと</label>
|
|
|
|
|
|
<textarea id="learnings" rows="3"
|
|
|
|
|
|
placeholder="技術的な発見、うまくいった方法、改善した点など。"></textarea>
|
2026-04-23 07:17:45 +00:00
|
|
|
|
</div>
|
2026-04-23 08:10:42 +00:00
|
|
|
|
|
|
|
|
|
|
<div class="field">
|
|
|
|
|
|
<label class="field-label" for="nexts">次の課題</label>
|
|
|
|
|
|
<textarea id="nexts" rows="3"
|
|
|
|
|
|
placeholder="次にやること、積み残し、気になっている問題など。"></textarea>
|
2026-04-23 07:17:45 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-23 08:10:42 +00:00
|
|
|
|
|
|
|
|
|
|
<div class="generate-area">
|
|
|
|
|
|
<button class="btn-generate" id="generateBtn">
|
|
|
|
|
|
<i data-lucide="sparkles" style="width:16px;height:16px;stroke-width:1.5"></i>
|
|
|
|
|
|
下書きを生成
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-04-23 07:17:45 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-04-23 08:10:42 +00:00
|
|
|
|
<!-- 下書き出力 -->
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div class="sec-label">下書き</div>
|
|
|
|
|
|
<div class="draft-card">
|
|
|
|
|
|
<div class="draft-header">
|
|
|
|
|
|
<span class="draft-title" id="draftLabel">生成待ち</span>
|
|
|
|
|
|
<div class="draft-actions" id="draftActions" style="display:none">
|
|
|
|
|
|
<button class="btn btn-ghost" id="copyBtn" style="font-size:12px;padding:6px 12px">
|
|
|
|
|
|
<i data-lucide="copy" style="width:14px;height:14px;stroke-width:1.5"></i>
|
|
|
|
|
|
コピー
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<a class="btn btn-ghost" href="https://journal.posimai.soar-enrich.com" target="_blank" rel="noopener"
|
|
|
|
|
|
style="font-size:12px;padding:6px 12px;display:inline-flex;align-items:center;gap:6px;text-decoration:none">
|
|
|
|
|
|
<i data-lucide="external-link" style="width:14px;height:14px;stroke-width:1.5"></i>
|
|
|
|
|
|
Journal
|
|
|
|
|
|
</a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="draft-body" id="draftBody">
|
|
|
|
|
|
<div class="empty-state">
|
|
|
|
|
|
<i data-lucide="file-text" style="width:24px;height:24px;stroke-width:1.5"></i>
|
|
|
|
|
|
<p>左の入力フォームを埋めて<br>「下書きを生成」を押してください</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-23 07:17:45 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-23 08:10:42 +00:00
|
|
|
|
|
|
|
|
|
|
</div>
|
2026-04-23 07:17:45 +00:00
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="toast" role="status" aria-live="polite"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<script src="https://posimai-ui.vercel.app/v1/base.js" defer></script>
|
2026-04-23 08:10:42 +00:00
|
|
|
|
<script>
|
|
|
|
|
|
const API = 'https://api.soar-enrich.com';
|
|
|
|
|
|
|
|
|
|
|
|
// ── 期間 ──────────────────────────────
|
|
|
|
|
|
let activeDays = 7;
|
|
|
|
|
|
function formatDate(d) {
|
|
|
|
|
|
return `${d.getFullYear()}/${String(d.getMonth()+1).padStart(2,'0')}/${String(d.getDate()).padStart(2,'0')}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
function updatePeriod() {
|
|
|
|
|
|
const end = new Date();
|
|
|
|
|
|
const start = new Date(end - activeDays * 86400000);
|
|
|
|
|
|
document.getElementById('periodRange').textContent = `${formatDate(start)} - ${formatDate(end)}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
document.querySelectorAll('.period-tab').forEach(btn => {
|
|
|
|
|
|
btn.addEventListener('click', () => {
|
|
|
|
|
|
document.querySelectorAll('.period-tab').forEach(b => b.classList.remove('active'));
|
|
|
|
|
|
btn.classList.add('active');
|
|
|
|
|
|
activeDays = +btn.dataset.days;
|
|
|
|
|
|
updatePeriod();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
updatePeriod();
|
|
|
|
|
|
|
|
|
|
|
|
// ── スライダー ────────────────────────
|
|
|
|
|
|
['mood','energy','focus'].forEach(id => {
|
|
|
|
|
|
const input = document.getElementById(id);
|
|
|
|
|
|
const val = document.getElementById(id + 'Val');
|
|
|
|
|
|
input.addEventListener('input', () => { val.textContent = input.value; });
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── 認証バナー ────────────────────────
|
|
|
|
|
|
function getToken() { return localStorage.getItem('posimai_token') || ''; }
|
|
|
|
|
|
const banner = document.getElementById('authBanner');
|
|
|
|
|
|
if (getToken()) banner.classList.add('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
// ── Markdown 簡易レンダラー ──────────
|
|
|
|
|
|
function renderMd(text) {
|
|
|
|
|
|
const escaped = text
|
|
|
|
|
|
.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
|
|
|
|
const lines = escaped.split('\n');
|
|
|
|
|
|
let html = '';
|
|
|
|
|
|
let inP = false;
|
|
|
|
|
|
for (const line of lines) {
|
|
|
|
|
|
if (!line.trim()) {
|
|
|
|
|
|
if (inP) { html += '</p>'; inP = false; }
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (line.startsWith('### ')) {
|
|
|
|
|
|
if (inP) { html += '</p>'; inP = false; }
|
|
|
|
|
|
html += `<h3>${inline(line.slice(4))}</h3>`;
|
|
|
|
|
|
} else if (line.startsWith('## ')) {
|
|
|
|
|
|
if (inP) { html += '</p>'; inP = false; }
|
|
|
|
|
|
html += `<h2>${inline(line.slice(3))}</h2>`;
|
|
|
|
|
|
} else if (line.startsWith('# ')) {
|
|
|
|
|
|
if (inP) { html += '</p>'; inP = false; }
|
|
|
|
|
|
html += `<h2>${inline(line.slice(2))}</h2>`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (!inP) { html += '<p>'; inP = true; }
|
|
|
|
|
|
else html += '<br>';
|
|
|
|
|
|
html += inline(line);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (inP) html += '</p>';
|
|
|
|
|
|
return html;
|
|
|
|
|
|
}
|
|
|
|
|
|
function inline(s) {
|
|
|
|
|
|
return s
|
|
|
|
|
|
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
|
|
|
|
.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── 生成 ──────────────────────────────
|
|
|
|
|
|
let lastDraft = '';
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('generateBtn').addEventListener('click', async () => {
|
|
|
|
|
|
const token = getToken();
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
|
banner.classList.remove('hidden');
|
|
|
|
|
|
showToast('Posimai へのログインが必要です');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const activities = document.getElementById('activities').value.trim();
|
|
|
|
|
|
if (!activities) {
|
|
|
|
|
|
showToast('「主な活動」を入力してください');
|
|
|
|
|
|
document.getElementById('activities').focus();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const btn = document.getElementById('generateBtn');
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
btn.innerHTML = '<span class="spinner"></span> 生成中…';
|
|
|
|
|
|
|
|
|
|
|
|
const end = new Date();
|
|
|
|
|
|
const start = new Date(end - activeDays * 86400000);
|
|
|
|
|
|
const period = `${formatDate(start)} から ${formatDate(end)}`;
|
|
|
|
|
|
|
|
|
|
|
|
const mood = document.getElementById('mood').value;
|
|
|
|
|
|
const energy = document.getElementById('energy').value;
|
|
|
|
|
|
const focus = document.getElementById('focus').value;
|
|
|
|
|
|
const learnings = document.getElementById('learnings').value.trim();
|
|
|
|
|
|
const nexts = document.getElementById('nexts').value.trim();
|
|
|
|
|
|
|
|
|
|
|
|
const systemPrompt = `あなたは開発者の振り返りを支援するライターです。
|
|
|
|
|
|
入力された活動情報をもとに、日本語の開発日記の下書きを作成してください。
|
|
|
|
|
|
|
|
|
|
|
|
ルール:
|
|
|
|
|
|
- markdown 形式(## 見出し + 本文段落)
|
|
|
|
|
|
- 3〜4段落構成:進捗 → 気づき・学び → 体調との関係 → 次の意志
|
|
|
|
|
|
- 「今週は」「この期間に」など期間を主語にする(一人称「私」は避ける)
|
|
|
|
|
|
- 技術的な内容はそのまま使い、誇張しない
|
|
|
|
|
|
- 公開できる読み物として書く(ですます調)
|
|
|
|
|
|
- 500〜700字程度
|
|
|
|
|
|
- コードブロックは使わない`;
|
|
|
|
|
|
|
|
|
|
|
|
const userPrompt = `期間: ${period}
|
|
|
|
|
|
主な活動:
|
|
|
|
|
|
${activities}
|
|
|
|
|
|
気分: ${mood}/5、エネルギー: ${energy}/5、集中度: ${focus}/5
|
|
|
|
|
|
${learnings ? `学んだこと:\n${learnings}` : ''}
|
|
|
|
|
|
${nexts ? `次の課題:\n${nexts}` : ''}`;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const resp = await fetch(`${API}/ai/generate`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'Authorization': `Bearer ${token}`
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
systemPrompt,
|
|
|
|
|
|
contents: [{ role: 'user', parts: [{ text: userPrompt }] }],
|
|
|
|
|
|
config: { maxOutputTokens: 800, temperature: 0.7 }
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
|
const err = await resp.json().catch(() => ({}));
|
|
|
|
|
|
throw new Error(err.error || `HTTP ${resp.status}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await resp.json();
|
|
|
|
|
|
lastDraft = data.text || '';
|
|
|
|
|
|
document.getElementById('draftBody').innerHTML = renderMd(lastDraft);
|
|
|
|
|
|
document.getElementById('draftLabel').textContent = `下書き — ${period}`;
|
|
|
|
|
|
document.getElementById('draftActions').style.display = 'flex';
|
|
|
|
|
|
showToast('下書きを生成しました');
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
showToast(e.message.includes('401') || e.message.includes('403')
|
|
|
|
|
|
? 'ログインが切れています。再ログインしてください'
|
|
|
|
|
|
: `生成に失敗しました: ${e.message}`);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
btn.innerHTML = '<i data-lucide="sparkles" style="width:16px;height:16px;stroke-width:1.5"></i> 下書きを生成';
|
|
|
|
|
|
lucide.createIcons();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── コピー ────────────────────────────
|
|
|
|
|
|
document.getElementById('copyBtn').addEventListener('click', async () => {
|
|
|
|
|
|
if (!lastDraft) return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
await navigator.clipboard.writeText(lastDraft);
|
|
|
|
|
|
showToast('クリップボードにコピーしました');
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
showToast('コピーに失敗しました');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-04-23 21:53:18 +00:00
|
|
|
|
// ── コミット読み込み ──────────────────
|
|
|
|
|
|
document.getElementById('loadCommitsBtn').addEventListener('click', async () => {
|
|
|
|
|
|
const token = getToken();
|
|
|
|
|
|
if (!token) { showToast('ログインが必要です'); return; }
|
|
|
|
|
|
|
|
|
|
|
|
const btn = document.getElementById('loadCommitsBtn');
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
btn.innerHTML = '<span class="spinner" style="border-color:rgba(0,0,0,.15);border-top-color:var(--text2)"></span> 読み込み中';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const resp = await fetch(`${API}/chronicle/activity?days=${activeDays}`, {
|
|
|
|
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (resp.status === 503) {
|
|
|
|
|
|
showToast('VPS に CHRONICLE_GITHUB_TOKEN が未設定です');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
|
|
|
|
|
|
|
|
|
|
const data = await resp.json();
|
|
|
|
|
|
if (!data.commits?.length) {
|
|
|
|
|
|
showToast('この期間にコミットが見つかりませんでした');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const byRepo = {};
|
|
|
|
|
|
for (const c of data.commits) {
|
|
|
|
|
|
if (!byRepo[c.repo]) byRepo[c.repo] = [];
|
|
|
|
|
|
byRepo[c.repo].push(c.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const lines = [];
|
|
|
|
|
|
for (const [repo, messages] of Object.entries(byRepo)) {
|
|
|
|
|
|
lines.push(`【${repo}】`);
|
|
|
|
|
|
for (const m of messages) lines.push(`- ${m}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const area = document.getElementById('activities');
|
|
|
|
|
|
area.value = lines.join('\n');
|
|
|
|
|
|
showToast(`${data.commits.length} 件のコミットを読み込みました`);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
showToast(`読み込み失敗: ${e.message}`);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
btn.innerHTML = '<i data-lucide="git-commit-horizontal" style="width:12px;height:12px;stroke-width:1.5"></i> コミットから読み込む';
|
|
|
|
|
|
lucide.createIcons();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-04-23 08:10:42 +00:00
|
|
|
|
// ── Toast ─────────────────────────────
|
|
|
|
|
|
function showToast(msg) {
|
|
|
|
|
|
const el = document.getElementById('toast');
|
|
|
|
|
|
el.textContent = msg;
|
|
|
|
|
|
el.classList.add('show');
|
|
|
|
|
|
setTimeout(() => el.classList.remove('show'), 3000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
if ('serviceWorker' in navigator) {
|
|
|
|
|
|
navigator.serviceWorker.register('/sw.js');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
2026-04-23 07:17:45 +00:00
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|