.wrap{padding:1.5rem 0;max-width:680px;margin:0 auto}
.header{display:flex;align-items:center;gap:10px;margin-bottom:1.5rem}
.header i{font-size:24px}
.header h2{font-size:18px;font-weight:500;margin:0}
.header p{font-size:13px;color:#666;margin:0}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px}
.form-group{display:flex;flex-direction:column;gap:6px}
.form-group label{font-size:13px;color:#666}
.form-group input,.form-group select{padding:8px 12px;border:1px solid #ddd;border-radius:8px;font-size:14px;width:100%;box-sizing:border-box}
.chip-group{display:flex;flex-wrap:wrap;gap:8px}
.chip{padding:6px 14px;border-radius:20px;border:1px solid #ddd;font-size:13px;cursor:pointer;background:#fff;color:#666;transition:all 0.15s}
.chip.active{background:#E6F1FB;border-color:#378ADD;color:#0C447C}
.btn-gen{width:100%;padding:12px;font-size:15px;font-weight:500;background:#fff;border:1px solid #ddd;border-radius:12px;cursor:pointer;margin-top:1rem;display:flex;align-items:center;justify-content:center;gap:8px}
.btn-gen:hover{background:#f5f5f5}
.btn-gen:disabled{opacity:0.5;cursor:not-allowed}
.result-card{margin-top:1.5rem;background:#fff;border:1px solid #eee;border-radius:12px;padding:1.25rem}
.route-title{font-size:16px;font-weight:500;margin:0 0 4px}
.route-meta{font-size:13px;color:#666;margin:0 0 1rem}
.route-meta span{margin-right:14px}
.tags-row{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:1rem}
.tag{font-size:12px;padding:3px 10px;border-radius:20px;background:#FAEEDA;color:#633806}
.waypoints{list-style:none;padding:0;margin:0 0 1.25rem}
.waypoint{display:flex;align-items:flex-start;gap:10px;padding:10px 0;border-bottom:1px solid #eee}
.waypoint:last-child{border-bottom:none}
.wp-num{width:24px;height:24px;border-radius:50%;background:#E6F1FB;color:#0C447C;font-size:12px;font-weight:500;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px}
.wp-name{font-size:14px;font-weight:500;margin:0 0 2px}
.wp-desc{font-size:13px;color:#666;margin:0}
.tip-box{background:#EAF3DE;border-radius:8px;padding:10px 14px;font-size:13px;color:#3B6D11;margin-bottom:1.25rem}
.gear-section{border-top:1px solid #eee;padding-top:1.25rem}
.gear-title{font-size:15px;font-weight:500;margin:0 0 12px}
.gear-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px}
.gear-card{background:#f9f9f9;border-radius:8px;padding:12px}
.gear-name{font-size:13px;font-weight:500;margin:0 0 4px}
.gear-why{font-size:12px;color:#666;margin:0 0 8px}
.gear-link{display:inline-flex;align-items:center;gap:4px;font-size:12px;color:#185FA5;text-decoration:none;border:1px solid #B5D4F4;border-radius:6px;padding:4px 10px;background:#E6F1FB}
.loading{text-align:center;padding:2rem;color:#666;font-size:14px}
.dot{display:inline-block;animation:blink 1.2s infinite}
.dot:nth-child(2){animation-delay:.2s}
.dot:nth-child(3){animation-delay:.4s}
@keyframes blink{0%,80%,100%{opacity:0}40%{opacity:1}}
.err{background:#FCEBEB;border-radius:8px;padding:10px 14px;font-size:13px;color:#A32D2D;margin-top:1rem}
.affiliate-note{font-size:11px;color:#aaa;margin-top:1rem;text-align:center}
.badge-must{font-size:11px;background:#FCEBEB;color:#A32D2D;padding:2px 7px;border-radius:10px;font-weight:400}
.badge-rec{font-size:11px;background:#EAF3DE;color:#3B6D11;padding:2px 7px;border-radius:10px;font-weight:400}
// ★ここにアフィリエイトリンクを入れてください★
const AFFILIATE_LINKS = {
“グローブ”: “
https://ここにA8やRakutenのリンクを貼る”,
“ヘルメット”: “
https://ここにA8やRakutenのリンクを貼る”,
“ジャケット”: “
https://ここにA8やRakutenのリンクを貼る”,
“レインウェア”: “
https://ここにA8やRakutenのリンクを貼る”,
“ブーツ”: “
https://ここにA8やRakutenのリンクを貼る”,
“タンクバッグ”: “
https://ここにA8やRakutenのリンクを貼る”,
“ナビ・スマホホルダー”: “
https://ここにA8やRakutenのリンクを貼る”,
“プロテクター”: “
https://ここにA8やRakutenのリンクを貼る”,
“インカム”: “
https://ここにA8やRakutenのリンクを貼る”,
“チェーンロック”: “
https://ここにA8やRakutenのリンクを貼る”
};
document.querySelectorAll(‘.chip’).forEach(c => {
c.addEventListener(‘click’, () => {
const group = c.closest(‘.chip-group’);
if (group.id === ‘seasons’) {
group.querySelectorAll(‘.chip’).forEach(x => x.classList.remove(‘active’));
}
c.classList.toggle(‘active’);
});
});
document.getElementById(‘genBtn’).addEventListener(‘click’, async () => {
const area = document.getElementById(‘area’).value.trim();
if (!area) { alert(‘出発エリアを入力してください’); return; }
const distance = document.getElementById(‘distance’).value;
const vibes = […document.querySelectorAll(‘#vibes .chip.active’)].map(c => c.dataset.val).join(‘、’) || ‘こだわりなし’;
const bike = […document.querySelectorAll(‘#bikes .chip.active’)].map(c => c.dataset.val).join(‘、’) || ‘指定なし’;
const season = […document.querySelectorAll(‘#seasons .chip.active’)].map(c => c.dataset.val).join(”) || ‘指定なし’;
const btn = document.getElementById(‘genBtn’);
const out = document.getElementById(‘output’);
btn.disabled = true;
out.innerHTML = ‘
AIがルートと装備を考えています...
‘;
const gearKeys = Object.keys(AFFILIATE_LINKS).join(‘、’);
const prompt = `あなたはベテランツーリングライダーです。以下の条件でツーリングルートと装備リストを提案してください。\n\n出発エリア: ${area}\n走行距離: ${distance}\n雰囲気: ${vibes}\nバイク: ${bike}\n季節: ${season}\n\n以下のJSON形式のみで返してください(コードブロック記号・前後の説明文は不要):\n{\n “title”: “ルート名”,\n “distance”: “総距離”,\n “time”: “所要時間”,\n “level”: “初級者向け or 中級者向け or 上級者向け”,\n “highlights”: [“ハイライト1″,”ハイライト2″,”ハイライト3”],\n “waypoints”: [\n {“name”:”地点名”,”desc”:”見どころ1〜2文”},\n {“name”:”地点名”,”desc”:”説明”},\n {“name”:”地点名”,”desc”:”説明”},\n {“name”:”地点名”,”desc”:”説明”},\n {“name”:”地点名”,”desc”:”説明”}\n ],\n “tip”: “ワンポイントアドバイス”,\n “gear”: [\n {“name”:”アイテム名(必ず次の中から: ${gearKeys})”,”why”:”このルート・季節でなぜ必要か1文”,”priority”:”必須 or おすすめ”},\n {“name”:”アイテム名”,”why”:”理由”,”priority”:”必須 or おすすめ”},\n {“name”:”アイテム名”,”why”:”理由”,”priority”:”必須 or おすすめ”},\n {“name”:”アイテム名”,”why”:”理由”,”priority”:”必須 or おすすめ”},\n {“name”:”アイテム名”,”why”:”理由”,”priority”:”必須 or おすすめ”}\n ]\n}`;
try {
const res = await fetch(‘
https://api.anthropic.com/v1/messages’, {
method: ‘POST’,
headers: {‘Content-Type’:’application/json’},
body: JSON.stringify({
model: ‘claude-sonnet-4-20250514′,
max_tokens: 1500,
messages: [{role:’user’, content: prompt}]
})
});
const data = await res.json();
const text = data.content?.map(i => i.text||”).join(”).trim().replace(/“`json|“`/g,”).trim();
const r = JSON.parse(text);
const gearHtml = r.gear.map(g => {
const link = AFFILIATE_LINKS[g.name];
const isPriority = g.priority === ‘必須’;
return `
${g.name} ${g.priority}
${g.why}
${link && !link.includes(‘ここに’) ? `
楽天で見る` : ”}
`;
}).join(”);
out.innerHTML = `
${r.title}
📍 ${r.distance}
⏱ ${r.time}
🔥 ${r.level}
${r.highlights.map(h => `${h}`).join(”)}
${r.waypoints.map((wp,i) => `-
${i+1}
`).join(”)}
💡 ${r.tip}
🎒 このルートにおすすめの装備
${gearHtml}
※商品リンクはアフィリエイトリンクを含みます
`;
} catch(e) {
out.innerHTML = ‘
生成に失敗しました。もう一度お試しください。
‘;
}
btn.disabled = false;
});