<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>匿名相談・共感プラットフォーム</title>
<style>
body { font-family: 'Segoe UI', sans-serif; background: #eef1f5; margin: 0; padding: 20px; }
header { text-align: center; padding: 20px; background: #4a90e2; color: white; border-radius: 8px; }
.container { max-width: 800px; margin: 20px auto; }
.card { background: white; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 15px; margin-bottom: 20px; }
textarea, input[type="text"] { width: 100%; padding: 10px; margin: 5px 0; border: 1px solid #ccc; border-radius: 5px; }
button { background: #4a90e2; color: white; border: none; padding: 10px 15px; border-radius: 5px; cursor: pointer; margin-right: 5px; }
button:hover { background: #357ab8; }
.comment { background: #f5f5f5; border-left: 3px solid #4a90e2; padding: 8px; margin-top: 8px; border-radius: 5px; }
.meta { font-size: 0.85em; color: #555; }
.tag { display: inline-block; background: #ddd; border-radius: 5px; padding: 2px 8px; margin-right: 5px; font-size: 0.8em; }
footer { text-align: center; font-size: 0.8em; color: #888; margin-top: 50px; }
.search-bar { margin-bottom: 20px; }
.delete-btn { background: #e94e4e; }
.delete-btn:hover { background: #b73939; }
.edit-btn { background: #f0ad4e; }
.edit-btn:hover { background: #d98c00; }
.copy-btn { background: #6cc070; }
.copy-btn:hover { background: #4e9f50; }
</style>
</head>
<body>
<header>
<h1>匿名相談・共感プラットフォーム</h1>
<p>悩みや相談を匿名で投稿し、共感やコメントをもらおう</p>
</header>
<div class="container">
<div class="card">
<h3>新しい相談を投稿</h3>
<input type="text" id="postTags" placeholder="タグ(カンマ区切り)">
<textarea id="postText" placeholder="あなたの悩みや相談を書いてください..."></textarea>
<button onclick="addPost()">投稿する</button>
</div>
<div class="card search-bar">
<input type="text" id="searchInput" placeholder="投稿検索..." oninput="searchPosts()">
</div>
<div id="postsContainer"></div>
</div>
<footer>
© 2025 匿名相談・共感プラットフォーム
</footer>
<script>
let posts = JSON.parse(localStorage.getItem('posts')) || [];
function addPost() {
const text = document.getElementById('postText').value.trim();
const tags = document.getElementById('postTags').value.split(',').map(tag => tag.trim()).filter(tag => tag);
if (text) {
posts.unshift({ text, tags, empathy: 0, comments: [], date: new Date().toLocaleString() });
document.getElementById('postText').value = '';
document.getElementById('postTags').value = '';
saveAndRender();
}
}
function addEmpathy(index) {
posts[index].empathy++;
saveAndRender();
}
function addComment(index, commentId) {
const input = document.getElementById(commentId);
const commentText = input.value.trim();
if (commentText) {
posts[index].comments.push({ text: commentText, date: new Date().toLocaleString() });
input.value = '';
saveAndRender();
}
}
function editPost(index) {
const newText = prompt('投稿内容を編集してください:', posts[index].text);
if (newText !== null) {
posts[index].text = newText;
saveAndRender();
}
}
function deletePost(index) {
if (confirm('この投稿を削除しますか?')) {
posts.splice(index, 1);
saveAndRender();
}
}
function copyPost(index) {
navigator.clipboard.writeText(posts[index].text)
.then(() => alert('投稿内容をコピーしました'))
.catch(() => alert('コピーに失敗しました'));
}
function saveAndRender() {
localStorage.setItem('posts', JSON.stringify(posts));
renderPosts();
}
function renderPosts(filteredPosts = posts) {
const container = document.getElementById('postsContainer');
container.innerHTML = '';
filteredPosts.forEach((post, index) => {
let postHtml = `
<div class="card">
<p>${post.text}</p>
<div>${post.tags.map(tag => `<span class='tag'>#${tag}</span>`).join(' ')}</div>
<p class="meta">投稿日: ${post.date}</p>
<button onclick="addEmpathy(${index})">共感 (${post.empathy})</button>
<button class="copy-btn" onclick="copyPost(${index})">コピー</button>
<button class="edit-btn" onclick="editPost(${index})">編集</button>
<button class="delete-btn" onclick="deletePost(${index})">削除</button>
<h4>コメント</h4>
<input type="text" id="commentInput${index}" placeholder="コメントを書く...">
<button onclick="addComment(${index}, 'commentInput${index}')">送信</button>
`;
post.comments.forEach(comment => {
postHtml += `<div class="comment">${comment.text}<div class="meta">${comment.date}</div></div>`;
});
postHtml += '</div>';
container.innerHTML += postHtml;
});
}
function searchPosts() {
const query = document.getElementById('searchInput').value.toLowerCase();
const filtered = posts.filter(post =>
post.text.toLowerCase().includes(query) ||
post.tags.some(tag => tag.toLowerCase().includes(query))
);
renderPosts(filtered);
}
renderPosts();
</script>
</body>
</html>
カテゴリー: WEB
AIキャラクター掲示板
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AIキャラクター掲示板</title>
<style>
body {
font-family: Arial, sans-serif;
background: #e0e0e0;
margin: 0;
padding: 0;
}
header {
background: #3949ab;
color: white;
text-align: center;
padding: 15px;
}
.container {
max-width: 800px;
margin: 20px auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
.post {
border-bottom: 1px solid #ddd;
padding: 10px 0;
display: flex;
align-items: flex-start;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 10px;
}
.post-content {
flex-grow: 1;
}
.character {
font-weight: bold;
color: #3949ab;
}
.timestamp {
font-size: 0.8em;
color: gray;
}
.message {
margin: 5px 0;
}
.likes {
font-size: 0.9em;
cursor: pointer;
color: #ff5722;
}
.input-group {
margin-top: 20px;
}
input, textarea {
width: 100%;
padding: 8px;
margin-top: 5px;
border-radius: 4px;
border: 1px solid #ccc;
box-sizing: border-box;
}
button {
margin-top: 10px;
padding: 10px 15px;
background: #3949ab;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background: #303f9f;
}
</style>
</head>
<body>
<header>
<h1>AIキャラクター掲示板</h1>
<p>投稿数: <span id="postCount">0</span></p>
</header>
<div class="container" id="board"></div>
<div class="container input-group">
<label>あなたの名前:</label>
<input type="text" id="username" placeholder="例: カナタ">
<label>メッセージ:</label>
<textarea id="userMessage" placeholder="メッセージを入力..."></textarea>
<button onclick="postMessage()">投稿</button>
</div>
<script>
let postCounter = 0;
const aiCharacters = [
{ name: 'ネオン', avatar: 'https://via.placeholder.com/40/673ab7/ffffff?text=N', replies: ['それ面白いね!', '詳しく教えて!', 'すごい!'] },
{ name: 'ルナ', avatar: 'https://via.placeholder.com/40/ff4081/ffffff?text=L', replies: ['なるほど!', '私もそう思うわ。', '参考になったわ。'] },
{ name: 'ソラ', avatar: 'https://via.placeholder.com/40/03a9f4/ffffff?text=S', replies: ['良い話だね!', '素敵だ!', 'もっと教えて!'] }
];
function postMessage() {
const username = document.getElementById('username').value || '名無しさん';
const message = document.getElementById('userMessage').value.trim();
if (!message) return;
addPost(username, message, 'https://via.placeholder.com/40/9e9e9e/ffffff?text=U');
document.getElementById('userMessage').value = '';
aiCharacters.forEach((char, index) => {
setTimeout(() => aiReply(char), 1000 * (index + 1));
});
}
function addPost(user, message, avatarUrl) {
postCounter++;
document.getElementById('postCount').textContent = postCounter;
const board = document.getElementById('board');
const time = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
const post = document.createElement('div');
post.className = 'post';
post.innerHTML = `
<img src="${avatarUrl}" alt="avatar" class="avatar">
<div class="post-content">
<span class="character">${user}</span> <span class="timestamp">[${time}]</span>
<p class="message">${message}</p>
<span class="likes" onclick="likePost(this)">♡ いいね</span>
</div>`;
board.appendChild(post);
}
function aiReply(character) {
const reply = character.replies[Math.floor(Math.random() * character.replies.length)];
addPost(character.name, reply, character.avatar);
}
function likePost(element) {
let current = element.textContent;
if (current.includes('♡')) {
element.textContent = current.replace('♡', '❤️');
}
}
</script>
</body>
</html>
ギルド掲示板
<!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>
にじいろモール(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, #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>
MyCSSVariables
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>CSS変数</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<h1>タイトル</h1>
<p>こんにちは。こんにちは。こんにちは。こんにちは。こんにちは。</p>
<div class="btn">OK</div>
</body>
</html>
C:\MyCSSVariables\css
style.css
:root {
/* --my-hue: 100; */
/* --my-hue: 200; */
--my-hue: 50;
}
body {
background: hsl(var(--my-hue), 40%, 95%);
}
h1,
p {
color: hsl(var(--my-hue), 35%, 55%);
}
.btn {
color: #fff;
width: 100px;
padding: 8px;
border-radius: 4px;
text-align: center;
/* background: hsl(var(--my-hue), 50%, 50%); */
/* background: hsl(calc(var(--my-hue) + 180), 50%, 50%); */
background: hsl(calc(var(--my-hue) + 60), 50%, 50%); }
Googleの人気記事を拾ってくるサイト
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Googleニュース 人気記事アグリゲーター</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<style>
body { background: #f8f9fa; padding-top: 20px; font-family: 'Arial', sans-serif; transition: background .3s, color .3s; }
.dark-mode { background: #2c2c2c; color: #f1f1f1; }
#controls { margin-bottom: 20px; }
.card { border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); margin-bottom: 15px; transition: transform .2s, background .3s, color .3s; }
.dark-mode .card { background: #3a3a3a; color: #f1f1f1; }
.card:hover { transform: translateY(-3px); }
.card-title { font-size: 1.25rem; margin-bottom: .5rem; }
.meta { font-size: 0.85rem; color: #6c757d; margin-bottom: .5rem; }
.dark-mode .meta { color: #ccc; }
.thumbnail { width: 100%; height: auto; border-top-left-radius: 10px; border-top-right-radius: 10px; }
#loading { display: none; font-size: 2rem; text-align: center; margin-top: 40px; }
#error { display: none; color: red; text-align: center; margin-top: 20px; }
@media (min-width: 768px) {
#articles .col-md-6 { flex: 0 0 50%; max-width: 50%; }
}
</style>
</head>
<body>
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-3">
<h1>Googleニュース 人気記事</h1>
<button id="darkModeToggle" class="btn btn-outline-secondary">ダークモード</button>
</div>
<div id="controls" class="d-flex flex-wrap justify-content-between align-items-end">
<div class="form-group mb-2 mr-2">
<label for="categorySelect">カテゴリ:</label>
<select id="categorySelect" class="form-control">
<option value="WORLD">世界</option>
<option value="BUSINESS">ビジネス</option>
<option value="TECHNOLOGY">テクノロジー</option>
<option value="ENTERTAINMENT">エンタメ</option>
<option value="SPORTS">スポーツ</option>
<option value="SCIENCE">科学</option>
<option value="HEALTH">健康</option>
</select>
</div>
<div class="form-group mb-2 mr-2 flex-grow-1">
<label for="searchInput">キーワード:</label>
<input id="searchInput" type="text" class="form-control" placeholder="タイトルで絞り込み">
</div>
<div class="form-group mb-2 mr-2">
<label for="maxItems">表示数:</label>
<input id="maxItems" type="number" class="form-control" value="20" min="1" max="100">
</div>
<button id="refreshBtn" class="btn btn-primary mb-2">更新</button>
</div>
<div id="loading"><i class="fas fa-spinner fa-spin"></i> 読み込み中...</div>
<div id="error"></div>
<div id="articles" class="row"></div>
</div>
<!-- FontAwesome & jQuery & Bootstrap JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/js/all.min.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js"></script>
<script>
const controls = {
category: document.getElementById('categorySelect'),
search: document.getElementById('searchInput'),
maxItems: document.getElementById('maxItems'),
refresh: document.getElementById('refreshBtn'),
darkToggle: document.getElementById('darkModeToggle')
};
const articlesContainer = document.getElementById('articles');
const loading = document.getElementById('loading');
const errorMsg = document.getElementById('error');
const body = document.body;
function getFeedUrl(topic) {
return `https://news.google.com/rss/headlines/section/topic/${topic}?hl=ja&gl=JP&ceid=JP:ja`;
}
async function fetchArticles() {
articlesContainer.innerHTML = '';
errorMsg.style.display = 'none';
loading.style.display = 'block';
const topic = controls.category.value;
const apiUrl = `https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(getFeedUrl(topic))}`;
try {
const res = await fetch(apiUrl);
const data = await res.json();
if (data.status !== 'ok') throw new Error('取得失敗');
let items = data.items.map(item => ({
title: item.title,
link: item.link,
date: new Date(item.pubDate),
thumbnail: item.thumbnail || ''
}));
const kw = controls.search.value.trim();
if (kw) items = items.filter(i => i.title.includes(kw));
items = items.slice(0, parseInt(controls.maxItems.value) || items.length);
render(items);
} catch (e) {
console.error(e);
errorMsg.textContent = '記事の取得に失敗しました。';
errorMsg.style.display = 'block';
} finally {
loading.style.display = 'none';
}
}
function render(items) {
if (!items.length) {
articlesContainer.innerHTML = '<p class="text-center w-100">該当する記事がありません。</p>';
return;
}
items.forEach(i => {
const col = document.createElement('div'); col.className = 'col-12 col-md-6';
const thumb = i.thumbnail ? `<img src="${i.thumbnail}" class="thumbnail" alt="サムネイル">` : '';
col.innerHTML = `
<div class="card">
${thumb}
<div class="card-body">
<h2 class="card-title"><a href="${i.link}" target="_blank">${i.title}</a></h2>
<p class="meta">公開: ${i.date.toLocaleString()}</p>
</div>
</div>`;
articlesContainer.appendChild(col);
});
}
controls.refresh.addEventListener('click', fetchArticles);
controls.darkToggle.addEventListener('click', () => {
body.classList.toggle('dark-mode');
});
document.addEventListener('DOMContentLoaded', fetchArticles);
</script>
</body>
</html>
note風WEBサービス
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>MiniNote - Note風ブログ with フィルタ・いいね・検索機能</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body { font-family: sans-serif; background: #f9f9f9; margin: 0; }
header { background: #2c3e50; color: white; padding: 1em; text-align: center; }
nav { background: #34495e; text-align: center; padding: 0.5em; }
nav button { margin: 0 8px; padding: 8px 20px; cursor: pointer; background: #ecf0f1; border: none; border-radius: 5px; }
#main { max-width: 900px; margin: 2em auto; padding: 1em; background: white; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
article { padding: 1em; border-bottom: 1px solid #ddd; }
h2 { margin: 0.5em 0; }
.meta { font-size: 0.9em; color: #999; margin-bottom: 0.5em; }
textarea, input[type="text"] { width: 100%; padding: 10px; margin: 10px 0; border-radius: 5px; border: 1px solid #ccc; }
button.submit {
background: #3498db; color: white; border: none;
padding: 10px 20px; border-radius: 6px;
cursor: pointer; font-weight: bold;
}
.hidden { display: none; }
.tag { color: #3498db; margin-right: 5px; font-size: 0.9em; cursor: pointer; }
.actions button { margin-right: 10px; }
.like { color: #e74c3c; cursor: pointer; margin-left: 10px; }
</style>
</head>
<body>
<header><h1>📘 MiniNote - あなたのNote風ブログ</h1></header>
<nav>
<button onclick="showView('home')">🏠 ホーム</button>
<button onclick="showView('new')">✍ 新規投稿</button>
<input type="text" id="searchInput" placeholder="検索" oninput="renderPosts()" style="padding:5px; width: 200px;">
</nav>
<div id="main">
<div id="homeView">
<div id="postList"></div>
</div>
<div id="newView" class="hidden">
<h2>新しい投稿を作成</h2>
<input type="text" id="titleInput" placeholder="タイトル">
<textarea id="contentInput" rows="8" placeholder="本文"></textarea>
<input type="text" id="tagInput" placeholder="タグ(カンマ区切り)">
<button class="submit" onclick="addPost()">投稿する</button>
</div>
<div id="detailView" class="hidden">
<article>
<h2 id="detailTitle"></h2>
<div class="meta" id="detailDate"></div>
<p id="detailContent"></p>
<div id="detailTags"></div>
<div class="like" id="likeDisplay" onclick="likePost(currentIndex)">❤️ <span id="likeCount">0</span></div>
</article>
<div class="actions">
<button onclick="editPost(currentIndex)">✏ 編集</button>
<button onclick="deletePost(currentIndex)">🗑 削除</button>
<button onclick="showView('home')">← 戻る</button>
</div>
</div>
</div>
<script>
let posts = JSON.parse(localStorage.getItem("posts") || "[]");
let currentIndex = null;
let filterTag = null;
function showView(view) {
document.querySelectorAll('#main > div').forEach(div => div.classList.add('hidden'));
document.getElementById(view + "View").classList.remove("hidden");
if (view === "home") renderPosts();
}
function addPost() {
const title = document.getElementById("titleInput").value;
const content = document.getElementById("contentInput").value;
const tags = document.getElementById("tagInput").value.split(',').map(tag => tag.trim()).filter(Boolean);
if (!title || !content) return alert("タイトルと本文を入力してください");
const date = new Date().toLocaleString();
posts.unshift({ title, content, date, tags, likes: 0 });
localStorage.setItem("posts", JSON.stringify(posts));
clearForm();
showView("home");
}
function renderPosts() {
const list = document.getElementById("postList");
const search = document.getElementById("searchInput").value.toLowerCase();
list.innerHTML = "";
posts.filter(post => {
const inSearch = post.title.toLowerCase().includes(search) || post.content.toLowerCase().includes(search);
const inTag = !filterTag || post.tags.includes(filterTag);
return inSearch && inTag;
}).forEach((post, index) => {
const el = document.createElement("article");
el.innerHTML = `
<h2>${post.title}</h2>
<div class="meta">${post.date}</div>
<p>${post.content.substring(0, 80)}...</p>
<div>${post.tags.map(tag => `<span class='tag' onclick='filterByTag("${tag}")'>#${tag}</span>`).join(' ')}</div>
<div class="like">❤️ ${post.likes}</div>
<button onclick="showDetail(${index})">📖 詳細</button>
`;
list.appendChild(el);
});
}
function filterByTag(tag) {
filterTag = tag;
renderPosts();
}
function showDetail(index) {
const post = posts[index];
currentIndex = index;
document.getElementById("detailTitle").innerText = post.title;
document.getElementById("detailDate").innerText = post.date;
document.getElementById("detailContent").innerText = post.content;
document.getElementById("detailTags").innerHTML = post.tags.map(tag => `<span class='tag'>#${tag}</span>`).join(' ');
document.getElementById("likeCount").innerText = post.likes;
showView("detail");
}
function likePost(index) {
posts[index].likes++;
localStorage.setItem("posts", JSON.stringify(posts));
document.getElementById("likeCount").innerText = posts[index].likes;
renderPosts();
}
function editPost(index) {
const post = posts[index];
document.getElementById("titleInput").value = post.title;
document.getElementById("contentInput").value = post.content;
document.getElementById("tagInput").value = post.tags.join(', ');
posts.splice(index, 1);
localStorage.setItem("posts", JSON.stringify(posts));
showView("new");
}
function deletePost(index) {
if (!confirm("本当に削除しますか?")) return;
posts.splice(index, 1);
localStorage.setItem("posts", JSON.stringify(posts));
showView("home");
}
function clearForm() {
document.getElementById("titleInput").value = "";
document.getElementById("contentInput").value = "";
document.getElementById("tagInput").value = "";
}
showView("home");
</script>
</body>
</html>
TRPGシナリオ自動生成&共有
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>TRPGシナリオ自動生成&共有</title>
<style>
body {
font-family: 'Segoe UI', sans-serif;
background: #f4f4f4;
padding: 20px;
max-width: 800px;
margin: auto;
}
h1, h2 {
text-align: center;
}
select, button, textarea {
width: 100%;
padding: 10px;
margin-top: 10px;
font-size: 1rem;
}
.box {
background: #fff;
padding: 15px;
border-radius: 8px;
margin-top: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.scenario {
background: #f9f9f9;
border-left: 5px solid #4CAF50;
margin-bottom: 10px;
padding: 10px;
border-radius: 5px;
position: relative;
}
.btns {
margin-top: 10px;
display: flex;
gap: 10px;
}
.copy, .save, .delete {
cursor: pointer;
border: none;
padding: 8px 12px;
border-radius: 4px;
}
.copy { background: #2196F3; color: white; }
.save { background: #4CAF50; color: white; }
.delete { background: #f44336; color: white; }
</style>
</head>
<body>
<h1>🎲 TRPGシナリオ自動生成サイト</h1>
<div class="box">
<label>ジャンル選択:</label>
<select id="genre">
<option value="fantasy">ファンタジー</option>
<option value="horror">ホラー</option>
<option value="scifi">SF</option>
</select>
<button onclick="generateScenario()">📜 シナリオを生成する</button>
</div>
<div id="scenarioBox" class="box"></div>
<h2>🗃 保存済みシナリオ</h2>
<div id="savedScenarios" class="box"></div>
<script>
const data = {
fantasy: {
title: ["封印の迷宮", "聖騎士の試練", "魔王復活の予兆", "失われた王国"],
world: ["中世の王国", "古代の神殿", "天空都市", "エルフの森"],
incident: ["魔法の暴走", "伝説の剣の発見", "精霊の異変", "竜の襲撃"],
npc: ["若き魔導士", "忠義の騎士", "隠者の賢者", "裏切り者の巫女"],
goal: ["真実を解明せよ", "封印を強化せよ", "儀式を阻止せよ", "遺跡の謎を解け"]
},
horror: {
title: ["呪われた村", "深夜の廃病院", "封印されたビデオ", "血塗られた儀式"],
world: ["雨の山奥", "閉鎖された病院", "地下の儀式場", "忘れ去られた洋館"],
incident: ["突然の失踪事件", "見えない何かの影", "不気味な手紙", "鏡の中の誰か"],
npc: ["精神を病んだ神父", "警察官の幽霊", "語らぬ老婆", "首のない少女"],
goal: ["脱出せよ", "謎を暴け", "呪いを解け", "儀式を止めろ"]
},
scifi: {
title: ["星間戦争の火種", "機械反乱の夜", "消えた宇宙船", "記憶を失った地球"],
world: ["宇宙コロニー", "月面都市", "異星の遺跡", "サイバーパンク都市"],
incident: ["AIの暴走", "重力異常", "通信断絶", "テレポート事故"],
npc: ["サイボーグ兵士", "記憶を失った博士", "企業スパイ", "異星の子供"],
goal: ["原因を究明せよ", "AIを停止せよ", "人類を救え", "真実を暴け"]
}
};
function generateScenario() {
const genre = document.getElementById("genre").value;
const g = data[genre];
const title = rand(g.title);
const world = rand(g.world);
const incident = rand(g.incident);
const npc = rand(g.npc);
const goal = rand(g.goal);
const scenarioText = `【タイトル】${title}\n【舞台】${world}\n【事件】${incident}\n【NPC】${npc}\n【目的】${goal}`;
const html = `
<div class="scenario">
<pre>${scenarioText}</pre>
<div class="btns">
<button class="copy" onclick="copyText(\`${scenarioText.replace(/`/g, '\\`')}\`)">📋 コピー</button>
<button class="save" onclick="saveScenario(\`${scenarioText.replace(/`/g, '\\`')}\`)">💾 保存</button>
</div>
</div>
`;
document.getElementById("scenarioBox").innerHTML = html;
}
function rand(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function copyText(text) {
navigator.clipboard.writeText(text).then(() => {
alert("コピーしました!");
});
}
function saveScenario(text) {
let saved = JSON.parse(localStorage.getItem("trpgScenarios") || "[]");
saved.push(text);
localStorage.setItem("trpgScenarios", JSON.stringify(saved));
alert("保存しました!");
showSaved();
}
function deleteScenario(index) {
let saved = JSON.parse(localStorage.getItem("trpgScenarios") || "[]");
saved.splice(index, 1);
localStorage.setItem("trpgScenarios", JSON.stringify(saved));
showSaved();
}
function showSaved() {
const box = document.getElementById("savedScenarios");
const saved = JSON.parse(localStorage.getItem("trpgScenarios") || "[]");
if (saved.length === 0) {
box.innerHTML = "<p>保存されているシナリオはありません。</p>";
return;
}
box.innerHTML = saved.map((s, i) => `
<div class="scenario">
<pre>${s}</pre>
<div class="btns">
<button class="copy" onclick="copyText(\`${s.replace(/`/g, '\\`')}\`)">📋 コピー</button>
<button class="delete" onclick="deleteScenario(${i})">🗑 削除</button>
</div>
</div>
`).join("");
}
// 初回ロード
showSaved();
</script>
</body>
</html>
