<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>仮想ギルド掲示板「ドラゴンズクレスト」</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
:root {
--bg: #1c1c2b;
--panel: #2e2e4d;
--card: #3a3a5f;
--accent: #6c63ff;
--text: #fff;
}
body {
margin: 0;
font-family: "Segoe UI", sans-serif;
background: var(--bg);
color: var(--text);
}
header {
background: var(--panel);
padding: 20px;
text-align: center;
font-size: 1.8em;
}
nav {
display: flex;
justify-content: center;
gap: 20px;
background: var(--card);
padding: 10px;
}
nav button {
background: transparent;
border: none;
color: #ddd;
font-size: 1em;
padding: 10px 20px;
cursor: pointer;
}
nav button.active {
border-bottom: 2px solid var(--accent);
color: var(--accent);
}
.container {
display: grid;
grid-template-columns: 1fr 250px;
gap: 20px;
max-width: 1200px;
margin: 20px auto;
padding: 0 10px;
}
.main {
background: var(--card);
padding: 20px;
border-radius: 12px;
}
.sidebar {
background: var(--card);
padding: 15px;
border-radius: 12px;
}
.post-form textarea {
width: 100%;
height: 80px;
border-radius: 6px;
padding: 10px;
border: none;
resize: vertical;
}
.post-form button {
margin-top: 10px;
padding: 10px 20px;
background: var(--accent);
border: none;
border-radius: 6px;
color: #fff;
cursor: pointer;
}
.post {
background: #404070;
border-radius: 10px;
padding: 15px;
margin-top: 15px;
display: flex;
gap: 10px;
flex-direction: row;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #6c63ff;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.post-content {
flex: 1;
}
.meta {
font-size: 0.9em;
color: #aaa;
margin-bottom: 5px;
}
.actions button {
margin-right: 8px;
background: #5a5a8f;
border: none;
color: white;
padding: 4px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 0.8em;
}
.reply-area {
margin-top: 10px;
}
.reply-area textarea {
width: 100%;
height: 50px;
margin-top: 5px;
padding: 6px;
border-radius: 4px;
border: none;
}
.reply {
margin-top: 5px;
font-size: 0.85em;
color: #ddd;
}
.guild-info, .members, .guild-schedule, .guild-rules {
margin-bottom: 20px;
}
.members ul {
list-style: none;
padding: 0;
}
.members li {
padding: 5px 0;
}
.members li.online::before {
content: "●";
color: #7fff7f;
margin-right: 5px;
}
.members li.offline::before {
content: "●";
color: #888;
margin-right: 5px;
}
@media (max-width: 800px) {
.container {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<header>仮想ギルド掲示板「ドラゴンズクレスト」</header>
<nav>
<button class="tab-button active" onclick="switchTab('general')">🏰 雑談</button>
<button class="tab-button" onclick="switchTab('raid')">⚔️ レイド</button>
<button class="tab-button" onclick="switchTab('trade')">💰 取引</button>
</nav>
<div class="container">
<div class="main">
<div class="post-form">
<textarea id="postText" placeholder="ギルドのみんなに伝えたいことは?"></textarea>
<button onclick="postMessage()">投稿する</button>
</div>
<div id="general" class="tab-content">
<!-- 投稿はここに追加されます -->
</div>
<div id="raid" class="tab-content" style="display:none;"></div>
<div id="trade" class="tab-content" style="display:none;"></div>
</div>
<div class="sidebar">
<div class="guild-info">
<h3>ギルド情報</h3>
<p>設立:2025年<br>メンバー数:32名<br>ギルマス:Reina</p>
</div>
<div class="guild-schedule">
<h3>今週のスケジュール</h3>
<ul>
<li>🗓️ 7月12日(土) 21:00〜:レイド「闇の塔」</li>
<li>🗓️ 7月14日(月) 22:00〜:PvP練習会</li>
<li>🗓️ 7月16日(水) 20:00〜:定例会議</li>
</ul>
</div>
<div class="guild-rules">
<h3>ギルドルール</h3>
<ol>
<li>他プレイヤーへの迷惑行為禁止</li>
<li>無断脱退・長期不在時は一言ください</li>
<li>レイド参加は20分前集合!</li>
</ol>
</div>
<div class="members">
<h3>メンバー(抜粋)</h3>
<ul>
<li class="online">Reina</li>
<li class="online">Shiro</li>
<li class="offline">Yuna</li>
<li class="online">Kuro</li>
<li class="offline">Mika</li>
</ul>
</div>
</div>
</div>
<script>
function postMessage() {
const textarea = document.getElementById("postText");
const content = textarea.value.trim();
if (!content) return;
const now = new Date().toLocaleString("ja-JP", { hour12: false });
const post = document.createElement("div");
post.className = "post";
post.innerHTML = `
<div class="avatar">Y</div>
<div class="post-content">
<div class="meta">あなた | ${now}</div>
<div class="content">${content.replace(/\n/g, "<br>")}</div>
<div class="actions">
<button onclick="likePost(this)">👍 <span>0</span></button>
<button onclick="toggleReply(this)">💬 返信</button>
<button onclick="deletePost(this)">🗑 削除</button>
</div>
<div class="reply-area" style="display:none;">
<textarea placeholder="返信を書く..."></textarea>
<button onclick="submitReply(this)">返信する</button>
</div>
<div class="replies"></div>
</div>
`;
const activeTab = document.querySelector(".tab-button.active").textContent.trim();
const tabId = activeTab.includes("雑談") ? "general" : activeTab.includes("レイド") ? "raid" : "trade";
document.getElementById(tabId).prepend(post);
textarea.value = "";
}
function switchTab(tabId) {
document.querySelectorAll(".tab-content").forEach(tab => tab.style.display = "none");
document.querySelectorAll(".tab-button").forEach(btn => btn.classList.remove("active"));
document.getElementById(tabId).style.display = "block";
event.target.classList.add("active");
}
function likePost(button) {
const span = button.querySelector("span");
span.textContent = parseInt(span.textContent) + 1;
}
function toggleReply(button) {
const replyArea = button.parentElement.nextElementSibling;
replyArea.style.display = replyArea.style.display === "none" ? "block" : "none";
}
function submitReply(button) {
const textarea = button.previousElementSibling;
const replyText = textarea.value.trim();
if (!replyText) return;
const replyDiv = document.createElement("div");
replyDiv.className = "reply";
replyDiv.innerHTML = `<small>あなた: ${replyText}</small>`;
button.closest(".post-content").querySelector(".replies").appendChild(replyDiv);
textarea.value = "";
}
function deletePost(button) {
if (confirm("この投稿を削除しますか?")) {
button.closest(".post").remove();
}
}
</script>
</body>
</html>
カテゴリー: HTML
AIイラストプロンプトメーカー
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>AIイラストプロンプトメーカー</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
:root {
--primary: #6c63ff;
--bg: #f2f2f2;
--text: #333;
--card: white;
}
body {
margin: 0;
font-family: 'Segoe UI', sans-serif;
background: var(--bg);
color: var(--text);
}
header {
background: var(--primary);
color: white;
padding: 20px;
text-align: center;
font-size: 1.8em;
}
.container {
max-width: 1000px;
margin: 30px auto;
background: var(--card);
padding: 30px;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
select, button, textarea, input[type="file"] {
width: 100%;
padding: 10px;
font-size: 1em;
border-radius: 8px;
border: 1px solid #ccc;
box-sizing: border-box;
}
.output {
background: #fafafa;
padding: 15px;
border-radius: 10px;
margin-top: 20px;
white-space: pre-wrap;
}
button {
background: var(--primary);
color: white;
border: none;
margin-top: 15px;
cursor: pointer;
}
button:hover {
background: #574fd9;
}
.image-preview {
margin-top: 20px;
text-align: center;
}
.image-preview img {
max-width: 100%;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.gallery {
margin-top: 40px;
}
.gallery img {
max-width: 100%;
margin: 10px 0;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
@media (max-width: 600px) {
.container {
padding: 20px;
margin: 10px;
}
}
</style>
</head>
<body>
<header>🎨 AIイラストプロンプトメーカー</header>
<div class="container">
<div class="form-group">
<label>キャラクター</label>
<select id="subject">
<option>狐の少女</option>
<option>魔法少女</option>
<option>サイバーパンクの戦士</option>
<option>猫耳の少年</option>
<option>吸血鬼の姫</option>
<option>天使の戦士</option>
<option>竜騎士</option>
<option>宇宙探検家</option>
<option>妖精</option>
<option>獣人の女王</option>
<option>機械生命体</option>
<option>デジタル妖精</option>
<option>スチームパンクの発明家</option>
<option>氷の精霊</option>
<option>砂漠の王女</option>
<option>未来の警察官</option>
<option>忍者少女</option>
<option>異世界の商人</option>
<option>獣耳魔法使い</option>
<option>雷の神</option>
<option>闇の王子</option>
<option>炎の踊り子</option>
<option>時間を操る司書</option>
<option>ポストアポカリプスの旅人</option>
<option>音楽を操る精霊</option>
<option>風の精霊使い</option>
<option>森の守護者</option>
<option>未来の芸術家</option>
<option>魔界の王女</option>
<option>電脳世界のハッカー</option>
<option>サーカス団の団長</option>
<option>時計仕掛けの人形</option>
<option>図書館の魔導師</option>
<option>宇宙アイドル</option>
<option>夢の案内人</option>
<option>四季を司る女神</option>
<option>時間旅行者</option>
<option>戦場の傭兵</option>
<option>星を読む預言者</option>
<option>電脳巫女</option>
<option>古代の王</option>
<option>未来の料理人</option>
<option>天空の案内人</option>
<option>人魚の王女</option>
<option>炎を纏う戦士</option>
<option>異界の騎士</option>
<option>霧の中の影</option>
<option>雷獣の化身</option>
<option>植物を操る錬金術師</option>
<option>おとぎ話の語り部</option>
<option>重力を操る少女</option>
<option>古の預言者</option>
<option>空を旅する郵便屋</option>
<option>眠りを司る精霊</option>
<option>お祭りの踊り子</option>
<option>砂嵐の遊牧民</option>
<option>泡の海の守護者</option>
<option>魔法道具職人</option>
<option>氷と炎の二重人格者</option>
<option>デジタル世界の探偵</option>
<option>孤独な塔の詩人</option>
<option>空飛ぶ書斎の管理人</option>
<option>鏡の中の分身</option>
<option>古城に棲む亡霊</option>
<option>地下世界の旅人</option>
<option>異星文明の観測者</option>
<option>時間停止の魔術師</option>
<option>夢の記録者</option>
<option>霧の海の漁師</option>
<option>重力反転の案内人</option>
<option>時空の管理者</option>
<option>流星に乗る観測者</option>
<option>伝説の召喚士</option>
<option>影を操る使者</option>
<option>魔法にかけられた人形</option>
<option>未来都市のDJ</option>
<option>空想世界の画家</option>
<option>炎の道化師</option>
<option>廃墟に住む猫型ロボット</option>
<option>雲の牧場の飼育員</option>
<option>月光に踊る騎士</option>
<option>泡でできた人間</option>
<option>時の狭間に生きる者</option>
</select>
</div>
<div class="form-group">
<label>衣装</label>
<select id="clothing">
<option>着物</option>
<option>ゴスロリ</option>
<option>セーラー服</option>
<option>鎧</option>
<option>メイド服</option>
<option>学生服</option>
<option>忍者装束</option>
<option>水着</option>
<option>アイドル衣装</option>
<option>宇宙服</option>
<option>ウェディングドレス</option>
<option>スーツ</option>
<option>チャイナドレス</option>
<option>パーカーとジーンズ</option>
<option>ボロボロの服</option>
<option>軍服</option>
<option>ドレスアーマー</option>
<option>モダンファッション</option>
<option>魔導士のローブ</option>
<option>未来的スーツ</option>
<option>フリルのついたロングドレス</option>
<option>サイバースーツ</option>
<option>狩人の装束</option>
<option>伝統的な王族の衣装</option>
<option>修道女の服</option>
<option>ポンチョスタイル</option>
<option>レザージャケット</option>
<option>花柄のワンピース</option>
<option>ホログラムドレス</option>
<option>羽付きの礼装</option>
<option>ロリータドレス</option>
<option>海賊風コート</option>
<option>スポーツユニフォーム</option>
<option>カウガールスタイル</option>
<option>研究者の白衣</option>
<option>錬金術師のローブ</option>
</select>
</div>
<div class="form-group">
<label>シチュエーション</label>
<select id="scene">
<option>桜の下</option>
<option>未来都市</option>
<option>夕焼けの海辺</option>
<option>廃墟の寺院</option>
<option>暗い森</option>
<option>宇宙船の中</option>
<option>雪山</option>
<option>草原</option>
<option>古代遺跡</option>
<option>空中庭園</option>
<option>星空の下</option>
<option>雨の街角</option>
<option>異世界の市場</option>
<option>火山地帯</option>
<option>地下の書庫</option>
<option>空港の滑走路</option>
<option>無重力空間</option>
<option>深海の遺跡</option>
<option>サーカス会場</option>
<option>魔法学園の中庭</option>
<option>闘技場</option>
<option>王宮のバルコニー</option>
<option>雲の上</option>
<option>ネオン輝く夜の街</option>
<option>幽霊船の甲板</option>
<option>滝の裏の洞窟</option>
</select>
</div>
<div class="form-group">
<label>表情</label>
<select id="emotion">
<option>微笑んでいる</option>
<option>驚いている</option>
<option>泣いている</option>
<option>真剣な表情</option>
<option>照れている</option>
<option>怒っている</option>
<option>眠そう</option>
<option>無表情</option>
<option>ウィンクしている</option>
<option>笑いながら泣いている</option>
<option>楽しそうに笑っている</option>
<option>不機嫌そうな顔</option>
<option>恥ずかしそうに俯いている</option>
<option>驚愕している</option>
<option>勝ち誇っている</option>
<option>安心している</option>
<option>寂しげな表情</option>
<option>苦悩している</option>
</select>
</div>
<div class="form-group">
<label>スタイル</label>
<select id="style">
<option>アニメ調</option>
<option>リアル調</option>
<option>水彩風</option>
<option>ピクセルアート</option>
<option>モノクロスケッチ</option>
<option>デフォルメ</option>
<option>シネマティック</option>
<option>油絵風</option>
<option>幻想的</option>
<option>ミッドセンチュリー</option>
<option>ドット絵</option>
<option>イラスト風3D</option>
<option>和風アート</option>
<option>ポップアート</option>
<option>ネオンアート</option>
<option>墨絵風</option>
<option>ミニマリスト</option>
<option>ダークファンタジー</option>
</select>
</div>
<div class="form-group">
<label>ライティング</label>
<select id="lighting">
<option>夕日</option>
<option>月明かり</option>
<option>逆光</option>
<option>スポットライト</option>
<option>柔らかい光</option>
<option>ネオンライト</option>
<option>キャンドルライト</option>
<option>青白い光</option>
<option>モノクローム</option>
<option>ファンタジー風</option>
<option>雷光</option>
<option>神秘的な光</option>
<option>焚き火の明かり</option>
<option>都市の夜明かり</option>
<option>レーザーライト</option>
<option>朝焼け</option>
<option>逆光のシルエット</option>
</select>
</div>
<div class="form-group">
<label>構図</label>
<select id="aspect">
<option>全身</option>
<option>バストアップ</option>
<option>クローズアップ</option>
<option>後ろ姿</option>
<option>斜め上から</option>
<option>ローアングル</option>
<option>ハイアングル</option>
<option>俯瞰図</option>
<option>対面</option>
<option>ポートレート</option>
<option>シルエット</option>
<option>鏡越しの視点</option>
<option>一部だけ見せる</option>
<option>肩越し視点</option>
<option>手元のアップ</option>
<option>足元のアップ</option>
<option>寝転んだ構図</option>
<option>振り返った構図</option>
</select>
</div>
<button onclick="generatePrompt()">✨ プロンプト生成</button>
<button onclick="copyPrompt()">📋 コピー</button>
<div class="output" id="japaneseOutput"></div>
<div class="output" id="englishOutput"></div>
<div class="output"><strong>🚫 Negative Prompt:</strong><br><span id="negativePrompt"></span></div>
<div class="form-group">
<label>🎨 画像アップロード(作品ギャラリー)</label>
<input type="file" accept="image/*" onchange="previewUpload(event)">
</div>
<div class="gallery" id="gallery"></div>
</div>
<script>
function generatePrompt() {
const subject = document.getElementById("subject").value;
const clothing = document.getElementById("clothing").value;
const scene = document.getElementById("scene").value;
const emotion = document.getElementById("emotion").value;
const style = document.getElementById("style").value;
const lighting = document.getElementById("lighting").value;
const aspect = document.getElementById("aspect").value;
const ja = `「${scene}」で「${clothing}」を着た「${subject}」が「${emotion}」表情をしている。「${style}」「${lighting}」で「${aspect}」構図。`;
const en = `${translate(subject)}, wearing ${translate(clothing)}, ${translate(scene)}, ${translate(emotion)}, ${translate(style)}, ${translate(lighting)}, ${translate(aspect)}, masterpiece, best quality`;
document.getElementById("japaneseOutput").textContent = "📝 日本語説明:\n" + ja;
document.getElementById("englishOutput").textContent = "🧩 English Tags:\n" + en;
document.getElementById("negativePrompt").textContent = "low quality, bad anatomy, blurry, extra limbs, deformed, watermark, text, signature";
}
function copyPrompt() {
const ja = document.getElementById("japaneseOutput").textContent;
const en = document.getElementById("englishOutput").textContent;
const neg = document.getElementById("negativePrompt").textContent;
const full = `${ja}\n\n${en}\n\nNegative Prompt:\n${neg}`;
navigator.clipboard.writeText(full).then(() => alert("プロンプトをコピーしました!"));
}
function previewUpload(event) {
const files = event.target.files;
const gallery = document.getElementById("gallery");
for (let i = 0; i < files.length; i++) {
const reader = new FileReader();
reader.onload = function(e) {
const img = document.createElement("img");
img.src = e.target.result;
gallery.appendChild(img);
}
reader.readAsDataURL(files[i]);
}
}
function translate(text) {
const dict = {
"狐の少女": "fox girl", "魔法少女": "magical girl", "サイバーパンクの戦士": "cyberpunk warrior",
"猫耳の少年": "catboy", "吸血鬼の姫": "vampire princess",
"着物": "kimono", "ゴスロリ": "gothic lolita", "セーラー服": "sailor uniform", "鎧": "armor", "メイド服": "maid outfit",
"桜の下": "under cherry blossoms", "未来都市": "in futuristic city", "夕焼けの海辺": "on sunset beach", "廃墟の寺院": "in ruined temple", "暗い森": "in dark forest",
"微笑んでいる": "smiling", "驚いている": "surprised", "泣いている": "crying", "真剣な表情": "serious", "照れている": "blushing",
"アニメ調": "anime style", "リアル調": "realistic", "水彩風": "watercolor", "ピクセルアート": "pixel art", "モノクロスケッチ": "monochrome sketch",
"夕日": "sunset lighting", "月明かり": "moonlight", "逆光": "backlight", "スポットライト": "spotlight", "柔らかい光": "soft light",
"全身": "full body", "バストアップ": "bust-up", "クローズアップ": "close-up"
};
return dict[text] || text;
}
</script>
</body>
</html>
ひとこと履歴書
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ひとこと履歴書 Ultra</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
font-family: "Helvetica Neue", sans-serif;
background: #f0f0f0;
padding: 30px;
max-width: 900px;
margin: auto;
}
h1 {
text-align: center;
margin-bottom: 20px;
}
textarea, input[type="date"], input[type="text"] {
width: 100%;
padding: 10px;
font-size: 1rem;
margin-bottom: 10px;
}
.emotion-btn {
font-size: 20px;
padding: 6px 12px;
border: 2px solid #ccc;
background: white;
cursor: pointer;
border-radius: 6px;
}
.emotion-btn.selected {
border-color: #4caf50;
background: #e8f5e9;
}
button.add {
background: #4caf50;
color: white;
border: none;
border-radius: 6px;
padding: 10px 20px;
cursor: pointer;
font-size: 1rem;
margin-top: 10px;
}
button.export {
background: #2196f3;
margin-left: 10px;
}
.entry {
background: white;
padding: 10px;
border-left: 5px solid #ccc;
margin: 10px 0;
border-radius: 6px;
}
.section-date {
font-weight: bold;
margin-top: 30px;
}
.entry .meta {
font-size: 0.8em;
color: #666;
}
#stats {
margin: 20px 0;
background: #fff;
padding: 10px;
border-radius: 8px;
box-shadow: 0 0 3px rgba(0,0,0,0.1);
}
</style>
</head>
<body>
<h1>ひとこと履歴書 Ultra</h1>
<textarea id="entryInput" placeholder="今日の一言を記録しよう!"></textarea>
<input type="date" id="entryDate">
<div>
<button class="emotion-btn" data-emotion="😊">😊 喜</button>
<button class="emotion-btn" data-emotion="😢">😢 哀</button>
<button class="emotion-btn" data-emotion="😠">😠 怒</button>
<button class="emotion-btn" data-emotion="😐">😐 中立</button>
</div>
<button class="add" onclick="addEntry()">記録</button>
<button class="add export" onclick="exportCSV()">CSVダウンロード</button>
<input type="text" id="searchBox" placeholder="キーワード検索(例:嬉しい、美術館)">
<div id="stats"></div>
<canvas id="emotionChart" height="200"></canvas>
<div id="entryList"></div>
<script>
const input = document.getElementById("entryInput");
const dateInput = document.getElementById("entryDate");
const searchBox = document.getElementById("searchBox");
const entryList = document.getElementById("entryList");
const stats = document.getElementById("stats");
const emotionButtons = document.querySelectorAll(".emotion-btn");
let selectedEmotion = "😊";
dateInput.valueAsDate = new Date();
emotionButtons.forEach(btn => {
btn.addEventListener("click", () => {
emotionButtons.forEach(b => b.classList.remove("selected"));
btn.classList.add("selected");
selectedEmotion = btn.dataset.emotion;
});
});
function addEntry() {
const text = input.value.trim();
const date = dateInput.value;
if (!text || !date || !selectedEmotion) return;
const entries = JSON.parse(localStorage.getItem("entries") || "[]");
entries.push({ text, date, emotion: selectedEmotion, timestamp: new Date().toISOString() });
localStorage.setItem("entries", JSON.stringify(entries));
input.value = "";
renderEntries();
}
function exportCSV() {
const entries = JSON.parse(localStorage.getItem("entries") || "[]");
let csv = "日付,感情,テキスト,記録日時\n";
entries.forEach(e => {
csv += `${e.date},${e.emotion},"${e.text.replace(/"/g, '""')}",${e.timestamp}\n`;
});
const blob = new Blob([csv], { type: "text/csv" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "hitokoto_entries.csv";
a.click();
URL.revokeObjectURL(url);
}
function groupByDate(entries) {
const grouped = {};
entries.forEach(entry => {
if (!grouped[entry.date]) grouped[entry.date] = [];
grouped[entry.date].push(entry);
});
return grouped;
}
function renderEntries() {
const entries = JSON.parse(localStorage.getItem("entries") || "[]").reverse();
const keyword = searchBox.value.trim();
const filtered = keyword
? entries.filter(e => e.text.includes(keyword))
: entries;
const grouped = groupByDate(filtered);
const emotionCounts = { "😊": 0, "😢": 0, "😠": 0, "😐": 0 };
entryList.innerHTML = "";
let totalTextLength = 0;
for (const date in grouped) {
const section = document.createElement("div");
section.innerHTML = `<div class="section-date">📅 ${date}</div>`;
grouped[date].forEach(entry => {
emotionCounts[entry.emotion]++;
totalTextLength += entry.text.length;
const div = document.createElement("div");
div.className = "entry";
div.innerHTML = `
<div>${entry.emotion} ${entry.text}</div>
<div class="meta">${new Date(entry.timestamp).toLocaleString()}</div>
`;
section.appendChild(div);
});
entryList.appendChild(section);
}
const total = filtered.length;
const avgLen = total ? Math.round(totalTextLength / total) : 0;
stats.innerHTML = `📌 総投稿数: ${total} 件|平均文字数: ${avgLen} 字`;
renderChart(emotionCounts);
}
function renderChart(counts) {
const ctx = document.getElementById("emotionChart").getContext("2d");
if (window.myChart) window.myChart.destroy();
window.myChart = new Chart(ctx, {
type: "pie",
data: {
labels: ["😊 喜", "😢 哀", "😠 怒", "😐 中立"],
datasets: [{
data: [
counts["😊"],
counts["😢"],
counts["😠"],
counts["😐"]
],
backgroundColor: ["gold", "skyblue", "tomato", "gray"]
}]
},
options: {
plugins: { legend: { position: "bottom" } }
}
});
}
searchBox.addEventListener("input", renderEntries);
renderEntries();
</script>
</body>
</html>
にじいろモール(beta) ECサイト
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>にじいろモール | オンラインショッピング</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="にじいろモールは、あらゆるジャンルの商品を取り揃えた総合ECサイトです。">
<link rel="icon" href="https://cdn-icons-png.flaticon.com/512/1170/1170576.png" type="image/png">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
margin: 0;
font-family: 'Noto Sans JP', sans-serif;
background: #f3f3f3;
}
header {
background-color: #5a4fcf;
color: white;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
header h1 {
font-size: 1.8em;
margin: 0;
letter-spacing: 2px;
}
.search-bar {
flex: 1;
margin: 10px;
max-width: 500px;
}
.search-bar input {
width: 100%;
padding: 10px;
font-size: 1em;
border-radius: 4px;
border: none;
}
main {
padding: 20px;
}
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 20px;
}
.product-card {
background: white;
border-radius: 6px;
padding: 15px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
transition: transform 0.2s;
}
.product-card:hover {
transform: translateY(-3px);
}
.product-card img {
width: 100%;
height: auto;
border-radius: 5px;
}
.product-card h3 {
margin: 10px 0 5px;
font-size: 1.2em;
}
.product-card p {
margin: 5px 0;
color: #555;
}
.price {
color: #d83535;
font-weight: bold;
font-size: 1.1em;
}
.product-card button {
width: 100%;
padding: 10px;
margin-top: 10px;
background: #ffce3d;
border: none;
border-radius: 4px;
font-weight: bold;
cursor: pointer;
transition: background 0.2s;
}
.product-card button:hover {
background: #f2b200;
}
footer {
background: #5a4fcf;
color: white;
text-align: center;
padding: 20px;
margin-top: 40px;
}
@media (max-width: 600px) {
.search-bar {
order: 3;
width: 100%;
}
header {
flex-direction: column;
align-items: flex-start;
}
}
</style>
</head>
<body>
<header>
<h1>🌈 にじいろモール</h1>
<div class="search-bar">
<input type="text" placeholder="商品を検索...">
</div>
</header>
<main>
<div class="product-grid">
<div class="product-card">
<a href="product1.html">
<img src="https://via.placeholder.com/240x160" alt="スマートウォッチ">
<h3>スマートウォッチ</h3>
</a>
<p class="price">¥12,800</p>
<p>心拍計測 / 防水 / 通知連携</p>
<button>カートに追加</button>
</div>
<div class="product-card">
<a href="product2.html">
<img src="https://via.placeholder.com/240x160" alt="話題の書籍">
<h3>話題の書籍</h3>
</a>
<p class="price">¥1,540</p>
<p>ベストセラー本</p>
<button>カートに追加</button>
</div>
<div class="product-card">
<a href="product3.html">
<img src="https://via.placeholder.com/240x160" alt="Bluetoothイヤホン">
<h3>Bluetoothイヤホン</h3>
</a>
<p class="price">¥5,990</p>
<p>高音質 / ノイズキャンセリング</p>
<button>カートに追加</button>
</div>
</div>
</main>
<footer>
© 2025 にじいろモール - すべての権利を保有します。
</footer>
</body>
</html>
ランダム創作メーカー
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>超・ランダム創作メーカー</title>
<style>
body {
font-family: 'Segoe UI', sans-serif;
background: linear-gradient(to right, #e0eafc, #cfdef3);
text-align: center;
padding: 50px;
}
h1 {
font-size: 32px;
color: #2c3e50;
}
button {
padding: 14px 28px;
font-size: 16px;
background-color: #2980b9;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
margin-top: 20px;
}
button:hover {
background-color: #1c5980;
}
.output {
background-color: #ffffff;
padding: 30px;
margin-top: 30px;
border-radius: 12px;
max-width: 900px;
margin-left: auto;
margin-right: auto;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
text-align: left;
}
.label {
font-weight: bold;
margin-top: 15px;
color: #34495e;
}
.story {
margin-top: 20px;
font-style: italic;
color: #444;
}
.divider {
border-top: 1px solid #ccc;
margin: 20px 0;
}
</style>
</head>
<body>
<h1>🌟 超・ランダム創作メーカー</h1>
<button onclick="generateStory()">ストーリー生成</button>
<div class="output" id="output">
<p>ここに詳細な創作設定が表示されます。</p>
</div>
<script>
const names = ["リク", "アリア", "クロウ", "セラ", "ハルカ", "ジン", "ノエル", "ミカ"];
const ages = ["16歳", "17歳", "18歳", "19歳", "20歳", "21歳", "不明"];
const genders = ["男性", "女性", "性別不明"];
const worlds = [
"重力が反転する都市", "魔素が枯渇した世界", "永遠に昼の国", "夢と現実が混ざる領域", "言葉が禁止された国"
];
const races = ["人間", "魔族", "人形", "精霊", "サイボーグ", "異形の存在"];
const jobs = ["時間跳躍士", "記憶修復者", "夢喰い", "観測者", "黒衣の処刑人"];
const traits = ["無表情", "多重人格", "感情過多", "孤独を愛する", "異常な記憶力"];
const goals = ["過去を修正する", "存在の意味を探す", "禁忌の書を開く", "誰かを蘇らせる", "終わりを始める"];
const keywords = ["赤い月", "反転する時計塔", "封じられた祭壇", "黒炎の花", "鏡に映らない影"];
function random(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function generateStory() {
const name = random(names);
const age = random(ages);
const gender = random(genders);
const world = random(worlds);
const race = random(races);
const job = random(jobs);
const trait = random(traits);
const goal = random(goals);
const keyword = random(keywords);
const result = `
<div class="label">🧑 キャラクター情報</div>
名前: ${name}<br>
年齢: ${age}<br>
性別: ${gender}<br>
種族: ${race}<br>
職業: ${job}<br>
性格: ${trait}
<div class="divider"></div>
<div class="label">🌍 世界観</div>
${world}
<div class="divider"></div>
<div class="label">🎯 目的</div>
${goal}
<div class="label">🗝️ キーワード</div>
${keyword}
<div class="story">
<br>――${name}は、${world}に生きる${race}の${job}。${trait}な性格の持ち主である彼(彼女)は、<br>
「${keyword}」にまつわる出来事をきっかけに、「${goal}」という運命を背負うことになる……。
</div>
`;
document.getElementById("output").innerHTML = result;
}
</script>
</body>
</html>
キャラ別ランダムセリフメーカー
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>キャラ別ランダムセリフメーカー</title>
<style>
body {
font-family: 'Segoe UI', sans-serif;
background: linear-gradient(to right, #eef2f3, #8e9eab);
text-align: center;
padding: 50px;
}
h1 {
font-size: 30px;
color: #2c3e50;
}
select, button {
padding: 10px;
font-size: 16px;
margin: 10px;
border-radius: 8px;
}
button {
background-color: #2980b9;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background-color: #1f6391;
}
.quote-box {
background: #fff;
margin-top: 30px;
padding: 30px;
border-radius: 12px;
max-width: 800px;
margin-left: auto;
margin-right: auto;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
font-size: 20px;
color: #333;
}
</style>
</head>
<body>
<h1>🎙️ キャラ別ランダムセリフメーカー</h1>
<p>ジャンルとキャラクターを選んで、名セリフを生成しよう!</p>
<select id="genre">
<option value="battle">バトル</option>
<option value="romance">恋愛</option>
<option value="drama">感動</option>
<option value="comedy">ギャグ</option>
</select>
<select id="character">
<option value="主人公">主人公</option>
<option value="ヒロイン">ヒロイン</option>
<option value="ライバル">ライバル</option>
<option value="師匠">師匠</option>
</select>
<br>
<button onclick="generateLine()">セリフを生成</button>
<div class="quote-box" id="quote">
ここにセリフが表示されます。
</div>
<script>
const lines = {
battle: {
主人公: [
"俺が倒さなきゃ、誰がやる!",
"まだ…終わっちゃいない!",
"立てるさ、何度でも!"
],
ヒロイン: [
"私だって…守れるんだから!",
"あなたを信じる、それが私の戦いよ。"
],
ライバル: [
"俺を超えてみろ…できるならな!",
"この一撃で、全てを終わらせる。"
],
師匠: [
"強さとは、心にあるものだ。",
"お前にすべてを託す!"
]
},
romance: {
主人公: [
"君に出会うために、生まれてきた気がする。",
"一緒に笑えるだけで、幸せなんだ。"
],
ヒロイン: [
"好きって、こんなにも苦しいの?",
"…バカ。でも、ありがとう。"
],
ライバル: [
"…なぜあいつなんだ?俺じゃ、だめなのか。",
"奪ってでも、お前を手に入れたい。"
],
師匠: [
"愛とは、時に強さよりも難しい。",
"惚れた弱みってやつだな…"
]
},
drama: {
主人公: [
"俺たちは、ただ幸せになりたかっただけなんだ…。",
"運命なんかに、負けてたまるか!"
],
ヒロイン: [
"もう一度…あなたに会いたい。",
"願いが一つだけ叶うなら、時間を戻したい。"
],
ライバル: [
"俺の存在に意味なんてない…と思ってた。",
"あの時の俺を、殴り飛ばしてやりたいよ。"
],
師匠: [
"選んだ道を信じろ。お前なら、やれる。",
"迷っていい。人間なんだからな。"
]
},
comedy: {
主人公: [
"いや、なんでパンツが空飛んでるんだ!?",
"オレの人生、どこで間違えた?"
],
ヒロイン: [
"あーもう!恥ずかしくて死ぬ!!",
"だから言ったでしょ!?ネコじゃないってば!"
],
ライバル: [
"笑うな!こっちは本気なんだぞ!?",
"俺がボケ担当じゃないって言ってるだろ!"
],
師匠: [
"ふぉっふぉっふ、若いのぅ…わしも昔はな…。",
"今日の修行は…温泉じゃ!"
]
}
};
function generateLine() {
const genre = document.getElementById('genre').value;
const character = document.getElementById('character').value;
const options = lines[genre][character];
const line = options[Math.floor(Math.random() * options.length)];
document.getElementById("quote").innerText = `${character}「${line}」`;
}
</script>
</body>
</html>
GSAP Todoリスト
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>GSAP Todoリスト</title>
<style>
body {
font-family: Arial, sans-serif;
background: #f0f8ff;
padding: 20px;
}
#todo-container {
max-width: 500px;
margin: auto;
}
input[type="text"] {
width: 70%;
padding: 10px;
font-size: 16px;
}
button {
padding: 10px;
font-size: 16px;
margin-left: 5px;
cursor: pointer;
}
ul {
list-style: none;
padding: 0;
margin-top: 20px;
}
li {
background: #ffffff;
margin-bottom: 10px;
padding: 10px;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
</style>
</head>
<body>
<div id="todo-container">
<h2>GSAP Todoリスト</h2>
<input type="text" id="task-input" placeholder="タスクを入力...">
<button onclick="addTask()">追加</button>
<ul id="task-list"></ul>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script>
function addTask() {
const input = document.getElementById("task-input");
const task = input.value.trim();
if (task === "") return;
const li = document.createElement("li");
li.innerHTML = `
<span>${task}</span>
<button onclick="removeTask(this)">削除</button>
`;
document.getElementById("task-list").appendChild(li);
// GSAP アニメーション(フェードイン)
gsap.from(li, {opacity: 0, y: -20, duration: 0.5});
input.value = "";
}
function removeTask(button) {
const li = button.parentElement;
gsap.to(li, {
opacity: 0,
y: 20,
duration: 0.4,
onComplete: () => li.remove()
});
}
</script>
</body>
</html>
GSAP入門 Tween編
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My GSAP</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>MySite</h1>
<nav>
<ul>
<li>Menu</li>
<li>Menu</li>
<li>Menu</li>
</ul>
</nav>
</header>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
<script src="main.js"></script>
</body>
</html>
main.js
'use strict';
{
gsap.from('h1', {
y: -32,
opacity: 0,
});
gsap.from('li', {
y: 32,
opacity: 0,
stagger: 0.3,
});
}
style.css
@charset "utf-8";
header {
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
h1 {
margin: 0;
}
ul {
margin: 0;
padding: 0;
list-style: none;
display: flex;
gap: 32px;
}
人間の記憶を記録するサイト
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Memory Recorder Pro</title>
<!-- Bootstrap CSS & Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
<!-- Chart.js for statistics -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<style>
:root{
--bg-main:#ffffff;
--bg-gradient:linear-gradient(135deg,#f8f9fa 0%,#e9ecef 100%);
--text-main:#212529;
--accent:#0d6efd;
}
:root.dark{
--bg-main:#1e1e1e;
--bg-gradient:linear-gradient(135deg,#2b2b2b 0%,#1e1e1e 100%);
--text-main:#f8f9fa;
}
body{
background:var(--bg-gradient);
color:var(--text-main);
min-height:100vh;
display:flex;
align-items:center;
justify-content:center;
font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
transition:background .3s ease,color .3s ease;
}
.memory-app{
width:100%;
max-width:920px;
background:var(--bg-main);
padding:2rem 2.5rem;
border-radius:1.5rem;
box-shadow:0 4px 20px rgba(0,0,0,.1);
transition:background .3s ease;
}
.memory-card{
border-left:4px solid var(--accent);
padding-left:1rem;
margin-bottom:1rem;
}
.tag-badge{
background:var(--accent);
color:#fff;
margin-right:.25rem;
cursor:pointer;
}
.btn-accent{
background:var(--accent);
border-color:var(--accent);
color:#fff;
}
</style>
</head>
<body>
<div class="memory-app">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="m-0">📝 Memory Recorder <small class="h6 fw-light">Pro</small></h1>
<div class="d-flex align-items-center gap-3">
<button id="statsBtn" class="btn btn-outline-secondary btn-sm"><i class="bi bi-bar-chart"></i></button>
<div class="form-check form-switch m-0">
<input class="form-check-input" type="checkbox" id="darkModeSwitch">
</div>
</div>
</div>
<!-- Search & Stats row -->
<div class="row g-3 align-items-end mb-4">
<div class="col-md-8">
<label for="searchInput" class="form-label">検索(テキスト / タグ)</label>
<input type="text" id="searchInput" class="form-control" placeholder="キーワードで検索...">
</div>
<div class="col-md-4 text-md-end">
<p id="stats" class="mb-0 small text-muted"></p>
</div>
</div>
<!-- Input Area -->
<div class="mb-3">
<label for="memoryText" class="form-label">新しい記憶</label>
<textarea class="form-control" id="memoryText" rows="3" placeholder="出来事・思い出など..."></textarea>
</div>
<div class="mb-4 row g-2 align-items-end">
<div class="col-md-8">
<label for="memoryTags" class="form-label">タグ(カンマ区切り)</label>
<input type="text" id="memoryTags" class="form-control" placeholder="例: 仕事, 家族, 趣味">
</div>
<div class="col-md-4 d-flex gap-2 mt-md-4">
<button id="saveBtn" class="btn btn-primary flex-grow-1"><i class="bi bi-save"></i> 保存</button>
<button id="voiceBtn" class="btn btn-outline-secondary" title="音声入力"><i class="bi bi-mic"></i></button>
</div>
</div>
<!-- File / Clear row -->
<div class="d-flex flex-wrap gap-2 mb-4">
<button id="exportBtn" class="btn btn-outline-secondary"><i class="bi bi-download"></i> エクスポート</button>
<button id="importBtn" class="btn btn-outline-secondary"><i class="bi bi-upload"></i> インポート</button>
<button id="clearAllBtn" class="btn btn-outline-danger ms-auto"><i class="bi bi-trash"></i> 全削除</button>
<input type="file" id="importFile" accept="application/json" class="d-none">
</div>
<hr>
<h2 class="h5 mb-3">📚 保存された記憶</h2>
<div id="memoryList"></div>
</div>
<!-- Statistics Modal -->
<div class="modal fade" id="statsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-graph-up"></i> 記憶統計</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<canvas id="statsChart" height="120"></canvas>
</div>
</div>
</div>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">記憶を編集</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="editText" class="form-label">内容</label>
<textarea id="editText" class="form-control" rows="4"></textarea>
</div>
<div class="mb-3">
<label for="editTags" class="form-label">タグ(カンマ区切り)</label>
<input id="editTags" class="form-control">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">キャンセル</button>
<button id="editSaveBtn" class="btn btn-primary">保存</button>
</div>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script>
// --------- Constants ---------
const STORAGE_KEY = "memories";
const THEME_KEY = "prefers-dark";
// --------- Helpers ---------
const $ = sel => document.querySelector(sel);
const modal = id => new bootstrap.Modal($(id));
const memories = {
list() { return JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]"); },
save(arr) { localStorage.setItem(STORAGE_KEY, JSON.stringify(arr)); },
add(obj){ const arr = this.list(); arr.push(obj); this.save(arr);} ,
remove(id){ this.save(this.list().filter(m=>m.id!==id));},
update(id,data){ const arr=this.list().map(m=>m.id===id?{...m,...data}:m); this.save(arr);} ,
};
const fmtDate = d => new Intl.DateTimeFormat("ja-JP",{dateStyle:"medium",timeStyle:"short"}).format(d);
// --------- Rendering ---------
function renderMemories(filter=""){
const listEl = $("#memoryList");
listEl.innerHTML="";
const all = memories.list();
const lower = filter.toLowerCase();
const visible = all.filter(m=>{
const tagMatch = m.tags.some(t=>t.toLowerCase().includes(lower));
const textMatch= m.text.toLowerCase().includes(lower);
return !lower || tagMatch || textMatch;
}).reverse();
// stats
$("#stats").textContent=`合計: ${all.length} 件`;
if(!visible.length){listEl.innerHTML='<p class="text-muted">該当する記憶がありません。</p>';return;}
visible.forEach(m=>{
const card=document.createElement("div");
card.className="memory-card card";
card.innerHTML=`<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-start flex-wrap">
<h5 class="card-title mb-1">${fmtDate(new Date(m.date))}</h5>
<div class="btn-group btn-group-sm">
<button class="btn btn-link text-primary" title="編集" onclick="openEditor('${m.id}')"><i class="bi bi-pencil"></i></button>
<button class="btn btn-link text-danger" title="削除" onclick="deleteMemory('${m.id}')"><i class="bi bi-trash"></i></button>
</div>
</div>
<p class="card-text" style="white-space:pre-wrap;">${m.text}</p>
<div class="mt-2">${m.tags.map(t=>`<span class=\"badge tag-badge\" onclick=\"filterTag('${t}')\">${t}</span>`).join(" ")}</div>
</div>`;
listEl.appendChild(card);
});
}
// --------- CRUD ---------
function saveMemory(){
const text=$("#memoryText").value.trim();
const tagRaw=$("#memoryTags").value.trim();
if(!text)return;
const tags=tagRaw?tagRaw.split(/\s*,\s*/).filter(Boolean):[];
memories.add({id:crypto.randomUUID(),text,tags,date:new Date().toISOString()});
$("#memoryText").value="";
$("#memoryTags").value="";
renderMemories($("#searchInput").value);
}
function deleteMemory(id){
if(!confirm("削除しますか?"))return;
memories.remove(id);
renderMemories($("#searchInput").value);
}
// --------- Edit ---------
let editingId=null;
function openEditor(id){
editingId=id;
const m=memories.list().find(x=>x.id===id);
$("#editText").value=m.text;
$("#editTags").value=m.tags.join(", ");
modal('#editModal').show();
}
$("#editSaveBtn").addEventListener("click",()=>{
const text=$("#editText").value.trim();
const tags=$("#editTags").value.trim().split(/\s*,\s*/).filter(Boolean);
memories.update(editingId,{text,tags});
modal('#editModal').hide();
renderMemories($("#searchInput").value);
});
// --------- Filter helper ---------
function filterTag(tag){
$("#searchInput").value=tag;
renderMemories(tag);
}
// --------- Export / Import ---------
function exportMemories(){
const blob=new Blob([JSON.stringify(memories.list(),null,2)],{type:"application/json"});
const url=URL.createObjectURL(blob);
const a=document.createElement("a");
a.href=url;a.download="memories.json";a.click();URL.revokeObjectURL(url);
}
function importMemories(file){
const reader=new FileReader();
reader.onload=e=>{try{const arr=JSON.parse(e.target.result);if(Array.isArray(arr)){memories.save([...memories.list(),...arr]);renderMemories($("#searchInput").value);}else throw 0;}catch{alert("無効なファイルです");}};
reader.readAsText(file);
}
// --------- Statistics ---------
let chartInstance=null;
function openStats(){
const data=memories.list();
const counts={};
data.forEach(m=>{
const key=m.date.slice(0,7); // YYYY-MM
counts[key]=(counts[key]||0)+1;
});
const labels=Object.keys(counts).sort();
const values=labels.map(l=>counts[l]);
const ctx=$("#statsChart");
if(chartInstance)chartInstance.destroy();
chartInstance=new Chart(ctx,{type:'bar',data:{labels,datasets:[{label:'記憶数',data:values}]},options:{plugins:{legend:{display:false}}}});
modal('#statsModal').show();
}
// --------- Dark Mode ---------
function applyTheme(dark){
document.documentElement.classList.toggle('dark',dark);
localStorage.setItem(THEME_KEY,dark?'1':'0');
$("#darkModeSwitch").checked=dark;
}
// --------- Voice Input (Experimental) ---------
let rec=null;
async function startVoice(){
if(!('webkitSpeechRecognition'in window||'SpeechRecognition'in window)){alert('音声認識非対応');return;}
const Speech=window.SpeechRecognition||window.webkitSpeechRecognition;
rec=new Speech();
rec.lang='ja-JP';
rec.continuous=false;
rec.interimResults=false;
rec.onresult=e=>{$('#memoryText').value=e.results[0][0].transcript;};
rec.start();
}
// --------- Init ---------
document.addEventListener('DOMContentLoaded',()=>{
// Theme
applyTheme(localStorage.getItem(THEME_KEY)==='1');
// Render memories
renderMemories();
// Listeners
$('#saveBtn').addEventListener('click',saveMemory);
$('#clearAllBtn').addEventListener('click',()=>{if(confirm('すべて削除しますか?')){localStorage.removeItem(STORAGE_KEY);renderMemories();}});
$('#exportBtn').addEventListener('click',exportMemories);
$('#importBtn').addEventListener('click',()=>$('#importFile').click());
$('#importFile').addEventListener('change',e=>{if(e.target.files[0])importMemories(e.target.files[0]);e.target.value='';});
$('#darkModeSwitch').addEventListener('change',e=>applyTheme(e.target.checked));
$('#searchInput').addEventListener('input',e=>renderMemories(e.target.value));
$('#memoryText').addEventListener('keydown',e=>{if(e.key==='Enter'&&(e.ctrlKey||e.metaKey))saveMemory();});
$('#statsBtn').addEventListener('click',openStats);
$('#voiceBtn').addEventListener('click',startVoice);
});
</script>
</body>
</html>
Eternal.html 画像投稿サイト
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Eternal</title>
<style>
body {
font-family: sans-serif;
background: #f5f5f5;
margin: 0;
padding: 0;
}
header {
background: #e60023;
color: white;
padding: 1em;
text-align: center;
font-size: 1.8em;
}
.controls {
padding: 1em;
background: #fff;
text-align: center;
}
.controls input, .controls button {
margin: 0.5em;
padding: 0.5em;
font-size: 1em;
}
.grid {
column-count: 4;
column-gap: 1em;
padding: 1em;
}
.pin {
background: white;
display: inline-block;
margin-bottom: 1em;
width: 100%;
box-sizing: border-box;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
overflow: hidden;
position: relative;
}
.pin img {
width: 100%;
cursor: pointer;
}
.description, .tags {
padding: 0.5em;
}
.favorite {
position: absolute;
top: 8px;
right: 8px;
font-size: 22px;
color: gray;
cursor: pointer;
user-select: none;
}
.favorite.active {
color: gold;
}
.likes {
font-size: 0.9em;
text-align: right;
padding: 0 0.5em 0.5em 0;
color: #888;
}
.tag {
display: inline-block;
background: #eee;
padding: 0.2em 0.5em;
border-radius: 5px;
margin: 0.2em;
cursor: pointer;
}
.tag:hover {
background: #ccc;
}
@media screen and (max-width: 1024px) {
.grid {
column-count: 3;
}
}
@media screen and (max-width: 768px) {
.grid {
column-count: 2;
}
}
@media screen and (max-width: 480px) {
.grid {
column-count: 1;
}
}
#modal {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.7);
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
}
#modal img {
max-width: 90%;
max-height: 80vh;
border-radius: 10px;
}
</style>
</head>
<body>
<header>Pintorrest 完全版</header>
<div class="controls">
<input type="file" id="imageInput" accept="image/*">
<input type="text" id="descInput" placeholder="説明">
<input type="text" id="tagInput" placeholder="タグ(カンマ区切り)">
<button onclick="addPin()">投稿</button>
<br>
<input type="text" id="searchBox" placeholder="検索..." oninput="filterPins()">
</div>
<div class="grid" id="grid"></div>
<div id="modal" onclick="this.style.display='none'">
<img id="modalImg" src="">
</div>
<script>
let pins = JSON.parse(localStorage.getItem("pins") || "[]");
function renderPins() {
const grid = document.getElementById("grid");
grid.innerHTML = '';
pins.forEach((pin, index) => {
const pinElem = document.createElement("div");
pinElem.className = "pin";
pinElem.innerHTML = `
<span class="favorite ${pin.favorited ? 'active' : ''}" onclick="toggleFavorite(${index}, this)">★</span>
<img src="${pin.image}" onclick="showModal('${pin.image}')">
<div class="description">${pin.desc}</div>
<div class="tags">${pin.tags.map(tag => `<span class="tag" onclick="filterByTag('${tag}')">${tag}</span>`).join(' ')}</div>
<div class="likes">♥ ${pin.likes}</div>
`;
grid.appendChild(pinElem);
});
}
function addPin() {
const imageInput = document.getElementById("imageInput");
const desc = document.getElementById("descInput").value.trim();
const tags = document.getElementById("tagInput").value.split(',').map(t => t.trim()).filter(Boolean);
const file = imageInput.files[0];
if (!file || !desc) return alert("画像と説明を入力してください");
const reader = new FileReader();
reader.onload = function(e) {
pins.unshift({
image: e.target.result,
desc: desc,
tags: tags,
likes: 0,
favorited: false
});
savePins();
renderPins();
document.getElementById("descInput").value = '';
document.getElementById("tagInput").value = '';
document.getElementById("imageInput").value = '';
};
reader.readAsDataURL(file);
}
function showModal(src) {
document.getElementById("modalImg").src = src;
document.getElementById("modal").style.display = 'flex';
}
function toggleFavorite(index, elem) {
pins[index].favorited = !pins[index].favorited;
pins[index].likes += pins[index].favorited ? 1 : -1;
savePins();
renderPins();
}
function savePins() {
localStorage.setItem("pins", JSON.stringify(pins));
}
function filterPins() {
const keyword = document.getElementById("searchBox").value.toLowerCase();
document.querySelectorAll(".pin").forEach(pin => {
const text = pin.textContent.toLowerCase();
pin.style.display = text.includes(keyword) ? "inline-block" : "none";
});
}
function filterByTag(tag) {
document.getElementById("searchBox").value = tag;
filterPins();
}
renderPins();
</script>
</body>
</html>
